diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..7901c38 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Remove: [-f*, -m*] diff --git a/.github/ISSUE_TEMPLATE/01_build_install_bug.yml b/.github/ISSUE_TEMPLATE/01_build_install_bug.yml index d3489e2..d809883 100644 --- a/.github/ISSUE_TEMPLATE/01_build_install_bug.yml +++ b/.github/ISSUE_TEMPLATE/01_build_install_bug.yml @@ -1,103 +1,103 @@ -name: Installation or build bug report -description: Report installation or build bugs -labels: ['bug'] -body: - - type: checkboxes - id: checklist - attributes: - label: Answers checklist. - description: Before submitting a new issue, please follow the checklist and try to find the answer. - options: - - label: I have read the documentation [XiaoZhi AI Programming Guide](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb) and the issue is not addressed there. - required: true - - label: I have updated my branch (master or release) to the latest version and checked that the issue is present there. - required: true - - label: I have searched the issue tracker for a similar issue and not found a similar issue. - required: true - - type: input - id: xiaozhi_ai_version - attributes: - label: XiaoZhi AI version. - description: On which XiaoZhi AI version does this issue occur on? Run `git describe --tags` to find it. - placeholder: ex. v1.1.0-44-g140aab8 - validations: - required: true - - type: dropdown - id: operating_system - attributes: - label: Operating System used. - multiple: false - options: - - Windows - - Linux - - macOS - validations: - required: true - - type: dropdown - id: build - attributes: - label: How did you build your project? - multiple: false - options: - - Command line with CMake - - Command line with idf.py - - CLion IDE - - VS Code IDE/Cursor - - Other (please specify in More Information) - validations: - required: true - - type: dropdown - id: windows_comand_line - attributes: - label: If you are using Windows, please specify command line type. - multiple: false - options: - - PowerShell - - CMD - validations: - required: false - - type: textarea - id: expected - attributes: - label: What is the expected behavior? - description: Please provide a clear and concise description of the expected behavior. - placeholder: I expected it to... - validations: - required: true - - type: textarea - id: actual - attributes: - label: What is the actual behavior? - description: Please describe actual behavior. - placeholder: Instead it... - validations: - required: true - - type: textarea - id: steps - attributes: - label: Steps to reproduce. - description: 'How do you trigger this bug? Please walk us through it step by step. If this is build bug, please attach sdkconfig file (from your project folder). Please attach your code here.' - value: | - 1. Step - 2. Step - 3. Step - ... - validations: - required: true - - type: textarea - id: debug_logs - attributes: - label: Build or installation Logs. - description: Build or installation log goes here, should contain the backtrace, as well as the reset source if it is a crash. - placeholder: Your log goes here. - render: plain - validations: - required: false - - type: textarea - id: more-info - attributes: - label: More Information. - description: Do you have any other information from investigating this? - placeholder: ex. Any more. - validations: +name: Installation or build bug report +description: Report installation or build bugs +labels: ['bug'] +body: + - type: checkboxes + id: checklist + attributes: + label: Answers checklist. + description: Before submitting a new issue, please follow the checklist and try to find the answer. + options: + - label: I have read the documentation [XiaoZhi AI Programming Guide](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb) and the issue is not addressed there. + required: true + - label: I have updated my branch (master or release) to the latest version and checked that the issue is present there. + required: true + - label: I have searched the issue tracker for a similar issue and not found a similar issue. + required: true + - type: input + id: xiaozhi_ai_version + attributes: + label: XiaoZhi AI version. + description: On which XiaoZhi AI version does this issue occur on? Run `git describe --tags` to find it. + placeholder: ex. v1.1.0-44-g140aab8 + validations: + required: true + - type: dropdown + id: operating_system + attributes: + label: Operating System used. + multiple: false + options: + - Windows + - Linux + - macOS + validations: + required: true + - type: dropdown + id: build + attributes: + label: How did you build your project? + multiple: false + options: + - Command line with CMake + - Command line with idf.py + - CLion IDE + - VS Code IDE/Cursor + - Other (please specify in More Information) + validations: + required: true + - type: dropdown + id: windows_comand_line + attributes: + label: If you are using Windows, please specify command line type. + multiple: false + options: + - PowerShell + - CMD + validations: + required: false + - type: textarea + id: expected + attributes: + label: What is the expected behavior? + description: Please provide a clear and concise description of the expected behavior. + placeholder: I expected it to... + validations: + required: true + - type: textarea + id: actual + attributes: + label: What is the actual behavior? + description: Please describe actual behavior. + placeholder: Instead it... + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce. + description: 'How do you trigger this bug? Please walk us through it step by step. If this is build bug, please attach sdkconfig file (from your project folder). Please attach your code here.' + value: | + 1. Step + 2. Step + 3. Step + ... + validations: + required: true + - type: textarea + id: debug_logs + attributes: + label: Build or installation Logs. + description: Build or installation log goes here, should contain the backtrace, as well as the reset source if it is a crash. + placeholder: Your log goes here. + render: plain + validations: + required: false + - type: textarea + id: more-info + attributes: + label: More Information. + description: Do you have any other information from investigating this? + placeholder: ex. Any more. + validations: required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/02_runtime_bug.yml b/.github/ISSUE_TEMPLATE/02_runtime_bug.yml index 60a62f8..8b3b7a3 100644 --- a/.github/ISSUE_TEMPLATE/02_runtime_bug.yml +++ b/.github/ISSUE_TEMPLATE/02_runtime_bug.yml @@ -1,115 +1,115 @@ -name: Runtime bug report -description: Report runtime bugs -labels: ['bug'] -body: - - type: checkboxes - id: checklist - attributes: - label: Answers checklist. - description: Before submitting a new issue, please follow the checklist and try to find the answer. - options: - - label: I have read the documentation [XiaoZhi AI Programming Guide](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb) and the issue is not addressed there. - required: true - - label: I have updated my firmware to the latest version and checked that the issue is present there. - required: true - - label: I have searched the issue tracker for a similar issue and not found a similar issue. - required: true - - type: input - id: xiaozhi_ai_firmware_version - attributes: - label: XiaoZhi AI firmware version. - description: On which firmware version does this issue occur on? - placeholder: ex. v1.2.1_bread-compact-wifi - validations: - required: true - - type: dropdown - id: operating_system - attributes: - label: Operating System used. - multiple: false - options: - - Windows - - Linux - - macOS - validations: - required: true - - type: dropdown - id: build - attributes: - label: How did you build your project? - multiple: false - options: - - Command line with CMake - - Command line with idf.py - - CLion IDE - - VS Code IDE/Cursor - - Other (please specify in More Information) - validations: - required: true - - type: dropdown - id: windows_comand_line - attributes: - label: If you are using Windows, please specify command line type. - multiple: false - options: - - PowerShell - - CMD - validations: - required: false - - type: dropdown - id: power_supply - attributes: - label: Power Supply used. - multiple: false - options: - - USB - - External 5V - - External 3.3V - - Battery - validations: - required: true - - type: textarea - id: expected - attributes: - label: What is the expected behavior? - description: Please provide a clear and concise description of the expected behavior. - placeholder: I expected it to... - validations: - required: true - - type: textarea - id: actual - attributes: - label: What is the actual behavior? - description: Please describe actual behavior. - placeholder: Instead it... - validations: - required: true - - type: textarea - id: steps - attributes: - label: Steps to reproduce. - description: 'How do you trigger this bug? Please walk us through it step by step. Please attach your code here.' - value: | - 1. Step - 2. Step - 3. Step - ... - validations: - required: true - - type: textarea - id: debug_logs - attributes: - label: Debug Logs. - description: Debug log goes here, should contain the backtrace, as well as the reset source if it is a crash. - placeholder: Your log goes here. - render: plain - validations: - required: false - - type: textarea - id: more-info - attributes: - label: More Information. - description: Do you have any other information from investigating this? - placeholder: ex. Any more. - validations: +name: Runtime bug report +description: Report runtime bugs +labels: ['bug'] +body: + - type: checkboxes + id: checklist + attributes: + label: Answers checklist. + description: Before submitting a new issue, please follow the checklist and try to find the answer. + options: + - label: I have read the documentation [XiaoZhi AI Programming Guide](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb) and the issue is not addressed there. + required: true + - label: I have updated my firmware to the latest version and checked that the issue is present there. + required: true + - label: I have searched the issue tracker for a similar issue and not found a similar issue. + required: true + - type: input + id: xiaozhi_ai_firmware_version + attributes: + label: XiaoZhi AI firmware version. + description: On which firmware version does this issue occur on? + placeholder: ex. v1.2.1_bread-compact-wifi + validations: + required: true + - type: dropdown + id: operating_system + attributes: + label: Operating System used. + multiple: false + options: + - Windows + - Linux + - macOS + validations: + required: true + - type: dropdown + id: build + attributes: + label: How did you build your project? + multiple: false + options: + - Command line with CMake + - Command line with idf.py + - CLion IDE + - VS Code IDE/Cursor + - Other (please specify in More Information) + validations: + required: true + - type: dropdown + id: windows_comand_line + attributes: + label: If you are using Windows, please specify command line type. + multiple: false + options: + - PowerShell + - CMD + validations: + required: false + - type: dropdown + id: power_supply + attributes: + label: Power Supply used. + multiple: false + options: + - USB + - External 5V + - External 3.3V + - Battery + validations: + required: true + - type: textarea + id: expected + attributes: + label: What is the expected behavior? + description: Please provide a clear and concise description of the expected behavior. + placeholder: I expected it to... + validations: + required: true + - type: textarea + id: actual + attributes: + label: What is the actual behavior? + description: Please describe actual behavior. + placeholder: Instead it... + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce. + description: 'How do you trigger this bug? Please walk us through it step by step. Please attach your code here.' + value: | + 1. Step + 2. Step + 3. Step + ... + validations: + required: true + - type: textarea + id: debug_logs + attributes: + label: Debug Logs. + description: Debug log goes here, should contain the backtrace, as well as the reset source if it is a crash. + placeholder: Your log goes here. + render: plain + validations: + required: false + - type: textarea + id: more-info + attributes: + label: More Information. + description: Do you have any other information from investigating this? + placeholder: ex. Any more. + validations: required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/03_feature_request.yml b/.github/ISSUE_TEMPLATE/03_feature_request.yml index 79cb921..0c258ca 100644 --- a/.github/ISSUE_TEMPLATE/03_feature_request.yml +++ b/.github/ISSUE_TEMPLATE/03_feature_request.yml @@ -1,34 +1,34 @@ -name: Feature request -description: Suggest an idea for this project. -labels: ['enhancement'] -body: - - type: markdown - attributes: - value: | - * We welcome any ideas or feature requests! It’s helpful if you can explain exactly why the feature would be useful. - * There are usually some outstanding feature requests in the [existing issues list](https://github.com/78/xiaozhi-esp32/labels/enhancement), feel free to add comments to them. - * If you would like to contribute, please read the [contributions guide](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb). - - type: textarea - id: problem-related - attributes: - label: Is your feature request related to a problem? - description: Please provide a clear and concise description of what the problem is. - placeholder: ex. I'm always frustrated when ... - - 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. - placeholder: ex. When using XiaoZhi ... - - 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. - placeholder: ex. Choosing other approach wouldn't work, because ... - - type: textarea - id: context - attributes: - label: Additional context. - description: Please add any other context or screenshots about the feature request here. +name: Feature request +description: Suggest an idea for this project. +labels: ['enhancement'] +body: + - type: markdown + attributes: + value: | + * We welcome any ideas or feature requests! It’s helpful if you can explain exactly why the feature would be useful. + * There are usually some outstanding feature requests in the [existing issues list](https://github.com/78/xiaozhi-esp32/labels/enhancement), feel free to add comments to them. + * If you would like to contribute, please read the [contributions guide](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb). + - type: textarea + id: problem-related + attributes: + label: Is your feature request related to a problem? + description: Please provide a clear and concise description of what the problem is. + placeholder: ex. I'm always frustrated when ... + - 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. + placeholder: ex. When using XiaoZhi ... + - 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. + placeholder: ex. Choosing other approach wouldn't work, because ... + - type: textarea + id: context + attributes: + label: Additional context. + description: Please add any other context or screenshots about the feature request here. placeholder: ex. This would work only when ... \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index d663ce7..5e83fcc 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ -blank_issues_enabled: true -contact_links: - - name: 小智 AI 官方网站 - url: https://xiaozhi.me/ - about: 激活设备、配置 AI、声纹识别、声音克隆等应有尽有,DIY 属于你自己的小智 - - name: 小智 AI 聊天机器人百科全书 - url: https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb +blank_issues_enabled: true +contact_links: + - name: 小智 AI 官方网站 + url: https://xiaozhi.me/ + about: 激活设备、配置 AI、声纹识别、声音克隆等应有尽有,DIY 属于你自己的小智 + - name: 小智 AI 聊天机器人百科全书 + url: https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb about: 开发文档、硬件制作、烧录教程、FAQ尽在小智百科 \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..dcfd98f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,111 @@ +name: Build Boards + +on: + push: + branches: + - main + - ci/* # for ci test + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + prepare: + name: Determine variants to build + runs-on: ubuntu-latest + outputs: + variants: ${{ steps.select.outputs.variants }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install jq + run: sudo apt-get update && sudo apt-get install -y jq + + - id: list + name: Get all variant list + run: | + echo "all_variants=$(python scripts/release.py --list-boards --json)" >> $GITHUB_OUTPUT + + - id: select + name: Select variants based on changes + env: + ALL_VARIANTS: ${{ steps.list.outputs.all_variants }} + run: | + EVENT_NAME="${{ github.event_name }}" + + # push 到 main 分支,编译全部变体 + if [[ "$EVENT_NAME" == "push" ]]; then + echo "variants=$ALL_VARIANTS" >> $GITHUB_OUTPUT + exit 0 + fi + + # pull_request 场景 + BASE_SHA="${{ github.event.pull_request.base.sha }}" + HEAD_SHA="${{ github.event.pull_request.head.sha }}" + echo "Base: $BASE_SHA, Head: $HEAD_SHA" + + CHANGED=$(git diff --name-only $BASE_SHA $HEAD_SHA || true) + echo -e "Changed files:\n$CHANGED" + + NEED_ALL=0 + declare -A AFFECTED + while IFS= read -r file; do + if [[ "$file" == main/* && "$file" != main/boards/* ]]; then + NEED_ALL=1 + fi + + if [[ "$file" == main/boards/common/* ]]; then + NEED_ALL=1 + fi + + if [[ "$file" == main/boards/* ]]; then + board=$(echo "$file" | cut -d '/' -f3) + AFFECTED[$board]=1 + fi + done <<< "$CHANGED" + + if [[ "$NEED_ALL" -eq 1 ]]; then + echo "variants=$ALL_VARIANTS" >> $GITHUB_OUTPUT + else + if [[ ${#AFFECTED[@]} -eq 0 ]]; then + echo "variants=[]" >> $GITHUB_OUTPUT + else + BOARDS_JSON=$(printf '%s\n' "${!AFFECTED[@]}" | sort -u | jq -R -s -c 'split("\n")[:-1]') + FILTERED=$(echo "$ALL_VARIANTS" | jq -c --argjson boards "$BOARDS_JSON" 'map(select(.board as $b | $boards | index($b)))') + echo "variants=$FILTERED" >> $GITHUB_OUTPUT + fi + fi + + build: + name: Build ${{ matrix.name }} + needs: prepare + if: ${{ needs.prepare.outputs.variants != '[]' }} + strategy: + fail-fast: false # 单个变体失败不影响其它变体 + matrix: + include: ${{ fromJson(needs.prepare.outputs.variants) }} + runs-on: ubuntu-latest + container: + image: espressif/idf:release-v5.4 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build current variant + shell: bash + run: | + source $IDF_PATH/export.sh + python scripts/release.py ${{ matrix.board }} --name ${{ matrix.name }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: xiaozhi_${{ matrix.name }}_${{ github.sha }}.bin + path: build/merged-binary.bin + if-no-files-found: error diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5af22d --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +tmp/ +components/ +managed_components/ +build/ +.vscode/ +.devcontainer/ +sdkconfig.old +sdkconfig +dependencies.lock +.env +releases/ +main/assets/lang_config.h +main/mmap_generate_emoji.h +.DS_Store +.cache +*.pyc +*.bin +mmap_generate_*.h diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100755 new mode 100644 index 9054045..c39d567 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,17 @@ -# For more information about build system see -# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html -# The following five lines of boilerplate have to be in your project's -# CMakeLists in this exact order for cmake to work correctly -cmake_minimum_required(VERSION 3.16) - -set(PROJECT_VER "2.0.1") - -# Add this line to disable the specific warning -add_compile_options(-Wno-missing-field-initializers) - -include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(xiaozhi) \ No newline at end of file +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +set(PROJECT_VER "2.0.3") + +# Add this line to disable the specific warning +add_compile_options(-Wno-missing-field-initializers) + +# Fix Windows command line length limit +set(CMAKE_NINJA_FORCE_RESPONSE_FILE 1) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(xiaozhi) + diff --git a/LICENSE b/LICENSE index e90b554..099ed95 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,22 @@ -MIT License - -Copyright (c) 2025 Shenzhen Xinzhi Future Technology Co., Ltd. -Copyright (c) 2025 Project Contributors - -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. +MIT License + +Copyright (c) 2025 Shenzhen Xinzhi Future Technology Co., Ltd. +Copyright (c) 2025 Project Contributors + +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. diff --git a/README.md b/README.md index 5dbccaa..792901d 100644 --- a/README.md +++ b/README.md @@ -1,218 +1,171 @@ -# 超级小智-ESP32 -(中文 | English(编写中) | 日本語(编写中)) - -基于 https://github.com/78/xiaozhi-esp32 改良的船新版本 - -## 💡介绍 -这是一个由虾哥开源的 ESP32 项目,以 MIT 许可证发布,允许任何人免费使用,或用于商业用途。 - -我们希望通过这个项目,能够帮助大家了解 AI 硬件开发,将当下飞速发展的大语言模型应用到实际的硬件设备中。 - -如果你有任何想法或建议,请随时提出 Issues 或加入 QQ 群:暂无,Telegram群:暂无。 - -项目主要贡献者:小霜霜Meow(抖音、B站UP)、空白泡泡糖果(B站UP),硅灵造物科技(B站UP) - -项目其它贡献者:[@zhubinsheng](https://github.com/zhubinsheng) - -贡献者说明:引入部分其它贡献者在其它项目上的代码,并进行了部分修改。 - -音乐服务器、相关源码提供者(为爱发电):小霜霜Meow - -音乐服务器源码请见 https://github.com/IntelligentlyEverything/MeowMusicServer - -### ❕注意事项 -1. 如果小智说找不到歌曲怎么办? -进入[小智后台](https://xiaozhi.me/),找到对应设备,修改角色配置 -- 选择 DeepSeekV3 大语言模型 -- 在人物介绍中填入 - - 收到音乐相关的需求时,只使用 MPC tool self.music.play_song 工具,同时禁止使用 search_music 功能。 - -2. 内置API调用失败怎么办? -请查看具体错误代码后,加入QQ群:865754861,或电报群 http://t.me/MeowMusicServer 给出错误代码和日志,等待我们修复。 - -### ⚙️已支持硬件芯片系列 - -- ESP32 -- ESP32-S3 -- ESP32-C3 -- ESP32-C6 -- ESP32-P4 - -❕大部分硬件由于没有进行完整测试,可能会存在一些问题,属于正常现象,具体可提交issues进行反馈。 - -### 项目改动范围 -新增: -- main/schedule_manager.h -- main/schedule_manager.cc -- main/audio/timer_manager.h -- main/audio/timer_manager.cc -- main/boards/common/music.h -- main/boards/common/esp32_music.h -- main/boards/common/esp32_music.cc -- main/display/esplog_display.h -- main/display/esplog_display.cc -- main/protocols/sleep_music_protocol.h -- main/protocols/sleep_music_protocol.cc - -修改: -- main/audio/codecs/no_audio_codec.h -- main/audio/codecs/no_audio_codec.cc -- main/audio/audio_codec.h -- main/audio/audio_codec.cc -- main/audio/audio_service.h -- main/audio/audio_service.cc -- main/boards/common/board.h -- main/boards/common/board.cc -- main/display/display.h -- main/display/display.cc -- main/display/lcd_display.h -- main/display/lcd_display.cc -- main/application.h -- main/application.cc -- main/idf_component.yml -- main/mcp_server.cc - -### 基于 MCP 控制万物 -小智 AI 聊天机器人作为一个语音交互入口,利用 Qwen / DeepSeek 等大模型的 AI 能力,通过 MCP 协议实现多端控制。 - -![通过MCP控制万物](docs/mcp-based-graph.jpg) - -### 已实现功能 - -- Wi-Fi / ML307 Cat.1 4G -- 离线语音唤醒 [ESP-SR](https://github.com/espressif/esp-sr) -- 支持两种通信协议([Websocket](docs/websocket.md) 或 MQTT+UDP) -- 采用 OPUS 音频编解码 -- 基于流式 ASR + LLM + TTS 架构的语音交互 -- 声纹识别,识别当前说话人的身份 [3D Speaker](https://github.com/modelscope/3D-Speaker) -- OLED / LCD 显示屏,支持表情显示 -- 电量显示与电源管理 -- 支持多语言(中文、英文、日文) -- 支持 ESP32-C3、ESP32-S3、ESP32-P4 芯片平台 -- 通过设备端 MCP 实现设备控制(音量、灯光、电机、GPIO 等) -- 通过云端 MCP 扩展大模型能力(智能家居控制、PC桌面操作、知识搜索、邮件收发等) -本项目新增功能: -- 新增音乐播放功能,支持播放本地音乐(开发中,敬请期待)、云端音乐(完善中)。 - -## 硬件 - -### 面包板手工制作实践 - -详见飞书文档教程: - -👉 [《小智 AI 聊天机器人百科全书》](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink) - -面包板效果图如下: - -![面包板效果图](docs/v1/wiring2.jpg) - -### 支持 70 多个开源硬件(仅展示部分) - -- 立创·实战派 ESP32-S3 开发板 -- 乐鑫 ESP32-S3-BOX3 -- M5Stack CoreS3 -- M5Stack AtomS3R + Echo Base -- 神奇按钮 2.4 -- 微雪电子 ESP32-S3-Touch-AMOLED-1.8 -- LILYGO T-Circle-S3 -- 虾哥 Mini C3 -- 璀璨·AI 吊坠 -- 无名科技 Nologo-星智-1.54TFT -- SenseCAP Watcher -- ESP-HI 超低成本机器狗 - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -## 软件 - -### 固件烧录 - -新手第一次操作建议先不要搭建开发环境,直接使用免开发环境烧录的固件。 - -固件默认接入 [xiaozhi.me](https://xiaozhi.me) 官方服务器,个人用户注册账号可以免费使用 Qwen 实时模型。 - -👉 [新手烧录固件教程](https://ccnphfhqs21z.feishu.cn/wiki/Zpz4wXBtdimBrLk25WdcXzxcnNS) - -### 开发环境 - -- Cursor 或 VSCode -- 安装 ESP-IDF 插件,选择 SDK 版本 5.4 或以上 -- Linux 比 Windows 更好,编译速度快,也免去驱动问题的困扰 -- 本项目使用 Google C++ 代码风格,提交代码时请确保符合规范 - -### 开发者文档 - -- [自定义开发板指南](main/boards/README.md) - 学习如何为小智 AI 创建自定义开发板 -- [MCP 协议物联网控制用法说明](docs/mcp-usage.md) - 了解如何通过 MCP 协议控制物联网设备 -- [MCP 协议交互流程](docs/mcp-protocol.md) - 设备端 MCP 协议的实现方式 -- [MQTT + UDP 混合通信协议文档](docs/mqtt-udp.md) -- [一份详细的 WebSocket 通信协议文档](docs/websocket.md) - -## 大模型配置 - -如果你已经拥有一个小智 AI 聊天机器人设备,并且已接入官方服务器,可以登录 [xiaozhi.me](https://xiaozhi.me) 控制台进行配置。 - -👉 [后台操作视频教程(旧版界面)](https://www.bilibili.com/video/BV1jUCUY2EKM/) - -## 相关开源项目 - -在个人电脑上部署服务器,可以参考以下第三方开源的项目: - -- [xinnan-tech/xiaozhi-esp32-server](https://github.com/xinnan-tech/xiaozhi-esp32-server) Python 服务器 -- [joey-zhou/xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) Java 服务器 -- [AnimeAIChat/xiaozhi-server-go](https://github.com/AnimeAIChat/xiaozhi-server-go) Golang 服务器 - -使用小智通信协议的第三方客户端项目: - -- [huangjunsen0406/py-xiaozhi](https://github.com/huangjunsen0406/py-xiaozhi) Python 客户端 -- [TOM88812/xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) Android 客户端 -- [100askTeam/xiaozhi-linux](http://github.com/100askTeam/xiaozhi-linux) 百问科技提供的 Linux 客户端 -- [78/xiaozhi-sf32](https://github.com/78/xiaozhi-sf32) 思澈科技的蓝牙芯片固件 -- [QuecPython/solution-xiaozhiAI](https://github.com/QuecPython/solution-xiaozhiAI) 移远提供的 QuecPython 固件 - -## Star History - - - - - - Star History Chart - - +# An MCP-based Chatbot | 一个基于 MCP 的聊天机器人 + +(中文 | [English](README_en.md) | [日本語](README_ja.md)) + +## 视频 + +👉 [人类:给 AI 装摄像头 vs AI:当场发现主人三天没洗头【bilibili】](https://www.bilibili.com/video/BV1bpjgzKEhd/) + +👉 [手工打造你的 AI 女友,新手入门教程【bilibili】](https://www.bilibili.com/video/BV1XnmFYLEJN/) + +## 介绍 + +这是一个由虾哥开源的 ESP32 项目,以 MIT 许可证发布,允许任何人免费使用,或用于商业用途。 + +我们希望通过这个项目,能够帮助大家了解 AI 硬件开发,将当下飞速发展的大语言模型应用到实际的硬件设备中。 + +如果你有任何想法或建议,请随时提出 Issues 或加入 QQ 群:1011329060 + +项目其它贡献者:@zhubinsheng + +贡献者说明:引入部分其它贡献者在其它项目上的代码,并进行了部分修改。 + +音乐服务器相关源码提供者(为爱发电):小霜霜Meow + +感谢群友 cz 提供音乐服务器 + +音乐服务器源码请见 https://github.com/IntelligentlyEverything/MeowMusicServer + +### 基于 MCP 控制万物 + +小智 AI 聊天机器人作为一个语音交互入口,利用 Qwen / DeepSeek 等大模型的 AI 能力,通过 MCP 协议实现多端控制。 + +![通过MCP控制万物](docs/mcp-based-graph.jpg) + +### 已实现功能 + +- Wi-Fi / ML307 Cat.1 4G +- 离线语音唤醒 [ESP-SR](https://github.com/espressif/esp-sr) +- 支持两种通信协议([Websocket](docs/websocket.md) 或 MQTT+UDP) +- 采用 OPUS 音频编解码 +- 基于流式 ASR + LLM + TTS 架构的语音交互 +- 声纹识别,识别当前说话人的身份 [3D Speaker](https://github.com/modelscope/3D-Speaker) +- OLED / LCD 显示屏,支持表情显示 +- 电量显示与电源管理 +- 支持多语言(中文、英文、日文) +- 支持 ESP32-C3、ESP32-S3、ESP32-P4 芯片平台 +- 通过设备端 MCP 实现设备控制(音量、灯光、电机、GPIO 等) +- 通过云端 MCP 扩展大模型能力(智能家居控制、PC桌面操作、知识搜索、邮件收发等) + +## 硬件 + +### 面包板手工制作实践 + +详见飞书文档教程: + +👉 [《小智 AI 聊天机器人百科全书》](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink) + +面包板效果图如下: + +![面包板效果图](docs/v1/wiring2.jpg) + +### 支持 70 多个开源硬件(仅展示部分) + +- 立创·实战派 ESP32-S3 开发板 +- 乐鑫 ESP32-S3-BOX3 +- M5Stack CoreS3 +- M5Stack AtomS3R + Echo Base +- 神奇按钮 2.4 +- 微雪电子 ESP32-S3-Touch-AMOLED-1.8 +- LILYGO T-Circle-S3 +- 虾哥 Mini C3 +- 璀璨·AI 吊坠 +- 无名科技 Nologo-星智-1.54TFT +- SenseCAP Watcher +- ESP-HI 超低成本机器狗 + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +## 软件 + +### 固件烧录 + +新手第一次操作建议先不要搭建开发环境,直接使用免开发环境烧录的固件。 + +固件默认接入 [xiaozhi.me](https://xiaozhi.me) 官方服务器,个人用户注册账号可以免费使用 Qwen 实时模型。 + +👉 [新手烧录固件教程](https://ccnphfhqs21z.feishu.cn/wiki/Zpz4wXBtdimBrLk25WdcXzxcnNS) + +### 开发环境 + +- Cursor 或 VSCode +- 安装 ESP-IDF 插件,选择 SDK 版本 5.4 或以上 +- Linux 比 Windows 更好,编译速度快,也免去驱动问题的困扰 +- 本项目使用 Google C++ 代码风格,提交代码时请确保符合规范 + +### 开发者文档 + +- [自定义开发板指南](main/boards/README.md) - 学习如何为小智 AI 创建自定义开发板 +- [MCP 协议物联网控制用法说明](docs/mcp-usage.md) - 了解如何通过 MCP 协议控制物联网设备 +- [MCP 协议交互流程](docs/mcp-protocol.md) - 设备端 MCP 协议的实现方式 +- [MQTT + UDP 混合通信协议文档](docs/mqtt-udp.md) +- [一份详细的 WebSocket 通信协议文档](docs/websocket.md) + +## 大模型配置 + +如果你已经拥有一个小智 AI 聊天机器人设备,并且已接入官方服务器,可以登录 [xiaozhi.me](https://xiaozhi.me) 控制台进行配置。 + +👉 [后台操作视频教程(旧版界面)](https://www.bilibili.com/video/BV1jUCUY2EKM/) + +## 相关开源项目 + +在个人电脑上部署服务器,可以参考以下第三方开源的项目: + +- [xinnan-tech/xiaozhi-esp32-server](https://github.com/xinnan-tech/xiaozhi-esp32-server) Python 服务器 +- [joey-zhou/xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) Java 服务器 +- [AnimeAIChat/xiaozhi-server-go](https://github.com/AnimeAIChat/xiaozhi-server-go) Golang 服务器 + +使用小智通信协议的第三方客户端项目: + +- [huangjunsen0406/py-xiaozhi](https://github.com/huangjunsen0406/py-xiaozhi) Python 客户端 +- [TOM88812/xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) Android 客户端 +- [100askTeam/xiaozhi-linux](http://github.com/100askTeam/xiaozhi-linux) 百问科技提供的 Linux 客户端 +- [78/xiaozhi-sf32](https://github.com/78/xiaozhi-sf32) 思澈科技的蓝牙芯片固件 +- [QuecPython/solution-xiaozhiAI](https://github.com/QuecPython/solution-xiaozhiAI) 移远提供的 QuecPython 固件 + +## Star History + + + + + + Star History Chart + + diff --git a/README_en.md b/README_en.md new file mode 100644 index 0000000..432c768 --- /dev/null +++ b/README_en.md @@ -0,0 +1,157 @@ +# An MCP-based Chatbot + +(English | [中文](README.md) | [日本語](README_ja.md)) + +## Video + +👉 [Human: Give AI a camera vs AI: Instantly finds out the owner hasn't washed hair for three days【bilibili】](https://www.bilibili.com/video/BV1bpjgzKEhd/) + +👉 [Handcraft your AI girlfriend, beginner's guide【bilibili】](https://www.bilibili.com/video/BV1XnmFYLEJN/) + +## Introduction + +This is an open-source ESP32 project, released under the MIT license, allowing anyone to use it for free, including for commercial purposes. + +We hope this project helps everyone understand AI hardware development and apply rapidly evolving large language models to real hardware devices. + +If you have any ideas or suggestions, please feel free to raise Issues or join the QQ group: 1011329060 + +### Control Everything with MCP + +As a voice interaction entry, the XiaoZhi AI chatbot leverages the AI capabilities of large models like Qwen / DeepSeek, and achieves multi-terminal control via the MCP protocol. + +![Control everything via MCP](docs/mcp-based-graph.jpg) + +### Features Implemented + +- Wi-Fi / ML307 Cat.1 4G +- Offline voice wake-up [ESP-SR](https://github.com/espressif/esp-sr) +- Supports two communication protocols ([Websocket](docs/websocket.md) or MQTT+UDP) +- Uses OPUS audio codec +- Voice interaction based on streaming ASR + LLM + TTS architecture +- Speaker recognition, identifies the current speaker [3D Speaker](https://github.com/modelscope/3D-Speaker) +- OLED / LCD display, supports emoji display +- Battery display and power management +- Multi-language support (Chinese, English, Japanese) +- Supports ESP32-C3, ESP32-S3, ESP32-P4 chip platforms +- Device-side MCP for device control (Speaker, LED, Servo, GPIO, etc.) +- Cloud-side MCP to extend large model capabilities (smart home control, PC desktop operation, knowledge search, email, etc.) + +## Hardware + +### Breadboard DIY Practice + +See the Feishu document tutorial: + +👉 ["XiaoZhi AI Chatbot Encyclopedia"](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink) + +Breadboard demo: + +![Breadboard Demo](docs/v1/wiring2.jpg) + +### Supports 70+ Open Source Hardware (Partial List) + +- LiChuang ESP32-S3 Development Board +- Espressif ESP32-S3-BOX3 +- M5Stack CoreS3 +- M5Stack AtomS3R + Echo Base +- Magic Button 2.4 +- Waveshare ESP32-S3-Touch-AMOLED-1.8 +- LILYGO T-Circle-S3 +- XiaGe Mini C3 +- CuiCan AI Pendant +- WMnologo-Xingzhi-1.54TFT +- SenseCAP Watcher +- ESP-HI Low Cost Robot Dog + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +## Software + +### Firmware Flashing + +For beginners, it is recommended to use the firmware that can be flashed without setting up a development environment. + +The firmware connects to the official [xiaozhi.me](https://xiaozhi.me) server by default. Personal users can register an account to use the Qwen real-time model for free. + +👉 [Beginner's Firmware Flashing Guide](https://ccnphfhqs21z.feishu.cn/wiki/Zpz4wXBtdimBrLk25WdcXzxcnNS) + +### Development Environment + +- Cursor or VSCode +- Install ESP-IDF plugin, select SDK version 5.4 or above +- Linux is better than Windows for faster compilation and fewer driver issues +- This project uses Google C++ code style, please ensure compliance when submitting code + +### Developer Documentation + +- [Custom Board Guide](main/boards/README.md) - Learn how to create custom boards for XiaoZhi AI +- [MCP Protocol IoT Control Usage](docs/mcp-usage.md) - Learn how to control IoT devices via MCP protocol +- [MCP Protocol Interaction Flow](docs/mcp-protocol.md) - Device-side MCP protocol implementation +- [A detailed WebSocket communication protocol document](docs/websocket.md) + +## Large Model Configuration + +If you already have a XiaoZhi AI chatbot device and have connected to the official server, you can log in to the [xiaozhi.me](https://xiaozhi.me) console for configuration. + +👉 [Backend Operation Video Tutorial (Old Interface)](https://www.bilibili.com/video/BV1jUCUY2EKM/) + +## Related Open Source Projects + +For server deployment on personal computers, refer to the following open-source projects: + +- [xinnan-tech/xiaozhi-esp32-server](https://github.com/xinnan-tech/xiaozhi-esp32-server) Python server +- [joey-zhou/xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) Java server +- [AnimeAIChat/xiaozhi-server-go](https://github.com/AnimeAIChat/xiaozhi-server-go) Golang server + +Other client projects using the XiaoZhi communication protocol: + +- [huangjunsen0406/py-xiaozhi](https://github.com/huangjunsen0406/py-xiaozhi) Python client +- [TOM88812/xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) Android client + +## Star History + + + + + + Star History Chart + + diff --git a/README_ja.md b/README_ja.md new file mode 100644 index 0000000..c01acf8 --- /dev/null +++ b/README_ja.md @@ -0,0 +1,157 @@ +# MCP ベースのチャットボット + +(日本語 | [中文](README.md) | [English](README_en.md)) + +## 動画 + +👉 [人間:AIにカメラを装着 vs AI:その場で飼い主が3日間髪を洗っていないことを発見【bilibili】](https://www.bilibili.com/video/BV1bpjgzKEhd/) + +👉 [手作りでAIガールフレンドを作る、初心者入門チュートリアル【bilibili】](https://www.bilibili.com/video/BV1XnmFYLEJN/) + +## イントロダクション + +これはエビ兄さんがオープンソースで公開しているESP32プロジェクトで、MITライセンスのもと、誰でも無料で、商用利用も可能です。 + +このプロジェクトを通じて、AIハードウェア開発を理解し、急速に進化する大規模言語モデルを実際のハードウェアデバイスに応用できるようになることを目指しています。 + +ご意見やご提案があれば、いつでもIssueを提出するか、QQグループ:1011329060 にご参加ください。 + +### MCPであらゆるものを制御 + +シャオジーAIチャットボットは音声インタラクションの入口として、Qwen / DeepSeekなどの大規模モデルのAI能力を活用し、MCPプロトコルを通じてマルチエンド制御を実現します。 + +![MCPであらゆるものを制御](docs/mcp-based-graph.jpg) + +### 実装済み機能 + +- Wi-Fi / ML307 Cat.1 4G +- オフライン音声ウェイクアップ [ESP-SR](https://github.com/espressif/esp-sr) +- 2種類の通信プロトコルに対応([Websocket](docs/websocket.md) または MQTT+UDP) +- OPUSオーディオコーデックを採用 +- ストリーミングASR + LLM + TTSアーキテクチャに基づく音声インタラクション +- 話者認識、現在話している人を識別 [3D Speaker](https://github.com/modelscope/3D-Speaker) +- OLED / LCDディスプレイ、表情表示対応 +- バッテリー表示と電源管理 +- 多言語対応(中国語、英語、日本語) +- ESP32-C3、ESP32-S3、ESP32-P4チッププラットフォーム対応 +- デバイス側MCPによるデバイス制御(音量・明るさ調整、アクション制御など) +- クラウド側MCPで大規模モデル能力を拡張(スマートホーム制御、PCデスクトップ操作、知識検索、メール送受信など) + +## ハードウェア + +### ブレッドボード手作り実践 + +Feishuドキュメントチュートリアルをご覧ください: + +👉 [「シャオジーAIチャットボット百科事典」](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink) + +ブレッドボードのデモ: + +![ブレッドボードデモ](docs/v1/wiring2.jpg) + +### 70種類以上のオープンソースハードウェアに対応(一部のみ表示) + +- 立創・実戦派 ESP32-S3 開発ボード +- 楽鑫 ESP32-S3-BOX3 +- M5Stack CoreS3 +- M5Stack AtomS3R + Echo Base +- マジックボタン2.4 +- 微雪電子 ESP32-S3-Touch-AMOLED-1.8 +- LILYGO T-Circle-S3 +- エビ兄さん Mini C3 +- CuiCan AIペンダント +- 無名科技Nologo-星智-1.54TFT +- SenseCAP Watcher +- ESP-HI 超低コストロボット犬 + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +## ソフトウェア + +### ファームウェア書き込み + +初心者の方は、まず開発環境を構築せずに書き込み可能なファームウェアを使用することをおすすめします。 + +ファームウェアはデフォルトで公式 [xiaozhi.me](https://xiaozhi.me) サーバーに接続します。個人ユーザーはアカウント登録でQwenリアルタイムモデルを無料で利用できます。 + +👉 [初心者向けファームウェア書き込みガイド](https://ccnphfhqs21z.feishu.cn/wiki/Zpz4wXBtdimBrLk25WdcXzxcnNS) + +### 開発環境 + +- Cursor または VSCode +- ESP-IDFプラグインをインストールし、SDKバージョン5.4以上を選択 +- LinuxはWindowsよりも優れており、コンパイルが速く、ドライバの問題も少ない +- 本プロジェクトはGoogle C++コードスタイルを採用、コード提出時は準拠を確認してください + +### 開発者ドキュメント + +- [カスタム開発ボードガイド](main/boards/README.md) - シャオジーAI用のカスタム開発ボード作成方法 +- [MCPプロトコルIoT制御使用法](docs/mcp-usage.md) - MCPプロトコルでIoTデバイスを制御する方法 +- [MCPプロトコルインタラクションフロー](docs/mcp-protocol.md) - デバイス側MCPプロトコルの実装方法 +- [詳細なWebSocket通信プロトコルドキュメント](docs/websocket.md) + +## 大規模モデル設定 + +すでにシャオジーAIチャットボットデバイスをお持ちで、公式サーバーに接続済みの場合は、[xiaozhi.me](https://xiaozhi.me) コンソールで設定できます。 + +👉 [バックエンド操作ビデオチュートリアル(旧インターフェース)](https://www.bilibili.com/video/BV1jUCUY2EKM/) + +## 関連オープンソースプロジェクト + +個人PCでサーバーをデプロイする場合は、以下のオープンソースプロジェクトを参照してください: + +- [xinnan-tech/xiaozhi-esp32-server](https://github.com/xinnan-tech/xiaozhi-esp32-server) Pythonサーバー +- [joey-zhou/xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) Javaサーバー +- [AnimeAIChat/xiaozhi-server-go](https://github.com/AnimeAIChat/xiaozhi-server-go) Golangサーバー + +シャオジー通信プロトコルを利用した他のクライアントプロジェクト: + +- [huangjunsen0406/py-xiaozhi](https://github.com/huangjunsen0406/py-xiaozhi) Pythonクライアント +- [TOM88812/xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) Androidクライアント + +## スター履歴 + + + + + + Star History Chart + + diff --git a/SERVER_CONFIG_GUIDE.md b/SERVER_CONFIG_GUIDE.md new file mode 100644 index 0000000..91192e8 --- /dev/null +++ b/SERVER_CONFIG_GUIDE.md @@ -0,0 +1,228 @@ +# ESP32服务器地址配置指南 + +## 📍 **配置文件位置** + +``` +xiaozhi-esp32_music/main/server_config.h +``` + +## 🔧 **如何修改服务器地址** + +### **步骤1:查看您的服务器IP地址** + +#### **Windows系统** +打开命令提示符(CMD),输入: +```cmd +ipconfig +``` + +查找"IPv4 地址",例如:`192.168.1.100` + +#### **Linux/Mac系统** +打开终端,输入: +```bash +ifconfig +# 或 +ip addr +``` + +查找局域网IP地址。 + +--- + +### **步骤2:编辑配置文件** + +打开文件: +``` +d:\esp32-music-server\Meow\MeowEmbeddedMusicServer\xiaozhi-esp32_music\xiaozhi-esp32_music\main\server_config.h +``` + +找到这一行: +```cpp +#define MUSIC_SERVER_URL "http://192.168.1.100:2233" +``` + +将 `192.168.1.100` 替换为您的服务器IP地址。 + +--- + +## 🌐 **配置示例** + +### **示例1:本地局域网(推荐)** + +服务器和ESP32在同一个WiFi网络中: + +```cpp +#define MUSIC_SERVER_URL "http://192.168.1.100:2233" +``` + +**优点**: +- ✅ 速度快 +- ✅ 延迟低 +- ✅ 不需要公网IP + +--- + +### **示例2:公网IP** + +如果您有公网IP或使用花生壳等内网穿透: + +```cpp +#define MUSIC_SERVER_URL "http://123.45.67.89:2233" +``` + +**注意**: +- ⚠️ 确保路由器端口转发2233端口 +- ⚠️ 注意服务器安全 + +--- + +### **示例3:域名** + +如果您有域名: + +```cpp +#define MUSIC_SERVER_URL "http://your-music-server.com:2233" +``` + +**注意**: +- ⚠️ 确保域名解析正确 +- ⚠️ 如果使用HTTPS,改为`https://` + +--- + +### **示例4:使用原作者在线服务器** + +```cpp +#define MUSIC_SERVER_URL "http://http-embedded-music.miao-lab.top:2233" +``` + +**说明**: +- ✅ 无需自己搭建服务器 +- ⚠️ 依赖外部服务可用性 +- ⚠️ 无法使用设备绑定等个性化功能 + +--- + +## 🔍 **如何测试服务器地址是否正确** + +### **方法1:浏览器测试** + +在浏览器中访问: +``` +http://您的服务器IP:2233 +``` + +应该看到Meow Music的Web界面。 + +--- + +### **方法2:curl测试** + +```bash +curl http://您的服务器IP:2233/api/search?song=江南 +``` + +应该返回JSON格式的搜索结果。 + +--- + +## 📝 **完整配置检查清单** + +- [ ] 确认服务器正在运行(`go run .`) +- [ ] 确认服务器端口是2233 +- [ ] 确认ESP32和服务器在同一网络(或有公网连接) +- [ ] 修改`server_config.h`中的IP地址 +- [ ] 保存文件 +- [ ] 重新编译ESP32固件(`idf.py build`) +- [ ] 烧录到ESP32(`idf.py flash`) +- [ ] 测试连接 + +--- + +## 🐛 **常见问题** + +### **问题1:ESP32无法连接服务器** + +**现象**: +``` +[Esp32Music] Failed to connect to music API +``` + +**排查**: +1. 检查服务器是否运行 +2. 检查IP地址是否正确 +3. 检查ESP32是否连接WiFi +4. Ping服务器IP测试网络连通性 + +--- + +### **问题2:地址写错了** + +**现象**: +``` +[Esp32Music] HTTP GET failed with status code: 404 +``` + +**解决**: +- 检查URL格式是否正确 +- 确保有`http://`前缀 +- 确保端口号是`:2233` + +--- + +### **问题3:防火墙阻止** + +**现象**: +- ESP32无法连接 +- 但浏览器可以访问 + +**解决(Windows)**: +``` +控制面板 → Windows防火墙 → 允许应用通过防火墙 +→ 找到Go程序 → 允许专用和公用网络 +``` + +--- + +## 🎯 **推荐配置** + +### **开发测试阶段** + +使用局域网IP: +```cpp +#define MUSIC_SERVER_URL "http://192.168.1.100:2233" +``` + +### **生产环境** + +使用域名: +```cpp +#define MUSIC_SERVER_URL "http://music.your-domain.com:2233" +``` + +--- + +## 💡 **高级技巧** + +### **使用环境变量(未来功能)** + +可以考虑在ESP32端添加NVS配置,通过Web界面修改服务器地址,无需重新编译。 + +### **mDNS服务发现(未来功能)** + +可以使用mDNS实现服务器自动发现: +``` +http://meow-music.local:2233 +``` + +--- + +## 📞 **技术支持** + +如有问题,请加入: +**喵波音律QQ交流群:865754861** + +--- + +**配置完成后,记得重新编译并烧录固件!** 🚀 diff --git a/combine.exe b/combine.exe new file mode 100755 index 0000000..b77470e Binary files /dev/null and b/combine.exe differ diff --git a/docs/mcp-protocol.md b/docs/mcp-protocol.md index 0c8ec90..65860e0 100644 --- a/docs/mcp-protocol.md +++ b/docs/mcp-protocol.md @@ -1,269 +1,269 @@ -# MCP (Model Context Protocol) 交互流程 - -NOTICE: AI 辅助生成, 在实现后台服务时, 请参照代码确认细节!! - -本项目中的 MCP 协议用于后台 API(MCP 客户端)与 ESP32 设备(MCP 服务器)之间的通信,以便后台能够发现和调用设备提供的功能(工具)。 - -## 协议格式 - -根据代码 (`main/protocols/protocol.cc`, `main/mcp_server.cc`),MCP 消息是封装在基础通信协议(如 WebSocket 或 MQTT)的消息体中的。其内部结构遵循 [JSON-RPC 2.0](https://www.jsonrpc.org/specification) 规范。 - -整体消息结构示例: - -```json -{ - "session_id": "...", // 会话 ID - "type": "mcp", // 消息类型,固定为 "mcp" - "payload": { // JSON-RPC 2.0 负载 - "jsonrpc": "2.0", - "method": "...", // 方法名 (如 "initialize", "tools/list", "tools/call") - "params": { ... }, // 方法参数 (对于 request) - "id": ..., // 请求 ID (对于 request 和 response) - "result": { ... }, // 方法执行结果 (对于 success response) - "error": { ... } // 错误信息 (对于 error response) - } -} -``` - -其中,`payload` 部分是标准的 JSON-RPC 2.0 消息: - -- `jsonrpc`: 固定的字符串 "2.0"。 -- `method`: 要调用的方法名称 (对于 Request)。 -- `params`: 方法的参数,一个结构化值,通常为对象 (对于 Request)。 -- `id`: 请求的标识符,客户端发送请求时提供,服务器响应时原样返回。用于匹配请求和响应。 -- `result`: 方法成功执行时的结果 (对于 Success Response)。 -- `error`: 方法执行失败时的错误信息 (对于 Error Response)。 - -## 交互流程及发送时机 - -MCP 的交互主要围绕客户端(后台 API)发现和调用设备上的“工具”(Tool)进行。 - -1. **连接建立与能力通告** - - - **时机:** 设备启动并成功连接到后台 API 后。 - - **发送方:** 设备。 - - **消息:** 设备发送基础协议的 "hello" 消息给后台 API,消息中包含设备支持的能力列表,例如通过支持 MCP 协议 (`"mcp": true`)。 - - **示例 (非 MCP 负载,而是基础协议消息):** - ```json - { - "type": "hello", - "version": ..., - "features": { - "mcp": true, - ... - }, - "transport": "websocket", // 或 "mqtt" - "audio_params": { ... }, - "session_id": "..." // 设备收到服务器hello后可能设置 - } - ``` - -2. **初始化 MCP 会话** - - - **时机:** 后台 API 收到设备 "hello" 消息,确认设备支持 MCP 后,通常作为 MCP 会话的第一个请求发送。 - - **发送方:** 后台 API (客户端)。 - - **方法:** `initialize` - - **消息 (MCP payload):** - - ```json - { - "jsonrpc": "2.0", - "method": "initialize", - "params": { - "capabilities": { - // 客户端能力,可选 - - // 摄像头视觉相关 - "vision": { - "url": "...", //摄像头: 图片处理地址(必须是http地址, 不是websocket地址) - "token": "..." // url token - } - - // ... 其他客户端能力 - } - }, - "id": 1 // 请求 ID - } - ``` - - - **设备响应时机:** 设备收到 `initialize` 请求并处理后。 - - **设备响应消息 (MCP payload):** - ```json - { - "jsonrpc": "2.0", - "id": 1, // 匹配请求 ID - "result": { - "protocolVersion": "2024-11-05", - "capabilities": { - "tools": {} // 这里的 tools 似乎不列出详细信息,需要 tools/list - }, - "serverInfo": { - "name": "...", // 设备名称 (BOARD_NAME) - "version": "..." // 设备固件版本 - } - } - } - ``` - -3. **发现设备工具列表** - - - **时机:** 后台 API 需要获取设备当前支持的具体功能(工具)列表及其调用方式时。 - - **发送方:** 后台 API (客户端)。 - - **方法:** `tools/list` - - **消息 (MCP payload):** - ```json - { - "jsonrpc": "2.0", - "method": "tools/list", - "params": { - "cursor": "" // 用于分页,首次请求为空字符串 - }, - "id": 2 // 请求 ID - } - ``` - - **设备响应时机:** 设备收到 `tools/list` 请求并生成工具列表后。 - - **设备响应消息 (MCP payload):** - ```json - { - "jsonrpc": "2.0", - "id": 2, // 匹配请求 ID - "result": { - "tools": [ // 工具对象列表 - { - "name": "self.get_device_status", - "description": "...", - "inputSchema": { ... } // 参数 schema - }, - { - "name": "self.audio_speaker.set_volume", - "description": "...", - "inputSchema": { ... } // 参数 schema - } - // ... 更多工具 - ], - "nextCursor": "..." // 如果列表很大需要分页,这里会包含下一个请求的 cursor 值 - } - } - ``` - - **分页处理:** 如果 `nextCursor` 字段非空,客户端需要再次发送 `tools/list` 请求,并在 `params` 中带上这个 `cursor` 值以获取下一页工具。 - -4. **调用设备工具** - - - **时机:** 后台 API 需要执行设备上的某个具体功能时。 - - **发送方:** 后台 API (客户端)。 - - **方法:** `tools/call` - - **消息 (MCP payload):** - ```json - { - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "self.audio_speaker.set_volume", // 要调用的工具名称 - "arguments": { - // 工具参数,对象格式 - "volume": 50 // 参数名及其值 - } - }, - "id": 3 // 请求 ID - } - ``` - - **设备响应时机:** 设备收到 `tools/call` 请求,执行相应的工具函数后。 - - **设备成功响应消息 (MCP payload):** - ```json - { - "jsonrpc": "2.0", - "id": 3, // 匹配请求 ID - "result": { - "content": [ - // 工具执行结果内容 - { "type": "text", "text": "true" } // 示例:set_volume 返回 bool - ], - "isError": false // 表示成功 - } - } - ``` - - **设备失败响应消息 (MCP payload):** - ```json - { - "jsonrpc": "2.0", - "id": 3, // 匹配请求 ID - "error": { - "code": -32601, // JSON-RPC 错误码,例如 Method not found (-32601) - "message": "Unknown tool: self.non_existent_tool" // 错误描述 - } - } - ``` - -5. **设备主动发送消息 (Notifications)** - - **时机:** 设备内部发生需要通知后台 API 的事件时(例如,状态变化,虽然代码示例中没有明确的工具发送此类消息,但 `Application::SendMcpMessage` 的存在暗示了设备可能主动发送 MCP 消息)。 - - **发送方:** 设备 (服务器)。 - - **方法:** 可能是以 `notifications/` 开头的方法名,或者其他自定义方法。 - - **消息 (MCP payload):** 遵循 JSON-RPC Notification 格式,没有 `id` 字段。 - ```json - { - "jsonrpc": "2.0", - "method": "notifications/state_changed", // 示例方法名 - "params": { - "newState": "idle", - "oldState": "connecting" - } - // 没有 id 字段 - } - ``` - - **后台 API 处理:** 接收到 Notification 后,后台 API 进行相应的处理,但不回复。 - -## 交互图 - -下面是一个简化的交互序列图,展示了主要的 MCP 消息流程: - -```mermaid -sequenceDiagram - participant Device as ESP32 Device - participant BackendAPI as 后台 API (Client) - - Note over Device, BackendAPI: 建立 WebSocket / MQTT 连接 - - Device->>BackendAPI: Hello Message (包含 "mcp": true) - - BackendAPI->>Device: MCP Initialize Request - Note over BackendAPI: method: initialize - Note over BackendAPI: params: { capabilities: ... } - - Device->>BackendAPI: MCP Initialize Response - Note over Device: result: { protocolVersion: ..., serverInfo: ... } - - BackendAPI->>Device: MCP Get Tools List Request - Note over BackendAPI: method: tools/list - Note over BackendAPI: params: { cursor: "" } - - Device->>BackendAPI: MCP Get Tools List Response - Note over Device: result: { tools: [...], nextCursor: ... } - - loop Optional Pagination - BackendAPI->>Device: MCP Get Tools List Request - Note over BackendAPI: method: tools/list - Note over BackendAPI: params: { cursor: "..." } - Device->>BackendAPI: MCP Get Tools List Response - Note over Device: result: { tools: [...], nextCursor: "" } - end - - BackendAPI->>Device: MCP Call Tool Request - Note over BackendAPI: method: tools/call - Note over BackendAPI: params: { name: "...", arguments: { ... } } - - alt Tool Call Successful - Device->>BackendAPI: MCP Tool Call Success Response - Note over Device: result: { content: [...], isError: false } - else Tool Call Failed - Device->>BackendAPI: MCP Tool Call Error Response - Note over Device: error: { code: ..., message: ... } - end - - opt Device Notification - Device->>BackendAPI: MCP Notification - Note over Device: method: notifications/... - Note over Device: params: { ... } - end -``` - -这份文档概述了该项目中 MCP 协议的主要交互流程。具体的参数细节和工具功能需要参考 `main/mcp_server.cc` 中 `McpServer::AddCommonTools` 以及各个工具的实现。 +# MCP (Model Context Protocol) 交互流程 + +NOTICE: AI 辅助生成, 在实现后台服务时, 请参照代码确认细节!! + +本项目中的 MCP 协议用于后台 API(MCP 客户端)与 ESP32 设备(MCP 服务器)之间的通信,以便后台能够发现和调用设备提供的功能(工具)。 + +## 协议格式 + +根据代码 (`main/protocols/protocol.cc`, `main/mcp_server.cc`),MCP 消息是封装在基础通信协议(如 WebSocket 或 MQTT)的消息体中的。其内部结构遵循 [JSON-RPC 2.0](https://www.jsonrpc.org/specification) 规范。 + +整体消息结构示例: + +```json +{ + "session_id": "...", // 会话 ID + "type": "mcp", // 消息类型,固定为 "mcp" + "payload": { // JSON-RPC 2.0 负载 + "jsonrpc": "2.0", + "method": "...", // 方法名 (如 "initialize", "tools/list", "tools/call") + "params": { ... }, // 方法参数 (对于 request) + "id": ..., // 请求 ID (对于 request 和 response) + "result": { ... }, // 方法执行结果 (对于 success response) + "error": { ... } // 错误信息 (对于 error response) + } +} +``` + +其中,`payload` 部分是标准的 JSON-RPC 2.0 消息: + +- `jsonrpc`: 固定的字符串 "2.0"。 +- `method`: 要调用的方法名称 (对于 Request)。 +- `params`: 方法的参数,一个结构化值,通常为对象 (对于 Request)。 +- `id`: 请求的标识符,客户端发送请求时提供,服务器响应时原样返回。用于匹配请求和响应。 +- `result`: 方法成功执行时的结果 (对于 Success Response)。 +- `error`: 方法执行失败时的错误信息 (对于 Error Response)。 + +## 交互流程及发送时机 + +MCP 的交互主要围绕客户端(后台 API)发现和调用设备上的“工具”(Tool)进行。 + +1. **连接建立与能力通告** + + - **时机:** 设备启动并成功连接到后台 API 后。 + - **发送方:** 设备。 + - **消息:** 设备发送基础协议的 "hello" 消息给后台 API,消息中包含设备支持的能力列表,例如通过支持 MCP 协议 (`"mcp": true`)。 + - **示例 (非 MCP 负载,而是基础协议消息):** + ```json + { + "type": "hello", + "version": ..., + "features": { + "mcp": true, + ... + }, + "transport": "websocket", // 或 "mqtt" + "audio_params": { ... }, + "session_id": "..." // 设备收到服务器hello后可能设置 + } + ``` + +2. **初始化 MCP 会话** + + - **时机:** 后台 API 收到设备 "hello" 消息,确认设备支持 MCP 后,通常作为 MCP 会话的第一个请求发送。 + - **发送方:** 后台 API (客户端)。 + - **方法:** `initialize` + - **消息 (MCP payload):** + + ```json + { + "jsonrpc": "2.0", + "method": "initialize", + "params": { + "capabilities": { + // 客户端能力,可选 + + // 摄像头视觉相关 + "vision": { + "url": "...", //摄像头: 图片处理地址(必须是http地址, 不是websocket地址) + "token": "..." // url token + } + + // ... 其他客户端能力 + } + }, + "id": 1 // 请求 ID + } + ``` + + - **设备响应时机:** 设备收到 `initialize` 请求并处理后。 + - **设备响应消息 (MCP payload):** + ```json + { + "jsonrpc": "2.0", + "id": 1, // 匹配请求 ID + "result": { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {} // 这里的 tools 似乎不列出详细信息,需要 tools/list + }, + "serverInfo": { + "name": "...", // 设备名称 (BOARD_NAME) + "version": "..." // 设备固件版本 + } + } + } + ``` + +3. **发现设备工具列表** + + - **时机:** 后台 API 需要获取设备当前支持的具体功能(工具)列表及其调用方式时。 + - **发送方:** 后台 API (客户端)。 + - **方法:** `tools/list` + - **消息 (MCP payload):** + ```json + { + "jsonrpc": "2.0", + "method": "tools/list", + "params": { + "cursor": "" // 用于分页,首次请求为空字符串 + }, + "id": 2 // 请求 ID + } + ``` + - **设备响应时机:** 设备收到 `tools/list` 请求并生成工具列表后。 + - **设备响应消息 (MCP payload):** + ```json + { + "jsonrpc": "2.0", + "id": 2, // 匹配请求 ID + "result": { + "tools": [ // 工具对象列表 + { + "name": "self.get_device_status", + "description": "...", + "inputSchema": { ... } // 参数 schema + }, + { + "name": "self.audio_speaker.set_volume", + "description": "...", + "inputSchema": { ... } // 参数 schema + } + // ... 更多工具 + ], + "nextCursor": "..." // 如果列表很大需要分页,这里会包含下一个请求的 cursor 值 + } + } + ``` + - **分页处理:** 如果 `nextCursor` 字段非空,客户端需要再次发送 `tools/list` 请求,并在 `params` 中带上这个 `cursor` 值以获取下一页工具。 + +4. **调用设备工具** + + - **时机:** 后台 API 需要执行设备上的某个具体功能时。 + - **发送方:** 后台 API (客户端)。 + - **方法:** `tools/call` + - **消息 (MCP payload):** + ```json + { + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "self.audio_speaker.set_volume", // 要调用的工具名称 + "arguments": { + // 工具参数,对象格式 + "volume": 50 // 参数名及其值 + } + }, + "id": 3 // 请求 ID + } + ``` + - **设备响应时机:** 设备收到 `tools/call` 请求,执行相应的工具函数后。 + - **设备成功响应消息 (MCP payload):** + ```json + { + "jsonrpc": "2.0", + "id": 3, // 匹配请求 ID + "result": { + "content": [ + // 工具执行结果内容 + { "type": "text", "text": "true" } // 示例:set_volume 返回 bool + ], + "isError": false // 表示成功 + } + } + ``` + - **设备失败响应消息 (MCP payload):** + ```json + { + "jsonrpc": "2.0", + "id": 3, // 匹配请求 ID + "error": { + "code": -32601, // JSON-RPC 错误码,例如 Method not found (-32601) + "message": "Unknown tool: self.non_existent_tool" // 错误描述 + } + } + ``` + +5. **设备主动发送消息 (Notifications)** + - **时机:** 设备内部发生需要通知后台 API 的事件时(例如,状态变化,虽然代码示例中没有明确的工具发送此类消息,但 `Application::SendMcpMessage` 的存在暗示了设备可能主动发送 MCP 消息)。 + - **发送方:** 设备 (服务器)。 + - **方法:** 可能是以 `notifications/` 开头的方法名,或者其他自定义方法。 + - **消息 (MCP payload):** 遵循 JSON-RPC Notification 格式,没有 `id` 字段。 + ```json + { + "jsonrpc": "2.0", + "method": "notifications/state_changed", // 示例方法名 + "params": { + "newState": "idle", + "oldState": "connecting" + } + // 没有 id 字段 + } + ``` + - **后台 API 处理:** 接收到 Notification 后,后台 API 进行相应的处理,但不回复。 + +## 交互图 + +下面是一个简化的交互序列图,展示了主要的 MCP 消息流程: + +```mermaid +sequenceDiagram + participant Device as ESP32 Device + participant BackendAPI as 后台 API (Client) + + Note over Device, BackendAPI: 建立 WebSocket / MQTT 连接 + + Device->>BackendAPI: Hello Message (包含 "mcp": true) + + BackendAPI->>Device: MCP Initialize Request + Note over BackendAPI: method: initialize + Note over BackendAPI: params: { capabilities: ... } + + Device->>BackendAPI: MCP Initialize Response + Note over Device: result: { protocolVersion: ..., serverInfo: ... } + + BackendAPI->>Device: MCP Get Tools List Request + Note over BackendAPI: method: tools/list + Note over BackendAPI: params: { cursor: "" } + + Device->>BackendAPI: MCP Get Tools List Response + Note over Device: result: { tools: [...], nextCursor: ... } + + loop Optional Pagination + BackendAPI->>Device: MCP Get Tools List Request + Note over BackendAPI: method: tools/list + Note over BackendAPI: params: { cursor: "..." } + Device->>BackendAPI: MCP Get Tools List Response + Note over Device: result: { tools: [...], nextCursor: "" } + end + + BackendAPI->>Device: MCP Call Tool Request + Note over BackendAPI: method: tools/call + Note over BackendAPI: params: { name: "...", arguments: { ... } } + + alt Tool Call Successful + Device->>BackendAPI: MCP Tool Call Success Response + Note over Device: result: { content: [...], isError: false } + else Tool Call Failed + Device->>BackendAPI: MCP Tool Call Error Response + Note over Device: error: { code: ..., message: ... } + end + + opt Device Notification + Device->>BackendAPI: MCP Notification + Note over Device: method: notifications/... + Note over Device: params: { ... } + end +``` + +这份文档概述了该项目中 MCP 协议的主要交互流程。具体的参数细节和工具功能需要参考 `main/mcp_server.cc` 中 `McpServer::AddCommonTools` 以及各个工具的实现。 diff --git a/docs/mcp-usage.md b/docs/mcp-usage.md index fa50a39..e79026f 100644 --- a/docs/mcp-usage.md +++ b/docs/mcp-usage.md @@ -1,115 +1,115 @@ -# MCP 协议物联网控制用法说明 - -> 本文档介绍如何基于 MCP 协议实现 ESP32 设备的物联网控制。详细协议流程请参考 [`mcp-protocol.md`](./mcp-protocol.md)。 - -## 简介 - -MCP(Model Context Protocol)是新一代推荐用于物联网控制的协议,通过标准 JSON-RPC 2.0 格式在后台与设备间发现和调用"工具"(Tool),实现灵活的设备控制。 - -## 典型使用流程 - -1. 设备启动后通过基础协议(如 WebSocket/MQTT)与后台建立连接。 -2. 后台通过 MCP 协议的 `initialize` 方法初始化会话。 -3. 后台通过 `tools/list` 获取设备支持的所有工具(功能)及参数说明。 -4. 后台通过 `tools/call` 调用具体工具,实现对设备的控制。 - -详细协议格式与交互请见 [`mcp-protocol.md`](./mcp-protocol.md)。 - -## 设备端工具注册方法说明 - -设备通过 `McpServer::AddTool` 方法注册可被后台调用的"工具"。其常用函数签名如下: - -```cpp -void AddTool( - const std::string& name, // 工具名称,建议唯一且有层次感,如 self.dog.forward - const std::string& description, // 工具描述,简明说明功能,便于大模型理解 - const PropertyList& properties, // 输入参数列表(可为空),支持类型:布尔、整数、字符串 - std::function callback // 工具被调用时的回调实现 -); -``` -- name:工具唯一标识,建议用"模块.功能"命名风格。 -- description:自然语言描述,便于 AI/用户理解。 -- properties:参数列表,支持类型有布尔、整数、字符串,可指定范围和默认值。 -- callback:收到调用请求时的实际执行逻辑,返回值可为 bool/int/string。 - -## 典型注册示例(以 ESP-Hi 为例) - -```cpp -void InitializeTools() { - auto& mcp_server = McpServer::GetInstance(); - // 例1:无参数,控制机器人前进 - mcp_server.AddTool("self.dog.forward", "机器人向前移动", PropertyList(), [this](const PropertyList&) -> ReturnValue { - servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL); - return true; - }); - // 例2:带参数,设置灯光 RGB 颜色 - mcp_server.AddTool("self.light.set_rgb", "设置RGB颜色", PropertyList({ - Property("r", kPropertyTypeInteger, 0, 255), - Property("g", kPropertyTypeInteger, 0, 255), - Property("b", kPropertyTypeInteger, 0, 255) - }), [this](const PropertyList& properties) -> ReturnValue { - int r = properties["r"].value(); - int g = properties["g"].value(); - int b = properties["b"].value(); - led_on_ = true; - SetLedColor(r, g, b); - return true; - }); -} -``` - -## 常见工具调用 JSON-RPC 示例 - -### 1. 获取工具列表 -```json -{ - "jsonrpc": "2.0", - "method": "tools/list", - "params": { "cursor": "" }, - "id": 1 -} -``` - -### 2. 控制底盘前进 -```json -{ - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "self.chassis.go_forward", - "arguments": {} - }, - "id": 2 -} -``` - -### 3. 切换灯光模式 -```json -{ - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "self.chassis.switch_light_mode", - "arguments": { "light_mode": 3 } - }, - "id": 3 -} -``` - -### 4. 摄像头翻转 -```json -{ - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "self.camera.set_camera_flipped", - "arguments": {} - }, - "id": 4 -} -``` - -## 备注 -- 工具名称、参数及返回值请以设备端 `AddTool` 注册为准。 -- 推荐所有新项目统一采用 MCP 协议进行物联网控制。 +# MCP 协议物联网控制用法说明 + +> 本文档介绍如何基于 MCP 协议实现 ESP32 设备的物联网控制。详细协议流程请参考 [`mcp-protocol.md`](./mcp-protocol.md)。 + +## 简介 + +MCP(Model Context Protocol)是新一代推荐用于物联网控制的协议,通过标准 JSON-RPC 2.0 格式在后台与设备间发现和调用"工具"(Tool),实现灵活的设备控制。 + +## 典型使用流程 + +1. 设备启动后通过基础协议(如 WebSocket/MQTT)与后台建立连接。 +2. 后台通过 MCP 协议的 `initialize` 方法初始化会话。 +3. 后台通过 `tools/list` 获取设备支持的所有工具(功能)及参数说明。 +4. 后台通过 `tools/call` 调用具体工具,实现对设备的控制。 + +详细协议格式与交互请见 [`mcp-protocol.md`](./mcp-protocol.md)。 + +## 设备端工具注册方法说明 + +设备通过 `McpServer::AddTool` 方法注册可被后台调用的"工具"。其常用函数签名如下: + +```cpp +void AddTool( + const std::string& name, // 工具名称,建议唯一且有层次感,如 self.dog.forward + const std::string& description, // 工具描述,简明说明功能,便于大模型理解 + const PropertyList& properties, // 输入参数列表(可为空),支持类型:布尔、整数、字符串 + std::function callback // 工具被调用时的回调实现 +); +``` +- name:工具唯一标识,建议用"模块.功能"命名风格。 +- description:自然语言描述,便于 AI/用户理解。 +- properties:参数列表,支持类型有布尔、整数、字符串,可指定范围和默认值。 +- callback:收到调用请求时的实际执行逻辑,返回值可为 bool/int/string。 + +## 典型注册示例(以 ESP-Hi 为例) + +```cpp +void InitializeTools() { + auto& mcp_server = McpServer::GetInstance(); + // 例1:无参数,控制机器人前进 + mcp_server.AddTool("self.dog.forward", "机器人向前移动", PropertyList(), [this](const PropertyList&) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL); + return true; + }); + // 例2:带参数,设置灯光 RGB 颜色 + mcp_server.AddTool("self.light.set_rgb", "设置RGB颜色", PropertyList({ + Property("r", kPropertyTypeInteger, 0, 255), + Property("g", kPropertyTypeInteger, 0, 255), + Property("b", kPropertyTypeInteger, 0, 255) + }), [this](const PropertyList& properties) -> ReturnValue { + int r = properties["r"].value(); + int g = properties["g"].value(); + int b = properties["b"].value(); + led_on_ = true; + SetLedColor(r, g, b); + return true; + }); +} +``` + +## 常见工具调用 JSON-RPC 示例 + +### 1. 获取工具列表 +```json +{ + "jsonrpc": "2.0", + "method": "tools/list", + "params": { "cursor": "" }, + "id": 1 +} +``` + +### 2. 控制底盘前进 +```json +{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "self.chassis.go_forward", + "arguments": {} + }, + "id": 2 +} +``` + +### 3. 切换灯光模式 +```json +{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "self.chassis.switch_light_mode", + "arguments": { "light_mode": 3 } + }, + "id": 3 +} +``` + +### 4. 摄像头翻转 +```json +{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "self.camera.set_camera_flipped", + "arguments": {} + }, + "id": 4 +} +``` + +## 备注 +- 工具名称、参数及返回值请以设备端 `AddTool` 注册为准。 +- 推荐所有新项目统一采用 MCP 协议进行物联网控制。 - 详细协议与进阶用法请查阅 [`mcp-protocol.md`](./mcp-protocol.md)。 \ No newline at end of file diff --git a/docs/mqtt-udp.md b/docs/mqtt-udp.md index 478e466..ed04fa6 100644 --- a/docs/mqtt-udp.md +++ b/docs/mqtt-udp.md @@ -1,393 +1,393 @@ -# MQTT + UDP 混合通信协议文档 - -基于代码实现整理的 MQTT + UDP 混合通信协议文档,概述设备端与服务器之间如何通过 MQTT 进行控制消息传输,通过 UDP 进行音频数据传输的交互方式。 - ---- - -## 1. 协议概览 - -本协议采用混合传输方式: -- **MQTT**:用于控制消息、状态同步、JSON 数据交换 -- **UDP**:用于实时音频数据传输,支持加密 - -### 1.1 协议特点 - -- **双通道设计**:控制与数据分离,确保实时性 -- **加密传输**:UDP 音频数据使用 AES-CTR 加密 -- **序列号保护**:防止数据包重放和乱序 -- **自动重连**:MQTT 连接断开时自动重连 - ---- - -## 2. 总体流程概览 - -```mermaid -sequenceDiagram - participant Device as ESP32 设备 - participant MQTT as MQTT 服务器 - participant UDP as UDP 服务器 - - Note over Device, UDP: 1. 建立 MQTT 连接 - Device->>MQTT: MQTT Connect - MQTT->>Device: Connected - - Note over Device, UDP: 2. 请求音频通道 - Device->>MQTT: Hello Message (type: "hello", transport: "udp") - MQTT->>Device: Hello Response (UDP 连接信息 + 加密密钥) - - Note over Device, UDP: 3. 建立 UDP 连接 - Device->>UDP: UDP Connect - UDP->>Device: Connected - - Note over Device, UDP: 4. 音频数据传输 - loop 音频流传输 - Device->>UDP: 加密音频数据 (Opus) - UDP->>Device: 加密音频数据 (Opus) - end - - Note over Device, UDP: 5. 控制消息交换 - par 控制消息 - Device->>MQTT: Listen/TTS/MCP 消息 - MQTT->>Device: STT/TTS/MCP 响应 - end - - Note over Device, UDP: 6. 关闭连接 - Device->>MQTT: Goodbye Message - Device->>UDP: Disconnect -``` - ---- - -## 3. MQTT 控制通道 - -### 3.1 连接建立 - -设备通过 MQTT 连接到服务器,连接参数包括: -- **Endpoint**:MQTT 服务器地址和端口 -- **Client ID**:设备唯一标识符 -- **Username/Password**:认证凭据 -- **Keep Alive**:心跳间隔(默认240秒) - -### 3.2 Hello 消息交换 - -#### 3.2.1 设备端发送 Hello - -```json -{ - "type": "hello", - "version": 3, - "transport": "udp", - "features": { - "mcp": true - }, - "audio_params": { - "format": "opus", - "sample_rate": 16000, - "channels": 1, - "frame_duration": 60 - } -} -``` - -#### 3.2.2 服务器响应 Hello - -```json -{ - "type": "hello", - "transport": "udp", - "session_id": "xxx", - "audio_params": { - "format": "opus", - "sample_rate": 24000, - "channels": 1, - "frame_duration": 60 - }, - "udp": { - "server": "192.168.1.100", - "port": 8888, - "key": "0123456789ABCDEF0123456789ABCDEF", - "nonce": "0123456789ABCDEF0123456789ABCDEF" - } -} -``` - -**字段说明:** -- `udp.server`:UDP 服务器地址 -- `udp.port`:UDP 服务器端口 -- `udp.key`:AES 加密密钥(十六进制字符串) -- `udp.nonce`:AES 加密随机数(十六进制字符串) - -### 3.3 JSON 消息类型 - -#### 3.3.1 设备端→服务器 - -1. **Listen 消息** - ```json - { - "session_id": "xxx", - "type": "listen", - "state": "start", - "mode": "manual" - } - ``` - -2. **Abort 消息** - ```json - { - "session_id": "xxx", - "type": "abort", - "reason": "wake_word_detected" - } - ``` - -3. **MCP 消息** - ```json - { - "session_id": "xxx", - "type": "mcp", - "payload": { - "jsonrpc": "2.0", - "id": 1, - "result": {...} - } - } - ``` - -4. **Goodbye 消息** - ```json - { - "session_id": "xxx", - "type": "goodbye" - } - ``` - -#### 3.3.2 服务器→设备端 - -支持的消息类型与 WebSocket 协议一致,包括: -- **STT**:语音识别结果 -- **TTS**:语音合成控制 -- **LLM**:情感表达控制 -- **MCP**:物联网控制 -- **System**:系统控制 -- **Custom**:自定义消息(可选) - ---- - -## 4. UDP 音频通道 - -### 4.1 连接建立 - -设备收到 MQTT Hello 响应后,使用其中的 UDP 连接信息建立音频通道: -1. 解析 UDP 服务器地址和端口 -2. 解析加密密钥和随机数 -3. 初始化 AES-CTR 加密上下文 -4. 建立 UDP 连接 - -### 4.2 音频数据格式 - -#### 4.2.1 加密音频包结构 - -``` -|type 1byte|flags 1byte|payload_len 2bytes|ssrc 4bytes|timestamp 4bytes|sequence 4bytes| -|payload payload_len bytes| -``` - -**字段说明:** -- `type`:数据包类型,固定为 0x01 -- `flags`:标志位,当前未使用 -- `payload_len`:负载长度(网络字节序) -- `ssrc`:同步源标识符 -- `timestamp`:时间戳(网络字节序) -- `sequence`:序列号(网络字节序) -- `payload`:加密的 Opus 音频数据 - -#### 4.2.2 加密算法 - -使用 **AES-CTR** 模式加密: -- **密钥**:128位,由服务器提供 -- **随机数**:128位,由服务器提供 -- **计数器**:包含时间戳和序列号信息 - -### 4.3 序列号管理 - -- **发送端**:`local_sequence_` 单调递增 -- **接收端**:`remote_sequence_` 验证连续性 -- **防重放**:拒绝序列号小于期望值的数据包 -- **容错处理**:允许轻微的序列号跳跃,记录警告 - -### 4.4 错误处理 - -1. **解密失败**:记录错误,丢弃数据包 -2. **序列号异常**:记录警告,但仍处理数据包 -3. **数据包格式错误**:记录错误,丢弃数据包 - ---- - -## 5. 状态管理 - -### 5.1 连接状态 - -```mermaid -stateDiagram - direction TB - [*] --> Disconnected - Disconnected --> MqttConnecting: StartMqttClient() - MqttConnecting --> MqttConnected: MQTT Connected - MqttConnecting --> Disconnected: Connect Failed - MqttConnected --> RequestingChannel: OpenAudioChannel() - RequestingChannel --> ChannelOpened: Hello Exchange Success - RequestingChannel --> MqttConnected: Hello Timeout/Failed - ChannelOpened --> UdpConnected: UDP Connect Success - UdpConnected --> AudioStreaming: Start Audio Transfer - AudioStreaming --> UdpConnected: Stop Audio Transfer - UdpConnected --> ChannelOpened: UDP Disconnect - ChannelOpened --> MqttConnected: CloseAudioChannel() - MqttConnected --> Disconnected: MQTT Disconnect -``` - -### 5.2 状态检查 - -设备通过以下条件判断音频通道是否可用: -```cpp -bool IsAudioChannelOpened() const { - return udp_ != nullptr && !error_occurred_ && !IsTimeout(); -} -``` - ---- - -## 6. 配置参数 - -### 6.1 MQTT 配置 - -从设置中读取的配置项: -- `endpoint`:MQTT 服务器地址 -- `client_id`:客户端标识符 -- `username`:用户名 -- `password`:密码 -- `keepalive`:心跳间隔(默认240秒) -- `publish_topic`:发布主题 - -### 6.2 音频参数 - -- **格式**:Opus -- **采样率**:16000 Hz(设备端)/ 24000 Hz(服务器端) -- **声道数**:1(单声道) -- **帧时长**:60ms - ---- - -## 7. 错误处理与重连 - -### 7.1 MQTT 重连机制 - -- 连接失败时自动重试 -- 支持错误上报控制 -- 断线时触发清理流程 - -### 7.2 UDP 连接管理 - -- 连接失败时不自动重试 -- 依赖 MQTT 通道重新协商 -- 支持连接状态查询 - -### 7.3 超时处理 - -基类 `Protocol` 提供超时检测: -- 默认超时时间:120 秒 -- 基于最后接收时间计算 -- 超时时自动标记为不可用 - ---- - -## 8. 安全考虑 - -### 8.1 传输加密 - -- **MQTT**:支持 TLS/SSL 加密(端口8883) -- **UDP**:使用 AES-CTR 加密音频数据 - -### 8.2 认证机制 - -- **MQTT**:用户名/密码认证 -- **UDP**:通过 MQTT 通道分发密钥 - -### 8.3 防重放攻击 - -- 序列号单调递增 -- 拒绝过期数据包 -- 时间戳验证 - ---- - -## 9. 性能优化 - -### 9.1 并发控制 - -使用互斥锁保护 UDP 连接: -```cpp -std::lock_guard lock(channel_mutex_); -``` - -### 9.2 内存管理 - -- 动态创建/销毁网络对象 -- 智能指针管理音频数据包 -- 及时释放加密上下文 - -### 9.3 网络优化 - -- UDP 连接复用 -- 数据包大小优化 -- 序列号连续性检查 - ---- - -## 10. 与 WebSocket 协议的比较 - -| 特性 | MQTT + UDP | WebSocket | -|------|------------|-----------| -| 控制通道 | MQTT | WebSocket | -| 音频通道 | UDP (加密) | WebSocket (二进制) | -| 实时性 | 高 (UDP) | 中等 | -| 可靠性 | 中等 | 高 | -| 复杂度 | 高 | 低 | -| 加密 | AES-CTR | TLS | -| 防火墙友好度 | 低 | 高 | - ---- - -## 11. 部署建议 - -### 11.1 网络环境 - -- 确保 UDP 端口可达 -- 配置防火墙规则 -- 考虑 NAT 穿透 - -### 11.2 服务器配置 - -- MQTT Broker 配置 -- UDP 服务器部署 -- 密钥管理系统 - -### 11.3 监控指标 - -- 连接成功率 -- 音频传输延迟 -- 数据包丢失率 -- 解密失败率 - ---- - -## 12. 总结 - -MQTT + UDP 混合协议通过以下设计实现高效的音视频通信: - -- **分离式架构**:控制与数据通道分离,各司其职 -- **加密保护**:AES-CTR 确保音频数据安全传输 -- **序列化管理**:防止重放攻击和数据乱序 -- **自动恢复**:支持连接断开后的自动重连 -- **性能优化**:UDP 传输保证音频数据的实时性 - +# MQTT + UDP 混合通信协议文档 + +基于代码实现整理的 MQTT + UDP 混合通信协议文档,概述设备端与服务器之间如何通过 MQTT 进行控制消息传输,通过 UDP 进行音频数据传输的交互方式。 + +--- + +## 1. 协议概览 + +本协议采用混合传输方式: +- **MQTT**:用于控制消息、状态同步、JSON 数据交换 +- **UDP**:用于实时音频数据传输,支持加密 + +### 1.1 协议特点 + +- **双通道设计**:控制与数据分离,确保实时性 +- **加密传输**:UDP 音频数据使用 AES-CTR 加密 +- **序列号保护**:防止数据包重放和乱序 +- **自动重连**:MQTT 连接断开时自动重连 + +--- + +## 2. 总体流程概览 + +```mermaid +sequenceDiagram + participant Device as ESP32 设备 + participant MQTT as MQTT 服务器 + participant UDP as UDP 服务器 + + Note over Device, UDP: 1. 建立 MQTT 连接 + Device->>MQTT: MQTT Connect + MQTT->>Device: Connected + + Note over Device, UDP: 2. 请求音频通道 + Device->>MQTT: Hello Message (type: "hello", transport: "udp") + MQTT->>Device: Hello Response (UDP 连接信息 + 加密密钥) + + Note over Device, UDP: 3. 建立 UDP 连接 + Device->>UDP: UDP Connect + UDP->>Device: Connected + + Note over Device, UDP: 4. 音频数据传输 + loop 音频流传输 + Device->>UDP: 加密音频数据 (Opus) + UDP->>Device: 加密音频数据 (Opus) + end + + Note over Device, UDP: 5. 控制消息交换 + par 控制消息 + Device->>MQTT: Listen/TTS/MCP 消息 + MQTT->>Device: STT/TTS/MCP 响应 + end + + Note over Device, UDP: 6. 关闭连接 + Device->>MQTT: Goodbye Message + Device->>UDP: Disconnect +``` + +--- + +## 3. MQTT 控制通道 + +### 3.1 连接建立 + +设备通过 MQTT 连接到服务器,连接参数包括: +- **Endpoint**:MQTT 服务器地址和端口 +- **Client ID**:设备唯一标识符 +- **Username/Password**:认证凭据 +- **Keep Alive**:心跳间隔(默认240秒) + +### 3.2 Hello 消息交换 + +#### 3.2.1 设备端发送 Hello + +```json +{ + "type": "hello", + "version": 3, + "transport": "udp", + "features": { + "mcp": true + }, + "audio_params": { + "format": "opus", + "sample_rate": 16000, + "channels": 1, + "frame_duration": 60 + } +} +``` + +#### 3.2.2 服务器响应 Hello + +```json +{ + "type": "hello", + "transport": "udp", + "session_id": "xxx", + "audio_params": { + "format": "opus", + "sample_rate": 24000, + "channels": 1, + "frame_duration": 60 + }, + "udp": { + "server": "192.168.1.100", + "port": 8888, + "key": "0123456789ABCDEF0123456789ABCDEF", + "nonce": "0123456789ABCDEF0123456789ABCDEF" + } +} +``` + +**字段说明:** +- `udp.server`:UDP 服务器地址 +- `udp.port`:UDP 服务器端口 +- `udp.key`:AES 加密密钥(十六进制字符串) +- `udp.nonce`:AES 加密随机数(十六进制字符串) + +### 3.3 JSON 消息类型 + +#### 3.3.1 设备端→服务器 + +1. **Listen 消息** + ```json + { + "session_id": "xxx", + "type": "listen", + "state": "start", + "mode": "manual" + } + ``` + +2. **Abort 消息** + ```json + { + "session_id": "xxx", + "type": "abort", + "reason": "wake_word_detected" + } + ``` + +3. **MCP 消息** + ```json + { + "session_id": "xxx", + "type": "mcp", + "payload": { + "jsonrpc": "2.0", + "id": 1, + "result": {...} + } + } + ``` + +4. **Goodbye 消息** + ```json + { + "session_id": "xxx", + "type": "goodbye" + } + ``` + +#### 3.3.2 服务器→设备端 + +支持的消息类型与 WebSocket 协议一致,包括: +- **STT**:语音识别结果 +- **TTS**:语音合成控制 +- **LLM**:情感表达控制 +- **MCP**:物联网控制 +- **System**:系统控制 +- **Custom**:自定义消息(可选) + +--- + +## 4. UDP 音频通道 + +### 4.1 连接建立 + +设备收到 MQTT Hello 响应后,使用其中的 UDP 连接信息建立音频通道: +1. 解析 UDP 服务器地址和端口 +2. 解析加密密钥和随机数 +3. 初始化 AES-CTR 加密上下文 +4. 建立 UDP 连接 + +### 4.2 音频数据格式 + +#### 4.2.1 加密音频包结构 + +``` +|type 1byte|flags 1byte|payload_len 2bytes|ssrc 4bytes|timestamp 4bytes|sequence 4bytes| +|payload payload_len bytes| +``` + +**字段说明:** +- `type`:数据包类型,固定为 0x01 +- `flags`:标志位,当前未使用 +- `payload_len`:负载长度(网络字节序) +- `ssrc`:同步源标识符 +- `timestamp`:时间戳(网络字节序) +- `sequence`:序列号(网络字节序) +- `payload`:加密的 Opus 音频数据 + +#### 4.2.2 加密算法 + +使用 **AES-CTR** 模式加密: +- **密钥**:128位,由服务器提供 +- **随机数**:128位,由服务器提供 +- **计数器**:包含时间戳和序列号信息 + +### 4.3 序列号管理 + +- **发送端**:`local_sequence_` 单调递增 +- **接收端**:`remote_sequence_` 验证连续性 +- **防重放**:拒绝序列号小于期望值的数据包 +- **容错处理**:允许轻微的序列号跳跃,记录警告 + +### 4.4 错误处理 + +1. **解密失败**:记录错误,丢弃数据包 +2. **序列号异常**:记录警告,但仍处理数据包 +3. **数据包格式错误**:记录错误,丢弃数据包 + +--- + +## 5. 状态管理 + +### 5.1 连接状态 + +```mermaid +stateDiagram + direction TB + [*] --> Disconnected + Disconnected --> MqttConnecting: StartMqttClient() + MqttConnecting --> MqttConnected: MQTT Connected + MqttConnecting --> Disconnected: Connect Failed + MqttConnected --> RequestingChannel: OpenAudioChannel() + RequestingChannel --> ChannelOpened: Hello Exchange Success + RequestingChannel --> MqttConnected: Hello Timeout/Failed + ChannelOpened --> UdpConnected: UDP Connect Success + UdpConnected --> AudioStreaming: Start Audio Transfer + AudioStreaming --> UdpConnected: Stop Audio Transfer + UdpConnected --> ChannelOpened: UDP Disconnect + ChannelOpened --> MqttConnected: CloseAudioChannel() + MqttConnected --> Disconnected: MQTT Disconnect +``` + +### 5.2 状态检查 + +设备通过以下条件判断音频通道是否可用: +```cpp +bool IsAudioChannelOpened() const { + return udp_ != nullptr && !error_occurred_ && !IsTimeout(); +} +``` + +--- + +## 6. 配置参数 + +### 6.1 MQTT 配置 + +从设置中读取的配置项: +- `endpoint`:MQTT 服务器地址 +- `client_id`:客户端标识符 +- `username`:用户名 +- `password`:密码 +- `keepalive`:心跳间隔(默认240秒) +- `publish_topic`:发布主题 + +### 6.2 音频参数 + +- **格式**:Opus +- **采样率**:16000 Hz(设备端)/ 24000 Hz(服务器端) +- **声道数**:1(单声道) +- **帧时长**:60ms + +--- + +## 7. 错误处理与重连 + +### 7.1 MQTT 重连机制 + +- 连接失败时自动重试 +- 支持错误上报控制 +- 断线时触发清理流程 + +### 7.2 UDP 连接管理 + +- 连接失败时不自动重试 +- 依赖 MQTT 通道重新协商 +- 支持连接状态查询 + +### 7.3 超时处理 + +基类 `Protocol` 提供超时检测: +- 默认超时时间:120 秒 +- 基于最后接收时间计算 +- 超时时自动标记为不可用 + +--- + +## 8. 安全考虑 + +### 8.1 传输加密 + +- **MQTT**:支持 TLS/SSL 加密(端口8883) +- **UDP**:使用 AES-CTR 加密音频数据 + +### 8.2 认证机制 + +- **MQTT**:用户名/密码认证 +- **UDP**:通过 MQTT 通道分发密钥 + +### 8.3 防重放攻击 + +- 序列号单调递增 +- 拒绝过期数据包 +- 时间戳验证 + +--- + +## 9. 性能优化 + +### 9.1 并发控制 + +使用互斥锁保护 UDP 连接: +```cpp +std::lock_guard lock(channel_mutex_); +``` + +### 9.2 内存管理 + +- 动态创建/销毁网络对象 +- 智能指针管理音频数据包 +- 及时释放加密上下文 + +### 9.3 网络优化 + +- UDP 连接复用 +- 数据包大小优化 +- 序列号连续性检查 + +--- + +## 10. 与 WebSocket 协议的比较 + +| 特性 | MQTT + UDP | WebSocket | +|------|------------|-----------| +| 控制通道 | MQTT | WebSocket | +| 音频通道 | UDP (加密) | WebSocket (二进制) | +| 实时性 | 高 (UDP) | 中等 | +| 可靠性 | 中等 | 高 | +| 复杂度 | 高 | 低 | +| 加密 | AES-CTR | TLS | +| 防火墙友好度 | 低 | 高 | + +--- + +## 11. 部署建议 + +### 11.1 网络环境 + +- 确保 UDP 端口可达 +- 配置防火墙规则 +- 考虑 NAT 穿透 + +### 11.2 服务器配置 + +- MQTT Broker 配置 +- UDP 服务器部署 +- 密钥管理系统 + +### 11.3 监控指标 + +- 连接成功率 +- 音频传输延迟 +- 数据包丢失率 +- 解密失败率 + +--- + +## 12. 总结 + +MQTT + UDP 混合协议通过以下设计实现高效的音视频通信: + +- **分离式架构**:控制与数据通道分离,各司其职 +- **加密保护**:AES-CTR 确保音频数据安全传输 +- **序列化管理**:防止重放攻击和数据乱序 +- **自动恢复**:支持连接断开后的自动重连 +- **性能优化**:UDP 传输保证音频数据的实时性 + 该协议适用于对实时性要求较高的语音交互场景,但需要在网络复杂度和传输性能之间做出权衡。 \ No newline at end of file diff --git a/docs/v0/atoms3r-echo-base.jpg b/docs/v0/atoms3r-echo-base.jpg old mode 100755 new mode 100644 diff --git a/docs/v1/movecall-cuican-esp32s3.jpg b/docs/v1/movecall-cuican-esp32s3.jpg old mode 100755 new mode 100644 diff --git a/docs/websocket.md b/docs/websocket.md index f767114..54de957 100644 --- a/docs/websocket.md +++ b/docs/websocket.md @@ -1,495 +1,495 @@ -以下是一份基于代码实现整理的 WebSocket 通信协议文档,概述设备端与服务器之间如何通过 WebSocket 进行交互。 - -该文档仅基于所提供的代码推断,实际部署时可能需要结合服务器端实现进行进一步确认或补充。 - ---- - -## 1. 总体流程概览 - -1. **设备端初始化** - - 设备上电、初始化 `Application`: - - 初始化音频编解码器、显示屏、LED 等 - - 连接网络 - - 创建并初始化实现 `Protocol` 接口的 WebSocket 协议实例(`WebsocketProtocol`) - - 进入主循环等待事件(音频输入、音频输出、调度任务等)。 - -2. **建立 WebSocket 连接** - - 当设备需要开始语音会话时(例如用户唤醒、手动按键触发等),调用 `OpenAudioChannel()`: - - 根据配置获取 WebSocket URL - - 设置若干请求头(`Authorization`, `Protocol-Version`, `Device-Id`, `Client-Id`) - - 调用 `Connect()` 与服务器建立 WebSocket 连接 - -3. **设备端发送 "hello" 消息** - - 连接成功后,设备会发送一条 JSON 消息,示例结构如下: - ```json - { - "type": "hello", - "version": 1, - "features": { - "mcp": true - }, - "transport": "websocket", - "audio_params": { - "format": "opus", - "sample_rate": 16000, - "channels": 1, - "frame_duration": 60 - } - } - ``` - - 其中 `features` 字段为可选,内容根据设备编译配置自动生成。例如:`"mcp": true` 表示支持 MCP 协议。 - - `frame_duration` 的值对应 `OPUS_FRAME_DURATION_MS`(例如 60ms)。 - -4. **服务器回复 "hello"** - - 设备等待服务器返回一条包含 `"type": "hello"` 的 JSON 消息,并检查 `"transport": "websocket"` 是否匹配。 - - 服务器可选下发 `session_id` 字段,设备端收到后会自动记录。 - - 示例: - ```json - { - "type": "hello", - "transport": "websocket", - "session_id": "xxx", - "audio_params": { - "format": "opus", - "sample_rate": 24000, - "channels": 1, - "frame_duration": 60 - } - } - ``` - - 如果匹配,则认为服务器已就绪,标记音频通道打开成功。 - - 如果在超时时间(默认 10 秒)内未收到正确回复,认为连接失败并触发网络错误回调。 - -5. **后续消息交互** - - 设备端和服务器端之间可发送两种主要类型的数据: - 1. **二进制音频数据**(Opus 编码) - 2. **文本 JSON 消息**(用于传输聊天状态、TTS/STT 事件、MCP 协议消息等) - - - 在代码里,接收回调主要分为: - - `OnData(...)`: - - 当 `binary` 为 `true` 时,认为是音频帧;设备会将其当作 Opus 数据进行解码。 - - 当 `binary` 为 `false` 时,认为是 JSON 文本,需要在设备端用 cJSON 进行解析并做相应业务逻辑处理(如聊天、TTS、MCP 协议消息等)。 - - - 当服务器或网络出现断连,回调 `OnDisconnected()` 被触发: - - 设备会调用 `on_audio_channel_closed_()`,并最终回到空闲状态。 - -6. **关闭 WebSocket 连接** - - 设备在需要结束语音会话时,会调用 `CloseAudioChannel()` 主动断开连接,并回到空闲状态。 - - 或者如果服务器端主动断开,也会引发同样的回调流程。 - ---- - -## 2. 通用请求头 - -在建立 WebSocket 连接时,代码示例中设置了以下请求头: - -- `Authorization`: 用于存放访问令牌,形如 `"Bearer "` -- `Protocol-Version`: 协议版本号,与 hello 消息体内的 `version` 字段保持一致 -- `Device-Id`: 设备物理网卡 MAC 地址 -- `Client-Id`: 软件生成的 UUID(擦除 NVS 或重新烧录完整固件会重置) - -这些头会随着 WebSocket 握手一起发送到服务器,服务器可根据需求进行校验、认证等。 - ---- - -## 3. 二进制协议版本 - -设备支持多种二进制协议版本,通过配置中的 `version` 字段指定: - -### 3.1 版本1(默认) -直接发送 Opus 音频数据,无额外元数据。Websocket 协议会区分 text 与 binary。 - -### 3.2 版本2 -使用 `BinaryProtocol2` 结构: -```c -struct BinaryProtocol2 { - uint16_t version; // 协议版本 - uint16_t type; // 消息类型 (0: OPUS, 1: JSON) - uint32_t reserved; // 保留字段 - uint32_t timestamp; // 时间戳(毫秒,用于服务器端AEC) - uint32_t payload_size; // 负载大小(字节) - uint8_t payload[]; // 负载数据 -} __attribute__((packed)); -``` - -### 3.3 版本3 -使用 `BinaryProtocol3` 结构: -```c -struct BinaryProtocol3 { - uint8_t type; // 消息类型 - uint8_t reserved; // 保留字段 - uint16_t payload_size; // 负载大小 - uint8_t payload[]; // 负载数据 -} __attribute__((packed)); -``` - ---- - -## 4. JSON 消息结构 - -WebSocket 文本帧以 JSON 方式传输,以下为常见的 `"type"` 字段及其对应业务逻辑。若消息里包含未列出的字段,可能为可选或特定实现细节。 - -### 4.1 设备端→服务器 - -1. **Hello** - - 连接成功后,由设备端发送,告知服务器基本参数。 - - 例: - ```json - { - "type": "hello", - "version": 1, - "features": { - "mcp": true - }, - "transport": "websocket", - "audio_params": { - "format": "opus", - "sample_rate": 16000, - "channels": 1, - "frame_duration": 60 - } - } - ``` - -2. **Listen** - - 表示设备端开始或停止录音监听。 - - 常见字段: - - `"session_id"`:会话标识 - - `"type": "listen"` - - `"state"`:`"start"`, `"stop"`, `"detect"`(唤醒检测已触发) - - `"mode"`:`"auto"`, `"manual"` 或 `"realtime"`,表示识别模式。 - - 例:开始监听 - ```json - { - "session_id": "xxx", - "type": "listen", - "state": "start", - "mode": "manual" - } - ``` - -3. **Abort** - - 终止当前说话(TTS 播放)或语音通道。 - - 例: - ```json - { - "session_id": "xxx", - "type": "abort", - "reason": "wake_word_detected" - } - ``` - - `reason` 值可为 `"wake_word_detected"` 或其他。 - -4. **Wake Word Detected** - - 用于设备端向服务器告知检测到唤醒词。 - - 在发送该消息之前,可提前发送唤醒词的 Opus 音频数据,用于服务器进行声纹检测。 - - 例: - ```json - { - "session_id": "xxx", - "type": "listen", - "state": "detect", - "text": "你好小明" - } - ``` - -5. **MCP** - - 推荐用于物联网控制的新一代协议。所有设备能力发现、工具调用等均通过 type: "mcp" 的消息进行,payload 内部为标准 JSON-RPC 2.0(详见 [MCP 协议文档](./mcp-protocol.md))。 - - - **设备端到服务器发送 result 的例子:** - ```json - { - "session_id": "xxx", - "type": "mcp", - "payload": { - "jsonrpc": "2.0", - "id": 1, - "result": { - "content": [ - { "type": "text", "text": "true" } - ], - "isError": false - } - } - } - ``` - ---- - -### 4.2 服务器→设备端 - -1. **Hello** - - 服务器端返回的握手确认消息。 - - 必须包含 `"type": "hello"` 和 `"transport": "websocket"`。 - - 可能会带有 `audio_params`,表示服务器期望的音频参数,或与设备端对齐的配置。 - - 服务器可选下发 `session_id` 字段,设备端收到后会自动记录。 - - 成功接收后设备端会设置事件标志,表示 WebSocket 通道就绪。 - -2. **STT** - - `{"session_id": "xxx", "type": "stt", "text": "..."}` - - 表示服务器端识别到了用户语音。(例如语音转文本结果) - - 设备可能将此文本显示到屏幕上,后续再进入回答等流程。 - -3. **LLM** - - `{"session_id": "xxx", "type": "llm", "emotion": "happy", "text": "😀"}` - - 服务器指示设备调整表情动画 / UI 表达。 - -4. **TTS** - - `{"session_id": "xxx", "type": "tts", "state": "start"}`:服务器准备下发 TTS 音频,设备端进入 "speaking" 播放状态。 - - `{"session_id": "xxx", "type": "tts", "state": "stop"}`:表示本次 TTS 结束。 - - `{"session_id": "xxx", "type": "tts", "state": "sentence_start", "text": "..."}` - - 让设备在界面上显示当前要播放或朗读的文本片段(例如用于显示给用户)。 - -5. **MCP** - - 服务器通过 type: "mcp" 的消息下发物联网相关的控制指令或返回调用结果,payload 结构同上。 - - - **服务器到设备端发送 tools/call 的例子:** - ```json - { - "session_id": "xxx", - "type": "mcp", - "payload": { - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "self.light.set_rgb", - "arguments": { "r": 255, "g": 0, "b": 0 } - }, - "id": 1 - } - } - ``` - -6. **System** - - 系统控制命令,常用于远程升级更新。 - - 例: - ```json - { - "session_id": "xxx", - "type": "system", - "command": "reboot" - } - ``` - - 支持的命令: - - `"reboot"`:重启设备 - -7. **Custom**(可选) - - 自定义消息,当 `CONFIG_RECEIVE_CUSTOM_MESSAGE` 启用时支持。 - - 例: - ```json - { - "session_id": "xxx", - "type": "custom", - "payload": { - "message": "自定义内容" - } - } - ``` - -8. **音频数据:二进制帧** - - 当服务器发送音频二进制帧(Opus 编码)时,设备端解码并播放。 - - 若设备端正在处于 "listening" (录音)状态,收到的音频帧会被忽略或清空以防冲突。 - ---- - -## 5. 音频编解码 - -1. **设备端发送录音数据** - - 音频输入经过可能的回声消除、降噪或音量增益后,通过 Opus 编码打包为二进制帧发送给服务器。 - - 根据协议版本,可能直接发送 Opus 数据(版本1)或使用带元数据的二进制协议(版本2/3)。 - -2. **设备端播放收到的音频** - - 收到服务器的二进制帧时,同样认定是 Opus 数据。 - - 设备端会进行解码,然后交由音频输出接口播放。 - - 如果服务器的音频采样率与设备不一致,会在解码后再进行重采样。 - ---- - -## 6. 常见状态流转 - -以下为常见设备端关键状态流转,与 WebSocket 消息对应: - -1. **Idle** → **Connecting** - - 用户触发或唤醒后,设备调用 `OpenAudioChannel()` → 建立 WebSocket 连接 → 发送 `"type":"hello"`。 - -2. **Connecting** → **Listening** - - 成功建立连接后,若继续执行 `SendStartListening(...)`,则进入录音状态。此时设备会持续编码麦克风数据并发送到服务器。 - -3. **Listening** → **Speaking** - - 收到服务器 TTS Start 消息 (`{"type":"tts","state":"start"}`) → 停止录音并播放接收到的音频。 - -4. **Speaking** → **Idle** - - 服务器 TTS Stop (`{"type":"tts","state":"stop"}`) → 音频播放结束。若未继续进入自动监听,则返回 Idle;如果配置了自动循环,则再度进入 Listening。 - -5. **Listening** / **Speaking** → **Idle**(遇到异常或主动中断) - - 调用 `SendAbortSpeaking(...)` 或 `CloseAudioChannel()` → 中断会话 → 关闭 WebSocket → 状态回到 Idle。 - -### 自动模式状态流转图 - -```mermaid -stateDiagram - direction TB - [*] --> kDeviceStateUnknown - kDeviceStateUnknown --> kDeviceStateStarting:初始化 - kDeviceStateStarting --> kDeviceStateWifiConfiguring:配置WiFi - kDeviceStateStarting --> kDeviceStateActivating:激活设备 - kDeviceStateActivating --> kDeviceStateUpgrading:检测到新版本 - kDeviceStateActivating --> kDeviceStateIdle:激活完成 - kDeviceStateIdle --> kDeviceStateConnecting:开始连接 - kDeviceStateConnecting --> kDeviceStateIdle:连接失败 - kDeviceStateConnecting --> kDeviceStateListening:连接成功 - kDeviceStateListening --> kDeviceStateSpeaking:开始说话 - kDeviceStateSpeaking --> kDeviceStateListening:结束说话 - kDeviceStateListening --> kDeviceStateIdle:手动终止 - kDeviceStateSpeaking --> kDeviceStateIdle:自动终止 -``` - -### 手动模式状态流转图 - -```mermaid -stateDiagram - direction TB - [*] --> kDeviceStateUnknown - kDeviceStateUnknown --> kDeviceStateStarting:初始化 - kDeviceStateStarting --> kDeviceStateWifiConfiguring:配置WiFi - kDeviceStateStarting --> kDeviceStateActivating:激活设备 - kDeviceStateActivating --> kDeviceStateUpgrading:检测到新版本 - kDeviceStateActivating --> kDeviceStateIdle:激活完成 - kDeviceStateIdle --> kDeviceStateConnecting:开始连接 - kDeviceStateConnecting --> kDeviceStateIdle:连接失败 - kDeviceStateConnecting --> kDeviceStateListening:连接成功 - kDeviceStateIdle --> kDeviceStateListening:开始监听 - kDeviceStateListening --> kDeviceStateIdle:停止监听 - kDeviceStateIdle --> kDeviceStateSpeaking:开始说话 - kDeviceStateSpeaking --> kDeviceStateIdle:结束说话 -``` - ---- - -## 7. 错误处理 - -1. **连接失败** - - 如果 `Connect(url)` 返回失败或在等待服务器 "hello" 消息时超时,触发 `on_network_error_()` 回调。设备会提示"无法连接到服务"或类似错误信息。 - -2. **服务器断开** - - 如果 WebSocket 异常断开,回调 `OnDisconnected()`: - - 设备回调 `on_audio_channel_closed_()` - - 切换到 Idle 或其他重试逻辑。 - ---- - -## 8. 其它注意事项 - -1. **鉴权** - - 设备通过设置 `Authorization: Bearer ` 提供鉴权,服务器端需验证是否有效。 - - 如果令牌过期或无效,服务器可拒绝握手或在后续断开。 - -2. **会话控制** - - 代码中部分消息包含 `session_id`,用于区分独立的对话或操作。服务端可根据需要对不同会话做分离处理。 - -3. **音频负载** - - 代码里默认使用 Opus 格式,并设置 `sample_rate = 16000`,单声道。帧时长由 `OPUS_FRAME_DURATION_MS` 控制,一般为 60ms。可根据带宽或性能做适当调整。为了获得更好的音乐播放效果,服务器下行音频可能使用 24000 采样率。 - -4. **协议版本配置** - - 通过设置中的 `version` 字段配置二进制协议版本(1、2 或 3) - - 版本1:直接发送 Opus 数据 - - 版本2:使用带时间戳的二进制协议,适用于服务器端 AEC - - 版本3:使用简化的二进制协议 - -5. **物联网控制推荐 MCP 协议** - - 设备与服务器之间的物联网能力发现、状态同步、控制指令等,建议全部通过 MCP 协议(type: "mcp")实现。原有的 type: "iot" 方案已废弃。 - - MCP 协议可在 WebSocket、MQTT 等多种底层协议上传输,具备更好的扩展性和标准化能力。 - - 详细用法请参考 [MCP 协议文档](./mcp-protocol.md) 及 [MCP 物联网控制用法](./mcp-usage.md)。 - -6. **错误或异常 JSON** - - 当 JSON 中缺少必要字段,例如 `{"type": ...}`,设备端会记录错误日志(`ESP_LOGE(TAG, "Missing message type, data: %s", data);`),不会执行任何业务。 - ---- - -## 9. 消息示例 - -下面给出一个典型的双向消息示例(流程简化示意): - -1. **设备端 → 服务器**(握手) - ```json - { - "type": "hello", - "version": 1, - "features": { - "mcp": true - }, - "transport": "websocket", - "audio_params": { - "format": "opus", - "sample_rate": 16000, - "channels": 1, - "frame_duration": 60 - } - } - ``` - -2. **服务器 → 设备端**(握手应答) - ```json - { - "type": "hello", - "transport": "websocket", - "session_id": "xxx", - "audio_params": { - "format": "opus", - "sample_rate": 16000 - } - } - ``` - -3. **设备端 → 服务器**(开始监听) - ```json - { - "session_id": "xxx", - "type": "listen", - "state": "start", - "mode": "auto" - } - ``` - 同时设备端开始发送二进制帧(Opus 数据)。 - -4. **服务器 → 设备端**(ASR 结果) - ```json - { - "session_id": "xxx", - "type": "stt", - "text": "用户说的话" - } - ``` - -5. **服务器 → 设备端**(TTS开始) - ```json - { - "session_id": "xxx", - "type": "tts", - "state": "start" - } - ``` - 接着服务器发送二进制音频帧给设备端播放。 - -6. **服务器 → 设备端**(TTS结束) - ```json - { - "session_id": "xxx", - "type": "tts", - "state": "stop" - } - ``` - 设备端停止播放音频,若无更多指令,则回到空闲状态。 - ---- - -## 10. 总结 - -本协议通过在 WebSocket 上层传输 JSON 文本与二进制音频帧,完成功能包括音频流上传、TTS 音频播放、语音识别与状态管理、MCP 指令下发等。其核心特征: - -- **握手阶段**:发送 `"type":"hello"`,等待服务器返回。 -- **音频通道**:采用 Opus 编码的二进制帧双向传输语音流,支持多种协议版本。 -- **JSON 消息**:使用 `"type"` 为核心字段标识不同业务逻辑,包括 TTS、STT、MCP、WakeWord、System、Custom 等。 -- **扩展性**:可根据实际需求在 JSON 消息中添加字段,或在 headers 里进行额外鉴权。 - -服务器与设备端需提前约定各类消息的字段含义、时序逻辑以及错误处理规则,方能保证通信顺畅。上述信息可作为基础文档,便于后续对接、开发或扩展。 +以下是一份基于代码实现整理的 WebSocket 通信协议文档,概述设备端与服务器之间如何通过 WebSocket 进行交互。 + +该文档仅基于所提供的代码推断,实际部署时可能需要结合服务器端实现进行进一步确认或补充。 + +--- + +## 1. 总体流程概览 + +1. **设备端初始化** + - 设备上电、初始化 `Application`: + - 初始化音频编解码器、显示屏、LED 等 + - 连接网络 + - 创建并初始化实现 `Protocol` 接口的 WebSocket 协议实例(`WebsocketProtocol`) + - 进入主循环等待事件(音频输入、音频输出、调度任务等)。 + +2. **建立 WebSocket 连接** + - 当设备需要开始语音会话时(例如用户唤醒、手动按键触发等),调用 `OpenAudioChannel()`: + - 根据配置获取 WebSocket URL + - 设置若干请求头(`Authorization`, `Protocol-Version`, `Device-Id`, `Client-Id`) + - 调用 `Connect()` 与服务器建立 WebSocket 连接 + +3. **设备端发送 "hello" 消息** + - 连接成功后,设备会发送一条 JSON 消息,示例结构如下: + ```json + { + "type": "hello", + "version": 1, + "features": { + "mcp": true + }, + "transport": "websocket", + "audio_params": { + "format": "opus", + "sample_rate": 16000, + "channels": 1, + "frame_duration": 60 + } + } + ``` + - 其中 `features` 字段为可选,内容根据设备编译配置自动生成。例如:`"mcp": true` 表示支持 MCP 协议。 + - `frame_duration` 的值对应 `OPUS_FRAME_DURATION_MS`(例如 60ms)。 + +4. **服务器回复 "hello"** + - 设备等待服务器返回一条包含 `"type": "hello"` 的 JSON 消息,并检查 `"transport": "websocket"` 是否匹配。 + - 服务器可选下发 `session_id` 字段,设备端收到后会自动记录。 + - 示例: + ```json + { + "type": "hello", + "transport": "websocket", + "session_id": "xxx", + "audio_params": { + "format": "opus", + "sample_rate": 24000, + "channels": 1, + "frame_duration": 60 + } + } + ``` + - 如果匹配,则认为服务器已就绪,标记音频通道打开成功。 + - 如果在超时时间(默认 10 秒)内未收到正确回复,认为连接失败并触发网络错误回调。 + +5. **后续消息交互** + - 设备端和服务器端之间可发送两种主要类型的数据: + 1. **二进制音频数据**(Opus 编码) + 2. **文本 JSON 消息**(用于传输聊天状态、TTS/STT 事件、MCP 协议消息等) + + - 在代码里,接收回调主要分为: + - `OnData(...)`: + - 当 `binary` 为 `true` 时,认为是音频帧;设备会将其当作 Opus 数据进行解码。 + - 当 `binary` 为 `false` 时,认为是 JSON 文本,需要在设备端用 cJSON 进行解析并做相应业务逻辑处理(如聊天、TTS、MCP 协议消息等)。 + + - 当服务器或网络出现断连,回调 `OnDisconnected()` 被触发: + - 设备会调用 `on_audio_channel_closed_()`,并最终回到空闲状态。 + +6. **关闭 WebSocket 连接** + - 设备在需要结束语音会话时,会调用 `CloseAudioChannel()` 主动断开连接,并回到空闲状态。 + - 或者如果服务器端主动断开,也会引发同样的回调流程。 + +--- + +## 2. 通用请求头 + +在建立 WebSocket 连接时,代码示例中设置了以下请求头: + +- `Authorization`: 用于存放访问令牌,形如 `"Bearer "` +- `Protocol-Version`: 协议版本号,与 hello 消息体内的 `version` 字段保持一致 +- `Device-Id`: 设备物理网卡 MAC 地址 +- `Client-Id`: 软件生成的 UUID(擦除 NVS 或重新烧录完整固件会重置) + +这些头会随着 WebSocket 握手一起发送到服务器,服务器可根据需求进行校验、认证等。 + +--- + +## 3. 二进制协议版本 + +设备支持多种二进制协议版本,通过配置中的 `version` 字段指定: + +### 3.1 版本1(默认) +直接发送 Opus 音频数据,无额外元数据。Websocket 协议会区分 text 与 binary。 + +### 3.2 版本2 +使用 `BinaryProtocol2` 结构: +```c +struct BinaryProtocol2 { + uint16_t version; // 协议版本 + uint16_t type; // 消息类型 (0: OPUS, 1: JSON) + uint32_t reserved; // 保留字段 + uint32_t timestamp; // 时间戳(毫秒,用于服务器端AEC) + uint32_t payload_size; // 负载大小(字节) + uint8_t payload[]; // 负载数据 +} __attribute__((packed)); +``` + +### 3.3 版本3 +使用 `BinaryProtocol3` 结构: +```c +struct BinaryProtocol3 { + uint8_t type; // 消息类型 + uint8_t reserved; // 保留字段 + uint16_t payload_size; // 负载大小 + uint8_t payload[]; // 负载数据 +} __attribute__((packed)); +``` + +--- + +## 4. JSON 消息结构 + +WebSocket 文本帧以 JSON 方式传输,以下为常见的 `"type"` 字段及其对应业务逻辑。若消息里包含未列出的字段,可能为可选或特定实现细节。 + +### 4.1 设备端→服务器 + +1. **Hello** + - 连接成功后,由设备端发送,告知服务器基本参数。 + - 例: + ```json + { + "type": "hello", + "version": 1, + "features": { + "mcp": true + }, + "transport": "websocket", + "audio_params": { + "format": "opus", + "sample_rate": 16000, + "channels": 1, + "frame_duration": 60 + } + } + ``` + +2. **Listen** + - 表示设备端开始或停止录音监听。 + - 常见字段: + - `"session_id"`:会话标识 + - `"type": "listen"` + - `"state"`:`"start"`, `"stop"`, `"detect"`(唤醒检测已触发) + - `"mode"`:`"auto"`, `"manual"` 或 `"realtime"`,表示识别模式。 + - 例:开始监听 + ```json + { + "session_id": "xxx", + "type": "listen", + "state": "start", + "mode": "manual" + } + ``` + +3. **Abort** + - 终止当前说话(TTS 播放)或语音通道。 + - 例: + ```json + { + "session_id": "xxx", + "type": "abort", + "reason": "wake_word_detected" + } + ``` + - `reason` 值可为 `"wake_word_detected"` 或其他。 + +4. **Wake Word Detected** + - 用于设备端向服务器告知检测到唤醒词。 + - 在发送该消息之前,可提前发送唤醒词的 Opus 音频数据,用于服务器进行声纹检测。 + - 例: + ```json + { + "session_id": "xxx", + "type": "listen", + "state": "detect", + "text": "你好小明" + } + ``` + +5. **MCP** + - 推荐用于物联网控制的新一代协议。所有设备能力发现、工具调用等均通过 type: "mcp" 的消息进行,payload 内部为标准 JSON-RPC 2.0(详见 [MCP 协议文档](./mcp-protocol.md))。 + + - **设备端到服务器发送 result 的例子:** + ```json + { + "session_id": "xxx", + "type": "mcp", + "payload": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "content": [ + { "type": "text", "text": "true" } + ], + "isError": false + } + } + } + ``` + +--- + +### 4.2 服务器→设备端 + +1. **Hello** + - 服务器端返回的握手确认消息。 + - 必须包含 `"type": "hello"` 和 `"transport": "websocket"`。 + - 可能会带有 `audio_params`,表示服务器期望的音频参数,或与设备端对齐的配置。 + - 服务器可选下发 `session_id` 字段,设备端收到后会自动记录。 + - 成功接收后设备端会设置事件标志,表示 WebSocket 通道就绪。 + +2. **STT** + - `{"session_id": "xxx", "type": "stt", "text": "..."}` + - 表示服务器端识别到了用户语音。(例如语音转文本结果) + - 设备可能将此文本显示到屏幕上,后续再进入回答等流程。 + +3. **LLM** + - `{"session_id": "xxx", "type": "llm", "emotion": "happy", "text": "😀"}` + - 服务器指示设备调整表情动画 / UI 表达。 + +4. **TTS** + - `{"session_id": "xxx", "type": "tts", "state": "start"}`:服务器准备下发 TTS 音频,设备端进入 "speaking" 播放状态。 + - `{"session_id": "xxx", "type": "tts", "state": "stop"}`:表示本次 TTS 结束。 + - `{"session_id": "xxx", "type": "tts", "state": "sentence_start", "text": "..."}` + - 让设备在界面上显示当前要播放或朗读的文本片段(例如用于显示给用户)。 + +5. **MCP** + - 服务器通过 type: "mcp" 的消息下发物联网相关的控制指令或返回调用结果,payload 结构同上。 + + - **服务器到设备端发送 tools/call 的例子:** + ```json + { + "session_id": "xxx", + "type": "mcp", + "payload": { + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "self.light.set_rgb", + "arguments": { "r": 255, "g": 0, "b": 0 } + }, + "id": 1 + } + } + ``` + +6. **System** + - 系统控制命令,常用于远程升级更新。 + - 例: + ```json + { + "session_id": "xxx", + "type": "system", + "command": "reboot" + } + ``` + - 支持的命令: + - `"reboot"`:重启设备 + +7. **Custom**(可选) + - 自定义消息,当 `CONFIG_RECEIVE_CUSTOM_MESSAGE` 启用时支持。 + - 例: + ```json + { + "session_id": "xxx", + "type": "custom", + "payload": { + "message": "自定义内容" + } + } + ``` + +8. **音频数据:二进制帧** + - 当服务器发送音频二进制帧(Opus 编码)时,设备端解码并播放。 + - 若设备端正在处于 "listening" (录音)状态,收到的音频帧会被忽略或清空以防冲突。 + +--- + +## 5. 音频编解码 + +1. **设备端发送录音数据** + - 音频输入经过可能的回声消除、降噪或音量增益后,通过 Opus 编码打包为二进制帧发送给服务器。 + - 根据协议版本,可能直接发送 Opus 数据(版本1)或使用带元数据的二进制协议(版本2/3)。 + +2. **设备端播放收到的音频** + - 收到服务器的二进制帧时,同样认定是 Opus 数据。 + - 设备端会进行解码,然后交由音频输出接口播放。 + - 如果服务器的音频采样率与设备不一致,会在解码后再进行重采样。 + +--- + +## 6. 常见状态流转 + +以下为常见设备端关键状态流转,与 WebSocket 消息对应: + +1. **Idle** → **Connecting** + - 用户触发或唤醒后,设备调用 `OpenAudioChannel()` → 建立 WebSocket 连接 → 发送 `"type":"hello"`。 + +2. **Connecting** → **Listening** + - 成功建立连接后,若继续执行 `SendStartListening(...)`,则进入录音状态。此时设备会持续编码麦克风数据并发送到服务器。 + +3. **Listening** → **Speaking** + - 收到服务器 TTS Start 消息 (`{"type":"tts","state":"start"}`) → 停止录音并播放接收到的音频。 + +4. **Speaking** → **Idle** + - 服务器 TTS Stop (`{"type":"tts","state":"stop"}`) → 音频播放结束。若未继续进入自动监听,则返回 Idle;如果配置了自动循环,则再度进入 Listening。 + +5. **Listening** / **Speaking** → **Idle**(遇到异常或主动中断) + - 调用 `SendAbortSpeaking(...)` 或 `CloseAudioChannel()` → 中断会话 → 关闭 WebSocket → 状态回到 Idle。 + +### 自动模式状态流转图 + +```mermaid +stateDiagram + direction TB + [*] --> kDeviceStateUnknown + kDeviceStateUnknown --> kDeviceStateStarting:初始化 + kDeviceStateStarting --> kDeviceStateWifiConfiguring:配置WiFi + kDeviceStateStarting --> kDeviceStateActivating:激活设备 + kDeviceStateActivating --> kDeviceStateUpgrading:检测到新版本 + kDeviceStateActivating --> kDeviceStateIdle:激活完成 + kDeviceStateIdle --> kDeviceStateConnecting:开始连接 + kDeviceStateConnecting --> kDeviceStateIdle:连接失败 + kDeviceStateConnecting --> kDeviceStateListening:连接成功 + kDeviceStateListening --> kDeviceStateSpeaking:开始说话 + kDeviceStateSpeaking --> kDeviceStateListening:结束说话 + kDeviceStateListening --> kDeviceStateIdle:手动终止 + kDeviceStateSpeaking --> kDeviceStateIdle:自动终止 +``` + +### 手动模式状态流转图 + +```mermaid +stateDiagram + direction TB + [*] --> kDeviceStateUnknown + kDeviceStateUnknown --> kDeviceStateStarting:初始化 + kDeviceStateStarting --> kDeviceStateWifiConfiguring:配置WiFi + kDeviceStateStarting --> kDeviceStateActivating:激活设备 + kDeviceStateActivating --> kDeviceStateUpgrading:检测到新版本 + kDeviceStateActivating --> kDeviceStateIdle:激活完成 + kDeviceStateIdle --> kDeviceStateConnecting:开始连接 + kDeviceStateConnecting --> kDeviceStateIdle:连接失败 + kDeviceStateConnecting --> kDeviceStateListening:连接成功 + kDeviceStateIdle --> kDeviceStateListening:开始监听 + kDeviceStateListening --> kDeviceStateIdle:停止监听 + kDeviceStateIdle --> kDeviceStateSpeaking:开始说话 + kDeviceStateSpeaking --> kDeviceStateIdle:结束说话 +``` + +--- + +## 7. 错误处理 + +1. **连接失败** + - 如果 `Connect(url)` 返回失败或在等待服务器 "hello" 消息时超时,触发 `on_network_error_()` 回调。设备会提示"无法连接到服务"或类似错误信息。 + +2. **服务器断开** + - 如果 WebSocket 异常断开,回调 `OnDisconnected()`: + - 设备回调 `on_audio_channel_closed_()` + - 切换到 Idle 或其他重试逻辑。 + +--- + +## 8. 其它注意事项 + +1. **鉴权** + - 设备通过设置 `Authorization: Bearer ` 提供鉴权,服务器端需验证是否有效。 + - 如果令牌过期或无效,服务器可拒绝握手或在后续断开。 + +2. **会话控制** + - 代码中部分消息包含 `session_id`,用于区分独立的对话或操作。服务端可根据需要对不同会话做分离处理。 + +3. **音频负载** + - 代码里默认使用 Opus 格式,并设置 `sample_rate = 16000`,单声道。帧时长由 `OPUS_FRAME_DURATION_MS` 控制,一般为 60ms。可根据带宽或性能做适当调整。为了获得更好的音乐播放效果,服务器下行音频可能使用 24000 采样率。 + +4. **协议版本配置** + - 通过设置中的 `version` 字段配置二进制协议版本(1、2 或 3) + - 版本1:直接发送 Opus 数据 + - 版本2:使用带时间戳的二进制协议,适用于服务器端 AEC + - 版本3:使用简化的二进制协议 + +5. **物联网控制推荐 MCP 协议** + - 设备与服务器之间的物联网能力发现、状态同步、控制指令等,建议全部通过 MCP 协议(type: "mcp")实现。原有的 type: "iot" 方案已废弃。 + - MCP 协议可在 WebSocket、MQTT 等多种底层协议上传输,具备更好的扩展性和标准化能力。 + - 详细用法请参考 [MCP 协议文档](./mcp-protocol.md) 及 [MCP 物联网控制用法](./mcp-usage.md)。 + +6. **错误或异常 JSON** + - 当 JSON 中缺少必要字段,例如 `{"type": ...}`,设备端会记录错误日志(`ESP_LOGE(TAG, "Missing message type, data: %s", data);`),不会执行任何业务。 + +--- + +## 9. 消息示例 + +下面给出一个典型的双向消息示例(流程简化示意): + +1. **设备端 → 服务器**(握手) + ```json + { + "type": "hello", + "version": 1, + "features": { + "mcp": true + }, + "transport": "websocket", + "audio_params": { + "format": "opus", + "sample_rate": 16000, + "channels": 1, + "frame_duration": 60 + } + } + ``` + +2. **服务器 → 设备端**(握手应答) + ```json + { + "type": "hello", + "transport": "websocket", + "session_id": "xxx", + "audio_params": { + "format": "opus", + "sample_rate": 16000 + } + } + ``` + +3. **设备端 → 服务器**(开始监听) + ```json + { + "session_id": "xxx", + "type": "listen", + "state": "start", + "mode": "auto" + } + ``` + 同时设备端开始发送二进制帧(Opus 数据)。 + +4. **服务器 → 设备端**(ASR 结果) + ```json + { + "session_id": "xxx", + "type": "stt", + "text": "用户说的话" + } + ``` + +5. **服务器 → 设备端**(TTS开始) + ```json + { + "session_id": "xxx", + "type": "tts", + "state": "start" + } + ``` + 接着服务器发送二进制音频帧给设备端播放。 + +6. **服务器 → 设备端**(TTS结束) + ```json + { + "session_id": "xxx", + "type": "tts", + "state": "stop" + } + ``` + 设备端停止播放音频,若无更多指令,则回到空闲状态。 + +--- + +## 10. 总结 + +本协议通过在 WebSocket 上层传输 JSON 文本与二进制音频帧,完成功能包括音频流上传、TTS 音频播放、语音识别与状态管理、MCP 指令下发等。其核心特征: + +- **握手阶段**:发送 `"type":"hello"`,等待服务器返回。 +- **音频通道**:采用 Opus 编码的二进制帧双向传输语音流,支持多种协议版本。 +- **JSON 消息**:使用 `"type"` 为核心字段标识不同业务逻辑,包括 TTS、STT、MCP、WakeWord、System、Custom 等。 +- **扩展性**:可根据实际需求在 JSON 消息中添加字段,或在 headers 里进行额外鉴权。 + +服务器与设备端需提前约定各类消息的字段含义、时序逻辑以及错误处理规则,方能保证通信顺畅。上述信息可作为基础文档,便于后续对接、开发或扩展。 diff --git a/esptool.exe b/esptool.exe new file mode 100755 index 0000000..b442514 Binary files /dev/null and b/esptool.exe differ diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 130ebe1..440d734 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,833 +1,805 @@ -# Define default assets files (Absolute url starting with http or https is supported) -set(ASSETS_URL_PREFIX "https://files.xiaozhi.me/assets/default/") - -set(ASSETS_PUHUI_COMMON_14_1 "${ASSETS_URL_PREFIX}none-font_puhui_common_14_1-none.bin") -set(ASSETS_XIAOZHI_WAKENET_ONLY "${ASSETS_URL_PREFIX}wn9_nihaoxiaozhi_tts-none-none.bin") -set(ASSETS_XIAOZHI_PUHUI_COMMON_14_1 "${ASSETS_URL_PREFIX}wn9_nihaoxiaozhi_tts-font_puhui_common_14_1-none.bin") -set(ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32 "${ASSETS_URL_PREFIX}wn9_nihaoxiaozhi_tts-font_puhui_common_16_4-emojis_32.bin") -set(ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_64 "${ASSETS_URL_PREFIX}wn9_nihaoxiaozhi_tts-font_puhui_common_16_4-emojis_64.bin") -set(ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64 "${ASSETS_URL_PREFIX}wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-emojis_64.bin") -set(ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64 "${ASSETS_URL_PREFIX}wn9_nihaoxiaozhi_tts-font_puhui_common_30_4-emojis_64.bin") -set(ASSETS_XIAOZHI_S_WAKENET_ONLY "${ASSETS_URL_PREFIX}wn9s_nihaoxiaozhi-none-none.bin") -set(ASSETS_XIAOZHI_S_PUHUI_COMMON_14_1 "${ASSETS_URL_PREFIX}wn9s_nihaoxiaozhi-font_puhui_common_14_1-none.bin") -set(ASSETS_XIAOZHI_S_PUHUI_COMMON_16_4_EMOJI_32 "${ASSETS_URL_PREFIX}wn9s_nihaoxiaozhi-font_puhui_common_16_4-emojis_32.bin") -set(ASSETS_XIAOZHI_S_PUHUI_COMMON_20_4_EMOJI_32 "${ASSETS_URL_PREFIX}wn9s_nihaoxiaozhi-font_puhui_common_20_4-emojis_32.bin") -set(ASSETS_XIAOZHI_S_PUHUI_COMMON_20_4_EMOJI_64 "${ASSETS_URL_PREFIX}wn9s_nihaoxiaozhi-font_puhui_common_20_4-emojis_64.bin") -set(ASSETS_XIAOZHI_S_PUHUI_COMMON_30_4_EMOJI_64 "${ASSETS_URL_PREFIX}wn9s_nihaoxiaozhi-font_puhui_common_30_4-emojis_64.bin") - -# Embedded font files defined in `xiaozhi-fonts` component -# Basic fonts include ASCII and about 600 characters used in assets/locales -set(FONT_PUHUI_BASIC_14_1 font_puhui_basic_14_1) -set(FONT_PUHUI_BASIC_16_4 font_puhui_basic_16_4) -set(FONT_PUHUI_BASIC_20_4 font_puhui_basic_20_4) -set(FONT_PUHUI_BASIC_30_4 font_puhui_basic_30_4) -# Common fonts include about 7000 common characters generated with DeepSeek R1 tokenizer -set(FONT_PUHUI_COMMON_14_1 font_puhui_14_1) -set(FONT_PUHUI_COMMON_16_4 font_puhui_16_4) -set(FONT_PUHUI_COMMON_20_4 font_puhui_20_4) -set(FONT_PUHUI_COMMON_30_4 font_puhui_30_4) -set(FONT_AWESOME_14_1 font_awesome_14_1) -set(FONT_AWESOME_30_1 font_awesome_30_1) -set(FONT_AWESOME_16_4 font_awesome_16_4) -set(FONT_AWESOME_20_4 font_awesome_20_4) -set(FONT_AWESOME_30_4 font_awesome_30_4) - - -# Define source files -set(SOURCES "audio/audio_codec.cc" - "audio/audio_service.cc" - "audio/codecs/no_audio_codec.cc" - "audio/codecs/box_audio_codec.cc" - "audio/codecs/es8311_audio_codec.cc" - "audio/codecs/es8374_audio_codec.cc" - "audio/codecs/es8388_audio_codec.cc" - "audio/codecs/es8389_audio_codec.cc" - "audio/codecs/dummy_audio_codec.cc" - "audio/processors/audio_debugger.cc" - "led/single_led.cc" - "led/circular_strip.cc" - "led/gpio_led.cc" - "display/display.cc" - "display/lcd_display.cc" - "display/oled_display.cc" - "display/lvgl_display/lvgl_display.cc" - "display/lvgl_display/emoji_collection.cc" - "display/lvgl_display/lvgl_theme.cc" - "display/lvgl_display/lvgl_font.cc" - "display/lvgl_display/lvgl_image.cc" - "display/lvgl_display/gif/lvgl_gif.cc" - "display/lvgl_display/gif/gifdec.c" - "display/esplog_display.cc" - "protocols/protocol.cc" - "protocols/mqtt_protocol.cc" - "protocols/websocket_protocol.cc" - "protocols/sleep_music_protocol.cc" - "mcp_server.cc" - "system_info.cc" - "application.cc" - "ota.cc" - "settings.cc" - "device_state_event.cc" - "assets.cc" - "schedule_manager.cc" - "timer_manager.cc" - "main.cc" - ) - -set(INCLUDE_DIRS "." "display" "display/lvgl_display" "audio" "protocols") - -# Add board common files -file(GLOB BOARD_COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/common/*.cc) -list(APPEND SOURCES ${BOARD_COMMON_SOURCES}) -list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/common) - -# Set default LVGL_TEXT_FONT and LVGL_ICON_FONT -set(LVGL_TEXT_FONT ${FONT_PUHUI_COMMON_14_1}) -set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) - -# Add board files according to BOARD_TYPE -# Set default assets if the board uses partition table V2 -if(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI) - set(BOARD_TYPE "bread-compact-wifi") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_14_1}) -elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307) - set(BOARD_TYPE "bread-compact-ml307") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_14_1}) -elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32) - set(BOARD_TYPE "bread-compact-esp32") -elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32_LCD) - set(BOARD_TYPE "bread-compact-esp32-lcd") - set(LVGL_TEXT_FONT ${FONT_PUHUI_COMMON_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) -elseif(CONFIG_BOARD_TYPE_DF_K10) - set(BOARD_TYPE "df-k10") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_DF_S3_AI_CAM) - set(BOARD_TYPE "df-s3-ai-cam") - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_WAKENET_ONLY}) -elseif(CONFIG_BOARD_TYPE_ESP_BOX_3) - set(BOARD_TYPE "esp-box-3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP_BOX) - set(BOARD_TYPE "esp-box") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP_BOX_LITE) - set(BOARD_TYPE "esp-box-lite") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_1) - set(BOARD_TYPE "kevin-box-1") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_14_1}) -elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_2) - set(BOARD_TYPE "kevin-box-2") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_14_1}) -elseif(CONFIG_BOARD_TYPE_KEVIN_C3) - set(BOARD_TYPE "kevin-c3") - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_S_WAKENET_ONLY}) -elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V3_DEV) - set(BOARD_TYPE "kevin-sp-v3-dev") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V4_DEV) - set(BOARD_TYPE "kevin-sp-v4-dev") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_KEVIN_YUYING_313LCD) - set(BOARD_TYPE "kevin-yuying-313lcd") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_30_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV) - set(BOARD_TYPE "lichuang-dev") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_LICHUANG_C3_DEV) - set(BOARD_TYPE "lichuang-c3-dev") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_S_PUHUI_COMMON_20_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_MAGICLICK_2P4) - set(BOARD_TYPE "magiclick-2p4") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_MAGICLICK_2P5) - set(BOARD_TYPE "magiclick-2p5") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_MAGICLICK_C3) - set(BOARD_TYPE "magiclick-c3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_S_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_MAGICLICK_C3_V2) - set(BOARD_TYPE "magiclick-c3-v2") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_S_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_M5STACK_CORE_S3) - set(BOARD_TYPE "m5stack-core-s3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_M5STACK_CORE_TAB5) - set(BOARD_TYPE "m5stack-tab5") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_30_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ATOMS3_ECHO_BASE) - set(BOARD_TYPE "atoms3-echo-base") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_ATOMS3R_ECHO_BASE) - set(BOARD_TYPE "atoms3r-echo-base") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE) - set(BOARD_TYPE "atoms3r-cam-m12-echo-base") - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_WAKENET_ONLY}) -elseif(CONFIG_BOARD_TYPE_ATOM_ECHOS3R) - set(BOARD_TYPE "atom-echos3r") - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_WAKENET_ONLY}) -elseif(CONFIG_BOARD_TYPE_ATOMMATRIX_ECHO_BASE) - set(BOARD_TYPE "atommatrix-echo-base") -elseif(CONFIG_BOARD_TYPE_XMINI_C3_V3) - set(BOARD_TYPE "xmini-c3-v3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_S_PUHUI_COMMON_14_1}) -elseif(CONFIG_BOARD_TYPE_XMINI_C3_4G) - set(BOARD_TYPE "xmini-c3-4g") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) - set(DEFAULT_ASSETS ${ASSETS_PUHUI_COMMON_14_1}) -elseif(CONFIG_BOARD_TYPE_XMINI_C3) - set(BOARD_TYPE "xmini-c3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_S_PUHUI_COMMON_14_1}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_KORVO2_V3) - set(BOARD_TYPE "esp32s3-korvo2-v3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP_SPARKBOT) - set(BOARD_TYPE "esp-sparkbot") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP_SPOT_S3) - set(BOARD_TYPE "esp-spot-s3") - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_WAKENET_ONLY}) -elseif(CONFIG_BOARD_TYPE_ESP_HI) - set(BOARD_TYPE "esp-hi") -elseif(CONFIG_BOARD_TYPE_ECHOEAR) - set(BOARD_TYPE "echoear") - set(LVGL_TEXT_FONT ${FONT_PUHUI_COMMON_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_AUDIO_BOARD) - set(BOARD_TYPE "waveshare-s3-audio-board") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8) - set(BOARD_TYPE "esp32-s3-touch-amoled-1.8") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_30_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06) - set(BOARD_TYPE "waveshare-s3-touch-amoled-2.06") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_30_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75) - set(BOARD_TYPE "waveshare-s3-touch-amoled-1.75") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_30_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_1_85C) - set(BOARD_TYPE "esp32-s3-touch-lcd-1.85c") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_1_85) - set(BOARD_TYPE "esp32-s3-touch-lcd-1.85") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_1_46) - set(BOARD_TYPE "esp32-s3-touch-lcd-1.46") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_3_5) - set(BOARD_TYPE "esp32-s3-touch-lcd-3.5") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_3_5B) - set(BOARD_TYPE "waveshare-s3-touch-lcd-3.5b") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_ESP32C6_LCD_1_69) - set(BOARD_TYPE "waveshare-c6-lcd-1.69") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_S_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32C6_Touch_AMOLED_1_43) - set(BOARD_TYPE "waveshare-c6-touch-amoled-1.43") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_30_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_S_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32P4_NANO) - set(BOARD_TYPE "waveshare-p4-nano") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_30_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B) - set(BOARD_TYPE "waveshare-p4-wifi6-touch-lcd-4b") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_30_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC) - set(BOARD_TYPE "waveshare-p4-wifi6-touch-lcd-xc") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_30_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_LCD) - set(BOARD_TYPE "bread-compact-wifi-lcd") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_TUDOUZI) - set(BOARD_TYPE "tudouzi") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_14_1}) -elseif(CONFIG_BOARD_TYPE_LILYGO_T_CIRCLE_S3) - set(BOARD_TYPE "lilygo-t-circle-s3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1) - set(BOARD_TYPE "lilygo-t-cameraplus-s3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA) - set(BOARD_TYPE "lilygo-t-display-s3-pro-mvsrlora") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI_ESP32S3) - set(BOARD_TYPE "movecall-moji-esp32s3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_MOVECALL_CUICAN_ESP32S3) - set(BOARD_TYPE "movecall-cuican-esp32s3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3) - set(BOARD_TYPE "atk-dnesp32s3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX) - set(BOARD_TYPE "atk-dnesp32s3-box") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX0) - set(BOARD_TYPE "atk-dnesp32s3-box0") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI) - set(BOARD_TYPE "atk-dnesp32s3-box2-wifi") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_4G) - set(BOARD_TYPE "atk-dnesp32s3-box2-4g") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_WIFI) - set(BOARD_TYPE "atk-dnesp32s3m-wifi") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_4G) - set(BOARD_TYPE "atk-dnesp32s3m-4g") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_DU_CHATX) - set(BOARD_TYPE "du-chatx") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_Taiji_Pi) - set(BOARD_TYPE "taiji-pi-s3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_85TFT_WIFI) - set(BOARD_TYPE "xingzhi-cube-0.85tft-wifi") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_85TFT_ML307) - set(BOARD_TYPE "xingzhi-cube-0.85tft-ml307") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_96OLED_WIFI) - set(BOARD_TYPE "xingzhi-cube-0.96oled-wifi") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_14_1}) -elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_96OLED_ML307) - set(BOARD_TYPE "xingzhi-cube-0.96oled-ml307") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_14_1}) -elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_1_54TFT_WIFI) - set(BOARD_TYPE "xingzhi-cube-1.54tft-wifi") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_1_54TFT_ML307) - set(BOARD_TYPE "xingzhi-cube-1.54tft-ml307") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_SENSECAP_WATCHER) - set(BOARD_TYPE "sensecap-watcher") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_DOIT_S3_AIBOX) - set(BOARD_TYPE "doit-s3-aibox") - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_WAKENET_ONLY}) -elseif(CONFIG_BOARD_TYPE_MIXGO_NOVA) - set(BOARD_TYPE "mixgo-nova") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_GENJUTECH_S3_1_54TFT) - set(BOARD_TYPE "genjutech-s3-1.54tft") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32_CGC) - set(BOARD_TYPE "esp32-cgc") - set(LVGL_TEXT_FONT ${FONT_PUHUI_COMMON_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) -elseif(CONFIG_BOARD_TYPE_ESP32_CGC_144) - set(BOARD_TYPE "esp32-cgc-144") - set(LVGL_TEXT_FONT ${FONT_PUHUI_COMMON_14_1}) - set(LVGL_ICON_FONT ${FONT_AWESOME_14_1}) -elseif(CONFIG_BOARD_TYPE_ESP_S3_LCD_EV_Board) - set(BOARD_TYPE "esp-s3-lcd-ev-board") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_30_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP_S3_LCD_EV_Board_2) - set(BOARD_TYPE "esp-s3-lcd-ev-board-2") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_30_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_30_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ZHENGCHEN_1_54TFT_WIFI) - set(BOARD_TYPE "zhengchen-1.54tft-wifi") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_MINSI_K08_DUAL) - set(BOARD_TYPE "minsi-k08-dual") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ZHENGCHEN_1_54TFT_ML307) - set(BOARD_TYPE "zhengchen-1.54tft-ml307") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_ESP32_S3_1_54_MUMA) - set(BOARD_TYPE "sp-esp32-s3-1.54-muma") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_ESP32_S3_1_28_BOX) - set(BOARD_TYPE "sp-esp32-s3-1.28-box") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_OTTO_ROBOT) - set(BOARD_TYPE "otto-robot") - set(LVGL_TEXT_FONT ${FONT_PUHUI_COMMON_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) -elseif(CONFIG_BOARD_TYPE_ELECTRON_BOT) - set(BOARD_TYPE "electron-bot") - set(LVGL_TEXT_FONT ${FONT_PUHUI_COMMON_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) -elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_CAM) - set(BOARD_TYPE "bread-compact-wifi-s3cam") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_16_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_16_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_JIUCHUAN) - set(BOARD_TYPE "jiuchuan-s3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_LABPLUS_MPYTHON_V3) - set(BOARD_TYPE "labplus-mpython-v3") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_LABPLUS_LEDONG_V2) - set(BOARD_TYPE "labplus-ledong-v2") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) -elseif(CONFIG_BOARD_TYPE_SURFER_C3_1_14TFT) - set(BOARD_TYPE "surfer-c3-1.14tft") - set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) - set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_S_PUHUI_COMMON_20_4_EMOJI_32}) -elseif(CONFIG_BOARD_TYPE_ESP32S3_SMART_SPEAKER) - set(BOARD_TYPE "esp32s3-smart-speaker") - set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_WAKENET_ONLY}) -endif() - -file(GLOB BOARD_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc - ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.c -) -list(APPEND SOURCES ${BOARD_SOURCES}) - -# Select audio processor according to Kconfig -if(CONFIG_USE_AUDIO_PROCESSOR) - list(APPEND SOURCES "audio/processors/afe_audio_processor.cc") -else() - list(APPEND SOURCES "audio/processors/no_audio_processor.cc") -endif() -if(CONFIG_USE_AFE_WAKE_WORD) - list(APPEND SOURCES "audio/wake_words/afe_wake_word.cc") -elseif(CONFIG_USE_ESP_WAKE_WORD) - list(APPEND SOURCES "audio/wake_words/esp_wake_word.cc") -elseif(CONFIG_USE_CUSTOM_WAKE_WORD) - list(APPEND SOURCES "audio/wake_words/custom_wake_word.cc") -endif() - -# Select language directory according to Kconfig -if(CONFIG_LANGUAGE_ZH_CN) - set(LANG_DIR "zh-CN") -elseif(CONFIG_LANGUAGE_ZH_TW) - set(LANG_DIR "zh-TW") -elseif(CONFIG_LANGUAGE_EN_US) - set(LANG_DIR "en-US") -elseif(CONFIG_LANGUAGE_JA_JP) - set(LANG_DIR "ja-JP") -elseif(CONFIG_LANGUAGE_KO_KR) - set(LANG_DIR "ko-KR") -elseif(CONFIG_LANGUAGE_VI_VN) - set(LANG_DIR "vi-VN") -elseif(CONFIG_LANGUAGE_TH_TH) - set(LANG_DIR "th-TH") -elseif(CONFIG_LANGUAGE_DE_DE) - set(LANG_DIR "de-DE") -elseif(CONFIG_LANGUAGE_FR_FR) - set(LANG_DIR "fr-FR") -elseif(CONFIG_LANGUAGE_ES_ES) - set(LANG_DIR "es-ES") -elseif(CONFIG_LANGUAGE_IT_IT) - set(LANG_DIR "it-IT") -elseif(CONFIG_LANGUAGE_RU_RU) - set(LANG_DIR "ru-RU") -elseif(CONFIG_LANGUAGE_AR_SA) - set(LANG_DIR "ar-SA") -elseif(CONFIG_LANGUAGE_HI_IN) - set(LANG_DIR "hi-IN") -elseif(CONFIG_LANGUAGE_PT_PT) - set(LANG_DIR "pt-PT") -elseif(CONFIG_LANGUAGE_PL_PL) - set(LANG_DIR "pl-PL") -elseif(CONFIG_LANGUAGE_CS_CZ) - set(LANG_DIR "cs-CZ") -elseif(CONFIG_LANGUAGE_FI_FI) - set(LANG_DIR "fi-FI") -elseif(CONFIG_LANGUAGE_TR_TR) - set(LANG_DIR "tr-TR") -elseif(CONFIG_LANGUAGE_ID_ID) - set(LANG_DIR "id-ID") -elseif(CONFIG_LANGUAGE_UK_UA) - set(LANG_DIR "uk-UA") -elseif(CONFIG_LANGUAGE_RO_RO) - set(LANG_DIR "ro-RO") -endif() - -# Define generation path -set(LANG_JSON "${CMAKE_CURRENT_SOURCE_DIR}/assets/locales/${LANG_DIR}/language.json") -set(LANG_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/assets/lang_config.h") -file(GLOB LANG_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/locales/${LANG_DIR}/*.ogg) -file(GLOB COMMON_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/common/*.ogg) - -# If target chip is ESP32, exclude specific files to avoid build errors -if(CONFIG_IDF_TARGET_ESP32) - list(REMOVE_ITEM SOURCES "audio/codecs/box_audio_codec.cc" - "audio/codecs/es8388_audio_codec.cc" - "audio/codecs/es8389_audio_codec.cc" - "led/gpio_led.cc" - ) -endif() - -idf_component_register(SRCS ${SOURCES} - EMBED_FILES ${LANG_SOUNDS} ${COMMON_SOUNDS} - INCLUDE_DIRS ${INCLUDE_DIRS} - WHOLE_ARCHIVE - ) - -# Use target_compile_definitions to define BOARD_TYPE, BOARD_NAME -# If BOARD_NAME is empty, use BOARD_TYPE -if(NOT BOARD_NAME) - set(BOARD_NAME ${BOARD_TYPE}) -endif() -target_compile_definitions(${COMPONENT_LIB} - PRIVATE BOARD_TYPE=\"${BOARD_TYPE}\" BOARD_NAME=\"${BOARD_NAME}\" - PRIVATE DEFAULT_ASSETS=\"${DEFAULT_ASSETS}\" LVGL_TEXT_FONT=${LVGL_TEXT_FONT} LVGL_ICON_FONT=${LVGL_ICON_FONT} - ) - -# Add generation rules -add_custom_command( - OUTPUT ${LANG_HEADER} - COMMAND python ${PROJECT_DIR}/scripts/gen_lang.py - --language "${LANG_DIR}" - --output "${LANG_HEADER}" - DEPENDS - ${LANG_JSON} - ${PROJECT_DIR}/scripts/gen_lang.py - COMMENT "Generating ${LANG_DIR} language config" -) - -# Force build generation dependencies -add_custom_target(lang_header ALL - DEPENDS ${LANG_HEADER} -) - -if(CONFIG_BOARD_TYPE_ESP_HI) -set(URL "https://github.com/espressif2022/image_player/raw/main/test_apps/test_8bit") -set(SPIFFS_DIR "${CMAKE_BINARY_DIR}/emoji") -file(MAKE_DIRECTORY ${SPIFFS_DIR}) - -# List all files to download -set(FILES_TO_DOWNLOAD "") -list(APPEND FILES_TO_DOWNLOAD "Anger_enter.aaf" "Anger_loop.aaf" "Anger_return.aaf") -list(APPEND FILES_TO_DOWNLOAD "happy_enter.aaf" "happy_loop.aaf" "happ_return.aaf") -list(APPEND FILES_TO_DOWNLOAD "sad_enter.aaf" "sad_loop.aaf" "sad_return.aaf") -list(APPEND FILES_TO_DOWNLOAD "scorn_enter.aaf" "scorn_loop.aaf" "scorn_return.aaf") -list(APPEND FILES_TO_DOWNLOAD "left_enter.aaf" "left_loop.aaf" "left_return.aaf") -list(APPEND FILES_TO_DOWNLOAD "right_enter.aaf" "right_loop.aaf" "right_return.aaf") -list(APPEND FILES_TO_DOWNLOAD "asking.aaf" "blink_once.aaf" "blink_quick.aaf") -list(APPEND FILES_TO_DOWNLOAD "connecting.aaf" "panic_enter.aaf" "panic_loop.aaf") -list(APPEND FILES_TO_DOWNLOAD "panic_return.aaf" "wake.aaf") - -foreach(FILENAME IN LISTS FILES_TO_DOWNLOAD) - set(REMOTE_FILE "${URL}/${FILENAME}") - set(LOCAL_FILE "${SPIFFS_DIR}/${FILENAME}") - - # Check if local file exists - if(EXISTS ${LOCAL_FILE}) - message(STATUS "File ${FILENAME} already exists, skipping download") - else() - message(STATUS "Downloading ${FILENAME}") - file(DOWNLOAD ${REMOTE_FILE} ${LOCAL_FILE} - STATUS DOWNLOAD_STATUS) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - if(NOT STATUS_CODE EQUAL 0) - message(FATAL_ERROR "Failed to download ${FILENAME} from ${URL}") - endif() - endif() -endforeach() - -spiffs_create_partition_assets( - assets_A - ${SPIFFS_DIR} - FLASH_IN_PROJECT - MMAP_FILE_SUPPORT_FORMAT ".aaf" -) -endif() - -if(CONFIG_BOARD_TYPE_ECHOEAR) - -idf_build_get_property(build_components BUILD_COMPONENTS) -foreach(COMPONENT ${build_components}) - if(COMPONENT MATCHES "esp_emote_gfx" OR COMPONENT MATCHES "espressif2022__esp_emote_gfx") - set(EMOTE_GFX_COMPONENT ${COMPONENT}) - idf_component_get_property(EMOTE_GFX_COMPONENT_PATH ${EMOTE_GFX_COMPONENT} COMPONENT_DIR) - set(SPIFFS_DIR "${EMOTE_GFX_COMPONENT_PATH}/emoji_normal") - break() - endif() -endforeach() - -spiffs_create_partition_assets( - assets_A - ${SPIFFS_DIR} - FLASH_IN_PROJECT - MMAP_FILE_SUPPORT_FORMAT ".aaf, ttf, bin" - IMPORT_INC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE} -) -endif() - -# Font configuration validation function -function(validate_font_config board_name text_font default_assets) - if(text_font) - # Check if DEFAULT_ASSETS contains font - if(default_assets AND default_assets MATCHES "font_") - # Rule 1: If DEFAULT_ASSETS uses font, LVGL_TEXT_FONT must be BASIC - if(NOT text_font MATCHES "basic") - message(FATAL_ERROR "Font config error for ${board_name}: DEFAULT_ASSETS contains COMMON font but LVGL_TEXT_FONT is not BASIC (${text_font})") - endif() - else() - # Rule 2: If no DEFAULT_ASSETS or DEFAULT_ASSETS doesn't contain font_, LVGL_TEXT_FONT must not be BASIC - if(text_font MATCHES "basic") - message(FATAL_ERROR "Font config error for ${board_name}: No DEFAULT_ASSETS with COMMON font but LVGL_TEXT_FONT is not COMMON (${text_font})") - endif() - endif() - # Pass validation - message(STATUS "Font config validation passed for ${board_name}: LVGL_TEXT_FONT=${text_font}, DEFAULT_ASSETS=${default_assets}") - endif() -endfunction() - -# DEFAULT_ASSETS prefix validation function -function(validate_default_assets_prefix board_name default_assets) - if(default_assets) - # Check for ESP32S3/P4 target - DEFAULT_ASSETS cannot start with "wn9s_" - if(CONFIG_IDF_TARGET_ESP32S3 OR CONFIG_IDF_TARGET_ESP32P4) - if(default_assets MATCHES "^wn9s_") - message(FATAL_ERROR "Assets config error for ${board_name}: DEFAULT_ASSETS cannot start with 'wn9s_' for ESP32S3 target (${default_assets})") - endif() - endif() - - # Check for ESP32C3/C6 target - DEFAULT_ASSETS cannot start with "wn9_" - if(CONFIG_IDF_TARGET_ESP32C3 OR CONFIG_IDF_TARGET_ESP32C6) - if(default_assets MATCHES "^wn9_") - message(FATAL_ERROR "Assets config error for ${board_name}: DEFAULT_ASSETS cannot start with 'wn9_' for ESP32C3/C6 target (${default_assets})") - endif() - endif() - - # Pass validation - message(STATUS "Assets prefix validation passed for ${board_name}: DEFAULT_ASSETS=${default_assets}") - endif() -endfunction() - -# Global font configuration validation -# This will validate the current board's font configuration -if(LVGL_TEXT_FONT) - validate_font_config("${BOARD_TYPE}" "${LVGL_TEXT_FONT}" "${DEFAULT_ASSETS}") -endif() - -# Global DEFAULT_ASSETS prefix validation -# This will validate the current board's DEFAULT_ASSETS prefix configuration -if(DEFAULT_ASSETS) - validate_default_assets_prefix("${BOARD_TYPE}" "${DEFAULT_ASSETS}") -endif() - -# Function to get local assets file path (handles both URL and local file) -function(get_assets_local_file assets_source assets_local_file_var) - # Check if it's a URL (starts with http:// or https://) - if(assets_source MATCHES "^https?://") - # It's a URL, download it - get_filename_component(ASSETS_FILENAME "${assets_source}" NAME) - set(ASSETS_LOCAL_FILE "${CMAKE_BINARY_DIR}/${ASSETS_FILENAME}") - set(ASSETS_TEMP_FILE "${CMAKE_BINARY_DIR}/${ASSETS_FILENAME}.tmp") - - # Check if local file exists - if(EXISTS ${ASSETS_LOCAL_FILE}) - message(STATUS "Assets file ${ASSETS_FILENAME} already exists, skipping download") - else() - message(STATUS "Downloading ${ASSETS_FILENAME}") - - # Clean up any existing temp file - if(EXISTS ${ASSETS_TEMP_FILE}) - file(REMOVE ${ASSETS_TEMP_FILE}) - endif() - - # Download to temporary file first - file(DOWNLOAD ${assets_source} ${ASSETS_TEMP_FILE} - STATUS DOWNLOAD_STATUS) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - if(NOT STATUS_CODE EQUAL 0) - # Clean up temp file on failure - if(EXISTS ${ASSETS_TEMP_FILE}) - file(REMOVE ${ASSETS_TEMP_FILE}) - endif() - message(FATAL_ERROR "Failed to download ${ASSETS_FILENAME} from ${assets_source}") - endif() - - # Move temp file to final location (atomic operation) - file(RENAME ${ASSETS_TEMP_FILE} ${ASSETS_LOCAL_FILE}) - message(STATUS "Successfully downloaded ${ASSETS_FILENAME}") - endif() - else() - # It's a local file path - if(IS_ABSOLUTE "${assets_source}") - set(ASSETS_LOCAL_FILE "${assets_source}") - else() - set(ASSETS_LOCAL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${assets_source}") - endif() - - # Check if local file exists - if(NOT EXISTS ${ASSETS_LOCAL_FILE}) - message(FATAL_ERROR "Assets file not found: ${ASSETS_LOCAL_FILE}") - endif() - - message(STATUS "Using assets file: ${ASSETS_LOCAL_FILE}") - endif() - - set(${assets_local_file_var} ${ASSETS_LOCAL_FILE} PARENT_SCOPE) -endfunction() - -# Flash assets based on configuration -if(CONFIG_FLASH_DEFAULT_ASSETS) - # Flash default assets - if(DEFAULT_ASSETS) - get_assets_local_file("${DEFAULT_ASSETS}" ASSETS_LOCAL_FILE) - esptool_py_flash_to_partition(flash "assets" "${ASSETS_LOCAL_FILE}") - message(STATUS "Default assets download and flash configured: ${DEFAULT_ASSETS} -> assets partition") - else() - message(WARNING "FLASH_DEFAULT_ASSETS is enabled but no DEFAULT_ASSETS is defined for board ${BOARD_TYPE}") - endif() -elseif(CONFIG_FLASH_CUSTOM_ASSETS) - # Flash custom assets - get_assets_local_file("${CONFIG_CUSTOM_ASSETS_FILE}" ASSETS_LOCAL_FILE) - esptool_py_flash_to_partition(flash "assets" "${ASSETS_LOCAL_FILE}") - message(STATUS "Custom assets flash configured: ${ASSETS_LOCAL_FILE} -> assets partition") -elseif(CONFIG_FLASH_NONE_ASSETS) - message(STATUS "Assets flashing disabled (FLASH_NONE_ASSETS)") -endif() \ No newline at end of file +# Define source files +set(SOURCES "audio/audio_codec.cc" + "audio/audio_service.cc" + "audio/codecs/no_audio_codec.cc" + "audio/codecs/box_audio_codec.cc" + "audio/codecs/es8311_audio_codec.cc" + "audio/codecs/es8374_audio_codec.cc" + "audio/codecs/es8388_audio_codec.cc" + "audio/codecs/es8389_audio_codec.cc" + "audio/codecs/dummy_audio_codec.cc" + "audio/processors/audio_debugger.cc" + "led/single_led.cc" + "led/circular_strip.cc" + "led/gpio_led.cc" + "display/display.cc" + "display/lcd_display.cc" + "display/oled_display.cc" + "display/lvgl_display/lvgl_display.cc" + "display/emote_display.cc" + "display/lvgl_display/emoji_collection.cc" + "display/lvgl_display/lvgl_theme.cc" + "display/lvgl_display/lvgl_font.cc" + "display/lvgl_display/lvgl_image.cc" + "display/lvgl_display/gif/lvgl_gif.cc" + "display/lvgl_display/gif/gifdec.c" + "display/lvgl_display/jpg/image_to_jpeg.cpp" + "display/lvgl_display/jpg/jpeg_encoder.cpp" + "protocols/protocol.cc" + "protocols/mqtt_protocol.cc" + "protocols/websocket_protocol.cc" + "mcp_server.cc" + "system_info.cc" + "application.cc" + "alarm_manager.cc" + "ota.cc" + "settings.cc" + "device_state_event.cc" + "device_manager.cc" + "assets.cc" + "main.cc" + ) + +set(INCLUDE_DIRS "." "display" "display/lvgl_display" "display/lvgl_display/jpg" "audio" "protocols") + +# Add board common files +file(GLOB BOARD_COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/common/*.cc) +list(APPEND SOURCES ${BOARD_COMMON_SOURCES}) +list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/common) + +idf_build_get_property(build_components BUILD_COMPONENTS) +# Function to find component dynamically by pattern +function(find_component_by_pattern PATTERN COMPONENT_VAR PATH_VAR) + foreach(COMPONENT ${build_components}) + if(COMPONENT MATCHES "${PATTERN}") + set(${COMPONENT_VAR} ${COMPONENT} PARENT_SCOPE) + idf_component_get_property(COMPONENT_PATH ${COMPONENT} COMPONENT_DIR) + set(${PATH_VAR} "${COMPONENT_PATH}" PARENT_SCOPE) + break() + endif() + endforeach() +endfunction() + +# Set default BUILTIN_TEXT_FONT and BUILTIN_ICON_FONT +set(BUILTIN_TEXT_FONT font_puhui_14_1) +set(BUILTIN_ICON_FONT font_awesome_14_1) + +# Add board files according to BOARD_TYPE +# Set default assets if the board uses partition table V2 +if(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI) + set(BOARD_TYPE "bread-compact-wifi") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307) + set(BOARD_TYPE "bread-compact-ml307") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32) + set(BOARD_TYPE "bread-compact-esp32") +elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32_LCD) + set(BOARD_TYPE "bread-compact-esp32-lcd") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_DF_K10) + set(BOARD_TYPE "df-k10") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_DF_S3_AI_CAM) + set(BOARD_TYPE "df-s3-ai-cam") +elseif(CONFIG_BOARD_TYPE_ESP_BOX_3) + set(BOARD_TYPE "esp-box-3") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP_BOX) + set(BOARD_TYPE "esp-box") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP_BOX_LITE) + set(BOARD_TYPE "esp-box-lite") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_1) + set(BOARD_TYPE "kevin-box-1") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_2) + set(BOARD_TYPE "kevin-box-2") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_KEVIN_C3) + set(BOARD_TYPE "kevin-c3") +elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V3_DEV) + set(BOARD_TYPE "kevin-sp-v3-dev") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V4_DEV) + set(BOARD_TYPE "kevin-sp-v4-dev") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_KEVIN_YUYING_313LCD) + set(BOARD_TYPE "kevin-yuying-313lcd") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV) + set(BOARD_TYPE "lichuang-dev") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_LICHUANG_C3_DEV) + set(BOARD_TYPE "lichuang-c3-dev") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_MAGICLICK_2P4) + set(BOARD_TYPE "magiclick-2p4") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_MAGICLICK_2P5) + set(BOARD_TYPE "magiclick-2p5") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_MAGICLICK_C3) + set(BOARD_TYPE "magiclick-c3") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_MAGICLICK_C3_V2) + set(BOARD_TYPE "magiclick-c3-v2") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_M5STACK_CORE_S3) + set(BOARD_TYPE "m5stack-core-s3") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_M5STACK_CORE_TAB5) + set(BOARD_TYPE "m5stack-tab5") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ATOMS3_ECHO_BASE) + set(BOARD_TYPE "atoms3-echo-base") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_ATOMS3R_ECHO_BASE) + set(BOARD_TYPE "atoms3r-echo-base") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE) + set(BOARD_TYPE "atoms3r-cam-m12-echo-base") +elseif(CONFIG_BOARD_TYPE_ATOM_ECHOS3R) + set(BOARD_TYPE "atom-echos3r") +elseif(CONFIG_BOARD_TYPE_ATOMMATRIX_ECHO_BASE) + set(BOARD_TYPE "atommatrix-echo-base") +elseif(CONFIG_BOARD_TYPE_XMINI_C3_V3) + set(BOARD_TYPE "xmini-c3-v3") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_XMINI_C3_4G) + set(BOARD_TYPE "xmini-c3-4g") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_XMINI_C3) + set(BOARD_TYPE "xmini-c3") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_ESP32S3_KORVO2_V3) + set(BOARD_TYPE "esp32s3-korvo2-v3") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP_SPARKBOT) + set(BOARD_TYPE "esp-sparkbot") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP_SPOT_S3) + set(BOARD_TYPE "esp-spot-s3") +elseif(CONFIG_BOARD_TYPE_ESP_HI) + set(BOARD_TYPE "esp-hi") + # Set ESP_HI emoji directory for DEFAULT_ASSETS_EXTRA_FILES + set(DEFAULT_ASSETS_EXTRA_FILES "${CMAKE_BINARY_DIR}/emoji") +elseif(CONFIG_BOARD_TYPE_ECHOEAR) + set(BOARD_TYPE "echoear") + set(BUILTIN_TEXT_FONT font_puhui_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32S3_AUDIO_BOARD) + set(BOARD_TYPE "waveshare-s3-audio-board") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8) + set(BOARD_TYPE "esp32-s3-touch-amoled-1.8") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06) + set(BOARD_TYPE "waveshare-s3-touch-amoled-2.06") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_4B) + set(BOARD_TYPE "waveshare-s3-touch-lcd-4b") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75) + set(BOARD_TYPE "waveshare-s3-touch-amoled-1.75") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_1_85C) + set(BOARD_TYPE "esp32-s3-touch-lcd-1.85c") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_1_85) + set(BOARD_TYPE "esp32-s3-touch-lcd-1.85") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_1_46) + set(BOARD_TYPE "esp32-s3-touch-lcd-1.46") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_3_5) + set(BOARD_TYPE "esp32-s3-touch-lcd-3.5") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_3_5B) + set(BOARD_TYPE "waveshare-s3-touch-lcd-3.5b") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_3_49) + set(BOARD_TYPE "waveshare-s3-touch-lcd-3.49") + set(LVGL_TEXT_FONT font_puhui_basic_30_4) + set(LVGL_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32C6_LCD_1_69) + set(BOARD_TYPE "waveshare-c6-lcd-1.69") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32C6_Touch_AMOLED_1_43) + set(BOARD_TYPE "waveshare-c6-touch-amoled-1.43") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32P4_NANO) + set(BOARD_TYPE "waveshare-p4-nano") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B) + set(BOARD_TYPE "waveshare-p4-wifi6-touch-lcd-4b") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC) + set(BOARD_TYPE "waveshare-p4-wifi6-touch-lcd-xc") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_LCD) + set(BOARD_TYPE "bread-compact-wifi-lcd") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_TUDOUZI) + set(BOARD_TYPE "tudouzi") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_LILYGO_T_CIRCLE_S3) + set(BOARD_TYPE "lilygo-t-circle-s3") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1) + set(BOARD_TYPE "lilygo-t-cameraplus-s3") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA) + set(BOARD_TYPE "lilygo-t-display-s3-pro-mvsrlora") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI_ESP32S3) + set(BOARD_TYPE "movecall-moji-esp32s3") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_MOVECALL_CUICAN_ESP32S3) + set(BOARD_TYPE "movecall-cuican-esp32s3") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3) + set(BOARD_TYPE "atk-dnesp32s3") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX) + set(BOARD_TYPE "atk-dnesp32s3-box") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX0) + set(BOARD_TYPE "atk-dnesp32s3-box0") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI) + set(BOARD_TYPE "atk-dnesp32s3-box2-wifi") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_4G) + set(BOARD_TYPE "atk-dnesp32s3-box2-4g") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_WIFI) + set(BOARD_TYPE "atk-dnesp32s3m-wifi") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_4G) + set(BOARD_TYPE "atk-dnesp32s3m-4g") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_DU_CHATX) + set(BOARD_TYPE "du-chatx") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_ESP32S3_Taiji_Pi) + set(BOARD_TYPE "taiji-pi-s3") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_85TFT_WIFI) + set(BOARD_TYPE "xingzhi-cube-0.85tft-wifi") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_85TFT_ML307) + set(BOARD_TYPE "xingzhi-cube-0.85tft-ml307") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_96OLED_WIFI) + set(BOARD_TYPE "xingzhi-cube-0.96oled-wifi") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_96OLED_ML307) + set(BOARD_TYPE "xingzhi-cube-0.96oled-ml307") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_1_54TFT_WIFI) + set(BOARD_TYPE "xingzhi-cube-1.54tft-wifi") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_1_54TFT_ML307) + set(BOARD_TYPE "xingzhi-cube-1.54tft-ml307") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_SENSECAP_WATCHER) + set(BOARD_TYPE "sensecap-watcher") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_DOIT_S3_AIBOX) + set(BOARD_TYPE "doit-s3-aibox") +elseif(CONFIG_BOARD_TYPE_MIXGO_NOVA) + set(BOARD_TYPE "mixgo-nova") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_GENJUTECH_S3_1_54TFT) + set(BOARD_TYPE "genjutech-s3-1.54tft") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32_CGC) + set(BOARD_TYPE "esp32-cgc") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_ESP32_CGC_144) + set(BOARD_TYPE "esp32-cgc-144") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) +elseif(CONFIG_BOARD_TYPE_ESP_S3_LCD_EV_Board) + set(BOARD_TYPE "esp-s3-lcd-ev-board") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP_S3_LCD_EV_Board_2) + set(BOARD_TYPE "esp-s3-lcd-ev-board-2") + set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) + set(BUILTIN_ICON_FONT font_awesome_30_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ZHENGCHEN_1_54TFT_WIFI) + set(BOARD_TYPE "zhengchen-1.54tft-wifi") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_MINSI_K08_DUAL) + set(BOARD_TYPE "minsi-k08-dual") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ZHENGCHEN_1_54TFT_ML307) + set(BOARD_TYPE "zhengchen-1.54tft-ml307") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_ESP32_S3_1_54_MUMA) + set(BOARD_TYPE "sp-esp32-s3-1.54-muma") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_ESP32_S3_1_28_BOX) + set(BOARD_TYPE "sp-esp32-s3-1.28-box") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_OTTO_ROBOT) + set(BOARD_TYPE "otto-robot") + set(BUILTIN_TEXT_FONT font_puhui_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) +elseif(CONFIG_BOARD_TYPE_ELECTRON_BOT) + set(BOARD_TYPE "electron-bot") + set(BUILTIN_TEXT_FONT font_puhui_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) +elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_CAM) + set(BOARD_TYPE "bread-compact-wifi-s3cam") + set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) + set(BUILTIN_ICON_FONT font_awesome_16_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_JIUCHUAN) + set(BOARD_TYPE "jiuchuan-s3") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_LABPLUS_MPYTHON_V3) + set(BOARD_TYPE "labplus-mpython-v3") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_LABPLUS_LEDONG_V2) + set(BOARD_TYPE "labplus-ledong-v2") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_SURFER_C3_1_14TFT) + set(BOARD_TYPE "surfer-c3-1.14tft") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_YUNLIAO_S3) + set(BOARD_TYPE "yunliao-s3") + set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) + set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) + set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) +elseif(CONFIG_BOARD_TYPE_JINAO_S3) + set(BOARD_TYPE "jinao-s3") + set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) + set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) + set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) +endif() + +file(GLOB BOARD_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc + ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.c +) +list(APPEND SOURCES ${BOARD_SOURCES}) + +# Select audio processor according to Kconfig +if(CONFIG_USE_AUDIO_PROCESSOR) + list(APPEND SOURCES "audio/processors/afe_audio_processor.cc") +else() + list(APPEND SOURCES "audio/processors/no_audio_processor.cc") +endif() +if(CONFIG_IDF_TARGET_ESP32S3 OR CONFIG_IDF_TARGET_ESP32P4) + list(APPEND SOURCES "audio/wake_words/afe_wake_word.cc") + list(APPEND SOURCES "audio/wake_words/custom_wake_word.cc") +else() + list(APPEND SOURCES "audio/wake_words/esp_wake_word.cc") +endif() + +# Select language directory according to Kconfig +if(CONFIG_LANGUAGE_ZH_CN) + set(LANG_DIR "zh-CN") +elseif(CONFIG_LANGUAGE_ZH_TW) + set(LANG_DIR "zh-TW") +elseif(CONFIG_LANGUAGE_EN_US) + set(LANG_DIR "en-US") +elseif(CONFIG_LANGUAGE_JA_JP) + set(LANG_DIR "ja-JP") +elseif(CONFIG_LANGUAGE_KO_KR) + set(LANG_DIR "ko-KR") +elseif(CONFIG_LANGUAGE_VI_VN) + set(LANG_DIR "vi-VN") +elseif(CONFIG_LANGUAGE_TH_TH) + set(LANG_DIR "th-TH") +elseif(CONFIG_LANGUAGE_DE_DE) + set(LANG_DIR "de-DE") +elseif(CONFIG_LANGUAGE_FR_FR) + set(LANG_DIR "fr-FR") +elseif(CONFIG_LANGUAGE_ES_ES) + set(LANG_DIR "es-ES") +elseif(CONFIG_LANGUAGE_IT_IT) + set(LANG_DIR "it-IT") +elseif(CONFIG_LANGUAGE_RU_RU) + set(LANG_DIR "ru-RU") +elseif(CONFIG_LANGUAGE_AR_SA) + set(LANG_DIR "ar-SA") +elseif(CONFIG_LANGUAGE_HI_IN) + set(LANG_DIR "hi-IN") +elseif(CONFIG_LANGUAGE_PT_PT) + set(LANG_DIR "pt-PT") +elseif(CONFIG_LANGUAGE_PL_PL) + set(LANG_DIR "pl-PL") +elseif(CONFIG_LANGUAGE_CS_CZ) + set(LANG_DIR "cs-CZ") +elseif(CONFIG_LANGUAGE_FI_FI) + set(LANG_DIR "fi-FI") +elseif(CONFIG_LANGUAGE_TR_TR) + set(LANG_DIR "tr-TR") +elseif(CONFIG_LANGUAGE_ID_ID) + set(LANG_DIR "id-ID") +elseif(CONFIG_LANGUAGE_UK_UA) + set(LANG_DIR "uk-UA") +elseif(CONFIG_LANGUAGE_RO_RO) + set(LANG_DIR "ro-RO") +endif() + +# Define generation path +set(LANG_JSON "${CMAKE_CURRENT_SOURCE_DIR}/assets/locales/${LANG_DIR}/language.json") +set(LANG_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/assets/lang_config.h") +file(GLOB LANG_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/locales/${LANG_DIR}/*.ogg) +file(GLOB COMMON_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/common/*.ogg) + +# If target chip is ESP32, exclude specific files to avoid build errors +if(CONFIG_IDF_TARGET_ESP32) + list(REMOVE_ITEM SOURCES "audio/codecs/box_audio_codec.cc" + "audio/codecs/es8388_audio_codec.cc" + "audio/codecs/es8389_audio_codec.cc" + "led/gpio_led.cc" + ) +endif() + +idf_component_register(SRCS ${SOURCES} + EMBED_FILES ${LANG_SOUNDS} ${COMMON_SOUNDS} + INCLUDE_DIRS ${INCLUDE_DIRS} + WHOLE_ARCHIVE + ) + +# Use target_compile_definitions to define BOARD_TYPE, BOARD_NAME +# If BOARD_NAME is empty, use BOARD_TYPE +if(NOT BOARD_NAME) + set(BOARD_NAME ${BOARD_TYPE}) +endif() +target_compile_definitions(${COMPONENT_LIB} + PRIVATE BOARD_TYPE=\"${BOARD_TYPE}\" BOARD_NAME=\"${BOARD_NAME}\" + PRIVATE BUILTIN_TEXT_FONT=${BUILTIN_TEXT_FONT} BUILTIN_ICON_FONT=${BUILTIN_ICON_FONT} + ) + +# Add generation rules +add_custom_command( + OUTPUT ${LANG_HEADER} + COMMAND python ${PROJECT_DIR}/scripts/gen_lang.py + --language "${LANG_DIR}" + --output "${LANG_HEADER}" + DEPENDS + ${LANG_JSON} + ${PROJECT_DIR}/scripts/gen_lang.py + COMMENT "Generating ${LANG_DIR} language config" +) + +# Force build generation dependencies +add_custom_target(lang_header ALL + DEPENDS ${LANG_HEADER} +) + +# Find ESP-SR component dynamically +find_component_by_pattern("espressif__esp-sr" ESP_SR_COMPONENT ESP_SR_COMPONENT_PATH) +if(ESP_SR_COMPONENT_PATH) + set(ESP_SR_MODEL_PATH "${ESP_SR_COMPONENT_PATH}/model") +endif() + +# Find xiaozhi-fonts component dynamically +find_component_by_pattern("xiaozhi-fonts" XIAOZHI_FONTS_COMPONENT XIAOZHI_FONTS_COMPONENT_PATH) +if(XIAOZHI_FONTS_COMPONENT_PATH) + set(XIAOZHI_FONTS_PATH "${XIAOZHI_FONTS_COMPONENT_PATH}") +endif() + +if(CONFIG_BOARD_TYPE_ESP_HI) +set(URL "https://github.com/espressif2022/image_player/raw/main/test_apps/test_8bit") +set(EMOJI_DIR "${CMAKE_BINARY_DIR}/emoji") +file(MAKE_DIRECTORY ${EMOJI_DIR}) + +# List all files to download +set(FILES_TO_DOWNLOAD "") +list(APPEND FILES_TO_DOWNLOAD "Anger_enter.aaf" "Anger_loop.aaf" "Anger_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "happy_enter.aaf" "happy_loop.aaf" "happ_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "sad_enter.aaf" "sad_loop.aaf" "sad_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "scorn_enter.aaf" "scorn_loop.aaf" "scorn_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "left_enter.aaf" "left_loop.aaf" "left_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "right_enter.aaf" "right_loop.aaf" "right_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "asking.aaf" "blink_once.aaf" "blink_quick.aaf") +list(APPEND FILES_TO_DOWNLOAD "connecting.aaf" "panic_enter.aaf" "panic_loop.aaf") +list(APPEND FILES_TO_DOWNLOAD "panic_return.aaf" "wake.aaf") + +foreach(FILENAME IN LISTS FILES_TO_DOWNLOAD) + set(REMOTE_FILE "${URL}/${FILENAME}") + set(LOCAL_FILE "${EMOJI_DIR}/${FILENAME}") + + # Check if local file exists + if(EXISTS ${LOCAL_FILE}) + message(STATUS "File ${FILENAME} already exists, skipping download") + else() + message(STATUS "Downloading ${FILENAME}") + file(DOWNLOAD ${REMOTE_FILE} ${LOCAL_FILE} + STATUS DOWNLOAD_STATUS) + list(GET DOWNLOAD_STATUS 0 STATUS_CODE) + if(NOT STATUS_CODE EQUAL 0) + message(FATAL_ERROR "Failed to download ${FILENAME} from ${URL}") + endif() + endif() +endforeach() + +endif() + + +# Function to build default assets based on configuration +function(build_default_assets_bin) + # Set output path for generated assets.bin + set(GENERATED_ASSETS_BIN "${CMAKE_BINARY_DIR}/generated_assets.bin") + + # Prepare arguments for build script + set(BUILD_ARGS + "--sdkconfig" "${SDKCONFIG}" + "--output" "${GENERATED_ASSETS_BIN}" + ) + + # Add builtin text font if defined + if(BUILTIN_TEXT_FONT) + list(APPEND BUILD_ARGS "--builtin_text_font" "${BUILTIN_TEXT_FONT}") + endif() + + # Add default emoji collection if defined + if(DEFAULT_EMOJI_COLLECTION) + list(APPEND BUILD_ARGS "--emoji_collection" "${DEFAULT_EMOJI_COLLECTION}") + endif() + + # Add default assets extra files if defined + if(DEFAULT_ASSETS_EXTRA_FILES) + list(APPEND BUILD_ARGS "--extra_files" "${DEFAULT_ASSETS_EXTRA_FILES}") + endif() + + list(APPEND BUILD_ARGS "--esp_sr_model_path" "${ESP_SR_MODEL_PATH}") + list(APPEND BUILD_ARGS "--xiaozhi_fonts_path" "${XIAOZHI_FONTS_PATH}") + + # Create custom command to build assets + add_custom_command( + OUTPUT ${GENERATED_ASSETS_BIN} + COMMAND python ${PROJECT_DIR}/scripts/build_default_assets.py ${BUILD_ARGS} + DEPENDS + ${SDKCONFIG} + ${PROJECT_DIR}/scripts/build_default_assets.py + COMMENT "Building default assets.bin based on configuration" + VERBATIM + ) + + # Create target for generated assets + add_custom_target(generated_default_assets ALL + DEPENDS ${GENERATED_ASSETS_BIN} + ) + + # Set the generated file path in parent scope + set(GENERATED_ASSETS_LOCAL_FILE ${GENERATED_ASSETS_BIN} PARENT_SCOPE) + + message(STATUS "Default assets build configured: ${GENERATED_ASSETS_BIN}") +endfunction() + + +# Function to get local assets file path (handles both URL and local file) +function(get_assets_local_file assets_source assets_local_file_var) + # Check if it's a URL (starts with http:// or https://) + if(assets_source MATCHES "^https?://") + # It's a URL, download it + get_filename_component(ASSETS_FILENAME "${assets_source}" NAME) + set(ASSETS_LOCAL_FILE "${CMAKE_BINARY_DIR}/${ASSETS_FILENAME}") + set(ASSETS_TEMP_FILE "${CMAKE_BINARY_DIR}/${ASSETS_FILENAME}.tmp") + + # Check if local file exists + if(EXISTS ${ASSETS_LOCAL_FILE}) + message(STATUS "Assets file ${ASSETS_FILENAME} already exists, skipping download") + else() + message(STATUS "Downloading ${ASSETS_FILENAME}") + + # Clean up any existing temp file + if(EXISTS ${ASSETS_TEMP_FILE}) + file(REMOVE ${ASSETS_TEMP_FILE}) + endif() + + # Download to temporary file first + file(DOWNLOAD ${assets_source} ${ASSETS_TEMP_FILE} + STATUS DOWNLOAD_STATUS) + list(GET DOWNLOAD_STATUS 0 STATUS_CODE) + if(NOT STATUS_CODE EQUAL 0) + # Clean up temp file on failure + if(EXISTS ${ASSETS_TEMP_FILE}) + file(REMOVE ${ASSETS_TEMP_FILE}) + endif() + message(FATAL_ERROR "Failed to download ${ASSETS_FILENAME} from ${assets_source}") + endif() + + # Move temp file to final location (atomic operation) + file(RENAME ${ASSETS_TEMP_FILE} ${ASSETS_LOCAL_FILE}) + message(STATUS "Successfully downloaded ${ASSETS_FILENAME}") + endif() + else() + # It's a local file path + if(IS_ABSOLUTE "${assets_source}") + set(ASSETS_LOCAL_FILE "${assets_source}") + else() + set(ASSETS_LOCAL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${assets_source}") + endif() + + # Check if local file exists + if(NOT EXISTS ${ASSETS_LOCAL_FILE}) + message(FATAL_ERROR "Assets file not found: ${ASSETS_LOCAL_FILE}") + endif() + + message(STATUS "Using assets file: ${ASSETS_LOCAL_FILE}") + endif() + + set(${assets_local_file_var} ${ASSETS_LOCAL_FILE} PARENT_SCOPE) +endfunction() + + +partition_table_get_partition_info(size "--partition-name assets" "size") +partition_table_get_partition_info(offset "--partition-name assets" "offset") +if ("${size}" AND "${offset}") + # Flash assets based on configuration + if(CONFIG_FLASH_DEFAULT_ASSETS) + # Build default assets based on configuration + build_default_assets_bin() + esptool_py_flash_to_partition(flash "assets" "${GENERATED_ASSETS_LOCAL_FILE}") + message(STATUS "Generated default assets flash configured: ${GENERATED_ASSETS_LOCAL_FILE} -> assets partition") + elseif(CONFIG_FLASH_CUSTOM_ASSETS) + # Flash custom assets + get_assets_local_file("${CONFIG_CUSTOM_ASSETS_FILE}" ASSETS_LOCAL_FILE) + esptool_py_flash_to_partition(flash "assets" "${ASSETS_LOCAL_FILE}") + message(STATUS "Custom assets flash configured: ${ASSETS_LOCAL_FILE} -> assets partition") + elseif(CONFIG_FLASH_NONE_ASSETS) + message(STATUS "Assets flashing disabled (FLASH_NONE_ASSETS)") + endif() +else() + message(STATUS "Assets partition not found, using v1 partition table") +endif() diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 57f7456..c069a76 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -1,595 +1,630 @@ -menu "Xiaozhi Assistant" - -config OTA_URL - string "Default OTA URL" - default "https://api.tenclass.net/xiaozhi/ota/" - help - The application will access this URL to check for new firmwares and server address. - -choice - prompt "Flash Assets" - default FLASH_NONE_ASSETS - help - Select the assets to flash. - - config FLASH_NONE_ASSETS - bool "Do not flash assets" - config FLASH_DEFAULT_ASSETS - bool "Flash Default Assets" - config FLASH_CUSTOM_ASSETS - bool "Flash Custom Assets" -endchoice - -config CUSTOM_ASSETS_FILE - depends on FLASH_CUSTOM_ASSETS - string "Custom Assets File" - default "assets.bin" - help - The custom assets file to flash. - It can be a local file relative to the project directory or a remote url. - -choice - prompt "Default Language" - default LANGUAGE_ZH_CN - help - Select device display language - - config LANGUAGE_ZH_CN - bool "Chinese" - config LANGUAGE_ZH_TW - bool "Chinese Traditional" - config LANGUAGE_EN_US - bool "English" - config LANGUAGE_JA_JP - bool "Japanese" - config LANGUAGE_KO_KR - bool "Korean" - config LANGUAGE_VI_VN - bool "Vietnamese" - config LANGUAGE_TH_TH - bool "Thai" - config LANGUAGE_DE_DE - bool "German" - config LANGUAGE_FR_FR - bool "French" - config LANGUAGE_ES_ES - bool "Spanish" - config LANGUAGE_IT_IT - bool "Italian" - config LANGUAGE_RU_RU - bool "Russian" - config LANGUAGE_AR_SA - bool "Arabic" - config LANGUAGE_HI_IN - bool "Hindi" - config LANGUAGE_PT_PT - bool "Portuguese" - config LANGUAGE_PL_PL - bool "Polish" - config LANGUAGE_CS_CZ - bool "Czech" - config LANGUAGE_FI_FI - bool "Finnish" - config LANGUAGE_TR_TR - bool "Turkish" - config LANGUAGE_ID_ID - bool "Indonesian" - config LANGUAGE_UK_UA - bool "Ukrainian" - config LANGUAGE_RO_RO - bool "Romanian" -endchoice - -choice BOARD_TYPE - prompt "Board Type" - default BOARD_TYPE_BREAD_COMPACT_WIFI - help - Board type. 开发板类型 - config BOARD_TYPE_BREAD_COMPACT_WIFI - bool "面包板新版接线(WiFi)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_BREAD_COMPACT_WIFI_LCD - bool "面包板新版接线(WiFi)+ LCD" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_BREAD_COMPACT_WIFI_CAM - bool "面包板新版接线(WiFi)+ LCD + Camera" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32S3_SMART_SPEAKER - bool "ESP32-S3 智能音箱(PCM5102+INMP441)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_BREAD_COMPACT_ML307 - bool "面包板新版接线(ML307 AT)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_BREAD_COMPACT_ESP32 - bool "面包板(WiFi) ESP32 DevKit" - depends on IDF_TARGET_ESP32 - config BOARD_TYPE_BREAD_COMPACT_ESP32_LCD - bool "面包板(WiFi+ LCD) ESP32 DevKit" - depends on IDF_TARGET_ESP32 - config BOARD_TYPE_XMINI_C3_V3 - bool "虾哥 Mini C3 V3" - depends on IDF_TARGET_ESP32C3 - config BOARD_TYPE_XMINI_C3_4G - bool "虾哥 Mini C3 4G" - depends on IDF_TARGET_ESP32C3 - config BOARD_TYPE_XMINI_C3 - bool "虾哥 Mini C3" - depends on IDF_TARGET_ESP32C3 - config BOARD_TYPE_ESP32S3_KORVO2_V3 - bool "ESP32S3_KORVO2_V3开发板" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP_SPARKBOT - bool "ESP-SparkBot开发板" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP_SPOT_S3 - bool "ESP-Spot-S3" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP_HI - bool "ESP-HI" - depends on IDF_TARGET_ESP32C3 - config BOARD_TYPE_ECHOEAR - bool "EchoEar" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP_BOX_3 - bool "ESP BOX 3" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP_BOX - bool "ESP BOX" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP_BOX_LITE - bool "ESP BOX Lite" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_KEVIN_BOX_1 - bool "Kevin Box 1" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_KEVIN_BOX_2 - bool "Kevin Box 2" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_KEVIN_C3 - bool "Kevin C3" - depends on IDF_TARGET_ESP32C3 - config BOARD_TYPE_KEVIN_SP_V3_DEV - bool "Kevin SP V3开发板" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_KEVIN_SP_V4_DEV - bool "Kevin SP V4开发板" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32_CGC - bool "ESP32 CGC" - depends on IDF_TARGET_ESP32 - config BOARD_TYPE_ESP32_CGC_144 - bool "ESP32 CGC 144" - depends on IDF_TARGET_ESP32 - config BOARD_TYPE_KEVIN_YUYING_313LCD - bool "鱼鹰科技3.13LCD开发板" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_LICHUANG_DEV - bool "立创·实战派ESP32-S3开发板" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_LICHUANG_C3_DEV - bool "立创·实战派ESP32-C3开发板" - depends on IDF_TARGET_ESP32C3 - config BOARD_TYPE_DF_K10 - bool "DFRobot 行空板 k10" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_DF_S3_AI_CAM - bool "DFRobot ESP32-S3 AI智能摄像头模块" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_MAGICLICK_2P4 - bool "神奇按钮 Magiclick_2.4" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_MAGICLICK_2P5 - bool "神奇按钮 Magiclick_2.5" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_MAGICLICK_C3 - bool "神奇按钮 Magiclick_C3" - depends on IDF_TARGET_ESP32C3 - config BOARD_TYPE_MAGICLICK_C3_V2 - bool "神奇按钮 Magiclick_C3_v2" - depends on IDF_TARGET_ESP32C3 - config BOARD_TYPE_M5STACK_CORE_S3 - bool "M5Stack CoreS3" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_M5STACK_CORE_TAB5 - bool "M5Stack Tab5" - depends on IDF_TARGET_ESP32P4 - config BOARD_TYPE_ATOMS3_ECHO_BASE - bool "AtomS3 + Echo Base" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ATOMS3R_ECHO_BASE - bool "AtomS3R + Echo Base" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE - bool "AtomS3R CAM/M12 + Echo Base" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ATOM_ECHOS3R - bool "AtomEchoS3R" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ATOMMATRIX_ECHO_BASE - bool "AtomMatrix + Echo Base" - depends on IDF_TARGET_ESP32 - config BOARD_TYPE_ESP32S3_AUDIO_BOARD - bool "Waveshare ESP32-S3-Audio-Board" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8 - bool "Waveshare ESP32-S3-Touch-AMOLED-1.8" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06 - bool "Waveshare ESP32-S3-Touch-AMOLED-2.06" - config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75 - bool "Waveshare ESP32-S3-Touch-AMOLED-1.75" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32S3_Touch_LCD_1_85C - bool "Waveshare ESP32-S3-Touch-LCD-1.85C" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32S3_Touch_LCD_1_85 - bool "Waveshare ESP32-S3-Touch-LCD-1.85" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32S3_Touch_LCD_1_46 - bool "Waveshare ESP32-S3-Touch-LCD-1.46" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32C6_LCD_1_69 - bool "Waveshare ESP32-C6-LCD-1.69" - depends on IDF_TARGET_ESP32C6 - config BOARD_TYPE_ESP32C6_Touch_AMOLED_1_43 - bool "Waveshare ESP32-C6-Touch-AMOLOED-1.43" - depends on IDF_TARGET_ESP32C6 - config BOARD_TYPE_ESP32S3_Touch_LCD_3_5 - bool "Waveshare ESP32-S3-Touch-LCD-3.5" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32S3_Touch_LCD_3_5B - bool "Waveshare ESP32-S3-Touch-LCD-3.5B" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32P4_NANO - bool "Waveshare ESP32-P4-NANO" - depends on IDF_TARGET_ESP32P4 - config BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B - bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-4B" - depends on IDF_TARGET_ESP32P4 - config BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC - bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C or ESP32-P4-WIFI6-Touch-LCD-4C" - depends on IDF_TARGET_ESP32P4 - config BOARD_TYPE_TUDOUZI - bool "土豆子" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_LILYGO_T_CIRCLE_S3 - bool "LILYGO T-Circle-S3" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1 - bool "LILYGO T-CameraPlus-S3_V1_0_V1_1" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 - bool "LILYGO T-CameraPlus-S3_V1_2" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA - bool "LILYGO T-Display-S3-Pro-MVSRLora" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA_NO_BATTERY - bool "LILYGO T-Display-S3-Pro-MVSRLora_No_Battery" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_MOVECALL_MOJI_ESP32S3 - bool "Movecall Moji 小智AI衍生版" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_MOVECALL_CUICAN_ESP32S3 - bool "Movecall CuiCan 璀璨·AI吊坠" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ATK_DNESP32S3 - bool "正点原子DNESP32S3开发板" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ATK_DNESP32S3_BOX - bool "正点原子DNESP32S3-BOX" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ATK_DNESP32S3_BOX0 - bool "正点原子DNESP32S3-BOX0" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI - bool "正点原子DNESP32S3-BOX2-WIFI" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ATK_DNESP32S3_BOX2_4G - bool "正点原子DNESP32S3-BOX2-4G" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ATK_DNESP32S3M_WIFI - bool "正点原子DNESP32S3M-WIFI" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ATK_DNESP32S3M_4G - bool "正点原子DNESP32S3M-4G" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_DU_CHATX - bool "嘟嘟开发板CHATX(wifi)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32S3_Taiji_Pi - bool "太极小派esp32s3" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_XINGZHI_Cube_0_85TFT_WIFI - bool "无名科技星智0.85(WIFI)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_XINGZHI_Cube_0_85TFT_ML307 - bool "无名科技星智0.85(ML307)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_XINGZHI_Cube_0_96OLED_WIFI - bool "无名科技星智0.96(WIFI)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_XINGZHI_Cube_0_96OLED_ML307 - bool "无名科技星智0.96(ML307)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_XINGZHI_Cube_1_54TFT_WIFI - bool "无名科技星智1.54(WIFI)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_XINGZHI_Cube_1_54TFT_ML307 - bool "无名科技星智1.54(ML307)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_SENSECAP_WATCHER - bool "SenseCAP Watcher" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_DOIT_S3_AIBOX - bool "四博智联AI陪伴盒子" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_MIXGO_NOVA - bool "元控·青春" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_GENJUTECH_S3_1_54TFT - bool "亘具科技1.54(s3)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP_S3_LCD_EV_Board - bool "乐鑫ESP S3 LCD EV Board开发板" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP_S3_LCD_EV_Board_2 - bool "乐鑫ESP S3 LCD EV Board 2开发板" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ZHENGCHEN_1_54TFT_WIFI - bool "征辰科技1.54(WIFI)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ZHENGCHEN_1_54TFT_ML307 - bool "征辰科技1.54(ML307)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_MINSI_K08_DUAL - bool "敏思科技K08(DUAL)" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32_S3_1_54_MUMA - bool "Spotpear ESP32-S3-1.54-MUMA" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_ESP32_S3_1_28_BOX - bool "Spotpear ESP32-S3-1.28-BOX" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_OTTO_ROBOT - bool "ottoRobot" - depends on IDF_TARGET_ESP32S3 - select LV_USE_GIF - select LV_GIF_CACHE_DECODE_DATA - config BOARD_TYPE_ELECTRON_BOT - bool "electronBot" - depends on IDF_TARGET_ESP32S3 - select LV_USE_GIF - select LV_GIF_CACHE_DECODE_DATA - config BOARD_TYPE_JIUCHUAN - bool "九川智能" - config BOARD_TYPE_LABPLUS_MPYTHON_V3 - bool "labplus mpython_v3 board" - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_LABPLUS_LEDONG_V2 - bool "labplus ledong_v2 board" - depends on IDF_TARGET_ESP32S3 - depends on IDF_TARGET_ESP32S3 - config BOARD_TYPE_SURFER_C3_1_14TFT - bool "Surfer-C3-1-14TFT" - depends on IDF_TARGET_ESP32C3 - config BOARD_TYPE_YUNLIAO_S3 - bool "小智云聊-S3" - depends on IDF_TARGET_ESP32S3 -endchoice - -choice ESP_S3_LCD_EV_Board_Version_TYPE - depends on BOARD_TYPE_ESP_S3_LCD_EV_Board - prompt "EV_BOARD Type" - default ESP_S3_LCD_EV_Board_1p4 - help - 开发板硬件版本型号选择 - config ESP_S3_LCD_EV_Board_1p4 - bool "乐鑫ESP32_S3_LCD_EV_Board-MB_V1.4" - config ESP_S3_LCD_EV_Board_1p5 - bool "乐鑫ESP32_S3_LCD_EV_Board-MB_V1.5" -endchoice - -choice DISPLAY_OLED_TYPE - depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_ESP32 - prompt "OLED Type" - default OLED_SSD1306_128X32 - help - OLED 屏幕类型选择 - config OLED_SSD1306_128X32 - bool "SSD1306, 分辨率128*32" - config OLED_SSD1306_128X64 - bool "SSD1306, 分辨率128*64" - config OLED_SH1106_128X64 - bool "SH1106, 分辨率128*64" -endchoice - -choice DISPLAY_LCD_TYPE - depends on BOARD_TYPE_BREAD_COMPACT_WIFI_LCD || BOARD_TYPE_BREAD_COMPACT_ESP32_LCD || BOARD_TYPE_ESP32_CGC || BOARD_TYPE_ESP32P4_NANO || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC || BOARD_TYPE_BREAD_COMPACT_WIFI_CAM - prompt "LCD Type" - default LCD_ST7789_240X320 - help - 屏幕类型选择 - config LCD_ST7789_240X320 - bool "ST7789, 分辨率240*320, IPS" - config LCD_ST7789_240X320_NO_IPS - bool "ST7789, 分辨率240*320, 非IPS" - config LCD_ST7789_170X320 - bool "ST7789, 分辨率170*320" - config LCD_ST7789_172X320 - bool "ST7789, 分辨率172*320" - config LCD_ST7789_240X280 - bool "ST7789, 分辨率240*280" - config LCD_ST7789_240X240 - bool "ST7789, 分辨率240*240" - config LCD_ST7789_240X240_7PIN - bool "ST7789, 分辨率240*240, 7PIN" - config LCD_ST7789_240X135 - bool "ST7789, 分辨率240*135" - config LCD_ST7735_128X160 - bool "ST7735, 分辨率128*160" - config LCD_ST7735_128X128 - bool "ST7735, 分辨率128*128" - config LCD_ST7796_320X480 - bool "ST7796, 分辨率320*480 IPS" - config LCD_ST7796_320X480_NO_IPS - bool "ST7796, 分辨率320*480, 非IPS" - config LCD_ILI9341_240X320 - bool "ILI9341, 分辨率240*320" - config LCD_ILI9341_240X320_NO_IPS - bool "ILI9341, 分辨率240*320, 非IPS" - config LCD_GC9A01_240X240 - bool "GC9A01, 分辨率240*240, 圆屏" - config LCD_TYPE_800_1280_10_1_INCH - bool "Waveshare 101M-8001280-IPS-CT-K Display" - config LCD_TYPE_800_1280_10_1_INCH_A - bool "Waveshare 10.1-DSI-TOUCH-A Display" - config LCD_TYPE_800_800_3_4_INCH - bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C with 800*800 3.4inch round display" - config LCD_TYPE_720_720_4_INCH - bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-4C with 720*720 4inch round display" - config LCD_CUSTOM - bool "自定义屏幕参数" -endchoice - -choice DISPLAY_ESP32S3_KORVO2_V3 - depends on BOARD_TYPE_ESP32S3_KORVO2_V3 - prompt "ESP32S3_KORVO2_V3 LCD Type" - default ESP32S3_KORVO2_V3_LCD_ST7789 - help - 屏幕类型选择 - config ESP32S3_KORVO2_V3_LCD_ST7789 - bool "ST7789, 分辨率240*280" - config ESP32S3_KORVO2_V3_LCD_ILI9341 - bool "ILI9341, 分辨率240*320" -endchoice - -choice DISPLAY_ESP32S3_AUDIO_BOARD - depends on BOARD_TYPE_ESP32S3_AUDIO_BOARD - prompt "ESP32S3_AUDIO_BOARD LCD Type" - default AUDIO_BOARD_LCD_JD9853 - help - 屏幕类型选择 - config AUDIO_BOARD_LCD_JD9853 - bool "JD9853, 分辨率320*172" - config AUDIO_BOARD_LCD_ST7789 - bool "ST7789, 分辨率240*320" -endchoice - -config USE_WECHAT_MESSAGE_STYLE - bool "Enable WeChat Message Style" - default n - help - 使用微信聊天界面风格 - -config USE_ESP_WAKE_WORD - bool "Enable Wake Word Detection (without AFE)" - default n - depends on IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C5 || IDF_TARGET_ESP32C6 || (IDF_TARGET_ESP32 && SPIRAM) - help - 支持 ESP32 C3、ESP32 C5 与 ESP32 C6,增加ESP32支持(需要开启PSRAM) - -config USE_AFE_WAKE_WORD - bool "Enable Wake Word Detection (AFE)" - default y - depends on (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM - help - 需要 ESP32 S3 与 PSRAM 支持 - -config USE_CUSTOM_WAKE_WORD - bool "Enable Custom Wake Word Detection" - default n - depends on (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM && (!USE_AFE_WAKE_WORD) - help - 需要 ESP32 S3 与 PSRAM 支持 - -config CUSTOM_WAKE_WORD - string "Custom Wake Word" - default "xiao tu dou" - depends on USE_CUSTOM_WAKE_WORD - help - 自定义唤醒词,中文用拼音表示,每个字之间用空格隔开 - -config CUSTOM_WAKE_WORD_DISPLAY - string "Custom Wake Word Display" - default "小土豆" - depends on USE_CUSTOM_WAKE_WORD - help - 唤醒后发送给服务器的问候语 - -config CUSTOM_WAKE_WORD_THRESHOLD - int "Custom Wake Word Threshold (%)" - default 20 - range 1 99 - depends on USE_CUSTOM_WAKE_WORD - help - 自定义唤醒词阈值,范围1-99,越小越敏感,默认10 - -config USE_AUDIO_PROCESSOR - bool "Enable Audio Noise Reduction" - default y - depends on (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM - help - 需要 ESP32 S3 与 PSRAM 支持 - -config USE_DEVICE_AEC - bool "Enable Device-Side AEC" - default n - depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE || BOARD_TYPE_LICHUANG_DEV || BOARD_TYPE_ESP32S3_KORVO2_V3 || BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75 || BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06 || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC || BOARD_TYPE_ESP_S3_LCD_EV_Board_2 || BOARD_TYPE_YUNLIAO_S3) - help - 因为性能不够,不建议和微信聊天界面风格同时开启 - -config USE_SERVER_AEC - bool "Enable Server-Side AEC (Unstable)" - default n - depends on USE_AUDIO_PROCESSOR - help - 启用服务器端 AEC,需要服务器支持 - -config USE_AUDIO_DEBUGGER - bool "Enable Audio Debugger" - default n - help - 启用音频调试功能,通过UDP发送音频数据 - -config USE_ACOUSTIC_WIFI_PROVISIONING - bool "Enable Acoustic WiFi Provisioning" - default n - help - 启用声波配网功能,使用音频信号传输 WiFi 配置数据 - -config AUDIO_DEBUG_UDP_SERVER - string "Audio Debug UDP Server Address" - default "192.168.2.100:8000" - depends on USE_AUDIO_DEBUGGER - help - UDP服务器地址,格式: IP:PORT,用于接收音频调试数据 - -config RECEIVE_CUSTOM_MESSAGE - bool "Enable Custom Message Reception" - default n - help - 启用接收自定义消息功能,允许设备接收来自服务器的自定义消息(最好通过 MQTT 协议) - -menu "TAIJIPAI_S3_CONFIG" - depends on BOARD_TYPE_ESP32S3_Taiji_Pi - choice I2S_TYPE_TAIJIPI_S3 - depends on BOARD_TYPE_ESP32S3_Taiji_Pi - prompt "taiji-pi-S3 I2S Type" - default TAIJIPAI_I2S_TYPE_STD - help - I2S 类型选择 - config TAIJIPAI_I2S_TYPE_STD - bool "I2S Type STD" - config TAIJIPAI_I2S_TYPE_PDM - bool "I2S Type PDM" - endchoice - - config I2S_USE_2SLOT - bool "Enable Use 2 Slot" - default n - help - 启动双声道 -endmenu - -endmenu \ No newline at end of file +menu "Xiaozhi Assistant" + +config OTA_URL + string "Default OTA URL" + default "https://api.tenclass.net/xiaozhi/ota/" + help + The application will access this URL to check for new firmwares and server address. + +choice + prompt "Flash Assets" + default FLASH_DEFAULT_ASSETS + help + Select the assets to flash. + + config FLASH_NONE_ASSETS + bool "Do not flash assets" + config FLASH_DEFAULT_ASSETS + bool "Flash Default Assets" + config FLASH_CUSTOM_ASSETS + bool "Flash Custom Assets" +endchoice + +config CUSTOM_ASSETS_FILE + depends on FLASH_CUSTOM_ASSETS + string "Custom Assets File" + default "assets.bin" + help + The custom assets file to flash. + It can be a local file relative to the project directory or a remote url. + +choice + prompt "Default Language" + default LANGUAGE_ZH_CN + help + Select device display language + + config LANGUAGE_ZH_CN + bool "Chinese" + config LANGUAGE_ZH_TW + bool "Chinese Traditional" + config LANGUAGE_EN_US + bool "English" + config LANGUAGE_JA_JP + bool "Japanese" + config LANGUAGE_KO_KR + bool "Korean" + config LANGUAGE_VI_VN + bool "Vietnamese" + config LANGUAGE_TH_TH + bool "Thai" + config LANGUAGE_DE_DE + bool "German" + config LANGUAGE_FR_FR + bool "French" + config LANGUAGE_ES_ES + bool "Spanish" + config LANGUAGE_IT_IT + bool "Italian" + config LANGUAGE_RU_RU + bool "Russian" + config LANGUAGE_AR_SA + bool "Arabic" + config LANGUAGE_HI_IN + bool "Hindi" + config LANGUAGE_PT_PT + bool "Portuguese" + config LANGUAGE_PL_PL + bool "Polish" + config LANGUAGE_CS_CZ + bool "Czech" + config LANGUAGE_FI_FI + bool "Finnish" + config LANGUAGE_TR_TR + bool "Turkish" + config LANGUAGE_ID_ID + bool "Indonesian" + config LANGUAGE_UK_UA + bool "Ukrainian" + config LANGUAGE_RO_RO + bool "Romanian" +endchoice + +choice BOARD_TYPE + prompt "Board Type" + default BOARD_TYPE_BREAD_COMPACT_WIFI + help + Board type. 开发板类型 + config BOARD_TYPE_BREAD_COMPACT_WIFI + bool "Bread Compact WiFi (面包板)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_BREAD_COMPACT_WIFI_LCD + bool "Bread Compact WiFi + LCD (面包板)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_BREAD_COMPACT_WIFI_CAM + bool "Bread Compact WiFi + LCD + Camera (面包板)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_BREAD_COMPACT_ML307 + bool "Bread Compact ML307/EC801E (面包板 4G)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_BREAD_COMPACT_ESP32 + bool "Bread Compact ESP32 DevKit (面包板)" + depends on IDF_TARGET_ESP32 + config BOARD_TYPE_BREAD_COMPACT_ESP32_LCD + bool "Bread Compact ESP32 DevKit + LCD (面包板)" + depends on IDF_TARGET_ESP32 + config BOARD_TYPE_XMINI_C3_V3 + bool "Xmini C3 V3" + depends on IDF_TARGET_ESP32C3 + config BOARD_TYPE_XMINI_C3_4G + bool "Xmini C3 4G" + depends on IDF_TARGET_ESP32C3 + config BOARD_TYPE_XMINI_C3 + bool "Xmini C3" + depends on IDF_TARGET_ESP32C3 + config BOARD_TYPE_ESP32S3_KORVO2_V3 + bool "ESP32S3 KORVO2 V3" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP_SPARKBOT + bool "ESP-SparkBot" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP_SPOT_S3 + bool "ESP-Spot-S3" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP_HI + bool "ESP-HI" + depends on IDF_TARGET_ESP32C3 + config BOARD_TYPE_ECHOEAR + bool "EchoEar" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP_BOX_3 + bool "ESP BOX 3" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP_BOX + bool "ESP BOX" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP_BOX_LITE + bool "ESP BOX Lite" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_KEVIN_BOX_1 + bool "Kevin Box 1" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_KEVIN_BOX_2 + bool "Kevin Box 2" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_KEVIN_C3 + bool "Kevin C3" + depends on IDF_TARGET_ESP32C3 + config BOARD_TYPE_KEVIN_SP_V3_DEV + bool "Kevin SP V3" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_KEVIN_SP_V4_DEV + bool "Kevin SP V4" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32_CGC + bool "ESP32 CGC" + depends on IDF_TARGET_ESP32 + config BOARD_TYPE_ESP32_CGC_144 + bool "ESP32 CGC 144" + depends on IDF_TARGET_ESP32 + config BOARD_TYPE_KEVIN_YUYING_313LCD + bool "鱼鹰科技 3.13LCD" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_LICHUANG_DEV + bool "立创·实战派 ESP32-S3" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_LICHUANG_C3_DEV + bool "立创·实战派 ESP32-C3" + depends on IDF_TARGET_ESP32C3 + config BOARD_TYPE_DF_K10 + bool "DFRobot 行空板 k10" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_DF_S3_AI_CAM + bool "DFRobot ESP32-S3 AI智能摄像头模块" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_MAGICLICK_2P4 + bool "神奇按钮 Magiclick_2.4" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_MAGICLICK_2P5 + bool "神奇按钮 Magiclick_2.5" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_MAGICLICK_C3 + bool "神奇按钮 Magiclick_C3" + depends on IDF_TARGET_ESP32C3 + config BOARD_TYPE_MAGICLICK_C3_V2 + bool "神奇按钮 Magiclick_C3_v2" + depends on IDF_TARGET_ESP32C3 + config BOARD_TYPE_M5STACK_CORE_S3 + bool "M5Stack CoreS3" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_M5STACK_CORE_TAB5 + bool "M5Stack Tab5" + depends on IDF_TARGET_ESP32P4 + config BOARD_TYPE_ATOMS3_ECHO_BASE + bool "AtomS3 + Echo Base" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATOMS3R_ECHO_BASE + bool "AtomS3R + Echo Base" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE + bool "AtomS3R CAM/M12 + Echo Base" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATOM_ECHOS3R + bool "AtomEchoS3R" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATOMMATRIX_ECHO_BASE + bool "AtomMatrix + Echo Base" + depends on IDF_TARGET_ESP32 + config BOARD_TYPE_ESP32S3_AUDIO_BOARD + bool "Waveshare ESP32-S3-Audio-Board" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8 + bool "Waveshare ESP32-S3-Touch-AMOLED-1.8" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06 + bool "Waveshare ESP32-S3-Touch-AMOLED-2.06" + config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75 + bool "Waveshare ESP32-S3-Touch-AMOLED-1.75" + config BOARD_TYPE_ESP32S3_Touch_LCD_4B + bool "Waveshare ESP32-S3-Touch-LCD-4B" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32S3_Touch_LCD_1_85C + bool "Waveshare ESP32-S3-Touch-LCD-1.85C" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32S3_Touch_LCD_1_85 + bool "Waveshare ESP32-S3-Touch-LCD-1.85" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32S3_Touch_LCD_1_46 + bool "Waveshare ESP32-S3-Touch-LCD-1.46" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32C6_LCD_1_69 + bool "Waveshare ESP32-C6-LCD-1.69" + depends on IDF_TARGET_ESP32C6 + config BOARD_TYPE_ESP32C6_Touch_AMOLED_1_43 + bool "Waveshare ESP32-C6-Touch-AMOLOED-1.43" + depends on IDF_TARGET_ESP32C6 + config BOARD_TYPE_ESP32S3_Touch_LCD_3_49 + bool "Waveshare ESP32-S3-Touch-LCD-3.49" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32S3_Touch_LCD_3_5 + bool "Waveshare ESP32-S3-Touch-LCD-3.5" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32S3_Touch_LCD_3_5B + bool "Waveshare ESP32-S3-Touch-LCD-3.5B" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32P4_NANO + bool "Waveshare ESP32-P4-NANO" + depends on IDF_TARGET_ESP32P4 + config BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B + bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-4B" + depends on IDF_TARGET_ESP32P4 + config BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC + bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C or ESP32-P4-WIFI6-Touch-LCD-4C" + depends on IDF_TARGET_ESP32P4 + config BOARD_TYPE_TUDOUZI + bool "土豆子" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_LILYGO_T_CIRCLE_S3 + bool "LILYGO T-Circle-S3" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1 + bool "LILYGO T-CameraPlus-S3_V1_0_V1_1" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 + bool "LILYGO T-CameraPlus-S3_V1_2" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA + bool "LILYGO T-Display-S3-Pro-MVSRLora" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA_NO_BATTERY + bool "LILYGO T-Display-S3-Pro-MVSRLora_No_Battery" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_MOVECALL_MOJI_ESP32S3 + bool "Movecall Moji 小智AI衍生版" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_MOVECALL_CUICAN_ESP32S3 + bool "Movecall CuiCan 璀璨·AI吊坠" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATK_DNESP32S3 + bool "正点原子DNESP32S3开发板" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATK_DNESP32S3_BOX + bool "正点原子DNESP32S3-BOX" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATK_DNESP32S3_BOX0 + bool "正点原子DNESP32S3-BOX0" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI + bool "正点原子DNESP32S3-BOX2-WIFI" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATK_DNESP32S3_BOX2_4G + bool "正点原子DNESP32S3-BOX2-4G" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATK_DNESP32S3M_WIFI + bool "正点原子DNESP32S3M-WIFI" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATK_DNESP32S3M_4G + bool "正点原子DNESP32S3M-4G" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_DU_CHATX + bool "嘟嘟开发板CHATX(wifi)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32S3_Taiji_Pi + bool "太极小派esp32s3" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_XINGZHI_Cube_0_85TFT_WIFI + bool "无名科技星智0.85(WIFI)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_XINGZHI_Cube_0_85TFT_ML307 + bool "无名科技星智0.85(ML307)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_XINGZHI_Cube_0_96OLED_WIFI + bool "无名科技星智0.96(WIFI)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_XINGZHI_Cube_0_96OLED_ML307 + bool "无名科技星智0.96(ML307)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_XINGZHI_Cube_1_54TFT_WIFI + bool "无名科技星智1.54(WIFI)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_XINGZHI_Cube_1_54TFT_ML307 + bool "无名科技星智1.54(ML307)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_SENSECAP_WATCHER + bool "SenseCAP Watcher" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_DOIT_S3_AIBOX + bool "四博智联AI陪伴盒子" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_MIXGO_NOVA + bool "元控·青春" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_GENJUTECH_S3_1_54TFT + bool "亘具科技1.54(s3)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP_S3_LCD_EV_Board + bool "乐鑫ESP S3 LCD EV Board开发板" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP_S3_LCD_EV_Board_2 + bool "乐鑫ESP S3 LCD EV Board 2开发板" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ZHENGCHEN_1_54TFT_WIFI + bool "征辰科技1.54(WIFI)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ZHENGCHEN_1_54TFT_ML307 + bool "征辰科技1.54(ML307)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_MINSI_K08_DUAL + bool "敏思科技K08(DUAL)" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32_S3_1_54_MUMA + bool "Spotpear ESP32-S3-1.54-MUMA" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ESP32_S3_1_28_BOX + bool "Spotpear ESP32-S3-1.28-BOX" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_OTTO_ROBOT + bool "ottoRobot" + depends on IDF_TARGET_ESP32S3 + select LV_USE_GIF + select LV_GIF_CACHE_DECODE_DATA + config BOARD_TYPE_ELECTRON_BOT + bool "electronBot" + depends on IDF_TARGET_ESP32S3 + select LV_USE_GIF + select LV_GIF_CACHE_DECODE_DATA + config BOARD_TYPE_JIUCHUAN + bool "九川智能" + config BOARD_TYPE_LABPLUS_MPYTHON_V3 + bool "labplus mpython_v3 board" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_LABPLUS_LEDONG_V2 + bool "labplus ledong_v2 board" + depends on IDF_TARGET_ESP32S3 + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_SURFER_C3_1_14TFT + bool "Surfer-C3-1.14TFT" + depends on IDF_TARGET_ESP32C3 + config BOARD_TYPE_YUNLIAO_S3 + bool "小智云聊-S3" + depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_JINAO_S3 + bool "极脑ESP32-S3开发板" + depends on IDF_TARGET_ESP32S3 +endchoice + +choice ESP_S3_LCD_EV_Board_Version_TYPE + depends on BOARD_TYPE_ESP_S3_LCD_EV_Board + prompt "EV_BOARD Type" + default ESP_S3_LCD_EV_Board_1p4 + config ESP_S3_LCD_EV_Board_1p4 + bool "乐鑫ESP32_S3_LCD_EV_Board-MB_V1.4" + config ESP_S3_LCD_EV_Board_1p5 + bool "乐鑫ESP32_S3_LCD_EV_Board-MB_V1.5" +endchoice + +choice DISPLAY_OLED_TYPE + depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_ESP32 + prompt "OLED Type" + default OLED_SSD1306_128X32 + help + OLED Monochrome Display Type + config OLED_SSD1306_128X32 + bool "SSD1306 128*32" + config OLED_SSD1306_128X64 + bool "SSD1306 128*64" + config OLED_SH1106_128X64 + bool "SH1106 128*64" +endchoice + +choice DISPLAY_LCD_TYPE + depends on BOARD_TYPE_BREAD_COMPACT_WIFI_LCD || BOARD_TYPE_BREAD_COMPACT_ESP32_LCD || BOARD_TYPE_ESP32_CGC || BOARD_TYPE_ESP32P4_NANO || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC || BOARD_TYPE_BREAD_COMPACT_WIFI_CAM + prompt "LCD Type" + default LCD_ST7789_240X320 + help + LCD Display Type + config LCD_ST7789_240X320 + bool "ST7789 240*320, IPS" + config LCD_ST7789_240X320_NO_IPS + bool "ST7789 240*320, Non-IPS" + config LCD_ST7789_170X320 + bool "ST7789 170*320" + config LCD_ST7789_172X320 + bool "ST7789 172*320" + config LCD_ST7789_240X280 + bool "ST7789 240*280" + config LCD_ST7789_240X240 + bool "ST7789 240*240" + config LCD_ST7789_240X240_7PIN + bool "ST7789 240*240, 7PIN" + config LCD_ST7789_240X135 + bool "ST7789 240*135" + config LCD_ST7735_128X160 + bool "ST7735 128*160" + config LCD_ST7735_128X128 + bool "ST7735 128*128" + config LCD_ST7796_320X480 + bool "ST7796 320*480 IPS" + config LCD_ST7796_320X480_NO_IPS + bool "ST7796 320*480, Non-IPS" + config LCD_ILI9341_240X320 + bool "ILI9341 240*320" + config LCD_ILI9341_240X320_NO_IPS + bool "ILI9341 240*320, Non-IPS" + config LCD_GC9A01_240X240 + bool "GC9A01 240*240 Circle" + config LCD_TYPE_800_1280_10_1_INCH + bool "Waveshare 101M-8001280-IPS-CT-K Display" + config LCD_TYPE_800_1280_10_1_INCH_A + bool "Waveshare 10.1-DSI-TOUCH-A Display" + config LCD_TYPE_800_800_3_4_INCH + bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C with 800*800 3.4inch round display" + config LCD_TYPE_720_720_4_INCH + bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-4C with 720*720 4inch round display" + config LCD_CUSTOM + bool "Custom LCD (自定义屏幕参数)" +endchoice + +choice DISPLAY_ESP32S3_KORVO2_V3 + depends on BOARD_TYPE_ESP32S3_KORVO2_V3 + prompt "ESP32S3_KORVO2_V3 LCD Type" + default ESP32S3_KORVO2_V3_LCD_ST7789 + help + LCD Display Type + config ESP32S3_KORVO2_V3_LCD_ST7789 + bool "ST7789 240*280" + config ESP32S3_KORVO2_V3_LCD_ILI9341 + bool "ILI9341 240*320" +endchoice + +choice DISPLAY_ESP32S3_AUDIO_BOARD + depends on BOARD_TYPE_ESP32S3_AUDIO_BOARD + prompt "ESP32S3_AUDIO_BOARD LCD Type" + default AUDIO_BOARD_LCD_JD9853 + help + LCD Display Type + config AUDIO_BOARD_LCD_JD9853 + bool "JD9853 320*172" + config AUDIO_BOARD_LCD_ST7789 + bool "ST7789 240*320" +endchoice + +choice DISPLAY_STYLE + prompt "Select display style" + default USE_DEFAULT_MESSAGE_STYLE + help + Select display style for Xiaozhi device + + config USE_DEFAULT_MESSAGE_STYLE + bool "Enable default message style" + + config USE_WECHAT_MESSAGE_STYLE + bool "Enable WeChat Message Style" + + config USE_EMOTE_MESSAGE_STYLE + bool "Emote animation style" + depends on BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ECHOEAR +endchoice + +choice WAKE_WORD_TYPE + prompt "Wake Word Implementation Type" + default USE_AFE_WAKE_WORD if (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM + default WAKE_WORD_DISABLED + help + Choose the type of wake word implementation to use + + config WAKE_WORD_DISABLED + bool "Disabled" + help + Disable wake word detection + + config USE_ESP_WAKE_WORD + bool "Wakenet model without AFE" + depends on IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C5 || IDF_TARGET_ESP32C6 || (IDF_TARGET_ESP32 && SPIRAM) + help + Support ESP32 C3、ESP32 C5 与 ESP32 C6, and (ESP32 with PSRAM) + + config USE_AFE_WAKE_WORD + bool "Wakenet model with AFE" + depends on (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM + help + Support AEC if available, requires ESP32 S3 and PSRAM + + config USE_CUSTOM_WAKE_WORD + bool "Multinet model (Custom Wake Word)" + depends on (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM + help + Requires ESP32 S3 and PSRAM + +endchoice + +config CUSTOM_WAKE_WORD + string "Custom Wake Word" + default "xiao tu dou" + depends on USE_CUSTOM_WAKE_WORD + help + Custom Wake Word, use pinyin for Chinese, separated by spaces + +config CUSTOM_WAKE_WORD_DISPLAY + string "Custom Wake Word Display" + default "小土豆" + depends on USE_CUSTOM_WAKE_WORD + help + Greeting sent to the server after wake word detection + +config CUSTOM_WAKE_WORD_THRESHOLD + int "Custom Wake Word Threshold (%)" + default 20 + range 1 99 + depends on USE_CUSTOM_WAKE_WORD + help + Custom Wake Word Threshold, range 1-99, the smaller the more sensitive, default 20 + +config SEND_WAKE_WORD_DATA + bool "Send Wake Word Data" + default y + depends on USE_AFE_WAKE_WORD || USE_CUSTOM_WAKE_WORD + help + Send wake word data to the server as the first message of the conversation and wait for response + +config USE_AUDIO_PROCESSOR + bool "Enable Audio Noise Reduction" + default y + depends on (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM + help + Requires ESP32 S3 and PSRAM + +config USE_DEVICE_AEC + bool "Enable Device-Side AEC" + default n + depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE \ + || BOARD_TYPE_LICHUANG_DEV || BOARD_TYPE_ESP32S3_KORVO2_V3 || BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75 \ + || BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06 || BOARD_TYPE_ESP32S3_Touch_LCD_4B || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B \ + || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC || BOARD_TYPE_ESP_S3_LCD_EV_Board_2 || BOARD_TYPE_YUNLIAO_S3 \ + || BOARD_TYPE_ECHOEAR || BOARD_TYPE_ESP32S3_Touch_LCD_3_49) + help + To work properly, device-side AEC requires a clean output reference path from the speaker signal and physical acoustic isolation between the microphone and speaker. + +config USE_SERVER_AEC + bool "Enable Server-Side AEC (Unstable)" + default n + depends on USE_AUDIO_PROCESSOR + help + To work perperly, server-side AEC requires server support + +config USE_AUDIO_DEBUGGER + bool "Enable Audio Debugger" + default n + help + Enable audio debugger, send audio data through UDP to the host machine + +config AUDIO_DEBUG_UDP_SERVER + string "Audio Debug UDP Server Address" + default "192.168.2.100:8000" + depends on USE_AUDIO_DEBUGGER + help + UDP server address, format: IP:PORT, used to receive audio debugging data + +config USE_ACOUSTIC_WIFI_PROVISIONING + bool "Enable Acoustic WiFi Provisioning" + default n + help + Enable acoustic WiFi provisioning, use audio signal to transmit WiFi configuration data + +config RECEIVE_CUSTOM_MESSAGE + bool "Enable Custom Message Reception" + default n + help + Enable custom message reception, allow the device to receive custom messages from the server (preferably through the MQTT protocol) + +menu TAIJIPAI_S3_CONFIG + depends on BOARD_TYPE_ESP32S3_Taiji_Pi + choice I2S_TYPE_TAIJIPI_S3 + prompt "taiji-pi-S3 I2S Type" + default TAIJIPAI_I2S_TYPE_STD + help + I2S 类型选择 + config TAIJIPAI_I2S_TYPE_STD + bool "I2S Type STD" + config TAIJIPAI_I2S_TYPE_PDM + bool "I2S Type PDM" + endchoice + + config I2S_USE_2SLOT + bool "Enable I2S 2 Slot" + default n + help + 启动双声道 +endmenu + +endmenu diff --git a/main/alarm_manager.cc b/main/alarm_manager.cc new file mode 100644 index 0000000..972f48e --- /dev/null +++ b/main/alarm_manager.cc @@ -0,0 +1,671 @@ +#include "alarm_manager.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "AlarmManager" +#define ALARM_SETTINGS_NAMESPACE "alarms" + +AlarmManager::AlarmManager() + : initialized_(false), next_alarm_id_(1), + default_snooze_minutes_(5), default_max_snooze_count_(3) { +} + +AlarmManager::~AlarmManager() { + Cleanup(); +} + +void AlarmManager::Initialize() { + if (initialized_) { + return; + } + + ESP_LOGI(TAG, "Initializing Alarm Manager"); + + // 初始化设置存储 + settings_ = std::make_unique(ALARM_SETTINGS_NAMESPACE, true); + + // 从存储中加载闹钟 + LoadAlarmsFromStorage(); + + // 获取下一个闹钟ID + next_alarm_id_ = settings_->GetInt("next_id", 1); + + initialized_ = true; + ESP_LOGI(TAG, "Alarm Manager initialized with %d alarms", alarms_.size()); +} + +void AlarmManager::Cleanup() { + if (!initialized_) { + return; + } + + ESP_LOGI(TAG, "Cleaning up Alarm Manager"); + + // 停止所有活动的闹钟 + StopAllActiveAlarms(); + + // 保存数据 + SaveAlarmsToStorage(); + + // 清理资源 + { + std::lock_guard lock(alarms_mutex_); + alarms_.clear(); + } + + settings_.reset(); + initialized_ = false; +} + +int AlarmManager::AddAlarm(int hour, int minute, AlarmRepeatMode repeat_mode, + const std::string& label, const std::string& music_name) { + if (!initialized_) { + ESP_LOGE(TAG, "AlarmManager not initialized"); + return -1; + } + + if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { + ESP_LOGE(TAG, "Invalid time: %02d:%02d", hour, minute); + return -1; + } + + std::lock_guard lock(alarms_mutex_); + + auto alarm = std::make_unique(); + alarm->id = GetNextAlarmId(); + alarm->hour = hour; + alarm->minute = minute; + alarm->repeat_mode = repeat_mode; + alarm->label = label; + alarm->music_name = music_name; + alarm->status = kAlarmEnabled; + alarm->snooze_minutes = default_snooze_minutes_; + alarm->max_snooze_count = default_max_snooze_count_; + + // 设置星期掩码 + switch (repeat_mode) { + case kAlarmDaily: + alarm->weekdays_mask = 0b1111111; // 每天 + break; + case kAlarmWeekdays: + alarm->weekdays_mask = 0b0111110; // 周一到周五 + break; + case kAlarmWeekends: + alarm->weekdays_mask = 0b1000001; // 周六周日 + break; + default: + alarm->weekdays_mask = 0; // 一次性或自定义 + break; + } + + int alarm_id = alarm->id; + alarms_.push_back(std::move(alarm)); + + // 保存到存储 + SaveAlarmToStorage(*alarms_.back()); + settings_->SetInt("next_id", next_alarm_id_); + + ESP_LOGI(TAG, "Added alarm %d: %02d:%02d, repeat=%d, label='%s', music='%s'", + alarm_id, hour, minute, repeat_mode, label.c_str(), music_name.c_str()); + + return alarm_id; +} + +bool AlarmManager::RemoveAlarm(int alarm_id) { + if (!initialized_) { + return false; + } + + std::lock_guard lock(alarms_mutex_); + + auto it = std::find_if(alarms_.begin(), alarms_.end(), + [alarm_id](const auto& alarm) { return alarm->id == alarm_id; }); + + if (it != alarms_.end()) { + ESP_LOGI(TAG, "Removing alarm %d", alarm_id); + alarms_.erase(it); + RemoveAlarmFromStorage(alarm_id); + return true; + } + + ESP_LOGW(TAG, "Alarm %d not found for removal", alarm_id); + return false; +} + +bool AlarmManager::EnableAlarm(int alarm_id, bool enabled) { + if (!initialized_) { + return false; + } + + std::lock_guard lock(alarms_mutex_); + + auto it = std::find_if(alarms_.begin(), alarms_.end(), + [alarm_id](const auto& alarm) { return alarm->id == alarm_id; }); + + if (it != alarms_.end()) { + (*it)->status = enabled ? kAlarmEnabled : kAlarmDisabled; + SaveAlarmToStorage(**it); + ESP_LOGI(TAG, "Alarm %d %s", alarm_id, enabled ? "enabled" : "disabled"); + return true; + } + + return false; +} + +bool AlarmManager::ModifyAlarm(int alarm_id, int hour, int minute, AlarmRepeatMode repeat_mode, + const std::string& label, const std::string& music_name) { + if (!initialized_) { + return false; + } + + if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { + ESP_LOGE(TAG, "Invalid time: %02d:%02d", hour, minute); + return false; + } + + std::lock_guard lock(alarms_mutex_); + + auto it = std::find_if(alarms_.begin(), alarms_.end(), + [alarm_id](const auto& alarm) { return alarm->id == alarm_id; }); + + if (it != alarms_.end()) { + (*it)->hour = hour; + (*it)->minute = minute; + (*it)->repeat_mode = repeat_mode; + (*it)->label = label; + (*it)->music_name = music_name; + + // 重新设置星期掩码 + switch (repeat_mode) { + case kAlarmDaily: + (*it)->weekdays_mask = 0b1111111; + break; + case kAlarmWeekdays: + (*it)->weekdays_mask = 0b0111110; + break; + case kAlarmWeekends: + (*it)->weekdays_mask = 0b1000001; + break; + default: + break; + } + + SaveAlarmToStorage(**it); + ESP_LOGI(TAG, "Modified alarm %d: %02d:%02d", alarm_id, hour, minute); + return true; + } + + return false; +} + +std::vector AlarmManager::GetAllAlarms() const { + if (!initialized_) { + return {}; + } + + std::lock_guard lock(alarms_mutex_); + std::vector result; + + for (const auto& alarm : alarms_) { + result.push_back(*alarm); + } + + return result; +} + +AlarmItem* AlarmManager::GetAlarm(int alarm_id) { + if (!initialized_) { + return nullptr; + } + + std::lock_guard lock(alarms_mutex_); + + auto it = std::find_if(alarms_.begin(), alarms_.end(), + [alarm_id](const auto& alarm) { return alarm->id == alarm_id; }); + + return (it != alarms_.end()) ? it->get() : nullptr; +} + +std::vector AlarmManager::GetActiveAlarms() const { + if (!initialized_) { + return {}; + } + + std::lock_guard lock(alarms_mutex_); + std::vector result; + + for (const auto& alarm : alarms_) { + if (alarm->status == kAlarmTriggered || alarm->status == kAlarmSnoozed) { + result.push_back(*alarm); + } + } + + return result; +} + +std::string AlarmManager::GetNextAlarmInfo() const { + if (!initialized_) { + return "闹钟管理器未初始化"; + } + + std::lock_guard lock(alarms_mutex_); + + // 查找下一个要触发的闹钟 + AlarmItem* next_alarm = nullptr; + int64_t current_time = GetCurrentTimeInMinutes(); + int current_weekday = GetCurrentWeekday(); + int64_t min_time_diff = 24 * 60 * 7; // 一周的分钟数 + + for (const auto& alarm : alarms_) { + if (alarm->status != kAlarmEnabled) { + continue; + } + + // 计算今天和明天的时间差 + int64_t alarm_time = alarm->hour * 60 + alarm->minute; + + for (int day_offset = 0; day_offset < 7; day_offset++) { + int check_weekday = (current_weekday + day_offset) % 7; + + if (day_offset == 0 && alarm_time <= current_time) { + continue; // 今天已经过了 + } + + if (alarm->repeat_mode == kAlarmOnce && day_offset > 0) { + continue; // 一次性闹钟只检查今天 + } + + if (IsWeekdayActive(*alarm, check_weekday)) { + int64_t time_diff = day_offset * 24 * 60 + alarm_time - current_time; + if (day_offset == 0) { + time_diff = alarm_time - current_time; + } + + if (time_diff < min_time_diff) { + min_time_diff = time_diff; + next_alarm = alarm.get(); + } + break; + } + } + } + + if (!next_alarm) { + return "无活动闹钟"; + } + + // 格式化下一个闹钟信息 + std::ostringstream oss; + oss << "下个闹钟: " << FormatTime(next_alarm->hour, next_alarm->minute); + + if (min_time_diff < 24 * 60) { + int hours = min_time_diff / 60; + int minutes = min_time_diff % 60; + if (hours > 0) { + oss << " (" << hours << "小时" << minutes << "分钟后)"; + } else { + oss << " (" << minutes << "分钟后)"; + } + } else { + int days = min_time_diff / (24 * 60); + oss << " (" << days << "天后)"; + } + + if (!next_alarm->label.empty()) { + oss << " - " << next_alarm->label; + } + + return oss.str(); +} + +bool AlarmManager::SnoozeAlarm(int alarm_id) { + if (!initialized_) { + return false; + } + + std::lock_guard lock(alarms_mutex_); + + auto it = std::find_if(alarms_.begin(), alarms_.end(), + [alarm_id](const auto& alarm) { return alarm->id == alarm_id; }); + + if (it != alarms_.end() && (*it)->status == kAlarmTriggered) { + if ((*it)->snooze_count < (*it)->max_snooze_count) { + (*it)->status = kAlarmSnoozed; + (*it)->snooze_count++; + (*it)->next_snooze_time = esp_timer_get_time() / 1000000 + (*it)->snooze_minutes * 60; + + ESP_LOGI(TAG, "Snoozed alarm %d for %d minutes (count: %d/%d)", + alarm_id, (*it)->snooze_minutes, (*it)->snooze_count, (*it)->max_snooze_count); + + if (on_alarm_snoozed_) { + on_alarm_snoozed_(**it); + } + return true; + } else { + ESP_LOGI(TAG, "Alarm %d exceeded max snooze count, stopping", alarm_id); + StopAlarm(alarm_id); + } + } + + return false; +} + +bool AlarmManager::StopAlarm(int alarm_id) { + if (!initialized_) { + return false; + } + + std::lock_guard lock(alarms_mutex_); + + auto it = std::find_if(alarms_.begin(), alarms_.end(), + [alarm_id](const auto& alarm) { return alarm->id == alarm_id; }); + + if (it != alarms_.end() && + ((*it)->status == kAlarmTriggered || (*it)->status == kAlarmSnoozed)) { + + AlarmStatus old_status = (*it)->status; + (*it)->status = ((*it)->repeat_mode == kAlarmOnce) ? kAlarmDisabled : kAlarmEnabled; + (*it)->snooze_count = 0; + (*it)->next_snooze_time = 0; + + ESP_LOGI(TAG, "Stopped alarm %d", alarm_id); + + if (on_alarm_stopped_) { + on_alarm_stopped_(**it); + } + + return true; + } + + return false; +} + +void AlarmManager::StopAllActiveAlarms() { + if (!initialized_) { + return; + } + + std::lock_guard lock(alarms_mutex_); + + for (auto& alarm : alarms_) { + if (alarm->status == kAlarmTriggered || alarm->status == kAlarmSnoozed) { + alarm->status = (alarm->repeat_mode == kAlarmOnce) ? kAlarmDisabled : kAlarmEnabled; + alarm->snooze_count = 0; + alarm->next_snooze_time = 0; + + if (on_alarm_stopped_) { + on_alarm_stopped_(*alarm); + } + } + } + + ESP_LOGI(TAG, "Stopped all active alarms"); +} + +void AlarmManager::CheckAlarms() { + if (!initialized_) { + return; + } + + int64_t current_time_seconds = esp_timer_get_time() / 1000000; + int64_t current_time_minutes = GetCurrentTimeInMinutes(); + + std::lock_guard lock(alarms_mutex_); + + for (auto& alarm : alarms_) { + // 检查贪睡闹钟 + if (alarm->status == kAlarmSnoozed && + current_time_seconds >= alarm->next_snooze_time) { + + alarm->status = kAlarmTriggered; + alarm->next_snooze_time = 0; + + ESP_LOGI(TAG, "Snooze ended for alarm %d, triggering again", alarm->id); + + if (on_alarm_triggered_) { + on_alarm_triggered_(*alarm); + } + continue; + } + + // 检查正常闹钟触发 + if (alarm->status != kAlarmEnabled) { + continue; + } + + int64_t alarm_time_minutes = alarm->hour * 60 + alarm->minute; + + // 检查是否是触发时间(精确到分钟) + if (alarm_time_minutes != current_time_minutes) { + continue; + } + + // 检查是否应该在今天触发 + if (!ShouldTriggerToday(*alarm)) { + continue; + } + + // 防止重复触发(同一分钟内) + int64_t current_time_in_seconds = esp_timer_get_time() / 1000000; + if (alarm->last_triggered_time > 0 && + (current_time_in_seconds - alarm->last_triggered_time) < 60) { + continue; + } + + // 触发闹钟 + alarm->status = kAlarmTriggered; + alarm->last_triggered_time = current_time_in_seconds; + alarm->snooze_count = 0; + + ESP_LOGI(TAG, "Triggering alarm %d: %02d:%02d - %s", + alarm->id, alarm->hour, alarm->minute, alarm->label.c_str()); + + if (on_alarm_triggered_) { + on_alarm_triggered_(*alarm); + } + } +} + +void AlarmManager::SetAlarmTriggeredCallback(AlarmTriggeredCallback callback) { + on_alarm_triggered_ = callback; +} + +void AlarmManager::SetAlarmSnoozeCallback(AlarmSnoozeCallback callback) { + on_alarm_snoozed_ = callback; +} + +void AlarmManager::SetAlarmStopCallback(AlarmStopCallback callback) { + on_alarm_stopped_ = callback; +} + +void AlarmManager::SetDefaultSnoozeMinutes(int minutes) { + default_snooze_minutes_ = std::max(1, std::min(60, minutes)); +} + +void AlarmManager::SetDefaultMaxSnoozeCount(int count) { + default_max_snooze_count_ = std::max(0, std::min(10, count)); +} + +// 静态工具方法 +std::string AlarmManager::FormatTime(int hour, int minute) { + std::ostringstream oss; + oss << std::setfill('0') << std::setw(2) << hour + << ":" << std::setfill('0') << std::setw(2) << minute; + return oss.str(); +} + +std::string AlarmManager::FormatAlarmTime(const AlarmItem& alarm) { + std::ostringstream oss; + oss << FormatTime(alarm.hour, alarm.minute); + + switch (alarm.repeat_mode) { + case kAlarmOnce: + oss << " (一次)"; + break; + case kAlarmDaily: + oss << " (每日)"; + break; + case kAlarmWeekdays: + oss << " (工作日)"; + break; + case kAlarmWeekends: + oss << " (周末)"; + break; + case kAlarmCustom: + oss << " (自定义)"; + break; + } + + return oss.str(); +} + +bool AlarmManager::IsWeekdayActive(const AlarmItem& alarm, int weekday) { + if (alarm.repeat_mode == kAlarmOnce) { + return true; // 一次性闹钟在任何一天都可以触发 + } + + return (alarm.weekdays_mask & (1 << weekday)) != 0; +} + +// 私有方法实现 +void AlarmManager::LoadAlarmsFromStorage() { + std::lock_guard lock(alarms_mutex_); + alarms_.clear(); + + // 读取闹钟数量 + int alarm_count = settings_->GetInt("count", 0); + ESP_LOGI(TAG, "Loading %d alarms from storage", alarm_count); + + for (int i = 0; i < alarm_count; i++) { + std::string alarm_key = "alarm_" + std::to_string(i); + std::string alarm_json = settings_->GetString(alarm_key); + + if (alarm_json.empty()) { + continue; + } + + cJSON* json = cJSON_Parse(alarm_json.c_str()); + if (!json) { + ESP_LOGW(TAG, "Failed to parse alarm JSON: %s", alarm_key.c_str()); + continue; + } + + auto alarm = std::make_unique(); + + // 解析JSON数据 + if (auto id = cJSON_GetObjectItem(json, "id")) alarm->id = id->valueint; + if (auto hour = cJSON_GetObjectItem(json, "hour")) alarm->hour = hour->valueint; + if (auto minute = cJSON_GetObjectItem(json, "minute")) alarm->minute = minute->valueint; + if (auto repeat = cJSON_GetObjectItem(json, "repeat")) alarm->repeat_mode = (AlarmRepeatMode)repeat->valueint; + if (auto mask = cJSON_GetObjectItem(json, "weekdays")) alarm->weekdays_mask = mask->valueint; + if (auto status = cJSON_GetObjectItem(json, "status")) alarm->status = (AlarmStatus)status->valueint; + if (auto label = cJSON_GetObjectItem(json, "label")) alarm->label = label->valuestring; + if (auto music = cJSON_GetObjectItem(json, "music")) alarm->music_name = music->valuestring; + if (auto snooze_min = cJSON_GetObjectItem(json, "snooze_minutes")) alarm->snooze_minutes = snooze_min->valueint; + if (auto max_snooze = cJSON_GetObjectItem(json, "max_snooze")) alarm->max_snooze_count = max_snooze->valueint; + + // 重置运行时状态 + alarm->snooze_count = 0; + alarm->last_triggered_time = 0; + alarm->next_snooze_time = 0; + if (alarm->status == kAlarmTriggered || alarm->status == kAlarmSnoozed) { + alarm->status = kAlarmEnabled; + } + + alarms_.push_back(std::move(alarm)); + cJSON_Delete(json); + } + + ESP_LOGI(TAG, "Loaded %d alarms successfully", alarms_.size()); +} + +void AlarmManager::SaveAlarmsToStorage() { + if (!settings_) { + return; + } + + std::lock_guard lock(alarms_mutex_); + + // 保存闹钟数量 + settings_->SetInt("count", alarms_.size()); + + // 保存每个闹钟 + for (size_t i = 0; i < alarms_.size(); i++) { + std::string alarm_key = "alarm_" + std::to_string(i); + SaveAlarmToStorage(*alarms_[i]); + } + + ESP_LOGI(TAG, "Saved %d alarms to storage", alarms_.size()); +} + +void AlarmManager::SaveAlarmToStorage(const AlarmItem& alarm) { + if (!settings_) { + return; + } + + cJSON* json = cJSON_CreateObject(); + + cJSON_AddNumberToObject(json, "id", alarm.id); + cJSON_AddNumberToObject(json, "hour", alarm.hour); + cJSON_AddNumberToObject(json, "minute", alarm.minute); + cJSON_AddNumberToObject(json, "repeat", alarm.repeat_mode); + cJSON_AddNumberToObject(json, "weekdays", alarm.weekdays_mask); + cJSON_AddNumberToObject(json, "status", alarm.status); + cJSON_AddStringToObject(json, "label", alarm.label.c_str()); + cJSON_AddStringToObject(json, "music", alarm.music_name.c_str()); + cJSON_AddNumberToObject(json, "snooze_minutes", alarm.snooze_minutes); + cJSON_AddNumberToObject(json, "max_snooze", alarm.max_snooze_count); + + char* json_string = cJSON_PrintUnformatted(json); + + // 找到这个闹钟在数组中的位置 + auto it = std::find_if(alarms_.begin(), alarms_.end(), + [&alarm](const auto& a) { return a->id == alarm.id; }); + + if (it != alarms_.end()) { + size_t index = std::distance(alarms_.begin(), it); + std::string alarm_key = "alarm_" + std::to_string(index); + settings_->SetString(alarm_key, json_string); + } + + cJSON_free(json_string); + cJSON_Delete(json); +} + +void AlarmManager::RemoveAlarmFromStorage(int alarm_id) { + // 重新保存所有闹钟(简单实现) + SaveAlarmsToStorage(); +} + +int AlarmManager::GetNextAlarmId() { + return next_alarm_id_++; +} + +bool AlarmManager::ShouldTriggerToday(const AlarmItem& alarm) const { + if (alarm.repeat_mode == kAlarmOnce) { + return true; + } + + int weekday = GetCurrentWeekday(); + return IsWeekdayActive(alarm, weekday); +} + +int64_t AlarmManager::GetCurrentTimeInMinutes() const { + time_t now; + time(&now); + struct tm* timeinfo = localtime(&now); + return timeinfo->tm_hour * 60 + timeinfo->tm_min; +} + +int AlarmManager::GetCurrentWeekday() const { + time_t now; + time(&now); + struct tm* timeinfo = localtime(&now); + return timeinfo->tm_wday; // 0=周日, 1=周一, ..., 6=周六 +} diff --git a/main/alarm_manager.h b/main/alarm_manager.h new file mode 100644 index 0000000..81fcdbc --- /dev/null +++ b/main/alarm_manager.h @@ -0,0 +1,140 @@ +#ifndef ALARM_MANAGER_H +#define ALARM_MANAGER_H + +#include +#include +#include +#include +#include +#include "settings.h" + +// 闹钟重复模式 +enum AlarmRepeatMode { + kAlarmOnce = 0, // 一次性闹钟 + kAlarmDaily = 1, // 每日重复 + kAlarmWeekdays = 2, // 工作日(周一到周五) + kAlarmWeekends = 3, // 周末(周六周日) + kAlarmCustom = 4 // 自定义星期(使用weekdays_mask) +}; + +// 闹钟状态 +enum AlarmStatus { + kAlarmEnabled = 0, // 启用 + kAlarmDisabled = 1, // 禁用 + kAlarmTriggered = 2, // 已触发(等待贪睡或关闭) + kAlarmSnoozed = 3 // 贪睡中 +}; + +// 闹钟项结构 +struct AlarmItem { + int id; // 闹钟ID + int hour; // 小时 (0-23) + int minute; // 分钟 (0-59) + AlarmRepeatMode repeat_mode; // 重复模式 + uint8_t weekdays_mask; // 星期掩码 (bit0=周日, bit1=周一, ..., bit6=周六) + AlarmStatus status; // 闹钟状态 + std::string label; // 闹钟标签/备注 + std::string music_name; // 指定的音乐名称(空则使用默认铃声) + int snooze_count; // 当前贪睡次数 + int max_snooze_count; // 最大贪睡次数 (默认3次) + int snooze_minutes; // 贪睡间隔(分钟,默认5分钟) + int64_t last_triggered_time; // 上次触发时间戳(避免重复触发) + int64_t next_snooze_time; // 下次贪睡时间戳 + + AlarmItem() : id(0), hour(0), minute(0), repeat_mode(kAlarmOnce), + weekdays_mask(0), status(kAlarmEnabled), label(""), + music_name(""), snooze_count(0), max_snooze_count(3), + snooze_minutes(5), last_triggered_time(0), next_snooze_time(0) {} +}; + +// 闹钟触发回调类型 +using AlarmTriggeredCallback = std::function; +using AlarmSnoozeCallback = std::function; +using AlarmStopCallback = std::function; + +class AlarmManager { +public: + static AlarmManager& GetInstance() { + static AlarmManager instance; + return instance; + } + + // 删除拷贝构造函数和赋值运算符 + AlarmManager(const AlarmManager&) = delete; + AlarmManager& operator=(const AlarmManager&) = delete; + + // 初始化和清理 + void Initialize(); + void Cleanup(); + + // 闹钟管理 + int AddAlarm(int hour, int minute, AlarmRepeatMode repeat_mode = kAlarmOnce, + const std::string& label = "", const std::string& music_name = ""); + bool RemoveAlarm(int alarm_id); + bool EnableAlarm(int alarm_id, bool enabled = true); + bool ModifyAlarm(int alarm_id, int hour, int minute, AlarmRepeatMode repeat_mode = kAlarmOnce, + const std::string& label = "", const std::string& music_name = ""); + + // 查询功能 + std::vector GetAllAlarms() const; + AlarmItem* GetAlarm(int alarm_id); + std::vector GetActiveAlarms() const; + std::string GetNextAlarmInfo() const; // 获取下一个闹钟的信息字符串 + + // 贪睡和停止 + bool SnoozeAlarm(int alarm_id); + bool StopAlarm(int alarm_id); + void StopAllActiveAlarms(); + + // 时间检查 (由Application的CLOCK_TICK调用) + void CheckAlarms(); + + // 回调设置 + void SetAlarmTriggeredCallback(AlarmTriggeredCallback callback); + void SetAlarmSnoozeCallback(AlarmSnoozeCallback callback); + void SetAlarmStopCallback(AlarmStopCallback callback); + + // 配置设置 + void SetDefaultSnoozeMinutes(int minutes); + void SetDefaultMaxSnoozeCount(int count); + + // 时间工具 + static std::string FormatTime(int hour, int minute); + static std::string FormatAlarmTime(const AlarmItem& alarm); + static bool IsWeekdayActive(const AlarmItem& alarm, int weekday); // 0=周日, 1=周一, ..., 6=周六 + +private: + AlarmManager(); + ~AlarmManager(); + + // 内部方法 + void LoadAlarmsFromStorage(); + void SaveAlarmsToStorage(); + void SaveAlarmToStorage(const AlarmItem& alarm); + void RemoveAlarmFromStorage(int alarm_id); + + int GetNextAlarmId(); + bool ShouldTriggerToday(const AlarmItem& alarm) const; + int64_t GetCurrentTimeInMinutes() const; // 获取当前时间的分钟数(从午夜开始) + int GetCurrentWeekday() const; // 获取当前星期几 + + // 成员变量 + std::vector> alarms_; + std::unique_ptr settings_; + bool initialized_; + int next_alarm_id_; + + // 配置 + int default_snooze_minutes_; + int default_max_snooze_count_; + + // 回调函数 + AlarmTriggeredCallback on_alarm_triggered_; + AlarmSnoozeCallback on_alarm_snoozed_; + AlarmStopCallback on_alarm_stopped_; + + // 互斥锁保护 + mutable std::mutex alarms_mutex_; +}; + +#endif // ALARM_MANAGER_H diff --git a/main/application.cc b/main/application.cc index b2381d8..e5e9a30 100644 --- a/main/application.cc +++ b/main/application.cc @@ -1,984 +1,1222 @@ -#include "application.h" -#include "board.h" -#include "display.h" -#include "system_info.h" -#include "audio_codec.h" -#include "mqtt_protocol.h" -#include "websocket_protocol.h" -#include "assets/lang_config.h" -#include "mcp_server.h" -#include "assets.h" -#include "settings.h" - -#include -#include -#include -#include -#include -#include - -#define TAG "Application" - - -static const char* const STATE_STRINGS[] = { - "unknown", - "starting", - "configuring", - "idle", - "connecting", - "listening", - "speaking", - "upgrading", - "activating", - "audio_testing", - "fatal_error", - "invalid_state" -}; - -Application::Application() { - event_group_ = xEventGroupCreate(); - -#if CONFIG_USE_DEVICE_AEC && CONFIG_USE_SERVER_AEC -#error "CONFIG_USE_DEVICE_AEC and CONFIG_USE_SERVER_AEC cannot be enabled at the same time" -#elif CONFIG_USE_DEVICE_AEC - aec_mode_ = kAecOnDeviceSide; -#elif CONFIG_USE_SERVER_AEC - aec_mode_ = kAecOnServerSide; -#else - aec_mode_ = kAecOff; -#endif - - esp_timer_create_args_t clock_timer_args = { - .callback = [](void* arg) { - Application* app = (Application*)arg; - xEventGroupSetBits(app->event_group_, MAIN_EVENT_CLOCK_TICK); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "clock_timer", - .skip_unhandled_events = true - }; - esp_timer_create(&clock_timer_args, &clock_timer_handle_); -} - -Application::~Application() { - if (clock_timer_handle_ != nullptr) { - esp_timer_stop(clock_timer_handle_); - esp_timer_delete(clock_timer_handle_); - } - vEventGroupDelete(event_group_); -} - -void Application::CheckAssetsVersion() { - auto& board = Board::GetInstance(); - auto display = board.GetDisplay(); - auto assets = board.GetAssets(); - if (!assets) { - ESP_LOGE(TAG, "Assets is not set for board %s", BOARD_NAME); - return; - } - - if (!assets->partition_valid()) { - ESP_LOGE(TAG, "Assets partition is not valid for board %s", BOARD_NAME); - return; - } - - Settings settings("assets", true); - // Check if there is a new assets need to be downloaded - std::string download_url = settings.GetString("download_url"); - if (!download_url.empty()) { - settings.EraseKey("download_url"); - } - if (download_url.empty() && !assets->checksum_valid()) { - download_url = assets->default_assets_url(); - } - - if (!download_url.empty()) { - char message[256]; - snprintf(message, sizeof(message), Lang::Strings::FOUND_NEW_ASSETS, download_url.c_str()); - Alert(Lang::Strings::LOADING_ASSETS, message, "cloud_arrow_down", Lang::Sounds::OGG_UPGRADE); - - // Wait for the audio service to be idle for 3 seconds - vTaskDelay(pdMS_TO_TICKS(3000)); - SetDeviceState(kDeviceStateUpgrading); - board.SetPowerSaveMode(false); - display->SetChatMessage("system", Lang::Strings::PLEASE_WAIT); - - bool success = assets->Download(download_url, [display](int progress, size_t speed) -> void { - std::thread([display, progress, speed]() { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024); - display->SetChatMessage("system", buffer); - }).detach(); - }); - - board.SetPowerSaveMode(true); - vTaskDelay(pdMS_TO_TICKS(1000)); - - if (!success) { - Alert(Lang::Strings::ERROR, Lang::Strings::DOWNLOAD_ASSETS_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); - vTaskDelay(pdMS_TO_TICKS(2000)); - return; - } - } - - // Apply assets - assets->Apply(); - display->SetChatMessage("system", ""); - display->SetEmotion("microchip_ai"); -} - -void Application::CheckNewVersion(Ota& ota) { - const int MAX_RETRY = 10; - int retry_count = 0; - int retry_delay = 10; // 初始重试延迟为10秒 - - auto& board = Board::GetInstance(); - while (true) { - SetDeviceState(kDeviceStateActivating); - auto display = board.GetDisplay(); - display->SetStatus(Lang::Strings::CHECKING_NEW_VERSION); - - if (!ota.CheckVersion()) { - retry_count++; - if (retry_count >= MAX_RETRY) { - ESP_LOGE(TAG, "Too many retries, exit version check"); - return; - } - - char buffer[256]; - snprintf(buffer, sizeof(buffer), Lang::Strings::CHECK_NEW_VERSION_FAILED, retry_delay, ota.GetCheckVersionUrl().c_str()); - Alert(Lang::Strings::ERROR, buffer, "cloud_slash", Lang::Sounds::OGG_EXCLAMATION); - - ESP_LOGW(TAG, "Check new version failed, retry in %d seconds (%d/%d)", retry_delay, retry_count, MAX_RETRY); - for (int i = 0; i < retry_delay; i++) { - vTaskDelay(pdMS_TO_TICKS(1000)); - if (device_state_ == kDeviceStateIdle) { - break; - } - } - retry_delay *= 2; // 每次重试后延迟时间翻倍 - continue; - } - retry_count = 0; - retry_delay = 10; // 重置重试延迟时间 - - if (ota.HasNewVersion()) { - if (UpgradeFirmware(ota)) { - return; // This line will never be reached after reboot - } - // If upgrade failed, continue to normal operation (don't break, just fall through) - } - - // No new version, mark the current version as valid - ota.MarkCurrentVersionValid(); - if (!ota.HasActivationCode() && !ota.HasActivationChallenge()) { - xEventGroupSetBits(event_group_, MAIN_EVENT_CHECK_NEW_VERSION_DONE); - // Exit the loop if done checking new version - break; - } - - display->SetStatus(Lang::Strings::ACTIVATION); - // Activation code is shown to the user and waiting for the user to input - if (ota.HasActivationCode()) { - ShowActivationCode(ota.GetActivationCode(), ota.GetActivationMessage()); - } - - // This will block the loop until the activation is done or timeout - for (int i = 0; i < 10; ++i) { - ESP_LOGI(TAG, "Activating... %d/%d", i + 1, 10); - esp_err_t err = ota.Activate(); - if (err == ESP_OK) { - xEventGroupSetBits(event_group_, MAIN_EVENT_CHECK_NEW_VERSION_DONE); - break; - } else if (err == ESP_ERR_TIMEOUT) { - vTaskDelay(pdMS_TO_TICKS(3000)); - } else { - vTaskDelay(pdMS_TO_TICKS(10000)); - } - if (device_state_ == kDeviceStateIdle) { - break; - } - } - } -} - -void Application::ShowActivationCode(const std::string& code, const std::string& message) { - struct digit_sound { - char digit; - const std::string_view& sound; - }; - static const std::array digit_sounds{{ - digit_sound{'0', Lang::Sounds::OGG_0}, - digit_sound{'1', Lang::Sounds::OGG_1}, - digit_sound{'2', Lang::Sounds::OGG_2}, - digit_sound{'3', Lang::Sounds::OGG_3}, - digit_sound{'4', Lang::Sounds::OGG_4}, - digit_sound{'5', Lang::Sounds::OGG_5}, - digit_sound{'6', Lang::Sounds::OGG_6}, - digit_sound{'7', Lang::Sounds::OGG_7}, - digit_sound{'8', Lang::Sounds::OGG_8}, - digit_sound{'9', Lang::Sounds::OGG_9} - }}; - - // This sentence uses 9KB of SRAM, so we need to wait for it to finish - Alert(Lang::Strings::ACTIVATION, message.c_str(), "link", Lang::Sounds::OGG_ACTIVATION); - - for (const auto& digit : code) { - auto it = std::find_if(digit_sounds.begin(), digit_sounds.end(), - [digit](const digit_sound& ds) { return ds.digit == digit; }); - if (it != digit_sounds.end()) { - audio_service_.PlaySound(it->sound); - } - } -} - -void Application::Alert(const char* status, const char* message, const char* emotion, const std::string_view& sound) { - ESP_LOGW(TAG, "Alert [%s] %s: %s", emotion, status, message); - auto display = Board::GetInstance().GetDisplay(); - display->SetStatus(status); - display->SetEmotion(emotion); - display->SetChatMessage("system", message); - if (!sound.empty()) { - audio_service_.PlaySound(sound); - } -} - -void Application::DismissAlert() { - if (device_state_ == kDeviceStateIdle) { - auto display = Board::GetInstance().GetDisplay(); - display->SetStatus(Lang::Strings::STANDBY); - display->SetEmotion("neutral"); - display->SetChatMessage("system", ""); - } -} - -void Application::ToggleChatState() { - if (device_state_ == kDeviceStateActivating) { - SetDeviceState(kDeviceStateIdle); - return; - } else if (device_state_ == kDeviceStateWifiConfiguring) { - audio_service_.EnableAudioTesting(true); - SetDeviceState(kDeviceStateAudioTesting); - return; - } else if (device_state_ == kDeviceStateAudioTesting) { - audio_service_.EnableAudioTesting(false); - SetDeviceState(kDeviceStateWifiConfiguring); - return; - } - - if (!protocol_) { - ESP_LOGE(TAG, "Protocol not initialized"); - return; - } - - if (device_state_ == kDeviceStateIdle) { - Schedule([this]() { - if (!protocol_->IsAudioChannelOpened()) { - SetDeviceState(kDeviceStateConnecting); - if (!protocol_->OpenAudioChannel()) { - return; - } - } - - SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); - }); - } else if (device_state_ == kDeviceStateSpeaking) { - Schedule([this]() { - AbortSpeaking(kAbortReasonNone); - }); - } else if (device_state_ == kDeviceStateListening) { - Schedule([this]() { - protocol_->CloseAudioChannel(); - }); - } -} - -void Application::StartListening() { - if (device_state_ == kDeviceStateActivating) { - SetDeviceState(kDeviceStateIdle); - return; - } else if (device_state_ == kDeviceStateWifiConfiguring) { - audio_service_.EnableAudioTesting(true); - SetDeviceState(kDeviceStateAudioTesting); - return; - } - - if (!protocol_) { - ESP_LOGE(TAG, "Protocol not initialized"); - return; - } - - if (device_state_ == kDeviceStateIdle) { - Schedule([this]() { - if (!protocol_->IsAudioChannelOpened()) { - SetDeviceState(kDeviceStateConnecting); - if (!protocol_->OpenAudioChannel()) { - return; - } - } - - SetListeningMode(kListeningModeManualStop); - }); - } else if (device_state_ == kDeviceStateSpeaking) { - Schedule([this]() { - AbortSpeaking(kAbortReasonNone); - SetListeningMode(kListeningModeManualStop); - }); - } -} - -void Application::StopListening() { - if (device_state_ == kDeviceStateAudioTesting) { - audio_service_.EnableAudioTesting(false); - SetDeviceState(kDeviceStateWifiConfiguring); - return; - } - - const std::array valid_states = { - kDeviceStateListening, - kDeviceStateSpeaking, - kDeviceStateIdle, - }; - // If not valid, do nothing - if (std::find(valid_states.begin(), valid_states.end(), device_state_) == valid_states.end()) { - return; - } - - Schedule([this]() { - if (device_state_ == kDeviceStateListening) { - protocol_->SendStopListening(); - SetDeviceState(kDeviceStateIdle); - } - }); -} - -void Application::Start() { - auto& board = Board::GetInstance(); - SetDeviceState(kDeviceStateStarting); - - /* Setup the display */ - auto display = board.GetDisplay(); - - /* Setup the audio service */ - auto codec = board.GetAudioCodec(); - audio_service_.Initialize(codec); - audio_service_.Start(); - - AudioServiceCallbacks callbacks; - callbacks.on_send_queue_available = [this]() { - xEventGroupSetBits(event_group_, MAIN_EVENT_SEND_AUDIO); - }; - callbacks.on_wake_word_detected = [this](const std::string& wake_word) { - xEventGroupSetBits(event_group_, MAIN_EVENT_WAKE_WORD_DETECTED); - }; - callbacks.on_vad_change = [this](bool speaking) { - xEventGroupSetBits(event_group_, MAIN_EVENT_VAD_CHANGE); - }; - audio_service_.SetCallbacks(callbacks); - - /* Start the clock timer to update the status bar */ - esp_timer_start_periodic(clock_timer_handle_, 1000000); - - /* Wait for the network to be ready */ - board.StartNetwork(); - - // Update the status bar immediately to show the network state - display->UpdateStatusBar(true); - - // Check for new assets version - CheckAssetsVersion(); - - // Check for new firmware version or get the MQTT broker address - Ota ota; - CheckNewVersion(ota); - - // Initialize the protocol - display->SetStatus(Lang::Strings::LOADING_PROTOCOL); - - // Add MCP common tools before initializing the protocol - auto& mcp_server = McpServer::GetInstance(); - mcp_server.AddCommonTools(); - mcp_server.AddUserOnlyTools(); - - if (ota.HasMqttConfig()) { - protocol_ = std::make_unique(); - } else if (ota.HasWebsocketConfig()) { - protocol_ = std::make_unique(); - } else { - ESP_LOGW(TAG, "No protocol specified in the OTA config, using MQTT"); - protocol_ = std::make_unique(); - } - - protocol_->OnConnected([this]() { - DismissAlert(); - }); - - protocol_->OnNetworkError([this](const std::string& message) { - last_error_message_ = message; - xEventGroupSetBits(event_group_, MAIN_EVENT_ERROR); - }); - protocol_->OnIncomingAudio([this](std::unique_ptr packet) { - if (device_state_ == kDeviceStateSpeaking) { - audio_service_.PushPacketToDecodeQueue(std::move(packet)); - } - }); - protocol_->OnAudioChannelOpened([this, codec, &board]() { - board.SetPowerSaveMode(false); - if (protocol_->server_sample_rate() != codec->output_sample_rate()) { - ESP_LOGW(TAG, "Server sample rate %d does not match device output sample rate %d, resampling may cause distortion", - protocol_->server_sample_rate(), codec->output_sample_rate()); - } - }); - protocol_->OnAudioChannelClosed([this, &board]() { - board.SetPowerSaveMode(true); - Schedule([this]() { - auto display = Board::GetInstance().GetDisplay(); - display->SetChatMessage("system", ""); - SetDeviceState(kDeviceStateIdle); - }); - }); - protocol_->OnIncomingJson([this, display](const cJSON* root) { - // Parse JSON data - auto type = cJSON_GetObjectItem(root, "type"); - if (strcmp(type->valuestring, "tts") == 0) { - auto state = cJSON_GetObjectItem(root, "state"); - if (strcmp(state->valuestring, "start") == 0) { - Schedule([this]() { - aborted_ = false; - if (device_state_ == kDeviceStateIdle || device_state_ == kDeviceStateListening) { - SetDeviceState(kDeviceStateSpeaking); - } - }); - } else if (strcmp(state->valuestring, "stop") == 0) { - Schedule([this]() { - if (device_state_ == kDeviceStateSpeaking) { - if (listening_mode_ == kListeningModeManualStop) { - SetDeviceState(kDeviceStateIdle); - } else { - SetDeviceState(kDeviceStateListening); - } - } - }); - } else if (strcmp(state->valuestring, "sentence_start") == 0) { - auto text = cJSON_GetObjectItem(root, "text"); - if (cJSON_IsString(text)) { - ESP_LOGI(TAG, "<< %s", text->valuestring); - Schedule([this, display, message = std::string(text->valuestring)]() { - display->SetChatMessage("assistant", message.c_str()); - }); - } - } - } else if (strcmp(type->valuestring, "stt") == 0) { - auto text = cJSON_GetObjectItem(root, "text"); - if (cJSON_IsString(text)) { - ESP_LOGI(TAG, ">> %s", text->valuestring); - Schedule([this, display, message = std::string(text->valuestring)]() { - display->SetChatMessage("user", message.c_str()); - }); - } - } else if (strcmp(type->valuestring, "llm") == 0) { - auto emotion = cJSON_GetObjectItem(root, "emotion"); - if (cJSON_IsString(emotion)) { - Schedule([this, display, emotion_str = std::string(emotion->valuestring)]() { - display->SetEmotion(emotion_str.c_str()); - }); - } - } else if (strcmp(type->valuestring, "mcp") == 0) { - auto payload = cJSON_GetObjectItem(root, "payload"); - if (cJSON_IsObject(payload)) { - McpServer::GetInstance().ParseMessage(payload); - } - } else if (strcmp(type->valuestring, "system") == 0) { - auto command = cJSON_GetObjectItem(root, "command"); - if (cJSON_IsString(command)) { - ESP_LOGI(TAG, "System command: %s", command->valuestring); - if (strcmp(command->valuestring, "reboot") == 0) { - // Do a reboot if user requests a OTA update - Schedule([this]() { - Reboot(); - }); - } else { - ESP_LOGW(TAG, "Unknown system command: %s", command->valuestring); - } - } - } else if (strcmp(type->valuestring, "alert") == 0) { - auto status = cJSON_GetObjectItem(root, "status"); - auto message = cJSON_GetObjectItem(root, "message"); - auto emotion = cJSON_GetObjectItem(root, "emotion"); - if (cJSON_IsString(status) && cJSON_IsString(message) && cJSON_IsString(emotion)) { - Alert(status->valuestring, message->valuestring, emotion->valuestring, Lang::Sounds::OGG_VIBRATION); - } else { - ESP_LOGW(TAG, "Alert command requires status, message and emotion"); - } -#if CONFIG_RECEIVE_CUSTOM_MESSAGE - } else if (strcmp(type->valuestring, "custom") == 0) { - auto payload = cJSON_GetObjectItem(root, "payload"); - ESP_LOGI(TAG, "Received custom message: %s", cJSON_PrintUnformatted(root)); - if (cJSON_IsObject(payload)) { - Schedule([this, display, payload_str = std::string(cJSON_PrintUnformatted(payload))]() { - display->SetChatMessage("system", payload_str.c_str()); - }); - } else { - ESP_LOGW(TAG, "Invalid custom message format: missing payload"); - } -#endif - } else { - ESP_LOGW(TAG, "Unknown message type: %s", type->valuestring); - } - }); - bool protocol_started = protocol_->Start(); - - SystemInfo::PrintHeapStats(); - SetDeviceState(kDeviceStateIdle); - - has_server_time_ = ota.HasServerTime(); - if (protocol_started) { - std::string message = std::string(Lang::Strings::VERSION) + ota.GetCurrentVersion(); - display->ShowNotification(message.c_str()); - display->SetChatMessage("system", ""); - // Play the success sound to indicate the device is ready - audio_service_.PlaySound(Lang::Sounds::OGG_SUCCESS); - } - - // Start the main event loop task with priority 3 - xTaskCreate([](void* arg) { - ((Application*)arg)->MainEventLoop(); - vTaskDelete(NULL); - }, "main_event_loop", 2048 * 4, this, 3, &main_event_loop_task_handle_); -} - -// Add a async task to MainLoop -void Application::Schedule(std::function callback) { - { - std::lock_guard lock(mutex_); - main_tasks_.push_back(std::move(callback)); - } - xEventGroupSetBits(event_group_, MAIN_EVENT_SCHEDULE); -} - -// The Main Event Loop controls the chat state and websocket connection -// If other tasks need to access the websocket or chat state, -// they should use Schedule to call this function -void Application::MainEventLoop() { - while (true) { - auto bits = xEventGroupWaitBits(event_group_, MAIN_EVENT_SCHEDULE | - MAIN_EVENT_SEND_AUDIO | - MAIN_EVENT_WAKE_WORD_DETECTED | - MAIN_EVENT_VAD_CHANGE | - MAIN_EVENT_CLOCK_TICK | - MAIN_EVENT_ERROR, pdTRUE, pdFALSE, portMAX_DELAY); - - if (bits & MAIN_EVENT_ERROR) { - SetDeviceState(kDeviceStateIdle); - Alert(Lang::Strings::ERROR, last_error_message_.c_str(), "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); - } - - if (bits & MAIN_EVENT_SEND_AUDIO) { - while (auto packet = audio_service_.PopPacketFromSendQueue()) { - if (protocol_ && !protocol_->SendAudio(std::move(packet))) { - break; - } - } - } - - if (bits & MAIN_EVENT_WAKE_WORD_DETECTED) { - OnWakeWordDetected(); - } - - if (bits & MAIN_EVENT_VAD_CHANGE) { - if (device_state_ == kDeviceStateListening) { - auto led = Board::GetInstance().GetLed(); - led->OnStateChanged(); - } - } - - if (bits & MAIN_EVENT_SCHEDULE) { - std::unique_lock lock(mutex_); - auto tasks = std::move(main_tasks_); - lock.unlock(); - for (auto& task : tasks) { - task(); - } - } - - if (bits & MAIN_EVENT_CLOCK_TICK) { - clock_ticks_++; - auto display = Board::GetInstance().GetDisplay(); - display->UpdateStatusBar(); - - // Print the debug info every 10 seconds - if (clock_ticks_ % 10 == 0) { - // SystemInfo::PrintTaskCpuUsage(pdMS_TO_TICKS(1000)); - // SystemInfo::PrintTaskList(); - SystemInfo::PrintHeapStats(); - } - } - } -} - -void Application::OnWakeWordDetected() { - if (!protocol_) { - return; - } - - if (device_state_ == kDeviceStateIdle) { - audio_service_.EncodeWakeWord(); - - if (!protocol_->IsAudioChannelOpened()) { - SetDeviceState(kDeviceStateConnecting); - if (!protocol_->OpenAudioChannel()) { - audio_service_.EnableWakeWordDetection(true); - return; - } - } - - auto wake_word = audio_service_.GetLastWakeWord(); - ESP_LOGI(TAG, "Wake word detected: %s", wake_word.c_str()); -#if CONFIG_USE_AFE_WAKE_WORD || CONFIG_USE_CUSTOM_WAKE_WORD - // Encode and send the wake word data to the server - while (auto packet = audio_service_.PopWakeWordPacket()) { - protocol_->SendAudio(std::move(packet)); - } - // Set the chat state to wake word detected - protocol_->SendWakeWordDetected(wake_word); - SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); -#else - SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); - // Play the pop up sound to indicate the wake word is detected - audio_service_.PlaySound(Lang::Sounds::OGG_POPUP); -#endif - } else if (device_state_ == kDeviceStateSpeaking) { - AbortSpeaking(kAbortReasonWakeWordDetected); - } else if (device_state_ == kDeviceStateActivating) { - SetDeviceState(kDeviceStateIdle); - } -} - -void Application::AbortSpeaking(AbortReason reason) { - ESP_LOGI(TAG, "Abort speaking"); - aborted_ = true; - if (protocol_) { - protocol_->SendAbortSpeaking(reason); - } -} - -void Application::SetListeningMode(ListeningMode mode) { - listening_mode_ = mode; - SetDeviceState(kDeviceStateListening); -} - -void Application::SetDeviceState(DeviceState state) { - if (device_state_ == state) { - return; - } - - clock_ticks_ = 0; - auto previous_state = device_state_; - device_state_ = state; - ESP_LOGI(TAG, "STATE: %s", STATE_STRINGS[device_state_]); - - // Send the state change event - DeviceStateEventManager::GetInstance().PostStateChangeEvent(previous_state, state); - - auto& board = Board::GetInstance(); - auto display = board.GetDisplay(); - auto led = board.GetLed(); - led->OnStateChanged(); - switch (state) { - case kDeviceStateUnknown: - case kDeviceStateIdle: - display->SetStatus(Lang::Strings::STANDBY); - display->SetEmotion("neutral"); - audio_service_.EnableVoiceProcessing(false); - audio_service_.EnableWakeWordDetection(true); - break; - case kDeviceStateConnecting: - display->SetStatus(Lang::Strings::CONNECTING); - display->SetEmotion("neutral"); - display->SetChatMessage("system", ""); - break; - case kDeviceStateListening: - display->SetStatus(Lang::Strings::LISTENING); - display->SetEmotion("neutral"); - - // Make sure the audio processor is running - if (!audio_service_.IsAudioProcessorRunning()) { - // Send the start listening command - protocol_->SendStartListening(listening_mode_); - audio_service_.EnableVoiceProcessing(true); - audio_service_.EnableWakeWordDetection(false); - } - break; - case kDeviceStateSpeaking: - display->SetStatus(Lang::Strings::SPEAKING); - - if (listening_mode_ != kListeningModeRealtime) { - audio_service_.EnableVoiceProcessing(false); - // Only AFE wake word can be detected in speaking mode -#if CONFIG_USE_AFE_WAKE_WORD - audio_service_.EnableWakeWordDetection(true); -#else - audio_service_.EnableWakeWordDetection(false); -#endif - } - audio_service_.ResetDecoder(); - break; - default: - // Do nothing - break; - } -} - -void Application::Reboot() { - ESP_LOGI(TAG, "Rebooting..."); - // Disconnect the audio channel - if (protocol_ && protocol_->IsAudioChannelOpened()) { - protocol_->CloseAudioChannel(); - } - protocol_.reset(); - audio_service_.Stop(); - - vTaskDelay(pdMS_TO_TICKS(1000)); - esp_restart(); -} - -bool Application::UpgradeFirmware(Ota& ota, const std::string& url) { - auto& board = Board::GetInstance(); - auto display = board.GetDisplay(); - - // Use provided URL or get from OTA object - std::string upgrade_url = url.empty() ? ota.GetFirmwareUrl() : url; - std::string version_info = url.empty() ? ota.GetFirmwareVersion() : "(Manual upgrade)"; - - // Close audio channel if it's open - if (protocol_ && protocol_->IsAudioChannelOpened()) { - ESP_LOGI(TAG, "Closing audio channel before firmware upgrade"); - protocol_->CloseAudioChannel(); - } - ESP_LOGI(TAG, "Starting firmware upgrade from URL: %s", upgrade_url.c_str()); - - Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "download", Lang::Sounds::OGG_UPGRADE); - vTaskDelay(pdMS_TO_TICKS(3000)); - - SetDeviceState(kDeviceStateUpgrading); - - std::string message = std::string(Lang::Strings::NEW_VERSION) + version_info; - display->SetChatMessage("system", message.c_str()); - - board.SetPowerSaveMode(false); - audio_service_.Stop(); - vTaskDelay(pdMS_TO_TICKS(1000)); - - bool upgrade_success = ota.StartUpgradeFromUrl(upgrade_url, [display](int progress, size_t speed) { - std::thread([display, progress, speed]() { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024); - display->SetChatMessage("system", buffer); - }).detach(); - }); - - if (!upgrade_success) { - // Upgrade failed, restart audio service and continue running - ESP_LOGE(TAG, "Firmware upgrade failed, restarting audio service and continuing operation..."); - audio_service_.Start(); // Restart audio service - board.SetPowerSaveMode(true); // Restore power save mode - Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); - vTaskDelay(pdMS_TO_TICKS(3000)); - return false; - } else { - // Upgrade success, reboot immediately - ESP_LOGI(TAG, "Firmware upgrade successful, rebooting..."); - display->SetChatMessage("system", "Upgrade successful, rebooting..."); - vTaskDelay(pdMS_TO_TICKS(1000)); // Brief pause to show message - Reboot(); - return true; - } -} - -void Application::WakeWordInvoke(const std::string& wake_word) { - if (device_state_ == kDeviceStateIdle) { - ToggleChatState(); - Schedule([this, wake_word]() { - if (protocol_) { - protocol_->SendWakeWordDetected(wake_word); - } - }); - } else if (device_state_ == kDeviceStateSpeaking) { - Schedule([this]() { - AbortSpeaking(kAbortReasonNone); - }); - } else if (device_state_ == kDeviceStateListening) { - Schedule([this]() { - if (protocol_) { - protocol_->CloseAudioChannel(); - } - }); - } -} - -bool Application::CanEnterSleepMode() { - if (device_state_ != kDeviceStateIdle) { - return false; - } - - if (protocol_ && protocol_->IsAudioChannelOpened()) { - return false; - } - - if (!audio_service_.IsIdle()) { - return false; - } - - // Now it is safe to enter sleep mode - return true; -} - -void Application::SendMcpMessage(const std::string& payload) { - if (protocol_ == nullptr) { - return; - } - - // Make sure you are using main thread to send MCP message - if (xTaskGetCurrentTaskHandle() == main_event_loop_task_handle_) { - ESP_LOGI(TAG, "Send MCP message in main thread"); - protocol_->SendMcpMessage(payload); - } else { - ESP_LOGI(TAG, "Send MCP message in sub thread"); - Schedule([this, payload = std::move(payload)]() { - protocol_->SendMcpMessage(payload); - }); - } -} - -void Application::SetAecMode(AecMode mode) { - aec_mode_ = mode; - Schedule([this]() { - auto& board = Board::GetInstance(); - auto display = board.GetDisplay(); - switch (aec_mode_) { - case kAecOff: - audio_service_.EnableDeviceAec(false); - display->ShowNotification(Lang::Strings::RTC_MODE_OFF); - break; - case kAecOnServerSide: - audio_service_.EnableDeviceAec(false); - display->ShowNotification(Lang::Strings::RTC_MODE_ON); - break; - case kAecOnDeviceSide: - audio_service_.EnableDeviceAec(true); - display->ShowNotification(Lang::Strings::RTC_MODE_ON); - break; - } - - // If the AEC mode is changed, close the audio channel - if (protocol_ && protocol_->IsAudioChannelOpened()) { - protocol_->CloseAudioChannel(); - } - }); -} - -// 新增:接收外部音频数据(如音乐播放) -void Application::AddAudioData(AudioStreamPacket&& packet) { - auto codec = Board::GetInstance().GetAudioCodec(); - if (device_state_ == kDeviceStateIdle && codec->output_enabled()) { - // packet.payload包含的是原始PCM数据(int16_t) - if (packet.payload.size() >= 2) { - size_t num_samples = packet.payload.size() / sizeof(int16_t); - std::vector pcm_data(num_samples); - memcpy(pcm_data.data(), packet.payload.data(), packet.payload.size()); - - // 检查采样率是否匹配,如果不匹配则进行简单重采样 - if (packet.sample_rate != codec->output_sample_rate()) { - // ESP_LOGI(TAG, "Resampling music audio from %d to %d Hz", - // packet.sample_rate, codec->output_sample_rate()); - - // 验证采样率参数 - if (packet.sample_rate <= 0 || codec->output_sample_rate() <= 0) { - ESP_LOGE(TAG, "Invalid sample rates: %d -> %d", - packet.sample_rate, codec->output_sample_rate()); - return; - } - - std::vector resampled; - - if (packet.sample_rate > codec->output_sample_rate()) { - ESP_LOGI(TAG, "Music Player: Adjust the sampling rate from %d Hz to %d Hz", - codec->output_sample_rate(), packet.sample_rate); - - // 尝试动态切换采样率 - if (codec->SetOutputSampleRate(packet.sample_rate)) { - ESP_LOGI(TAG, "Successfully switched to music playback sampling rate: %d Hz", packet.sample_rate); - } else { - ESP_LOGW(TAG, "Unable to switch sampling rate, continue using current sampling rate: %d Hz", codec->output_sample_rate()); - } - } else { - if (packet.sample_rate > codec->output_sample_rate()) { - // 下采样:简单丢弃部分样本 - float downsample_ratio = static_cast(packet.sample_rate) / codec->output_sample_rate(); - size_t expected_size = static_cast(pcm_data.size() / downsample_ratio + 0.5f); - std::vector resampled(expected_size); - size_t resampled_index = 0; - - for (size_t i = 0; i < pcm_data.size(); ++i) { - if (i % static_cast(downsample_ratio) == 0) { - resampled[resampled_index++] = pcm_data[i]; - } - } - - pcm_data = std::move(resampled); - ESP_LOGI(TAG, "Downsampled %d -> %d samples (ratio: %.2f)", - pcm_data.size(), resampled.size(), downsample_ratio); - } else if (packet.sample_rate < codec->output_sample_rate()) { - // 上采样:线性插值 - float upsample_ratio = codec->output_sample_rate() / static_cast(packet.sample_rate); - size_t expected_size = static_cast(pcm_data.size() * upsample_ratio + 0.5f); - resampled.reserve(expected_size); - - for (size_t i = 0; i < pcm_data.size(); ++i) { - // 添加原始样本 - resampled.push_back(pcm_data[i]); - - - // 计算需要插值的样本数 - int interpolation_count = static_cast(upsample_ratio) - 1; - if (interpolation_count > 0 && i + 1 < pcm_data.size()) { - int16_t current = pcm_data[i]; - int16_t next = pcm_data[i + 1]; - for (int j = 1; j <= interpolation_count; ++j) { - float t = static_cast(j) / (interpolation_count + 1); - int16_t interpolated = static_cast(current + (next - current) * t); - resampled.push_back(interpolated); - } - } else if (interpolation_count > 0) { - // 最后一个样本,直接重复 - for (int j = 1; j <= interpolation_count; ++j) { - resampled.push_back(pcm_data[i]); - } - } - } - - ESP_LOGI(TAG, "Upsampled %d -> %d samples (ratio: %.2f)", - pcm_data.size(), resampled.size(), upsample_ratio); - } - } - - pcm_data = std::move(resampled); - } - - // 确保音频输出已启用 - if (!codec->output_enabled()) { - codec->EnableOutput(true); - } - - // 发送PCM数据到音频编解码器 - codec->OutputData(pcm_data); - - audio_service_.UpdateOutputTimestamp(); - } - } -} - -void Application::PlaySound(const std::string_view& sound) { - audio_service_.PlaySound(sound); +#include "application.h" +#include "board.h" +#include "display.h" +#include "system_info.h" +#include "audio_codec.h" +#include "mqtt_protocol.h" +#include "websocket_protocol.h" +#include "assets/lang_config.h" +#include "mcp_server.h" +#include "assets.h" +#include "settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "Application" + + +static const char* const STATE_STRINGS[] = { + "unknown", + "starting", + "configuring", + "idle", + "connecting", + "listening", + "speaking", + "upgrading", + "activating", + "audio_testing", + "fatal_error", + "invalid_state" +}; + +Application::Application() { + event_group_ = xEventGroupCreate(); + +#if CONFIG_USE_DEVICE_AEC && CONFIG_USE_SERVER_AEC +#error "CONFIG_USE_DEVICE_AEC and CONFIG_USE_SERVER_AEC cannot be enabled at the same time" +#elif CONFIG_USE_DEVICE_AEC + aec_mode_ = kAecOnDeviceSide; +#elif CONFIG_USE_SERVER_AEC + aec_mode_ = kAecOnServerSide; +#else + aec_mode_ = kAecOff; +#endif + + esp_timer_create_args_t clock_timer_args = { + .callback = [](void* arg) { + Application* app = (Application*)arg; + xEventGroupSetBits(app->event_group_, MAIN_EVENT_CLOCK_TICK); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "clock_timer", + .skip_unhandled_events = true + }; + esp_timer_create(&clock_timer_args, &clock_timer_handle_); +} + +Application::~Application() { + if (clock_timer_handle_ != nullptr) { + esp_timer_stop(clock_timer_handle_); + esp_timer_delete(clock_timer_handle_); + } + vEventGroupDelete(event_group_); +} + +void Application::CheckAssetsVersion() { + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + auto& assets = Assets::GetInstance(); + + if (!assets.partition_valid()) { + ESP_LOGW(TAG, "Assets partition is disabled for board %s", BOARD_NAME); + return; + } + + Settings settings("assets", true); + // Check if there is a new assets need to be downloaded + std::string download_url = settings.GetString("download_url"); + + if (!download_url.empty()) { + settings.EraseKey("download_url"); + + char message[256]; + snprintf(message, sizeof(message), Lang::Strings::FOUND_NEW_ASSETS, download_url.c_str()); + Alert(Lang::Strings::LOADING_ASSETS, message, "cloud_arrow_down", Lang::Sounds::OGG_UPGRADE); + + // Wait for the audio service to be idle for 3 seconds + vTaskDelay(pdMS_TO_TICKS(3000)); + SetDeviceState(kDeviceStateUpgrading); + board.SetPowerSaveMode(false); + display->SetChatMessage("system", Lang::Strings::PLEASE_WAIT); + + bool success = assets.Download(download_url, [display](int progress, size_t speed) -> void { + std::thread([display, progress, speed]() { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024); + display->SetChatMessage("system", buffer); + }).detach(); + }); + + board.SetPowerSaveMode(true); + vTaskDelay(pdMS_TO_TICKS(1000)); + + if (!success) { + Alert(Lang::Strings::ERROR, Lang::Strings::DOWNLOAD_ASSETS_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); + vTaskDelay(pdMS_TO_TICKS(2000)); + return; + } + } + + // Apply assets + assets.Apply(); + display->SetChatMessage("system", ""); + display->SetEmotion("microchip_ai"); +} + +void Application::CheckNewVersion(Ota& ota) { + const int MAX_RETRY = 10; + int retry_count = 0; + int retry_delay = 10; // 初始重试延迟为10秒 + + auto& board = Board::GetInstance(); + while (true) { + SetDeviceState(kDeviceStateActivating); + auto display = board.GetDisplay(); + display->SetStatus(Lang::Strings::CHECKING_NEW_VERSION); + + if (!ota.CheckVersion()) { + retry_count++; + if (retry_count >= MAX_RETRY) { + ESP_LOGE(TAG, "Too many retries, exit version check"); + return; + } + + char buffer[256]; + snprintf(buffer, sizeof(buffer), Lang::Strings::CHECK_NEW_VERSION_FAILED, retry_delay, ota.GetCheckVersionUrl().c_str()); + Alert(Lang::Strings::ERROR, buffer, "cloud_slash", Lang::Sounds::OGG_EXCLAMATION); + + ESP_LOGW(TAG, "Check new version failed, retry in %d seconds (%d/%d)", retry_delay, retry_count, MAX_RETRY); + for (int i = 0; i < retry_delay; i++) { + vTaskDelay(pdMS_TO_TICKS(1000)); + if (device_state_ == kDeviceStateIdle) { + break; + } + } + retry_delay *= 2; // 每次重试后延迟时间翻倍 + continue; + } + retry_count = 0; + retry_delay = 10; // 重置重试延迟时间 + + if (ota.HasNewVersion()) { + if (UpgradeFirmware(ota)) { + return; // This line will never be reached after reboot + } + // If upgrade failed, continue to normal operation (don't break, just fall through) + } + + // No new version, mark the current version as valid + ota.MarkCurrentVersionValid(); + if (!ota.HasActivationCode() && !ota.HasActivationChallenge()) { + xEventGroupSetBits(event_group_, MAIN_EVENT_CHECK_NEW_VERSION_DONE); + // Exit the loop if done checking new version + break; + } + + display->SetStatus(Lang::Strings::ACTIVATION); + // Activation code is shown to the user and waiting for the user to input + if (ota.HasActivationCode()) { + ShowActivationCode(ota.GetActivationCode(), ota.GetActivationMessage()); + } + + // This will block the loop until the activation is done or timeout + for (int i = 0; i < 10; ++i) { + ESP_LOGI(TAG, "Activating... %d/%d", i + 1, 10); + esp_err_t err = ota.Activate(); + if (err == ESP_OK) { + xEventGroupSetBits(event_group_, MAIN_EVENT_CHECK_NEW_VERSION_DONE); + break; + } else if (err == ESP_ERR_TIMEOUT) { + vTaskDelay(pdMS_TO_TICKS(3000)); + } else { + vTaskDelay(pdMS_TO_TICKS(10000)); + } + if (device_state_ == kDeviceStateIdle) { + break; + } + } + } +} + +void Application::ShowActivationCode(const std::string& code, const std::string& message) { + struct digit_sound { + char digit; + const std::string_view& sound; + }; + static const std::array digit_sounds{{ + digit_sound{'0', Lang::Sounds::OGG_0}, + digit_sound{'1', Lang::Sounds::OGG_1}, + digit_sound{'2', Lang::Sounds::OGG_2}, + digit_sound{'3', Lang::Sounds::OGG_3}, + digit_sound{'4', Lang::Sounds::OGG_4}, + digit_sound{'5', Lang::Sounds::OGG_5}, + digit_sound{'6', Lang::Sounds::OGG_6}, + digit_sound{'7', Lang::Sounds::OGG_7}, + digit_sound{'8', Lang::Sounds::OGG_8}, + digit_sound{'9', Lang::Sounds::OGG_9} + }}; + + // This sentence uses 9KB of SRAM, so we need to wait for it to finish + Alert(Lang::Strings::ACTIVATION, message.c_str(), "link", Lang::Sounds::OGG_ACTIVATION); + + for (const auto& digit : code) { + auto it = std::find_if(digit_sounds.begin(), digit_sounds.end(), + [digit](const digit_sound& ds) { return ds.digit == digit; }); + if (it != digit_sounds.end()) { + audio_service_.PlaySound(it->sound); + } + } +} + +void Application::Alert(const char* status, const char* message, const char* emotion, const std::string_view& sound) { + ESP_LOGW(TAG, "Alert [%s] %s: %s", emotion, status, message); + auto display = Board::GetInstance().GetDisplay(); + display->SetStatus(status); + display->SetEmotion(emotion); + display->SetChatMessage("system", message); + if (!sound.empty()) { + audio_service_.PlaySound(sound); + } +} + +void Application::DismissAlert() { + if (device_state_ == kDeviceStateIdle) { + auto display = Board::GetInstance().GetDisplay(); + display->SetStatus(Lang::Strings::STANDBY); + display->SetEmotion("neutral"); + display->SetChatMessage("system", ""); + } +} + +void Application::ToggleChatState() { + if (device_state_ == kDeviceStateActivating) { + SetDeviceState(kDeviceStateIdle); + return; + } else if (device_state_ == kDeviceStateWifiConfiguring) { + audio_service_.EnableAudioTesting(true); + SetDeviceState(kDeviceStateAudioTesting); + return; + } else if (device_state_ == kDeviceStateAudioTesting) { + audio_service_.EnableAudioTesting(false); + SetDeviceState(kDeviceStateWifiConfiguring); + return; + } + + if (!protocol_) { + ESP_LOGE(TAG, "Protocol not initialized"); + return; + } + + if (device_state_ == kDeviceStateIdle) { + Schedule([this]() { + if (!protocol_->IsAudioChannelOpened()) { + SetDeviceState(kDeviceStateConnecting); + if (!protocol_->OpenAudioChannel()) { + return; + } + } + + SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); + }); + } else if (device_state_ == kDeviceStateSpeaking) { + Schedule([this]() { + AbortSpeaking(kAbortReasonNone); + }); + } else if (device_state_ == kDeviceStateListening) { + Schedule([this]() { + protocol_->CloseAudioChannel(); + }); + } +} + +void Application::StartListening() { + if (device_state_ == kDeviceStateActivating) { + SetDeviceState(kDeviceStateIdle); + return; + } else if (device_state_ == kDeviceStateWifiConfiguring) { + audio_service_.EnableAudioTesting(true); + SetDeviceState(kDeviceStateAudioTesting); + return; + } + + if (!protocol_) { + ESP_LOGE(TAG, "Protocol not initialized"); + return; + } + + if (device_state_ == kDeviceStateIdle) { + Schedule([this]() { + if (!protocol_->IsAudioChannelOpened()) { + SetDeviceState(kDeviceStateConnecting); + if (!protocol_->OpenAudioChannel()) { + return; + } + } + + SetListeningMode(kListeningModeManualStop); + }); + } else if (device_state_ == kDeviceStateSpeaking) { + Schedule([this]() { + AbortSpeaking(kAbortReasonNone); + SetListeningMode(kListeningModeManualStop); + }); + } +} + +void Application::StopListening() { + if (device_state_ == kDeviceStateAudioTesting) { + audio_service_.EnableAudioTesting(false); + SetDeviceState(kDeviceStateWifiConfiguring); + return; + } + + const std::array valid_states = { + kDeviceStateListening, + kDeviceStateSpeaking, + kDeviceStateIdle, + }; + // If not valid, do nothing + if (std::find(valid_states.begin(), valid_states.end(), device_state_) == valid_states.end()) { + return; + } + + Schedule([this]() { + if (device_state_ == kDeviceStateListening) { + protocol_->SendStopListening(); + SetDeviceState(kDeviceStateIdle); + } + }); +} + +void Application::Start() { + auto& board = Board::GetInstance(); + SetDeviceState(kDeviceStateStarting); + + /* Setup the display */ + auto display = board.GetDisplay(); + + // Print board name/version info + display->SetChatMessage("system", SystemInfo::GetUserAgent().c_str()); + + /* Setup the audio service */ + auto codec = board.GetAudioCodec(); + audio_service_.Initialize(codec); + audio_service_.Start(); + + AudioServiceCallbacks callbacks; + callbacks.on_send_queue_available = [this]() { + xEventGroupSetBits(event_group_, MAIN_EVENT_SEND_AUDIO); + }; + callbacks.on_wake_word_detected = [this](const std::string& wake_word) { + xEventGroupSetBits(event_group_, MAIN_EVENT_WAKE_WORD_DETECTED); + }; + callbacks.on_vad_change = [this](bool speaking) { + xEventGroupSetBits(event_group_, MAIN_EVENT_VAD_CHANGE); + }; + audio_service_.SetCallbacks(callbacks); + + // Start the main event loop task with priority 3 + xTaskCreate([](void* arg) { + ((Application*)arg)->MainEventLoop(); + vTaskDelete(NULL); + }, "main_event_loop", 2048 * 4, this, 3, &main_event_loop_task_handle_); + + /* Start the clock timer to update the status bar */ + esp_timer_start_periodic(clock_timer_handle_, 1000000); + + /* Wait for the network to be ready */ + board.StartNetwork(); + + // Update the status bar immediately to show the network state + display->UpdateStatusBar(true); + + // Check for new assets version + CheckAssetsVersion(); + + // Check for new firmware version or get the MQTT broker address + Ota ota; + CheckNewVersion(ota); + + // Initialize the protocol + display->SetStatus(Lang::Strings::LOADING_PROTOCOL); + + // Add MCP common tools before initializing the protocol + auto& mcp_server = McpServer::GetInstance(); + mcp_server.AddCommonTools(); + mcp_server.AddUserOnlyTools(); + + if (ota.HasMqttConfig()) { + protocol_ = std::make_unique(); + } else if (ota.HasWebsocketConfig()) { + protocol_ = std::make_unique(); + } else { + ESP_LOGW(TAG, "No protocol specified in the OTA config, using MQTT"); + protocol_ = std::make_unique(); + } + + protocol_->OnConnected([this]() { + DismissAlert(); + }); + + protocol_->OnNetworkError([this](const std::string& message) { + last_error_message_ = message; + xEventGroupSetBits(event_group_, MAIN_EVENT_ERROR); + }); + protocol_->OnIncomingAudio([this](std::unique_ptr packet) { + if (device_state_ == kDeviceStateSpeaking) { + audio_service_.PushPacketToDecodeQueue(std::move(packet)); + } + }); + protocol_->OnAudioChannelOpened([this, codec, &board]() { + board.SetPowerSaveMode(false); + if (protocol_->server_sample_rate() != codec->output_sample_rate()) { + ESP_LOGW(TAG, "Server sample rate %d does not match device output sample rate %d, resampling may cause distortion", + protocol_->server_sample_rate(), codec->output_sample_rate()); + } + }); + protocol_->OnAudioChannelClosed([this, &board]() { + board.SetPowerSaveMode(true); + Schedule([this]() { + auto display = Board::GetInstance().GetDisplay(); + display->SetChatMessage("system", ""); + SetDeviceState(kDeviceStateIdle); + }); + }); + protocol_->OnIncomingJson([this, display](const cJSON* root) { + // Parse JSON data + auto type = cJSON_GetObjectItem(root, "type"); + if (strcmp(type->valuestring, "tts") == 0) { + auto state = cJSON_GetObjectItem(root, "state"); + if (strcmp(state->valuestring, "start") == 0) { + Schedule([this]() { + aborted_ = false; + if (device_state_ == kDeviceStateIdle || device_state_ == kDeviceStateListening) { + SetDeviceState(kDeviceStateSpeaking); + } + }); + } else if (strcmp(state->valuestring, "stop") == 0) { + Schedule([this]() { + if (device_state_ == kDeviceStateSpeaking) { + if (listening_mode_ == kListeningModeManualStop) { + SetDeviceState(kDeviceStateIdle); + } else { + SetDeviceState(kDeviceStateListening); + } + } + }); + } else if (strcmp(state->valuestring, "sentence_start") == 0) { + auto text = cJSON_GetObjectItem(root, "text"); + if (cJSON_IsString(text)) { + ESP_LOGI(TAG, "<< %s", text->valuestring); + Schedule([this, display, message = std::string(text->valuestring)]() { + display->SetChatMessage("assistant", message.c_str()); + }); + } + } + } else if (strcmp(type->valuestring, "stt") == 0) { + auto text = cJSON_GetObjectItem(root, "text"); + if (cJSON_IsString(text)) { + ESP_LOGI(TAG, ">> %s", text->valuestring); + Schedule([this, display, message = std::string(text->valuestring)]() { + display->SetChatMessage("user", message.c_str()); + }); + } + } else if (strcmp(type->valuestring, "llm") == 0) { + auto emotion = cJSON_GetObjectItem(root, "emotion"); + if (cJSON_IsString(emotion)) { + Schedule([this, display, emotion_str = std::string(emotion->valuestring)]() { + display->SetEmotion(emotion_str.c_str()); + }); + } + } else if (strcmp(type->valuestring, "mcp") == 0) { + auto payload = cJSON_GetObjectItem(root, "payload"); + if (cJSON_IsObject(payload)) { + McpServer::GetInstance().ParseMessage(payload); + } + } else if (strcmp(type->valuestring, "system") == 0) { + auto command = cJSON_GetObjectItem(root, "command"); + if (cJSON_IsString(command)) { + ESP_LOGI(TAG, "System command: %s", command->valuestring); + if (strcmp(command->valuestring, "reboot") == 0) { + // Do a reboot if user requests a OTA update + Schedule([this]() { + Reboot(); + }); + } else { + ESP_LOGW(TAG, "Unknown system command: %s", command->valuestring); + } + } + } else if (strcmp(type->valuestring, "alert") == 0) { + auto status = cJSON_GetObjectItem(root, "status"); + auto message = cJSON_GetObjectItem(root, "message"); + auto emotion = cJSON_GetObjectItem(root, "emotion"); + if (cJSON_IsString(status) && cJSON_IsString(message) && cJSON_IsString(emotion)) { + Alert(status->valuestring, message->valuestring, emotion->valuestring, Lang::Sounds::OGG_VIBRATION); + } else { + ESP_LOGW(TAG, "Alert command requires status, message and emotion"); + } +#if CONFIG_RECEIVE_CUSTOM_MESSAGE + } else if (strcmp(type->valuestring, "custom") == 0) { + auto payload = cJSON_GetObjectItem(root, "payload"); + ESP_LOGI(TAG, "Received custom message: %s", cJSON_PrintUnformatted(root)); + if (cJSON_IsObject(payload)) { + Schedule([this, display, payload_str = std::string(cJSON_PrintUnformatted(payload))]() { + display->SetChatMessage("system", payload_str.c_str()); + }); + } else { + ESP_LOGW(TAG, "Invalid custom message format: missing payload"); + } +#endif + } else { + ESP_LOGW(TAG, "Unknown message type: %s", type->valuestring); + } + }); + bool protocol_started = protocol_->Start(); + + SystemInfo::PrintHeapStats(); + SetDeviceState(kDeviceStateIdle); + + has_server_time_ = ota.HasServerTime(); + if (protocol_started) { + std::string message = std::string(Lang::Strings::VERSION) + ota.GetCurrentVersion(); + display->ShowNotification(message.c_str()); + display->SetChatMessage("system", ""); + + // 初始化闹钟管理器 + auto& alarm_manager = AlarmManager::GetInstance(); + alarm_manager.Initialize(); + + // 设置闹钟回调 + alarm_manager.SetAlarmTriggeredCallback([this](const AlarmItem& alarm) { + Schedule([this, alarm]() { OnAlarmTriggered(alarm); }); + }); + alarm_manager.SetAlarmSnoozeCallback([this](const AlarmItem& alarm) { + Schedule([this, alarm]() { OnAlarmSnoozed(alarm); }); + }); + alarm_manager.SetAlarmStopCallback([this](const AlarmItem& alarm) { + Schedule([this, alarm]() { OnAlarmStopped(alarm); }); + }); + + // Play the success sound to indicate the device is ready + audio_service_.PlaySound(Lang::Sounds::OGG_SUCCESS); + } +} + +// Add a async task to MainLoop +void Application::Schedule(std::function callback) { + { + std::lock_guard lock(mutex_); + main_tasks_.push_back(std::move(callback)); + } + xEventGroupSetBits(event_group_, MAIN_EVENT_SCHEDULE); +} + +// The Main Event Loop controls the chat state and websocket connection +// If other tasks need to access the websocket or chat state, +// they should use Schedule to call this function +void Application::MainEventLoop() { + while (true) { + auto bits = xEventGroupWaitBits(event_group_, MAIN_EVENT_SCHEDULE | + MAIN_EVENT_SEND_AUDIO | + MAIN_EVENT_WAKE_WORD_DETECTED | + MAIN_EVENT_VAD_CHANGE | + MAIN_EVENT_CLOCK_TICK | + MAIN_EVENT_ERROR, pdTRUE, pdFALSE, portMAX_DELAY); + + if (bits & MAIN_EVENT_ERROR) { + SetDeviceState(kDeviceStateIdle); + Alert(Lang::Strings::ERROR, last_error_message_.c_str(), "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); + } + + if (bits & MAIN_EVENT_SEND_AUDIO) { + while (auto packet = audio_service_.PopPacketFromSendQueue()) { + if (protocol_ && !protocol_->SendAudio(std::move(packet))) { + break; + } + } + } + + if (bits & MAIN_EVENT_WAKE_WORD_DETECTED) { + OnWakeWordDetected(); + } + + if (bits & MAIN_EVENT_VAD_CHANGE) { + if (device_state_ == kDeviceStateListening) { + auto led = Board::GetInstance().GetLed(); + led->OnStateChanged(); + } + } + + if (bits & MAIN_EVENT_SCHEDULE) { + std::unique_lock lock(mutex_); + auto tasks = std::move(main_tasks_); + lock.unlock(); + for (auto& task : tasks) { + task(); + } + } + + if (bits & MAIN_EVENT_CLOCK_TICK) { + clock_ticks_++; + auto display = Board::GetInstance().GetDisplay(); + display->UpdateStatusBar(); + display->OnClockTimer(); + + // 检查闹钟(每秒检查一次) + auto& alarm_manager = AlarmManager::GetInstance(); + alarm_manager.CheckAlarms(); + + // 更新音乐播放进度(每秒更新一次) + if (is_music_playing_) { + UpdateMusicProgress(); + } + + // Print the debug info every 10 seconds + if (clock_ticks_ % 10 == 0) { + // SystemInfo::PrintTaskCpuUsage(pdMS_TO_TICKS(1000)); + // SystemInfo::PrintTaskList(); + SystemInfo::PrintHeapStats(); + } + } + } +} + +void Application::OnWakeWordDetected() { + if (!protocol_) { + return; + } + + if (device_state_ == kDeviceStateIdle) { + audio_service_.EncodeWakeWord(); + + if (!protocol_->IsAudioChannelOpened()) { + SetDeviceState(kDeviceStateConnecting); + if (!protocol_->OpenAudioChannel()) { + audio_service_.EnableWakeWordDetection(true); + return; + } + } + + auto wake_word = audio_service_.GetLastWakeWord(); + ESP_LOGI(TAG, "Wake word detected: %s", wake_word.c_str()); +#if CONFIG_SEND_WAKE_WORD_DATA + // Encode and send the wake word data to the server + while (auto packet = audio_service_.PopWakeWordPacket()) { + protocol_->SendAudio(std::move(packet)); + } + // Set the chat state to wake word detected + protocol_->SendWakeWordDetected(wake_word); + SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); +#else + SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); + // Play the pop up sound to indicate the wake word is detected + audio_service_.PlaySound(Lang::Sounds::OGG_POPUP); +#endif + } else if (device_state_ == kDeviceStateSpeaking) { + AbortSpeaking(kAbortReasonWakeWordDetected); + } else if (device_state_ == kDeviceStateActivating) { + SetDeviceState(kDeviceStateIdle); + } +} + +void Application::AbortSpeaking(AbortReason reason) { + ESP_LOGI(TAG, "Abort speaking"); + aborted_ = true; + if (protocol_) { + protocol_->SendAbortSpeaking(reason); + } +} + +void Application::SetListeningMode(ListeningMode mode) { + listening_mode_ = mode; + SetDeviceState(kDeviceStateListening); +} + +void Application::SetDeviceState(DeviceState state) { + if (device_state_ == state) { + return; + } + + clock_ticks_ = 0; + auto previous_state = device_state_; + device_state_ = state; + ESP_LOGI(TAG, "STATE: %s", STATE_STRINGS[device_state_]); + + // Send the state change event + DeviceStateEventManager::GetInstance().PostStateChangeEvent(previous_state, state); + + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + auto led = board.GetLed(); + led->OnStateChanged(); + display->OnStateChanged(); + switch (state) { + case kDeviceStateUnknown: + case kDeviceStateIdle: + display->SetStatus(Lang::Strings::STANDBY); + display->SetEmotion("neutral"); + audio_service_.EnableVoiceProcessing(false); + audio_service_.EnableWakeWordDetection(true); + break; + case kDeviceStateConnecting: + display->SetStatus(Lang::Strings::CONNECTING); + display->SetEmotion("neutral"); + display->SetChatMessage("system", ""); + break; + case kDeviceStateListening: + display->SetStatus(Lang::Strings::LISTENING); + display->SetEmotion("neutral"); + + // Make sure the audio processor is running + if (!audio_service_.IsAudioProcessorRunning()) { + // Send the start listening command + protocol_->SendStartListening(listening_mode_); + audio_service_.EnableVoiceProcessing(true); + audio_service_.EnableWakeWordDetection(false); + } + break; + case kDeviceStateSpeaking: + display->SetStatus(Lang::Strings::SPEAKING); + + if (listening_mode_ != kListeningModeRealtime) { + audio_service_.EnableVoiceProcessing(false); + // Only AFE wake word can be detected in speaking mode + audio_service_.EnableWakeWordDetection(audio_service_.IsAfeWakeWord()); + } + audio_service_.ResetDecoder(); + break; + default: + // Do nothing + break; + } +} + +void Application::Reboot() { + ESP_LOGI(TAG, "Rebooting..."); + // Disconnect the audio channel + if (protocol_ && protocol_->IsAudioChannelOpened()) { + protocol_->CloseAudioChannel(); + } + protocol_.reset(); + audio_service_.Stop(); + + vTaskDelay(pdMS_TO_TICKS(1000)); + esp_restart(); +} + +bool Application::UpgradeFirmware(Ota& ota, const std::string& url) { + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + + // Use provided URL or get from OTA object + std::string upgrade_url = url.empty() ? ota.GetFirmwareUrl() : url; + std::string version_info = url.empty() ? ota.GetFirmwareVersion() : "(Manual upgrade)"; + + // Close audio channel if it's open + if (protocol_ && protocol_->IsAudioChannelOpened()) { + ESP_LOGI(TAG, "Closing audio channel before firmware upgrade"); + protocol_->CloseAudioChannel(); + } + ESP_LOGI(TAG, "Starting firmware upgrade from URL: %s", upgrade_url.c_str()); + + Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "download", Lang::Sounds::OGG_UPGRADE); + vTaskDelay(pdMS_TO_TICKS(3000)); + + SetDeviceState(kDeviceStateUpgrading); + + std::string message = std::string(Lang::Strings::NEW_VERSION) + version_info; + display->SetChatMessage("system", message.c_str()); + + board.SetPowerSaveMode(false); + audio_service_.Stop(); + vTaskDelay(pdMS_TO_TICKS(1000)); + + bool upgrade_success = ota.StartUpgradeFromUrl(upgrade_url, [display](int progress, size_t speed) { + std::thread([display, progress, speed]() { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024); + display->SetChatMessage("system", buffer); + }).detach(); + }); + + if (!upgrade_success) { + // Upgrade failed, restart audio service and continue running + ESP_LOGE(TAG, "Firmware upgrade failed, restarting audio service and continuing operation..."); + audio_service_.Start(); // Restart audio service + board.SetPowerSaveMode(true); // Restore power save mode + Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); + vTaskDelay(pdMS_TO_TICKS(3000)); + return false; + } else { + // Upgrade success, reboot immediately + ESP_LOGI(TAG, "Firmware upgrade successful, rebooting..."); + display->SetChatMessage("system", "Upgrade successful, rebooting..."); + vTaskDelay(pdMS_TO_TICKS(1000)); // Brief pause to show message + Reboot(); + return true; + } +} + +void Application::WakeWordInvoke(const std::string& wake_word) { + if (device_state_ == kDeviceStateIdle) { + ToggleChatState(); + Schedule([this, wake_word]() { + if (protocol_) { + protocol_->SendWakeWordDetected(wake_word); + } + }); + } else if (device_state_ == kDeviceStateSpeaking) { + Schedule([this]() { + AbortSpeaking(kAbortReasonNone); + }); + } else if (device_state_ == kDeviceStateListening) { + Schedule([this]() { + if (protocol_) { + protocol_->CloseAudioChannel(); + } + }); + } +} + +bool Application::CanEnterSleepMode() { + if (device_state_ != kDeviceStateIdle) { + return false; + } + + if (protocol_ && protocol_->IsAudioChannelOpened()) { + return false; + } + + if (!audio_service_.IsIdle()) { + return false; + } + + // Now it is safe to enter sleep mode + return true; +} + +void Application::SendMcpMessage(const std::string& payload) { + if (protocol_ == nullptr) { + return; + } + + // Make sure you are using main thread to send MCP message + if (xTaskGetCurrentTaskHandle() == main_event_loop_task_handle_) { + protocol_->SendMcpMessage(payload); + } else { + Schedule([this, payload = std::move(payload)]() { + protocol_->SendMcpMessage(payload); + }); + } +} + +void Application::SetAecMode(AecMode mode) { + aec_mode_ = mode; + Schedule([this]() { + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + switch (aec_mode_) { + case kAecOff: + audio_service_.EnableDeviceAec(false); + display->ShowNotification(Lang::Strings::RTC_MODE_OFF); + break; + case kAecOnServerSide: + audio_service_.EnableDeviceAec(false); + display->ShowNotification(Lang::Strings::RTC_MODE_ON); + break; + case kAecOnDeviceSide: + audio_service_.EnableDeviceAec(true); + display->ShowNotification(Lang::Strings::RTC_MODE_ON); + break; + } + + // If the AEC mode is changed, close the audio channel + if (protocol_ && protocol_->IsAudioChannelOpened()) { + protocol_->CloseAudioChannel(); + } + }); +} + +void Application::PlaySound(const std::string_view& sound) { + audio_service_.PlaySound(sound); +} + +// 新增:接收外部音频数据(如音乐播放) +void Application::AddAudioData(AudioStreamPacket&& packet) { + auto codec = Board::GetInstance().GetAudioCodec(); + if (device_state_ == kDeviceStateIdle && codec->output_enabled()) { + // packet.payload包含的是原始PCM数据(int16_t) + if (packet.payload.size() >= 2) { + size_t num_samples = packet.payload.size() / sizeof(int16_t); + std::vector pcm_data(num_samples); + memcpy(pcm_data.data(), packet.payload.data(), packet.payload.size()); + + // 检查采样率是否匹配,如果不匹配则进行简单重采样 + if (packet.sample_rate != codec->output_sample_rate()) { + // 验证采样率参数 + if (packet.sample_rate <= 0 || codec->output_sample_rate() <= 0) { + ESP_LOGE(TAG, "Invalid sample rates: %d -> %d", + packet.sample_rate, codec->output_sample_rate()); + return; + } + + // 尝试动态切换采样率 + if (codec->SetOutputSampleRate(packet.sample_rate)) { + ESP_LOGI(TAG, "Successfully switched to music playback sampling rate: %d Hz", packet.sample_rate); + } else { + ESP_LOGW(TAG, "Unable to switch sampling rate, continue using current sampling rate: %d Hz", codec->output_sample_rate()); + // 如果无法切换采样率,继续使用当前的采样率进行处理 + if (packet.sample_rate > codec->output_sample_rate()) { + // 下采样:简单丢弃部分样本 + float downsample_ratio = static_cast(packet.sample_rate) / codec->output_sample_rate(); + size_t expected_size = static_cast(pcm_data.size() / downsample_ratio + 0.5f); + std::vector resampled(expected_size); + size_t resampled_index = 0; + + for (size_t i = 0; i < pcm_data.size(); ++i) { + if (i % static_cast(downsample_ratio) == 0) { + resampled[resampled_index++] = pcm_data[i]; + } + } + + pcm_data = std::move(resampled); + ESP_LOGI(TAG, "Downsampled %d -> %d samples (ratio: %.2f)", + pcm_data.size(), resampled.size(), downsample_ratio); + } else if (packet.sample_rate < codec->output_sample_rate()) { + // 上采样:线性插值 + float upsample_ratio = codec->output_sample_rate() / static_cast(packet.sample_rate); + size_t expected_size = static_cast(pcm_data.size() * upsample_ratio + 0.5f); + std::vector resampled(expected_size); + + for (size_t i = 0; i < pcm_data.size(); ++i) { + // 添加原始样本 + resampled[i * static_cast(upsample_ratio)] = pcm_data[i]; + + // 计算需要插值的样本数 + int interpolation_count = static_cast(upsample_ratio) - 1; + if (interpolation_count > 0 && i + 1 < pcm_data.size()) { + int16_t current = pcm_data[i]; + int16_t next = pcm_data[i + 1]; + for (int j = 1; j <= interpolation_count; ++j) { + float t = static_cast(j) / (interpolation_count + 1); + int16_t interpolated = static_cast(current + (next - current) * t); + resampled[i * static_cast(upsample_ratio) + j] = interpolated; + } + } else if (interpolation_count > 0) { + // 最后一个样本,直接重复 + for (int j = 1; j <= interpolation_count; ++j) { + resampled[i * static_cast(upsample_ratio) + j] = pcm_data[i]; + } + } + } + + pcm_data = std::move(resampled); + ESP_LOGI(TAG, "Upsampled %d -> %d samples (ratio: %.2f)", + pcm_data.size() / static_cast(upsample_ratio), pcm_data.size(), upsample_ratio); + } + } + } + + // 确保音频输出已启用 + if (!codec->output_enabled()) { + codec->EnableOutput(true); + } + + // 发送PCM数据到音频编解码器 + codec->OutputData(pcm_data); + + audio_service_.UpdateOutputTimestamp(); + } + } +} + +// 随机闹钟音乐列表 - 流行、经典、适合早晨的歌曲 +static const std::vector DEFAULT_ALARM_SONGS = { + "晴天", "七里香", "青花瓷", "稻香", "彩虹", "告白气球", "说好不哭", + "夜曲", "花海", "简单爱", "听妈妈的话", "东风破", "菊花台", + "起风了", "红豆", "好久不见", "匆匆那年", "老男孩", "那些年", + "小幸运", "成都", "南山南", "演员", "体面", "盗将行", "大鱼", + "新不了情", "月亮代表我的心", "甜蜜蜜", "邓丽君", "我只在乎你", + "友谊之光", "童年", "海阔天空", "光辉岁月", "真的爱你", "喜欢你", + "突然好想你", "情非得已", "温柔", "倔强", "知足", "三个傻瓜", + "恋爱循环", "千本樱", "打上花火", "lemon", "残酷天使的行动纲领", + "鸟笼", "虹", "青鸟", "closer", "sugar", "shape of you", + "despacito", "perfect", "happier", "someone like you" +}; + +// 获取随机闹钟音乐 +static std::string GetRandomAlarmMusic() { + if (DEFAULT_ALARM_SONGS.empty()) { + return ""; + } + + // 使用当前时间作为随机种子 + srand(esp_timer_get_time() / 1000000); + size_t index = rand() % DEFAULT_ALARM_SONGS.size(); + return DEFAULT_ALARM_SONGS[index]; +} + +// 闹钟回调方法实现 +void Application::OnAlarmTriggered(const AlarmItem& alarm) { + ESP_LOGI("Application", "Alarm triggered: %s at %02d:%02d", + alarm.label.c_str(), alarm.hour, alarm.minute); + + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + auto music = board.GetMusic(); + + // 显示闹钟信息 + std::string alarm_message = "🎵 闹钟"; + if (!alarm.label.empty()) { + alarm_message += "\n" + alarm.label; + } + alarm_message += "\n" + AlarmManager::FormatTime(alarm.hour, alarm.minute); + + // 优先在时钟界面显示(如果支持) + display->ShowAlarmOnIdleScreen(alarm_message.c_str()); + + // 同时设置聊天消息(作为备用) + display->SetChatMessage("system", alarm_message.c_str()); + display->SetEmotion("music"); + + // 确定要播放的音乐 + std::string music_to_play; + if (!alarm.music_name.empty()) { + // 使用用户指定的音乐 + music_to_play = alarm.music_name; + ESP_LOGI("Application", "Playing user specified alarm music: %s", music_to_play.c_str()); + } else { + // 随机选择一首默认闹钟音乐 + music_to_play = GetRandomAlarmMusic(); + ESP_LOGI("Application", "Playing random alarm music: %s", music_to_play.c_str()); + } + + // 播放音乐 + if (music && !music_to_play.empty()) { + // 更新显示,显示正在播放的歌曲 + std::string playing_message = "🎵 正在播放: " + music_to_play; + display->SetChatMessage("system", playing_message.c_str()); + + // 开始下载并播放音乐 + if (music->Download(music_to_play)) { + ESP_LOGI("Application", "Successfully started alarm music: %s", music_to_play.c_str()); + + // 开始音乐进度跟踪 + current_music_name_ = music_to_play; + music_start_time_ms_ = esp_timer_get_time() / 1000; // 转换为毫秒 + is_music_playing_ = true; + + // 尝试获取真实的歌曲长度 + int real_duration = music->GetCurrentSongDurationSeconds(); + if (real_duration > 0) { + music_duration_seconds_ = real_duration; + ESP_LOGI("Application", "Got real song duration: %d seconds", real_duration); + } + + // 启动进度显示 + display->SetMusicProgress(music_to_play.c_str(), 0, music_duration_seconds_, 0.0f); + } else { + ESP_LOGW("Application", "Failed to download alarm music: %s, using fallback", music_to_play.c_str()); + // 如果下载失败,播放默认铃声 + audio_service_.PlaySound(Lang::Sounds::OGG_VIBRATION); + } + } else { + ESP_LOGW("Application", "Music service not available or no music selected, using default alarm sound"); + // 如果没有音乐功能或选择失败,播放默认铃声 + audio_service_.PlaySound(Lang::Sounds::OGG_VIBRATION); + } + + // 显示闹钟控制提示 + std::string control_message = "🎵 说\"贪睡\"延后5分钟,说\"关闭闹钟\"停止音乐"; + display->ShowNotification(control_message.c_str()); +} + +void Application::OnAlarmSnoozed(const AlarmItem& alarm) { + ESP_LOGI("Application", "Alarm snoozed: %s, count: %d/%d", + alarm.label.c_str(), alarm.snooze_count, alarm.max_snooze_count); + + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + + // 隐藏空闲屏幕上的闹钟信息 + display->HideAlarmOnIdleScreen(); + + // 停止当前播放的音乐 + auto music = board.GetMusic(); + if (music) { + music->StopStreaming(); + } + + // 停止音乐进度跟踪并清除音乐界面 + is_music_playing_ = false; + display->ClearMusicInfo(); + + std::string snooze_message = "💤 闹钟已贪睡 " + std::to_string(alarm.snooze_minutes) + " 分钟"; + display->SetChatMessage("system", snooze_message.c_str()); + display->SetEmotion("neutral"); + + audio_service_.PlaySound(Lang::Sounds::OGG_SUCCESS); +} + +void Application::OnAlarmStopped(const AlarmItem& alarm) { + ESP_LOGI("Application", "Alarm stopped: %s", alarm.label.c_str()); + + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + + // 隐藏空闲屏幕上的闹钟信息 + display->HideAlarmOnIdleScreen(); + + // 停止当前播放的音乐 + auto music = board.GetMusic(); + if (music) { + music->StopStreaming(); + } + + // 停止音乐进度跟踪并清除音乐界面 + is_music_playing_ = false; + display->ClearMusicInfo(); + + display->SetChatMessage("system", "✅ 闹钟已关闭"); + display->SetEmotion("neutral"); + + // 显示下一个闹钟信息 + auto& alarm_manager = AlarmManager::GetInstance(); + std::string next_alarm_info = alarm_manager.GetNextAlarmInfo(); + display->ShowNotification(next_alarm_info.c_str()); + + audio_service_.PlaySound(Lang::Sounds::OGG_SUCCESS); +} + +// 获取默认闹钟音乐列表 +std::vector Application::GetDefaultAlarmMusicList() const { + return DEFAULT_ALARM_SONGS; +} + +// 更新音乐播放进度 +void Application::UpdateMusicProgress() { + if (!is_music_playing_) { + return; + } + + auto& board = Board::GetInstance(); + auto music = board.GetMusic(); + auto display = board.GetDisplay(); + + if (!music || !display) { + return; + } + + // 从音乐播放器获取真实的播放信息 + int real_current_seconds = music->GetCurrentPlayTimeSeconds(); + int real_duration_seconds = music->GetCurrentSongDurationSeconds(); + float real_progress_percent = music->GetPlayProgress(); + + // 更新存储的歌曲长度(如果有变化) + if (real_duration_seconds > 0 && real_duration_seconds != music_duration_seconds_) { + music_duration_seconds_ = real_duration_seconds; + ESP_LOGI("Application", "Updated song duration: %d seconds", music_duration_seconds_); + } + + // 检查是否播放结束 + bool is_still_playing = music->IsDownloading() || (real_current_seconds < real_duration_seconds && real_current_seconds > 0); + + if (!is_still_playing && real_current_seconds >= real_duration_seconds && real_duration_seconds > 0) { + is_music_playing_ = false; // 停止跟踪 + ESP_LOGI("Application", "Music playback finished: %s (%d/%d seconds)", + current_music_name_.c_str(), real_current_seconds, real_duration_seconds); + + // 🎵 音乐播放完毕,自动停止所有活跃的闹钟 + auto& alarm_manager = AlarmManager::GetInstance(); + auto active_alarms = alarm_manager.GetActiveAlarms(); + if (!active_alarms.empty()) { + ESP_LOGI("Application", "Auto-stopping alarms after music finished"); + + // 停止闹钟 + for (const auto& alarm : active_alarms) { + alarm_manager.StopAlarm(alarm.id); + } + + // 在界面上显示用户确认消息 + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + if (display) { + display->SetChatMessage("user", "我听到你的闹钟啦 ✅"); + display->SetEmotion("happy"); + } + } + } + + // 更新显示(使用真实的播放时间) + if (is_music_playing_) { + display->SetMusicProgress(current_music_name_.c_str(), + real_current_seconds, + real_duration_seconds, + real_progress_percent); + + ESP_LOGD("Application", "Music progress: %s - %d/%d seconds (%.1f%%)", + current_music_name_.c_str(), real_current_seconds, real_duration_seconds, real_progress_percent); + } else { + // 播放结束,清除界面 + display->ClearMusicInfo(); + } } \ No newline at end of file diff --git a/main/application.h b/main/application.h index 4fdfb99..d0d5c57 100644 --- a/main/application.h +++ b/main/application.h @@ -1,111 +1,129 @@ -#ifndef _APPLICATION_H_ -#define _APPLICATION_H_ - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "protocol.h" -#include "ota.h" -#include "audio_service.h" -#include "device_state_event.h" - - -#define MAIN_EVENT_SCHEDULE (1 << 0) -#define MAIN_EVENT_SEND_AUDIO (1 << 1) -#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2) -#define MAIN_EVENT_VAD_CHANGE (1 << 3) -#define MAIN_EVENT_ERROR (1 << 4) -#define MAIN_EVENT_CHECK_NEW_VERSION_DONE (1 << 5) -#define MAIN_EVENT_CLOCK_TICK (1 << 6) - - -enum AecMode { - kAecOff, - kAecOnDeviceSide, - kAecOnServerSide, -}; - -class Application { -public: - static Application& GetInstance() { - static Application instance; - return instance; - } - // 删除拷贝构造函数和赋值运算符 - Application(const Application&) = delete; - Application& operator=(const Application&) = delete; - - void Start(); - void MainEventLoop(); - DeviceState GetDeviceState() const { return device_state_; } - bool IsVoiceDetected() const { return audio_service_.IsVoiceDetected(); } - void Schedule(std::function callback); - void SetDeviceState(DeviceState state); - void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = ""); - void DismissAlert(); - void AbortSpeaking(AbortReason reason); - void ToggleChatState(); - void StartListening(); - void StopListening(); - void Reboot(); - void WakeWordInvoke(const std::string& wake_word); - bool UpgradeFirmware(Ota& ota, const std::string& url = ""); - bool CanEnterSleepMode(); - void SendMcpMessage(const std::string& payload); - void SetAecMode(AecMode mode); - AecMode GetAecMode() const { return aec_mode_; } - void PlaySound(const std::string_view& sound); - AudioService& GetAudioService() { return audio_service_; } - void AddAudioData(AudioStreamPacket&& packet); - -private: - Application(); - ~Application(); - - std::mutex mutex_; - std::deque> main_tasks_; - std::unique_ptr protocol_; - EventGroupHandle_t event_group_ = nullptr; - esp_timer_handle_t clock_timer_handle_ = nullptr; - volatile DeviceState device_state_ = kDeviceStateUnknown; - ListeningMode listening_mode_ = kListeningModeAutoStop; - AecMode aec_mode_ = kAecOff; - std::string last_error_message_; - AudioService audio_service_; - - bool has_server_time_ = false; - bool aborted_ = false; - int clock_ticks_ = 0; - TaskHandle_t check_new_version_task_handle_ = nullptr; - TaskHandle_t main_event_loop_task_handle_ = nullptr; - - void OnWakeWordDetected(); - void CheckNewVersion(Ota& ota); - void CheckAssetsVersion(); - void ShowActivationCode(const std::string& code, const std::string& message); - void SetListeningMode(ListeningMode mode); -}; - - -class TaskPriorityReset { -public: - TaskPriorityReset(BaseType_t priority) { - original_priority_ = uxTaskPriorityGet(NULL); - vTaskPrioritySet(NULL, priority); - } - ~TaskPriorityReset() { - vTaskPrioritySet(NULL, original_priority_); - } - -private: - BaseType_t original_priority_; -}; - -#endif // _APPLICATION_H_ +#ifndef _APPLICATION_H_ +#define _APPLICATION_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "protocol.h" +#include "ota.h" +#include "audio_service.h" +#include "device_state_event.h" +#include "alarm_manager.h" + + +#define MAIN_EVENT_SCHEDULE (1 << 0) +#define MAIN_EVENT_SEND_AUDIO (1 << 1) +#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2) +#define MAIN_EVENT_VAD_CHANGE (1 << 3) +#define MAIN_EVENT_ERROR (1 << 4) +#define MAIN_EVENT_CHECK_NEW_VERSION_DONE (1 << 5) +#define MAIN_EVENT_CLOCK_TICK (1 << 6) + + +enum AecMode { + kAecOff, + kAecOnDeviceSide, + kAecOnServerSide, +}; + +class Application { +public: + static Application& GetInstance() { + static Application instance; + return instance; + } + // 删除拷贝构造函数和赋值运算符 + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + void Start(); + void MainEventLoop(); + DeviceState GetDeviceState() const { return device_state_; } + bool IsVoiceDetected() const { return audio_service_.IsVoiceDetected(); } + void Schedule(std::function callback); + void SetDeviceState(DeviceState state); + void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = ""); + void DismissAlert(); + void AbortSpeaking(AbortReason reason); + void ToggleChatState(); + void StartListening(); + void StopListening(); + void Reboot(); + void WakeWordInvoke(const std::string& wake_word); + bool UpgradeFirmware(Ota& ota, const std::string& url = ""); + bool CanEnterSleepMode(); + void SendMcpMessage(const std::string& payload); + void SetAecMode(AecMode mode); + AecMode GetAecMode() const { return aec_mode_; } + void PlaySound(const std::string_view& sound); + // 新增:接收外部音频数据(如音乐播放) + void AddAudioData(AudioStreamPacket&& packet); + AudioService& GetAudioService() { return audio_service_; } + + // 闹钟功能 + AlarmManager& GetAlarmManager() { return AlarmManager::GetInstance(); } + std::vector GetDefaultAlarmMusicList() const; + +private: + Application(); + ~Application(); + + std::mutex mutex_; + std::deque> main_tasks_; + std::unique_ptr protocol_; + EventGroupHandle_t event_group_ = nullptr; + esp_timer_handle_t clock_timer_handle_ = nullptr; + volatile DeviceState device_state_ = kDeviceStateUnknown; + ListeningMode listening_mode_ = kListeningModeAutoStop; + AecMode aec_mode_ = kAecOff; + std::string last_error_message_; + AudioService audio_service_; + + bool has_server_time_ = false; + bool aborted_ = false; + int clock_ticks_ = 0; + TaskHandle_t check_new_version_task_handle_ = nullptr; + TaskHandle_t main_event_loop_task_handle_ = nullptr; + + void OnWakeWordDetected(); + void CheckNewVersion(Ota& ota); + void CheckAssetsVersion(); + void ShowActivationCode(const std::string& code, const std::string& message); + void SetListeningMode(ListeningMode mode); + + // 闹钟相关私有方法 + void OnAlarmTriggered(const AlarmItem& alarm); + void OnAlarmSnoozed(const AlarmItem& alarm); + void OnAlarmStopped(const AlarmItem& alarm); + + // 音乐进度跟踪相关 + void UpdateMusicProgress(); + std::string current_music_name_; + int64_t music_start_time_ms_ = 0; // 音乐开始播放的时间戳 + int music_duration_seconds_ = 180; // 默认歌曲长度(3分钟) + bool is_music_playing_ = false; +}; + + +class TaskPriorityReset { +public: + TaskPriorityReset(BaseType_t priority) { + original_priority_ = uxTaskPriorityGet(NULL); + vTaskPrioritySet(NULL, priority); + } + ~TaskPriorityReset() { + vTaskPrioritySet(NULL, original_priority_); + } + +private: + BaseType_t original_priority_; +}; + +#endif // _APPLICATION_H_ diff --git a/main/assets.cc b/main/assets.cc index 48d790f..94b602e 100644 --- a/main/assets.cc +++ b/main/assets.cc @@ -1,406 +1,517 @@ -#include "assets.h" -#include "board.h" -#include "display.h" -#include "application.h" -#include "lvgl_theme.h" - -#include -#include -#include -#include - - -#define TAG "Assets" - -struct mmap_assets_table { - char asset_name[32]; /*!< Name of the asset */ - uint32_t asset_size; /*!< Size of the asset */ - uint32_t asset_offset; /*!< Offset of the asset */ - uint16_t asset_width; /*!< Width of the asset */ - uint16_t asset_height; /*!< Height of the asset */ -}; - - -Assets::Assets(std::string default_assets_url) { - if (default_assets_url.find("http") == 0) { - default_assets_url_ = default_assets_url; - } else { - ESP_LOGE(TAG, "The default assets url is not a http url: %s", default_assets_url.c_str()); - } - - // Initialize the partition - InitializePartition(); -} - -Assets::~Assets() { - if (mmap_handle_ != 0) { - esp_partition_munmap(mmap_handle_); - } -} - -uint32_t Assets::CalculateChecksum(const char* data, uint32_t length) { - uint32_t checksum = 0; - for (uint32_t i = 0; i < length; i++) { - checksum += data[i]; - } - return checksum & 0xFFFF; -} - -bool Assets::InitializePartition() { - partition_valid_ = false; - checksum_valid_ = false; - assets_.clear(); - - partition_ = esp_partition_find_first(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, "assets"); - if (partition_ == nullptr) { - ESP_LOGI(TAG, "No assets partition found"); - return false; - } - - int free_pages = spi_flash_mmap_get_free_pages(SPI_FLASH_MMAP_DATA); - uint32_t storage_size = free_pages * 64 * 1024; - ESP_LOGI(TAG, "The storage free size is %ld KB", storage_size / 1024); - ESP_LOGI(TAG, "The partition size is %ld KB", partition_->size / 1024); - if (storage_size < partition_->size) { - ESP_LOGE(TAG, "The free size %ld KB is less than assets partition required %ld KB", storage_size / 1024, partition_->size / 1024); - return false; - } - - esp_err_t err = esp_partition_mmap(partition_, 0, partition_->size, ESP_PARTITION_MMAP_DATA, (const void**)&mmap_root_, &mmap_handle_); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to mmap assets partition: %s", esp_err_to_name(err)); - return false; - } - - partition_valid_ = true; - - uint32_t stored_files = *(uint32_t*)(mmap_root_ + 0); - uint32_t stored_chksum = *(uint32_t*)(mmap_root_ + 4); - uint32_t stored_len = *(uint32_t*)(mmap_root_ + 8); - - if (stored_len > partition_->size - 12) { - ESP_LOGD(TAG, "The stored_len (0x%lx) is greater than the partition size (0x%lx) - 12", stored_len, partition_->size); - return false; - } - - auto start_time = esp_timer_get_time(); - uint32_t calculated_checksum = CalculateChecksum(mmap_root_ + 12, stored_len); - auto end_time = esp_timer_get_time(); - ESP_LOGI(TAG, "The checksum calculation time is %d ms", int((end_time - start_time) / 1000)); - - if (calculated_checksum != stored_chksum) { - ESP_LOGE(TAG, "The calculated checksum (0x%lx) does not match the stored checksum (0x%lx)", calculated_checksum, stored_chksum); - return false; - } - - checksum_valid_ = true; - - for (uint32_t i = 0; i < stored_files; i++) { - auto item = (const mmap_assets_table*)(mmap_root_ + 12 + i * sizeof(mmap_assets_table)); - auto asset = Asset{ - .size = static_cast(item->asset_size), - .offset = static_cast(12 + sizeof(mmap_assets_table) * stored_files + item->asset_offset) - }; - assets_[item->asset_name] = asset; - } - return checksum_valid_; -} - -bool Assets::Apply() { - void* ptr = nullptr; - size_t size = 0; - if (!GetAssetData("index.json", ptr, size)) { - ESP_LOGE(TAG, "The index.json file is not found"); - return false; - } - cJSON* root = cJSON_ParseWithLength(static_cast(ptr), size); - if (root == nullptr) { - ESP_LOGE(TAG, "The index.json file is not valid"); - return false; - } - - cJSON* version = cJSON_GetObjectItem(root, "version"); - if (cJSON_IsNumber(version)) { - if (version->valuedouble > 1) { - ESP_LOGE(TAG, "The assets version %d is not supported, please upgrade the firmware", version->valueint); - return false; - } - } - - cJSON* srmodels = cJSON_GetObjectItem(root, "srmodels"); - if (cJSON_IsString(srmodels)) { - std::string srmodels_file = srmodels->valuestring; - if (GetAssetData(srmodels_file, ptr, size)) { - if (models_list_ != nullptr) { - esp_srmodel_deinit(models_list_); - models_list_ = nullptr; - } - models_list_ = srmodel_load(static_cast(ptr)); - if (models_list_ != nullptr) { - auto& app = Application::GetInstance(); - app.GetAudioService().SetModelsList(models_list_); - } else { - ESP_LOGE(TAG, "Failed to load srmodels.bin"); - } - } else { - ESP_LOGE(TAG, "The srmodels file %s is not found", srmodels_file.c_str()); - } - } - -#ifdef HAVE_LVGL - auto& theme_manager = LvglThemeManager::GetInstance(); - auto light_theme = theme_manager.GetTheme("light"); - auto dark_theme = theme_manager.GetTheme("dark"); - - cJSON* font = cJSON_GetObjectItem(root, "text_font"); - if (cJSON_IsString(font)) { - std::string fonts_text_file = font->valuestring; - if (GetAssetData(fonts_text_file, ptr, size)) { - auto text_font = std::make_shared(ptr); - if (text_font->font() == nullptr) { - ESP_LOGE(TAG, "Failed to load fonts.bin"); - return false; - } - if (light_theme != nullptr) { - light_theme->set_text_font(text_font); - } - if (dark_theme != nullptr) { - dark_theme->set_text_font(text_font); - } - } else { - ESP_LOGE(TAG, "The font file %s is not found", fonts_text_file.c_str()); - } - } - - cJSON* emoji_collection = cJSON_GetObjectItem(root, "emoji_collection"); - if (cJSON_IsArray(emoji_collection)) { - auto custom_emoji_collection = std::make_shared(); - int emoji_count = cJSON_GetArraySize(emoji_collection); - for (int i = 0; i < emoji_count; i++) { - cJSON* emoji = cJSON_GetArrayItem(emoji_collection, i); - if (cJSON_IsObject(emoji)) { - cJSON* name = cJSON_GetObjectItem(emoji, "name"); - cJSON* file = cJSON_GetObjectItem(emoji, "file"); - if (cJSON_IsString(name) && cJSON_IsString(file)) { - if (!GetAssetData(file->valuestring, ptr, size)) { - ESP_LOGE(TAG, "Emoji %s image file %s is not found", name->valuestring, file->valuestring); - continue; - } - custom_emoji_collection->AddEmoji(name->valuestring, new LvglRawImage(ptr, size)); - } - } - } - if (light_theme != nullptr) { - light_theme->set_emoji_collection(custom_emoji_collection); - } - if (dark_theme != nullptr) { - dark_theme->set_emoji_collection(custom_emoji_collection); - } - } - - cJSON* skin = cJSON_GetObjectItem(root, "skin"); - if (cJSON_IsObject(skin)) { - cJSON* light_skin = cJSON_GetObjectItem(skin, "light"); - if (cJSON_IsObject(light_skin) && light_theme != nullptr) { - cJSON* text_color = cJSON_GetObjectItem(light_skin, "text_color"); - cJSON* background_color = cJSON_GetObjectItem(light_skin, "background_color"); - cJSON* background_image = cJSON_GetObjectItem(light_skin, "background_image"); - if (cJSON_IsString(text_color)) { - light_theme->set_text_color(LvglTheme::ParseColor(text_color->valuestring)); - } - if (cJSON_IsString(background_color)) { - light_theme->set_background_color(LvglTheme::ParseColor(background_color->valuestring)); - light_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring)); - } - if (cJSON_IsString(background_image)) { - if (!GetAssetData(background_image->valuestring, ptr, size)) { - ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring); - return false; - } - auto background_image = std::make_shared(ptr); - light_theme->set_background_image(background_image); - } - } - cJSON* dark_skin = cJSON_GetObjectItem(skin, "dark"); - if (cJSON_IsObject(dark_skin) && dark_theme != nullptr) { - cJSON* text_color = cJSON_GetObjectItem(dark_skin, "text_color"); - cJSON* background_color = cJSON_GetObjectItem(dark_skin, "background_color"); - cJSON* background_image = cJSON_GetObjectItem(dark_skin, "background_image"); - if (cJSON_IsString(text_color)) { - dark_theme->set_text_color(LvglTheme::ParseColor(text_color->valuestring)); - } - if (cJSON_IsString(background_color)) { - dark_theme->set_background_color(LvglTheme::ParseColor(background_color->valuestring)); - dark_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring)); - } - if (cJSON_IsString(background_image)) { - if (!GetAssetData(background_image->valuestring, ptr, size)) { - ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring); - return false; - } - auto background_image = std::make_shared(ptr); - dark_theme->set_background_image(background_image); - } - } - } -#endif - - auto display = Board::GetInstance().GetDisplay(); - ESP_LOGI(TAG, "Refreshing display theme..."); - - auto current_theme = display->GetTheme(); - if (current_theme != nullptr) { - display->SetTheme(current_theme); - } - cJSON_Delete(root); - return true; -} - -bool Assets::Download(std::string url, std::function progress_callback) { - ESP_LOGI(TAG, "Downloading new version of assets from %s", url.c_str()); - - // 取消当前资源分区的内存映射 - if (mmap_handle_ != 0) { - esp_partition_munmap(mmap_handle_); - mmap_handle_ = 0; - mmap_root_ = nullptr; - } - checksum_valid_ = false; - assets_.clear(); - - // 下载新的资源文件 - auto network = Board::GetInstance().GetNetwork(); - auto http = network->CreateHttp(0); - - if (!http->Open("GET", url)) { - ESP_LOGE(TAG, "Failed to open HTTP connection"); - return false; - } - - if (http->GetStatusCode() != 200) { - ESP_LOGE(TAG, "Failed to get assets, status code: %d", http->GetStatusCode()); - return false; - } - - size_t content_length = http->GetBodyLength(); - if (content_length == 0) { - ESP_LOGE(TAG, "Failed to get content length"); - return false; - } - - if (content_length > partition_->size) { - ESP_LOGE(TAG, "Assets file size (%u) is larger than partition size (%lu)", content_length, partition_->size); - return false; - } - - // 定义扇区大小为4KB(ESP32的标准扇区大小) - const size_t SECTOR_SIZE = esp_partition_get_main_flash_sector_size(); - - // 计算需要擦除的扇区数量 - size_t sectors_to_erase = (content_length + SECTOR_SIZE - 1) / SECTOR_SIZE; // 向上取整 - size_t total_erase_size = sectors_to_erase * SECTOR_SIZE; - - ESP_LOGI(TAG, "Sector size: %u, content length: %u, sectors to erase: %u, total erase size: %u", - SECTOR_SIZE, content_length, sectors_to_erase, total_erase_size); - - // 写入新的资源文件到分区,一边erase一边写入 - char buffer[512]; - size_t total_written = 0; - size_t recent_written = 0; - size_t current_sector = 0; - auto last_calc_time = esp_timer_get_time(); - - while (true) { - int ret = http->Read(buffer, sizeof(buffer)); - if (ret < 0) { - ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret)); - return false; - } - - if (ret == 0) { - break; - } - - // 检查是否需要擦除新的扇区 - size_t write_end_offset = total_written + ret; - size_t needed_sectors = (write_end_offset + SECTOR_SIZE - 1) / SECTOR_SIZE; - - // 擦除需要的新扇区 - while (current_sector < needed_sectors) { - size_t sector_start = current_sector * SECTOR_SIZE; - size_t sector_end = (current_sector + 1) * SECTOR_SIZE; - - // 确保擦除范围不超过分区大小 - if (sector_end > partition_->size) { - ESP_LOGE(TAG, "Sector end (%u) exceeds partition size (%lu)", sector_end, partition_->size); - return false; - } - - ESP_LOGD(TAG, "Erasing sector %u (offset: %u, size: %u)", current_sector, sector_start, SECTOR_SIZE); - esp_err_t err = esp_partition_erase_range(partition_, sector_start, SECTOR_SIZE); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to erase sector %u at offset %u: %s", current_sector, sector_start, esp_err_to_name(err)); - return false; - } - - current_sector++; - } - - // 写入数据到分区 - esp_err_t err = esp_partition_write(partition_, total_written, buffer, ret); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to write to assets partition at offset %u: %s", total_written, esp_err_to_name(err)); - return false; - } - - total_written += ret; - recent_written += ret; - - // 计算进度和速度 - if (esp_timer_get_time() - last_calc_time >= 1000000 || total_written == content_length || ret == 0) { - size_t progress = total_written * 100 / content_length; - size_t speed = recent_written; // 每秒的字节数 - ESP_LOGI(TAG, "Progress: %u%% (%u/%u), Speed: %u B/s, Sectors erased: %u", - progress, total_written, content_length, speed, current_sector); - if (progress_callback) { - progress_callback(progress, speed); - } - last_calc_time = esp_timer_get_time(); - recent_written = 0; // 重置最近写入的字节数 - } - } - - http->Close(); - - if (total_written != content_length) { - ESP_LOGE(TAG, "Downloaded size (%u) does not match expected size (%u)", total_written, content_length); - return false; - } - - ESP_LOGI(TAG, "Assets download completed, total written: %u bytes, total sectors erased: %u", - total_written, current_sector); - - // 重新初始化资源分区 - if (!InitializePartition()) { - ESP_LOGE(TAG, "Failed to re-initialize assets partition"); - return false; - } - - return true; -} - -bool Assets::GetAssetData(const std::string& name, void*& ptr, size_t& size) { - auto asset = assets_.find(name); - if (asset == assets_.end()) { - return false; - } - auto data = (const char*)(mmap_root_ + asset->second.offset); - if (data[0] != 'Z' || data[1] != 'Z') { - ESP_LOGE(TAG, "The asset %s is not valid with magic %02x%02x", name.c_str(), data[0], data[1]); - return false; - } - - ptr = static_cast(const_cast(data + 2)); - size = asset->second.size; - return true; -} +#include "assets.h" +#include "board.h" +#include "display.h" +#include "application.h" +#include "lvgl_theme.h" +#include "emote_display.h" + +#include +#include +#include +#include + + +#define TAG "Assets" + +struct mmap_assets_table { + char asset_name[32]; /*!< Name of the asset */ + uint32_t asset_size; /*!< Size of the asset */ + uint32_t asset_offset; /*!< Offset of the asset */ + uint16_t asset_width; /*!< Width of the asset */ + uint16_t asset_height; /*!< Height of the asset */ +}; + + +Assets::Assets() { + // Initialize the partition + InitializePartition(); +} + +Assets::~Assets() { + if (mmap_handle_ != 0) { + esp_partition_munmap(mmap_handle_); + } +} + +uint32_t Assets::CalculateChecksum(const char* data, uint32_t length) { + uint32_t checksum = 0; + for (uint32_t i = 0; i < length; i++) { + checksum += data[i]; + } + return checksum & 0xFFFF; +} + +bool Assets::InitializePartition() { + partition_valid_ = false; + checksum_valid_ = false; + assets_.clear(); + + partition_ = esp_partition_find_first(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, "assets"); + if (partition_ == nullptr) { + ESP_LOGI(TAG, "No assets partition found"); + return false; + } + + int free_pages = spi_flash_mmap_get_free_pages(SPI_FLASH_MMAP_DATA); + uint32_t storage_size = free_pages * 64 * 1024; + ESP_LOGI(TAG, "The storage free size is %ld KB", storage_size / 1024); + ESP_LOGI(TAG, "The partition size is %ld KB", partition_->size / 1024); + if (storage_size < partition_->size) { + ESP_LOGE(TAG, "The free size %ld KB is less than assets partition required %ld KB", storage_size / 1024, partition_->size / 1024); + return false; + } + + esp_err_t err = esp_partition_mmap(partition_, 0, partition_->size, ESP_PARTITION_MMAP_DATA, (const void**)&mmap_root_, &mmap_handle_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to mmap assets partition: %s", esp_err_to_name(err)); + return false; + } + + partition_valid_ = true; + + uint32_t stored_files = *(uint32_t*)(mmap_root_ + 0); + uint32_t stored_chksum = *(uint32_t*)(mmap_root_ + 4); + uint32_t stored_len = *(uint32_t*)(mmap_root_ + 8); + + if (stored_len > partition_->size - 12) { + ESP_LOGD(TAG, "The stored_len (0x%lx) is greater than the partition size (0x%lx) - 12", stored_len, partition_->size); + return false; + } + + auto start_time = esp_timer_get_time(); + uint32_t calculated_checksum = CalculateChecksum(mmap_root_ + 12, stored_len); + auto end_time = esp_timer_get_time(); + ESP_LOGI(TAG, "The checksum calculation time is %d ms", int((end_time - start_time) / 1000)); + + if (calculated_checksum != stored_chksum) { + ESP_LOGE(TAG, "The calculated checksum (0x%lx) does not match the stored checksum (0x%lx)", calculated_checksum, stored_chksum); + return false; + } + + checksum_valid_ = true; + + for (uint32_t i = 0; i < stored_files; i++) { + auto item = (const mmap_assets_table*)(mmap_root_ + 12 + i * sizeof(mmap_assets_table)); + auto asset = Asset{ + .size = static_cast(item->asset_size), + .offset = static_cast(12 + sizeof(mmap_assets_table) * stored_files + item->asset_offset) + }; + assets_[item->asset_name] = asset; + } + return checksum_valid_; +} + +bool Assets::Apply() { + void* ptr = nullptr; + size_t size = 0; + if (!GetAssetData("index.json", ptr, size)) { + ESP_LOGE(TAG, "The index.json file is not found"); + return false; + } + + cJSON* root = cJSON_ParseWithLength(static_cast(ptr), size); + if (root == nullptr) { + ESP_LOGE(TAG, "The index.json file is not valid"); + return false; + } + + cJSON* version = cJSON_GetObjectItem(root, "version"); + if (cJSON_IsNumber(version)) { + if (version->valuedouble > 1) { + ESP_LOGE(TAG, "The assets version %d is not supported, please upgrade the firmware", version->valueint); + return false; + } + } + + cJSON* srmodels = cJSON_GetObjectItem(root, "srmodels"); + if (cJSON_IsString(srmodels)) { + std::string srmodels_file = srmodels->valuestring; + if (GetAssetData(srmodels_file, ptr, size)) { + if (models_list_ != nullptr) { + esp_srmodel_deinit(models_list_); + models_list_ = nullptr; + } + models_list_ = srmodel_load(static_cast(ptr)); + if (models_list_ != nullptr) { + auto& app = Application::GetInstance(); + app.GetAudioService().SetModelsList(models_list_); + } else { + ESP_LOGE(TAG, "Failed to load srmodels.bin"); + } + } else { + ESP_LOGE(TAG, "The srmodels file %s is not found", srmodels_file.c_str()); + } + } + +#ifdef HAVE_LVGL + auto& theme_manager = LvglThemeManager::GetInstance(); + auto light_theme = theme_manager.GetTheme("light"); + auto dark_theme = theme_manager.GetTheme("dark"); + + cJSON* font = cJSON_GetObjectItem(root, "text_font"); + if (cJSON_IsString(font)) { + std::string fonts_text_file = font->valuestring; + if (GetAssetData(fonts_text_file, ptr, size)) { + auto text_font = std::make_shared(ptr); + if (text_font->font() == nullptr) { + ESP_LOGE(TAG, "Failed to load fonts.bin"); + return false; + } + if (light_theme != nullptr) { + light_theme->set_text_font(text_font); + } + if (dark_theme != nullptr) { + dark_theme->set_text_font(text_font); + } + } else { + ESP_LOGE(TAG, "The font file %s is not found", fonts_text_file.c_str()); + } + } + + cJSON* emoji_collection = cJSON_GetObjectItem(root, "emoji_collection"); + if (cJSON_IsArray(emoji_collection)) { + auto custom_emoji_collection = std::make_shared(); + int emoji_count = cJSON_GetArraySize(emoji_collection); + for (int i = 0; i < emoji_count; i++) { + cJSON* emoji = cJSON_GetArrayItem(emoji_collection, i); + if (cJSON_IsObject(emoji)) { + cJSON* name = cJSON_GetObjectItem(emoji, "name"); + cJSON* file = cJSON_GetObjectItem(emoji, "file"); + cJSON* eaf = cJSON_GetObjectItem(emoji, "eaf"); + if (cJSON_IsString(name) && cJSON_IsString(file) && (NULL== eaf)) { + if (!GetAssetData(file->valuestring, ptr, size)) { + ESP_LOGE(TAG, "Emoji %s image file %s is not found", name->valuestring, file->valuestring); + continue; + } + custom_emoji_collection->AddEmoji(name->valuestring, new LvglRawImage(ptr, size)); + } + } + } + if (light_theme != nullptr) { + light_theme->set_emoji_collection(custom_emoji_collection); + } + if (dark_theme != nullptr) { + dark_theme->set_emoji_collection(custom_emoji_collection); + } + } + + cJSON* skin = cJSON_GetObjectItem(root, "skin"); + if (cJSON_IsObject(skin)) { + cJSON* light_skin = cJSON_GetObjectItem(skin, "light"); + if (cJSON_IsObject(light_skin) && light_theme != nullptr) { + cJSON* text_color = cJSON_GetObjectItem(light_skin, "text_color"); + cJSON* background_color = cJSON_GetObjectItem(light_skin, "background_color"); + cJSON* background_image = cJSON_GetObjectItem(light_skin, "background_image"); + if (cJSON_IsString(text_color)) { + light_theme->set_text_color(LvglTheme::ParseColor(text_color->valuestring)); + } + if (cJSON_IsString(background_color)) { + light_theme->set_background_color(LvglTheme::ParseColor(background_color->valuestring)); + light_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring)); + } + if (cJSON_IsString(background_image)) { + if (!GetAssetData(background_image->valuestring, ptr, size)) { + ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring); + return false; + } + auto background_image = std::make_shared(ptr); + light_theme->set_background_image(background_image); + } + } + cJSON* dark_skin = cJSON_GetObjectItem(skin, "dark"); + if (cJSON_IsObject(dark_skin) && dark_theme != nullptr) { + cJSON* text_color = cJSON_GetObjectItem(dark_skin, "text_color"); + cJSON* background_color = cJSON_GetObjectItem(dark_skin, "background_color"); + cJSON* background_image = cJSON_GetObjectItem(dark_skin, "background_image"); + if (cJSON_IsString(text_color)) { + dark_theme->set_text_color(LvglTheme::ParseColor(text_color->valuestring)); + } + if (cJSON_IsString(background_color)) { + dark_theme->set_background_color(LvglTheme::ParseColor(background_color->valuestring)); + dark_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring)); + } + if (cJSON_IsString(background_image)) { + if (!GetAssetData(background_image->valuestring, ptr, size)) { + ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring); + return false; + } + auto background_image = std::make_shared(ptr); + dark_theme->set_background_image(background_image); + } + } + } + + auto display = Board::GetInstance().GetDisplay(); + ESP_LOGI(TAG, "Refreshing display theme..."); + + auto current_theme = display->GetTheme(); + if (current_theme != nullptr) { + display->SetTheme(current_theme); + } +#elif defined(CONFIG_USE_EMOTE_MESSAGE_STYLE) + auto &board = Board::GetInstance(); + auto display = board.GetDisplay(); + auto emote_display = dynamic_cast(display); + + cJSON* font = cJSON_GetObjectItem(root, "text_font"); + if (cJSON_IsString(font)) { + std::string fonts_text_file = font->valuestring; + if (GetAssetData(fonts_text_file, ptr, size)) { + auto text_font = std::make_shared(ptr); + if (text_font->font() == nullptr) { + ESP_LOGE(TAG, "Failed to load fonts.bin"); + return false; + } + + if (emote_display) { + emote_display->AddTextFont(text_font); + } + } else { + ESP_LOGE(TAG, "The font file %s is not found", fonts_text_file.c_str()); + } + } + + cJSON* emoji_collection = cJSON_GetObjectItem(root, "emoji_collection"); + if (cJSON_IsArray(emoji_collection)) { + int emoji_count = cJSON_GetArraySize(emoji_collection); + if (emote_display) { + for (int i = 0; i < emoji_count; i++) { + cJSON* icon = cJSON_GetArrayItem(emoji_collection, i); + if (cJSON_IsObject(icon)) { + cJSON* name = cJSON_GetObjectItem(icon, "name"); + cJSON* file = cJSON_GetObjectItem(icon, "file"); + + if (cJSON_IsString(name) && cJSON_IsString(file)) { + if (GetAssetData(file->valuestring, ptr, size)) { + cJSON* eaf = cJSON_GetObjectItem(icon, "eaf"); + bool lack_value = false; + bool loop_value = false; + int fps_value = 0; + + if (cJSON_IsObject(eaf)) { + cJSON* lack = cJSON_GetObjectItem(eaf, "lack"); + cJSON* loop = cJSON_GetObjectItem(eaf, "loop"); + cJSON* fps = cJSON_GetObjectItem(eaf, "fps"); + + lack_value = lack ? cJSON_IsTrue(lack) : false; + loop_value = loop ? cJSON_IsTrue(loop) : false; + fps_value = fps ? fps->valueint : 0; + + emote_display->AddEmojiData(name->valuestring, ptr, size, + static_cast(fps_value), + loop_value, lack_value); + } + + } else { + ESP_LOGE(TAG, "Emoji \"%10s\" image file %s is not found", name->valuestring, file->valuestring); + } + } + } + } + } + } + + cJSON* icon_collection = cJSON_GetObjectItem(root, "icon_collection"); + if (cJSON_IsArray(icon_collection)) { + if (emote_display) { + int icon_count = cJSON_GetArraySize(icon_collection); + for (int i = 0; i < icon_count; i++) { + cJSON* icon = cJSON_GetArrayItem(icon_collection, i); + if (cJSON_IsObject(icon)) { + cJSON* name = cJSON_GetObjectItem(icon, "name"); + cJSON* file = cJSON_GetObjectItem(icon, "file"); + + if (cJSON_IsString(name) && cJSON_IsString(file)) { + if (GetAssetData(file->valuestring, ptr, size)) { + emote_display->AddIconData(name->valuestring, ptr, size); + } else { + ESP_LOGE(TAG, "Icon \"%10s\" image file %s is not found", name->valuestring, file->valuestring); + } + } + } + } + } + } + + cJSON* layout_json = cJSON_GetObjectItem(root, "layout"); + if (cJSON_IsArray(layout_json)) { + int layout_count = cJSON_GetArraySize(layout_json); + + for (int i = 0; i < layout_count; i++) { + cJSON* layout_item = cJSON_GetArrayItem(layout_json, i); + if (cJSON_IsObject(layout_item)) { + cJSON* name = cJSON_GetObjectItem(layout_item, "name"); + cJSON* align = cJSON_GetObjectItem(layout_item, "align"); + cJSON* x = cJSON_GetObjectItem(layout_item, "x"); + cJSON* y = cJSON_GetObjectItem(layout_item, "y"); + cJSON* width = cJSON_GetObjectItem(layout_item, "width"); + cJSON* height = cJSON_GetObjectItem(layout_item, "height"); + + if (cJSON_IsString(name) && cJSON_IsString(align) && cJSON_IsNumber(x) && cJSON_IsNumber(y)) { + int width_val = cJSON_IsNumber(width) ? width->valueint : 0; + int height_val = cJSON_IsNumber(height) ? height->valueint : 0; + + if (emote_display) { + emote_display->AddLayoutData(name->valuestring, align->valuestring, + x->valueint, y->valueint, width_val, height_val); + } + } else { + ESP_LOGW(TAG, "Invalid layout item %d: missing required fields", i); + } + } + } + } +#endif + + cJSON_Delete(root); + return true; +} + +bool Assets::Download(std::string url, std::function progress_callback) { + ESP_LOGI(TAG, "Downloading new version of assets from %s", url.c_str()); + + // 取消当前资源分区的内存映射 + if (mmap_handle_ != 0) { + esp_partition_munmap(mmap_handle_); + mmap_handle_ = 0; + mmap_root_ = nullptr; + } + checksum_valid_ = false; + assets_.clear(); + + // 下载新的资源文件 + auto network = Board::GetInstance().GetNetwork(); + auto http = network->CreateHttp(0); + + if (!http->Open("GET", url)) { + ESP_LOGE(TAG, "Failed to open HTTP connection"); + return false; + } + + if (http->GetStatusCode() != 200) { + ESP_LOGE(TAG, "Failed to get assets, status code: %d", http->GetStatusCode()); + return false; + } + + size_t content_length = http->GetBodyLength(); + if (content_length == 0) { + ESP_LOGE(TAG, "Failed to get content length"); + return false; + } + + if (content_length > partition_->size) { + ESP_LOGE(TAG, "Assets file size (%u) is larger than partition size (%lu)", content_length, partition_->size); + return false; + } + + // 定义扇区大小为4KB(ESP32的标准扇区大小) + const size_t SECTOR_SIZE = esp_partition_get_main_flash_sector_size(); + + // 计算需要擦除的扇区数量 + size_t sectors_to_erase = (content_length + SECTOR_SIZE - 1) / SECTOR_SIZE; // 向上取整 + size_t total_erase_size = sectors_to_erase * SECTOR_SIZE; + + ESP_LOGI(TAG, "Sector size: %u, content length: %u, sectors to erase: %u, total erase size: %u", + SECTOR_SIZE, content_length, sectors_to_erase, total_erase_size); + + // 写入新的资源文件到分区,一边erase一边写入 + char buffer[512]; + size_t total_written = 0; + size_t recent_written = 0; + size_t current_sector = 0; + auto last_calc_time = esp_timer_get_time(); + + while (true) { + int ret = http->Read(buffer, sizeof(buffer)); + if (ret < 0) { + ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret)); + return false; + } + + if (ret == 0) { + break; + } + + // 检查是否需要擦除新的扇区 + size_t write_end_offset = total_written + ret; + size_t needed_sectors = (write_end_offset + SECTOR_SIZE - 1) / SECTOR_SIZE; + + // 擦除需要的新扇区 + while (current_sector < needed_sectors) { + size_t sector_start = current_sector * SECTOR_SIZE; + size_t sector_end = (current_sector + 1) * SECTOR_SIZE; + + // 确保擦除范围不超过分区大小 + if (sector_end > partition_->size) { + ESP_LOGE(TAG, "Sector end (%u) exceeds partition size (%lu)", sector_end, partition_->size); + return false; + } + + ESP_LOGD(TAG, "Erasing sector %u (offset: %u, size: %u)", current_sector, sector_start, SECTOR_SIZE); + esp_err_t err = esp_partition_erase_range(partition_, sector_start, SECTOR_SIZE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase sector %u at offset %u: %s", current_sector, sector_start, esp_err_to_name(err)); + return false; + } + + current_sector++; + } + + // 写入数据到分区 + esp_err_t err = esp_partition_write(partition_, total_written, buffer, ret); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to write to assets partition at offset %u: %s", total_written, esp_err_to_name(err)); + return false; + } + + total_written += ret; + recent_written += ret; + + // 计算进度和速度 + if (esp_timer_get_time() - last_calc_time >= 1000000 || total_written == content_length || ret == 0) { + size_t progress = total_written * 100 / content_length; + size_t speed = recent_written; // 每秒的字节数 + ESP_LOGI(TAG, "Progress: %u%% (%u/%u), Speed: %u B/s, Sectors erased: %u", + progress, total_written, content_length, speed, current_sector); + if (progress_callback) { + progress_callback(progress, speed); + } + last_calc_time = esp_timer_get_time(); + recent_written = 0; // 重置最近写入的字节数 + } + } + + http->Close(); + + if (total_written != content_length) { + ESP_LOGE(TAG, "Downloaded size (%u) does not match expected size (%u)", total_written, content_length); + return false; + } + + ESP_LOGI(TAG, "Assets download completed, total written: %u bytes, total sectors erased: %u", + total_written, current_sector); + + // 重新初始化资源分区 + if (!InitializePartition()) { + ESP_LOGE(TAG, "Failed to re-initialize assets partition"); + return false; + } + + return true; +} + +bool Assets::GetAssetData(const std::string& name, void*& ptr, size_t& size) { + auto asset = assets_.find(name); + if (asset == assets_.end()) { + return false; + } + auto data = (const char*)(mmap_root_ + asset->second.offset); + if (data[0] != 'Z' || data[1] != 'Z') { + ESP_LOGE(TAG, "The asset %s is not valid with magic %02x%02x", name.c_str(), data[0], data[1]); + return false; + } + + ptr = static_cast(const_cast(data + 2)); + size = asset->second.size; + return true; +} diff --git a/main/assets.h b/main/assets.h index ff9a59e..5907c0d 100644 --- a/main/assets.h +++ b/main/assets.h @@ -1,65 +1,52 @@ -#ifndef ASSETS_H -#define ASSETS_H - -#include -#include -#include - -#include -#include -#include - - -// All combinations of wakenet_model, text_font, emoji_collection can be found from the following url: -// https://github.com/78/xiaozhi-fonts/releases/tag/assets - -#define ASSETS_PUHUI_COMMON_14_1 "none-font_puhui_common_14_1-none.bin" -#define ASSETS_XIAOZHI_WAKENET "wn9_nihaoxiaozhi_tts-none-none.bin" -#define ASSETS_XIAOZHI_WAKENET_SMALL "wn9s_nihaoxiaozhi-none-none.bin" -#define ASSETS_XIAOZHI_PUHUI_COMMON_14_1 "wn9_nihaoxiaozhi_tts-font_puhui_common_14_1-none.bin" -#define ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32 "wn9_nihaoxiaozhi_tts-font_puhui_common_16_4-emojis_32.bin" -#define ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_64 "wn9_nihaoxiaozhi_tts-font_puhui_common_16_4-emojis_64.bin" -#define ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64 "wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-emojis_64.bin" -#define ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64 "wn9_nihaoxiaozhi_tts-font_puhui_common_30_4-emojis_64.bin" -#define ASSETS_XIAOZHI_S_PUHUI_COMMON_14_1 "wn9s_nihaoxiaozhi-font_puhui_common_14_1-none.bin" -#define ASSETS_XIAOZHI_S_PUHUI_COMMON_16_4_EMOJI_32 "wn9s_nihaoxiaozhi-font_puhui_common_16_4-emojis_32.bin" -#define ASSETS_XIAOZHI_S_PUHUI_COMMON_20_4_EMOJI_32 "wn9s_nihaoxiaozhi-font_puhui_common_20_4-emojis_32.bin" -#define ASSETS_XIAOZHI_S_PUHUI_COMMON_20_4_EMOJI_64 "wn9s_nihaoxiaozhi-font_puhui_common_20_4-emojis_64.bin" -#define ASSETS_XIAOZHI_S_PUHUI_COMMON_30_4_EMOJI_64 "wn9s_nihaoxiaozhi-font_puhui_common_30_4-emojis_64.bin" - -struct Asset { - size_t size; - size_t offset; -}; - -class Assets { -public: - Assets(std::string default_assets_url); - ~Assets(); - - bool Download(std::string url, std::function progress_callback); - bool Apply(); - - inline bool partition_valid() const { return partition_valid_; } - inline bool checksum_valid() const { return checksum_valid_; } - inline std::string default_assets_url() const { return default_assets_url_; } - -private: - Assets(const Assets&) = delete; - Assets& operator=(const Assets&) = delete; - - bool InitializePartition(); - uint32_t CalculateChecksum(const char* data, uint32_t length); - bool GetAssetData(const std::string& name, void*& ptr, size_t& size); - - const esp_partition_t* partition_ = nullptr; - esp_partition_mmap_handle_t mmap_handle_ = 0; - const char* mmap_root_ = nullptr; - bool partition_valid_ = false; - bool checksum_valid_ = false; - std::string default_assets_url_; - srmodel_list_t* models_list_ = nullptr; - std::map assets_; -}; - -#endif +#ifndef ASSETS_H +#define ASSETS_H + +#include +#include +#include + +#include +#include +#include + + +struct Asset { + size_t size; + size_t offset; +}; + +class Assets { +public: + static Assets& GetInstance() { + static Assets instance; + return instance; + } + ~Assets(); + + bool Download(std::string url, std::function progress_callback); + bool Apply(); + bool GetAssetData(const std::string& name, void*& ptr, size_t& size); + + inline bool partition_valid() const { return partition_valid_; } + inline bool checksum_valid() const { return checksum_valid_; } + inline std::string default_assets_url() const { return default_assets_url_; } + +private: + Assets(); + Assets(const Assets&) = delete; + Assets& operator=(const Assets&) = delete; + + bool InitializePartition(); + uint32_t CalculateChecksum(const char* data, uint32_t length); + + const esp_partition_t* partition_ = nullptr; + esp_partition_mmap_handle_t mmap_handle_ = 0; + const char* mmap_root_ = nullptr; + bool partition_valid_ = false; + bool checksum_valid_ = false; + std::string default_assets_url_; + srmodel_list_t* models_list_ = nullptr; + std::map assets_; +}; + +#endif diff --git a/main/assets/lang_config.h b/main/assets/lang_config.h index 77479bc..0c28b12 100644 --- a/main/assets/lang_config.h +++ b/main/assets/lang_config.h @@ -1,218 +1,218 @@ -// Auto-generated language config -// Language: zh-CN with en-US fallback -#pragma once - -#include - -#ifndef zh_cn - #define zh_cn // 預設語言 -#endif - -namespace Lang { - // 语言元数据 - constexpr const char* CODE = "zh-CN"; - - // 字符串资源 (en-US as fallback for missing keys) - namespace Strings { - constexpr const char* ACCESS_VIA_BROWSER = ",浏览器访问 "; - constexpr const char* ACTIVATION = "激活设备"; - constexpr const char* BATTERY_CHARGING = "正在充电"; - constexpr const char* BATTERY_FULL = "电量已满"; - constexpr const char* BATTERY_LOW = "电量不足"; - constexpr const char* BATTERY_NEED_CHARGE = "电量低,请充电"; - constexpr const char* CHECKING_NEW_VERSION = "检查新版本..."; - constexpr const char* CHECK_NEW_VERSION_FAILED = "检查新版本失败,将在 %d 秒后重试:%s"; - constexpr const char* CONNECTED_TO = "已连接 "; - constexpr const char* CONNECTING = "连接中..."; - constexpr const char* CONNECTION_SUCCESSFUL = "Connection Successful"; - constexpr const char* CONNECT_TO = "连接 "; - constexpr const char* CONNECT_TO_HOTSPOT = "手机连接热点 "; - constexpr const char* DETECTING_MODULE = "检测模组..."; - constexpr const char* DOWNLOAD_ASSETS_FAILED = "下载资源失败"; - constexpr const char* ENTERING_WIFI_CONFIG_MODE = "进入配网模式..."; - constexpr const char* ERROR = "错误"; - constexpr const char* FOUND_NEW_ASSETS = "发现新资源: %s"; - constexpr const char* HELLO_MY_FRIEND = "你好,我的朋友!"; - constexpr const char* INFO = "信息"; - constexpr const char* INITIALIZING = "正在初始化..."; - constexpr const char* LISTENING = "聆听中..."; - constexpr const char* LOADING_ASSETS = "加载资源..."; - constexpr const char* LOADING_PROTOCOL = "登录服务器..."; - constexpr const char* MAX_VOLUME = "最大音量"; - constexpr const char* MUTED = "已静音"; - constexpr const char* NEW_VERSION = "新版本 "; - constexpr const char* OTA_UPGRADE = "OTA 升级"; - constexpr const char* PIN_ERROR = "请插入 SIM 卡"; - constexpr const char* PLEASE_WAIT = "请稍候..."; - constexpr const char* REGISTERING_NETWORK = "等待网络..."; - constexpr const char* REG_ERROR = "无法接入网络,请检查流量卡状态"; - constexpr const char* RTC_MODE_OFF = "AEC 关闭"; - constexpr const char* RTC_MODE_ON = "AEC 开启"; - constexpr const char* SCANNING_WIFI = "扫描 Wi-Fi..."; - constexpr const char* SERVER_ERROR = "发送失败,请检查网络"; - constexpr const char* SERVER_NOT_CONNECTED = "无法连接服务,请稍后再试"; - constexpr const char* SERVER_NOT_FOUND = "正在寻找可用服务"; - constexpr const char* SERVER_TIMEOUT = "等待响应超时"; - constexpr const char* SPEAKING = "说话中..."; - constexpr const char* STANDBY = "待命"; - constexpr const char* SWITCH_TO_4G_NETWORK = "切换到 4G..."; - constexpr const char* SWITCH_TO_WIFI_NETWORK = "切换到 Wi-Fi..."; - constexpr const char* UPGRADE_FAILED = "升级失败"; - constexpr const char* UPGRADING = "正在升级系统..."; - constexpr const char* VERSION = "版本 "; - constexpr const char* VOLUME = "音量 "; - constexpr const char* WARNING = "警告"; - constexpr const char* WIFI_CONFIG_MODE = "配网模式"; - } - - // 音效资源 (en-US as fallback for missing audio files) - namespace Sounds { - - extern const char ogg_0_start[] asm("_binary_0_ogg_start"); - extern const char ogg_0_end[] asm("_binary_0_ogg_end"); - static const std::string_view OGG_0 { - static_cast(ogg_0_start), - static_cast(ogg_0_end - ogg_0_start) - }; - - extern const char ogg_1_start[] asm("_binary_1_ogg_start"); - extern const char ogg_1_end[] asm("_binary_1_ogg_end"); - static const std::string_view OGG_1 { - static_cast(ogg_1_start), - static_cast(ogg_1_end - ogg_1_start) - }; - - extern const char ogg_2_start[] asm("_binary_2_ogg_start"); - extern const char ogg_2_end[] asm("_binary_2_ogg_end"); - static const std::string_view OGG_2 { - static_cast(ogg_2_start), - static_cast(ogg_2_end - ogg_2_start) - }; - - extern const char ogg_3_start[] asm("_binary_3_ogg_start"); - extern const char ogg_3_end[] asm("_binary_3_ogg_end"); - static const std::string_view OGG_3 { - static_cast(ogg_3_start), - static_cast(ogg_3_end - ogg_3_start) - }; - - extern const char ogg_4_start[] asm("_binary_4_ogg_start"); - extern const char ogg_4_end[] asm("_binary_4_ogg_end"); - static const std::string_view OGG_4 { - static_cast(ogg_4_start), - static_cast(ogg_4_end - ogg_4_start) - }; - - extern const char ogg_5_start[] asm("_binary_5_ogg_start"); - extern const char ogg_5_end[] asm("_binary_5_ogg_end"); - static const std::string_view OGG_5 { - static_cast(ogg_5_start), - static_cast(ogg_5_end - ogg_5_start) - }; - - extern const char ogg_6_start[] asm("_binary_6_ogg_start"); - extern const char ogg_6_end[] asm("_binary_6_ogg_end"); - static const std::string_view OGG_6 { - static_cast(ogg_6_start), - static_cast(ogg_6_end - ogg_6_start) - }; - - extern const char ogg_7_start[] asm("_binary_7_ogg_start"); - extern const char ogg_7_end[] asm("_binary_7_ogg_end"); - static const std::string_view OGG_7 { - static_cast(ogg_7_start), - static_cast(ogg_7_end - ogg_7_start) - }; - - extern const char ogg_8_start[] asm("_binary_8_ogg_start"); - extern const char ogg_8_end[] asm("_binary_8_ogg_end"); - static const std::string_view OGG_8 { - static_cast(ogg_8_start), - static_cast(ogg_8_end - ogg_8_start) - }; - - extern const char ogg_9_start[] asm("_binary_9_ogg_start"); - extern const char ogg_9_end[] asm("_binary_9_ogg_end"); - static const std::string_view OGG_9 { - static_cast(ogg_9_start), - static_cast(ogg_9_end - ogg_9_start) - }; - - extern const char ogg_activation_start[] asm("_binary_activation_ogg_start"); - extern const char ogg_activation_end[] asm("_binary_activation_ogg_end"); - static const std::string_view OGG_ACTIVATION { - static_cast(ogg_activation_start), - static_cast(ogg_activation_end - ogg_activation_start) - }; - - extern const char ogg_err_pin_start[] asm("_binary_err_pin_ogg_start"); - extern const char ogg_err_pin_end[] asm("_binary_err_pin_ogg_end"); - static const std::string_view OGG_ERR_PIN { - static_cast(ogg_err_pin_start), - static_cast(ogg_err_pin_end - ogg_err_pin_start) - }; - - extern const char ogg_err_reg_start[] asm("_binary_err_reg_ogg_start"); - extern const char ogg_err_reg_end[] asm("_binary_err_reg_ogg_end"); - static const std::string_view OGG_ERR_REG { - static_cast(ogg_err_reg_start), - static_cast(ogg_err_reg_end - ogg_err_reg_start) - }; - - extern const char ogg_exclamation_start[] asm("_binary_exclamation_ogg_start"); - extern const char ogg_exclamation_end[] asm("_binary_exclamation_ogg_end"); - static const std::string_view OGG_EXCLAMATION { - static_cast(ogg_exclamation_start), - static_cast(ogg_exclamation_end - ogg_exclamation_start) - }; - - extern const char ogg_low_battery_start[] asm("_binary_low_battery_ogg_start"); - extern const char ogg_low_battery_end[] asm("_binary_low_battery_ogg_end"); - static const std::string_view OGG_LOW_BATTERY { - static_cast(ogg_low_battery_start), - static_cast(ogg_low_battery_end - ogg_low_battery_start) - }; - - extern const char ogg_popup_start[] asm("_binary_popup_ogg_start"); - extern const char ogg_popup_end[] asm("_binary_popup_ogg_end"); - static const std::string_view OGG_POPUP { - static_cast(ogg_popup_start), - static_cast(ogg_popup_end - ogg_popup_start) - }; - - extern const char ogg_success_start[] asm("_binary_success_ogg_start"); - extern const char ogg_success_end[] asm("_binary_success_ogg_end"); - static const std::string_view OGG_SUCCESS { - static_cast(ogg_success_start), - static_cast(ogg_success_end - ogg_success_start) - }; - - extern const char ogg_upgrade_start[] asm("_binary_upgrade_ogg_start"); - extern const char ogg_upgrade_end[] asm("_binary_upgrade_ogg_end"); - static const std::string_view OGG_UPGRADE { - static_cast(ogg_upgrade_start), - static_cast(ogg_upgrade_end - ogg_upgrade_start) - }; - - extern const char ogg_vibration_start[] asm("_binary_vibration_ogg_start"); - extern const char ogg_vibration_end[] asm("_binary_vibration_ogg_end"); - static const std::string_view OGG_VIBRATION { - static_cast(ogg_vibration_start), - static_cast(ogg_vibration_end - ogg_vibration_start) - }; - - extern const char ogg_welcome_start[] asm("_binary_welcome_ogg_start"); - extern const char ogg_welcome_end[] asm("_binary_welcome_ogg_end"); - static const std::string_view OGG_WELCOME { - static_cast(ogg_welcome_start), - static_cast(ogg_welcome_end - ogg_welcome_start) - }; - - extern const char ogg_wificonfig_start[] asm("_binary_wificonfig_ogg_start"); - extern const char ogg_wificonfig_end[] asm("_binary_wificonfig_ogg_end"); - static const std::string_view OGG_WIFICONFIG { - static_cast(ogg_wificonfig_start), - static_cast(ogg_wificonfig_end - ogg_wificonfig_start) - }; - } -} +// Auto-generated language config +// Language: en-US with en-US fallback +#pragma once + +#include + +#ifndef en_us + #define en_us // 預設語言 +#endif + +namespace Lang { + // 语言元数据 + constexpr const char* CODE = "en-US"; + + // 字符串资源 (en-US as fallback for missing keys) + namespace Strings { + constexpr const char* ACCESS_VIA_BROWSER = " Config URL: "; + constexpr const char* ACTIVATION = "Activation"; + constexpr const char* BATTERY_CHARGING = "Charging"; + constexpr const char* BATTERY_FULL = "Battery full"; + constexpr const char* BATTERY_LOW = "Low battery"; + constexpr const char* BATTERY_NEED_CHARGE = "Low battery, please charge"; + constexpr const char* CHECKING_NEW_VERSION = "Checking for new version..."; + constexpr const char* CHECK_NEW_VERSION_FAILED = "Check for new version failed, will retry in %d seconds: %s"; + constexpr const char* CONNECTED_TO = "Connected to "; + constexpr const char* CONNECTING = "Connecting..."; + constexpr const char* CONNECTION_SUCCESSFUL = "Connection Successful"; + constexpr const char* CONNECT_TO = "Connect to "; + constexpr const char* CONNECT_TO_HOTSPOT = "Hotspot: "; + constexpr const char* DETECTING_MODULE = "Detecting module..."; + constexpr const char* DOWNLOAD_ASSETS_FAILED = "Failed to download assets"; + constexpr const char* ENTERING_WIFI_CONFIG_MODE = "Entering Wi-Fi configuration mode..."; + constexpr const char* ERROR = "Error"; + constexpr const char* FOUND_NEW_ASSETS = "Found new assets: %s"; + constexpr const char* HELLO_MY_FRIEND = "Hello, my friend!"; + constexpr const char* INFO = "Information"; + constexpr const char* INITIALIZING = "Initializing..."; + constexpr const char* LISTENING = "Listening..."; + constexpr const char* LOADING_ASSETS = "Loading assets..."; + constexpr const char* LOADING_PROTOCOL = "Logging in..."; + constexpr const char* MAX_VOLUME = "Max volume"; + constexpr const char* MUTED = "Muted"; + constexpr const char* NEW_VERSION = "New version "; + constexpr const char* OTA_UPGRADE = "OTA Upgrade"; + constexpr const char* PIN_ERROR = "Please insert SIM card"; + constexpr const char* PLEASE_WAIT = "Please wait..."; + constexpr const char* REGISTERING_NETWORK = "Waiting for network..."; + constexpr const char* REG_ERROR = "Unable to access network, please check SIM card status"; + constexpr const char* RTC_MODE_OFF = "AEC Off"; + constexpr const char* RTC_MODE_ON = "AEC On"; + constexpr const char* SCANNING_WIFI = "Scanning Wi-Fi..."; + constexpr const char* SERVER_ERROR = "Sending failed, please check the network"; + constexpr const char* SERVER_NOT_CONNECTED = "Unable to connect to service, please try again later"; + constexpr const char* SERVER_NOT_FOUND = "Looking for available service"; + constexpr const char* SERVER_TIMEOUT = "Waiting for response timeout"; + constexpr const char* SPEAKING = "Speaking..."; + constexpr const char* STANDBY = "Standby"; + constexpr const char* SWITCH_TO_4G_NETWORK = "Switching to 4G..."; + constexpr const char* SWITCH_TO_WIFI_NETWORK = "Switching to Wi-Fi..."; + constexpr const char* UPGRADE_FAILED = "Upgrade failed"; + constexpr const char* UPGRADING = "System is upgrading..."; + constexpr const char* VERSION = "Ver "; + constexpr const char* VOLUME = "Volume "; + constexpr const char* WARNING = "Warning"; + constexpr const char* WIFI_CONFIG_MODE = "Wi-Fi Configuration Mode"; + } + + // 音效资源 (en-US as fallback for missing audio files) + namespace Sounds { + + extern const char ogg_0_start[] asm("_binary_0_ogg_start"); + extern const char ogg_0_end[] asm("_binary_0_ogg_end"); + static const std::string_view OGG_0 { + static_cast(ogg_0_start), + static_cast(ogg_0_end - ogg_0_start) + }; + + extern const char ogg_1_start[] asm("_binary_1_ogg_start"); + extern const char ogg_1_end[] asm("_binary_1_ogg_end"); + static const std::string_view OGG_1 { + static_cast(ogg_1_start), + static_cast(ogg_1_end - ogg_1_start) + }; + + extern const char ogg_2_start[] asm("_binary_2_ogg_start"); + extern const char ogg_2_end[] asm("_binary_2_ogg_end"); + static const std::string_view OGG_2 { + static_cast(ogg_2_start), + static_cast(ogg_2_end - ogg_2_start) + }; + + extern const char ogg_3_start[] asm("_binary_3_ogg_start"); + extern const char ogg_3_end[] asm("_binary_3_ogg_end"); + static const std::string_view OGG_3 { + static_cast(ogg_3_start), + static_cast(ogg_3_end - ogg_3_start) + }; + + extern const char ogg_4_start[] asm("_binary_4_ogg_start"); + extern const char ogg_4_end[] asm("_binary_4_ogg_end"); + static const std::string_view OGG_4 { + static_cast(ogg_4_start), + static_cast(ogg_4_end - ogg_4_start) + }; + + extern const char ogg_5_start[] asm("_binary_5_ogg_start"); + extern const char ogg_5_end[] asm("_binary_5_ogg_end"); + static const std::string_view OGG_5 { + static_cast(ogg_5_start), + static_cast(ogg_5_end - ogg_5_start) + }; + + extern const char ogg_6_start[] asm("_binary_6_ogg_start"); + extern const char ogg_6_end[] asm("_binary_6_ogg_end"); + static const std::string_view OGG_6 { + static_cast(ogg_6_start), + static_cast(ogg_6_end - ogg_6_start) + }; + + extern const char ogg_7_start[] asm("_binary_7_ogg_start"); + extern const char ogg_7_end[] asm("_binary_7_ogg_end"); + static const std::string_view OGG_7 { + static_cast(ogg_7_start), + static_cast(ogg_7_end - ogg_7_start) + }; + + extern const char ogg_8_start[] asm("_binary_8_ogg_start"); + extern const char ogg_8_end[] asm("_binary_8_ogg_end"); + static const std::string_view OGG_8 { + static_cast(ogg_8_start), + static_cast(ogg_8_end - ogg_8_start) + }; + + extern const char ogg_9_start[] asm("_binary_9_ogg_start"); + extern const char ogg_9_end[] asm("_binary_9_ogg_end"); + static const std::string_view OGG_9 { + static_cast(ogg_9_start), + static_cast(ogg_9_end - ogg_9_start) + }; + + extern const char ogg_activation_start[] asm("_binary_activation_ogg_start"); + extern const char ogg_activation_end[] asm("_binary_activation_ogg_end"); + static const std::string_view OGG_ACTIVATION { + static_cast(ogg_activation_start), + static_cast(ogg_activation_end - ogg_activation_start) + }; + + extern const char ogg_err_pin_start[] asm("_binary_err_pin_ogg_start"); + extern const char ogg_err_pin_end[] asm("_binary_err_pin_ogg_end"); + static const std::string_view OGG_ERR_PIN { + static_cast(ogg_err_pin_start), + static_cast(ogg_err_pin_end - ogg_err_pin_start) + }; + + extern const char ogg_err_reg_start[] asm("_binary_err_reg_ogg_start"); + extern const char ogg_err_reg_end[] asm("_binary_err_reg_ogg_end"); + static const std::string_view OGG_ERR_REG { + static_cast(ogg_err_reg_start), + static_cast(ogg_err_reg_end - ogg_err_reg_start) + }; + + extern const char ogg_exclamation_start[] asm("_binary_exclamation_ogg_start"); + extern const char ogg_exclamation_end[] asm("_binary_exclamation_ogg_end"); + static const std::string_view OGG_EXCLAMATION { + static_cast(ogg_exclamation_start), + static_cast(ogg_exclamation_end - ogg_exclamation_start) + }; + + extern const char ogg_low_battery_start[] asm("_binary_low_battery_ogg_start"); + extern const char ogg_low_battery_end[] asm("_binary_low_battery_ogg_end"); + static const std::string_view OGG_LOW_BATTERY { + static_cast(ogg_low_battery_start), + static_cast(ogg_low_battery_end - ogg_low_battery_start) + }; + + extern const char ogg_popup_start[] asm("_binary_popup_ogg_start"); + extern const char ogg_popup_end[] asm("_binary_popup_ogg_end"); + static const std::string_view OGG_POPUP { + static_cast(ogg_popup_start), + static_cast(ogg_popup_end - ogg_popup_start) + }; + + extern const char ogg_success_start[] asm("_binary_success_ogg_start"); + extern const char ogg_success_end[] asm("_binary_success_ogg_end"); + static const std::string_view OGG_SUCCESS { + static_cast(ogg_success_start), + static_cast(ogg_success_end - ogg_success_start) + }; + + extern const char ogg_upgrade_start[] asm("_binary_upgrade_ogg_start"); + extern const char ogg_upgrade_end[] asm("_binary_upgrade_ogg_end"); + static const std::string_view OGG_UPGRADE { + static_cast(ogg_upgrade_start), + static_cast(ogg_upgrade_end - ogg_upgrade_start) + }; + + extern const char ogg_vibration_start[] asm("_binary_vibration_ogg_start"); + extern const char ogg_vibration_end[] asm("_binary_vibration_ogg_end"); + static const std::string_view OGG_VIBRATION { + static_cast(ogg_vibration_start), + static_cast(ogg_vibration_end - ogg_vibration_start) + }; + + extern const char ogg_welcome_start[] asm("_binary_welcome_ogg_start"); + extern const char ogg_welcome_end[] asm("_binary_welcome_ogg_end"); + static const std::string_view OGG_WELCOME { + static_cast(ogg_welcome_start), + static_cast(ogg_welcome_end - ogg_welcome_start) + }; + + extern const char ogg_wificonfig_start[] asm("_binary_wificonfig_ogg_start"); + extern const char ogg_wificonfig_end[] asm("_binary_wificonfig_ogg_end"); + static const std::string_view OGG_WIFICONFIG { + static_cast(ogg_wificonfig_start), + static_cast(ogg_wificonfig_end - ogg_wificonfig_start) + }; + } +} diff --git a/main/assets/locales/ar-SA/language.json b/main/assets/locales/ar-SA/language.json index 88263e4..360b1c7 100644 --- a/main/assets/locales/ar-SA/language.json +++ b/main/assets/locales/ar-SA/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "ar-SA" - }, - "strings": { - "WARNING": "تحذير", - "INFO": "معلومات", - "ERROR": "خطأ", - "VERSION": "الإصدار ", - "LOADING_PROTOCOL": "الاتصال بالخادم...", - "INITIALIZING": "التهيئة...", - "PIN_ERROR": "يرجى إدخال بطاقة SIM", - "REG_ERROR": "لا يمكن الوصول إلى الشبكة، يرجى التحقق من حالة بطاقة البيانات", - "DETECTING_MODULE": "اكتشاف الوحدة...", - "REGISTERING_NETWORK": "انتظار الشبكة...", - "CHECKING_NEW_VERSION": "فحص الإصدار الجديد...", - "CHECK_NEW_VERSION_FAILED": "فشل فحص الإصدار الجديد، سيتم المحاولة خلال %d ثانية: %s", - "SWITCH_TO_WIFI_NETWORK": "التبديل إلى Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "التبديل إلى 4G...", - "STANDBY": "في الانتظار", - "CONNECT_TO": "الاتصال بـ ", - "CONNECTING": "جاري الاتصال...", - "CONNECTED_TO": "متصل بـ ", - "LISTENING": "الاستماع...", - "SPEAKING": "التحدث...", - "SERVER_NOT_FOUND": "البحث عن خدمة متاحة", - "SERVER_NOT_CONNECTED": "لا يمكن الاتصال بالخدمة، يرجى المحاولة لاحقاً", - "SERVER_TIMEOUT": "انتهت مهلة الاستجابة", - "SERVER_ERROR": "فشل الإرسال، يرجى التحقق من الشبكة", - "CONNECT_TO_HOTSPOT": "اتصل الهاتف بنقطة الاتصال ", - "ACCESS_VIA_BROWSER": "،الوصول عبر المتصفح ", - "WIFI_CONFIG_MODE": "وضع تكوين الشبكة", - "ENTERING_WIFI_CONFIG_MODE": "الدخول في وضع تكوين الشبكة...", - "SCANNING_WIFI": "فحص Wi-Fi...", - "NEW_VERSION": "إصدار جديد ", - "OTA_UPGRADE": "تحديث OTA", - "UPGRADING": "تحديث النظام...", - "UPGRADE_FAILED": "فشل التحديث", - "ACTIVATION": "تفعيل الجهاز", - "BATTERY_LOW": "البطارية منخفضة", - "BATTERY_CHARGING": "جاري الشحن", - "BATTERY_FULL": "البطارية ممتلئة", - "BATTERY_NEED_CHARGE": "البطارية منخفضة، يرجى الشحن", - "VOLUME": "الصوت ", - "MUTED": "صامت", - "MAX_VOLUME": "أقصى صوت", - "RTC_MODE_OFF": "AEC مُوقف", - "RTC_MODE_ON": "AEC مُشغل", - "DOWNLOAD_ASSETS_FAILED": "فشل في تنزيل الموارد", - "LOADING_ASSETS": "جاري تحميل الموارد...", - "PLEASE_WAIT": "يرجى الانتظار...", - "FOUND_NEW_ASSETS": "تم العثور على موارد جديدة: %s", - "HELLO_MY_FRIEND": "مرحباً، صديقي!" - } +{ + "language": { + "type": "ar-SA" + }, + "strings": { + "WARNING": "تحذير", + "INFO": "معلومات", + "ERROR": "خطأ", + "VERSION": "الإصدار ", + "LOADING_PROTOCOL": "الاتصال بالخادم...", + "INITIALIZING": "التهيئة...", + "PIN_ERROR": "يرجى إدخال بطاقة SIM", + "REG_ERROR": "لا يمكن الوصول إلى الشبكة، يرجى التحقق من حالة بطاقة البيانات", + "DETECTING_MODULE": "اكتشاف الوحدة...", + "REGISTERING_NETWORK": "انتظار الشبكة...", + "CHECKING_NEW_VERSION": "فحص الإصدار الجديد...", + "CHECK_NEW_VERSION_FAILED": "فشل فحص الإصدار الجديد، سيتم المحاولة خلال %d ثانية: %s", + "SWITCH_TO_WIFI_NETWORK": "التبديل إلى Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "التبديل إلى 4G...", + "STANDBY": "في الانتظار", + "CONNECT_TO": "الاتصال بـ ", + "CONNECTING": "جاري الاتصال...", + "CONNECTED_TO": "متصل بـ ", + "LISTENING": "الاستماع...", + "SPEAKING": "التحدث...", + "SERVER_NOT_FOUND": "البحث عن خدمة متاحة", + "SERVER_NOT_CONNECTED": "لا يمكن الاتصال بالخدمة، يرجى المحاولة لاحقاً", + "SERVER_TIMEOUT": "انتهت مهلة الاستجابة", + "SERVER_ERROR": "فشل الإرسال، يرجى التحقق من الشبكة", + "CONNECT_TO_HOTSPOT": "اتصل الهاتف بنقطة الاتصال ", + "ACCESS_VIA_BROWSER": "،الوصول عبر المتصفح ", + "WIFI_CONFIG_MODE": "وضع تكوين الشبكة", + "ENTERING_WIFI_CONFIG_MODE": "الدخول في وضع تكوين الشبكة...", + "SCANNING_WIFI": "فحص Wi-Fi...", + "NEW_VERSION": "إصدار جديد ", + "OTA_UPGRADE": "تحديث OTA", + "UPGRADING": "تحديث النظام...", + "UPGRADE_FAILED": "فشل التحديث", + "ACTIVATION": "تفعيل الجهاز", + "BATTERY_LOW": "البطارية منخفضة", + "BATTERY_CHARGING": "جاري الشحن", + "BATTERY_FULL": "البطارية ممتلئة", + "BATTERY_NEED_CHARGE": "البطارية منخفضة، يرجى الشحن", + "VOLUME": "الصوت ", + "MUTED": "صامت", + "MAX_VOLUME": "أقصى صوت", + "RTC_MODE_OFF": "AEC مُوقف", + "RTC_MODE_ON": "AEC مُشغل", + "DOWNLOAD_ASSETS_FAILED": "فشل في تنزيل الموارد", + "LOADING_ASSETS": "جاري تحميل الموارد...", + "PLEASE_WAIT": "يرجى الانتظار...", + "FOUND_NEW_ASSETS": "تم العثور على موارد جديدة: %s", + "HELLO_MY_FRIEND": "مرحباً، صديقي!" + } } \ No newline at end of file diff --git a/main/assets/locales/cs-CZ/language.json b/main/assets/locales/cs-CZ/language.json index 1e18a5c..410d020 100644 --- a/main/assets/locales/cs-CZ/language.json +++ b/main/assets/locales/cs-CZ/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "cs-CZ" - }, - "strings": { - "WARNING": "Varování", - "INFO": "Informace", - "ERROR": "Chyba", - "VERSION": "Verze ", - "LOADING_PROTOCOL": "Připojování k serveru...", - "INITIALIZING": "Inicializace...", - "PIN_ERROR": "Prosím vložte SIM kartu", - "REG_ERROR": "Nelze se připojit k síti, zkontrolujte stav datové karty", - "DETECTING_MODULE": "Detekce modulu...", - "REGISTERING_NETWORK": "Čekání na síť...", - "CHECKING_NEW_VERSION": "Kontrola nové verze...", - "CHECK_NEW_VERSION_FAILED": "Kontrola nové verze selhala, opakování za %d sekund: %s", - "SWITCH_TO_WIFI_NETWORK": "Přepínání na Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "Přepínání na 4G...", - "STANDBY": "Pohotovost", - "CONNECT_TO": "Připojit k ", - "CONNECTING": "Připojování...", - "CONNECTED_TO": "Připojeno k ", - "LISTENING": "Naslouchání...", - "SPEAKING": "Mluvení...", - "SERVER_NOT_FOUND": "Hledání dostupné služby", - "SERVER_NOT_CONNECTED": "Nelze se připojit ke službě, zkuste to později", - "SERVER_TIMEOUT": "Čas odpovědi vypršel", - "SERVER_ERROR": "Odeslání selhalo, zkontrolujte síť", - "CONNECT_TO_HOTSPOT": "Připojte telefon k hotspotu ", - "ACCESS_VIA_BROWSER": ",přístup přes prohlížeč ", - "WIFI_CONFIG_MODE": "Režim konfigurace sítě", - "ENTERING_WIFI_CONFIG_MODE": "Vstup do režimu konfigurace sítě...", - "SCANNING_WIFI": "Skenování Wi-Fi...", - "NEW_VERSION": "Nová verze ", - "OTA_UPGRADE": "OTA upgrade", - "UPGRADING": "Aktualizace systému...", - "UPGRADE_FAILED": "Upgrade selhal", - "ACTIVATION": "Aktivace zařízení", - "BATTERY_LOW": "Slabá baterie", - "BATTERY_CHARGING": "Nabíjení", - "BATTERY_FULL": "Baterie plná", - "BATTERY_NEED_CHARGE": "Slabá baterie, prosím nabijte", - "VOLUME": "Hlasitost ", - "MUTED": "Ztlumeno", - "MAX_VOLUME": "Maximální hlasitost", - "RTC_MODE_OFF": "AEC vypnuto", - "RTC_MODE_ON": "AEC zapnuto", - "DOWNLOAD_ASSETS_FAILED": "Nepodařilo se stáhnout prostředky", - "LOADING_ASSETS": "Načítání prostředků...", - "PLEASE_WAIT": "Prosím čekejte...", - "FOUND_NEW_ASSETS": "Nalezeny nové prostředky: %s", - "HELLO_MY_FRIEND": "Ahoj, můj příteli!" - } +{ + "language": { + "type": "cs-CZ" + }, + "strings": { + "WARNING": "Varování", + "INFO": "Informace", + "ERROR": "Chyba", + "VERSION": "Verze ", + "LOADING_PROTOCOL": "Připojování k serveru...", + "INITIALIZING": "Inicializace...", + "PIN_ERROR": "Prosím vložte SIM kartu", + "REG_ERROR": "Nelze se připojit k síti, zkontrolujte stav datové karty", + "DETECTING_MODULE": "Detekce modulu...", + "REGISTERING_NETWORK": "Čekání na síť...", + "CHECKING_NEW_VERSION": "Kontrola nové verze...", + "CHECK_NEW_VERSION_FAILED": "Kontrola nové verze selhala, opakování za %d sekund: %s", + "SWITCH_TO_WIFI_NETWORK": "Přepínání na Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "Přepínání na 4G...", + "STANDBY": "Pohotovost", + "CONNECT_TO": "Připojit k ", + "CONNECTING": "Připojování...", + "CONNECTED_TO": "Připojeno k ", + "LISTENING": "Naslouchání...", + "SPEAKING": "Mluvení...", + "SERVER_NOT_FOUND": "Hledání dostupné služby", + "SERVER_NOT_CONNECTED": "Nelze se připojit ke službě, zkuste to později", + "SERVER_TIMEOUT": "Čas odpovědi vypršel", + "SERVER_ERROR": "Odeslání selhalo, zkontrolujte síť", + "CONNECT_TO_HOTSPOT": "Připojte telefon k hotspotu ", + "ACCESS_VIA_BROWSER": ",přístup přes prohlížeč ", + "WIFI_CONFIG_MODE": "Režim konfigurace sítě", + "ENTERING_WIFI_CONFIG_MODE": "Vstup do režimu konfigurace sítě...", + "SCANNING_WIFI": "Skenování Wi-Fi...", + "NEW_VERSION": "Nová verze ", + "OTA_UPGRADE": "OTA upgrade", + "UPGRADING": "Aktualizace systému...", + "UPGRADE_FAILED": "Upgrade selhal", + "ACTIVATION": "Aktivace zařízení", + "BATTERY_LOW": "Slabá baterie", + "BATTERY_CHARGING": "Nabíjení", + "BATTERY_FULL": "Baterie plná", + "BATTERY_NEED_CHARGE": "Slabá baterie, prosím nabijte", + "VOLUME": "Hlasitost ", + "MUTED": "Ztlumeno", + "MAX_VOLUME": "Maximální hlasitost", + "RTC_MODE_OFF": "AEC vypnuto", + "RTC_MODE_ON": "AEC zapnuto", + "DOWNLOAD_ASSETS_FAILED": "Nepodařilo se stáhnout prostředky", + "LOADING_ASSETS": "Načítání prostředků...", + "PLEASE_WAIT": "Prosím čekejte...", + "FOUND_NEW_ASSETS": "Nalezeny nové prostředky: %s", + "HELLO_MY_FRIEND": "Ahoj, můj příteli!" + } } \ No newline at end of file diff --git a/main/assets/locales/de-DE/language.json b/main/assets/locales/de-DE/language.json index 724e267..9b81ea2 100644 --- a/main/assets/locales/de-DE/language.json +++ b/main/assets/locales/de-DE/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "de-DE" - }, - "strings": { - "WARNING": "Warnung", - "INFO": "Information", - "ERROR": "Fehler", - "VERSION": "Version ", - "LOADING_PROTOCOL": "Verbindung zum Server...", - "INITIALIZING": "Initialisierung...", - "PIN_ERROR": "Bitte SIM-Karte einlegen", - "REG_ERROR": "Netzwerkverbindung fehlgeschlagen, bitte Datenkartenstatus prüfen", - "DETECTING_MODULE": "Modul erkennen...", - "REGISTERING_NETWORK": "Auf Netzwerk warten...", - "CHECKING_NEW_VERSION": "Neue Version prüfen...", - "CHECK_NEW_VERSION_FAILED": "Neue Version prüfen fehlgeschlagen, Wiederholung in %d Sekunden: %s", - "SWITCH_TO_WIFI_NETWORK": "Zu Wi-Fi wechseln...", - "SWITCH_TO_4G_NETWORK": "Zu 4G wechseln...", - "STANDBY": "Bereitschaft", - "CONNECT_TO": "Verbinden zu ", - "CONNECTING": "Verbindung wird hergestellt...", - "CONNECTED_TO": "Verbunden mit ", - "LISTENING": "Zuhören...", - "SPEAKING": "Sprechen...", - "SERVER_NOT_FOUND": "Verfügbaren Service suchen", - "SERVER_NOT_CONNECTED": "Service-Verbindung fehlgeschlagen, bitte später versuchen", - "SERVER_TIMEOUT": "Antwort-Timeout", - "SERVER_ERROR": "Senden fehlgeschlagen, bitte Netzwerk prüfen", - "CONNECT_TO_HOTSPOT": "Handy mit Hotspot verbinden ", - "ACCESS_VIA_BROWSER": ",Browser öffnen ", - "WIFI_CONFIG_MODE": "Netzwerkkonfigurationsmodus", - "ENTERING_WIFI_CONFIG_MODE": "Netzwerkkonfigurationsmodus eingeben...", - "SCANNING_WIFI": "Wi-Fi scannen...", - "NEW_VERSION": "Neue Version ", - "OTA_UPGRADE": "OTA-Upgrade", - "UPGRADING": "System wird aktualisiert...", - "UPGRADE_FAILED": "Upgrade fehlgeschlagen", - "ACTIVATION": "Gerät aktivieren", - "BATTERY_LOW": "Niedriger Batteriestand", - "BATTERY_CHARGING": "Wird geladen", - "BATTERY_FULL": "Batterie voll", - "BATTERY_NEED_CHARGE": "Niedriger Batteriestand, bitte aufladen", - "VOLUME": "Lautstärke ", - "MUTED": "Stummgeschaltet", - "MAX_VOLUME": "Maximale Lautstärke", - "RTC_MODE_OFF": "AEC aus", - "RTC_MODE_ON": "AEC ein", - "DOWNLOAD_ASSETS_FAILED": "Fehler beim Herunterladen der Ressourcen", - "LOADING_ASSETS": "Ressourcen werden geladen...", - "PLEASE_WAIT": "Bitte warten...", - "FOUND_NEW_ASSETS": "Neue Ressourcen gefunden: %s", - "HELLO_MY_FRIEND": "Hallo, mein Freund!" - } +{ + "language": { + "type": "de-DE" + }, + "strings": { + "WARNING": "Warnung", + "INFO": "Information", + "ERROR": "Fehler", + "VERSION": "Version ", + "LOADING_PROTOCOL": "Verbindung zum Server...", + "INITIALIZING": "Initialisierung...", + "PIN_ERROR": "Bitte SIM-Karte einlegen", + "REG_ERROR": "Netzwerkverbindung fehlgeschlagen, bitte Datenkartenstatus prüfen", + "DETECTING_MODULE": "Modul erkennen...", + "REGISTERING_NETWORK": "Auf Netzwerk warten...", + "CHECKING_NEW_VERSION": "Neue Version prüfen...", + "CHECK_NEW_VERSION_FAILED": "Neue Version prüfen fehlgeschlagen, Wiederholung in %d Sekunden: %s", + "SWITCH_TO_WIFI_NETWORK": "Zu Wi-Fi wechseln...", + "SWITCH_TO_4G_NETWORK": "Zu 4G wechseln...", + "STANDBY": "Bereitschaft", + "CONNECT_TO": "Verbinden zu ", + "CONNECTING": "Verbindung wird hergestellt...", + "CONNECTED_TO": "Verbunden mit ", + "LISTENING": "Zuhören...", + "SPEAKING": "Sprechen...", + "SERVER_NOT_FOUND": "Verfügbaren Service suchen", + "SERVER_NOT_CONNECTED": "Service-Verbindung fehlgeschlagen, bitte später versuchen", + "SERVER_TIMEOUT": "Antwort-Timeout", + "SERVER_ERROR": "Senden fehlgeschlagen, bitte Netzwerk prüfen", + "CONNECT_TO_HOTSPOT": "Handy mit Hotspot verbinden ", + "ACCESS_VIA_BROWSER": ",Browser öffnen ", + "WIFI_CONFIG_MODE": "Netzwerkkonfigurationsmodus", + "ENTERING_WIFI_CONFIG_MODE": "Netzwerkkonfigurationsmodus eingeben...", + "SCANNING_WIFI": "Wi-Fi scannen...", + "NEW_VERSION": "Neue Version ", + "OTA_UPGRADE": "OTA-Upgrade", + "UPGRADING": "System wird aktualisiert...", + "UPGRADE_FAILED": "Upgrade fehlgeschlagen", + "ACTIVATION": "Gerät aktivieren", + "BATTERY_LOW": "Niedriger Batteriestand", + "BATTERY_CHARGING": "Wird geladen", + "BATTERY_FULL": "Batterie voll", + "BATTERY_NEED_CHARGE": "Niedriger Batteriestand, bitte aufladen", + "VOLUME": "Lautstärke ", + "MUTED": "Stummgeschaltet", + "MAX_VOLUME": "Maximale Lautstärke", + "RTC_MODE_OFF": "AEC aus", + "RTC_MODE_ON": "AEC ein", + "DOWNLOAD_ASSETS_FAILED": "Fehler beim Herunterladen der Ressourcen", + "LOADING_ASSETS": "Ressourcen werden geladen...", + "PLEASE_WAIT": "Bitte warten...", + "FOUND_NEW_ASSETS": "Neue Ressourcen gefunden: %s", + "HELLO_MY_FRIEND": "Hallo, mein Freund!" + } } \ No newline at end of file diff --git a/main/assets/locales/en-US/language.json b/main/assets/locales/en-US/language.json index 1758d34..16ffc9f 100644 --- a/main/assets/locales/en-US/language.json +++ b/main/assets/locales/en-US/language.json @@ -1,56 +1,56 @@ -{ - "language": { - "type": "en-US" - }, - "strings": { - "WARNING": "Warning", - "INFO": "Information", - "ERROR": "Error", - "VERSION": "Ver ", - "LOADING_PROTOCOL": "Logging in...", - "INITIALIZING": "Initializing...", - "PIN_ERROR": "Please insert SIM card", - "REG_ERROR": "Unable to access network, please check SIM card status", - "DETECTING_MODULE": "Detecting module...", - "REGISTERING_NETWORK": "Waiting for network...", - "CHECKING_NEW_VERSION": "Checking for new version...", - "CHECK_NEW_VERSION_FAILED": "Check for new version failed, will retry in %d seconds: %s", - "SWITCH_TO_WIFI_NETWORK": "Switching to Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "Switching to 4G...", - "STANDBY": "Standby", - "CONNECT_TO": "Connect to ", - "CONNECTING": "Connecting...", - "CONNECTION_SUCCESSFUL": "Connection Successful", - "CONNECTED_TO": "Connected to ", - "LISTENING": "Listening...", - "SPEAKING": "Speaking...", - "SERVER_NOT_FOUND": "Looking for available service", - "SERVER_NOT_CONNECTED": "Unable to connect to service, please try again later", - "SERVER_TIMEOUT": "Waiting for response timeout", - "SERVER_ERROR": "Sending failed, please check the network", - "CONNECT_TO_HOTSPOT": "Hotspot: ", - "ACCESS_VIA_BROWSER": " Config URL: ", - "WIFI_CONFIG_MODE": "Wi-Fi Configuration Mode", - "ENTERING_WIFI_CONFIG_MODE": "Entering Wi-Fi configuration mode...", - "SCANNING_WIFI": "Scanning Wi-Fi...", - "NEW_VERSION": "New version ", - "OTA_UPGRADE": "OTA Upgrade", - "UPGRADING": "System is upgrading...", - "UPGRADE_FAILED": "Upgrade failed", - "ACTIVATION": "Activation", - "BATTERY_LOW": "Low battery", - "BATTERY_CHARGING": "Charging", - "BATTERY_FULL": "Battery full", - "BATTERY_NEED_CHARGE": "Low battery, please charge", - "VOLUME": "Volume ", - "MUTED": "Muted", - "MAX_VOLUME": "Max volume", - "RTC_MODE_OFF": "AEC Off", - "RTC_MODE_ON": "AEC On", - "PLEASE_WAIT": "Please wait...", - "FOUND_NEW_ASSETS": "Found new assets: %s", - "DOWNLOAD_ASSETS_FAILED": "Failed to download assets", - "LOADING_ASSETS": "Loading assets...", - "HELLO_MY_FRIEND": "Hello, my friend!" - } +{ + "language": { + "type": "en-US" + }, + "strings": { + "WARNING": "Warning", + "INFO": "Information", + "ERROR": "Error", + "VERSION": "Ver ", + "LOADING_PROTOCOL": "Logging in...", + "INITIALIZING": "Initializing...", + "PIN_ERROR": "Please insert SIM card", + "REG_ERROR": "Unable to access network, please check SIM card status", + "DETECTING_MODULE": "Detecting module...", + "REGISTERING_NETWORK": "Waiting for network...", + "CHECKING_NEW_VERSION": "Checking for new version...", + "CHECK_NEW_VERSION_FAILED": "Check for new version failed, will retry in %d seconds: %s", + "SWITCH_TO_WIFI_NETWORK": "Switching to Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "Switching to 4G...", + "STANDBY": "Standby", + "CONNECT_TO": "Connect to ", + "CONNECTING": "Connecting...", + "CONNECTION_SUCCESSFUL": "Connection Successful", + "CONNECTED_TO": "Connected to ", + "LISTENING": "Listening...", + "SPEAKING": "Speaking...", + "SERVER_NOT_FOUND": "Looking for available service", + "SERVER_NOT_CONNECTED": "Unable to connect to service, please try again later", + "SERVER_TIMEOUT": "Waiting for response timeout", + "SERVER_ERROR": "Sending failed, please check the network", + "CONNECT_TO_HOTSPOT": "Hotspot: ", + "ACCESS_VIA_BROWSER": " Config URL: ", + "WIFI_CONFIG_MODE": "Wi-Fi Configuration Mode", + "ENTERING_WIFI_CONFIG_MODE": "Entering Wi-Fi configuration mode...", + "SCANNING_WIFI": "Scanning Wi-Fi...", + "NEW_VERSION": "New version ", + "OTA_UPGRADE": "OTA Upgrade", + "UPGRADING": "System is upgrading...", + "UPGRADE_FAILED": "Upgrade failed", + "ACTIVATION": "Activation", + "BATTERY_LOW": "Low battery", + "BATTERY_CHARGING": "Charging", + "BATTERY_FULL": "Battery full", + "BATTERY_NEED_CHARGE": "Low battery, please charge", + "VOLUME": "Volume ", + "MUTED": "Muted", + "MAX_VOLUME": "Max volume", + "RTC_MODE_OFF": "AEC Off", + "RTC_MODE_ON": "AEC On", + "PLEASE_WAIT": "Please wait...", + "FOUND_NEW_ASSETS": "Found new assets: %s", + "DOWNLOAD_ASSETS_FAILED": "Failed to download assets", + "LOADING_ASSETS": "Loading assets...", + "HELLO_MY_FRIEND": "Hello, my friend!" + } } \ No newline at end of file diff --git a/main/assets/locales/es-ES/language.json b/main/assets/locales/es-ES/language.json index e7f349b..2b80807 100644 --- a/main/assets/locales/es-ES/language.json +++ b/main/assets/locales/es-ES/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "es-ES" - }, - "strings": { - "WARNING": "Advertencia", - "INFO": "Información", - "ERROR": "Error", - "VERSION": "Versión ", - "LOADING_PROTOCOL": "Conectando al servidor...", - "INITIALIZING": "Inicializando...", - "PIN_ERROR": "Por favor inserte la tarjeta SIM", - "REG_ERROR": "No se puede acceder a la red, verifique el estado de la tarjeta de datos", - "DETECTING_MODULE": "Detectando módulo...", - "REGISTERING_NETWORK": "Esperando red...", - "CHECKING_NEW_VERSION": "Verificando nueva versión...", - "CHECK_NEW_VERSION_FAILED": "Error al verificar nueva versión, reintentando en %d segundos: %s", - "SWITCH_TO_WIFI_NETWORK": "Cambiando a Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "Cambiando a 4G...", - "STANDBY": "En espera", - "CONNECT_TO": "Conectar a ", - "CONNECTING": "Conectando...", - "CONNECTED_TO": "Conectado a ", - "LISTENING": "Escuchando...", - "SPEAKING": "Hablando...", - "SERVER_NOT_FOUND": "Buscando servicio disponible", - "SERVER_NOT_CONNECTED": "No se puede conectar al servicio, inténtelo más tarde", - "SERVER_TIMEOUT": "Tiempo de espera agotado", - "SERVER_ERROR": "Error de envío, verifique la red", - "CONNECT_TO_HOTSPOT": "Conectar teléfono al punto de acceso ", - "ACCESS_VIA_BROWSER": ",acceder mediante navegador ", - "WIFI_CONFIG_MODE": "Modo configuración de red", - "ENTERING_WIFI_CONFIG_MODE": "Entrando en modo configuración de red...", - "SCANNING_WIFI": "Escaneando Wi-Fi...", - "NEW_VERSION": "Nueva versión ", - "OTA_UPGRADE": "Actualización OTA", - "UPGRADING": "Actualizando sistema...", - "UPGRADE_FAILED": "Actualización fallida", - "ACTIVATION": "Activación del dispositivo", - "BATTERY_LOW": "Batería baja", - "BATTERY_CHARGING": "Cargando", - "BATTERY_FULL": "Batería llena", - "BATTERY_NEED_CHARGE": "Batería baja, por favor cargar", - "VOLUME": "Volumen ", - "MUTED": "Silenciado", - "MAX_VOLUME": "Volumen máximo", - "RTC_MODE_OFF": "AEC desactivado", - "RTC_MODE_ON": "AEC activado", - "DOWNLOAD_ASSETS_FAILED": "Error al descargar recursos", - "LOADING_ASSETS": "Cargando recursos...", - "PLEASE_WAIT": "Por favor espere...", - "FOUND_NEW_ASSETS": "Encontrados nuevos recursos: %s", - "HELLO_MY_FRIEND": "¡Hola, mi amigo!" - } +{ + "language": { + "type": "es-ES" + }, + "strings": { + "WARNING": "Advertencia", + "INFO": "Información", + "ERROR": "Error", + "VERSION": "Versión ", + "LOADING_PROTOCOL": "Conectando al servidor...", + "INITIALIZING": "Inicializando...", + "PIN_ERROR": "Por favor inserte la tarjeta SIM", + "REG_ERROR": "No se puede acceder a la red, verifique el estado de la tarjeta de datos", + "DETECTING_MODULE": "Detectando módulo...", + "REGISTERING_NETWORK": "Esperando red...", + "CHECKING_NEW_VERSION": "Verificando nueva versión...", + "CHECK_NEW_VERSION_FAILED": "Error al verificar nueva versión, reintentando en %d segundos: %s", + "SWITCH_TO_WIFI_NETWORK": "Cambiando a Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "Cambiando a 4G...", + "STANDBY": "En espera", + "CONNECT_TO": "Conectar a ", + "CONNECTING": "Conectando...", + "CONNECTED_TO": "Conectado a ", + "LISTENING": "Escuchando...", + "SPEAKING": "Hablando...", + "SERVER_NOT_FOUND": "Buscando servicio disponible", + "SERVER_NOT_CONNECTED": "No se puede conectar al servicio, inténtelo más tarde", + "SERVER_TIMEOUT": "Tiempo de espera agotado", + "SERVER_ERROR": "Error de envío, verifique la red", + "CONNECT_TO_HOTSPOT": "Conectar teléfono al punto de acceso ", + "ACCESS_VIA_BROWSER": ",acceder mediante navegador ", + "WIFI_CONFIG_MODE": "Modo configuración de red", + "ENTERING_WIFI_CONFIG_MODE": "Entrando en modo configuración de red...", + "SCANNING_WIFI": "Escaneando Wi-Fi...", + "NEW_VERSION": "Nueva versión ", + "OTA_UPGRADE": "Actualización OTA", + "UPGRADING": "Actualizando sistema...", + "UPGRADE_FAILED": "Actualización fallida", + "ACTIVATION": "Activación del dispositivo", + "BATTERY_LOW": "Batería baja", + "BATTERY_CHARGING": "Cargando", + "BATTERY_FULL": "Batería llena", + "BATTERY_NEED_CHARGE": "Batería baja, por favor cargar", + "VOLUME": "Volumen ", + "MUTED": "Silenciado", + "MAX_VOLUME": "Volumen máximo", + "RTC_MODE_OFF": "AEC desactivado", + "RTC_MODE_ON": "AEC activado", + "DOWNLOAD_ASSETS_FAILED": "Error al descargar recursos", + "LOADING_ASSETS": "Cargando recursos...", + "PLEASE_WAIT": "Por favor espere...", + "FOUND_NEW_ASSETS": "Encontrados nuevos recursos: %s", + "HELLO_MY_FRIEND": "¡Hola, mi amigo!" + } } \ No newline at end of file diff --git a/main/assets/locales/fi-FI/language.json b/main/assets/locales/fi-FI/language.json index 2326ee4..7c57248 100644 --- a/main/assets/locales/fi-FI/language.json +++ b/main/assets/locales/fi-FI/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "fi-FI" - }, - "strings": { - "WARNING": "Varoitus", - "INFO": "Tieto", - "ERROR": "Virhe", - "VERSION": "Versio ", - "LOADING_PROTOCOL": "Yhdistetään palvelimeen...", - "INITIALIZING": "Alustetaan...", - "PIN_ERROR": "Ole hyvä ja aseta SIM-kortti", - "REG_ERROR": "Ei voi muodostaa yhteyttä verkkoon, tarkista datakortin tila", - "DETECTING_MODULE": "Tunnistetaan moduuli...", - "REGISTERING_NETWORK": "Odotetaan verkkoa...", - "CHECKING_NEW_VERSION": "Tarkistetaan uutta versiota...", - "CHECK_NEW_VERSION_FAILED": "Uuden version tarkistus epäonnistui, yritetään uudelleen %d sekunnin kuluttua: %s", - "SWITCH_TO_WIFI_NETWORK": "Vaihdetaan Wi-Fi:hin...", - "SWITCH_TO_4G_NETWORK": "Vaihdetaan 4G:hen...", - "STANDBY": "Valmiustila", - "CONNECT_TO": "Yhdistä ", - "CONNECTING": "Yhdistetään...", - "CONNECTED_TO": "Yhdistetty ", - "LISTENING": "Kuunnellaan...", - "SPEAKING": "Puhutaan...", - "SERVER_NOT_FOUND": "Etsitään käytettävissä olevaa palvelua", - "SERVER_NOT_CONNECTED": "Ei voi yhdistää palveluun, yritä myöhemmin", - "SERVER_TIMEOUT": "Vastauksen aikakatkaisu", - "SERVER_ERROR": "Lähetys epäonnistui, tarkista verkko", - "CONNECT_TO_HOTSPOT": "Yhdistä puhelin hotspottiin ", - "ACCESS_VIA_BROWSER": ",pääsy selaimen kautta ", - "WIFI_CONFIG_MODE": "Verkon konfigurointitila", - "ENTERING_WIFI_CONFIG_MODE": "Siirrytään verkon konfigurointitilaan...", - "SCANNING_WIFI": "Skannataan Wi-Fi...", - "NEW_VERSION": "Uusi versio ", - "OTA_UPGRADE": "OTA-päivitys", - "UPGRADING": "Päivitetään järjestelmää...", - "UPGRADE_FAILED": "Päivitys epäonnistui", - "ACTIVATION": "Laitteen aktivointi", - "BATTERY_LOW": "Akku vähissä", - "BATTERY_CHARGING": "Ladataan", - "BATTERY_FULL": "Akku täynnä", - "BATTERY_NEED_CHARGE": "Akku vähissä, ole hyvä ja lataa", - "VOLUME": "Äänenvoimakkuus ", - "MUTED": "Mykistetty", - "MAX_VOLUME": "Maksimi äänenvoimakkuus", - "RTC_MODE_OFF": "AEC pois päältä", - "RTC_MODE_ON": "AEC päällä", - "DOWNLOAD_ASSETS_FAILED": "Resurssien lataaminen epäonnistui", - "LOADING_ASSETS": "Ladataan resursseja...", - "PLEASE_WAIT": "Odota hetki...", - "FOUND_NEW_ASSETS": "Löydetty uusia resursseja: %s", - "HELLO_MY_FRIEND": "Hei, ystäväni!" - } +{ + "language": { + "type": "fi-FI" + }, + "strings": { + "WARNING": "Varoitus", + "INFO": "Tieto", + "ERROR": "Virhe", + "VERSION": "Versio ", + "LOADING_PROTOCOL": "Yhdistetään palvelimeen...", + "INITIALIZING": "Alustetaan...", + "PIN_ERROR": "Ole hyvä ja aseta SIM-kortti", + "REG_ERROR": "Ei voi muodostaa yhteyttä verkkoon, tarkista datakortin tila", + "DETECTING_MODULE": "Tunnistetaan moduuli...", + "REGISTERING_NETWORK": "Odotetaan verkkoa...", + "CHECKING_NEW_VERSION": "Tarkistetaan uutta versiota...", + "CHECK_NEW_VERSION_FAILED": "Uuden version tarkistus epäonnistui, yritetään uudelleen %d sekunnin kuluttua: %s", + "SWITCH_TO_WIFI_NETWORK": "Vaihdetaan Wi-Fi:hin...", + "SWITCH_TO_4G_NETWORK": "Vaihdetaan 4G:hen...", + "STANDBY": "Valmiustila", + "CONNECT_TO": "Yhdistä ", + "CONNECTING": "Yhdistetään...", + "CONNECTED_TO": "Yhdistetty ", + "LISTENING": "Kuunnellaan...", + "SPEAKING": "Puhutaan...", + "SERVER_NOT_FOUND": "Etsitään käytettävissä olevaa palvelua", + "SERVER_NOT_CONNECTED": "Ei voi yhdistää palveluun, yritä myöhemmin", + "SERVER_TIMEOUT": "Vastauksen aikakatkaisu", + "SERVER_ERROR": "Lähetys epäonnistui, tarkista verkko", + "CONNECT_TO_HOTSPOT": "Yhdistä puhelin hotspottiin ", + "ACCESS_VIA_BROWSER": ",pääsy selaimen kautta ", + "WIFI_CONFIG_MODE": "Verkon konfigurointitila", + "ENTERING_WIFI_CONFIG_MODE": "Siirrytään verkon konfigurointitilaan...", + "SCANNING_WIFI": "Skannataan Wi-Fi...", + "NEW_VERSION": "Uusi versio ", + "OTA_UPGRADE": "OTA-päivitys", + "UPGRADING": "Päivitetään järjestelmää...", + "UPGRADE_FAILED": "Päivitys epäonnistui", + "ACTIVATION": "Laitteen aktivointi", + "BATTERY_LOW": "Akku vähissä", + "BATTERY_CHARGING": "Ladataan", + "BATTERY_FULL": "Akku täynnä", + "BATTERY_NEED_CHARGE": "Akku vähissä, ole hyvä ja lataa", + "VOLUME": "Äänenvoimakkuus ", + "MUTED": "Mykistetty", + "MAX_VOLUME": "Maksimi äänenvoimakkuus", + "RTC_MODE_OFF": "AEC pois päältä", + "RTC_MODE_ON": "AEC päällä", + "DOWNLOAD_ASSETS_FAILED": "Resurssien lataaminen epäonnistui", + "LOADING_ASSETS": "Ladataan resursseja...", + "PLEASE_WAIT": "Odota hetki...", + "FOUND_NEW_ASSETS": "Löydetty uusia resursseja: %s", + "HELLO_MY_FRIEND": "Hei, ystäväni!" + } } \ No newline at end of file diff --git a/main/assets/locales/fr-FR/language.json b/main/assets/locales/fr-FR/language.json index 244cade..71b2586 100644 --- a/main/assets/locales/fr-FR/language.json +++ b/main/assets/locales/fr-FR/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "fr-FR" - }, - "strings": { - "WARNING": "Avertissement", - "INFO": "Information", - "ERROR": "Erreur", - "VERSION": "Version ", - "LOADING_PROTOCOL": "Connexion au serveur...", - "INITIALIZING": "Initialisation...", - "PIN_ERROR": "Veuillez insérer la carte SIM", - "REG_ERROR": "Impossible d'accéder au réseau, veuillez vérifier l'état de la carte de données", - "DETECTING_MODULE": "Détection du module...", - "REGISTERING_NETWORK": "En attente du réseau...", - "CHECKING_NEW_VERSION": "Vérification de nouvelle version...", - "CHECK_NEW_VERSION_FAILED": "Échec de vérification de nouvelle version, nouvelle tentative dans %d secondes : %s", - "SWITCH_TO_WIFI_NETWORK": "Basculer vers Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "Basculer vers 4G...", - "STANDBY": "En attente", - "CONNECT_TO": "Se connecter à ", - "CONNECTING": "Connexion en cours...", - "CONNECTED_TO": "Connecté à ", - "LISTENING": "Écoute...", - "SPEAKING": "Parole...", - "SERVER_NOT_FOUND": "Recherche d'un service disponible", - "SERVER_NOT_CONNECTED": "Impossible de se connecter au service, veuillez réessayer plus tard", - "SERVER_TIMEOUT": "Délai d'attente de réponse", - "SERVER_ERROR": "Échec d'envoi, veuillez vérifier le réseau", - "CONNECT_TO_HOTSPOT": "Connecter le téléphone au point d'accès ", - "ACCESS_VIA_BROWSER": ",accéder via le navigateur ", - "WIFI_CONFIG_MODE": "Mode configuration réseau", - "ENTERING_WIFI_CONFIG_MODE": "Entrer en mode configuration réseau...", - "SCANNING_WIFI": "Scan Wi-Fi...", - "NEW_VERSION": "Nouvelle version ", - "OTA_UPGRADE": "Mise à jour OTA", - "UPGRADING": "Mise à jour du système...", - "UPGRADE_FAILED": "Échec de mise à jour", - "ACTIVATION": "Activation de l'appareil", - "BATTERY_LOW": "Batterie faible", - "BATTERY_CHARGING": "En charge", - "BATTERY_FULL": "Batterie pleine", - "BATTERY_NEED_CHARGE": "Batterie faible, veuillez charger", - "VOLUME": "Volume ", - "MUTED": "Muet", - "MAX_VOLUME": "Volume maximum", - "RTC_MODE_OFF": "AEC désactivé", - "RTC_MODE_ON": "AEC activé", - "DOWNLOAD_ASSETS_FAILED": "Échec du téléchargement des ressources", - "LOADING_ASSETS": "Chargement des ressources...", - "PLEASE_WAIT": "Veuillez patienter...", - "FOUND_NEW_ASSETS": "Nouvelles ressources trouvées: %s", - "HELLO_MY_FRIEND": "Bonjour, mon ami !" - } +{ + "language": { + "type": "fr-FR" + }, + "strings": { + "WARNING": "Avertissement", + "INFO": "Information", + "ERROR": "Erreur", + "VERSION": "Version ", + "LOADING_PROTOCOL": "Connexion au serveur...", + "INITIALIZING": "Initialisation...", + "PIN_ERROR": "Veuillez insérer la carte SIM", + "REG_ERROR": "Impossible d'accéder au réseau, veuillez vérifier l'état de la carte de données", + "DETECTING_MODULE": "Détection du module...", + "REGISTERING_NETWORK": "En attente du réseau...", + "CHECKING_NEW_VERSION": "Vérification de nouvelle version...", + "CHECK_NEW_VERSION_FAILED": "Échec de vérification de nouvelle version, nouvelle tentative dans %d secondes : %s", + "SWITCH_TO_WIFI_NETWORK": "Basculer vers Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "Basculer vers 4G...", + "STANDBY": "En attente", + "CONNECT_TO": "Se connecter à ", + "CONNECTING": "Connexion en cours...", + "CONNECTED_TO": "Connecté à ", + "LISTENING": "Écoute...", + "SPEAKING": "Parole...", + "SERVER_NOT_FOUND": "Recherche d'un service disponible", + "SERVER_NOT_CONNECTED": "Impossible de se connecter au service, veuillez réessayer plus tard", + "SERVER_TIMEOUT": "Délai d'attente de réponse", + "SERVER_ERROR": "Échec d'envoi, veuillez vérifier le réseau", + "CONNECT_TO_HOTSPOT": "Connecter le téléphone au point d'accès ", + "ACCESS_VIA_BROWSER": ",accéder via le navigateur ", + "WIFI_CONFIG_MODE": "Mode configuration réseau", + "ENTERING_WIFI_CONFIG_MODE": "Entrer en mode configuration réseau...", + "SCANNING_WIFI": "Scan Wi-Fi...", + "NEW_VERSION": "Nouvelle version ", + "OTA_UPGRADE": "Mise à jour OTA", + "UPGRADING": "Mise à jour du système...", + "UPGRADE_FAILED": "Échec de mise à jour", + "ACTIVATION": "Activation de l'appareil", + "BATTERY_LOW": "Batterie faible", + "BATTERY_CHARGING": "En charge", + "BATTERY_FULL": "Batterie pleine", + "BATTERY_NEED_CHARGE": "Batterie faible, veuillez charger", + "VOLUME": "Volume ", + "MUTED": "Muet", + "MAX_VOLUME": "Volume maximum", + "RTC_MODE_OFF": "AEC désactivé", + "RTC_MODE_ON": "AEC activé", + "DOWNLOAD_ASSETS_FAILED": "Échec du téléchargement des ressources", + "LOADING_ASSETS": "Chargement des ressources...", + "PLEASE_WAIT": "Veuillez patienter...", + "FOUND_NEW_ASSETS": "Nouvelles ressources trouvées: %s", + "HELLO_MY_FRIEND": "Bonjour, mon ami !" + } } \ No newline at end of file diff --git a/main/assets/locales/hi-IN/language.json b/main/assets/locales/hi-IN/language.json index 732176e..d0df638 100644 --- a/main/assets/locales/hi-IN/language.json +++ b/main/assets/locales/hi-IN/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "hi-IN" - }, - "strings": { - "WARNING": "चेतावनी", - "INFO": "जानकारी", - "ERROR": "त्रुटि", - "VERSION": "संस्करण ", - "LOADING_PROTOCOL": "सर्वर से कनेक्ट हो रहे हैं...", - "INITIALIZING": "आरंभीकरण...", - "PIN_ERROR": "कृपया सिम कार्ड डालें", - "REG_ERROR": "नेटवर्क तक पहुंच नहीं हो सकती, कृपया डेटा कार्ड स्थिति जांचें", - "DETECTING_MODULE": "मॉड्यूल का पता लगाया जा रहा है...", - "REGISTERING_NETWORK": "नेटवर्क की प्रतीक्षा...", - "CHECKING_NEW_VERSION": "नया संस्करण जाँच रहे हैं...", - "CHECK_NEW_VERSION_FAILED": "नया संस्करण जाँचना असफल, %d सेकंड में पुनः प्रयास: %s", - "SWITCH_TO_WIFI_NETWORK": "Wi-Fi पर स्विच कर रहे हैं...", - "SWITCH_TO_4G_NETWORK": "4G पर स्विच कर रहे हैं...", - "STANDBY": "स्टैंडबाय", - "CONNECT_TO": "कनेक्ट करें ", - "CONNECTING": "कनेक्ट हो रहे हैं...", - "CONNECTED_TO": "कनेक्ट हो गए ", - "LISTENING": "सुन रहे हैं...", - "SPEAKING": "बोल रहे हैं...", - "SERVER_NOT_FOUND": "उपलब्ध सेवा खोज रहे हैं", - "SERVER_NOT_CONNECTED": "सेवा से कनेक्ट नहीं हो सकते, कृपया बाद में कोशिश करें", - "SERVER_TIMEOUT": "प्रतिक्रिया का समय समाप्त", - "SERVER_ERROR": "भेजना असफल, कृपया नेटवर्क जांचें", - "CONNECT_TO_HOTSPOT": "फोन को हॉटस्पॉट से कनेक्ट करें ", - "ACCESS_VIA_BROWSER": ",ब्राउज़र के माध्यम से पहुंचें ", - "WIFI_CONFIG_MODE": "नेटवर्क कॉन्फ़िगरेशन मोड", - "ENTERING_WIFI_CONFIG_MODE": "नेटवर्क कॉन्फ़िगरेशन मोड में प्रवेश...", - "SCANNING_WIFI": "Wi-Fi स्कैन कर रहे हैं...", - "NEW_VERSION": "नया संस्करण ", - "OTA_UPGRADE": "OTA अपग्रेड", - "UPGRADING": "सिस्टम अपग्रेड हो रहा है...", - "UPGRADE_FAILED": "अपग्रेड असफल", - "ACTIVATION": "डिवाइस सक्रियण", - "BATTERY_LOW": "बैटरी कम", - "BATTERY_CHARGING": "चार्ज हो रही है", - "BATTERY_FULL": "बैटरी फुल", - "BATTERY_NEED_CHARGE": "बैटरी कम है, कृपया चार्ज करें", - "VOLUME": "आवाज़ ", - "MUTED": "म्यूट", - "MAX_VOLUME": "अधिकतम आवाज़", - "RTC_MODE_OFF": "AEC बंद", - "RTC_MODE_ON": "AEC चालू", - "DOWNLOAD_ASSETS_FAILED": "संसाधन डाउनलोड करने में विफल", - "LOADING_ASSETS": "संसाधन लोड हो रहे हैं...", - "PLEASE_WAIT": "कृपया प्रतीक्षा करें...", - "FOUND_NEW_ASSETS": "नए संसाधन मिले: %s", - "HELLO_MY_FRIEND": "नमस्ते, मेरे दोस्त!" - } +{ + "language": { + "type": "hi-IN" + }, + "strings": { + "WARNING": "चेतावनी", + "INFO": "जानकारी", + "ERROR": "त्रुटि", + "VERSION": "संस्करण ", + "LOADING_PROTOCOL": "सर्वर से कनेक्ट हो रहे हैं...", + "INITIALIZING": "आरंभीकरण...", + "PIN_ERROR": "कृपया सिम कार्ड डालें", + "REG_ERROR": "नेटवर्क तक पहुंच नहीं हो सकती, कृपया डेटा कार्ड स्थिति जांचें", + "DETECTING_MODULE": "मॉड्यूल का पता लगाया जा रहा है...", + "REGISTERING_NETWORK": "नेटवर्क की प्रतीक्षा...", + "CHECKING_NEW_VERSION": "नया संस्करण जाँच रहे हैं...", + "CHECK_NEW_VERSION_FAILED": "नया संस्करण जाँचना असफल, %d सेकंड में पुनः प्रयास: %s", + "SWITCH_TO_WIFI_NETWORK": "Wi-Fi पर स्विच कर रहे हैं...", + "SWITCH_TO_4G_NETWORK": "4G पर स्विच कर रहे हैं...", + "STANDBY": "स्टैंडबाय", + "CONNECT_TO": "कनेक्ट करें ", + "CONNECTING": "कनेक्ट हो रहे हैं...", + "CONNECTED_TO": "कनेक्ट हो गए ", + "LISTENING": "सुन रहे हैं...", + "SPEAKING": "बोल रहे हैं...", + "SERVER_NOT_FOUND": "उपलब्ध सेवा खोज रहे हैं", + "SERVER_NOT_CONNECTED": "सेवा से कनेक्ट नहीं हो सकते, कृपया बाद में कोशिश करें", + "SERVER_TIMEOUT": "प्रतिक्रिया का समय समाप्त", + "SERVER_ERROR": "भेजना असफल, कृपया नेटवर्क जांचें", + "CONNECT_TO_HOTSPOT": "फोन को हॉटस्पॉट से कनेक्ट करें ", + "ACCESS_VIA_BROWSER": ",ब्राउज़र के माध्यम से पहुंचें ", + "WIFI_CONFIG_MODE": "नेटवर्क कॉन्फ़िगरेशन मोड", + "ENTERING_WIFI_CONFIG_MODE": "नेटवर्क कॉन्फ़िगरेशन मोड में प्रवेश...", + "SCANNING_WIFI": "Wi-Fi स्कैन कर रहे हैं...", + "NEW_VERSION": "नया संस्करण ", + "OTA_UPGRADE": "OTA अपग्रेड", + "UPGRADING": "सिस्टम अपग्रेड हो रहा है...", + "UPGRADE_FAILED": "अपग्रेड असफल", + "ACTIVATION": "डिवाइस सक्रियण", + "BATTERY_LOW": "बैटरी कम", + "BATTERY_CHARGING": "चार्ज हो रही है", + "BATTERY_FULL": "बैटरी फुल", + "BATTERY_NEED_CHARGE": "बैटरी कम है, कृपया चार्ज करें", + "VOLUME": "आवाज़ ", + "MUTED": "म्यूट", + "MAX_VOLUME": "अधिकतम आवाज़", + "RTC_MODE_OFF": "AEC बंद", + "RTC_MODE_ON": "AEC चालू", + "DOWNLOAD_ASSETS_FAILED": "संसाधन डाउनलोड करने में विफल", + "LOADING_ASSETS": "संसाधन लोड हो रहे हैं...", + "PLEASE_WAIT": "कृपया प्रतीक्षा करें...", + "FOUND_NEW_ASSETS": "नए संसाधन मिले: %s", + "HELLO_MY_FRIEND": "नमस्ते, मेरे दोस्त!" + } } \ No newline at end of file diff --git a/main/assets/locales/id-ID/language.json b/main/assets/locales/id-ID/language.json index 11224a6..b84c457 100644 --- a/main/assets/locales/id-ID/language.json +++ b/main/assets/locales/id-ID/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "id-ID" - }, - "strings": { - "WARNING": "Peringatan", - "INFO": "Informasi", - "ERROR": "Kesalahan", - "VERSION": "Versi ", - "LOADING_PROTOCOL": "Menghubungkan ke server...", - "INITIALIZING": "Menginisialisasi...", - "PIN_ERROR": "Silakan masukkan kartu SIM", - "REG_ERROR": "Tidak dapat mengakses jaringan, periksa status kartu data", - "DETECTING_MODULE": "Mendeteksi modul...", - "REGISTERING_NETWORK": "Menunggu jaringan...", - "CHECKING_NEW_VERSION": "Memeriksa versi baru...", - "CHECK_NEW_VERSION_FAILED": "Pemeriksaan versi baru gagal, mencoba lagi dalam %d detik: %s", - "SWITCH_TO_WIFI_NETWORK": "Beralih ke Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "Beralih ke 4G...", - "STANDBY": "Siaga", - "CONNECT_TO": "Hubungkan ke ", - "CONNECTING": "Menghubungkan...", - "CONNECTED_TO": "Terhubung ke ", - "LISTENING": "Mendengarkan...", - "SPEAKING": "Berbicara...", - "SERVER_NOT_FOUND": "Mencari layanan yang tersedia", - "SERVER_NOT_CONNECTED": "Tidak dapat terhubung ke layanan, coba lagi nanti", - "SERVER_TIMEOUT": "Waktu respons habis", - "SERVER_ERROR": "Pengiriman gagal, periksa jaringan", - "CONNECT_TO_HOTSPOT": "Hubungkan ponsel ke hotspot ", - "ACCESS_VIA_BROWSER": ",akses melalui browser ", - "WIFI_CONFIG_MODE": "Mode konfigurasi jaringan", - "ENTERING_WIFI_CONFIG_MODE": "Memasuki mode konfigurasi jaringan...", - "SCANNING_WIFI": "Memindai Wi-Fi...", - "NEW_VERSION": "Versi baru ", - "OTA_UPGRADE": "Pembaruan OTA", - "UPGRADING": "Memperbarui sistem...", - "UPGRADE_FAILED": "Pembaruan gagal", - "ACTIVATION": "Aktivasi perangkat", - "BATTERY_LOW": "Baterai lemah", - "BATTERY_CHARGING": "Mengisi", - "BATTERY_FULL": "Baterai penuh", - "BATTERY_NEED_CHARGE": "Baterai lemah, silakan isi", - "VOLUME": "Volume ", - "MUTED": "Bisu", - "MAX_VOLUME": "Volume maksimum", - "RTC_MODE_OFF": "AEC mati", - "RTC_MODE_ON": "AEC nyala", - "DOWNLOAD_ASSETS_FAILED": "Gagal mengunduh aset", - "LOADING_ASSETS": "Memuat aset...", - "PLEASE_WAIT": "Mohon tunggu...", - "FOUND_NEW_ASSETS": "Ditemukan aset baru: %s", - "HELLO_MY_FRIEND": "Halo, teman saya!" - } +{ + "language": { + "type": "id-ID" + }, + "strings": { + "WARNING": "Peringatan", + "INFO": "Informasi", + "ERROR": "Kesalahan", + "VERSION": "Versi ", + "LOADING_PROTOCOL": "Menghubungkan ke server...", + "INITIALIZING": "Menginisialisasi...", + "PIN_ERROR": "Silakan masukkan kartu SIM", + "REG_ERROR": "Tidak dapat mengakses jaringan, periksa status kartu data", + "DETECTING_MODULE": "Mendeteksi modul...", + "REGISTERING_NETWORK": "Menunggu jaringan...", + "CHECKING_NEW_VERSION": "Memeriksa versi baru...", + "CHECK_NEW_VERSION_FAILED": "Pemeriksaan versi baru gagal, mencoba lagi dalam %d detik: %s", + "SWITCH_TO_WIFI_NETWORK": "Beralih ke Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "Beralih ke 4G...", + "STANDBY": "Siaga", + "CONNECT_TO": "Hubungkan ke ", + "CONNECTING": "Menghubungkan...", + "CONNECTED_TO": "Terhubung ke ", + "LISTENING": "Mendengarkan...", + "SPEAKING": "Berbicara...", + "SERVER_NOT_FOUND": "Mencari layanan yang tersedia", + "SERVER_NOT_CONNECTED": "Tidak dapat terhubung ke layanan, coba lagi nanti", + "SERVER_TIMEOUT": "Waktu respons habis", + "SERVER_ERROR": "Pengiriman gagal, periksa jaringan", + "CONNECT_TO_HOTSPOT": "Hubungkan ponsel ke hotspot ", + "ACCESS_VIA_BROWSER": ",akses melalui browser ", + "WIFI_CONFIG_MODE": "Mode konfigurasi jaringan", + "ENTERING_WIFI_CONFIG_MODE": "Memasuki mode konfigurasi jaringan...", + "SCANNING_WIFI": "Memindai Wi-Fi...", + "NEW_VERSION": "Versi baru ", + "OTA_UPGRADE": "Pembaruan OTA", + "UPGRADING": "Memperbarui sistem...", + "UPGRADE_FAILED": "Pembaruan gagal", + "ACTIVATION": "Aktivasi perangkat", + "BATTERY_LOW": "Baterai lemah", + "BATTERY_CHARGING": "Mengisi", + "BATTERY_FULL": "Baterai penuh", + "BATTERY_NEED_CHARGE": "Baterai lemah, silakan isi", + "VOLUME": "Volume ", + "MUTED": "Bisu", + "MAX_VOLUME": "Volume maksimum", + "RTC_MODE_OFF": "AEC mati", + "RTC_MODE_ON": "AEC nyala", + "DOWNLOAD_ASSETS_FAILED": "Gagal mengunduh aset", + "LOADING_ASSETS": "Memuat aset...", + "PLEASE_WAIT": "Mohon tunggu...", + "FOUND_NEW_ASSETS": "Ditemukan aset baru: %s", + "HELLO_MY_FRIEND": "Halo, teman saya!" + } } \ No newline at end of file diff --git a/main/assets/locales/it-IT/language.json b/main/assets/locales/it-IT/language.json index 54cc9bc..778f008 100644 --- a/main/assets/locales/it-IT/language.json +++ b/main/assets/locales/it-IT/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "it-IT" - }, - "strings": { - "WARNING": "Avviso", - "INFO": "Informazione", - "ERROR": "Errore", - "VERSION": "Versione ", - "LOADING_PROTOCOL": "Connessione al server...", - "INITIALIZING": "Inizializzazione...", - "PIN_ERROR": "Inserire la scheda SIM", - "REG_ERROR": "Impossibile accedere alla rete, controllare lo stato della scheda dati", - "DETECTING_MODULE": "Rilevamento modulo...", - "REGISTERING_NETWORK": "In attesa della rete...", - "CHECKING_NEW_VERSION": "Controllo nuova versione...", - "CHECK_NEW_VERSION_FAILED": "Controllo nuova versione fallito, riprovo tra %d secondi: %s", - "SWITCH_TO_WIFI_NETWORK": "Passaggio a Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "Passaggio a 4G...", - "STANDBY": "In attesa", - "CONNECT_TO": "Connetti a ", - "CONNECTING": "Connessione...", - "CONNECTED_TO": "Connesso a ", - "LISTENING": "In ascolto...", - "SPEAKING": "Parlando...", - "SERVER_NOT_FOUND": "Ricerca servizio disponibile", - "SERVER_NOT_CONNECTED": "Impossibile connettersi al servizio, riprovare più tardi", - "SERVER_TIMEOUT": "Timeout risposta", - "SERVER_ERROR": "Invio fallito, controllare la rete", - "CONNECT_TO_HOTSPOT": "Connetti telefono al hotspot ", - "ACCESS_VIA_BROWSER": ",accedi tramite browser ", - "WIFI_CONFIG_MODE": "Modalità configurazione rete", - "ENTERING_WIFI_CONFIG_MODE": "Entrata in modalità configurazione rete...", - "SCANNING_WIFI": "Scansione Wi-Fi...", - "NEW_VERSION": "Nuova versione ", - "OTA_UPGRADE": "Aggiornamento OTA", - "UPGRADING": "Aggiornamento sistema...", - "UPGRADE_FAILED": "Aggiornamento fallito", - "ACTIVATION": "Attivazione dispositivo", - "BATTERY_LOW": "Batteria scarica", - "BATTERY_CHARGING": "In carica", - "BATTERY_FULL": "Batteria piena", - "BATTERY_NEED_CHARGE": "Batteria scarica, ricaricare", - "VOLUME": "Volume ", - "MUTED": "Silenziato", - "MAX_VOLUME": "Volume massimo", - "RTC_MODE_OFF": "AEC disattivato", - "RTC_MODE_ON": "AEC attivato", - "DOWNLOAD_ASSETS_FAILED": "Impossibile scaricare le risorse", - "LOADING_ASSETS": "Caricamento risorse...", - "PLEASE_WAIT": "Attendere prego...", - "FOUND_NEW_ASSETS": "Trovate nuove risorse: %s", - "HELLO_MY_FRIEND": "Ciao, amico mio!" - } +{ + "language": { + "type": "it-IT" + }, + "strings": { + "WARNING": "Avviso", + "INFO": "Informazione", + "ERROR": "Errore", + "VERSION": "Versione ", + "LOADING_PROTOCOL": "Connessione al server...", + "INITIALIZING": "Inizializzazione...", + "PIN_ERROR": "Inserire la scheda SIM", + "REG_ERROR": "Impossibile accedere alla rete, controllare lo stato della scheda dati", + "DETECTING_MODULE": "Rilevamento modulo...", + "REGISTERING_NETWORK": "In attesa della rete...", + "CHECKING_NEW_VERSION": "Controllo nuova versione...", + "CHECK_NEW_VERSION_FAILED": "Controllo nuova versione fallito, riprovo tra %d secondi: %s", + "SWITCH_TO_WIFI_NETWORK": "Passaggio a Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "Passaggio a 4G...", + "STANDBY": "In attesa", + "CONNECT_TO": "Connetti a ", + "CONNECTING": "Connessione...", + "CONNECTED_TO": "Connesso a ", + "LISTENING": "In ascolto...", + "SPEAKING": "Parlando...", + "SERVER_NOT_FOUND": "Ricerca servizio disponibile", + "SERVER_NOT_CONNECTED": "Impossibile connettersi al servizio, riprovare più tardi", + "SERVER_TIMEOUT": "Timeout risposta", + "SERVER_ERROR": "Invio fallito, controllare la rete", + "CONNECT_TO_HOTSPOT": "Connetti telefono al hotspot ", + "ACCESS_VIA_BROWSER": ",accedi tramite browser ", + "WIFI_CONFIG_MODE": "Modalità configurazione rete", + "ENTERING_WIFI_CONFIG_MODE": "Entrata in modalità configurazione rete...", + "SCANNING_WIFI": "Scansione Wi-Fi...", + "NEW_VERSION": "Nuova versione ", + "OTA_UPGRADE": "Aggiornamento OTA", + "UPGRADING": "Aggiornamento sistema...", + "UPGRADE_FAILED": "Aggiornamento fallito", + "ACTIVATION": "Attivazione dispositivo", + "BATTERY_LOW": "Batteria scarica", + "BATTERY_CHARGING": "In carica", + "BATTERY_FULL": "Batteria piena", + "BATTERY_NEED_CHARGE": "Batteria scarica, ricaricare", + "VOLUME": "Volume ", + "MUTED": "Silenziato", + "MAX_VOLUME": "Volume massimo", + "RTC_MODE_OFF": "AEC disattivato", + "RTC_MODE_ON": "AEC attivato", + "DOWNLOAD_ASSETS_FAILED": "Impossibile scaricare le risorse", + "LOADING_ASSETS": "Caricamento risorse...", + "PLEASE_WAIT": "Attendere prego...", + "FOUND_NEW_ASSETS": "Trovate nuove risorse: %s", + "HELLO_MY_FRIEND": "Ciao, amico mio!" + } } \ No newline at end of file diff --git a/main/assets/locales/ja-JP/language.json b/main/assets/locales/ja-JP/language.json index 1c2bd82..787133b 100644 --- a/main/assets/locales/ja-JP/language.json +++ b/main/assets/locales/ja-JP/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "ja-JP" - }, - "strings": { - "WARNING": "警告", - "INFO": "情報", - "ERROR": "エラー", - "VERSION": "バージョン ", - "LOADING_PROTOCOL": "サーバーにログイン中...", - "INITIALIZING": "初期化中...", - "PIN_ERROR": "SIMカードを挿入してください", - "REG_ERROR": "ネットワークに接続できません。ネットワーク状態を確認してください", - "DETECTING_MODULE": "モジュールを検出中...", - "REGISTERING_NETWORK": "ネットワーク接続待機中...", - "CHECKING_NEW_VERSION": "新しいバージョンを確認中...", - "CHECK_NEW_VERSION_FAILED": "更新確認に失敗しました。%d 秒後に再試行します: %s", - "SWITCH_TO_WIFI_NETWORK": "Wi-Fiに切り替え中...", - "SWITCH_TO_4G_NETWORK": "4Gに切り替え中...", - "STANDBY": "待機中", - "CONNECT_TO": "接続先 ", - "CONNECTING": "接続中...", - "CONNECTED_TO": "接続完了 ", - "LISTENING": "リスニング中...", - "SPEAKING": "話しています...", - "SERVER_NOT_FOUND": "利用可能なサーバーを探しています", - "SERVER_NOT_CONNECTED": "サーバーに接続できません。後でもう一度お試しください", - "SERVER_TIMEOUT": "応答待機時間が終了しました", - "SERVER_ERROR": "送信に失敗しました。ネットワークを確認してください", - "CONNECT_TO_HOTSPOT": "スマートフォンをWi-Fi ", - "ACCESS_VIA_BROWSER": " に接続し、ブラウザでアクセスしてください ", - "WIFI_CONFIG_MODE": "ネットワーク設定モード", - "ENTERING_WIFI_CONFIG_MODE": "ネットワーク設定中...", - "SCANNING_WIFI": "Wi-Fiをスキャン中...", - "NEW_VERSION": "新しいバージョン ", - "OTA_UPGRADE": "OTAアップグレード", - "UPGRADING": "システムをアップグレード中...", - "UPGRADE_FAILED": "アップグレード失敗", - "ACTIVATION": "デバイスをアクティベート", - "BATTERY_LOW": "バッテリーが少なくなっています", - "BATTERY_CHARGING": "充電中", - "BATTERY_FULL": "バッテリー満タン", - "BATTERY_NEED_CHARGE": "バッテリーが低下しています。充電してください", - "VOLUME": "音量 ", - "MUTED": "ミュートされています", - "MAX_VOLUME": "最大音量", - "RTC_MODE_OFF": "AEC 無効", - "RTC_MODE_ON": "AEC 有効", - "DOWNLOAD_ASSETS_FAILED": "アセットのダウンロードに失敗しました", - "LOADING_ASSETS": "アセットを読み込み中...", - "PLEASE_WAIT": "お待ちください...", - "FOUND_NEW_ASSETS": "新しいアセットが見つかりました: %s", - "HELLO_MY_FRIEND": "こんにちは、友達!" - } +{ + "language": { + "type": "ja-JP" + }, + "strings": { + "WARNING": "警告", + "INFO": "情報", + "ERROR": "エラー", + "VERSION": "バージョン ", + "LOADING_PROTOCOL": "サーバーにログイン中...", + "INITIALIZING": "初期化中...", + "PIN_ERROR": "SIMカードを挿入してください", + "REG_ERROR": "ネットワークに接続できません。ネットワーク状態を確認してください", + "DETECTING_MODULE": "モジュールを検出中...", + "REGISTERING_NETWORK": "ネットワーク接続待機中...", + "CHECKING_NEW_VERSION": "新しいバージョンを確認中...", + "CHECK_NEW_VERSION_FAILED": "更新確認に失敗しました。%d 秒後に再試行します: %s", + "SWITCH_TO_WIFI_NETWORK": "Wi-Fiに切り替え中...", + "SWITCH_TO_4G_NETWORK": "4Gに切り替え中...", + "STANDBY": "待機中", + "CONNECT_TO": "接続先 ", + "CONNECTING": "接続中...", + "CONNECTED_TO": "接続完了 ", + "LISTENING": "リスニング中...", + "SPEAKING": "話しています...", + "SERVER_NOT_FOUND": "利用可能なサーバーを探しています", + "SERVER_NOT_CONNECTED": "サーバーに接続できません。後でもう一度お試しください", + "SERVER_TIMEOUT": "応答待機時間が終了しました", + "SERVER_ERROR": "送信に失敗しました。ネットワークを確認してください", + "CONNECT_TO_HOTSPOT": "スマートフォンをWi-Fi ", + "ACCESS_VIA_BROWSER": " に接続し、ブラウザでアクセスしてください ", + "WIFI_CONFIG_MODE": "ネットワーク設定モード", + "ENTERING_WIFI_CONFIG_MODE": "ネットワーク設定中...", + "SCANNING_WIFI": "Wi-Fiをスキャン中...", + "NEW_VERSION": "新しいバージョン ", + "OTA_UPGRADE": "OTAアップグレード", + "UPGRADING": "システムをアップグレード中...", + "UPGRADE_FAILED": "アップグレード失敗", + "ACTIVATION": "デバイスをアクティベート", + "BATTERY_LOW": "バッテリーが少なくなっています", + "BATTERY_CHARGING": "充電中", + "BATTERY_FULL": "バッテリー満タン", + "BATTERY_NEED_CHARGE": "バッテリーが低下しています。充電してください", + "VOLUME": "音量 ", + "MUTED": "ミュートされています", + "MAX_VOLUME": "最大音量", + "RTC_MODE_OFF": "AEC 無効", + "RTC_MODE_ON": "AEC 有効", + "DOWNLOAD_ASSETS_FAILED": "アセットのダウンロードに失敗しました", + "LOADING_ASSETS": "アセットを読み込み中...", + "PLEASE_WAIT": "お待ちください...", + "FOUND_NEW_ASSETS": "新しいアセットが見つかりました: %s", + "HELLO_MY_FRIEND": "こんにちは、友達!" + } } \ No newline at end of file diff --git a/main/assets/locales/ko-KR/language.json b/main/assets/locales/ko-KR/language.json index 1d65bfb..31baabd 100644 --- a/main/assets/locales/ko-KR/language.json +++ b/main/assets/locales/ko-KR/language.json @@ -1,56 +1,56 @@ -{ - "language": { - "type": "ko-KR" - }, - "strings": { - "WARNING": "경고", - "INFO": "정보", - "ERROR": "오류", - "VERSION": "버전 ", - "LOADING_PROTOCOL": "로그인 중...", - "INITIALIZING": "초기화 중...", - "PIN_ERROR": "SIM 카드를 삽입하세요", - "REG_ERROR": "네트워크에 접속할 수 없습니다. SIM 카드 상태를 확인하세요", - "DETECTING_MODULE": "모듈 감지 중...", - "REGISTERING_NETWORK": "네트워크 대기 중...", - "CHECKING_NEW_VERSION": "새 버전 확인 중...", - "CHECK_NEW_VERSION_FAILED": "새 버전 확인에 실패했습니다. %d초 후에 다시 시도합니다: %s", - "SWITCH_TO_WIFI_NETWORK": "Wi-Fi로 전환 중...", - "SWITCH_TO_4G_NETWORK": "4G로 전환 중...", - "STANDBY": "대기", - "CONNECT_TO": "연결 대상: ", - "CONNECTING": "연결 중...", - "CONNECTION_SUCCESSFUL": "연결 성공", - "CONNECTED_TO": "연결됨: ", - "LISTENING": "듣는 중...", - "SPEAKING": "말하는 중...", - "SERVER_NOT_FOUND": "사용 가능한 서비스를 찾는 중", - "SERVER_NOT_CONNECTED": "서비스에 연결할 수 없습니다. 나중에 다시 시도하세요", - "SERVER_TIMEOUT": "응답 대기 시간 초과", - "SERVER_ERROR": "전송 실패, 네트워크를 확인하세요", - "CONNECT_TO_HOTSPOT": "핫스팟: ", - "ACCESS_VIA_BROWSER": " 설정 URL: ", - "WIFI_CONFIG_MODE": "Wi-Fi 설정 모드", - "ENTERING_WIFI_CONFIG_MODE": "Wi-Fi 설정 모드 진입 중...", - "SCANNING_WIFI": "Wi-Fi 스캔 중...", - "NEW_VERSION": "새 버전 ", - "OTA_UPGRADE": "OTA 업그레이드", - "UPGRADING": "시스템 업그레이드 중...", - "UPGRADE_FAILED": "업그레이드 실패", - "ACTIVATION": "활성화", - "BATTERY_LOW": "배터리 부족", - "BATTERY_CHARGING": "충전 중", - "BATTERY_FULL": "배터리 완충", - "BATTERY_NEED_CHARGE": "배터리 부족, 충전하세요", - "VOLUME": "볼륨 ", - "MUTED": "음소거", - "MAX_VOLUME": "최대 볼륨", - "RTC_MODE_OFF": "AEC 끄기", - "RTC_MODE_ON": "AEC 켜기", - "DOWNLOAD_ASSETS_FAILED": "에셋 다운로드 실패", - "LOADING_ASSETS": "에셋 로딩 중...", - "PLEASE_WAIT": "잠시 기다려 주세요...", - "FOUND_NEW_ASSETS": "새로운 에셋을 발견했습니다: %s", - "HELLO_MY_FRIEND": "안녕하세요, 친구!" - } +{ + "language": { + "type": "ko-KR" + }, + "strings": { + "WARNING": "경고", + "INFO": "정보", + "ERROR": "오류", + "VERSION": "버전 ", + "LOADING_PROTOCOL": "로그인 중...", + "INITIALIZING": "초기화 중...", + "PIN_ERROR": "SIM 카드를 삽입하세요", + "REG_ERROR": "네트워크에 접속할 수 없습니다. SIM 카드 상태를 확인하세요", + "DETECTING_MODULE": "모듈 감지 중...", + "REGISTERING_NETWORK": "네트워크 대기 중...", + "CHECKING_NEW_VERSION": "새 버전 확인 중...", + "CHECK_NEW_VERSION_FAILED": "새 버전 확인에 실패했습니다. %d초 후에 다시 시도합니다: %s", + "SWITCH_TO_WIFI_NETWORK": "Wi-Fi로 전환 중...", + "SWITCH_TO_4G_NETWORK": "4G로 전환 중...", + "STANDBY": "대기", + "CONNECT_TO": "연결 대상: ", + "CONNECTING": "연결 중...", + "CONNECTION_SUCCESSFUL": "연결 성공", + "CONNECTED_TO": "연결됨: ", + "LISTENING": "듣는 중...", + "SPEAKING": "말하는 중...", + "SERVER_NOT_FOUND": "사용 가능한 서비스를 찾는 중", + "SERVER_NOT_CONNECTED": "서비스에 연결할 수 없습니다. 나중에 다시 시도하세요", + "SERVER_TIMEOUT": "응답 대기 시간 초과", + "SERVER_ERROR": "전송 실패, 네트워크를 확인하세요", + "CONNECT_TO_HOTSPOT": "핫스팟: ", + "ACCESS_VIA_BROWSER": " 설정 URL: ", + "WIFI_CONFIG_MODE": "Wi-Fi 설정 모드", + "ENTERING_WIFI_CONFIG_MODE": "Wi-Fi 설정 모드 진입 중...", + "SCANNING_WIFI": "Wi-Fi 스캔 중...", + "NEW_VERSION": "새 버전 ", + "OTA_UPGRADE": "OTA 업그레이드", + "UPGRADING": "시스템 업그레이드 중...", + "UPGRADE_FAILED": "업그레이드 실패", + "ACTIVATION": "활성화", + "BATTERY_LOW": "배터리 부족", + "BATTERY_CHARGING": "충전 중", + "BATTERY_FULL": "배터리 완충", + "BATTERY_NEED_CHARGE": "배터리 부족, 충전하세요", + "VOLUME": "볼륨 ", + "MUTED": "음소거", + "MAX_VOLUME": "최대 볼륨", + "RTC_MODE_OFF": "AEC 끄기", + "RTC_MODE_ON": "AEC 켜기", + "DOWNLOAD_ASSETS_FAILED": "에셋 다운로드 실패", + "LOADING_ASSETS": "에셋 로딩 중...", + "PLEASE_WAIT": "잠시 기다려 주세요...", + "FOUND_NEW_ASSETS": "새로운 에셋을 발견했습니다: %s", + "HELLO_MY_FRIEND": "안녕하세요, 친구!" + } } \ No newline at end of file diff --git a/main/assets/locales/pl-PL/language.json b/main/assets/locales/pl-PL/language.json index b54c292..aa07919 100644 --- a/main/assets/locales/pl-PL/language.json +++ b/main/assets/locales/pl-PL/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "pl-PL" - }, - "strings": { - "WARNING": "Ostrzeżenie", - "INFO": "Informacja", - "ERROR": "Błąd", - "VERSION": "Wersja ", - "LOADING_PROTOCOL": "Łączenie z serwerem...", - "INITIALIZING": "Inicjalizacja...", - "PIN_ERROR": "Proszę włożyć kartę SIM", - "REG_ERROR": "Nie można uzyskać dostępu do sieci, sprawdź stan karty danych", - "DETECTING_MODULE": "Wykrywanie modułu...", - "REGISTERING_NETWORK": "Oczekiwanie na sieć...", - "CHECKING_NEW_VERSION": "Sprawdzanie nowej wersji...", - "CHECK_NEW_VERSION_FAILED": "Sprawdzanie nowej wersji nie powiodło się, ponowna próba za %d sekund: %s", - "SWITCH_TO_WIFI_NETWORK": "Przełączanie na Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "Przełączanie na 4G...", - "STANDBY": "Gotowość", - "CONNECT_TO": "Połącz z ", - "CONNECTING": "Łączenie...", - "CONNECTED_TO": "Połączono z ", - "LISTENING": "Słuchanie...", - "SPEAKING": "Mówienie...", - "SERVER_NOT_FOUND": "Szukanie dostępnej usługi", - "SERVER_NOT_CONNECTED": "Nie można połączyć się z usługą, spróbuj ponownie później", - "SERVER_TIMEOUT": "Przekroczono czas oczekiwania na odpowiedź", - "SERVER_ERROR": "Wysyłanie nie powiodło się, sprawdź sieć", - "CONNECT_TO_HOTSPOT": "Podłącz telefon do hotspotu ", - "ACCESS_VIA_BROWSER": ",dostęp przez przeglądarkę ", - "WIFI_CONFIG_MODE": "Tryb konfiguracji sieci", - "ENTERING_WIFI_CONFIG_MODE": "Wchodzenie w tryb konfiguracji sieci...", - "SCANNING_WIFI": "Skanowanie Wi-Fi...", - "NEW_VERSION": "Nowa wersja ", - "OTA_UPGRADE": "Aktualizacja OTA", - "UPGRADING": "Aktualizacja systemu...", - "UPGRADE_FAILED": "Aktualizacja nie powiodła się", - "ACTIVATION": "Aktywacja urządzenia", - "BATTERY_LOW": "Niski poziom baterii", - "BATTERY_CHARGING": "Ładowanie", - "BATTERY_FULL": "Bateria pełna", - "BATTERY_NEED_CHARGE": "Niski poziom baterii, proszę naładować", - "VOLUME": "Głośność ", - "MUTED": "Wyciszony", - "MAX_VOLUME": "Maksymalna głośność", - "RTC_MODE_OFF": "AEC wyłączony", - "RTC_MODE_ON": "AEC włączony", - "DOWNLOAD_ASSETS_FAILED": "Nie udało się pobrać zasobów", - "LOADING_ASSETS": "Ładowanie zasobów...", - "PLEASE_WAIT": "Proszę czekać...", - "FOUND_NEW_ASSETS": "Znaleziono nowe zasoby: %s", - "HELLO_MY_FRIEND": "Cześć, mój przyjacielu!" - } +{ + "language": { + "type": "pl-PL" + }, + "strings": { + "WARNING": "Ostrzeżenie", + "INFO": "Informacja", + "ERROR": "Błąd", + "VERSION": "Wersja ", + "LOADING_PROTOCOL": "Łączenie z serwerem...", + "INITIALIZING": "Inicjalizacja...", + "PIN_ERROR": "Proszę włożyć kartę SIM", + "REG_ERROR": "Nie można uzyskać dostępu do sieci, sprawdź stan karty danych", + "DETECTING_MODULE": "Wykrywanie modułu...", + "REGISTERING_NETWORK": "Oczekiwanie na sieć...", + "CHECKING_NEW_VERSION": "Sprawdzanie nowej wersji...", + "CHECK_NEW_VERSION_FAILED": "Sprawdzanie nowej wersji nie powiodło się, ponowna próba za %d sekund: %s", + "SWITCH_TO_WIFI_NETWORK": "Przełączanie na Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "Przełączanie na 4G...", + "STANDBY": "Gotowość", + "CONNECT_TO": "Połącz z ", + "CONNECTING": "Łączenie...", + "CONNECTED_TO": "Połączono z ", + "LISTENING": "Słuchanie...", + "SPEAKING": "Mówienie...", + "SERVER_NOT_FOUND": "Szukanie dostępnej usługi", + "SERVER_NOT_CONNECTED": "Nie można połączyć się z usługą, spróbuj ponownie później", + "SERVER_TIMEOUT": "Przekroczono czas oczekiwania na odpowiedź", + "SERVER_ERROR": "Wysyłanie nie powiodło się, sprawdź sieć", + "CONNECT_TO_HOTSPOT": "Podłącz telefon do hotspotu ", + "ACCESS_VIA_BROWSER": ",dostęp przez przeglądarkę ", + "WIFI_CONFIG_MODE": "Tryb konfiguracji sieci", + "ENTERING_WIFI_CONFIG_MODE": "Wchodzenie w tryb konfiguracji sieci...", + "SCANNING_WIFI": "Skanowanie Wi-Fi...", + "NEW_VERSION": "Nowa wersja ", + "OTA_UPGRADE": "Aktualizacja OTA", + "UPGRADING": "Aktualizacja systemu...", + "UPGRADE_FAILED": "Aktualizacja nie powiodła się", + "ACTIVATION": "Aktywacja urządzenia", + "BATTERY_LOW": "Niski poziom baterii", + "BATTERY_CHARGING": "Ładowanie", + "BATTERY_FULL": "Bateria pełna", + "BATTERY_NEED_CHARGE": "Niski poziom baterii, proszę naładować", + "VOLUME": "Głośność ", + "MUTED": "Wyciszony", + "MAX_VOLUME": "Maksymalna głośność", + "RTC_MODE_OFF": "AEC wyłączony", + "RTC_MODE_ON": "AEC włączony", + "DOWNLOAD_ASSETS_FAILED": "Nie udało się pobrać zasobów", + "LOADING_ASSETS": "Ładowanie zasobów...", + "PLEASE_WAIT": "Proszę czekać...", + "FOUND_NEW_ASSETS": "Znaleziono nowe zasoby: %s", + "HELLO_MY_FRIEND": "Cześć, mój przyjacielu!" + } } \ No newline at end of file diff --git a/main/assets/locales/pt-PT/language.json b/main/assets/locales/pt-PT/language.json index da0e0e2..1dcac4b 100644 --- a/main/assets/locales/pt-PT/language.json +++ b/main/assets/locales/pt-PT/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "pt-PT" - }, - "strings": { - "WARNING": "Aviso", - "INFO": "Informação", - "ERROR": "Erro", - "VERSION": "Versão ", - "LOADING_PROTOCOL": "Ligando ao servidor...", - "INITIALIZING": "A inicializar...", - "PIN_ERROR": "Por favor insira o cartão SIM", - "REG_ERROR": "Não é possível aceder à rede, verifique o estado do cartão de dados", - "DETECTING_MODULE": "A detectar módulo...", - "REGISTERING_NETWORK": "À espera da rede...", - "CHECKING_NEW_VERSION": "A verificar nova versão...", - "CHECK_NEW_VERSION_FAILED": "Falha na verificação de nova versão, nova tentativa em %d segundos: %s", - "SWITCH_TO_WIFI_NETWORK": "A mudar para Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "A mudar para 4G...", - "STANDBY": "Em espera", - "CONNECT_TO": "Ligar a ", - "CONNECTING": "A ligar...", - "CONNECTED_TO": "Ligado a ", - "LISTENING": "A escutar...", - "SPEAKING": "A falar...", - "SERVER_NOT_FOUND": "A procurar serviço disponível", - "SERVER_NOT_CONNECTED": "Não é possível ligar ao serviço, tente mais tarde", - "SERVER_TIMEOUT": "Tempo limite de resposta", - "SERVER_ERROR": "Falha no envio, verifique a rede", - "CONNECT_TO_HOTSPOT": "Ligue o telefone ao hotspot ", - "ACCESS_VIA_BROWSER": ",aceder através do navegador ", - "WIFI_CONFIG_MODE": "Modo de configuração de rede", - "ENTERING_WIFI_CONFIG_MODE": "A entrar no modo de configuração de rede...", - "SCANNING_WIFI": "A procurar Wi-Fi...", - "NEW_VERSION": "Nova versão ", - "OTA_UPGRADE": "Atualização OTA", - "UPGRADING": "A atualizar sistema...", - "UPGRADE_FAILED": "Atualização falhada", - "ACTIVATION": "Ativação do dispositivo", - "BATTERY_LOW": "Bateria fraca", - "BATTERY_CHARGING": "A carregar", - "BATTERY_FULL": "Bateria cheia", - "BATTERY_NEED_CHARGE": "Bateria fraca, por favor carregue", - "VOLUME": "Volume ", - "MUTED": "Silenciado", - "MAX_VOLUME": "Volume máximo", - "RTC_MODE_OFF": "AEC desligado", - "RTC_MODE_ON": "AEC ligado", - "DOWNLOAD_ASSETS_FAILED": "Falha ao descarregar recursos", - "LOADING_ASSETS": "A carregar recursos...", - "PLEASE_WAIT": "Por favor aguarde...", - "FOUND_NEW_ASSETS": "Encontrados novos recursos: %s", - "HELLO_MY_FRIEND": "Olá, meu amigo!" - } +{ + "language": { + "type": "pt-PT" + }, + "strings": { + "WARNING": "Aviso", + "INFO": "Informação", + "ERROR": "Erro", + "VERSION": "Versão ", + "LOADING_PROTOCOL": "Ligando ao servidor...", + "INITIALIZING": "A inicializar...", + "PIN_ERROR": "Por favor insira o cartão SIM", + "REG_ERROR": "Não é possível aceder à rede, verifique o estado do cartão de dados", + "DETECTING_MODULE": "A detectar módulo...", + "REGISTERING_NETWORK": "À espera da rede...", + "CHECKING_NEW_VERSION": "A verificar nova versão...", + "CHECK_NEW_VERSION_FAILED": "Falha na verificação de nova versão, nova tentativa em %d segundos: %s", + "SWITCH_TO_WIFI_NETWORK": "A mudar para Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "A mudar para 4G...", + "STANDBY": "Em espera", + "CONNECT_TO": "Ligar a ", + "CONNECTING": "A ligar...", + "CONNECTED_TO": "Ligado a ", + "LISTENING": "A escutar...", + "SPEAKING": "A falar...", + "SERVER_NOT_FOUND": "A procurar serviço disponível", + "SERVER_NOT_CONNECTED": "Não é possível ligar ao serviço, tente mais tarde", + "SERVER_TIMEOUT": "Tempo limite de resposta", + "SERVER_ERROR": "Falha no envio, verifique a rede", + "CONNECT_TO_HOTSPOT": "Ligue o telefone ao hotspot ", + "ACCESS_VIA_BROWSER": ",aceder através do navegador ", + "WIFI_CONFIG_MODE": "Modo de configuração de rede", + "ENTERING_WIFI_CONFIG_MODE": "A entrar no modo de configuração de rede...", + "SCANNING_WIFI": "A procurar Wi-Fi...", + "NEW_VERSION": "Nova versão ", + "OTA_UPGRADE": "Atualização OTA", + "UPGRADING": "A atualizar sistema...", + "UPGRADE_FAILED": "Atualização falhada", + "ACTIVATION": "Ativação do dispositivo", + "BATTERY_LOW": "Bateria fraca", + "BATTERY_CHARGING": "A carregar", + "BATTERY_FULL": "Bateria cheia", + "BATTERY_NEED_CHARGE": "Bateria fraca, por favor carregue", + "VOLUME": "Volume ", + "MUTED": "Silenciado", + "MAX_VOLUME": "Volume máximo", + "RTC_MODE_OFF": "AEC desligado", + "RTC_MODE_ON": "AEC ligado", + "DOWNLOAD_ASSETS_FAILED": "Falha ao descarregar recursos", + "LOADING_ASSETS": "A carregar recursos...", + "PLEASE_WAIT": "Por favor aguarde...", + "FOUND_NEW_ASSETS": "Encontrados novos recursos: %s", + "HELLO_MY_FRIEND": "Olá, meu amigo!" + } } \ No newline at end of file diff --git a/main/assets/locales/ro-RO/language.json b/main/assets/locales/ro-RO/language.json index d8ec774..8178717 100644 --- a/main/assets/locales/ro-RO/language.json +++ b/main/assets/locales/ro-RO/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "ro-RO" - }, - "strings": { - "WARNING": "Avertisment", - "INFO": "Informație", - "ERROR": "Eroare", - "VERSION": "Versiune ", - "LOADING_PROTOCOL": "Se conectează la server...", - "INITIALIZING": "Se inițializează...", - "PIN_ERROR": "Vă rugăm să introduceți cardul SIM", - "REG_ERROR": "Nu se poate accesa rețeaua, verificați starea cardului de date", - "DETECTING_MODULE": "Se detectează modulul...", - "REGISTERING_NETWORK": "Se așteaptă rețeaua...", - "CHECKING_NEW_VERSION": "Se verifică versiunea nouă...", - "CHECK_NEW_VERSION_FAILED": "Verificarea versiunii noi a eșuat, se reîncearcă în %d secunde: %s", - "SWITCH_TO_WIFI_NETWORK": "Se comută la Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "Se comută la 4G...", - "STANDBY": "În așteptare", - "CONNECT_TO": "Conectare la ", - "CONNECTING": "Se conectează...", - "CONNECTED_TO": "Conectat la ", - "LISTENING": "Se ascultă...", - "SPEAKING": "Se vorbește...", - "SERVER_NOT_FOUND": "Se caută serviciul disponibil", - "SERVER_NOT_CONNECTED": "Nu se poate conecta la serviciu, încercați mai târziu", - "SERVER_TIMEOUT": "Timpul de răspuns a expirat", - "SERVER_ERROR": "Trimiterea a eșuat, verificați rețeaua", - "CONNECT_TO_HOTSPOT": "Conectați telefonul la hotspot ", - "ACCESS_VIA_BROWSER": ",accesați prin browser ", - "WIFI_CONFIG_MODE": "Modul de configurare rețea", - "ENTERING_WIFI_CONFIG_MODE": "Se intră în modul de configurare rețea...", - "SCANNING_WIFI": "Se scanează Wi-Fi...", - "NEW_VERSION": "Versiune nouă ", - "OTA_UPGRADE": "Actualizare OTA", - "UPGRADING": "Se actualizează sistemul...", - "UPGRADE_FAILED": "Actualizarea a eșuat", - "ACTIVATION": "Activarea dispozitivului", - "BATTERY_LOW": "Baterie scăzută", - "BATTERY_CHARGING": "Se încarcă", - "BATTERY_FULL": "Baterie plină", - "BATTERY_NEED_CHARGE": "Baterie scăzută, vă rugăm să încărcați", - "VOLUME": "Volum ", - "MUTED": "Silențios", - "MAX_VOLUME": "Volum maxim", - "RTC_MODE_OFF": "AEC oprit", - "RTC_MODE_ON": "AEC pornit", - "DOWNLOAD_ASSETS_FAILED": "Eșec la descărcarea resurselor", - "LOADING_ASSETS": "Se încarcă resursele...", - "PLEASE_WAIT": "Vă rugăm să așteptați...", - "FOUND_NEW_ASSETS": "S-au găsit resurse noi: %s", - "HELLO_MY_FRIEND": "Salut, prietenul meu!" - } +{ + "language": { + "type": "ro-RO" + }, + "strings": { + "WARNING": "Avertisment", + "INFO": "Informație", + "ERROR": "Eroare", + "VERSION": "Versiune ", + "LOADING_PROTOCOL": "Se conectează la server...", + "INITIALIZING": "Se inițializează...", + "PIN_ERROR": "Vă rugăm să introduceți cardul SIM", + "REG_ERROR": "Nu se poate accesa rețeaua, verificați starea cardului de date", + "DETECTING_MODULE": "Se detectează modulul...", + "REGISTERING_NETWORK": "Se așteaptă rețeaua...", + "CHECKING_NEW_VERSION": "Se verifică versiunea nouă...", + "CHECK_NEW_VERSION_FAILED": "Verificarea versiunii noi a eșuat, se reîncearcă în %d secunde: %s", + "SWITCH_TO_WIFI_NETWORK": "Se comută la Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "Se comută la 4G...", + "STANDBY": "În așteptare", + "CONNECT_TO": "Conectare la ", + "CONNECTING": "Se conectează...", + "CONNECTED_TO": "Conectat la ", + "LISTENING": "Se ascultă...", + "SPEAKING": "Se vorbește...", + "SERVER_NOT_FOUND": "Se caută serviciul disponibil", + "SERVER_NOT_CONNECTED": "Nu se poate conecta la serviciu, încercați mai târziu", + "SERVER_TIMEOUT": "Timpul de răspuns a expirat", + "SERVER_ERROR": "Trimiterea a eșuat, verificați rețeaua", + "CONNECT_TO_HOTSPOT": "Conectați telefonul la hotspot ", + "ACCESS_VIA_BROWSER": ",accesați prin browser ", + "WIFI_CONFIG_MODE": "Modul de configurare rețea", + "ENTERING_WIFI_CONFIG_MODE": "Se intră în modul de configurare rețea...", + "SCANNING_WIFI": "Se scanează Wi-Fi...", + "NEW_VERSION": "Versiune nouă ", + "OTA_UPGRADE": "Actualizare OTA", + "UPGRADING": "Se actualizează sistemul...", + "UPGRADE_FAILED": "Actualizarea a eșuat", + "ACTIVATION": "Activarea dispozitivului", + "BATTERY_LOW": "Baterie scăzută", + "BATTERY_CHARGING": "Se încarcă", + "BATTERY_FULL": "Baterie plină", + "BATTERY_NEED_CHARGE": "Baterie scăzută, vă rugăm să încărcați", + "VOLUME": "Volum ", + "MUTED": "Silențios", + "MAX_VOLUME": "Volum maxim", + "RTC_MODE_OFF": "AEC oprit", + "RTC_MODE_ON": "AEC pornit", + "DOWNLOAD_ASSETS_FAILED": "Eșec la descărcarea resurselor", + "LOADING_ASSETS": "Se încarcă resursele...", + "PLEASE_WAIT": "Vă rugăm să așteptați...", + "FOUND_NEW_ASSETS": "S-au găsit resurse noi: %s", + "HELLO_MY_FRIEND": "Salut, prietenul meu!" + } } \ No newline at end of file diff --git a/main/assets/locales/ru-RU/language.json b/main/assets/locales/ru-RU/language.json index ebac606..481a91f 100644 --- a/main/assets/locales/ru-RU/language.json +++ b/main/assets/locales/ru-RU/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "ru-RU" - }, - "strings": { - "WARNING": "Предупреждение", - "INFO": "Информация", - "ERROR": "Ошибка", - "VERSION": "Версия ", - "LOADING_PROTOCOL": "Подключение к серверу...", - "INITIALIZING": "Инициализация...", - "PIN_ERROR": "Пожалуйста, вставьте SIM-карту", - "REG_ERROR": "Невозможно подключиться к сети, проверьте состояние карты данных", - "DETECTING_MODULE": "Обнаружение модуля...", - "REGISTERING_NETWORK": "Ожидание сети...", - "CHECKING_NEW_VERSION": "Проверка новой версии...", - "CHECK_NEW_VERSION_FAILED": "Ошибка проверки новой версии, повтор через %d секунд: %s", - "SWITCH_TO_WIFI_NETWORK": "Переключение на Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "Переключение на 4G...", - "STANDBY": "Ожидание", - "CONNECT_TO": "Подключение к ", - "CONNECTING": "Подключение...", - "CONNECTED_TO": "Подключено к ", - "LISTENING": "Прослушивание...", - "SPEAKING": "Говорение...", - "SERVER_NOT_FOUND": "Поиск доступного сервиса", - "SERVER_NOT_CONNECTED": "Невозможно подключиться к сервису, попробуйте позже", - "SERVER_TIMEOUT": "Тайм-аут ответа", - "SERVER_ERROR": "Ошибка отправки, проверьте сеть", - "CONNECT_TO_HOTSPOT": "Подключите телефон к точке доступа ", - "ACCESS_VIA_BROWSER": ",доступ через браузер ", - "WIFI_CONFIG_MODE": "Режим настройки сети", - "ENTERING_WIFI_CONFIG_MODE": "Вход в режим настройки сети...", - "SCANNING_WIFI": "Сканирование Wi-Fi...", - "NEW_VERSION": "Новая версия ", - "OTA_UPGRADE": "Обновление OTA", - "UPGRADING": "Обновление системы...", - "UPGRADE_FAILED": "Обновление не удалось", - "ACTIVATION": "Активация устройства", - "BATTERY_LOW": "Низкий заряд батареи", - "BATTERY_CHARGING": "Зарядка", - "BATTERY_FULL": "Батарея полная", - "BATTERY_NEED_CHARGE": "Низкий заряд, пожалуйста, зарядите", - "VOLUME": "Громкость ", - "MUTED": "Звук отключен", - "MAX_VOLUME": "Максимальная громкость", - "RTC_MODE_OFF": "AEC выключен", - "RTC_MODE_ON": "AEC включен", - "DOWNLOAD_ASSETS_FAILED": "Не удалось загрузить ресурсы", - "LOADING_ASSETS": "Загрузка ресурсов...", - "PLEASE_WAIT": "Пожалуйста, подождите...", - "FOUND_NEW_ASSETS": "Найдены новые ресурсы: %s", - "HELLO_MY_FRIEND": "Привет, мой друг!" - } +{ + "language": { + "type": "ru-RU" + }, + "strings": { + "WARNING": "Предупреждение", + "INFO": "Информация", + "ERROR": "Ошибка", + "VERSION": "Версия ", + "LOADING_PROTOCOL": "Подключение к серверу...", + "INITIALIZING": "Инициализация...", + "PIN_ERROR": "Пожалуйста, вставьте SIM-карту", + "REG_ERROR": "Невозможно подключиться к сети, проверьте состояние карты данных", + "DETECTING_MODULE": "Обнаружение модуля...", + "REGISTERING_NETWORK": "Ожидание сети...", + "CHECKING_NEW_VERSION": "Проверка новой версии...", + "CHECK_NEW_VERSION_FAILED": "Ошибка проверки новой версии, повтор через %d секунд: %s", + "SWITCH_TO_WIFI_NETWORK": "Переключение на Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "Переключение на 4G...", + "STANDBY": "Ожидание", + "CONNECT_TO": "Подключение к ", + "CONNECTING": "Подключение...", + "CONNECTED_TO": "Подключено к ", + "LISTENING": "Прослушивание...", + "SPEAKING": "Говорение...", + "SERVER_NOT_FOUND": "Поиск доступного сервиса", + "SERVER_NOT_CONNECTED": "Невозможно подключиться к сервису, попробуйте позже", + "SERVER_TIMEOUT": "Тайм-аут ответа", + "SERVER_ERROR": "Ошибка отправки, проверьте сеть", + "CONNECT_TO_HOTSPOT": "Подключите телефон к точке доступа ", + "ACCESS_VIA_BROWSER": ",доступ через браузер ", + "WIFI_CONFIG_MODE": "Режим настройки сети", + "ENTERING_WIFI_CONFIG_MODE": "Вход в режим настройки сети...", + "SCANNING_WIFI": "Сканирование Wi-Fi...", + "NEW_VERSION": "Новая версия ", + "OTA_UPGRADE": "Обновление OTA", + "UPGRADING": "Обновление системы...", + "UPGRADE_FAILED": "Обновление не удалось", + "ACTIVATION": "Активация устройства", + "BATTERY_LOW": "Низкий заряд батареи", + "BATTERY_CHARGING": "Зарядка", + "BATTERY_FULL": "Батарея полная", + "BATTERY_NEED_CHARGE": "Низкий заряд, пожалуйста, зарядите", + "VOLUME": "Громкость ", + "MUTED": "Звук отключен", + "MAX_VOLUME": "Максимальная громкость", + "RTC_MODE_OFF": "AEC выключен", + "RTC_MODE_ON": "AEC включен", + "DOWNLOAD_ASSETS_FAILED": "Не удалось загрузить ресурсы", + "LOADING_ASSETS": "Загрузка ресурсов...", + "PLEASE_WAIT": "Пожалуйста, подождите...", + "FOUND_NEW_ASSETS": "Найдены новые ресурсы: %s", + "HELLO_MY_FRIEND": "Привет, мой друг!" + } } \ No newline at end of file diff --git a/main/assets/locales/th-TH/language.json b/main/assets/locales/th-TH/language.json index e97fed1..b6c96b2 100644 --- a/main/assets/locales/th-TH/language.json +++ b/main/assets/locales/th-TH/language.json @@ -1,56 +1,56 @@ -{ - "language": { - "type": "th-TH" - }, - "strings": { - "WARNING": "คำเตือน", - "INFO": "ข้อมูล", - "ERROR": "ข้อผิดพลาด", - "VERSION": "เวอร์ชัน ", - "LOADING_PROTOCOL": "กำลังเข้าสู่ระบบ...", - "INITIALIZING": "กำลังเริ่มต้นระบบ...", - "PIN_ERROR": "กรุณาใส่ซิมการ์ด", - "REG_ERROR": "ไม่สามารถเข้าถึงเครือข่ายได้ กรุณาตรวจสอบสถานะซิมการ์ด", - "DETECTING_MODULE": "กำลังตรวจจับโมดูล...", - "REGISTERING_NETWORK": "กำลังรอเครือข่าย...", - "CHECKING_NEW_VERSION": "กำลังตรวจสอบเวอร์ชันใหม่...", - "CHECK_NEW_VERSION_FAILED": "การตรวจสอบเวอร์ชันใหม่ล้มเหลว จะลองใหม่ใน %d วินาที: %s", - "SWITCH_TO_WIFI_NETWORK": "กำลังเปลี่ยนเป็น Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "กำลังเปลี่ยนเป็น 4G...", - "STANDBY": "พร้อม", - "CONNECT_TO": "เชื่อมต่อกับ ", - "CONNECTING": "กำลังเชื่อมต่อ...", - "CONNECTION_SUCCESSFUL": "เชื่อมต่อสำเร็จ", - "CONNECTED_TO": "เชื่อมต่อกับ ", - "LISTENING": "กำลังฟัง...", - "SPEAKING": "กำลังพูด...", - "SERVER_NOT_FOUND": "กำลังค้นหาบริการที่ใช้งานได้", - "SERVER_NOT_CONNECTED": "ไม่สามารถเชื่อมต่อกับบริการได้ กรุณาลองใหม่ในภายหลัง", - "SERVER_TIMEOUT": "หมดเวลารอการตอบกลับ", - "SERVER_ERROR": "การส่งข้อมูลล้มเหลว กรุณาตรวจสอบเครือข่าย", - "CONNECT_TO_HOTSPOT": "ฮอตสปอต: ", - "ACCESS_VIA_BROWSER": " URL การตั้งค่า: ", - "WIFI_CONFIG_MODE": "โหมดการตั้งค่า Wi-Fi", - "ENTERING_WIFI_CONFIG_MODE": "กำลังเข้าสู่โหมดการตั้งค่า Wi-Fi...", - "SCANNING_WIFI": "กำลังสแกน Wi-Fi...", - "NEW_VERSION": "เวอร์ชันใหม่ ", - "OTA_UPGRADE": "การอัปเกรด OTA", - "UPGRADING": "ระบบกำลังอัปเกรด...", - "UPGRADE_FAILED": "การอัปเกรดล้มเหลว", - "ACTIVATION": "การเปิดใช้งาน", - "BATTERY_LOW": "แบตเตอรี่ต่ำ", - "BATTERY_CHARGING": "กำลังชาร์จ", - "BATTERY_FULL": "แบตเตอรี่เต็ม", - "BATTERY_NEED_CHARGE": "แบตเตอรี่ต่ำ กรุณาชาร์จ", - "VOLUME": "เสียง ", - "MUTED": "ปิดเสียง", - "MAX_VOLUME": "เสียงสูงสุด", - "RTC_MODE_OFF": "ปิด AEC", - "RTC_MODE_ON": "เปิด AEC", - "DOWNLOAD_ASSETS_FAILED": "ดาวน์โหลดทรัพยากรล้มเหลว", - "LOADING_ASSETS": "กำลังโหลดทรัพยากร...", - "PLEASE_WAIT": "กรุณารอสักครู่...", - "FOUND_NEW_ASSETS": "พบทรัพยากรใหม่: %s", - "HELLO_MY_FRIEND": "สวัสดี เพื่อนของฉัน!" - } +{ + "language": { + "type": "th-TH" + }, + "strings": { + "WARNING": "คำเตือน", + "INFO": "ข้อมูล", + "ERROR": "ข้อผิดพลาด", + "VERSION": "เวอร์ชัน ", + "LOADING_PROTOCOL": "กำลังเข้าสู่ระบบ...", + "INITIALIZING": "กำลังเริ่มต้นระบบ...", + "PIN_ERROR": "กรุณาใส่ซิมการ์ด", + "REG_ERROR": "ไม่สามารถเข้าถึงเครือข่ายได้ กรุณาตรวจสอบสถานะซิมการ์ด", + "DETECTING_MODULE": "กำลังตรวจจับโมดูล...", + "REGISTERING_NETWORK": "กำลังรอเครือข่าย...", + "CHECKING_NEW_VERSION": "กำลังตรวจสอบเวอร์ชันใหม่...", + "CHECK_NEW_VERSION_FAILED": "การตรวจสอบเวอร์ชันใหม่ล้มเหลว จะลองใหม่ใน %d วินาที: %s", + "SWITCH_TO_WIFI_NETWORK": "กำลังเปลี่ยนเป็น Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "กำลังเปลี่ยนเป็น 4G...", + "STANDBY": "พร้อม", + "CONNECT_TO": "เชื่อมต่อกับ ", + "CONNECTING": "กำลังเชื่อมต่อ...", + "CONNECTION_SUCCESSFUL": "เชื่อมต่อสำเร็จ", + "CONNECTED_TO": "เชื่อมต่อกับ ", + "LISTENING": "กำลังฟัง...", + "SPEAKING": "กำลังพูด...", + "SERVER_NOT_FOUND": "กำลังค้นหาบริการที่ใช้งานได้", + "SERVER_NOT_CONNECTED": "ไม่สามารถเชื่อมต่อกับบริการได้ กรุณาลองใหม่ในภายหลัง", + "SERVER_TIMEOUT": "หมดเวลารอการตอบกลับ", + "SERVER_ERROR": "การส่งข้อมูลล้มเหลว กรุณาตรวจสอบเครือข่าย", + "CONNECT_TO_HOTSPOT": "ฮอตสปอต: ", + "ACCESS_VIA_BROWSER": " URL การตั้งค่า: ", + "WIFI_CONFIG_MODE": "โหมดการตั้งค่า Wi-Fi", + "ENTERING_WIFI_CONFIG_MODE": "กำลังเข้าสู่โหมดการตั้งค่า Wi-Fi...", + "SCANNING_WIFI": "กำลังสแกน Wi-Fi...", + "NEW_VERSION": "เวอร์ชันใหม่ ", + "OTA_UPGRADE": "การอัปเกรด OTA", + "UPGRADING": "ระบบกำลังอัปเกรด...", + "UPGRADE_FAILED": "การอัปเกรดล้มเหลว", + "ACTIVATION": "การเปิดใช้งาน", + "BATTERY_LOW": "แบตเตอรี่ต่ำ", + "BATTERY_CHARGING": "กำลังชาร์จ", + "BATTERY_FULL": "แบตเตอรี่เต็ม", + "BATTERY_NEED_CHARGE": "แบตเตอรี่ต่ำ กรุณาชาร์จ", + "VOLUME": "เสียง ", + "MUTED": "ปิดเสียง", + "MAX_VOLUME": "เสียงสูงสุด", + "RTC_MODE_OFF": "ปิด AEC", + "RTC_MODE_ON": "เปิด AEC", + "DOWNLOAD_ASSETS_FAILED": "ดาวน์โหลดทรัพยากรล้มเหลว", + "LOADING_ASSETS": "กำลังโหลดทรัพยากร...", + "PLEASE_WAIT": "กรุณารอสักครู่...", + "FOUND_NEW_ASSETS": "พบทรัพยากรใหม่: %s", + "HELLO_MY_FRIEND": "สวัสดี เพื่อนของฉัน!" + } } \ No newline at end of file diff --git a/main/assets/locales/tr-TR/language.json b/main/assets/locales/tr-TR/language.json index 4d0f70f..cdb7a3f 100644 --- a/main/assets/locales/tr-TR/language.json +++ b/main/assets/locales/tr-TR/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "tr-TR" - }, - "strings": { - "WARNING": "Uyarı", - "INFO": "Bilgi", - "ERROR": "Hata", - "VERSION": "Sürüm ", - "LOADING_PROTOCOL": "Sunucuya bağlanıyor...", - "INITIALIZING": "Başlatılıyor...", - "PIN_ERROR": "Lütfen SIM kartı takın", - "REG_ERROR": "Ağa erişilemiyor, veri kartı durumunu kontrol edin", - "DETECTING_MODULE": "Modül algılanıyor...", - "REGISTERING_NETWORK": "Ağ bekleniyor...", - "CHECKING_NEW_VERSION": "Yeni sürüm kontrol ediliyor...", - "CHECK_NEW_VERSION_FAILED": "Yeni sürüm kontrolü başarısız, %d saniye sonra tekrar denenecek: %s", - "SWITCH_TO_WIFI_NETWORK": "Wi-Fi'ye geçiliyor...", - "SWITCH_TO_4G_NETWORK": "4G'ye geçiliyor...", - "STANDBY": "Bekleme", - "CONNECT_TO": "Bağlan ", - "CONNECTING": "Bağlanıyor...", - "CONNECTED_TO": "Bağlandı ", - "LISTENING": "Dinleniyor...", - "SPEAKING": "Konuşuluyor...", - "SERVER_NOT_FOUND": "Mevcut hizmet aranıyor", - "SERVER_NOT_CONNECTED": "Hizmete bağlanılamıyor, lütfen daha sonra deneyin", - "SERVER_TIMEOUT": "Yanıt zaman aşımı", - "SERVER_ERROR": "Gönderme başarısız, ağı kontrol edin", - "CONNECT_TO_HOTSPOT": "Telefonu hotspot'a bağlayın ", - "ACCESS_VIA_BROWSER": ",tarayıcı üzerinden erişin ", - "WIFI_CONFIG_MODE": "Ağ yapılandırma modu", - "ENTERING_WIFI_CONFIG_MODE": "Ağ yapılandırma moduna giriliyor...", - "SCANNING_WIFI": "Wi-Fi taranıyor...", - "NEW_VERSION": "Yeni sürüm ", - "OTA_UPGRADE": "OTA güncelleme", - "UPGRADING": "Sistem güncelleniyor...", - "UPGRADE_FAILED": "Güncelleme başarısız", - "ACTIVATION": "Cihaz aktivasyonu", - "BATTERY_LOW": "Pil düşük", - "BATTERY_CHARGING": "Şarj oluyor", - "BATTERY_FULL": "Pil dolu", - "BATTERY_NEED_CHARGE": "Pil düşük, lütfen şarj edin", - "VOLUME": "Ses ", - "MUTED": "Sessiz", - "MAX_VOLUME": "Maksimum ses", - "RTC_MODE_OFF": "AEC kapalı", - "RTC_MODE_ON": "AEC açık", - "DOWNLOAD_ASSETS_FAILED": "Varlıklar indirilemedi", - "LOADING_ASSETS": "Varlıklar yükleniyor...", - "PLEASE_WAIT": "Lütfen bekleyin...", - "FOUND_NEW_ASSETS": "Yeni varlıklar bulundu: %s", - "HELLO_MY_FRIEND": "Merhaba, arkadaşım!" - } +{ + "language": { + "type": "tr-TR" + }, + "strings": { + "WARNING": "Uyarı", + "INFO": "Bilgi", + "ERROR": "Hata", + "VERSION": "Sürüm ", + "LOADING_PROTOCOL": "Sunucuya bağlanıyor...", + "INITIALIZING": "Başlatılıyor...", + "PIN_ERROR": "Lütfen SIM kartı takın", + "REG_ERROR": "Ağa erişilemiyor, veri kartı durumunu kontrol edin", + "DETECTING_MODULE": "Modül algılanıyor...", + "REGISTERING_NETWORK": "Ağ bekleniyor...", + "CHECKING_NEW_VERSION": "Yeni sürüm kontrol ediliyor...", + "CHECK_NEW_VERSION_FAILED": "Yeni sürüm kontrolü başarısız, %d saniye sonra tekrar denenecek: %s", + "SWITCH_TO_WIFI_NETWORK": "Wi-Fi'ye geçiliyor...", + "SWITCH_TO_4G_NETWORK": "4G'ye geçiliyor...", + "STANDBY": "Bekleme", + "CONNECT_TO": "Bağlan ", + "CONNECTING": "Bağlanıyor...", + "CONNECTED_TO": "Bağlandı ", + "LISTENING": "Dinleniyor...", + "SPEAKING": "Konuşuluyor...", + "SERVER_NOT_FOUND": "Mevcut hizmet aranıyor", + "SERVER_NOT_CONNECTED": "Hizmete bağlanılamıyor, lütfen daha sonra deneyin", + "SERVER_TIMEOUT": "Yanıt zaman aşımı", + "SERVER_ERROR": "Gönderme başarısız, ağı kontrol edin", + "CONNECT_TO_HOTSPOT": "Telefonu hotspot'a bağlayın ", + "ACCESS_VIA_BROWSER": ",tarayıcı üzerinden erişin ", + "WIFI_CONFIG_MODE": "Ağ yapılandırma modu", + "ENTERING_WIFI_CONFIG_MODE": "Ağ yapılandırma moduna giriliyor...", + "SCANNING_WIFI": "Wi-Fi taranıyor...", + "NEW_VERSION": "Yeni sürüm ", + "OTA_UPGRADE": "OTA güncelleme", + "UPGRADING": "Sistem güncelleniyor...", + "UPGRADE_FAILED": "Güncelleme başarısız", + "ACTIVATION": "Cihaz aktivasyonu", + "BATTERY_LOW": "Pil düşük", + "BATTERY_CHARGING": "Şarj oluyor", + "BATTERY_FULL": "Pil dolu", + "BATTERY_NEED_CHARGE": "Pil düşük, lütfen şarj edin", + "VOLUME": "Ses ", + "MUTED": "Sessiz", + "MAX_VOLUME": "Maksimum ses", + "RTC_MODE_OFF": "AEC kapalı", + "RTC_MODE_ON": "AEC açık", + "DOWNLOAD_ASSETS_FAILED": "Varlıklar indirilemedi", + "LOADING_ASSETS": "Varlıklar yükleniyor...", + "PLEASE_WAIT": "Lütfen bekleyin...", + "FOUND_NEW_ASSETS": "Yeni varlıklar bulundu: %s", + "HELLO_MY_FRIEND": "Merhaba, arkadaşım!" + } } \ No newline at end of file diff --git a/main/assets/locales/uk-UA/language.json b/main/assets/locales/uk-UA/language.json index d844a38..c7de676 100644 --- a/main/assets/locales/uk-UA/language.json +++ b/main/assets/locales/uk-UA/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "uk-UA" - }, - "strings": { - "WARNING": "Попередження", - "INFO": "Інформація", - "ERROR": "Помилка", - "VERSION": "Версія ", - "LOADING_PROTOCOL": "Підключення до сервера...", - "INITIALIZING": "Ініціалізація...", - "PIN_ERROR": "Будь ласка, вставте SIM-карту", - "REG_ERROR": "Неможливо отримати доступ до мережі, перевірте стан карти даних", - "DETECTING_MODULE": "Виявлення модуля...", - "REGISTERING_NETWORK": "Очікування мережі...", - "CHECKING_NEW_VERSION": "Перевірка нової версії...", - "CHECK_NEW_VERSION_FAILED": "Перевірка нової версії не вдалася, повтор через %d секунд: %s", - "SWITCH_TO_WIFI_NETWORK": "Перемикання на Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "Перемикання на 4G...", - "STANDBY": "Очікування", - "CONNECT_TO": "Підключитися до ", - "CONNECTING": "Підключення...", - "CONNECTED_TO": "Підключено до ", - "LISTENING": "Прослуховування...", - "SPEAKING": "Говоріння...", - "SERVER_NOT_FOUND": "Пошук доступного сервісу", - "SERVER_NOT_CONNECTED": "Неможливо підключитися до сервісу, спробуйте пізніше", - "SERVER_TIMEOUT": "Час очікування відповіді", - "SERVER_ERROR": "Помилка відправки, перевірте мережу", - "CONNECT_TO_HOTSPOT": "Підключіть телефон до точки доступу ", - "ACCESS_VIA_BROWSER": ",доступ через браузер ", - "WIFI_CONFIG_MODE": "Режим налаштування мережі", - "ENTERING_WIFI_CONFIG_MODE": "Вхід у режим налаштування мережі...", - "SCANNING_WIFI": "Сканування Wi-Fi...", - "NEW_VERSION": "Нова версія ", - "OTA_UPGRADE": "Оновлення OTA", - "UPGRADING": "Оновлення системи...", - "UPGRADE_FAILED": "Оновлення не вдалося", - "ACTIVATION": "Активація пристрою", - "BATTERY_LOW": "Низький заряд батареї", - "BATTERY_CHARGING": "Зарядка", - "BATTERY_FULL": "Батарея повна", - "BATTERY_NEED_CHARGE": "Низький заряд, будь ласка, зарядіть", - "VOLUME": "Гучність ", - "MUTED": "Звук вимкнено", - "MAX_VOLUME": "Максимальна гучність", - "RTC_MODE_OFF": "AEC вимкнено", - "RTC_MODE_ON": "AEC увімкнено", - "DOWNLOAD_ASSETS_FAILED": "Не вдалося завантажити ресурси", - "LOADING_ASSETS": "Завантаження ресурсів...", - "PLEASE_WAIT": "Будь ласка, зачекайте...", - "FOUND_NEW_ASSETS": "Знайдено нові ресурси: %s", - "HELLO_MY_FRIEND": "Привіт, мій друже!" - } +{ + "language": { + "type": "uk-UA" + }, + "strings": { + "WARNING": "Попередження", + "INFO": "Інформація", + "ERROR": "Помилка", + "VERSION": "Версія ", + "LOADING_PROTOCOL": "Підключення до сервера...", + "INITIALIZING": "Ініціалізація...", + "PIN_ERROR": "Будь ласка, вставте SIM-карту", + "REG_ERROR": "Неможливо отримати доступ до мережі, перевірте стан карти даних", + "DETECTING_MODULE": "Виявлення модуля...", + "REGISTERING_NETWORK": "Очікування мережі...", + "CHECKING_NEW_VERSION": "Перевірка нової версії...", + "CHECK_NEW_VERSION_FAILED": "Перевірка нової версії не вдалася, повтор через %d секунд: %s", + "SWITCH_TO_WIFI_NETWORK": "Перемикання на Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "Перемикання на 4G...", + "STANDBY": "Очікування", + "CONNECT_TO": "Підключитися до ", + "CONNECTING": "Підключення...", + "CONNECTED_TO": "Підключено до ", + "LISTENING": "Прослуховування...", + "SPEAKING": "Говоріння...", + "SERVER_NOT_FOUND": "Пошук доступного сервісу", + "SERVER_NOT_CONNECTED": "Неможливо підключитися до сервісу, спробуйте пізніше", + "SERVER_TIMEOUT": "Час очікування відповіді", + "SERVER_ERROR": "Помилка відправки, перевірте мережу", + "CONNECT_TO_HOTSPOT": "Підключіть телефон до точки доступу ", + "ACCESS_VIA_BROWSER": ",доступ через браузер ", + "WIFI_CONFIG_MODE": "Режим налаштування мережі", + "ENTERING_WIFI_CONFIG_MODE": "Вхід у режим налаштування мережі...", + "SCANNING_WIFI": "Сканування Wi-Fi...", + "NEW_VERSION": "Нова версія ", + "OTA_UPGRADE": "Оновлення OTA", + "UPGRADING": "Оновлення системи...", + "UPGRADE_FAILED": "Оновлення не вдалося", + "ACTIVATION": "Активація пристрою", + "BATTERY_LOW": "Низький заряд батареї", + "BATTERY_CHARGING": "Зарядка", + "BATTERY_FULL": "Батарея повна", + "BATTERY_NEED_CHARGE": "Низький заряд, будь ласка, зарядіть", + "VOLUME": "Гучність ", + "MUTED": "Звук вимкнено", + "MAX_VOLUME": "Максимальна гучність", + "RTC_MODE_OFF": "AEC вимкнено", + "RTC_MODE_ON": "AEC увімкнено", + "DOWNLOAD_ASSETS_FAILED": "Не вдалося завантажити ресурси", + "LOADING_ASSETS": "Завантаження ресурсів...", + "PLEASE_WAIT": "Будь ласка, зачекайте...", + "FOUND_NEW_ASSETS": "Знайдено нові ресурси: %s", + "HELLO_MY_FRIEND": "Привіт, мій друже!" + } } \ No newline at end of file diff --git a/main/assets/locales/vi-VN/language.json b/main/assets/locales/vi-VN/language.json index e010b7b..dc13c22 100644 --- a/main/assets/locales/vi-VN/language.json +++ b/main/assets/locales/vi-VN/language.json @@ -1,56 +1,56 @@ -{ - "language": { - "type": "vi-VN" - }, - "strings": { - "WARNING": "Cảnh báo", - "INFO": "Thông tin", - "ERROR": "Lỗi", - "VERSION": "Phiên bản ", - "LOADING_PROTOCOL": "Đang đăng nhập...", - "INITIALIZING": "Đang khởi tạo...", - "PIN_ERROR": "Vui lòng cắm thẻ SIM", - "REG_ERROR": "Không thể truy cập mạng, vui lòng kiểm tra trạng thái thẻ SIM", - "DETECTING_MODULE": "Đang phát hiện module...", - "REGISTERING_NETWORK": "Đang chờ mạng...", - "CHECKING_NEW_VERSION": "Đang kiểm tra phiên bản mới...", - "CHECK_NEW_VERSION_FAILED": "Kiểm tra phiên bản mới thất bại, sẽ thử lại sau %d giây: %s", - "SWITCH_TO_WIFI_NETWORK": "Đang chuyển sang Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "Đang chuyển sang 4G...", - "STANDBY": "Chờ", - "CONNECT_TO": "Kết nối đến ", - "CONNECTING": "Đang kết nối...", - "CONNECTION_SUCCESSFUL": "Kết nối thành công", - "CONNECTED_TO": "Đã kết nối đến ", - "LISTENING": "Đang lắng nghe...", - "SPEAKING": "Đang nói...", - "SERVER_NOT_FOUND": "Đang tìm dịch vụ khả dụng", - "SERVER_NOT_CONNECTED": "Không thể kết nối đến dịch vụ, vui lòng thử lại sau", - "SERVER_TIMEOUT": "Hết thời gian chờ phản hồi", - "SERVER_ERROR": "Gửi thất bại, vui lòng kiểm tra mạng", - "CONNECT_TO_HOTSPOT": "Điểm phát sóng: ", - "ACCESS_VIA_BROWSER": " URL cấu hình: ", - "WIFI_CONFIG_MODE": "Chế độ cấu hình Wi-Fi", - "ENTERING_WIFI_CONFIG_MODE": "Đang vào chế độ cấu hình Wi-Fi...", - "SCANNING_WIFI": "Đang quét Wi-Fi...", - "NEW_VERSION": "Phiên bản mới ", - "OTA_UPGRADE": "Nâng cấp OTA", - "UPGRADING": "Hệ thống đang nâng cấp...", - "UPGRADE_FAILED": "Nâng cấp thất bại", - "ACTIVATION": "Kích hoạt", - "BATTERY_LOW": "Pin yếu", - "BATTERY_CHARGING": "Đang sạc", - "BATTERY_FULL": "Pin đầy", - "BATTERY_NEED_CHARGE": "Pin yếu, vui lòng sạc", - "VOLUME": "Âm lượng ", - "MUTED": "Tắt tiếng", - "MAX_VOLUME": "Âm lượng tối đa", - "RTC_MODE_OFF": "Tắt AEC", - "RTC_MODE_ON": "Bật AEC", - "DOWNLOAD_ASSETS_FAILED": "Tải xuống tài nguyên thất bại", - "LOADING_ASSETS": "Đang tải tài nguyên...", - "PLEASE_WAIT": "Vui lòng đợi...", - "FOUND_NEW_ASSETS": "Tìm thấy tài nguyên mới: %s", - "HELLO_MY_FRIEND": "Xin chào, bạn của tôi!" - } +{ + "language": { + "type": "vi-VN" + }, + "strings": { + "WARNING": "Cảnh báo", + "INFO": "Thông tin", + "ERROR": "Lỗi", + "VERSION": "Phiên bản ", + "LOADING_PROTOCOL": "Đang đăng nhập...", + "INITIALIZING": "Đang khởi tạo...", + "PIN_ERROR": "Vui lòng cắm thẻ SIM", + "REG_ERROR": "Không thể truy cập mạng, vui lòng kiểm tra trạng thái thẻ SIM", + "DETECTING_MODULE": "Đang phát hiện module...", + "REGISTERING_NETWORK": "Đang chờ mạng...", + "CHECKING_NEW_VERSION": "Đang kiểm tra phiên bản mới...", + "CHECK_NEW_VERSION_FAILED": "Kiểm tra phiên bản mới thất bại, sẽ thử lại sau %d giây: %s", + "SWITCH_TO_WIFI_NETWORK": "Đang chuyển sang Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "Đang chuyển sang 4G...", + "STANDBY": "Chờ", + "CONNECT_TO": "Kết nối đến ", + "CONNECTING": "Đang kết nối...", + "CONNECTION_SUCCESSFUL": "Kết nối thành công", + "CONNECTED_TO": "Đã kết nối đến ", + "LISTENING": "Đang lắng nghe...", + "SPEAKING": "Đang nói...", + "SERVER_NOT_FOUND": "Đang tìm dịch vụ khả dụng", + "SERVER_NOT_CONNECTED": "Không thể kết nối đến dịch vụ, vui lòng thử lại sau", + "SERVER_TIMEOUT": "Hết thời gian chờ phản hồi", + "SERVER_ERROR": "Gửi thất bại, vui lòng kiểm tra mạng", + "CONNECT_TO_HOTSPOT": "Điểm phát sóng: ", + "ACCESS_VIA_BROWSER": " URL cấu hình: ", + "WIFI_CONFIG_MODE": "Chế độ cấu hình Wi-Fi", + "ENTERING_WIFI_CONFIG_MODE": "Đang vào chế độ cấu hình Wi-Fi...", + "SCANNING_WIFI": "Đang quét Wi-Fi...", + "NEW_VERSION": "Phiên bản mới ", + "OTA_UPGRADE": "Nâng cấp OTA", + "UPGRADING": "Hệ thống đang nâng cấp...", + "UPGRADE_FAILED": "Nâng cấp thất bại", + "ACTIVATION": "Kích hoạt", + "BATTERY_LOW": "Pin yếu", + "BATTERY_CHARGING": "Đang sạc", + "BATTERY_FULL": "Pin đầy", + "BATTERY_NEED_CHARGE": "Pin yếu, vui lòng sạc", + "VOLUME": "Âm lượng ", + "MUTED": "Tắt tiếng", + "MAX_VOLUME": "Âm lượng tối đa", + "RTC_MODE_OFF": "Tắt AEC", + "RTC_MODE_ON": "Bật AEC", + "DOWNLOAD_ASSETS_FAILED": "Tải xuống tài nguyên thất bại", + "LOADING_ASSETS": "Đang tải tài nguyên...", + "PLEASE_WAIT": "Vui lòng đợi...", + "FOUND_NEW_ASSETS": "Tìm thấy tài nguyên mới: %s", + "HELLO_MY_FRIEND": "Xin chào, bạn của tôi!" + } } \ No newline at end of file diff --git a/main/assets/locales/zh-CN/language.json b/main/assets/locales/zh-CN/language.json index 9eb619d..49bfb8a 100644 --- a/main/assets/locales/zh-CN/language.json +++ b/main/assets/locales/zh-CN/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "zh-CN" - }, - "strings": { - "WARNING": "警告", - "INFO": "信息", - "ERROR": "错误", - "VERSION": "版本 ", - "LOADING_PROTOCOL": "登录服务器...", - "INITIALIZING": "正在初始化...", - "PIN_ERROR": "请插入 SIM 卡", - "REG_ERROR": "无法接入网络,请检查流量卡状态", - "DETECTING_MODULE": "检测模组...", - "REGISTERING_NETWORK": "等待网络...", - "CHECKING_NEW_VERSION": "检查新版本...", - "CHECK_NEW_VERSION_FAILED": "检查新版本失败,将在 %d 秒后重试:%s", - "SWITCH_TO_WIFI_NETWORK": "切换到 Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "切换到 4G...", - "STANDBY": "待命", - "CONNECT_TO": "连接 ", - "CONNECTING": "连接中...", - "CONNECTED_TO": "已连接 ", - "LISTENING": "聆听中...", - "SPEAKING": "说话中...", - "SERVER_NOT_FOUND": "正在寻找可用服务", - "SERVER_NOT_CONNECTED": "无法连接服务,请稍后再试", - "SERVER_TIMEOUT": "等待响应超时", - "SERVER_ERROR": "发送失败,请检查网络", - "CONNECT_TO_HOTSPOT": "手机连接热点 ", - "ACCESS_VIA_BROWSER": ",浏览器访问 ", - "WIFI_CONFIG_MODE": "配网模式", - "ENTERING_WIFI_CONFIG_MODE": "进入配网模式...", - "SCANNING_WIFI": "扫描 Wi-Fi...", - "NEW_VERSION": "新版本 ", - "OTA_UPGRADE": "OTA 升级", - "UPGRADING": "正在升级系统...", - "UPGRADE_FAILED": "升级失败", - "ACTIVATION": "激活设备", - "BATTERY_LOW": "电量不足", - "BATTERY_CHARGING": "正在充电", - "BATTERY_FULL": "电量已满", - "BATTERY_NEED_CHARGE": "电量低,请充电", - "VOLUME": "音量 ", - "MUTED": "已静音", - "MAX_VOLUME": "最大音量", - "RTC_MODE_OFF": "AEC 关闭", - "RTC_MODE_ON": "AEC 开启", - "DOWNLOAD_ASSETS_FAILED": "下载资源失败", - "LOADING_ASSETS": "加载资源...", - "PLEASE_WAIT": "请稍候...", - "FOUND_NEW_ASSETS": "发现新资源: %s", - "HELLO_MY_FRIEND": "你好,我的朋友!" - } +{ + "language": { + "type": "zh-CN" + }, + "strings": { + "WARNING": "警告", + "INFO": "信息", + "ERROR": "错误", + "VERSION": "版本 ", + "LOADING_PROTOCOL": "登录服务器...", + "INITIALIZING": "正在初始化...", + "PIN_ERROR": "请插入 SIM 卡", + "REG_ERROR": "无法接入网络,请检查流量卡状态", + "DETECTING_MODULE": "检测模组...", + "REGISTERING_NETWORK": "等待网络...", + "CHECKING_NEW_VERSION": "检查新版本...", + "CHECK_NEW_VERSION_FAILED": "检查新版本失败,将在 %d 秒后重试:%s", + "SWITCH_TO_WIFI_NETWORK": "切换到 Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "切换到 4G...", + "STANDBY": "待命", + "CONNECT_TO": "连接 ", + "CONNECTING": "连接中...", + "CONNECTED_TO": "已连接 ", + "LISTENING": "聆听中...", + "SPEAKING": "说话中...", + "SERVER_NOT_FOUND": "正在寻找可用服务", + "SERVER_NOT_CONNECTED": "无法连接服务,请稍后再试", + "SERVER_TIMEOUT": "等待响应超时", + "SERVER_ERROR": "发送失败,请检查网络", + "CONNECT_TO_HOTSPOT": "手机连接热点 ", + "ACCESS_VIA_BROWSER": ",浏览器访问 ", + "WIFI_CONFIG_MODE": "配网模式", + "ENTERING_WIFI_CONFIG_MODE": "进入配网模式...", + "SCANNING_WIFI": "扫描 Wi-Fi...", + "NEW_VERSION": "新版本 ", + "OTA_UPGRADE": "OTA 升级", + "UPGRADING": "正在升级系统...", + "UPGRADE_FAILED": "升级失败", + "ACTIVATION": "激活设备", + "BATTERY_LOW": "电量不足", + "BATTERY_CHARGING": "正在充电", + "BATTERY_FULL": "电量已满", + "BATTERY_NEED_CHARGE": "电量低,请充电", + "VOLUME": "音量 ", + "MUTED": "已静音", + "MAX_VOLUME": "最大音量", + "RTC_MODE_OFF": "AEC 关闭", + "RTC_MODE_ON": "AEC 开启", + "DOWNLOAD_ASSETS_FAILED": "下载资源失败", + "LOADING_ASSETS": "加载资源...", + "PLEASE_WAIT": "请稍候...", + "FOUND_NEW_ASSETS": "发现新资源: %s", + "HELLO_MY_FRIEND": "你好,我的朋友!" + } } \ No newline at end of file diff --git a/main/assets/locales/zh-TW/language.json b/main/assets/locales/zh-TW/language.json index f035b36..357a579 100644 --- a/main/assets/locales/zh-TW/language.json +++ b/main/assets/locales/zh-TW/language.json @@ -1,55 +1,55 @@ -{ - "language": { - "type": "zh-TW" - }, - "strings": { - "WARNING": "警告", - "INFO": "資訊", - "ERROR": "錯誤", - "VERSION": "版本 ", - "LOADING_PROTOCOL": "登入伺服器...", - "INITIALIZING": "正在初始化...", - "PIN_ERROR": "請插入 SIM 卡", - "REG_ERROR": "無法接入網絡,請檢查網路狀態", - "DETECTING_MODULE": "檢測模組...", - "REGISTERING_NETWORK": "等待網絡...", - "CHECKING_NEW_VERSION": "檢查新版本...", - "CHECK_NEW_VERSION_FAILED": "檢查新版本失敗,將在 %d 秒後重試:%s", - "SWITCH_TO_WIFI_NETWORK": "切換到 Wi-Fi...", - "SWITCH_TO_4G_NETWORK": "切換到 4G...", - "STANDBY": "待命", - "CONNECT_TO": "連接 ", - "CONNECTING": "連接中...", - "CONNECTED_TO": "已連接 ", - "LISTENING": "聆聽中...", - "SPEAKING": "說話中...", - "SERVER_NOT_FOUND": "正在尋找可用服務", - "SERVER_NOT_CONNECTED": "無法連接服務,請稍後再試", - "SERVER_TIMEOUT": "等待響應超時", - "SERVER_ERROR": "發送失敗,請檢查網絡", - "CONNECT_TO_HOTSPOT": "手機連接WiFi ", - "ACCESS_VIA_BROWSER": ",瀏覽器訪問 ", - "WIFI_CONFIG_MODE": "網路設定模式", - "ENTERING_WIFI_CONFIG_MODE": "正在設定網路...", - "SCANNING_WIFI": "掃描 Wi-Fi...", - "NEW_VERSION": "新版本 ", - "OTA_UPGRADE": "OTA 升級", - "UPGRADING": "正在升級系統...", - "UPGRADE_FAILED": "升級失敗", - "ACTIVATION": "啟用設備", - "BATTERY_LOW": "電量不足", - "BATTERY_CHARGING": "正在充電", - "BATTERY_FULL": "電量已滿", - "BATTERY_NEED_CHARGE": "電量低,請充電", - "VOLUME": "音量 ", - "MUTED": "已靜音", - "MAX_VOLUME": "最大音量", - "RTC_MODE_OFF": "AEC 關閉", - "RTC_MODE_ON": "AEC 開啟", - "DOWNLOAD_ASSETS_FAILED": "下載資源失敗", - "LOADING_ASSETS": "載入資源...", - "PLEASE_WAIT": "請稍候...", - "FOUND_NEW_ASSETS": "發現新資源: %s", - "HELLO_MY_FRIEND": "你好,我的朋友!" - } +{ + "language": { + "type": "zh-TW" + }, + "strings": { + "WARNING": "警告", + "INFO": "資訊", + "ERROR": "錯誤", + "VERSION": "版本 ", + "LOADING_PROTOCOL": "登入伺服器...", + "INITIALIZING": "正在初始化...", + "PIN_ERROR": "請插入 SIM 卡", + "REG_ERROR": "無法接入網絡,請檢查網路狀態", + "DETECTING_MODULE": "檢測模組...", + "REGISTERING_NETWORK": "等待網絡...", + "CHECKING_NEW_VERSION": "檢查新版本...", + "CHECK_NEW_VERSION_FAILED": "檢查新版本失敗,將在 %d 秒後重試:%s", + "SWITCH_TO_WIFI_NETWORK": "切換到 Wi-Fi...", + "SWITCH_TO_4G_NETWORK": "切換到 4G...", + "STANDBY": "待命", + "CONNECT_TO": "連接 ", + "CONNECTING": "連接中...", + "CONNECTED_TO": "已連接 ", + "LISTENING": "聆聽中...", + "SPEAKING": "說話中...", + "SERVER_NOT_FOUND": "正在尋找可用服務", + "SERVER_NOT_CONNECTED": "無法連接服務,請稍後再試", + "SERVER_TIMEOUT": "等待響應超時", + "SERVER_ERROR": "發送失敗,請檢查網絡", + "CONNECT_TO_HOTSPOT": "手機連接WiFi ", + "ACCESS_VIA_BROWSER": ",瀏覽器訪問 ", + "WIFI_CONFIG_MODE": "網路設定模式", + "ENTERING_WIFI_CONFIG_MODE": "正在設定網路...", + "SCANNING_WIFI": "掃描 Wi-Fi...", + "NEW_VERSION": "新版本 ", + "OTA_UPGRADE": "OTA 升級", + "UPGRADING": "正在升級系統...", + "UPGRADE_FAILED": "升級失敗", + "ACTIVATION": "啟用設備", + "BATTERY_LOW": "電量不足", + "BATTERY_CHARGING": "正在充電", + "BATTERY_FULL": "電量已滿", + "BATTERY_NEED_CHARGE": "電量低,請充電", + "VOLUME": "音量 ", + "MUTED": "已靜音", + "MAX_VOLUME": "最大音量", + "RTC_MODE_OFF": "AEC 關閉", + "RTC_MODE_ON": "AEC 開啟", + "DOWNLOAD_ASSETS_FAILED": "下載資源失敗", + "LOADING_ASSETS": "載入資源...", + "PLEASE_WAIT": "請稍候...", + "FOUND_NEW_ASSETS": "發現新資源: %s", + "HELLO_MY_FRIEND": "你好,我的朋友!" + } } \ No newline at end of file diff --git a/main/audio/README.md b/main/audio/README.md index 6159f9a..44c165b 100644 --- a/main/audio/README.md +++ b/main/audio/README.md @@ -1,88 +1,88 @@ -# Audio Service Architecture - -The audio service is a core component responsible for managing all audio-related functionalities, including capturing audio from the microphone, processing it, encoding/decoding, and playing back audio through the speaker. It is designed to be modular and efficient, running its main operations in dedicated FreeRTOS tasks to ensure real-time performance. - -## Key Components - -- **`AudioService`**: The central orchestrator. It initializes and manages all other audio components, tasks, and data queues. -- **`AudioCodec`**: A hardware abstraction layer (HAL) for the physical audio codec chip. It handles the raw I2S communication for audio input and output. -- **`AudioProcessor`**: Performs real-time audio processing on the microphone input stream. This typically includes Acoustic Echo Cancellation (AEC), noise suppression, and Voice Activity Detection (VAD). `AfeAudioProcessor` is the default implementation, utilizing the ESP-ADF Audio Front-End. -- **`WakeWord`**: Detects keywords (e.g., "你好,小智", "Hi, ESP") from the audio stream. It runs independently from the main audio processor until a wake word is detected. -- **`OpusEncoderWrapper` / `OpusDecoderWrapper`**: Manages the encoding of PCM audio to the Opus format and decoding Opus packets back to PCM. Opus is used for its high compression and low latency, making it ideal for voice streaming. -- **`OpusResampler`**: A utility to convert audio streams between different sample rates (e.g., resampling from the codec's native sample rate to the required 16kHz for processing). - -## Threading Model - -The service operates on three primary tasks to handle the different stages of the audio pipeline concurrently: - -1. **`AudioInputTask`**: Solely responsible for reading raw PCM data from the `AudioCodec`. It then feeds this data to either the `WakeWord` engine or the `AudioProcessor` based on the current state. -2. **`AudioOutputTask`**: Responsible for playing audio. It retrieves decoded PCM data from the `audio_playback_queue_` and sends it to the `AudioCodec` to be played on the speaker. -3. **`OpusCodecTask`**: A worker task that handles both encoding and decoding. It fetches raw audio from `audio_encode_queue_`, encodes it into Opus packets, and places them in the `audio_send_queue_`. Concurrently, it fetches Opus packets from `audio_decode_queue_`, decodes them into PCM, and places the result in the `audio_playback_queue_`. - -## Data Flow - -There are two primary data flows: audio input (uplink) and audio output (downlink). - -### 1. Audio Input (Uplink) Flow - -This flow captures audio from the microphone, processes it, encodes it, and prepares it for sending to a server. - -```mermaid -graph TD - subgraph Device - Mic[("Microphone")] -->|I2S| Codec(AudioCodec) - - subgraph AudioInputTask - Codec -->|Raw PCM| Read(ReadAudioData) - Read -->|16kHz PCM| Processor(AudioProcessor) - end - - subgraph OpusCodecTask - Processor -->|Clean PCM| EncodeQueue(audio_encode_queue_) - EncodeQueue --> Encoder(OpusEncoder) - Encoder -->|Opus Packet| SendQueue(audio_send_queue_) - end - - SendQueue --> |"PopPacketFromSendQueue()"| App(Application Layer) - end - - App -->|Network| Server((Cloud Server)) -``` - -- The `AudioInputTask` continuously reads raw PCM data from the `AudioCodec`. -- This data is fed into an `AudioProcessor` for cleaning (AEC, VAD). -- The processed PCM data is pushed into the `audio_encode_queue_`. -- The `OpusCodecTask` picks up the PCM data, encodes it into Opus format, and pushes the resulting packet to the `audio_send_queue_`. -- The application can then retrieve these Opus packets and send them over the network. - -### 2. Audio Output (Downlink) Flow - -This flow receives encoded audio data, decodes it, and plays it on the speaker. - -```mermaid -graph TD - Server((Cloud Server)) -->|Network| App(Application Layer) - - subgraph Device - App -->|"PushPacketToDecodeQueue()"| DecodeQueue(audio_decode_queue_) - - subgraph OpusCodecTask - DecodeQueue -->|Opus Packet| Decoder(OpusDecoder) - Decoder -->|PCM| PlaybackQueue(audio_playback_queue_) - end - - subgraph AudioOutputTask - PlaybackQueue -->|PCM| Codec(AudioCodec) - end - - Codec -->|I2S| Speaker[("Speaker")] - end -``` - -- The application receives Opus packets from the network and pushes them into the `audio_decode_queue_`. -- The `OpusCodecTask` retrieves these packets, decodes them back into PCM data, and pushes the data to the `audio_playback_queue_`. -- The `AudioOutputTask` takes the PCM data from the queue and sends it to the `AudioCodec` for playback. - -## Power Management - +# Audio Service Architecture + +The audio service is a core component responsible for managing all audio-related functionalities, including capturing audio from the microphone, processing it, encoding/decoding, and playing back audio through the speaker. It is designed to be modular and efficient, running its main operations in dedicated FreeRTOS tasks to ensure real-time performance. + +## Key Components + +- **`AudioService`**: The central orchestrator. It initializes and manages all other audio components, tasks, and data queues. +- **`AudioCodec`**: A hardware abstraction layer (HAL) for the physical audio codec chip. It handles the raw I2S communication for audio input and output. +- **`AudioProcessor`**: Performs real-time audio processing on the microphone input stream. This typically includes Acoustic Echo Cancellation (AEC), noise suppression, and Voice Activity Detection (VAD). `AfeAudioProcessor` is the default implementation, utilizing the ESP-ADF Audio Front-End. +- **`WakeWord`**: Detects keywords (e.g., "你好,小智", "Hi, ESP") from the audio stream. It runs independently from the main audio processor until a wake word is detected. +- **`OpusEncoderWrapper` / `OpusDecoderWrapper`**: Manages the encoding of PCM audio to the Opus format and decoding Opus packets back to PCM. Opus is used for its high compression and low latency, making it ideal for voice streaming. +- **`OpusResampler`**: A utility to convert audio streams between different sample rates (e.g., resampling from the codec's native sample rate to the required 16kHz for processing). + +## Threading Model + +The service operates on three primary tasks to handle the different stages of the audio pipeline concurrently: + +1. **`AudioInputTask`**: Solely responsible for reading raw PCM data from the `AudioCodec`. It then feeds this data to either the `WakeWord` engine or the `AudioProcessor` based on the current state. +2. **`AudioOutputTask`**: Responsible for playing audio. It retrieves decoded PCM data from the `audio_playback_queue_` and sends it to the `AudioCodec` to be played on the speaker. +3. **`OpusCodecTask`**: A worker task that handles both encoding and decoding. It fetches raw audio from `audio_encode_queue_`, encodes it into Opus packets, and places them in the `audio_send_queue_`. Concurrently, it fetches Opus packets from `audio_decode_queue_`, decodes them into PCM, and places the result in the `audio_playback_queue_`. + +## Data Flow + +There are two primary data flows: audio input (uplink) and audio output (downlink). + +### 1. Audio Input (Uplink) Flow + +This flow captures audio from the microphone, processes it, encodes it, and prepares it for sending to a server. + +```mermaid +graph TD + subgraph Device + Mic[("Microphone")] -->|I2S| Codec(AudioCodec) + + subgraph AudioInputTask + Codec -->|Raw PCM| Read(ReadAudioData) + Read -->|16kHz PCM| Processor(AudioProcessor) + end + + subgraph OpusCodecTask + Processor -->|Clean PCM| EncodeQueue(audio_encode_queue_) + EncodeQueue --> Encoder(OpusEncoder) + Encoder -->|Opus Packet| SendQueue(audio_send_queue_) + end + + SendQueue --> |"PopPacketFromSendQueue()"| App(Application Layer) + end + + App -->|Network| Server((Cloud Server)) +``` + +- The `AudioInputTask` continuously reads raw PCM data from the `AudioCodec`. +- This data is fed into an `AudioProcessor` for cleaning (AEC, VAD). +- The processed PCM data is pushed into the `audio_encode_queue_`. +- The `OpusCodecTask` picks up the PCM data, encodes it into Opus format, and pushes the resulting packet to the `audio_send_queue_`. +- The application can then retrieve these Opus packets and send them over the network. + +### 2. Audio Output (Downlink) Flow + +This flow receives encoded audio data, decodes it, and plays it on the speaker. + +```mermaid +graph TD + Server((Cloud Server)) -->|Network| App(Application Layer) + + subgraph Device + App -->|"PushPacketToDecodeQueue()"| DecodeQueue(audio_decode_queue_) + + subgraph OpusCodecTask + DecodeQueue -->|Opus Packet| Decoder(OpusDecoder) + Decoder -->|PCM| PlaybackQueue(audio_playback_queue_) + end + + subgraph AudioOutputTask + PlaybackQueue -->|PCM| Codec(AudioCodec) + end + + Codec -->|I2S| Speaker[("Speaker")] + end +``` + +- The application receives Opus packets from the network and pushes them into the `audio_decode_queue_`. +- The `OpusCodecTask` retrieves these packets, decodes them back into PCM data, and pushes the data to the `audio_playback_queue_`. +- The `AudioOutputTask` takes the PCM data from the queue and sends it to the `AudioCodec` for playback. + +## Power Management + To conserve energy, the audio codec's input (ADC) and output (DAC) channels are automatically disabled after a period of inactivity (`AUDIO_POWER_TIMEOUT_MS`). A timer (`audio_power_timer_`) periodically checks for activity and manages the power state. The channels are automatically re-enabled when new audio needs to be captured or played. \ No newline at end of file diff --git a/main/audio/audio_codec.cc b/main/audio/audio_codec.cc index 0748f9e..e86e02c 100644 --- a/main/audio/audio_codec.cc +++ b/main/audio/audio_codec.cc @@ -1,162 +1,149 @@ -#include "audio_codec.h" -#include "board.h" -#include "settings.h" - -#include -#include -#include - -#define TAG "AudioCodec" - -AudioCodec::AudioCodec() { -} - -AudioCodec::~AudioCodec() { -} - -void AudioCodec::OutputData(std::vector& data) { - Write(data.data(), data.size()); -} - -bool AudioCodec::InputData(std::vector& data) { - int samples = Read(data.data(), data.size()); - if (samples > 0) { - return true; - } - return false; -} - -void AudioCodec::Start() { - Settings settings("audio", false); - output_volume_ = settings.GetInt("output_volume", output_volume_); - if (output_volume_ <= 0) { - ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_); - output_volume_ = 10; - } - - // 保存原始输出采样率 - if (original_output_sample_rate_ == 0) { - original_output_sample_rate_ = output_sample_rate_; - ESP_LOGI(TAG, "Saved original output sample rate: %d Hz", original_output_sample_rate_); - } - - if (tx_handle_ != nullptr) { - esp_err_t err = i2s_channel_enable(tx_handle_); - if (err == ESP_ERR_INVALID_STATE) { - // 已经启用,忽略 - ESP_LOGW(TAG, "TX channel already enabled"); - } else { - ESP_ERROR_CHECK(err); - } - } - - if (rx_handle_ != nullptr) { - esp_err_t err = i2s_channel_enable(rx_handle_); - if (err == ESP_ERR_INVALID_STATE) { - ESP_LOGW(TAG, "RX channel already enabled"); - } else { - ESP_ERROR_CHECK(err); - } - } - - EnableInput(true); - EnableOutput(true); - ESP_LOGI(TAG, "Audio codec started"); -} - -void AudioCodec::SetOutputVolume(int volume) { - output_volume_ = volume; - ESP_LOGI(TAG, "Set output volume to %d", output_volume_); - - Settings settings("audio", true); - settings.SetInt("output_volume", output_volume_); -} - -void AudioCodec::EnableInput(bool enable) { - if (enable == input_enabled_) { - return; - } - input_enabled_ = enable; - ESP_LOGI(TAG, "Set input enable to %s", enable ? "true" : "false"); -} - -void AudioCodec::EnableOutput(bool enable) { - if (enable == output_enabled_) { - return; - } - output_enabled_ = enable; - ESP_LOGI(TAG, "Set output enable to %s", enable ? "true" : "false"); -} - -bool AudioCodec::SetOutputSampleRate(int sample_rate) { - // 特殊处理:如果传入 -1,表示重置到原始采样率 - if (sample_rate == -1) { - if (original_output_sample_rate_ > 0) { - sample_rate = original_output_sample_rate_; - ESP_LOGI(TAG, "Resetting to original output sample rate: %d Hz", sample_rate); - } else { - ESP_LOGW(TAG, "Original sample rate not available, cannot reset"); - return false; - } - } - - if (sample_rate <= 0 || sample_rate > 192000) { - ESP_LOGE(TAG, "Invalid sample rate: %d", sample_rate); - return false; - } - - if (output_sample_rate_ == sample_rate) { - ESP_LOGI(TAG, "Sample rate already set to %d Hz", sample_rate); - return true; - } - - if (tx_handle_ == nullptr) { - ESP_LOGW(TAG, "TX handle is null, only updating sample rate variable"); - output_sample_rate_ = sample_rate; - return true; - } - - ESP_LOGI(TAG, "Changing output sample rate from %d to %d Hz", output_sample_rate_, sample_rate); - - // 先尝试禁用 I2S 通道(如果已启用的话) - bool was_enabled = false; - esp_err_t disable_ret = i2s_channel_disable(tx_handle_); - if (disable_ret == ESP_OK) { - was_enabled = true; - ESP_LOGI(TAG, "Disabled I2S TX channel for reconfiguration"); - } else if (disable_ret == ESP_ERR_INVALID_STATE) { - // 通道可能已经是禁用状态,这是正常的 - ESP_LOGI(TAG, "I2S TX channel was already disabled"); - } else { - ESP_LOGW(TAG, "Failed to disable I2S TX channel: %s", esp_err_to_name(disable_ret)); - } - - // 重新配置 I2S 时钟 - i2s_std_clk_config_t clk_cfg = { - .sample_rate_hz = (uint32_t)sample_rate, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, -#ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, -#endif - }; - - esp_err_t ret = i2s_channel_reconfig_std_clock(tx_handle_, &clk_cfg); - - // 重新启用通道(无论之前是什么状态,现在都需要启用以便播放音频) - esp_err_t enable_ret = i2s_channel_enable(tx_handle_); - if (enable_ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to enable I2S TX channel: %s", esp_err_to_name(enable_ret)); - } else { - ESP_LOGI(TAG, "Enabled I2S TX channel"); - } - - if (ret == ESP_OK) { - output_sample_rate_ = sample_rate; - ESP_LOGI(TAG, "Successfully changed output sample rate to %d Hz", sample_rate); - return true; - } else { - ESP_LOGE(TAG, "Failed to change sample rate to %d Hz: %s", sample_rate, esp_err_to_name(ret)); - return false; - } +#include "audio_codec.h" +#include "board.h" +#include "settings.h" + +#include +#include +#include + +#define TAG "AudioCodec" + +AudioCodec::AudioCodec() { +} + +AudioCodec::~AudioCodec() { +} + +void AudioCodec::OutputData(std::vector& data) { + Write(data.data(), data.size()); +} + +bool AudioCodec::InputData(std::vector& data) { + int samples = Read(data.data(), data.size()); + if (samples > 0) { + return true; + } + return false; +} + +void AudioCodec::Start() { + Settings settings("audio", false); + output_volume_ = settings.GetInt("output_volume", output_volume_); + if (output_volume_ <= 0) { + ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_); + output_volume_ = 10; + } + // 保存原始输出采样率 + if (original_output_sample_rate_ == 0){ + original_output_sample_rate_ = output_sample_rate_; + ESP_LOGI(TAG, "Saved original output sample rate: %d Hz", original_output_sample_rate_); + } + if (tx_handle_ != nullptr) { + ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_)); + } + + if (rx_handle_ != nullptr) { + ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_)); + } + + EnableInput(true); + EnableOutput(true); + ESP_LOGI(TAG, "Audio codec started"); +} + +void AudioCodec::SetOutputVolume(int volume) { + output_volume_ = volume; + ESP_LOGI(TAG, "Set output volume to %d", output_volume_); + + Settings settings("audio", true); + settings.SetInt("output_volume", output_volume_); +} + +void AudioCodec::EnableInput(bool enable) { + if (enable == input_enabled_) { + return; + } + input_enabled_ = enable; + ESP_LOGI(TAG, "Set input enable to %s", enable ? "true" : "false"); +} + +void AudioCodec::EnableOutput(bool enable) { + if (enable == output_enabled_) { + return; + } + output_enabled_ = enable; + ESP_LOGI(TAG, "Set output enable to %s", enable ? "true" : "false"); +} + +bool AudioCodec::SetOutputSampleRate(int sample_rate) { + // 特殊处理:如果传入 -1,表示重置到原始采样率 + if (sample_rate == -1) { + if (original_output_sample_rate_ > 0) { + sample_rate = original_output_sample_rate_; + ESP_LOGI(TAG, "Resetting to original output sample rate: %d Hz", sample_rate); + } else { + ESP_LOGW(TAG, "Original sample rate not available, cannot reset"); + return false; + } + } + + if (sample_rate <= 0 || sample_rate > 192000) { + ESP_LOGE(TAG, "Invalid sample rate: %d", sample_rate); + return false; + } + + if (output_sample_rate_ == sample_rate) { + ESP_LOGI(TAG, "Sample rate already set to %d Hz", sample_rate); + return true; + } + + if (tx_handle_ == nullptr) { + ESP_LOGW(TAG, "TX handle is null, only updating sample rate variable"); + output_sample_rate_ = sample_rate; + return true; + } + + ESP_LOGI(TAG, "Changing output sample rate from %d to %d Hz", output_sample_rate_, sample_rate); + + // 先尝试禁用 I2S 通道(如果已启用的话) + bool was_enabled = false; + esp_err_t disable_ret = i2s_channel_disable(tx_handle_); + if (disable_ret == ESP_OK) { + was_enabled = true; + ESP_LOGI(TAG, "Disabled I2S TX channel for reconfiguration"); + } else if (disable_ret == ESP_ERR_INVALID_STATE) { + // 通道可能已经是禁用状态,这是正常的 + ESP_LOGI(TAG, "I2S TX channel was already disabled"); + } else { + ESP_LOGW(TAG, "Failed to disable I2S TX channel: %s", esp_err_to_name(disable_ret)); + } + + // 重新配置 I2S 时钟 + i2s_std_clk_config_t clk_cfg = { + .sample_rate_hz = (uint32_t)sample_rate, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, +#ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, +#endif + }; + + esp_err_t ret = i2s_channel_reconfig_std_clock(tx_handle_, &clk_cfg); + + // 重新启用通道(无论之前是什么状态,现在都需要启用以便播放音频) + esp_err_t enable_ret = i2s_channel_enable(tx_handle_); + if (enable_ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable I2S TX channel: %s", esp_err_to_name(enable_ret)); + } else { + ESP_LOGI(TAG, "Enabled I2S TX channel"); + } + + if (ret == ESP_OK) { + output_sample_rate_ = sample_rate; + ESP_LOGI(TAG, "Successfully changed output sample rate to %d Hz", sample_rate); + return true; + } else { + ESP_LOGE(TAG, "Failed to change sample rate to %d Hz: %s", sample_rate, esp_err_to_name(ret)); + return false; + } } \ No newline at end of file diff --git a/main/audio/audio_codec.h b/main/audio/audio_codec.h index 0229fae..c0d6cdf 100644 --- a/main/audio/audio_codec.h +++ b/main/audio/audio_codec.h @@ -1,62 +1,62 @@ -#ifndef _AUDIO_CODEC_H -#define _AUDIO_CODEC_H - -#include -#include -#include - -#include -#include -#include - -#include "board.h" - -#define AUDIO_CODEC_DMA_DESC_NUM 6 -#define AUDIO_CODEC_DMA_FRAME_NUM 240 -#define AUDIO_CODEC_DEFAULT_MIC_GAIN 30.0 - -class AudioCodec { -public: - AudioCodec(); - virtual ~AudioCodec(); - - virtual void SetOutputVolume(int volume); - virtual void EnableInput(bool enable); - virtual void EnableOutput(bool enable); - virtual bool SetOutputSampleRate(int sample_rate); - - virtual void OutputData(std::vector& data); - virtual bool InputData(std::vector& data); - virtual void Start(); - - inline bool duplex() const { return duplex_; } - inline bool input_reference() const { return input_reference_; } - inline int input_sample_rate() const { return input_sample_rate_; } - inline int output_sample_rate() const { return output_sample_rate_; } - inline int original_output_sample_rate() const { return original_output_sample_rate_; } - inline int input_channels() const { return input_channels_; } - inline int output_channels() const { return output_channels_; } - inline int output_volume() const { return output_volume_; } - inline bool input_enabled() const { return input_enabled_; } - inline bool output_enabled() const { return output_enabled_; } - -protected: - i2s_chan_handle_t tx_handle_ = nullptr; - i2s_chan_handle_t rx_handle_ = nullptr; - - bool duplex_ = false; - bool input_reference_ = false; - bool input_enabled_ = false; - bool output_enabled_ = false; - int input_sample_rate_ = 0; - int output_sample_rate_ = 0; - int original_output_sample_rate_ = 0; - int input_channels_ = 1; - int output_channels_ = 1; - int output_volume_ = 70; - - virtual int Read(int16_t* dest, int samples) = 0; - virtual int Write(const int16_t* data, int samples) = 0; -}; - -#endif // _AUDIO_CODEC_H \ No newline at end of file +#ifndef _AUDIO_CODEC_H +#define _AUDIO_CODEC_H + +#include +#include +#include + +#include +#include +#include + +#include "board.h" + +#define AUDIO_CODEC_DMA_DESC_NUM 6 +#define AUDIO_CODEC_DMA_FRAME_NUM 240 +#define AUDIO_CODEC_DEFAULT_MIC_GAIN 30.0 + +class AudioCodec { +public: + AudioCodec(); + virtual ~AudioCodec(); + + virtual void SetOutputVolume(int volume); + virtual void EnableInput(bool enable); + virtual void EnableOutput(bool enable); + virtual bool SetOutputSampleRate(int sample_rate); + + virtual void OutputData(std::vector& data); + virtual bool InputData(std::vector& data); + virtual void Start(); + + inline bool duplex() const { return duplex_; } + inline bool input_reference() const { return input_reference_; } + inline int input_sample_rate() const { return input_sample_rate_; } + inline int output_sample_rate() const { return output_sample_rate_; } + inline int original_output_sample_rate() const { return original_output_sample_rate_; } + inline int input_channels() const { return input_channels_; } + inline int output_channels() const { return output_channels_; } + inline int output_volume() const { return output_volume_; } + inline bool input_enabled() const { return input_enabled_; } + inline bool output_enabled() const { return output_enabled_; } + +protected: + i2s_chan_handle_t tx_handle_ = nullptr; + i2s_chan_handle_t rx_handle_ = nullptr; + + bool duplex_ = false; + bool input_reference_ = false; + bool input_enabled_ = false; + bool output_enabled_ = false; + int input_sample_rate_ = 0; + int output_sample_rate_ = 0; + int original_output_sample_rate_ = 0; + int input_channels_ = 1; + int output_channels_ = 1; + int output_volume_ = 70; + + virtual int Read(int16_t* dest, int samples) = 0; + virtual int Write(const int16_t* data, int samples) = 0; +}; + +#endif // _AUDIO_CODEC_H diff --git a/main/audio/audio_processor.h b/main/audio/audio_processor.h index 543c7ae..c49a1af 100644 --- a/main/audio/audio_processor.h +++ b/main/audio/audio_processor.h @@ -1,26 +1,26 @@ -#ifndef AUDIO_PROCESSOR_H -#define AUDIO_PROCESSOR_H - -#include -#include -#include - -#include -#include "audio_codec.h" - -class AudioProcessor { -public: - virtual ~AudioProcessor() = default; - - virtual void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) = 0; - virtual void Feed(std::vector&& data) = 0; - virtual void Start() = 0; - virtual void Stop() = 0; - virtual bool IsRunning() = 0; - virtual void OnOutput(std::function&& data)> callback) = 0; - virtual void OnVadStateChange(std::function callback) = 0; - virtual size_t GetFeedSize() = 0; - virtual void EnableDeviceAec(bool enable) = 0; -}; - -#endif +#ifndef AUDIO_PROCESSOR_H +#define AUDIO_PROCESSOR_H + +#include +#include +#include + +#include +#include "audio_codec.h" + +class AudioProcessor { +public: + virtual ~AudioProcessor() = default; + + virtual void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) = 0; + virtual void Feed(std::vector&& data) = 0; + virtual void Start() = 0; + virtual void Stop() = 0; + virtual bool IsRunning() = 0; + virtual void OnOutput(std::function&& data)> callback) = 0; + virtual void OnVadStateChange(std::function callback) = 0; + virtual size_t GetFeedSize() = 0; + virtual void EnableDeviceAec(bool enable) = 0; +}; + +#endif diff --git a/main/audio/audio_service.cc b/main/audio/audio_service.cc index c854e71..9e01adb 100644 --- a/main/audio/audio_service.cc +++ b/main/audio/audio_service.cc @@ -1,678 +1,690 @@ -#include "audio_service.h" -#include -#include - -#if CONFIG_USE_AUDIO_PROCESSOR -#include "processors/afe_audio_processor.h" -#else -#include "processors/no_audio_processor.h" -#endif - -#if CONFIG_USE_AFE_WAKE_WORD -#include "wake_words/afe_wake_word.h" -#elif CONFIG_USE_ESP_WAKE_WORD -#include "wake_words/esp_wake_word.h" -#elif CONFIG_USE_CUSTOM_WAKE_WORD -#include "wake_words/custom_wake_word.h" -#endif - -#define TAG "AudioService" - - -AudioService::AudioService() { - event_group_ = xEventGroupCreate(); -} - -AudioService::~AudioService() { - if (event_group_ != nullptr) { - vEventGroupDelete(event_group_); - } -} - - -void AudioService::Initialize(AudioCodec* codec) { - codec_ = codec; - codec_->Start(); - - /* Setup the audio codec */ - opus_decoder_ = std::make_unique(codec->output_sample_rate(), 1, OPUS_FRAME_DURATION_MS); - opus_encoder_ = std::make_unique(16000, 1, OPUS_FRAME_DURATION_MS); - opus_encoder_->SetComplexity(0); - - if (codec->input_sample_rate() != 16000) { - input_resampler_.Configure(codec->input_sample_rate(), 16000); - reference_resampler_.Configure(codec->input_sample_rate(), 16000); - } - -#if CONFIG_USE_AUDIO_PROCESSOR - audio_processor_ = std::make_unique(); -#else - audio_processor_ = std::make_unique(); -#endif - -#if CONFIG_USE_AFE_WAKE_WORD - wake_word_ = std::make_unique(); -#elif CONFIG_USE_ESP_WAKE_WORD - wake_word_ = std::make_unique(); -#elif CONFIG_USE_CUSTOM_WAKE_WORD - wake_word_ = std::make_unique(); -#else - wake_word_ = nullptr; -#endif - - audio_processor_->OnOutput([this](std::vector&& data) { - PushTaskToEncodeQueue(kAudioTaskTypeEncodeToSendQueue, std::move(data)); - }); - - audio_processor_->OnVadStateChange([this](bool speaking) { - voice_detected_ = speaking; - if (callbacks_.on_vad_change) { - callbacks_.on_vad_change(speaking); - } - }); - - if (wake_word_) { - wake_word_->OnWakeWordDetected([this](const std::string& wake_word) { - if (callbacks_.on_wake_word_detected) { - callbacks_.on_wake_word_detected(wake_word); - } - }); - } - - esp_timer_create_args_t audio_power_timer_args = { - .callback = [](void* arg) { - AudioService* audio_service = (AudioService*)arg; - audio_service->CheckAndUpdateAudioPowerState(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "audio_power_timer", - .skip_unhandled_events = true, - }; - esp_timer_create(&audio_power_timer_args, &audio_power_timer_); -} - -void AudioService::Start() { - service_stopped_ = false; - xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING | AS_EVENT_WAKE_WORD_RUNNING | AS_EVENT_AUDIO_PROCESSOR_RUNNING); - - esp_timer_start_periodic(audio_power_timer_, 1000000); - -#if CONFIG_USE_AUDIO_PROCESSOR - /* Start the audio input task */ - xTaskCreatePinnedToCore([](void* arg) { - AudioService* audio_service = (AudioService*)arg; - audio_service->AudioInputTask(); - vTaskDelete(NULL); - }, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_, 1); - - /* Start the audio output task */ - xTaskCreate([](void* arg) { - AudioService* audio_service = (AudioService*)arg; - audio_service->AudioOutputTask(); - vTaskDelete(NULL); - }, "audio_output", 2048 * 2, this, 4, &audio_output_task_handle_); -#else - /* Start the audio input task */ - xTaskCreate([](void* arg) { - AudioService* audio_service = (AudioService*)arg; - audio_service->AudioInputTask(); - vTaskDelete(NULL); - }, "audio_input", 2048 * 2, this, 8, &audio_input_task_handle_); - - /* Start the audio output task */ - xTaskCreate([](void* arg) { - AudioService* audio_service = (AudioService*)arg; - audio_service->AudioOutputTask(); - vTaskDelete(NULL); - }, "audio_output", 2048, this, 4, &audio_output_task_handle_); -#endif - - /* Start the opus codec task */ - xTaskCreate([](void* arg) { - AudioService* audio_service = (AudioService*)arg; - audio_service->OpusCodecTask(); - vTaskDelete(NULL); - }, "opus_codec", 2048 * 13, this, 2, &opus_codec_task_handle_); -} - -void AudioService::Stop() { - esp_timer_stop(audio_power_timer_); - service_stopped_ = true; - xEventGroupSetBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING | - AS_EVENT_WAKE_WORD_RUNNING | - AS_EVENT_AUDIO_PROCESSOR_RUNNING); - - std::lock_guard lock(audio_queue_mutex_); - audio_encode_queue_.clear(); - audio_decode_queue_.clear(); - audio_playback_queue_.clear(); - audio_testing_queue_.clear(); - audio_queue_cv_.notify_all(); -} - -bool AudioService::ReadAudioData(std::vector& data, int sample_rate, int samples) { - if (!codec_->input_enabled()) { - esp_timer_stop(audio_power_timer_); - esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000); - codec_->EnableInput(true); - } - - if (codec_->input_sample_rate() != sample_rate) { - data.resize(samples * codec_->input_sample_rate() / sample_rate * codec_->input_channels()); - if (!codec_->InputData(data)) { - return false; - } - if (codec_->input_channels() == 2) { - auto mic_channel = std::vector(data.size() / 2); - auto reference_channel = std::vector(data.size() / 2); - for (size_t i = 0, j = 0; i < mic_channel.size(); ++i, j += 2) { - mic_channel[i] = data[j]; - reference_channel[i] = data[j + 1]; - } - auto resampled_mic = std::vector(input_resampler_.GetOutputSamples(mic_channel.size())); - auto resampled_reference = std::vector(reference_resampler_.GetOutputSamples(reference_channel.size())); - input_resampler_.Process(mic_channel.data(), mic_channel.size(), resampled_mic.data()); - reference_resampler_.Process(reference_channel.data(), reference_channel.size(), resampled_reference.data()); - data.resize(resampled_mic.size() + resampled_reference.size()); - for (size_t i = 0, j = 0; i < resampled_mic.size(); ++i, j += 2) { - data[j] = resampled_mic[i]; - data[j + 1] = resampled_reference[i]; - } - } else { - auto resampled = std::vector(input_resampler_.GetOutputSamples(data.size())); - input_resampler_.Process(data.data(), data.size(), resampled.data()); - data = std::move(resampled); - } - } else { - data.resize(samples * codec_->input_channels()); - if (!codec_->InputData(data)) { - return false; - } - } - - /* Update the last input time */ - last_input_time_ = std::chrono::steady_clock::now(); - debug_statistics_.input_count++; - -#if CONFIG_USE_AUDIO_DEBUGGER - // 音频调试:发送原始音频数据 - if (audio_debugger_ == nullptr) { - audio_debugger_ = std::make_unique(); - } - audio_debugger_->Feed(data); -#endif - - return true; -} - -void AudioService::AudioInputTask() { - while (true) { - EventBits_t bits = xEventGroupWaitBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING | - AS_EVENT_WAKE_WORD_RUNNING | AS_EVENT_AUDIO_PROCESSOR_RUNNING, - pdFALSE, pdFALSE, portMAX_DELAY); - - if (service_stopped_) { - break; - } - if (audio_input_need_warmup_) { - audio_input_need_warmup_ = false; - vTaskDelay(pdMS_TO_TICKS(120)); - continue; - } - - /* Used for audio testing in NetworkConfiguring mode by clicking the BOOT button */ - if (bits & AS_EVENT_AUDIO_TESTING_RUNNING) { - if (audio_testing_queue_.size() >= AUDIO_TESTING_MAX_DURATION_MS / OPUS_FRAME_DURATION_MS) { - ESP_LOGW(TAG, "Audio testing queue is full, stopping audio testing"); - EnableAudioTesting(false); - continue; - } - std::vector data; - int samples = OPUS_FRAME_DURATION_MS * 16000 / 1000; - if (ReadAudioData(data, 16000, samples)) { - // If input channels is 2, we need to fetch the left channel data - if (codec_->input_channels() == 2) { - auto mono_data = std::vector(data.size() / 2); - for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) { - mono_data[i] = data[j]; - } - data = std::move(mono_data); - } - PushTaskToEncodeQueue(kAudioTaskTypeEncodeToTestingQueue, std::move(data)); - continue; - } - } - - /* Feed the wake word */ - if (bits & AS_EVENT_WAKE_WORD_RUNNING) { - std::vector data; - int samples = wake_word_->GetFeedSize(); - if (samples > 0) { - if (ReadAudioData(data, 16000, samples)) { - wake_word_->Feed(data); - continue; - } - } - } - - /* Feed the audio processor */ - if (bits & AS_EVENT_AUDIO_PROCESSOR_RUNNING) { - std::vector data; - int samples = audio_processor_->GetFeedSize(); - if (samples > 0) { - if (ReadAudioData(data, 16000, samples)) { - audio_processor_->Feed(std::move(data)); - continue; - } - } - } - - ESP_LOGE(TAG, "Should not be here, bits: %lx", bits); - break; - } - - ESP_LOGW(TAG, "Audio input task stopped"); -} - -void AudioService::AudioOutputTask() { - while (true) { - std::unique_lock lock(audio_queue_mutex_); - audio_queue_cv_.wait(lock, [this]() { return !audio_playback_queue_.empty() || service_stopped_; }); - if (service_stopped_) { - break; - } - - auto task = std::move(audio_playback_queue_.front()); - audio_playback_queue_.pop_front(); - audio_queue_cv_.notify_all(); - lock.unlock(); - - if (!codec_->output_enabled()) { - esp_timer_stop(audio_power_timer_); - esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000); - codec_->EnableOutput(true); - } - codec_->OutputData(task->pcm); - - /* Update the last output time */ - last_output_time_ = std::chrono::steady_clock::now(); - debug_statistics_.playback_count++; - -#if CONFIG_USE_SERVER_AEC - /* Record the timestamp for server AEC */ - if (task->timestamp > 0) { - lock.lock(); - timestamp_queue_.push_back(task->timestamp); - } -#endif - } - - ESP_LOGW(TAG, "Audio output task stopped"); -} - -void AudioService::OpusCodecTask() { - while (true) { - std::unique_lock lock(audio_queue_mutex_); - audio_queue_cv_.wait(lock, [this]() { - return service_stopped_ || - (!audio_encode_queue_.empty() && audio_send_queue_.size() < MAX_SEND_PACKETS_IN_QUEUE) || - (!audio_decode_queue_.empty() && audio_playback_queue_.size() < MAX_PLAYBACK_TASKS_IN_QUEUE); - }); - if (service_stopped_) { - break; - } - - /* Decode the audio from decode queue */ - if (!audio_decode_queue_.empty() && audio_playback_queue_.size() < MAX_PLAYBACK_TASKS_IN_QUEUE) { - auto packet = std::move(audio_decode_queue_.front()); - audio_decode_queue_.pop_front(); - audio_queue_cv_.notify_all(); - lock.unlock(); - - auto task = std::make_unique(); - task->type = kAudioTaskTypeDecodeToPlaybackQueue; - task->timestamp = packet->timestamp; - - SetDecodeSampleRate(packet->sample_rate, packet->frame_duration); - if (opus_decoder_->Decode(std::move(packet->payload), task->pcm)) { - // Resample if the sample rate is different - if (opus_decoder_->sample_rate() != codec_->output_sample_rate()) { - int target_size = output_resampler_.GetOutputSamples(task->pcm.size()); - std::vector resampled(target_size); - output_resampler_.Process(task->pcm.data(), task->pcm.size(), resampled.data()); - task->pcm = std::move(resampled); - } - - lock.lock(); - audio_playback_queue_.push_back(std::move(task)); - audio_queue_cv_.notify_all(); - } else { - ESP_LOGE(TAG, "Failed to decode audio"); - lock.lock(); - } - debug_statistics_.decode_count++; - } - - /* Encode the audio to send queue */ - if (!audio_encode_queue_.empty() && audio_send_queue_.size() < MAX_SEND_PACKETS_IN_QUEUE) { - auto task = std::move(audio_encode_queue_.front()); - audio_encode_queue_.pop_front(); - audio_queue_cv_.notify_all(); - lock.unlock(); - - auto packet = std::make_unique(); - packet->frame_duration = OPUS_FRAME_DURATION_MS; - packet->sample_rate = 16000; - packet->timestamp = task->timestamp; - if (!opus_encoder_->Encode(std::move(task->pcm), packet->payload)) { - ESP_LOGE(TAG, "Failed to encode audio"); - continue; - } - - if (task->type == kAudioTaskTypeEncodeToSendQueue) { - { - std::lock_guard lock(audio_queue_mutex_); - audio_send_queue_.push_back(std::move(packet)); - } - if (callbacks_.on_send_queue_available) { - callbacks_.on_send_queue_available(); - } - } else if (task->type == kAudioTaskTypeEncodeToTestingQueue) { - std::lock_guard lock(audio_queue_mutex_); - audio_testing_queue_.push_back(std::move(packet)); - } - debug_statistics_.encode_count++; - lock.lock(); - } - } - - ESP_LOGW(TAG, "Opus codec task stopped"); -} - -void AudioService::SetDecodeSampleRate(int sample_rate, int frame_duration) { - if (opus_decoder_->sample_rate() == sample_rate && opus_decoder_->duration_ms() == frame_duration) { - return; - } - - opus_decoder_.reset(); - opus_decoder_ = std::make_unique(sample_rate, 1, frame_duration); - - auto codec = Board::GetInstance().GetAudioCodec(); - if (opus_decoder_->sample_rate() != codec->output_sample_rate()) { - ESP_LOGI(TAG, "Resampling audio from %d to %d", opus_decoder_->sample_rate(), codec->output_sample_rate()); - output_resampler_.Configure(opus_decoder_->sample_rate(), codec->output_sample_rate()); - } -} - -void AudioService::PushTaskToEncodeQueue(AudioTaskType type, std::vector&& pcm) { - auto task = std::make_unique(); - task->type = type; - task->pcm = std::move(pcm); - - /* Push the task to the encode queue */ - std::unique_lock lock(audio_queue_mutex_); - - /* If the task is to send queue, we need to set the timestamp */ - if (type == kAudioTaskTypeEncodeToSendQueue && !timestamp_queue_.empty()) { - if (timestamp_queue_.size() <= MAX_TIMESTAMPS_IN_QUEUE) { - task->timestamp = timestamp_queue_.front(); - } else { - ESP_LOGW(TAG, "Timestamp queue (%u) is full, dropping timestamp", timestamp_queue_.size()); - } - timestamp_queue_.pop_front(); - } - - audio_queue_cv_.wait(lock, [this]() { return audio_encode_queue_.size() < MAX_ENCODE_TASKS_IN_QUEUE; }); - audio_encode_queue_.push_back(std::move(task)); - audio_queue_cv_.notify_all(); -} - -bool AudioService::PushPacketToDecodeQueue(std::unique_ptr packet, bool wait) { - std::unique_lock lock(audio_queue_mutex_); - if (audio_decode_queue_.size() >= MAX_DECODE_PACKETS_IN_QUEUE) { - if (wait) { - audio_queue_cv_.wait(lock, [this]() { return audio_decode_queue_.size() < MAX_DECODE_PACKETS_IN_QUEUE; }); - } else { - return false; - } - } - audio_decode_queue_.push_back(std::move(packet)); - audio_queue_cv_.notify_all(); - return true; -} - -std::unique_ptr AudioService::PopPacketFromSendQueue() { - std::lock_guard lock(audio_queue_mutex_); - if (audio_send_queue_.empty()) { - return nullptr; - } - auto packet = std::move(audio_send_queue_.front()); - audio_send_queue_.pop_front(); - audio_queue_cv_.notify_all(); - return packet; -} - -void AudioService::EncodeWakeWord() { - if (wake_word_) { - wake_word_->EncodeWakeWordData(); - } -} - -const std::string& AudioService::GetLastWakeWord() const { - return wake_word_->GetLastDetectedWakeWord(); -} - -std::unique_ptr AudioService::PopWakeWordPacket() { - auto packet = std::make_unique(); - if (wake_word_->GetWakeWordOpus(packet->payload)) { - return packet; - } - return nullptr; -} - -void AudioService::EnableWakeWordDetection(bool enable) { - if (!wake_word_) { - return; - } - - ESP_LOGD(TAG, "%s wake word detection", enable ? "Enabling" : "Disabling"); - if (enable) { - if (!wake_word_initialized_) { - if (!wake_word_->Initialize(codec_, models_list_)) { - ESP_LOGE(TAG, "Failed to initialize wake word"); - return; - } - wake_word_initialized_ = true; - } - wake_word_->Start(); - xEventGroupSetBits(event_group_, AS_EVENT_WAKE_WORD_RUNNING); - } else { - wake_word_->Stop(); - xEventGroupClearBits(event_group_, AS_EVENT_WAKE_WORD_RUNNING); - } -} - -void AudioService::EnableVoiceProcessing(bool enable) { - ESP_LOGD(TAG, "%s voice processing", enable ? "Enabling" : "Disabling"); - if (enable) { - if (!audio_processor_initialized_) { - audio_processor_->Initialize(codec_, OPUS_FRAME_DURATION_MS, models_list_); - audio_processor_initialized_ = true; - } - - /* We should make sure no audio is playing */ - ResetDecoder(); - audio_input_need_warmup_ = true; - audio_processor_->Start(); - xEventGroupSetBits(event_group_, AS_EVENT_AUDIO_PROCESSOR_RUNNING); - } else { - audio_processor_->Stop(); - xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_PROCESSOR_RUNNING); - } -} - -void AudioService::EnableAudioTesting(bool enable) { - ESP_LOGI(TAG, "%s audio testing", enable ? "Enabling" : "Disabling"); - if (enable) { - xEventGroupSetBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING); - } else { - xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING); - /* Copy audio_testing_queue_ to audio_decode_queue_ */ - std::lock_guard lock(audio_queue_mutex_); - audio_decode_queue_ = std::move(audio_testing_queue_); - audio_queue_cv_.notify_all(); - } -} - -void AudioService::EnableDeviceAec(bool enable) { - ESP_LOGI(TAG, "%s device AEC", enable ? "Enabling" : "Disabling"); - if (!audio_processor_initialized_) { - audio_processor_->Initialize(codec_, OPUS_FRAME_DURATION_MS, models_list_); - audio_processor_initialized_ = true; - } - - audio_processor_->EnableDeviceAec(enable); -} - -void AudioService::SetCallbacks(AudioServiceCallbacks& callbacks) { - callbacks_ = callbacks; -} - -void AudioService::PlaySound(const std::string_view& ogg) { - if (!codec_->output_enabled()) { - esp_timer_stop(audio_power_timer_); - esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000); - codec_->EnableOutput(true); - } - - const uint8_t* buf = reinterpret_cast(ogg.data()); - size_t size = ogg.size(); - size_t offset = 0; - - auto find_page = [&](size_t start)->size_t { - for (size_t i = start; i + 4 <= size; ++i) { - if (buf[i] == 'O' && buf[i+1] == 'g' && buf[i+2] == 'g' && buf[i+3] == 'S') return i; - } - return static_cast(-1); - }; - - bool seen_head = false; - bool seen_tags = false; - int sample_rate = 16000; // 默认值 - - while (true) { - size_t pos = find_page(offset); - if (pos == static_cast(-1)) break; - offset = pos; - if (offset + 27 > size) break; - - const uint8_t* page = buf + offset; - uint8_t page_segments = page[26]; - size_t seg_table_off = offset + 27; - if (seg_table_off + page_segments > size) break; - - size_t body_size = 0; - for (size_t i = 0; i < page_segments; ++i) body_size += page[27 + i]; - - size_t body_off = seg_table_off + page_segments; - if (body_off + body_size > size) break; - - // Parse packets using lacing - size_t cur = body_off; - size_t seg_idx = 0; - while (seg_idx < page_segments) { - size_t pkt_len = 0; - size_t pkt_start = cur; - bool continued = false; - do { - uint8_t l = page[27 + seg_idx++]; - pkt_len += l; - cur += l; - continued = (l == 255); - } while (continued && seg_idx < page_segments); - - if (pkt_len == 0) continue; - const uint8_t* pkt_ptr = buf + pkt_start; - - if (!seen_head) { - // 解析OpusHead包 - if (pkt_len >= 19 && std::memcmp(pkt_ptr, "OpusHead", 8) == 0) { - seen_head = true; - - // OpusHead结构:[0-7] "OpusHead", [8] version, [9] channel_count, [10-11] pre_skip - // [12-15] input_sample_rate, [16-17] output_gain, [18] mapping_family - if (pkt_len >= 12) { - uint8_t version = pkt_ptr[8]; - uint8_t channel_count = pkt_ptr[9]; - - if (pkt_len >= 16) { - // 读取输入采样率 (little-endian) - sample_rate = pkt_ptr[12] | (pkt_ptr[13] << 8) | - (pkt_ptr[14] << 16) | (pkt_ptr[15] << 24); - ESP_LOGI(TAG, "OpusHead: version=%d, channels=%d, sample_rate=%d", - version, channel_count, sample_rate); - } - } - } - continue; - } - if (!seen_tags) { - // Expect OpusTags in second packet - if (pkt_len >= 8 && std::memcmp(pkt_ptr, "OpusTags", 8) == 0) { - seen_tags = true; - } - continue; - } - - // Audio packet (Opus) - auto packet = std::make_unique(); - packet->sample_rate = sample_rate; - packet->frame_duration = 60; - packet->payload.resize(pkt_len); - std::memcpy(packet->payload.data(), pkt_ptr, pkt_len); - PushPacketToDecodeQueue(std::move(packet), true); - } - - offset = body_off + body_size; - } -} - -bool AudioService::IsIdle() { - std::lock_guard lock(audio_queue_mutex_); - return audio_encode_queue_.empty() && audio_decode_queue_.empty() && audio_playback_queue_.empty() && audio_testing_queue_.empty(); -} - -void AudioService::ResetDecoder() { - std::lock_guard lock(audio_queue_mutex_); - opus_decoder_->ResetState(); - timestamp_queue_.clear(); - audio_decode_queue_.clear(); - audio_playback_queue_.clear(); - audio_testing_queue_.clear(); - audio_queue_cv_.notify_all(); -} - -void AudioService::CheckAndUpdateAudioPowerState() { - auto now = std::chrono::steady_clock::now(); - auto input_elapsed = std::chrono::duration_cast(now - last_input_time_).count(); - auto output_elapsed = std::chrono::duration_cast(now - last_output_time_).count(); - if (input_elapsed > AUDIO_POWER_TIMEOUT_MS && codec_->input_enabled()) { - codec_->EnableInput(false); - } - if (output_elapsed > AUDIO_POWER_TIMEOUT_MS && codec_->output_enabled()) { - codec_->EnableOutput(false); - } - if (!codec_->input_enabled() && !codec_->output_enabled()) { - esp_timer_stop(audio_power_timer_); - } -} - -void AudioService::SetModelsList(srmodel_list_t* models_list) { - models_list_ = models_list; -} - - -void AudioService::UpdateOutputTimestamp() { - last_output_time_ = std::chrono::steady_clock::now(); -} \ No newline at end of file +#include "audio_service.h" +#include +#include + +#if CONFIG_USE_AUDIO_PROCESSOR +#include "processors/afe_audio_processor.h" +#else +#include "processors/no_audio_processor.h" +#endif + +#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4 +#include "wake_words/afe_wake_word.h" +#include "wake_words/custom_wake_word.h" +#else +#include "wake_words/esp_wake_word.h" +#endif + +#define TAG "AudioService" + + +AudioService::AudioService() { + event_group_ = xEventGroupCreate(); +} + +AudioService::~AudioService() { + if (event_group_ != nullptr) { + vEventGroupDelete(event_group_); + } +} + + +void AudioService::Initialize(AudioCodec* codec) { + codec_ = codec; + codec_->Start(); + + /* Setup the audio codec */ + opus_decoder_ = std::make_unique(codec->output_sample_rate(), 1, OPUS_FRAME_DURATION_MS); + opus_encoder_ = std::make_unique(16000, 1, OPUS_FRAME_DURATION_MS); + opus_encoder_->SetComplexity(0); + + if (codec->input_sample_rate() != 16000) { + input_resampler_.Configure(codec->input_sample_rate(), 16000); + reference_resampler_.Configure(codec->input_sample_rate(), 16000); + } + +#if CONFIG_USE_AUDIO_PROCESSOR + audio_processor_ = std::make_unique(); +#else + audio_processor_ = std::make_unique(); +#endif + + audio_processor_->OnOutput([this](std::vector&& data) { + PushTaskToEncodeQueue(kAudioTaskTypeEncodeToSendQueue, std::move(data)); + }); + + audio_processor_->OnVadStateChange([this](bool speaking) { + voice_detected_ = speaking; + if (callbacks_.on_vad_change) { + callbacks_.on_vad_change(speaking); + } + }); + + esp_timer_create_args_t audio_power_timer_args = { + .callback = [](void* arg) { + AudioService* audio_service = (AudioService*)arg; + audio_service->CheckAndUpdateAudioPowerState(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "audio_power_timer", + .skip_unhandled_events = true, + }; + esp_timer_create(&audio_power_timer_args, &audio_power_timer_); +} + +void AudioService::Start() { + service_stopped_ = false; + xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING | AS_EVENT_WAKE_WORD_RUNNING | AS_EVENT_AUDIO_PROCESSOR_RUNNING); + + esp_timer_start_periodic(audio_power_timer_, 1000000); + +#if CONFIG_USE_AUDIO_PROCESSOR + /* Start the audio input task */ + xTaskCreatePinnedToCore([](void* arg) { + AudioService* audio_service = (AudioService*)arg; + audio_service->AudioInputTask(); + vTaskDelete(NULL); + }, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_, 0); + + /* Start the audio output task */ + xTaskCreate([](void* arg) { + AudioService* audio_service = (AudioService*)arg; + audio_service->AudioOutputTask(); + vTaskDelete(NULL); + }, "audio_output", 2048 * 2, this, 4, &audio_output_task_handle_); +#else + /* Start the audio input task */ + xTaskCreate([](void* arg) { + AudioService* audio_service = (AudioService*)arg; + audio_service->AudioInputTask(); + vTaskDelete(NULL); + }, "audio_input", 2048 * 2, this, 8, &audio_input_task_handle_); + + /* Start the audio output task */ + xTaskCreate([](void* arg) { + AudioService* audio_service = (AudioService*)arg; + audio_service->AudioOutputTask(); + vTaskDelete(NULL); + }, "audio_output", 2048, this, 4, &audio_output_task_handle_); +#endif + + /* Start the opus codec task */ + xTaskCreate([](void* arg) { + AudioService* audio_service = (AudioService*)arg; + audio_service->OpusCodecTask(); + vTaskDelete(NULL); + }, "opus_codec", 2048 * 13, this, 2, &opus_codec_task_handle_); +} + +void AudioService::Stop() { + esp_timer_stop(audio_power_timer_); + service_stopped_ = true; + xEventGroupSetBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING | + AS_EVENT_WAKE_WORD_RUNNING | + AS_EVENT_AUDIO_PROCESSOR_RUNNING); + + std::lock_guard lock(audio_queue_mutex_); + audio_encode_queue_.clear(); + audio_decode_queue_.clear(); + audio_playback_queue_.clear(); + audio_testing_queue_.clear(); + audio_queue_cv_.notify_all(); +} + +bool AudioService::ReadAudioData(std::vector& data, int sample_rate, int samples) { + if (!codec_->input_enabled()) { + esp_timer_stop(audio_power_timer_); + esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000); + codec_->EnableInput(true); + } + + if (codec_->input_sample_rate() != sample_rate) { + data.resize(samples * codec_->input_sample_rate() / sample_rate * codec_->input_channels()); + if (!codec_->InputData(data)) { + return false; + } + if (codec_->input_channels() == 2) { + auto mic_channel = std::vector(data.size() / 2); + auto reference_channel = std::vector(data.size() / 2); + for (size_t i = 0, j = 0; i < mic_channel.size(); ++i, j += 2) { + mic_channel[i] = data[j]; + reference_channel[i] = data[j + 1]; + } + auto resampled_mic = std::vector(input_resampler_.GetOutputSamples(mic_channel.size())); + auto resampled_reference = std::vector(reference_resampler_.GetOutputSamples(reference_channel.size())); + input_resampler_.Process(mic_channel.data(), mic_channel.size(), resampled_mic.data()); + reference_resampler_.Process(reference_channel.data(), reference_channel.size(), resampled_reference.data()); + data.resize(resampled_mic.size() + resampled_reference.size()); + for (size_t i = 0, j = 0; i < resampled_mic.size(); ++i, j += 2) { + data[j] = resampled_mic[i]; + data[j + 1] = resampled_reference[i]; + } + } else { + auto resampled = std::vector(input_resampler_.GetOutputSamples(data.size())); + input_resampler_.Process(data.data(), data.size(), resampled.data()); + data = std::move(resampled); + } + } else { + data.resize(samples * codec_->input_channels()); + if (!codec_->InputData(data)) { + return false; + } + } + + /* Update the last input time */ + last_input_time_ = std::chrono::steady_clock::now(); + debug_statistics_.input_count++; + +#if CONFIG_USE_AUDIO_DEBUGGER + // 音频调试:发送原始音频数据 + if (audio_debugger_ == nullptr) { + audio_debugger_ = std::make_unique(); + } + audio_debugger_->Feed(data); +#endif + + return true; +} + +void AudioService::AudioInputTask() { + while (true) { + EventBits_t bits = xEventGroupWaitBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING | + AS_EVENT_WAKE_WORD_RUNNING | AS_EVENT_AUDIO_PROCESSOR_RUNNING, + pdFALSE, pdFALSE, portMAX_DELAY); + + if (service_stopped_) { + break; + } + if (audio_input_need_warmup_) { + audio_input_need_warmup_ = false; + vTaskDelay(pdMS_TO_TICKS(120)); + continue; + } + + /* Used for audio testing in NetworkConfiguring mode by clicking the BOOT button */ + if (bits & AS_EVENT_AUDIO_TESTING_RUNNING) { + if (audio_testing_queue_.size() >= AUDIO_TESTING_MAX_DURATION_MS / OPUS_FRAME_DURATION_MS) { + ESP_LOGW(TAG, "Audio testing queue is full, stopping audio testing"); + EnableAudioTesting(false); + continue; + } + std::vector data; + int samples = OPUS_FRAME_DURATION_MS * 16000 / 1000; + if (ReadAudioData(data, 16000, samples)) { + // If input channels is 2, we need to fetch the left channel data + if (codec_->input_channels() == 2) { + auto mono_data = std::vector(data.size() / 2); + for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) { + mono_data[i] = data[j]; + } + data = std::move(mono_data); + } + PushTaskToEncodeQueue(kAudioTaskTypeEncodeToTestingQueue, std::move(data)); + continue; + } + } + + /* Feed the wake word */ + if (bits & AS_EVENT_WAKE_WORD_RUNNING) { + std::vector data; + int samples = wake_word_->GetFeedSize(); + if (samples > 0) { + if (ReadAudioData(data, 16000, samples)) { + wake_word_->Feed(data); + continue; + } + } + } + + /* Feed the audio processor */ + if (bits & AS_EVENT_AUDIO_PROCESSOR_RUNNING) { + std::vector data; + int samples = audio_processor_->GetFeedSize(); + if (samples > 0) { + if (ReadAudioData(data, 16000, samples)) { + audio_processor_->Feed(std::move(data)); + continue; + } + } + } + + ESP_LOGE(TAG, "Should not be here, bits: %lx", bits); + break; + } + + ESP_LOGW(TAG, "Audio input task stopped"); +} + +void AudioService::AudioOutputTask() { + while (true) { + std::unique_lock lock(audio_queue_mutex_); + audio_queue_cv_.wait(lock, [this]() { return !audio_playback_queue_.empty() || service_stopped_; }); + if (service_stopped_) { + break; + } + + auto task = std::move(audio_playback_queue_.front()); + audio_playback_queue_.pop_front(); + audio_queue_cv_.notify_all(); + lock.unlock(); + + if (!codec_->output_enabled()) { + esp_timer_stop(audio_power_timer_); + esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000); + codec_->EnableOutput(true); + } + codec_->OutputData(task->pcm); + + /* Update the last output time */ + last_output_time_ = std::chrono::steady_clock::now(); + debug_statistics_.playback_count++; + +#if CONFIG_USE_SERVER_AEC + /* Record the timestamp for server AEC */ + if (task->timestamp > 0) { + lock.lock(); + timestamp_queue_.push_back(task->timestamp); + } +#endif + } + + ESP_LOGW(TAG, "Audio output task stopped"); +} + +void AudioService::OpusCodecTask() { + while (true) { + std::unique_lock lock(audio_queue_mutex_); + audio_queue_cv_.wait(lock, [this]() { + return service_stopped_ || + (!audio_encode_queue_.empty() && audio_send_queue_.size() < MAX_SEND_PACKETS_IN_QUEUE) || + (!audio_decode_queue_.empty() && audio_playback_queue_.size() < MAX_PLAYBACK_TASKS_IN_QUEUE); + }); + if (service_stopped_) { + break; + } + + /* Decode the audio from decode queue */ + if (!audio_decode_queue_.empty() && audio_playback_queue_.size() < MAX_PLAYBACK_TASKS_IN_QUEUE) { + auto packet = std::move(audio_decode_queue_.front()); + audio_decode_queue_.pop_front(); + audio_queue_cv_.notify_all(); + lock.unlock(); + + auto task = std::make_unique(); + task->type = kAudioTaskTypeDecodeToPlaybackQueue; + task->timestamp = packet->timestamp; + + SetDecodeSampleRate(packet->sample_rate, packet->frame_duration); + if (opus_decoder_->Decode(std::move(packet->payload), task->pcm)) { + // Resample if the sample rate is different + if (opus_decoder_->sample_rate() != codec_->output_sample_rate()) { + int target_size = output_resampler_.GetOutputSamples(task->pcm.size()); + std::vector resampled(target_size); + output_resampler_.Process(task->pcm.data(), task->pcm.size(), resampled.data()); + task->pcm = std::move(resampled); + } + + lock.lock(); + audio_playback_queue_.push_back(std::move(task)); + audio_queue_cv_.notify_all(); + } else { + ESP_LOGE(TAG, "Failed to decode audio"); + lock.lock(); + } + debug_statistics_.decode_count++; + } + + /* Encode the audio to send queue */ + if (!audio_encode_queue_.empty() && audio_send_queue_.size() < MAX_SEND_PACKETS_IN_QUEUE) { + auto task = std::move(audio_encode_queue_.front()); + audio_encode_queue_.pop_front(); + audio_queue_cv_.notify_all(); + lock.unlock(); + + auto packet = std::make_unique(); + packet->frame_duration = OPUS_FRAME_DURATION_MS; + packet->sample_rate = 16000; + packet->timestamp = task->timestamp; + if (!opus_encoder_->Encode(std::move(task->pcm), packet->payload)) { + ESP_LOGE(TAG, "Failed to encode audio"); + continue; + } + + if (task->type == kAudioTaskTypeEncodeToSendQueue) { + { + std::lock_guard lock(audio_queue_mutex_); + audio_send_queue_.push_back(std::move(packet)); + } + if (callbacks_.on_send_queue_available) { + callbacks_.on_send_queue_available(); + } + } else if (task->type == kAudioTaskTypeEncodeToTestingQueue) { + std::lock_guard lock(audio_queue_mutex_); + audio_testing_queue_.push_back(std::move(packet)); + } + debug_statistics_.encode_count++; + lock.lock(); + } + } + + ESP_LOGW(TAG, "Opus codec task stopped"); +} + +void AudioService::SetDecodeSampleRate(int sample_rate, int frame_duration) { + if (opus_decoder_->sample_rate() == sample_rate && opus_decoder_->duration_ms() == frame_duration) { + return; + } + + opus_decoder_.reset(); + opus_decoder_ = std::make_unique(sample_rate, 1, frame_duration); + + auto codec = Board::GetInstance().GetAudioCodec(); + if (opus_decoder_->sample_rate() != codec->output_sample_rate()) { + ESP_LOGI(TAG, "Resampling audio from %d to %d", opus_decoder_->sample_rate(), codec->output_sample_rate()); + output_resampler_.Configure(opus_decoder_->sample_rate(), codec->output_sample_rate()); + } +} + +void AudioService::PushTaskToEncodeQueue(AudioTaskType type, std::vector&& pcm) { + auto task = std::make_unique(); + task->type = type; + task->pcm = std::move(pcm); + + /* Push the task to the encode queue */ + std::unique_lock lock(audio_queue_mutex_); + + /* If the task is to send queue, we need to set the timestamp */ + if (type == kAudioTaskTypeEncodeToSendQueue && !timestamp_queue_.empty()) { + if (timestamp_queue_.size() <= MAX_TIMESTAMPS_IN_QUEUE) { + task->timestamp = timestamp_queue_.front(); + } else { + ESP_LOGW(TAG, "Timestamp queue (%u) is full, dropping timestamp", timestamp_queue_.size()); + } + timestamp_queue_.pop_front(); + } + + audio_queue_cv_.wait(lock, [this]() { return audio_encode_queue_.size() < MAX_ENCODE_TASKS_IN_QUEUE; }); + audio_encode_queue_.push_back(std::move(task)); + audio_queue_cv_.notify_all(); +} + +bool AudioService::PushPacketToDecodeQueue(std::unique_ptr packet, bool wait) { + std::unique_lock lock(audio_queue_mutex_); + if (audio_decode_queue_.size() >= MAX_DECODE_PACKETS_IN_QUEUE) { + if (wait) { + audio_queue_cv_.wait(lock, [this]() { return audio_decode_queue_.size() < MAX_DECODE_PACKETS_IN_QUEUE; }); + } else { + return false; + } + } + audio_decode_queue_.push_back(std::move(packet)); + audio_queue_cv_.notify_all(); + return true; +} + +std::unique_ptr AudioService::PopPacketFromSendQueue() { + std::lock_guard lock(audio_queue_mutex_); + if (audio_send_queue_.empty()) { + return nullptr; + } + auto packet = std::move(audio_send_queue_.front()); + audio_send_queue_.pop_front(); + audio_queue_cv_.notify_all(); + return packet; +} + +void AudioService::EncodeWakeWord() { + if (wake_word_) { + wake_word_->EncodeWakeWordData(); + } +} + +const std::string& AudioService::GetLastWakeWord() const { + return wake_word_->GetLastDetectedWakeWord(); +} + +std::unique_ptr AudioService::PopWakeWordPacket() { + auto packet = std::make_unique(); + if (wake_word_->GetWakeWordOpus(packet->payload)) { + return packet; + } + return nullptr; +} + +void AudioService::EnableWakeWordDetection(bool enable) { + if (!wake_word_) { + return; + } + + ESP_LOGD(TAG, "%s wake word detection", enable ? "Enabling" : "Disabling"); + if (enable) { + if (!wake_word_initialized_) { + if (!wake_word_->Initialize(codec_, models_list_)) { + ESP_LOGE(TAG, "Failed to initialize wake word"); + return; + } + wake_word_initialized_ = true; + } + wake_word_->Start(); + xEventGroupSetBits(event_group_, AS_EVENT_WAKE_WORD_RUNNING); + } else { + wake_word_->Stop(); + xEventGroupClearBits(event_group_, AS_EVENT_WAKE_WORD_RUNNING); + } +} + +void AudioService::EnableVoiceProcessing(bool enable) { + ESP_LOGD(TAG, "%s voice processing", enable ? "Enabling" : "Disabling"); + if (enable) { + if (!audio_processor_initialized_) { + audio_processor_->Initialize(codec_, OPUS_FRAME_DURATION_MS, models_list_); + audio_processor_initialized_ = true; + } + + /* We should make sure no audio is playing */ + ResetDecoder(); + audio_input_need_warmup_ = true; + audio_processor_->Start(); + xEventGroupSetBits(event_group_, AS_EVENT_AUDIO_PROCESSOR_RUNNING); + } else { + audio_processor_->Stop(); + xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_PROCESSOR_RUNNING); + } +} + +void AudioService::EnableAudioTesting(bool enable) { + ESP_LOGI(TAG, "%s audio testing", enable ? "Enabling" : "Disabling"); + if (enable) { + xEventGroupSetBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING); + } else { + xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING); + /* Copy audio_testing_queue_ to audio_decode_queue_ */ + std::lock_guard lock(audio_queue_mutex_); + audio_decode_queue_ = std::move(audio_testing_queue_); + audio_queue_cv_.notify_all(); + } +} + +void AudioService::EnableDeviceAec(bool enable) { + ESP_LOGI(TAG, "%s device AEC", enable ? "Enabling" : "Disabling"); + if (!audio_processor_initialized_) { + audio_processor_->Initialize(codec_, OPUS_FRAME_DURATION_MS, models_list_); + audio_processor_initialized_ = true; + } + + audio_processor_->EnableDeviceAec(enable); +} + +void AudioService::SetCallbacks(AudioServiceCallbacks& callbacks) { + callbacks_ = callbacks; +} + +void AudioService::PlaySound(const std::string_view& ogg) { + if (!codec_->output_enabled()) { + esp_timer_stop(audio_power_timer_); + esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000); + codec_->EnableOutput(true); + } + + const uint8_t* buf = reinterpret_cast(ogg.data()); + size_t size = ogg.size(); + size_t offset = 0; + + auto find_page = [&](size_t start)->size_t { + for (size_t i = start; i + 4 <= size; ++i) { + if (buf[i] == 'O' && buf[i+1] == 'g' && buf[i+2] == 'g' && buf[i+3] == 'S') return i; + } + return static_cast(-1); + }; + + bool seen_head = false; + bool seen_tags = false; + int sample_rate = 16000; // 默认值 + + while (true) { + size_t pos = find_page(offset); + if (pos == static_cast(-1)) break; + offset = pos; + if (offset + 27 > size) break; + + const uint8_t* page = buf + offset; + uint8_t page_segments = page[26]; + size_t seg_table_off = offset + 27; + if (seg_table_off + page_segments > size) break; + + size_t body_size = 0; + for (size_t i = 0; i < page_segments; ++i) body_size += page[27 + i]; + + size_t body_off = seg_table_off + page_segments; + if (body_off + body_size > size) break; + + // Parse packets using lacing + size_t cur = body_off; + size_t seg_idx = 0; + while (seg_idx < page_segments) { + size_t pkt_len = 0; + size_t pkt_start = cur; + bool continued = false; + do { + uint8_t l = page[27 + seg_idx++]; + pkt_len += l; + cur += l; + continued = (l == 255); + } while (continued && seg_idx < page_segments); + + if (pkt_len == 0) continue; + const uint8_t* pkt_ptr = buf + pkt_start; + + if (!seen_head) { + // 解析OpusHead包 + if (pkt_len >= 19 && std::memcmp(pkt_ptr, "OpusHead", 8) == 0) { + seen_head = true; + + // OpusHead结构:[0-7] "OpusHead", [8] version, [9] channel_count, [10-11] pre_skip + // [12-15] input_sample_rate, [16-17] output_gain, [18] mapping_family + if (pkt_len >= 12) { + uint8_t version = pkt_ptr[8]; + uint8_t channel_count = pkt_ptr[9]; + + if (pkt_len >= 16) { + // 读取输入采样率 (little-endian) + sample_rate = pkt_ptr[12] | (pkt_ptr[13] << 8) | + (pkt_ptr[14] << 16) | (pkt_ptr[15] << 24); + ESP_LOGI(TAG, "OpusHead: version=%d, channels=%d, sample_rate=%d", + version, channel_count, sample_rate); + } + } + } + continue; + } + if (!seen_tags) { + // Expect OpusTags in second packet + if (pkt_len >= 8 && std::memcmp(pkt_ptr, "OpusTags", 8) == 0) { + seen_tags = true; + } + continue; + } + + // Audio packet (Opus) + auto packet = std::make_unique(); + packet->sample_rate = sample_rate; + packet->frame_duration = 60; + packet->payload.resize(pkt_len); + std::memcpy(packet->payload.data(), pkt_ptr, pkt_len); + PushPacketToDecodeQueue(std::move(packet), true); + } + + offset = body_off + body_size; + } +} + +bool AudioService::IsIdle() { + std::lock_guard lock(audio_queue_mutex_); + return audio_encode_queue_.empty() && audio_decode_queue_.empty() && audio_playback_queue_.empty() && audio_testing_queue_.empty(); +} + +void AudioService::ResetDecoder() { + std::lock_guard lock(audio_queue_mutex_); + opus_decoder_->ResetState(); + timestamp_queue_.clear(); + audio_decode_queue_.clear(); + audio_playback_queue_.clear(); + audio_testing_queue_.clear(); + audio_queue_cv_.notify_all(); +} + +void AudioService::CheckAndUpdateAudioPowerState() { + auto now = std::chrono::steady_clock::now(); + auto input_elapsed = std::chrono::duration_cast(now - last_input_time_).count(); + auto output_elapsed = std::chrono::duration_cast(now - last_output_time_).count(); + if (input_elapsed > AUDIO_POWER_TIMEOUT_MS && codec_->input_enabled()) { + codec_->EnableInput(false); + } + if (output_elapsed > AUDIO_POWER_TIMEOUT_MS && codec_->output_enabled()) { + codec_->EnableOutput(false); + } + if (!codec_->input_enabled() && !codec_->output_enabled()) { + esp_timer_stop(audio_power_timer_); + } +} + +void AudioService::SetModelsList(srmodel_list_t* models_list) { + models_list_ = models_list; + +#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4 + if (esp_srmodel_filter(models_list_, ESP_MN_PREFIX, NULL) != nullptr) { + wake_word_ = std::make_unique(); + } else if (esp_srmodel_filter(models_list_, ESP_WN_PREFIX, NULL) != nullptr) { + wake_word_ = std::make_unique(); + } else { + wake_word_ = nullptr; + } +#else + if (esp_srmodel_filter(models_list_, ESP_WN_PREFIX, NULL) != nullptr) { + wake_word_ = std::make_unique(); + } else { + wake_word_ = nullptr; + } +#endif + + if (wake_word_) { + wake_word_->OnWakeWordDetected([this](const std::string& wake_word) { + if (callbacks_.on_wake_word_detected) { + callbacks_.on_wake_word_detected(wake_word); + } + }); + } +} + +bool AudioService::IsAfeWakeWord() { +#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4 + return wake_word_ != nullptr && dynamic_cast(wake_word_.get()) != nullptr; +#else + return false; +#endif +} + +void AudioService::UpdateOutputTimestamp() { + last_output_time_ = std::chrono::steady_clock::now(); +} diff --git a/main/audio/audio_service.h b/main/audio/audio_service.h index 41f60e7..592d615 100644 --- a/main/audio/audio_service.h +++ b/main/audio/audio_service.h @@ -1,160 +1,162 @@ -#ifndef AUDIO_SERVICE_H -#define AUDIO_SERVICE_H - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "audio_codec.h" -#include "audio_processor.h" -#include "processors/audio_debugger.h" -#include "wake_word.h" -#include "protocol.h" - - -/* - * There are two types of audio data flow: - * 1. (MIC) -> [Processors] -> {Encode Queue} -> [Opus Encoder] -> {Send Queue} -> (Server) - * 2. (Server) -> {Decode Queue} -> [Opus Decoder] -> {Playback Queue} -> (Speaker) - * - * We use one task for MIC / Speaker / Processors, and one task for Opus Encoder / Opus Decoder. - * - * Decode Queue and Send Queue are the main queues, because Opus packets are quite smaller than PCM packets. - * - */ - -#define OPUS_FRAME_DURATION_MS 60 -#define MAX_ENCODE_TASKS_IN_QUEUE 2 -#define MAX_PLAYBACK_TASKS_IN_QUEUE 2 -#define MAX_DECODE_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS) -#define MAX_SEND_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS) -#define AUDIO_TESTING_MAX_DURATION_MS 10000 -#define MAX_TIMESTAMPS_IN_QUEUE 3 - -#define AUDIO_POWER_TIMEOUT_MS 15000 -#define AUDIO_POWER_CHECK_INTERVAL_MS 1000 - - -#define AS_EVENT_AUDIO_TESTING_RUNNING (1 << 0) -#define AS_EVENT_WAKE_WORD_RUNNING (1 << 1) -#define AS_EVENT_AUDIO_PROCESSOR_RUNNING (1 << 2) -#define AS_EVENT_PLAYBACK_NOT_EMPTY (1 << 3) - -struct AudioServiceCallbacks { - std::function on_send_queue_available; - std::function on_wake_word_detected; - std::function on_vad_change; - std::function on_audio_testing_queue_full; -}; - - -enum AudioTaskType { - kAudioTaskTypeEncodeToSendQueue, - kAudioTaskTypeEncodeToTestingQueue, - kAudioTaskTypeDecodeToPlaybackQueue, -}; - -struct AudioTask { - AudioTaskType type; - std::vector pcm; - uint32_t timestamp; -}; - -struct DebugStatistics { - uint32_t input_count = 0; - uint32_t decode_count = 0; - uint32_t encode_count = 0; - uint32_t playback_count = 0; -}; - -class AudioService { -public: - AudioService(); - ~AudioService(); - - void Initialize(AudioCodec* codec); - void Start(); - void Stop(); - void EncodeWakeWord(); - std::unique_ptr PopWakeWordPacket(); - const std::string& GetLastWakeWord() const; - bool IsVoiceDetected() const { return voice_detected_; } - bool IsIdle(); - bool IsWakeWordRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_WAKE_WORD_RUNNING; } - bool IsAudioProcessorRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_AUDIO_PROCESSOR_RUNNING; } - - void EnableWakeWordDetection(bool enable); - void EnableVoiceProcessing(bool enable); - void EnableAudioTesting(bool enable); - void EnableDeviceAec(bool enable); - - void SetCallbacks(AudioServiceCallbacks& callbacks); - - bool PushPacketToDecodeQueue(std::unique_ptr packet, bool wait = false); - std::unique_ptr PopPacketFromSendQueue(); - void PlaySound(const std::string_view& sound); - bool ReadAudioData(std::vector& data, int sample_rate, int samples); - void ResetDecoder(); - void SetModelsList(srmodel_list_t* models_list); - void UpdateOutputTimestamp(); -private: - AudioCodec* codec_ = nullptr; - AudioServiceCallbacks callbacks_; - std::unique_ptr audio_processor_; - std::unique_ptr wake_word_; - std::unique_ptr audio_debugger_; - std::unique_ptr opus_encoder_; - std::unique_ptr opus_decoder_; - OpusResampler input_resampler_; - OpusResampler reference_resampler_; - OpusResampler output_resampler_; - DebugStatistics debug_statistics_; - srmodel_list_t* models_list_ = nullptr; - - EventGroupHandle_t event_group_; - - // Audio encode / decode - TaskHandle_t audio_input_task_handle_ = nullptr; - TaskHandle_t audio_output_task_handle_ = nullptr; - TaskHandle_t opus_codec_task_handle_ = nullptr; - std::mutex audio_queue_mutex_; - std::condition_variable audio_queue_cv_; - std::deque> audio_decode_queue_; - std::deque> audio_send_queue_; - std::deque> audio_testing_queue_; - std::deque> audio_encode_queue_; - std::deque> audio_playback_queue_; - // For server AEC - std::deque timestamp_queue_; - - bool wake_word_initialized_ = false; - bool audio_processor_initialized_ = false; - bool voice_detected_ = false; - bool service_stopped_ = true; - bool audio_input_need_warmup_ = false; - - esp_timer_handle_t audio_power_timer_ = nullptr; - std::chrono::steady_clock::time_point last_input_time_; - std::chrono::steady_clock::time_point last_output_time_; - - void AudioInputTask(); - void AudioOutputTask(); - void OpusCodecTask(); - void PushTaskToEncodeQueue(AudioTaskType type, std::vector&& pcm); - void SetDecodeSampleRate(int sample_rate, int frame_duration); - void CheckAndUpdateAudioPowerState(); -}; - +#ifndef AUDIO_SERVICE_H +#define AUDIO_SERVICE_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "audio_codec.h" +#include "audio_processor.h" +#include "processors/audio_debugger.h" +#include "wake_word.h" +#include "protocol.h" + + +/* + * There are two types of audio data flow: + * 1. (MIC) -> [Processors] -> {Encode Queue} -> [Opus Encoder] -> {Send Queue} -> (Server) + * 2. (Server) -> {Decode Queue} -> [Opus Decoder] -> {Playback Queue} -> (Speaker) + * + * We use one task for MIC / Speaker / Processors, and one task for Opus Encoder / Opus Decoder. + * + * Decode Queue and Send Queue are the main queues, because Opus packets are quite smaller than PCM packets. + * + */ + +#define OPUS_FRAME_DURATION_MS 60 +#define MAX_ENCODE_TASKS_IN_QUEUE 2 +#define MAX_PLAYBACK_TASKS_IN_QUEUE 2 +#define MAX_DECODE_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS) +#define MAX_SEND_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS) +#define AUDIO_TESTING_MAX_DURATION_MS 10000 +#define MAX_TIMESTAMPS_IN_QUEUE 3 + +#define AUDIO_POWER_TIMEOUT_MS 15000 +#define AUDIO_POWER_CHECK_INTERVAL_MS 1000 + + +#define AS_EVENT_AUDIO_TESTING_RUNNING (1 << 0) +#define AS_EVENT_WAKE_WORD_RUNNING (1 << 1) +#define AS_EVENT_AUDIO_PROCESSOR_RUNNING (1 << 2) +#define AS_EVENT_PLAYBACK_NOT_EMPTY (1 << 3) + +struct AudioServiceCallbacks { + std::function on_send_queue_available; + std::function on_wake_word_detected; + std::function on_vad_change; + std::function on_audio_testing_queue_full; +}; + + +enum AudioTaskType { + kAudioTaskTypeEncodeToSendQueue, + kAudioTaskTypeEncodeToTestingQueue, + kAudioTaskTypeDecodeToPlaybackQueue, +}; + +struct AudioTask { + AudioTaskType type; + std::vector pcm; + uint32_t timestamp; +}; + +struct DebugStatistics { + uint32_t input_count = 0; + uint32_t decode_count = 0; + uint32_t encode_count = 0; + uint32_t playback_count = 0; +}; + +class AudioService { +public: + AudioService(); + ~AudioService(); + + void Initialize(AudioCodec* codec); + void Start(); + void Stop(); + void EncodeWakeWord(); + std::unique_ptr PopWakeWordPacket(); + const std::string& GetLastWakeWord() const; + bool IsVoiceDetected() const { return voice_detected_; } + bool IsIdle(); + bool IsWakeWordRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_WAKE_WORD_RUNNING; } + bool IsAudioProcessorRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_AUDIO_PROCESSOR_RUNNING; } + bool IsAfeWakeWord(); + + void EnableWakeWordDetection(bool enable); + void EnableVoiceProcessing(bool enable); + void EnableAudioTesting(bool enable); + void EnableDeviceAec(bool enable); + + void SetCallbacks(AudioServiceCallbacks& callbacks); + + bool PushPacketToDecodeQueue(std::unique_ptr packet, bool wait = false); + std::unique_ptr PopPacketFromSendQueue(); + void PlaySound(const std::string_view& sound); + bool ReadAudioData(std::vector& data, int sample_rate, int samples); + void ResetDecoder(); + void SetModelsList(srmodel_list_t* models_list); + void UpdateOutputTimestamp(); + +private: + AudioCodec* codec_ = nullptr; + AudioServiceCallbacks callbacks_; + std::unique_ptr audio_processor_; + std::unique_ptr wake_word_; + std::unique_ptr audio_debugger_; + std::unique_ptr opus_encoder_; + std::unique_ptr opus_decoder_; + OpusResampler input_resampler_; + OpusResampler reference_resampler_; + OpusResampler output_resampler_; + DebugStatistics debug_statistics_; + srmodel_list_t* models_list_ = nullptr; + + EventGroupHandle_t event_group_; + + // Audio encode / decode + TaskHandle_t audio_input_task_handle_ = nullptr; + TaskHandle_t audio_output_task_handle_ = nullptr; + TaskHandle_t opus_codec_task_handle_ = nullptr; + std::mutex audio_queue_mutex_; + std::condition_variable audio_queue_cv_; + std::deque> audio_decode_queue_; + std::deque> audio_send_queue_; + std::deque> audio_testing_queue_; + std::deque> audio_encode_queue_; + std::deque> audio_playback_queue_; + // For server AEC + std::deque timestamp_queue_; + + bool wake_word_initialized_ = false; + bool audio_processor_initialized_ = false; + bool voice_detected_ = false; + bool service_stopped_ = true; + bool audio_input_need_warmup_ = false; + + esp_timer_handle_t audio_power_timer_ = nullptr; + std::chrono::steady_clock::time_point last_input_time_; + std::chrono::steady_clock::time_point last_output_time_; + + void AudioInputTask(); + void AudioOutputTask(); + void OpusCodecTask(); + void PushTaskToEncodeQueue(AudioTaskType type, std::vector&& pcm); + void SetDecodeSampleRate(int sample_rate, int frame_duration); + void CheckAndUpdateAudioPowerState(); +}; + #endif \ No newline at end of file diff --git a/main/audio/codecs/box_audio_codec.cc b/main/audio/codecs/box_audio_codec.cc index a4a3eee..a49dfc7 100644 --- a/main/audio/codecs/box_audio_codec.cc +++ b/main/audio/codecs/box_audio_codec.cc @@ -1,244 +1,244 @@ -#include "box_audio_codec.h" - -#include -#include -#include - -#define TAG "BoxAudioCodec" - -BoxAudioCodec::BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference) { - duplex_ = true; // 是否双工 - input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 - input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - CreateDuplexChannels(mclk, bclk, ws, dout, din); - - // Do initialize of related interface: data_if, ctrl_if and gpio_if - audio_codec_i2s_cfg_t i2s_cfg = { - .port = I2S_NUM_0, - .rx_handle = rx_handle_, - .tx_handle = tx_handle_, - }; - data_if_ = audio_codec_new_i2s_data(&i2s_cfg); - assert(data_if_ != NULL); - - // Output - audio_codec_i2c_cfg_t i2c_cfg = { - .port = (i2c_port_t)1, - .addr = es8311_addr, - .bus_handle = i2c_master_handle, - }; - out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(out_ctrl_if_ != NULL); - - gpio_if_ = audio_codec_new_gpio(); - assert(gpio_if_ != NULL); - - es8311_codec_cfg_t es8311_cfg = {}; - es8311_cfg.ctrl_if = out_ctrl_if_; - es8311_cfg.gpio_if = gpio_if_; - es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC; - es8311_cfg.pa_pin = pa_pin; - es8311_cfg.use_mclk = true; - es8311_cfg.hw_gain.pa_voltage = 5.0; - es8311_cfg.hw_gain.codec_dac_voltage = 3.3; - out_codec_if_ = es8311_codec_new(&es8311_cfg); - assert(out_codec_if_ != NULL); - - esp_codec_dev_cfg_t dev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_OUT, - .codec_if = out_codec_if_, - .data_if = data_if_, - }; - output_dev_ = esp_codec_dev_new(&dev_cfg); - assert(output_dev_ != NULL); - - // Input - i2c_cfg.addr = es7210_addr; - in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(in_ctrl_if_ != NULL); - - es7210_codec_cfg_t es7210_cfg = {}; - es7210_cfg.ctrl_if = in_ctrl_if_; - es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4; - in_codec_if_ = es7210_codec_new(&es7210_cfg); - assert(in_codec_if_ != NULL); - - dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; - dev_cfg.codec_if = in_codec_if_; - input_dev_ = esp_codec_dev_new(&dev_cfg); - assert(input_dev_ != NULL); - - ESP_LOGI(TAG, "BoxAudioDevice initialized"); -} - -BoxAudioCodec::~BoxAudioCodec() { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - esp_codec_dev_delete(output_dev_); - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - esp_codec_dev_delete(input_dev_); - - audio_codec_delete_codec_if(in_codec_if_); - audio_codec_delete_ctrl_if(in_ctrl_if_); - audio_codec_delete_codec_if(out_codec_if_); - audio_codec_delete_ctrl_if(out_ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void BoxAudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { - assert(input_sample_rate_ == output_sample_rate_); - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, - .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256 - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_16BIT, - .ws_pol = false, - .bit_shift = true, - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - i2s_tdm_config_t tdm_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)input_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - .bclk_div = 8, - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), - .ws_width = I2S_TDM_AUTO_WS_WIDTH, - .ws_pol = false, - .bit_shift = true, - .left_align = false, - .big_endian = false, - .bit_order_lsb = false, - .skip_mask = false, - .total_slot = I2S_TDM_AUTO_SLOT_NUM - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = I2S_GPIO_UNUSED, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle_, &tdm_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); -} - -void BoxAudioCodec::SetOutputVolume(int volume) { - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); - AudioCodec::SetOutputVolume(volume); -} - -void BoxAudioCodec::EnableInput(bool enable) { - std::lock_guard lock(data_if_mutex_); - if (enable == input_enabled_) { - return; - } - if (enable) { - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 4, - .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - if (input_reference_) { - fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1); - } - ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), AUDIO_CODEC_DEFAULT_MIC_GAIN)); - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - } - AudioCodec::EnableInput(enable); -} - -void BoxAudioCodec::EnableOutput(bool enable) { - std::lock_guard lock(data_if_mutex_); - if (enable == output_enabled_) { - return; - } - if (enable) { - // Play 16bit 1 channel - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - } - AudioCodec::EnableOutput(enable); -} - -int BoxAudioCodec::Read(int16_t* dest, int samples) { - if (input_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); - } - return samples; -} - -int BoxAudioCodec::Write(const int16_t* data, int samples) { - if (output_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); - } - return samples; +#include "box_audio_codec.h" + +#include +#include +#include + +#define TAG "BoxAudioCodec" + +BoxAudioCodec::BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference) { + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + CreateDuplexChannels(mclk, bclk, ws, dout, din); + + // Do initialize of related interface: data_if, ctrl_if and gpio_if + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = rx_handle_, + .tx_handle = tx_handle_, + }; + data_if_ = audio_codec_new_i2s_data(&i2s_cfg); + assert(data_if_ != NULL); + + // Output + audio_codec_i2c_cfg_t i2c_cfg = { + .port = (i2c_port_t)1, + .addr = es8311_addr, + .bus_handle = i2c_master_handle, + }; + out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(out_ctrl_if_ != NULL); + + gpio_if_ = audio_codec_new_gpio(); + assert(gpio_if_ != NULL); + + es8311_codec_cfg_t es8311_cfg = {}; + es8311_cfg.ctrl_if = out_ctrl_if_; + es8311_cfg.gpio_if = gpio_if_; + es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC; + es8311_cfg.pa_pin = pa_pin; + es8311_cfg.use_mclk = true; + es8311_cfg.hw_gain.pa_voltage = 5.0; + es8311_cfg.hw_gain.codec_dac_voltage = 3.3; + out_codec_if_ = es8311_codec_new(&es8311_cfg); + assert(out_codec_if_ != NULL); + + esp_codec_dev_cfg_t dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_OUT, + .codec_if = out_codec_if_, + .data_if = data_if_, + }; + output_dev_ = esp_codec_dev_new(&dev_cfg); + assert(output_dev_ != NULL); + + // Input + i2c_cfg.addr = es7210_addr; + in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(in_ctrl_if_ != NULL); + + es7210_codec_cfg_t es7210_cfg = {}; + es7210_cfg.ctrl_if = in_ctrl_if_; + es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4; + in_codec_if_ = es7210_codec_new(&es7210_cfg); + assert(in_codec_if_ != NULL); + + dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; + dev_cfg.codec_if = in_codec_if_; + input_dev_ = esp_codec_dev_new(&dev_cfg); + assert(input_dev_ != NULL); + + ESP_LOGI(TAG, "BoxAudioDevice initialized"); +} + +BoxAudioCodec::~BoxAudioCodec() { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + esp_codec_dev_delete(output_dev_); + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + esp_codec_dev_delete(input_dev_); + + audio_codec_delete_codec_if(in_codec_if_); + audio_codec_delete_ctrl_if(in_ctrl_if_); + audio_codec_delete_codec_if(out_codec_if_); + audio_codec_delete_ctrl_if(out_ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void BoxAudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + assert(input_sample_rate_ == output_sample_rate_); + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, + .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256 + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + i2s_tdm_config_t tdm_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)input_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + .bclk_div = 8, + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), + .ws_width = I2S_TDM_AUTO_WS_WIDTH, + .ws_pol = false, + .bit_shift = true, + .left_align = false, + .big_endian = false, + .bit_order_lsb = false, + .skip_mask = false, + .total_slot = I2S_TDM_AUTO_SLOT_NUM + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = I2S_GPIO_UNUSED, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle_, &tdm_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + +void BoxAudioCodec::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void BoxAudioCodec::EnableInput(bool enable) { + std::lock_guard lock(data_if_mutex_); + if (enable == input_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 4, + .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + if (input_reference_) { + fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1); + } + ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), AUDIO_CODEC_DEFAULT_MIC_GAIN)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void BoxAudioCodec::EnableOutput(bool enable) { + std::lock_guard lock(data_if_mutex_); + if (enable == output_enabled_) { + return; + } + if (enable) { + // Play 16bit 1 channel + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + } + AudioCodec::EnableOutput(enable); +} + +int BoxAudioCodec::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} + +int BoxAudioCodec::Write(const int16_t* data, int samples) { + if (output_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); + } + return samples; } \ No newline at end of file diff --git a/main/audio/codecs/box_audio_codec.h b/main/audio/codecs/box_audio_codec.h index cb7d389..dcca118 100644 --- a/main/audio/codecs/box_audio_codec.h +++ b/main/audio/codecs/box_audio_codec.h @@ -1,40 +1,40 @@ -#ifndef _BOX_AUDIO_CODEC_H -#define _BOX_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include -#include - - -class BoxAudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t* data_if_ = nullptr; - const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; - const audio_codec_if_t* out_codec_if_ = nullptr; - const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; - const audio_codec_if_t* in_codec_if_ = nullptr; - const audio_codec_gpio_if_t* gpio_if_ = nullptr; - - esp_codec_dev_handle_t output_dev_ = nullptr; - esp_codec_dev_handle_t input_dev_ = nullptr; - std::mutex data_if_mutex_; - - void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); - - virtual int Read(int16_t* dest, int samples) override; - virtual int Write(const int16_t* data, int samples) override; - -public: - BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference); - virtual ~BoxAudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - -#endif // _BOX_AUDIO_CODEC_H +#ifndef _BOX_AUDIO_CODEC_H +#define _BOX_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include +#include + + +class BoxAudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t* data_if_ = nullptr; + const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; + const audio_codec_if_t* out_codec_if_ = nullptr; + const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; + const audio_codec_if_t* in_codec_if_ = nullptr; + const audio_codec_gpio_if_t* gpio_if_ = nullptr; + + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + std::mutex data_if_mutex_; + + void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference); + virtual ~BoxAudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _BOX_AUDIO_CODEC_H diff --git a/main/audio/codecs/dummy_audio_codec.cc b/main/audio/codecs/dummy_audio_codec.cc index 5f646b3..951609d 100644 --- a/main/audio/codecs/dummy_audio_codec.cc +++ b/main/audio/codecs/dummy_audio_codec.cc @@ -1,20 +1,20 @@ -#include "dummy_audio_codec.h" - -DummyAudioCodec::DummyAudioCodec(int input_sample_rate, int output_sample_rate) { - duplex_ = true; - input_reference_ = false; - input_channels_ = 1; - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; -} - -DummyAudioCodec::~DummyAudioCodec() { -} - -int DummyAudioCodec::Read(int16_t* dest, int samples) { - return 0; -} - -int DummyAudioCodec::Write(const int16_t* data, int samples) { - return 0; -} +#include "dummy_audio_codec.h" + +DummyAudioCodec::DummyAudioCodec(int input_sample_rate, int output_sample_rate) { + duplex_ = true; + input_reference_ = false; + input_channels_ = 1; + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; +} + +DummyAudioCodec::~DummyAudioCodec() { +} + +int DummyAudioCodec::Read(int16_t* dest, int samples) { + return 0; +} + +int DummyAudioCodec::Write(const int16_t* data, int samples) { + return 0; +} diff --git a/main/audio/codecs/dummy_audio_codec.h b/main/audio/codecs/dummy_audio_codec.h index 158f140..66b17b7 100644 --- a/main/audio/codecs/dummy_audio_codec.h +++ b/main/audio/codecs/dummy_audio_codec.h @@ -1,16 +1,16 @@ -#ifndef _DUMMY_AUDIO_CODEC_H -#define _DUMMY_AUDIO_CODEC_H - -#include "audio_codec.h" - -class DummyAudioCodec : public AudioCodec { -private: - virtual int Read(int16_t* dest, int samples) override; - virtual int Write(const int16_t* data, int samples) override; - -public: - DummyAudioCodec(int input_sample_rate, int output_sample_rate); - virtual ~DummyAudioCodec(); -}; - +#ifndef _DUMMY_AUDIO_CODEC_H +#define _DUMMY_AUDIO_CODEC_H + +#include "audio_codec.h" + +class DummyAudioCodec : public AudioCodec { +private: + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + DummyAudioCodec(int input_sample_rate, int output_sample_rate); + virtual ~DummyAudioCodec(); +}; + #endif // _DUMMY_AUDIO_CODEC_H \ No newline at end of file diff --git a/main/audio/codecs/es8311_audio_codec.cc b/main/audio/codecs/es8311_audio_codec.cc index 33c8c9d..bd36357 100644 --- a/main/audio/codecs/es8311_audio_codec.cc +++ b/main/audio/codecs/es8311_audio_codec.cc @@ -1,187 +1,187 @@ -#include "es8311_audio_codec.h" - -#include - -#define TAG "Es8311AudioCodec" - -Es8311AudioCodec::Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk, bool pa_inverted) { - duplex_ = true; // 是否双工 - input_reference_ = false; // 是否使用参考输入,实现回声消除 - input_channels_ = 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - pa_pin_ = pa_pin; - pa_inverted_ = pa_inverted; - - assert(input_sample_rate_ == output_sample_rate_); - CreateDuplexChannels(mclk, bclk, ws, dout, din); - - // Do initialize of related interface: data_if, ctrl_if and gpio_if - audio_codec_i2s_cfg_t i2s_cfg = { - .port = I2S_NUM_0, - .rx_handle = rx_handle_, - .tx_handle = tx_handle_, - }; - data_if_ = audio_codec_new_i2s_data(&i2s_cfg); - assert(data_if_ != NULL); - - // Output - audio_codec_i2c_cfg_t i2c_cfg = { - .port = i2c_port, - .addr = es8311_addr, - .bus_handle = i2c_master_handle, - }; - ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(ctrl_if_ != NULL); - - gpio_if_ = audio_codec_new_gpio(); - assert(gpio_if_ != NULL); - - es8311_codec_cfg_t es8311_cfg = {}; - es8311_cfg.ctrl_if = ctrl_if_; - es8311_cfg.gpio_if = gpio_if_; - es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH; - es8311_cfg.pa_pin = pa_pin; - es8311_cfg.use_mclk = use_mclk; - es8311_cfg.hw_gain.pa_voltage = 5.0; - es8311_cfg.hw_gain.codec_dac_voltage = 3.3; - es8311_cfg.pa_reverted = pa_inverted_; - codec_if_ = es8311_codec_new(&es8311_cfg); - assert(codec_if_ != NULL); - - ESP_LOGI(TAG, "Es8311AudioCodec initialized"); -} - -Es8311AudioCodec::~Es8311AudioCodec() { - esp_codec_dev_delete(dev_); - - audio_codec_delete_codec_if(codec_if_); - audio_codec_delete_ctrl_if(ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void Es8311AudioCodec::UpdateDeviceState() { - if ((input_enabled_ || output_enabled_) && dev_ == nullptr) { - esp_codec_dev_cfg_t dev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_IN_OUT, - .codec_if = codec_if_, - .data_if = data_if_, - }; - dev_ = esp_codec_dev_new(&dev_cfg); - assert(dev_ != NULL); - - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, - .sample_rate = (uint32_t)input_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(dev_, AUDIO_CODEC_DEFAULT_MIC_GAIN)); - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(dev_, output_volume_)); - } else if (!input_enabled_ && !output_enabled_ && dev_ != nullptr) { - esp_codec_dev_close(dev_); - dev_ = nullptr; - } - if (pa_pin_ != GPIO_NUM_NC) { - int level = output_enabled_ ? 1 : 0; - gpio_set_level(pa_pin_, pa_inverted_ ? !level : level); - } -} - -void Es8311AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { - assert(input_sample_rate_ == output_sample_rate_); - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, - .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_16BIT, - .ws_pol = false, - .bit_shift = true, - #ifdef I2S_HW_VERSION_2 - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - #endif - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); -} - -void Es8311AudioCodec::SetOutputVolume(int volume) { - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(dev_, volume)); - AudioCodec::SetOutputVolume(volume); -} - -void Es8311AudioCodec::EnableInput(bool enable) { - std::lock_guard lock(data_if_mutex_); - if (enable == input_enabled_) { - return; - } - AudioCodec::EnableInput(enable); - UpdateDeviceState(); -} - -void Es8311AudioCodec::EnableOutput(bool enable) { - std::lock_guard lock(data_if_mutex_); - if (enable == output_enabled_) { - return; - } - AudioCodec::EnableOutput(enable); - UpdateDeviceState(); -} - -int Es8311AudioCodec::Read(int16_t* dest, int samples) { - if (input_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(dev_, (void*)dest, samples * sizeof(int16_t))); - } - return samples; -} - -int Es8311AudioCodec::Write(const int16_t* data, int samples) { - if (output_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(dev_, (void*)data, samples * sizeof(int16_t))); - } - return samples; +#include "es8311_audio_codec.h" + +#include + +#define TAG "Es8311AudioCodec" + +Es8311AudioCodec::Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk, bool pa_inverted) { + duplex_ = true; // 是否双工 + input_reference_ = false; // 是否使用参考输入,实现回声消除 + input_channels_ = 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + pa_pin_ = pa_pin; + pa_inverted_ = pa_inverted; + + assert(input_sample_rate_ == output_sample_rate_); + CreateDuplexChannels(mclk, bclk, ws, dout, din); + + // Do initialize of related interface: data_if, ctrl_if and gpio_if + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = rx_handle_, + .tx_handle = tx_handle_, + }; + data_if_ = audio_codec_new_i2s_data(&i2s_cfg); + assert(data_if_ != NULL); + + // Output + audio_codec_i2c_cfg_t i2c_cfg = { + .port = i2c_port, + .addr = es8311_addr, + .bus_handle = i2c_master_handle, + }; + ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(ctrl_if_ != NULL); + + gpio_if_ = audio_codec_new_gpio(); + assert(gpio_if_ != NULL); + + es8311_codec_cfg_t es8311_cfg = {}; + es8311_cfg.ctrl_if = ctrl_if_; + es8311_cfg.gpio_if = gpio_if_; + es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH; + es8311_cfg.pa_pin = pa_pin; + es8311_cfg.use_mclk = use_mclk; + es8311_cfg.hw_gain.pa_voltage = 5.0; + es8311_cfg.hw_gain.codec_dac_voltage = 3.3; + es8311_cfg.pa_reverted = pa_inverted_; + codec_if_ = es8311_codec_new(&es8311_cfg); + assert(codec_if_ != NULL); + + ESP_LOGI(TAG, "Es8311AudioCodec initialized"); +} + +Es8311AudioCodec::~Es8311AudioCodec() { + esp_codec_dev_delete(dev_); + + audio_codec_delete_codec_if(codec_if_); + audio_codec_delete_ctrl_if(ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void Es8311AudioCodec::UpdateDeviceState() { + if ((input_enabled_ || output_enabled_) && dev_ == nullptr) { + esp_codec_dev_cfg_t dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_IN_OUT, + .codec_if = codec_if_, + .data_if = data_if_, + }; + dev_ = esp_codec_dev_new(&dev_cfg); + assert(dev_ != NULL); + + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)input_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(dev_, AUDIO_CODEC_DEFAULT_MIC_GAIN)); + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(dev_, output_volume_)); + } else if (!input_enabled_ && !output_enabled_ && dev_ != nullptr) { + esp_codec_dev_close(dev_); + dev_ = nullptr; + } + if (pa_pin_ != GPIO_NUM_NC) { + int level = output_enabled_ ? 1 : 0; + gpio_set_level(pa_pin_, pa_inverted_ ? !level : level); + } +} + +void Es8311AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + assert(input_sample_rate_ == output_sample_rate_); + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, + .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + #ifdef I2S_HW_VERSION_2 + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + #endif + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + +void Es8311AudioCodec::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void Es8311AudioCodec::EnableInput(bool enable) { + std::lock_guard lock(data_if_mutex_); + if (enable == input_enabled_) { + return; + } + AudioCodec::EnableInput(enable); + UpdateDeviceState(); +} + +void Es8311AudioCodec::EnableOutput(bool enable) { + std::lock_guard lock(data_if_mutex_); + if (enable == output_enabled_) { + return; + } + AudioCodec::EnableOutput(enable); + UpdateDeviceState(); +} + +int Es8311AudioCodec::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} + +int Es8311AudioCodec::Write(const int16_t* data, int samples) { + if (output_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(dev_, (void*)data, samples * sizeof(int16_t))); + } + return samples; } \ No newline at end of file diff --git a/main/audio/codecs/es8311_audio_codec.h b/main/audio/codecs/es8311_audio_codec.h index dd0e639..a493aa2 100644 --- a/main/audio/codecs/es8311_audio_codec.h +++ b/main/audio/codecs/es8311_audio_codec.h @@ -1,42 +1,42 @@ -#ifndef _ES8311_AUDIO_CODEC_H -#define _ES8311_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include -#include -#include -#include - - -class Es8311AudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t* data_if_ = nullptr; - const audio_codec_ctrl_if_t* ctrl_if_ = nullptr; - const audio_codec_if_t* codec_if_ = nullptr; - const audio_codec_gpio_if_t* gpio_if_ = nullptr; - - esp_codec_dev_handle_t dev_ = nullptr; - gpio_num_t pa_pin_ = GPIO_NUM_NC; - bool pa_inverted_ = false; - std::mutex data_if_mutex_; - - void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); - void UpdateDeviceState(); - - virtual int Read(int16_t* dest, int samples) override; - virtual int Write(const int16_t* data, int samples) override; - -public: - Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true, bool pa_inverted = false); - virtual ~Es8311AudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - +#ifndef _ES8311_AUDIO_CODEC_H +#define _ES8311_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include +#include +#include +#include + + +class Es8311AudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t* data_if_ = nullptr; + const audio_codec_ctrl_if_t* ctrl_if_ = nullptr; + const audio_codec_if_t* codec_if_ = nullptr; + const audio_codec_gpio_if_t* gpio_if_ = nullptr; + + esp_codec_dev_handle_t dev_ = nullptr; + gpio_num_t pa_pin_ = GPIO_NUM_NC; + bool pa_inverted_ = false; + std::mutex data_if_mutex_; + + void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); + void UpdateDeviceState(); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true, bool pa_inverted = false); + virtual ~Es8311AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + #endif // _ES8311_AUDIO_CODEC_H \ No newline at end of file diff --git a/main/audio/codecs/es8374_audio_codec.cc b/main/audio/codecs/es8374_audio_codec.cc index 11dea58..999754d 100644 --- a/main/audio/codecs/es8374_audio_codec.cc +++ b/main/audio/codecs/es8374_audio_codec.cc @@ -1,196 +1,196 @@ -#include "es8374_audio_codec.h" - -#include - -#define TAG "Es8374AudioCodec" - -Es8374AudioCodec::Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8374_addr, bool use_mclk) { - duplex_ = true; // 是否双工 - input_reference_ = false; // 是否使用参考输入,实现回声消除 - input_channels_ = 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - pa_pin_ = pa_pin; - CreateDuplexChannels(mclk, bclk, ws, dout, din); - - // Do initialize of related interface: data_if, ctrl_if and gpio_if - audio_codec_i2s_cfg_t i2s_cfg = { - .port = I2S_NUM_0, - .rx_handle = rx_handle_, - .tx_handle = tx_handle_, - }; - data_if_ = audio_codec_new_i2s_data(&i2s_cfg); - assert(data_if_ != NULL); - - // Output - audio_codec_i2c_cfg_t i2c_cfg = { - .port = i2c_port, - .addr = es8374_addr, - .bus_handle = i2c_master_handle, - }; - ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(ctrl_if_ != NULL); - - gpio_if_ = audio_codec_new_gpio(); - assert(gpio_if_ != NULL); - - es8374_codec_cfg_t es8374_cfg = {}; - es8374_cfg.ctrl_if = ctrl_if_; - es8374_cfg.gpio_if = gpio_if_; - es8374_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH; - es8374_cfg.pa_pin = pa_pin; - codec_if_ = es8374_codec_new(&es8374_cfg); - assert(codec_if_ != NULL); - - esp_codec_dev_cfg_t dev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_OUT, - .codec_if = codec_if_, - .data_if = data_if_, - }; - output_dev_ = esp_codec_dev_new(&dev_cfg); - assert(output_dev_ != NULL); - dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; - input_dev_ = esp_codec_dev_new(&dev_cfg); - assert(input_dev_ != NULL); - esp_codec_set_disable_when_closed(output_dev_, false); - esp_codec_set_disable_when_closed(input_dev_, false); - ESP_LOGI(TAG, "Es8374AudioCodec initialized"); -} - -Es8374AudioCodec::~Es8374AudioCodec() { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - esp_codec_dev_delete(output_dev_); - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - esp_codec_dev_delete(input_dev_); - - audio_codec_delete_codec_if(codec_if_); - audio_codec_delete_ctrl_if(ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void Es8374AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { - assert(input_sample_rate_ == output_sample_rate_); - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = 6, - .dma_frame_num = 240, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_16BIT, - .ws_pol = false, - .bit_shift = true, - #ifdef I2S_HW_VERSION_2 - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - #endif - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); -} - -void Es8374AudioCodec::SetOutputVolume(int volume) { - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); - AudioCodec::SetOutputVolume(volume); -} - -void Es8374AudioCodec::EnableInput(bool enable) { - std::lock_guard lock(data_if_mutex_); - if (enable == input_enabled_) { - return; - } - if (enable) { - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, - .sample_rate = (uint32_t)input_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, AUDIO_CODEC_DEFAULT_MIC_GAIN)); - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - } - AudioCodec::EnableInput(enable); -} - -void Es8374AudioCodec::EnableOutput(bool enable) { - std::lock_guard lock(data_if_mutex_); - if (enable == output_enabled_) { - return; - } - if (enable) { - // Play 16bit 1 channel - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); - if (pa_pin_ != GPIO_NUM_NC) { - gpio_set_level(pa_pin_, 1); - } - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - if (pa_pin_ != GPIO_NUM_NC) { - gpio_set_level(pa_pin_, 0); - } - } - AudioCodec::EnableOutput(enable); -} - -int Es8374AudioCodec::Read(int16_t* dest, int samples) { - if (input_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); - } - return samples; -} - -int Es8374AudioCodec::Write(const int16_t* data, int samples) { - if (output_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); - } - return samples; +#include "es8374_audio_codec.h" + +#include + +#define TAG "Es8374AudioCodec" + +Es8374AudioCodec::Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8374_addr, bool use_mclk) { + duplex_ = true; // 是否双工 + input_reference_ = false; // 是否使用参考输入,实现回声消除 + input_channels_ = 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + pa_pin_ = pa_pin; + CreateDuplexChannels(mclk, bclk, ws, dout, din); + + // Do initialize of related interface: data_if, ctrl_if and gpio_if + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = rx_handle_, + .tx_handle = tx_handle_, + }; + data_if_ = audio_codec_new_i2s_data(&i2s_cfg); + assert(data_if_ != NULL); + + // Output + audio_codec_i2c_cfg_t i2c_cfg = { + .port = i2c_port, + .addr = es8374_addr, + .bus_handle = i2c_master_handle, + }; + ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(ctrl_if_ != NULL); + + gpio_if_ = audio_codec_new_gpio(); + assert(gpio_if_ != NULL); + + es8374_codec_cfg_t es8374_cfg = {}; + es8374_cfg.ctrl_if = ctrl_if_; + es8374_cfg.gpio_if = gpio_if_; + es8374_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH; + es8374_cfg.pa_pin = pa_pin; + codec_if_ = es8374_codec_new(&es8374_cfg); + assert(codec_if_ != NULL); + + esp_codec_dev_cfg_t dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_OUT, + .codec_if = codec_if_, + .data_if = data_if_, + }; + output_dev_ = esp_codec_dev_new(&dev_cfg); + assert(output_dev_ != NULL); + dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; + input_dev_ = esp_codec_dev_new(&dev_cfg); + assert(input_dev_ != NULL); + esp_codec_set_disable_when_closed(output_dev_, false); + esp_codec_set_disable_when_closed(input_dev_, false); + ESP_LOGI(TAG, "Es8374AudioCodec initialized"); +} + +Es8374AudioCodec::~Es8374AudioCodec() { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + esp_codec_dev_delete(output_dev_); + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + esp_codec_dev_delete(input_dev_); + + audio_codec_delete_codec_if(codec_if_); + audio_codec_delete_ctrl_if(ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void Es8374AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + assert(input_sample_rate_ == output_sample_rate_); + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = 6, + .dma_frame_num = 240, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + #ifdef I2S_HW_VERSION_2 + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + #endif + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + +void Es8374AudioCodec::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void Es8374AudioCodec::EnableInput(bool enable) { + std::lock_guard lock(data_if_mutex_); + if (enable == input_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)input_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, AUDIO_CODEC_DEFAULT_MIC_GAIN)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void Es8374AudioCodec::EnableOutput(bool enable) { + std::lock_guard lock(data_if_mutex_); + if (enable == output_enabled_) { + return; + } + if (enable) { + // Play 16bit 1 channel + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); + if (pa_pin_ != GPIO_NUM_NC) { + gpio_set_level(pa_pin_, 1); + } + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + if (pa_pin_ != GPIO_NUM_NC) { + gpio_set_level(pa_pin_, 0); + } + } + AudioCodec::EnableOutput(enable); +} + +int Es8374AudioCodec::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} + +int Es8374AudioCodec::Write(const int16_t* data, int samples) { + if (output_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); + } + return samples; } \ No newline at end of file diff --git a/main/audio/codecs/es8374_audio_codec.h b/main/audio/codecs/es8374_audio_codec.h index 7533c22..97a8f5d 100644 --- a/main/audio/codecs/es8374_audio_codec.h +++ b/main/audio/codecs/es8374_audio_codec.h @@ -1,41 +1,41 @@ -#ifndef _ES8374_AUDIO_CODEC_H -#define _ES8374_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include -#include -#include -#include - - -class Es8374AudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t* data_if_ = nullptr; - const audio_codec_ctrl_if_t* ctrl_if_ = nullptr; - const audio_codec_if_t* codec_if_ = nullptr; - const audio_codec_gpio_if_t* gpio_if_ = nullptr; - - esp_codec_dev_handle_t output_dev_ = nullptr; - esp_codec_dev_handle_t input_dev_ = nullptr; - gpio_num_t pa_pin_ = GPIO_NUM_NC; - std::mutex data_if_mutex_; - - void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); - - virtual int Read(int16_t* dest, int samples) override; - virtual int Write(const int16_t* data, int samples) override; - -public: - Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8374_addr, bool use_mclk = true); - virtual ~Es8374AudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - +#ifndef _ES8374_AUDIO_CODEC_H +#define _ES8374_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include +#include +#include +#include + + +class Es8374AudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t* data_if_ = nullptr; + const audio_codec_ctrl_if_t* ctrl_if_ = nullptr; + const audio_codec_if_t* codec_if_ = nullptr; + const audio_codec_gpio_if_t* gpio_if_ = nullptr; + + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + gpio_num_t pa_pin_ = GPIO_NUM_NC; + std::mutex data_if_mutex_; + + void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8374_addr, bool use_mclk = true); + virtual ~Es8374AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + #endif // _ES8374_AUDIO_CODEC_H \ No newline at end of file diff --git a/main/audio/codecs/es8388_audio_codec.cc b/main/audio/codecs/es8388_audio_codec.cc index 3fd327b..c9092cd 100644 --- a/main/audio/codecs/es8388_audio_codec.cc +++ b/main/audio/codecs/es8388_audio_codec.cc @@ -1,219 +1,219 @@ -#include "es8388_audio_codec.h" - -#include - -#define TAG "Es8388AudioCodec" - -Es8388AudioCodec::Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8388_addr, bool input_reference) { - duplex_ = true; // 是否双工 - input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 - input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - pa_pin_ = pa_pin; - CreateDuplexChannels(mclk, bclk, ws, dout, din); - - // Do initialize of related interface: data_if, ctrl_if and gpio_if - audio_codec_i2s_cfg_t i2s_cfg = { - .port = I2S_NUM_0, - .rx_handle = rx_handle_, - .tx_handle = tx_handle_, - }; - data_if_ = audio_codec_new_i2s_data(&i2s_cfg); - assert(data_if_ != NULL); - - // Output - audio_codec_i2c_cfg_t i2c_cfg = { - .port = i2c_port, - .addr = es8388_addr, - .bus_handle = i2c_master_handle, - }; - ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(ctrl_if_ != NULL); - - gpio_if_ = audio_codec_new_gpio(); - assert(gpio_if_ != NULL); - - es8388_codec_cfg_t es8388_cfg = {}; - es8388_cfg.ctrl_if = ctrl_if_; - es8388_cfg.gpio_if = gpio_if_; - es8388_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH; - es8388_cfg.master_mode = true; - es8388_cfg.pa_pin = pa_pin; - es8388_cfg.pa_reverted = false; - es8388_cfg.hw_gain.pa_voltage = 5.0; - es8388_cfg.hw_gain.codec_dac_voltage = 3.3; - codec_if_ = es8388_codec_new(&es8388_cfg); - assert(codec_if_ != NULL); - - esp_codec_dev_cfg_t outdev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_OUT, - .codec_if = codec_if_, - .data_if = data_if_, - }; - output_dev_ = esp_codec_dev_new(&outdev_cfg); - assert(output_dev_ != NULL); - - esp_codec_dev_cfg_t indev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_IN, - .codec_if = codec_if_, - .data_if = data_if_, - }; - input_dev_ = esp_codec_dev_new(&indev_cfg); - assert(input_dev_ != NULL); - esp_codec_set_disable_when_closed(output_dev_, false); - esp_codec_set_disable_when_closed(input_dev_, false); - ESP_LOGI(TAG, "Es8388AudioCodec initialized"); -} - -Es8388AudioCodec::~Es8388AudioCodec() { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - esp_codec_dev_delete(output_dev_); - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - esp_codec_dev_delete(input_dev_); - - audio_codec_delete_codec_if(codec_if_); - audio_codec_delete_ctrl_if(ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void Es8388AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din){ - assert(input_sample_rate_ == output_sample_rate_); - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, - .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256 - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_16BIT, - .ws_pol = false, - .bit_shift = true, - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); -} - -void Es8388AudioCodec::SetOutputVolume(int volume) { - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); - AudioCodec::SetOutputVolume(volume); -} - -void Es8388AudioCodec::EnableInput(bool enable) { - std::lock_guard lock(data_if_mutex_); - if (enable == input_enabled_) { - return; - } - if (enable) { - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = (uint8_t) input_channels_, - .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), - .sample_rate = (uint32_t)input_sample_rate_, - .mclk_multiple = 0, - }; - if (input_reference_) { - fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1); - } - ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); - if (input_reference_) { - uint8_t gain = (11 << 4) + 0; - ctrl_if_->write_reg(ctrl_if_, 0x09, 1, &gain, 1); - }else{ - ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 24.0)); - } - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - } - AudioCodec::EnableInput(enable); -} - -void Es8388AudioCodec::EnableOutput(bool enable) { - std::lock_guard lock(data_if_mutex_); - if (enable == output_enabled_) { - return; - } - if (enable) { - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); - - // Set analog output volume to 0dB, default is -45dB - uint8_t reg_val = 30; // 0dB - if(input_reference_){ - reg_val = 27; - } - uint8_t regs[] = { 46, 47, 48, 49 }; // HP_LVOL, HP_RVOL, SPK_LVOL, SPK_RVOL - for (uint8_t reg : regs) { - ctrl_if_->write_reg(ctrl_if_, reg, 1, ®_val, 1); - } - - if (pa_pin_ != GPIO_NUM_NC) { - gpio_set_level(pa_pin_, 1); - } - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - if (pa_pin_ != GPIO_NUM_NC) { - gpio_set_level(pa_pin_, 0); - } - } - AudioCodec::EnableOutput(enable); -} - -int Es8388AudioCodec::Read(int16_t* dest, int samples) { - if (input_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); - } - return samples; -} - -int Es8388AudioCodec::Write(const int16_t* data, int samples) { - if (output_enabled_ && output_dev_ && data != nullptr) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); - } - return samples; -} +#include "es8388_audio_codec.h" + +#include + +#define TAG "Es8388AudioCodec" + +Es8388AudioCodec::Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8388_addr, bool input_reference) { + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + pa_pin_ = pa_pin; + CreateDuplexChannels(mclk, bclk, ws, dout, din); + + // Do initialize of related interface: data_if, ctrl_if and gpio_if + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = rx_handle_, + .tx_handle = tx_handle_, + }; + data_if_ = audio_codec_new_i2s_data(&i2s_cfg); + assert(data_if_ != NULL); + + // Output + audio_codec_i2c_cfg_t i2c_cfg = { + .port = i2c_port, + .addr = es8388_addr, + .bus_handle = i2c_master_handle, + }; + ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(ctrl_if_ != NULL); + + gpio_if_ = audio_codec_new_gpio(); + assert(gpio_if_ != NULL); + + es8388_codec_cfg_t es8388_cfg = {}; + es8388_cfg.ctrl_if = ctrl_if_; + es8388_cfg.gpio_if = gpio_if_; + es8388_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH; + es8388_cfg.master_mode = true; + es8388_cfg.pa_pin = pa_pin; + es8388_cfg.pa_reverted = false; + es8388_cfg.hw_gain.pa_voltage = 5.0; + es8388_cfg.hw_gain.codec_dac_voltage = 3.3; + codec_if_ = es8388_codec_new(&es8388_cfg); + assert(codec_if_ != NULL); + + esp_codec_dev_cfg_t outdev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_OUT, + .codec_if = codec_if_, + .data_if = data_if_, + }; + output_dev_ = esp_codec_dev_new(&outdev_cfg); + assert(output_dev_ != NULL); + + esp_codec_dev_cfg_t indev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_IN, + .codec_if = codec_if_, + .data_if = data_if_, + }; + input_dev_ = esp_codec_dev_new(&indev_cfg); + assert(input_dev_ != NULL); + esp_codec_set_disable_when_closed(output_dev_, false); + esp_codec_set_disable_when_closed(input_dev_, false); + ESP_LOGI(TAG, "Es8388AudioCodec initialized"); +} + +Es8388AudioCodec::~Es8388AudioCodec() { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + esp_codec_dev_delete(output_dev_); + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + esp_codec_dev_delete(input_dev_); + + audio_codec_delete_codec_if(codec_if_); + audio_codec_delete_ctrl_if(ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void Es8388AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din){ + assert(input_sample_rate_ == output_sample_rate_); + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, + .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256 + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + +void Es8388AudioCodec::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void Es8388AudioCodec::EnableInput(bool enable) { + std::lock_guard lock(data_if_mutex_); + if (enable == input_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = (uint8_t) input_channels_, + .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), + .sample_rate = (uint32_t)input_sample_rate_, + .mclk_multiple = 0, + }; + if (input_reference_) { + fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1); + } + ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); + if (input_reference_) { + uint8_t gain = (11 << 4) + 0; + ctrl_if_->write_reg(ctrl_if_, 0x09, 1, &gain, 1); + }else{ + ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 24.0)); + } + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void Es8388AudioCodec::EnableOutput(bool enable) { + std::lock_guard lock(data_if_mutex_); + if (enable == output_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); + + // Set analog output volume to 0dB, default is -45dB + uint8_t reg_val = 30; // 0dB + if(input_reference_){ + reg_val = 27; + } + uint8_t regs[] = { 46, 47, 48, 49 }; // HP_LVOL, HP_RVOL, SPK_LVOL, SPK_RVOL + for (uint8_t reg : regs) { + ctrl_if_->write_reg(ctrl_if_, reg, 1, ®_val, 1); + } + + if (pa_pin_ != GPIO_NUM_NC) { + gpio_set_level(pa_pin_, 1); + } + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + if (pa_pin_ != GPIO_NUM_NC) { + gpio_set_level(pa_pin_, 0); + } + } + AudioCodec::EnableOutput(enable); +} + +int Es8388AudioCodec::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} + +int Es8388AudioCodec::Write(const int16_t* data, int samples) { + if (output_enabled_ && output_dev_ && data != nullptr) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); + } + return samples; +} diff --git a/main/audio/codecs/es8388_audio_codec.h b/main/audio/codecs/es8388_audio_codec.h index 316dfce..30bdfbd 100644 --- a/main/audio/codecs/es8388_audio_codec.h +++ b/main/audio/codecs/es8388_audio_codec.h @@ -1,40 +1,40 @@ -#ifndef _ES8388_AUDIO_CODEC_H -#define _ES8388_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include -#include -#include - - -class Es8388AudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t* data_if_ = nullptr; - const audio_codec_ctrl_if_t* ctrl_if_ = nullptr; - const audio_codec_if_t* codec_if_ = nullptr; - const audio_codec_gpio_if_t* gpio_if_ = nullptr; - - esp_codec_dev_handle_t output_dev_ = nullptr; - esp_codec_dev_handle_t input_dev_ = nullptr; - gpio_num_t pa_pin_ = GPIO_NUM_NC; - std::mutex data_if_mutex_; - - void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); - - virtual int Read(int16_t* dest, int samples) override; - virtual int Write(const int16_t* data, int samples) override; - -public: - Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8388_addr, bool input_reference = false); - virtual ~Es8388AudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - -#endif // _ES8388_AUDIO_CODEC_H +#ifndef _ES8388_AUDIO_CODEC_H +#define _ES8388_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include +#include +#include + + +class Es8388AudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t* data_if_ = nullptr; + const audio_codec_ctrl_if_t* ctrl_if_ = nullptr; + const audio_codec_if_t* codec_if_ = nullptr; + const audio_codec_gpio_if_t* gpio_if_ = nullptr; + + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + gpio_num_t pa_pin_ = GPIO_NUM_NC; + std::mutex data_if_mutex_; + + void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8388_addr, bool input_reference = false); + virtual ~Es8388AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _ES8388_AUDIO_CODEC_H diff --git a/main/audio/codecs/es8389_audio_codec.cc b/main/audio/codecs/es8389_audio_codec.cc index ece34b9..07aebb0 100644 --- a/main/audio/codecs/es8389_audio_codec.cc +++ b/main/audio/codecs/es8389_audio_codec.cc @@ -1,203 +1,203 @@ -#include "es8389_audio_codec.h" - -#include - -static const char TAG[] = "Es8389AudioCodec"; - -Es8389AudioCodec::Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8389_addr, bool use_mclk) { - duplex_ = true; // 是否双工 - input_reference_ = false; // 是否使用参考输入,实现回声消除 - input_channels_ = 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - pa_pin_ = pa_pin; - CreateDuplexChannels(mclk, bclk, ws, dout, din); - - // Do initialize of related interface: data_if, ctrl_if and gpio_if - audio_codec_i2s_cfg_t i2s_cfg = { - .port = I2S_NUM_0, - .rx_handle = rx_handle_, - .tx_handle = tx_handle_, - }; - data_if_ = audio_codec_new_i2s_data(&i2s_cfg); - assert(data_if_ != NULL); - - // Output - audio_codec_i2c_cfg_t i2c_cfg = { - .port = i2c_port, - .addr = es8389_addr, - .bus_handle = i2c_master_handle, - }; - ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(ctrl_if_ != NULL); - - gpio_if_ = audio_codec_new_gpio(); - assert(gpio_if_ != NULL); - - es8389_codec_cfg_t es8389_cfg = {}; - es8389_cfg.ctrl_if = ctrl_if_; - es8389_cfg.gpio_if = gpio_if_; - es8389_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH; - es8389_cfg.pa_pin = pa_pin; - es8389_cfg.use_mclk = use_mclk; - es8389_cfg.hw_gain.pa_voltage = 5.0; - es8389_cfg.hw_gain.codec_dac_voltage = 3.3; - codec_if_ = es8389_codec_new(&es8389_cfg); - - assert(codec_if_ != NULL); - - esp_codec_dev_cfg_t outdev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_OUT, - .codec_if = codec_if_, - .data_if = data_if_, - }; - output_dev_ = esp_codec_dev_new(&outdev_cfg); - assert(output_dev_ != NULL); - - esp_codec_dev_cfg_t indev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_IN, - .codec_if = codec_if_, - .data_if = data_if_, - }; - input_dev_ = esp_codec_dev_new(&indev_cfg); - assert(input_dev_ != NULL); - esp_codec_set_disable_when_closed(output_dev_, false); - esp_codec_set_disable_when_closed(input_dev_, false); - ESP_LOGI(TAG, "Es8389AudioCodec initialized"); -} - -Es8389AudioCodec::~Es8389AudioCodec() { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - esp_codec_dev_delete(output_dev_); - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - esp_codec_dev_delete(input_dev_); - - audio_codec_delete_codec_if(codec_if_); - audio_codec_delete_ctrl_if(ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void Es8389AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { - assert(input_sample_rate_ == output_sample_rate_); - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = 6, - .dma_frame_num = 240, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, -#ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, -#endif - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_16BIT, - .ws_pol = false, - .bit_shift = true, - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); -} - -void Es8389AudioCodec::SetOutputVolume(int volume) { - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); - AudioCodec::SetOutputVolume(volume); -} - -void Es8389AudioCodec::EnableInput(bool enable) { - std::lock_guard lock(data_if_mutex_); - if (enable == input_enabled_) { - return; - } - if (enable) { - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, - .sample_rate = (uint32_t)input_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 40.0)); - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - } - AudioCodec::EnableInput(enable); -} - -void Es8389AudioCodec::EnableOutput(bool enable) { - std::lock_guard lock(data_if_mutex_); - if (enable == output_enabled_) { - return; - } - if (enable) { - // Play 16bit 1 channel - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); - if (pa_pin_ != GPIO_NUM_NC) { - gpio_set_level(pa_pin_, 1); - } - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - if (pa_pin_ != GPIO_NUM_NC) { - gpio_set_level(pa_pin_, 0); - } - } - AudioCodec::EnableOutput(enable); -} - -int Es8389AudioCodec::Read(int16_t* dest, int samples) { - if (input_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); - } - return samples; -} - -int Es8389AudioCodec::Write(const int16_t* data, int samples) { - if (output_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); - } - return samples; +#include "es8389_audio_codec.h" + +#include + +static const char TAG[] = "Es8389AudioCodec"; + +Es8389AudioCodec::Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8389_addr, bool use_mclk) { + duplex_ = true; // 是否双工 + input_reference_ = false; // 是否使用参考输入,实现回声消除 + input_channels_ = 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + pa_pin_ = pa_pin; + CreateDuplexChannels(mclk, bclk, ws, dout, din); + + // Do initialize of related interface: data_if, ctrl_if and gpio_if + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = rx_handle_, + .tx_handle = tx_handle_, + }; + data_if_ = audio_codec_new_i2s_data(&i2s_cfg); + assert(data_if_ != NULL); + + // Output + audio_codec_i2c_cfg_t i2c_cfg = { + .port = i2c_port, + .addr = es8389_addr, + .bus_handle = i2c_master_handle, + }; + ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(ctrl_if_ != NULL); + + gpio_if_ = audio_codec_new_gpio(); + assert(gpio_if_ != NULL); + + es8389_codec_cfg_t es8389_cfg = {}; + es8389_cfg.ctrl_if = ctrl_if_; + es8389_cfg.gpio_if = gpio_if_; + es8389_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH; + es8389_cfg.pa_pin = pa_pin; + es8389_cfg.use_mclk = use_mclk; + es8389_cfg.hw_gain.pa_voltage = 5.0; + es8389_cfg.hw_gain.codec_dac_voltage = 3.3; + codec_if_ = es8389_codec_new(&es8389_cfg); + + assert(codec_if_ != NULL); + + esp_codec_dev_cfg_t outdev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_OUT, + .codec_if = codec_if_, + .data_if = data_if_, + }; + output_dev_ = esp_codec_dev_new(&outdev_cfg); + assert(output_dev_ != NULL); + + esp_codec_dev_cfg_t indev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_IN, + .codec_if = codec_if_, + .data_if = data_if_, + }; + input_dev_ = esp_codec_dev_new(&indev_cfg); + assert(input_dev_ != NULL); + esp_codec_set_disable_when_closed(output_dev_, false); + esp_codec_set_disable_when_closed(input_dev_, false); + ESP_LOGI(TAG, "Es8389AudioCodec initialized"); +} + +Es8389AudioCodec::~Es8389AudioCodec() { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + esp_codec_dev_delete(output_dev_); + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + esp_codec_dev_delete(input_dev_); + + audio_codec_delete_codec_if(codec_if_); + audio_codec_delete_ctrl_if(ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void Es8389AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + assert(input_sample_rate_ == output_sample_rate_); + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = 6, + .dma_frame_num = 240, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, +#ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, +#endif + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + +void Es8389AudioCodec::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void Es8389AudioCodec::EnableInput(bool enable) { + std::lock_guard lock(data_if_mutex_); + if (enable == input_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)input_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 40.0)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void Es8389AudioCodec::EnableOutput(bool enable) { + std::lock_guard lock(data_if_mutex_); + if (enable == output_enabled_) { + return; + } + if (enable) { + // Play 16bit 1 channel + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); + if (pa_pin_ != GPIO_NUM_NC) { + gpio_set_level(pa_pin_, 1); + } + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + if (pa_pin_ != GPIO_NUM_NC) { + gpio_set_level(pa_pin_, 0); + } + } + AudioCodec::EnableOutput(enable); +} + +int Es8389AudioCodec::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} + +int Es8389AudioCodec::Write(const int16_t* data, int samples) { + if (output_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); + } + return samples; } \ No newline at end of file diff --git a/main/audio/codecs/es8389_audio_codec.h b/main/audio/codecs/es8389_audio_codec.h index b55b427..78752d9 100644 --- a/main/audio/codecs/es8389_audio_codec.h +++ b/main/audio/codecs/es8389_audio_codec.h @@ -1,40 +1,40 @@ -#ifndef _ES8389_AUDIO_CODEC_H -#define _ES8389_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include -#include -#include -#include - -class Es8389AudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t* data_if_ = nullptr; - const audio_codec_ctrl_if_t* ctrl_if_ = nullptr; - const audio_codec_if_t* codec_if_ = nullptr; - const audio_codec_gpio_if_t* gpio_if_ = nullptr; - - esp_codec_dev_handle_t output_dev_ = nullptr; - esp_codec_dev_handle_t input_dev_ = nullptr; - gpio_num_t pa_pin_ = GPIO_NUM_NC; - std::mutex data_if_mutex_; - - void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); - - virtual int Read(int16_t* dest, int samples) override; - virtual int Write(const int16_t* data, int samples) override; - -public: - Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8389_addr, bool use_mclk = true); - virtual ~Es8389AudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - -#endif // _ES8389_AUDIO_CODEC_H +#ifndef _ES8389_AUDIO_CODEC_H +#define _ES8389_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include +#include +#include +#include + +class Es8389AudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t* data_if_ = nullptr; + const audio_codec_ctrl_if_t* ctrl_if_ = nullptr; + const audio_codec_if_t* codec_if_ = nullptr; + const audio_codec_gpio_if_t* gpio_if_ = nullptr; + + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + gpio_num_t pa_pin_ = GPIO_NUM_NC; + std::mutex data_if_mutex_; + + void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8389_addr, bool use_mclk = true); + virtual ~Es8389AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _ES8389_AUDIO_CODEC_H diff --git a/main/audio/codecs/no_audio_codec.cc b/main/audio/codecs/no_audio_codec.cc index 4de2c9c..7846416 100644 --- a/main/audio/codecs/no_audio_codec.cc +++ b/main/audio/codecs/no_audio_codec.cc @@ -1,418 +1,410 @@ -#include "no_audio_codec.h" - -#include -#include -#include - -#define TAG "NoAudioCodec" - -NoAudioCodec::~NoAudioCodec() { - if (rx_handle_ != nullptr) { - ESP_ERROR_CHECK(i2s_channel_disable(rx_handle_)); - } - if (tx_handle_ != nullptr) { - ESP_ERROR_CHECK(i2s_channel_disable(tx_handle_)); - } -} - -NoAudioCodecDuplex::NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { - duplex_ = true; - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, - .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_32BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_MONO, - .slot_mask = I2S_STD_SLOT_LEFT, - .ws_width = I2S_DATA_BIT_WIDTH_32BIT, - .ws_pol = false, - .bit_shift = true, - #ifdef I2S_HW_VERSION_2 - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - #endif - - }, - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); -} - - -NoAudioCodecSimplex::NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din) { - duplex_ = false; - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - // Create a new channel for speaker - i2s_chan_config_t chan_cfg = { - .id = (i2s_port_t)0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, - .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, nullptr)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_32BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_32BIT, - .ws_pol = false, - .bit_shift = true, - #ifdef I2S_HW_VERSION_2 - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - #endif - - }, - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = spk_bclk, - .ws = spk_ws, - .dout = spk_dout, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - - // Create a new channel for MIC - chan_cfg.id = (i2s_port_t)1; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, nullptr, &rx_handle_)); - std_cfg.clk_cfg.sample_rate_hz = (uint32_t)input_sample_rate_; - // RX 使用单声道 LEFT - std_cfg.slot_cfg.slot_mode = I2S_SLOT_MODE_MONO; - std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT; - std_cfg.gpio_cfg.bclk = mic_sck; - std_cfg.gpio_cfg.ws = mic_ws; - std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED; - std_cfg.gpio_cfg.din = mic_din; - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); - ESP_LOGI(TAG, "Simplex channels created"); -} - -NoAudioCodecSimplex::NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din, i2s_std_slot_mask_t mic_slot_mask){ - duplex_ = false; - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - // Create a new channel for speaker - i2s_chan_config_t chan_cfg = { - .id = (i2s_port_t)0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, - .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, nullptr)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_32BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_MONO, - .slot_mask = spk_slot_mask, - .ws_width = I2S_DATA_BIT_WIDTH_32BIT, - .ws_pol = false, - .bit_shift = true, - #ifdef I2S_HW_VERSION_2 - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - #endif - - }, - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = spk_bclk, - .ws = spk_ws, - .dout = spk_dout, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - - // Create a new channel for MIC - chan_cfg.id = (i2s_port_t)1; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, nullptr, &rx_handle_)); - std_cfg.clk_cfg.sample_rate_hz = (uint32_t)input_sample_rate_; - std_cfg.slot_cfg.slot_mask = mic_slot_mask; - std_cfg.gpio_cfg.bclk = mic_sck; - std_cfg.gpio_cfg.ws = mic_ws; - std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED; - std_cfg.gpio_cfg.din = mic_din; - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); - ESP_LOGI(TAG, "Simplex channels created"); -} - -NoAudioCodecSimplexPdm::NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask,gpio_num_t mic_sck, gpio_num_t mic_din) { - duplex_ = false; - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - // Create a new channel for speaker - i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)1, I2S_ROLE_MASTER); - tx_chan_cfg.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM; - tx_chan_cfg.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM; - tx_chan_cfg.auto_clear_after_cb = true; - tx_chan_cfg.auto_clear_before_cb = false; - tx_chan_cfg.intr_priority = 0; - ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_handle_, NULL)); - - - i2s_std_config_t tx_std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_32BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_MONO, - .slot_mask = spk_slot_mask, - .ws_width = I2S_DATA_BIT_WIDTH_32BIT, - .ws_pol = false, - .bit_shift = true, - #ifdef I2S_HW_VERSION_2 - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - #endif - - }, - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = spk_bclk, - .ws = spk_ws, - .dout = spk_dout, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false, - }, - }, - }; - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &tx_std_cfg)); -#if SOC_I2S_SUPPORTS_PDM_RX - // Create a new channel for MIC in PDM mode - i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)0, I2S_ROLE_MASTER); - ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle_)); - i2s_pdm_rx_config_t pdm_rx_cfg = { - .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG((uint32_t)input_sample_rate_), - /* The data bit-width of PDM mode is fixed to 16 */ - .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), - .gpio_cfg = { - .clk = mic_sck, - .din = mic_din, - - .invert_flags = { - .clk_inv = false, - }, - }, - }; - ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &pdm_rx_cfg)); -#else - ESP_LOGE(TAG, "PDM is not supported"); -#endif - ESP_LOGI(TAG, "Simplex channels created"); -} - -NoAudioCodecSimplexPdm::NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_din) { - duplex_ = false; - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - // Create a new channel for speaker - i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)1, I2S_ROLE_MASTER); - tx_chan_cfg.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM; - tx_chan_cfg.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM; - tx_chan_cfg.auto_clear_after_cb = true; - tx_chan_cfg.auto_clear_before_cb = false; - tx_chan_cfg.intr_priority = 0; - ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_handle_, NULL)); - - - i2s_std_config_t tx_std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - - }, - .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO), - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = spk_bclk, - .ws = spk_ws, - .dout = spk_dout, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false, - }, - }, - }; - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &tx_std_cfg)); -#if SOC_I2S_SUPPORTS_PDM_RX - // Create a new channel for MIC in PDM mode - i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)0, I2S_ROLE_MASTER); - ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle_)); - i2s_pdm_rx_config_t pdm_rx_cfg = { - .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG((uint32_t)input_sample_rate_), - /* The data bit-width of PDM mode is fixed to 16 */ - .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), - .gpio_cfg = { - .clk = mic_sck, - .din = mic_din, - - .invert_flags = { - .clk_inv = false, - }, - }, - }; - ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &pdm_rx_cfg)); -#else - ESP_LOGE(TAG, "PDM is not supported"); -#endif - ESP_LOGI(TAG, "Simplex channels created"); -} - -int NoAudioCodec::Write(const int16_t* data, int samples) { - std::lock_guard lock(data_if_mutex_); - // 立体声交织输出:L,R,L,R ... (每声道32位) - std::vector buffer(samples * 2); - - // output_volume_: 0-100 - // volume_factor_: 0-65536 - int32_t volume_factor = pow(double(output_volume_) / 100.0, 2) * 65536; - for (int i = 0; i < samples; i++) { - int64_t temp = int64_t(data[i]) * volume_factor; // 使用 int64_t 进行乘法运算 - int32_t s32; - if (temp > INT32_MAX) { - s32 = INT32_MAX; - } else if (temp < INT32_MIN) { - s32 = INT32_MIN; - } else { - s32 = static_cast(temp); - } - // 交织到左右声道 - buffer[2 * i] = s32; // Left - buffer[2 * i + 1] = s32; // Right(复制) - } - - size_t bytes_written; - ESP_ERROR_CHECK(i2s_channel_write(tx_handle_, buffer.data(), (samples * 2) * sizeof(int32_t), &bytes_written, portMAX_DELAY)); - return bytes_written / sizeof(int32_t) / 2; // 返回每声道样本数 -} - -int NoAudioCodec::Read(int16_t* dest, int samples) { - size_t bytes_read; - - std::vector bit32_buffer(samples); - if (i2s_channel_read(rx_handle_, bit32_buffer.data(), samples * sizeof(int32_t), &bytes_read, portMAX_DELAY) != ESP_OK) { - ESP_LOGE(TAG, "Read Failed!"); - return 0; - } - - samples = bytes_read / sizeof(int32_t); - for (int i = 0; i < samples; i++) { - int32_t value = bit32_buffer[i] >> 12; - dest[i] = (value > INT16_MAX) ? INT16_MAX : (value < -INT16_MAX) ? -INT16_MAX : (int16_t)value; - } - return samples; -} - -int NoAudioCodecSimplexPdm::Read(int16_t* dest, int samples) { - size_t bytes_read; - - // PDM 解调后的数据位宽为 16 位,直接读取到目标缓冲区 - if (i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY) != ESP_OK) { - ESP_LOGE(TAG, "Read Failed!"); - return 0; - } - - // 计算实际读取的样本数 - return bytes_read / sizeof(int16_t); -} \ No newline at end of file +#include "no_audio_codec.h" + +#include +#include +#include + +#define TAG "NoAudioCodec" + +NoAudioCodec::~NoAudioCodec() { + if (rx_handle_ != nullptr) { + ESP_ERROR_CHECK(i2s_channel_disable(rx_handle_)); + } + if (tx_handle_ != nullptr) { + ESP_ERROR_CHECK(i2s_channel_disable(tx_handle_)); + } +} + +NoAudioCodecDuplex::NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + duplex_ = true; + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, + .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_32BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_MONO, + .slot_mask = I2S_STD_SLOT_LEFT, + .ws_width = I2S_DATA_BIT_WIDTH_32BIT, + .ws_pol = false, + .bit_shift = true, + #ifdef I2S_HW_VERSION_2 + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + #endif + + }, + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + + +NoAudioCodecSimplex::NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din) { + duplex_ = false; + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + // Create a new channel for speaker + i2s_chan_config_t chan_cfg = { + .id = (i2s_port_t)0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, + .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, nullptr)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_32BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_MONO, + .slot_mask = I2S_STD_SLOT_LEFT, + .ws_width = I2S_DATA_BIT_WIDTH_32BIT, + .ws_pol = false, + .bit_shift = true, + #ifdef I2S_HW_VERSION_2 + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + #endif + + }, + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = spk_bclk, + .ws = spk_ws, + .dout = spk_dout, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + + // Create a new channel for MIC + chan_cfg.id = (i2s_port_t)1; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, nullptr, &rx_handle_)); + std_cfg.clk_cfg.sample_rate_hz = (uint32_t)input_sample_rate_; + std_cfg.gpio_cfg.bclk = mic_sck; + std_cfg.gpio_cfg.ws = mic_ws; + std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED; + std_cfg.gpio_cfg.din = mic_din; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); + ESP_LOGI(TAG, "Simplex channels created"); +} + +NoAudioCodecSimplex::NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din, i2s_std_slot_mask_t mic_slot_mask){ + duplex_ = false; + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + // Create a new channel for speaker + i2s_chan_config_t chan_cfg = { + .id = (i2s_port_t)0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, + .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, nullptr)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_32BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_MONO, + .slot_mask = spk_slot_mask, + .ws_width = I2S_DATA_BIT_WIDTH_32BIT, + .ws_pol = false, + .bit_shift = true, + #ifdef I2S_HW_VERSION_2 + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + #endif + + }, + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = spk_bclk, + .ws = spk_ws, + .dout = spk_dout, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + + // Create a new channel for MIC + chan_cfg.id = (i2s_port_t)1; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, nullptr, &rx_handle_)); + std_cfg.clk_cfg.sample_rate_hz = (uint32_t)input_sample_rate_; + std_cfg.slot_cfg.slot_mask = mic_slot_mask; + std_cfg.gpio_cfg.bclk = mic_sck; + std_cfg.gpio_cfg.ws = mic_ws; + std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED; + std_cfg.gpio_cfg.din = mic_din; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); + ESP_LOGI(TAG, "Simplex channels created"); +} + +NoAudioCodecSimplexPdm::NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask,gpio_num_t mic_sck, gpio_num_t mic_din) { + duplex_ = false; + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + // Create a new channel for speaker + i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)1, I2S_ROLE_MASTER); + tx_chan_cfg.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM; + tx_chan_cfg.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM; + tx_chan_cfg.auto_clear_after_cb = true; + tx_chan_cfg.auto_clear_before_cb = false; + tx_chan_cfg.intr_priority = 0; + ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_handle_, NULL)); + + + i2s_std_config_t tx_std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_32BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_MONO, + .slot_mask = spk_slot_mask, + .ws_width = I2S_DATA_BIT_WIDTH_32BIT, + .ws_pol = false, + .bit_shift = true, + #ifdef I2S_HW_VERSION_2 + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + #endif + + }, + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = spk_bclk, + .ws = spk_ws, + .dout = spk_dout, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &tx_std_cfg)); +#if SOC_I2S_SUPPORTS_PDM_RX + // Create a new channel for MIC in PDM mode + i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)0, I2S_ROLE_MASTER); + ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle_)); + i2s_pdm_rx_config_t pdm_rx_cfg = { + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG((uint32_t)input_sample_rate_), + /* The data bit-width of PDM mode is fixed to 16 */ + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .gpio_cfg = { + .clk = mic_sck, + .din = mic_din, + + .invert_flags = { + .clk_inv = false, + }, + }, + }; + ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &pdm_rx_cfg)); +#else + ESP_LOGE(TAG, "PDM is not supported"); +#endif + ESP_LOGI(TAG, "Simplex channels created"); +} + +NoAudioCodecSimplexPdm::NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_din) { + duplex_ = false; + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + // Create a new channel for speaker + i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)1, I2S_ROLE_MASTER); + tx_chan_cfg.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM; + tx_chan_cfg.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM; + tx_chan_cfg.auto_clear_after_cb = true; + tx_chan_cfg.auto_clear_before_cb = false; + tx_chan_cfg.intr_priority = 0; + ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_handle_, NULL)); + + + i2s_std_config_t tx_std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + + }, + .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = spk_bclk, + .ws = spk_ws, + .dout = spk_dout, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &tx_std_cfg)); +#if SOC_I2S_SUPPORTS_PDM_RX + // Create a new channel for MIC in PDM mode + i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)0, I2S_ROLE_MASTER); + ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle_)); + i2s_pdm_rx_config_t pdm_rx_cfg = { + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG((uint32_t)input_sample_rate_), + /* The data bit-width of PDM mode is fixed to 16 */ + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .gpio_cfg = { + .clk = mic_sck, + .din = mic_din, + + .invert_flags = { + .clk_inv = false, + }, + }, + }; + ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &pdm_rx_cfg)); +#else + ESP_LOGE(TAG, "PDM is not supported"); +#endif + ESP_LOGI(TAG, "Simplex channels created"); +} + +int NoAudioCodec::Write(const int16_t* data, int samples) { + std::lock_guard lock(data_if_mutex_); + std::vector buffer(samples); + + // output_volume_: 0-100 + // volume_factor_: 0-65536 + int32_t volume_factor = pow(double(output_volume_) / 100.0, 2) * 65536; + for (int i = 0; i < samples; i++) { + int64_t temp = int64_t(data[i]) * volume_factor; // 使用 int64_t 进行乘法运算 + if (temp > INT32_MAX) { + buffer[i] = INT32_MAX; + } else if (temp < INT32_MIN) { + buffer[i] = INT32_MIN; + } else { + buffer[i] = static_cast(temp); + } + } + + size_t bytes_written; + ESP_ERROR_CHECK(i2s_channel_write(tx_handle_, buffer.data(), samples * sizeof(int32_t), &bytes_written, portMAX_DELAY)); + return bytes_written / sizeof(int32_t); +} + +int NoAudioCodec::Read(int16_t* dest, int samples) { + size_t bytes_read; + + std::vector bit32_buffer(samples); + if (i2s_channel_read(rx_handle_, bit32_buffer.data(), samples * sizeof(int32_t), &bytes_read, portMAX_DELAY) != ESP_OK) { + ESP_LOGE(TAG, "Read Failed!"); + return 0; + } + + samples = bytes_read / sizeof(int32_t); + for (int i = 0; i < samples; i++) { + int32_t value = bit32_buffer[i] >> 12; + dest[i] = (value > INT16_MAX) ? INT16_MAX : (value < -INT16_MAX) ? -INT16_MAX : (int16_t)value; + } + return samples; +} + +int NoAudioCodecSimplexPdm::Read(int16_t* dest, int samples) { + size_t bytes_read; + + // PDM 解调后的数据位宽为 16 位,直接读取到目标缓冲区 + if (i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY) != ESP_OK) { + ESP_LOGE(TAG, "Read Failed!"); + return 0; + } + + // 计算实际读取的样本数 + return bytes_read / sizeof(int16_t); +} diff --git a/main/audio/codecs/no_audio_codec.h b/main/audio/codecs/no_audio_codec.h index c7a4728..a19cac4 100644 --- a/main/audio/codecs/no_audio_codec.h +++ b/main/audio/codecs/no_audio_codec.h @@ -1,39 +1,39 @@ -#ifndef _NO_AUDIO_CODEC_H -#define _NO_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include -#include - -class NoAudioCodec : public AudioCodec { -protected: - std::mutex data_if_mutex_; - - virtual int Write(const int16_t* data, int samples) override; - virtual int Read(int16_t* dest, int samples) override; - -public: - virtual ~NoAudioCodec(); -}; - -class NoAudioCodecDuplex : public NoAudioCodec { -public: - NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); -}; - -class NoAudioCodecSimplex : public NoAudioCodec { -public: - NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din); - NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din, i2s_std_slot_mask_t mic_slot_mask); -}; - -class NoAudioCodecSimplexPdm : public NoAudioCodec { -public: - NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_din); - NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_din); - int Read(int16_t* dest, int samples); -}; - -#endif // _NO_AUDIO_CODEC_H \ No newline at end of file +#ifndef _NO_AUDIO_CODEC_H +#define _NO_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include +#include + +class NoAudioCodec : public AudioCodec { +protected: + std::mutex data_if_mutex_; + + virtual int Write(const int16_t* data, int samples) override; + virtual int Read(int16_t* dest, int samples) override; + +public: + virtual ~NoAudioCodec(); +}; + +class NoAudioCodecDuplex : public NoAudioCodec { +public: + NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); +}; + +class NoAudioCodecSimplex : public NoAudioCodec { +public: + NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din); + NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din, i2s_std_slot_mask_t mic_slot_mask); +}; + +class NoAudioCodecSimplexPdm : public NoAudioCodec { +public: + NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_din); + NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_din); + int Read(int16_t* dest, int samples); +}; + +#endif // _NO_AUDIO_CODEC_H diff --git a/main/audio/processors/afe_audio_processor.cc b/main/audio/processors/afe_audio_processor.cc index a9145be..3a1ecb5 100644 --- a/main/audio/processors/afe_audio_processor.cc +++ b/main/audio/processors/afe_audio_processor.cc @@ -1,189 +1,187 @@ -#include "afe_audio_processor.h" -#include - -#define PROCESSOR_RUNNING 0x01 - -#define TAG "AfeAudioProcessor" - -AfeAudioProcessor::AfeAudioProcessor() - : afe_data_(nullptr) { - event_group_ = xEventGroupCreate(); -} - -void AfeAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) { - codec_ = codec; - frame_samples_ = frame_duration_ms * 16000 / 1000; - - // Pre-allocate output buffer capacity - output_buffer_.reserve(frame_samples_); - - int ref_num = codec_->input_reference() ? 1 : 0; - - std::string input_format; - for (int i = 0; i < codec_->input_channels() - ref_num; i++) { - input_format.push_back('M'); - } - for (int i = 0; i < ref_num; i++) { - input_format.push_back('R'); - } - - srmodel_list_t *models; - if (models_list == nullptr) { - models = esp_srmodel_init("model"); - } else { - models = models_list; - } - - char* ns_model_name = esp_srmodel_filter(models, ESP_NSNET_PREFIX, NULL); - char* vad_model_name = esp_srmodel_filter(models, ESP_VADN_PREFIX, NULL); - - afe_config_t* afe_config = afe_config_init(input_format.c_str(), NULL, AFE_TYPE_VC, AFE_MODE_HIGH_PERF); - afe_config->aec_mode = AEC_MODE_VOIP_HIGH_PERF; - afe_config->vad_mode = VAD_MODE_0; - afe_config->vad_min_noise_ms = 100; - if (vad_model_name != nullptr) { - afe_config->vad_model_name = vad_model_name; - } - - if (ns_model_name != nullptr) { - afe_config->ns_init = true; - afe_config->ns_model_name = ns_model_name; - afe_config->afe_ns_mode = AFE_NS_MODE_NET; - } else { - afe_config->ns_init = false; - } - - afe_config->afe_perferred_core = 1; - afe_config->afe_perferred_priority = 1; - afe_config->agc_init = false; - afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM; - -#ifdef CONFIG_USE_DEVICE_AEC - afe_config->aec_init = true; - afe_config->vad_init = false; -#else - afe_config->aec_init = false; - afe_config->vad_init = true; -#endif - - afe_iface_ = esp_afe_handle_from_config(afe_config); - afe_data_ = afe_iface_->create_from_config(afe_config); - - xTaskCreate([](void* arg) { - auto this_ = (AfeAudioProcessor*)arg; - this_->AudioProcessorTask(); - vTaskDelete(NULL); - }, "audio_communication", 4096, this, 3, NULL); -} - -AfeAudioProcessor::~AfeAudioProcessor() { - if (afe_data_ != nullptr) { - afe_iface_->destroy(afe_data_); - } - vEventGroupDelete(event_group_); -} - -size_t AfeAudioProcessor::GetFeedSize() { - if (afe_data_ == nullptr) { - return 0; - } - return afe_iface_->get_feed_chunksize(afe_data_); -} - -void AfeAudioProcessor::Feed(std::vector&& data) { - if (afe_data_ == nullptr) { - return; - } - afe_iface_->feed(afe_data_, data.data()); -} - -void AfeAudioProcessor::Start() { - xEventGroupSetBits(event_group_, PROCESSOR_RUNNING); -} - -void AfeAudioProcessor::Stop() { - xEventGroupClearBits(event_group_, PROCESSOR_RUNNING); - if (afe_data_ != nullptr) { - afe_iface_->reset_buffer(afe_data_); - } -} - -bool AfeAudioProcessor::IsRunning() { - return xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING; -} - -void AfeAudioProcessor::OnOutput(std::function&& data)> callback) { - output_callback_ = callback; -} - -void AfeAudioProcessor::OnVadStateChange(std::function callback) { - vad_state_change_callback_ = callback; -} - -void AfeAudioProcessor::AudioProcessorTask() { - auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_); - auto feed_size = afe_iface_->get_feed_chunksize(afe_data_); - ESP_LOGI(TAG, "Audio communication task started, feed size: %d fetch size: %d", - feed_size, fetch_size); - - while (true) { - xEventGroupWaitBits(event_group_, PROCESSOR_RUNNING, pdFALSE, pdTRUE, portMAX_DELAY); - - auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY); - if ((xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING) == 0) { - continue; - } - if (res == nullptr || res->ret_value == ESP_FAIL) { - if (res != nullptr) { - ESP_LOGI(TAG, "Error code: %d", res->ret_value); - } - continue; - } - - // VAD state change - if (vad_state_change_callback_) { - if (res->vad_state == VAD_SPEECH && !is_speaking_) { - is_speaking_ = true; - vad_state_change_callback_(true); - } else if (res->vad_state == VAD_SILENCE && is_speaking_) { - is_speaking_ = false; - vad_state_change_callback_(false); - } - } - - if (output_callback_) { - size_t samples = res->data_size / sizeof(int16_t); - - // Add data to buffer - output_buffer_.insert(output_buffer_.end(), res->data, res->data + samples); - - // Output complete frames when buffer has enough data - while (output_buffer_.size() >= frame_samples_) { - if (output_buffer_.size() == frame_samples_) { - // If buffer size equals frame size, move the entire buffer - output_callback_(std::move(output_buffer_)); - output_buffer_.clear(); - output_buffer_.reserve(frame_samples_); - } else { - // If buffer size exceeds frame size, copy one frame and remove it - output_callback_(std::vector(output_buffer_.begin(), output_buffer_.begin() + frame_samples_)); - output_buffer_.erase(output_buffer_.begin(), output_buffer_.begin() + frame_samples_); - } - } - } - } -} - -void AfeAudioProcessor::EnableDeviceAec(bool enable) { - if (enable) { -#if CONFIG_USE_DEVICE_AEC - afe_iface_->disable_vad(afe_data_); - afe_iface_->enable_aec(afe_data_); -#else - ESP_LOGE(TAG, "Device AEC is not supported"); -#endif - } else { - afe_iface_->disable_aec(afe_data_); - afe_iface_->enable_vad(afe_data_); - } -} +#include "afe_audio_processor.h" +#include + +#define PROCESSOR_RUNNING 0x01 + +#define TAG "AfeAudioProcessor" + +AfeAudioProcessor::AfeAudioProcessor() + : afe_data_(nullptr) { + event_group_ = xEventGroupCreate(); +} + +void AfeAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) { + codec_ = codec; + frame_samples_ = frame_duration_ms * 16000 / 1000; + + // Pre-allocate output buffer capacity + output_buffer_.reserve(frame_samples_); + + int ref_num = codec_->input_reference() ? 1 : 0; + + std::string input_format; + for (int i = 0; i < codec_->input_channels() - ref_num; i++) { + input_format.push_back('M'); + } + for (int i = 0; i < ref_num; i++) { + input_format.push_back('R'); + } + + srmodel_list_t *models; + if (models_list == nullptr) { + models = esp_srmodel_init("model"); + } else { + models = models_list; + } + + char* ns_model_name = esp_srmodel_filter(models, ESP_NSNET_PREFIX, NULL); + char* vad_model_name = esp_srmodel_filter(models, ESP_VADN_PREFIX, NULL); + + afe_config_t* afe_config = afe_config_init(input_format.c_str(), NULL, AFE_TYPE_VC, AFE_MODE_HIGH_PERF); + afe_config->aec_mode = AEC_MODE_VOIP_HIGH_PERF; + afe_config->vad_mode = VAD_MODE_0; + afe_config->vad_min_noise_ms = 100; + if (vad_model_name != nullptr) { + afe_config->vad_model_name = vad_model_name; + } + + if (ns_model_name != nullptr) { + afe_config->ns_init = true; + afe_config->ns_model_name = ns_model_name; + afe_config->afe_ns_mode = AFE_NS_MODE_NET; + } else { + afe_config->ns_init = false; + } + + afe_config->agc_init = false; + afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM; + +#ifdef CONFIG_USE_DEVICE_AEC + afe_config->aec_init = true; + afe_config->vad_init = false; +#else + afe_config->aec_init = false; + afe_config->vad_init = true; +#endif + + afe_iface_ = esp_afe_handle_from_config(afe_config); + afe_data_ = afe_iface_->create_from_config(afe_config); + + xTaskCreate([](void* arg) { + auto this_ = (AfeAudioProcessor*)arg; + this_->AudioProcessorTask(); + vTaskDelete(NULL); + }, "audio_communication", 4096, this, 3, NULL); +} + +AfeAudioProcessor::~AfeAudioProcessor() { + if (afe_data_ != nullptr) { + afe_iface_->destroy(afe_data_); + } + vEventGroupDelete(event_group_); +} + +size_t AfeAudioProcessor::GetFeedSize() { + if (afe_data_ == nullptr) { + return 0; + } + return afe_iface_->get_feed_chunksize(afe_data_); +} + +void AfeAudioProcessor::Feed(std::vector&& data) { + if (afe_data_ == nullptr) { + return; + } + afe_iface_->feed(afe_data_, data.data()); +} + +void AfeAudioProcessor::Start() { + xEventGroupSetBits(event_group_, PROCESSOR_RUNNING); +} + +void AfeAudioProcessor::Stop() { + xEventGroupClearBits(event_group_, PROCESSOR_RUNNING); + if (afe_data_ != nullptr) { + afe_iface_->reset_buffer(afe_data_); + } +} + +bool AfeAudioProcessor::IsRunning() { + return xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING; +} + +void AfeAudioProcessor::OnOutput(std::function&& data)> callback) { + output_callback_ = callback; +} + +void AfeAudioProcessor::OnVadStateChange(std::function callback) { + vad_state_change_callback_ = callback; +} + +void AfeAudioProcessor::AudioProcessorTask() { + auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_); + auto feed_size = afe_iface_->get_feed_chunksize(afe_data_); + ESP_LOGI(TAG, "Audio communication task started, feed size: %d fetch size: %d", + feed_size, fetch_size); + + while (true) { + xEventGroupWaitBits(event_group_, PROCESSOR_RUNNING, pdFALSE, pdTRUE, portMAX_DELAY); + + auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY); + if ((xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING) == 0) { + continue; + } + if (res == nullptr || res->ret_value == ESP_FAIL) { + if (res != nullptr) { + ESP_LOGI(TAG, "Error code: %d", res->ret_value); + } + continue; + } + + // VAD state change + if (vad_state_change_callback_) { + if (res->vad_state == VAD_SPEECH && !is_speaking_) { + is_speaking_ = true; + vad_state_change_callback_(true); + } else if (res->vad_state == VAD_SILENCE && is_speaking_) { + is_speaking_ = false; + vad_state_change_callback_(false); + } + } + + if (output_callback_) { + size_t samples = res->data_size / sizeof(int16_t); + + // Add data to buffer + output_buffer_.insert(output_buffer_.end(), res->data, res->data + samples); + + // Output complete frames when buffer has enough data + while (output_buffer_.size() >= frame_samples_) { + if (output_buffer_.size() == frame_samples_) { + // If buffer size equals frame size, move the entire buffer + output_callback_(std::move(output_buffer_)); + output_buffer_.clear(); + output_buffer_.reserve(frame_samples_); + } else { + // If buffer size exceeds frame size, copy one frame and remove it + output_callback_(std::vector(output_buffer_.begin(), output_buffer_.begin() + frame_samples_)); + output_buffer_.erase(output_buffer_.begin(), output_buffer_.begin() + frame_samples_); + } + } + } + } +} + +void AfeAudioProcessor::EnableDeviceAec(bool enable) { + if (enable) { +#if CONFIG_USE_DEVICE_AEC + afe_iface_->disable_vad(afe_data_); + afe_iface_->enable_aec(afe_data_); +#else + ESP_LOGE(TAG, "Device AEC is not supported"); +#endif + } else { + afe_iface_->disable_aec(afe_data_); + afe_iface_->enable_vad(afe_data_); + } +} diff --git a/main/audio/processors/afe_audio_processor.h b/main/audio/processors/afe_audio_processor.h index 38b1dad..0d89a3c 100644 --- a/main/audio/processors/afe_audio_processor.h +++ b/main/audio/processors/afe_audio_processor.h @@ -1,45 +1,45 @@ -#ifndef AFE_AUDIO_PROCESSOR_H -#define AFE_AUDIO_PROCESSOR_H - -#include -#include -#include -#include - -#include -#include -#include - -#include "audio_processor.h" -#include "audio_codec.h" - -class AfeAudioProcessor : public AudioProcessor { -public: - AfeAudioProcessor(); - ~AfeAudioProcessor(); - - void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) override; - void Feed(std::vector&& data) override; - void Start() override; - void Stop() override; - bool IsRunning() override; - void OnOutput(std::function&& data)> callback) override; - void OnVadStateChange(std::function callback) override; - size_t GetFeedSize() override; - void EnableDeviceAec(bool enable) override; - -private: - EventGroupHandle_t event_group_ = nullptr; - esp_afe_sr_iface_t* afe_iface_ = nullptr; - esp_afe_sr_data_t* afe_data_ = nullptr; - std::function&& data)> output_callback_; - std::function vad_state_change_callback_; - AudioCodec* codec_ = nullptr; - int frame_samples_ = 0; - bool is_speaking_ = false; - std::vector output_buffer_; - - void AudioProcessorTask(); -}; - +#ifndef AFE_AUDIO_PROCESSOR_H +#define AFE_AUDIO_PROCESSOR_H + +#include +#include +#include +#include + +#include +#include +#include + +#include "audio_processor.h" +#include "audio_codec.h" + +class AfeAudioProcessor : public AudioProcessor { +public: + AfeAudioProcessor(); + ~AfeAudioProcessor(); + + void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) override; + void Feed(std::vector&& data) override; + void Start() override; + void Stop() override; + bool IsRunning() override; + void OnOutput(std::function&& data)> callback) override; + void OnVadStateChange(std::function callback) override; + size_t GetFeedSize() override; + void EnableDeviceAec(bool enable) override; + +private: + EventGroupHandle_t event_group_ = nullptr; + esp_afe_sr_iface_t* afe_iface_ = nullptr; + esp_afe_sr_data_t* afe_data_ = nullptr; + std::function&& data)> output_callback_; + std::function vad_state_change_callback_; + AudioCodec* codec_ = nullptr; + int frame_samples_ = 0; + bool is_speaking_ = false; + std::vector output_buffer_; + + void AudioProcessorTask(); +}; + #endif \ No newline at end of file diff --git a/main/audio/processors/audio_debugger.cc b/main/audio/processors/audio_debugger.cc index 630057c..90f83b4 100644 --- a/main/audio/processors/audio_debugger.cc +++ b/main/audio/processors/audio_debugger.cc @@ -1,68 +1,68 @@ -#include "audio_debugger.h" -#include "sdkconfig.h" - -#if CONFIG_USE_AUDIO_DEBUGGER -#include -#include -#include -#include -#include -#include -#endif - -#define TAG "AudioDebugger" - - -AudioDebugger::AudioDebugger() { -#if CONFIG_USE_AUDIO_DEBUGGER - udp_sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); - if (udp_sockfd_ >= 0) { - // 解析配置的服务器地址 "IP:PORT" - std::string server_addr = CONFIG_AUDIO_DEBUG_UDP_SERVER; - size_t colon_pos = server_addr.find(':'); - - if (colon_pos != std::string::npos) { - std::string ip = server_addr.substr(0, colon_pos); - int port = std::stoi(server_addr.substr(colon_pos + 1)); - - memset(&udp_server_addr_, 0, sizeof(udp_server_addr_)); - udp_server_addr_.sin_family = AF_INET; - udp_server_addr_.sin_port = htons(port); - inet_pton(AF_INET, ip.c_str(), &udp_server_addr_.sin_addr); - - ESP_LOGI(TAG, "Initialized server address: %s", CONFIG_AUDIO_DEBUG_UDP_SERVER); - } else { - ESP_LOGW(TAG, "Invalid server address: %s, should be IP:PORT", CONFIG_AUDIO_DEBUG_UDP_SERVER); - close(udp_sockfd_); - udp_sockfd_ = -1; - } - } else { - ESP_LOGW(TAG, "Failed to create UDP socket: %d", errno); - } -#endif -} - -AudioDebugger::~AudioDebugger() { -#if CONFIG_USE_AUDIO_DEBUGGER - if (udp_sockfd_ >= 0) { - close(udp_sockfd_); - ESP_LOGI(TAG, "Closed UDP socket"); - } -#endif -} - -void AudioDebugger::Feed(const std::vector& data) { -#if CONFIG_USE_AUDIO_DEBUGGER - if (udp_sockfd_ >= 0) { - ssize_t sent = sendto(udp_sockfd_, data.data(), data.size() * sizeof(int16_t), 0, - (struct sockaddr*)&udp_server_addr_, sizeof(udp_server_addr_)); - if (sent < 0) { - ESP_LOGW(TAG, "Failed to send audio data to %s: %d", CONFIG_AUDIO_DEBUG_UDP_SERVER, errno); - } else { - ESP_LOGD(TAG, "Sent %d bytes audio data to %s", sent, CONFIG_AUDIO_DEBUG_UDP_SERVER); - } - } -#endif -} - +#include "audio_debugger.h" +#include "sdkconfig.h" + +#if CONFIG_USE_AUDIO_DEBUGGER +#include +#include +#include +#include +#include +#include +#endif + +#define TAG "AudioDebugger" + + +AudioDebugger::AudioDebugger() { +#if CONFIG_USE_AUDIO_DEBUGGER + udp_sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); + if (udp_sockfd_ >= 0) { + // 解析配置的服务器地址 "IP:PORT" + std::string server_addr = CONFIG_AUDIO_DEBUG_UDP_SERVER; + size_t colon_pos = server_addr.find(':'); + + if (colon_pos != std::string::npos) { + std::string ip = server_addr.substr(0, colon_pos); + int port = std::stoi(server_addr.substr(colon_pos + 1)); + + memset(&udp_server_addr_, 0, sizeof(udp_server_addr_)); + udp_server_addr_.sin_family = AF_INET; + udp_server_addr_.sin_port = htons(port); + inet_pton(AF_INET, ip.c_str(), &udp_server_addr_.sin_addr); + + ESP_LOGI(TAG, "Initialized server address: %s", CONFIG_AUDIO_DEBUG_UDP_SERVER); + } else { + ESP_LOGW(TAG, "Invalid server address: %s, should be IP:PORT", CONFIG_AUDIO_DEBUG_UDP_SERVER); + close(udp_sockfd_); + udp_sockfd_ = -1; + } + } else { + ESP_LOGW(TAG, "Failed to create UDP socket: %d", errno); + } +#endif +} + +AudioDebugger::~AudioDebugger() { +#if CONFIG_USE_AUDIO_DEBUGGER + if (udp_sockfd_ >= 0) { + close(udp_sockfd_); + ESP_LOGI(TAG, "Closed UDP socket"); + } +#endif +} + +void AudioDebugger::Feed(const std::vector& data) { +#if CONFIG_USE_AUDIO_DEBUGGER + if (udp_sockfd_ >= 0) { + ssize_t sent = sendto(udp_sockfd_, data.data(), data.size() * sizeof(int16_t), 0, + (struct sockaddr*)&udp_server_addr_, sizeof(udp_server_addr_)); + if (sent < 0) { + ESP_LOGW(TAG, "Failed to send audio data to %s: %d", CONFIG_AUDIO_DEBUG_UDP_SERVER, errno); + } else { + ESP_LOGD(TAG, "Sent %d bytes audio data to %s", sent, CONFIG_AUDIO_DEBUG_UDP_SERVER); + } + } +#endif +} + \ No newline at end of file diff --git a/main/audio/processors/audio_debugger.h b/main/audio/processors/audio_debugger.h index a81336c..6397271 100644 --- a/main/audio/processors/audio_debugger.h +++ b/main/audio/processors/audio_debugger.h @@ -1,22 +1,22 @@ -#ifndef AUDIO_DEBUGGER_H -#define AUDIO_DEBUGGER_H - -#include -#include - -#include -#include - -class AudioDebugger { -public: - AudioDebugger(); - ~AudioDebugger(); - - void Feed(const std::vector& data); - -private: - int udp_sockfd_ = -1; - struct sockaddr_in udp_server_addr_; -}; - +#ifndef AUDIO_DEBUGGER_H +#define AUDIO_DEBUGGER_H + +#include +#include + +#include +#include + +class AudioDebugger { +public: + AudioDebugger(); + ~AudioDebugger(); + + void Feed(const std::vector& data); + +private: + int udp_sockfd_ = -1; + struct sockaddr_in udp_server_addr_; +}; + #endif \ No newline at end of file diff --git a/main/audio/processors/no_audio_processor.cc b/main/audio/processors/no_audio_processor.cc index bac5bad..65bd3e6 100644 --- a/main/audio/processors/no_audio_processor.cc +++ b/main/audio/processors/no_audio_processor.cc @@ -1,59 +1,59 @@ -#include "no_audio_processor.h" -#include - -#define TAG "NoAudioProcessor" - -void NoAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) { - codec_ = codec; - frame_samples_ = frame_duration_ms * 16000 / 1000; -} - -void NoAudioProcessor::Feed(std::vector&& data) { - if (!is_running_ || !output_callback_) { - return; - } - - if (codec_->input_channels() == 2) { - // If input channels is 2, we need to fetch the left channel data - auto mono_data = std::vector(data.size() / 2); - for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) { - mono_data[i] = data[j]; - } - output_callback_(std::move(mono_data)); - } else { - output_callback_(std::move(data)); - } -} - -void NoAudioProcessor::Start() { - is_running_ = true; -} - -void NoAudioProcessor::Stop() { - is_running_ = false; -} - -bool NoAudioProcessor::IsRunning() { - return is_running_; -} - -void NoAudioProcessor::OnOutput(std::function&& data)> callback) { - output_callback_ = callback; -} - -void NoAudioProcessor::OnVadStateChange(std::function callback) { - vad_state_change_callback_ = callback; -} - -size_t NoAudioProcessor::GetFeedSize() { - if (!codec_) { - return 0; - } - return frame_samples_; -} - -void NoAudioProcessor::EnableDeviceAec(bool enable) { - if (enable) { - ESP_LOGE(TAG, "Device AEC is not supported"); - } -} +#include "no_audio_processor.h" +#include + +#define TAG "NoAudioProcessor" + +void NoAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) { + codec_ = codec; + frame_samples_ = frame_duration_ms * 16000 / 1000; +} + +void NoAudioProcessor::Feed(std::vector&& data) { + if (!is_running_ || !output_callback_) { + return; + } + + if (codec_->input_channels() == 2) { + // If input channels is 2, we need to fetch the left channel data + auto mono_data = std::vector(data.size() / 2); + for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) { + mono_data[i] = data[j]; + } + output_callback_(std::move(mono_data)); + } else { + output_callback_(std::move(data)); + } +} + +void NoAudioProcessor::Start() { + is_running_ = true; +} + +void NoAudioProcessor::Stop() { + is_running_ = false; +} + +bool NoAudioProcessor::IsRunning() { + return is_running_; +} + +void NoAudioProcessor::OnOutput(std::function&& data)> callback) { + output_callback_ = callback; +} + +void NoAudioProcessor::OnVadStateChange(std::function callback) { + vad_state_change_callback_ = callback; +} + +size_t NoAudioProcessor::GetFeedSize() { + if (!codec_) { + return 0; + } + return frame_samples_; +} + +void NoAudioProcessor::EnableDeviceAec(bool enable) { + if (enable) { + ESP_LOGE(TAG, "Device AEC is not supported"); + } +} diff --git a/main/audio/processors/no_audio_processor.h b/main/audio/processors/no_audio_processor.h index d326d50..6413490 100644 --- a/main/audio/processors/no_audio_processor.h +++ b/main/audio/processors/no_audio_processor.h @@ -1,33 +1,33 @@ -#ifndef DUMMY_AUDIO_PROCESSOR_H -#define DUMMY_AUDIO_PROCESSOR_H - -#include -#include - -#include "audio_processor.h" -#include "audio_codec.h" - -class NoAudioProcessor : public AudioProcessor { -public: - NoAudioProcessor() = default; - ~NoAudioProcessor() = default; - - void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) override; - void Feed(std::vector&& data) override; - void Start() override; - void Stop() override; - bool IsRunning() override; - void OnOutput(std::function&& data)> callback) override; - void OnVadStateChange(std::function callback) override; - size_t GetFeedSize() override; - void EnableDeviceAec(bool enable) override; - -private: - AudioCodec* codec_ = nullptr; - int frame_samples_ = 0; - std::function&& data)> output_callback_; - std::function vad_state_change_callback_; - bool is_running_ = false; -}; - +#ifndef DUMMY_AUDIO_PROCESSOR_H +#define DUMMY_AUDIO_PROCESSOR_H + +#include +#include + +#include "audio_processor.h" +#include "audio_codec.h" + +class NoAudioProcessor : public AudioProcessor { +public: + NoAudioProcessor() = default; + ~NoAudioProcessor() = default; + + void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) override; + void Feed(std::vector&& data) override; + void Start() override; + void Stop() override; + bool IsRunning() override; + void OnOutput(std::function&& data)> callback) override; + void OnVadStateChange(std::function callback) override; + size_t GetFeedSize() override; + void EnableDeviceAec(bool enable) override; + +private: + AudioCodec* codec_ = nullptr; + int frame_samples_ = 0; + std::function&& data)> output_callback_; + std::function vad_state_change_callback_; + bool is_running_ = false; +}; + #endif \ No newline at end of file diff --git a/main/audio/wake_word.h b/main/audio/wake_word.h index 9b8986a..3570609 100644 --- a/main/audio/wake_word.h +++ b/main/audio/wake_word.h @@ -1,26 +1,26 @@ -#ifndef WAKE_WORD_H -#define WAKE_WORD_H - -#include -#include -#include - -#include -#include "audio_codec.h" - -class WakeWord { -public: - virtual ~WakeWord() = default; - - virtual bool Initialize(AudioCodec* codec, srmodel_list_t* models_list) = 0; - virtual void Feed(const std::vector& data) = 0; - virtual void OnWakeWordDetected(std::function callback) = 0; - virtual void Start() = 0; - virtual void Stop() = 0; - virtual size_t GetFeedSize() = 0; - virtual void EncodeWakeWordData() = 0; - virtual bool GetWakeWordOpus(std::vector& opus) = 0; - virtual const std::string& GetLastDetectedWakeWord() const = 0; -}; - -#endif +#ifndef WAKE_WORD_H +#define WAKE_WORD_H + +#include +#include +#include + +#include +#include "audio_codec.h" + +class WakeWord { +public: + virtual ~WakeWord() = default; + + virtual bool Initialize(AudioCodec* codec, srmodel_list_t* models_list) = 0; + virtual void Feed(const std::vector& data) = 0; + virtual void OnWakeWordDetected(std::function callback) = 0; + virtual void Start() = 0; + virtual void Stop() = 0; + virtual size_t GetFeedSize() = 0; + virtual void EncodeWakeWordData() = 0; + virtual bool GetWakeWordOpus(std::vector& opus) = 0; + virtual const std::string& GetLastDetectedWakeWord() const = 0; +}; + +#endif diff --git a/main/audio/wake_words/afe_wake_word.cc b/main/audio/wake_words/afe_wake_word.cc index bcdc697..a1b9644 100644 --- a/main/audio/wake_words/afe_wake_word.cc +++ b/main/audio/wake_words/afe_wake_word.cc @@ -1,208 +1,208 @@ -#include "afe_wake_word.h" -#include "audio_service.h" - -#include -#include - -#define DETECTION_RUNNING_EVENT 1 - -#define TAG "AfeWakeWord" - -AfeWakeWord::AfeWakeWord() - : afe_data_(nullptr), - wake_word_pcm_(), - wake_word_opus_() { - - event_group_ = xEventGroupCreate(); -} - -AfeWakeWord::~AfeWakeWord() { - if (afe_data_ != nullptr) { - afe_iface_->destroy(afe_data_); - } - - if (wake_word_encode_task_stack_ != nullptr) { - heap_caps_free(wake_word_encode_task_stack_); - } - - if (wake_word_encode_task_buffer_ != nullptr) { - heap_caps_free(wake_word_encode_task_buffer_); - } - - if (models_ != nullptr) { - esp_srmodel_deinit(models_); - } - - vEventGroupDelete(event_group_); -} - -bool AfeWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) { - codec_ = codec; - int ref_num = codec_->input_reference() ? 1 : 0; - - if (models_list == nullptr) { - models_ = esp_srmodel_init("model"); - } else { - models_ = models_list; - } - - if (models_ == nullptr || models_->num == -1) { - ESP_LOGE(TAG, "Failed to initialize wakenet model"); - return false; - } - for (int i = 0; i < models_->num; i++) { - ESP_LOGI(TAG, "Model %d: %s", i, models_->model_name[i]); - if (strstr(models_->model_name[i], ESP_WN_PREFIX) != NULL) { - wakenet_model_ = models_->model_name[i]; - auto words = esp_srmodel_get_wake_words(models_, wakenet_model_); - // split by ";" to get all wake words - std::stringstream ss(words); - std::string word; - while (std::getline(ss, word, ';')) { - wake_words_.push_back(word); - } - } - } - - std::string input_format; - for (int i = 0; i < codec_->input_channels() - ref_num; i++) { - input_format.push_back('M'); - } - for (int i = 0; i < ref_num; i++) { - input_format.push_back('R'); - } - afe_config_t* afe_config = afe_config_init(input_format.c_str(), models_, AFE_TYPE_SR, AFE_MODE_HIGH_PERF); - afe_config->aec_init = codec_->input_reference(); - afe_config->aec_mode = AEC_MODE_SR_HIGH_PERF; - afe_config->afe_perferred_core = 1; - afe_config->afe_perferred_priority = 1; - afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM; - - afe_iface_ = esp_afe_handle_from_config(afe_config); - afe_data_ = afe_iface_->create_from_config(afe_config); - - xTaskCreate([](void* arg) { - auto this_ = (AfeWakeWord*)arg; - this_->AudioDetectionTask(); - vTaskDelete(NULL); - }, "audio_detection", 4096, this, 3, nullptr); - - return true; -} - -void AfeWakeWord::OnWakeWordDetected(std::function callback) { - wake_word_detected_callback_ = callback; -} - -void AfeWakeWord::Start() { - xEventGroupSetBits(event_group_, DETECTION_RUNNING_EVENT); -} - -void AfeWakeWord::Stop() { - xEventGroupClearBits(event_group_, DETECTION_RUNNING_EVENT); - if (afe_data_ != nullptr) { - afe_iface_->reset_buffer(afe_data_); - } -} - -void AfeWakeWord::Feed(const std::vector& data) { - if (afe_data_ == nullptr) { - return; - } - afe_iface_->feed(afe_data_, data.data()); -} - -size_t AfeWakeWord::GetFeedSize() { - if (afe_data_ == nullptr) { - return 0; - } - return afe_iface_->get_feed_chunksize(afe_data_); -} - -void AfeWakeWord::AudioDetectionTask() { - auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_); - auto feed_size = afe_iface_->get_feed_chunksize(afe_data_); - ESP_LOGI(TAG, "Audio detection task started, feed size: %d fetch size: %d", - feed_size, fetch_size); - - while (true) { - xEventGroupWaitBits(event_group_, DETECTION_RUNNING_EVENT, pdFALSE, pdTRUE, portMAX_DELAY); - - auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY); - if (res == nullptr || res->ret_value == ESP_FAIL) { - continue;; - } - - // Store the wake word data for voice recognition, like who is speaking - StoreWakeWordData(res->data, res->data_size / sizeof(int16_t)); - - if (res->wakeup_state == WAKENET_DETECTED) { - Stop(); - last_detected_wake_word_ = wake_words_[res->wakenet_model_index - 1]; - - if (wake_word_detected_callback_) { - wake_word_detected_callback_(last_detected_wake_word_); - } - } - } -} - -void AfeWakeWord::StoreWakeWordData(const int16_t* data, size_t samples) { - // store audio data to wake_word_pcm_ - wake_word_pcm_.emplace_back(std::vector(data, data + samples)); - // keep about 2 seconds of data, detect duration is 30ms (sample_rate == 16000, chunksize == 512) - while (wake_word_pcm_.size() > 2000 / 30) { - wake_word_pcm_.pop_front(); - } -} - -void AfeWakeWord::EncodeWakeWordData() { - const size_t stack_size = 4096 * 7; - wake_word_opus_.clear(); - if (wake_word_encode_task_stack_ == nullptr) { - wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM); - assert(wake_word_encode_task_stack_ != nullptr); - } - if (wake_word_encode_task_buffer_ == nullptr) { - wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL); - assert(wake_word_encode_task_buffer_ != nullptr); - } - - wake_word_encode_task_ = xTaskCreateStatic([](void* arg) { - auto this_ = (AfeWakeWord*)arg; - { - auto start_time = esp_timer_get_time(); - auto encoder = std::make_unique(16000, 1, OPUS_FRAME_DURATION_MS); - encoder->SetComplexity(0); // 0 is the fastest - - int packets = 0; - for (auto& pcm: this_->wake_word_pcm_) { - encoder->Encode(std::move(pcm), [this_](std::vector&& opus) { - std::lock_guard lock(this_->wake_word_mutex_); - this_->wake_word_opus_.emplace_back(std::move(opus)); - this_->wake_word_cv_.notify_all(); - }); - packets++; - } - this_->wake_word_pcm_.clear(); - - auto end_time = esp_timer_get_time(); - ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000)); - - std::lock_guard lock(this_->wake_word_mutex_); - this_->wake_word_opus_.push_back(std::vector()); - this_->wake_word_cv_.notify_all(); - } - vTaskDelete(NULL); - }, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_); -} - -bool AfeWakeWord::GetWakeWordOpus(std::vector& opus) { - std::unique_lock lock(wake_word_mutex_); - wake_word_cv_.wait(lock, [this]() { - return !wake_word_opus_.empty(); - }); - opus.swap(wake_word_opus_.front()); - wake_word_opus_.pop_front(); - return !opus.empty(); -} +#include "afe_wake_word.h" +#include "audio_service.h" + +#include +#include + +#define DETECTION_RUNNING_EVENT 1 + +#define TAG "AfeWakeWord" + +AfeWakeWord::AfeWakeWord() + : afe_data_(nullptr), + wake_word_pcm_(), + wake_word_opus_() { + + event_group_ = xEventGroupCreate(); +} + +AfeWakeWord::~AfeWakeWord() { + if (afe_data_ != nullptr) { + afe_iface_->destroy(afe_data_); + } + + if (wake_word_encode_task_stack_ != nullptr) { + heap_caps_free(wake_word_encode_task_stack_); + } + + if (wake_word_encode_task_buffer_ != nullptr) { + heap_caps_free(wake_word_encode_task_buffer_); + } + + if (models_ != nullptr) { + esp_srmodel_deinit(models_); + } + + vEventGroupDelete(event_group_); +} + +bool AfeWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) { + codec_ = codec; + int ref_num = codec_->input_reference() ? 1 : 0; + + if (models_list == nullptr) { + models_ = esp_srmodel_init("model"); + } else { + models_ = models_list; + } + + if (models_ == nullptr || models_->num == -1) { + ESP_LOGE(TAG, "Failed to initialize wakenet model"); + return false; + } + for (int i = 0; i < models_->num; i++) { + ESP_LOGI(TAG, "Model %d: %s", i, models_->model_name[i]); + if (strstr(models_->model_name[i], ESP_WN_PREFIX) != NULL) { + wakenet_model_ = models_->model_name[i]; + auto words = esp_srmodel_get_wake_words(models_, wakenet_model_); + // split by ";" to get all wake words + std::stringstream ss(words); + std::string word; + while (std::getline(ss, word, ';')) { + wake_words_.push_back(word); + } + } + } + + std::string input_format; + for (int i = 0; i < codec_->input_channels() - ref_num; i++) { + input_format.push_back('M'); + } + for (int i = 0; i < ref_num; i++) { + input_format.push_back('R'); + } + afe_config_t* afe_config = afe_config_init(input_format.c_str(), models_, AFE_TYPE_SR, AFE_MODE_HIGH_PERF); + afe_config->aec_init = codec_->input_reference(); + afe_config->aec_mode = AEC_MODE_SR_HIGH_PERF; + afe_config->afe_perferred_core = 1; + afe_config->afe_perferred_priority = 1; + afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM; + + afe_iface_ = esp_afe_handle_from_config(afe_config); + afe_data_ = afe_iface_->create_from_config(afe_config); + + xTaskCreate([](void* arg) { + auto this_ = (AfeWakeWord*)arg; + this_->AudioDetectionTask(); + vTaskDelete(NULL); + }, "audio_detection", 4096, this, 3, nullptr); + + return true; +} + +void AfeWakeWord::OnWakeWordDetected(std::function callback) { + wake_word_detected_callback_ = callback; +} + +void AfeWakeWord::Start() { + xEventGroupSetBits(event_group_, DETECTION_RUNNING_EVENT); +} + +void AfeWakeWord::Stop() { + xEventGroupClearBits(event_group_, DETECTION_RUNNING_EVENT); + if (afe_data_ != nullptr) { + afe_iface_->reset_buffer(afe_data_); + } +} + +void AfeWakeWord::Feed(const std::vector& data) { + if (afe_data_ == nullptr) { + return; + } + afe_iface_->feed(afe_data_, data.data()); +} + +size_t AfeWakeWord::GetFeedSize() { + if (afe_data_ == nullptr) { + return 0; + } + return afe_iface_->get_feed_chunksize(afe_data_); +} + +void AfeWakeWord::AudioDetectionTask() { + auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_); + auto feed_size = afe_iface_->get_feed_chunksize(afe_data_); + ESP_LOGI(TAG, "Audio detection task started, feed size: %d fetch size: %d", + feed_size, fetch_size); + + while (true) { + xEventGroupWaitBits(event_group_, DETECTION_RUNNING_EVENT, pdFALSE, pdTRUE, portMAX_DELAY); + + auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY); + if (res == nullptr || res->ret_value == ESP_FAIL) { + continue;; + } + + // Store the wake word data for voice recognition, like who is speaking + StoreWakeWordData(res->data, res->data_size / sizeof(int16_t)); + + if (res->wakeup_state == WAKENET_DETECTED) { + Stop(); + last_detected_wake_word_ = wake_words_[res->wakenet_model_index - 1]; + + if (wake_word_detected_callback_) { + wake_word_detected_callback_(last_detected_wake_word_); + } + } + } +} + +void AfeWakeWord::StoreWakeWordData(const int16_t* data, size_t samples) { + // store audio data to wake_word_pcm_ + wake_word_pcm_.emplace_back(std::vector(data, data + samples)); + // keep about 2 seconds of data, detect duration is 30ms (sample_rate == 16000, chunksize == 512) + while (wake_word_pcm_.size() > 2000 / 30) { + wake_word_pcm_.pop_front(); + } +} + +void AfeWakeWord::EncodeWakeWordData() { + const size_t stack_size = 4096 * 7; + wake_word_opus_.clear(); + if (wake_word_encode_task_stack_ == nullptr) { + wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM); + assert(wake_word_encode_task_stack_ != nullptr); + } + if (wake_word_encode_task_buffer_ == nullptr) { + wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL); + assert(wake_word_encode_task_buffer_ != nullptr); + } + + wake_word_encode_task_ = xTaskCreateStatic([](void* arg) { + auto this_ = (AfeWakeWord*)arg; + { + auto start_time = esp_timer_get_time(); + auto encoder = std::make_unique(16000, 1, OPUS_FRAME_DURATION_MS); + encoder->SetComplexity(0); // 0 is the fastest + + int packets = 0; + for (auto& pcm: this_->wake_word_pcm_) { + encoder->Encode(std::move(pcm), [this_](std::vector&& opus) { + std::lock_guard lock(this_->wake_word_mutex_); + this_->wake_word_opus_.emplace_back(std::move(opus)); + this_->wake_word_cv_.notify_all(); + }); + packets++; + } + this_->wake_word_pcm_.clear(); + + auto end_time = esp_timer_get_time(); + ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000)); + + std::lock_guard lock(this_->wake_word_mutex_); + this_->wake_word_opus_.push_back(std::vector()); + this_->wake_word_cv_.notify_all(); + } + vTaskDelete(NULL); + }, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_); +} + +bool AfeWakeWord::GetWakeWordOpus(std::vector& opus) { + std::unique_lock lock(wake_word_mutex_); + wake_word_cv_.wait(lock, [this]() { + return !wake_word_opus_.empty(); + }); + opus.swap(wake_word_opus_.front()); + wake_word_opus_.pop_front(); + return !opus.empty(); +} diff --git a/main/audio/wake_words/afe_wake_word.h b/main/audio/wake_words/afe_wake_word.h index 65ac1f8..1818dc2 100644 --- a/main/audio/wake_words/afe_wake_word.h +++ b/main/audio/wake_words/afe_wake_word.h @@ -1,60 +1,60 @@ -#ifndef AFE_WAKE_WORD_H -#define AFE_WAKE_WORD_H - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "audio_codec.h" -#include "wake_word.h" - -class AfeWakeWord : public WakeWord { -public: - AfeWakeWord(); - ~AfeWakeWord(); - - bool Initialize(AudioCodec* codec, srmodel_list_t* models_list); - void Feed(const std::vector& data); - void OnWakeWordDetected(std::function callback); - void Start(); - void Stop(); - size_t GetFeedSize(); - void EncodeWakeWordData(); - bool GetWakeWordOpus(std::vector& opus); - const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; } - -private: - srmodel_list_t *models_ = nullptr; - esp_afe_sr_iface_t* afe_iface_ = nullptr; - esp_afe_sr_data_t* afe_data_ = nullptr; - char* wakenet_model_ = NULL; - std::vector wake_words_; - EventGroupHandle_t event_group_; - std::function wake_word_detected_callback_; - AudioCodec* codec_ = nullptr; - std::string last_detected_wake_word_; - - TaskHandle_t wake_word_encode_task_ = nullptr; - StaticTask_t* wake_word_encode_task_buffer_ = nullptr; - StackType_t* wake_word_encode_task_stack_ = nullptr; - std::deque> wake_word_pcm_; - std::deque> wake_word_opus_; - std::mutex wake_word_mutex_; - std::condition_variable wake_word_cv_; - - void StoreWakeWordData(const int16_t* data, size_t size); - void AudioDetectionTask(); -}; - -#endif +#ifndef AFE_WAKE_WORD_H +#define AFE_WAKE_WORD_H + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "audio_codec.h" +#include "wake_word.h" + +class AfeWakeWord : public WakeWord { +public: + AfeWakeWord(); + ~AfeWakeWord(); + + bool Initialize(AudioCodec* codec, srmodel_list_t* models_list); + void Feed(const std::vector& data); + void OnWakeWordDetected(std::function callback); + void Start(); + void Stop(); + size_t GetFeedSize(); + void EncodeWakeWordData(); + bool GetWakeWordOpus(std::vector& opus); + const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; } + +private: + srmodel_list_t *models_ = nullptr; + esp_afe_sr_iface_t* afe_iface_ = nullptr; + esp_afe_sr_data_t* afe_data_ = nullptr; + char* wakenet_model_ = NULL; + std::vector wake_words_; + EventGroupHandle_t event_group_; + std::function wake_word_detected_callback_; + AudioCodec* codec_ = nullptr; + std::string last_detected_wake_word_; + + TaskHandle_t wake_word_encode_task_ = nullptr; + StaticTask_t* wake_word_encode_task_buffer_ = nullptr; + StackType_t* wake_word_encode_task_stack_ = nullptr; + std::deque> wake_word_pcm_; + std::deque> wake_word_opus_; + std::mutex wake_word_mutex_; + std::condition_variable wake_word_cv_; + + void StoreWakeWordData(const int16_t* data, size_t size); + void AudioDetectionTask(); +}; + +#endif diff --git a/main/audio/wake_words/custom_wake_word.cc b/main/audio/wake_words/custom_wake_word.cc index 7c7f3fb..f33e783 100644 --- a/main/audio/wake_words/custom_wake_word.cc +++ b/main/audio/wake_words/custom_wake_word.cc @@ -1,190 +1,250 @@ -#include "custom_wake_word.h" -#include "audio_service.h" -#include "system_info.h" - -#include -#include "esp_mn_iface.h" -#include "esp_mn_models.h" -#include "esp_mn_speech_commands.h" - - -#define TAG "CustomWakeWord" - - -CustomWakeWord::CustomWakeWord() - : wake_word_pcm_(), wake_word_opus_() { -} - -CustomWakeWord::~CustomWakeWord() { - if (multinet_model_data_ != nullptr && multinet_ != nullptr) { - multinet_->destroy(multinet_model_data_); - multinet_model_data_ = nullptr; - } - - if (wake_word_encode_task_stack_ != nullptr) { - heap_caps_free(wake_word_encode_task_stack_); - } - - if (wake_word_encode_task_buffer_ != nullptr) { - heap_caps_free(wake_word_encode_task_buffer_); - } - - if (models_ != nullptr) { - esp_srmodel_deinit(models_); - } -} - -bool CustomWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) { - codec_ = codec; - - if (models_list == nullptr) { - models_ = esp_srmodel_init("model"); - } else { - models_ = models_list; - } - - if (models_ == nullptr || models_->num == -1) { - ESP_LOGE(TAG, "Failed to initialize wakenet model"); - return false; - } - - // 初始化 multinet (命令词识别) - mn_name_ = esp_srmodel_filter(models_, ESP_MN_PREFIX, ESP_MN_CHINESE); - if (mn_name_ == nullptr) { - ESP_LOGE(TAG, "Failed to initialize multinet, mn_name is nullptr"); - ESP_LOGI(TAG, "Please refer to https://pcn7cs20v8cr.feishu.cn/wiki/CpQjwQsCJiQSWSkYEvrcxcbVnwh to add custom wake word"); - return false; - } - - ESP_LOGI(TAG, "multinet: %s", mn_name_); - multinet_ = esp_mn_handle_from_name(mn_name_); - multinet_model_data_ = multinet_->create(mn_name_, 3000); // 3 秒超时 - multinet_->set_det_threshold(multinet_model_data_, CONFIG_CUSTOM_WAKE_WORD_THRESHOLD / 100.0f); - esp_mn_commands_clear(); - esp_mn_commands_add(1, CONFIG_CUSTOM_WAKE_WORD); - esp_mn_commands_update(); - - multinet_->print_active_speech_commands(multinet_model_data_); - return true; -} - -void CustomWakeWord::OnWakeWordDetected(std::function callback) { - wake_word_detected_callback_ = callback; -} - -void CustomWakeWord::Start() { - running_ = true; -} - -void CustomWakeWord::Stop() { - running_ = false; -} - -void CustomWakeWord::Feed(const std::vector& data) { - if (multinet_model_data_ == nullptr || !running_) { - return; - } - - esp_mn_state_t mn_state; - // If input channels is 2, we need to fetch the left channel data - if (codec_->input_channels() == 2) { - auto mono_data = std::vector(data.size() / 2); - for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) { - mono_data[i] = data[j]; - } - - StoreWakeWordData(mono_data); - mn_state = multinet_->detect(multinet_model_data_, const_cast(mono_data.data())); - } else { - StoreWakeWordData(data); - mn_state = multinet_->detect(multinet_model_data_, const_cast(data.data())); - } - - if (mn_state == ESP_MN_STATE_DETECTING) { - return; - } else if (mn_state == ESP_MN_STATE_DETECTED) { - esp_mn_results_t *mn_result = multinet_->get_results(multinet_model_data_); - ESP_LOGI(TAG, "Custom wake word detected: command_id=%d, string=%s, prob=%f", - mn_result->command_id[0], mn_result->string, mn_result->prob[0]); - - if (mn_result->command_id[0] == 1) { - last_detected_wake_word_ = CONFIG_CUSTOM_WAKE_WORD_DISPLAY; - } - running_ = false; - - if (wake_word_detected_callback_) { - wake_word_detected_callback_(last_detected_wake_word_); - } - multinet_->clean(multinet_model_data_); - } else if (mn_state == ESP_MN_STATE_TIMEOUT) { - ESP_LOGD(TAG, "Command word detection timeout, cleaning state"); - multinet_->clean(multinet_model_data_); - } -} - -size_t CustomWakeWord::GetFeedSize() { - if (multinet_model_data_ == nullptr) { - return 0; - } - return multinet_->get_samp_chunksize(multinet_model_data_); -} - -void CustomWakeWord::StoreWakeWordData(const std::vector& data) { - // store audio data to wake_word_pcm_ - wake_word_pcm_.push_back(data); - // keep about 2 seconds of data, detect duration is 30ms (sample_rate == 16000, chunksize == 512) - while (wake_word_pcm_.size() > 2000 / 30) { - wake_word_pcm_.pop_front(); - } -} - -void CustomWakeWord::EncodeWakeWordData() { - const size_t stack_size = 4096 * 7; - wake_word_opus_.clear(); - if (wake_word_encode_task_stack_ == nullptr) { - wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM); - assert(wake_word_encode_task_stack_ != nullptr); - } - if (wake_word_encode_task_buffer_ == nullptr) { - wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL); - assert(wake_word_encode_task_buffer_ != nullptr); - } - - wake_word_encode_task_ = xTaskCreateStatic([](void* arg) { - auto this_ = (CustomWakeWord*)arg; - { - auto start_time = esp_timer_get_time(); - auto encoder = std::make_unique(16000, 1, OPUS_FRAME_DURATION_MS); - encoder->SetComplexity(0); // 0 is the fastest - - int packets = 0; - for (auto& pcm: this_->wake_word_pcm_) { - encoder->Encode(std::move(pcm), [this_](std::vector&& opus) { - std::lock_guard lock(this_->wake_word_mutex_); - this_->wake_word_opus_.emplace_back(std::move(opus)); - this_->wake_word_cv_.notify_all(); - }); - packets++; - } - this_->wake_word_pcm_.clear(); - - auto end_time = esp_timer_get_time(); - ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000)); - - std::lock_guard lock(this_->wake_word_mutex_); - this_->wake_word_opus_.push_back(std::vector()); - this_->wake_word_cv_.notify_all(); - } - vTaskDelete(NULL); - }, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_); -} - -bool CustomWakeWord::GetWakeWordOpus(std::vector& opus) { - std::unique_lock lock(wake_word_mutex_); - wake_word_cv_.wait(lock, [this]() { - return !wake_word_opus_.empty(); - }); - opus.swap(wake_word_opus_.front()); - wake_word_opus_.pop_front(); - return !opus.empty(); -} +#include "custom_wake_word.h" +#include "audio_service.h" +#include "system_info.h" +#include "assets.h" + +#include +#include +#include +#include +#include + + +#define TAG "CustomWakeWord" + + +CustomWakeWord::CustomWakeWord() + : wake_word_pcm_(), wake_word_opus_() { +} + +CustomWakeWord::~CustomWakeWord() { + if (multinet_model_data_ != nullptr && multinet_ != nullptr) { + multinet_->destroy(multinet_model_data_); + multinet_model_data_ = nullptr; + } + + if (wake_word_encode_task_stack_ != nullptr) { + heap_caps_free(wake_word_encode_task_stack_); + } + + if (wake_word_encode_task_buffer_ != nullptr) { + heap_caps_free(wake_word_encode_task_buffer_); + } + + if (models_ != nullptr) { + esp_srmodel_deinit(models_); + } +} + +void CustomWakeWord::ParseWakenetModelConfig() { + // Read index.json + auto& assets = Assets::GetInstance(); + void* ptr = nullptr; + size_t size = 0; + if (!assets.GetAssetData("index.json", ptr, size)) { + ESP_LOGE(TAG, "Failed to read index.json"); + return; + } + cJSON* root = cJSON_ParseWithLength(static_cast(ptr), size); + if (root == nullptr) { + ESP_LOGE(TAG, "Failed to parse index.json"); + return; + } + cJSON* multinet_model = cJSON_GetObjectItem(root, "multinet_model"); + if (cJSON_IsObject(multinet_model)) { + cJSON* language = cJSON_GetObjectItem(multinet_model, "language"); + cJSON* duration = cJSON_GetObjectItem(multinet_model, "duration"); + cJSON* threshold = cJSON_GetObjectItem(multinet_model, "threshold"); + cJSON* commands = cJSON_GetObjectItem(multinet_model, "commands"); + if (cJSON_IsString(language)) { + language_ = language->valuestring; + } + if (cJSON_IsNumber(duration)) { + duration_ = duration->valueint; + } + if (cJSON_IsNumber(threshold)) { + threshold_ = threshold->valuedouble; + } + if (cJSON_IsArray(commands)) { + for (int i = 0; i < cJSON_GetArraySize(commands); i++) { + cJSON* command = cJSON_GetArrayItem(commands, i); + if (cJSON_IsObject(command)) { + cJSON* command_name = cJSON_GetObjectItem(command, "command"); + cJSON* text = cJSON_GetObjectItem(command, "text"); + cJSON* action = cJSON_GetObjectItem(command, "action"); + if (cJSON_IsString(command_name) && cJSON_IsString(text) && cJSON_IsString(action)) { + commands_.push_back({command_name->valuestring, text->valuestring, action->valuestring}); + ESP_LOGI(TAG, "Command: %s, Text: %s, Action: %s", command_name->valuestring, text->valuestring, action->valuestring); + } + } + } + } + } + cJSON_Delete(root); +} + + +bool CustomWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) { + codec_ = codec; + commands_.clear(); + + if (models_list == nullptr) { + language_ = "cn"; + models_ = esp_srmodel_init("model"); +#ifdef CONFIG_CUSTOM_WAKE_WORD + threshold_ = CONFIG_CUSTOM_WAKE_WORD_THRESHOLD / 100.0f; + commands_.push_back({CONFIG_CUSTOM_WAKE_WORD, CONFIG_CUSTOM_WAKE_WORD_DISPLAY, "wake"}); +#endif + } else { + models_ = models_list; + ParseWakenetModelConfig(); + } + + if (models_ == nullptr || models_->num == -1) { + ESP_LOGE(TAG, "Failed to initialize wakenet model"); + return false; + } + + // 初始化 multinet (命令词识别) + mn_name_ = esp_srmodel_filter(models_, ESP_MN_PREFIX, language_.c_str()); + if (mn_name_ == nullptr) { + ESP_LOGE(TAG, "Failed to initialize multinet, mn_name is nullptr"); + ESP_LOGI(TAG, "Please refer to https://pcn7cs20v8cr.feishu.cn/wiki/CpQjwQsCJiQSWSkYEvrcxcbVnwh to add custom wake word"); + return false; + } + + multinet_ = esp_mn_handle_from_name(mn_name_); + multinet_model_data_ = multinet_->create(mn_name_, duration_); + multinet_->set_det_threshold(multinet_model_data_, threshold_); + esp_mn_commands_clear(); + for (int i = 0; i < commands_.size(); i++) { + esp_mn_commands_add(i + 1, commands_[i].command.c_str()); + } + esp_mn_commands_update(); + + multinet_->print_active_speech_commands(multinet_model_data_); + return true; +} + +void CustomWakeWord::OnWakeWordDetected(std::function callback) { + wake_word_detected_callback_ = callback; +} + +void CustomWakeWord::Start() { + running_ = true; +} + +void CustomWakeWord::Stop() { + running_ = false; +} + +void CustomWakeWord::Feed(const std::vector& data) { + if (multinet_model_data_ == nullptr || !running_) { + return; + } + + esp_mn_state_t mn_state; + // If input channels is 2, we need to fetch the left channel data + if (codec_->input_channels() == 2) { + auto mono_data = std::vector(data.size() / 2); + for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) { + mono_data[i] = data[j]; + } + + StoreWakeWordData(mono_data); + mn_state = multinet_->detect(multinet_model_data_, const_cast(mono_data.data())); + } else { + StoreWakeWordData(data); + mn_state = multinet_->detect(multinet_model_data_, const_cast(data.data())); + } + + if (mn_state == ESP_MN_STATE_DETECTING) { + return; + } else if (mn_state == ESP_MN_STATE_DETECTED) { + esp_mn_results_t *mn_result = multinet_->get_results(multinet_model_data_); + for (int i = 0; i < mn_result->num && running_; i++) { + ESP_LOGI(TAG, "Custom wake word detected: command_id=%d, string=%s, prob=%f", + mn_result->command_id[i], mn_result->string, mn_result->prob[i]); + auto& command = commands_[mn_result->command_id[i] - 1]; + if (command.action == "wake") { + last_detected_wake_word_ = command.text; + running_ = false; + + if (wake_word_detected_callback_) { + wake_word_detected_callback_(last_detected_wake_word_); + } + } + } + multinet_->clean(multinet_model_data_); + } else if (mn_state == ESP_MN_STATE_TIMEOUT) { + ESP_LOGD(TAG, "Command word detection timeout, cleaning state"); + multinet_->clean(multinet_model_data_); + } +} + +size_t CustomWakeWord::GetFeedSize() { + if (multinet_model_data_ == nullptr) { + return 0; + } + return multinet_->get_samp_chunksize(multinet_model_data_); +} + +void CustomWakeWord::StoreWakeWordData(const std::vector& data) { + // store audio data to wake_word_pcm_ + wake_word_pcm_.push_back(data); + // keep about 2 seconds of data, detect duration is 30ms (sample_rate == 16000, chunksize == 512) + while (wake_word_pcm_.size() > 2000 / 30) { + wake_word_pcm_.pop_front(); + } +} + +void CustomWakeWord::EncodeWakeWordData() { + const size_t stack_size = 4096 * 7; + wake_word_opus_.clear(); + if (wake_word_encode_task_stack_ == nullptr) { + wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM); + assert(wake_word_encode_task_stack_ != nullptr); + } + if (wake_word_encode_task_buffer_ == nullptr) { + wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL); + assert(wake_word_encode_task_buffer_ != nullptr); + } + + wake_word_encode_task_ = xTaskCreateStatic([](void* arg) { + auto this_ = (CustomWakeWord*)arg; + { + auto start_time = esp_timer_get_time(); + auto encoder = std::make_unique(16000, 1, OPUS_FRAME_DURATION_MS); + encoder->SetComplexity(0); // 0 is the fastest + + int packets = 0; + for (auto& pcm: this_->wake_word_pcm_) { + encoder->Encode(std::move(pcm), [this_](std::vector&& opus) { + std::lock_guard lock(this_->wake_word_mutex_); + this_->wake_word_opus_.emplace_back(std::move(opus)); + this_->wake_word_cv_.notify_all(); + }); + packets++; + } + this_->wake_word_pcm_.clear(); + + auto end_time = esp_timer_get_time(); + ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000)); + + std::lock_guard lock(this_->wake_word_mutex_); + this_->wake_word_opus_.push_back(std::vector()); + this_->wake_word_cv_.notify_all(); + } + vTaskDelete(NULL); + }, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_); +} + +bool CustomWakeWord::GetWakeWordOpus(std::vector& opus) { + std::unique_lock lock(wake_word_mutex_); + wake_word_cv_.wait(lock, [this]() { + return !wake_word_opus_.empty(); + }); + opus.swap(wake_word_opus_.front()); + wake_word_opus_.pop_front(); + return !opus.empty(); +} diff --git a/main/audio/wake_words/custom_wake_word.h b/main/audio/wake_words/custom_wake_word.h index e715747..0ce8493 100644 --- a/main/audio/wake_words/custom_wake_word.h +++ b/main/audio/wake_words/custom_wake_word.h @@ -1,58 +1,69 @@ -#ifndef CUSTOM_WAKE_WORD_H -#define CUSTOM_WAKE_WORD_H - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "audio_codec.h" -#include "wake_word.h" - -class CustomWakeWord : public WakeWord { -public: - CustomWakeWord(); - ~CustomWakeWord(); - - bool Initialize(AudioCodec* codec, srmodel_list_t* models_list); - void Feed(const std::vector& data); - void OnWakeWordDetected(std::function callback); - void Start(); - void Stop(); - size_t GetFeedSize(); - void EncodeWakeWordData(); - bool GetWakeWordOpus(std::vector& opus); - const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; } - -private: - // multinet 相关成员变量 - esp_mn_iface_t* multinet_ = nullptr; - model_iface_data_t* multinet_model_data_ = nullptr; - srmodel_list_t *models_ = nullptr; - char* mn_name_ = nullptr; - - std::function wake_word_detected_callback_; - AudioCodec* codec_ = nullptr; - std::string last_detected_wake_word_; - std::atomic running_ = false; - - TaskHandle_t wake_word_encode_task_ = nullptr; - StaticTask_t* wake_word_encode_task_buffer_ = nullptr; - StackType_t* wake_word_encode_task_stack_ = nullptr; - std::deque> wake_word_pcm_; - std::deque> wake_word_opus_; - std::mutex wake_word_mutex_; - std::condition_variable wake_word_cv_; - - void StoreWakeWordData(const std::vector& data); -}; - -#endif +#ifndef CUSTOM_WAKE_WORD_H +#define CUSTOM_WAKE_WORD_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "audio_codec.h" +#include "wake_word.h" + +class CustomWakeWord : public WakeWord { +public: + CustomWakeWord(); + ~CustomWakeWord(); + + bool Initialize(AudioCodec* codec, srmodel_list_t* models_list); + void Feed(const std::vector& data); + void OnWakeWordDetected(std::function callback); + void Start(); + void Stop(); + size_t GetFeedSize(); + void EncodeWakeWordData(); + bool GetWakeWordOpus(std::vector& opus); + const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; } + +private: + struct Command { + std::string command; + std::string text; + std::string action; + }; + + // multinet 相关成员变量 + esp_mn_iface_t* multinet_ = nullptr; + model_iface_data_t* multinet_model_data_ = nullptr; + srmodel_list_t *models_ = nullptr; + char* mn_name_ = nullptr; + std::string language_ = "cn"; + int duration_ = 3000; + float threshold_ = 0.2; + std::deque commands_; + + std::function wake_word_detected_callback_; + AudioCodec* codec_ = nullptr; + std::string last_detected_wake_word_; + std::atomic running_ = false; + + TaskHandle_t wake_word_encode_task_ = nullptr; + StaticTask_t* wake_word_encode_task_buffer_ = nullptr; + StackType_t* wake_word_encode_task_stack_ = nullptr; + std::deque> wake_word_pcm_; + std::deque> wake_word_opus_; + std::mutex wake_word_mutex_; + std::condition_variable wake_word_cv_; + + void StoreWakeWordData(const std::vector& data); + void ParseWakenetModelConfig(); +}; + +#endif diff --git a/main/audio/wake_words/esp_wake_word.cc b/main/audio/wake_words/esp_wake_word.cc index d4aaf9d..41541b7 100644 --- a/main/audio/wake_words/esp_wake_word.cc +++ b/main/audio/wake_words/esp_wake_word.cc @@ -1,87 +1,87 @@ -#include "esp_wake_word.h" -#include - - -#define TAG "EspWakeWord" - -EspWakeWord::EspWakeWord() { -} - -EspWakeWord::~EspWakeWord() { - if (wakenet_data_ != nullptr) { - wakenet_iface_->destroy(wakenet_data_); - esp_srmodel_deinit(wakenet_model_); - } -} - -bool EspWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) { - codec_ = codec; - - if (models_list == nullptr) { - wakenet_model_ = esp_srmodel_init("model"); - } else { - wakenet_model_ = models_list; - } - - if (wakenet_model_ == nullptr || wakenet_model_->num == -1) { - ESP_LOGE(TAG, "Failed to initialize wakenet model"); - return false; - } - if(wakenet_model_->num > 1) { - ESP_LOGW(TAG, "More than one model found, using the first one"); - } else if (wakenet_model_->num == 0) { - ESP_LOGE(TAG, "No model found"); - return false; - } - char *model_name = wakenet_model_->model_name[0]; - wakenet_iface_ = (esp_wn_iface_t*)esp_wn_handle_from_name(model_name); - wakenet_data_ = wakenet_iface_->create(model_name, DET_MODE_95); - - int frequency = wakenet_iface_->get_samp_rate(wakenet_data_); - int audio_chunksize = wakenet_iface_->get_samp_chunksize(wakenet_data_); - ESP_LOGI(TAG, "Wake word(%s),freq: %d, chunksize: %d", model_name, frequency, audio_chunksize); - - return true; -} - -void EspWakeWord::OnWakeWordDetected(std::function callback) { - wake_word_detected_callback_ = callback; -} - -void EspWakeWord::Start() { - running_ = true; -} - -void EspWakeWord::Stop() { - running_ = false; -} - -void EspWakeWord::Feed(const std::vector& data) { - if (wakenet_data_ == nullptr || !running_) { - return; - } - - int res = wakenet_iface_->detect(wakenet_data_, (int16_t *)data.data()); - if (res > 0) { - last_detected_wake_word_ = wakenet_iface_->get_word_name(wakenet_data_, res); - running_ = false; - - if (wake_word_detected_callback_) { - wake_word_detected_callback_(last_detected_wake_word_); - } - } -} - -size_t EspWakeWord::GetFeedSize() { - if (wakenet_data_ == nullptr) { - return 0; - } - return wakenet_iface_->get_samp_chunksize(wakenet_data_); -} - -void EspWakeWord::EncodeWakeWordData() { -} - -bool EspWakeWord::GetWakeWordOpus(std::vector& opus) { - return false; -} +#include "esp_wake_word.h" +#include + + +#define TAG "EspWakeWord" + +EspWakeWord::EspWakeWord() { +} + +EspWakeWord::~EspWakeWord() { + if (wakenet_data_ != nullptr) { + wakenet_iface_->destroy(wakenet_data_); + esp_srmodel_deinit(wakenet_model_); + } +} + +bool EspWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) { + codec_ = codec; + + if (models_list == nullptr) { + wakenet_model_ = esp_srmodel_init("model"); + } else { + wakenet_model_ = models_list; + } + + if (wakenet_model_ == nullptr || wakenet_model_->num == -1) { + ESP_LOGE(TAG, "Failed to initialize wakenet model"); + return false; + } + if(wakenet_model_->num > 1) { + ESP_LOGW(TAG, "More than one model found, using the first one"); + } else if (wakenet_model_->num == 0) { + ESP_LOGE(TAG, "No model found"); + return false; + } + char *model_name = wakenet_model_->model_name[0]; + wakenet_iface_ = (esp_wn_iface_t*)esp_wn_handle_from_name(model_name); + wakenet_data_ = wakenet_iface_->create(model_name, DET_MODE_95); + + int frequency = wakenet_iface_->get_samp_rate(wakenet_data_); + int audio_chunksize = wakenet_iface_->get_samp_chunksize(wakenet_data_); + ESP_LOGI(TAG, "Wake word(%s),freq: %d, chunksize: %d", model_name, frequency, audio_chunksize); + + return true; +} + +void EspWakeWord::OnWakeWordDetected(std::function callback) { + wake_word_detected_callback_ = callback; +} + +void EspWakeWord::Start() { + running_ = true; +} + +void EspWakeWord::Stop() { + running_ = false; +} + +void EspWakeWord::Feed(const std::vector& data) { + if (wakenet_data_ == nullptr || !running_) { + return; + } + + int res = wakenet_iface_->detect(wakenet_data_, (int16_t *)data.data()); + if (res > 0) { + last_detected_wake_word_ = wakenet_iface_->get_word_name(wakenet_data_, res); + running_ = false; + + if (wake_word_detected_callback_) { + wake_word_detected_callback_(last_detected_wake_word_); + } + } +} + +size_t EspWakeWord::GetFeedSize() { + if (wakenet_data_ == nullptr) { + return 0; + } + return wakenet_iface_->get_samp_chunksize(wakenet_data_); +} + +void EspWakeWord::EncodeWakeWordData() { +} + +bool EspWakeWord::GetWakeWordOpus(std::vector& opus) { + return false; +} diff --git a/main/audio/wake_words/esp_wake_word.h b/main/audio/wake_words/esp_wake_word.h index 9a1d73a..9a83758 100644 --- a/main/audio/wake_words/esp_wake_word.h +++ b/main/audio/wake_words/esp_wake_word.h @@ -1,42 +1,42 @@ -#ifndef ESP_WAKE_WORD_H -#define ESP_WAKE_WORD_H - -#include -#include -#include - -#include -#include -#include -#include - -#include "audio_codec.h" -#include "wake_word.h" - -class EspWakeWord : public WakeWord { -public: - EspWakeWord(); - ~EspWakeWord(); - - bool Initialize(AudioCodec* codec, srmodel_list_t* models_list); - void Feed(const std::vector& data); - void OnWakeWordDetected(std::function callback); - void Start(); - void Stop(); - size_t GetFeedSize(); - void EncodeWakeWordData(); - bool GetWakeWordOpus(std::vector& opus); - const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; } - -private: - esp_wn_iface_t *wakenet_iface_ = nullptr; - model_iface_data_t *wakenet_data_ = nullptr; - srmodel_list_t *wakenet_model_ = nullptr; - AudioCodec* codec_ = nullptr; - std::atomic running_ = false; - - std::function wake_word_detected_callback_; - std::string last_detected_wake_word_; -}; - -#endif +#ifndef ESP_WAKE_WORD_H +#define ESP_WAKE_WORD_H + +#include +#include +#include + +#include +#include +#include +#include + +#include "audio_codec.h" +#include "wake_word.h" + +class EspWakeWord : public WakeWord { +public: + EspWakeWord(); + ~EspWakeWord(); + + bool Initialize(AudioCodec* codec, srmodel_list_t* models_list); + void Feed(const std::vector& data); + void OnWakeWordDetected(std::function callback); + void Start(); + void Stop(); + size_t GetFeedSize(); + void EncodeWakeWordData(); + bool GetWakeWordOpus(std::vector& opus); + const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; } + +private: + esp_wn_iface_t *wakenet_iface_ = nullptr; + model_iface_data_t *wakenet_data_ = nullptr; + srmodel_list_t *wakenet_model_ = nullptr; + AudioCodec* codec_ = nullptr; + std::atomic running_ = false; + + std::function wake_word_detected_callback_; + std::string last_detected_wake_word_; +}; + +#endif diff --git a/main/boards/README.md b/main/boards/README.md index f5ba9be..8c95cc2 100644 --- a/main/boards/README.md +++ b/main/boards/README.md @@ -1,326 +1,326 @@ -# 自定义开发板指南 - -本指南介绍如何为小智AI语音聊天机器人项目定制一个新的开发板初始化程序。小智AI支持70多种ESP32系列开发板,每个开发板的初始化代码都放在对应的目录下。 - -## 重要提示 - -> **警告**: 对于自定义开发板,当IO配置与原有开发板不同时,切勿直接覆盖原有开发板的配置编译固件。必须创建新的开发板类型,或者通过config.json文件中的builds配置不同的name和sdkconfig宏定义来区分。使用 `python scripts/release.py [开发板目录名字]` 来编译打包固件。 -> -> 如果直接覆盖原有配置,将来OTA升级时,您的自定义固件可能会被原有开发板的标准固件覆盖,导致您的设备无法正常工作。每个开发板有唯一的标识和对应的固件升级通道,保持开发板标识的唯一性非常重要。 - -## 目录结构 - -每个开发板的目录结构通常包含以下文件: - -- `xxx_board.cc` - 主要的板级初始化代码,实现了板子相关的初始化和功能 -- `config.h` - 板级配置文件,定义了硬件管脚映射和其他配置项 -- `config.json` - 编译配置,指定目标芯片和特殊的编译选项 -- `README.md` - 开发板相关的说明文档 - -## 定制开发板步骤 - -### 1. 创建新的开发板目录 - -首先在`boards/`目录下创建一个新的目录,例如`my-custom-board/`: - -```bash -mkdir main/boards/my-custom-board -``` - -### 2. 创建配置文件 - -#### config.h - -在`config.h`中定义所有的硬件配置,包括: - -- 音频采样率和I2S引脚配置 -- 音频编解码芯片地址和I2C引脚配置 -- 按钮和LED引脚配置 -- 显示屏参数和引脚配置 - -参考示例(来自lichuang-c3-dev): - -```c -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -// 音频配置 -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_13 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -// 按钮配置 -#define BOOT_BUTTON_GPIO GPIO_NUM_9 - -// 显示屏配置 -#define DISPLAY_SPI_SCK_PIN GPIO_NUM_3 -#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5 -#define DISPLAY_DC_PIN GPIO_NUM_6 -#define DISPLAY_SPI_CS_PIN GPIO_NUM_4 - -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY true - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - -#endif // _BOARD_CONFIG_H_ -``` - -#### config.json - -在`config.json`中定义编译配置: - -```json -{ - "target": "esp32s3", // 目标芯片型号: esp32, esp32s3, esp32c3等 - "builds": [ - { - "name": "my-custom-board", // 开发板名称 - "sdkconfig_append": [ - // 额外需要的编译配置 - "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" - ] - } - ] -} -``` - -### 3. 编写板级初始化代码 - -创建一个`my_custom_board.cc`文件,实现开发板的所有初始化逻辑。 - -一个基本的开发板类定义包含以下几个部分: - -1. **类定义**:继承自`WifiBoard`或`Ml307Board` -2. **初始化函数**:包括I2C、显示屏、按钮、IoT等组件的初始化 -3. **虚函数重写**:如`GetAudioCodec()`、`GetDisplay()`、`GetBacklight()`等 -4. **注册开发板**:使用`DECLARE_BOARD`宏注册开发板 - -```cpp -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "mcp_server.h" - -#include -#include -#include - -#define TAG "MyCustomBoard" - -class MyCustomBoard : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - - // I2C初始化 - void InitializeI2c() { - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - // SPI初始化(用于显示屏) - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - // 按钮初始化 - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - // 显示屏初始化(以ST7789为例) - void InitializeDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 2; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, true); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - // 创建显示屏对象 - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, - DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - // MCP Tools 初始化 - void InitializeTools() { - // 参考 MCP 文档 - } - -public: - // 构造函数 - MyCustomBoard() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeSpi(); - InitializeDisplay(); - InitializeButtons(); - InitializeTools(); - GetBacklight()->SetBrightness(100); - } - - // 获取音频编解码器 - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec( - codec_i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - // 获取显示屏 - virtual Display* GetDisplay() override { - return display_; - } - - // 获取背光控制 - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -// 注册开发板 -DECLARE_BOARD(MyCustomBoard); -``` - -### 4. 创建README.md - -在README.md中说明开发板的特性、硬件要求、编译和烧录步骤: - - -## 常见开发板组件 - -### 1. 显示屏 - -项目支持多种显示屏驱动,包括: -- ST7789 (SPI) -- ILI9341 (SPI) -- SH8601 (QSPI) -- 等... - -### 2. 音频编解码器 - -支持的编解码器包括: -- ES8311 (常用) -- ES7210 (麦克风阵列) -- AW88298 (功放) -- 等... - -### 3. 电源管理 - -一些开发板使用电源管理芯片: -- AXP2101 -- 其他可用的PMIC - -### 4. MCP设备控制 - -可以添加各种MCP工具,让AI能够使用: -- Speaker (扬声器控制) -- Screen (屏幕亮度调节) -- Battery (电池电量读取) -- Light (灯光控制) -- 等... - -## 开发板类继承关系 - -- `Board` - 基础板级类 - - `WifiBoard` - Wi-Fi连接的开发板 - - `Ml307Board` - 使用4G模块的开发板 - - `DualNetworkBoard` - 支持Wi-Fi与4G网络切换的开发板 - -## 开发技巧 - -1. **参考相似的开发板**:如果您的新开发板与现有开发板有相似之处,可以参考现有实现 -2. **分步调试**:先实现基础功能(如显示),再添加更复杂的功能(如音频) -3. **管脚映射**:确保在config.h中正确配置所有管脚映射 -4. **检查硬件兼容性**:确认所有芯片和驱动程序的兼容性 - -## 可能遇到的问题 - -1. **显示屏不正常**:检查SPI配置、镜像设置和颜色反转设置 -2. **音频无输出**:检查I2S配置、PA使能引脚和编解码器地址 -3. **无法连接网络**:检查Wi-Fi凭据和网络配置 -4. **无法与服务器通信**:检查MQTT或WebSocket配置 - -## 参考资料 - -- ESP-IDF 文档: https://docs.espressif.com/projects/esp-idf/ -- LVGL 文档: https://docs.lvgl.io/ +# 自定义开发板指南 + +本指南介绍如何为小智AI语音聊天机器人项目定制一个新的开发板初始化程序。小智AI支持70多种ESP32系列开发板,每个开发板的初始化代码都放在对应的目录下。 + +## 重要提示 + +> **警告**: 对于自定义开发板,当IO配置与原有开发板不同时,切勿直接覆盖原有开发板的配置编译固件。必须创建新的开发板类型,或者通过config.json文件中的builds配置不同的name和sdkconfig宏定义来区分。使用 `python scripts/release.py [开发板目录名字]` 来编译打包固件。 +> +> 如果直接覆盖原有配置,将来OTA升级时,您的自定义固件可能会被原有开发板的标准固件覆盖,导致您的设备无法正常工作。每个开发板有唯一的标识和对应的固件升级通道,保持开发板标识的唯一性非常重要。 + +## 目录结构 + +每个开发板的目录结构通常包含以下文件: + +- `xxx_board.cc` - 主要的板级初始化代码,实现了板子相关的初始化和功能 +- `config.h` - 板级配置文件,定义了硬件管脚映射和其他配置项 +- `config.json` - 编译配置,指定目标芯片和特殊的编译选项 +- `README.md` - 开发板相关的说明文档 + +## 定制开发板步骤 + +### 1. 创建新的开发板目录 + +首先在`boards/`目录下创建一个新的目录,例如`my-custom-board/`: + +```bash +mkdir main/boards/my-custom-board +``` + +### 2. 创建配置文件 + +#### config.h + +在`config.h`中定义所有的硬件配置,包括: + +- 音频采样率和I2S引脚配置 +- 音频编解码芯片地址和I2C引脚配置 +- 按钮和LED引脚配置 +- 显示屏参数和引脚配置 + +参考示例(来自lichuang-c3-dev): + +```c +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +// 音频配置 +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_13 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +// 按钮配置 +#define BOOT_BUTTON_GPIO GPIO_NUM_9 + +// 显示屏配置 +#define DISPLAY_SPI_SCK_PIN GPIO_NUM_3 +#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5 +#define DISPLAY_DC_PIN GPIO_NUM_6 +#define DISPLAY_SPI_CS_PIN GPIO_NUM_4 + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +#endif // _BOARD_CONFIG_H_ +``` + +#### config.json + +在`config.json`中定义编译配置: + +```json +{ + "target": "esp32s3", // 目标芯片型号: esp32, esp32s3, esp32c3等 + "builds": [ + { + "name": "my-custom-board", // 开发板名称 + "sdkconfig_append": [ + // 额外需要的编译配置 + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" + ] + } + ] +} +``` + +### 3. 编写板级初始化代码 + +创建一个`my_custom_board.cc`文件,实现开发板的所有初始化逻辑。 + +一个基本的开发板类定义包含以下几个部分: + +1. **类定义**:继承自`WifiBoard`或`Ml307Board` +2. **初始化函数**:包括I2C、显示屏、按钮、IoT等组件的初始化 +3. **虚函数重写**:如`GetAudioCodec()`、`GetDisplay()`、`GetBacklight()`等 +4. **注册开发板**:使用`DECLARE_BOARD`宏注册开发板 + +```cpp +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "mcp_server.h" + +#include +#include +#include + +#define TAG "MyCustomBoard" + +class MyCustomBoard : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + + // I2C初始化 + void InitializeI2c() { + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + // SPI初始化(用于显示屏) + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + // 按钮初始化 + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + // 显示屏初始化(以ST7789为例) + void InitializeDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 2; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + // 创建显示屏对象 + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, + DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + // MCP Tools 初始化 + void InitializeTools() { + // 参考 MCP 文档 + } + +public: + // 构造函数 + MyCustomBoard() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeDisplay(); + InitializeButtons(); + InitializeTools(); + GetBacklight()->SetBrightness(100); + } + + // 获取音频编解码器 + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec( + codec_i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + // 获取显示屏 + virtual Display* GetDisplay() override { + return display_; + } + + // 获取背光控制 + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +// 注册开发板 +DECLARE_BOARD(MyCustomBoard); +``` + +### 4. 创建README.md + +在README.md中说明开发板的特性、硬件要求、编译和烧录步骤: + + +## 常见开发板组件 + +### 1. 显示屏 + +项目支持多种显示屏驱动,包括: +- ST7789 (SPI) +- ILI9341 (SPI) +- SH8601 (QSPI) +- 等... + +### 2. 音频编解码器 + +支持的编解码器包括: +- ES8311 (常用) +- ES7210 (麦克风阵列) +- AW88298 (功放) +- 等... + +### 3. 电源管理 + +一些开发板使用电源管理芯片: +- AXP2101 +- 其他可用的PMIC + +### 4. MCP设备控制 + +可以添加各种MCP工具,让AI能够使用: +- Speaker (扬声器控制) +- Screen (屏幕亮度调节) +- Battery (电池电量读取) +- Light (灯光控制) +- 等... + +## 开发板类继承关系 + +- `Board` - 基础板级类 + - `WifiBoard` - Wi-Fi连接的开发板 + - `Ml307Board` - 使用4G模块的开发板 + - `DualNetworkBoard` - 支持Wi-Fi与4G网络切换的开发板 + +## 开发技巧 + +1. **参考相似的开发板**:如果您的新开发板与现有开发板有相似之处,可以参考现有实现 +2. **分步调试**:先实现基础功能(如显示),再添加更复杂的功能(如音频) +3. **管脚映射**:确保在config.h中正确配置所有管脚映射 +4. **检查硬件兼容性**:确认所有芯片和驱动程序的兼容性 + +## 可能遇到的问题 + +1. **显示屏不正常**:检查SPI配置、镜像设置和颜色反转设置 +2. **音频无输出**:检查I2S配置、PA使能引脚和编解码器地址 +3. **无法连接网络**:检查Wi-Fi凭据和网络配置 +4. **无法与服务器通信**:检查MQTT或WebSocket配置 + +## 参考资料 + +- ESP-IDF 文档: https://docs.espressif.com/projects/esp-idf/ +- LVGL 文档: https://docs.lvgl.io/ - ESP-SR 文档: https://github.com/espressif/esp-sr \ No newline at end of file diff --git a/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc b/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc index 1375949..c10925d 100644 --- a/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc +++ b/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc @@ -1,299 +1,299 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" -#include "i2c_device.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TAG "atk_dnesp32s3_box" - -class ATK_NoAudioCodecDuplex : public NoAudioCodec { -public: - ATK_NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { - duplex_ = true; - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, - .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_16BIT, - .ws_pol = false, - .bit_shift = true, - #ifdef I2S_HW_VERSION_2 - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - #endif - }, - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); - } -}; - -class XL9555_IN : public I2cDevice { -public: - XL9555_IN(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - WriteReg(0x06, 0x3B); - WriteReg(0x07, 0xFE); - } - - void xl9555_cfg(void) { - WriteReg(0x06, 0x1B); - WriteReg(0x07, 0xFE); - } - - void SetOutputState(uint8_t bit, uint8_t level) { - uint16_t data; - int index = bit; - - if (bit < 8) { - data = ReadReg(0x02); - } else { - data = ReadReg(0x03); - index -= 8; - } - - data = (data & ~(1 << index)) | (level << index); - - if (bit < 8) { - WriteReg(0x02, data); - } else { - WriteReg(0x03, data); - } - } - - int GetPingState(uint16_t pin) { - uint8_t data; - if (pin <= 0x0080) { - data = ReadReg(0x00); - return (data & (uint8_t)(pin & 0xFF)) ? 1 : 0; - } else { - data = ReadReg(0x01); - return (data & (uint8_t)((pin >> 8) & 0xFF )) ? 1 : 0; - } - - return 0; - } -}; - -class atk_dnesp32s3_box : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - i2c_master_dev_handle_t xl9555_handle_; - Button boot_button_; - LcdDisplay* display_; - XL9555_IN* xl9555_in_; - bool es8311_detected_ = false; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = GPIO_NUM_48, - .scl_io_num = GPIO_NUM_45, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - - // Initialize XL9555 - xl9555_in_ = new XL9555_IN(i2c_bus_, 0x20); - - if (xl9555_in_->GetPingState(0x0020) == 1) { - es8311_detected_ = true; /* 音频设备标志位,SPK_CTRL_IO为高电平时,该标志位置1,且判定为ES8311 */ - } else { - es8311_detected_ = false; /* 音频设备标志位,SPK_CTRL_IO为低电平时,该标志位置0,且判定为NS4168 */ - } - - xl9555_in_->xl9555_cfg(); - } - - void InitializeATK_ST7789_80_Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - /* 配置RD引脚 */ - gpio_config_t gpio_init_struct; - gpio_init_struct.intr_type = GPIO_INTR_DISABLE; - gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT; - gpio_init_struct.pin_bit_mask = 1ull << LCD_NUM_RD; - gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; - gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&gpio_init_struct); - gpio_set_level(LCD_NUM_RD, 1); - - esp_lcd_i80_bus_handle_t i80_bus = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = LCD_NUM_DC, - .wr_gpio_num = LCD_NUM_WR, - .clk_src = LCD_CLK_SRC_DEFAULT, - .data_gpio_nums = { - GPIO_LCD_D0, - GPIO_LCD_D1, - GPIO_LCD_D2, - GPIO_LCD_D3, - GPIO_LCD_D4, - GPIO_LCD_D5, - GPIO_LCD_D6, - GPIO_LCD_D7, - }, - .bus_width = 8, - .max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t), - .psram_trans_align = 64, - .sram_trans_align = 4, - }; - ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = LCD_NUM_CS, - .pclk_hz = (10 * 1000 * 1000), - .trans_queue_depth = 10, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .dc_levels = { - .dc_idle_level = 0, - .dc_cmd_level = 0, - .dc_dummy_level = 0, - .dc_data_level = 1, - }, - .flags = { - .swap_color_bytes = 0, - }, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io)); - - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = LCD_NUM_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - esp_lcd_panel_set_gap(panel, 0, 0); - uint8_t data0[] = {0x00}; - uint8_t data1[] = {0x65}; - esp_lcd_panel_io_tx_param(panel_io, 0x36, data0, 1); - esp_lcd_panel_io_tx_param(panel_io, 0x3A, data1, 1); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - -public: - atk_dnesp32s3_box() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeATK_ST7789_80_Display(); - xl9555_in_->SetOutputState(5, 1); - xl9555_in_->SetOutputState(7, 1); - InitializeButtons(); - } - - virtual AudioCodec* GetAudioCodec() override { - /* 根据探测结果初始化编解码器 */ - if (es8311_detected_) { - /* 使用ES8311 驱动 */ - static Es8311AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - GPIO_NUM_NC, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8311_ADDR, - false); - return &audio_codec; - } else { - static ATK_NoAudioCodecDuplex audio_codec( - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN); - return &audio_codec; - } - return NULL; - } - - virtual Display* GetDisplay() override { - return display_; - } -}; - -DECLARE_BOARD(atk_dnesp32s3_box); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" +#include "i2c_device.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "atk_dnesp32s3_box" + +class ATK_NoAudioCodecDuplex : public NoAudioCodec { +public: + ATK_NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + duplex_ = true; + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, + .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + #ifdef I2S_HW_VERSION_2 + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + #endif + }, + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); + } +}; + +class XL9555_IN : public I2cDevice { +public: + XL9555_IN(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + WriteReg(0x06, 0x3B); + WriteReg(0x07, 0xFE); + } + + void xl9555_cfg(void) { + WriteReg(0x06, 0x1B); + WriteReg(0x07, 0xFE); + } + + void SetOutputState(uint8_t bit, uint8_t level) { + uint16_t data; + int index = bit; + + if (bit < 8) { + data = ReadReg(0x02); + } else { + data = ReadReg(0x03); + index -= 8; + } + + data = (data & ~(1 << index)) | (level << index); + + if (bit < 8) { + WriteReg(0x02, data); + } else { + WriteReg(0x03, data); + } + } + + int GetPingState(uint16_t pin) { + uint8_t data; + if (pin <= 0x0080) { + data = ReadReg(0x00); + return (data & (uint8_t)(pin & 0xFF)) ? 1 : 0; + } else { + data = ReadReg(0x01); + return (data & (uint8_t)((pin >> 8) & 0xFF )) ? 1 : 0; + } + + return 0; + } +}; + +class atk_dnesp32s3_box : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + i2c_master_dev_handle_t xl9555_handle_; + Button boot_button_; + LcdDisplay* display_; + XL9555_IN* xl9555_in_; + bool es8311_detected_ = false; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = GPIO_NUM_48, + .scl_io_num = GPIO_NUM_45, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + + // Initialize XL9555 + xl9555_in_ = new XL9555_IN(i2c_bus_, 0x20); + + if (xl9555_in_->GetPingState(0x0020) == 1) { + es8311_detected_ = true; /* 音频设备标志位,SPK_CTRL_IO为高电平时,该标志位置1,且判定为ES8311 */ + } else { + es8311_detected_ = false; /* 音频设备标志位,SPK_CTRL_IO为低电平时,该标志位置0,且判定为NS4168 */ + } + + xl9555_in_->xl9555_cfg(); + } + + void InitializeATK_ST7789_80_Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + /* 配置RD引脚 */ + gpio_config_t gpio_init_struct; + gpio_init_struct.intr_type = GPIO_INTR_DISABLE; + gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT; + gpio_init_struct.pin_bit_mask = 1ull << LCD_NUM_RD; + gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; + gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&gpio_init_struct); + gpio_set_level(LCD_NUM_RD, 1); + + esp_lcd_i80_bus_handle_t i80_bus = NULL; + esp_lcd_i80_bus_config_t bus_config = { + .dc_gpio_num = LCD_NUM_DC, + .wr_gpio_num = LCD_NUM_WR, + .clk_src = LCD_CLK_SRC_DEFAULT, + .data_gpio_nums = { + GPIO_LCD_D0, + GPIO_LCD_D1, + GPIO_LCD_D2, + GPIO_LCD_D3, + GPIO_LCD_D4, + GPIO_LCD_D5, + GPIO_LCD_D6, + GPIO_LCD_D7, + }, + .bus_width = 8, + .max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t), + .psram_trans_align = 64, + .sram_trans_align = 4, + }; + ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); + + esp_lcd_panel_io_i80_config_t io_config = { + .cs_gpio_num = LCD_NUM_CS, + .pclk_hz = (10 * 1000 * 1000), + .trans_queue_depth = 10, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .dc_levels = { + .dc_idle_level = 0, + .dc_cmd_level = 0, + .dc_dummy_level = 0, + .dc_data_level = 1, + }, + .flags = { + .swap_color_bytes = 0, + }, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io)); + + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = LCD_NUM_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + esp_lcd_panel_set_gap(panel, 0, 0); + uint8_t data0[] = {0x00}; + uint8_t data1[] = {0x65}; + esp_lcd_panel_io_tx_param(panel_io, 0x36, data0, 1); + esp_lcd_panel_io_tx_param(panel_io, 0x3A, data1, 1); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + +public: + atk_dnesp32s3_box() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeATK_ST7789_80_Display(); + xl9555_in_->SetOutputState(5, 1); + xl9555_in_->SetOutputState(7, 1); + InitializeButtons(); + } + + virtual AudioCodec* GetAudioCodec() override { + /* 根据探测结果初始化编解码器 */ + if (es8311_detected_) { + /* 使用ES8311 驱动 */ + static Es8311AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + GPIO_NUM_NC, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8311_ADDR, + false); + return &audio_codec; + } else { + static ATK_NoAudioCodecDuplex audio_codec( + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN); + return &audio_codec; + } + return NULL; + } + + virtual Display* GetDisplay() override { + return display_; + } +}; + +DECLARE_BOARD(atk_dnesp32s3_box); diff --git a/main/boards/atk-dnesp32s3-box/config.h b/main/boards/atk-dnesp32s3-box/config.h index 4b8e002..a278bfe 100644 --- a/main/boards/atk-dnesp32s3-box/config.h +++ b/main/boards/atk-dnesp32s3-box/config.h @@ -1,46 +1,46 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_WS GPIO_NUM_13 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_21 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_47 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_14 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_4 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_SWAP_XY true -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - - -// Pin Definitions -#define LCD_NUM_CS GPIO_NUM_1 -#define LCD_NUM_DC GPIO_NUM_2 -#define LCD_NUM_RD GPIO_NUM_41 -#define LCD_NUM_WR GPIO_NUM_42 -#define LCD_NUM_RST GPIO_NUM_NC - -#define GPIO_LCD_D0 GPIO_NUM_40 -#define GPIO_LCD_D1 GPIO_NUM_39 -#define GPIO_LCD_D2 GPIO_NUM_38 -#define GPIO_LCD_D3 GPIO_NUM_12 -#define GPIO_LCD_D4 GPIO_NUM_11 -#define GPIO_LCD_D5 GPIO_NUM_10 -#define GPIO_LCD_D6 GPIO_NUM_9 -#define GPIO_LCD_D7 GPIO_NUM_46 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_WS GPIO_NUM_13 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_21 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_47 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_14 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_4 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_SWAP_XY true +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + + +// Pin Definitions +#define LCD_NUM_CS GPIO_NUM_1 +#define LCD_NUM_DC GPIO_NUM_2 +#define LCD_NUM_RD GPIO_NUM_41 +#define LCD_NUM_WR GPIO_NUM_42 +#define LCD_NUM_RST GPIO_NUM_NC + +#define GPIO_LCD_D0 GPIO_NUM_40 +#define GPIO_LCD_D1 GPIO_NUM_39 +#define GPIO_LCD_D2 GPIO_NUM_38 +#define GPIO_LCD_D3 GPIO_NUM_12 +#define GPIO_LCD_D4 GPIO_NUM_11 +#define GPIO_LCD_D5 GPIO_NUM_10 +#define GPIO_LCD_D6 GPIO_NUM_9 +#define GPIO_LCD_D7 GPIO_NUM_46 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/atk-dnesp32s3-box/config.json b/main/boards/atk-dnesp32s3-box/config.json index 21e97d3..e39a384 100644 --- a/main/boards/atk-dnesp32s3-box/config.json +++ b/main/boards/atk-dnesp32s3-box/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "atk-dnesp32s3-box", - "sdkconfig_append": [ - "CONFIG_USE_WECHAT_MESSAGE_STYLE=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "atk-dnesp32s3-box", + "sdkconfig_append": [ + "CONFIG_USE_WECHAT_MESSAGE_STYLE=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc b/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc index ce0bc43..54fc8fe 100644 --- a/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc +++ b/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc @@ -1,388 +1,388 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "power_manager.h" - -#include "i2c_device.h" -#include -#include -#include - -#include -#include - -#define TAG "atk_dnesp32s3_box0" - -class atk_dnesp32s3_box0 : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button right_button_; - Button left_button_; - Button middle_button_; - LcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - PowerSupply power_status_; - LcdStatus LcdStatus_ = kDevicelcdbacklightOn; - PowerSleep power_sleep_ = kDeviceNoSleep; - WakeStatus wake_status_ = kDeviceAwakened; - XiaozhiStatus XiaozhiStatus_ = kDevice_Exit_Distributionnetwork; - esp_timer_handle_t wake_timer_handle_; - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - int ticks_ = 0; - const int kChgCtrlInterval = 5; - - void InitializeBoardPowerManager() { - gpio_config_t gpio_init_struct = {0}; - gpio_init_struct.intr_type = GPIO_INTR_DISABLE; - gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT; - gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; - gpio_init_struct.pin_bit_mask = (1ull << CODEC_PWR_PIN) | (1ull << SYS_POW_PIN); - gpio_config(&gpio_init_struct); - - gpio_set_level(CODEC_PWR_PIN, 1); - gpio_set_level(SYS_POW_PIN, 1); - - gpio_config_t chg_init_struct = {0}; - - chg_init_struct.intr_type = GPIO_INTR_DISABLE; - chg_init_struct.mode = GPIO_MODE_INPUT; - chg_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; - chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; - chg_init_struct.pin_bit_mask = 1ull << CHRG_PIN; - ESP_ERROR_CHECK(gpio_config(&chg_init_struct)); - - chg_init_struct.mode = GPIO_MODE_OUTPUT; - chg_init_struct.pull_up_en = GPIO_PULLUP_DISABLE; - chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; - chg_init_struct.pin_bit_mask = 1ull << CHG_CTRL_PIN; - ESP_ERROR_CHECK(gpio_config(&chg_init_struct)); - gpio_set_level(CHG_CTRL_PIN, 1); - - if (gpio_get_level(CHRG_PIN) == 0) { - power_status_ = kDeviceTypecSupply; - } else { - power_status_ = kDeviceBatterySupply; - } - - esp_timer_create_args_t wake_display_timer_args = { - .callback = [](void *arg) { - atk_dnesp32s3_box0* self = static_cast(arg); - if (self->LcdStatus_ == kDevicelcdbacklightOff && Application::GetInstance().GetDeviceState() == kDeviceStateListening - && self->wake_status_ == kDeviceWaitWake) { - - if (self->power_sleep_ == kDeviceNeutralSleep) { - self->power_save_timer_->WakeUp(); - } - - self->GetBacklight()->RestoreBrightness(); - self->wake_status_ = kDeviceAwakened; - self->LcdStatus_ = kDevicelcdbacklightOn; - } else if (self->power_sleep_ == kDeviceNeutralSleep && Application::GetInstance().GetDeviceState() == kDeviceStateListening - && self->LcdStatus_ != kDevicelcdbacklightOff && self->wake_status_ == kDeviceAwakened) { - self->power_save_timer_->WakeUp(); - self->power_sleep_ = kDeviceNoSleep; - } else { - self->ticks_ ++; - if (self->ticks_ % self->kChgCtrlInterval == 0) { - if (gpio_get_level(CHRG_PIN) == 0) { - self->power_status_ = kDeviceTypecSupply; - } else { - self->power_status_ = kDeviceBatterySupply; - } - - if (self->power_manager_->low_voltage_ < 2877 && self->power_status_ != kDeviceTypecSupply) { - esp_timer_stop(self->power_manager_->timer_handle_); - gpio_set_level(CHG_CTRL_PIN, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - gpio_set_level(SYS_POW_PIN, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - } - } - } - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "wake_update_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 300000)); - } - - void InitializePowerManager() { - power_manager_ = new PowerManager(CHRG_PIN); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - power_sleep_ = kDeviceNeutralSleep; - XiaozhiStatus_ = kDevice_join_Sleep; - GetDisplay()->SetPowerSaveMode(true); - - if (LcdStatus_ != kDevicelcdbacklightOff) { - GetBacklight()->SetBrightness(1); - } - }); - power_save_timer_->OnExitSleepMode([this]() { - power_sleep_ = kDeviceNoSleep; - GetDisplay()->SetPowerSaveMode(false); - - if (XiaozhiStatus_ != kDevice_Exit_Sleep) { - GetBacklight()->RestoreBrightness(); - } - }); - power_save_timer_->OnShutdownRequest([this]() { - if (power_status_ == kDeviceBatterySupply) { - esp_timer_stop(power_manager_->timer_handle_); - gpio_set_level(CHG_CTRL_PIN, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - gpio_set_level(SYS_POW_PIN, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - } - }); - - power_save_timer_->SetEnabled(true); - } - - // Initialize I2C peripheral - void InitializeI2c() { - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - // Initialize spi peripheral - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = LCD_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = LCD_SCLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - middle_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - - if (LcdStatus_ != kDevicelcdbacklightOff) { - if (power_sleep_ == kDeviceNeutralSleep) { - power_save_timer_->WakeUp(); - power_sleep_ = kDeviceNoSleep; - } - - app.ToggleChatState(); - } - }); - - middle_button_.OnPressUp([this]() { - if (LcdStatus_ == kDevicelcdbacklightOff) { - Application::GetInstance().StopListening(); - Application::GetInstance().SetDeviceState(kDeviceStateIdle); - wake_status_ = kDeviceWaitWake; - } - - if (XiaozhiStatus_ == kDevice_Distributionnetwork || XiaozhiStatus_ == kDevice_Exit_Sleep) { - esp_timer_stop(power_manager_->timer_handle_); - gpio_set_level(CHG_CTRL_PIN, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - gpio_set_level(SYS_POW_PIN, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - } else if (XiaozhiStatus_ == kDevice_join_Sleep) { - GetBacklight()->RestoreBrightness(); - XiaozhiStatus_ = kDevice_null; - } - }); - - middle_button_.OnLongPress([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - - if (app.GetDeviceState() != kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { - if (app.GetDeviceState() == kDeviceStateWifiConfiguring && power_status_ != kDeviceTypecSupply) { - GetBacklight()->SetBrightness(0); - XiaozhiStatus_ = kDevice_Distributionnetwork; - } else if (power_status_ == kDeviceBatterySupply && LcdStatus_ != kDevicelcdbacklightOff) { - Application::GetInstance().StartListening(); - GetBacklight()->SetBrightness(0); - XiaozhiStatus_ = kDevice_Exit_Sleep; - } else if (power_status_ == kDeviceTypecSupply && LcdStatus_ == kDevicelcdbacklightOn && Application::GetInstance().GetDeviceState() != kDeviceStateStarting) { - Application::GetInstance().StartListening(); - GetBacklight()->SetBrightness(0); - LcdStatus_ = kDevicelcdbacklightOff; - } else if (LcdStatus_ == kDevicelcdbacklightOff && (power_status_ == kDeviceTypecSupply || power_status_ == kDeviceBatterySupply)) { - GetDisplay()->SetChatMessage("system", ""); - GetBacklight()->RestoreBrightness(); - wake_status_ = kDeviceAwakened; - LcdStatus_ = kDevicelcdbacklightOn; - } - } - }); - - left_button_.OnClick([this]() { - if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) { - power_save_timer_->WakeUp(); - power_sleep_ = kDeviceNoSleep; - } - - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - left_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - - right_button_.OnClick([this]() { - if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) { - power_save_timer_->WakeUp(); - power_sleep_ = kDeviceNoSleep; - } - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - right_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - } - - void InitializeSt7789Display() { - ESP_LOGI(TAG, "Install panel IO"); - - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = LCD_CS_PIN; - io_config.dc_gpio_num = LCD_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 7; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io); - - ESP_LOGI(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = LCD_RST_PIN; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG, - esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_invert_color(panel, true); - esp_lcd_panel_init(panel); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - atk_dnesp32s3_box0() : - right_button_(R_BUTTON_GPIO, false), - left_button_(L_BUTTON_GPIO, false), - middle_button_(M_BUTTON_GPIO, true) { - InitializeBoardPowerManager(); - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeI2c(); - InitializeSpi(); - InitializeSt7789Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - GPIO_NUM_NC, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8311_ADDR, - false); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(atk_dnesp32s3_box0); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "power_manager.h" + +#include "i2c_device.h" +#include +#include +#include + +#include +#include + +#define TAG "atk_dnesp32s3_box0" + +class atk_dnesp32s3_box0 : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button right_button_; + Button left_button_; + Button middle_button_; + LcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + PowerSupply power_status_; + LcdStatus LcdStatus_ = kDevicelcdbacklightOn; + PowerSleep power_sleep_ = kDeviceNoSleep; + WakeStatus wake_status_ = kDeviceAwakened; + XiaozhiStatus XiaozhiStatus_ = kDevice_Exit_Distributionnetwork; + esp_timer_handle_t wake_timer_handle_; + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + int ticks_ = 0; + const int kChgCtrlInterval = 5; + + void InitializeBoardPowerManager() { + gpio_config_t gpio_init_struct = {0}; + gpio_init_struct.intr_type = GPIO_INTR_DISABLE; + gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT; + gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; + gpio_init_struct.pin_bit_mask = (1ull << CODEC_PWR_PIN) | (1ull << SYS_POW_PIN); + gpio_config(&gpio_init_struct); + + gpio_set_level(CODEC_PWR_PIN, 1); + gpio_set_level(SYS_POW_PIN, 1); + + gpio_config_t chg_init_struct = {0}; + + chg_init_struct.intr_type = GPIO_INTR_DISABLE; + chg_init_struct.mode = GPIO_MODE_INPUT; + chg_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; + chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; + chg_init_struct.pin_bit_mask = 1ull << CHRG_PIN; + ESP_ERROR_CHECK(gpio_config(&chg_init_struct)); + + chg_init_struct.mode = GPIO_MODE_OUTPUT; + chg_init_struct.pull_up_en = GPIO_PULLUP_DISABLE; + chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; + chg_init_struct.pin_bit_mask = 1ull << CHG_CTRL_PIN; + ESP_ERROR_CHECK(gpio_config(&chg_init_struct)); + gpio_set_level(CHG_CTRL_PIN, 1); + + if (gpio_get_level(CHRG_PIN) == 0) { + power_status_ = kDeviceTypecSupply; + } else { + power_status_ = kDeviceBatterySupply; + } + + esp_timer_create_args_t wake_display_timer_args = { + .callback = [](void *arg) { + atk_dnesp32s3_box0* self = static_cast(arg); + if (self->LcdStatus_ == kDevicelcdbacklightOff && Application::GetInstance().GetDeviceState() == kDeviceStateListening + && self->wake_status_ == kDeviceWaitWake) { + + if (self->power_sleep_ == kDeviceNeutralSleep) { + self->power_save_timer_->WakeUp(); + } + + self->GetBacklight()->RestoreBrightness(); + self->wake_status_ = kDeviceAwakened; + self->LcdStatus_ = kDevicelcdbacklightOn; + } else if (self->power_sleep_ == kDeviceNeutralSleep && Application::GetInstance().GetDeviceState() == kDeviceStateListening + && self->LcdStatus_ != kDevicelcdbacklightOff && self->wake_status_ == kDeviceAwakened) { + self->power_save_timer_->WakeUp(); + self->power_sleep_ = kDeviceNoSleep; + } else { + self->ticks_ ++; + if (self->ticks_ % self->kChgCtrlInterval == 0) { + if (gpio_get_level(CHRG_PIN) == 0) { + self->power_status_ = kDeviceTypecSupply; + } else { + self->power_status_ = kDeviceBatterySupply; + } + + if (self->power_manager_->low_voltage_ < 2877 && self->power_status_ != kDeviceTypecSupply) { + esp_timer_stop(self->power_manager_->timer_handle_); + gpio_set_level(CHG_CTRL_PIN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + gpio_set_level(SYS_POW_PIN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + } + } + } + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "wake_update_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 300000)); + } + + void InitializePowerManager() { + power_manager_ = new PowerManager(CHRG_PIN); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + power_sleep_ = kDeviceNeutralSleep; + XiaozhiStatus_ = kDevice_join_Sleep; + GetDisplay()->SetPowerSaveMode(true); + + if (LcdStatus_ != kDevicelcdbacklightOff) { + GetBacklight()->SetBrightness(1); + } + }); + power_save_timer_->OnExitSleepMode([this]() { + power_sleep_ = kDeviceNoSleep; + GetDisplay()->SetPowerSaveMode(false); + + if (XiaozhiStatus_ != kDevice_Exit_Sleep) { + GetBacklight()->RestoreBrightness(); + } + }); + power_save_timer_->OnShutdownRequest([this]() { + if (power_status_ == kDeviceBatterySupply) { + esp_timer_stop(power_manager_->timer_handle_); + gpio_set_level(CHG_CTRL_PIN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + gpio_set_level(SYS_POW_PIN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + } + }); + + power_save_timer_->SetEnabled(true); + } + + // Initialize I2C peripheral + void InitializeI2c() { + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + // Initialize spi peripheral + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = LCD_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = LCD_SCLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + middle_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + + if (LcdStatus_ != kDevicelcdbacklightOff) { + if (power_sleep_ == kDeviceNeutralSleep) { + power_save_timer_->WakeUp(); + power_sleep_ = kDeviceNoSleep; + } + + app.ToggleChatState(); + } + }); + + middle_button_.OnPressUp([this]() { + if (LcdStatus_ == kDevicelcdbacklightOff) { + Application::GetInstance().StopListening(); + Application::GetInstance().SetDeviceState(kDeviceStateIdle); + wake_status_ = kDeviceWaitWake; + } + + if (XiaozhiStatus_ == kDevice_Distributionnetwork || XiaozhiStatus_ == kDevice_Exit_Sleep) { + esp_timer_stop(power_manager_->timer_handle_); + gpio_set_level(CHG_CTRL_PIN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + gpio_set_level(SYS_POW_PIN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + } else if (XiaozhiStatus_ == kDevice_join_Sleep) { + GetBacklight()->RestoreBrightness(); + XiaozhiStatus_ = kDevice_null; + } + }); + + middle_button_.OnLongPress([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + + if (app.GetDeviceState() != kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + if (app.GetDeviceState() == kDeviceStateWifiConfiguring && power_status_ != kDeviceTypecSupply) { + GetBacklight()->SetBrightness(0); + XiaozhiStatus_ = kDevice_Distributionnetwork; + } else if (power_status_ == kDeviceBatterySupply && LcdStatus_ != kDevicelcdbacklightOff) { + Application::GetInstance().StartListening(); + GetBacklight()->SetBrightness(0); + XiaozhiStatus_ = kDevice_Exit_Sleep; + } else if (power_status_ == kDeviceTypecSupply && LcdStatus_ == kDevicelcdbacklightOn && Application::GetInstance().GetDeviceState() != kDeviceStateStarting) { + Application::GetInstance().StartListening(); + GetBacklight()->SetBrightness(0); + LcdStatus_ = kDevicelcdbacklightOff; + } else if (LcdStatus_ == kDevicelcdbacklightOff && (power_status_ == kDeviceTypecSupply || power_status_ == kDeviceBatterySupply)) { + GetDisplay()->SetChatMessage("system", ""); + GetBacklight()->RestoreBrightness(); + wake_status_ = kDeviceAwakened; + LcdStatus_ = kDevicelcdbacklightOn; + } + } + }); + + left_button_.OnClick([this]() { + if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) { + power_save_timer_->WakeUp(); + power_sleep_ = kDeviceNoSleep; + } + + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + left_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + + right_button_.OnClick([this]() { + if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) { + power_save_timer_->WakeUp(); + power_sleep_ = kDeviceNoSleep; + } + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + right_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + } + + void InitializeSt7789Display() { + ESP_LOGI(TAG, "Install panel IO"); + + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = LCD_CS_PIN; + io_config.dc_gpio_num = LCD_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 7; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io); + + ESP_LOGI(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = LCD_RST_PIN; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG, + esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_init(panel); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + atk_dnesp32s3_box0() : + right_button_(R_BUTTON_GPIO, false), + left_button_(L_BUTTON_GPIO, false), + middle_button_(M_BUTTON_GPIO, true) { + InitializeBoardPowerManager(); + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeI2c(); + InitializeSpi(); + InitializeSt7789Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + GPIO_NUM_NC, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8311_ADDR, + false); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(atk_dnesp32s3_box0); diff --git a/main/boards/atk-dnesp32s3-box0/config.h b/main/boards/atk-dnesp32s3-box0/config.h index f2c940e..80b47c4 100644 --- a/main/boards/atk-dnesp32s3-box0/config.h +++ b/main/boards/atk-dnesp32s3-box0/config.h @@ -1,84 +1,84 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -enum XiaozhiStatus { - kDevice_null, - kDevice_join_Sleep, - kDevice_Exit_Sleep, - kDevice_Distributionnetwork, - kDevice_Exit_Distributionnetwork, -}; - -enum LcdStatus { - kDevicelcdbacklightOn, - kDevicelcdbacklightOff, -}; - -enum WakeStatus { - kDeviceAwakened, - kDeviceWaitWake, - kDeviceSleeped, -}; - -enum PowerSupply { - kDeviceTypecSupply, - kDeviceBatterySupply, -}; - -enum PowerSleep { - kDeviceNoSleep, - kDeviceDeepSleep, - kDeviceNeutralSleep, -}; - -#define SYS_POW_PIN GPIO_NUM_2 -#define CHG_CTRL_PIN GPIO_NUM_47 -#define CODEC_PWR_PIN GPIO_NUM_14 -#define CHRG_PIN GPIO_NUM_48 - -#define BAT_VSEN_PIN GPIO_NUM_1 - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 16000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_10 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_11 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_12 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define AUDIO_SPK_GPIO_PIN GPIO_NUM_21 - -#define R_BUTTON_GPIO GPIO_NUM_0 -#define M_BUTTON_GPIO GPIO_NUM_4 -#define L_BUTTON_GPIO GPIO_NUM_3 - -#define BUILTIN_LED_GPIO GPIO_NUM_13 - -#define LCD_SCLK_PIN GPIO_NUM_39 -#define LCD_MOSI_PIN GPIO_NUM_40 -#define LCD_MISO_PIN GPIO_NUM_NC -#define LCD_DC_PIN GPIO_NUM_38 -#define LCD_CS_PIN GPIO_NUM_41 -#define LCD_RST_PIN GPIO_NUM_NC - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ - +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +enum XiaozhiStatus { + kDevice_null, + kDevice_join_Sleep, + kDevice_Exit_Sleep, + kDevice_Distributionnetwork, + kDevice_Exit_Distributionnetwork, +}; + +enum LcdStatus { + kDevicelcdbacklightOn, + kDevicelcdbacklightOff, +}; + +enum WakeStatus { + kDeviceAwakened, + kDeviceWaitWake, + kDeviceSleeped, +}; + +enum PowerSupply { + kDeviceTypecSupply, + kDeviceBatterySupply, +}; + +enum PowerSleep { + kDeviceNoSleep, + kDeviceDeepSleep, + kDeviceNeutralSleep, +}; + +#define SYS_POW_PIN GPIO_NUM_2 +#define CHG_CTRL_PIN GPIO_NUM_47 +#define CODEC_PWR_PIN GPIO_NUM_14 +#define CHRG_PIN GPIO_NUM_48 + +#define BAT_VSEN_PIN GPIO_NUM_1 + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 16000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_10 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_11 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_12 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define AUDIO_SPK_GPIO_PIN GPIO_NUM_21 + +#define R_BUTTON_GPIO GPIO_NUM_0 +#define M_BUTTON_GPIO GPIO_NUM_4 +#define L_BUTTON_GPIO GPIO_NUM_3 + +#define BUILTIN_LED_GPIO GPIO_NUM_13 + +#define LCD_SCLK_PIN GPIO_NUM_39 +#define LCD_MOSI_PIN GPIO_NUM_40 +#define LCD_MISO_PIN GPIO_NUM_NC +#define LCD_DC_PIN GPIO_NUM_38 +#define LCD_CS_PIN GPIO_NUM_41 +#define LCD_RST_PIN GPIO_NUM_NC + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ + diff --git a/main/boards/atk-dnesp32s3-box0/config.json b/main/boards/atk-dnesp32s3-box0/config.json index 20849d4..0768421 100644 --- a/main/boards/atk-dnesp32s3-box0/config.json +++ b/main/boards/atk-dnesp32s3-box0/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "atk-dnesp32s3-box0", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "atk-dnesp32s3-box0", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/atk-dnesp32s3-box0/power_manager.h b/main/boards/atk-dnesp32s3-box0/power_manager.h index e9a206c..ea52e8a 100644 --- a/main/boards/atk-dnesp32s3-box0/power_manager.h +++ b/main/boards/atk-dnesp32s3-box0/power_manager.h @@ -1,193 +1,193 @@ -#pragma once -#include -#include - -#include -#include -#include - - -class PowerManager { -private: - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 0; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value; - uint32_t temp_val = 0; - - gpio_set_level(CHG_CTRL_PIN, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - - for(int t = 0; t < 10; t ++) { - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); - temp_val += adc_value; - } - - gpio_set_level(CHG_CTRL_PIN, 1); - vTaskDelay(pdMS_TO_TICKS(100)); - - adc_value = temp_val / 10; - - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += value; - } - average_adc /= adc_values_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {2951, 0}, /* 3.80V */ - {3019, 20}, - {3037, 40}, - {3091, 60}, /* 3.88 */ - {3124, 80}, - {3231, 100} - }; - - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - low_voltage_ = adc_value; - - ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - -public: - esp_timer_handle_t timer_handle_; - uint16_t low_voltage_ = 2877; - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include + +#include +#include +#include + + +class PowerManager { +private: + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 0; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + uint32_t temp_val = 0; + + gpio_set_level(CHG_CTRL_PIN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + + for(int t = 0; t < 10; t ++) { + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); + temp_val += adc_value; + } + + gpio_set_level(CHG_CTRL_PIN, 1); + vTaskDelay(pdMS_TO_TICKS(100)); + + adc_value = temp_val / 10; + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {2951, 0}, /* 3.80V */ + {3019, 20}, + {3037, 40}, + {3091, 60}, /* 3.88 */ + {3124, 80}, + {3231, 100} + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + low_voltage_ = adc_value; + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + esp_timer_handle_t timer_handle_; + uint16_t low_voltage_ = 2877; + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc b/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc index 4a906db..0544eaf 100644 --- a/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc +++ b/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc @@ -1,477 +1,477 @@ -#include "dual_network_board.h" -#include "codecs/es8389_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "power_manager.h" - -#include "i2c_device.h" -#include -#include -#include - -#include -#include -#include "esp_io_expander_tca95xx_16bit.h" - -#define TAG "atk_dnesp32s3_box2_4g" - -class atk_dnesp32s3_box2_4g : public DualNetworkBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - LcdDisplay* display_; - esp_io_expander_handle_t io_exp_handle; - button_handle_t btns; - button_driver_t* btn_driver_ = nullptr; - static atk_dnesp32s3_box2_4g* instance_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - PowerSupply power_status_; - esp_timer_handle_t wake_timer_handle_; - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - int ticks_ = 0; - const int kChgCtrlInterval = 5; - - void InitializeBoardPowerManager() { - instance_ = this; - - if (IoExpanderGetLevel(XIO_CHRG) == 0) { - power_status_ = kDeviceTypecSupply; - } else { - power_status_ = kDeviceBatterySupply; - } - - esp_timer_create_args_t wake_display_timer_args = { - .callback = [](void *arg) { - atk_dnesp32s3_box2_4g* self = static_cast(arg); - - self->ticks_ ++; - if (self->ticks_ % self->kChgCtrlInterval == 0) { - if (self->IoExpanderGetLevel(XIO_CHRG) == 0) { - self->power_status_ = kDeviceTypecSupply; - } else { - self->power_status_ = kDeviceBatterySupply; - } - - /* 低于某个电量,会自动关机 */ - if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) { - esp_timer_stop(self->power_manager_->timer_handle_); - - esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - - esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT); - esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - } - } - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "wake_update_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000)); - } - - void InitializePowerManager() { - power_manager_ = new PowerManager(io_exp_handle); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - if (power_status_ == kDeviceBatterySupply) { - GetBacklight()->SetBrightness(0); - esp_timer_stop(power_manager_->timer_handle_); - esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0); - } - }); - - power_save_timer_->SetEnabled(true); - } - - void audio_volume_change(bool direction) { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume(); - - if (direction) { - volume += 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - } else { - volume -= 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - } - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - } - - void audio_volume_minimum(){ - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - } - - void audio_volume_maxmum(){ - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - } - - esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) { - return esp_io_expander_set_level(io_exp_handle, pin_mask, level); - } - - uint8_t IoExpanderGetLevel(uint16_t pin_mask) { - uint32_t pin_val = 0; - esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val); - pin_mask &= DRV_IO_EXP_INPUT_MASK; - return (uint8_t)((pin_val & pin_mask) ? 1 : 0); - } - - void InitializeIoExpander() { - esp_err_t ret = ESP_OK; - esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle); - - ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT); - ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT); - - ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1); - ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1); - ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1); - ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1); - ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1); - ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0); - - assert(ret == ESP_OK); - } - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeButtons() { - instance_ = this; - - button_config_t l_btn_cfg = { - .long_press_time = 800, - .short_press_time = 500 - }; - - button_config_t m_btn_cfg = { - .long_press_time = 800, - .short_press_time = 500 - }; - - button_config_t r_btn_cfg = { - .long_press_time = 800, - .short_press_time = 500 - }; - - button_driver_t* xio_l_btn_driver_ = nullptr; - button_driver_t* xio_m_btn_driver_ = nullptr; - - button_handle_t l_btn_handle = NULL; - button_handle_t m_btn_handle = NULL; - button_handle_t r_btn_handle = NULL; - - xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); - xio_l_btn_driver_->enable_power_save = false; - xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { - return !instance_->IoExpanderGetLevel(XIO_KEY_L); - }; - ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle)); - - xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); - xio_m_btn_driver_->enable_power_save = false; - xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { - return instance_->IoExpanderGetLevel(XIO_KEY_M); - }; - ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle)); - - button_gpio_config_t r_cfg = { - .gpio_num = R_BUTTON_GPIO, - .active_level = BUTTON_INACTIVE, - .enable_power_save = false, - .disable_pull = false - }; - ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle)); - - iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->power_save_timer_->WakeUp(); - self->audio_volume_change(false); - }, this); - - iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->power_save_timer_->WakeUp(); - self->audio_volume_minimum(); - }, this); - - iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (self->GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { - } - else { - app.ToggleChatState(); - } - } else { - app.ToggleChatState(); - } - }, this); - - iot_button_register_cb(m_btn_handle, BUTTON_DOUBLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { - self->SwitchNetworkType(); - } - }, this); - - iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - - auto& app = Application::GetInstance(); - if (self->GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - auto& wifi_board = static_cast(self->GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); - } - } - - if (self->power_status_ == kDeviceBatterySupply) { - auto backlight = Board::GetInstance().GetBacklight(); - backlight->SetBrightness(0); - esp_timer_stop(self->power_manager_->timer_handle_); - esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - } - }, this); - - iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->power_save_timer_->WakeUp(); - self->audio_volume_change(true); - }, this); - - iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->power_save_timer_->WakeUp(); - self->audio_volume_maxmum(); - }, this); - } - - void InitializeSt7789Display() { - ESP_LOGI(TAG, "Install panel IO"); - - /*RD PIN */ - gpio_config_t gpio_init_struct; - gpio_init_struct.intr_type = GPIO_INTR_DISABLE; - gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT; - gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD; - gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; - gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&gpio_init_struct); - gpio_set_level(LCD_PIN_RD, 1); - - /* BL PIN */ - gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN; - gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; - gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&gpio_init_struct); - - esp_lcd_i80_bus_handle_t i80_bus = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = LCD_PIN_DC, - .wr_gpio_num = LCD_PIN_WR, - .clk_src = LCD_CLK_SRC_DEFAULT, - .data_gpio_nums = { - LCD_PIN_D0, - LCD_PIN_D1, - LCD_PIN_D2, - LCD_PIN_D3, - LCD_PIN_D4, - LCD_PIN_D5, - LCD_PIN_D6, - LCD_PIN_D7, - }, - .bus_width = 8, - .max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t), - .psram_trans_align = 64, - .sram_trans_align = 4, - }; - ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = LCD_PIN_CS, - .pclk_hz = (20 * 1000 * 1000), - .trans_queue_depth = 7, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .dc_levels = { - .dc_idle_level = 1, - .dc_cmd_level = 0, - .dc_dummy_level = 0, - .dc_data_level = 1, - }, - .flags = { - .cs_active_high = 0, - .pclk_active_neg = 0, - .pclk_idle_low = 0, - }, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io)); - - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = LCD_PIN_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, true); - esp_lcd_panel_set_gap(panel, 0, 0); - esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3); - esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4); - esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3); - esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5); - esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2); - esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2); - esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14); - esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14); - esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - atk_dnesp32s3_box2_4g() : - DualNetworkBoard(Module_4G_TX_PIN, Module_4G_RX_PIN) { - InitializeI2c(); - InitializeIoExpander(); - InitializePowerSaveTimer(); - InitializePowerManager(); - InitializeSt7789Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - InitializeBoardPowerManager(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8389AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8389_ADDR - ); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - DualNetworkBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(atk_dnesp32s3_box2_4g); - -// 定义静态成员变量 -atk_dnesp32s3_box2_4g* atk_dnesp32s3_box2_4g::instance_ = nullptr; +#include "dual_network_board.h" +#include "codecs/es8389_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "power_manager.h" + +#include "i2c_device.h" +#include +#include +#include + +#include +#include +#include "esp_io_expander_tca95xx_16bit.h" + +#define TAG "atk_dnesp32s3_box2_4g" + +class atk_dnesp32s3_box2_4g : public DualNetworkBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + LcdDisplay* display_; + esp_io_expander_handle_t io_exp_handle; + button_handle_t btns; + button_driver_t* btn_driver_ = nullptr; + static atk_dnesp32s3_box2_4g* instance_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + PowerSupply power_status_; + esp_timer_handle_t wake_timer_handle_; + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + int ticks_ = 0; + const int kChgCtrlInterval = 5; + + void InitializeBoardPowerManager() { + instance_ = this; + + if (IoExpanderGetLevel(XIO_CHRG) == 0) { + power_status_ = kDeviceTypecSupply; + } else { + power_status_ = kDeviceBatterySupply; + } + + esp_timer_create_args_t wake_display_timer_args = { + .callback = [](void *arg) { + atk_dnesp32s3_box2_4g* self = static_cast(arg); + + self->ticks_ ++; + if (self->ticks_ % self->kChgCtrlInterval == 0) { + if (self->IoExpanderGetLevel(XIO_CHRG) == 0) { + self->power_status_ = kDeviceTypecSupply; + } else { + self->power_status_ = kDeviceBatterySupply; + } + + /* 低于某个电量,会自动关机 */ + if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) { + esp_timer_stop(self->power_manager_->timer_handle_); + + esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + + esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT); + esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + } + } + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "wake_update_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000)); + } + + void InitializePowerManager() { + power_manager_ = new PowerManager(io_exp_handle); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + if (power_status_ == kDeviceBatterySupply) { + GetBacklight()->SetBrightness(0); + esp_timer_stop(power_manager_->timer_handle_); + esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0); + } + }); + + power_save_timer_->SetEnabled(true); + } + + void audio_volume_change(bool direction) { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume(); + + if (direction) { + volume += 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + } else { + volume -= 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + } + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + } + + void audio_volume_minimum(){ + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + } + + void audio_volume_maxmum(){ + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + } + + esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) { + return esp_io_expander_set_level(io_exp_handle, pin_mask, level); + } + + uint8_t IoExpanderGetLevel(uint16_t pin_mask) { + uint32_t pin_val = 0; + esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val); + pin_mask &= DRV_IO_EXP_INPUT_MASK; + return (uint8_t)((pin_val & pin_mask) ? 1 : 0); + } + + void InitializeIoExpander() { + esp_err_t ret = ESP_OK; + esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle); + + ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT); + ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT); + + ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1); + ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1); + ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1); + ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1); + ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1); + ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0); + + assert(ret == ESP_OK); + } + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeButtons() { + instance_ = this; + + button_config_t l_btn_cfg = { + .long_press_time = 800, + .short_press_time = 500 + }; + + button_config_t m_btn_cfg = { + .long_press_time = 800, + .short_press_time = 500 + }; + + button_config_t r_btn_cfg = { + .long_press_time = 800, + .short_press_time = 500 + }; + + button_driver_t* xio_l_btn_driver_ = nullptr; + button_driver_t* xio_m_btn_driver_ = nullptr; + + button_handle_t l_btn_handle = NULL; + button_handle_t m_btn_handle = NULL; + button_handle_t r_btn_handle = NULL; + + xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + xio_l_btn_driver_->enable_power_save = false; + xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return !instance_->IoExpanderGetLevel(XIO_KEY_L); + }; + ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle)); + + xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + xio_m_btn_driver_->enable_power_save = false; + xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return instance_->IoExpanderGetLevel(XIO_KEY_M); + }; + ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle)); + + button_gpio_config_t r_cfg = { + .gpio_num = R_BUTTON_GPIO, + .active_level = BUTTON_INACTIVE, + .enable_power_save = false, + .disable_pull = false + }; + ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle)); + + iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_change(false); + }, this); + + iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_minimum(); + }, this); + + iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (self->GetNetworkType() == NetworkType::WIFI) { + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + } + else { + app.ToggleChatState(); + } + } else { + app.ToggleChatState(); + } + }, this); + + iot_button_register_cb(m_btn_handle, BUTTON_DOUBLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + self->SwitchNetworkType(); + } + }, this); + + iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + + auto& app = Application::GetInstance(); + if (self->GetNetworkType() == NetworkType::WIFI) { + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + auto& wifi_board = static_cast(self->GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + } + } + + if (self->power_status_ == kDeviceBatterySupply) { + auto backlight = Board::GetInstance().GetBacklight(); + backlight->SetBrightness(0); + esp_timer_stop(self->power_manager_->timer_handle_); + esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + } + }, this); + + iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_change(true); + }, this); + + iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_maxmum(); + }, this); + } + + void InitializeSt7789Display() { + ESP_LOGI(TAG, "Install panel IO"); + + /*RD PIN */ + gpio_config_t gpio_init_struct; + gpio_init_struct.intr_type = GPIO_INTR_DISABLE; + gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT; + gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD; + gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; + gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&gpio_init_struct); + gpio_set_level(LCD_PIN_RD, 1); + + /* BL PIN */ + gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN; + gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; + gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&gpio_init_struct); + + esp_lcd_i80_bus_handle_t i80_bus = NULL; + esp_lcd_i80_bus_config_t bus_config = { + .dc_gpio_num = LCD_PIN_DC, + .wr_gpio_num = LCD_PIN_WR, + .clk_src = LCD_CLK_SRC_DEFAULT, + .data_gpio_nums = { + LCD_PIN_D0, + LCD_PIN_D1, + LCD_PIN_D2, + LCD_PIN_D3, + LCD_PIN_D4, + LCD_PIN_D5, + LCD_PIN_D6, + LCD_PIN_D7, + }, + .bus_width = 8, + .max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t), + .psram_trans_align = 64, + .sram_trans_align = 4, + }; + ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); + + esp_lcd_panel_io_i80_config_t io_config = { + .cs_gpio_num = LCD_PIN_CS, + .pclk_hz = (20 * 1000 * 1000), + .trans_queue_depth = 7, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .dc_levels = { + .dc_idle_level = 1, + .dc_cmd_level = 0, + .dc_dummy_level = 0, + .dc_data_level = 1, + }, + .flags = { + .cs_active_high = 0, + .pclk_active_neg = 0, + .pclk_idle_low = 0, + }, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io)); + + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = LCD_PIN_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_set_gap(panel, 0, 0); + esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3); + esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4); + esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3); + esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5); + esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2); + esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2); + esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14); + esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14); + esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + atk_dnesp32s3_box2_4g() : + DualNetworkBoard(Module_4G_TX_PIN, Module_4G_RX_PIN) { + InitializeI2c(); + InitializeIoExpander(); + InitializePowerSaveTimer(); + InitializePowerManager(); + InitializeSt7789Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + InitializeBoardPowerManager(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8389AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8389_ADDR + ); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + DualNetworkBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(atk_dnesp32s3_box2_4g); + +// 定义静态成员变量 +atk_dnesp32s3_box2_4g* atk_dnesp32s3_box2_4g::instance_ = nullptr; diff --git a/main/boards/atk-dnesp32s3-box2-4g/config.h b/main/boards/atk-dnesp32s3-box2-4g/config.h index 70e9d4d..53016ad 100644 --- a/main/boards/atk-dnesp32s3-box2-4g/config.h +++ b/main/boards/atk-dnesp32s3-box2-4g/config.h @@ -1,80 +1,80 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - - -#include - -enum PowerSupply { - kDeviceTypecSupply, - kDeviceBatterySupply, -}; - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 16000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_42 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47 -#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR - -#define SPISD_PIN_MOSI GPIO_NUM_16 -#define SPISD_PIN_MISO GPIO_NUM_18 -#define SPISD_PIN_CLK GPIO_NUM_17 -#define SPISD_PIN_TS GPIO_NUM_15 - -#define R_BUTTON_GPIO GPIO_NUM_0 - -#define XL9555_INT_GPIO GPIO_NUM_2 -#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3) -#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4) -#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5) -#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6) -#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7) -#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8) -#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9) -#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10) -#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11) -#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12) -#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13) -#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14) -#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15) - -#define DRV_IO_EXP_OUTPUT_MASK 0x3F18 -#define DRV_IO_EXP_INPUT_MASK 0xC0E7 - -#define LCD_PIN_CS GPIO_NUM_14 -#define LCD_PIN_DC GPIO_NUM_12 -#define LCD_PIN_RD GPIO_NUM_10 -#define LCD_PIN_WR GPIO_NUM_11 -#define LCD_PIN_RST GPIO_NUM_NC -#define LCD_PIN_D0 GPIO_NUM_13 -#define LCD_PIN_D1 GPIO_NUM_9 -#define LCD_PIN_D2 GPIO_NUM_8 -#define LCD_PIN_D3 GPIO_NUM_7 -#define LCD_PIN_D4 GPIO_NUM_6 -#define LCD_PIN_D5 GPIO_NUM_5 -#define LCD_PIN_D6 GPIO_NUM_4 -#define LCD_PIN_D7 GPIO_NUM_3 - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define Module_4G_RX_PIN GPIO_NUM_44 -#define Module_4G_TX_PIN GPIO_NUM_43 - -#endif // _BOARD_CONFIG_H_ - +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + + +#include + +enum PowerSupply { + kDeviceTypecSupply, + kDeviceBatterySupply, +}; + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 16000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_42 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47 +#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR + +#define SPISD_PIN_MOSI GPIO_NUM_16 +#define SPISD_PIN_MISO GPIO_NUM_18 +#define SPISD_PIN_CLK GPIO_NUM_17 +#define SPISD_PIN_TS GPIO_NUM_15 + +#define R_BUTTON_GPIO GPIO_NUM_0 + +#define XL9555_INT_GPIO GPIO_NUM_2 +#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3) +#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4) +#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5) +#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6) +#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7) +#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8) +#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9) +#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10) +#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11) +#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12) +#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13) +#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14) +#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15) + +#define DRV_IO_EXP_OUTPUT_MASK 0x3F18 +#define DRV_IO_EXP_INPUT_MASK 0xC0E7 + +#define LCD_PIN_CS GPIO_NUM_14 +#define LCD_PIN_DC GPIO_NUM_12 +#define LCD_PIN_RD GPIO_NUM_10 +#define LCD_PIN_WR GPIO_NUM_11 +#define LCD_PIN_RST GPIO_NUM_NC +#define LCD_PIN_D0 GPIO_NUM_13 +#define LCD_PIN_D1 GPIO_NUM_9 +#define LCD_PIN_D2 GPIO_NUM_8 +#define LCD_PIN_D3 GPIO_NUM_7 +#define LCD_PIN_D4 GPIO_NUM_6 +#define LCD_PIN_D5 GPIO_NUM_5 +#define LCD_PIN_D6 GPIO_NUM_4 +#define LCD_PIN_D7 GPIO_NUM_3 + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define Module_4G_RX_PIN GPIO_NUM_44 +#define Module_4G_TX_PIN GPIO_NUM_43 + +#endif // _BOARD_CONFIG_H_ + diff --git a/main/boards/atk-dnesp32s3-box2-4g/config.json b/main/boards/atk-dnesp32s3-box2-4g/config.json index 076253a..71e3c34 100644 --- a/main/boards/atk-dnesp32s3-box2-4g/config.json +++ b/main/boards/atk-dnesp32s3-box2-4g/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "atk-dnesp32s3-box2-4g", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "atk-dnesp32s3-box2-4g", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/atk-dnesp32s3-box2-4g/power_manager.h b/main/boards/atk-dnesp32s3-box2-4g/power_manager.h index 103ce88..1a5de70 100644 --- a/main/boards/atk-dnesp32s3-box2-4g/power_manager.h +++ b/main/boards/atk-dnesp32s3-box2-4g/power_manager.h @@ -1,195 +1,195 @@ -#pragma once -#include -#include -#include "esp_io_expander_tca95xx_16bit.h" -#include -#include -#include - - -class PowerManager { -private: - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - esp_io_expander_handle_t xl9555_; - uint32_t pin_val = 0; - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - // Get charging status - esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val); - bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value; - uint32_t temp_val = 0; - - esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0); - vTaskDelay(pdMS_TO_TICKS(500)); - - for(int t = 0; t < 10; t ++) { - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); - temp_val += adc_value; - } - - esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT); - - adc_value = temp_val / 10; - - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += value; - } - average_adc /= adc_values_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {2696, 0}, /* 3.48V -屏幕闪屏 */ - {2724, 20}, /* 3.53V */ - {2861, 40}, /* 3.7V */ - {3038, 60}, /* 3.90V */ - {3150, 80}, /* 4.02V */ - {3280, 100} /* 4.14V */ - }; - - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - low_voltage_ = adc_value; - - // ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - -public: - esp_timer_handle_t timer_handle_; - uint16_t low_voltage_ = 2630; - PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) { - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include +#include "esp_io_expander_tca95xx_16bit.h" +#include +#include +#include + + +class PowerManager { +private: + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + esp_io_expander_handle_t xl9555_; + uint32_t pin_val = 0; + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val); + bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + uint32_t temp_val = 0; + + esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0); + vTaskDelay(pdMS_TO_TICKS(500)); + + for(int t = 0; t < 10; t ++) { + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); + temp_val += adc_value; + } + + esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT); + + adc_value = temp_val / 10; + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {2696, 0}, /* 3.48V -屏幕闪屏 */ + {2724, 20}, /* 3.53V */ + {2861, 40}, /* 3.7V */ + {3038, 60}, /* 3.90V */ + {3150, 80}, /* 4.02V */ + {3280, 100} /* 4.14V */ + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + low_voltage_ = adc_value; + + // ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + esp_timer_handle_t timer_handle_; + uint16_t low_voltage_ = 2630; + PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) { + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc b/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc index acc2e21..1557bb5 100644 --- a/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc +++ b/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc @@ -1,456 +1,456 @@ -#include "wifi_board.h" -#include "codecs/es8389_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "power_manager.h" - -#include "i2c_device.h" -#include -#include -#include - -#include -#include -#include "esp_io_expander_tca95xx_16bit.h" - -#define TAG "atk_dnesp32s3_box2_wifi" - -class atk_dnesp32s3_box2_wifi : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - LcdDisplay* display_; - esp_io_expander_handle_t io_exp_handle; - button_handle_t btns; - button_driver_t* btn_driver_ = nullptr; - static atk_dnesp32s3_box2_wifi* instance_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - PowerSupply power_status_; - esp_timer_handle_t wake_timer_handle_; - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - int ticks_ = 0; - const int kChgCtrlInterval = 5; - - void InitializeBoardPowerManager() { - instance_ = this; - - if (IoExpanderGetLevel(XIO_CHRG) == 0) { - power_status_ = kDeviceTypecSupply; - } else { - power_status_ = kDeviceBatterySupply; - } - - esp_timer_create_args_t wake_display_timer_args = { - .callback = [](void *arg) { - atk_dnesp32s3_box2_wifi* self = static_cast(arg); - - self->ticks_ ++; - if (self->ticks_ % self->kChgCtrlInterval == 0) { - if (self->IoExpanderGetLevel(XIO_CHRG) == 0) { - self->power_status_ = kDeviceTypecSupply; - } else { - self->power_status_ = kDeviceBatterySupply; - } - - /* 低于某个电量,会自动关机 */ - if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) { - esp_timer_stop(self->power_manager_->timer_handle_); - - esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - - esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT); - esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - } - } - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "wake_update_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000)); - } - - void InitializePowerManager() { - power_manager_ = new PowerManager(io_exp_handle); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - if (power_status_ == kDeviceBatterySupply) { - GetBacklight()->SetBrightness(0); - esp_timer_stop(power_manager_->timer_handle_); - esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0); - } - }); - - power_save_timer_->SetEnabled(true); - } - - void audio_volume_change(bool direction) { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume(); - - if (direction) { - volume += 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - } else { - volume -= 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - } - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - } - - void audio_volume_minimum(){ - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - } - - void audio_volume_maxmum(){ - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - } - - esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) { - return esp_io_expander_set_level(io_exp_handle, pin_mask, level); - } - - uint8_t IoExpanderGetLevel(uint16_t pin_mask) { - uint32_t pin_val = 0; - esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val); - pin_mask &= DRV_IO_EXP_INPUT_MASK; - return (uint8_t)((pin_val & pin_mask) ? 1 : 0); - } - - void InitializeIoExpander() { - esp_err_t ret = ESP_OK; - esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle); - - ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT); - ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT); - - ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1); - ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1); - ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1); - ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1); - ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1); - ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0); - - assert(ret == ESP_OK); - } - - // Initialize I2C peripheral - void InitializeI2c() { - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeButtons() { - instance_ = this; - - button_config_t l_btn_cfg = { - .long_press_time = 800, - .short_press_time = 500 - }; - - button_config_t m_btn_cfg = { - .long_press_time = 800, - .short_press_time = 500 - }; - - button_config_t r_btn_cfg = { - .long_press_time = 800, - .short_press_time = 500 - }; - - button_driver_t* xio_l_btn_driver_ = nullptr; - button_driver_t* xio_m_btn_driver_ = nullptr; - - button_handle_t l_btn_handle = NULL; - button_handle_t m_btn_handle = NULL; - button_handle_t r_btn_handle = NULL; - - xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); - xio_l_btn_driver_->enable_power_save = false; - xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { - return !instance_->IoExpanderGetLevel(XIO_KEY_L); - }; - ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle)); - - xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); - xio_m_btn_driver_->enable_power_save = false; - xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { - return instance_->IoExpanderGetLevel(XIO_KEY_M); - }; - ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle)); - - button_gpio_config_t r_cfg = { - .gpio_num = R_BUTTON_GPIO, - .active_level = BUTTON_INACTIVE, - .enable_power_save = false, - .disable_pull = false - }; - ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle)); - - iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->power_save_timer_->WakeUp(); - self->audio_volume_change(false); - }, this); - - iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->power_save_timer_->WakeUp(); - self->audio_volume_minimum(); - }, this); - - iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - app.ToggleChatState(); - }, this); - - iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); - } - - if (self->power_status_ == kDeviceBatterySupply) { - auto backlight = Board::GetInstance().GetBacklight(); - backlight->SetBrightness(0); - esp_timer_stop(self->power_manager_->timer_handle_); - esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0); - vTaskDelay(pdMS_TO_TICKS(100)); - } - }, this); - - iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->power_save_timer_->WakeUp(); - self->audio_volume_change(true); - }, this); - - iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->power_save_timer_->WakeUp(); - self->audio_volume_maxmum(); - }, this); - } - - void InitializeSt7789Display() { - ESP_LOGI(TAG, "Install panel IO"); - - /* RD PIN */ - gpio_config_t gpio_init_struct; - gpio_init_struct.intr_type = GPIO_INTR_DISABLE; - gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT; - gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD; - gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; - gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&gpio_init_struct); - gpio_set_level(LCD_PIN_RD, 1); - - /* BL PIN */ - gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN; - gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; - gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&gpio_init_struct); - - esp_lcd_i80_bus_handle_t i80_bus = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = LCD_PIN_DC, - .wr_gpio_num = LCD_PIN_WR, - .clk_src = LCD_CLK_SRC_DEFAULT, - .data_gpio_nums = { - LCD_PIN_D0, - LCD_PIN_D1, - LCD_PIN_D2, - LCD_PIN_D3, - LCD_PIN_D4, - LCD_PIN_D5, - LCD_PIN_D6, - LCD_PIN_D7, - }, - .bus_width = 8, - .max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t), - .psram_trans_align = 64, - .sram_trans_align = 4, - }; - ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = LCD_PIN_CS, - .pclk_hz = (20 * 1000 * 1000), - .trans_queue_depth = 7, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .dc_levels = { - .dc_idle_level = 1, - .dc_cmd_level = 0, - .dc_dummy_level = 0, - .dc_data_level = 1, - }, - .flags = { - .cs_active_high = 0, - .pclk_active_neg = 0, - .pclk_idle_low = 0, - }, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io)); - - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = LCD_PIN_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, true); - esp_lcd_panel_set_gap(panel, 0, 0); - esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3); - esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4); - esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3); - esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5); - esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2); - esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2); - esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1); - esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14); - esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14); - esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - atk_dnesp32s3_box2_wifi() { - InitializeI2c(); - InitializeIoExpander(); - InitializePowerSaveTimer(); - InitializePowerManager(); - InitializeSt7789Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - InitializeBoardPowerManager(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8389AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8389_ADDR, - false); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(atk_dnesp32s3_box2_wifi); - -// 定义静态成员变量 -atk_dnesp32s3_box2_wifi* atk_dnesp32s3_box2_wifi::instance_ = nullptr; +#include "wifi_board.h" +#include "codecs/es8389_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "power_manager.h" + +#include "i2c_device.h" +#include +#include +#include + +#include +#include +#include "esp_io_expander_tca95xx_16bit.h" + +#define TAG "atk_dnesp32s3_box2_wifi" + +class atk_dnesp32s3_box2_wifi : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + LcdDisplay* display_; + esp_io_expander_handle_t io_exp_handle; + button_handle_t btns; + button_driver_t* btn_driver_ = nullptr; + static atk_dnesp32s3_box2_wifi* instance_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + PowerSupply power_status_; + esp_timer_handle_t wake_timer_handle_; + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + int ticks_ = 0; + const int kChgCtrlInterval = 5; + + void InitializeBoardPowerManager() { + instance_ = this; + + if (IoExpanderGetLevel(XIO_CHRG) == 0) { + power_status_ = kDeviceTypecSupply; + } else { + power_status_ = kDeviceBatterySupply; + } + + esp_timer_create_args_t wake_display_timer_args = { + .callback = [](void *arg) { + atk_dnesp32s3_box2_wifi* self = static_cast(arg); + + self->ticks_ ++; + if (self->ticks_ % self->kChgCtrlInterval == 0) { + if (self->IoExpanderGetLevel(XIO_CHRG) == 0) { + self->power_status_ = kDeviceTypecSupply; + } else { + self->power_status_ = kDeviceBatterySupply; + } + + /* 低于某个电量,会自动关机 */ + if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) { + esp_timer_stop(self->power_manager_->timer_handle_); + + esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + + esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT); + esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + } + } + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "wake_update_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000)); + } + + void InitializePowerManager() { + power_manager_ = new PowerManager(io_exp_handle); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + if (power_status_ == kDeviceBatterySupply) { + GetBacklight()->SetBrightness(0); + esp_timer_stop(power_manager_->timer_handle_); + esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0); + } + }); + + power_save_timer_->SetEnabled(true); + } + + void audio_volume_change(bool direction) { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume(); + + if (direction) { + volume += 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + } else { + volume -= 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + } + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + } + + void audio_volume_minimum(){ + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + } + + void audio_volume_maxmum(){ + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + } + + esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) { + return esp_io_expander_set_level(io_exp_handle, pin_mask, level); + } + + uint8_t IoExpanderGetLevel(uint16_t pin_mask) { + uint32_t pin_val = 0; + esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val); + pin_mask &= DRV_IO_EXP_INPUT_MASK; + return (uint8_t)((pin_val & pin_mask) ? 1 : 0); + } + + void InitializeIoExpander() { + esp_err_t ret = ESP_OK; + esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle); + + ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT); + ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT); + + ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1); + ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1); + ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1); + ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1); + ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1); + ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0); + + assert(ret == ESP_OK); + } + + // Initialize I2C peripheral + void InitializeI2c() { + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeButtons() { + instance_ = this; + + button_config_t l_btn_cfg = { + .long_press_time = 800, + .short_press_time = 500 + }; + + button_config_t m_btn_cfg = { + .long_press_time = 800, + .short_press_time = 500 + }; + + button_config_t r_btn_cfg = { + .long_press_time = 800, + .short_press_time = 500 + }; + + button_driver_t* xio_l_btn_driver_ = nullptr; + button_driver_t* xio_m_btn_driver_ = nullptr; + + button_handle_t l_btn_handle = NULL; + button_handle_t m_btn_handle = NULL; + button_handle_t r_btn_handle = NULL; + + xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + xio_l_btn_driver_->enable_power_save = false; + xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return !instance_->IoExpanderGetLevel(XIO_KEY_L); + }; + ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle)); + + xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + xio_m_btn_driver_->enable_power_save = false; + xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return instance_->IoExpanderGetLevel(XIO_KEY_M); + }; + ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle)); + + button_gpio_config_t r_cfg = { + .gpio_num = R_BUTTON_GPIO, + .active_level = BUTTON_INACTIVE, + .enable_power_save = false, + .disable_pull = false + }; + ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle)); + + iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_change(false); + }, this); + + iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_minimum(); + }, this); + + iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + app.ToggleChatState(); + }, this); + + iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + self->ResetWifiConfiguration(); + } + + if (self->power_status_ == kDeviceBatterySupply) { + auto backlight = Board::GetInstance().GetBacklight(); + backlight->SetBrightness(0); + esp_timer_stop(self->power_manager_->timer_handle_); + esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + } + }, this); + + iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_change(true); + }, this); + + iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_maxmum(); + }, this); + } + + void InitializeSt7789Display() { + ESP_LOGI(TAG, "Install panel IO"); + + /* RD PIN */ + gpio_config_t gpio_init_struct; + gpio_init_struct.intr_type = GPIO_INTR_DISABLE; + gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT; + gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD; + gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; + gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&gpio_init_struct); + gpio_set_level(LCD_PIN_RD, 1); + + /* BL PIN */ + gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN; + gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; + gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&gpio_init_struct); + + esp_lcd_i80_bus_handle_t i80_bus = NULL; + esp_lcd_i80_bus_config_t bus_config = { + .dc_gpio_num = LCD_PIN_DC, + .wr_gpio_num = LCD_PIN_WR, + .clk_src = LCD_CLK_SRC_DEFAULT, + .data_gpio_nums = { + LCD_PIN_D0, + LCD_PIN_D1, + LCD_PIN_D2, + LCD_PIN_D3, + LCD_PIN_D4, + LCD_PIN_D5, + LCD_PIN_D6, + LCD_PIN_D7, + }, + .bus_width = 8, + .max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t), + .psram_trans_align = 64, + .sram_trans_align = 4, + }; + ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); + + esp_lcd_panel_io_i80_config_t io_config = { + .cs_gpio_num = LCD_PIN_CS, + .pclk_hz = (20 * 1000 * 1000), + .trans_queue_depth = 7, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .dc_levels = { + .dc_idle_level = 1, + .dc_cmd_level = 0, + .dc_dummy_level = 0, + .dc_data_level = 1, + }, + .flags = { + .cs_active_high = 0, + .pclk_active_neg = 0, + .pclk_idle_low = 0, + }, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io)); + + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = LCD_PIN_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_set_gap(panel, 0, 0); + esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3); + esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4); + esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3); + esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5); + esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2); + esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2); + esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1); + esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14); + esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14); + esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + atk_dnesp32s3_box2_wifi() { + InitializeI2c(); + InitializeIoExpander(); + InitializePowerSaveTimer(); + InitializePowerManager(); + InitializeSt7789Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + InitializeBoardPowerManager(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8389AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8389_ADDR, + false); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(atk_dnesp32s3_box2_wifi); + +// 定义静态成员变量 +atk_dnesp32s3_box2_wifi* atk_dnesp32s3_box2_wifi::instance_ = nullptr; diff --git a/main/boards/atk-dnesp32s3-box2-wifi/config.h b/main/boards/atk-dnesp32s3-box2-wifi/config.h index a12384c..1a8abcf 100644 --- a/main/boards/atk-dnesp32s3-box2-wifi/config.h +++ b/main/boards/atk-dnesp32s3-box2-wifi/config.h @@ -1,71 +1,71 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -enum PowerSupply { - kDeviceTypecSupply, - kDeviceBatterySupply, -}; - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 16000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_42 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47 -#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR - -#define R_BUTTON_GPIO GPIO_NUM_0 - -#define XL9555_INT_GPIO GPIO_NUM_2 -#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3) -#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4) -#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5) -#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6) -#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7) -#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8) -#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9) -#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10) -#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11) -#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12) -#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13) -#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14) -#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15) - -#define DRV_IO_EXP_OUTPUT_MASK 0x3F18 -#define DRV_IO_EXP_INPUT_MASK 0xC0E7 - -#define LCD_PIN_CS GPIO_NUM_14 -#define LCD_PIN_DC GPIO_NUM_12 -#define LCD_PIN_RD GPIO_NUM_10 -#define LCD_PIN_WR GPIO_NUM_11 -#define LCD_PIN_RST GPIO_NUM_NC -#define LCD_PIN_D0 GPIO_NUM_13 -#define LCD_PIN_D1 GPIO_NUM_9 -#define LCD_PIN_D2 GPIO_NUM_8 -#define LCD_PIN_D3 GPIO_NUM_7 -#define LCD_PIN_D4 GPIO_NUM_6 -#define LCD_PIN_D5 GPIO_NUM_5 -#define LCD_PIN_D6 GPIO_NUM_4 -#define LCD_PIN_D7 GPIO_NUM_3 - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ - +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +enum PowerSupply { + kDeviceTypecSupply, + kDeviceBatterySupply, +}; + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 16000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_42 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47 +#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR + +#define R_BUTTON_GPIO GPIO_NUM_0 + +#define XL9555_INT_GPIO GPIO_NUM_2 +#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3) +#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4) +#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5) +#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6) +#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7) +#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8) +#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9) +#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10) +#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11) +#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12) +#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13) +#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14) +#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15) + +#define DRV_IO_EXP_OUTPUT_MASK 0x3F18 +#define DRV_IO_EXP_INPUT_MASK 0xC0E7 + +#define LCD_PIN_CS GPIO_NUM_14 +#define LCD_PIN_DC GPIO_NUM_12 +#define LCD_PIN_RD GPIO_NUM_10 +#define LCD_PIN_WR GPIO_NUM_11 +#define LCD_PIN_RST GPIO_NUM_NC +#define LCD_PIN_D0 GPIO_NUM_13 +#define LCD_PIN_D1 GPIO_NUM_9 +#define LCD_PIN_D2 GPIO_NUM_8 +#define LCD_PIN_D3 GPIO_NUM_7 +#define LCD_PIN_D4 GPIO_NUM_6 +#define LCD_PIN_D5 GPIO_NUM_5 +#define LCD_PIN_D6 GPIO_NUM_4 +#define LCD_PIN_D7 GPIO_NUM_3 + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ + diff --git a/main/boards/atk-dnesp32s3-box2-wifi/config.json b/main/boards/atk-dnesp32s3-box2-wifi/config.json index 2add471..3b9f28e 100644 --- a/main/boards/atk-dnesp32s3-box2-wifi/config.json +++ b/main/boards/atk-dnesp32s3-box2-wifi/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "atk-dnesp32s3-box2-wifi", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "atk-dnesp32s3-box2-wifi", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/atk-dnesp32s3-box2-wifi/power_manager.h b/main/boards/atk-dnesp32s3-box2-wifi/power_manager.h index 103ce88..1a5de70 100644 --- a/main/boards/atk-dnesp32s3-box2-wifi/power_manager.h +++ b/main/boards/atk-dnesp32s3-box2-wifi/power_manager.h @@ -1,195 +1,195 @@ -#pragma once -#include -#include -#include "esp_io_expander_tca95xx_16bit.h" -#include -#include -#include - - -class PowerManager { -private: - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - esp_io_expander_handle_t xl9555_; - uint32_t pin_val = 0; - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - // Get charging status - esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val); - bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value; - uint32_t temp_val = 0; - - esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0); - vTaskDelay(pdMS_TO_TICKS(500)); - - for(int t = 0; t < 10; t ++) { - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); - temp_val += adc_value; - } - - esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT); - - adc_value = temp_val / 10; - - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += value; - } - average_adc /= adc_values_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {2696, 0}, /* 3.48V -屏幕闪屏 */ - {2724, 20}, /* 3.53V */ - {2861, 40}, /* 3.7V */ - {3038, 60}, /* 3.90V */ - {3150, 80}, /* 4.02V */ - {3280, 100} /* 4.14V */ - }; - - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - low_voltage_ = adc_value; - - // ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - -public: - esp_timer_handle_t timer_handle_; - uint16_t low_voltage_ = 2630; - PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) { - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include +#include "esp_io_expander_tca95xx_16bit.h" +#include +#include +#include + + +class PowerManager { +private: + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + esp_io_expander_handle_t xl9555_; + uint32_t pin_val = 0; + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val); + bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + uint32_t temp_val = 0; + + esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0); + vTaskDelay(pdMS_TO_TICKS(500)); + + for(int t = 0; t < 10; t ++) { + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); + temp_val += adc_value; + } + + esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT); + + adc_value = temp_val / 10; + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {2696, 0}, /* 3.48V -屏幕闪屏 */ + {2724, 20}, /* 3.53V */ + {2861, 40}, /* 3.7V */ + {3038, 60}, /* 3.90V */ + {3150, 80}, /* 4.02V */ + {3280, 100} /* 4.14V */ + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + low_voltage_ = adc_value; + + // ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + esp_timer_handle_t timer_handle_; + uint16_t low_voltage_ = 2630; + PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) { + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc b/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc index 0dd9125..145fd07 100644 --- a/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc +++ b/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc @@ -1,230 +1,230 @@ -#include "wifi_board.h" -#include "codecs/es8388_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" -#include "led/single_led.h" -#include "esp32_camera.h" - -#include -#include -#include -#include -#include - -#define TAG "atk_dnesp32s3" - -class XL9555 : public I2cDevice { -public: - XL9555(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - WriteReg(0x06, 0x03); - WriteReg(0x07, 0xF0); - } - - void SetOutputState(uint8_t bit, uint8_t level) { - uint16_t data; - int index = bit; - - if (bit < 8) { - data = ReadReg(0x02); - } else { - data = ReadReg(0x03); - index -= 8; - } - - data = (data & ~(1 << index)) | (level << index); - - if (bit < 8) { - WriteReg(0x02, data); - } else { - WriteReg(0x03, data); - } - } -}; - -class atk_dnesp32s3 : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - XL9555* xl9555_; - Esp32Camera* camera_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - - // Initialize XL9555 - xl9555_ = new XL9555(i2c_bus_, 0x20); - } - - // Initialize spi peripheral - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = LCD_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = LCD_SCLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - ESP_LOGD(TAG, "Install panel IO"); - // 液晶屏控制IO初始化 - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = LCD_CS_PIN; - io_config.dc_gpio_num = LCD_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 20 * 1000 * 1000; - io_config.trans_queue_depth = 7; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG, - esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel); - - esp_lcd_panel_reset(panel); - xl9555_->SetOutputState(8, 1); - xl9555_->SetOutputState(2, 0); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - // 初始化摄像头:ov2640; - // 根据正点原子官方示例参数 - void InitializeCamera() { - - xl9555_->SetOutputState(OV_PWDN_IO, 0); // PWDN=低 (上电) - xl9555_->SetOutputState(OV_RESET_IO, 0); // 确保复位 - vTaskDelay(pdMS_TO_TICKS(50)); // 延长复位保持时间 - xl9555_->SetOutputState(OV_RESET_IO, 1); // 释放复位 - vTaskDelay(pdMS_TO_TICKS(50)); // 延长 50ms - - camera_config_t config = {}; - - config.pin_pwdn = CAM_PIN_PWDN; // 实际由 XL9555 控制 - config.pin_reset = CAM_PIN_RESET;// 实际由 XL9555 控制 - config.pin_xclk = CAM_PIN_XCLK; - config.pin_sccb_sda = CAM_PIN_SIOD; - config.pin_sccb_scl = CAM_PIN_SIOC; - - config.pin_d7 = CAM_PIN_D7; - config.pin_d6 = CAM_PIN_D6; - config.pin_d5 = CAM_PIN_D5; - config.pin_d4 = CAM_PIN_D4; - config.pin_d3 = CAM_PIN_D3; - config.pin_d2 = CAM_PIN_D2; - config.pin_d1 = CAM_PIN_D1; - config.pin_d0 = CAM_PIN_D0; - config.pin_vsync = CAM_PIN_VSYNC; - config.pin_href = CAM_PIN_HREF; - config.pin_pclk = CAM_PIN_PCLK; - - /* XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) */ - config.xclk_freq_hz = 24000000; - config.ledc_timer = LEDC_TIMER_0; - config.ledc_channel = LEDC_CHANNEL_0; - - config.pixel_format = PIXFORMAT_RGB565; /* YUV422,GRAYSCALE,RGB565,JPEG */ - config.frame_size = FRAMESIZE_QVGA; /* QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates */ - - config.jpeg_quality = 12; /* 0-63, for OV series camera sensors, lower number means higher quality */ - config.fb_count = 2; /* When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode */ - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - esp_err_t err = esp_camera_init(&config); // 测试相机是否存在 - if (err != ESP_OK) { - ESP_LOGE(TAG, "Camera is not plugged in or not supported, error: %s", esp_err_to_name(err)); - // 如果摄像头初始化失败,设置 camera_ 为 nullptr - camera_ = nullptr; - return; - }else - { - esp_camera_deinit();// 释放之前的摄像头资源,为正确初始化做准备 - camera_ = new Esp32Camera(config); - } - - } - -public: - atk_dnesp32s3() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeSpi(); - InitializeSt7789Display(); - InitializeButtons(); - InitializeCamera(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8388AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8388_ADDR - ); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(atk_dnesp32s3); +#include "wifi_board.h" +#include "codecs/es8388_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "led/single_led.h" +#include "esp32_camera.h" + +#include +#include +#include +#include +#include + +#define TAG "atk_dnesp32s3" + +class XL9555 : public I2cDevice { +public: + XL9555(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + WriteReg(0x06, 0x03); + WriteReg(0x07, 0xF0); + } + + void SetOutputState(uint8_t bit, uint8_t level) { + uint16_t data; + int index = bit; + + if (bit < 8) { + data = ReadReg(0x02); + } else { + data = ReadReg(0x03); + index -= 8; + } + + data = (data & ~(1 << index)) | (level << index); + + if (bit < 8) { + WriteReg(0x02, data); + } else { + WriteReg(0x03, data); + } + } +}; + +class atk_dnesp32s3 : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + XL9555* xl9555_; + Esp32Camera* camera_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + + // Initialize XL9555 + xl9555_ = new XL9555(i2c_bus_, 0x20); + } + + // Initialize spi peripheral + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = LCD_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = LCD_SCLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + ESP_LOGD(TAG, "Install panel IO"); + // 液晶屏控制IO初始化 + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = LCD_CS_PIN; + io_config.dc_gpio_num = LCD_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 20 * 1000 * 1000; + io_config.trans_queue_depth = 7; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG, + esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel); + + esp_lcd_panel_reset(panel); + xl9555_->SetOutputState(8, 1); + xl9555_->SetOutputState(2, 0); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + // 初始化摄像头:ov2640; + // 根据正点原子官方示例参数 + void InitializeCamera() { + + xl9555_->SetOutputState(OV_PWDN_IO, 0); // PWDN=低 (上电) + xl9555_->SetOutputState(OV_RESET_IO, 0); // 确保复位 + vTaskDelay(pdMS_TO_TICKS(50)); // 延长复位保持时间 + xl9555_->SetOutputState(OV_RESET_IO, 1); // 释放复位 + vTaskDelay(pdMS_TO_TICKS(50)); // 延长 50ms + + camera_config_t config = {}; + + config.pin_pwdn = CAM_PIN_PWDN; // 实际由 XL9555 控制 + config.pin_reset = CAM_PIN_RESET;// 实际由 XL9555 控制 + config.pin_xclk = CAM_PIN_XCLK; + config.pin_sccb_sda = CAM_PIN_SIOD; + config.pin_sccb_scl = CAM_PIN_SIOC; + + config.pin_d7 = CAM_PIN_D7; + config.pin_d6 = CAM_PIN_D6; + config.pin_d5 = CAM_PIN_D5; + config.pin_d4 = CAM_PIN_D4; + config.pin_d3 = CAM_PIN_D3; + config.pin_d2 = CAM_PIN_D2; + config.pin_d1 = CAM_PIN_D1; + config.pin_d0 = CAM_PIN_D0; + config.pin_vsync = CAM_PIN_VSYNC; + config.pin_href = CAM_PIN_HREF; + config.pin_pclk = CAM_PIN_PCLK; + + /* XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) */ + config.xclk_freq_hz = 24000000; + config.ledc_timer = LEDC_TIMER_0; + config.ledc_channel = LEDC_CHANNEL_0; + + config.pixel_format = PIXFORMAT_RGB565; /* YUV422,GRAYSCALE,RGB565,JPEG */ + config.frame_size = FRAMESIZE_QVGA; /* QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates */ + + config.jpeg_quality = 12; /* 0-63, for OV series camera sensors, lower number means higher quality */ + config.fb_count = 2; /* When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode */ + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + esp_err_t err = esp_camera_init(&config); // 测试相机是否存在 + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera is not plugged in or not supported, error: %s", esp_err_to_name(err)); + // 如果摄像头初始化失败,设置 camera_ 为 nullptr + camera_ = nullptr; + return; + }else + { + esp_camera_deinit();// 释放之前的摄像头资源,为正确初始化做准备 + camera_ = new Esp32Camera(config); + } + + } + +public: + atk_dnesp32s3() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeSt7789Display(); + InitializeButtons(); + InitializeCamera(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8388AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8388_ADDR + ); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(atk_dnesp32s3); diff --git a/main/boards/atk-dnesp32s3/config.h b/main/boards/atk-dnesp32s3/config.h index c0b72da..f28afdb 100644 --- a/main/boards/atk-dnesp32s3/config.h +++ b/main/boards/atk-dnesp32s3/config.h @@ -1,64 +1,64 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_3 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_9 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_46 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_41 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_42 -#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define BUILTIN_LED_GPIO GPIO_NUM_1 - -#define LCD_SCLK_PIN GPIO_NUM_12 -#define LCD_MOSI_PIN GPIO_NUM_11 -#define LCD_MISO_PIN GPIO_NUM_13 -#define LCD_DC_PIN GPIO_NUM_40 -#define LCD_CS_PIN GPIO_NUM_21 - -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY true - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - -/* 相机引脚配置 */ -#define CAM_PIN_PWDN GPIO_NUM_NC -#define CAM_PIN_RESET GPIO_NUM_NC -#define CAM_PIN_VSYNC GPIO_NUM_47 -#define CAM_PIN_HREF GPIO_NUM_48 -#define CAM_PIN_PCLK GPIO_NUM_45 -#define CAM_PIN_XCLK GPIO_NUM_NC -#define CAM_PIN_SIOD GPIO_NUM_39 -#define CAM_PIN_SIOC GPIO_NUM_38 -#define CAM_PIN_D0 GPIO_NUM_4 -#define CAM_PIN_D1 GPIO_NUM_5 -#define CAM_PIN_D2 GPIO_NUM_6 -#define CAM_PIN_D3 GPIO_NUM_7 -#define CAM_PIN_D4 GPIO_NUM_15 -#define CAM_PIN_D5 GPIO_NUM_16 -#define CAM_PIN_D6 GPIO_NUM_17 -#define CAM_PIN_D7 GPIO_NUM_18 -#define OV_PWDN_IO 4 -#define OV_RESET_IO 5 - -#endif // _BOARD_CONFIG_H_ - + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_3 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_9 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_46 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_41 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_42 +#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define BUILTIN_LED_GPIO GPIO_NUM_1 + +#define LCD_SCLK_PIN GPIO_NUM_12 +#define LCD_MOSI_PIN GPIO_NUM_11 +#define LCD_MISO_PIN GPIO_NUM_13 +#define LCD_DC_PIN GPIO_NUM_40 +#define LCD_CS_PIN GPIO_NUM_21 + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +/* 相机引脚配置 */ +#define CAM_PIN_PWDN GPIO_NUM_NC +#define CAM_PIN_RESET GPIO_NUM_NC +#define CAM_PIN_VSYNC GPIO_NUM_47 +#define CAM_PIN_HREF GPIO_NUM_48 +#define CAM_PIN_PCLK GPIO_NUM_45 +#define CAM_PIN_XCLK GPIO_NUM_NC +#define CAM_PIN_SIOD GPIO_NUM_39 +#define CAM_PIN_SIOC GPIO_NUM_38 +#define CAM_PIN_D0 GPIO_NUM_4 +#define CAM_PIN_D1 GPIO_NUM_5 +#define CAM_PIN_D2 GPIO_NUM_6 +#define CAM_PIN_D3 GPIO_NUM_7 +#define CAM_PIN_D4 GPIO_NUM_15 +#define CAM_PIN_D5 GPIO_NUM_16 +#define CAM_PIN_D6 GPIO_NUM_17 +#define CAM_PIN_D7 GPIO_NUM_18 +#define OV_PWDN_IO 4 +#define OV_RESET_IO 5 + +#endif // _BOARD_CONFIG_H_ + diff --git a/main/boards/atk-dnesp32s3/config.json b/main/boards/atk-dnesp32s3/config.json index 2f3837d..c47ca27 100644 --- a/main/boards/atk-dnesp32s3/config.json +++ b/main/boards/atk-dnesp32s3/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "atk-dnesp32s3", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "atk-dnesp32s3", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc b/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc index 9f2f701..cd5db4e 100644 --- a/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc +++ b/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc @@ -1,220 +1,220 @@ -#include "ml307_board.h" -#include "codecs/es8388_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" -#include "led/single_led.h" -#include "driver/gpio.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include -#include - -#define TAG "atk_dnesp32s3m_4g" - -class atk_dnesp32s3m_4g : public Ml307Board { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - Button phone_button_; - LcdDisplay* display_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - // Initialize spi peripheral - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = LCD_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = LCD_SCLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - Application::GetInstance().ToggleChatState(); - }); - - volume_up_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - - //不插耳机 - phone_button_.OnPressDown([this]() { - gpio_set_level(SPK_EN_PIN, 1); - }); - - //插入耳机 - phone_button_.OnPressUp([this]() { - gpio_set_level(SPK_EN_PIN, 0); - }); - } - - void InitializeSt7735Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = LCD_CS_PIN; - io_config.dc_gpio_num = LCD_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 60 * 1000 * 1000; - io_config.trans_queue_depth = 7; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io); - - // 初始化液晶屏驱动芯片ST7735 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = LCD_RST_PIN; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; - panel_config.bits_per_pixel = 16; - panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG, - esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel); - - //使能功放引脚 - gpio_config_t io_conf; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&io_conf); - gpio_set_level(SPK_EN_PIN, 0); - - //检测耳机是否插入,插入时为高电平 - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&io_conf); - - //耳机插入 - if (gpio_get_level(PHONE_CK_PIN)) { - gpio_set_level(SPK_EN_PIN, 1); - } - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - - uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10}; - uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10}; - esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16); - esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16); - - esp_lcd_panel_invert_color(panel, true); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - atk_dnesp32s3m_4g() : Ml307Board(Module_4G_TX_PIN, Module_4G_RX_PIN), - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), - phone_button_(PHONE_CK_PIN, true) { - InitializeI2c(); - InitializeSpi(); - InitializeSt7735Display(); - InitializeButtons(); - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - GetBacklight()->RestoreBrightness(); - } - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8388AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8388_ADDR - ); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - return nullptr; - } -}; - -DECLARE_BOARD(atk_dnesp32s3m_4g); +#include "ml307_board.h" +#include "codecs/es8388_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "led/single_led.h" +#include "driver/gpio.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include +#include +#include + +#define TAG "atk_dnesp32s3m_4g" + +class atk_dnesp32s3m_4g : public Ml307Board { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + Button phone_button_; + LcdDisplay* display_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + // Initialize spi peripheral + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = LCD_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = LCD_SCLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + Application::GetInstance().ToggleChatState(); + }); + + volume_up_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + + //不插耳机 + phone_button_.OnPressDown([this]() { + gpio_set_level(SPK_EN_PIN, 1); + }); + + //插入耳机 + phone_button_.OnPressUp([this]() { + gpio_set_level(SPK_EN_PIN, 0); + }); + } + + void InitializeSt7735Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = LCD_CS_PIN; + io_config.dc_gpio_num = LCD_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 60 * 1000 * 1000; + io_config.trans_queue_depth = 7; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io); + + // 初始化液晶屏驱动芯片ST7735 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = LCD_RST_PIN; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; + panel_config.bits_per_pixel = 16; + panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG, + esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel); + + //使能功放引脚 + gpio_config_t io_conf; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + gpio_set_level(SPK_EN_PIN, 0); + + //检测耳机是否插入,插入时为高电平 + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + + //耳机插入 + if (gpio_get_level(PHONE_CK_PIN)) { + gpio_set_level(SPK_EN_PIN, 1); + } + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + + uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10}; + uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10}; + esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16); + esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16); + + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + atk_dnesp32s3m_4g() : Ml307Board(Module_4G_TX_PIN, Module_4G_RX_PIN), + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), + phone_button_(PHONE_CK_PIN, true) { + InitializeI2c(); + InitializeSpi(); + InitializeSt7735Display(); + InitializeButtons(); + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + GetBacklight()->RestoreBrightness(); + } + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8388AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8388_ADDR + ); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + return nullptr; + } +}; + +DECLARE_BOARD(atk_dnesp32s3m_4g); diff --git a/main/boards/atk-dnesp32s3m-4g/config.h b/main/boards/atk-dnesp32s3m-4g/config.h index 92505c5..97b833d 100644 --- a/main/boards/atk-dnesp32s3m-4g/config.h +++ b/main/boards/atk-dnesp32s3m-4g/config.h @@ -1,53 +1,53 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_48 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47 - -#define Module_4G_RX_PIN GPIO_NUM_21 -#define Module_4G_TX_PIN GPIO_NUM_45 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_16 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5 -#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define BUILTIN_LED_GPIO GPIO_NUM_1 - -#define LCD_SCLK_PIN GPIO_NUM_12 -#define LCD_MOSI_PIN GPIO_NUM_11 -#define LCD_MISO_PIN GPIO_NUM_13 -#define LCD_DC_PIN GPIO_NUM_40 -#define LCD_CS_PIN GPIO_NUM_39 -#define LCD_RST_PIN GPIO_NUM_38 - -#define SPK_EN_PIN GPIO_NUM_42 -#define PHONE_CK_PIN GPIO_NUM_3 - -#define DISPLAY_WIDTH 160 -#define DISPLAY_HEIGHT 80 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY true - -#define DISPLAY_OFFSET_X 1 -#define DISPLAY_OFFSET_Y 26 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ - +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_48 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47 + +#define Module_4G_RX_PIN GPIO_NUM_21 +#define Module_4G_TX_PIN GPIO_NUM_45 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_16 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5 +#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define BUILTIN_LED_GPIO GPIO_NUM_1 + +#define LCD_SCLK_PIN GPIO_NUM_12 +#define LCD_MOSI_PIN GPIO_NUM_11 +#define LCD_MISO_PIN GPIO_NUM_13 +#define LCD_DC_PIN GPIO_NUM_40 +#define LCD_CS_PIN GPIO_NUM_39 +#define LCD_RST_PIN GPIO_NUM_38 + +#define SPK_EN_PIN GPIO_NUM_42 +#define PHONE_CK_PIN GPIO_NUM_3 + +#define DISPLAY_WIDTH 160 +#define DISPLAY_HEIGHT 80 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 1 +#define DISPLAY_OFFSET_Y 26 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ + diff --git a/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc b/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc index 1ae96a1..24e2ac4 100644 --- a/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc +++ b/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc @@ -1,230 +1,230 @@ -#include "wifi_board.h" -#include "codecs/es8388_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" -#include "led/single_led.h" -#include "driver/gpio.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include -#include - -#define TAG "atk_dnesp32s3m_wifi" - -class atk_dnesp32s3m_wifi : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - Button phone_button_; - LcdDisplay* display_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - // Initialize spi peripheral - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = LCD_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = LCD_SCLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - boot_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - - volume_up_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - - //不插耳机 - phone_button_.OnPressDown([this]() { - gpio_set_level(SPK_EN_PIN, 1); - }); - - //插入耳机 - phone_button_.OnPressUp([this]() { - gpio_set_level(SPK_EN_PIN, 0); - }); - - } - - void InitializeSt7735Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = LCD_CS_PIN; - io_config.dc_gpio_num = LCD_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 60 * 1000 * 1000; - io_config.trans_queue_depth = 7; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = LCD_RST_PIN; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; - panel_config.bits_per_pixel = 16; - panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - //使能功放引脚 - gpio_config_t io_conf; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&io_conf); - gpio_set_level(SPK_EN_PIN, 0); - - //检测耳机是否插入,插入时为高电平 - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&io_conf); - - //耳机插入 - if (gpio_get_level(PHONE_CK_PIN)) { - gpio_set_level(SPK_EN_PIN, 1); - } - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - - uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10}; - uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10}; - esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16); - esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16); - - esp_lcd_panel_invert_color(panel, true); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - atk_dnesp32s3m_wifi() : - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), - phone_button_(PHONE_CK_PIN, true) { - InitializeI2c(); - InitializeSpi(); - InitializeSt7735Display(); - InitializeButtons(); - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - GetBacklight()->RestoreBrightness(); - } - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8388AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8388_ADDR - ); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - return nullptr; - } -}; - -DECLARE_BOARD(atk_dnesp32s3m_wifi); +#include "wifi_board.h" +#include "codecs/es8388_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "led/single_led.h" +#include "driver/gpio.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include +#include +#include + +#define TAG "atk_dnesp32s3m_wifi" + +class atk_dnesp32s3m_wifi : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + Button phone_button_; + LcdDisplay* display_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + // Initialize spi peripheral + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = LCD_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = LCD_SCLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + boot_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + + volume_up_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + + //不插耳机 + phone_button_.OnPressDown([this]() { + gpio_set_level(SPK_EN_PIN, 1); + }); + + //插入耳机 + phone_button_.OnPressUp([this]() { + gpio_set_level(SPK_EN_PIN, 0); + }); + + } + + void InitializeSt7735Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = LCD_CS_PIN; + io_config.dc_gpio_num = LCD_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 60 * 1000 * 1000; + io_config.trans_queue_depth = 7; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = LCD_RST_PIN; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; + panel_config.bits_per_pixel = 16; + panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + //使能功放引脚 + gpio_config_t io_conf; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + gpio_set_level(SPK_EN_PIN, 0); + + //检测耳机是否插入,插入时为高电平 + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + + //耳机插入 + if (gpio_get_level(PHONE_CK_PIN)) { + gpio_set_level(SPK_EN_PIN, 1); + } + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + + uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10}; + uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10}; + esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16); + esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16); + + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + atk_dnesp32s3m_wifi() : + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), + phone_button_(PHONE_CK_PIN, true) { + InitializeI2c(); + InitializeSpi(); + InitializeSt7735Display(); + InitializeButtons(); + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + GetBacklight()->RestoreBrightness(); + } + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8388AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8388_ADDR + ); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + return nullptr; + } +}; + +DECLARE_BOARD(atk_dnesp32s3m_wifi); diff --git a/main/boards/atk-dnesp32s3m-wifi/config.h b/main/boards/atk-dnesp32s3m-wifi/config.h index df22181..7ca011f 100644 --- a/main/boards/atk-dnesp32s3m-wifi/config.h +++ b/main/boards/atk-dnesp32s3m-wifi/config.h @@ -1,52 +1,52 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_48 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_16 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5 -#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define BUILTIN_LED_GPIO GPIO_NUM_1 - -#define LCD_SCLK_PIN GPIO_NUM_12 -#define LCD_MOSI_PIN GPIO_NUM_11 -#define LCD_MISO_PIN GPIO_NUM_13 -#define LCD_DC_PIN GPIO_NUM_40 -#define LCD_CS_PIN GPIO_NUM_39 -#define LCD_RST_PIN GPIO_NUM_38 - -#define SPK_EN_PIN GPIO_NUM_42 -#define PHONE_CK_PIN GPIO_NUM_3 - -#define DISPLAY_WIDTH 160 -#define DISPLAY_HEIGHT 80 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY true - -#define DISPLAY_OFFSET_X 1 -#define DISPLAY_OFFSET_Y 26 -// #define DISPLAY_OFFSET_X 0 -// #define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ - +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_48 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_16 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5 +#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define BUILTIN_LED_GPIO GPIO_NUM_1 + +#define LCD_SCLK_PIN GPIO_NUM_12 +#define LCD_MOSI_PIN GPIO_NUM_11 +#define LCD_MISO_PIN GPIO_NUM_13 +#define LCD_DC_PIN GPIO_NUM_40 +#define LCD_CS_PIN GPIO_NUM_39 +#define LCD_RST_PIN GPIO_NUM_38 + +#define SPK_EN_PIN GPIO_NUM_42 +#define PHONE_CK_PIN GPIO_NUM_3 + +#define DISPLAY_WIDTH 160 +#define DISPLAY_HEIGHT 80 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 1 +#define DISPLAY_OFFSET_Y 26 +// #define DISPLAY_OFFSET_X 0 +// #define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ + diff --git a/main/boards/atom-echos3r/README.md b/main/boards/atom-echos3r/README.md index ccb452f..256132a 100644 --- a/main/boards/atom-echos3r/README.md +++ b/main/boards/atom-echos3r/README.md @@ -1,45 +1,45 @@ -# AtomEchoS3R -## 简介 - -AtomEchoS3R 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。 - -开发版**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。 - -## 配置、编译命令 - -**配置编译目标为 ESP32S3** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig 并配置** - -```bash -idf.py menuconfig -``` - -分别配置如下选项: - -- `Xiaozhi Assistant` → `Board Type` → 选择 `AtomEchoS3R` -- `Partition Table` → `Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv` -- `Serial flasher config` → `Flash size` → 选择 `8 MB` -- `Component config` → `ESP PSRAM` → `Support for external, SPI-connected RAM` → `SPI RAM config` → 选择 `Octal Mode PSRAM` - -按 `S` 保存,按 `Q` 退出。 - -**编译** - -```bash -idf.py build -``` - -**烧录** - -将 AtomEchoS3R 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。 - -```bash -idf.py flash -``` - -烧录完毕后,按一下 RESET 按钮重启设备。 +# AtomEchoS3R +## 简介 + +AtomEchoS3R 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。 + +开发版**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。 + +## 配置、编译命令 + +**配置编译目标为 ESP32S3** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig 并配置** + +```bash +idf.py menuconfig +``` + +分别配置如下选项: + +- `Xiaozhi Assistant` → `Board Type` → 选择 `AtomEchoS3R` +- `Partition Table` → `Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv` +- `Serial flasher config` → `Flash size` → 选择 `8 MB` +- `Component config` → `ESP PSRAM` → `Support for external, SPI-connected RAM` → `SPI RAM config` → 选择 `Octal Mode PSRAM` + +按 `S` 保存,按 `Q` 退出。 + +**编译** + +```bash +idf.py build +``` + +**烧录** + +将 AtomEchoS3R 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。 + +```bash +idf.py flash +``` + +烧录完毕后,按一下 RESET 按钮重启设备。 diff --git a/main/boards/atom-echos3r/atom_echos3r.cc b/main/boards/atom-echos3r/atom_echos3r.cc index bb9c8e4..e28a032 100644 --- a/main/boards/atom-echos3r/atom_echos3r.cc +++ b/main/boards/atom-echos3r/atom_echos3r.cc @@ -1,91 +1,91 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" -#include "assets/lang_config.h" - -#include -#include -#include - -#define TAG "AtomEchoS3R" - -class AtomEchoS3rBaseBoard : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void I2cDetect() { - uint8_t address; - printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); - for (int i = 0; i < 128; i += 16) { - printf("%02x: ", i); - for (int j = 0; j < 16; j++) { - fflush(stdout); - address = i + j; - esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); - if (ret == ESP_OK) { - printf("%02x ", address); - } else if (ret == ESP_ERR_TIMEOUT) { - printf("UU "); - } else { - printf("-- "); - } - } - printf("\r\n"); - } - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } -public: - AtomEchoS3rBaseBoard() : boot_button_(USER_BUTTON_GPIO) { - InitializeI2c(); - I2cDetect(); - InitializeButtons(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_GPIO_PA, - AUDIO_CODEC_ES8311_ADDR, - false); - return &audio_codec; - } -}; - -DECLARE_BOARD(AtomEchoS3rBaseBoard); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "assets/lang_config.h" + +#include +#include +#include + +#define TAG "AtomEchoS3R" + +class AtomEchoS3rBaseBoard : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void I2cDetect() { + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK) { + printf("%02x ", address); + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } +public: + AtomEchoS3rBaseBoard() : boot_button_(USER_BUTTON_GPIO) { + InitializeI2c(); + I2cDetect(); + InitializeButtons(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_GPIO_PA, + AUDIO_CODEC_ES8311_ADDR, + false); + return &audio_codec; + } +}; + +DECLARE_BOARD(AtomEchoS3rBaseBoard); diff --git a/main/boards/atom-echos3r/config.h b/main/boards/atom-echos3r/config.h index 4803c42..0505eb7 100644 --- a/main/boards/atom-echos3r/config.h +++ b/main/boards/atom-echos3r/config.h @@ -1,29 +1,29 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -// AtomEchoS3R Board configuration - -#include - -#define AUDIO_INPUT_REFERENCE true -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_11 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_3 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_4 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_45 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_0 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_GPIO_PA GPIO_NUM_18 - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define USER_BUTTON_GPIO GPIO_NUM_41 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// AtomEchoS3R Board configuration + +#include + +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_11 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_3 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_4 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_45 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_0 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_GPIO_PA GPIO_NUM_18 + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define USER_BUTTON_GPIO GPIO_NUM_41 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/atom-echos3r/config.json b/main/boards/atom-echos3r/config.json index 7e7328d..ad129ec 100644 --- a/main/boards/atom-echos3r/config.json +++ b/main/boards/atom-echos3r/config.json @@ -1,12 +1,12 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "atom-echos3r", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" - ] - } - ] -} +{ + "target": "esp32s3", + "builds": [ + { + "name": "atom-echos3r", + "sdkconfig_append": [ + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" + ] + } + ] +} diff --git a/main/boards/atommatrix-echo-base/README.md b/main/boards/atommatrix-echo-base/README.md index 52cb3dd..ed944c8 100644 --- a/main/boards/atommatrix-echo-base/README.md +++ b/main/boards/atommatrix-echo-base/README.md @@ -1,37 +1,25 @@ -# 编译配置命令 - -**配置编译目标为 ESP32:** - -```bash -idf.py set-target esp32 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> AtomMatrix + Echo Base -``` - -**修改 flash 大小:** - -``` -Serial flasher config -> Flash size -> 4 MB -``` - -**修改分区表:** - -``` -Partition Table -> Custom partition CSV file -> partitions/v1/4m.csv -``` - -**编译:** - -```bash -idf.py build +# 编译配置命令 + +**配置编译目标为 ESP32:** + +```bash +idf.py set-target esp32 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> AtomMatrix + Echo Base +``` + +**编译:** + +```bash +idf.py build ``` \ No newline at end of file diff --git a/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc b/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc index 5bfd3cc..df2da5c 100644 --- a/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc +++ b/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc @@ -1,133 +1,133 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" - -#include -#include -#include -#include "led/circular_strip.h" - -#define TAG "XX+EchoBase" - -#define PI4IOE_ADDR 0x43 -#define PI4IOE_REG_CTRL 0x00 -#define PI4IOE_REG_IO_PP 0x07 -#define PI4IOE_REG_IO_DIR 0x03 -#define PI4IOE_REG_IO_OUT 0x05 -#define PI4IOE_REG_IO_PULLUP 0x0D - -class Pi4ioe : public I2cDevice { -public: - Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance - WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up - WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1 - WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1 - } - - void SetSpeakerMute(bool mute) { - WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF); - } -}; - -class AtomMatrixEchoBaseBoard : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - - Pi4ioe* pi4ioe_; - - Button face_button_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void I2cDetect() { - uint8_t address; - printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); - for (int i = 0; i < 128; i += 16) { - printf("%02x: ", i); - for (int j = 0; j < 16; j++) { - fflush(stdout); - address = i + j; - esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); - if (ret == ESP_OK) { - printf("%02x ", address); - } else if (ret == ESP_ERR_TIMEOUT) { - printf("UU "); - } else { - printf("-- "); - } - } - printf("\r\n"); - } - } - - void InitializePi4ioe() { - ESP_LOGI(TAG, "Init PI4IOE"); - pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR); - pi4ioe_->SetSpeakerMute(false); - } - - void InitializeButtons() { - face_button_.OnClick([this]() { - - ESP_LOGI(TAG, " ===>>> face_button_.OnClick "); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - -public: - AtomMatrixEchoBaseBoard() : face_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - I2cDetect(); - InitializePi4ioe(); - InitializeButtons(); - } - - virtual Led* GetLed() override { - static CircularStrip led(BUILTIN_LED_GPIO, 25); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_1, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_GPIO_PA, - AUDIO_CODEC_ES8311_ADDR, - false); - return &audio_codec; - } - -}; - -DECLARE_BOARD(AtomMatrixEchoBaseBoard); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" + +#include +#include +#include +#include "led/circular_strip.h" + +#define TAG "XX+EchoBase" + +#define PI4IOE_ADDR 0x43 +#define PI4IOE_REG_CTRL 0x00 +#define PI4IOE_REG_IO_PP 0x07 +#define PI4IOE_REG_IO_DIR 0x03 +#define PI4IOE_REG_IO_OUT 0x05 +#define PI4IOE_REG_IO_PULLUP 0x0D + +class Pi4ioe : public I2cDevice { +public: + Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance + WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up + WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1 + WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1 + } + + void SetSpeakerMute(bool mute) { + WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF); + } +}; + +class AtomMatrixEchoBaseBoard : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + + Pi4ioe* pi4ioe_; + + Button face_button_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void I2cDetect() { + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK) { + printf("%02x ", address); + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + } + + void InitializePi4ioe() { + ESP_LOGI(TAG, "Init PI4IOE"); + pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR); + pi4ioe_->SetSpeakerMute(false); + } + + void InitializeButtons() { + face_button_.OnClick([this]() { + + ESP_LOGI(TAG, " ===>>> face_button_.OnClick "); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + +public: + AtomMatrixEchoBaseBoard() : face_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + I2cDetect(); + InitializePi4ioe(); + InitializeButtons(); + } + + virtual Led* GetLed() override { + static CircularStrip led(BUILTIN_LED_GPIO, 25); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_1, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_GPIO_PA, + AUDIO_CODEC_ES8311_ADDR, + false); + return &audio_codec; + } + +}; + +DECLARE_BOARD(AtomMatrixEchoBaseBoard); diff --git a/main/boards/atommatrix-echo-base/config.h b/main/boards/atommatrix-echo-base/config.h index d1684cb..8c95252 100644 --- a/main/boards/atommatrix-echo-base/config.h +++ b/main/boards/atommatrix-echo-base/config.h @@ -1,29 +1,29 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -// AtomMatrix+EchoBase Board configuration - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC -#define AUDIO_I2S_GPIO_WS GPIO_NUM_19 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_33 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_23 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_22 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_25 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC - -#define BUILTIN_LED_GPIO GPIO_NUM_27 -#define BOOT_BUTTON_GPIO GPIO_NUM_39 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// AtomMatrix+EchoBase Board configuration + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC +#define AUDIO_I2S_GPIO_WS GPIO_NUM_19 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_33 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_23 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_22 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_25 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC + +#define BUILTIN_LED_GPIO GPIO_NUM_27 +#define BOOT_BUTTON_GPIO GPIO_NUM_39 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/atommatrix-echo-base/config.json b/main/boards/atommatrix-echo-base/config.json index e93cec0..a775540 100644 --- a/main/boards/atommatrix-echo-base/config.json +++ b/main/boards/atommatrix-echo-base/config.json @@ -1,12 +1,10 @@ -{ - "target": "esp32", - "builds": [ - { - "name": "atommatrix-echo-base", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m.csv\"" - ] - } - ] +{ + "target": "esp32", + "builds": [ + { + "name": "atommatrix-echo-base", + "sdkconfig_append": [ + ] + } + ] } \ No newline at end of file diff --git a/main/boards/atoms3-echo-base/README.md b/main/boards/atoms3-echo-base/README.md index ef2ad28..4acd86c 100644 --- a/main/boards/atoms3-echo-base/README.md +++ b/main/boards/atoms3-echo-base/README.md @@ -1,49 +1,49 @@ -# 编译配置命令 - -**配置编译目标为 ESP32S3:** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> AtomS3 + Echo Base -``` - -**关闭语音唤醒:** - -``` -Xiaozhi Assistant -> [ ] 启用语音唤醒与音频处理 -> Unselect -``` - -**修改 flash 大小:** - -``` -Serial flasher config -> Flash size -> 8 MB -``` - -**修改分区表:** - -``` -Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv -``` - -**关闭片外 PSRAM:** - -``` -Component config -> ESP PSRAM -> [ ] Support for external, SPI-connected RAM -> Unselect -``` - -**编译:** - -```bash -idf.py build +# 编译配置命令 + +**配置编译目标为 ESP32S3:** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> AtomS3 + Echo Base +``` + +**关闭语音唤醒:** + +``` +Xiaozhi Assistant -> [ ] 启用语音唤醒与音频处理 -> Unselect +``` + +**修改 flash 大小:** + +``` +Serial flasher config -> Flash size -> 8 MB +``` + +**修改分区表:** + +``` +Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv +``` + +**关闭片外 PSRAM:** + +``` +Component config -> ESP PSRAM -> [ ] Support for external, SPI-connected RAM -> Unselect +``` + +**编译:** + +```bash +idf.py build ``` \ No newline at end of file diff --git a/main/boards/atoms3-echo-base/atoms3_echo_base.cc b/main/boards/atoms3-echo-base/atoms3_echo_base.cc index 64e592e..d6f08da 100644 --- a/main/boards/atoms3-echo-base/atoms3_echo_base.cc +++ b/main/boards/atoms3-echo-base/atoms3_echo_base.cc @@ -1,228 +1,228 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include - -#define TAG "AtomS3+EchoBase" - -static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { - // {cmd, { data }, data_size, delay_ms} - {0xfe, (uint8_t[]){0x00}, 0, 0}, - {0xef, (uint8_t[]){0x00}, 0, 0}, - {0xb0, (uint8_t[]){0xc0}, 1, 0}, - {0xb2, (uint8_t[]){0x2f}, 1, 0}, - {0xb3, (uint8_t[]){0x03}, 1, 0}, - {0xb6, (uint8_t[]){0x19}, 1, 0}, - {0xb7, (uint8_t[]){0x01}, 1, 0}, - {0xac, (uint8_t[]){0xcb}, 1, 0}, - {0xab, (uint8_t[]){0x0e}, 1, 0}, - {0xb4, (uint8_t[]){0x04}, 1, 0}, - {0xa8, (uint8_t[]){0x19}, 1, 0}, - {0xb8, (uint8_t[]){0x08}, 1, 0}, - {0xe8, (uint8_t[]){0x24}, 1, 0}, - {0xe9, (uint8_t[]){0x48}, 1, 0}, - {0xea, (uint8_t[]){0x22}, 1, 0}, - {0xc6, (uint8_t[]){0x30}, 1, 0}, - {0xc7, (uint8_t[]){0x18}, 1, 0}, - {0xf0, - (uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06, - 0x00, 0x1c, 0x1f, 0x0f}, - 14, 0}, - {0xf1, - (uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00, - 0x07, 0x0d, 0x11, 0x0f}, - 14, 0}, -}; - -class AtomS3EchoBaseBoard : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Display* display_; - Button boot_button_; - bool is_echo_base_connected_ = false; - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void I2cDetect() { - is_echo_base_connected_ = false; - uint8_t echo_base_connected_flag = 0x00; - uint8_t address; - printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); - for (int i = 0; i < 128; i += 16) { - printf("%02x: ", i); - for (int j = 0; j < 16; j++) { - fflush(stdout); - address = i + j; - esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); - if (ret == ESP_OK) { - printf("%02x ", address); - if (address == 0x18) { - echo_base_connected_flag |= 0xF0; - } else if (address == 0x43) { - echo_base_connected_flag |= 0x0F; - } - } else if (ret == ESP_ERR_TIMEOUT) { - printf("UU "); - } else { - printf("-- "); - } - } - printf("\r\n"); - } - is_echo_base_connected_ = (echo_base_connected_flag == 0xFF); - } - - void CheckEchoBaseConnection() { - if (is_echo_base_connected_) { - return; - } - - // Pop error page - InitializeSpi(); - InitializeGc9107Display(); - InitializeButtons(); - GetBacklight()->SetBrightness(100); - display_->SetStatus(Lang::Strings::ERROR); - display_->SetEmotion("triangle_exclamation"); - display_->SetChatMessage("system", "Echo Base\nnot connected"); - - while (1) { - ESP_LOGE(TAG, "Atomic Echo Base is disconnected"); - vTaskDelay(pdMS_TO_TICKS(1000)); - - // Rerun detection - I2cDetect(); - if (is_echo_base_connected_) { - vTaskDelay(pdMS_TO_TICKS(500)); - I2cDetect(); - if (is_echo_base_connected_) { - ESP_LOGI(TAG, "Atomic Echo Base is reconnected"); - vTaskDelay(pdMS_TO_TICKS(200)); - esp_restart(); - } - } - } - } - - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize SPI bus"); - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_21; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_17; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeGc9107Display() { - ESP_LOGI(TAG, "Init GC9107 display"); - - ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_15; - io_config.dc_gpio_num = GPIO_NUM_33; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); - - ESP_LOGI(TAG, "Install GC9A01 panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - gc9a01_vendor_config_t gc9107_vendor_config = { - .init_cmds = gc9107_lcd_init_cmds, - .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), - }; - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_34; // Set to -1 if not use - panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; - panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) - panel_config.vendor_config = &gc9107_vendor_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); - - display_ = new SpiLcdDisplay(io_handle, panel_handle, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - -public: - AtomS3EchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - I2cDetect(); - CheckEchoBaseConnection(); - InitializeSpi(); - InitializeGc9107Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_1, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_GPIO_PA, - AUDIO_CODEC_ES8311_ADDR, - false); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 256); - return &backlight; - } -}; - +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include +#include + +#define TAG "AtomS3+EchoBase" + +static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { + // {cmd, { data }, data_size, delay_ms} + {0xfe, (uint8_t[]){0x00}, 0, 0}, + {0xef, (uint8_t[]){0x00}, 0, 0}, + {0xb0, (uint8_t[]){0xc0}, 1, 0}, + {0xb2, (uint8_t[]){0x2f}, 1, 0}, + {0xb3, (uint8_t[]){0x03}, 1, 0}, + {0xb6, (uint8_t[]){0x19}, 1, 0}, + {0xb7, (uint8_t[]){0x01}, 1, 0}, + {0xac, (uint8_t[]){0xcb}, 1, 0}, + {0xab, (uint8_t[]){0x0e}, 1, 0}, + {0xb4, (uint8_t[]){0x04}, 1, 0}, + {0xa8, (uint8_t[]){0x19}, 1, 0}, + {0xb8, (uint8_t[]){0x08}, 1, 0}, + {0xe8, (uint8_t[]){0x24}, 1, 0}, + {0xe9, (uint8_t[]){0x48}, 1, 0}, + {0xea, (uint8_t[]){0x22}, 1, 0}, + {0xc6, (uint8_t[]){0x30}, 1, 0}, + {0xc7, (uint8_t[]){0x18}, 1, 0}, + {0xf0, + (uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06, + 0x00, 0x1c, 0x1f, 0x0f}, + 14, 0}, + {0xf1, + (uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00, + 0x07, 0x0d, 0x11, 0x0f}, + 14, 0}, +}; + +class AtomS3EchoBaseBoard : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Display* display_; + Button boot_button_; + bool is_echo_base_connected_ = false; + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void I2cDetect() { + is_echo_base_connected_ = false; + uint8_t echo_base_connected_flag = 0x00; + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK) { + printf("%02x ", address); + if (address == 0x18) { + echo_base_connected_flag |= 0xF0; + } else if (address == 0x43) { + echo_base_connected_flag |= 0x0F; + } + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + is_echo_base_connected_ = (echo_base_connected_flag == 0xFF); + } + + void CheckEchoBaseConnection() { + if (is_echo_base_connected_) { + return; + } + + // Pop error page + InitializeSpi(); + InitializeGc9107Display(); + InitializeButtons(); + GetBacklight()->SetBrightness(100); + display_->SetStatus(Lang::Strings::ERROR); + display_->SetEmotion("triangle_exclamation"); + display_->SetChatMessage("system", "Echo Base\nnot connected"); + + while (1) { + ESP_LOGE(TAG, "Atomic Echo Base is disconnected"); + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Rerun detection + I2cDetect(); + if (is_echo_base_connected_) { + vTaskDelay(pdMS_TO_TICKS(500)); + I2cDetect(); + if (is_echo_base_connected_) { + ESP_LOGI(TAG, "Atomic Echo Base is reconnected"); + vTaskDelay(pdMS_TO_TICKS(200)); + esp_restart(); + } + } + } + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize SPI bus"); + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_21; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_17; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeGc9107Display() { + ESP_LOGI(TAG, "Init GC9107 display"); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_15; + io_config.dc_gpio_num = GPIO_NUM_33; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); + + ESP_LOGI(TAG, "Install GC9A01 panel driver"); + esp_lcd_panel_handle_t panel_handle = NULL; + gc9a01_vendor_config_t gc9107_vendor_config = { + .init_cmds = gc9107_lcd_init_cmds, + .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), + }; + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_34; // Set to -1 if not use + panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; + panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) + panel_config.vendor_config = &gc9107_vendor_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); + + display_ = new SpiLcdDisplay(io_handle, panel_handle, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + +public: + AtomS3EchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + I2cDetect(); + CheckEchoBaseConnection(); + InitializeSpi(); + InitializeGc9107Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_1, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_GPIO_PA, + AUDIO_CODEC_ES8311_ADDR, + false); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 256); + return &backlight; + } +}; + DECLARE_BOARD(AtomS3EchoBaseBoard); \ No newline at end of file diff --git a/main/boards/atoms3-echo-base/config.h b/main/boards/atoms3-echo-base/config.h index 6b2fdad..f1965e0 100644 --- a/main/boards/atoms3-echo-base/config.h +++ b/main/boards/atoms3-echo-base/config.h @@ -1,43 +1,43 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -// AtomS3+EchoBase Board configuration - -#include - -#define AUDIO_INPUT_REFERENCE true -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC -#define AUDIO_I2S_GPIO_WS GPIO_NUM_6 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_41 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SDA_PIN GPIO_NUM_NC -#define DISPLAY_SCL_PIN GPIO_NUM_NC -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 32 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_16 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - - +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// AtomS3+EchoBase Board configuration + +#include + +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC +#define AUDIO_I2S_GPIO_WS GPIO_NUM_6 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_41 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SDA_PIN GPIO_NUM_NC +#define DISPLAY_SCL_PIN GPIO_NUM_NC +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 32 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_16 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + + #endif // _BOARD_CONFIG_H_ \ No newline at end of file diff --git a/main/boards/atoms3-echo-base/config.json b/main/boards/atoms3-echo-base/config.json index 69bcafc..deb5182 100644 --- a/main/boards/atoms3-echo-base/config.json +++ b/main/boards/atoms3-echo-base/config.json @@ -1,14 +1,13 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "atoms3-echo-base", - "sdkconfig_append": [ - "CONFIG_SPIRAM=n", - "CONFIG_USE_AFE=n", - "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "atoms3-echo-base", + "sdkconfig_append": [ + "CONFIG_SPIRAM=n", + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/atoms3r-cam-m12-echo-base/README.md b/main/boards/atoms3r-cam-m12-echo-base/README.md index 6da3280..ef691f2 100644 --- a/main/boards/atoms3r-cam-m12-echo-base/README.md +++ b/main/boards/atoms3r-cam-m12-echo-base/README.md @@ -1,54 +1,54 @@ -# AtomS3R CAM/M12 + Echo Base - -## 简介 - - - -AtomS3R CAM、AtomS3R M12 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,搭载了摄像头。Atomic Echo Base 是一款专为 M5 Atom 系列主机设计的语音识别底座,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。 - -两款开发版均**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。 - -## 配置、编译命令 - -**配置编译目标为 ESP32S3** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig 并配置** - -```bash -idf.py menuconfig -``` - -分别配置如下选项: - -- `Xiaozhi Assistant` → `Board Type` → 选择 `AtomS3R CAM/M12 + Echo Base` -- `Xiaozhi Assistant` → `IoT Protocol` → 选择 `MCP协议` 可开启摄像头识别功能 -- `Partition Table` → `Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv` -- `Serial flasher config` → `Flash size` → 选择 `8 MB` - -按 `S` 保存,按 `Q` 退出。 - -**编译** - -```bash -idf.py build -``` - -**烧录** - -将 AtomS3R CAM/M12 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。 - -```bash -idf.py flash -``` - -烧录完毕后,按一下 RESET 按钮重启。 +# AtomS3R CAM/M12 + Echo Base + +## 简介 + + + +AtomS3R CAM、AtomS3R M12 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,搭载了摄像头。Atomic Echo Base 是一款专为 M5 Atom 系列主机设计的语音识别底座,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。 + +两款开发版均**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。 + +## 配置、编译命令 + +**配置编译目标为 ESP32S3** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig 并配置** + +```bash +idf.py menuconfig +``` + +分别配置如下选项: + +- `Xiaozhi Assistant` → `Board Type` → 选择 `AtomS3R CAM/M12 + Echo Base` +- `Xiaozhi Assistant` → `IoT Protocol` → 选择 `MCP协议` 可开启摄像头识别功能 +- `Partition Table` → `Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv` +- `Serial flasher config` → `Flash size` → 选择 `8 MB` + +按 `S` 保存,按 `Q` 退出。 + +**编译** + +```bash +idf.py build +``` + +**烧录** + +将 AtomS3R CAM/M12 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。 + +```bash +idf.py flash +``` + +烧录完毕后,按一下 RESET 按钮重启。 diff --git a/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc b/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc index f7acca0..07405c8 100644 --- a/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc +++ b/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc @@ -1,190 +1,190 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include "esp32_camera.h" - -#define TAG "AtomS3R CAM/M12 + EchoBase" - -#define PI4IOE_ADDR 0x43 -#define PI4IOE_REG_CTRL 0x00 -#define PI4IOE_REG_IO_PP 0x07 -#define PI4IOE_REG_IO_DIR 0x03 -#define PI4IOE_REG_IO_OUT 0x05 -#define PI4IOE_REG_IO_PULLUP 0x0D - -class Pi4ioe : public I2cDevice { -public: - Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance - WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up - WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1 - WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1 - } - - void SetSpeakerMute(bool mute) { - WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF); - } -}; - -class AtomS3rCamM12EchoBaseBoard : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Pi4ioe* pi4ioe_ = nullptr; - bool is_echo_base_connected_ = false; - Esp32Camera* camera_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void I2cDetect() { - is_echo_base_connected_ = false; - uint8_t echo_base_connected_flag = 0x00; - uint8_t address; - printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); - for (int i = 0; i < 128; i += 16) { - printf("%02x: ", i); - for (int j = 0; j < 16; j++) { - fflush(stdout); - address = i + j; - esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); - if (ret == ESP_OK) { - printf("%02x ", address); - if (address == 0x18) { - echo_base_connected_flag |= 0xF0; - } else if (address == 0x43) { - echo_base_connected_flag |= 0x0F; - } - } else if (ret == ESP_ERR_TIMEOUT) { - printf("UU "); - } else { - printf("-- "); - } - } - printf("\r\n"); - } - is_echo_base_connected_ = (echo_base_connected_flag == 0xFF); - } - - void CheckEchoBaseConnection() { - if (is_echo_base_connected_) { - return; - } - - while (1) { - ESP_LOGE(TAG, "Atomic Echo Base is disconnected"); - vTaskDelay(pdMS_TO_TICKS(1000)); - - // Rerun detection - I2cDetect(); - if (is_echo_base_connected_) { - vTaskDelay(pdMS_TO_TICKS(500)); - I2cDetect(); - if (is_echo_base_connected_) { - ESP_LOGI(TAG, "Atomic Echo Base is reconnected"); - vTaskDelay(pdMS_TO_TICKS(200)); - esp_restart(); - } - } - } - } - - void InitializePi4ioe() { - ESP_LOGI(TAG, "Init PI4IOE"); - pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR); - pi4ioe_->SetSpeakerMute(false); - } - - void EnableCameraPower() { - gpio_reset_pin((gpio_num_t)18); - gpio_set_direction((gpio_num_t)18, GPIO_MODE_OUTPUT); - gpio_set_pull_mode((gpio_num_t)18, GPIO_PULLDOWN_ONLY); - - ESP_LOGI(TAG, "Camera Power Enabled"); - - vTaskDelay(pdMS_TO_TICKS(300)); - } - - void InitializeCamera() { - camera_config_t config = {}; - config.pin_d0 = CAMERA_PIN_D0; - config.pin_d1 = CAMERA_PIN_D1; - config.pin_d2 = CAMERA_PIN_D2; - config.pin_d3 = CAMERA_PIN_D3; - config.pin_d4 = CAMERA_PIN_D4; - config.pin_d5 = CAMERA_PIN_D5; - config.pin_d6 = CAMERA_PIN_D6; - config.pin_d7 = CAMERA_PIN_D7; - config.pin_xclk = CAMERA_PIN_XCLK; - config.pin_pclk = CAMERA_PIN_PCLK; - config.pin_vsync = CAMERA_PIN_VSYNC; - config.pin_href = CAMERA_PIN_HREF; - config.pin_sccb_sda = CAMERA_PIN_SIOD; - config.pin_sccb_scl = CAMERA_PIN_SIOC; - config.sccb_i2c_port = 1; - config.pin_pwdn = CAMERA_PIN_PWDN; - config.pin_reset = CAMERA_PIN_RESET; - config.xclk_freq_hz = XCLK_FREQ_HZ; - config.pixel_format = PIXFORMAT_RGB565; - config.frame_size = FRAMESIZE_QVGA; - config.jpeg_quality = 12; - config.fb_count = 1; - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - camera_ = new Esp32Camera(config); - camera_->SetHMirror(false); - } - - virtual Camera* GetCamera() override { - return camera_; - } -public: - AtomS3rCamM12EchoBaseBoard() { - EnableCameraPower(); // IO18 还会控制指示灯 - InitializeCamera(); - InitializeI2c(); - I2cDetect(); - CheckEchoBaseConnection(); - InitializePi4ioe(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_GPIO_PA, - AUDIO_CODEC_ES8311_ADDR, - false); - return &audio_codec; - } -}; - -DECLARE_BOARD(AtomS3rCamM12EchoBaseBoard); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include "esp32_camera.h" + +#define TAG "AtomS3R CAM/M12 + EchoBase" + +#define PI4IOE_ADDR 0x43 +#define PI4IOE_REG_CTRL 0x00 +#define PI4IOE_REG_IO_PP 0x07 +#define PI4IOE_REG_IO_DIR 0x03 +#define PI4IOE_REG_IO_OUT 0x05 +#define PI4IOE_REG_IO_PULLUP 0x0D + +class Pi4ioe : public I2cDevice { +public: + Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance + WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up + WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1 + WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1 + } + + void SetSpeakerMute(bool mute) { + WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF); + } +}; + +class AtomS3rCamM12EchoBaseBoard : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Pi4ioe* pi4ioe_ = nullptr; + bool is_echo_base_connected_ = false; + Esp32Camera* camera_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void I2cDetect() { + is_echo_base_connected_ = false; + uint8_t echo_base_connected_flag = 0x00; + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK) { + printf("%02x ", address); + if (address == 0x18) { + echo_base_connected_flag |= 0xF0; + } else if (address == 0x43) { + echo_base_connected_flag |= 0x0F; + } + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + is_echo_base_connected_ = (echo_base_connected_flag == 0xFF); + } + + void CheckEchoBaseConnection() { + if (is_echo_base_connected_) { + return; + } + + while (1) { + ESP_LOGE(TAG, "Atomic Echo Base is disconnected"); + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Rerun detection + I2cDetect(); + if (is_echo_base_connected_) { + vTaskDelay(pdMS_TO_TICKS(500)); + I2cDetect(); + if (is_echo_base_connected_) { + ESP_LOGI(TAG, "Atomic Echo Base is reconnected"); + vTaskDelay(pdMS_TO_TICKS(200)); + esp_restart(); + } + } + } + } + + void InitializePi4ioe() { + ESP_LOGI(TAG, "Init PI4IOE"); + pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR); + pi4ioe_->SetSpeakerMute(false); + } + + void EnableCameraPower() { + gpio_reset_pin((gpio_num_t)18); + gpio_set_direction((gpio_num_t)18, GPIO_MODE_OUTPUT); + gpio_set_pull_mode((gpio_num_t)18, GPIO_PULLDOWN_ONLY); + + ESP_LOGI(TAG, "Camera Power Enabled"); + + vTaskDelay(pdMS_TO_TICKS(300)); + } + + void InitializeCamera() { + camera_config_t config = {}; + config.pin_d0 = CAMERA_PIN_D0; + config.pin_d1 = CAMERA_PIN_D1; + config.pin_d2 = CAMERA_PIN_D2; + config.pin_d3 = CAMERA_PIN_D3; + config.pin_d4 = CAMERA_PIN_D4; + config.pin_d5 = CAMERA_PIN_D5; + config.pin_d6 = CAMERA_PIN_D6; + config.pin_d7 = CAMERA_PIN_D7; + config.pin_xclk = CAMERA_PIN_XCLK; + config.pin_pclk = CAMERA_PIN_PCLK; + config.pin_vsync = CAMERA_PIN_VSYNC; + config.pin_href = CAMERA_PIN_HREF; + config.pin_sccb_sda = CAMERA_PIN_SIOD; + config.pin_sccb_scl = CAMERA_PIN_SIOC; + config.sccb_i2c_port = 1; + config.pin_pwdn = CAMERA_PIN_PWDN; + config.pin_reset = CAMERA_PIN_RESET; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_QVGA; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + camera_ = new Esp32Camera(config); + camera_->SetHMirror(false); + } + + virtual Camera* GetCamera() override { + return camera_; + } +public: + AtomS3rCamM12EchoBaseBoard() { + EnableCameraPower(); // IO18 还会控制指示灯 + InitializeCamera(); + InitializeI2c(); + I2cDetect(); + CheckEchoBaseConnection(); + InitializePi4ioe(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_GPIO_PA, + AUDIO_CODEC_ES8311_ADDR, + false); + return &audio_codec; + } +}; + +DECLARE_BOARD(AtomS3rCamM12EchoBaseBoard); diff --git a/main/boards/atoms3r-cam-m12-echo-base/config.h b/main/boards/atoms3r-cam-m12-echo-base/config.h index 61fef49..fe8c9e9 100644 --- a/main/boards/atoms3r-cam-m12-echo-base/config.h +++ b/main/boards/atoms3r-cam-m12-echo-base/config.h @@ -1,49 +1,49 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -// AtomS3R M12+EchoBase Board configuration - -#include - -#define AUDIO_INPUT_REFERENCE true -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC -#define AUDIO_I2S_GPIO_WS GPIO_NUM_6 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_41 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - - -#define CAMERA_PIN_PWDN GPIO_NUM_NC -#define CAMERA_PIN_RESET GPIO_NUM_NC -#define CAMERA_PIN_VSYNC GPIO_NUM_10 -#define CAMERA_PIN_HREF GPIO_NUM_14 -#define CAMERA_PIN_PCLK GPIO_NUM_40 -#define CAMERA_PIN_XCLK GPIO_NUM_21 -#define CAMERA_PIN_SIOD GPIO_NUM_12 -#define CAMERA_PIN_SIOC GPIO_NUM_9 -#define CAMERA_PIN_D0 GPIO_NUM_3 -#define CAMERA_PIN_D1 GPIO_NUM_42 -#define CAMERA_PIN_D2 GPIO_NUM_46 -#define CAMERA_PIN_D3 GPIO_NUM_48 -#define CAMERA_PIN_D4 GPIO_NUM_4 -#define CAMERA_PIN_D5 GPIO_NUM_17 -#define CAMERA_PIN_D6 GPIO_NUM_11 -#define CAMERA_PIN_D7 GPIO_NUM_13 -#define CAMERA_XCLK_FREQ (20000000) -#define XCLK_FREQ_HZ CAMERA_XCLK_FREQ - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// AtomS3R M12+EchoBase Board configuration + +#include + +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC +#define AUDIO_I2S_GPIO_WS GPIO_NUM_6 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_41 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + + +#define CAMERA_PIN_PWDN GPIO_NUM_NC +#define CAMERA_PIN_RESET GPIO_NUM_NC +#define CAMERA_PIN_VSYNC GPIO_NUM_10 +#define CAMERA_PIN_HREF GPIO_NUM_14 +#define CAMERA_PIN_PCLK GPIO_NUM_40 +#define CAMERA_PIN_XCLK GPIO_NUM_21 +#define CAMERA_PIN_SIOD GPIO_NUM_12 +#define CAMERA_PIN_SIOC GPIO_NUM_9 +#define CAMERA_PIN_D0 GPIO_NUM_3 +#define CAMERA_PIN_D1 GPIO_NUM_42 +#define CAMERA_PIN_D2 GPIO_NUM_46 +#define CAMERA_PIN_D3 GPIO_NUM_48 +#define CAMERA_PIN_D4 GPIO_NUM_4 +#define CAMERA_PIN_D5 GPIO_NUM_17 +#define CAMERA_PIN_D6 GPIO_NUM_11 +#define CAMERA_PIN_D7 GPIO_NUM_13 +#define CAMERA_XCLK_FREQ (20000000) +#define XCLK_FREQ_HZ CAMERA_XCLK_FREQ + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/atoms3r-cam-m12-echo-base/config.json b/main/boards/atoms3r-cam-m12-echo-base/config.json index b8d6629..59ee6a5 100644 --- a/main/boards/atoms3r-cam-m12-echo-base/config.json +++ b/main/boards/atoms3r-cam-m12-echo-base/config.json @@ -1,12 +1,12 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "atoms3r-cam-m12-echo-base", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "atoms3r-cam-m12-echo-base", + "sdkconfig_append": [ + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc b/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc index 3407828..6ade9c0 100644 --- a/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc +++ b/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc @@ -1,308 +1,308 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include - -#define TAG "AtomS3R+EchoBase" - -#define PI4IOE_ADDR 0x43 -#define PI4IOE_REG_CTRL 0x00 -#define PI4IOE_REG_IO_PP 0x07 -#define PI4IOE_REG_IO_DIR 0x03 -#define PI4IOE_REG_IO_OUT 0x05 -#define PI4IOE_REG_IO_PULLUP 0x0D - -class Pi4ioe : public I2cDevice { -public: - Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance - WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up - WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1 - WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1 - } - - void SetSpeakerMute(bool mute) { - WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF); - } -}; - -class Lp5562 : public I2cDevice { -public: - Lp5562(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - WriteReg(0x00, 0B01000000); // Set chip_en to 1 - WriteReg(0x08, 0B00000001); // Enable internal clock - WriteReg(0x70, 0B00000000); // Configure all LED outputs to be controlled from I2C registers - - // PWM clock frequency 558 Hz - auto data = ReadReg(0x08); - data = data | 0B01000000; - WriteReg(0x08, data); - } - - void SetBrightness(uint8_t brightness) { - // Map 0~100 to 0~255 - brightness = brightness * 255 / 100; - WriteReg(0x0E, brightness); - } -}; - -class CustomBacklight : public Backlight { -public: - CustomBacklight(Lp5562* lp5562) : lp5562_(lp5562) {} - - void SetBrightnessImpl(uint8_t brightness) override { - if (lp5562_) { - lp5562_->SetBrightness(brightness); - } else { - ESP_LOGE(TAG, "LP5562 not available"); - } - } - -private: - Lp5562* lp5562_ = nullptr; -}; - -static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { - // {cmd, { data }, data_size, delay_ms} - {0xfe, (uint8_t[]){0x00}, 0, 0}, - {0xef, (uint8_t[]){0x00}, 0, 0}, - {0xb0, (uint8_t[]){0xc0}, 1, 0}, - {0xb2, (uint8_t[]){0x2f}, 1, 0}, - {0xb3, (uint8_t[]){0x03}, 1, 0}, - {0xb6, (uint8_t[]){0x19}, 1, 0}, - {0xb7, (uint8_t[]){0x01}, 1, 0}, - {0xac, (uint8_t[]){0xcb}, 1, 0}, - {0xab, (uint8_t[]){0x0e}, 1, 0}, - {0xb4, (uint8_t[]){0x04}, 1, 0}, - {0xa8, (uint8_t[]){0x19}, 1, 0}, - {0xb8, (uint8_t[]){0x08}, 1, 0}, - {0xe8, (uint8_t[]){0x24}, 1, 0}, - {0xe9, (uint8_t[]){0x48}, 1, 0}, - {0xea, (uint8_t[]){0x22}, 1, 0}, - {0xc6, (uint8_t[]){0x30}, 1, 0}, - {0xc7, (uint8_t[]){0x18}, 1, 0}, - {0xf0, - (uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06, - 0x00, 0x1c, 0x1f, 0x0f}, - 14, 0}, - {0xf1, - (uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00, - 0x07, 0x0d, 0x11, 0x0f}, - 14, 0}, -}; - -class AtomS3rEchoBaseBoard : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - i2c_master_bus_handle_t i2c_bus_internal_; - Pi4ioe* pi4ioe_ = nullptr; - Lp5562* lp5562_ = nullptr; - Display* display_ = nullptr; - Button boot_button_; - bool is_echo_base_connected_ = false; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - - i2c_bus_cfg.i2c_port = I2C_NUM_0; - i2c_bus_cfg.sda_io_num = GPIO_NUM_45; - i2c_bus_cfg.scl_io_num = GPIO_NUM_0; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_internal_)); - } - - void I2cDetect() { - is_echo_base_connected_ = false; - uint8_t echo_base_connected_flag = 0x00; - uint8_t address; - printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); - for (int i = 0; i < 128; i += 16) { - printf("%02x: ", i); - for (int j = 0; j < 16; j++) { - fflush(stdout); - address = i + j; - esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); - if (ret == ESP_OK) { - printf("%02x ", address); - if (address == 0x18) { - echo_base_connected_flag |= 0xF0; - } else if (address == 0x43) { - echo_base_connected_flag |= 0x0F; - } - } else if (ret == ESP_ERR_TIMEOUT) { - printf("UU "); - } else { - printf("-- "); - } - } - printf("\r\n"); - } - is_echo_base_connected_ = (echo_base_connected_flag == 0xFF); - } - - void CheckEchoBaseConnection() { - if (is_echo_base_connected_) { - return; - } - - // Pop error page - InitializeLp5562(); - InitializeSpi(); - InitializeGc9107Display(); - InitializeButtons(); - GetBacklight()->SetBrightness(100); - display_->SetStatus(Lang::Strings::ERROR); - display_->SetEmotion("triangle_exclamation"); - display_->SetChatMessage("system", "Echo Base\nnot connected"); - - while (1) { - ESP_LOGE(TAG, "Atomic Echo Base is disconnected"); - vTaskDelay(pdMS_TO_TICKS(1000)); - - // Rerun detection - I2cDetect(); - if (is_echo_base_connected_) { - vTaskDelay(pdMS_TO_TICKS(500)); - I2cDetect(); - if (is_echo_base_connected_) { - ESP_LOGI(TAG, "Atomic Echo Base is reconnected"); - vTaskDelay(pdMS_TO_TICKS(200)); - esp_restart(); - } - } - } - } - - void InitializePi4ioe() { - ESP_LOGI(TAG, "Init PI4IOE"); - pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR); - pi4ioe_->SetSpeakerMute(false); - } - - void InitializeLp5562() { - ESP_LOGI(TAG, "Init LP5562"); - lp5562_ = new Lp5562(i2c_bus_internal_, 0x30); - } - - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize SPI bus"); - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_21; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_15; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeGc9107Display() { - ESP_LOGI(TAG, "Init GC9107 display"); - - ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_14; - io_config.dc_gpio_num = GPIO_NUM_42; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); - - ESP_LOGI(TAG, "Install GC9A01 panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - gc9a01_vendor_config_t gc9107_vendor_config = { - .init_cmds = gc9107_lcd_init_cmds, - .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), - }; - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_48; // Set to -1 if not use - panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; - panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) - panel_config.vendor_config = &gc9107_vendor_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); - - display_ = new SpiLcdDisplay(io_handle, panel_handle, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - -public: - AtomS3rEchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - I2cDetect(); - CheckEchoBaseConnection(); - InitializePi4ioe(); - InitializeLp5562(); - InitializeSpi(); - InitializeGc9107Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_1, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_GPIO_PA, - AUDIO_CODEC_ES8311_ADDR, - false); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight *GetBacklight() override { - static CustomBacklight backlight(lp5562_); - return &backlight; - } -}; - -DECLARE_BOARD(AtomS3rEchoBaseBoard); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include +#include + +#define TAG "AtomS3R+EchoBase" + +#define PI4IOE_ADDR 0x43 +#define PI4IOE_REG_CTRL 0x00 +#define PI4IOE_REG_IO_PP 0x07 +#define PI4IOE_REG_IO_DIR 0x03 +#define PI4IOE_REG_IO_OUT 0x05 +#define PI4IOE_REG_IO_PULLUP 0x0D + +class Pi4ioe : public I2cDevice { +public: + Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance + WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up + WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1 + WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1 + } + + void SetSpeakerMute(bool mute) { + WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF); + } +}; + +class Lp5562 : public I2cDevice { +public: + Lp5562(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + WriteReg(0x00, 0B01000000); // Set chip_en to 1 + WriteReg(0x08, 0B00000001); // Enable internal clock + WriteReg(0x70, 0B00000000); // Configure all LED outputs to be controlled from I2C registers + + // PWM clock frequency 558 Hz + auto data = ReadReg(0x08); + data = data | 0B01000000; + WriteReg(0x08, data); + } + + void SetBrightness(uint8_t brightness) { + // Map 0~100 to 0~255 + brightness = brightness * 255 / 100; + WriteReg(0x0E, brightness); + } +}; + +class CustomBacklight : public Backlight { +public: + CustomBacklight(Lp5562* lp5562) : lp5562_(lp5562) {} + + void SetBrightnessImpl(uint8_t brightness) override { + if (lp5562_) { + lp5562_->SetBrightness(brightness); + } else { + ESP_LOGE(TAG, "LP5562 not available"); + } + } + +private: + Lp5562* lp5562_ = nullptr; +}; + +static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { + // {cmd, { data }, data_size, delay_ms} + {0xfe, (uint8_t[]){0x00}, 0, 0}, + {0xef, (uint8_t[]){0x00}, 0, 0}, + {0xb0, (uint8_t[]){0xc0}, 1, 0}, + {0xb2, (uint8_t[]){0x2f}, 1, 0}, + {0xb3, (uint8_t[]){0x03}, 1, 0}, + {0xb6, (uint8_t[]){0x19}, 1, 0}, + {0xb7, (uint8_t[]){0x01}, 1, 0}, + {0xac, (uint8_t[]){0xcb}, 1, 0}, + {0xab, (uint8_t[]){0x0e}, 1, 0}, + {0xb4, (uint8_t[]){0x04}, 1, 0}, + {0xa8, (uint8_t[]){0x19}, 1, 0}, + {0xb8, (uint8_t[]){0x08}, 1, 0}, + {0xe8, (uint8_t[]){0x24}, 1, 0}, + {0xe9, (uint8_t[]){0x48}, 1, 0}, + {0xea, (uint8_t[]){0x22}, 1, 0}, + {0xc6, (uint8_t[]){0x30}, 1, 0}, + {0xc7, (uint8_t[]){0x18}, 1, 0}, + {0xf0, + (uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06, + 0x00, 0x1c, 0x1f, 0x0f}, + 14, 0}, + {0xf1, + (uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00, + 0x07, 0x0d, 0x11, 0x0f}, + 14, 0}, +}; + +class AtomS3rEchoBaseBoard : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + i2c_master_bus_handle_t i2c_bus_internal_; + Pi4ioe* pi4ioe_ = nullptr; + Lp5562* lp5562_ = nullptr; + Display* display_ = nullptr; + Button boot_button_; + bool is_echo_base_connected_ = false; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + + i2c_bus_cfg.i2c_port = I2C_NUM_0; + i2c_bus_cfg.sda_io_num = GPIO_NUM_45; + i2c_bus_cfg.scl_io_num = GPIO_NUM_0; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_internal_)); + } + + void I2cDetect() { + is_echo_base_connected_ = false; + uint8_t echo_base_connected_flag = 0x00; + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK) { + printf("%02x ", address); + if (address == 0x18) { + echo_base_connected_flag |= 0xF0; + } else if (address == 0x43) { + echo_base_connected_flag |= 0x0F; + } + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + is_echo_base_connected_ = (echo_base_connected_flag == 0xFF); + } + + void CheckEchoBaseConnection() { + if (is_echo_base_connected_) { + return; + } + + // Pop error page + InitializeLp5562(); + InitializeSpi(); + InitializeGc9107Display(); + InitializeButtons(); + GetBacklight()->SetBrightness(100); + display_->SetStatus(Lang::Strings::ERROR); + display_->SetEmotion("triangle_exclamation"); + display_->SetChatMessage("system", "Echo Base\nnot connected"); + + while (1) { + ESP_LOGE(TAG, "Atomic Echo Base is disconnected"); + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Rerun detection + I2cDetect(); + if (is_echo_base_connected_) { + vTaskDelay(pdMS_TO_TICKS(500)); + I2cDetect(); + if (is_echo_base_connected_) { + ESP_LOGI(TAG, "Atomic Echo Base is reconnected"); + vTaskDelay(pdMS_TO_TICKS(200)); + esp_restart(); + } + } + } + } + + void InitializePi4ioe() { + ESP_LOGI(TAG, "Init PI4IOE"); + pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR); + pi4ioe_->SetSpeakerMute(false); + } + + void InitializeLp5562() { + ESP_LOGI(TAG, "Init LP5562"); + lp5562_ = new Lp5562(i2c_bus_internal_, 0x30); + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize SPI bus"); + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_21; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_15; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeGc9107Display() { + ESP_LOGI(TAG, "Init GC9107 display"); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_14; + io_config.dc_gpio_num = GPIO_NUM_42; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); + + ESP_LOGI(TAG, "Install GC9A01 panel driver"); + esp_lcd_panel_handle_t panel_handle = NULL; + gc9a01_vendor_config_t gc9107_vendor_config = { + .init_cmds = gc9107_lcd_init_cmds, + .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), + }; + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_48; // Set to -1 if not use + panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; + panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) + panel_config.vendor_config = &gc9107_vendor_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); + + display_ = new SpiLcdDisplay(io_handle, panel_handle, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + +public: + AtomS3rEchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + I2cDetect(); + CheckEchoBaseConnection(); + InitializePi4ioe(); + InitializeLp5562(); + InitializeSpi(); + InitializeGc9107Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_1, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_GPIO_PA, + AUDIO_CODEC_ES8311_ADDR, + false); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight *GetBacklight() override { + static CustomBacklight backlight(lp5562_); + return &backlight; + } +}; + +DECLARE_BOARD(AtomS3rEchoBaseBoard); diff --git a/main/boards/atoms3r-echo-base/config.h b/main/boards/atoms3r-echo-base/config.h index d519c2e..f65df96 100644 --- a/main/boards/atoms3r-echo-base/config.h +++ b/main/boards/atoms3r-echo-base/config.h @@ -1,43 +1,43 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -// AtomS3R+EchoBase Board configuration - -#include - -#define AUDIO_INPUT_REFERENCE true -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC -#define AUDIO_I2S_GPIO_WS GPIO_NUM_6 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_41 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SDA_PIN GPIO_NUM_NC -#define DISPLAY_SCL_PIN GPIO_NUM_NC -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 32 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// AtomS3R+EchoBase Board configuration + +#include + +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC +#define AUDIO_I2S_GPIO_WS GPIO_NUM_6 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_41 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SDA_PIN GPIO_NUM_NC +#define DISPLAY_SCL_PIN GPIO_NUM_NC +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 32 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/atoms3r-echo-base/config.json b/main/boards/atoms3r-echo-base/config.json index d3b1549..50294d2 100644 --- a/main/boards/atoms3r-echo-base/config.json +++ b/main/boards/atoms3r-echo-base/config.json @@ -1,12 +1,12 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "atoms3r-echo-base", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "atoms3r-echo-base", + "sdkconfig_append": [ + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/bread-compact-esp32-lcd/config.h b/main/boards/bread-compact-esp32-lcd/config.h index 2068a2d..a9695e4 100644 --- a/main/boards/bread-compact-esp32-lcd/config.h +++ b/main/boards/bread-compact-esp32-lcd/config.h @@ -1,276 +1,278 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -// 如果使用 Duplex I2S 模式,请注释下面一行 -#define AUDIO_I2S_METHOD_SIMPLEX - -#ifdef AUDIO_I2S_METHOD_SIMPLEX - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32 - -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27 - -#else - -#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 - -#endif - - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define TOUCH_BUTTON_GPIO GPIO_NUM_5 -#define ASR_BUTTON_GPIO GPIO_NUM_19 -#define BUILTIN_LED_GPIO GPIO_NUM_2 - - -#ifdef CONFIG_LCD_ST7789_240X240_7PIN -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_22 -#define DISPLAY_CS_PIN GPIO_NUM_NC -#else -#define DISPLAY_CS_PIN GPIO_NUM_22 -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_23 -#endif - -#define DISPLAY_MOSI_PIN GPIO_NUM_4 -#define DISPLAY_CLK_PIN GPIO_NUM_15 -#define DISPLAY_DC_PIN GPIO_NUM_21 -#define DISPLAY_RST_PIN GPIO_NUM_18 - - -#ifdef CONFIG_LCD_ST7789_240X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_170X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 170 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 35 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_172X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 172 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 34 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X280 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 280 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 20 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X240 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X240_7PIN -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 2 -#endif - -#ifdef CONFIG_LCD_ST7789_240X135 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 135 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY true -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 40 -#define DISPLAY_OFFSET_Y 53 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7735_128X160 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 160 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7735_128X128 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 32 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7796_320X480 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 480 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ILI9341_240X320 -#define LCD_TYPE_ILI9341_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS -#define LCD_TYPE_ILI9341_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_GC9A01_240X240 -#define LCD_TYPE_GC9A01_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_CUSTOM -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +// 如果使用 Duplex I2S 模式,请注释下面一行 +#define AUDIO_I2S_METHOD_SIMPLEX + +#ifdef AUDIO_I2S_METHOD_SIMPLEX + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32 + +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27 + +#else + +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +#endif + + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define TOUCH_BUTTON_GPIO GPIO_NUM_5 +#define ASR_BUTTON_GPIO GPIO_NUM_19 +#define BUILTIN_LED_GPIO GPIO_NUM_2 + +#define ML307_RX_PIN GPIO_NUM_16 +#define ML307_TX_PIN GPIO_NUM_17 + +#ifdef CONFIG_LCD_ST7789_240X240_7PIN +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_22 +#define DISPLAY_CS_PIN GPIO_NUM_NC +#else +#define DISPLAY_CS_PIN GPIO_NUM_22 +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_23 +#endif + +#define DISPLAY_MOSI_PIN GPIO_NUM_4 +#define DISPLAY_CLK_PIN GPIO_NUM_15 +#define DISPLAY_DC_PIN GPIO_NUM_21 +#define DISPLAY_RST_PIN GPIO_NUM_18 + + +#ifdef CONFIG_LCD_ST7789_240X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_170X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 170 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 35 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_172X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 172 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 34 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X280 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 280 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 20 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X240 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X240_7PIN +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 2 +#endif + +#ifdef CONFIG_LCD_ST7789_240X135 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 135 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 40 +#define DISPLAY_OFFSET_Y 53 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7735_128X160 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 160 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7735_128X128 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 32 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7796_320X480 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 480 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ILI9341_240X320 +#define LCD_TYPE_ILI9341_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS +#define LCD_TYPE_ILI9341_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_GC9A01_240X240 +#define LCD_TYPE_GC9A01_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_CUSTOM +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/bread-compact-esp32-lcd/config.json b/main/boards/bread-compact-esp32-lcd/config.json index 99de0e6..95c9fef 100644 --- a/main/boards/bread-compact-esp32-lcd/config.json +++ b/main/boards/bread-compact-esp32-lcd/config.json @@ -1,13 +1,11 @@ -{ - "target": "esp32", - "builds": [ - { - "name": "bread-compact-esp32-lcd", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m.csv\"", - "LCD_ST7789_240X240_7PIN=y" - ] - } - ] +{ + "target": "esp32", + "builds": [ + { + "name": "bread-compact-esp32-lcd", + "sdkconfig_append": [ + "LCD_ST7789_240X240_7PIN=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc b/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc index f1c53e3..c52474c 100644 --- a/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc +++ b/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc @@ -1,200 +1,213 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" - -#include -#include -#include -#include -#include -#include -#include - -#if defined(LCD_TYPE_ILI9341_SERIAL) -#include "esp_lcd_ili9341.h" -#endif - -#if defined(LCD_TYPE_GC9A01_SERIAL) -#include "esp_lcd_gc9a01.h" -static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { - // {cmd, { data }, data_size, delay_ms} - {0xfe, (uint8_t[]){0x00}, 0, 0}, - {0xef, (uint8_t[]){0x00}, 0, 0}, - {0xb0, (uint8_t[]){0xc0}, 1, 0}, - {0xb1, (uint8_t[]){0x80}, 1, 0}, - {0xb2, (uint8_t[]){0x27}, 1, 0}, - {0xb3, (uint8_t[]){0x13}, 1, 0}, - {0xb6, (uint8_t[]){0x19}, 1, 0}, - {0xb7, (uint8_t[]){0x05}, 1, 0}, - {0xac, (uint8_t[]){0xc8}, 1, 0}, - {0xab, (uint8_t[]){0x0f}, 1, 0}, - {0x3a, (uint8_t[]){0x05}, 1, 0}, - {0xb4, (uint8_t[]){0x04}, 1, 0}, - {0xa8, (uint8_t[]){0x08}, 1, 0}, - {0xb8, (uint8_t[]){0x08}, 1, 0}, - {0xea, (uint8_t[]){0x02}, 1, 0}, - {0xe8, (uint8_t[]){0x2A}, 1, 0}, - {0xe9, (uint8_t[]){0x47}, 1, 0}, - {0xe7, (uint8_t[]){0x5f}, 1, 0}, - {0xc6, (uint8_t[]){0x21}, 1, 0}, - {0xc7, (uint8_t[]){0x15}, 1, 0}, - {0xf0, - (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, - 0x04, 0x12, 0x14, 0x1f}, - 14, 0}, - {0xf1, - (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, - 0x0C, 0x1A, 0x14, 0x1E}, - 14, 0}, - {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, - {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, -}; -#endif - -#define TAG "ESP32-LCD-MarsbearSupport" - -class CompactWifiBoardLCD : public WifiBoard { -private: - Button boot_button_; - Button touch_button_; - Button asr_button_; - - LcdDisplay* display_; - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_CLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeLcdDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = DISPLAY_SPI_MODE; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; -#if defined(LCD_TYPE_ILI9341_SERIAL) - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); -#elif defined(LCD_TYPE_GC9A01_SERIAL) - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel)); - gc9a01_vendor_config_t gc9107_vendor_config = { - .init_cmds = gc9107_lcd_init_cmds, - .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), - }; -#else - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); -#endif - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); -#ifdef LCD_TYPE_GC9A01_SERIAL - panel_config.vendor_config = &gc9107_vendor_config; -#endif - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - - // 配置 GPIO - gpio_config_t io_conf = { - .pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 设置需要配置的 GPIO 引脚 - .mode = GPIO_MODE_OUTPUT, // 设置为输出模式 - .pull_up_en = GPIO_PULLUP_DISABLE, // 禁用上拉 - .pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉 - .intr_type = GPIO_INTR_DISABLE // 禁用中断 - }; - gpio_config(&io_conf); // 应用配置 - - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - gpio_set_level(BUILTIN_LED_GPIO, 1); - app.ToggleChatState(); - }); - - asr_button_.OnClick([this]() { - std::string wake_word="你好小智"; - Application::GetInstance().WakeWordInvoke(wake_word); - }); - - touch_button_.OnPressDown([this]() { - gpio_set_level(BUILTIN_LED_GPIO, 1); - Application::GetInstance().StartListening(); - }); - - touch_button_.OnPressUp([this]() { - gpio_set_level(BUILTIN_LED_GPIO, 0); - Application::GetInstance().StopListening(); - }); - - } - -public: - CompactWifiBoardLCD() : - boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) { - InitializeSpi(); - InitializeLcdDisplay(); - InitializeButtons(); - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - GetBacklight()->RestoreBrightness(); - } - - } - - virtual AudioCodec* GetAudioCodec() override { -#ifdef AUDIO_I2S_METHOD_SIMPLEX - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); -#else - static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); -#endif - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - return nullptr; - } -}; - -DECLARE_BOARD(CompactWifiBoardLCD); +#include "dual_network_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(LCD_TYPE_ILI9341_SERIAL) +#include "esp_lcd_ili9341.h" +#endif + +#if defined(LCD_TYPE_GC9A01_SERIAL) +#include "esp_lcd_gc9a01.h" +static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { + // {cmd, { data }, data_size, delay_ms} + {0xfe, (uint8_t[]){0x00}, 0, 0}, + {0xef, (uint8_t[]){0x00}, 0, 0}, + {0xb0, (uint8_t[]){0xc0}, 1, 0}, + {0xb1, (uint8_t[]){0x80}, 1, 0}, + {0xb2, (uint8_t[]){0x27}, 1, 0}, + {0xb3, (uint8_t[]){0x13}, 1, 0}, + {0xb6, (uint8_t[]){0x19}, 1, 0}, + {0xb7, (uint8_t[]){0x05}, 1, 0}, + {0xac, (uint8_t[]){0xc8}, 1, 0}, + {0xab, (uint8_t[]){0x0f}, 1, 0}, + {0x3a, (uint8_t[]){0x05}, 1, 0}, + {0xb4, (uint8_t[]){0x04}, 1, 0}, + {0xa8, (uint8_t[]){0x08}, 1, 0}, + {0xb8, (uint8_t[]){0x08}, 1, 0}, + {0xea, (uint8_t[]){0x02}, 1, 0}, + {0xe8, (uint8_t[]){0x2A}, 1, 0}, + {0xe9, (uint8_t[]){0x47}, 1, 0}, + {0xe7, (uint8_t[]){0x5f}, 1, 0}, + {0xc6, (uint8_t[]){0x21}, 1, 0}, + {0xc7, (uint8_t[]){0x15}, 1, 0}, + {0xf0, + (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, + 0x04, 0x12, 0x14, 0x1f}, + 14, 0}, + {0xf1, + (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, + 0x0C, 0x1A, 0x14, 0x1E}, + 14, 0}, + {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, + {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, +}; +#endif + +#define TAG "ESP32-LCD-MarsbearSupport" + +class CompactWifiBoardLCD : public DualNetworkBoard { +private: + Button boot_button_; + Button touch_button_; + Button asr_button_; + + LcdDisplay* display_; + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = DISPLAY_SPI_MODE; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; +#if defined(LCD_TYPE_ILI9341_SERIAL) + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); +#elif defined(LCD_TYPE_GC9A01_SERIAL) + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel)); + gc9a01_vendor_config_t gc9107_vendor_config = { + .init_cmds = gc9107_lcd_init_cmds, + .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), + }; +#else + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); +#endif + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); +#ifdef LCD_TYPE_GC9A01_SERIAL + panel_config.vendor_config = &gc9107_vendor_config; +#endif + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + + // 配置 GPIO + gpio_config_t io_conf = { + .pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 设置需要配置的 GPIO 引脚 + .mode = GPIO_MODE_OUTPUT, // 设置为输出模式 + .pull_up_en = GPIO_PULLUP_DISABLE, // 禁用上拉 + .pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉 + .intr_type = GPIO_INTR_DISABLE // 禁用中断 + }; + gpio_config(&io_conf); // 应用配置 + + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (GetNetworkType() == NetworkType::WIFI) { + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + // cast to WifiBoard + auto& wifi_board = static_cast(GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + } + } + gpio_set_level(BUILTIN_LED_GPIO, 1); + app.ToggleChatState(); + }); + + + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + SwitchNetworkType(); + } + }); + + asr_button_.OnClick([this]() { + std::string wake_word="你好小智"; + Application::GetInstance().WakeWordInvoke(wake_word); + }); + + touch_button_.OnPressDown([this]() { + gpio_set_level(BUILTIN_LED_GPIO, 1); + Application::GetInstance().StartListening(); + }); + + touch_button_.OnPressUp([this]() { + gpio_set_level(BUILTIN_LED_GPIO, 0); + Application::GetInstance().StopListening(); + }); + + } + +public: + CompactWifiBoardLCD() : + DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), + boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) { + InitializeSpi(); + InitializeLcdDisplay(); + InitializeButtons(); + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + GetBacklight()->RestoreBrightness(); + } + + } + + virtual AudioCodec* GetAudioCodec() override { +#ifdef AUDIO_I2S_METHOD_SIMPLEX + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); +#else + static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); +#endif + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + return nullptr; + } +}; + +DECLARE_BOARD(CompactWifiBoardLCD); diff --git a/main/boards/bread-compact-esp32/README.md b/main/boards/bread-compact-esp32/README.md index bb355eb..d879dfb 100644 --- a/main/boards/bread-compact-esp32/README.md +++ b/main/boards/bread-compact-esp32/README.md @@ -1,37 +1,25 @@ -# 编译配置命令 - -**配置编译目标为 ESP32:** - -```bash -idf.py set-target esp32 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> 面包板 ESP32 DevKit -``` - -**修改 flash 大小:** - -``` -Serial flasher config -> Flash size -> 4 MB -``` - -**修改分区表:** - -``` -Partition Table -> Custom partition CSV file -> partitions/v1/4m.csv -``` - -**编译:** - -```bash -idf.py build +# 编译配置命令 + +**配置编译目标为 ESP32:** + +```bash +idf.py set-target esp32 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> 面包板 ESP32 DevKit +``` + +**编译:** + +```bash +idf.py build ``` \ No newline at end of file diff --git a/main/boards/bread-compact-esp32/config.h b/main/boards/bread-compact-esp32/config.h index 3ff28ea..601f9d4 100644 --- a/main/boards/bread-compact-esp32/config.h +++ b/main/boards/bread-compact-esp32/config.h @@ -1,55 +1,58 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -// 如果使用 Duplex I2S 模式,请注释下面一行 -#define AUDIO_I2S_METHOD_SIMPLEX - -#ifdef AUDIO_I2S_METHOD_SIMPLEX - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32 - -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27 - -#else - -#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 - -#endif - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define TOUCH_BUTTON_GPIO GPIO_NUM_5 -#define ASR_BUTTON_GPIO GPIO_NUM_19 -#define BUILTIN_LED_GPIO GPIO_NUM_2 - -#define DISPLAY_SDA_PIN GPIO_NUM_4 -#define DISPLAY_SCL_PIN GPIO_NUM_15 -#define DISPLAY_WIDTH 128 - -#if CONFIG_OLED_SSD1306_128X32 -#define DISPLAY_HEIGHT 32 -#elif CONFIG_OLED_SSD1306_128X64 -#define DISPLAY_HEIGHT 64 -#else -#error "未选择 OLED 屏幕类型" -#endif - -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true - - -// A MCP Test: Control a lamp -#define LAMP_GPIO GPIO_NUM_18 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +// 如果使用 Duplex I2S 模式,请注释下面一行 +#define AUDIO_I2S_METHOD_SIMPLEX + +#ifdef AUDIO_I2S_METHOD_SIMPLEX + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32 + +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27 + +#else + +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +#endif + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define TOUCH_BUTTON_GPIO GPIO_NUM_5 +#define ASR_BUTTON_GPIO GPIO_NUM_19 +#define BUILTIN_LED_GPIO GPIO_NUM_2 + +#define ML307_RX_PIN GPIO_NUM_16 +#define ML307_TX_PIN GPIO_NUM_17 + +#define DISPLAY_SDA_PIN GPIO_NUM_4 +#define DISPLAY_SCL_PIN GPIO_NUM_15 +#define DISPLAY_WIDTH 128 + +#if CONFIG_OLED_SSD1306_128X32 +#define DISPLAY_HEIGHT 32 +#elif CONFIG_OLED_SSD1306_128X64 +#define DISPLAY_HEIGHT 64 +#else +#error "未选择 OLED 屏幕类型" +#endif + +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true + + +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_18 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/bread-compact-esp32/config.json b/main/boards/bread-compact-esp32/config.json index 174c20f..af09525 100644 --- a/main/boards/bread-compact-esp32/config.json +++ b/main/boards/bread-compact-esp32/config.json @@ -1,21 +1,17 @@ -{ - "target": "esp32", - "builds": [ - { - "name": "bread-compact-esp32", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m.csv\"", - "CONFIG_OLED_SSD1306_128X64=y" - ] - }, - { - "name": "bread-compact-esp32-128x32", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m.csv\"", - "CONFIG_OLED_SSD1306_128X32=y" - ] - } - ] +{ + "target": "esp32", + "builds": [ + { + "name": "bread-compact-esp32", + "sdkconfig_append": [ + "CONFIG_OLED_SSD1306_128X64=y" + ] + }, + { + "name": "bread-compact-esp32-128x32", + "sdkconfig_append": [ + "CONFIG_OLED_SSD1306_128X32=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/bread-compact-esp32/esp32_bread_board.cc b/main/boards/bread-compact-esp32/esp32_bread_board.cc index 7c32ce4..356b741 100644 --- a/main/boards/bread-compact-esp32/esp32_bread_board.cc +++ b/main/boards/bread-compact-esp32/esp32_bread_board.cc @@ -1,162 +1,174 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "mcp_server.h" -#include "lamp_controller.h" -#include "led/single_led.h" -#include "display/oled_display.h" - -#include -#include -#include -#include -#include - -#define TAG "ESP32-MarsbearSupport" - -class CompactWifiBoard : public WifiBoard { -private: - Button boot_button_; - Button touch_button_; - Button asr_button_; - - i2c_master_bus_handle_t display_i2c_bus_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - Display* display_ = nullptr; - - void InitializeDisplayI2c() { - i2c_master_bus_config_t bus_config = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = DISPLAY_SDA_PIN, - .scl_io_num = DISPLAY_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } - - void InitializeButtons() { - - // 配置 GPIO - gpio_config_t io_conf = { - .pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 设置需要配置的 GPIO 引脚 - .mode = GPIO_MODE_OUTPUT, // 设置为输出模式 - .pull_up_en = GPIO_PULLUP_DISABLE, // 禁用上拉 - .pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉 - .intr_type = GPIO_INTR_DISABLE // 禁用中断 - }; - gpio_config(&io_conf); // 应用配置 - - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - gpio_set_level(BUILTIN_LED_GPIO, 1); - app.ToggleChatState(); - }); - - asr_button_.OnClick([this]() { - std::string wake_word="你好小智"; - Application::GetInstance().WakeWordInvoke(wake_word); - }); - - touch_button_.OnPressDown([this]() { - gpio_set_level(BUILTIN_LED_GPIO, 1); - Application::GetInstance().StartListening(); - }); - touch_button_.OnPressUp([this]() { - gpio_set_level(BUILTIN_LED_GPIO, 0); - Application::GetInstance().StopListening(); - }); - } - - // 物联网初始化,添加对 AI 可见设备 - void InitializeTools() { - static LampController lamp(LAMP_GPIO); - } - -public: - CompactWifiBoard() : boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) - { - InitializeDisplayI2c(); - InitializeSsd1306Display(); - InitializeButtons(); - InitializeTools(); - } - - virtual AudioCodec* GetAudioCodec() override - { -#ifdef AUDIO_I2S_METHOD_SIMPLEX - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); -#else - static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); -#endif - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - -}; - -DECLARE_BOARD(CompactWifiBoard); +#include "dual_network_board.h" +#include "codecs/no_audio_codec.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "mcp_server.h" +#include "lamp_controller.h" +#include "led/single_led.h" +#include "display/oled_display.h" + +#include +#include +#include +#include +#include + +#define TAG "ESP32-MarsbearSupport" + +class CompactWifiBoard : public DualNetworkBoard { +private: + Button boot_button_; + Button touch_button_; + Button asr_button_; + + i2c_master_bus_handle_t display_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + Display* display_ = nullptr; + + void InitializeDisplayI2c() { + i2c_master_bus_config_t bus_config = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = DISPLAY_SDA_PIN, + .scl_io_num = DISPLAY_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); + } + + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void InitializeButtons() { + + // 配置 GPIO + gpio_config_t io_conf = { + .pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 设置需要配置的 GPIO 引脚 + .mode = GPIO_MODE_OUTPUT, // 设置为输出模式 + .pull_up_en = GPIO_PULLUP_DISABLE, // 禁用上拉 + .pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉 + .intr_type = GPIO_INTR_DISABLE // 禁用中断 + }; + gpio_config(&io_conf); // 应用配置 + + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (GetNetworkType() == NetworkType::WIFI) { + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + // cast to WifiBoard + auto& wifi_board = static_cast(GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + } + } + gpio_set_level(BUILTIN_LED_GPIO, 1); + app.ToggleChatState(); + }); + + + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + SwitchNetworkType(); + } + }); + + asr_button_.OnClick([this]() { + std::string wake_word="你好小智"; + Application::GetInstance().WakeWordInvoke(wake_word); + }); + + touch_button_.OnPressDown([this]() { + gpio_set_level(BUILTIN_LED_GPIO, 1); + Application::GetInstance().StartListening(); + }); + touch_button_.OnPressUp([this]() { + gpio_set_level(BUILTIN_LED_GPIO, 0); + Application::GetInstance().StopListening(); + }); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeTools() { + static LampController lamp(LAMP_GPIO); + } + +public: + CompactWifiBoard() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) + { + InitializeDisplayI2c(); + InitializeSsd1306Display(); + InitializeButtons(); + InitializeTools(); + } + + virtual AudioCodec* GetAudioCodec() override + { +#ifdef AUDIO_I2S_METHOD_SIMPLEX + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); +#else + static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); +#endif + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + +}; + +DECLARE_BOARD(CompactWifiBoard); diff --git a/main/boards/bread-compact-ml307/compact_ml307_board.cc b/main/boards/bread-compact-ml307/compact_ml307_board.cc index e004b55..3bd2618 100644 --- a/main/boards/bread-compact-ml307/compact_ml307_board.cc +++ b/main/boards/bread-compact-ml307/compact_ml307_board.cc @@ -1,191 +1,191 @@ -#include "dual_network_board.h" -#include "codecs/no_audio_codec.h" -#include "display/oled_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "mcp_server.h" -#include "lamp_controller.h" -#include "led/single_led.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include - -#define TAG "CompactMl307Board" - -class CompactMl307Board : public DualNetworkBoard { -private: - i2c_master_bus_handle_t display_i2c_bus_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - Display* display_ = nullptr; - Button boot_button_; - Button touch_button_; - Button volume_up_button_; - Button volume_down_button_; - - void InitializeDisplayI2c() { - i2c_master_bus_config_t bus_config = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = DISPLAY_SDA_PIN, - .scl_io_num = DISPLAY_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - // cast to WifiBoard - auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); - } - } - app.ToggleChatState(); - }); - boot_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { - SwitchNetworkType(); - } - }); - - touch_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - touch_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - - volume_up_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - - // 物联网初始化,添加对 AI 可见设备 - void InitializeTools() { - static LampController lamp(LAMP_GPIO); - } - -public: - CompactMl307Board() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC), - boot_button_(BOOT_BUTTON_GPIO), - touch_button_(TOUCH_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - - InitializeDisplayI2c(); - InitializeSsd1306Display(); - InitializeButtons(); - InitializeTools(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { -#ifdef AUDIO_I2S_METHOD_SIMPLEX - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); -#else - static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); -#endif - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } -}; - -DECLARE_BOARD(CompactMl307Board); +#include "dual_network_board.h" +#include "codecs/no_audio_codec.h" +#include "display/oled_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "mcp_server.h" +#include "lamp_controller.h" +#include "led/single_led.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include + +#define TAG "CompactMl307Board" + +class CompactMl307Board : public DualNetworkBoard { +private: + i2c_master_bus_handle_t display_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + Display* display_ = nullptr; + Button boot_button_; + Button touch_button_; + Button volume_up_button_; + Button volume_down_button_; + + void InitializeDisplayI2c() { + i2c_master_bus_config_t bus_config = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = DISPLAY_SDA_PIN, + .scl_io_num = DISPLAY_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); + } + + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (GetNetworkType() == NetworkType::WIFI) { + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + // cast to WifiBoard + auto& wifi_board = static_cast(GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + } + } + app.ToggleChatState(); + }); + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + SwitchNetworkType(); + } + }); + + touch_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + touch_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + + volume_up_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeTools() { + static LampController lamp(LAMP_GPIO); + } + +public: + CompactMl307Board() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC), + boot_button_(BOOT_BUTTON_GPIO), + touch_button_(TOUCH_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + + InitializeDisplayI2c(); + InitializeSsd1306Display(); + InitializeButtons(); + InitializeTools(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { +#ifdef AUDIO_I2S_METHOD_SIMPLEX + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); +#else + static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); +#endif + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } +}; + +DECLARE_BOARD(CompactMl307Board); diff --git a/main/boards/bread-compact-ml307/config.h b/main/boards/bread-compact-ml307/config.h index de9ef7f..c39c984 100644 --- a/main/boards/bread-compact-ml307/config.h +++ b/main/boards/bread-compact-ml307/config.h @@ -1,59 +1,59 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -// 如果使用 Duplex I2S 模式,请注释下面一行 -#define AUDIO_I2S_METHOD_SIMPLEX - -#ifdef AUDIO_I2S_METHOD_SIMPLEX - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#else - -#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 - -#endif - -#define BUILTIN_LED_GPIO GPIO_NUM_48 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define TOUCH_BUTTON_GPIO GPIO_NUM_47 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 - -#define DISPLAY_SDA_PIN GPIO_NUM_41 -#define DISPLAY_SCL_PIN GPIO_NUM_42 -#define DISPLAY_WIDTH 128 - -#if CONFIG_OLED_SSD1306_128X32 -#define DISPLAY_HEIGHT 32 -#elif CONFIG_OLED_SSD1306_128X64 -#define DISPLAY_HEIGHT 64 -#else -#error "未选择 OLED 屏幕类型" -#endif - -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true - - -#define ML307_RX_PIN GPIO_NUM_11 -#define ML307_TX_PIN GPIO_NUM_12 - - -// A MCP Test: Control a lamp -#define LAMP_GPIO GPIO_NUM_18 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +// 如果使用 Duplex I2S 模式,请注释下面一行 +#define AUDIO_I2S_METHOD_SIMPLEX + +#ifdef AUDIO_I2S_METHOD_SIMPLEX + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#else + +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +#endif + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define TOUCH_BUTTON_GPIO GPIO_NUM_47 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 + +#define DISPLAY_SDA_PIN GPIO_NUM_41 +#define DISPLAY_SCL_PIN GPIO_NUM_42 +#define DISPLAY_WIDTH 128 + +#if CONFIG_OLED_SSD1306_128X32 +#define DISPLAY_HEIGHT 32 +#elif CONFIG_OLED_SSD1306_128X64 +#define DISPLAY_HEIGHT 64 +#else +#error "未选择 OLED 屏幕类型" +#endif + +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true + + +#define ML307_RX_PIN GPIO_NUM_11 +#define ML307_TX_PIN GPIO_NUM_12 + + +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_18 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/bread-compact-ml307/config.json b/main/boards/bread-compact-ml307/config.json index 9da8cab..6c94732 100644 --- a/main/boards/bread-compact-ml307/config.json +++ b/main/boards/bread-compact-ml307/config.json @@ -1,17 +1,17 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "bread-compact-ml307", - "sdkconfig_append": [ - "CONFIG_OLED_SSD1306_128X32=y" - ] - }, - { - "name": "bread-compact-ml307-128x64", - "sdkconfig_append": [ - "CONFIG_OLED_SSD1306_128X64=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "bread-compact-ml307", + "sdkconfig_append": [ + "CONFIG_OLED_SSD1306_128X32=y" + ] + }, + { + "name": "bread-compact-ml307-128x64", + "sdkconfig_append": [ + "CONFIG_OLED_SSD1306_128X64=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc b/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc index 5e0263e..468d739 100644 --- a/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc +++ b/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc @@ -1,183 +1,183 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "mcp_server.h" -#include "lamp_controller.h" -#include "led/single_led.h" - -#include -#include -#include -#include -#include -#include -#include - -#if defined(LCD_TYPE_ILI9341_SERIAL) -#include "esp_lcd_ili9341.h" -#endif - -#if defined(LCD_TYPE_GC9A01_SERIAL) -#include "esp_lcd_gc9a01.h" -static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { - // {cmd, { data }, data_size, delay_ms} - {0xfe, (uint8_t[]){0x00}, 0, 0}, - {0xef, (uint8_t[]){0x00}, 0, 0}, - {0xb0, (uint8_t[]){0xc0}, 1, 0}, - {0xb1, (uint8_t[]){0x80}, 1, 0}, - {0xb2, (uint8_t[]){0x27}, 1, 0}, - {0xb3, (uint8_t[]){0x13}, 1, 0}, - {0xb6, (uint8_t[]){0x19}, 1, 0}, - {0xb7, (uint8_t[]){0x05}, 1, 0}, - {0xac, (uint8_t[]){0xc8}, 1, 0}, - {0xab, (uint8_t[]){0x0f}, 1, 0}, - {0x3a, (uint8_t[]){0x05}, 1, 0}, - {0xb4, (uint8_t[]){0x04}, 1, 0}, - {0xa8, (uint8_t[]){0x08}, 1, 0}, - {0xb8, (uint8_t[]){0x08}, 1, 0}, - {0xea, (uint8_t[]){0x02}, 1, 0}, - {0xe8, (uint8_t[]){0x2A}, 1, 0}, - {0xe9, (uint8_t[]){0x47}, 1, 0}, - {0xe7, (uint8_t[]){0x5f}, 1, 0}, - {0xc6, (uint8_t[]){0x21}, 1, 0}, - {0xc7, (uint8_t[]){0x15}, 1, 0}, - {0xf0, - (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, - 0x04, 0x12, 0x14, 0x1f}, - 14, 0}, - {0xf1, - (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, - 0x0C, 0x1A, 0x14, 0x1E}, - 14, 0}, - {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, - {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, -}; -#endif - -#define TAG "CompactWifiBoardLCD" - -class CompactWifiBoardLCD : public WifiBoard { -private: - - Button boot_button_; - LcdDisplay* display_; - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_CLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeLcdDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = DISPLAY_SPI_MODE; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; -#if defined(LCD_TYPE_ILI9341_SERIAL) - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); -#elif defined(LCD_TYPE_GC9A01_SERIAL) - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel)); - gc9a01_vendor_config_t gc9107_vendor_config = { - .init_cmds = gc9107_lcd_init_cmds, - .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), - }; -#else - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); -#endif - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); -#ifdef LCD_TYPE_GC9A01_SERIAL - panel_config.vendor_config = &gc9107_vendor_config; -#endif - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - // 物联网初始化,添加对 AI 可见设备 - void InitializeTools() { - static LampController lamp(LAMP_GPIO); - } - -public: - CompactWifiBoardLCD() : - boot_button_(BOOT_BUTTON_GPIO) { - InitializeSpi(); - InitializeLcdDisplay(); - InitializeButtons(); - InitializeTools(); - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - GetBacklight()->RestoreBrightness(); - } - - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { -#ifdef AUDIO_I2S_METHOD_SIMPLEX - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); -#else - static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); -#endif - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - return nullptr; - } -}; - -DECLARE_BOARD(CompactWifiBoardLCD); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "mcp_server.h" +#include "lamp_controller.h" +#include "led/single_led.h" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(LCD_TYPE_ILI9341_SERIAL) +#include "esp_lcd_ili9341.h" +#endif + +#if defined(LCD_TYPE_GC9A01_SERIAL) +#include "esp_lcd_gc9a01.h" +static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { + // {cmd, { data }, data_size, delay_ms} + {0xfe, (uint8_t[]){0x00}, 0, 0}, + {0xef, (uint8_t[]){0x00}, 0, 0}, + {0xb0, (uint8_t[]){0xc0}, 1, 0}, + {0xb1, (uint8_t[]){0x80}, 1, 0}, + {0xb2, (uint8_t[]){0x27}, 1, 0}, + {0xb3, (uint8_t[]){0x13}, 1, 0}, + {0xb6, (uint8_t[]){0x19}, 1, 0}, + {0xb7, (uint8_t[]){0x05}, 1, 0}, + {0xac, (uint8_t[]){0xc8}, 1, 0}, + {0xab, (uint8_t[]){0x0f}, 1, 0}, + {0x3a, (uint8_t[]){0x05}, 1, 0}, + {0xb4, (uint8_t[]){0x04}, 1, 0}, + {0xa8, (uint8_t[]){0x08}, 1, 0}, + {0xb8, (uint8_t[]){0x08}, 1, 0}, + {0xea, (uint8_t[]){0x02}, 1, 0}, + {0xe8, (uint8_t[]){0x2A}, 1, 0}, + {0xe9, (uint8_t[]){0x47}, 1, 0}, + {0xe7, (uint8_t[]){0x5f}, 1, 0}, + {0xc6, (uint8_t[]){0x21}, 1, 0}, + {0xc7, (uint8_t[]){0x15}, 1, 0}, + {0xf0, + (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, + 0x04, 0x12, 0x14, 0x1f}, + 14, 0}, + {0xf1, + (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, + 0x0C, 0x1A, 0x14, 0x1E}, + 14, 0}, + {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, + {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, +}; +#endif + +#define TAG "CompactWifiBoardLCD" + +class CompactWifiBoardLCD : public WifiBoard { +private: + + Button boot_button_; + LcdDisplay* display_; + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = DISPLAY_SPI_MODE; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; +#if defined(LCD_TYPE_ILI9341_SERIAL) + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); +#elif defined(LCD_TYPE_GC9A01_SERIAL) + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel)); + gc9a01_vendor_config_t gc9107_vendor_config = { + .init_cmds = gc9107_lcd_init_cmds, + .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), + }; +#else + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); +#endif + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); +#ifdef LCD_TYPE_GC9A01_SERIAL + panel_config.vendor_config = &gc9107_vendor_config; +#endif + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeTools() { + static LampController lamp(LAMP_GPIO); + } + +public: + CompactWifiBoardLCD() : + boot_button_(BOOT_BUTTON_GPIO) { + InitializeSpi(); + InitializeLcdDisplay(); + InitializeButtons(); + InitializeTools(); + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + GetBacklight()->RestoreBrightness(); + } + + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { +#ifdef AUDIO_I2S_METHOD_SIMPLEX + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); +#else + static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); +#endif + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + return nullptr; + } +}; + +DECLARE_BOARD(CompactWifiBoardLCD); diff --git a/main/boards/bread-compact-wifi-lcd/config.h b/main/boards/bread-compact-wifi-lcd/config.h index 12d3802..e5fb567 100644 --- a/main/boards/bread-compact-wifi-lcd/config.h +++ b/main/boards/bread-compact-wifi-lcd/config.h @@ -1,289 +1,289 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -// 如果使用 Duplex I2S 模式,请注释下面一行 -#define AUDIO_I2S_METHOD_SIMPLEX - -#ifdef AUDIO_I2S_METHOD_SIMPLEX - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#else - -#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 - -#endif - - -#define BUILTIN_LED_GPIO GPIO_NUM_48 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define TOUCH_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 -#define DISPLAY_MOSI_PIN GPIO_NUM_47 -#define DISPLAY_CLK_PIN GPIO_NUM_21 -#define DISPLAY_DC_PIN GPIO_NUM_40 -#define DISPLAY_RST_PIN GPIO_NUM_45 -#define DISPLAY_CS_PIN GPIO_NUM_41 - - -#ifdef CONFIG_LCD_ST7789_240X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_170X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 170 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 35 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_172X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 172 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 34 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X280 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 280 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 20 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X240 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X240_7PIN -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 3 -#endif - -#ifdef CONFIG_LCD_ST7789_240X135 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 135 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY true -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 40 -#define DISPLAY_OFFSET_Y 53 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7735_128X160 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 160 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7735_128X128 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 32 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7796_320X480 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 480 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 480 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ILI9341_240X320 -#define LCD_TYPE_ILI9341_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS -#define LCD_TYPE_ILI9341_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_GC9A01_240X240 -#define LCD_TYPE_GC9A01_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_CUSTOM -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - - -// A MCP Test: Control a lamp -#define LAMP_GPIO GPIO_NUM_18 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +// 如果使用 Duplex I2S 模式,请注释下面一行 +#define AUDIO_I2S_METHOD_SIMPLEX + +#ifdef AUDIO_I2S_METHOD_SIMPLEX + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#else + +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +#endif + + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define TOUCH_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 +#define DISPLAY_MOSI_PIN GPIO_NUM_47 +#define DISPLAY_CLK_PIN GPIO_NUM_21 +#define DISPLAY_DC_PIN GPIO_NUM_40 +#define DISPLAY_RST_PIN GPIO_NUM_45 +#define DISPLAY_CS_PIN GPIO_NUM_41 + + +#ifdef CONFIG_LCD_ST7789_240X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_170X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 170 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 35 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_172X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 172 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 34 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X280 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 280 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 20 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X240 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X240_7PIN +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 3 +#endif + +#ifdef CONFIG_LCD_ST7789_240X135 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 135 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 40 +#define DISPLAY_OFFSET_Y 53 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7735_128X160 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 160 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7735_128X128 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 32 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7796_320X480 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 480 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 480 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ILI9341_240X320 +#define LCD_TYPE_ILI9341_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS +#define LCD_TYPE_ILI9341_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_GC9A01_240X240 +#define LCD_TYPE_GC9A01_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_CUSTOM +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + + +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_18 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/bread-compact-wifi-s3cam/README.md b/main/boards/bread-compact-wifi-s3cam/README.md index 9eac5f1..368ed68 100644 --- a/main/boards/bread-compact-wifi-s3cam/README.md +++ b/main/boards/bread-compact-wifi-s3cam/README.md @@ -1,31 +1,31 @@ -硬件基于基于ESP32S3CAM开发板,代码基于bread-compact-wifi-lcd修改 -使用的摄像头是OV2640 -注意因为摄像头占用IO较多,所以占用了ESP32S3的USB 19 20两个引脚 -连线方式参考config.h文件中对引脚的定义 - - -# 编译配置命令 - -**配置编译目标为 ESP32S3:** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type ->面包板新版接线(WiFi)+ LCD + Camera -``` - -**编译烧入:** - -```bash -idf.py build flash +硬件基于基于ESP32S3CAM开发板,代码基于bread-compact-wifi-lcd修改 +使用的摄像头是OV2640 +注意因为摄像头占用IO较多,所以占用了ESP32S3的USB 19 20两个引脚 +连线方式参考config.h文件中对引脚的定义 + + +# 编译配置命令 + +**配置编译目标为 ESP32S3:** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type ->面包板新版接线(WiFi)+ LCD + Camera +``` + +**编译烧入:** + +```bash +idf.py build flash ``` \ No newline at end of file diff --git a/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc b/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc index af4b6b9..c6213bb 100644 --- a/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc +++ b/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc @@ -1,214 +1,214 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "mcp_server.h" -#include "lamp_controller.h" -#include "led/single_led.h" -#include "esp32_camera.h" - -#include -#include -#include -#include -#include -#include -#include - -#if defined(LCD_TYPE_ILI9341_SERIAL) -#include "esp_lcd_ili9341.h" -#endif - -#if defined(LCD_TYPE_GC9A01_SERIAL) -#include "esp_lcd_gc9a01.h" -static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { - // {cmd, { data }, data_size, delay_ms} - {0xfe, (uint8_t[]){0x00}, 0, 0}, - {0xef, (uint8_t[]){0x00}, 0, 0}, - {0xb0, (uint8_t[]){0xc0}, 1, 0}, - {0xb1, (uint8_t[]){0x80}, 1, 0}, - {0xb2, (uint8_t[]){0x27}, 1, 0}, - {0xb3, (uint8_t[]){0x13}, 1, 0}, - {0xb6, (uint8_t[]){0x19}, 1, 0}, - {0xb7, (uint8_t[]){0x05}, 1, 0}, - {0xac, (uint8_t[]){0xc8}, 1, 0}, - {0xab, (uint8_t[]){0x0f}, 1, 0}, - {0x3a, (uint8_t[]){0x05}, 1, 0}, - {0xb4, (uint8_t[]){0x04}, 1, 0}, - {0xa8, (uint8_t[]){0x08}, 1, 0}, - {0xb8, (uint8_t[]){0x08}, 1, 0}, - {0xea, (uint8_t[]){0x02}, 1, 0}, - {0xe8, (uint8_t[]){0x2A}, 1, 0}, - {0xe9, (uint8_t[]){0x47}, 1, 0}, - {0xe7, (uint8_t[]){0x5f}, 1, 0}, - {0xc6, (uint8_t[]){0x21}, 1, 0}, - {0xc7, (uint8_t[]){0x15}, 1, 0}, - {0xf0, - (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, - 0x04, 0x12, 0x14, 0x1f}, - 14, 0}, - {0xf1, - (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, - 0x0C, 0x1A, 0x14, 0x1E}, - 14, 0}, - {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, - {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, -}; -#endif - -#define TAG "CompactWifiBoardS3Cam" - -class CompactWifiBoardS3Cam : public WifiBoard { -private: - - Button boot_button_; - LcdDisplay* display_; - Esp32Camera* camera_; - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_CLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeLcdDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = DISPLAY_SPI_MODE; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; -#if defined(LCD_TYPE_ILI9341_SERIAL) - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); -#elif defined(LCD_TYPE_GC9A01_SERIAL) - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel)); - gc9a01_vendor_config_t gc9107_vendor_config = { - .init_cmds = gc9107_lcd_init_cmds, - .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), - }; -#else - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); -#endif - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); -#ifdef LCD_TYPE_GC9A01_SERIAL - panel_config.vendor_config = &gc9107_vendor_config; -#endif - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeCamera() { - camera_config_t config = {}; - config.pin_d0 = CAMERA_PIN_D0; - config.pin_d1 = CAMERA_PIN_D1; - config.pin_d2 = CAMERA_PIN_D2; - config.pin_d3 = CAMERA_PIN_D3; - config.pin_d4 = CAMERA_PIN_D4; - config.pin_d5 = CAMERA_PIN_D5; - config.pin_d6 = CAMERA_PIN_D6; - config.pin_d7 = CAMERA_PIN_D7; - config.pin_xclk = CAMERA_PIN_XCLK; - config.pin_pclk = CAMERA_PIN_PCLK; - config.pin_vsync = CAMERA_PIN_VSYNC; - config.pin_href = CAMERA_PIN_HREF; - config.pin_sccb_sda = CAMERA_PIN_SIOD; - config.pin_sccb_scl = CAMERA_PIN_SIOC; - config.sccb_i2c_port = 0; - config.pin_pwdn = CAMERA_PIN_PWDN; - config.pin_reset = CAMERA_PIN_RESET; - config.xclk_freq_hz = XCLK_FREQ_HZ; - config.pixel_format = PIXFORMAT_RGB565; - config.frame_size = FRAMESIZE_QVGA; - config.jpeg_quality = 12; - config.fb_count = 1; - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - camera_ = new Esp32Camera(config); - camera_->SetHMirror(false); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - -public: - CompactWifiBoardS3Cam() : - boot_button_(BOOT_BUTTON_GPIO) { - InitializeSpi(); - InitializeLcdDisplay(); - InitializeButtons(); - InitializeCamera(); - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - GetBacklight()->RestoreBrightness(); - } - - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { -#ifdef AUDIO_I2S_METHOD_SIMPLEX - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); -#else - static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); -#endif - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - return nullptr; - } - - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(CompactWifiBoardS3Cam); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "mcp_server.h" +#include "lamp_controller.h" +#include "led/single_led.h" +#include "esp32_camera.h" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(LCD_TYPE_ILI9341_SERIAL) +#include "esp_lcd_ili9341.h" +#endif + +#if defined(LCD_TYPE_GC9A01_SERIAL) +#include "esp_lcd_gc9a01.h" +static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { + // {cmd, { data }, data_size, delay_ms} + {0xfe, (uint8_t[]){0x00}, 0, 0}, + {0xef, (uint8_t[]){0x00}, 0, 0}, + {0xb0, (uint8_t[]){0xc0}, 1, 0}, + {0xb1, (uint8_t[]){0x80}, 1, 0}, + {0xb2, (uint8_t[]){0x27}, 1, 0}, + {0xb3, (uint8_t[]){0x13}, 1, 0}, + {0xb6, (uint8_t[]){0x19}, 1, 0}, + {0xb7, (uint8_t[]){0x05}, 1, 0}, + {0xac, (uint8_t[]){0xc8}, 1, 0}, + {0xab, (uint8_t[]){0x0f}, 1, 0}, + {0x3a, (uint8_t[]){0x05}, 1, 0}, + {0xb4, (uint8_t[]){0x04}, 1, 0}, + {0xa8, (uint8_t[]){0x08}, 1, 0}, + {0xb8, (uint8_t[]){0x08}, 1, 0}, + {0xea, (uint8_t[]){0x02}, 1, 0}, + {0xe8, (uint8_t[]){0x2A}, 1, 0}, + {0xe9, (uint8_t[]){0x47}, 1, 0}, + {0xe7, (uint8_t[]){0x5f}, 1, 0}, + {0xc6, (uint8_t[]){0x21}, 1, 0}, + {0xc7, (uint8_t[]){0x15}, 1, 0}, + {0xf0, + (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, + 0x04, 0x12, 0x14, 0x1f}, + 14, 0}, + {0xf1, + (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, + 0x0C, 0x1A, 0x14, 0x1E}, + 14, 0}, + {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, + {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, +}; +#endif + +#define TAG "CompactWifiBoardS3Cam" + +class CompactWifiBoardS3Cam : public WifiBoard { +private: + + Button boot_button_; + LcdDisplay* display_; + Esp32Camera* camera_; + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = DISPLAY_SPI_MODE; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; +#if defined(LCD_TYPE_ILI9341_SERIAL) + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); +#elif defined(LCD_TYPE_GC9A01_SERIAL) + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel)); + gc9a01_vendor_config_t gc9107_vendor_config = { + .init_cmds = gc9107_lcd_init_cmds, + .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), + }; +#else + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); +#endif + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); +#ifdef LCD_TYPE_GC9A01_SERIAL + panel_config.vendor_config = &gc9107_vendor_config; +#endif + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeCamera() { + camera_config_t config = {}; + config.pin_d0 = CAMERA_PIN_D0; + config.pin_d1 = CAMERA_PIN_D1; + config.pin_d2 = CAMERA_PIN_D2; + config.pin_d3 = CAMERA_PIN_D3; + config.pin_d4 = CAMERA_PIN_D4; + config.pin_d5 = CAMERA_PIN_D5; + config.pin_d6 = CAMERA_PIN_D6; + config.pin_d7 = CAMERA_PIN_D7; + config.pin_xclk = CAMERA_PIN_XCLK; + config.pin_pclk = CAMERA_PIN_PCLK; + config.pin_vsync = CAMERA_PIN_VSYNC; + config.pin_href = CAMERA_PIN_HREF; + config.pin_sccb_sda = CAMERA_PIN_SIOD; + config.pin_sccb_scl = CAMERA_PIN_SIOC; + config.sccb_i2c_port = 0; + config.pin_pwdn = CAMERA_PIN_PWDN; + config.pin_reset = CAMERA_PIN_RESET; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_QVGA; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + camera_ = new Esp32Camera(config); + camera_->SetHMirror(false); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + +public: + CompactWifiBoardS3Cam() : + boot_button_(BOOT_BUTTON_GPIO) { + InitializeSpi(); + InitializeLcdDisplay(); + InitializeButtons(); + InitializeCamera(); + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + GetBacklight()->RestoreBrightness(); + } + + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { +#ifdef AUDIO_I2S_METHOD_SIMPLEX + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); +#else + static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); +#endif + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + return nullptr; + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(CompactWifiBoardS3Cam); diff --git a/main/boards/bread-compact-wifi-s3cam/config.h b/main/boards/bread-compact-wifi-s3cam/config.h index 857d8c0..1109762 100644 --- a/main/boards/bread-compact-wifi-s3cam/config.h +++ b/main/boards/bread-compact-wifi-s3cam/config.h @@ -1,308 +1,308 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -// 如果使用 Duplex I2S 模式,请注释下面一行 -#define AUDIO_I2S_METHOD_SIMPLEX - -#ifdef AUDIO_I2S_METHOD_SIMPLEX - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_1 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_2 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_42 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_39 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_40 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_41 - -#else - -#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 - -#endif - - -#define BUILTIN_LED_GPIO GPIO_NUM_48 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define TOUCH_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -//Camera Config -#define CAMERA_PIN_D0 GPIO_NUM_11 -#define CAMERA_PIN_D1 GPIO_NUM_9 -#define CAMERA_PIN_D2 GPIO_NUM_8 -#define CAMERA_PIN_D3 GPIO_NUM_10 -#define CAMERA_PIN_D4 GPIO_NUM_12 -#define CAMERA_PIN_D5 GPIO_NUM_18 -#define CAMERA_PIN_D6 GPIO_NUM_17 -#define CAMERA_PIN_D7 GPIO_NUM_16 -#define CAMERA_PIN_XCLK GPIO_NUM_15 -#define CAMERA_PIN_PCLK GPIO_NUM_13 -#define CAMERA_PIN_VSYNC GPIO_NUM_6 -#define CAMERA_PIN_HREF GPIO_NUM_7 -#define CAMERA_PIN_SIOC GPIO_NUM_5 -#define CAMERA_PIN_SIOD GPIO_NUM_4 -#define CAMERA_PIN_PWDN GPIO_NUM_NC -#define CAMERA_PIN_RESET GPIO_NUM_NC -#define XCLK_FREQ_HZ 20000000 - - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_38 -#define DISPLAY_MOSI_PIN GPIO_NUM_20 -#define DISPLAY_CLK_PIN GPIO_NUM_19 -#define DISPLAY_DC_PIN GPIO_NUM_47 -#define DISPLAY_RST_PIN GPIO_NUM_21 -#define DISPLAY_CS_PIN GPIO_NUM_45 - - -#ifdef CONFIG_LCD_ST7789_240X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_170X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 170 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 35 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_172X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 172 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 34 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X280 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 280 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 20 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X240 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X240_7PIN -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 3 -#endif - -#ifdef CONFIG_LCD_ST7789_240X135 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 135 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY true -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 40 -#define DISPLAY_OFFSET_Y 53 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7735_128X160 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 160 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7735_128X128 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 32 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7796_320X480 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 480 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 480 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ILI9341_240X320 -#define LCD_TYPE_ILI9341_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS -#define LCD_TYPE_ILI9341_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_GC9A01_240X240 -#define LCD_TYPE_GC9A01_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_CUSTOM -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - - -// A MCP Test: Control a lamp -#define LAMP_GPIO GPIO_NUM_14 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +// 如果使用 Duplex I2S 模式,请注释下面一行 +#define AUDIO_I2S_METHOD_SIMPLEX + +#ifdef AUDIO_I2S_METHOD_SIMPLEX + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_1 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_2 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_42 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_39 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_40 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_41 + +#else + +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +#endif + + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define TOUCH_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +//Camera Config +#define CAMERA_PIN_D0 GPIO_NUM_11 +#define CAMERA_PIN_D1 GPIO_NUM_9 +#define CAMERA_PIN_D2 GPIO_NUM_8 +#define CAMERA_PIN_D3 GPIO_NUM_10 +#define CAMERA_PIN_D4 GPIO_NUM_12 +#define CAMERA_PIN_D5 GPIO_NUM_18 +#define CAMERA_PIN_D6 GPIO_NUM_17 +#define CAMERA_PIN_D7 GPIO_NUM_16 +#define CAMERA_PIN_XCLK GPIO_NUM_15 +#define CAMERA_PIN_PCLK GPIO_NUM_13 +#define CAMERA_PIN_VSYNC GPIO_NUM_6 +#define CAMERA_PIN_HREF GPIO_NUM_7 +#define CAMERA_PIN_SIOC GPIO_NUM_5 +#define CAMERA_PIN_SIOD GPIO_NUM_4 +#define CAMERA_PIN_PWDN GPIO_NUM_NC +#define CAMERA_PIN_RESET GPIO_NUM_NC +#define XCLK_FREQ_HZ 20000000 + + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_38 +#define DISPLAY_MOSI_PIN GPIO_NUM_20 +#define DISPLAY_CLK_PIN GPIO_NUM_19 +#define DISPLAY_DC_PIN GPIO_NUM_47 +#define DISPLAY_RST_PIN GPIO_NUM_21 +#define DISPLAY_CS_PIN GPIO_NUM_45 + + +#ifdef CONFIG_LCD_ST7789_240X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_170X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 170 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 35 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_172X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 172 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 34 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X280 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 280 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 20 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X240 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X240_7PIN +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 3 +#endif + +#ifdef CONFIG_LCD_ST7789_240X135 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 135 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 40 +#define DISPLAY_OFFSET_Y 53 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7735_128X160 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 160 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7735_128X128 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 32 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7796_320X480 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 480 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 480 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ILI9341_240X320 +#define LCD_TYPE_ILI9341_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS +#define LCD_TYPE_ILI9341_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_GC9A01_240X240 +#define LCD_TYPE_GC9A01_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_CUSTOM +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + + +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_14 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/bread-compact-wifi/compact_wifi_board.cc b/main/boards/bread-compact-wifi/compact_wifi_board.cc index fca4d1c..9fa40f0 100644 --- a/main/boards/bread-compact-wifi/compact_wifi_board.cc +++ b/main/boards/bread-compact-wifi/compact_wifi_board.cc @@ -1,188 +1,188 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/oled_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "mcp_server.h" -#include "lamp_controller.h" -#include "led/single_led.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include - -#ifdef SH1106 -#include -#endif - -#define TAG "CompactWifiBoard" - -class CompactWifiBoard : public WifiBoard { -private: - i2c_master_bus_handle_t display_i2c_bus_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - Display* display_ = nullptr; - Button boot_button_; - Button touch_button_; - Button volume_up_button_; - Button volume_down_button_; - - void InitializeDisplayI2c() { - i2c_master_bus_config_t bus_config = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = DISPLAY_SDA_PIN, - .scl_io_num = DISPLAY_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - -#ifdef SH1106 - ESP_ERROR_CHECK(esp_lcd_new_panel_sh1106(panel_io_, &panel_config, &panel_)); -#else - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); -#endif - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; - } - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false)); - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - touch_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - touch_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - - volume_up_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - - // 物联网初始化,逐步迁移到 MCP 协议 - void InitializeTools() { - static LampController lamp(LAMP_GPIO); - } - -public: - CompactWifiBoard() : - boot_button_(BOOT_BUTTON_GPIO), - touch_button_(TOUCH_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializeDisplayI2c(); - InitializeSsd1306Display(); - InitializeButtons(); - InitializeTools(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { -#ifdef AUDIO_I2S_METHOD_SIMPLEX - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); -#else - static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); -#endif - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } -}; - -DECLARE_BOARD(CompactWifiBoard); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/oled_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "mcp_server.h" +#include "lamp_controller.h" +#include "led/single_led.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include + +#ifdef SH1106 +#include +#endif + +#define TAG "CompactWifiBoard" + +class CompactWifiBoard : public WifiBoard { +private: + i2c_master_bus_handle_t display_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + Display* display_ = nullptr; + Button boot_button_; + Button touch_button_; + Button volume_up_button_; + Button volume_down_button_; + + void InitializeDisplayI2c() { + i2c_master_bus_config_t bus_config = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = DISPLAY_SDA_PIN, + .scl_io_num = DISPLAY_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); + } + + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + +#ifdef SH1106 + ESP_ERROR_CHECK(esp_lcd_new_panel_sh1106(panel_io_, &panel_config, &panel_)); +#else + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); +#endif + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false)); + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + touch_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + touch_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + + volume_up_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + + // 物联网初始化,逐步迁移到 MCP 协议 + void InitializeTools() { + static LampController lamp(LAMP_GPIO); + } + +public: + CompactWifiBoard() : + boot_button_(BOOT_BUTTON_GPIO), + touch_button_(TOUCH_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializeDisplayI2c(); + InitializeSsd1306Display(); + InitializeButtons(); + InitializeTools(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { +#ifdef AUDIO_I2S_METHOD_SIMPLEX + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); +#else + static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); +#endif + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } +}; + +DECLARE_BOARD(CompactWifiBoard); diff --git a/main/boards/bread-compact-wifi/config.h b/main/boards/bread-compact-wifi/config.h index 7bb0982..c49cbd8 100644 --- a/main/boards/bread-compact-wifi/config.h +++ b/main/boards/bread-compact-wifi/config.h @@ -1,59 +1,59 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -// 如果使用 Duplex I2S 模式,请注释下面一行 -#define AUDIO_I2S_METHOD_SIMPLEX - -#ifdef AUDIO_I2S_METHOD_SIMPLEX - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#else - -#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 - -#endif - - -#define BUILTIN_LED_GPIO GPIO_NUM_48 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define TOUCH_BUTTON_GPIO GPIO_NUM_47 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 - -#define DISPLAY_SDA_PIN GPIO_NUM_41 -#define DISPLAY_SCL_PIN GPIO_NUM_42 -#define DISPLAY_WIDTH 128 - -#if CONFIG_OLED_SSD1306_128X32 -#define DISPLAY_HEIGHT 32 -#elif CONFIG_OLED_SSD1306_128X64 -#define DISPLAY_HEIGHT 64 -#elif CONFIG_OLED_SH1106_128X64 -#define DISPLAY_HEIGHT 64 -#define SH1106 -#else -#error "未选择 OLED 屏幕类型" -#endif - -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true - - -// A MCP Test: Control a lamp -#define LAMP_GPIO GPIO_NUM_18 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +// 如果使用 Duplex I2S 模式,请注释下面一行 +#define AUDIO_I2S_METHOD_SIMPLEX + +#ifdef AUDIO_I2S_METHOD_SIMPLEX + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#else + +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +#endif + + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define TOUCH_BUTTON_GPIO GPIO_NUM_47 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 + +#define DISPLAY_SDA_PIN GPIO_NUM_41 +#define DISPLAY_SCL_PIN GPIO_NUM_42 +#define DISPLAY_WIDTH 128 + +#if CONFIG_OLED_SSD1306_128X32 +#define DISPLAY_HEIGHT 32 +#elif CONFIG_OLED_SSD1306_128X64 +#define DISPLAY_HEIGHT 64 +#elif CONFIG_OLED_SH1106_128X64 +#define DISPLAY_HEIGHT 64 +#define SH1106 +#else +#error "未选择 OLED 屏幕类型" +#endif + +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true + + +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_18 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/bread-compact-wifi/config.json b/main/boards/bread-compact-wifi/config.json index ea296f9..a51102b 100644 --- a/main/boards/bread-compact-wifi/config.json +++ b/main/boards/bread-compact-wifi/config.json @@ -1,17 +1,17 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "bread-compact-wifi", - "sdkconfig_append": [ - "CONFIG_OLED_SSD1306_128X32=y" - ] - }, - { - "name": "bread-compact-wifi-128x64", - "sdkconfig_append": [ - "CONFIG_OLED_SSD1306_128X64=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "bread-compact-wifi", + "sdkconfig_append": [ + "CONFIG_OLED_SSD1306_128X32=y" + ] + }, + { + "name": "bread-compact-wifi-128x64", + "sdkconfig_append": [ + "CONFIG_OLED_SSD1306_128X64=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/common/adc_battery_monitor.cc b/main/boards/common/adc_battery_monitor.cc index 9edf11c..cc2a593 100644 --- a/main/boards/common/adc_battery_monitor.cc +++ b/main/boards/common/adc_battery_monitor.cc @@ -1,81 +1,81 @@ -#include "adc_battery_monitor.h" - -AdcBatteryMonitor::AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin) - : charging_pin_(charging_pin) { - - // Initialize charging pin - gpio_config_t gpio_cfg = { - .pin_bit_mask = 1ULL << charging_pin, - .mode = GPIO_MODE_INPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - ESP_ERROR_CHECK(gpio_config(&gpio_cfg)); - - // Initialize ADC battery estimation - adc_battery_estimation_t adc_cfg = { - .internal = { - .adc_unit = adc_unit, - .adc_bitwidth = ADC_BITWIDTH_12, - .adc_atten = ADC_ATTEN_DB_12, - }, - .adc_channel = adc_channel, - .upper_resistor = upper_resistor, - .lower_resistor = lower_resistor - }; - adc_cfg.charging_detect_cb = [](void *user_data) -> bool { - AdcBatteryMonitor *self = (AdcBatteryMonitor *)user_data; - return gpio_get_level(self->charging_pin_) == 1; - }; - adc_cfg.charging_detect_user_data = this; - adc_battery_estimation_handle_ = adc_battery_estimation_create(&adc_cfg); - - // Initialize timer - esp_timer_create_args_t timer_cfg = { - .callback = [](void *arg) { - AdcBatteryMonitor *self = (AdcBatteryMonitor *)arg; - self->CheckBatteryStatus(); - }, - .arg = this, - .name = "adc_battery_monitor", - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_cfg, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); -} - -AdcBatteryMonitor::~AdcBatteryMonitor() { - if (adc_battery_estimation_handle_) { - ESP_ERROR_CHECK(adc_battery_estimation_destroy(adc_battery_estimation_handle_)); - } -} - -bool AdcBatteryMonitor::IsCharging() { - bool is_charging = false; - ESP_ERROR_CHECK(adc_battery_estimation_get_charging_state(adc_battery_estimation_handle_, &is_charging)); - return is_charging; -} - -bool AdcBatteryMonitor::IsDischarging() { - return !IsCharging(); -} - -uint8_t AdcBatteryMonitor::GetBatteryLevel() { - float capacity = 0; - ESP_ERROR_CHECK(adc_battery_estimation_get_capacity(adc_battery_estimation_handle_, &capacity)); - return capacity; -} - -void AdcBatteryMonitor::OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; -} - -void AdcBatteryMonitor::CheckBatteryStatus() { - bool new_charging_status = IsCharging(); - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - } +#include "adc_battery_monitor.h" + +AdcBatteryMonitor::AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin) + : charging_pin_(charging_pin) { + + // Initialize charging pin + gpio_config_t gpio_cfg = { + .pin_bit_mask = 1ULL << charging_pin, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_ERROR_CHECK(gpio_config(&gpio_cfg)); + + // Initialize ADC battery estimation + adc_battery_estimation_t adc_cfg = { + .internal = { + .adc_unit = adc_unit, + .adc_bitwidth = ADC_BITWIDTH_12, + .adc_atten = ADC_ATTEN_DB_12, + }, + .adc_channel = adc_channel, + .upper_resistor = upper_resistor, + .lower_resistor = lower_resistor + }; + adc_cfg.charging_detect_cb = [](void *user_data) -> bool { + AdcBatteryMonitor *self = (AdcBatteryMonitor *)user_data; + return gpio_get_level(self->charging_pin_) == 1; + }; + adc_cfg.charging_detect_user_data = this; + adc_battery_estimation_handle_ = adc_battery_estimation_create(&adc_cfg); + + // Initialize timer + esp_timer_create_args_t timer_cfg = { + .callback = [](void *arg) { + AdcBatteryMonitor *self = (AdcBatteryMonitor *)arg; + self->CheckBatteryStatus(); + }, + .arg = this, + .name = "adc_battery_monitor", + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_cfg, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); +} + +AdcBatteryMonitor::~AdcBatteryMonitor() { + if (adc_battery_estimation_handle_) { + ESP_ERROR_CHECK(adc_battery_estimation_destroy(adc_battery_estimation_handle_)); + } +} + +bool AdcBatteryMonitor::IsCharging() { + bool is_charging = false; + ESP_ERROR_CHECK(adc_battery_estimation_get_charging_state(adc_battery_estimation_handle_, &is_charging)); + return is_charging; +} + +bool AdcBatteryMonitor::IsDischarging() { + return !IsCharging(); +} + +uint8_t AdcBatteryMonitor::GetBatteryLevel() { + float capacity = 0; + ESP_ERROR_CHECK(adc_battery_estimation_get_capacity(adc_battery_estimation_handle_, &capacity)); + return capacity; +} + +void AdcBatteryMonitor::OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; +} + +void AdcBatteryMonitor::CheckBatteryStatus() { + bool new_charging_status = IsCharging(); + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + } } \ No newline at end of file diff --git a/main/boards/common/adc_battery_monitor.h b/main/boards/common/adc_battery_monitor.h index 6a123a7..a2312c5 100644 --- a/main/boards/common/adc_battery_monitor.h +++ b/main/boards/common/adc_battery_monitor.h @@ -1,30 +1,30 @@ -#ifndef ADC_BATTERY_MONITOR_H -#define ADC_BATTERY_MONITOR_H - -#include -#include -#include -#include - -class AdcBatteryMonitor { -public: - AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin = GPIO_NUM_NC); - ~AdcBatteryMonitor(); - - bool IsCharging(); - bool IsDischarging(); - uint8_t GetBatteryLevel(); - - void OnChargingStatusChanged(std::function callback); - -private: - gpio_num_t charging_pin_; - adc_battery_estimation_handle_t adc_battery_estimation_handle_ = nullptr; - esp_timer_handle_t timer_handle_ = nullptr; - bool is_charging_ = false; - std::function on_charging_status_changed_; - - void CheckBatteryStatus(); -}; - -#endif // ADC_BATTERY_MONITOR_H +#ifndef ADC_BATTERY_MONITOR_H +#define ADC_BATTERY_MONITOR_H + +#include +#include +#include +#include + +class AdcBatteryMonitor { +public: + AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin = GPIO_NUM_NC); + ~AdcBatteryMonitor(); + + bool IsCharging(); + bool IsDischarging(); + uint8_t GetBatteryLevel(); + + void OnChargingStatusChanged(std::function callback); + +private: + gpio_num_t charging_pin_; + adc_battery_estimation_handle_t adc_battery_estimation_handle_ = nullptr; + esp_timer_handle_t timer_handle_ = nullptr; + bool is_charging_ = false; + std::function on_charging_status_changed_; + + void CheckBatteryStatus(); +}; + +#endif // ADC_BATTERY_MONITOR_H diff --git a/main/boards/common/afsk_demod.cc b/main/boards/common/afsk_demod.cc index 9e040de..01f1d31 100644 --- a/main/boards/common/afsk_demod.cc +++ b/main/boards/common/afsk_demod.cc @@ -1,371 +1,371 @@ -#include "afsk_demod.h" -#include -#include -#include "esp_log.h" -#include "display.h" - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -namespace audio_wifi_config -{ - static const char *kLogTag = "AUDIO_WIFI_CONFIG"; - - void ReceiveWifiCredentialsFromAudio(Application *app, - WifiConfigurationAp *wifi_ap, - Display *display, - size_t input_channels - ) - { - const int kInputSampleRate = 16000; // Input sampling rate - const float kDownsampleStep = static_cast(kInputSampleRate) / static_cast(kAudioSampleRate); // Downsampling step - std::vector audio_data; - AudioSignalProcessor signal_processor(kAudioSampleRate, kMarkFrequency, kSpaceFrequency, kBitRate, kWindowSize); - AudioDataBuffer data_buffer; - - while (true) - { - // 检查Application状态,只有在WiFi配置模式下才处理音频 - if (app->GetDeviceState() != kDeviceStateWifiConfiguring) { - // 不在WiFi配置状态,休眠100ms后再检查 - vTaskDelay(pdMS_TO_TICKS(100)); - continue; - } - - if (!app->GetAudioService().ReadAudioData(audio_data, 16000, 480)) { // 16kHz, 480 samples corresponds to 30ms data - // 读取音频失败,短暂延迟后重试 - ESP_LOGI(kLogTag, "Failed to read audio data, retrying."); - vTaskDelay(pdMS_TO_TICKS(10)); - continue; - } - - if (input_channels == 2) { // 如果是双声道输入,转换为单声道 - auto mono_data = std::vector(audio_data.size() / 2); - for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) { - mono_data[i] = audio_data[j]; - } - audio_data = std::move(mono_data); - } - - // Downsample the audio data - std::vector downsampled_data; - size_t last_index = 0; - - if (kDownsampleStep > 1.0f) { - downsampled_data.reserve(audio_data.size() / static_cast(kDownsampleStep)); - for (size_t i = 0; i < audio_data.size(); ++i) { - size_t sample_index = static_cast(i / kDownsampleStep); - if ((sample_index + 1) > last_index) { - downsampled_data.push_back(static_cast(audio_data[i])); - last_index = sample_index + 1; - } - } - } else { - downsampled_data.reserve(audio_data.size()); - for (int16_t sample : audio_data) { - downsampled_data.push_back(static_cast(sample)); - } - } - - // Process audio samples to get probability data - auto probabilities = signal_processor.ProcessAudioSamples(downsampled_data); - - // Feed probability data to the data buffer - if (data_buffer.ProcessProbabilityData(probabilities, 0.5f)) { - // If complete data was received, extract WiFi credentials - if (data_buffer.decoded_text.has_value()) { - ESP_LOGI(kLogTag, "Received text data: %s", data_buffer.decoded_text->c_str()); - display->SetChatMessage("system", data_buffer.decoded_text->c_str()); - - // Split SSID and password by newline character - std::string wifi_ssid, wifi_password; - size_t newline_position = data_buffer.decoded_text->find('\n'); - if (newline_position != std::string::npos) { - wifi_ssid = data_buffer.decoded_text->substr(0, newline_position); - wifi_password = data_buffer.decoded_text->substr(newline_position + 1); - ESP_LOGI(kLogTag, "WiFi SSID: %s, Password: %s", wifi_ssid.c_str(), wifi_password.c_str()); - } else { - ESP_LOGE(kLogTag, "Invalid data format, no newline character found"); - continue; - } - - if (wifi_ap->ConnectToWifi(wifi_ssid, wifi_password)) { - wifi_ap->Save(wifi_ssid, wifi_password); // Save WiFi credentials - esp_restart(); // Restart device to apply new WiFi configuration - } else { - ESP_LOGE(kLogTag, "Failed to connect to WiFi with received credentials"); - } - data_buffer.decoded_text.reset(); // Clear processed data - } - } - vTaskDelay(pdMS_TO_TICKS(1)); // 1ms delay - } - } - - // Default start and end transmission identifiers - // \x01\x02 = 00000001 00000010 - const std::vector kDefaultStartTransmissionPattern = { - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0}; - - // \x03\x04 = 00000011 00000100 - const std::vector kDefaultEndTransmissionPattern = { - 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0}; - - // FrequencyDetector implementation - FrequencyDetector::FrequencyDetector(float frequency, size_t window_size) - : frequency_(frequency), window_size_(window_size) { - frequency_bin_ = std::floor(frequency_ * static_cast(window_size_)); - angular_frequency_ = 2.0f * M_PI * frequency_; - cos_coefficient_ = std::cos(angular_frequency_); - sin_coefficient_ = std::sin(angular_frequency_); - filter_coefficient_ = 2.0f * cos_coefficient_; - - // Initialize state buffer - state_buffer_.push_back(0.0f); - state_buffer_.push_back(0.0f); - } - - void FrequencyDetector::Reset() { - state_buffer_.clear(); - state_buffer_.push_back(0.0f); - state_buffer_.push_back(0.0f); - } - - void FrequencyDetector::ProcessSample(float sample) { - if (state_buffer_.size() < 2) { - return; - } - - float s_minus_2 = state_buffer_.front(); // S[-2] - state_buffer_.pop_front(); - float s_minus_1 = state_buffer_.front(); // S[-1] - state_buffer_.pop_front(); - - float s_current = sample + filter_coefficient_ * s_minus_1 - s_minus_2; - - state_buffer_.push_back(s_minus_1); // Put S[-1] back - state_buffer_.push_back(s_current); // Add new S[0] - } - - float FrequencyDetector::GetAmplitude() const { - if (state_buffer_.size() < 2) { - return 0.0f; - } - - float s_minus_1 = state_buffer_[1]; // S[-1] - float s_minus_2 = state_buffer_[0]; // S[-2] - float real_part = cos_coefficient_ * s_minus_1 - s_minus_2; // Real part - float imaginary_part = sin_coefficient_ * s_minus_1; // Imaginary part - - return std::sqrt(real_part * real_part + imaginary_part * imaginary_part) / - (static_cast(window_size_) / 2.0f); - } - - // AudioSignalProcessor implementation - AudioSignalProcessor::AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency, - size_t bit_rate, size_t window_size) - : input_buffer_size_(window_size), output_sample_count_(0) { - if (sample_rate % bit_rate != 0) { - // On ESP32 we can continue execution, but log the error - ESP_LOGW(kLogTag, "Sample rate %zu is not divisible by bit rate %zu", sample_rate, bit_rate); - } - - float normalized_mark_freq = static_cast(mark_frequency) / static_cast(sample_rate); - float normalized_space_freq = static_cast(space_frequency) / static_cast(sample_rate); - - mark_detector_ = std::make_unique(normalized_mark_freq, window_size); - space_detector_ = std::make_unique(normalized_space_freq, window_size); - - samples_per_bit_ = sample_rate / bit_rate; // Number of samples per bit - } - - std::vector AudioSignalProcessor::ProcessAudioSamples(const std::vector &samples) { - std::vector result; - - for (float sample : samples) { - if (input_buffer_.size() < input_buffer_size_) { - input_buffer_.push_back(sample); // Just add, don't process yet - } else { - // Input buffer is full, process the data - input_buffer_.pop_front(); // Remove oldest sample - input_buffer_.push_back(sample); // Add new sample - output_sample_count_++; - - if (output_sample_count_ >= samples_per_bit_) { - // Process all samples in the window using Goertzel algorithm - for (float window_sample : input_buffer_) { - mark_detector_->ProcessSample(window_sample); - space_detector_->ProcessSample(window_sample); - } - - float mark_amplitude = mark_detector_->GetAmplitude(); // Mark amplitude - float space_amplitude = space_detector_->GetAmplitude(); // Space amplitude - - // Avoid division by zero - float mark_probability = mark_amplitude / - (space_amplitude + mark_amplitude + std::numeric_limits::epsilon()); - result.push_back(mark_probability); - - // Reset detector windows - mark_detector_->Reset(); - space_detector_->Reset(); - output_sample_count_ = 0; // Reset output counter - } - } - } - - return result; - } - - // AudioDataBuffer implementation - AudioDataBuffer::AudioDataBuffer() - : current_state_(DataReceptionState::kInactive), - start_of_transmission_(kDefaultStartTransmissionPattern), - end_of_transmission_(kDefaultEndTransmissionPattern), - enable_checksum_validation_(true) { - identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size()); - max_bit_buffer_size_ = 776; // Preset bit buffer size, 776 bits = (32 + 1 + 63 + 1) * 8 = 776 - - bit_buffer_.reserve(max_bit_buffer_size_); - } - - AudioDataBuffer::AudioDataBuffer(size_t max_byte_size, const std::vector &start_identifier, - const std::vector &end_identifier, bool enable_checksum) - : current_state_(DataReceptionState::kInactive), - start_of_transmission_(start_identifier), - end_of_transmission_(end_identifier), - enable_checksum_validation_(enable_checksum) { - identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size()); - max_bit_buffer_size_ = max_byte_size * 8; // Bit buffer size in bytes - - bit_buffer_.reserve(max_bit_buffer_size_); - } - - uint8_t AudioDataBuffer::CalculateChecksum(const std::string &text) { - uint8_t checksum = 0; - for (char character : text) { - checksum += static_cast(character); - } - return checksum; - } - - void AudioDataBuffer::ClearBuffers() { - identifier_buffer_.clear(); - bit_buffer_.clear(); - } - - bool AudioDataBuffer::ProcessProbabilityData(const std::vector &probabilities, float threshold) { - for (float probability : probabilities) { - uint8_t bit = (probability > threshold) ? 1 : 0; - - if (identifier_buffer_.size() >= identifier_buffer_size_) { - identifier_buffer_.pop_front(); // Maintain buffer size - } - identifier_buffer_.push_back(bit); - - // Process received bit based on state machine - switch (current_state_) { - case DataReceptionState::kInactive: - if (identifier_buffer_.size() >= start_of_transmission_.size()) { - current_state_ = DataReceptionState::kWaiting; // Enter waiting state - ESP_LOGI(kLogTag, "Entering Waiting state"); - } - break; - - case DataReceptionState::kWaiting: - // Waiting state, possibly waiting for transmission end - if (identifier_buffer_.size() >= start_of_transmission_.size()) { - std::vector identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end()); - if (identifier_snapshot == start_of_transmission_) - { - ClearBuffers(); // Clear buffers - current_state_ = DataReceptionState::kReceiving; // Enter receiving state - ESP_LOGI(kLogTag, "Entering Receiving state"); - } - } - break; - - case DataReceptionState::kReceiving: - bit_buffer_.push_back(bit); - if (identifier_buffer_.size() >= end_of_transmission_.size()) { - std::vector identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end()); - if (identifier_snapshot == end_of_transmission_) { - current_state_ = DataReceptionState::kInactive; // Enter inactive state - - // Convert bits to bytes - std::vector bytes = ConvertBitsToBytes(bit_buffer_); - - uint8_t received_checksum = 0; - size_t minimum_length = 0; - - if (enable_checksum_validation_) { - // If checksum is required, last byte is checksum - minimum_length = 1 + start_of_transmission_.size() / 8; - if (bytes.size() >= minimum_length) - { - received_checksum = bytes[bytes.size() - start_of_transmission_.size() / 8 - 1]; - } - } else { - minimum_length = start_of_transmission_.size() / 8; - } - - if (bytes.size() < minimum_length) { - ClearBuffers(); - ESP_LOGW(kLogTag, "Data too short, clearing buffer"); - return false; // Data too short, return failure - } - - // Extract text data (remove trailing identifier part) - std::vector text_bytes( - bytes.begin(), bytes.begin() + bytes.size() - minimum_length); - - std::string result(text_bytes.begin(), text_bytes.end()); - - // Validate checksum if required - if (enable_checksum_validation_) { - uint8_t calculated_checksum = CalculateChecksum(result); - if (calculated_checksum != received_checksum) { - // Checksum mismatch - ESP_LOGW(kLogTag, "Checksum mismatch: expected %d, got %d", - received_checksum, calculated_checksum); - ClearBuffers(); - return false; - } - } - - ClearBuffers(); - decoded_text = result; - return true; // Return success - } else if (bit_buffer_.size() >= max_bit_buffer_size_) { - // If not end identifier and bit buffer is full, reset - ClearBuffers(); - ESP_LOGW(kLogTag, "Buffer overflow, clearing buffer"); - current_state_ = DataReceptionState::kInactive; // Reset state machine - } - } - break; - } - } - - return false; - } - - std::vector AudioDataBuffer::ConvertBitsToBytes(const std::vector &bits) const { - std::vector bytes; - - // Ensure number of bits is a multiple of 8 - size_t complete_bytes_count = bits.size() / 8; - bytes.reserve(complete_bytes_count); - - for (size_t i = 0; i < complete_bytes_count; ++i) { - uint8_t byte_value = 0; - for (size_t j = 0; j < 8; ++j) { - byte_value |= bits[i * 8 + j] << (7 - j); - } - bytes.push_back(byte_value); - } - - return bytes; - } -} +#include "afsk_demod.h" +#include +#include +#include "esp_log.h" +#include "display.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace audio_wifi_config +{ + static const char *kLogTag = "AUDIO_WIFI_CONFIG"; + + void ReceiveWifiCredentialsFromAudio(Application *app, + WifiConfigurationAp *wifi_ap, + Display *display, + size_t input_channels + ) + { + const int kInputSampleRate = 16000; // Input sampling rate + const float kDownsampleStep = static_cast(kInputSampleRate) / static_cast(kAudioSampleRate); // Downsampling step + std::vector audio_data; + AudioSignalProcessor signal_processor(kAudioSampleRate, kMarkFrequency, kSpaceFrequency, kBitRate, kWindowSize); + AudioDataBuffer data_buffer; + + while (true) + { + // 检查Application状态,只有在WiFi配置模式下才处理音频 + if (app->GetDeviceState() != kDeviceStateWifiConfiguring) { + // 不在WiFi配置状态,休眠100ms后再检查 + vTaskDelay(pdMS_TO_TICKS(100)); + continue; + } + + if (!app->GetAudioService().ReadAudioData(audio_data, 16000, 480)) { // 16kHz, 480 samples corresponds to 30ms data + // 读取音频失败,短暂延迟后重试 + ESP_LOGI(kLogTag, "Failed to read audio data, retrying."); + vTaskDelay(pdMS_TO_TICKS(10)); + continue; + } + + if (input_channels == 2) { // 如果是双声道输入,转换为单声道 + auto mono_data = std::vector(audio_data.size() / 2); + for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) { + mono_data[i] = audio_data[j]; + } + audio_data = std::move(mono_data); + } + + // Downsample the audio data + std::vector downsampled_data; + size_t last_index = 0; + + if (kDownsampleStep > 1.0f) { + downsampled_data.reserve(audio_data.size() / static_cast(kDownsampleStep)); + for (size_t i = 0; i < audio_data.size(); ++i) { + size_t sample_index = static_cast(i / kDownsampleStep); + if ((sample_index + 1) > last_index) { + downsampled_data.push_back(static_cast(audio_data[i])); + last_index = sample_index + 1; + } + } + } else { + downsampled_data.reserve(audio_data.size()); + for (int16_t sample : audio_data) { + downsampled_data.push_back(static_cast(sample)); + } + } + + // Process audio samples to get probability data + auto probabilities = signal_processor.ProcessAudioSamples(downsampled_data); + + // Feed probability data to the data buffer + if (data_buffer.ProcessProbabilityData(probabilities, 0.5f)) { + // If complete data was received, extract WiFi credentials + if (data_buffer.decoded_text.has_value()) { + ESP_LOGI(kLogTag, "Received text data: %s", data_buffer.decoded_text->c_str()); + display->SetChatMessage("system", data_buffer.decoded_text->c_str()); + + // Split SSID and password by newline character + std::string wifi_ssid, wifi_password; + size_t newline_position = data_buffer.decoded_text->find('\n'); + if (newline_position != std::string::npos) { + wifi_ssid = data_buffer.decoded_text->substr(0, newline_position); + wifi_password = data_buffer.decoded_text->substr(newline_position + 1); + ESP_LOGI(kLogTag, "WiFi SSID: %s, Password: %s", wifi_ssid.c_str(), wifi_password.c_str()); + } else { + ESP_LOGE(kLogTag, "Invalid data format, no newline character found"); + continue; + } + + if (wifi_ap->ConnectToWifi(wifi_ssid, wifi_password)) { + wifi_ap->Save(wifi_ssid, wifi_password); // Save WiFi credentials + esp_restart(); // Restart device to apply new WiFi configuration + } else { + ESP_LOGE(kLogTag, "Failed to connect to WiFi with received credentials"); + } + data_buffer.decoded_text.reset(); // Clear processed data + } + } + vTaskDelay(pdMS_TO_TICKS(1)); // 1ms delay + } + } + + // Default start and end transmission identifiers + // \x01\x02 = 00000001 00000010 + const std::vector kDefaultStartTransmissionPattern = { + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0}; + + // \x03\x04 = 00000011 00000100 + const std::vector kDefaultEndTransmissionPattern = { + 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0}; + + // FrequencyDetector implementation + FrequencyDetector::FrequencyDetector(float frequency, size_t window_size) + : frequency_(frequency), window_size_(window_size) { + frequency_bin_ = std::floor(frequency_ * static_cast(window_size_)); + angular_frequency_ = 2.0f * M_PI * frequency_; + cos_coefficient_ = std::cos(angular_frequency_); + sin_coefficient_ = std::sin(angular_frequency_); + filter_coefficient_ = 2.0f * cos_coefficient_; + + // Initialize state buffer + state_buffer_.push_back(0.0f); + state_buffer_.push_back(0.0f); + } + + void FrequencyDetector::Reset() { + state_buffer_.clear(); + state_buffer_.push_back(0.0f); + state_buffer_.push_back(0.0f); + } + + void FrequencyDetector::ProcessSample(float sample) { + if (state_buffer_.size() < 2) { + return; + } + + float s_minus_2 = state_buffer_.front(); // S[-2] + state_buffer_.pop_front(); + float s_minus_1 = state_buffer_.front(); // S[-1] + state_buffer_.pop_front(); + + float s_current = sample + filter_coefficient_ * s_minus_1 - s_minus_2; + + state_buffer_.push_back(s_minus_1); // Put S[-1] back + state_buffer_.push_back(s_current); // Add new S[0] + } + + float FrequencyDetector::GetAmplitude() const { + if (state_buffer_.size() < 2) { + return 0.0f; + } + + float s_minus_1 = state_buffer_[1]; // S[-1] + float s_minus_2 = state_buffer_[0]; // S[-2] + float real_part = cos_coefficient_ * s_minus_1 - s_minus_2; // Real part + float imaginary_part = sin_coefficient_ * s_minus_1; // Imaginary part + + return std::sqrt(real_part * real_part + imaginary_part * imaginary_part) / + (static_cast(window_size_) / 2.0f); + } + + // AudioSignalProcessor implementation + AudioSignalProcessor::AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency, + size_t bit_rate, size_t window_size) + : input_buffer_size_(window_size), output_sample_count_(0) { + if (sample_rate % bit_rate != 0) { + // On ESP32 we can continue execution, but log the error + ESP_LOGW(kLogTag, "Sample rate %zu is not divisible by bit rate %zu", sample_rate, bit_rate); + } + + float normalized_mark_freq = static_cast(mark_frequency) / static_cast(sample_rate); + float normalized_space_freq = static_cast(space_frequency) / static_cast(sample_rate); + + mark_detector_ = std::make_unique(normalized_mark_freq, window_size); + space_detector_ = std::make_unique(normalized_space_freq, window_size); + + samples_per_bit_ = sample_rate / bit_rate; // Number of samples per bit + } + + std::vector AudioSignalProcessor::ProcessAudioSamples(const std::vector &samples) { + std::vector result; + + for (float sample : samples) { + if (input_buffer_.size() < input_buffer_size_) { + input_buffer_.push_back(sample); // Just add, don't process yet + } else { + // Input buffer is full, process the data + input_buffer_.pop_front(); // Remove oldest sample + input_buffer_.push_back(sample); // Add new sample + output_sample_count_++; + + if (output_sample_count_ >= samples_per_bit_) { + // Process all samples in the window using Goertzel algorithm + for (float window_sample : input_buffer_) { + mark_detector_->ProcessSample(window_sample); + space_detector_->ProcessSample(window_sample); + } + + float mark_amplitude = mark_detector_->GetAmplitude(); // Mark amplitude + float space_amplitude = space_detector_->GetAmplitude(); // Space amplitude + + // Avoid division by zero + float mark_probability = mark_amplitude / + (space_amplitude + mark_amplitude + std::numeric_limits::epsilon()); + result.push_back(mark_probability); + + // Reset detector windows + mark_detector_->Reset(); + space_detector_->Reset(); + output_sample_count_ = 0; // Reset output counter + } + } + } + + return result; + } + + // AudioDataBuffer implementation + AudioDataBuffer::AudioDataBuffer() + : current_state_(DataReceptionState::kInactive), + start_of_transmission_(kDefaultStartTransmissionPattern), + end_of_transmission_(kDefaultEndTransmissionPattern), + enable_checksum_validation_(true) { + identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size()); + max_bit_buffer_size_ = 776; // Preset bit buffer size, 776 bits = (32 + 1 + 63 + 1) * 8 = 776 + + bit_buffer_.reserve(max_bit_buffer_size_); + } + + AudioDataBuffer::AudioDataBuffer(size_t max_byte_size, const std::vector &start_identifier, + const std::vector &end_identifier, bool enable_checksum) + : current_state_(DataReceptionState::kInactive), + start_of_transmission_(start_identifier), + end_of_transmission_(end_identifier), + enable_checksum_validation_(enable_checksum) { + identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size()); + max_bit_buffer_size_ = max_byte_size * 8; // Bit buffer size in bytes + + bit_buffer_.reserve(max_bit_buffer_size_); + } + + uint8_t AudioDataBuffer::CalculateChecksum(const std::string &text) { + uint8_t checksum = 0; + for (char character : text) { + checksum += static_cast(character); + } + return checksum; + } + + void AudioDataBuffer::ClearBuffers() { + identifier_buffer_.clear(); + bit_buffer_.clear(); + } + + bool AudioDataBuffer::ProcessProbabilityData(const std::vector &probabilities, float threshold) { + for (float probability : probabilities) { + uint8_t bit = (probability > threshold) ? 1 : 0; + + if (identifier_buffer_.size() >= identifier_buffer_size_) { + identifier_buffer_.pop_front(); // Maintain buffer size + } + identifier_buffer_.push_back(bit); + + // Process received bit based on state machine + switch (current_state_) { + case DataReceptionState::kInactive: + if (identifier_buffer_.size() >= start_of_transmission_.size()) { + current_state_ = DataReceptionState::kWaiting; // Enter waiting state + ESP_LOGI(kLogTag, "Entering Waiting state"); + } + break; + + case DataReceptionState::kWaiting: + // Waiting state, possibly waiting for transmission end + if (identifier_buffer_.size() >= start_of_transmission_.size()) { + std::vector identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end()); + if (identifier_snapshot == start_of_transmission_) + { + ClearBuffers(); // Clear buffers + current_state_ = DataReceptionState::kReceiving; // Enter receiving state + ESP_LOGI(kLogTag, "Entering Receiving state"); + } + } + break; + + case DataReceptionState::kReceiving: + bit_buffer_.push_back(bit); + if (identifier_buffer_.size() >= end_of_transmission_.size()) { + std::vector identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end()); + if (identifier_snapshot == end_of_transmission_) { + current_state_ = DataReceptionState::kInactive; // Enter inactive state + + // Convert bits to bytes + std::vector bytes = ConvertBitsToBytes(bit_buffer_); + + uint8_t received_checksum = 0; + size_t minimum_length = 0; + + if (enable_checksum_validation_) { + // If checksum is required, last byte is checksum + minimum_length = 1 + start_of_transmission_.size() / 8; + if (bytes.size() >= minimum_length) + { + received_checksum = bytes[bytes.size() - start_of_transmission_.size() / 8 - 1]; + } + } else { + minimum_length = start_of_transmission_.size() / 8; + } + + if (bytes.size() < minimum_length) { + ClearBuffers(); + ESP_LOGW(kLogTag, "Data too short, clearing buffer"); + return false; // Data too short, return failure + } + + // Extract text data (remove trailing identifier part) + std::vector text_bytes( + bytes.begin(), bytes.begin() + bytes.size() - minimum_length); + + std::string result(text_bytes.begin(), text_bytes.end()); + + // Validate checksum if required + if (enable_checksum_validation_) { + uint8_t calculated_checksum = CalculateChecksum(result); + if (calculated_checksum != received_checksum) { + // Checksum mismatch + ESP_LOGW(kLogTag, "Checksum mismatch: expected %d, got %d", + received_checksum, calculated_checksum); + ClearBuffers(); + return false; + } + } + + ClearBuffers(); + decoded_text = result; + return true; // Return success + } else if (bit_buffer_.size() >= max_bit_buffer_size_) { + // If not end identifier and bit buffer is full, reset + ClearBuffers(); + ESP_LOGW(kLogTag, "Buffer overflow, clearing buffer"); + current_state_ = DataReceptionState::kInactive; // Reset state machine + } + } + break; + } + } + + return false; + } + + std::vector AudioDataBuffer::ConvertBitsToBytes(const std::vector &bits) const { + std::vector bytes; + + // Ensure number of bits is a multiple of 8 + size_t complete_bytes_count = bits.size() / 8; + bytes.reserve(complete_bytes_count); + + for (size_t i = 0; i < complete_bytes_count; ++i) { + uint8_t byte_value = 0; + for (size_t j = 0; j < 8; ++j) { + byte_value |= bits[i * 8 + j] << (7 - j); + } + bytes.push_back(byte_value); + } + + return bytes; + } +} diff --git a/main/boards/common/afsk_demod.h b/main/boards/common/afsk_demod.h index 87b3805..8215104 100644 --- a/main/boards/common/afsk_demod.h +++ b/main/boards/common/afsk_demod.h @@ -1,177 +1,177 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include "wifi_configuration_ap.h" -#include "application.h" - -// Audio signal processing constants for WiFi configuration via audio -const size_t kAudioSampleRate = 6400; -const size_t kMarkFrequency = 1800; -const size_t kSpaceFrequency = 1500; -const size_t kBitRate = 100; -const size_t kWindowSize = 64; - -namespace audio_wifi_config -{ - // Main function to receive WiFi credentials through audio signal - void ReceiveWifiCredentialsFromAudio(Application *app, WifiConfigurationAp *wifi_ap, Display *display, - size_t input_channels = 1); - - /** - * Goertzel algorithm implementation for single frequency detection - * Used to detect specific audio frequencies in the AFSK demodulation process - */ - class FrequencyDetector - { - private: - float frequency_; // Target frequency (normalized, i.e., f / fs) - size_t window_size_; // Window size for analysis - float frequency_bin_; // Frequency bin - float angular_frequency_; // Angular frequency - float cos_coefficient_; // cos(w) - float sin_coefficient_; // sin(w) - float filter_coefficient_; // 2 * cos(w) - std::deque state_buffer_; // Circular buffer for storing S[-1] and S[-2] - - public: - /** - * Constructor - * @param frequency Normalized frequency (f / fs) - * @param window_size Window size for analysis - */ - FrequencyDetector(float frequency, size_t window_size); - - /** - * Reset the detector state - */ - void Reset(); - - /** - * Process one audio sample - * @param sample Input audio sample - */ - void ProcessSample(float sample); - - /** - * Calculate current amplitude - * @return Amplitude value - */ - float GetAmplitude() const; - }; - - /** - * Audio signal processor for Mark/Space frequency pair detection - * Processes audio signals to extract digital data using AFSK demodulation - */ - class AudioSignalProcessor - { - private: - std::deque input_buffer_; // Input sample buffer - size_t input_buffer_size_; // Input buffer size = window size - size_t output_sample_count_; // Output sample counter - size_t samples_per_bit_; // Samples per bit threshold - std::unique_ptr mark_detector_; // Mark frequency detector - std::unique_ptr space_detector_; // Space frequency detector - - public: - /** - * Constructor - * @param sample_rate Audio sampling rate - * @param mark_frequency Mark frequency for digital '1' - * @param space_frequency Space frequency for digital '0' - * @param bit_rate Data transmission bit rate - * @param window_size Analysis window size - */ - AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency, - size_t bit_rate, size_t window_size); - - /** - * Process input audio samples - * @param samples Input audio sample vector - * @return Vector of Mark probability values (0.0 to 1.0) - */ - std::vector ProcessAudioSamples(const std::vector &samples); - }; - - /** - * Data reception state machine states - */ - enum class DataReceptionState - { - kInactive, // Waiting for start signal - kWaiting, // Detected potential start, waiting for confirmation - kReceiving // Actively receiving data - }; - - /** - * Data buffer for managing audio-to-digital data conversion - * Handles the complete process from audio signal to decoded text data - */ - class AudioDataBuffer - { - private: - DataReceptionState current_state_; // Current reception state - std::deque identifier_buffer_; // Buffer for start/end identifier detection - size_t identifier_buffer_size_; // Identifier buffer size - std::vector bit_buffer_; // Buffer for storing bit stream - size_t max_bit_buffer_size_; // Maximum bit buffer size - const std::vector start_of_transmission_; // Start-of-transmission identifier - const std::vector end_of_transmission_; // End-of-transmission identifier - bool enable_checksum_validation_; // Whether to validate checksum - - public: - std::optional decoded_text; // Successfully decoded text data - - /** - * Default constructor using predefined start and end identifiers - */ - AudioDataBuffer(); - - /** - * Constructor with custom parameters - * @param max_byte_size Expected maximum data size in bytes - * @param start_identifier Start-of-transmission identifier - * @param end_identifier End-of-transmission identifier - * @param enable_checksum Whether to enable checksum validation - */ - AudioDataBuffer(size_t max_byte_size, const std::vector &start_identifier, - const std::vector &end_identifier, bool enable_checksum = false); - - /** - * Process probability data and attempt to decode - * @param probabilities Vector of Mark probabilities - * @param threshold Decision threshold for bit detection - * @return true if complete data was successfully received and decoded - */ - bool ProcessProbabilityData(const std::vector &probabilities, float threshold = 0.5f); - - /** - * Calculate checksum for ASCII text - * @param text Input text string - * @return Checksum value (0-255) - */ - static uint8_t CalculateChecksum(const std::string &text); - - private: - /** - * Convert bit vector to byte vector - * @param bits Input bit vector - * @return Converted byte vector - */ - std::vector ConvertBitsToBytes(const std::vector &bits) const; - - /** - * Clear all buffers and reset state - */ - void ClearBuffers(); - }; - - // Default start and end transmission identifiers - extern const std::vector kDefaultStartTransmissionPattern; - extern const std::vector kDefaultEndTransmissionPattern; +#pragma once + +#include +#include +#include +#include +#include +#include +#include "wifi_configuration_ap.h" +#include "application.h" + +// Audio signal processing constants for WiFi configuration via audio +const size_t kAudioSampleRate = 6400; +const size_t kMarkFrequency = 1800; +const size_t kSpaceFrequency = 1500; +const size_t kBitRate = 100; +const size_t kWindowSize = 64; + +namespace audio_wifi_config +{ + // Main function to receive WiFi credentials through audio signal + void ReceiveWifiCredentialsFromAudio(Application *app, WifiConfigurationAp *wifi_ap, Display *display, + size_t input_channels = 1); + + /** + * Goertzel algorithm implementation for single frequency detection + * Used to detect specific audio frequencies in the AFSK demodulation process + */ + class FrequencyDetector + { + private: + float frequency_; // Target frequency (normalized, i.e., f / fs) + size_t window_size_; // Window size for analysis + float frequency_bin_; // Frequency bin + float angular_frequency_; // Angular frequency + float cos_coefficient_; // cos(w) + float sin_coefficient_; // sin(w) + float filter_coefficient_; // 2 * cos(w) + std::deque state_buffer_; // Circular buffer for storing S[-1] and S[-2] + + public: + /** + * Constructor + * @param frequency Normalized frequency (f / fs) + * @param window_size Window size for analysis + */ + FrequencyDetector(float frequency, size_t window_size); + + /** + * Reset the detector state + */ + void Reset(); + + /** + * Process one audio sample + * @param sample Input audio sample + */ + void ProcessSample(float sample); + + /** + * Calculate current amplitude + * @return Amplitude value + */ + float GetAmplitude() const; + }; + + /** + * Audio signal processor for Mark/Space frequency pair detection + * Processes audio signals to extract digital data using AFSK demodulation + */ + class AudioSignalProcessor + { + private: + std::deque input_buffer_; // Input sample buffer + size_t input_buffer_size_; // Input buffer size = window size + size_t output_sample_count_; // Output sample counter + size_t samples_per_bit_; // Samples per bit threshold + std::unique_ptr mark_detector_; // Mark frequency detector + std::unique_ptr space_detector_; // Space frequency detector + + public: + /** + * Constructor + * @param sample_rate Audio sampling rate + * @param mark_frequency Mark frequency for digital '1' + * @param space_frequency Space frequency for digital '0' + * @param bit_rate Data transmission bit rate + * @param window_size Analysis window size + */ + AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency, + size_t bit_rate, size_t window_size); + + /** + * Process input audio samples + * @param samples Input audio sample vector + * @return Vector of Mark probability values (0.0 to 1.0) + */ + std::vector ProcessAudioSamples(const std::vector &samples); + }; + + /** + * Data reception state machine states + */ + enum class DataReceptionState + { + kInactive, // Waiting for start signal + kWaiting, // Detected potential start, waiting for confirmation + kReceiving // Actively receiving data + }; + + /** + * Data buffer for managing audio-to-digital data conversion + * Handles the complete process from audio signal to decoded text data + */ + class AudioDataBuffer + { + private: + DataReceptionState current_state_; // Current reception state + std::deque identifier_buffer_; // Buffer for start/end identifier detection + size_t identifier_buffer_size_; // Identifier buffer size + std::vector bit_buffer_; // Buffer for storing bit stream + size_t max_bit_buffer_size_; // Maximum bit buffer size + const std::vector start_of_transmission_; // Start-of-transmission identifier + const std::vector end_of_transmission_; // End-of-transmission identifier + bool enable_checksum_validation_; // Whether to validate checksum + + public: + std::optional decoded_text; // Successfully decoded text data + + /** + * Default constructor using predefined start and end identifiers + */ + AudioDataBuffer(); + + /** + * Constructor with custom parameters + * @param max_byte_size Expected maximum data size in bytes + * @param start_identifier Start-of-transmission identifier + * @param end_identifier End-of-transmission identifier + * @param enable_checksum Whether to enable checksum validation + */ + AudioDataBuffer(size_t max_byte_size, const std::vector &start_identifier, + const std::vector &end_identifier, bool enable_checksum = false); + + /** + * Process probability data and attempt to decode + * @param probabilities Vector of Mark probabilities + * @param threshold Decision threshold for bit detection + * @return true if complete data was successfully received and decoded + */ + bool ProcessProbabilityData(const std::vector &probabilities, float threshold = 0.5f); + + /** + * Calculate checksum for ASCII text + * @param text Input text string + * @return Checksum value (0-255) + */ + static uint8_t CalculateChecksum(const std::string &text); + + private: + /** + * Convert bit vector to byte vector + * @param bits Input bit vector + * @return Converted byte vector + */ + std::vector ConvertBitsToBytes(const std::vector &bits) const; + + /** + * Clear all buffers and reset state + */ + void ClearBuffers(); + }; + + // Default start and end transmission identifiers + extern const std::vector kDefaultStartTransmissionPattern; + extern const std::vector kDefaultEndTransmissionPattern; } \ No newline at end of file diff --git a/main/boards/common/axp2101.cc b/main/boards/common/axp2101.cc index 854f0b8..f57ad64 100644 --- a/main/boards/common/axp2101.cc +++ b/main/boards/common/axp2101.cc @@ -1,41 +1,41 @@ -#include "axp2101.h" -#include "board.h" -#include "display.h" - -#include - -#define TAG "Axp2101" - -Axp2101::Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { -} - -int Axp2101::GetBatteryCurrentDirection() { - return (ReadReg(0x01) & 0b01100000) >> 5; -} - -bool Axp2101::IsCharging() { - return GetBatteryCurrentDirection() == 1; -} - -bool Axp2101::IsDischarging() { - return GetBatteryCurrentDirection() == 2; -} - -bool Axp2101::IsChargingDone() { - uint8_t value = ReadReg(0x01); - return (value & 0b00000111) == 0b00000100; -} - -int Axp2101::GetBatteryLevel() { - return ReadReg(0xA4); -} - -float Axp2101::GetTemperature() { - return ReadReg(0xA5); -} - -void Axp2101::PowerOff() { - uint8_t value = ReadReg(0x10); - value = value | 0x01; - WriteReg(0x10, value); -} +#include "axp2101.h" +#include "board.h" +#include "display.h" + +#include + +#define TAG "Axp2101" + +Axp2101::Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { +} + +int Axp2101::GetBatteryCurrentDirection() { + return (ReadReg(0x01) & 0b01100000) >> 5; +} + +bool Axp2101::IsCharging() { + return GetBatteryCurrentDirection() == 1; +} + +bool Axp2101::IsDischarging() { + return GetBatteryCurrentDirection() == 2; +} + +bool Axp2101::IsChargingDone() { + uint8_t value = ReadReg(0x01); + return (value & 0b00000111) == 0b00000100; +} + +int Axp2101::GetBatteryLevel() { + return ReadReg(0xA4); +} + +float Axp2101::GetTemperature() { + return ReadReg(0xA5); +} + +void Axp2101::PowerOff() { + uint8_t value = ReadReg(0x10); + value = value | 0x01; + WriteReg(0x10, value); +} diff --git a/main/boards/common/axp2101.h b/main/boards/common/axp2101.h index 473cd3e..25df986 100644 --- a/main/boards/common/axp2101.h +++ b/main/boards/common/axp2101.h @@ -1,20 +1,20 @@ -#ifndef __AXP2101_H__ -#define __AXP2101_H__ - -#include "i2c_device.h" - -class Axp2101 : public I2cDevice { -public: - Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr); - bool IsCharging(); - bool IsDischarging(); - bool IsChargingDone(); - int GetBatteryLevel(); - float GetTemperature(); - void PowerOff(); - -private: - int GetBatteryCurrentDirection(); -}; - -#endif +#ifndef __AXP2101_H__ +#define __AXP2101_H__ + +#include "i2c_device.h" + +class Axp2101 : public I2cDevice { +public: + Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr); + bool IsCharging(); + bool IsDischarging(); + bool IsChargingDone(); + int GetBatteryLevel(); + float GetTemperature(); + void PowerOff(); + +private: + int GetBatteryCurrentDirection(); +}; + +#endif diff --git a/main/boards/common/backlight.cc b/main/boards/common/backlight.cc index c62fd7a..5d91bab 100644 --- a/main/boards/common/backlight.cc +++ b/main/boards/common/backlight.cc @@ -1,121 +1,121 @@ -#include "backlight.h" -#include "settings.h" - -#include -#include - -#define TAG "Backlight" - - -Backlight::Backlight() { - // 创建背光渐变定时器 - const esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - auto self = static_cast(arg); - self->OnTransitionTimer(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "backlight_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &transition_timer_)); -} - -Backlight::~Backlight() { - if (transition_timer_ != nullptr) { - esp_timer_stop(transition_timer_); - esp_timer_delete(transition_timer_); - } -} - -void Backlight::RestoreBrightness() { - // Load brightness from settings - Settings settings("display"); - int saved_brightness = settings.GetInt("brightness", 75); - - // 检查亮度值是否为0或过小,设置默认值 - if (saved_brightness <= 0) { - ESP_LOGW(TAG, "Brightness value (%d) is too small, setting to default (10)", saved_brightness); - saved_brightness = 10; // 设置一个较低的默认值 - } - - SetBrightness(saved_brightness); -} - -void Backlight::SetBrightness(uint8_t brightness, bool permanent) { - if (brightness > 100) { - brightness = 100; - } - - if (brightness_ == brightness) { - return; - } - - if (permanent) { - Settings settings("display", true); - settings.SetInt("brightness", brightness); - } - - target_brightness_ = brightness; - step_ = (target_brightness_ > brightness_) ? 1 : -1; - - if (transition_timer_ != nullptr) { - // 启动定时器,每 5ms 更新一次 - esp_timer_start_periodic(transition_timer_, 5 * 1000); - } - ESP_LOGI(TAG, "Set brightness to %d", brightness); -} - -void Backlight::OnTransitionTimer() { - if (brightness_ == target_brightness_) { - esp_timer_stop(transition_timer_); - return; - } - - brightness_ += step_; - SetBrightnessImpl(brightness_); - - if (brightness_ == target_brightness_) { - esp_timer_stop(transition_timer_); - } -} - -PwmBacklight::PwmBacklight(gpio_num_t pin, bool output_invert, uint32_t freq_hz) : Backlight() { - const ledc_timer_config_t backlight_timer = { - .speed_mode = LEDC_LOW_SPEED_MODE, - .duty_resolution = LEDC_TIMER_10_BIT, - .timer_num = LEDC_TIMER_0, - .freq_hz = freq_hz, //背光pwm频率需要高一点,防止电感啸叫 - .clk_cfg = LEDC_AUTO_CLK, - .deconfigure = false - }; - ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer)); - - // Setup LEDC peripheral for PWM backlight control - const ledc_channel_config_t backlight_channel = { - .gpio_num = pin, - .speed_mode = LEDC_LOW_SPEED_MODE, - .channel = LEDC_CHANNEL_0, - .intr_type = LEDC_INTR_DISABLE, - .timer_sel = LEDC_TIMER_0, - .duty = 0, - .hpoint = 0, - .flags = { - .output_invert = output_invert, - } - }; - ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel)); -} - -PwmBacklight::~PwmBacklight() { - ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0); -} - -void PwmBacklight::SetBrightnessImpl(uint8_t brightness) { - // LEDC resolution set to 10bits, thus: 100% = 1023 - uint32_t duty_cycle = (1023 * brightness) / 100; - ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty_cycle); - ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0); -} - +#include "backlight.h" +#include "settings.h" + +#include +#include + +#define TAG "Backlight" + + +Backlight::Backlight() { + // 创建背光渐变定时器 + const esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + auto self = static_cast(arg); + self->OnTransitionTimer(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "backlight_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &transition_timer_)); +} + +Backlight::~Backlight() { + if (transition_timer_ != nullptr) { + esp_timer_stop(transition_timer_); + esp_timer_delete(transition_timer_); + } +} + +void Backlight::RestoreBrightness() { + // Load brightness from settings + Settings settings("display"); + int saved_brightness = settings.GetInt("brightness", 75); + + // 检查亮度值是否为0或过小,设置默认值 + if (saved_brightness <= 0) { + ESP_LOGW(TAG, "Brightness value (%d) is too small, setting to default (10)", saved_brightness); + saved_brightness = 10; // 设置一个较低的默认值 + } + + SetBrightness(saved_brightness); +} + +void Backlight::SetBrightness(uint8_t brightness, bool permanent) { + if (brightness > 100) { + brightness = 100; + } + + if (brightness_ == brightness) { + return; + } + + if (permanent) { + Settings settings("display", true); + settings.SetInt("brightness", brightness); + } + + target_brightness_ = brightness; + step_ = (target_brightness_ > brightness_) ? 1 : -1; + + if (transition_timer_ != nullptr) { + // 启动定时器,每 5ms 更新一次 + esp_timer_start_periodic(transition_timer_, 5 * 1000); + } + ESP_LOGI(TAG, "Set brightness to %d", brightness); +} + +void Backlight::OnTransitionTimer() { + if (brightness_ == target_brightness_) { + esp_timer_stop(transition_timer_); + return; + } + + brightness_ += step_; + SetBrightnessImpl(brightness_); + + if (brightness_ == target_brightness_) { + esp_timer_stop(transition_timer_); + } +} + +PwmBacklight::PwmBacklight(gpio_num_t pin, bool output_invert, uint32_t freq_hz) : Backlight() { + const ledc_timer_config_t backlight_timer = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .duty_resolution = LEDC_TIMER_10_BIT, + .timer_num = LEDC_TIMER_0, + .freq_hz = freq_hz, //背光pwm频率需要高一点,防止电感啸叫 + .clk_cfg = LEDC_AUTO_CLK, + .deconfigure = false + }; + ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer)); + + // Setup LEDC peripheral for PWM backlight control + const ledc_channel_config_t backlight_channel = { + .gpio_num = pin, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = LEDC_CHANNEL_0, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = LEDC_TIMER_0, + .duty = 0, + .hpoint = 0, + .flags = { + .output_invert = output_invert, + } + }; + ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel)); +} + +PwmBacklight::~PwmBacklight() { + ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0); +} + +void PwmBacklight::SetBrightnessImpl(uint8_t brightness) { + // LEDC resolution set to 10bits, thus: 100% = 1023 + uint32_t duty_cycle = (1023 * brightness) / 100; + ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty_cycle); + ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0); +} + diff --git a/main/boards/common/backlight.h b/main/boards/common/backlight.h index 5c09b3d..eb62ec8 100644 --- a/main/boards/common/backlight.h +++ b/main/boards/common/backlight.h @@ -1,36 +1,36 @@ -#pragma once - -#include -#include - -#include -#include - - -class Backlight { -public: - Backlight(); - ~Backlight(); - - void RestoreBrightness(); - void SetBrightness(uint8_t brightness, bool permanent = false); - inline uint8_t brightness() const { return brightness_; } - -protected: - void OnTransitionTimer(); - virtual void SetBrightnessImpl(uint8_t brightness) = 0; - - esp_timer_handle_t transition_timer_ = nullptr; - uint8_t brightness_ = 0; - uint8_t target_brightness_ = 0; - uint8_t step_ = 1; -}; - - -class PwmBacklight : public Backlight { -public: - PwmBacklight(gpio_num_t pin, bool output_invert = false, uint32_t freq_hz = 25000); - ~PwmBacklight(); - - void SetBrightnessImpl(uint8_t brightness) override; -}; +#pragma once + +#include +#include + +#include +#include + + +class Backlight { +public: + Backlight(); + ~Backlight(); + + void RestoreBrightness(); + void SetBrightness(uint8_t brightness, bool permanent = false); + inline uint8_t brightness() const { return brightness_; } + +protected: + void OnTransitionTimer(); + virtual void SetBrightnessImpl(uint8_t brightness) = 0; + + esp_timer_handle_t transition_timer_ = nullptr; + uint8_t brightness_ = 0; + uint8_t target_brightness_ = 0; + uint8_t step_ = 1; +}; + + +class PwmBacklight : public Backlight { +public: + PwmBacklight(gpio_num_t pin, bool output_invert = false, uint32_t freq_hz = 25000); + ~PwmBacklight(); + + void SetBrightnessImpl(uint8_t brightness) override; +}; diff --git a/main/boards/common/board.cc b/main/boards/common/board.cc index 6a0d3a2..fc7144f 100644 --- a/main/boards/common/board.cc +++ b/main/boards/common/board.cc @@ -1,207 +1,192 @@ -#include "board.h" -#include "system_info.h" -#include "settings.h" -#include "display/display.h" -#include "display/oled_display.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include - -#include "esp32_music.h" - -#define TAG "Board" - -Board::Board() { - music_ = nullptr; // 先初始化为空指针 - - Settings settings("board", true); - uuid_ = settings.GetString("uuid"); - if (uuid_.empty()) { - uuid_ = GenerateUuid(); - settings.SetString("uuid", uuid_); - } - ESP_LOGI(TAG, "UUID=%s SKU=%s", uuid_.c_str(), BOARD_NAME); - - // 初始化音乐播放器 - music_ = new Esp32Music(); - ESP_LOGI(TAG, "Music player initialized for all boards"); -} - -Board::~Board() { - if (music_) { - delete music_; - music_ = nullptr; - ESP_LOGI(TAG, "Music player destroyed"); - } -} - -std::string Board::GenerateUuid() { - // UUID v4 需要 16 字节的随机数据 - uint8_t uuid[16]; - - // 使用 ESP32 的硬件随机数生成器 - esp_fill_random(uuid, sizeof(uuid)); - - // 设置版本 (版本 4) 和变体位 - uuid[6] = (uuid[6] & 0x0F) | 0x40; // 版本 4 - uuid[8] = (uuid[8] & 0x3F) | 0x80; // 变体 1 - - // 将字节转换为标准的 UUID 字符串格式 - char uuid_str[37]; - snprintf(uuid_str, sizeof(uuid_str), - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - uuid[0], uuid[1], uuid[2], uuid[3], - uuid[4], uuid[5], uuid[6], uuid[7], - uuid[8], uuid[9], uuid[10], uuid[11], - uuid[12], uuid[13], uuid[14], uuid[15]); - - return std::string(uuid_str); -} - -bool Board::GetBatteryLevel(int &level, bool& charging, bool& discharging) { - return false; -} - -bool Board::GetTemperature(float& esp32temp){ - return false; -} - -Display* Board::GetDisplay() { - static NoDisplay display; - return &display; -} - -Camera* Board::GetCamera() { - return nullptr; -} - -Music* Board::GetMusic() { - return music_; -} - -Led* Board::GetLed() { - static NoLed led; - return &led; -} - -std::string Board::GetSystemInfoJson() { - /* - { - "version": 2, - "flash_size": 4194304, - "psram_size": 0, - "minimum_free_heap_size": 123456, - "mac_address": "00:00:00:00:00:00", - "uuid": "00000000-0000-0000-0000-000000000000", - "chip_model_name": "esp32s3", - "chip_info": { - "model": 1, - "cores": 2, - "revision": 0, - "features": 0 - }, - "application": { - "name": "my-app", - "version": "1.0.0", - "compile_time": "2021-01-01T00:00:00Z" - "idf_version": "4.2-dev" - "elf_sha256": "" - }, - "partition_table": [ - "app": { - "label": "app", - "type": 1, - "subtype": 2, - "address": 0x10000, - "size": 0x100000 - } - ], - "ota": { - "label": "ota_0" - }, - "board": { - ... - } - } - */ - std::string json = R"({"version":2,"language":")" + std::string(Lang::CODE) + R"(",)"; - json += R"("flash_size":)" + std::to_string(SystemInfo::GetFlashSize()) + R"(,)"; - json += R"("minimum_free_heap_size":")" + std::to_string(SystemInfo::GetMinimumFreeHeapSize()) + R"(",)"; - json += R"("mac_address":")" + SystemInfo::GetMacAddress() + R"(",)"; - json += R"("uuid":")" + uuid_ + R"(",)"; - json += R"("chip_model_name":")" + SystemInfo::GetChipModelName() + R"(",)"; - - esp_chip_info_t chip_info; - esp_chip_info(&chip_info); - json += R"("chip_info":{)"; - json += R"("model":)" + std::to_string(chip_info.model) + R"(,)"; - json += R"("cores":)" + std::to_string(chip_info.cores) + R"(,)"; - json += R"("revision":)" + std::to_string(chip_info.revision) + R"(,)"; - json += R"("features":)" + std::to_string(chip_info.features) + R"(},)"; - - auto app_desc = esp_app_get_description(); - json += R"("application":{)"; - json += R"("name":")" + std::string(app_desc->project_name) + R"(",)"; - json += R"("version":")" + std::string(app_desc->version) + R"(",)"; - json += R"("compile_time":")" + std::string(app_desc->date) + R"(T)" + std::string(app_desc->time) + R"(Z",)"; - json += R"("idf_version":")" + std::string(app_desc->idf_ver) + R"(",)"; - char sha256_str[65]; - for (int i = 0; i < 32; i++) { - snprintf(sha256_str + i * 2, sizeof(sha256_str) - i * 2, "%02x", app_desc->app_elf_sha256[i]); - } - json += R"("elf_sha256":")" + std::string(sha256_str) + R"(")"; - json += R"(},)"; - - json += R"("partition_table": [)"; - esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); - while (it) { - const esp_partition_t *partition = esp_partition_get(it); - json += R"({)"; - json += R"("label":")" + std::string(partition->label) + R"(",)"; - json += R"("type":)" + std::to_string(partition->type) + R"(,)"; - json += R"("subtype":)" + std::to_string(partition->subtype) + R"(,)"; - json += R"("address":)" + std::to_string(partition->address) + R"(,)"; - json += R"("size":)" + std::to_string(partition->size) + R"(},)";; - it = esp_partition_next(it); - } - json.pop_back(); // Remove the last comma - json += R"(],)"; - - json += R"("ota":{)"; - auto ota_partition = esp_ota_get_running_partition(); - json += R"("label":")" + std::string(ota_partition->label) + R"(")"; - json += R"(},)"; - - // Append display info - auto display = GetDisplay(); - if (display) { - json += R"("display":{)"; - if (dynamic_cast(display)) { - json += R"("monochrome":)" + std::string("true") + R"(,)"; - } else { - json += R"("monochrome":)" + std::string("false") + R"(,)"; - } - json += R"("width":)" + std::to_string(display->width()) + R"(,)"; - json += R"("height":)" + std::to_string(display->height()) + R"(,)"; - json.pop_back(); // Remove the last comma - } - json += R"(},)"; - - json += R"("board":)" + GetBoardJson(); - - // Close the JSON object - json += R"(})"; - return json; -} - -Assets* Board::GetAssets() { -#ifdef DEFAULT_ASSETS - static Assets assets(DEFAULT_ASSETS); - return &assets; -#else - return nullptr; -#endif -} \ No newline at end of file +#include "board.h" +#include "system_info.h" +#include "settings.h" +#include "display/display.h" +#include "display/oled_display.h" +#include "assets/lang_config.h" +#include "esp32_music.h" +#include +#include +#include +#include + +#define TAG "Board" + +Board::Board() { + music_ = nullptr; // 先初始化为空指针 + Settings settings("board", true); + uuid_ = settings.GetString("uuid"); + if (uuid_.empty()) { + uuid_ = GenerateUuid(); + settings.SetString("uuid", uuid_); + } + ESP_LOGI(TAG, "UUID=%s SKU=%s", uuid_.c_str(), BOARD_NAME); + + // 初始化音乐播放器 + music_ = new Esp32Music(); + ESP_LOGI(TAG, "Music player initialized for all boards"); +} +Board::~Board() { + if (music_) { + delete music_; + music_ = nullptr; + ESP_LOGI(TAG, "Music player destroyed"); + } +} +std::string Board::GenerateUuid() { + // UUID v4 需要 16 字节的随机数据 + uint8_t uuid[16]; + + // 使用 ESP32 的硬件随机数生成器 + esp_fill_random(uuid, sizeof(uuid)); + + // 设置版本 (版本 4) 和变体位 + uuid[6] = (uuid[6] & 0x0F) | 0x40; // 版本 4 + uuid[8] = (uuid[8] & 0x3F) | 0x80; // 变体 1 + + // 将字节转换为标准的 UUID 字符串格式 + char uuid_str[37]; + snprintf(uuid_str, sizeof(uuid_str), + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]); + + return std::string(uuid_str); +} + +bool Board::GetBatteryLevel(int &level, bool& charging, bool& discharging) { + return false; +} + +bool Board::GetTemperature(float& esp32temp){ + return false; +} + +Display* Board::GetDisplay() { + static NoDisplay display; + return &display; +} + +Camera* Board::GetCamera() { + return nullptr; +} + +Led* Board::GetLed() { + static NoLed led; + return &led; +} +Music* Board::GetMusic() { + return music_; +} + +std::string Board::GetSystemInfoJson() { + /* + { + "version": 2, + "flash_size": 4194304, + "psram_size": 0, + "minimum_free_heap_size": 123456, + "mac_address": "00:00:00:00:00:00", + "uuid": "00000000-0000-0000-0000-000000000000", + "chip_model_name": "esp32s3", + "chip_info": { + "model": 1, + "cores": 2, + "revision": 0, + "features": 0 + }, + "application": { + "name": "my-app", + "version": "1.0.0", + "compile_time": "2021-01-01T00:00:00Z" + "idf_version": "4.2-dev" + "elf_sha256": "" + }, + "partition_table": [ + "app": { + "label": "app", + "type": 1, + "subtype": 2, + "address": 0x10000, + "size": 0x100000 + } + ], + "ota": { + "label": "ota_0" + }, + "board": { + ... + } + } + */ + std::string json = R"({"version":2,"language":")" + std::string(Lang::CODE) + R"(",)"; + json += R"("flash_size":)" + std::to_string(SystemInfo::GetFlashSize()) + R"(,)"; + json += R"("minimum_free_heap_size":")" + std::to_string(SystemInfo::GetMinimumFreeHeapSize()) + R"(",)"; + json += R"("mac_address":")" + SystemInfo::GetMacAddress() + R"(",)"; + json += R"("uuid":")" + uuid_ + R"(",)"; + json += R"("chip_model_name":")" + SystemInfo::GetChipModelName() + R"(",)"; + + esp_chip_info_t chip_info; + esp_chip_info(&chip_info); + json += R"("chip_info":{)"; + json += R"("model":)" + std::to_string(chip_info.model) + R"(,)"; + json += R"("cores":)" + std::to_string(chip_info.cores) + R"(,)"; + json += R"("revision":)" + std::to_string(chip_info.revision) + R"(,)"; + json += R"("features":)" + std::to_string(chip_info.features) + R"(},)"; + + auto app_desc = esp_app_get_description(); + json += R"("application":{)"; + json += R"("name":")" + std::string(app_desc->project_name) + R"(",)"; + json += R"("version":")" + std::string(app_desc->version) + R"(",)"; + json += R"("compile_time":")" + std::string(app_desc->date) + R"(T)" + std::string(app_desc->time) + R"(Z",)"; + json += R"("idf_version":")" + std::string(app_desc->idf_ver) + R"(",)"; + char sha256_str[65]; + for (int i = 0; i < 32; i++) { + snprintf(sha256_str + i * 2, sizeof(sha256_str) - i * 2, "%02x", app_desc->app_elf_sha256[i]); + } + json += R"("elf_sha256":")" + std::string(sha256_str) + R"(")"; + json += R"(},)"; + + json += R"("partition_table": [)"; + esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); + while (it) { + const esp_partition_t *partition = esp_partition_get(it); + json += R"({)"; + json += R"("label":")" + std::string(partition->label) + R"(",)"; + json += R"("type":)" + std::to_string(partition->type) + R"(,)"; + json += R"("subtype":)" + std::to_string(partition->subtype) + R"(,)"; + json += R"("address":)" + std::to_string(partition->address) + R"(,)"; + json += R"("size":)" + std::to_string(partition->size) + R"(},)";; + it = esp_partition_next(it); + } + json.pop_back(); // Remove the last comma + json += R"(],)"; + + json += R"("ota":{)"; + auto ota_partition = esp_ota_get_running_partition(); + json += R"("label":")" + std::string(ota_partition->label) + R"(")"; + json += R"(},)"; + + // Append display info + auto display = GetDisplay(); + if (display) { + json += R"("display":{)"; + if (dynamic_cast(display)) { + json += R"("monochrome":)" + std::string("true") + R"(,)"; + } else { + json += R"("monochrome":)" + std::string("false") + R"(,)"; + } + json += R"("width":)" + std::to_string(display->width()) + R"(,)"; + json += R"("height":)" + std::to_string(display->height()) + R"(,)"; + json.pop_back(); // Remove the last comma + } + json += R"(},)"; + + json += R"("board":)" + GetBoardJson(); + + // Close the JSON object + json += R"(})"; + return json; +} diff --git a/main/boards/common/board.h b/main/boards/common/board.h index ddc6d2e..8f5ed25 100644 --- a/main/boards/common/board.h +++ b/main/boards/common/board.h @@ -1,69 +1,66 @@ -#ifndef BOARD_H -#define BOARD_H - -#include -#include -#include -#include -#include -#include - -#include "led/led.h" -#include "backlight.h" -#include "camera.h" -#include "assets.h" - -#include "music.h" - - -void* create_board(); -class AudioCodec; -class Display; -class Board { -private: - Board(const Board&) = delete; // 禁用拷贝构造函数 - Board& operator=(const Board&) = delete; // 禁用赋值操作 - -protected: - Board(); - std::string GenerateUuid(); - - // 软件生成的设备唯一标识 - std::string uuid_; - - // 音乐播放器实例 - Music* music_; - -public: - static Board& GetInstance() { - static Board* instance = static_cast(create_board()); - return *instance; - } - - virtual ~Board(); // 改为非默认析构函数,用于清理 music_ - virtual std::string GetBoardType() = 0; - virtual std::string GetUuid() { return uuid_; } - virtual Backlight* GetBacklight() { return nullptr; } - virtual Led* GetLed(); - virtual AudioCodec* GetAudioCodec() = 0; - virtual bool GetTemperature(float& esp32temp); - virtual Display* GetDisplay(); - virtual Camera* GetCamera(); - virtual Music* GetMusic(); - virtual NetworkInterface* GetNetwork() = 0; - virtual void StartNetwork() = 0; - virtual const char* GetNetworkStateIcon() = 0; - virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging); - virtual std::string GetSystemInfoJson(); - virtual void SetPowerSaveMode(bool enabled) = 0; - virtual std::string GetBoardJson() = 0; - virtual std::string GetDeviceStatusJson() = 0; - virtual Assets* GetAssets(); -}; - -#define DECLARE_BOARD(BOARD_CLASS_NAME) \ -void* create_board() { \ - return new BOARD_CLASS_NAME(); \ -} - -#endif // BOARD_H +#ifndef BOARD_H +#define BOARD_H + +#include +#include +#include +#include +#include +#include + +#include "led/led.h" +#include "backlight.h" +#include "camera.h" +#include "assets.h" +#include "music.h" + +void* create_board(); +class AudioCodec; +class Display; +class Board { +private: + Board(const Board&) = delete; // 禁用拷贝构造函数 + Board& operator=(const Board&) = delete; // 禁用赋值操作 + +protected: + Board(); + std::string GenerateUuid(); + + // 软件生成的设备唯一标识 + std::string uuid_; + + // 音乐播放器实例 + Music* music_; + +public: + static Board& GetInstance() { + static Board* instance = static_cast(create_board()); + return *instance; + } + + virtual ~Board(); // 改为非默认析构函数,用于清理 music_ + virtual std::string GetBoardType() = 0; + virtual std::string GetUuid() { return uuid_; } + virtual Backlight* GetBacklight() { return nullptr; } + virtual Led* GetLed(); + virtual Music* GetMusic(); + virtual AudioCodec* GetAudioCodec() = 0; + virtual bool GetTemperature(float& esp32temp); + virtual Display* GetDisplay(); + virtual Camera* GetCamera(); + virtual NetworkInterface* GetNetwork() = 0; + virtual void StartNetwork() = 0; + virtual const char* GetNetworkStateIcon() = 0; + virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging); + virtual std::string GetSystemInfoJson(); + virtual void SetPowerSaveMode(bool enabled) = 0; + virtual std::string GetBoardJson() = 0; + virtual std::string GetDeviceStatusJson() = 0; +}; + +#define DECLARE_BOARD(BOARD_CLASS_NAME) \ +void* create_board() { \ + return new BOARD_CLASS_NAME(); \ +} + +#endif // BOARD_H diff --git a/main/boards/common/button.cc b/main/boards/common/button.cc index 9570a5f..ce8be9f 100644 --- a/main/boards/common/button.cc +++ b/main/boards/common/button.cc @@ -1,125 +1,125 @@ -#include "button.h" - -#include -#include - -#define TAG "Button" - -#if CONFIG_SOC_ADC_SUPPORTED -AdcButton::AdcButton(const button_adc_config_t& adc_config) : Button(nullptr) { - button_config_t btn_config = { - .long_press_time = 2000, - .short_press_time = 0, - }; - ESP_ERROR_CHECK(iot_button_new_adc_device(&btn_config, &adc_config, &button_handle_)); -} -#endif - -Button::Button(button_handle_t button_handle) : button_handle_(button_handle) { -} - -Button::Button(gpio_num_t gpio_num, bool active_high, uint16_t long_press_time, uint16_t short_press_time, bool enable_power_save) : gpio_num_(gpio_num) { - if (gpio_num == GPIO_NUM_NC) { - return; - } - button_config_t button_config = { - .long_press_time = long_press_time, - .short_press_time = short_press_time - }; - button_gpio_config_t gpio_config = { - .gpio_num = gpio_num, - .active_level = static_cast(active_high ? 1 : 0), - .enable_power_save = enable_power_save, - .disable_pull = false - }; - ESP_ERROR_CHECK(iot_button_new_gpio_device(&button_config, &gpio_config, &button_handle_)); -} - -Button::~Button() { - if (button_handle_ != NULL) { - iot_button_delete(button_handle_); - } -} - -void Button::OnPressDown(std::function callback) { - if (button_handle_ == nullptr) { - return; - } - on_press_down_ = callback; - iot_button_register_cb(button_handle_, BUTTON_PRESS_DOWN, nullptr, [](void* handle, void* usr_data) { - Button* button = static_cast(usr_data); - if (button->on_press_down_) { - button->on_press_down_(); - } - }, this); -} - -void Button::OnPressUp(std::function callback) { - if (button_handle_ == nullptr) { - return; - } - on_press_up_ = callback; - iot_button_register_cb(button_handle_, BUTTON_PRESS_UP, nullptr, [](void* handle, void* usr_data) { - Button* button = static_cast(usr_data); - if (button->on_press_up_) { - button->on_press_up_(); - } - }, this); -} - -void Button::OnLongPress(std::function callback) { - if (button_handle_ == nullptr) { - return; - } - on_long_press_ = callback; - iot_button_register_cb(button_handle_, BUTTON_LONG_PRESS_START, nullptr, [](void* handle, void* usr_data) { - Button* button = static_cast(usr_data); - if (button->on_long_press_) { - button->on_long_press_(); - } - }, this); -} - -void Button::OnClick(std::function callback) { - if (button_handle_ == nullptr) { - return; - } - on_click_ = callback; - iot_button_register_cb(button_handle_, BUTTON_SINGLE_CLICK, nullptr, [](void* handle, void* usr_data) { - Button* button = static_cast(usr_data); - if (button->on_click_) { - button->on_click_(); - } - }, this); -} - -void Button::OnDoubleClick(std::function callback) { - if (button_handle_ == nullptr) { - return; - } - on_double_click_ = callback; - iot_button_register_cb(button_handle_, BUTTON_DOUBLE_CLICK, nullptr, [](void* handle, void* usr_data) { - Button* button = static_cast(usr_data); - if (button->on_double_click_) { - button->on_double_click_(); - } - }, this); -} - -void Button::OnMultipleClick(std::function callback, uint8_t click_count) { - if (button_handle_ == nullptr) { - return; - } - on_multiple_click_ = callback; - button_event_args_t event_args = { - .multiple_clicks = { - .clicks = click_count - } - }; - iot_button_register_cb(button_handle_, BUTTON_MULTIPLE_CLICK, &event_args, [](void* handle, void* usr_data) { - Button* button = static_cast(usr_data); - if (button->on_multiple_click_) { - button->on_multiple_click_(); - } - }, this); +#include "button.h" + +#include +#include + +#define TAG "Button" + +#if CONFIG_SOC_ADC_SUPPORTED +AdcButton::AdcButton(const button_adc_config_t& adc_config) : Button(nullptr) { + button_config_t btn_config = { + .long_press_time = 2000, + .short_press_time = 0, + }; + ESP_ERROR_CHECK(iot_button_new_adc_device(&btn_config, &adc_config, &button_handle_)); +} +#endif + +Button::Button(button_handle_t button_handle) : button_handle_(button_handle) { +} + +Button::Button(gpio_num_t gpio_num, bool active_high, uint16_t long_press_time, uint16_t short_press_time, bool enable_power_save) : gpio_num_(gpio_num) { + if (gpio_num == GPIO_NUM_NC) { + return; + } + button_config_t button_config = { + .long_press_time = long_press_time, + .short_press_time = short_press_time + }; + button_gpio_config_t gpio_config = { + .gpio_num = gpio_num, + .active_level = static_cast(active_high ? 1 : 0), + .enable_power_save = enable_power_save, + .disable_pull = false + }; + ESP_ERROR_CHECK(iot_button_new_gpio_device(&button_config, &gpio_config, &button_handle_)); +} + +Button::~Button() { + if (button_handle_ != NULL) { + iot_button_delete(button_handle_); + } +} + +void Button::OnPressDown(std::function callback) { + if (button_handle_ == nullptr) { + return; + } + on_press_down_ = callback; + iot_button_register_cb(button_handle_, BUTTON_PRESS_DOWN, nullptr, [](void* handle, void* usr_data) { + Button* button = static_cast(usr_data); + if (button->on_press_down_) { + button->on_press_down_(); + } + }, this); +} + +void Button::OnPressUp(std::function callback) { + if (button_handle_ == nullptr) { + return; + } + on_press_up_ = callback; + iot_button_register_cb(button_handle_, BUTTON_PRESS_UP, nullptr, [](void* handle, void* usr_data) { + Button* button = static_cast(usr_data); + if (button->on_press_up_) { + button->on_press_up_(); + } + }, this); +} + +void Button::OnLongPress(std::function callback) { + if (button_handle_ == nullptr) { + return; + } + on_long_press_ = callback; + iot_button_register_cb(button_handle_, BUTTON_LONG_PRESS_START, nullptr, [](void* handle, void* usr_data) { + Button* button = static_cast(usr_data); + if (button->on_long_press_) { + button->on_long_press_(); + } + }, this); +} + +void Button::OnClick(std::function callback) { + if (button_handle_ == nullptr) { + return; + } + on_click_ = callback; + iot_button_register_cb(button_handle_, BUTTON_SINGLE_CLICK, nullptr, [](void* handle, void* usr_data) { + Button* button = static_cast(usr_data); + if (button->on_click_) { + button->on_click_(); + } + }, this); +} + +void Button::OnDoubleClick(std::function callback) { + if (button_handle_ == nullptr) { + return; + } + on_double_click_ = callback; + iot_button_register_cb(button_handle_, BUTTON_DOUBLE_CLICK, nullptr, [](void* handle, void* usr_data) { + Button* button = static_cast(usr_data); + if (button->on_double_click_) { + button->on_double_click_(); + } + }, this); +} + +void Button::OnMultipleClick(std::function callback, uint8_t click_count) { + if (button_handle_ == nullptr) { + return; + } + on_multiple_click_ = callback; + button_event_args_t event_args = { + .multiple_clicks = { + .clicks = click_count + } + }; + iot_button_register_cb(button_handle_, BUTTON_MULTIPLE_CLICK, &event_args, [](void* handle, void* usr_data) { + Button* button = static_cast(usr_data); + if (button->on_multiple_click_) { + button->on_multiple_click_(); + } + }, this); } \ No newline at end of file diff --git a/main/boards/common/button.h b/main/boards/common/button.h index ceecbe5..35a8762 100644 --- a/main/boards/common/button.h +++ b/main/boards/common/button.h @@ -1,49 +1,49 @@ -#ifndef BUTTON_H_ -#define BUTTON_H_ - -#include -#include -#include -#include -#include -#include - -class Button { -public: - Button(button_handle_t button_handle); - Button(gpio_num_t gpio_num, bool active_high = false, uint16_t long_press_time = 0, uint16_t short_press_time = 0, bool enable_power_save = false); - ~Button(); - - void OnPressDown(std::function callback); - void OnPressUp(std::function callback); - void OnLongPress(std::function callback); - void OnClick(std::function callback); - void OnDoubleClick(std::function callback); - void OnMultipleClick(std::function callback, uint8_t click_count = 3); - -protected: - gpio_num_t gpio_num_; - button_handle_t button_handle_ = nullptr; - - std::function on_press_down_; - std::function on_press_up_; - std::function on_long_press_; - std::function on_click_; - std::function on_double_click_; - std::function on_multiple_click_; -}; - -#if CONFIG_SOC_ADC_SUPPORTED -class AdcButton : public Button { -public: - AdcButton(const button_adc_config_t& adc_config); -}; -#endif - -class PowerSaveButton : public Button { -public: - PowerSaveButton(gpio_num_t gpio_num) : Button(gpio_num, false, 0, 0, true) { - } -}; - -#endif // BUTTON_H_ +#ifndef BUTTON_H_ +#define BUTTON_H_ + +#include +#include +#include +#include +#include +#include + +class Button { +public: + Button(button_handle_t button_handle); + Button(gpio_num_t gpio_num, bool active_high = false, uint16_t long_press_time = 0, uint16_t short_press_time = 0, bool enable_power_save = false); + ~Button(); + + void OnPressDown(std::function callback); + void OnPressUp(std::function callback); + void OnLongPress(std::function callback); + void OnClick(std::function callback); + void OnDoubleClick(std::function callback); + void OnMultipleClick(std::function callback, uint8_t click_count = 3); + +protected: + gpio_num_t gpio_num_; + button_handle_t button_handle_ = nullptr; + + std::function on_press_down_; + std::function on_press_up_; + std::function on_long_press_; + std::function on_click_; + std::function on_double_click_; + std::function on_multiple_click_; +}; + +#if CONFIG_SOC_ADC_SUPPORTED +class AdcButton : public Button { +public: + AdcButton(const button_adc_config_t& adc_config); +}; +#endif + +class PowerSaveButton : public Button { +public: + PowerSaveButton(gpio_num_t gpio_num) : Button(gpio_num, false, 0, 0, true) { + } +}; + +#endif // BUTTON_H_ diff --git a/main/boards/common/camera.h b/main/boards/common/camera.h index 75c780c..8840bd7 100644 --- a/main/boards/common/camera.h +++ b/main/boards/common/camera.h @@ -1,15 +1,15 @@ -#ifndef CAMERA_H -#define CAMERA_H - -#include - -class Camera { -public: - virtual void SetExplainUrl(const std::string& url, const std::string& token) = 0; - virtual bool Capture() = 0; - virtual bool SetHMirror(bool enabled) = 0; - virtual bool SetVFlip(bool enabled) = 0; - virtual std::string Explain(const std::string& question) = 0; -}; - -#endif // CAMERA_H +#ifndef CAMERA_H +#define CAMERA_H + +#include + +class Camera { +public: + virtual void SetExplainUrl(const std::string& url, const std::string& token) = 0; + virtual bool Capture() = 0; + virtual bool SetHMirror(bool enabled) = 0; + virtual bool SetVFlip(bool enabled) = 0; + virtual std::string Explain(const std::string& question) = 0; +}; + +#endif // CAMERA_H diff --git a/main/boards/common/dual_network_board.cc b/main/boards/common/dual_network_board.cc index e6fe964..0d698e4 100644 --- a/main/boards/common/dual_network_board.cc +++ b/main/boards/common/dual_network_board.cc @@ -1,93 +1,93 @@ -#include "dual_network_board.h" -#include "application.h" -#include "display.h" -#include "assets/lang_config.h" -#include "settings.h" -#include - -static const char *TAG = "DualNetworkBoard"; - -DualNetworkBoard::DualNetworkBoard(gpio_num_t ml307_tx_pin, gpio_num_t ml307_rx_pin, gpio_num_t ml307_dtr_pin, int32_t default_net_type) - : Board(), - ml307_tx_pin_(ml307_tx_pin), - ml307_rx_pin_(ml307_rx_pin), - ml307_dtr_pin_(ml307_dtr_pin) { - - // 从Settings加载网络类型 - network_type_ = LoadNetworkTypeFromSettings(default_net_type); - - // 只初始化当前网络类型对应的板卡 - InitializeCurrentBoard(); -} - -NetworkType DualNetworkBoard::LoadNetworkTypeFromSettings(int32_t default_net_type) { - Settings settings("network", true); - int network_type = settings.GetInt("type", default_net_type); // 默认使用ML307 (1) - return network_type == 1 ? NetworkType::ML307 : NetworkType::WIFI; -} - -void DualNetworkBoard::SaveNetworkTypeToSettings(NetworkType type) { - Settings settings("network", true); - int network_type = (type == NetworkType::ML307) ? 1 : 0; - settings.SetInt("type", network_type); -} - -void DualNetworkBoard::InitializeCurrentBoard() { - if (network_type_ == NetworkType::ML307) { - ESP_LOGI(TAG, "Initialize ML307 board"); - current_board_ = std::make_unique(ml307_tx_pin_, ml307_rx_pin_, ml307_dtr_pin_); - } else { - ESP_LOGI(TAG, "Initialize WiFi board"); - current_board_ = std::make_unique(); - } -} - -void DualNetworkBoard::SwitchNetworkType() { - auto display = GetDisplay(); - if (network_type_ == NetworkType::WIFI) { - SaveNetworkTypeToSettings(NetworkType::ML307); - display->ShowNotification(Lang::Strings::SWITCH_TO_4G_NETWORK); - } else { - SaveNetworkTypeToSettings(NetworkType::WIFI); - display->ShowNotification(Lang::Strings::SWITCH_TO_WIFI_NETWORK); - } - vTaskDelay(pdMS_TO_TICKS(1000)); - auto& app = Application::GetInstance(); - app.Reboot(); -} - - -std::string DualNetworkBoard::GetBoardType() { - return current_board_->GetBoardType(); -} - -void DualNetworkBoard::StartNetwork() { - auto display = Board::GetInstance().GetDisplay(); - - if (network_type_ == NetworkType::WIFI) { - display->SetStatus(Lang::Strings::CONNECTING); - } else { - display->SetStatus(Lang::Strings::DETECTING_MODULE); - } - current_board_->StartNetwork(); -} - -NetworkInterface* DualNetworkBoard::GetNetwork() { - return current_board_->GetNetwork(); -} - -const char* DualNetworkBoard::GetNetworkStateIcon() { - return current_board_->GetNetworkStateIcon(); -} - -void DualNetworkBoard::SetPowerSaveMode(bool enabled) { - current_board_->SetPowerSaveMode(enabled); -} - -std::string DualNetworkBoard::GetBoardJson() { - return current_board_->GetBoardJson(); -} - -std::string DualNetworkBoard::GetDeviceStatusJson() { - return current_board_->GetDeviceStatusJson(); -} +#include "dual_network_board.h" +#include "application.h" +#include "display.h" +#include "assets/lang_config.h" +#include "settings.h" +#include + +static const char *TAG = "DualNetworkBoard"; + +DualNetworkBoard::DualNetworkBoard(gpio_num_t ml307_tx_pin, gpio_num_t ml307_rx_pin, gpio_num_t ml307_dtr_pin, int32_t default_net_type) + : Board(), + ml307_tx_pin_(ml307_tx_pin), + ml307_rx_pin_(ml307_rx_pin), + ml307_dtr_pin_(ml307_dtr_pin) { + + // 从Settings加载网络类型 + network_type_ = LoadNetworkTypeFromSettings(default_net_type); + + // 只初始化当前网络类型对应的板卡 + InitializeCurrentBoard(); +} + +NetworkType DualNetworkBoard::LoadNetworkTypeFromSettings(int32_t default_net_type) { + Settings settings("network", true); + int network_type = settings.GetInt("type", default_net_type); // 默认使用ML307 (1) + return network_type == 1 ? NetworkType::ML307 : NetworkType::WIFI; +} + +void DualNetworkBoard::SaveNetworkTypeToSettings(NetworkType type) { + Settings settings("network", true); + int network_type = (type == NetworkType::ML307) ? 1 : 0; + settings.SetInt("type", network_type); +} + +void DualNetworkBoard::InitializeCurrentBoard() { + if (network_type_ == NetworkType::ML307) { + ESP_LOGI(TAG, "Initialize ML307 board"); + current_board_ = std::make_unique(ml307_tx_pin_, ml307_rx_pin_, ml307_dtr_pin_); + } else { + ESP_LOGI(TAG, "Initialize WiFi board"); + current_board_ = std::make_unique(); + } +} + +void DualNetworkBoard::SwitchNetworkType() { + auto display = GetDisplay(); + if (network_type_ == NetworkType::WIFI) { + SaveNetworkTypeToSettings(NetworkType::ML307); + display->ShowNotification(Lang::Strings::SWITCH_TO_4G_NETWORK); + } else { + SaveNetworkTypeToSettings(NetworkType::WIFI); + display->ShowNotification(Lang::Strings::SWITCH_TO_WIFI_NETWORK); + } + vTaskDelay(pdMS_TO_TICKS(1000)); + auto& app = Application::GetInstance(); + app.Reboot(); +} + + +std::string DualNetworkBoard::GetBoardType() { + return current_board_->GetBoardType(); +} + +void DualNetworkBoard::StartNetwork() { + auto display = Board::GetInstance().GetDisplay(); + + if (network_type_ == NetworkType::WIFI) { + display->SetStatus(Lang::Strings::CONNECTING); + } else { + display->SetStatus(Lang::Strings::DETECTING_MODULE); + } + current_board_->StartNetwork(); +} + +NetworkInterface* DualNetworkBoard::GetNetwork() { + return current_board_->GetNetwork(); +} + +const char* DualNetworkBoard::GetNetworkStateIcon() { + return current_board_->GetNetworkStateIcon(); +} + +void DualNetworkBoard::SetPowerSaveMode(bool enabled) { + current_board_->SetPowerSaveMode(enabled); +} + +std::string DualNetworkBoard::GetBoardJson() { + return current_board_->GetBoardJson(); +} + +std::string DualNetworkBoard::GetDeviceStatusJson() { + return current_board_->GetDeviceStatusJson(); +} diff --git a/main/boards/common/dual_network_board.h b/main/boards/common/dual_network_board.h index efd88a9..620c27e 100644 --- a/main/boards/common/dual_network_board.h +++ b/main/boards/common/dual_network_board.h @@ -1,59 +1,59 @@ -#ifndef DUAL_NETWORK_BOARD_H -#define DUAL_NETWORK_BOARD_H - -#include "board.h" -#include "wifi_board.h" -#include "ml307_board.h" -#include - -//enum NetworkType -enum class NetworkType { - WIFI, - ML307 -}; - -// 双网络板卡类,可以在WiFi和ML307之间切换 -class DualNetworkBoard : public Board { -private: - // 使用基类指针存储当前活动的板卡 - std::unique_ptr current_board_; - NetworkType network_type_ = NetworkType::ML307; // Default to ML307 - - // ML307的引脚配置 - gpio_num_t ml307_tx_pin_; - gpio_num_t ml307_rx_pin_; - gpio_num_t ml307_dtr_pin_; - - // 从Settings加载网络类型 - NetworkType LoadNetworkTypeFromSettings(int32_t default_net_type); - - // 保存网络类型到Settings - void SaveNetworkTypeToSettings(NetworkType type); - - // 初始化当前网络类型对应的板卡 - void InitializeCurrentBoard(); - -public: - DualNetworkBoard(gpio_num_t ml307_tx_pin, gpio_num_t ml307_rx_pin, gpio_num_t ml307_dtr_pin = GPIO_NUM_NC, int32_t default_net_type = 1); - virtual ~DualNetworkBoard() = default; - - // 切换网络类型 - void SwitchNetworkType(); - - // 获取当前网络类型 - NetworkType GetNetworkType() const { return network_type_; } - - // 获取当前活动的板卡引用 - Board& GetCurrentBoard() const { return *current_board_; } - - // 重写Board接口 - virtual std::string GetBoardType() override; - virtual void StartNetwork() override; - virtual NetworkInterface* GetNetwork() override; - virtual const char* GetNetworkStateIcon() override; - virtual void SetPowerSaveMode(bool enabled) override; - virtual std::string GetBoardJson() override; - virtual std::string GetDeviceStatusJson() override; -}; - +#ifndef DUAL_NETWORK_BOARD_H +#define DUAL_NETWORK_BOARD_H + +#include "board.h" +#include "wifi_board.h" +#include "ml307_board.h" +#include + +//enum NetworkType +enum class NetworkType { + WIFI, + ML307 +}; + +// 双网络板卡类,可以在WiFi和ML307之间切换 +class DualNetworkBoard : public Board { +private: + // 使用基类指针存储当前活动的板卡 + std::unique_ptr current_board_; + NetworkType network_type_ = NetworkType::ML307; // Default to ML307 + + // ML307的引脚配置 + gpio_num_t ml307_tx_pin_; + gpio_num_t ml307_rx_pin_; + gpio_num_t ml307_dtr_pin_; + + // 从Settings加载网络类型 + NetworkType LoadNetworkTypeFromSettings(int32_t default_net_type); + + // 保存网络类型到Settings + void SaveNetworkTypeToSettings(NetworkType type); + + // 初始化当前网络类型对应的板卡 + void InitializeCurrentBoard(); + +public: + DualNetworkBoard(gpio_num_t ml307_tx_pin, gpio_num_t ml307_rx_pin, gpio_num_t ml307_dtr_pin = GPIO_NUM_NC, int32_t default_net_type = 1); + virtual ~DualNetworkBoard() = default; + + // 切换网络类型 + void SwitchNetworkType(); + + // 获取当前网络类型 + NetworkType GetNetworkType() const { return network_type_; } + + // 获取当前活动的板卡引用 + Board& GetCurrentBoard() const { return *current_board_; } + + // 重写Board接口 + virtual std::string GetBoardType() override; + virtual void StartNetwork() override; + virtual NetworkInterface* GetNetwork() override; + virtual const char* GetNetworkStateIcon() override; + virtual void SetPowerSaveMode(bool enabled) override; + virtual std::string GetBoardJson() override; + virtual std::string GetDeviceStatusJson() override; +}; + #endif // DUAL_NETWORK_BOARD_H \ No newline at end of file diff --git a/main/boards/common/esp32_camera.cc b/main/boards/common/esp32_camera.cc index 8864325..b04c160 100644 --- a/main/boards/common/esp32_camera.cc +++ b/main/boards/common/esp32_camera.cc @@ -1,259 +1,260 @@ -#include "esp32_camera.h" -#include "mcp_server.h" -#include "display.h" -#include "board.h" -#include "system_info.h" -#include "lvgl_display.h" - -#include -#include -#include -#include - -#define TAG "Esp32Camera" - -Esp32Camera::Esp32Camera(const camera_config_t& config) { - // camera init - esp_err_t err = esp_camera_init(&config); // 配置上面定义的参数 - if (err != ESP_OK) { - ESP_LOGE(TAG, "Camera init failed with error 0x%x", err); - return; - } - - sensor_t *s = esp_camera_sensor_get(); // 获取摄像头型号 - if (s->id.PID == GC0308_PID) { - s->set_hmirror(s, 0); // 这里控制摄像头镜像 写1镜像 写0不镜像 - } -} - -Esp32Camera::~Esp32Camera() { - if (fb_) { - esp_camera_fb_return(fb_); - fb_ = nullptr; - } - esp_camera_deinit(); -} - -void Esp32Camera::SetExplainUrl(const std::string& url, const std::string& token) { - explain_url_ = url; - explain_token_ = token; -} - -bool Esp32Camera::Capture() { - if (encoder_thread_.joinable()) { - encoder_thread_.join(); - } - - auto start_time = esp_timer_get_time(); - int frames_to_get = 2; - // Try to get a stable frame - for (int i = 0; i < frames_to_get; i++) { - if (fb_ != nullptr) { - esp_camera_fb_return(fb_); - } - fb_ = esp_camera_fb_get(); - if (fb_ == nullptr) { - ESP_LOGE(TAG, "Camera capture failed"); - return false; - } - } - auto end_time = esp_timer_get_time(); - ESP_LOGI(TAG, "Camera captured %d frames in %d ms", frames_to_get, int((end_time - start_time) / 1000)); - - // 显示预览图片 - auto display = dynamic_cast(Board::GetInstance().GetDisplay()); - if (display != nullptr) { - auto data = (uint8_t*)heap_caps_malloc(fb_->len, MALLOC_CAP_SPIRAM); - if (data == nullptr) { - ESP_LOGE(TAG, "Failed to allocate memory for preview image"); - return false; - } - - auto src = (uint16_t*)fb_->buf; - auto dst = (uint16_t*)data; - size_t pixel_count = fb_->len / 2; - for (size_t i = 0; i < pixel_count; i++) { - // 交换每个16位字内的字节 - dst[i] = __builtin_bswap16(src[i]); - } - - auto image = std::make_unique(data, fb_->len, fb_->width, fb_->height, fb_->width * 2, LV_COLOR_FORMAT_RGB565); - display->SetPreviewImage(std::move(image)); - } - return true; -} - -bool Esp32Camera::SetHMirror(bool enabled) { - sensor_t *s = esp_camera_sensor_get(); - if (s == nullptr) { - ESP_LOGE(TAG, "Failed to get camera sensor"); - return false; - } - - esp_err_t err = s->set_hmirror(s, enabled); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to set horizontal mirror: %d", err); - return false; - } - - ESP_LOGI(TAG, "Camera horizontal mirror set to: %s", enabled ? "enabled" : "disabled"); - return true; -} - -bool Esp32Camera::SetVFlip(bool enabled) { - sensor_t *s = esp_camera_sensor_get(); - if (s == nullptr) { - ESP_LOGE(TAG, "Failed to get camera sensor"); - return false; - } - - esp_err_t err = s->set_vflip(s, enabled); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to set vertical flip: %d", err); - return false; - } - - ESP_LOGI(TAG, "Camera vertical flip set to: %s", enabled ? "enabled" : "disabled"); - return true; -} - -/** - * @brief 将摄像头捕获的图像发送到远程服务器进行AI分析和解释 - * - * 该函数将当前摄像头缓冲区中的图像编码为JPEG格式,并通过HTTP POST请求 - * 以multipart/form-data的形式发送到指定的解释服务器。服务器将根据提供的 - * 问题对图像进行AI分析并返回结果。 - * - * 实现特点: - * - 使用独立线程编码JPEG,与主线程分离 - * - 采用分块传输编码(chunked transfer encoding)优化内存使用 - * - 通过队列机制实现编码线程和发送线程的数据同步 - * - 支持设备ID、客户端ID和认证令牌的HTTP头部配置 - * - * @param question 要向AI提出的关于图像的问题,将作为表单字段发送 - * @return std::string 服务器返回的JSON格式响应字符串 - * 成功时包含AI分析结果,失败时包含错误信息 - * 格式示例:{"success": true, "result": "分析结果"} - * {"success": false, "message": "错误信息"} - * - * @note 调用此函数前必须先调用SetExplainUrl()设置服务器URL - * @note 函数会等待之前的编码线程完成后再开始新的处理 - * @warning 如果摄像头缓冲区为空或网络连接失败,将返回错误信息 - */ -std::string Esp32Camera::Explain(const std::string& question) { - if (explain_url_.empty()) { - throw std::runtime_error("Image explain URL or token is not set"); - } - - // 创建局部的 JPEG 队列, 40 entries is about to store 512 * 40 = 20480 bytes of JPEG data - QueueHandle_t jpeg_queue = xQueueCreate(40, sizeof(JpegChunk)); - if (jpeg_queue == nullptr) { - ESP_LOGE(TAG, "Failed to create JPEG queue"); - throw std::runtime_error("Failed to create JPEG queue"); - } - - // We spawn a thread to encode the image to JPEG - encoder_thread_ = std::thread([this, jpeg_queue]() { - frame2jpg_cb(fb_, 80, [](void* arg, size_t index, const void* data, size_t len) -> unsigned int { - auto jpeg_queue = (QueueHandle_t)arg; - JpegChunk chunk = { - .data = (uint8_t*)heap_caps_aligned_alloc(16, len, MALLOC_CAP_SPIRAM), - .len = len - }; - memcpy(chunk.data, data, len); - xQueueSend(jpeg_queue, &chunk, portMAX_DELAY); - return len; - }, jpeg_queue); - }); - - auto network = Board::GetInstance().GetNetwork(); - auto http = network->CreateHttp(3); - // 构造multipart/form-data请求体 - std::string boundary = "----ESP32_CAMERA_BOUNDARY"; - - // 配置HTTP客户端,使用分块传输编码 - http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); - http->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str()); - if (!explain_token_.empty()) { - http->SetHeader("Authorization", "Bearer " + explain_token_); - } - http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary); - http->SetHeader("Transfer-Encoding", "chunked"); - if (!http->Open("POST", explain_url_)) { - ESP_LOGE(TAG, "Failed to connect to explain URL"); - // Clear the queue - encoder_thread_.join(); - JpegChunk chunk; - while (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) == pdPASS) { - if (chunk.data != nullptr) { - heap_caps_free(chunk.data); - } else { - break; - } - } - vQueueDelete(jpeg_queue); - throw std::runtime_error("Failed to connect to explain URL"); - } - - { - // 第一块:question字段 - std::string question_field; - question_field += "--" + boundary + "\r\n"; - question_field += "Content-Disposition: form-data; name=\"question\"\r\n"; - question_field += "\r\n"; - question_field += question + "\r\n"; - http->Write(question_field.c_str(), question_field.size()); - } - { - // 第二块:文件字段头部 - std::string file_header; - file_header += "--" + boundary + "\r\n"; - file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n"; - file_header += "Content-Type: image/jpeg\r\n"; - file_header += "\r\n"; - http->Write(file_header.c_str(), file_header.size()); - } - - // 第三块:JPEG数据 - size_t total_sent = 0; - while (true) { - JpegChunk chunk; - if (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) != pdPASS) { - ESP_LOGE(TAG, "Failed to receive JPEG chunk"); - break; - } - if (chunk.data == nullptr) { - break; // The last chunk - } - http->Write((const char*)chunk.data, chunk.len); - total_sent += chunk.len; - heap_caps_free(chunk.data); - } - // Wait for the encoder thread to finish - encoder_thread_.join(); - // 清理队列 - vQueueDelete(jpeg_queue); - - { - // 第四块:multipart尾部 - std::string multipart_footer; - multipart_footer += "\r\n--" + boundary + "--\r\n"; - http->Write(multipart_footer.c_str(), multipart_footer.size()); - } - // 结束块 - http->Write("", 0); - - if (http->GetStatusCode() != 200) { - ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode()); - throw std::runtime_error("Failed to upload photo"); - } - - std::string result = http->ReadAll(); - http->Close(); - - // Get remain task stack size - size_t remain_stack_size = uxTaskGetStackHighWaterMark(nullptr); - ESP_LOGI(TAG, "Explain image size=%dx%d, compressed size=%d, remain stack size=%d, question=%s\n%s", - fb_->width, fb_->height, total_sent, remain_stack_size, question.c_str(), result.c_str()); - return result; -} +#include "esp32_camera.h" +#include "mcp_server.h" +#include "display.h" +#include "board.h" +#include "system_info.h" +#include "lvgl_display.h" +#include "jpg/image_to_jpeg.h" + +#include +#include +#include + +#define TAG "Esp32Camera" + +Esp32Camera::Esp32Camera(const camera_config_t& config) { + // camera init + esp_err_t err = esp_camera_init(&config); // 配置上面定义的参数 + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera init failed with error 0x%x", err); + return; + } + + sensor_t *s = esp_camera_sensor_get(); // 获取摄像头型号 + if (s->id.PID == GC0308_PID) { + s->set_hmirror(s, 0); // 这里控制摄像头镜像 写1镜像 写0不镜像 + } +} + +Esp32Camera::~Esp32Camera() { + if (fb_) { + esp_camera_fb_return(fb_); + fb_ = nullptr; + } + esp_camera_deinit(); +} + +void Esp32Camera::SetExplainUrl(const std::string& url, const std::string& token) { + explain_url_ = url; + explain_token_ = token; +} + +bool Esp32Camera::Capture() { + if (encoder_thread_.joinable()) { + encoder_thread_.join(); + } + + auto start_time = esp_timer_get_time(); + int frames_to_get = 2; + // Try to get a stable frame + for (int i = 0; i < frames_to_get; i++) { + if (fb_ != nullptr) { + esp_camera_fb_return(fb_); + } + fb_ = esp_camera_fb_get(); + if (fb_ == nullptr) { + ESP_LOGE(TAG, "Camera capture failed"); + return false; + } + } + auto end_time = esp_timer_get_time(); + ESP_LOGI(TAG, "Camera captured %d frames in %d ms", frames_to_get, int((end_time - start_time) / 1000)); + + // 显示预览图片 + auto display = dynamic_cast(Board::GetInstance().GetDisplay()); + if (display != nullptr) { + auto data = (uint8_t*)heap_caps_malloc(fb_->len, MALLOC_CAP_SPIRAM); + if (data == nullptr) { + ESP_LOGE(TAG, "Failed to allocate memory for preview image"); + return false; + } + + auto src = (uint16_t*)fb_->buf; + auto dst = (uint16_t*)data; + size_t pixel_count = fb_->len / 2; + for (size_t i = 0; i < pixel_count; i++) { + // 交换每个16位字内的字节 + dst[i] = __builtin_bswap16(src[i]); + } + + auto image = std::make_unique(data, fb_->len, fb_->width, fb_->height, fb_->width * 2, LV_COLOR_FORMAT_RGB565); + display->SetPreviewImage(std::move(image)); + } + return true; +} + +bool Esp32Camera::SetHMirror(bool enabled) { + sensor_t *s = esp_camera_sensor_get(); + if (s == nullptr) { + ESP_LOGE(TAG, "Failed to get camera sensor"); + return false; + } + + esp_err_t err = s->set_hmirror(s, enabled); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set horizontal mirror: %d", err); + return false; + } + + ESP_LOGI(TAG, "Camera horizontal mirror set to: %s", enabled ? "enabled" : "disabled"); + return true; +} + +bool Esp32Camera::SetVFlip(bool enabled) { + sensor_t *s = esp_camera_sensor_get(); + if (s == nullptr) { + ESP_LOGE(TAG, "Failed to get camera sensor"); + return false; + } + + esp_err_t err = s->set_vflip(s, enabled); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set vertical flip: %d", err); + return false; + } + + ESP_LOGI(TAG, "Camera vertical flip set to: %s", enabled ? "enabled" : "disabled"); + return true; +} + +/** + * @brief 将摄像头捕获的图像发送到远程服务器进行AI分析和解释 + * + * 该函数将当前摄像头缓冲区中的图像编码为JPEG格式,并通过HTTP POST请求 + * 以multipart/form-data的形式发送到指定的解释服务器。服务器将根据提供的 + * 问题对图像进行AI分析并返回结果。 + * + * 实现特点: + * - 使用独立线程编码JPEG,与主线程分离 + * - 采用分块传输编码(chunked transfer encoding)优化内存使用 + * - 通过队列机制实现编码线程和发送线程的数据同步 + * - 支持设备ID、客户端ID和认证令牌的HTTP头部配置 + * + * @param question 要向AI提出的关于图像的问题,将作为表单字段发送 + * @return std::string 服务器返回的JSON格式响应字符串 + * 成功时包含AI分析结果,失败时包含错误信息 + * 格式示例:{"success": true, "result": "分析结果"} + * {"success": false, "message": "错误信息"} + * + * @note 调用此函数前必须先调用SetExplainUrl()设置服务器URL + * @note 函数会等待之前的编码线程完成后再开始新的处理 + * @warning 如果摄像头缓冲区为空或网络连接失败,将返回错误信息 + */ +std::string Esp32Camera::Explain(const std::string& question) { + if (explain_url_.empty()) { + throw std::runtime_error("Image explain URL or token is not set"); + } + + // 创建局部的 JPEG 队列, 40 entries is about to store 512 * 40 = 20480 bytes of JPEG data + QueueHandle_t jpeg_queue = xQueueCreate(40, sizeof(JpegChunk)); + if (jpeg_queue == nullptr) { + ESP_LOGE(TAG, "Failed to create JPEG queue"); + throw std::runtime_error("Failed to create JPEG queue"); + } + + // We spawn a thread to encode the image to JPEG using optimized encoder (cost about 500ms and 8KB SRAM) + encoder_thread_ = std::thread([this, jpeg_queue]() { + image_to_jpeg_cb(fb_->buf, fb_->len, fb_->width, fb_->height, fb_->format, 80, + [](void* arg, size_t index, const void* data, size_t len) -> size_t { + auto jpeg_queue = (QueueHandle_t)arg; + JpegChunk chunk = { + .data = (uint8_t*)heap_caps_aligned_alloc(16, len, MALLOC_CAP_SPIRAM), + .len = len + }; + memcpy(chunk.data, data, len); + xQueueSend(jpeg_queue, &chunk, portMAX_DELAY); + return len; + }, jpeg_queue); + }); + + auto network = Board::GetInstance().GetNetwork(); + auto http = network->CreateHttp(3); + // 构造multipart/form-data请求体 + std::string boundary = "----ESP32_CAMERA_BOUNDARY"; + + // 配置HTTP客户端,使用分块传输编码 + http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); + http->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str()); + if (!explain_token_.empty()) { + http->SetHeader("Authorization", "Bearer " + explain_token_); + } + http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary); + http->SetHeader("Transfer-Encoding", "chunked"); + if (!http->Open("POST", explain_url_)) { + ESP_LOGE(TAG, "Failed to connect to explain URL"); + // Clear the queue + encoder_thread_.join(); + JpegChunk chunk; + while (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) == pdPASS) { + if (chunk.data != nullptr) { + heap_caps_free(chunk.data); + } else { + break; + } + } + vQueueDelete(jpeg_queue); + throw std::runtime_error("Failed to connect to explain URL"); + } + + { + // 第一块:question字段 + std::string question_field; + question_field += "--" + boundary + "\r\n"; + question_field += "Content-Disposition: form-data; name=\"question\"\r\n"; + question_field += "\r\n"; + question_field += question + "\r\n"; + http->Write(question_field.c_str(), question_field.size()); + } + { + // 第二块:文件字段头部 + std::string file_header; + file_header += "--" + boundary + "\r\n"; + file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n"; + file_header += "Content-Type: image/jpeg\r\n"; + file_header += "\r\n"; + http->Write(file_header.c_str(), file_header.size()); + } + + // 第三块:JPEG数据 + size_t total_sent = 0; + while (true) { + JpegChunk chunk; + if (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) != pdPASS) { + ESP_LOGE(TAG, "Failed to receive JPEG chunk"); + break; + } + if (chunk.data == nullptr) { + break; // The last chunk + } + http->Write((const char*)chunk.data, chunk.len); + total_sent += chunk.len; + heap_caps_free(chunk.data); + } + // Wait for the encoder thread to finish + encoder_thread_.join(); + // 清理队列 + vQueueDelete(jpeg_queue); + + { + // 第四块:multipart尾部 + std::string multipart_footer; + multipart_footer += "\r\n--" + boundary + "--\r\n"; + http->Write(multipart_footer.c_str(), multipart_footer.size()); + } + // 结束块 + http->Write("", 0); + + if (http->GetStatusCode() != 200) { + ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode()); + throw std::runtime_error("Failed to upload photo"); + } + + std::string result = http->ReadAll(); + http->Close(); + + // Get remain task stack size + size_t remain_stack_size = uxTaskGetStackHighWaterMark(nullptr); + ESP_LOGI(TAG, "Explain image size=%dx%d, compressed size=%d, remain stack size=%d, question=%s\n%s", + fb_->width, fb_->height, total_sent, remain_stack_size, question.c_str(), result.c_str()); + return result; +} diff --git a/main/boards/common/esp32_camera.h b/main/boards/common/esp32_camera.h index 7d5c63f..c2bea6e 100644 --- a/main/boards/common/esp32_camera.h +++ b/main/boards/common/esp32_camera.h @@ -1,38 +1,38 @@ -#ifndef ESP32_CAMERA_H -#define ESP32_CAMERA_H - -#include -#include -#include -#include - -#include -#include - -#include "camera.h" - -struct JpegChunk { - uint8_t* data; - size_t len; -}; - -class Esp32Camera : public Camera { -private: - camera_fb_t* fb_ = nullptr; - std::string explain_url_; - std::string explain_token_; - std::thread encoder_thread_; - -public: - Esp32Camera(const camera_config_t& config); - ~Esp32Camera(); - - virtual void SetExplainUrl(const std::string& url, const std::string& token); - virtual bool Capture(); - // 翻转控制函数 - virtual bool SetHMirror(bool enabled) override; - virtual bool SetVFlip(bool enabled) override; - virtual std::string Explain(const std::string& question); -}; - +#ifndef ESP32_CAMERA_H +#define ESP32_CAMERA_H + +#include +#include +#include +#include + +#include +#include + +#include "camera.h" + +struct JpegChunk { + uint8_t* data; + size_t len; +}; + +class Esp32Camera : public Camera { +private: + camera_fb_t* fb_ = nullptr; + std::string explain_url_; + std::string explain_token_; + std::thread encoder_thread_; + +public: + Esp32Camera(const camera_config_t& config); + ~Esp32Camera(); + + virtual void SetExplainUrl(const std::string& url, const std::string& token); + virtual bool Capture(); + // 翻转控制函数 + virtual bool SetHMirror(bool enabled) override; + virtual bool SetVFlip(bool enabled) override; + virtual std::string Explain(const std::string& question); +}; + #endif // ESP32_CAMERA_H \ No newline at end of file diff --git a/main/boards/common/esp32_music.cc b/main/boards/common/esp32_music.cc index bdfe6ba..3a96a64 100644 --- a/main/boards/common/esp32_music.cc +++ b/main/boards/common/esp32_music.cc @@ -1,1539 +1,1613 @@ -#include "esp32_music.h" -#include "board.h" -#include "system_info.h" -#include "audio/audio_codec.h" -#include "application.h" -#include "protocols/protocol.h" -#include "display/display.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include // 为isdigit函数 -#include // 为线程ID比较 -#include -#include - -#define TAG "Esp32Music" - -// ========== 简单的ESP32认证函数 ========== - -/** - * @brief 获取设备MAC地址 - * @return MAC地址字符串 - */ -static std::string get_device_mac() { - return SystemInfo::GetMacAddress(); -} - -/** - * @brief 获取设备芯片ID - * @return 芯片ID字符串 - */ -static std::string get_device_chip_id() { - // 使用MAC地址作为芯片ID,去除冒号分隔符 - std::string mac = SystemInfo::GetMacAddress(); - // 去除所有冒号 - mac.erase(std::remove(mac.begin(), mac.end(), ':'), mac.end()); - return mac; -} - -/** - * @brief 生成动态密钥 - * @param timestamp 时间戳 - * @return 动态密钥字符串 - */ -static std::string generate_dynamic_key(int64_t timestamp) { - // 密钥(请修改为与服务端一致) - const std::string secret_key = "your-esp32-secret-key-2024"; - - // 获取设备信息 - std::string mac = get_device_mac(); - std::string chip_id = get_device_chip_id(); - - // 组合数据:MAC:芯片ID:时间戳:密钥 - std::string data = mac + ":" + chip_id + ":" + std::to_string(timestamp) + ":" + secret_key; - - // SHA256哈希 - unsigned char hash[32]; - mbedtls_sha256((unsigned char*)data.c_str(), data.length(), hash, 0); - - // 转换为十六进制字符串(前16字节) - std::string key; - for (int i = 0; i < 16; i++) { - char hex[3]; - snprintf(hex, sizeof(hex), "%02X", hash[i]); - key += hex; - } - - return key; -} - -/** - * @brief 为HTTP请求添加认证头 - * @param http HTTP客户端指针 - */ -static void add_auth_headers(Http* http) { - // 获取当前时间戳 - int64_t timestamp = esp_timer_get_time() / 1000000; // 转换为秒 - - // 生成动态密钥 - std::string dynamic_key = generate_dynamic_key(timestamp); - - // 获取设备信息 - std::string mac = get_device_mac(); - std::string chip_id = get_device_chip_id(); - - // 添加认证头 - if (http) { - http->SetHeader("X-MAC-Address", mac); - http->SetHeader("X-Chip-ID", chip_id); - http->SetHeader("X-Timestamp", std::to_string(timestamp)); - http->SetHeader("X-Dynamic-Key", dynamic_key); - - ESP_LOGI(TAG, "Added auth headers - MAC: %s, ChipID: %s, Timestamp: %lld", - mac.c_str(), chip_id.c_str(), timestamp); - } -} - -// URL编码函数 -static std::string url_encode(const std::string& str) { - std::string encoded; - char hex[4]; - - for (size_t i = 0; i < str.length(); i++) { - unsigned char c = str[i]; - - if ((c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - (c >= '0' && c <= '9') || - c == '-' || c == '_' || c == '.' || c == '~') { - encoded += c; - } else if (c == ' ') { - encoded += '+'; // 空格编码为'+'或'%20' - } else { - snprintf(hex, sizeof(hex), "%%%02X", c); - encoded += hex; - } - } - return encoded; -} - -//// 在文件开头添加一个辅助函数,统一处理URL构建 -//static std::string buildUrlWithParams(const std::string& base_url, const std::string& path, const std::string& query) { -// std::string result_url = base_url + path + "?"; -// size_t pos = 0; -// size_t amp_pos = 0; -// -// while ((amp_pos = query.find("&", pos)) != std::string::npos) { -// std::string param = query.substr(pos, amp_pos - pos); -// size_t eq_pos = param.find("="); -// -// if (eq_pos != std::string::npos) { -// std::string key = param.substr(0, eq_pos); -// std::string value = param.substr(eq_pos + 1); -// result_url += key + "=" + url_encode(value) + "&"; -// } else { -// result_url += param + "&"; -// } -// -// pos = amp_pos + 1; -// } -// -// // 处理最后一个参数 -// std::string last_param = query.substr(pos); -// size_t eq_pos = last_param.find("="); -// -// if (eq_pos != std::string::npos) { -// std::string key = last_param.substr(0, eq_pos); -// std::string value = last_param.substr(eq_pos + 1); -// result_url += key + "=" + url_encode(value); -// } else { -// result_url += last_param; -// } -// -// return result_url; -//} - -Esp32Music::Esp32Music() : last_downloaded_data_(), current_music_url_(), current_song_name_(), - song_name_displayed_(false), current_lyric_url_(), lyrics_(), - current_lyric_index_(-1), lyric_thread_(), is_lyric_running_(false), - display_mode_(DISPLAY_MODE_LYRICS), is_playing_(false), is_downloading_(false), - is_paused_(false), play_thread_(), download_thread_(), audio_buffer_(), buffer_mutex_(), - buffer_cv_(), buffer_size_(0), mp3_decoder_(nullptr), mp3_frame_info_(), - mp3_decoder_initialized_(false) { - ESP_LOGI(TAG, "Music player initialized with default spectrum display mode"); - // 延迟MP3解码器初始化,避免在构造函数中初始化导致的问题 - // InitializeMp3Decoder(); -} - -Esp32Music::~Esp32Music() { - ESP_LOGI(TAG, "Destroying music player - stopping all operations"); - - // 停止所有操作 - is_downloading_ = false; - is_playing_ = false; - is_lyric_running_ = false; - - // 通知所有等待的线程 - { - std::lock_guard lock(buffer_mutex_); - buffer_cv_.notify_all(); - } - - // 等待下载线程结束,设置5秒超时 - if (download_thread_.joinable()) { - ESP_LOGI(TAG, "Waiting for download thread to finish (timeout: 5s)"); - auto start_time = std::chrono::steady_clock::now(); - - // 等待线程结束 - bool thread_finished = false; - while (!thread_finished) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - auto elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start_time).count(); - - if (elapsed >= 5) { - ESP_LOGW(TAG, "Download thread join timeout after 5 seconds"); - break; - } - - // 再次设置停止标志,确保线程能够检测到 - is_downloading_ = false; - - // 通知条件变量 - { - std::lock_guard lock(buffer_mutex_); - buffer_cv_.notify_all(); - } - - // 检查线程是否已经结束 - if (!download_thread_.joinable()) { - thread_finished = true; - } - - // 定期打印等待信息 - if (elapsed > 0 && elapsed % 1 == 0) { - ESP_LOGI(TAG, "Still waiting for download thread to finish... (%ds)", (int)elapsed); - } - } - - if (download_thread_.joinable()) { - download_thread_.join(); - } - ESP_LOGI(TAG, "Download thread finished"); - } - - // 等待播放线程结束,设置3秒超时 - if (play_thread_.joinable()) { - ESP_LOGI(TAG, "Waiting for playback thread to finish (timeout: 3s)"); - auto start_time = std::chrono::steady_clock::now(); - - bool thread_finished = false; - while (!thread_finished) { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - auto elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start_time).count(); - - if (elapsed >= 3) { - ESP_LOGW(TAG, "Playback thread join timeout after 3 seconds"); - break; - } - - // 再次设置停止标志 - is_playing_ = false; - - // 通知条件变量 - { - std::lock_guard lock(buffer_mutex_); - buffer_cv_.notify_all(); - } - - // 检查线程是否已经结束 - if (!play_thread_.joinable()) { - thread_finished = true; - } - } - - if (play_thread_.joinable()) { - play_thread_.join(); - } - ESP_LOGI(TAG, "Playback thread finished"); - } - - // 等待歌词线程结束 - if (lyric_thread_.joinable()) { - ESP_LOGI(TAG, "Waiting for lyric thread to finish"); - lyric_thread_.join(); - ESP_LOGI(TAG, "Lyric thread finished"); - } - - // 清理缓冲区和MP3解码器 - ClearAudioBuffer(); - CleanupMp3Decoder(); - - ESP_LOGI(TAG, "Music player destroyed successfully"); -} - -bool Esp32Music::Download(const std::string& song_name, const std::string& artist_name) { - ESP_LOGI(TAG, "Starting to get music details for: %s", song_name.c_str()); - ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供"); - ESP_LOGI(TAG, "喵波音律QQ交流群:865754861"); - - // 清空之前的下载数据 - last_downloaded_data_.clear(); - - // 保存歌名用于后续显示 - current_song_name_ = song_name; - - // 第一步:请求stream_pcm接口获取音频信息 - std::string base_url = "http://http-embedded-music.miao-lab.top:2233"; - std::string full_url = base_url + "/stream_pcm?song=" + url_encode(song_name) + "&artist=" + url_encode(artist_name); - - ESP_LOGI(TAG, "Request URL: %s", full_url.c_str()); - - // 使用Board提供的HTTP客户端 - auto network = Board::GetInstance().GetNetwork(); - auto http = network->CreateHttp(0); - - // 复用连接(服务端支持 Keep-Alive) - http->SetHeader("Connection", "keep-alive"); - - // 设置基本请求头 - http->SetHeader("User-Agent", "ESP32-Music-Player/1.0"); - http->SetHeader("Accept", "application/json"); - - // 添加ESP32认证头 - add_auth_headers(http.get()); - - // 打开GET连接 - if (!http->Open("GET", full_url)) { - ESP_LOGE(TAG, "Failed to connect to music API"); - return false; - } - - // 添加超时 - http->SetTimeout(15000); - - // 检查响应状态码 - int status_code = http->GetStatusCode(); - if (status_code != 200) { - ESP_LOGE(TAG, "HTTP GET failed with status code: %d", status_code); - http->Close(); - return false; - } - - // 读取响应数据 - last_downloaded_data_ = http->ReadAll(); - http->Close(); - - ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d", status_code, last_downloaded_data_.length()); - ESP_LOGD(TAG, "Complete music details response: %s", last_downloaded_data_.c_str()); - - // 简单的认证响应检查(可选) - if (last_downloaded_data_.find("ESP32动态密钥验证失败") != std::string::npos) { - ESP_LOGE(TAG, "Authentication failed for song: %s", song_name.c_str()); - return false; - } - - if (!last_downloaded_data_.empty()) { - // 解析响应JSON以提取音频URL - cJSON* response_json = cJSON_Parse(last_downloaded_data_.c_str()); - if (response_json) { - // 提取关键信息 - cJSON* artist = cJSON_GetObjectItem(response_json, "artist"); - cJSON* title = cJSON_GetObjectItem(response_json, "title"); - cJSON* audio_url = cJSON_GetObjectItem(response_json, "audio_url"); - cJSON* lyric_url = cJSON_GetObjectItem(response_json, "lyric_url"); - - if (cJSON_IsString(artist)) { - ESP_LOGI(TAG, "Artist: %s", artist->valuestring); - } - if (cJSON_IsString(title)) { - ESP_LOGI(TAG, "Title: %s", title->valuestring); - } - - // 检查audio_url是否有效 - if (cJSON_IsString(audio_url) && audio_url->valuestring && strlen(audio_url->valuestring) > 0) { - ESP_LOGI(TAG, "Audio URL path: %s", audio_url->valuestring); - - // 第二步:直接使用音频URL开始流式播放 - std::string current_music_url_ = audio_url->valuestring; - - ESP_LOGI(TAG, "Starting streaming playback for: %s", song_name.c_str()); - song_name_displayed_ = false; // 重置歌名显示标志 - StartStreaming(current_music_url_); - - // 处理歌词URL - 只有在歌词显示模式下才启动歌词 - if (cJSON_IsString(lyric_url) && lyric_url->valuestring && strlen(lyric_url->valuestring) > 0) { - // 使用歌词URL获取歌词 - std::string current_lyric_url_ = lyric_url->valuestring; - - // 根据显示模式决定是否启动歌词 - if (display_mode_ == DISPLAY_MODE_LYRICS) { - ESP_LOGI(TAG, "Loading lyrics for: %s (lyrics display mode)", song_name.c_str()); - - // 启动歌词下载和显示 - if (is_lyric_running_) { - is_lyric_running_ = false; - if (lyric_thread_.joinable()) { - lyric_thread_.join(); - } - } - - is_lyric_running_ = true; - current_lyric_index_ = -1; - lyrics_.clear(); - - lyric_thread_ = std::thread(&Esp32Music::LyricDisplayThread, this); - } else { - ESP_LOGI(TAG, "Lyric URL found but spectrum display mode is active, skipping lyrics"); - } - } else { - ESP_LOGW(TAG, "No lyric URL found for this song"); - } - - cJSON_Delete(response_json); - return true; - } else { - // audio_url为空或无效 - ESP_LOGE(TAG, "Audio URL not found or empty for song: %s", song_name.c_str()); - ESP_LOGE(TAG, "Failed to find music: 没有找到歌曲 '%s'", song_name.c_str()); - cJSON_Delete(response_json); - return false; - } - } else { - ESP_LOGE(TAG, "Failed to parse JSON response"); - } - } else { - ESP_LOGE(TAG, "Empty response from music API"); - } - - return false; -} - - - -std::string Esp32Music::GetDownloadResult() { - return last_downloaded_data_; -} - -// 开始流式播放 -bool Esp32Music::StartStreaming(const std::string& music_url) { - if (music_url.empty()) { - ESP_LOGE(TAG, "Music URL is empty"); - return false; - } - - ESP_LOGD(TAG, "Starting streaming for URL: %s", music_url.c_str()); - - // 确保MP3解码器已初始化 - if (!mp3_decoder_initialized_) { - if (!InitializeMp3Decoder()) { - ESP_LOGE(TAG, "Failed to initialize MP3 decoder"); - return false; - } - } - - // 停止之前的播放和下载 - is_downloading_ = false; - is_playing_ = false; - - // 等待之前的线程完全结束 - if (download_thread_.joinable()) { - { - std::lock_guard lock(buffer_mutex_); - buffer_cv_.notify_all(); // 通知线程退出 - } - download_thread_.join(); - } - if (play_thread_.joinable()) { - { - std::lock_guard lock(buffer_mutex_); - buffer_cv_.notify_all(); // 通知线程退出 - } - play_thread_.join(); - } - - // 清空缓冲区 - ClearAudioBuffer(); - - // 配置线程栈大小以避免栈溢出 - esp_pthread_cfg_t cfg = esp_pthread_get_default_config(); - cfg.stack_size = 8192; // 8KB栈大小 - cfg.prio = 5; // 中等优先级 - cfg.thread_name = "audio_stream"; - esp_pthread_set_cfg(&cfg); - - // 开始下载线程 - is_downloading_ = true; - download_thread_ = std::thread(&Esp32Music::DownloadAudioStream, this, music_url); - - // 开始播放线程(会等待缓冲区有足够数据) - is_playing_ = true; - play_thread_ = std::thread(&Esp32Music::PlayAudioStream, this); - - ESP_LOGI(TAG, "Streaming threads started successfully"); - - return true; -} - -// 停止流式播放 -bool Esp32Music::StopStreaming() { - ESP_LOGI(TAG, "Stopping music streaming - current state: downloading=%d, playing=%d", - is_downloading_.load(), is_playing_.load()); - - // 重置采样率到原始值 - ResetSampleRate(); - - // 检查是否有流式播放正在进行 - if (!is_playing_ && !is_downloading_) { - ESP_LOGW(TAG, "No streaming in progress"); - return true; - } - - // 停止下载和播放标志 - is_downloading_ = false; - is_playing_ = false; - is_paused_ = false; // 重置暂停状态 - - // 清空歌名显示 - auto& board = Board::GetInstance(); - auto display = board.GetDisplay(); - if (display) { - display->SetMusicInfo(""); // 清空歌名显示 - ESP_LOGI(TAG, "Cleared song name display"); - } - - // 通知所有等待的线程 - { - std::lock_guard lock(buffer_mutex_); - buffer_cv_.notify_all(); - } - - // 等待线程结束(避免重复代码,让StopStreaming也能等待线程完全停止) - if (download_thread_.joinable()) { - download_thread_.join(); - ESP_LOGI(TAG, "Download thread joined in StopStreaming"); - } - - // 等待播放线程结束,使用更安全的方式 - if (play_thread_.joinable()) { - // 先设置停止标志 - is_playing_ = false; - - // 通知条件变量,确保线程能够退出 - { - std::lock_guard lock(buffer_mutex_); - buffer_cv_.notify_all(); - } - - // 使用超时机制等待线程结束,避免死锁 - bool thread_finished = false; - int wait_count = 0; - const int max_wait = 100; // 最多等待1秒 - - while (!thread_finished && wait_count < max_wait) { - vTaskDelay(pdMS_TO_TICKS(10)); - wait_count++; - - // 检查线程是否仍然可join - if (!play_thread_.joinable()) { - thread_finished = true; - break; - } - } - - if (play_thread_.joinable()) { - if (wait_count >= max_wait) { - ESP_LOGW(TAG, "Play thread join timeout, detaching thread"); - play_thread_.detach(); - } else { - play_thread_.join(); - ESP_LOGI(TAG, "Play thread joined in StopStreaming"); - } - } - } - - // 在线程完全结束后,只在频谱模式下停止FFT显示 - if (display && display_mode_ == DISPLAY_MODE_SPECTRUM) { - display->stopFft(); - ESP_LOGI(TAG, "Stopped FFT display in StopStreaming (spectrum mode)"); - } else if (display) { - ESP_LOGI(TAG, "Not in spectrum mode, skipping FFT stop in StopStreaming"); - } - - ESP_LOGI(TAG, "Music streaming stop signal sent"); - return true; -} - -// 流式下载音频数据 -void Esp32Music::DownloadAudioStream(const std::string& music_url) { - ESP_LOGD(TAG, "Starting audio stream download from: %s", music_url.c_str()); - - // 验证URL有效性 - if (music_url.empty() || music_url.find("http") != 0) { - ESP_LOGE(TAG, "Invalid URL format: %s", music_url.c_str()); - is_downloading_ = false; - return; - } - - auto network = Board::GetInstance().GetNetwork(); - auto http = network->CreateHttp(0); - - // 设置基本请求头 - http->SetHeader("User-Agent", "ESP32-Music-Player/1.0"); - http->SetHeader("Accept", "*/*"); - http->SetHeader("Range", "bytes=0-"); // 支持断点续传 - - // 添加ESP32认证头 - add_auth_headers(http.get()); - - if (!http->Open("GET", music_url)) { - ESP_LOGE(TAG, "Failed to connect to music stream URL"); - is_downloading_ = false; - return; - } - - int status_code = http->GetStatusCode(); - if (status_code != 200 && status_code != 206) { // 206 for partial content - ESP_LOGE(TAG, "HTTP GET failed with status code: %d", status_code); - http->Close(); - is_downloading_ = false; - return; - } - - ESP_LOGI(TAG, "Started downloading audio stream, status: %d", status_code); - - // 分块读取音频数据 - const size_t chunk_size = 4096; // 4KB每块 - char buffer[chunk_size]; - size_t total_downloaded = 0; - - while (is_downloading_ && is_playing_) { - int bytes_read = http->Read(buffer, chunk_size); - if (bytes_read < 0) { - ESP_LOGE(TAG, "Failed to read audio data: error code %d", bytes_read); - break; - } - if (bytes_read == 0) { - ESP_LOGI(TAG, "Audio stream download completed, total: %d bytes", total_downloaded); - break; - } - - // 打印数据块信息 - // ESP_LOGI(TAG, "Downloaded chunk: %d bytes at offset %d", bytes_read, total_downloaded); - - // 安全地打印数据块的十六进制内容(前16字节) - if (bytes_read >= 16) { - // ESP_LOGI(TAG, "Data: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X ...", - // (unsigned char)buffer[0], (unsigned char)buffer[1], (unsigned char)buffer[2], (unsigned char)buffer[3], - // (unsigned char)buffer[4], (unsigned char)buffer[5], (unsigned char)buffer[6], (unsigned char)buffer[7], - // (unsigned char)buffer[8], (unsigned char)buffer[9], (unsigned char)buffer[10], (unsigned char)buffer[11], - // (unsigned char)buffer[12], (unsigned char)buffer[13], (unsigned char)buffer[14], (unsigned char)buffer[15]); - } else { - ESP_LOGI(TAG, "Data chunk too small: %d bytes", bytes_read); - } - - // 尝试检测文件格式(检查文件头) - if (total_downloaded == 0 && bytes_read >= 4) { - if (memcmp(buffer, "ID3", 3) == 0) { - ESP_LOGI(TAG, "Detected MP3 file with ID3 tag"); - } else if (buffer[0] == 0xFF && (buffer[1] & 0xE0) == 0xE0) { - ESP_LOGI(TAG, "Detected MP3 file header"); - } else if (memcmp(buffer, "RIFF", 4) == 0) { - ESP_LOGI(TAG, "Detected WAV file"); - } else if (memcmp(buffer, "fLaC", 4) == 0) { - ESP_LOGI(TAG, "Detected FLAC file"); - } else if (memcmp(buffer, "OggS", 4) == 0) { - ESP_LOGI(TAG, "Detected OGG file"); - } else { - ESP_LOGI(TAG, "Unknown audio format, first 4 bytes: %02X %02X %02X %02X", - (unsigned char)buffer[0], (unsigned char)buffer[1], - (unsigned char)buffer[2], (unsigned char)buffer[3]); - } - } - - // 创建音频数据块 - uint8_t* chunk_data = (uint8_t*)heap_caps_malloc(bytes_read, MALLOC_CAP_SPIRAM); - if (!chunk_data) { - ESP_LOGE(TAG, "Failed to allocate memory for audio chunk"); - break; - } - memcpy(chunk_data, buffer, bytes_read); - - // 等待缓冲区有空间 - { - std::unique_lock lock(buffer_mutex_); - buffer_cv_.wait(lock, [this] { return buffer_size_ < MAX_BUFFER_SIZE || !is_downloading_; }); - - if (is_downloading_) { - audio_buffer_.push(AudioChunk(chunk_data, bytes_read)); - buffer_size_ += bytes_read; - total_downloaded += bytes_read; - - // 通知播放线程有新数据 - buffer_cv_.notify_one(); - - if (total_downloaded % (256 * 1024) == 0) { // 每256KB打印一次进度 - ESP_LOGI(TAG, "Downloaded %d bytes, buffer size: %d", total_downloaded, buffer_size_); - } - } else { - heap_caps_free(chunk_data); - break; - } - } - } - - http->Close(); - is_downloading_ = false; - - // 通知播放线程下载完成 - { - std::lock_guard lock(buffer_mutex_); - buffer_cv_.notify_all(); - } - - ESP_LOGI(TAG, "Audio stream download thread finished"); -} - -// 流式播放音频数据 -void Esp32Music::PlayAudioStream() { - ESP_LOGI(TAG, "Starting audio stream playback"); - - // 初始化时间跟踪变量 - current_play_time_ms_ = 0; - last_frame_time_ms_ = 0; - total_frames_decoded_ = 0; - - auto codec = Board::GetInstance().GetAudioCodec(); - if (!codec || !codec->output_enabled()) { - ESP_LOGE(TAG, "Audio codec not available or not enabled"); - is_playing_ = false; - return; - } - - if (!mp3_decoder_initialized_) { - ESP_LOGE(TAG, "MP3 decoder not initialized"); - is_playing_ = false; - return; - } - - - // 等待缓冲区有足够数据开始播放 - { - std::unique_lock lock(buffer_mutex_); - buffer_cv_.wait(lock, [this] { - return buffer_size_ >= MIN_BUFFER_SIZE || (!is_downloading_ && !audio_buffer_.empty()); - }); - } - - ESP_LOGI(TAG, "Starting playback with buffer size: %d", buffer_size_); - - size_t total_played = 0; - uint8_t* mp3_input_buffer = nullptr; - int bytes_left = 0; - uint8_t* read_ptr = nullptr; - - // 分配MP3输入缓冲区 - mp3_input_buffer = (uint8_t*)heap_caps_malloc(8192, MALLOC_CAP_SPIRAM); - if (!mp3_input_buffer) { - ESP_LOGE(TAG, "Failed to allocate MP3 input buffer"); - is_playing_ = false; - return; - } - - // 标记是否已经处理过ID3标签 - bool id3_processed = false; - - while (is_playing_) { - // 检查是否被暂停 - if (is_paused_) { - ESP_LOGD(TAG, "Music playback paused, waiting..."); - vTaskDelay(pdMS_TO_TICKS(100)); - continue; - } - - // 检查设备状态,只有在空闲状态才播放音乐 - auto& app = Application::GetInstance(); - DeviceState current_state = app.GetDeviceState(); - - // 状态转换:说话中-》聆听中-》待机状态-》播放音乐 - if (current_state == kDeviceStateListening || current_state == kDeviceStateSpeaking) { - if (current_state == kDeviceStateSpeaking) { - ESP_LOGI(TAG, "Device is in speaking state, switching to listening state for music playback"); - } - if (current_state == kDeviceStateListening) { - ESP_LOGI(TAG, "Device is in listening state, switching to idle state for music playback"); - } - // 切换状态 - app.ToggleChatState(); // 变成待机状态 - vTaskDelay(pdMS_TO_TICKS(300)); - continue; - } else if (current_state != kDeviceStateIdle) { // 不是待机状态,就一直卡在这里,不让播放音乐 - ESP_LOGD(TAG, "Device state is %d, pausing music playback", current_state); - // 如果不是空闲状态,暂停播放 - vTaskDelay(pdMS_TO_TICKS(50)); - continue; - } - - // 设备状态检查通过,显示当前播放的歌名 - if (!song_name_displayed_ && !current_song_name_.empty()) { - auto& board = Board::GetInstance(); - auto display = board.GetDisplay(); - if (display) { - // 格式化歌名显示为《歌名》播放中... - std::string formatted_song_name = "《" + current_song_name_ + "》播放中..."; - display->SetMusicInfo(formatted_song_name.c_str()); - ESP_LOGI(TAG, "Displaying song name: %s", formatted_song_name.c_str()); - song_name_displayed_ = true; - } - - // 根据显示模式启动相应的显示功能 - if (display) { - if (display_mode_ == DISPLAY_MODE_SPECTRUM) { - display->start(); - ESP_LOGI(TAG, "Display start() called for spectrum visualization"); - } else { - ESP_LOGI(TAG, "Lyrics display mode active, FFT visualization disabled"); - } - } - } - - // 如果需要更多MP3数据,从缓冲区读取 - if (bytes_left < 4096) { // 保持至少4KB数据用于解码 - AudioChunk chunk; - - // 从缓冲区获取音频数据 - { - std::unique_lock lock(buffer_mutex_); - if (audio_buffer_.empty()) { - if (!is_downloading_) { - // 下载完成且缓冲区为空,播放结束 - ESP_LOGI(TAG, "Playback finished, total played: %d bytes", total_played); - break; - } - // 等待新数据 - buffer_cv_.wait(lock, [this] { return !audio_buffer_.empty() || !is_downloading_; }); - if (audio_buffer_.empty()) { - continue; - } - } - - chunk = audio_buffer_.front(); - audio_buffer_.pop(); - buffer_size_ -= chunk.size; - - // 通知下载线程缓冲区有空间 - buffer_cv_.notify_one(); - } - - // 将新数据添加到MP3输入缓冲区 - if (chunk.data && chunk.size > 0) { - // 移动剩余数据到缓冲区开头 - if (bytes_left > 0 && read_ptr != mp3_input_buffer) { - memmove(mp3_input_buffer, read_ptr, bytes_left); - } - - // 检查缓冲区空间 - size_t space_available = 8192 - bytes_left; - size_t copy_size = std::min(chunk.size, space_available); - - // 复制新数据 - memcpy(mp3_input_buffer + bytes_left, chunk.data, copy_size); - bytes_left += copy_size; - read_ptr = mp3_input_buffer; - - // 检查并跳过ID3标签(仅在开始时处理一次) - if (!id3_processed && bytes_left >= 10) { - size_t id3_skip = SkipId3Tag(read_ptr, bytes_left); - if (id3_skip > 0) { - read_ptr += id3_skip; - bytes_left -= id3_skip; - ESP_LOGI(TAG, "Skipped ID3 tag: %u bytes", (unsigned int)id3_skip); - } - id3_processed = true; - } - - // 释放chunk内存 - heap_caps_free(chunk.data); - } - } - - // 尝试找到MP3帧同步 - int sync_offset = MP3FindSyncWord(read_ptr, bytes_left); - if (sync_offset < 0) { - ESP_LOGW(TAG, "No MP3 sync word found, skipping %d bytes", bytes_left); - bytes_left = 0; - continue; - } - - // 跳过到同步位置 - if (sync_offset > 0) { - read_ptr += sync_offset; - bytes_left -= sync_offset; - } - - // 解码MP3帧 - int16_t pcm_buffer[2304]; - int decode_result = MP3Decode(mp3_decoder_, &read_ptr, &bytes_left, pcm_buffer, 0); - - if (decode_result == 0) { - // 解码成功,获取帧信息 - MP3GetLastFrameInfo(mp3_decoder_, &mp3_frame_info_); - total_frames_decoded_++; - - // 基本的帧信息有效性检查,防止除零错误 - if (mp3_frame_info_.samprate == 0 || mp3_frame_info_.nChans == 0) { - ESP_LOGW(TAG, "Invalid frame info: rate=%d, channels=%d, skipping", - mp3_frame_info_.samprate, mp3_frame_info_.nChans); - continue; - } - - // 计算当前帧的持续时间(毫秒) - int frame_duration_ms = (mp3_frame_info_.outputSamps * 1000) / - (mp3_frame_info_.samprate * mp3_frame_info_.nChans); - - // 更新当前播放时间 - current_play_time_ms_ += frame_duration_ms; - - ESP_LOGD(TAG, "Frame %d: time=%lldms, duration=%dms, rate=%d, ch=%d", - total_frames_decoded_, current_play_time_ms_, frame_duration_ms, - mp3_frame_info_.samprate, mp3_frame_info_.nChans); - - // 更新歌词显示 - int buffer_latency_ms = 600; // 实测调整值 - UpdateLyricDisplay(current_play_time_ms_ + buffer_latency_ms); - - // 将PCM数据发送到Application的音频解码队列 - if (mp3_frame_info_.outputSamps > 0) { - int16_t* final_pcm_data = pcm_buffer; - int final_sample_count = mp3_frame_info_.outputSamps; - std::vector mono_buffer; - - // 如果是双通道,转换为单通道混合 - if (mp3_frame_info_.nChans == 2) { - // 双通道转单通道:将左右声道混合 - int stereo_samples = mp3_frame_info_.outputSamps; // 包含左右声道的总样本数 - int mono_samples = stereo_samples / 2; // 实际的单声道样本数 - - mono_buffer.resize(mono_samples); - - for (int i = 0; i < mono_samples; ++i) { - // 混合左右声道 (L + R) / 2 - int left = pcm_buffer[i * 2]; // 左声道 - int right = pcm_buffer[i * 2 + 1]; // 右声道 - mono_buffer[i] = (int16_t)((left + right) / 2); - } - - final_pcm_data = mono_buffer.data(); - final_sample_count = mono_samples; - - ESP_LOGD(TAG, "Converted stereo to mono: %d -> %d samples", - stereo_samples, mono_samples); - } else if (mp3_frame_info_.nChans == 1) { - // 已经是单声道,无需转换 - ESP_LOGD(TAG, "Already mono audio: %d samples", final_sample_count); - } else { - ESP_LOGW(TAG, "Unsupported channel count: %d, treating as mono", - mp3_frame_info_.nChans); - } - - // 创建AudioStreamPacket - AudioStreamPacket packet; - packet.sample_rate = mp3_frame_info_.samprate; - packet.frame_duration = 60; // 使用Application默认的帧时长 - packet.timestamp = 0; - - // 将int16_t PCM数据转换为uint8_t字节数组 - size_t pcm_size_bytes = final_sample_count * sizeof(int16_t); - packet.payload.resize(pcm_size_bytes); - memcpy(packet.payload.data(), final_pcm_data, pcm_size_bytes); - - if (final_pcm_data_fft == nullptr) { - final_pcm_data_fft = (int16_t*)heap_caps_malloc( - final_sample_count * sizeof(int16_t), - MALLOC_CAP_SPIRAM - ); - } - - memcpy( - final_pcm_data_fft, - final_pcm_data, - final_sample_count * sizeof(int16_t) - ); - - ESP_LOGD(TAG, "Sending %d PCM samples (%d bytes, rate=%d, channels=%d->1) to Application", - final_sample_count, pcm_size_bytes, mp3_frame_info_.samprate, mp3_frame_info_.nChans); - - // 发送到Application的音频解码队列 - app.AddAudioData(std::move(packet)); - total_played += pcm_size_bytes; - - // 打印播放进度 - if (total_played % (128 * 1024) == 0) { - ESP_LOGI(TAG, "Played %d bytes, buffer size: %d", total_played, buffer_size_); - } - } - - } else { - // 解码失败 - ESP_LOGW(TAG, "MP3 decode failed with error: %d", decode_result); - - // 跳过一些字节继续尝试 - if (bytes_left > 1) { - read_ptr++; - bytes_left--; - } else { - bytes_left = 0; - } - } - } - - // 清理 - if (mp3_input_buffer) { - heap_caps_free(mp3_input_buffer); - } - - // 播放结束时进行基本清理,但不调用StopStreaming避免线程自我等待 - ESP_LOGI(TAG, "Audio stream playback finished, total played: %d bytes", total_played); - ESP_LOGI(TAG, "Performing basic cleanup from play thread"); - - // 停止播放标志 - is_playing_ = false; - - // 只在频谱显示模式下才停止FFT显示 - if (display_mode_ == DISPLAY_MODE_SPECTRUM) { - auto& board = Board::GetInstance(); - auto display = board.GetDisplay(); - if (display) { - display->stopFft(); - ESP_LOGI(TAG, "Stopped FFT display from play thread (spectrum mode)"); - } - } else { - ESP_LOGI(TAG, "Not in spectrum mode, skipping FFT stop"); - } -} - -// 清空音频缓冲区 -void Esp32Music::ClearAudioBuffer() { - std::lock_guard lock(buffer_mutex_); - - while (!audio_buffer_.empty()) { - AudioChunk chunk = audio_buffer_.front(); - audio_buffer_.pop(); - if (chunk.data) { - heap_caps_free(chunk.data); - } - } - - buffer_size_ = 0; - ESP_LOGI(TAG, "Audio buffer cleared"); -} - -// 初始化MP3解码器 -bool Esp32Music::InitializeMp3Decoder() { - mp3_decoder_ = MP3InitDecoder(); - if (mp3_decoder_ == nullptr) { - ESP_LOGE(TAG, "Failed to initialize MP3 decoder"); - mp3_decoder_initialized_ = false; - return false; - } - - mp3_decoder_initialized_ = true; - ESP_LOGI(TAG, "MP3 decoder initialized successfully"); - return true; -} - -// 清理MP3解码器 -void Esp32Music::CleanupMp3Decoder() { - if (mp3_decoder_ != nullptr) { - MP3FreeDecoder(mp3_decoder_); - mp3_decoder_ = nullptr; - } - mp3_decoder_initialized_ = false; - ESP_LOGI(TAG, "MP3 decoder cleaned up"); -} - -// 重置采样率到原始值 -void Esp32Music::ResetSampleRate() { - auto& board = Board::GetInstance(); - auto codec = board.GetAudioCodec(); - if (codec && codec->original_output_sample_rate() > 0 && - codec->output_sample_rate() != codec->original_output_sample_rate()) { - ESP_LOGI(TAG, "重置采样率:从 %d Hz 重置到原始值 %d Hz", - codec->output_sample_rate(), codec->original_output_sample_rate()); - if (codec->SetOutputSampleRate(-1)) { // -1 表示重置到原始值 - ESP_LOGI(TAG, "成功重置采样率到原始值: %d Hz", codec->output_sample_rate()); - } else { - ESP_LOGW(TAG, "无法重置采样率到原始值"); - } - } -} - -// 跳过MP3文件开头的ID3标签 -size_t Esp32Music::SkipId3Tag(uint8_t* data, size_t size) { - if (!data || size < 10) { - return 0; - } - - // 检查ID3v2标签头 "ID3" - if (memcmp(data, "ID3", 3) != 0) { - return 0; - } - - // 计算标签大小(synchsafe integer格式) - uint32_t tag_size = ((uint32_t)(data[6] & 0x7F) << 21) | - ((uint32_t)(data[7] & 0x7F) << 14) | - ((uint32_t)(data[8] & 0x7F) << 7) | - ((uint32_t)(data[9] & 0x7F)); - - // ID3v2头部(10字节) + 标签内容 - size_t total_skip = 10 + tag_size; - - // 确保不超过可用数据大小 - if (total_skip > size) { - total_skip = size; - } - - ESP_LOGI(TAG, "Found ID3v2 tag, skipping %u bytes", (unsigned int)total_skip); - return total_skip; -} - -// 下载歌词 -bool Esp32Music::DownloadLyrics(const std::string& lyric_url) { - ESP_LOGI(TAG, "Downloading lyrics from: %s", lyric_url.c_str()); - - // 检查URL是否为空 - if (lyric_url.empty()) { - ESP_LOGE(TAG, "Lyric URL is empty!"); - return false; - } - - // 添加重试逻辑 - const int max_retries = 3; - int retry_count = 0; - bool success = false; - std::string lyric_content; - std::string current_url = lyric_url; - int redirect_count = 0; - const int max_redirects = 5; // 最多允许5次重定向 - - while (retry_count < max_retries && !success && redirect_count < max_redirects) { - if (retry_count > 0) { - ESP_LOGI(TAG, "Retrying lyric download (attempt %d of %d)", retry_count + 1, max_retries); - // 重试前暂停一下 - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - - // 使用Board提供的HTTP客户端 - auto network = Board::GetInstance().GetNetwork(); - auto http = network->CreateHttp(0); - if (!http) { - ESP_LOGE(TAG, "Failed to create HTTP client for lyric download"); - retry_count++; - continue; - } - - // 设置基本请求头 - http->SetHeader("User-Agent", "ESP32-Music-Player/1.0"); - http->SetHeader("Accept", "text/plain"); - - // 添加ESP32认证头 - add_auth_headers(http.get()); - - // 打开GET连接 - if (!http->Open("GET", current_url)) { - ESP_LOGE(TAG, "Failed to open HTTP connection for lyrics"); - // 移除delete http; 因为unique_ptr会自动管理内存 - retry_count++; - continue; - } - - // 检查HTTP状态码 - int status_code = http->GetStatusCode(); - ESP_LOGI(TAG, "Lyric download HTTP status code: %d", status_code); - - // 处理重定向 - 由于Http类没有GetHeader方法,我们只能根据状态码判断 - if (status_code == 301 || status_code == 302 || status_code == 303 || status_code == 307 || status_code == 308) { - // 由于无法获取Location头,只能报告重定向但无法继续 - ESP_LOGW(TAG, "Received redirect status %d but cannot follow redirect (no GetHeader method)", status_code); - http->Close(); - retry_count++; - continue; - } - - // 非200系列状态码视为错误 - if (status_code < 200 || status_code >= 300) { - ESP_LOGE(TAG, "HTTP GET failed with status code: %d", status_code); - http->Close(); - retry_count++; - continue; - } - - // 读取响应 - lyric_content.clear(); - char buffer[1024]; - int bytes_read; - bool read_error = false; - int total_read = 0; - - // 由于无法获取Content-Length和Content-Type头,我们不知道预期大小和内容类型 - ESP_LOGD(TAG, "Starting to read lyric content"); - - while (true) { - bytes_read = http->Read(buffer, sizeof(buffer) - 1); - // ESP_LOGD(TAG, "Lyric HTTP read returned %d bytes", bytes_read); // 注释掉以减少日志输出 - - if (bytes_read > 0) { - buffer[bytes_read] = '\0'; - lyric_content += buffer; - total_read += bytes_read; - - // 定期打印下载进度 - 改为DEBUG级别减少输出 - if (total_read % 4096 == 0) { - ESP_LOGD(TAG, "Downloaded %d bytes so far", total_read); - } - } else if (bytes_read == 0) { - // 正常结束,没有更多数据 - ESP_LOGD(TAG, "Lyric download completed, total bytes: %d", total_read); - success = true; - break; - } else { - // bytes_read < 0,可能是ESP-IDF的已知问题 - // 如果已经读取到了一些数据,则认为下载成功 - if (!lyric_content.empty()) { - ESP_LOGW(TAG, "HTTP read returned %d, but we have data (%d bytes), continuing", bytes_read, lyric_content.length()); - success = true; - break; - } else { - ESP_LOGE(TAG, "Failed to read lyric data: error code %d", bytes_read); - read_error = true; - break; - } - } - } - - http->Close(); - - if (read_error) { - retry_count++; - continue; - } - - // 如果成功读取数据,跳出重试循环 - if (success) { - break; - } - } - - // 检查是否超过了最大重试次数 - if (retry_count >= max_retries) { - ESP_LOGE(TAG, "Failed to download lyrics after %d attempts", max_retries); - return false; - } - - // 记录前几个字节的数据,帮助调试 - if (!lyric_content.empty()) { - size_t preview_size = std::min(lyric_content.size(), size_t(50)); - std::string preview = lyric_content.substr(0, preview_size); - ESP_LOGD(TAG, "Lyric content preview (%d bytes): %s", lyric_content.length(), preview.c_str()); - } else { - ESP_LOGE(TAG, "Failed to download lyrics or lyrics are empty"); - return false; - } - - ESP_LOGI(TAG, "Lyrics downloaded successfully, size: %d bytes", lyric_content.length()); - return ParseLyrics(lyric_content); -} - -// 解析歌词 -bool Esp32Music::ParseLyrics(const std::string& lyric_content) { - ESP_LOGI(TAG, "Parsing lyrics content"); - - // 使用锁保护lyrics_数组访问 - std::lock_guard lock(lyrics_mutex_); - - lyrics_.clear(); - - // 按行分割歌词内容 - std::istringstream stream(lyric_content); - std::string line; - - while (std::getline(stream, line)) { - // 去除行尾的回车符 - if (!line.empty() && line.back() == '\r') { - line.pop_back(); - } - - // 跳过空行 - if (line.empty()) { - continue; - } - - // 解析LRC格式: [mm:ss.xx]歌词文本 - if (line.length() > 10 && line[0] == '[') { - size_t close_bracket = line.find(']'); - if (close_bracket != std::string::npos) { - std::string tag_or_time = line.substr(1, close_bracket - 1); - std::string content = line.substr(close_bracket + 1); - - // 检查是否是元数据标签而不是时间戳 - // 元数据标签通常是 [ti:标题], [ar:艺术家], [al:专辑] 等 - size_t colon_pos = tag_or_time.find(':'); - if (colon_pos != std::string::npos) { - std::string left_part = tag_or_time.substr(0, colon_pos); - - // 检查冒号左边是否是时间(数字) - bool is_time_format = true; - for (char c : left_part) { - if (!isdigit(c)) { - is_time_format = false; - break; - } - } - - // 如果不是时间格式,跳过这一行(元数据标签) - if (!is_time_format) { - // 可以在这里处理元数据,例如提取标题、艺术家等信息 - ESP_LOGD(TAG, "Skipping metadata tag: [%s]", tag_or_time.c_str()); - continue; - } - - // 是时间格式,解析时间戳 - try { - int minutes = std::stoi(tag_or_time.substr(0, colon_pos)); - float seconds = std::stof(tag_or_time.substr(colon_pos + 1)); - int timestamp_ms = minutes * 60 * 1000 + (int)(seconds * 1000); - - // 安全处理歌词文本,确保UTF-8编码正确 - std::string safe_lyric_text; - if (!content.empty()) { - // 创建安全副本并验证字符串 - safe_lyric_text = content; - // 确保字符串以null结尾 - safe_lyric_text.shrink_to_fit(); - } - - lyrics_.push_back(std::make_pair(timestamp_ms, safe_lyric_text)); - - if (!safe_lyric_text.empty()) { - // 限制日志输出长度,避免中文字符截断问题 - size_t log_len = std::min(safe_lyric_text.length(), size_t(50)); - std::string log_text = safe_lyric_text.substr(0, log_len); - ESP_LOGD(TAG, "Parsed lyric: [%d ms] %s", timestamp_ms, log_text.c_str()); - } else { - ESP_LOGD(TAG, "Parsed lyric: [%d ms] (empty)", timestamp_ms); - } - } catch (const std::exception& e) { - ESP_LOGW(TAG, "Failed to parse time: %s", tag_or_time.c_str()); - } - } - } - } - } - - // 按时间戳排序 - std::sort(lyrics_.begin(), lyrics_.end()); - - ESP_LOGI(TAG, "Parsed %d lyric lines", lyrics_.size()); - return !lyrics_.empty(); -} - -// 歌词显示线程 -void Esp32Music::LyricDisplayThread() { - ESP_LOGI(TAG, "Lyric display thread started"); - - if (!DownloadLyrics(current_lyric_url_)) { - ESP_LOGE(TAG, "Failed to download or parse lyrics"); - is_lyric_running_ = false; - return; - } - - // 定期检查是否需要更新显示(频率可以降低) - while (is_lyric_running_ && is_playing_) { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - - ESP_LOGI(TAG, "Lyric display thread finished"); -} - -void Esp32Music::UpdateLyricDisplay(int64_t current_time_ms) { - std::lock_guard lock(lyrics_mutex_); - - if (lyrics_.empty()) { - return; - } - - // 查找当前应该显示的歌词 - int new_lyric_index = -1; - - // 从当前歌词索引开始查找,提高效率 - int start_index = (current_lyric_index_.load() >= 0) ? current_lyric_index_.load() : 0; - - // 正向查找:找到最后一个时间戳小于等于当前时间的歌词 - for (int i = start_index; i < (int)lyrics_.size(); i++) { - if (lyrics_[i].first <= current_time_ms) { - new_lyric_index = i; - } else { - break; // 时间戳已超过当前时间 - } - } - - // 如果没有找到(可能当前时间比第一句歌词还早),显示空 - if (new_lyric_index == -1) { - new_lyric_index = -1; - } - - // 如果歌词索引发生变化,更新显示 - if (new_lyric_index != current_lyric_index_) { - current_lyric_index_ = new_lyric_index; - - auto& board = Board::GetInstance(); - auto display = board.GetDisplay(); - if (display) { - std::string lyric_text; - - if (current_lyric_index_ >= 0 && current_lyric_index_ < (int)lyrics_.size()) { - lyric_text = lyrics_[current_lyric_index_].second; - } - - // 显示歌词 - display->SetChatMessage("lyric", lyric_text.c_str()); - - ESP_LOGD(TAG, "Lyric update at %lldms: %s", - current_time_ms, - lyric_text.empty() ? "(no lyric)" : lyric_text.c_str()); - } - } -} - -// 删除复杂的认证初始化方法,使用简单的静态函数 - -// 删除复杂的类方法,使用简单的静态函数 - -/** - * @brief 添加认证头到HTTP请求 - * @param http_client HTTP客户端指针 - * - * 添加的认证头包括: - * - X-MAC-Address: 设备MAC地址 - * - X-Chip-ID: 设备芯片ID - * - X-Timestamp: 当前时间戳 - * - X-Dynamic-Key: 动态生成的密钥 - */ -// 删除复杂的AddAuthHeaders方法,使用简单的静态函数 - -// 删除复杂的认证验证和配置方法,使用简单的静态函数 - -// 显示模式控制方法实现 -void Esp32Music::SetDisplayMode(DisplayMode mode) { - DisplayMode old_mode = display_mode_.load(); - display_mode_ = mode; - - ESP_LOGI(TAG, "Display mode changed from %s to %s", - (old_mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS", - (mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS"); -} - -// MCP工具需要的方法实现 -bool Esp32Music::SetVolume(int volume) { - ESP_LOGI(TAG, "SetVolume called with volume: %d", volume); - - // 验证音量范围 - if (volume < 0 || volume > 100) { - ESP_LOGW(TAG, "Invalid volume level: %d, must be between 0-100", volume); - return false; - } - - // 通过Board获取AudioCodec并设置音量 - auto& board = Board::GetInstance(); - auto codec = board.GetAudioCodec(); - if (codec) { - codec->SetOutputVolume(volume); - ESP_LOGI(TAG, "Volume set to %d%%", volume); - return true; - } else { - ESP_LOGE(TAG, "No audio codec available"); - return false; - } -} - -bool Esp32Music::PlaySong() { - ESP_LOGI(TAG, "PlaySong called"); - return false; -} - -bool Esp32Music::StopSong() { - ESP_LOGI(TAG, "StopSong called"); - return StopStreaming(); -} - -bool Esp32Music::PauseSong() { - ESP_LOGI(TAG, "PauseSong called"); - - // 检查是否正在播放 - if (!is_playing_) { - ESP_LOGW(TAG, "No music is currently playing"); - return false; - } - - // 检查是否已经暂停 - if (is_paused_) { - ESP_LOGW(TAG, "Music is already paused"); - return true; - } - - // 设置暂停标志 - is_paused_ = true; - ESP_LOGI(TAG, "Music playback paused"); - - // 更新显示状态 - auto& board = Board::GetInstance(); - auto display = board.GetDisplay(); - if (display && !current_song_name_.empty()) { - std::string formatted_song_name = "《" + current_song_name_ + "》已暂停"; - display->SetMusicInfo(formatted_song_name.c_str()); - ESP_LOGI(TAG, "Updated display: %s", formatted_song_name.c_str()); - } - - return true; -} - -bool Esp32Music::ResumeSong() { - ESP_LOGI(TAG, "ResumeSong called"); - - // 检查是否正在播放 - if (!is_playing_) { - ESP_LOGW(TAG, "No music is currently playing"); - return false; - } - - // 检查是否已经恢复 - if (!is_paused_) { - ESP_LOGW(TAG, "Music is not paused"); - return true; - } - - // 清除暂停标志 - is_paused_ = false; - ESP_LOGI(TAG, "Music playback resumed"); - - // 更新显示状态 - auto& board = Board::GetInstance(); - auto display = board.GetDisplay(); - if (display && !current_song_name_.empty()) { - std::string formatted_song_name = "《" + current_song_name_ + "》播放中..."; - display->SetMusicInfo(formatted_song_name.c_str()); - ESP_LOGI(TAG, "Updated display: %s", formatted_song_name.c_str()); - } - - return true; +#include "esp32_music.h" +#include "board.h" +#include "system_info.h" +#include "audio/audio_codec.h" +#include "application.h" +#include "protocols/protocol.h" +#include "display/display.h" +#include "server_config.h" +#include "device_manager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // 为isdigit函数 +#include // 为线程ID比较 +#include +#include + +#define TAG "Esp32Music" + +// ========== 简单的ESP32认证函数 ========== + +/** + * @brief 获取设备MAC地址 + * @return MAC地址字符串 + */ +static std::string get_device_mac() { + return SystemInfo::GetMacAddress(); +} + +/** + * @brief 获取设备芯片ID + * @return 芯片ID字符串 + */ +static std::string get_device_chip_id() { + // 使用MAC地址作为芯片ID,去除冒号分隔符 + std::string mac = SystemInfo::GetMacAddress(); + // 去除所有冒号 + mac.erase(std::remove(mac.begin(), mac.end(), ':'), mac.end()); + return mac; +} + +/** + * @brief 生成动态密钥 + * @param timestamp 时间戳 + * @return 动态密钥字符串 + */ +static std::string generate_dynamic_key(int64_t timestamp) { + // 密钥(请修改为与服务端一致) + const std::string secret_key = "your-esp32-secret-key-2024"; + + // 获取设备信息 + std::string mac = get_device_mac(); + std::string chip_id = get_device_chip_id(); + + // 组合数据:MAC:芯片ID:时间戳:密钥 + std::string data = mac + ":" + chip_id + ":" + std::to_string(timestamp) + ":" + secret_key; + + // SHA256哈希 + unsigned char hash[32]; + mbedtls_sha256((unsigned char*)data.c_str(), data.length(), hash, 0); + + // 转换为十六进制字符串(前16字节) + std::string key; + for (int i = 0; i < 16; i++) { + char hex[3]; + snprintf(hex, sizeof(hex), "%02X", hash[i]); + key += hex; + } + + return key; +} + +/** + * @brief 为HTTP请求添加认证头 + * @param http HTTP客户端指针 + */ +static void add_auth_headers(Http* http) { + // 获取当前时间戳 + int64_t timestamp = esp_timer_get_time() / 1000000; // 转换为秒 + + // 生成动态密钥 + std::string dynamic_key = generate_dynamic_key(timestamp); + + // 获取设备信息 + std::string mac = get_device_mac(); + std::string chip_id = get_device_chip_id(); + + // 添加认证头 + if (http) { + http->SetHeader("X-MAC-Address", mac); + http->SetHeader("X-Chip-ID", chip_id); + http->SetHeader("X-Timestamp", std::to_string(timestamp)); + http->SetHeader("X-Dynamic-Key", dynamic_key); + + // 获取并添加设备Token + auto& device_manager = DeviceManager::GetInstance(); + std::string token = device_manager.GetDeviceToken(); + if (!token.empty()) { + http->SetHeader("X-Device-Token", token); + ESP_LOGI(TAG, "Added X-Device-Token: %s...", token.substr(0, 8).c_str()); + } + + ESP_LOGI(TAG, "Added auth headers - MAC: %s, ChipID: %s, Timestamp: %lld", + mac.c_str(), chip_id.c_str(), timestamp); + } +} + +// URL编码函数 +static std::string url_encode(const std::string& str) { + std::string encoded; + char hex[4]; + + for (size_t i = 0; i < str.length(); i++) { + unsigned char c = str[i]; + + if ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '-' || c == '_' || c == '.' || c == '~') { + encoded += c; + } else if (c == ' ') { + encoded += '+'; // 空格编码为'+'或'%20' + } else { + snprintf(hex, sizeof(hex), "%%%02X", c); + encoded += hex; + } + } + return encoded; +} + +// 在文件开头添加一个辅助函数,统一处理URL构建 +static std::string buildUrlWithParams(const std::string& base_url, const std::string& path, const std::string& query) { + std::string result_url = base_url + path + "?"; + size_t pos = 0; + size_t amp_pos = 0; + + while ((amp_pos = query.find("&", pos)) != std::string::npos) { + std::string param = query.substr(pos, amp_pos - pos); + size_t eq_pos = param.find("="); + + if (eq_pos != std::string::npos) { + std::string key = param.substr(0, eq_pos); + std::string value = param.substr(eq_pos + 1); + result_url += key + "=" + url_encode(value) + "&"; + } else { + result_url += param + "&"; + } + + pos = amp_pos + 1; + } + + // 处理最后一个参数 + std::string last_param = query.substr(pos); + size_t eq_pos = last_param.find("="); + + if (eq_pos != std::string::npos) { + std::string key = last_param.substr(0, eq_pos); + std::string value = last_param.substr(eq_pos + 1); + result_url += key + "=" + url_encode(value); + } else { + result_url += last_param; + } + + return result_url; +} + +Esp32Music::Esp32Music() : last_downloaded_data_(), current_music_url_(), current_song_name_(), + song_name_displayed_(false), current_lyric_url_(), lyrics_(), + current_lyric_index_(-1), lyric_thread_(), is_lyric_running_(false), + display_mode_(DISPLAY_MODE_LYRICS), is_playing_(false), is_downloading_(false), + play_thread_(), download_thread_(), current_play_time_ms_(0), + last_frame_time_ms_(0), total_frames_decoded_(0), current_song_duration_seconds_(0), + audio_buffer_(), buffer_mutex_(), + buffer_cv_(), buffer_size_(0), mp3_decoder_(nullptr), mp3_frame_info_(), + mp3_decoder_initialized_(false), playlist_(), playlist_mutex_(), + current_playlist_index_(-1), playlist_mode_(false), playlist_thread_() { + ESP_LOGI(TAG, "Music player initialized with default spectrum display mode"); + InitializeMp3Decoder(); +} + +Esp32Music::~Esp32Music() { + ESP_LOGI(TAG, "Destroying music player - stopping all operations"); + + // 停止所有操作 + is_downloading_ = false; + is_playing_ = false; + is_lyric_running_ = false; + playlist_mode_ = false; + + // 通知所有等待的线程 + { + std::lock_guard lock(buffer_mutex_); + buffer_cv_.notify_all(); + } + + // 等待下载线程结束,设置5秒超时 + if (download_thread_.joinable()) { + ESP_LOGI(TAG, "Waiting for download thread to finish (timeout: 5s)"); + auto start_time = std::chrono::steady_clock::now(); + + // 等待线程结束 + bool thread_finished = false; + while (!thread_finished) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + auto elapsed = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time).count(); + + if (elapsed >= 5) { + ESP_LOGW(TAG, "Download thread join timeout after 5 seconds"); + break; + } + + // 再次设置停止标志,确保线程能够检测到 + is_downloading_ = false; + + // 通知条件变量 + { + std::lock_guard lock(buffer_mutex_); + buffer_cv_.notify_all(); + } + + // 检查线程是否已经结束 + if (!download_thread_.joinable()) { + thread_finished = true; + } + + // 定期打印等待信息 + if (elapsed > 0 && elapsed % 1 == 0) { + ESP_LOGI(TAG, "Still waiting for download thread to finish... (%ds)", (int)elapsed); + } + } + + if (download_thread_.joinable()) { + download_thread_.join(); + } + ESP_LOGI(TAG, "Download thread finished"); + } + + // 等待播放线程结束,设置3秒超时 + if (play_thread_.joinable()) { + ESP_LOGI(TAG, "Waiting for playback thread to finish (timeout: 3s)"); + auto start_time = std::chrono::steady_clock::now(); + + bool thread_finished = false; + while (!thread_finished) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + auto elapsed = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time).count(); + + if (elapsed >= 3) { + ESP_LOGW(TAG, "Playback thread join timeout after 3 seconds"); + break; + } + + // 再次设置停止标志 + is_playing_ = false; + + // 通知条件变量 + { + std::lock_guard lock(buffer_mutex_); + buffer_cv_.notify_all(); + } + + // 检查线程是否已经结束 + if (!play_thread_.joinable()) { + thread_finished = true; + } + } + + if (play_thread_.joinable()) { + play_thread_.join(); + } + ESP_LOGI(TAG, "Playback thread finished"); + } + + // 等待歌词线程结束 + if (lyric_thread_.joinable()) { + ESP_LOGI(TAG, "Waiting for lyric thread to finish"); + lyric_thread_.join(); + ESP_LOGI(TAG, "Lyric thread finished"); + } + + // 等待播放队列线程结束 + if (playlist_thread_.joinable()) { + ESP_LOGI(TAG, "Waiting for playlist thread to finish"); + playlist_thread_.join(); + ESP_LOGI(TAG, "Playlist thread finished"); + } + + // 清理缓冲区和MP3解码器 + ClearAudioBuffer(); + CleanupMp3Decoder(); + + ESP_LOGI(TAG, "Music player destroyed successfully"); +} + +bool Esp32Music::Download(const std::string& song_name, const std::string& artist_name) { + ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供"); + ESP_LOGI(TAG, "喵波音律QQ交流群:865754861"); + ESP_LOGI(TAG, "Starting to get music details for: %s", song_name.c_str()); + + // 清空之前的下载数据 + last_downloaded_data_.clear(); + + // 保存歌名用于后续显示 + current_song_name_ = song_name; + + // 第一步:请求stream_pcm接口获取音频信息 + std::string base_url = MUSIC_SERVER_URL; + std::string full_url = base_url + "/stream_pcm?song=" + url_encode(song_name) + "&singer=" + url_encode(artist_name); + + ESP_LOGI(TAG, "Request URL: %s", full_url.c_str()); + + // 使用Board提供的HTTP客户端 + auto network = Board::GetInstance().GetNetwork(); + auto http = network->CreateHttp(0); + + // 设置基本请求头 + http->SetHeader("User-Agent", "ESP32-Music-Player/1.0"); + http->SetHeader("Accept", "application/json"); + + // 添加ESP32认证头 + add_auth_headers(http.get()); + + // 打开GET连接 + if (!http->Open("GET", full_url)) { + ESP_LOGE(TAG, "Failed to connect to music API"); + return false; + } + + // 检查响应状态码 + int status_code = http->GetStatusCode(); + if (status_code != 200) { + ESP_LOGE(TAG, "HTTP GET failed with status code: %d", status_code); + http->Close(); + return false; + } + + // 读取响应数据 + last_downloaded_data_ = http->ReadAll(); + http->Close(); + + ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d", status_code, last_downloaded_data_.length()); + ESP_LOGD(TAG, "Complete music details response: %s", last_downloaded_data_.c_str()); + + // 简单的认证响应检查(可选) + if (last_downloaded_data_.find("ESP32动态密钥验证失败") != std::string::npos) { + ESP_LOGE(TAG, "Authentication failed for song: %s", song_name.c_str()); + return false; + } + + if (!last_downloaded_data_.empty()) { + // 解析响应JSON以提取音频URL + cJSON* response_json = cJSON_Parse(last_downloaded_data_.c_str()); + if (response_json) { + // 提取关键信息 + cJSON* artist = cJSON_GetObjectItem(response_json, "artist"); + cJSON* title = cJSON_GetObjectItem(response_json, "title"); + cJSON* audio_url = cJSON_GetObjectItem(response_json, "audio_url"); + cJSON* lyric_url = cJSON_GetObjectItem(response_json, "lyric_url"); + cJSON* duration = cJSON_GetObjectItem(response_json, "duration"); + + if (cJSON_IsString(artist)) { + ESP_LOGI(TAG, "Artist: %s", artist->valuestring); + } + if (cJSON_IsString(title)) { + ESP_LOGI(TAG, "Title: %s", title->valuestring); + } + + // 解析歌曲总时长 + if (cJSON_IsNumber(duration)) { + current_song_duration_seconds_ = duration->valueint; + ESP_LOGI(TAG, "Song duration: %d seconds", current_song_duration_seconds_); + } else { + // 如果API没有返回时长,设置为0(未知) + current_song_duration_seconds_ = 0; + ESP_LOGW(TAG, "Song duration not available from API"); + } + + // 检查audio_url是否有效 + if (cJSON_IsString(audio_url) && audio_url->valuestring && strlen(audio_url->valuestring) > 0) { + ESP_LOGI(TAG, "Audio URL path: %s", audio_url->valuestring); + + // 第二步:直接使用audio_url播放音乐 + std::string audio_path = audio_url->valuestring; + current_music_url_ = audio_path; + + ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供"); + ESP_LOGI(TAG, "喵波音律QQ交流群:865754861"); + ESP_LOGI(TAG, "Starting streaming playback for: %s", song_name.c_str()); + song_name_displayed_ = false; // 重置歌名显示标志 + StartStreaming(current_music_url_); + + // 处理歌词URL - 只有在歌词显示模式下才启动歌词 + if (cJSON_IsString(lyric_url) && lyric_url->valuestring && strlen(lyric_url->valuestring) > 0) { + // 直接使用歌词URL + std::string lyric_path = lyric_url->valuestring; + current_lyric_url_ = lyric_path; + + // 根据显示模式决定是否启动歌词 + if (display_mode_ == DISPLAY_MODE_LYRICS) { + ESP_LOGI(TAG, "Loading lyrics for: %s (lyrics display mode)", song_name.c_str()); + + // 启动歌词下载和显示 + if (is_lyric_running_) { + is_lyric_running_ = false; + if (lyric_thread_.joinable()) { + lyric_thread_.join(); + } + } + + is_lyric_running_ = true; + current_lyric_index_ = -1; + lyrics_.clear(); + + lyric_thread_ = std::thread(&Esp32Music::LyricDisplayThread, this); + } else { + ESP_LOGI(TAG, "Lyric URL found but spectrum display mode is active, skipping lyrics"); + } + } else { + ESP_LOGW(TAG, "No lyric URL found for this song"); + } + + cJSON_Delete(response_json); + return true; + } else { + // audio_url为空或无效 + ESP_LOGE(TAG, "Audio URL not found or empty for song: %s", song_name.c_str()); + ESP_LOGE(TAG, "Failed to find music: 没有找到歌曲 '%s'", song_name.c_str()); + cJSON_Delete(response_json); + return false; + } + } else { + ESP_LOGE(TAG, "Failed to parse JSON response"); + } + } else { + ESP_LOGE(TAG, "Empty response from music API"); + } + + return false; +} + + + +std::string Esp32Music::GetDownloadResult() { + return last_downloaded_data_; +} + +// 开始流式播放 +bool Esp32Music::StartStreaming(const std::string& music_url) { + if (music_url.empty()) { + ESP_LOGE(TAG, "Music URL is empty"); + return false; + } + + ESP_LOGD(TAG, "Starting streaming for URL: %s", music_url.c_str()); + + // 停止之前的播放和下载 + is_downloading_ = false; + is_playing_ = false; + + // 等待之前的线程完全结束 + if (download_thread_.joinable()) { + { + std::lock_guard lock(buffer_mutex_); + buffer_cv_.notify_all(); // 通知线程退出 + } + download_thread_.join(); + } + if (play_thread_.joinable()) { + { + std::lock_guard lock(buffer_mutex_); + buffer_cv_.notify_all(); // 通知线程退出 + } + play_thread_.join(); + } + + // 清空缓冲区 + ClearAudioBuffer(); + + // 配置线程栈大小以避免栈溢出 + esp_pthread_cfg_t cfg = esp_pthread_get_default_config(); + cfg.stack_size = 8192; // 8KB栈大小 + cfg.prio = 5; // 中等优先级 + cfg.thread_name = "audio_stream"; + esp_pthread_set_cfg(&cfg); + + // 开始下载线程 + is_downloading_ = true; + download_thread_ = std::thread(&Esp32Music::DownloadAudioStream, this, music_url); + + // 开始播放线程(会等待缓冲区有足够数据) + is_playing_ = true; + play_thread_ = std::thread(&Esp32Music::PlayAudioStream, this); + + ESP_LOGI(TAG, "Streaming threads started successfully"); + + return true; +} + +// 停止流式播放 +bool Esp32Music::StopStreaming() { + ESP_LOGI(TAG, "Stopping music streaming - current state: downloading=%d, playing=%d", + is_downloading_.load(), is_playing_.load()); + + // 重置采样率到原始值 + ResetSampleRate(); + + // 检查是否有流式播放正在进行 + if (!is_playing_ && !is_downloading_) { + ESP_LOGW(TAG, "No streaming in progress"); + return true; + } + + // 停止下载和播放标志 + is_downloading_ = false; + is_playing_ = false; + + // 清空歌名显示 + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + if (display) { + display->SetMusicInfo(""); // 清空歌名显示 + ESP_LOGI(TAG, "Cleared song name display"); + } + + // 通知所有等待的线程 + { + std::lock_guard lock(buffer_mutex_); + buffer_cv_.notify_all(); + } + + // 等待线程结束(避免重复代码,让StopStreaming也能等待线程完全停止) + if (download_thread_.joinable()) { + download_thread_.join(); + ESP_LOGI(TAG, "Download thread joined in StopStreaming"); + } + + // 等待播放线程结束,使用更安全的方式 + if (play_thread_.joinable()) { + // 先设置停止标志 + is_playing_ = false; + + // 通知条件变量,确保线程能够退出 + { + std::lock_guard lock(buffer_mutex_); + buffer_cv_.notify_all(); + } + + // 使用超时机制等待线程结束,避免死锁 + bool thread_finished = false; + int wait_count = 0; + const int max_wait = 100; // 最多等待1秒 + + while (!thread_finished && wait_count < max_wait) { + vTaskDelay(pdMS_TO_TICKS(10)); + wait_count++; + + // 检查线程是否仍然可join + if (!play_thread_.joinable()) { + thread_finished = true; + break; + } + } + + if (play_thread_.joinable()) { + if (wait_count >= max_wait) { + ESP_LOGW(TAG, "Play thread join timeout, detaching thread"); + play_thread_.detach(); + } else { + play_thread_.join(); + ESP_LOGI(TAG, "Play thread joined in StopStreaming"); + } + } + } + + // 在线程完全结束后,只在频谱模式下停止FFT显示 + if (display && display_mode_ == DISPLAY_MODE_SPECTRUM) { + display->stopFft(); + ESP_LOGI(TAG, "Stopped FFT display in StopStreaming (spectrum mode)"); + } else if (display) { + ESP_LOGI(TAG, "Not in spectrum mode, skipping FFT stop in StopStreaming"); + } + + ESP_LOGI(TAG, "Music streaming stop signal sent"); + return true; +} + +// 流式下载音频数据 +void Esp32Music::DownloadAudioStream(const std::string& music_url) { + ESP_LOGD(TAG, "Starting audio stream download from: %s", music_url.c_str()); + + // 验证URL有效性 + if (music_url.empty() || music_url.find("http") != 0) { + ESP_LOGE(TAG, "Invalid URL format: %s", music_url.c_str()); + is_downloading_ = false; + return; + } + + auto network = Board::GetInstance().GetNetwork(); + auto http = network->CreateHttp(0); + + // 设置基本请求头 + http->SetHeader("User-Agent", "ESP32-Music-Player/1.0"); + http->SetHeader("Accept", "*/*"); + http->SetHeader("Range", "bytes=0-"); // 支持断点续传 + + // 添加ESP32认证头 + add_auth_headers(http.get()); + + if (!http->Open("GET", music_url)) { + ESP_LOGE(TAG, "Failed to connect to music stream URL"); + is_downloading_ = false; + return; + } + + int status_code = http->GetStatusCode(); + if (status_code != 200 && status_code != 206) { // 206 for partial content + ESP_LOGE(TAG, "HTTP GET failed with status code: %d", status_code); + http->Close(); + is_downloading_ = false; + return; + } + + ESP_LOGI(TAG, "Started downloading audio stream, status: %d", status_code); + + // 分块读取音频数据 + const size_t chunk_size = 4096; // 4KB每块 + char buffer[chunk_size]; + size_t total_downloaded = 0; + + while (is_downloading_ && is_playing_) { + int bytes_read = http->Read(buffer, chunk_size); + if (bytes_read < 0) { + ESP_LOGE(TAG, "Failed to read audio data: error code %d", bytes_read); + break; + } + if (bytes_read == 0) { + ESP_LOGI(TAG, "Audio stream download completed, total: %d bytes", total_downloaded); + break; + } + + // 打印数据块信息 + // ESP_LOGI(TAG, "Downloaded chunk: %d bytes at offset %d", bytes_read, total_downloaded); + + // 安全地打印数据块的十六进制内容(前16字节) + if (bytes_read >= 16) { + // ESP_LOGI(TAG, "Data: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X ...", + // (unsigned char)buffer[0], (unsigned char)buffer[1], (unsigned char)buffer[2], (unsigned char)buffer[3], + // (unsigned char)buffer[4], (unsigned char)buffer[5], (unsigned char)buffer[6], (unsigned char)buffer[7], + // (unsigned char)buffer[8], (unsigned char)buffer[9], (unsigned char)buffer[10], (unsigned char)buffer[11], + // (unsigned char)buffer[12], (unsigned char)buffer[13], (unsigned char)buffer[14], (unsigned char)buffer[15]); + } else { + ESP_LOGI(TAG, "Data chunk too small: %d bytes", bytes_read); + } + + // 尝试检测文件格式(检查文件头) + if (total_downloaded == 0 && bytes_read >= 4) { + if (memcmp(buffer, "ID3", 3) == 0) { + ESP_LOGI(TAG, "Detected MP3 file with ID3 tag"); + } else if (buffer[0] == 0xFF && (buffer[1] & 0xE0) == 0xE0) { + ESP_LOGI(TAG, "Detected MP3 file header"); + } else if (memcmp(buffer, "RIFF", 4) == 0) { + ESP_LOGI(TAG, "Detected WAV file"); + } else if (memcmp(buffer, "fLaC", 4) == 0) { + ESP_LOGI(TAG, "Detected FLAC file"); + } else if (memcmp(buffer, "OggS", 4) == 0) { + ESP_LOGI(TAG, "Detected OGG file"); + } else { + ESP_LOGI(TAG, "Unknown audio format, first 4 bytes: %02X %02X %02X %02X", + (unsigned char)buffer[0], (unsigned char)buffer[1], + (unsigned char)buffer[2], (unsigned char)buffer[3]); + } + } + + // 创建音频数据块 + uint8_t* chunk_data = (uint8_t*)heap_caps_malloc(bytes_read, MALLOC_CAP_SPIRAM); + if (!chunk_data) { + ESP_LOGE(TAG, "Failed to allocate memory for audio chunk"); + break; + } + memcpy(chunk_data, buffer, bytes_read); + + // 等待缓冲区有空间 + { + std::unique_lock lock(buffer_mutex_); + buffer_cv_.wait(lock, [this] { return buffer_size_ < MAX_BUFFER_SIZE || !is_downloading_; }); + + if (is_downloading_) { + audio_buffer_.push(AudioChunk(chunk_data, bytes_read)); + buffer_size_ += bytes_read; + total_downloaded += bytes_read; + + // 通知播放线程有新数据 + buffer_cv_.notify_one(); + + if (total_downloaded % (256 * 1024) == 0) { // 每256KB打印一次进度 + ESP_LOGI(TAG, "Downloaded %d bytes, buffer size: %d", total_downloaded, buffer_size_); + } + } else { + heap_caps_free(chunk_data); + break; + } + } + } + + http->Close(); + is_downloading_ = false; + + // 通知播放线程下载完成 + { + std::lock_guard lock(buffer_mutex_); + buffer_cv_.notify_all(); + } + + ESP_LOGI(TAG, "Audio stream download thread finished"); +} + +// 流式播放音频数据 +void Esp32Music::PlayAudioStream() { + ESP_LOGI(TAG, "Starting audio stream playback"); + + // 初始化时间跟踪变量 + current_play_time_ms_ = 0; + last_frame_time_ms_ = 0; + total_frames_decoded_ = 0; + + auto codec = Board::GetInstance().GetAudioCodec(); + if (!codec || !codec->output_enabled()) { + ESP_LOGE(TAG, "Audio codec not available or not enabled"); + is_playing_ = false; + return; + } + + if (!mp3_decoder_initialized_) { + ESP_LOGE(TAG, "MP3 decoder not initialized"); + is_playing_ = false; + return; + } + + + // 等待缓冲区有足够数据开始播放 + { + std::unique_lock lock(buffer_mutex_); + buffer_cv_.wait(lock, [this] { + return buffer_size_ >= MIN_BUFFER_SIZE || (!is_downloading_ && !audio_buffer_.empty()); + }); + } + + ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供"); + ESP_LOGI(TAG, "喵波音律QQ交流群:865754861"); + ESP_LOGI(TAG, "Starting playback with buffer size: %d", buffer_size_); + + size_t total_played = 0; + uint8_t* mp3_input_buffer = nullptr; + int bytes_left = 0; + uint8_t* read_ptr = nullptr; + + // 分配MP3输入缓冲区 + mp3_input_buffer = (uint8_t*)heap_caps_malloc(8192, MALLOC_CAP_SPIRAM); + if (!mp3_input_buffer) { + ESP_LOGE(TAG, "Failed to allocate MP3 input buffer"); + is_playing_ = false; + return; + } + + // 标记是否已经处理过ID3标签 + bool id3_processed = false; + + while (is_playing_) { + // 检查设备状态,只有在空闲状态才播放音乐 + auto& app = Application::GetInstance(); + DeviceState current_state = app.GetDeviceState(); + + // 状态转换:说话中-》聆听中-》待机状态-》播放音乐 + if (current_state == kDeviceStateListening || current_state == kDeviceStateSpeaking) { + if (current_state == kDeviceStateSpeaking) { + ESP_LOGI(TAG, "Device is in speaking state, switching to listening state for music playback"); + } + if (current_state == kDeviceStateListening) { + ESP_LOGI(TAG, "Device is in listening state, switching to idle state for music playback"); + } + // 切换状态 + app.ToggleChatState(); // 变成待机状态 + vTaskDelay(pdMS_TO_TICKS(300)); + continue; + } else if (current_state != kDeviceStateIdle) { // 不是待机状态,就一直卡在这里,不让播放音乐 + ESP_LOGD(TAG, "Device state is %d, pausing music playback", current_state); + // 如果不是空闲状态,暂停播放 + vTaskDelay(pdMS_TO_TICKS(50)); + continue; + } + + // 设备状态检查通过,显示当前播放的歌名 + if (!song_name_displayed_ && !current_song_name_.empty()) { + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + if (display) { + // 格式化歌名显示为《歌名》播放中... + std::string formatted_song_name = "《" + current_song_name_ + "》播放中..."; + display->SetMusicInfo(formatted_song_name.c_str()); + ESP_LOGI(TAG, "Displaying song name: %s", formatted_song_name.c_str()); + song_name_displayed_ = true; + } + + // 根据显示模式启动相应的显示功能 + if (display) { + if (display_mode_ == DISPLAY_MODE_SPECTRUM) { + display->start(); + ESP_LOGI(TAG, "Display start() called for spectrum visualization"); + } else { + ESP_LOGI(TAG, "Lyrics display mode active, FFT visualization disabled"); + } + } + } + + // 如果需要更多MP3数据,从缓冲区读取 + if (bytes_left < 4096) { // 保持至少4KB数据用于解码 + AudioChunk chunk; + + // 从缓冲区获取音频数据 + { + std::unique_lock lock(buffer_mutex_); + if (audio_buffer_.empty()) { + if (!is_downloading_) { + // 下载完成且缓冲区为空,播放结束 + ESP_LOGI(TAG, "Playback finished, total played: %d bytes", total_played); + break; + } + // 等待新数据 + buffer_cv_.wait(lock, [this] { return !audio_buffer_.empty() || !is_downloading_; }); + if (audio_buffer_.empty()) { + continue; + } + } + + chunk = audio_buffer_.front(); + audio_buffer_.pop(); + buffer_size_ -= chunk.size; + + // 通知下载线程缓冲区有空间 + buffer_cv_.notify_one(); + } + + // 将新数据添加到MP3输入缓冲区 + if (chunk.data && chunk.size > 0) { + // 移动剩余数据到缓冲区开头 + if (bytes_left > 0 && read_ptr != mp3_input_buffer) { + memmove(mp3_input_buffer, read_ptr, bytes_left); + } + + // 检查缓冲区空间 + size_t space_available = 8192 - bytes_left; + size_t copy_size = std::min(chunk.size, space_available); + + // 复制新数据 + memcpy(mp3_input_buffer + bytes_left, chunk.data, copy_size); + bytes_left += copy_size; + read_ptr = mp3_input_buffer; + + // 检查并跳过ID3标签(仅在开始时处理一次) + if (!id3_processed && bytes_left >= 10) { + size_t id3_skip = SkipId3Tag(read_ptr, bytes_left); + if (id3_skip > 0) { + read_ptr += id3_skip; + bytes_left -= id3_skip; + ESP_LOGI(TAG, "Skipped ID3 tag: %u bytes", (unsigned int)id3_skip); + } + id3_processed = true; + } + + // 释放chunk内存 + heap_caps_free(chunk.data); + } + } + + // 尝试找到MP3帧同步 + int sync_offset = MP3FindSyncWord(read_ptr, bytes_left); + if (sync_offset < 0) { + ESP_LOGW(TAG, "No MP3 sync word found, skipping %d bytes", bytes_left); + bytes_left = 0; + continue; + } + + // 跳过到同步位置 + if (sync_offset > 0) { + read_ptr += sync_offset; + bytes_left -= sync_offset; + } + + // 解码MP3帧 + int16_t pcm_buffer[2304]; + int decode_result = MP3Decode(mp3_decoder_, &read_ptr, &bytes_left, pcm_buffer, 0); + + if (decode_result == 0) { + // 解码成功,获取帧信息 + MP3GetLastFrameInfo(mp3_decoder_, &mp3_frame_info_); + total_frames_decoded_++; + + // 基本的帧信息有效性检查,防止除零错误 + if (mp3_frame_info_.samprate == 0 || mp3_frame_info_.nChans == 0) { + ESP_LOGW(TAG, "Invalid frame info: rate=%d, channels=%d, skipping", + mp3_frame_info_.samprate, mp3_frame_info_.nChans); + continue; + } + + // 计算当前帧的持续时间(毫秒) + int frame_duration_ms = (mp3_frame_info_.outputSamps * 1000) / + (mp3_frame_info_.samprate * mp3_frame_info_.nChans); + + // 更新当前播放时间 + current_play_time_ms_ += frame_duration_ms; + + ESP_LOGD(TAG, "Frame %d: time=%lldms, duration=%dms, rate=%d, ch=%d", + total_frames_decoded_, current_play_time_ms_, frame_duration_ms, + mp3_frame_info_.samprate, mp3_frame_info_.nChans); + + // 更新歌词显示 + int buffer_latency_ms = 600; // 实测调整值 + UpdateLyricDisplay(current_play_time_ms_ + buffer_latency_ms); + + // 将PCM数据发送到Application的音频解码队列 + if (mp3_frame_info_.outputSamps > 0) { + int16_t* final_pcm_data = pcm_buffer; + int final_sample_count = mp3_frame_info_.outputSamps; + std::vector mono_buffer; + + // 如果是双通道,转换为单通道混合 + if (mp3_frame_info_.nChans == 2) { + // 双通道转单通道:将左右声道混合 + int stereo_samples = mp3_frame_info_.outputSamps; // 包含左右声道的总样本数 + int mono_samples = stereo_samples / 2; // 实际的单声道样本数 + + mono_buffer.resize(mono_samples); + + for (int i = 0; i < mono_samples; ++i) { + // 混合左右声道 (L + R) / 2 + int left = pcm_buffer[i * 2]; // 左声道 + int right = pcm_buffer[i * 2 + 1]; // 右声道 + mono_buffer[i] = (int16_t)((left + right) / 2); + } + + final_pcm_data = mono_buffer.data(); + final_sample_count = mono_samples; + + ESP_LOGD(TAG, "Converted stereo to mono: %d -> %d samples", + stereo_samples, mono_samples); + } else if (mp3_frame_info_.nChans == 1) { + // 已经是单声道,无需转换 + ESP_LOGD(TAG, "Already mono audio: %d samples", final_sample_count); + } else { + ESP_LOGW(TAG, "Unsupported channel count: %d, treating as mono", + mp3_frame_info_.nChans); + } + + // 创建AudioStreamPacket + AudioStreamPacket packet; + packet.sample_rate = mp3_frame_info_.samprate; + packet.frame_duration = 60; // 使用Application默认的帧时长 + packet.timestamp = 0; + + // 将int16_t PCM数据转换为uint8_t字节数组 + size_t pcm_size_bytes = final_sample_count * sizeof(int16_t); + packet.payload.resize(pcm_size_bytes); + memcpy(packet.payload.data(), final_pcm_data, pcm_size_bytes); + + if (final_pcm_data_fft == nullptr) { + final_pcm_data_fft = (int16_t*)heap_caps_malloc( + final_sample_count * sizeof(int16_t), + MALLOC_CAP_SPIRAM + ); + } + + memcpy( + final_pcm_data_fft, + final_pcm_data, + final_sample_count * sizeof(int16_t) + ); + + ESP_LOGD(TAG, "Sending %d PCM samples (%d bytes, rate=%d, channels=%d->1) to Application", + final_sample_count, pcm_size_bytes, mp3_frame_info_.samprate, mp3_frame_info_.nChans); + + // 发送到Application的音频解码队列 + app.AddAudioData(std::move(packet)); + total_played += pcm_size_bytes; + + // 打印播放进度 + if (total_played % (128 * 1024) == 0) { + ESP_LOGI(TAG, "Played %d bytes, buffer size: %d", total_played, buffer_size_); + } + } + + } else { + // 解码失败 + ESP_LOGW(TAG, "MP3 decode failed with error: %d", decode_result); + + // 跳过一些字节继续尝试 + if (bytes_left > 1) { + read_ptr++; + bytes_left--; + } else { + bytes_left = 0; + } + } + } + + // 清理 + if (mp3_input_buffer) { + heap_caps_free(mp3_input_buffer); + } + + // 播放结束时进行基本清理,但不调用StopStreaming避免线程自我等待 + ESP_LOGI(TAG, "Audio stream playback finished, total played: %d bytes", total_played); + ESP_LOGI(TAG, "Performing basic cleanup from play thread"); + + // 停止播放标志 + is_playing_ = false; + + // 只在频谱显示模式下才停止FFT显示 + if (display_mode_ == DISPLAY_MODE_SPECTRUM) { + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + if (display) { + display->stopFft(); + ESP_LOGI(TAG, "Stopped FFT display from play thread (spectrum mode)"); + } + } else { + ESP_LOGI(TAG, "Not in spectrum mode, skipping FFT stop"); + } +} + +// 清空音频缓冲区 +void Esp32Music::ClearAudioBuffer() { + std::lock_guard lock(buffer_mutex_); + + while (!audio_buffer_.empty()) { + AudioChunk chunk = audio_buffer_.front(); + audio_buffer_.pop(); + if (chunk.data) { + heap_caps_free(chunk.data); + } + } + + buffer_size_ = 0; + ESP_LOGI(TAG, "Audio buffer cleared"); +} + +// 初始化MP3解码器 +bool Esp32Music::InitializeMp3Decoder() { + mp3_decoder_ = MP3InitDecoder(); + if (mp3_decoder_ == nullptr) { + ESP_LOGE(TAG, "Failed to initialize MP3 decoder"); + mp3_decoder_initialized_ = false; + return false; + } + + mp3_decoder_initialized_ = true; + ESP_LOGI(TAG, "MP3 decoder initialized successfully"); + return true; +} + +// 清理MP3解码器 +void Esp32Music::CleanupMp3Decoder() { + if (mp3_decoder_ != nullptr) { + MP3FreeDecoder(mp3_decoder_); + mp3_decoder_ = nullptr; + } + mp3_decoder_initialized_ = false; + ESP_LOGI(TAG, "MP3 decoder cleaned up"); +} + +// 重置采样率到原始值 +void Esp32Music::ResetSampleRate() { + auto& board = Board::GetInstance(); + auto codec = board.GetAudioCodec(); + if (codec && codec->original_output_sample_rate() > 0 && + codec->output_sample_rate() != codec->original_output_sample_rate()) { + ESP_LOGI(TAG, "重置采样率:从 %d Hz 重置到原始值 %d Hz", + codec->output_sample_rate(), codec->original_output_sample_rate()); + if (codec->SetOutputSampleRate(-1)) { // -1 表示重置到原始值 + ESP_LOGI(TAG, "成功重置采样率到原始值: %d Hz", codec->output_sample_rate()); + } else { + ESP_LOGW(TAG, "无法重置采样率到原始值"); + } + } +} + +// 跳过MP3文件开头的ID3标签 +size_t Esp32Music::SkipId3Tag(uint8_t* data, size_t size) { + if (!data || size < 10) { + return 0; + } + + // 检查ID3v2标签头 "ID3" + if (memcmp(data, "ID3", 3) != 0) { + return 0; + } + + // 计算标签大小(synchsafe integer格式) + uint32_t tag_size = ((uint32_t)(data[6] & 0x7F) << 21) | + ((uint32_t)(data[7] & 0x7F) << 14) | + ((uint32_t)(data[8] & 0x7F) << 7) | + ((uint32_t)(data[9] & 0x7F)); + + // ID3v2头部(10字节) + 标签内容 + size_t total_skip = 10 + tag_size; + + // 确保不超过可用数据大小 + if (total_skip > size) { + total_skip = size; + } + + ESP_LOGI(TAG, "Found ID3v2 tag, skipping %u bytes", (unsigned int)total_skip); + return total_skip; +} + +// 下载歌词 +bool Esp32Music::DownloadLyrics(const std::string& lyric_url) { + ESP_LOGI(TAG, "Downloading lyrics from: %s", lyric_url.c_str()); + + // 检查URL是否为空 + if (lyric_url.empty()) { + ESP_LOGE(TAG, "Lyric URL is empty!"); + return false; + } + + // 添加重试逻辑 + const int max_retries = 3; + int retry_count = 0; + bool success = false; + std::string lyric_content; + std::string current_url = lyric_url; + int redirect_count = 0; + const int max_redirects = 5; // 最多允许5次重定向 + + while (retry_count < max_retries && !success && redirect_count < max_redirects) { + if (retry_count > 0) { + ESP_LOGI(TAG, "Retrying lyric download (attempt %d of %d)", retry_count + 1, max_retries); + // 重试前暂停一下 + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + + // 使用Board提供的HTTP客户端 + auto network = Board::GetInstance().GetNetwork(); + auto http = network->CreateHttp(0); + if (!http) { + ESP_LOGE(TAG, "Failed to create HTTP client for lyric download"); + retry_count++; + continue; + } + + // 设置基本请求头 + http->SetHeader("User-Agent", "ESP32-Music-Player/1.0"); + http->SetHeader("Accept", "text/plain"); + + // 添加ESP32认证头 + add_auth_headers(http.get()); + + // 打开GET连接 + ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供"); + ESP_LOGI(TAG, "喵波音律QQ交流群:865754861"); + if (!http->Open("GET", current_url)) { + ESP_LOGE(TAG, "Failed to open HTTP connection for lyrics"); + // 移除delete http; 因为unique_ptr会自动管理内存 + retry_count++; + continue; + } + + // 检查HTTP状态码 + int status_code = http->GetStatusCode(); + ESP_LOGI(TAG, "Lyric download HTTP status code: %d", status_code); + + // 处理重定向 - 由于Http类没有GetHeader方法,我们只能根据状态码判断 + if (status_code == 301 || status_code == 302 || status_code == 303 || status_code == 307 || status_code == 308) { + // 由于无法获取Location头,只能报告重定向但无法继续 + ESP_LOGW(TAG, "Received redirect status %d but cannot follow redirect (no GetHeader method)", status_code); + http->Close(); + retry_count++; + continue; + } + + // 非200系列状态码视为错误 + if (status_code < 200 || status_code >= 300) { + ESP_LOGE(TAG, "HTTP GET failed with status code: %d", status_code); + http->Close(); + retry_count++; + continue; + } + + // 读取响应 + lyric_content.clear(); + char buffer[1024]; + int bytes_read; + bool read_error = false; + int total_read = 0; + + // 由于无法获取Content-Length和Content-Type头,我们不知道预期大小和内容类型 + ESP_LOGD(TAG, "Starting to read lyric content"); + + while (true) { + bytes_read = http->Read(buffer, sizeof(buffer) - 1); + // ESP_LOGD(TAG, "Lyric HTTP read returned %d bytes", bytes_read); // 注释掉以减少日志输出 + + if (bytes_read > 0) { + buffer[bytes_read] = '\0'; + lyric_content += buffer; + total_read += bytes_read; + + // 定期打印下载进度 - 改为DEBUG级别减少输出 + if (total_read % 4096 == 0) { + ESP_LOGD(TAG, "Downloaded %d bytes so far", total_read); + } + } else if (bytes_read == 0) { + // 正常结束,没有更多数据 + ESP_LOGD(TAG, "Lyric download completed, total bytes: %d", total_read); + success = true; + break; + } else { + // bytes_read < 0,可能是ESP-IDF的已知问题 + // 如果已经读取到了一些数据,则认为下载成功 + if (!lyric_content.empty()) { + ESP_LOGW(TAG, "HTTP read returned %d, but we have data (%d bytes), continuing", bytes_read, lyric_content.length()); + success = true; + break; + } else { + ESP_LOGE(TAG, "Failed to read lyric data: error code %d", bytes_read); + read_error = true; + break; + } + } + } + + http->Close(); + + if (read_error) { + retry_count++; + continue; + } + + // 如果成功读取数据,跳出重试循环 + if (success) { + break; + } + } + + // 检查是否超过了最大重试次数 + if (retry_count >= max_retries) { + ESP_LOGE(TAG, "Failed to download lyrics after %d attempts", max_retries); + return false; + } + + // 记录前几个字节的数据,帮助调试 + if (!lyric_content.empty()) { + size_t preview_size = std::min(lyric_content.size(), size_t(50)); + std::string preview = lyric_content.substr(0, preview_size); + ESP_LOGD(TAG, "Lyric content preview (%d bytes): %s", lyric_content.length(), preview.c_str()); + } else { + ESP_LOGE(TAG, "Failed to download lyrics or lyrics are empty"); + return false; + } + + ESP_LOGI(TAG, "Lyrics downloaded successfully, size: %d bytes", lyric_content.length()); + return ParseLyrics(lyric_content); +} + +// 解析歌词 +bool Esp32Music::ParseLyrics(const std::string& lyric_content) { + ESP_LOGI(TAG, "Parsing lyrics content"); + + // 使用锁保护lyrics_数组访问 + std::lock_guard lock(lyrics_mutex_); + + lyrics_.clear(); + + // 按行分割歌词内容 + std::istringstream stream(lyric_content); + std::string line; + + while (std::getline(stream, line)) { + // 去除行尾的回车符 + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + + // 跳过空行 + if (line.empty()) { + continue; + } + + // 解析LRC格式: [mm:ss.xx]歌词文本 + if (line.length() > 10 && line[0] == '[') { + size_t close_bracket = line.find(']'); + if (close_bracket != std::string::npos) { + std::string tag_or_time = line.substr(1, close_bracket - 1); + std::string content = line.substr(close_bracket + 1); + + // 检查是否是元数据标签而不是时间戳 + // 元数据标签通常是 [ti:标题], [ar:艺术家], [al:专辑] 等 + size_t colon_pos = tag_or_time.find(':'); + if (colon_pos != std::string::npos) { + std::string left_part = tag_or_time.substr(0, colon_pos); + + // 检查冒号左边是否是时间(数字) + bool is_time_format = true; + for (char c : left_part) { + if (!isdigit(c)) { + is_time_format = false; + break; + } + } + + // 如果不是时间格式,跳过这一行(元数据标签) + if (!is_time_format) { + // 可以在这里处理元数据,例如提取标题、艺术家等信息 + ESP_LOGD(TAG, "Skipping metadata tag: [%s]", tag_or_time.c_str()); + continue; + } + + // 是时间格式,解析时间戳 + try { + int minutes = std::stoi(tag_or_time.substr(0, colon_pos)); + float seconds = std::stof(tag_or_time.substr(colon_pos + 1)); + int timestamp_ms = minutes * 60 * 1000 + (int)(seconds * 1000); + + // 安全处理歌词文本,确保UTF-8编码正确 + std::string safe_lyric_text; + if (!content.empty()) { + // 创建安全副本并验证字符串 + safe_lyric_text = content; + // 确保字符串以null结尾 + safe_lyric_text.shrink_to_fit(); + } + + lyrics_.push_back(std::make_pair(timestamp_ms, safe_lyric_text)); + + if (!safe_lyric_text.empty()) { + // 限制日志输出长度,避免中文字符截断问题 + size_t log_len = std::min(safe_lyric_text.length(), size_t(50)); + std::string log_text = safe_lyric_text.substr(0, log_len); + ESP_LOGD(TAG, "Parsed lyric: [%d ms] %s", timestamp_ms, log_text.c_str()); + } else { + ESP_LOGD(TAG, "Parsed lyric: [%d ms] (empty)", timestamp_ms); + } + } catch (const std::exception& e) { + ESP_LOGW(TAG, "Failed to parse time: %s", tag_or_time.c_str()); + } + } + } + } + } + + // 按时间戳排序 + std::sort(lyrics_.begin(), lyrics_.end()); + + ESP_LOGI(TAG, "Parsed %d lyric lines", lyrics_.size()); + return !lyrics_.empty(); +} + +// 歌词显示线程 +void Esp32Music::LyricDisplayThread() { + ESP_LOGI(TAG, "Lyric display thread started"); + + if (!DownloadLyrics(current_lyric_url_)) { + ESP_LOGE(TAG, "Failed to download or parse lyrics"); + is_lyric_running_ = false; + return; + } + + // 定期检查是否需要更新显示(频率可以降低) + while (is_lyric_running_ && is_playing_) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + ESP_LOGI(TAG, "Lyric display thread finished"); +} + +void Esp32Music::UpdateLyricDisplay(int64_t current_time_ms) { + std::lock_guard lock(lyrics_mutex_); + + if (lyrics_.empty()) { + return; + } + + // 查找当前应该显示的歌词 + int new_lyric_index = -1; + + // 从当前歌词索引开始查找,提高效率 + int start_index = (current_lyric_index_.load() >= 0) ? current_lyric_index_.load() : 0; + + // 正向查找:找到最后一个时间戳小于等于当前时间的歌词 + for (int i = start_index; i < (int)lyrics_.size(); i++) { + if (lyrics_[i].first <= current_time_ms) { + new_lyric_index = i; + } else { + break; // 时间戳已超过当前时间 + } + } + + // 如果没有找到(可能当前时间比第一句歌词还早),显示空 + if (new_lyric_index == -1) { + new_lyric_index = -1; + } + + // 如果歌词索引发生变化,更新显示 + if (new_lyric_index != current_lyric_index_) { + current_lyric_index_ = new_lyric_index; + + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + if (display) { + std::string lyric_text; + + if (current_lyric_index_ >= 0 && current_lyric_index_ < (int)lyrics_.size()) { + lyric_text = lyrics_[current_lyric_index_].second; + } + + // 显示歌词 + display->SetChatMessage("lyric", lyric_text.c_str()); + + ESP_LOGD(TAG, "Lyric update at %lldms: %s", + current_time_ms, + lyric_text.empty() ? "(no lyric)" : lyric_text.c_str()); + } + } +} + +// 删除复杂的认证初始化方法,使用简单的静态函数 + +// 删除复杂的类方法,使用简单的静态函数 + +/** + * @brief 添加认证头到HTTP请求 + * @param http_client HTTP客户端指针 + * + * 添加的认证头包括: + * - X-MAC-Address: 设备MAC地址 + * - X-Chip-ID: 设备芯片ID + * - X-Timestamp: 当前时间戳 + * - X-Dynamic-Key: 动态生成的密钥 + */ +// 删除复杂的AddAuthHeaders方法,使用简单的静态函数 + +// 删除复杂的认证验证和配置方法,使用简单的静态函数 + +// 显示模式控制方法实现 +void Esp32Music::SetDisplayMode(DisplayMode mode) { + DisplayMode old_mode = display_mode_.load(); + display_mode_ = mode; + + ESP_LOGI(TAG, "Display mode changed from %s to %s", + (old_mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS", + (mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS"); +} + +// ========== 播放队列功能实现 ========== + +bool Esp32Music::PlayPlaylist(const std::vector& songs) { + if (songs.empty()) { + ESP_LOGW(TAG, "Playlist is empty"); + return false; + } + + ESP_LOGI(TAG, "Starting playlist with %d songs", (int)songs.size()); + + // 停止当前播放 + StopPlaylist(); + + // 设置播放队列 + { + std::lock_guard lock(playlist_mutex_); + playlist_ = songs; + current_playlist_index_ = 0; + playlist_mode_ = true; + } + + // 启动播放队列管理线程 + playlist_thread_ = std::thread(&Esp32Music::PlaylistManagerThread, this); + + return true; +} + +bool Esp32Music::NextSong() { + if (!playlist_mode_.load()) { + ESP_LOGW(TAG, "Not in playlist mode"); + return false; + } + + std::lock_guard lock(playlist_mutex_); + if (current_playlist_index_ + 1 < (int)playlist_.size()) { + current_playlist_index_++; + ESP_LOGI(TAG, "Moving to next song: %d/%d", current_playlist_index_ + 1, (int)playlist_.size()); + return true; + } else { + ESP_LOGI(TAG, "Reached end of playlist"); + return false; + } +} + +bool Esp32Music::PreviousSong() { + if (!playlist_mode_.load()) { + ESP_LOGW(TAG, "Not in playlist mode"); + return false; + } + + std::lock_guard lock(playlist_mutex_); + if (current_playlist_index_ > 0) { + current_playlist_index_--; + ESP_LOGI(TAG, "Moving to previous song: %d/%d", current_playlist_index_ + 1, (int)playlist_.size()); + return true; + } else { + ESP_LOGI(TAG, "Already at first song"); + return false; + } +} + +void Esp32Music::StopPlaylist() { + ESP_LOGI(TAG, "Stopping playlist"); + + playlist_mode_ = false; + + // 停止当前播放 + StopStreaming(); + + // 等待播放队列线程结束 + if (playlist_thread_.joinable()) { + playlist_thread_.join(); + } + + // 清空播放队列 + { + std::lock_guard lock(playlist_mutex_); + playlist_.clear(); + current_playlist_index_ = -1; + } +} + +size_t Esp32Music::GetPlaylistSize() const { + std::lock_guard lock(playlist_mutex_); + return playlist_.size(); +} + +SongInfo Esp32Music::GetCurrentSong() const { + std::lock_guard lock(playlist_mutex_); + if (current_playlist_index_ >= 0 && current_playlist_index_ < (int)playlist_.size()) { + return playlist_[current_playlist_index_]; + } + return SongInfo(); +} + +void Esp32Music::PlaylistManagerThread() { + ESP_LOGI(TAG, "Playlist manager thread started"); + + while (playlist_mode_.load()) { + SongInfo current_song; + + // 获取当前要播放的歌曲 + { + std::lock_guard lock(playlist_mutex_); + if (current_playlist_index_ >= 0 && current_playlist_index_ < (int)playlist_.size()) { + current_song = playlist_[current_playlist_index_]; + } else { + ESP_LOGI(TAG, "Playlist finished"); + break; + } + } + + // 播放当前歌曲 + ESP_LOGI(TAG, "Playing song %d/%d: %s - %s", + current_playlist_index_ + 1, (int)GetPlaylistSize(), + current_song.title.c_str(), current_song.artist.c_str()); + + PlayCurrentSong(); + + // 等待当前歌曲播放完成 + while (playlist_mode_.load() && (is_playing_.load() || is_downloading_.load())) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + + if (!playlist_mode_.load()) { + break; + } + + // 自动播放下一首 + if (!NextSong()) { + ESP_LOGI(TAG, "Playlist completed"); + break; + } + } + + ESP_LOGI(TAG, "Playlist manager thread finished"); + playlist_mode_ = false; +} + +void Esp32Music::PlayCurrentSong() { + SongInfo song = GetCurrentSong(); + if (song.title.empty()) { + ESP_LOGE(TAG, "No current song to play"); + return; + } + + // 调用现有的Download方法播放歌曲 + if (!Download(song.title, song.artist)) { + ESP_LOGE(TAG, "Failed to play song: %s - %s", song.title.c_str(), song.artist.c_str()); + } } \ No newline at end of file diff --git a/main/boards/common/esp32_music.h b/main/boards/common/esp32_music.h index d4b4415..8a789d3 100644 --- a/main/boards/common/esp32_music.h +++ b/main/boards/common/esp32_music.h @@ -1,121 +1,151 @@ -#ifndef ESP32_MUSIC_H -#define ESP32_MUSIC_H - -#include -#include -#include -#include -#include -#include -#include - -#include "music.h" - -// MP3解码器支持 -extern "C" { -#include "mp3dec.h" -} - -// 音频数据块结构 -struct AudioChunk { - uint8_t* data; - size_t size; - - AudioChunk() : data(nullptr), size(0) {} - AudioChunk(uint8_t* d, size_t s) : data(d), size(s) {} -}; - -class Esp32Music : public Music { -public: - // 显示模式控制 - 移动到public区域 - enum DisplayMode { - DISPLAY_MODE_SPECTRUM = 0, // 默认显示频谱 - DISPLAY_MODE_LYRICS = 1 // 显示歌词 - }; - -private: - std::string last_downloaded_data_; - std::string current_music_url_; - std::string current_song_name_; - bool song_name_displayed_; - - // 歌词相关 - std::string current_lyric_url_; - std::vector> lyrics_; // 时间戳和歌词文本 - std::mutex lyrics_mutex_; // 保护lyrics_数组的互斥锁 - std::atomic current_lyric_index_; - std::thread lyric_thread_; - std::atomic is_lyric_running_; - - std::atomic display_mode_; - std::atomic is_playing_; - std::atomic is_downloading_; - std::atomic is_paused_; - std::thread play_thread_; - std::thread download_thread_; - int64_t current_play_time_ms_; // 当前播放时间(毫秒) - int64_t last_frame_time_ms_; // 上一帧的时间戳 - int total_frames_decoded_; // 已解码的帧数 - - // 音频缓冲区 - std::queue audio_buffer_; - std::mutex buffer_mutex_; - std::condition_variable buffer_cv_; - size_t buffer_size_; - static constexpr size_t MAX_BUFFER_SIZE = 256 * 1024; // 256KB缓冲区(降低以减少brownout风险) - static constexpr size_t MIN_BUFFER_SIZE = 32 * 1024; // 32KB最小播放缓冲(降低以减少brownout风险) - - // MP3解码器相关 - HMP3Decoder mp3_decoder_; - MP3FrameInfo mp3_frame_info_; - bool mp3_decoder_initialized_; - - // 私有方法 - void DownloadAudioStream(const std::string& music_url); - void PlayAudioStream(); - void ClearAudioBuffer(); - bool InitializeMp3Decoder(); - void CleanupMp3Decoder(); - void ResetSampleRate(); // 重置采样率到原始值 - - // 歌词相关私有方法 - bool DownloadLyrics(const std::string& lyric_url); - bool ParseLyrics(const std::string& lyric_content); - void LyricDisplayThread(); - void UpdateLyricDisplay(int64_t current_time_ms); - - // ID3标签处理 - size_t SkipId3Tag(uint8_t* data, size_t size); - - int16_t* final_pcm_data_fft = nullptr; - -public: - Esp32Music(); - ~Esp32Music(); - - virtual bool Download(const std::string& song_name, const std::string& artist_name) override; - - virtual std::string GetDownloadResult() override; - - // 新增方法 - virtual bool StartStreaming(const std::string& music_url) override; - virtual bool StopStreaming() override; // 停止流式播放 - virtual size_t GetBufferSize() const override { return buffer_size_; } - virtual bool IsDownloading() const override { return is_downloading_; } - virtual bool IsPlaying() const override { return is_playing_; } - virtual bool IsPaused() const override { return is_paused_; } - virtual int16_t* GetAudioData() override { return final_pcm_data_fft; } - - // 显示模式控制方法 - void SetDisplayMode(DisplayMode mode); - DisplayMode GetDisplayMode() const { return display_mode_.load(); } - - // MCP工具需要的方法 - virtual bool PlaySong() override; - virtual bool SetVolume(int volume) override; - virtual bool StopSong() override; - virtual bool PauseSong() override; - virtual bool ResumeSong() override; -}; - +#ifndef ESP32_MUSIC_H +#define ESP32_MUSIC_H + +#include +#include +#include +#include +#include +#include +#include + +#include "music.h" + +// MP3解码器支持 +extern "C" { +#include "mp3dec.h" +} + +// 音频数据块结构 +struct AudioChunk { + uint8_t* data; + size_t size; + + AudioChunk() : data(nullptr), size(0) {} + AudioChunk(uint8_t* d, size_t s) : data(d), size(s) {} +}; + +// 歌曲信息结构 +struct SongInfo { + std::string title; + std::string artist; + + SongInfo() : title(""), artist("") {} + SongInfo(const std::string& t, const std::string& a) : title(t), artist(a) {} +}; + +class Esp32Music : public Music { +public: + // 显示模式控制 - 移动到public区域 + enum DisplayMode { + DISPLAY_MODE_SPECTRUM = 0, // 默认显示频谱 + DISPLAY_MODE_LYRICS = 1 // 显示歌词 + }; + +private: + std::string last_downloaded_data_; + std::string current_music_url_; + std::string current_song_name_; + bool song_name_displayed_; + std::atomic stop_flag_{false}; // 停止播放标志位 + + // 歌词相关 + std::string current_lyric_url_; + std::vector> lyrics_; // 时间戳和歌词文本 + std::mutex lyrics_mutex_; // 保护lyrics_数组的互斥锁 + std::atomic current_lyric_index_; + std::thread lyric_thread_; + std::atomic is_lyric_running_; + + std::atomic display_mode_; + std::atomic is_playing_; + std::atomic is_downloading_; + std::thread play_thread_; + std::thread download_thread_; + int64_t current_play_time_ms_; // 当前播放时间(毫秒) + int64_t last_frame_time_ms_; // 上一帧的时间戳 + int total_frames_decoded_; // 已解码的帧数 + int current_song_duration_seconds_; // 当前歌曲总时长(秒) + + // 音频缓冲区 + std::queue audio_buffer_; + std::mutex buffer_mutex_; + std::condition_variable buffer_cv_; + size_t buffer_size_; + static constexpr size_t MAX_BUFFER_SIZE = 256 * 1024; // 256KB缓冲区(降低以减少brownout风险) + static constexpr size_t MIN_BUFFER_SIZE = 32 * 1024; // 32KB最小播放缓冲(降低以减少brownout风险) + + // MP3解码器相关 + HMP3Decoder mp3_decoder_; + MP3FrameInfo mp3_frame_info_; + bool mp3_decoder_initialized_; + + // 播放队列相关 + std::vector playlist_; + mutable std::mutex playlist_mutex_; + std::atomic current_playlist_index_; + std::atomic playlist_mode_; + std::thread playlist_thread_; + + // 私有方法 + void DownloadAudioStream(const std::string& music_url); + void PlayAudioStream(); + void ClearAudioBuffer(); + bool InitializeMp3Decoder(); + void CleanupMp3Decoder(); + void ResetSampleRate(); // 重置采样率到原始值 + + // 歌词相关私有方法 + bool DownloadLyrics(const std::string& lyric_url); + bool ParseLyrics(const std::string& lyric_content); + void LyricDisplayThread(); + void UpdateLyricDisplay(int64_t current_time_ms); + + // ID3标签处理 + size_t SkipId3Tag(uint8_t* data, size_t size); + + // 播放队列管理私有方法 + void PlaylistManagerThread(); + void PlayCurrentSong(); + + int16_t* final_pcm_data_fft = nullptr; + +public: + Esp32Music(); + ~Esp32Music(); + + virtual bool Download(const std::string& song_name, const std::string& artist_name) override; + + virtual std::string GetDownloadResult() override; + + // 新增方法 + virtual bool StartStreaming(const std::string& music_url) override; + virtual bool StopStreaming() override; // 停止流式播放 + virtual size_t GetBufferSize() const override { return buffer_size_; } + virtual bool IsDownloading() const override { return is_downloading_; } + virtual int16_t* GetAudioData() override { return final_pcm_data_fft; } + + // 显示模式控制方法 + void SetDisplayMode(DisplayMode mode); + DisplayMode GetDisplayMode() const { return display_mode_.load(); } + + // 音乐播放信息获取方法 + virtual int GetCurrentSongDurationSeconds() const override { return current_song_duration_seconds_; } + virtual int GetCurrentPlayTimeSeconds() const override { return (int)(current_play_time_ms_ / 1000); } + virtual float GetPlayProgress() const override { + if (current_song_duration_seconds_ <= 0) return 0.0f; + return (float)(current_play_time_ms_ / 1000) / current_song_duration_seconds_ * 100.0f; + } + + // 播放队列相关方法 + virtual bool PlayPlaylist(const std::vector& songs) override; + virtual bool NextSong() override; + virtual bool PreviousSong() override; + virtual void StopPlaylist() override; + virtual bool IsPlaylistMode() const override { return playlist_mode_.load(); } + virtual int GetCurrentPlaylistIndex() const override { return current_playlist_index_.load(); } + virtual size_t GetPlaylistSize() const override; + virtual SongInfo GetCurrentSong() const override; +}; + #endif // ESP32_MUSIC_H \ No newline at end of file diff --git a/main/boards/common/i2c_device.cc b/main/boards/common/i2c_device.cc index 835997d..7c9ba86 100644 --- a/main/boards/common/i2c_device.cc +++ b/main/boards/common/i2c_device.cc @@ -1,35 +1,35 @@ -#include "i2c_device.h" - -#include - -#define TAG "I2cDevice" - - -I2cDevice::I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr) { - i2c_device_config_t i2c_device_cfg = { - .dev_addr_length = I2C_ADDR_BIT_LEN_7, - .device_address = addr, - .scl_speed_hz = 400 * 1000, - .scl_wait_us = 0, - .flags = { - .disable_ack_check = 0, - }, - }; - ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus, &i2c_device_cfg, &i2c_device_)); - assert(i2c_device_ != NULL); -} - -void I2cDevice::WriteReg(uint8_t reg, uint8_t value) { - uint8_t buffer[2] = {reg, value}; - ESP_ERROR_CHECK(i2c_master_transmit(i2c_device_, buffer, 2, 100)); -} - -uint8_t I2cDevice::ReadReg(uint8_t reg) { - uint8_t buffer[1]; - ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, 1, 100)); - return buffer[0]; -} - -void I2cDevice::ReadRegs(uint8_t reg, uint8_t* buffer, size_t length) { - ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, length, 100)); +#include "i2c_device.h" + +#include + +#define TAG "I2cDevice" + + +I2cDevice::I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr) { + i2c_device_config_t i2c_device_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = addr, + .scl_speed_hz = 400 * 1000, + .scl_wait_us = 0, + .flags = { + .disable_ack_check = 0, + }, + }; + ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus, &i2c_device_cfg, &i2c_device_)); + assert(i2c_device_ != NULL); +} + +void I2cDevice::WriteReg(uint8_t reg, uint8_t value) { + uint8_t buffer[2] = {reg, value}; + ESP_ERROR_CHECK(i2c_master_transmit(i2c_device_, buffer, 2, 100)); +} + +uint8_t I2cDevice::ReadReg(uint8_t reg) { + uint8_t buffer[1]; + ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, 1, 100)); + return buffer[0]; +} + +void I2cDevice::ReadRegs(uint8_t reg, uint8_t* buffer, size_t length) { + ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, length, 100)); } \ No newline at end of file diff --git a/main/boards/common/i2c_device.h b/main/boards/common/i2c_device.h index 7bc917b..f148bee 100644 --- a/main/boards/common/i2c_device.h +++ b/main/boards/common/i2c_device.h @@ -1,18 +1,18 @@ -#ifndef I2C_DEVICE_H -#define I2C_DEVICE_H - -#include - -class I2cDevice { -public: - I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr); - -protected: - i2c_master_dev_handle_t i2c_device_; - - void WriteReg(uint8_t reg, uint8_t value); - uint8_t ReadReg(uint8_t reg); - void ReadRegs(uint8_t reg, uint8_t* buffer, size_t length); -}; - -#endif // I2C_DEVICE_H +#ifndef I2C_DEVICE_H +#define I2C_DEVICE_H + +#include + +class I2cDevice { +public: + I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr); + +protected: + i2c_master_dev_handle_t i2c_device_; + + void WriteReg(uint8_t reg, uint8_t value); + uint8_t ReadReg(uint8_t reg); + void ReadRegs(uint8_t reg, uint8_t* buffer, size_t length); +}; + +#endif // I2C_DEVICE_H diff --git a/main/boards/common/knob.cc b/main/boards/common/knob.cc index 350fda2..fda00ba 100644 --- a/main/boards/common/knob.cc +++ b/main/boards/common/knob.cc @@ -1,52 +1,52 @@ -#include "knob.h" - -static const char* TAG = "Knob"; - -Knob::Knob(gpio_num_t pin_a, gpio_num_t pin_b) { - knob_config_t config = { - .default_direction = 0, - .gpio_encoder_a = static_cast(pin_a), - .gpio_encoder_b = static_cast(pin_b), - }; - - esp_err_t err = ESP_OK; - knob_handle_ = iot_knob_create(&config); - if (knob_handle_ == NULL) { - ESP_LOGE(TAG, "Failed to create knob instance"); - return; - } - - err = iot_knob_register_cb(knob_handle_, KNOB_LEFT, knob_callback, this); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to register left callback: %s", esp_err_to_name(err)); - return; - } - - err = iot_knob_register_cb(knob_handle_, KNOB_RIGHT, knob_callback, this); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to register right callback: %s", esp_err_to_name(err)); - return; - } - - ESP_LOGI(TAG, "Knob initialized with pins A:%d B:%d", pin_a, pin_b); -} - -Knob::~Knob() { - if (knob_handle_ != NULL) { - iot_knob_delete(knob_handle_); - knob_handle_ = NULL; - } -} - -void Knob::OnRotate(std::function callback) { - on_rotate_ = callback; -} - -void Knob::knob_callback(void* arg, void* data) { - Knob* knob = static_cast(data); - knob_event_t event = iot_knob_get_event(arg); - - if (knob->on_rotate_) { - knob->on_rotate_(event == KNOB_RIGHT); - } +#include "knob.h" + +static const char* TAG = "Knob"; + +Knob::Knob(gpio_num_t pin_a, gpio_num_t pin_b) { + knob_config_t config = { + .default_direction = 0, + .gpio_encoder_a = static_cast(pin_a), + .gpio_encoder_b = static_cast(pin_b), + }; + + esp_err_t err = ESP_OK; + knob_handle_ = iot_knob_create(&config); + if (knob_handle_ == NULL) { + ESP_LOGE(TAG, "Failed to create knob instance"); + return; + } + + err = iot_knob_register_cb(knob_handle_, KNOB_LEFT, knob_callback, this); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register left callback: %s", esp_err_to_name(err)); + return; + } + + err = iot_knob_register_cb(knob_handle_, KNOB_RIGHT, knob_callback, this); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register right callback: %s", esp_err_to_name(err)); + return; + } + + ESP_LOGI(TAG, "Knob initialized with pins A:%d B:%d", pin_a, pin_b); +} + +Knob::~Knob() { + if (knob_handle_ != NULL) { + iot_knob_delete(knob_handle_); + knob_handle_ = NULL; + } +} + +void Knob::OnRotate(std::function callback) { + on_rotate_ = callback; +} + +void Knob::knob_callback(void* arg, void* data) { + Knob* knob = static_cast(data); + knob_event_t event = iot_knob_get_event(arg); + + if (knob->on_rotate_) { + knob->on_rotate_(event == KNOB_RIGHT); + } } \ No newline at end of file diff --git a/main/boards/common/knob.h b/main/boards/common/knob.h index efea5f5..c24eadb 100644 --- a/main/boards/common/knob.h +++ b/main/boards/common/knob.h @@ -1,25 +1,25 @@ -#ifndef KNOB_H_ -#define KNOB_H_ - -#include -#include -#include -#include - -class Knob { -public: - Knob(gpio_num_t pin_a, gpio_num_t pin_b); - ~Knob(); - - void OnRotate(std::function callback); - -private: - static void knob_callback(void* arg, void* data); - - knob_handle_t knob_handle_; - gpio_num_t pin_a_; - gpio_num_t pin_b_; - std::function on_rotate_; -}; - +#ifndef KNOB_H_ +#define KNOB_H_ + +#include +#include +#include +#include + +class Knob { +public: + Knob(gpio_num_t pin_a, gpio_num_t pin_b); + ~Knob(); + + void OnRotate(std::function callback); + +private: + static void knob_callback(void* arg, void* data); + + knob_handle_t knob_handle_; + gpio_num_t pin_a_; + gpio_num_t pin_b_; + std::function on_rotate_; +}; + #endif // KNOB_H_ \ No newline at end of file diff --git a/main/boards/common/lamp_controller.h b/main/boards/common/lamp_controller.h index 1ed142c..ec9a9bd 100644 --- a/main/boards/common/lamp_controller.h +++ b/main/boards/common/lamp_controller.h @@ -1,44 +1,44 @@ -#ifndef __LAMP_CONTROLLER_H__ -#define __LAMP_CONTROLLER_H__ - -#include "mcp_server.h" - - -class LampController { -private: - bool power_ = false; - gpio_num_t gpio_num_; - -public: - LampController(gpio_num_t gpio_num) : gpio_num_(gpio_num) { - gpio_config_t config = { - .pin_bit_mask = (1ULL << gpio_num_), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - ESP_ERROR_CHECK(gpio_config(&config)); - gpio_set_level(gpio_num_, 0); - - auto& mcp_server = McpServer::GetInstance(); - mcp_server.AddTool("self.lamp.get_state", "Get the power state of the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - return power_ ? "{\"power\": true}" : "{\"power\": false}"; - }); - - mcp_server.AddTool("self.lamp.turn_on", "Turn on the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - power_ = true; - gpio_set_level(gpio_num_, 1); - return true; - }); - - mcp_server.AddTool("self.lamp.turn_off", "Turn off the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - power_ = false; - gpio_set_level(gpio_num_, 0); - return true; - }); - } -}; - - -#endif // __LAMP_CONTROLLER_H__ +#ifndef __LAMP_CONTROLLER_H__ +#define __LAMP_CONTROLLER_H__ + +#include "mcp_server.h" + + +class LampController { +private: + bool power_ = false; + gpio_num_t gpio_num_; + +public: + LampController(gpio_num_t gpio_num) : gpio_num_(gpio_num) { + gpio_config_t config = { + .pin_bit_mask = (1ULL << gpio_num_), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_ERROR_CHECK(gpio_config(&config)); + gpio_set_level(gpio_num_, 0); + + auto& mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.lamp.get_state", "Get the power state of the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + return power_ ? "{\"power\": true}" : "{\"power\": false}"; + }); + + mcp_server.AddTool("self.lamp.turn_on", "Turn on the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + power_ = true; + gpio_set_level(gpio_num_, 1); + return true; + }); + + mcp_server.AddTool("self.lamp.turn_off", "Turn off the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + power_ = false; + gpio_set_level(gpio_num_, 0); + return true; + }); + } +}; + + +#endif // __LAMP_CONTROLLER_H__ diff --git a/main/boards/common/ml307_board.cc b/main/boards/common/ml307_board.cc index 63cf09c..03dc700 100644 --- a/main/boards/common/ml307_board.cc +++ b/main/boards/common/ml307_board.cc @@ -1,197 +1,197 @@ -#include "ml307_board.h" - -#include "application.h" -#include "display.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include - -static const char *TAG = "Ml307Board"; - -Ml307Board::Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin) : tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin) { -} - -std::string Ml307Board::GetBoardType() { - return "ml307"; -} - -void Ml307Board::StartNetwork() { - auto& application = Application::GetInstance(); - auto display = Board::GetInstance().GetDisplay(); - display->SetStatus(Lang::Strings::DETECTING_MODULE); - - while (true) { - modem_ = AtModem::Detect(tx_pin_, rx_pin_, dtr_pin_, 921600); - if (modem_ != nullptr) { - break; - } - vTaskDelay(pdMS_TO_TICKS(1000)); - } - - modem_->OnNetworkStateChanged([this, &application](bool network_ready) { - if (network_ready) { - ESP_LOGI(TAG, "Network is ready"); - } else { - ESP_LOGE(TAG, "Network is down"); - auto device_state = application.GetDeviceState(); - if (device_state == kDeviceStateListening || device_state == kDeviceStateSpeaking) { - application.Schedule([this, &application]() { - application.SetDeviceState(kDeviceStateIdle); - }); - } - } - }); - - // Wait for network ready - display->SetStatus(Lang::Strings::REGISTERING_NETWORK); - while (true) { - auto result = modem_->WaitForNetworkReady(); - if (result == NetworkStatus::ErrorInsertPin) { - application.Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_PIN); - } else if (result == NetworkStatus::ErrorRegistrationDenied) { - application.Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_REG); - } else { - break; - } - vTaskDelay(pdMS_TO_TICKS(10000)); - } - - // Print the ML307 modem information - std::string module_revision = modem_->GetModuleRevision(); - std::string imei = modem_->GetImei(); - std::string iccid = modem_->GetIccid(); - ESP_LOGI(TAG, "ML307 Revision: %s", module_revision.c_str()); - ESP_LOGI(TAG, "ML307 IMEI: %s", imei.c_str()); - ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str()); -} - -NetworkInterface* Ml307Board::GetNetwork() { - return modem_.get(); -} - -const char* Ml307Board::GetNetworkStateIcon() { - if (modem_ == nullptr || !modem_->network_ready()) { - return FONT_AWESOME_SIGNAL_OFF; - } - int csq = modem_->GetCsq(); - if (csq == -1) { - return FONT_AWESOME_SIGNAL_OFF; - } else if (csq >= 0 && csq <= 14) { - return FONT_AWESOME_SIGNAL_WEAK; - } else if (csq >= 15 && csq <= 19) { - return FONT_AWESOME_SIGNAL_FAIR; - } else if (csq >= 20 && csq <= 24) { - return FONT_AWESOME_SIGNAL_GOOD; - } else if (csq >= 25 && csq <= 31) { - return FONT_AWESOME_SIGNAL_STRONG; - } - - ESP_LOGW(TAG, "Invalid CSQ: %d", csq); - return FONT_AWESOME_SIGNAL_OFF; -} - -std::string Ml307Board::GetBoardJson() { - // Set the board type for OTA - std::string board_json = std::string("{\"type\":\"" BOARD_TYPE "\","); - board_json += "\"name\":\"" BOARD_NAME "\","; - board_json += "\"revision\":\"" + modem_->GetModuleRevision() + "\","; - board_json += "\"carrier\":\"" + modem_->GetCarrierName() + "\","; - board_json += "\"csq\":\"" + std::to_string(modem_->GetCsq()) + "\","; - board_json += "\"imei\":\"" + modem_->GetImei() + "\","; - board_json += "\"iccid\":\"" + modem_->GetIccid() + "\","; - board_json += "\"cereg\":" + modem_->GetRegistrationState().ToString() + "}"; - return board_json; -} - -void Ml307Board::SetPowerSaveMode(bool enabled) { - // TODO: Implement power save mode for ML307 -} - -std::string Ml307Board::GetDeviceStatusJson() { - /* - * 返回设备状态JSON - * - * 返回的JSON结构如下: - * { - * "audio_speaker": { - * "volume": 70 - * }, - * "screen": { - * "brightness": 100, - * "theme": "light" - * }, - * "battery": { - * "level": 50, - * "charging": true - * }, - * "network": { - * "type": "cellular", - * "carrier": "CHINA MOBILE", - * "csq": 10 - * } - * } - */ - auto& board = Board::GetInstance(); - auto root = cJSON_CreateObject(); - - // Audio speaker - auto audio_speaker = cJSON_CreateObject(); - auto audio_codec = board.GetAudioCodec(); - if (audio_codec) { - cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume()); - } - cJSON_AddItemToObject(root, "audio_speaker", audio_speaker); - - // Screen brightness - auto backlight = board.GetBacklight(); - auto screen = cJSON_CreateObject(); - if (backlight) { - cJSON_AddNumberToObject(screen, "brightness", backlight->brightness()); - } - auto display = board.GetDisplay(); - if (display && display->height() > 64) { // For LCD display only - auto theme = display->GetTheme(); - if (theme != nullptr) { - cJSON_AddStringToObject(screen, "theme", theme->name().c_str()); - } - } - cJSON_AddItemToObject(root, "screen", screen); - - // Battery - int battery_level = 0; - bool charging = false; - bool discharging = false; - if (board.GetBatteryLevel(battery_level, charging, discharging)) { - cJSON* battery = cJSON_CreateObject(); - cJSON_AddNumberToObject(battery, "level", battery_level); - cJSON_AddBoolToObject(battery, "charging", charging); - cJSON_AddItemToObject(root, "battery", battery); - } - - // Network - auto network = cJSON_CreateObject(); - cJSON_AddStringToObject(network, "type", "cellular"); - cJSON_AddStringToObject(network, "carrier", modem_->GetCarrierName().c_str()); - int csq = modem_->GetCsq(); - if (csq == -1) { - cJSON_AddStringToObject(network, "signal", "unknown"); - } else if (csq >= 0 && csq <= 14) { - cJSON_AddStringToObject(network, "signal", "very weak"); - } else if (csq >= 15 && csq <= 19) { - cJSON_AddStringToObject(network, "signal", "weak"); - } else if (csq >= 20 && csq <= 24) { - cJSON_AddStringToObject(network, "signal", "medium"); - } else if (csq >= 25 && csq <= 31) { - cJSON_AddStringToObject(network, "signal", "strong"); - } - cJSON_AddItemToObject(root, "network", network); - - auto json_str = cJSON_PrintUnformatted(root); - std::string json(json_str); - cJSON_free(json_str); - cJSON_Delete(root); - return json; -} +#include "ml307_board.h" + +#include "application.h" +#include "display.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include + +static const char *TAG = "Ml307Board"; + +Ml307Board::Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin) : tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin) { +} + +std::string Ml307Board::GetBoardType() { + return "ml307"; +} + +void Ml307Board::StartNetwork() { + auto& application = Application::GetInstance(); + auto display = Board::GetInstance().GetDisplay(); + display->SetStatus(Lang::Strings::DETECTING_MODULE); + + while (true) { + modem_ = AtModem::Detect(tx_pin_, rx_pin_, dtr_pin_, 921600); + if (modem_ != nullptr) { + break; + } + vTaskDelay(pdMS_TO_TICKS(1000)); + } + + modem_->OnNetworkStateChanged([this, &application](bool network_ready) { + if (network_ready) { + ESP_LOGI(TAG, "Network is ready"); + } else { + ESP_LOGE(TAG, "Network is down"); + auto device_state = application.GetDeviceState(); + if (device_state == kDeviceStateListening || device_state == kDeviceStateSpeaking) { + application.Schedule([this, &application]() { + application.SetDeviceState(kDeviceStateIdle); + }); + } + } + }); + + // Wait for network ready + display->SetStatus(Lang::Strings::REGISTERING_NETWORK); + while (true) { + auto result = modem_->WaitForNetworkReady(); + if (result == NetworkStatus::ErrorInsertPin) { + application.Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_PIN); + } else if (result == NetworkStatus::ErrorRegistrationDenied) { + application.Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_REG); + } else { + break; + } + vTaskDelay(pdMS_TO_TICKS(10000)); + } + + // Print the ML307 modem information + std::string module_revision = modem_->GetModuleRevision(); + std::string imei = modem_->GetImei(); + std::string iccid = modem_->GetIccid(); + ESP_LOGI(TAG, "ML307 Revision: %s", module_revision.c_str()); + ESP_LOGI(TAG, "ML307 IMEI: %s", imei.c_str()); + ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str()); +} + +NetworkInterface* Ml307Board::GetNetwork() { + return modem_.get(); +} + +const char* Ml307Board::GetNetworkStateIcon() { + if (modem_ == nullptr || !modem_->network_ready()) { + return FONT_AWESOME_SIGNAL_OFF; + } + int csq = modem_->GetCsq(); + if (csq == -1) { + return FONT_AWESOME_SIGNAL_OFF; + } else if (csq >= 0 && csq <= 14) { + return FONT_AWESOME_SIGNAL_WEAK; + } else if (csq >= 15 && csq <= 19) { + return FONT_AWESOME_SIGNAL_FAIR; + } else if (csq >= 20 && csq <= 24) { + return FONT_AWESOME_SIGNAL_GOOD; + } else if (csq >= 25 && csq <= 31) { + return FONT_AWESOME_SIGNAL_STRONG; + } + + ESP_LOGW(TAG, "Invalid CSQ: %d", csq); + return FONT_AWESOME_SIGNAL_OFF; +} + +std::string Ml307Board::GetBoardJson() { + // Set the board type for OTA + std::string board_json = std::string("{\"type\":\"" BOARD_TYPE "\","); + board_json += "\"name\":\"" BOARD_NAME "\","; + board_json += "\"revision\":\"" + modem_->GetModuleRevision() + "\","; + board_json += "\"carrier\":\"" + modem_->GetCarrierName() + "\","; + board_json += "\"csq\":\"" + std::to_string(modem_->GetCsq()) + "\","; + board_json += "\"imei\":\"" + modem_->GetImei() + "\","; + board_json += "\"iccid\":\"" + modem_->GetIccid() + "\","; + board_json += "\"cereg\":" + modem_->GetRegistrationState().ToString() + "}"; + return board_json; +} + +void Ml307Board::SetPowerSaveMode(bool enabled) { + // TODO: Implement power save mode for ML307 +} + +std::string Ml307Board::GetDeviceStatusJson() { + /* + * 返回设备状态JSON + * + * 返回的JSON结构如下: + * { + * "audio_speaker": { + * "volume": 70 + * }, + * "screen": { + * "brightness": 100, + * "theme": "light" + * }, + * "battery": { + * "level": 50, + * "charging": true + * }, + * "network": { + * "type": "cellular", + * "carrier": "CHINA MOBILE", + * "csq": 10 + * } + * } + */ + auto& board = Board::GetInstance(); + auto root = cJSON_CreateObject(); + + // Audio speaker + auto audio_speaker = cJSON_CreateObject(); + auto audio_codec = board.GetAudioCodec(); + if (audio_codec) { + cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume()); + } + cJSON_AddItemToObject(root, "audio_speaker", audio_speaker); + + // Screen brightness + auto backlight = board.GetBacklight(); + auto screen = cJSON_CreateObject(); + if (backlight) { + cJSON_AddNumberToObject(screen, "brightness", backlight->brightness()); + } + auto display = board.GetDisplay(); + if (display && display->height() > 64) { // For LCD display only + auto theme = display->GetTheme(); + if (theme != nullptr) { + cJSON_AddStringToObject(screen, "theme", theme->name().c_str()); + } + } + cJSON_AddItemToObject(root, "screen", screen); + + // Battery + int battery_level = 0; + bool charging = false; + bool discharging = false; + if (board.GetBatteryLevel(battery_level, charging, discharging)) { + cJSON* battery = cJSON_CreateObject(); + cJSON_AddNumberToObject(battery, "level", battery_level); + cJSON_AddBoolToObject(battery, "charging", charging); + cJSON_AddItemToObject(root, "battery", battery); + } + + // Network + auto network = cJSON_CreateObject(); + cJSON_AddStringToObject(network, "type", "cellular"); + cJSON_AddStringToObject(network, "carrier", modem_->GetCarrierName().c_str()); + int csq = modem_->GetCsq(); + if (csq == -1) { + cJSON_AddStringToObject(network, "signal", "unknown"); + } else if (csq >= 0 && csq <= 14) { + cJSON_AddStringToObject(network, "signal", "very weak"); + } else if (csq >= 15 && csq <= 19) { + cJSON_AddStringToObject(network, "signal", "weak"); + } else if (csq >= 20 && csq <= 24) { + cJSON_AddStringToObject(network, "signal", "medium"); + } else if (csq >= 25 && csq <= 31) { + cJSON_AddStringToObject(network, "signal", "strong"); + } + cJSON_AddItemToObject(root, "network", network); + + auto json_str = cJSON_PrintUnformatted(root); + std::string json(json_str); + cJSON_free(json_str); + cJSON_Delete(root); + return json; +} diff --git a/main/boards/common/ml307_board.h b/main/boards/common/ml307_board.h index 22dddf9..dd43136 100644 --- a/main/boards/common/ml307_board.h +++ b/main/boards/common/ml307_board.h @@ -1,29 +1,29 @@ -#ifndef ML307_BOARD_H -#define ML307_BOARD_H - -#include -#include -#include "board.h" - - -class Ml307Board : public Board { -protected: - std::unique_ptr modem_; - gpio_num_t tx_pin_; - gpio_num_t rx_pin_; - gpio_num_t dtr_pin_; - - virtual std::string GetBoardJson() override; - -public: - Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin = GPIO_NUM_NC); - virtual std::string GetBoardType() override; - virtual void StartNetwork() override; - virtual NetworkInterface* GetNetwork() override; - virtual const char* GetNetworkStateIcon() override; - virtual void SetPowerSaveMode(bool enabled) override; - virtual AudioCodec* GetAudioCodec() override { return nullptr; } - virtual std::string GetDeviceStatusJson() override; -}; - -#endif // ML307_BOARD_H +#ifndef ML307_BOARD_H +#define ML307_BOARD_H + +#include +#include +#include "board.h" + + +class Ml307Board : public Board { +protected: + std::unique_ptr modem_; + gpio_num_t tx_pin_; + gpio_num_t rx_pin_; + gpio_num_t dtr_pin_; + + virtual std::string GetBoardJson() override; + +public: + Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin = GPIO_NUM_NC); + virtual std::string GetBoardType() override; + virtual void StartNetwork() override; + virtual NetworkInterface* GetNetwork() override; + virtual const char* GetNetworkStateIcon() override; + virtual void SetPowerSaveMode(bool enabled) override; + virtual AudioCodec* GetAudioCodec() override { return nullptr; } + virtual std::string GetDeviceStatusJson() override; +}; + +#endif // ML307_BOARD_H diff --git a/main/boards/common/music.h b/main/boards/common/music.h index 76da004..383feec 100644 --- a/main/boards/common/music.h +++ b/main/boards/common/music.h @@ -2,6 +2,10 @@ #define MUSIC_H #include +#include + +// 前向声明 +struct SongInfo; class Music { public: @@ -15,16 +19,22 @@ public: virtual bool StopStreaming() = 0; // 停止流式播放 virtual size_t GetBufferSize() const = 0; virtual bool IsDownloading() const = 0; - virtual bool IsPlaying() const = 0; - virtual bool IsPaused() const = 0; virtual int16_t* GetAudioData() = 0; - // MCP工具需要的方法 - virtual bool PlaySong() = 0; - virtual bool SetVolume(int volume) = 0; - virtual bool StopSong() = 0; - virtual bool PauseSong() = 0; - virtual bool ResumeSong() = 0; + // 音乐播放信息获取方法 + virtual int GetCurrentSongDurationSeconds() const = 0; + virtual int GetCurrentPlayTimeSeconds() const = 0; + virtual float GetPlayProgress() const = 0; + + // 播放队列相关方法 + virtual bool PlayPlaylist(const std::vector& songs) = 0; + virtual bool NextSong() = 0; + virtual bool PreviousSong() = 0; + virtual void StopPlaylist() = 0; + virtual bool IsPlaylistMode() const = 0; + virtual int GetCurrentPlaylistIndex() const = 0; + virtual size_t GetPlaylistSize() const = 0; + virtual SongInfo GetCurrentSong() const = 0; }; #endif // MUSIC_H \ No newline at end of file diff --git a/main/boards/common/power_save_timer.cc b/main/boards/common/power_save_timer.cc index 522e64c..84cea0e 100644 --- a/main/boards/common/power_save_timer.cc +++ b/main/boards/common/power_save_timer.cc @@ -1,132 +1,132 @@ -#include "power_save_timer.h" -#include "application.h" -#include "settings.h" - -#include - -#define TAG "PowerSaveTimer" - - -PowerSaveTimer::PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep, int seconds_to_shutdown) - : cpu_max_freq_(cpu_max_freq), seconds_to_sleep_(seconds_to_sleep), seconds_to_shutdown_(seconds_to_shutdown) { - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - auto self = static_cast(arg); - self->PowerSaveCheck(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "power_save_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &power_save_timer_)); -} - -PowerSaveTimer::~PowerSaveTimer() { - esp_timer_stop(power_save_timer_); - esp_timer_delete(power_save_timer_); -} - -void PowerSaveTimer::SetEnabled(bool enabled) { - if (enabled && !enabled_) { - Settings settings("wifi", false); - if (!settings.GetBool("sleep_mode", true)) { - ESP_LOGI(TAG, "Power save timer is disabled by settings"); - return; - } - - ticks_ = 0; - enabled_ = enabled; - ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000)); - ESP_LOGI(TAG, "Power save timer enabled"); - } else if (!enabled && enabled_) { - ESP_ERROR_CHECK(esp_timer_stop(power_save_timer_)); - enabled_ = enabled; - WakeUp(); - ESP_LOGI(TAG, "Power save timer disabled"); - } -} - -void PowerSaveTimer::OnEnterSleepMode(std::function callback) { - on_enter_sleep_mode_ = callback; -} - -void PowerSaveTimer::OnExitSleepMode(std::function callback) { - on_exit_sleep_mode_ = callback; -} - -void PowerSaveTimer::OnShutdownRequest(std::function callback) { - on_shutdown_request_ = callback; -} - -void PowerSaveTimer::PowerSaveCheck() { - auto& app = Application::GetInstance(); - if (!in_sleep_mode_ && !app.CanEnterSleepMode()) { - ticks_ = 0; - return; - } - - ticks_++; - if (seconds_to_sleep_ != -1 && ticks_ >= seconds_to_sleep_) { - if (!in_sleep_mode_) { - ESP_LOGI(TAG, "Enabling power save mode"); - in_sleep_mode_ = true; - if (on_enter_sleep_mode_) { - on_enter_sleep_mode_(); - } - - if (cpu_max_freq_ != -1) { - // Disable wake word detection - auto& audio_service = app.GetAudioService(); - is_wake_word_running_ = audio_service.IsWakeWordRunning(); - if (is_wake_word_running_) { - audio_service.EnableWakeWordDetection(false); - vTaskDelay(pdMS_TO_TICKS(100)); - } - // Disable audio input - auto codec = Board::GetInstance().GetAudioCodec(); - if (codec) { - codec->EnableInput(false); - } - - esp_pm_config_t pm_config = { - .max_freq_mhz = cpu_max_freq_, - .min_freq_mhz = 40, - .light_sleep_enable = true, - }; - esp_pm_configure(&pm_config); - } - } - } - if (seconds_to_shutdown_ != -1 && ticks_ >= seconds_to_shutdown_ && on_shutdown_request_) { - on_shutdown_request_(); - } -} - -void PowerSaveTimer::WakeUp() { - ticks_ = 0; - if (in_sleep_mode_) { - ESP_LOGI(TAG, "Exiting power save mode"); - in_sleep_mode_ = false; - - if (cpu_max_freq_ != -1) { - esp_pm_config_t pm_config = { - .max_freq_mhz = cpu_max_freq_, - .min_freq_mhz = cpu_max_freq_, - .light_sleep_enable = false, - }; - esp_pm_configure(&pm_config); - - // Enable wake word detection - auto& app = Application::GetInstance(); - auto& audio_service = app.GetAudioService(); - if (is_wake_word_running_) { - audio_service.EnableWakeWordDetection(true); - } - } - - if (on_exit_sleep_mode_) { - on_exit_sleep_mode_(); - } - } -} +#include "power_save_timer.h" +#include "application.h" +#include "settings.h" + +#include + +#define TAG "PowerSaveTimer" + + +PowerSaveTimer::PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep, int seconds_to_shutdown) + : cpu_max_freq_(cpu_max_freq), seconds_to_sleep_(seconds_to_sleep), seconds_to_shutdown_(seconds_to_shutdown) { + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + auto self = static_cast(arg); + self->PowerSaveCheck(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "power_save_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &power_save_timer_)); +} + +PowerSaveTimer::~PowerSaveTimer() { + esp_timer_stop(power_save_timer_); + esp_timer_delete(power_save_timer_); +} + +void PowerSaveTimer::SetEnabled(bool enabled) { + if (enabled && !enabled_) { + Settings settings("wifi", false); + if (!settings.GetBool("sleep_mode", true)) { + ESP_LOGI(TAG, "Power save timer is disabled by settings"); + return; + } + + ticks_ = 0; + enabled_ = enabled; + ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000)); + ESP_LOGI(TAG, "Power save timer enabled"); + } else if (!enabled && enabled_) { + ESP_ERROR_CHECK(esp_timer_stop(power_save_timer_)); + enabled_ = enabled; + WakeUp(); + ESP_LOGI(TAG, "Power save timer disabled"); + } +} + +void PowerSaveTimer::OnEnterSleepMode(std::function callback) { + on_enter_sleep_mode_ = callback; +} + +void PowerSaveTimer::OnExitSleepMode(std::function callback) { + on_exit_sleep_mode_ = callback; +} + +void PowerSaveTimer::OnShutdownRequest(std::function callback) { + on_shutdown_request_ = callback; +} + +void PowerSaveTimer::PowerSaveCheck() { + auto& app = Application::GetInstance(); + if (!in_sleep_mode_ && !app.CanEnterSleepMode()) { + ticks_ = 0; + return; + } + + ticks_++; + if (seconds_to_sleep_ != -1 && ticks_ >= seconds_to_sleep_) { + if (!in_sleep_mode_) { + ESP_LOGI(TAG, "Enabling power save mode"); + in_sleep_mode_ = true; + if (on_enter_sleep_mode_) { + on_enter_sleep_mode_(); + } + + if (cpu_max_freq_ != -1) { + // Disable wake word detection + auto& audio_service = app.GetAudioService(); + is_wake_word_running_ = audio_service.IsWakeWordRunning(); + if (is_wake_word_running_) { + audio_service.EnableWakeWordDetection(false); + vTaskDelay(pdMS_TO_TICKS(100)); + } + // Disable audio input + auto codec = Board::GetInstance().GetAudioCodec(); + if (codec) { + codec->EnableInput(false); + } + + esp_pm_config_t pm_config = { + .max_freq_mhz = cpu_max_freq_, + .min_freq_mhz = 40, + .light_sleep_enable = true, + }; + esp_pm_configure(&pm_config); + } + } + } + if (seconds_to_shutdown_ != -1 && ticks_ >= seconds_to_shutdown_ && on_shutdown_request_) { + on_shutdown_request_(); + } +} + +void PowerSaveTimer::WakeUp() { + ticks_ = 0; + if (in_sleep_mode_) { + ESP_LOGI(TAG, "Exiting power save mode"); + in_sleep_mode_ = false; + + if (cpu_max_freq_ != -1) { + esp_pm_config_t pm_config = { + .max_freq_mhz = cpu_max_freq_, + .min_freq_mhz = cpu_max_freq_, + .light_sleep_enable = false, + }; + esp_pm_configure(&pm_config); + + // Enable wake word detection + auto& app = Application::GetInstance(); + auto& audio_service = app.GetAudioService(); + if (is_wake_word_running_) { + audio_service.EnableWakeWordDetection(true); + } + } + + if (on_exit_sleep_mode_) { + on_exit_sleep_mode_(); + } + } +} diff --git a/main/boards/common/power_save_timer.h b/main/boards/common/power_save_timer.h index 4c95671..d5b04a1 100644 --- a/main/boards/common/power_save_timer.h +++ b/main/boards/common/power_save_timer.h @@ -1,34 +1,34 @@ -#pragma once - -#include - -#include -#include - -class PowerSaveTimer { -public: - PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep = 20, int seconds_to_shutdown = -1); - ~PowerSaveTimer(); - - void SetEnabled(bool enabled); - void OnEnterSleepMode(std::function callback); - void OnExitSleepMode(std::function callback); - void OnShutdownRequest(std::function callback); - void WakeUp(); - -private: - void PowerSaveCheck(); - - esp_timer_handle_t power_save_timer_ = nullptr; - bool enabled_ = false; - bool in_sleep_mode_ = false; - bool is_wake_word_running_ = false; - int ticks_ = 0; - int cpu_max_freq_; - int seconds_to_sleep_; - int seconds_to_shutdown_; - - std::function on_enter_sleep_mode_; - std::function on_exit_sleep_mode_; - std::function on_shutdown_request_; -}; +#pragma once + +#include + +#include +#include + +class PowerSaveTimer { +public: + PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep = 20, int seconds_to_shutdown = -1); + ~PowerSaveTimer(); + + void SetEnabled(bool enabled); + void OnEnterSleepMode(std::function callback); + void OnExitSleepMode(std::function callback); + void OnShutdownRequest(std::function callback); + void WakeUp(); + +private: + void PowerSaveCheck(); + + esp_timer_handle_t power_save_timer_ = nullptr; + bool enabled_ = false; + bool in_sleep_mode_ = false; + bool is_wake_word_running_ = false; + int ticks_ = 0; + int cpu_max_freq_; + int seconds_to_sleep_; + int seconds_to_shutdown_; + + std::function on_enter_sleep_mode_; + std::function on_exit_sleep_mode_; + std::function on_shutdown_request_; +}; diff --git a/main/boards/common/press_to_talk_mcp_tool.cc b/main/boards/common/press_to_talk_mcp_tool.cc index d6b0bc9..12a024f 100644 --- a/main/boards/common/press_to_talk_mcp_tool.cc +++ b/main/boards/common/press_to_talk_mcp_tool.cc @@ -1,57 +1,57 @@ -#include "press_to_talk_mcp_tool.h" -#include - -static const char* TAG = "PressToTalkMcpTool"; - -PressToTalkMcpTool::PressToTalkMcpTool() - : press_to_talk_enabled_(false) { -} - -void PressToTalkMcpTool::Initialize() { - // 从设置中读取当前状态 - Settings settings("vendor"); - press_to_talk_enabled_ = settings.GetInt("press_to_talk", 0) != 0; - - // 注册MCP工具 - auto& mcp_server = McpServer::GetInstance(); - mcp_server.AddTool("self.set_press_to_talk", - "Switch between press to talk mode (长按说话) and click to talk mode (单击说话).\n" - "The mode can be `press_to_talk` or `click_to_talk`.", - PropertyList({ - Property("mode", kPropertyTypeString) - }), - [this](const PropertyList& properties) -> ReturnValue { - return HandleSetPressToTalk(properties); - }); - - ESP_LOGI(TAG, "PressToTalkMcpTool initialized, current mode: %s", - press_to_talk_enabled_ ? "press_to_talk" : "click_to_talk"); -} - -bool PressToTalkMcpTool::IsPressToTalkEnabled() const { - return press_to_talk_enabled_; -} - -ReturnValue PressToTalkMcpTool::HandleSetPressToTalk(const PropertyList& properties) { - auto mode = properties["mode"].value(); - - if (mode == "press_to_talk") { - SetPressToTalkEnabled(true); - ESP_LOGI(TAG, "Switched to press to talk mode"); - return true; - } else if (mode == "click_to_talk") { - SetPressToTalkEnabled(false); - ESP_LOGI(TAG, "Switched to click to talk mode"); - return true; - } - - throw std::runtime_error("Invalid mode: " + mode); -} - -void PressToTalkMcpTool::SetPressToTalkEnabled(bool enabled) { - press_to_talk_enabled_ = enabled; - - Settings settings("vendor", true); - settings.SetInt("press_to_talk", enabled ? 1 : 0); - ESP_LOGI(TAG, "Press to talk enabled: %d", enabled); +#include "press_to_talk_mcp_tool.h" +#include + +static const char* TAG = "PressToTalkMcpTool"; + +PressToTalkMcpTool::PressToTalkMcpTool() + : press_to_talk_enabled_(false) { +} + +void PressToTalkMcpTool::Initialize() { + // 从设置中读取当前状态 + Settings settings("vendor"); + press_to_talk_enabled_ = settings.GetInt("press_to_talk", 0) != 0; + + // 注册MCP工具 + auto& mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.set_press_to_talk", + "Switch between press to talk mode (长按说话) and click to talk mode (单击说话).\n" + "The mode can be `press_to_talk` or `click_to_talk`.", + PropertyList({ + Property("mode", kPropertyTypeString) + }), + [this](const PropertyList& properties) -> ReturnValue { + return HandleSetPressToTalk(properties); + }); + + ESP_LOGI(TAG, "PressToTalkMcpTool initialized, current mode: %s", + press_to_talk_enabled_ ? "press_to_talk" : "click_to_talk"); +} + +bool PressToTalkMcpTool::IsPressToTalkEnabled() const { + return press_to_talk_enabled_; +} + +ReturnValue PressToTalkMcpTool::HandleSetPressToTalk(const PropertyList& properties) { + auto mode = properties["mode"].value(); + + if (mode == "press_to_talk") { + SetPressToTalkEnabled(true); + ESP_LOGI(TAG, "Switched to press to talk mode"); + return true; + } else if (mode == "click_to_talk") { + SetPressToTalkEnabled(false); + ESP_LOGI(TAG, "Switched to click to talk mode"); + return true; + } + + throw std::runtime_error("Invalid mode: " + mode); +} + +void PressToTalkMcpTool::SetPressToTalkEnabled(bool enabled) { + press_to_talk_enabled_ = enabled; + + Settings settings("vendor", true); + settings.SetInt("press_to_talk", enabled ? 1 : 0); + ESP_LOGI(TAG, "Press to talk enabled: %d", enabled); } \ No newline at end of file diff --git a/main/boards/common/press_to_talk_mcp_tool.h b/main/boards/common/press_to_talk_mcp_tool.h index 3231a28..f6d2789 100644 --- a/main/boards/common/press_to_talk_mcp_tool.h +++ b/main/boards/common/press_to_talk_mcp_tool.h @@ -1,29 +1,29 @@ -#ifndef PRESS_TO_TALK_MCP_TOOL_H -#define PRESS_TO_TALK_MCP_TOOL_H - -#include "mcp_server.h" -#include "settings.h" - -// 可复用的按键说话模式MCP工具类 -class PressToTalkMcpTool { -private: - bool press_to_talk_enabled_; - -public: - PressToTalkMcpTool(); - - // 初始化工具,注册到MCP服务器 - void Initialize(); - - // 获取当前按键说话模式状态 - bool IsPressToTalkEnabled() const; - -private: - // MCP工具的回调函数 - ReturnValue HandleSetPressToTalk(const PropertyList& properties); - - // 内部方法:设置press to talk状态并保存到设置 - void SetPressToTalkEnabled(bool enabled); -}; - +#ifndef PRESS_TO_TALK_MCP_TOOL_H +#define PRESS_TO_TALK_MCP_TOOL_H + +#include "mcp_server.h" +#include "settings.h" + +// 可复用的按键说话模式MCP工具类 +class PressToTalkMcpTool { +private: + bool press_to_talk_enabled_; + +public: + PressToTalkMcpTool(); + + // 初始化工具,注册到MCP服务器 + void Initialize(); + + // 获取当前按键说话模式状态 + bool IsPressToTalkEnabled() const; + +private: + // MCP工具的回调函数 + ReturnValue HandleSetPressToTalk(const PropertyList& properties); + + // 内部方法:设置press to talk状态并保存到设置 + void SetPressToTalkEnabled(bool enabled); +}; + #endif // PRESS_TO_TALK_MCP_TOOL_H \ No newline at end of file diff --git a/main/boards/common/sleep_timer.cc b/main/boards/common/sleep_timer.cc index 3490f6c..ab92f39 100644 --- a/main/boards/common/sleep_timer.cc +++ b/main/boards/common/sleep_timer.cc @@ -1,133 +1,133 @@ -#include "sleep_timer.h" -#include "application.h" -#include "board.h" -#include "display.h" -#include "settings.h" - -#include -#include -#include - -#define TAG "SleepTimer" - - -SleepTimer::SleepTimer(int seconds_to_light_sleep, int seconds_to_deep_sleep) - : seconds_to_light_sleep_(seconds_to_light_sleep), seconds_to_deep_sleep_(seconds_to_deep_sleep) { - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - auto self = static_cast(arg); - self->CheckTimer(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "sleep_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &sleep_timer_)); -} - -SleepTimer::~SleepTimer() { - esp_timer_stop(sleep_timer_); - esp_timer_delete(sleep_timer_); -} - -void SleepTimer::SetEnabled(bool enabled) { - if (enabled && !enabled_) { - Settings settings("wifi", false); - if (!settings.GetBool("sleep_mode", true)) { - ESP_LOGI(TAG, "Power save timer is disabled by settings"); - return; - } - - ticks_ = 0; - enabled_ = enabled; - ESP_ERROR_CHECK(esp_timer_start_periodic(sleep_timer_, 1000000)); - ESP_LOGI(TAG, "Sleep timer enabled"); - } else if (!enabled && enabled_) { - ESP_ERROR_CHECK(esp_timer_stop(sleep_timer_)); - enabled_ = enabled; - WakeUp(); - ESP_LOGI(TAG, "Sleep timer disabled"); - } -} - -void SleepTimer::OnEnterLightSleepMode(std::function callback) { - on_enter_light_sleep_mode_ = callback; -} - -void SleepTimer::OnExitLightSleepMode(std::function callback) { - on_exit_light_sleep_mode_ = callback; -} - -void SleepTimer::OnEnterDeepSleepMode(std::function callback) { - on_enter_deep_sleep_mode_ = callback; -} - -void SleepTimer::CheckTimer() { - auto& app = Application::GetInstance(); - if (!app.CanEnterSleepMode()) { - ticks_ = 0; - return; - } - - ticks_++; - if (seconds_to_light_sleep_ != -1 && ticks_ >= seconds_to_light_sleep_) { - if (!in_light_sleep_mode_) { - in_light_sleep_mode_ = true; - if (on_enter_light_sleep_mode_) { - on_enter_light_sleep_mode_(); - } - - auto& audio_service = app.GetAudioService(); - bool is_wake_word_running = audio_service.IsWakeWordRunning(); - if (is_wake_word_running) { - audio_service.EnableWakeWordDetection(false); - vTaskDelay(pdMS_TO_TICKS(100)); - } - - app.Schedule([this, &app]() { - while (in_light_sleep_mode_) { - auto& board = Board::GetInstance(); - board.GetDisplay()->UpdateStatusBar(true); - lv_refr_now(nullptr); - lvgl_port_stop(); - - // 配置timer唤醒源(30秒后自动唤醒) - esp_sleep_enable_timer_wakeup(30 * 1000000); - - // 进入light sleep模式 - esp_light_sleep_start(); - lvgl_port_resume(); - - auto wakeup_reason = esp_sleep_get_wakeup_cause(); - ESP_LOGI(TAG, "Wake up from light sleep, wakeup_reason: %d", wakeup_reason); - if (wakeup_reason != ESP_SLEEP_WAKEUP_TIMER) { - break; - } - } - WakeUp(); - }); - - if (is_wake_word_running) { - audio_service.EnableWakeWordDetection(true); - } - } - } - if (seconds_to_deep_sleep_ != -1 && ticks_ >= seconds_to_deep_sleep_) { - if (on_enter_deep_sleep_mode_) { - on_enter_deep_sleep_mode_(); - } - - esp_deep_sleep_start(); - } -} - -void SleepTimer::WakeUp() { - ticks_ = 0; - if (in_light_sleep_mode_) { - in_light_sleep_mode_ = false; - if (on_exit_light_sleep_mode_) { - on_exit_light_sleep_mode_(); - } - } -} +#include "sleep_timer.h" +#include "application.h" +#include "board.h" +#include "display.h" +#include "settings.h" + +#include +#include +#include + +#define TAG "SleepTimer" + + +SleepTimer::SleepTimer(int seconds_to_light_sleep, int seconds_to_deep_sleep) + : seconds_to_light_sleep_(seconds_to_light_sleep), seconds_to_deep_sleep_(seconds_to_deep_sleep) { + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + auto self = static_cast(arg); + self->CheckTimer(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "sleep_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &sleep_timer_)); +} + +SleepTimer::~SleepTimer() { + esp_timer_stop(sleep_timer_); + esp_timer_delete(sleep_timer_); +} + +void SleepTimer::SetEnabled(bool enabled) { + if (enabled && !enabled_) { + Settings settings("wifi", false); + if (!settings.GetBool("sleep_mode", true)) { + ESP_LOGI(TAG, "Power save timer is disabled by settings"); + return; + } + + ticks_ = 0; + enabled_ = enabled; + ESP_ERROR_CHECK(esp_timer_start_periodic(sleep_timer_, 1000000)); + ESP_LOGI(TAG, "Sleep timer enabled"); + } else if (!enabled && enabled_) { + ESP_ERROR_CHECK(esp_timer_stop(sleep_timer_)); + enabled_ = enabled; + WakeUp(); + ESP_LOGI(TAG, "Sleep timer disabled"); + } +} + +void SleepTimer::OnEnterLightSleepMode(std::function callback) { + on_enter_light_sleep_mode_ = callback; +} + +void SleepTimer::OnExitLightSleepMode(std::function callback) { + on_exit_light_sleep_mode_ = callback; +} + +void SleepTimer::OnEnterDeepSleepMode(std::function callback) { + on_enter_deep_sleep_mode_ = callback; +} + +void SleepTimer::CheckTimer() { + auto& app = Application::GetInstance(); + if (!app.CanEnterSleepMode()) { + ticks_ = 0; + return; + } + + ticks_++; + if (seconds_to_light_sleep_ != -1 && ticks_ >= seconds_to_light_sleep_) { + if (!in_light_sleep_mode_) { + in_light_sleep_mode_ = true; + if (on_enter_light_sleep_mode_) { + on_enter_light_sleep_mode_(); + } + + auto& audio_service = app.GetAudioService(); + bool is_wake_word_running = audio_service.IsWakeWordRunning(); + if (is_wake_word_running) { + audio_service.EnableWakeWordDetection(false); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + app.Schedule([this, &app]() { + while (in_light_sleep_mode_) { + auto& board = Board::GetInstance(); + board.GetDisplay()->UpdateStatusBar(true); + lv_refr_now(nullptr); + lvgl_port_stop(); + + // 配置timer唤醒源(30秒后自动唤醒) + esp_sleep_enable_timer_wakeup(30 * 1000000); + + // 进入light sleep模式 + esp_light_sleep_start(); + lvgl_port_resume(); + + auto wakeup_reason = esp_sleep_get_wakeup_cause(); + ESP_LOGI(TAG, "Wake up from light sleep, wakeup_reason: %d", wakeup_reason); + if (wakeup_reason != ESP_SLEEP_WAKEUP_TIMER) { + break; + } + } + WakeUp(); + }); + + if (is_wake_word_running) { + audio_service.EnableWakeWordDetection(true); + } + } + } + if (seconds_to_deep_sleep_ != -1 && ticks_ >= seconds_to_deep_sleep_) { + if (on_enter_deep_sleep_mode_) { + on_enter_deep_sleep_mode_(); + } + + esp_deep_sleep_start(); + } +} + +void SleepTimer::WakeUp() { + ticks_ = 0; + if (in_light_sleep_mode_) { + in_light_sleep_mode_ = false; + if (on_exit_light_sleep_mode_) { + on_exit_light_sleep_mode_(); + } + } +} diff --git a/main/boards/common/sleep_timer.h b/main/boards/common/sleep_timer.h index 159e220..b11a496 100644 --- a/main/boards/common/sleep_timer.h +++ b/main/boards/common/sleep_timer.h @@ -1,32 +1,32 @@ -#pragma once - -#include - -#include -#include - -class SleepTimer { -public: - SleepTimer(int seconds_to_light_sleep = 20, int seconds_to_deep_sleep = -1); - ~SleepTimer(); - - void SetEnabled(bool enabled); - void OnEnterLightSleepMode(std::function callback); - void OnExitLightSleepMode(std::function callback); - void OnEnterDeepSleepMode(std::function callback); - void WakeUp(); - -private: - void CheckTimer(); - - esp_timer_handle_t sleep_timer_ = nullptr; - bool enabled_ = false; - int ticks_ = 0; - int seconds_to_light_sleep_; - int seconds_to_deep_sleep_; - bool in_light_sleep_mode_ = false; - - std::function on_enter_light_sleep_mode_; - std::function on_exit_light_sleep_mode_; - std::function on_enter_deep_sleep_mode_; -}; +#pragma once + +#include + +#include +#include + +class SleepTimer { +public: + SleepTimer(int seconds_to_light_sleep = 20, int seconds_to_deep_sleep = -1); + ~SleepTimer(); + + void SetEnabled(bool enabled); + void OnEnterLightSleepMode(std::function callback); + void OnExitLightSleepMode(std::function callback); + void OnEnterDeepSleepMode(std::function callback); + void WakeUp(); + +private: + void CheckTimer(); + + esp_timer_handle_t sleep_timer_ = nullptr; + bool enabled_ = false; + int ticks_ = 0; + int seconds_to_light_sleep_; + int seconds_to_deep_sleep_; + bool in_light_sleep_mode_ = false; + + std::function on_enter_light_sleep_mode_; + std::function on_exit_light_sleep_mode_; + std::function on_enter_deep_sleep_mode_; +}; diff --git a/main/boards/common/sy6970.cc b/main/boards/common/sy6970.cc index 8a45d75..39219a7 100644 --- a/main/boards/common/sy6970.cc +++ b/main/boards/common/sy6970.cc @@ -1,65 +1,65 @@ -#include "sy6970.h" -#include "board.h" -#include "display.h" - -#include - -#define TAG "Sy6970" - -Sy6970::Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { -} - -int Sy6970::GetChangingStatus() { - return (ReadReg(0x0B) >> 3) & 0x03; -} - -bool Sy6970::IsCharging() { - return GetChangingStatus() != 0; -} - -bool Sy6970::IsPowerGood() { - return (ReadReg(0x0B) & 0x04) != 0; -} - -bool Sy6970::IsChargingDone() { - return GetChangingStatus() == 3; -} - -int Sy6970::GetBatteryVoltage() { - uint8_t value = ReadReg(0x0E); - value &= 0x7F; - if (value == 0) { - return 0; - } - return value * 20 + 2304; -} - -int Sy6970::GetChargeTargetVoltage() { - uint8_t value = ReadReg(0x06); - value = (value & 0xFC) >> 2; - if (value > 0x30) { - return 4608; - } - return value * 16 + 3840; -} - -int Sy6970::GetBatteryLevel() { - int level = 0; - // 电池所能掉电的最低电压 - int battery_minimum_voltage = 3200; - int battery_voltage = GetBatteryVoltage(); - int charge_voltage_limit = GetChargeTargetVoltage(); - // ESP_LOGI(TAG, "battery_voltage: %d, charge_voltage_limit: %d", battery_voltage, charge_voltage_limit); - if (battery_voltage > battery_minimum_voltage && charge_voltage_limit > battery_minimum_voltage) { - level = (((float) battery_voltage - (float) battery_minimum_voltage) / ((float) charge_voltage_limit - (float) battery_minimum_voltage)) * 100.0; - } - // 不连接电池时读取的充电状态不稳定且battery_voltage有时会超过charge_voltage_limit - if (level > 100) { - level = 100; - } - return level; -} - -void Sy6970::PowerOff() { - WriteReg(0x09, 0B01100100); -} +#include "sy6970.h" +#include "board.h" +#include "display.h" + +#include + +#define TAG "Sy6970" + +Sy6970::Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { +} + +int Sy6970::GetChangingStatus() { + return (ReadReg(0x0B) >> 3) & 0x03; +} + +bool Sy6970::IsCharging() { + return GetChangingStatus() != 0; +} + +bool Sy6970::IsPowerGood() { + return (ReadReg(0x0B) & 0x04) != 0; +} + +bool Sy6970::IsChargingDone() { + return GetChangingStatus() == 3; +} + +int Sy6970::GetBatteryVoltage() { + uint8_t value = ReadReg(0x0E); + value &= 0x7F; + if (value == 0) { + return 0; + } + return value * 20 + 2304; +} + +int Sy6970::GetChargeTargetVoltage() { + uint8_t value = ReadReg(0x06); + value = (value & 0xFC) >> 2; + if (value > 0x30) { + return 4608; + } + return value * 16 + 3840; +} + +int Sy6970::GetBatteryLevel() { + int level = 0; + // 电池所能掉电的最低电压 + int battery_minimum_voltage = 3200; + int battery_voltage = GetBatteryVoltage(); + int charge_voltage_limit = GetChargeTargetVoltage(); + // ESP_LOGI(TAG, "battery_voltage: %d, charge_voltage_limit: %d", battery_voltage, charge_voltage_limit); + if (battery_voltage > battery_minimum_voltage && charge_voltage_limit > battery_minimum_voltage) { + level = (((float) battery_voltage - (float) battery_minimum_voltage) / ((float) charge_voltage_limit - (float) battery_minimum_voltage)) * 100.0; + } + // 不连接电池时读取的充电状态不稳定且battery_voltage有时会超过charge_voltage_limit + if (level > 100) { + level = 100; + } + return level; +} + +void Sy6970::PowerOff() { + WriteReg(0x09, 0B01100100); +} diff --git a/main/boards/common/sy6970.h b/main/boards/common/sy6970.h index a2eaaca..7e832c5 100644 --- a/main/boards/common/sy6970.h +++ b/main/boards/common/sy6970.h @@ -1,21 +1,21 @@ -#ifndef __SY6970_H__ -#define __SY6970_H__ - -#include "i2c_device.h" - -class Sy6970 : public I2cDevice { -public: - Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr); - bool IsCharging(); - bool IsPowerGood(); - bool IsChargingDone(); - int GetBatteryLevel(); - void PowerOff(); - -private: - int GetChangingStatus(); - int GetBatteryVoltage(); - int GetChargeTargetVoltage(); -}; - +#ifndef __SY6970_H__ +#define __SY6970_H__ + +#include "i2c_device.h" + +class Sy6970 : public I2cDevice { +public: + Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr); + bool IsCharging(); + bool IsPowerGood(); + bool IsChargingDone(); + int GetBatteryLevel(); + void PowerOff(); + +private: + int GetChangingStatus(); + int GetBatteryVoltage(); + int GetChargeTargetVoltage(); +}; + #endif \ No newline at end of file diff --git a/main/boards/common/system_reset.cc b/main/boards/common/system_reset.cc index f51249b..ffafc69 100644 --- a/main/boards/common/system_reset.cc +++ b/main/boards/common/system_reset.cc @@ -1,72 +1,72 @@ -#include "system_reset.h" - -#include -#include -#include -#include -#include -#include - - -#define TAG "SystemReset" - - -SystemReset::SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin) : reset_nvs_pin_(reset_nvs_pin), reset_factory_pin_(reset_factory_pin) { - // Configure GPIO1, GPIO2 as INPUT, reset NVS flash if the button is pressed - gpio_config_t io_conf; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << reset_nvs_pin_) | (1ULL << reset_factory_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&io_conf); -} - - -void SystemReset::CheckButtons() { - if (gpio_get_level(reset_factory_pin_) == 0) { - ESP_LOGI(TAG, "Button is pressed, reset to factory"); - ResetNvsFlash(); - ResetToFactory(); - } - - if (gpio_get_level(reset_nvs_pin_) == 0) { - ESP_LOGI(TAG, "Button is pressed, reset NVS flash"); - ResetNvsFlash(); - } -} - -void SystemReset::ResetNvsFlash() { - ESP_LOGI(TAG, "Resetting NVS flash"); - esp_err_t ret = nvs_flash_erase(); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to erase NVS flash"); - } - ret = nvs_flash_init(); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize NVS flash"); - } -} - -void SystemReset::ResetToFactory() { - ESP_LOGI(TAG, "Resetting to factory"); - // Erase otadata partition - const esp_partition_t* partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL); - if (partition == NULL) { - ESP_LOGE(TAG, "Failed to find otadata partition"); - return; - } - esp_partition_erase_range(partition, 0, partition->size); - ESP_LOGI(TAG, "Erased otadata partition"); - - // Reboot in 3 seconds - RestartInSeconds(3); -} - -void SystemReset::RestartInSeconds(int seconds) { - for (int i = seconds; i > 0; i--) { - ESP_LOGI(TAG, "Resetting in %d seconds", i); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - esp_restart(); -} +#include "system_reset.h" + +#include +#include +#include +#include +#include +#include + + +#define TAG "SystemReset" + + +SystemReset::SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin) : reset_nvs_pin_(reset_nvs_pin), reset_factory_pin_(reset_factory_pin) { + // Configure GPIO1, GPIO2 as INPUT, reset NVS flash if the button is pressed + gpio_config_t io_conf; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << reset_nvs_pin_) | (1ULL << reset_factory_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); +} + + +void SystemReset::CheckButtons() { + if (gpio_get_level(reset_factory_pin_) == 0) { + ESP_LOGI(TAG, "Button is pressed, reset to factory"); + ResetNvsFlash(); + ResetToFactory(); + } + + if (gpio_get_level(reset_nvs_pin_) == 0) { + ESP_LOGI(TAG, "Button is pressed, reset NVS flash"); + ResetNvsFlash(); + } +} + +void SystemReset::ResetNvsFlash() { + ESP_LOGI(TAG, "Resetting NVS flash"); + esp_err_t ret = nvs_flash_erase(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase NVS flash"); + } + ret = nvs_flash_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize NVS flash"); + } +} + +void SystemReset::ResetToFactory() { + ESP_LOGI(TAG, "Resetting to factory"); + // Erase otadata partition + const esp_partition_t* partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL); + if (partition == NULL) { + ESP_LOGE(TAG, "Failed to find otadata partition"); + return; + } + esp_partition_erase_range(partition, 0, partition->size); + ESP_LOGI(TAG, "Erased otadata partition"); + + // Reboot in 3 seconds + RestartInSeconds(3); +} + +void SystemReset::RestartInSeconds(int seconds) { + for (int i = seconds; i > 0; i--) { + ESP_LOGI(TAG, "Resetting in %d seconds", i); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + esp_restart(); +} diff --git a/main/boards/common/system_reset.h b/main/boards/common/system_reset.h index 7e78296..c3f3f92 100644 --- a/main/boards/common/system_reset.h +++ b/main/boards/common/system_reset.h @@ -1,21 +1,21 @@ -#ifndef _SYSTEM_RESET_H -#define _SYSTEM_RESET_H - -#include - -class SystemReset { -public: - SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin); // 构造函数私有化 - void CheckButtons(); - -private: - gpio_num_t reset_nvs_pin_; - gpio_num_t reset_factory_pin_; - - void ResetNvsFlash(); - void ResetToFactory(); - void RestartInSeconds(int seconds); -}; - - -#endif +#ifndef _SYSTEM_RESET_H +#define _SYSTEM_RESET_H + +#include + +class SystemReset { +public: + SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin); // 构造函数私有化 + void CheckButtons(); + +private: + gpio_num_t reset_nvs_pin_; + gpio_num_t reset_factory_pin_; + + void ResetNvsFlash(); + void ResetToFactory(); + void RestartInSeconds(int seconds); +}; + + +#endif diff --git a/main/boards/common/wifi_board.cc b/main/boards/common/wifi_board.cc index 7b3bf06..d3469a0 100644 --- a/main/boards/common/wifi_board.cc +++ b/main/boards/common/wifi_board.cc @@ -1,265 +1,268 @@ -#include "wifi_board.h" - -#include "display.h" -#include "application.h" -#include "system_info.h" -#include "settings.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include "afsk_demod.h" - -static const char *TAG = "WifiBoard"; - -WifiBoard::WifiBoard() { - Settings settings("wifi", true); - wifi_config_mode_ = settings.GetInt("force_ap") == 1; - if (wifi_config_mode_) { - ESP_LOGI(TAG, "force_ap is set to 1, reset to 0"); - settings.SetInt("force_ap", 0); - } -} - -std::string WifiBoard::GetBoardType() { - return "wifi"; -} - -void WifiBoard::EnterWifiConfigMode() { - auto& application = Application::GetInstance(); - application.SetDeviceState(kDeviceStateWifiConfiguring); - - auto& wifi_ap = WifiConfigurationAp::GetInstance(); - wifi_ap.SetLanguage(Lang::CODE); - wifi_ap.SetSsidPrefix("Xiaozhi"); - wifi_ap.Start(); - - // 显示 WiFi 配置 AP 的 SSID 和 Web 服务器 URL - std::string hint = Lang::Strings::CONNECT_TO_HOTSPOT; - hint += wifi_ap.GetSsid(); - hint += Lang::Strings::ACCESS_VIA_BROWSER; - hint += wifi_ap.GetWebServerUrl(); - hint += "\n\n"; - - // 播报配置 WiFi 的提示 - application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "gear", Lang::Sounds::OGG_WIFICONFIG); - - #if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING - auto display = Board::GetInstance().GetDisplay(); - auto codec = Board::GetInstance().GetAudioCodec(); - int channel = 1; - if (codec) { - channel = codec->input_channels(); - } - ESP_LOGI(TAG, "Start receiving WiFi credentials from audio, input channels: %d", channel); - audio_wifi_config::ReceiveWifiCredentialsFromAudio(&application, &wifi_ap, display, channel); - #endif - - // Wait forever until reset after configuration - while (true) { - vTaskDelay(pdMS_TO_TICKS(10000)); - } -} - -void WifiBoard::StartNetwork() { - // User can press BOOT button while starting to enter WiFi configuration mode - if (wifi_config_mode_) { - EnterWifiConfigMode(); - return; - } - - // If no WiFi SSID is configured, enter WiFi configuration mode - auto& ssid_manager = SsidManager::GetInstance(); - auto ssid_list = ssid_manager.GetSsidList(); - if (ssid_list.empty()) { - wifi_config_mode_ = true; - EnterWifiConfigMode(); - return; - } - - auto& wifi_station = WifiStation::GetInstance(); - wifi_station.OnScanBegin([this]() { - auto display = Board::GetInstance().GetDisplay(); - display->ShowNotification(Lang::Strings::SCANNING_WIFI, 30000); - }); - wifi_station.OnConnect([this](const std::string& ssid) { - auto display = Board::GetInstance().GetDisplay(); - std::string notification = Lang::Strings::CONNECT_TO; - notification += ssid; - notification += "..."; - display->ShowNotification(notification.c_str(), 30000); - }); - wifi_station.OnConnected([this](const std::string& ssid) { - auto display = Board::GetInstance().GetDisplay(); - std::string notification = Lang::Strings::CONNECTED_TO; - notification += ssid; - display->ShowNotification(notification.c_str(), 30000); - }); - wifi_station.Start(); - - // Try to connect to WiFi, if failed, launch the WiFi configuration AP - if (!wifi_station.WaitForConnected(60 * 1000)) { - wifi_station.Stop(); - wifi_config_mode_ = true; - EnterWifiConfigMode(); - return; - } -} - -NetworkInterface* WifiBoard::GetNetwork() { - static EspNetwork network; - return &network; -} - -const char* WifiBoard::GetNetworkStateIcon() { - if (wifi_config_mode_) { - return FONT_AWESOME_WIFI; - } - auto& wifi_station = WifiStation::GetInstance(); - if (!wifi_station.IsConnected()) { - return FONT_AWESOME_WIFI_SLASH; - } - int8_t rssi = wifi_station.GetRssi(); - if (rssi >= -60) { - return FONT_AWESOME_WIFI; - } else if (rssi >= -70) { - return FONT_AWESOME_WIFI_FAIR; - } else { - return FONT_AWESOME_WIFI_WEAK; - } -} - -std::string WifiBoard::GetBoardJson() { - // Set the board type for OTA - auto& wifi_station = WifiStation::GetInstance(); - std::string board_json = R"({)"; - board_json += R"("type":")" + std::string(BOARD_TYPE) + R"(",)"; - board_json += R"("name":")" + std::string(BOARD_NAME) + R"(",)"; - if (!wifi_config_mode_) { - board_json += R"("ssid":")" + wifi_station.GetSsid() + R"(",)"; - board_json += R"("rssi":)" + std::to_string(wifi_station.GetRssi()) + R"(,)"; - board_json += R"("channel":)" + std::to_string(wifi_station.GetChannel()) + R"(,)"; - board_json += R"("ip":")" + wifi_station.GetIpAddress() + R"(",)"; - } - board_json += R"("mac":")" + SystemInfo::GetMacAddress() + R"(")"; - board_json += R"(})"; - return board_json; -} - -void WifiBoard::SetPowerSaveMode(bool enabled) { - auto& wifi_station = WifiStation::GetInstance(); - wifi_station.SetPowerSaveMode(enabled); -} - -void WifiBoard::ResetWifiConfiguration() { - // Set a flag and reboot the device to enter the network configuration mode - { - Settings settings("wifi", true); - settings.SetInt("force_ap", 1); - } - GetDisplay()->ShowNotification(Lang::Strings::ENTERING_WIFI_CONFIG_MODE); - vTaskDelay(pdMS_TO_TICKS(1000)); - // Reboot the device - esp_restart(); -} - -std::string WifiBoard::GetDeviceStatusJson() { - /* - * 返回设备状态JSON - * - * 返回的JSON结构如下: - * { - * "audio_speaker": { - * "volume": 70 - * }, - * "screen": { - * "brightness": 100, - * "theme": "light" - * }, - * "battery": { - * "level": 50, - * "charging": true - * }, - * "network": { - * "type": "wifi", - * "ssid": "Xiaozhi", - * "rssi": -60 - * }, - * "chip": { - * "temperature": 25 - * } - * } - */ - auto& board = Board::GetInstance(); - auto root = cJSON_CreateObject(); - - // Audio speaker - auto audio_speaker = cJSON_CreateObject(); - auto audio_codec = board.GetAudioCodec(); - if (audio_codec) { - cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume()); - } - cJSON_AddItemToObject(root, "audio_speaker", audio_speaker); - - // Screen brightness - auto backlight = board.GetBacklight(); - auto screen = cJSON_CreateObject(); - if (backlight) { - cJSON_AddNumberToObject(screen, "brightness", backlight->brightness()); - } - auto display = board.GetDisplay(); - if (display && display->height() > 64) { // For LCD display only - auto theme = display->GetTheme(); - if (theme != nullptr) { - cJSON_AddStringToObject(screen, "theme", theme->name().c_str()); - } - } - cJSON_AddItemToObject(root, "screen", screen); - - // Battery - int battery_level = 0; - bool charging = false; - bool discharging = false; - if (board.GetBatteryLevel(battery_level, charging, discharging)) { - cJSON* battery = cJSON_CreateObject(); - cJSON_AddNumberToObject(battery, "level", battery_level); - cJSON_AddBoolToObject(battery, "charging", charging); - cJSON_AddItemToObject(root, "battery", battery); - } - - // Network - auto network = cJSON_CreateObject(); - auto& wifi_station = WifiStation::GetInstance(); - cJSON_AddStringToObject(network, "type", "wifi"); - cJSON_AddStringToObject(network, "ssid", wifi_station.GetSsid().c_str()); - int rssi = wifi_station.GetRssi(); - if (rssi >= -60) { - cJSON_AddStringToObject(network, "signal", "strong"); - } else if (rssi >= -70) { - cJSON_AddStringToObject(network, "signal", "medium"); - } else { - cJSON_AddStringToObject(network, "signal", "weak"); - } - cJSON_AddItemToObject(root, "network", network); - - // Chip - float esp32temp = 0.0f; - if (board.GetTemperature(esp32temp)) { - auto chip = cJSON_CreateObject(); - cJSON_AddNumberToObject(chip, "temperature", esp32temp); - cJSON_AddItemToObject(root, "chip", chip); - } - - auto json_str = cJSON_PrintUnformatted(root); - std::string json(json_str); - cJSON_free(json_str); - cJSON_Delete(root); - return json; -} +#include "wifi_board.h" + +#include "display.h" +#include "application.h" +#include "system_info.h" +#include "settings.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include "afsk_demod.h" + +static const char *TAG = "WifiBoard"; + +WifiBoard::WifiBoard() { + Settings settings("wifi", true); + wifi_config_mode_ = settings.GetInt("force_ap") == 1; + if (wifi_config_mode_) { + ESP_LOGI(TAG, "force_ap is set to 1, reset to 0"); + settings.SetInt("force_ap", 0); + } +} + +std::string WifiBoard::GetBoardType() { + return "wifi"; +} + +void WifiBoard::EnterWifiConfigMode() { + auto& application = Application::GetInstance(); + application.SetDeviceState(kDeviceStateWifiConfiguring); + + auto& wifi_ap = WifiConfigurationAp::GetInstance(); + wifi_ap.SetLanguage(Lang::CODE); + wifi_ap.SetSsidPrefix("Xiaozhi"); + wifi_ap.Start(); + + // 等待 1.5 秒显示开发板信息 + vTaskDelay(pdMS_TO_TICKS(1500)); + + // 显示 WiFi 配置 AP 的 SSID 和 Web 服务器 URL + std::string hint = Lang::Strings::CONNECT_TO_HOTSPOT; + hint += wifi_ap.GetSsid(); + hint += Lang::Strings::ACCESS_VIA_BROWSER; + hint += wifi_ap.GetWebServerUrl(); + hint += "\n\n"; + + // 播报配置 WiFi 的提示 + application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "gear", Lang::Sounds::OGG_WIFICONFIG); + + #if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING + auto display = Board::GetInstance().GetDisplay(); + auto codec = Board::GetInstance().GetAudioCodec(); + int channel = 1; + if (codec) { + channel = codec->input_channels(); + } + ESP_LOGI(TAG, "Start receiving WiFi credentials from audio, input channels: %d", channel); + audio_wifi_config::ReceiveWifiCredentialsFromAudio(&application, &wifi_ap, display, channel); + #endif + + // Wait forever until reset after configuration + while (true) { + vTaskDelay(pdMS_TO_TICKS(10000)); + } +} + +void WifiBoard::StartNetwork() { + // User can press BOOT button while starting to enter WiFi configuration mode + if (wifi_config_mode_) { + EnterWifiConfigMode(); + return; + } + + // If no WiFi SSID is configured, enter WiFi configuration mode + auto& ssid_manager = SsidManager::GetInstance(); + auto ssid_list = ssid_manager.GetSsidList(); + if (ssid_list.empty()) { + wifi_config_mode_ = true; + EnterWifiConfigMode(); + return; + } + + auto& wifi_station = WifiStation::GetInstance(); + wifi_station.OnScanBegin([this]() { + auto display = Board::GetInstance().GetDisplay(); + display->ShowNotification(Lang::Strings::SCANNING_WIFI, 30000); + }); + wifi_station.OnConnect([this](const std::string& ssid) { + auto display = Board::GetInstance().GetDisplay(); + std::string notification = Lang::Strings::CONNECT_TO; + notification += ssid; + notification += "..."; + display->ShowNotification(notification.c_str(), 30000); + }); + wifi_station.OnConnected([this](const std::string& ssid) { + auto display = Board::GetInstance().GetDisplay(); + std::string notification = Lang::Strings::CONNECTED_TO; + notification += ssid; + display->ShowNotification(notification.c_str(), 30000); + }); + wifi_station.Start(); + + // Try to connect to WiFi, if failed, launch the WiFi configuration AP + if (!wifi_station.WaitForConnected(60 * 1000)) { + wifi_station.Stop(); + wifi_config_mode_ = true; + EnterWifiConfigMode(); + return; + } +} + +NetworkInterface* WifiBoard::GetNetwork() { + static EspNetwork network; + return &network; +} + +const char* WifiBoard::GetNetworkStateIcon() { + if (wifi_config_mode_) { + return FONT_AWESOME_WIFI; + } + auto& wifi_station = WifiStation::GetInstance(); + if (!wifi_station.IsConnected()) { + return FONT_AWESOME_WIFI_SLASH; + } + int8_t rssi = wifi_station.GetRssi(); + if (rssi >= -60) { + return FONT_AWESOME_WIFI; + } else if (rssi >= -70) { + return FONT_AWESOME_WIFI_FAIR; + } else { + return FONT_AWESOME_WIFI_WEAK; + } +} + +std::string WifiBoard::GetBoardJson() { + // Set the board type for OTA + auto& wifi_station = WifiStation::GetInstance(); + std::string board_json = R"({)"; + board_json += R"("type":")" + std::string(BOARD_TYPE) + R"(",)"; + board_json += R"("name":")" + std::string(BOARD_NAME) + R"(",)"; + if (!wifi_config_mode_) { + board_json += R"("ssid":")" + wifi_station.GetSsid() + R"(",)"; + board_json += R"("rssi":)" + std::to_string(wifi_station.GetRssi()) + R"(,)"; + board_json += R"("channel":)" + std::to_string(wifi_station.GetChannel()) + R"(,)"; + board_json += R"("ip":")" + wifi_station.GetIpAddress() + R"(",)"; + } + board_json += R"("mac":")" + SystemInfo::GetMacAddress() + R"(")"; + board_json += R"(})"; + return board_json; +} + +void WifiBoard::SetPowerSaveMode(bool enabled) { + auto& wifi_station = WifiStation::GetInstance(); + wifi_station.SetPowerSaveMode(enabled); +} + +void WifiBoard::ResetWifiConfiguration() { + // Set a flag and reboot the device to enter the network configuration mode + { + Settings settings("wifi", true); + settings.SetInt("force_ap", 1); + } + GetDisplay()->ShowNotification(Lang::Strings::ENTERING_WIFI_CONFIG_MODE); + vTaskDelay(pdMS_TO_TICKS(1000)); + // Reboot the device + esp_restart(); +} + +std::string WifiBoard::GetDeviceStatusJson() { + /* + * 返回设备状态JSON + * + * 返回的JSON结构如下: + * { + * "audio_speaker": { + * "volume": 70 + * }, + * "screen": { + * "brightness": 100, + * "theme": "light" + * }, + * "battery": { + * "level": 50, + * "charging": true + * }, + * "network": { + * "type": "wifi", + * "ssid": "Xiaozhi", + * "rssi": -60 + * }, + * "chip": { + * "temperature": 25 + * } + * } + */ + auto& board = Board::GetInstance(); + auto root = cJSON_CreateObject(); + + // Audio speaker + auto audio_speaker = cJSON_CreateObject(); + auto audio_codec = board.GetAudioCodec(); + if (audio_codec) { + cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume()); + } + cJSON_AddItemToObject(root, "audio_speaker", audio_speaker); + + // Screen brightness + auto backlight = board.GetBacklight(); + auto screen = cJSON_CreateObject(); + if (backlight) { + cJSON_AddNumberToObject(screen, "brightness", backlight->brightness()); + } + auto display = board.GetDisplay(); + if (display && display->height() > 64) { // For LCD display only + auto theme = display->GetTheme(); + if (theme != nullptr) { + cJSON_AddStringToObject(screen, "theme", theme->name().c_str()); + } + } + cJSON_AddItemToObject(root, "screen", screen); + + // Battery + int battery_level = 0; + bool charging = false; + bool discharging = false; + if (board.GetBatteryLevel(battery_level, charging, discharging)) { + cJSON* battery = cJSON_CreateObject(); + cJSON_AddNumberToObject(battery, "level", battery_level); + cJSON_AddBoolToObject(battery, "charging", charging); + cJSON_AddItemToObject(root, "battery", battery); + } + + // Network + auto network = cJSON_CreateObject(); + auto& wifi_station = WifiStation::GetInstance(); + cJSON_AddStringToObject(network, "type", "wifi"); + cJSON_AddStringToObject(network, "ssid", wifi_station.GetSsid().c_str()); + int rssi = wifi_station.GetRssi(); + if (rssi >= -60) { + cJSON_AddStringToObject(network, "signal", "strong"); + } else if (rssi >= -70) { + cJSON_AddStringToObject(network, "signal", "medium"); + } else { + cJSON_AddStringToObject(network, "signal", "weak"); + } + cJSON_AddItemToObject(root, "network", network); + + // Chip + float esp32temp = 0.0f; + if (board.GetTemperature(esp32temp)) { + auto chip = cJSON_CreateObject(); + cJSON_AddNumberToObject(chip, "temperature", esp32temp); + cJSON_AddItemToObject(root, "chip", chip); + } + + auto json_str = cJSON_PrintUnformatted(root); + std::string json(json_str); + cJSON_free(json_str); + cJSON_Delete(root); + return json; +} diff --git a/main/boards/common/wifi_board.h b/main/boards/common/wifi_board.h index c84cf0f..a6f3b31 100644 --- a/main/boards/common/wifi_board.h +++ b/main/boards/common/wifi_board.h @@ -1,24 +1,24 @@ -#ifndef WIFI_BOARD_H -#define WIFI_BOARD_H - -#include "board.h" - -class WifiBoard : public Board { -protected: - bool wifi_config_mode_ = false; - void EnterWifiConfigMode(); - virtual std::string GetBoardJson() override; - -public: - WifiBoard(); - virtual std::string GetBoardType() override; - virtual void StartNetwork() override; - virtual NetworkInterface* GetNetwork() override; - virtual const char* GetNetworkStateIcon() override; - virtual void SetPowerSaveMode(bool enabled) override; - virtual void ResetWifiConfiguration(); - virtual AudioCodec* GetAudioCodec() override { return nullptr; } - virtual std::string GetDeviceStatusJson() override; -}; - -#endif // WIFI_BOARD_H +#ifndef WIFI_BOARD_H +#define WIFI_BOARD_H + +#include "board.h" + +class WifiBoard : public Board { +protected: + bool wifi_config_mode_ = false; + void EnterWifiConfigMode(); + virtual std::string GetBoardJson() override; + +public: + WifiBoard(); + virtual std::string GetBoardType() override; + virtual void StartNetwork() override; + virtual NetworkInterface* GetNetwork() override; + virtual const char* GetNetworkStateIcon() override; + virtual void SetPowerSaveMode(bool enabled) override; + virtual void ResetWifiConfiguration(); + virtual AudioCodec* GetAudioCodec() override { return nullptr; } + virtual std::string GetDeviceStatusJson() override; +}; + +#endif // WIFI_BOARD_H diff --git a/main/boards/df-k10/config.h b/main/boards/df-k10/config.h index c61c86f..0e19f83 100644 --- a/main/boards/df-k10/config.h +++ b/main/boards/df-k10/config.h @@ -1,76 +1,76 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_3 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_38 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_0 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR 0x23 - -#define BUILTIN_LED_GPIO GPIO_NUM_46 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -/* Expander */ -#define DRV_IO_EXP_INPUT_MASK (IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_12) - - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -/* DFRobot K10 Camera pins */ -#define PWDN_GPIO_NUM -1 -#define RESET_GPIO_NUM -1 -#define XCLK_GPIO_NUM 7 - -#define VSYNC_GPIO_NUM 4 -#define HREF_GPIO_NUM 5 -#define PCLK_GPIO_NUM 17 -#define SIOD_GPIO_NUM 20 -#define SIOC_GPIO_NUM 19 - -/* Camera pins */ -#define CAMERA_PIN_PWDN PWDN_GPIO_NUM -#define CAMERA_PIN_RESET RESET_GPIO_NUM -#define CAMERA_PIN_XCLK XCLK_GPIO_NUM -#define CAMERA_PIN_SIOD SIOD_GPIO_NUM -#define CAMERA_PIN_SIOC SIOC_GPIO_NUM - -#define CAMERA_PIN_D9 6 -#define CAMERA_PIN_D8 15 -#define CAMERA_PIN_D7 16 -#define CAMERA_PIN_D6 18 -#define CAMERA_PIN_D5 9 -#define CAMERA_PIN_D4 11 -#define CAMERA_PIN_D3 10 -#define CAMERA_PIN_D2 8 -#define CAMERA_PIN_VSYNC VSYNC_GPIO_NUM -#define CAMERA_PIN_HREF HREF_GPIO_NUM -#define CAMERA_PIN_PCLK PCLK_GPIO_NUM - -#define XCLK_FREQ_HZ 20000000 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_3 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_38 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_0 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR 0x23 + +#define BUILTIN_LED_GPIO GPIO_NUM_46 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +/* Expander */ +#define DRV_IO_EXP_INPUT_MASK (IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_12) + + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +/* DFRobot K10 Camera pins */ +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 7 + +#define VSYNC_GPIO_NUM 4 +#define HREF_GPIO_NUM 5 +#define PCLK_GPIO_NUM 17 +#define SIOD_GPIO_NUM 20 +#define SIOC_GPIO_NUM 19 + +/* Camera pins */ +#define CAMERA_PIN_PWDN PWDN_GPIO_NUM +#define CAMERA_PIN_RESET RESET_GPIO_NUM +#define CAMERA_PIN_XCLK XCLK_GPIO_NUM +#define CAMERA_PIN_SIOD SIOD_GPIO_NUM +#define CAMERA_PIN_SIOC SIOC_GPIO_NUM + +#define CAMERA_PIN_D9 6 +#define CAMERA_PIN_D8 15 +#define CAMERA_PIN_D7 16 +#define CAMERA_PIN_D6 18 +#define CAMERA_PIN_D5 9 +#define CAMERA_PIN_D4 11 +#define CAMERA_PIN_D3 10 +#define CAMERA_PIN_D2 8 +#define CAMERA_PIN_VSYNC VSYNC_GPIO_NUM +#define CAMERA_PIN_HREF HREF_GPIO_NUM +#define CAMERA_PIN_PCLK PCLK_GPIO_NUM + +#define XCLK_FREQ_HZ 20000000 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/df-k10/config.json b/main/boards/df-k10/config.json index 55137d4..71f6d21 100644 --- a/main/boards/df-k10/config.json +++ b/main/boards/df-k10/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "df-k10", - "sdkconfig_append": [ - "CONFIG_SPIRAM_MODE_OCT=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "df-k10", + "sdkconfig_append": [ + "CONFIG_SPIRAM_MODE_OCT=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/df-k10/df_k10_board.cc b/main/boards/df-k10/df_k10_board.cc index 614be11..5280b77 100644 --- a/main/boards/df-k10/df_k10_board.cc +++ b/main/boards/df-k10/df_k10_board.cc @@ -1,286 +1,286 @@ -#include "wifi_board.h" -#include "k10_audio_codec.h" -#include "display/lcd_display.h" -#include "esp_lcd_ili9341.h" -#include "led_control.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "esp32_camera.h" - -#include "led/circular_strip.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include - -#include "esp_io_expander_tca95xx_16bit.h" - -#define TAG "DF-K10" - -class Df_K10Board : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - esp_io_expander_handle_t io_expander; - LcdDisplay *display_; - button_handle_t btn_a; - button_handle_t btn_b; - Esp32Camera* camera_; - - button_driver_t* btn_a_driver_ = nullptr; - button_driver_t* btn_b_driver_ = nullptr; - - CircularStrip* led_strip_; - - static Df_K10Board* instance_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_21; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_12; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) { - return esp_io_expander_set_level(io_expander, pin_mask, level); - } - - uint8_t IoExpanderGetLevel(uint16_t pin_mask) { - uint32_t pin_val = 0; - esp_io_expander_get_level(io_expander, DRV_IO_EXP_INPUT_MASK, &pin_val); - pin_mask &= DRV_IO_EXP_INPUT_MASK; - return (uint8_t)((pin_val & pin_mask) ? 1 : 0); - } - - void InitializeIoExpander() { - esp_io_expander_new_i2c_tca95xx_16bit( - i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_expander); - - esp_err_t ret; - ret = esp_io_expander_print_state(io_expander); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Print state failed: %s", esp_err_to_name(ret)); - } - ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0, - IO_EXPANDER_OUTPUT); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Set direction failed: %s", esp_err_to_name(ret)); - } - ret = esp_io_expander_set_level(io_expander, 0, 1); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Set level failed: %s", esp_err_to_name(ret)); - } - ret = esp_io_expander_set_dir( - io_expander, DRV_IO_EXP_INPUT_MASK, - IO_EXPANDER_INPUT); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Set direction failed: %s", esp_err_to_name(ret)); - } - } - - void InitializeButtons() { - instance_ = this; - - // Button A - button_config_t btn_a_config = { - .long_press_time = 1000, - .short_press_time = 0 - }; - btn_a_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); - btn_a_driver_->enable_power_save = false; - btn_a_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { - return !instance_->IoExpanderGetLevel(IO_EXPANDER_PIN_NUM_2); - }; - ESP_ERROR_CHECK(iot_button_create(&btn_a_config, btn_a_driver_, &btn_a)); - iot_button_register_cb(btn_a, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); - } - app.ToggleChatState(); - }, this); - iot_button_register_cb(btn_a, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - auto codec = self->GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - self->GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }, this); - - // Button B - button_config_t btn_b_config = { - .long_press_time = 1000, - .short_press_time = 0 - }; - btn_b_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); - btn_b_driver_->enable_power_save = false; - btn_b_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { - return !instance_->IoExpanderGetLevel(IO_EXPANDER_PIN_NUM_12); - }; - ESP_ERROR_CHECK(iot_button_create(&btn_b_config, btn_b_driver_, &btn_b)); - iot_button_register_cb(btn_b, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); - } - app.ToggleChatState(); - }, this); - iot_button_register_cb(btn_b, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - auto codec = self->GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - self->GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }, this); - } - - void InitializeCamera() { - - camera_config_t config = {}; - config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 - config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 - config.pin_d0 = CAMERA_PIN_D2; - config.pin_d1 = CAMERA_PIN_D3; - config.pin_d2 = CAMERA_PIN_D4; - config.pin_d3 = CAMERA_PIN_D5; - config.pin_d4 = CAMERA_PIN_D6; - config.pin_d5 = CAMERA_PIN_D7; - config.pin_d6 = CAMERA_PIN_D8; - config.pin_d7 = CAMERA_PIN_D9; - config.pin_xclk = CAMERA_PIN_XCLK; - config.pin_pclk = CAMERA_PIN_PCLK; - config.pin_vsync = CAMERA_PIN_VSYNC; - config.pin_href = CAMERA_PIN_HREF; - config.pin_sccb_sda = -1; // 这里如果写-1 表示使用已经初始化的I2C接口 - config.pin_sccb_scl = CAMERA_PIN_SIOC; - config.sccb_i2c_port = 1; // 这里如果写1 默认使用I2C1 - config.pin_pwdn = CAMERA_PIN_PWDN; - config.pin_reset = CAMERA_PIN_RESET; - config.xclk_freq_hz = XCLK_FREQ_HZ; - config.pixel_format = PIXFORMAT_RGB565; - config.frame_size = FRAMESIZE_VGA; - config.jpeg_quality = 12; - config.fb_count = 1; - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - camera_ = new Esp32Camera(config); - } - - void InitializeIli9341Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_14; - io_config.dc_gpio_num = GPIO_NUM_13; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.bits_per_pixel = 16; - panel_config.color_space = ESP_LCD_COLOR_SPACE_BGR; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 3); - new LedStripControl(led_strip_); - } - -public: - Df_K10Board() { - InitializeI2c(); - InitializeIoExpander(); - InitializeSpi(); - InitializeIli9341Display(); - InitializeButtons(); - InitializeIot(); - InitializeCamera(); - } - - virtual Led* GetLed() override { - return led_strip_; - } - - virtual AudioCodec *GetAudioCodec() override { - static K10AudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Camera* GetCamera() override { - return camera_; - } - - virtual Display *GetDisplay() override { - return display_; - } -}; - -DECLARE_BOARD(Df_K10Board); - -Df_K10Board* Df_K10Board::instance_ = nullptr; +#include "wifi_board.h" +#include "k10_audio_codec.h" +#include "display/lcd_display.h" +#include "esp_lcd_ili9341.h" +#include "led_control.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "esp32_camera.h" + +#include "led/circular_strip.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include + +#include "esp_io_expander_tca95xx_16bit.h" + +#define TAG "DF-K10" + +class Df_K10Board : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + esp_io_expander_handle_t io_expander; + LcdDisplay *display_; + button_handle_t btn_a; + button_handle_t btn_b; + Esp32Camera* camera_; + + button_driver_t* btn_a_driver_ = nullptr; + button_driver_t* btn_b_driver_ = nullptr; + + CircularStrip* led_strip_; + + static Df_K10Board* instance_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_21; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_12; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) { + return esp_io_expander_set_level(io_expander, pin_mask, level); + } + + uint8_t IoExpanderGetLevel(uint16_t pin_mask) { + uint32_t pin_val = 0; + esp_io_expander_get_level(io_expander, DRV_IO_EXP_INPUT_MASK, &pin_val); + pin_mask &= DRV_IO_EXP_INPUT_MASK; + return (uint8_t)((pin_val & pin_mask) ? 1 : 0); + } + + void InitializeIoExpander() { + esp_io_expander_new_i2c_tca95xx_16bit( + i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_expander); + + esp_err_t ret; + ret = esp_io_expander_print_state(io_expander); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Print state failed: %s", esp_err_to_name(ret)); + } + ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0, + IO_EXPANDER_OUTPUT); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Set direction failed: %s", esp_err_to_name(ret)); + } + ret = esp_io_expander_set_level(io_expander, 0, 1); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Set level failed: %s", esp_err_to_name(ret)); + } + ret = esp_io_expander_set_dir( + io_expander, DRV_IO_EXP_INPUT_MASK, + IO_EXPANDER_INPUT); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Set direction failed: %s", esp_err_to_name(ret)); + } + } + + void InitializeButtons() { + instance_ = this; + + // Button A + button_config_t btn_a_config = { + .long_press_time = 1000, + .short_press_time = 0 + }; + btn_a_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + btn_a_driver_->enable_power_save = false; + btn_a_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return !instance_->IoExpanderGetLevel(IO_EXPANDER_PIN_NUM_2); + }; + ESP_ERROR_CHECK(iot_button_create(&btn_a_config, btn_a_driver_, &btn_a)); + iot_button_register_cb(btn_a, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + self->ResetWifiConfiguration(); + } + app.ToggleChatState(); + }, this); + iot_button_register_cb(btn_a, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + auto codec = self->GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + self->GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }, this); + + // Button B + button_config_t btn_b_config = { + .long_press_time = 1000, + .short_press_time = 0 + }; + btn_b_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + btn_b_driver_->enable_power_save = false; + btn_b_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return !instance_->IoExpanderGetLevel(IO_EXPANDER_PIN_NUM_12); + }; + ESP_ERROR_CHECK(iot_button_create(&btn_b_config, btn_b_driver_, &btn_b)); + iot_button_register_cb(btn_b, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + self->ResetWifiConfiguration(); + } + app.ToggleChatState(); + }, this); + iot_button_register_cb(btn_b, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + auto codec = self->GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + self->GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }, this); + } + + void InitializeCamera() { + + camera_config_t config = {}; + config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 + config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 + config.pin_d0 = CAMERA_PIN_D2; + config.pin_d1 = CAMERA_PIN_D3; + config.pin_d2 = CAMERA_PIN_D4; + config.pin_d3 = CAMERA_PIN_D5; + config.pin_d4 = CAMERA_PIN_D6; + config.pin_d5 = CAMERA_PIN_D7; + config.pin_d6 = CAMERA_PIN_D8; + config.pin_d7 = CAMERA_PIN_D9; + config.pin_xclk = CAMERA_PIN_XCLK; + config.pin_pclk = CAMERA_PIN_PCLK; + config.pin_vsync = CAMERA_PIN_VSYNC; + config.pin_href = CAMERA_PIN_HREF; + config.pin_sccb_sda = -1; // 这里如果写-1 表示使用已经初始化的I2C接口 + config.pin_sccb_scl = CAMERA_PIN_SIOC; + config.sccb_i2c_port = 1; // 这里如果写1 默认使用I2C1 + config.pin_pwdn = CAMERA_PIN_PWDN; + config.pin_reset = CAMERA_PIN_RESET; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_VGA; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + camera_ = new Esp32Camera(config); + } + + void InitializeIli9341Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_14; + io_config.dc_gpio_num = GPIO_NUM_13; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.bits_per_pixel = 16; + panel_config.color_space = ESP_LCD_COLOR_SPACE_BGR; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeIot() { + led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 3); + new LedStripControl(led_strip_); + } + +public: + Df_K10Board() { + InitializeI2c(); + InitializeIoExpander(); + InitializeSpi(); + InitializeIli9341Display(); + InitializeButtons(); + InitializeIot(); + InitializeCamera(); + } + + virtual Led* GetLed() override { + return led_strip_; + } + + virtual AudioCodec *GetAudioCodec() override { + static K10AudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Camera* GetCamera() override { + return camera_; + } + + virtual Display *GetDisplay() override { + return display_; + } +}; + +DECLARE_BOARD(Df_K10Board); + +Df_K10Board* Df_K10Board::instance_ = nullptr; diff --git a/main/boards/df-k10/k10_audio_codec.cc b/main/boards/df-k10/k10_audio_codec.cc index 5a30948..30fbf0f 100644 --- a/main/boards/df-k10/k10_audio_codec.cc +++ b/main/boards/df-k10/k10_audio_codec.cc @@ -1,225 +1,225 @@ -#include "k10_audio_codec.h" - -#include -#include -#include -#include - -static const char TAG[] = "K10AudioCodec"; - -K10AudioCodec::K10AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference) { - duplex_ = true; // 是否双工 - input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 - input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - CreateDuplexChannels(mclk, bclk, ws, dout, din); - - // Do initialize of related interface: data_if, ctrl_if and gpio_if - audio_codec_i2s_cfg_t i2s_cfg = { - .port = I2S_NUM_0, - .rx_handle = rx_handle_, - .tx_handle = tx_handle_, - }; - data_if_ = audio_codec_new_i2s_data(&i2s_cfg); - assert(data_if_ != NULL); - - audio_codec_i2c_cfg_t i2c_cfg = { - .port = I2C_NUM_1, - .addr = es7210_addr, - .bus_handle = i2c_master_handle, - }; - const audio_codec_ctrl_if_t *in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(in_ctrl_if_ != NULL); - - es7243e_codec_cfg_t es7243e_cfg = { - .ctrl_if = in_ctrl_if_, - }; - const audio_codec_if_t *in_codec_if_ = es7243e_codec_new(&es7243e_cfg); - assert(in_codec_if_ != NULL); - - esp_codec_dev_cfg_t codec_es7243e_dev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_IN, - .codec_if = in_codec_if_, - .data_if = data_if_, - }; - input_dev_ = esp_codec_dev_new(&codec_es7243e_dev_cfg); - - assert(input_dev_ != NULL); - - ESP_LOGI(TAG, "DF-K10 AudioDevice initialized"); -} - -K10AudioCodec::~K10AudioCodec() { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - esp_codec_dev_delete(output_dev_); - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - esp_codec_dev_delete(input_dev_); - - audio_codec_delete_codec_if(in_codec_if_); - audio_codec_delete_ctrl_if(in_ctrl_if_); - audio_codec_delete_codec_if(out_codec_if_); - audio_codec_delete_ctrl_if(out_ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void K10AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { - assert(input_sample_rate_ == output_sample_rate_); - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, - .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256 - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_MONO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_16BIT, - .ws_pol = false, - .bit_shift = true, - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - }, - .gpio_cfg = { - // .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - i2s_tdm_config_t tdm_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)input_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - .bclk_div = 8, - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), - .ws_width = I2S_TDM_AUTO_WS_WIDTH, - .ws_pol = false, - .bit_shift = true, - .left_align = false, - .big_endian = false, - .bit_order_lsb = false, - .skip_mask = false, - .total_slot = I2S_TDM_AUTO_SLOT_NUM - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = I2S_GPIO_UNUSED, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle_, &tdm_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); -} - -void K10AudioCodec::SetOutputVolume(int volume) { - AudioCodec::SetOutputVolume(volume); -} - -void K10AudioCodec::EnableInput(bool enable) { - if (enable == input_enabled_) { - return; - } - if (enable) { - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 4, - .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - if (input_reference_) { - fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1); - } - ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 37.5)); //麦克风增益解决收音太小的问题 - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - } - AudioCodec::EnableInput(enable); -} - -void K10AudioCodec::EnableOutput(bool enable) { - if (enable == output_enabled_) { - return; - } - AudioCodec::SetOutputVolume(output_volume_); - AudioCodec::EnableOutput(enable); -} - -int K10AudioCodec::Read(int16_t* dest, int samples) { - if (input_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); - } - return samples; -} - -int K10AudioCodec::Write(const int16_t* data, int samples) { - if (output_enabled_) { - std::vector buffer(samples * 2); // Allocate buffer for 2x samples - - // Apply volume adjustment (same as before) - int32_t volume_factor = pow(double(output_volume_) / 100.0, 2) * 65536; - for (int i = 0; i < samples; i++) { - int64_t temp = int64_t(data[i]) * volume_factor; - if (temp > INT32_MAX) { - buffer[i * 2] = INT32_MAX; - } else if (temp < INT32_MIN) { - buffer[i * 2] = INT32_MIN; - } else { - buffer[i * 2] = static_cast(temp); - } - - // Repeat each sample for slow playback (assuming mono audio) - buffer[i * 2 + 1] = buffer[i * 2]; - } - - size_t bytes_written; - ESP_ERROR_CHECK(i2s_channel_write(tx_handle_, buffer.data(), samples * 2 * sizeof(int32_t), &bytes_written, portMAX_DELAY)); - return bytes_written / sizeof(int32_t); - } - return samples; -} +#include "k10_audio_codec.h" + +#include +#include +#include +#include + +static const char TAG[] = "K10AudioCodec"; + +K10AudioCodec::K10AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference) { + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + CreateDuplexChannels(mclk, bclk, ws, dout, din); + + // Do initialize of related interface: data_if, ctrl_if and gpio_if + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = rx_handle_, + .tx_handle = tx_handle_, + }; + data_if_ = audio_codec_new_i2s_data(&i2s_cfg); + assert(data_if_ != NULL); + + audio_codec_i2c_cfg_t i2c_cfg = { + .port = I2C_NUM_1, + .addr = es7210_addr, + .bus_handle = i2c_master_handle, + }; + const audio_codec_ctrl_if_t *in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(in_ctrl_if_ != NULL); + + es7243e_codec_cfg_t es7243e_cfg = { + .ctrl_if = in_ctrl_if_, + }; + const audio_codec_if_t *in_codec_if_ = es7243e_codec_new(&es7243e_cfg); + assert(in_codec_if_ != NULL); + + esp_codec_dev_cfg_t codec_es7243e_dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_IN, + .codec_if = in_codec_if_, + .data_if = data_if_, + }; + input_dev_ = esp_codec_dev_new(&codec_es7243e_dev_cfg); + + assert(input_dev_ != NULL); + + ESP_LOGI(TAG, "DF-K10 AudioDevice initialized"); +} + +K10AudioCodec::~K10AudioCodec() { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + esp_codec_dev_delete(output_dev_); + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + esp_codec_dev_delete(input_dev_); + + audio_codec_delete_codec_if(in_codec_if_); + audio_codec_delete_ctrl_if(in_ctrl_if_); + audio_codec_delete_codec_if(out_codec_if_); + audio_codec_delete_ctrl_if(out_ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void K10AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + assert(input_sample_rate_ == output_sample_rate_); + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, + .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256 + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_MONO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + }, + .gpio_cfg = { + // .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + i2s_tdm_config_t tdm_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)input_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + .bclk_div = 8, + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), + .ws_width = I2S_TDM_AUTO_WS_WIDTH, + .ws_pol = false, + .bit_shift = true, + .left_align = false, + .big_endian = false, + .bit_order_lsb = false, + .skip_mask = false, + .total_slot = I2S_TDM_AUTO_SLOT_NUM + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = I2S_GPIO_UNUSED, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle_, &tdm_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + +void K10AudioCodec::SetOutputVolume(int volume) { + AudioCodec::SetOutputVolume(volume); +} + +void K10AudioCodec::EnableInput(bool enable) { + if (enable == input_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 4, + .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + if (input_reference_) { + fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1); + } + ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 37.5)); //麦克风增益解决收音太小的问题 + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void K10AudioCodec::EnableOutput(bool enable) { + if (enable == output_enabled_) { + return; + } + AudioCodec::SetOutputVolume(output_volume_); + AudioCodec::EnableOutput(enable); +} + +int K10AudioCodec::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} + +int K10AudioCodec::Write(const int16_t* data, int samples) { + if (output_enabled_) { + std::vector buffer(samples * 2); // Allocate buffer for 2x samples + + // Apply volume adjustment (same as before) + int32_t volume_factor = pow(double(output_volume_) / 100.0, 2) * 65536; + for (int i = 0; i < samples; i++) { + int64_t temp = int64_t(data[i]) * volume_factor; + if (temp > INT32_MAX) { + buffer[i * 2] = INT32_MAX; + } else if (temp < INT32_MIN) { + buffer[i * 2] = INT32_MIN; + } else { + buffer[i * 2] = static_cast(temp); + } + + // Repeat each sample for slow playback (assuming mono audio) + buffer[i * 2 + 1] = buffer[i * 2]; + } + + size_t bytes_written; + ESP_ERROR_CHECK(i2s_channel_write(tx_handle_, buffer.data(), samples * 2 * sizeof(int32_t), &bytes_written, portMAX_DELAY)); + return bytes_written / sizeof(int32_t); + } + return samples; +} diff --git a/main/boards/df-k10/k10_audio_codec.h b/main/boards/df-k10/k10_audio_codec.h index 061adbe..44a8d0b 100644 --- a/main/boards/df-k10/k10_audio_codec.h +++ b/main/boards/df-k10/k10_audio_codec.h @@ -1,37 +1,37 @@ -#ifndef _BOX_AUDIO_CODEC_H -#define _BOX_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include - -class K10AudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t* data_if_ = nullptr; - const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; - const audio_codec_if_t* out_codec_if_ = nullptr; - const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; - const audio_codec_if_t* in_codec_if_ = nullptr; - const audio_codec_gpio_if_t* gpio_if_ = nullptr; - - esp_codec_dev_handle_t output_dev_ = nullptr; - esp_codec_dev_handle_t input_dev_ = nullptr; - - void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); - - virtual int Read(int16_t* dest, int samples) override; - virtual int Write(const int16_t* data, int samples) override; - -public: - K10AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference); - virtual ~K10AudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - -#endif // _BOX_AUDIO_CODEC_H +#ifndef _BOX_AUDIO_CODEC_H +#define _BOX_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include + +class K10AudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t* data_if_ = nullptr; + const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; + const audio_codec_if_t* out_codec_if_ = nullptr; + const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; + const audio_codec_if_t* in_codec_if_ = nullptr; + const audio_codec_gpio_if_t* gpio_if_ = nullptr; + + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + + void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + K10AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference); + virtual ~K10AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _BOX_AUDIO_CODEC_H diff --git a/main/boards/df-k10/led_control.cc b/main/boards/df-k10/led_control.cc index ce5164a..454b0ca 100644 --- a/main/boards/df-k10/led_control.cc +++ b/main/boards/df-k10/led_control.cc @@ -1,124 +1,124 @@ -#include "led_control.h" -#include "settings.h" -#include "mcp_server.h" -#include - -#define TAG "LedStripControl" - - -int LedStripControl::LevelToBrightness(int level) const { - if (level < 0) level = 0; - if (level > 8) level = 8; - return (1 << level) - 1; // 2^n - 1 -} - -StripColor LedStripControl::RGBToColor(int red, int green, int blue) { - return {static_cast(red), static_cast(green), static_cast(blue)}; -} - -LedStripControl::LedStripControl(CircularStrip* led_strip) - : led_strip_(led_strip) { - // 从设置中读取亮度等级 - Settings settings("led_strip"); - brightness_level_ = settings.GetInt("brightness", 4); // 默认等级4 - led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4); - - auto& mcp_server = McpServer::GetInstance(); - mcp_server.AddTool("self.led_strip.get_brightness", - "Get the brightness of the led strip (0-8)", - PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - return brightness_level_; - }); - - mcp_server.AddTool("self.led_strip.set_brightness", - "Set the brightness of the led strip (0-8)", - PropertyList({ - Property("level", kPropertyTypeInteger, 0, 8) - }), [this](const PropertyList& properties) -> ReturnValue { - int level = properties["level"].value(); - ESP_LOGI(TAG, "Set LedStrip brightness level to %d", level); - brightness_level_ = level; - led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4); - - // 保存设置 - Settings settings("led_strip", true); - settings.SetInt("brightness", brightness_level_); - - return true; - }); - - mcp_server.AddTool("self.led_strip.set_single_color", - "Set the color of a single led.", - PropertyList({ - Property("index", kPropertyTypeInteger, 0, 2), - Property("red", kPropertyTypeInteger, 0, 255), - Property("green", kPropertyTypeInteger, 0, 255), - Property("blue", kPropertyTypeInteger, 0, 255) - }), [this](const PropertyList& properties) -> ReturnValue { - int index = properties["index"].value(); - int red = properties["red"].value(); - int green = properties["green"].value(); - int blue = properties["blue"].value(); - ESP_LOGI(TAG, "Set led strip single color %d to %d, %d, %d", - index, red, green, blue); - led_strip_->SetSingleColor(index, RGBToColor(red, green, blue)); - return true; - }); - - mcp_server.AddTool("self.led_strip.set_all_color", - "Set the color of all leds.", - PropertyList({ - Property("red", kPropertyTypeInteger, 0, 255), - Property("green", kPropertyTypeInteger, 0, 255), - Property("blue", kPropertyTypeInteger, 0, 255) - }), [this](const PropertyList& properties) -> ReturnValue { - int red = properties["red"].value(); - int green = properties["green"].value(); - int blue = properties["blue"].value(); - ESP_LOGI(TAG, "Set led strip all color to %d, %d, %d", - red, green, blue); - led_strip_->SetAllColor(RGBToColor(red, green, blue)); - return true; - }); - - mcp_server.AddTool("self.led_strip.blink", - "Blink the led strip. (闪烁)", - PropertyList({ - Property("red", kPropertyTypeInteger, 0, 255), - Property("green", kPropertyTypeInteger, 0, 255), - Property("blue", kPropertyTypeInteger, 0, 255), - Property("interval", kPropertyTypeInteger, 0, 1000) - }), [this](const PropertyList& properties) -> ReturnValue { - int red = properties["red"].value(); - int green = properties["green"].value(); - int blue = properties["blue"].value(); - int interval = properties["interval"].value(); - ESP_LOGI(TAG, "Blink led strip with color %d, %d, %d, interval %dms", - red, green, blue, interval); - led_strip_->Blink(RGBToColor(red, green, blue), interval); - return true; - }); - - mcp_server.AddTool("self.led_strip.scroll", - "Scroll the led strip. (跑马灯)", - PropertyList({ - Property("red", kPropertyTypeInteger, 0, 255), - Property("green", kPropertyTypeInteger, 0, 255), - Property("blue", kPropertyTypeInteger, 0, 255), - Property("length", kPropertyTypeInteger, 1, 7), - Property("interval", kPropertyTypeInteger, 0, 1000) - }), [this](const PropertyList& properties) -> ReturnValue { - int red = properties["red"].value(); - int green = properties["green"].value(); - int blue = properties["blue"].value(); - int interval = properties["interval"].value(); - int length = properties["length"].value(); - ESP_LOGI(TAG, "Scroll led strip with color %d, %d, %d, length %d, interval %dms", - red, green, blue, length, interval); - StripColor low = RGBToColor(4, 4, 4); - StripColor high = RGBToColor(red, green, blue); - led_strip_->Scroll(low, high, length, interval); - return true; - }); - -} +#include "led_control.h" +#include "settings.h" +#include "mcp_server.h" +#include + +#define TAG "LedStripControl" + + +int LedStripControl::LevelToBrightness(int level) const { + if (level < 0) level = 0; + if (level > 8) level = 8; + return (1 << level) - 1; // 2^n - 1 +} + +StripColor LedStripControl::RGBToColor(int red, int green, int blue) { + return {static_cast(red), static_cast(green), static_cast(blue)}; +} + +LedStripControl::LedStripControl(CircularStrip* led_strip) + : led_strip_(led_strip) { + // 从设置中读取亮度等级 + Settings settings("led_strip"); + brightness_level_ = settings.GetInt("brightness", 4); // 默认等级4 + led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4); + + auto& mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.led_strip.get_brightness", + "Get the brightness of the led strip (0-8)", + PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + return brightness_level_; + }); + + mcp_server.AddTool("self.led_strip.set_brightness", + "Set the brightness of the led strip (0-8)", + PropertyList({ + Property("level", kPropertyTypeInteger, 0, 8) + }), [this](const PropertyList& properties) -> ReturnValue { + int level = properties["level"].value(); + ESP_LOGI(TAG, "Set LedStrip brightness level to %d", level); + brightness_level_ = level; + led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4); + + // 保存设置 + Settings settings("led_strip", true); + settings.SetInt("brightness", brightness_level_); + + return true; + }); + + mcp_server.AddTool("self.led_strip.set_single_color", + "Set the color of a single led.", + PropertyList({ + Property("index", kPropertyTypeInteger, 0, 2), + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255) + }), [this](const PropertyList& properties) -> ReturnValue { + int index = properties["index"].value(); + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + ESP_LOGI(TAG, "Set led strip single color %d to %d, %d, %d", + index, red, green, blue); + led_strip_->SetSingleColor(index, RGBToColor(red, green, blue)); + return true; + }); + + mcp_server.AddTool("self.led_strip.set_all_color", + "Set the color of all leds.", + PropertyList({ + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255) + }), [this](const PropertyList& properties) -> ReturnValue { + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + ESP_LOGI(TAG, "Set led strip all color to %d, %d, %d", + red, green, blue); + led_strip_->SetAllColor(RGBToColor(red, green, blue)); + return true; + }); + + mcp_server.AddTool("self.led_strip.blink", + "Blink the led strip. (闪烁)", + PropertyList({ + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255), + Property("interval", kPropertyTypeInteger, 0, 1000) + }), [this](const PropertyList& properties) -> ReturnValue { + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + int interval = properties["interval"].value(); + ESP_LOGI(TAG, "Blink led strip with color %d, %d, %d, interval %dms", + red, green, blue, interval); + led_strip_->Blink(RGBToColor(red, green, blue), interval); + return true; + }); + + mcp_server.AddTool("self.led_strip.scroll", + "Scroll the led strip. (跑马灯)", + PropertyList({ + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255), + Property("length", kPropertyTypeInteger, 1, 7), + Property("interval", kPropertyTypeInteger, 0, 1000) + }), [this](const PropertyList& properties) -> ReturnValue { + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + int interval = properties["interval"].value(); + int length = properties["length"].value(); + ESP_LOGI(TAG, "Scroll led strip with color %d, %d, %d, length %d, interval %dms", + red, green, blue, length, interval); + StripColor low = RGBToColor(4, 4, 4); + StripColor high = RGBToColor(red, green, blue); + led_strip_->Scroll(low, high, length, interval); + return true; + }); + +} diff --git a/main/boards/df-k10/led_control.h b/main/boards/df-k10/led_control.h index 30e1020..02f6f06 100644 --- a/main/boards/df-k10/led_control.h +++ b/main/boards/df-k10/led_control.h @@ -1,18 +1,18 @@ -#ifndef LED_CONTROL_H -#define LED_CONTROL_H - -#include "led/circular_strip.h" - -class LedStripControl { -private: - CircularStrip* led_strip_; - int brightness_level_; // 亮度等级 (0-8) - - int LevelToBrightness(int level) const; // 将等级转换为实际亮度值 - StripColor RGBToColor(int red, int green, int blue); - -public: - explicit LedStripControl(CircularStrip* led_strip); -}; - -#endif // LED_STRIP_CONTROL_H +#ifndef LED_CONTROL_H +#define LED_CONTROL_H + +#include "led/circular_strip.h" + +class LedStripControl { +private: + CircularStrip* led_strip_; + int brightness_level_; // 亮度等级 (0-8) + + int LevelToBrightness(int level) const; // 将等级转换为实际亮度值 + StripColor RGBToColor(int red, int green, int blue); + +public: + explicit LedStripControl(CircularStrip* led_strip); +}; + +#endif // LED_STRIP_CONTROL_H diff --git a/main/boards/df-s3-ai-cam/config.h b/main/boards/df-s3-ai-cam/config.h index 42c440f..19c1414 100644 --- a/main/boards/df-s3-ai-cam/config.h +++ b/main/boards/df-s3-ai-cam/config.h @@ -1,63 +1,63 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_38 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_42 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_45 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_46 -#define AUDIO_I2S_SPK_GPIO_GAIN GPIO_NUM_41 - -#define BUILTIN_LED_GPIO GPIO_NUM_3 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define TOUCH_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC -#define RESET_NVS_BUTTON_GPIO GPIO_NUM_NC -#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC - -/* DFRobot Camera pins */ -#define PWDN_GPIO_NUM -1 -#define RESET_GPIO_NUM -1 -#define XCLK_GPIO_NUM 5 -#define Y9_GPIO_NUM 4 -#define Y8_GPIO_NUM 6 -#define Y7_GPIO_NUM 7 -#define Y6_GPIO_NUM 14 -#define Y5_GPIO_NUM 17 -#define Y4_GPIO_NUM 21 -#define Y3_GPIO_NUM 18 -#define Y2_GPIO_NUM 16 -#define VSYNC_GPIO_NUM 1 -#define HREF_GPIO_NUM 2 -#define PCLK_GPIO_NUM 15 -#define SIOD_GPIO_NUM 8 -#define SIOC_GPIO_NUM 9 - -/* Camera pins */ -#define CAMERA_PIN_PWDN PWDN_GPIO_NUM -#define CAMERA_PIN_RESET RESET_GPIO_NUM -#define CAMERA_PIN_XCLK XCLK_GPIO_NUM -#define CAMERA_PIN_SIOD SIOD_GPIO_NUM -#define CAMERA_PIN_SIOC SIOC_GPIO_NUM - -#define CAMERA_PIN_D7 Y9_GPIO_NUM -#define CAMERA_PIN_D6 Y8_GPIO_NUM -#define CAMERA_PIN_D5 Y7_GPIO_NUM -#define CAMERA_PIN_D4 Y6_GPIO_NUM -#define CAMERA_PIN_D3 Y5_GPIO_NUM -#define CAMERA_PIN_D2 Y4_GPIO_NUM -#define CAMERA_PIN_D1 Y3_GPIO_NUM -#define CAMERA_PIN_D0 Y2_GPIO_NUM -#define CAMERA_PIN_VSYNC VSYNC_GPIO_NUM -#define CAMERA_PIN_HREF HREF_GPIO_NUM -#define CAMERA_PIN_PCLK PCLK_GPIO_NUM - -#define XCLK_FREQ_HZ 20000000 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_38 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_42 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_45 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_46 +#define AUDIO_I2S_SPK_GPIO_GAIN GPIO_NUM_41 + +#define BUILTIN_LED_GPIO GPIO_NUM_3 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define TOUCH_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define RESET_NVS_BUTTON_GPIO GPIO_NUM_NC +#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC + +/* DFRobot Camera pins */ +#define PWDN_GPIO_NUM -1 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 5 +#define Y9_GPIO_NUM 4 +#define Y8_GPIO_NUM 6 +#define Y7_GPIO_NUM 7 +#define Y6_GPIO_NUM 14 +#define Y5_GPIO_NUM 17 +#define Y4_GPIO_NUM 21 +#define Y3_GPIO_NUM 18 +#define Y2_GPIO_NUM 16 +#define VSYNC_GPIO_NUM 1 +#define HREF_GPIO_NUM 2 +#define PCLK_GPIO_NUM 15 +#define SIOD_GPIO_NUM 8 +#define SIOC_GPIO_NUM 9 + +/* Camera pins */ +#define CAMERA_PIN_PWDN PWDN_GPIO_NUM +#define CAMERA_PIN_RESET RESET_GPIO_NUM +#define CAMERA_PIN_XCLK XCLK_GPIO_NUM +#define CAMERA_PIN_SIOD SIOD_GPIO_NUM +#define CAMERA_PIN_SIOC SIOC_GPIO_NUM + +#define CAMERA_PIN_D7 Y9_GPIO_NUM +#define CAMERA_PIN_D6 Y8_GPIO_NUM +#define CAMERA_PIN_D5 Y7_GPIO_NUM +#define CAMERA_PIN_D4 Y6_GPIO_NUM +#define CAMERA_PIN_D3 Y5_GPIO_NUM +#define CAMERA_PIN_D2 Y4_GPIO_NUM +#define CAMERA_PIN_D1 Y3_GPIO_NUM +#define CAMERA_PIN_D0 Y2_GPIO_NUM +#define CAMERA_PIN_VSYNC VSYNC_GPIO_NUM +#define CAMERA_PIN_HREF HREF_GPIO_NUM +#define CAMERA_PIN_PCLK PCLK_GPIO_NUM + +#define XCLK_FREQ_HZ 20000000 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/df-s3-ai-cam/config.json b/main/boards/df-s3-ai-cam/config.json index ff50ca6..ad7a4df 100644 --- a/main/boards/df-s3-ai-cam/config.json +++ b/main/boards/df-s3-ai-cam/config.json @@ -1,13 +1,13 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "df-s3-ai-cam", - "sdkconfig_append": [ - "CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=10", - "CONFIG_ESP_PHY_MAX_TX_POWER=10", - "CONFIG_SPIRAM_MODE_OCT=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "df-s3-ai-cam", + "sdkconfig_append": [ + "CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=10", + "CONFIG_ESP_PHY_MAX_TX_POWER=10", + "CONFIG_SPIRAM_MODE_OCT=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc b/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc index d85dc53..5331388 100644 --- a/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc +++ b/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc @@ -1,91 +1,91 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "esp32_camera.h" - -#include "led/gpio_led.h" -#include -#include -#include -#include - -#define TAG "DfrobotEsp32S3AiCam" - -class DfrobotEsp32S3AiCam : public WifiBoard { - private: - Button boot_button_; - Esp32Camera* camera_; - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeCamera() { - camera_config_t config = {}; - config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 - config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 - config.pin_d0 = CAMERA_PIN_D0; - config.pin_d1 = CAMERA_PIN_D1; - config.pin_d2 = CAMERA_PIN_D2; - config.pin_d3 = CAMERA_PIN_D3; - config.pin_d4 = CAMERA_PIN_D4; - config.pin_d5 = CAMERA_PIN_D5; - config.pin_d6 = CAMERA_PIN_D6; - config.pin_d7 = CAMERA_PIN_D7; - config.pin_xclk = CAMERA_PIN_XCLK; - config.pin_pclk = CAMERA_PIN_PCLK; - config.pin_vsync = CAMERA_PIN_VSYNC; - config.pin_href = CAMERA_PIN_HREF; - config.pin_sccb_sda = CAMERA_PIN_SIOD; // 这里如果写-1 表示使用已经初始化的I2C接口 - config.pin_sccb_scl = CAMERA_PIN_SIOC; - config.sccb_i2c_port = 1; // 这里如果写1 默认使用I2C1 - config.pin_pwdn = CAMERA_PIN_PWDN; - config.pin_reset = CAMERA_PIN_RESET; - config.xclk_freq_hz = XCLK_FREQ_HZ; - config.pixel_format = PIXFORMAT_RGB565; - config.frame_size = FRAMESIZE_VGA; - config.jpeg_quality = 12; - config.fb_count = 1; - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - camera_ = new Esp32Camera(config); - camera_->SetVFlip(1); - } - - public: - DfrobotEsp32S3AiCam() : - boot_button_(BOOT_BUTTON_GPIO) { - InitializeButtons(); - InitializeCamera(); - } - - // Wakenet model only - - virtual Led* GetLed() override { - static GpioLed led(BUILTIN_LED_GPIO, 0); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplexPdm audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, - AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(DfrobotEsp32S3AiCam); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "esp32_camera.h" + +#include "led/gpio_led.h" +#include +#include +#include +#include + +#define TAG "DfrobotEsp32S3AiCam" + +class DfrobotEsp32S3AiCam : public WifiBoard { + private: + Button boot_button_; + Esp32Camera* camera_; + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeCamera() { + camera_config_t config = {}; + config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 + config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 + config.pin_d0 = CAMERA_PIN_D0; + config.pin_d1 = CAMERA_PIN_D1; + config.pin_d2 = CAMERA_PIN_D2; + config.pin_d3 = CAMERA_PIN_D3; + config.pin_d4 = CAMERA_PIN_D4; + config.pin_d5 = CAMERA_PIN_D5; + config.pin_d6 = CAMERA_PIN_D6; + config.pin_d7 = CAMERA_PIN_D7; + config.pin_xclk = CAMERA_PIN_XCLK; + config.pin_pclk = CAMERA_PIN_PCLK; + config.pin_vsync = CAMERA_PIN_VSYNC; + config.pin_href = CAMERA_PIN_HREF; + config.pin_sccb_sda = CAMERA_PIN_SIOD; // 这里如果写-1 表示使用已经初始化的I2C接口 + config.pin_sccb_scl = CAMERA_PIN_SIOC; + config.sccb_i2c_port = 1; // 这里如果写1 默认使用I2C1 + config.pin_pwdn = CAMERA_PIN_PWDN; + config.pin_reset = CAMERA_PIN_RESET; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_VGA; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + camera_ = new Esp32Camera(config); + camera_->SetVFlip(1); + } + + public: + DfrobotEsp32S3AiCam() : + boot_button_(BOOT_BUTTON_GPIO) { + InitializeButtons(); + InitializeCamera(); + } + + // Wakenet model only + + virtual Led* GetLed() override { + static GpioLed led(BUILTIN_LED_GPIO, 0); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplexPdm audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, + AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(DfrobotEsp32S3AiCam); diff --git a/main/boards/doit-s3-aibox/config.h b/main/boards/doit-s3-aibox/config.h index 9c44200..d79c706 100644 --- a/main/boards/doit-s3-aibox/config.h +++ b/main/boards/doit-s3-aibox/config.h @@ -1,29 +1,29 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_41 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_40 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_42 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_18 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_17 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -/* -IO9: BUTTON2 -IO10: BUTTON3 引出:KEY3 -IO15: BUTTON1 -*/ -#define BUILTIN_LED_GPIO GPIO_NUM_45 -#define BOOT_BUTTON_GPIO GPIO_NUM_10 -#define TOUCH_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_15 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_9 -#define RESET_NVS_BUTTON_GPIO GPIO_NUM_10 -#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_41 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_40 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_42 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_18 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_17 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +/* +IO9: BUTTON2 +IO10: BUTTON3 引出:KEY3 +IO15: BUTTON1 +*/ +#define BUILTIN_LED_GPIO GPIO_NUM_45 +#define BOOT_BUTTON_GPIO GPIO_NUM_10 +#define TOUCH_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_15 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_9 +#define RESET_NVS_BUTTON_GPIO GPIO_NUM_10 +#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/doit-s3-aibox/config.json b/main/boards/doit-s3-aibox/config.json index a9de834..38e262e 100644 --- a/main/boards/doit-s3-aibox/config.json +++ b/main/boards/doit-s3-aibox/config.json @@ -1,10 +1,10 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "doit-s3-aibox", - "sdkconfig_append": [ - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "doit-s3-aibox", + "sdkconfig_append": [ + ] + } + ] } \ No newline at end of file diff --git a/main/boards/doit-s3-aibox/doit_s3_aibox.cc b/main/boards/doit-s3-aibox/doit_s3_aibox.cc index 54fd9df..9b45af3 100644 --- a/main/boards/doit-s3-aibox/doit_s3_aibox.cc +++ b/main/boards/doit-s3-aibox/doit_s3_aibox.cc @@ -1,137 +1,137 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/gpio_led.h" -#include -#include -#include -#include - -#define TAG "DoitS3AiBox" - -class DoitS3AiBox : public WifiBoard { -private: - Button boot_button_; - Button touch_button_; - Button volume_up_button_; - Button volume_down_button_; - uint8_t click_times; - uint32_t check_time; - - void InitializeButtons() { - click_times = 0; - check_time = 0; - boot_button_.OnClick([this]() { - if(click_times==0) { - check_time = esp_timer_get_time()/1000; - } - if(esp_timer_get_time()/1000-check_time<1000) { - click_times++; - check_time = esp_timer_get_time()/1000; - } else { - click_times = 0; - check_time = 0; - } - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - boot_button_.OnDoubleClick([this]() { - click_times++; - ESP_LOGI(TAG, "DoubleClick times %d", click_times); - if(click_times==3) { - click_times = 0; - ResetWifiConfiguration(); - } - }); - - boot_button_.OnLongPress([this]() { - if(click_times>=3) { - ResetWifiConfiguration(); - } else { - click_times = 0; - check_time = 0; - } - }); - - touch_button_.OnPressDown([this]() { - click_times = 0; - Application::GetInstance().StartListening(); - }); - touch_button_.OnPressUp([this]() { - click_times = 0; - Application::GetInstance().StopListening(); - }); - - volume_up_button_.OnClick([this]() { - click_times = 0; - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - }); - - volume_up_button_.OnLongPress([this]() { - click_times = 0; - GetAudioCodec()->SetOutputVolume(100); - }); - - volume_down_button_.OnClick([this]() { - click_times = 0; - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - }); - - volume_down_button_.OnLongPress([this]() { - click_times = 0; - GetAudioCodec()->SetOutputVolume(0); - }); - } - - void InitializeGpio(gpio_num_t gpio_num_) { - gpio_config_t config = { - .pin_bit_mask = (1ULL << gpio_num_), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_ENABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - ESP_ERROR_CHECK(gpio_config(&config)); - gpio_set_level(gpio_num_, 1); - } - -public: - DoitS3AiBox() : - boot_button_(BOOT_BUTTON_GPIO), - touch_button_(TOUCH_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO){ - // 上拉io48 置高电平 - InitializeGpio(GPIO_NUM_48); - InitializeButtons(); - } - - virtual Led* GetLed() override { - static GpioLed led(BUILTIN_LED_GPIO, 1); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplexPdm audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } -}; - -DECLARE_BOARD(DoitS3AiBox); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/gpio_led.h" +#include +#include +#include +#include + +#define TAG "DoitS3AiBox" + +class DoitS3AiBox : public WifiBoard { +private: + Button boot_button_; + Button touch_button_; + Button volume_up_button_; + Button volume_down_button_; + uint8_t click_times; + uint32_t check_time; + + void InitializeButtons() { + click_times = 0; + check_time = 0; + boot_button_.OnClick([this]() { + if(click_times==0) { + check_time = esp_timer_get_time()/1000; + } + if(esp_timer_get_time()/1000-check_time<1000) { + click_times++; + check_time = esp_timer_get_time()/1000; + } else { + click_times = 0; + check_time = 0; + } + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + boot_button_.OnDoubleClick([this]() { + click_times++; + ESP_LOGI(TAG, "DoubleClick times %d", click_times); + if(click_times==3) { + click_times = 0; + ResetWifiConfiguration(); + } + }); + + boot_button_.OnLongPress([this]() { + if(click_times>=3) { + ResetWifiConfiguration(); + } else { + click_times = 0; + check_time = 0; + } + }); + + touch_button_.OnPressDown([this]() { + click_times = 0; + Application::GetInstance().StartListening(); + }); + touch_button_.OnPressUp([this]() { + click_times = 0; + Application::GetInstance().StopListening(); + }); + + volume_up_button_.OnClick([this]() { + click_times = 0; + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + }); + + volume_up_button_.OnLongPress([this]() { + click_times = 0; + GetAudioCodec()->SetOutputVolume(100); + }); + + volume_down_button_.OnClick([this]() { + click_times = 0; + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + }); + + volume_down_button_.OnLongPress([this]() { + click_times = 0; + GetAudioCodec()->SetOutputVolume(0); + }); + } + + void InitializeGpio(gpio_num_t gpio_num_) { + gpio_config_t config = { + .pin_bit_mask = (1ULL << gpio_num_), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_ERROR_CHECK(gpio_config(&config)); + gpio_set_level(gpio_num_, 1); + } + +public: + DoitS3AiBox() : + boot_button_(BOOT_BUTTON_GPIO), + touch_button_(TOUCH_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO){ + // 上拉io48 置高电平 + InitializeGpio(GPIO_NUM_48); + InitializeButtons(); + } + + virtual Led* GetLed() override { + static GpioLed led(BUILTIN_LED_GPIO, 1); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplexPdm audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } +}; + +DECLARE_BOARD(DoitS3AiBox); diff --git a/main/boards/du-chatx/config.h b/main/boards/du-chatx/config.h index 729e1f4..66c1498 100644 --- a/main/boards/du-chatx/config.h +++ b/main/boards/du-chatx/config.h @@ -1,40 +1,40 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_39 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_38 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_40 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_42 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_2 - -#define BUILTIN_LED_GPIO GPIO_NUM_48 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define TOUCH_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_9 -#define DISPLAY_MOSI_PIN GPIO_NUM_18 -#define DISPLAY_CLK_PIN GPIO_NUM_17 -#define DISPLAY_DC_PIN GPIO_NUM_8 -#define DISPLAY_RST_PIN GPIO_NUM_20 -#define DISPLAY_CS_PIN GPIO_NUM_16 - -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 160 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 2 -#define DISPLAY_OFFSET_Y 1 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_39 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_38 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_40 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_42 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_2 + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define TOUCH_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_9 +#define DISPLAY_MOSI_PIN GPIO_NUM_18 +#define DISPLAY_CLK_PIN GPIO_NUM_17 +#define DISPLAY_DC_PIN GPIO_NUM_8 +#define DISPLAY_RST_PIN GPIO_NUM_20 +#define DISPLAY_CS_PIN GPIO_NUM_16 + +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 160 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 2 +#define DISPLAY_OFFSET_Y 1 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/du-chatx/config.json b/main/boards/du-chatx/config.json index e6abd31..1457278 100644 --- a/main/boards/du-chatx/config.json +++ b/main/boards/du-chatx/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "du-chatx", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "du-chatx", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/du-chatx/du-chatx-wifi.cc b/main/boards/du-chatx/du-chatx-wifi.cc index d9eea67..075ba4f 100644 --- a/main/boards/du-chatx/du-chatx-wifi.cc +++ b/main/boards/du-chatx/du-chatx-wifi.cc @@ -1,164 +1,164 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" -#include "power_manager.h" -#include "power_save_timer.h" - -#include -#include -#include -#include -#include -#include -#include - -#define TAG "DuChatX" - -class DuChatX : public WifiBoard { -private: - Button boot_button_; - LcdDisplay *display_; - PowerManager *power_manager_; - PowerSaveTimer *power_save_timer_; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_6); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - void InitializePowerSaveTimer() { - rtc_gpio_init(GPIO_NUM_1); - rtc_gpio_set_direction(GPIO_NUM_1, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_1, 1); - - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - rtc_gpio_set_level(GPIO_NUM_1, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_1); - esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - esp_deep_sleep_start(); - }); - power_save_timer_->SetEnabled(true); - } - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_CLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeLcdDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel_ IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel_)); - esp_lcd_panel_reset(panel_); - esp_lcd_panel_init(panel_); - esp_lcd_panel_invert_color(panel_, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new SpiLcdDisplay(panel_io, panel_,DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y,DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - -public: - DuChatX() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeSpi(); - InitializeLcdDisplay(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - InitializePowerSaveTimer(); - InitializePowerManager(); - } - - virtual Led *GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec *GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display *GetDisplay() override { - return display_; - } - - virtual Backlight *GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(DuChatX); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" +#include "power_manager.h" +#include "power_save_timer.h" + +#include +#include +#include +#include +#include +#include +#include + +#define TAG "DuChatX" + +class DuChatX : public WifiBoard { +private: + Button boot_button_; + LcdDisplay *display_; + PowerManager *power_manager_; + PowerSaveTimer *power_save_timer_; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_6); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_1); + rtc_gpio_set_direction(GPIO_NUM_1, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_1, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_1, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_1); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel_ IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel_)); + esp_lcd_panel_reset(panel_); + esp_lcd_panel_init(panel_); + esp_lcd_panel_invert_color(panel_, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel_,DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y,DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + +public: + DuChatX() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeSpi(); + InitializeLcdDisplay(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + InitializePowerSaveTimer(); + InitializePowerManager(); + } + + virtual Led *GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec *GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display *GetDisplay() override { + return display_; + } + + virtual Backlight *GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(DuChatX); diff --git a/main/boards/du-chatx/power_manager.h b/main/boards/du-chatx/power_manager.h index 8438880..69a8c9f 100644 --- a/main/boards/du-chatx/power_manager.h +++ b/main/boards/du-chatx/power_manager.h @@ -1,186 +1,186 @@ -#pragma once -#include -#include - -#include -#include -#include - - -class PowerManager { -private: - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 1; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_5, &adc_value)); - - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += value; - } - average_adc /= adc_values_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {1120, 0}, - {1140, 20}, - {1160, 40}, - {1170, 60}, - {1190, 80}, - {1217, 100} - }; - - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_5, &chan_config)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include + +#include +#include +#include + + +class PowerManager { +private: + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 1; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_5, &adc_value)); + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {1120, 0}, + {1140, 20}, + {1160, 40}, + {1170, 60}, + {1190, 80}, + {1217, 100} + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_5, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/echoear/EchoEar.cc b/main/boards/echoear/EchoEar.cc index fdc588b..c187a1e 100644 --- a/main/boards/echoear/EchoEar.cc +++ b/main/boards/echoear/EchoEar.cc @@ -1,638 +1,633 @@ -#include "wifi_board.h" -#include "codecs/box_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "backlight.h" -#include "emote_display.h" - -#include -#include - -#include -#include -#include "i2c_device.h" -#include -#include -#include -#include "esp_lcd_touch_cst816s.h" -#include "touch.h" - -#include "driver/temperature_sensor.h" -#include -#include -#include - -#define TAG "EchoEar" - -#define USE_LVGL_DEFAULT 0 - -temperature_sensor_handle_t temp_sensor = NULL; -static const st77916_lcd_init_cmd_t vendor_specific_init_yysj[] = { - {0xF0, (uint8_t []){0x28}, 1, 0}, - {0xF2, (uint8_t []){0x28}, 1, 0}, - {0x73, (uint8_t []){0xF0}, 1, 0}, - {0x7C, (uint8_t []){0xD1}, 1, 0}, - {0x83, (uint8_t []){0xE0}, 1, 0}, - {0x84, (uint8_t []){0x61}, 1, 0}, - {0xF2, (uint8_t []){0x82}, 1, 0}, - {0xF0, (uint8_t []){0x00}, 1, 0}, - {0xF0, (uint8_t []){0x01}, 1, 0}, - {0xF1, (uint8_t []){0x01}, 1, 0}, - {0xB0, (uint8_t []){0x56}, 1, 0}, - {0xB1, (uint8_t []){0x4D}, 1, 0}, - {0xB2, (uint8_t []){0x24}, 1, 0}, - {0xB4, (uint8_t []){0x87}, 1, 0}, - {0xB5, (uint8_t []){0x44}, 1, 0}, - {0xB6, (uint8_t []){0x8B}, 1, 0}, - {0xB7, (uint8_t []){0x40}, 1, 0}, - {0xB8, (uint8_t []){0x86}, 1, 0}, - {0xBA, (uint8_t []){0x00}, 1, 0}, - {0xBB, (uint8_t []){0x08}, 1, 0}, - {0xBC, (uint8_t []){0x08}, 1, 0}, - {0xBD, (uint8_t []){0x00}, 1, 0}, - {0xC0, (uint8_t []){0x80}, 1, 0}, - {0xC1, (uint8_t []){0x10}, 1, 0}, - {0xC2, (uint8_t []){0x37}, 1, 0}, - {0xC3, (uint8_t []){0x80}, 1, 0}, - {0xC4, (uint8_t []){0x10}, 1, 0}, - {0xC5, (uint8_t []){0x37}, 1, 0}, - {0xC6, (uint8_t []){0xA9}, 1, 0}, - {0xC7, (uint8_t []){0x41}, 1, 0}, - {0xC8, (uint8_t []){0x01}, 1, 0}, - {0xC9, (uint8_t []){0xA9}, 1, 0}, - {0xCA, (uint8_t []){0x41}, 1, 0}, - {0xCB, (uint8_t []){0x01}, 1, 0}, - {0xD0, (uint8_t []){0x91}, 1, 0}, - {0xD1, (uint8_t []){0x68}, 1, 0}, - {0xD2, (uint8_t []){0x68}, 1, 0}, - {0xF5, (uint8_t []){0x00, 0xA5}, 2, 0}, - {0xDD, (uint8_t []){0x4F}, 1, 0}, - {0xDE, (uint8_t []){0x4F}, 1, 0}, - {0xF1, (uint8_t []){0x10}, 1, 0}, - {0xF0, (uint8_t []){0x00}, 1, 0}, - {0xF0, (uint8_t []){0x02}, 1, 0}, - {0xE0, (uint8_t []){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0}, - {0xE1, (uint8_t []){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0}, - {0xF0, (uint8_t []){0x10}, 1, 0}, - {0xF3, (uint8_t []){0x10}, 1, 0}, - {0xE0, (uint8_t []){0x07}, 1, 0}, - {0xE1, (uint8_t []){0x00}, 1, 0}, - {0xE2, (uint8_t []){0x00}, 1, 0}, - {0xE3, (uint8_t []){0x00}, 1, 0}, - {0xE4, (uint8_t []){0xE0}, 1, 0}, - {0xE5, (uint8_t []){0x06}, 1, 0}, - {0xE6, (uint8_t []){0x21}, 1, 0}, - {0xE7, (uint8_t []){0x01}, 1, 0}, - {0xE8, (uint8_t []){0x05}, 1, 0}, - {0xE9, (uint8_t []){0x02}, 1, 0}, - {0xEA, (uint8_t []){0xDA}, 1, 0}, - {0xEB, (uint8_t []){0x00}, 1, 0}, - {0xEC, (uint8_t []){0x00}, 1, 0}, - {0xED, (uint8_t []){0x0F}, 1, 0}, - {0xEE, (uint8_t []){0x00}, 1, 0}, - {0xEF, (uint8_t []){0x00}, 1, 0}, - {0xF8, (uint8_t []){0x00}, 1, 0}, - {0xF9, (uint8_t []){0x00}, 1, 0}, - {0xFA, (uint8_t []){0x00}, 1, 0}, - {0xFB, (uint8_t []){0x00}, 1, 0}, - {0xFC, (uint8_t []){0x00}, 1, 0}, - {0xFD, (uint8_t []){0x00}, 1, 0}, - {0xFE, (uint8_t []){0x00}, 1, 0}, - {0xFF, (uint8_t []){0x00}, 1, 0}, - {0x60, (uint8_t []){0x40}, 1, 0}, - {0x61, (uint8_t []){0x04}, 1, 0}, - {0x62, (uint8_t []){0x00}, 1, 0}, - {0x63, (uint8_t []){0x42}, 1, 0}, - {0x64, (uint8_t []){0xD9}, 1, 0}, - {0x65, (uint8_t []){0x00}, 1, 0}, - {0x66, (uint8_t []){0x00}, 1, 0}, - {0x67, (uint8_t []){0x00}, 1, 0}, - {0x68, (uint8_t []){0x00}, 1, 0}, - {0x69, (uint8_t []){0x00}, 1, 0}, - {0x6A, (uint8_t []){0x00}, 1, 0}, - {0x6B, (uint8_t []){0x00}, 1, 0}, - {0x70, (uint8_t []){0x40}, 1, 0}, - {0x71, (uint8_t []){0x03}, 1, 0}, - {0x72, (uint8_t []){0x00}, 1, 0}, - {0x73, (uint8_t []){0x42}, 1, 0}, - {0x74, (uint8_t []){0xD8}, 1, 0}, - {0x75, (uint8_t []){0x00}, 1, 0}, - {0x76, (uint8_t []){0x00}, 1, 0}, - {0x77, (uint8_t []){0x00}, 1, 0}, - {0x78, (uint8_t []){0x00}, 1, 0}, - {0x79, (uint8_t []){0x00}, 1, 0}, - {0x7A, (uint8_t []){0x00}, 1, 0}, - {0x7B, (uint8_t []){0x00}, 1, 0}, - {0x80, (uint8_t []){0x48}, 1, 0}, - {0x81, (uint8_t []){0x00}, 1, 0}, - {0x82, (uint8_t []){0x06}, 1, 0}, - {0x83, (uint8_t []){0x02}, 1, 0}, - {0x84, (uint8_t []){0xD6}, 1, 0}, - {0x85, (uint8_t []){0x04}, 1, 0}, - {0x86, (uint8_t []){0x00}, 1, 0}, - {0x87, (uint8_t []){0x00}, 1, 0}, - {0x88, (uint8_t []){0x48}, 1, 0}, - {0x89, (uint8_t []){0x00}, 1, 0}, - {0x8A, (uint8_t []){0x08}, 1, 0}, - {0x8B, (uint8_t []){0x02}, 1, 0}, - {0x8C, (uint8_t []){0xD8}, 1, 0}, - {0x8D, (uint8_t []){0x04}, 1, 0}, - {0x8E, (uint8_t []){0x00}, 1, 0}, - {0x8F, (uint8_t []){0x00}, 1, 0}, - {0x90, (uint8_t []){0x48}, 1, 0}, - {0x91, (uint8_t []){0x00}, 1, 0}, - {0x92, (uint8_t []){0x0A}, 1, 0}, - {0x93, (uint8_t []){0x02}, 1, 0}, - {0x94, (uint8_t []){0xDA}, 1, 0}, - {0x95, (uint8_t []){0x04}, 1, 0}, - {0x96, (uint8_t []){0x00}, 1, 0}, - {0x97, (uint8_t []){0x00}, 1, 0}, - {0x98, (uint8_t []){0x48}, 1, 0}, - {0x99, (uint8_t []){0x00}, 1, 0}, - {0x9A, (uint8_t []){0x0C}, 1, 0}, - {0x9B, (uint8_t []){0x02}, 1, 0}, - {0x9C, (uint8_t []){0xDC}, 1, 0}, - {0x9D, (uint8_t []){0x04}, 1, 0}, - {0x9E, (uint8_t []){0x00}, 1, 0}, - {0x9F, (uint8_t []){0x00}, 1, 0}, - {0xA0, (uint8_t []){0x48}, 1, 0}, - {0xA1, (uint8_t []){0x00}, 1, 0}, - {0xA2, (uint8_t []){0x05}, 1, 0}, - {0xA3, (uint8_t []){0x02}, 1, 0}, - {0xA4, (uint8_t []){0xD5}, 1, 0}, - {0xA5, (uint8_t []){0x04}, 1, 0}, - {0xA6, (uint8_t []){0x00}, 1, 0}, - {0xA7, (uint8_t []){0x00}, 1, 0}, - {0xA8, (uint8_t []){0x48}, 1, 0}, - {0xA9, (uint8_t []){0x00}, 1, 0}, - {0xAA, (uint8_t []){0x07}, 1, 0}, - {0xAB, (uint8_t []){0x02}, 1, 0}, - {0xAC, (uint8_t []){0xD7}, 1, 0}, - {0xAD, (uint8_t []){0x04}, 1, 0}, - {0xAE, (uint8_t []){0x00}, 1, 0}, - {0xAF, (uint8_t []){0x00}, 1, 0}, - {0xB0, (uint8_t []){0x48}, 1, 0}, - {0xB1, (uint8_t []){0x00}, 1, 0}, - {0xB2, (uint8_t []){0x09}, 1, 0}, - {0xB3, (uint8_t []){0x02}, 1, 0}, - {0xB4, (uint8_t []){0xD9}, 1, 0}, - {0xB5, (uint8_t []){0x04}, 1, 0}, - {0xB6, (uint8_t []){0x00}, 1, 0}, - {0xB7, (uint8_t []){0x00}, 1, 0}, - {0xB8, (uint8_t []){0x48}, 1, 0}, - {0xB9, (uint8_t []){0x00}, 1, 0}, - {0xBA, (uint8_t []){0x0B}, 1, 0}, - {0xBB, (uint8_t []){0x02}, 1, 0}, - {0xBC, (uint8_t []){0xDB}, 1, 0}, - {0xBD, (uint8_t []){0x04}, 1, 0}, - {0xBE, (uint8_t []){0x00}, 1, 0}, - {0xBF, (uint8_t []){0x00}, 1, 0}, - {0xC0, (uint8_t []){0x10}, 1, 0}, - {0xC1, (uint8_t []){0x47}, 1, 0}, - {0xC2, (uint8_t []){0x56}, 1, 0}, - {0xC3, (uint8_t []){0x65}, 1, 0}, - {0xC4, (uint8_t []){0x74}, 1, 0}, - {0xC5, (uint8_t []){0x88}, 1, 0}, - {0xC6, (uint8_t []){0x99}, 1, 0}, - {0xC7, (uint8_t []){0x01}, 1, 0}, - {0xC8, (uint8_t []){0xBB}, 1, 0}, - {0xC9, (uint8_t []){0xAA}, 1, 0}, - {0xD0, (uint8_t []){0x10}, 1, 0}, - {0xD1, (uint8_t []){0x47}, 1, 0}, - {0xD2, (uint8_t []){0x56}, 1, 0}, - {0xD3, (uint8_t []){0x65}, 1, 0}, - {0xD4, (uint8_t []){0x74}, 1, 0}, - {0xD5, (uint8_t []){0x88}, 1, 0}, - {0xD6, (uint8_t []){0x99}, 1, 0}, - {0xD7, (uint8_t []){0x01}, 1, 0}, - {0xD8, (uint8_t []){0xBB}, 1, 0}, - {0xD9, (uint8_t []){0xAA}, 1, 0}, - {0xF3, (uint8_t []){0x01}, 1, 0}, - {0xF0, (uint8_t []){0x00}, 1, 0}, - {0x21, (uint8_t []){}, 0, 0}, - {0x11, (uint8_t []){}, 0, 0}, - {0x00, (uint8_t []){}, 0, 120}, -}; -float tsens_value; -gpio_num_t AUDIO_I2S_GPIO_DIN = AUDIO_I2S_GPIO_DIN_1; -gpio_num_t AUDIO_CODEC_PA_PIN = AUDIO_CODEC_PA_PIN_1; -gpio_num_t QSPI_PIN_NUM_LCD_RST = QSPI_PIN_NUM_LCD_RST_1; -gpio_num_t TOUCH_PAD2 = TOUCH_PAD2_1; -gpio_num_t UART1_TX = UART1_TX_1; -gpio_num_t UART1_RX = UART1_RX_1; - -class Charge : public I2cDevice { -public: - Charge(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) - { - read_buffer_ = new uint8_t[8]; - } - ~Charge() - { - delete[] read_buffer_; - } - void Printcharge() - { - ReadRegs(0x08, read_buffer_, 2); - ReadRegs(0x0c, read_buffer_ + 2, 2); - ESP_ERROR_CHECK(temperature_sensor_get_celsius(temp_sensor, &tsens_value)); - - int16_t voltage = static_cast(read_buffer_[1] << 8 | read_buffer_[0]); - int16_t current = static_cast(read_buffer_[3] << 8 | read_buffer_[2]); - - // Use the variables to avoid warnings (can be removed if actual implementation uses them) - (void)voltage; - (void)current; - } - static void TaskFunction(void *pvParameters) - { - Charge* charge = static_cast(pvParameters); - while (true) { - charge->Printcharge(); - vTaskDelay(pdMS_TO_TICKS(300)); - } - } - -private: - uint8_t* read_buffer_ = nullptr; -}; - -class Cst816s : public I2cDevice { -public: - struct TouchPoint_t { - int num = 0; - int x = -1; - int y = -1; - }; - - enum TouchEvent { - TOUCH_NONE, - TOUCH_PRESS, - TOUCH_RELEASE, - TOUCH_HOLD - }; - - Cst816s(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) - { - read_buffer_ = new uint8_t[6]; - was_touched_ = false; - press_count_ = 0; - - // Create touch interrupt semaphore - touch_isr_mux_ = xSemaphoreCreateBinary(); - if (touch_isr_mux_ == NULL) { - ESP_LOGE("EchoEar", "Failed to create touch semaphore"); - } - } - - ~Cst816s() - { - delete[] read_buffer_; - - // Delete semaphore if it exists - if (touch_isr_mux_ != NULL) { - vSemaphoreDelete(touch_isr_mux_); - touch_isr_mux_ = NULL; - } - } - - void UpdateTouchPoint() - { - ReadRegs(0x02, read_buffer_, 6); - tp_.num = read_buffer_[0] & 0x0F; - tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; - tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; - } - - const TouchPoint_t &GetTouchPoint() - { - return tp_; - } - - TouchEvent CheckTouchEvent() - { - bool is_touched = (tp_.num > 0); - TouchEvent event = TOUCH_NONE; - - if (is_touched && !was_touched_) { - // Press event (transition from not touched to touched) - press_count_++; - event = TOUCH_PRESS; - ESP_LOGI("EchoEar", "TOUCH PRESS - count: %d, x: %d, y: %d", press_count_, tp_.x, tp_.y); - } else if (!is_touched && was_touched_) { - // Release event (transition from touched to not touched) - event = TOUCH_RELEASE; - ESP_LOGI("EchoEar", "TOUCH RELEASE - total presses: %d", press_count_); - } else if (is_touched && was_touched_) { - // Continuous touch (hold) - event = TOUCH_HOLD; - ESP_LOGD("EchoEar", "TOUCH HOLD - x: %d, y: %d", tp_.x, tp_.y); - } - - // Update previous state - was_touched_ = is_touched; - return event; - } - - int GetPressCount() const - { - return press_count_; - } - - void ResetPressCount() - { - press_count_ = 0; - } - - // Semaphore management methods - SemaphoreHandle_t GetTouchSemaphore() - { - return touch_isr_mux_; - } - - bool WaitForTouchEvent(TickType_t timeout = portMAX_DELAY) - { - if (touch_isr_mux_ != NULL) { - return xSemaphoreTake(touch_isr_mux_, timeout) == pdTRUE; - } - return false; - } - - void NotifyTouchEvent() - { - if (touch_isr_mux_ != NULL) { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xSemaphoreGiveFromISR(touch_isr_mux_, &xHigherPriorityTaskWoken); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - } - } - -private: - uint8_t* read_buffer_ = nullptr; - TouchPoint_t tp_; - - // Touch state tracking - bool was_touched_; - int press_count_; - - // Touch interrupt semaphore - SemaphoreHandle_t touch_isr_mux_; -}; - -class EspS3Cat : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Cst816s* cst816s_; - Charge* charge_; - Button boot_button_; -#if USE_LVGL_DEFAULT - LcdDisplay* display_; -#else - anim::EmoteDisplay* display_ = nullptr; -#endif - PwmBacklight* backlight_ = nullptr; - esp_timer_handle_t touchpad_timer_; - esp_lcd_touch_handle_t tp; // LCD touch handle - - void InitializeI2c() - { - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - - temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(10, 50); - ESP_ERROR_CHECK(temperature_sensor_install(&temp_sensor_config, &temp_sensor)); - ESP_ERROR_CHECK(temperature_sensor_enable(temp_sensor)); - - } - uint8_t DetectPcbVersion() - { - esp_err_t ret = i2c_master_probe(i2c_bus_, 0x18, 100); - uint8_t pcb_verison = 0; - if (ret == ESP_OK) { - ESP_LOGI(TAG, "PCB verison V1.0"); - pcb_verison = 0; - } else { - gpio_config_t gpio_conf = { - .pin_bit_mask = (1ULL << GPIO_NUM_48), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE - }; - ESP_ERROR_CHECK(gpio_config(&gpio_conf)); - ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_48, 1)); - vTaskDelay(pdMS_TO_TICKS(100)); - ret = i2c_master_probe(i2c_bus_, 0x18, 100); - if (ret == ESP_OK) { - ESP_LOGI(TAG, "PCB verison V1.2"); - pcb_verison = 1; - AUDIO_I2S_GPIO_DIN = AUDIO_I2S_GPIO_DIN_2; - AUDIO_CODEC_PA_PIN = AUDIO_CODEC_PA_PIN_2; - QSPI_PIN_NUM_LCD_RST = QSPI_PIN_NUM_LCD_RST_2; - TOUCH_PAD2 = TOUCH_PAD2_2; - UART1_TX = UART1_TX_2; - UART1_RX = UART1_RX_2; - } else { - ESP_LOGE(TAG, "PCB version detection error"); - - } - } - return pcb_verison; - } - - static void touch_isr_callback(void* arg) - { - Cst816s* touchpad = static_cast(arg); - if (touchpad != nullptr) { - touchpad->NotifyTouchEvent(); - } - } - - static void touch_event_task(void* arg) - { - Cst816s* touchpad = static_cast(arg); - if (touchpad == nullptr) { - ESP_LOGE(TAG, "Invalid touchpad pointer in touch_event_task"); - vTaskDelete(NULL); - return; - } - - while (true) { - if (touchpad->WaitForTouchEvent()) { - auto &app = Application::GetInstance(); - auto &board = (EspS3Cat &)Board::GetInstance(); - - ESP_LOGI(TAG, "Touch event, TP_PIN_NUM_INT: %d", gpio_get_level(TP_PIN_NUM_INT)); - touchpad->UpdateTouchPoint(); - auto touch_event = touchpad->CheckTouchEvent(); - - if (touch_event == Cst816s::TOUCH_RELEASE) { - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - board.ResetWifiConfiguration(); - } else { - app.ToggleChatState(); - } - } - } - } - } - - void InitializeCharge() - { - charge_ = new Charge(i2c_bus_, 0x55); - xTaskCreatePinnedToCore(Charge::TaskFunction, "batterydecTask", 3 * 1024, charge_, 6, NULL, 0); - } - - void InitializeCst816sTouchPad() - { - cst816s_ = new Cst816s(i2c_bus_, 0x15); - - xTaskCreatePinnedToCore(touch_event_task, "touch_task", 4 * 1024, cst816s_, 5, NULL, 1); - - const gpio_config_t int_gpio_config = { - .pin_bit_mask = (1ULL << TP_PIN_NUM_INT), - .mode = GPIO_MODE_INPUT, - // .intr_type = GPIO_INTR_NEGEDGE - .intr_type = GPIO_INTR_ANYEDGE - }; - gpio_config(&int_gpio_config); - gpio_install_isr_service(0); - gpio_intr_enable(TP_PIN_NUM_INT); - gpio_isr_handler_add(TP_PIN_NUM_INT, EspS3Cat::touch_isr_callback, cst816s_); - } - - void InitializeSpi() - { - const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK, - QSPI_PIN_NUM_LCD_DATA0, - QSPI_PIN_NUM_LCD_DATA1, - QSPI_PIN_NUM_LCD_DATA2, - QSPI_PIN_NUM_LCD_DATA3, - QSPI_LCD_H_RES * 80 * sizeof(uint16_t)); - ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO)); - } - - void Initializest77916Display(uint8_t pcb_verison) - { - - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - const esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(QSPI_PIN_NUM_LCD_CS, NULL, NULL); - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io)); - st77916_vendor_config_t vendor_config = { - .init_cmds = vendor_specific_init_yysj, - .init_cmds_size = sizeof(vendor_specific_init_yysj) / sizeof(st77916_lcd_init_cmd_t), - .flags = { - .use_qspi_interface = 1, - }, - }; - const esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = QSPI_PIN_NUM_LCD_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, - .flags = { - .reset_active_high = pcb_verison, - }, - .vendor_config = &vendor_config, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_disp_on_off(panel, true); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - -#if USE_LVGL_DEFAULT - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); -#else - display_ = new anim::EmoteDisplay(panel, panel_io); -#endif - backlight_ = new PwmBacklight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - backlight_->RestoreBrightness(); - } - - void InitializeButtons() - { - boot_button_.OnClick([this]() { - auto &app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ESP_LOGI(TAG, "Boot button pressed, enter WiFi configuration mode"); - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - gpio_config_t power_gpio_config = { - .pin_bit_mask = (BIT64(POWER_CTRL)), - .mode = GPIO_MODE_OUTPUT, - - }; - ESP_ERROR_CHECK(gpio_config(&power_gpio_config)); - - gpio_set_level(POWER_CTRL, 0); - } - -public: - EspS3Cat() : boot_button_(BOOT_BUTTON_GPIO) - { - InitializeI2c(); - uint8_t pcb_verison = DetectPcbVersion(); - InitializeCharge(); - InitializeCst816sTouchPad(); - - InitializeSpi(); - Initializest77916Display(pcb_verison); - InitializeButtons(); - } - - virtual AudioCodec* GetAudioCodec() override - { - static BoxAudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override - { - return display_; - } - - Cst816s* GetTouchpad() - { - return cst816s_; - } - - virtual Backlight* GetBacklight() override - { - return backlight_; - } -}; - -DECLARE_BOARD(EspS3Cat); +#include "wifi_board.h" +#include "codecs/box_audio_codec.h" +#include "display/lcd_display.h" +#include "display/emote_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "backlight.h" + +#include +#include + +#include +#include +#include "i2c_device.h" +#include +#include +#include +#include "esp_lcd_touch_cst816s.h" +#include "touch.h" + +#include "driver/temperature_sensor.h" +#include +#include +#include + +#define TAG "EchoEar" + + +temperature_sensor_handle_t temp_sensor = NULL; +static const st77916_lcd_init_cmd_t vendor_specific_init_yysj[] = { + {0xF0, (uint8_t []){0x28}, 1, 0}, + {0xF2, (uint8_t []){0x28}, 1, 0}, + {0x73, (uint8_t []){0xF0}, 1, 0}, + {0x7C, (uint8_t []){0xD1}, 1, 0}, + {0x83, (uint8_t []){0xE0}, 1, 0}, + {0x84, (uint8_t []){0x61}, 1, 0}, + {0xF2, (uint8_t []){0x82}, 1, 0}, + {0xF0, (uint8_t []){0x00}, 1, 0}, + {0xF0, (uint8_t []){0x01}, 1, 0}, + {0xF1, (uint8_t []){0x01}, 1, 0}, + {0xB0, (uint8_t []){0x56}, 1, 0}, + {0xB1, (uint8_t []){0x4D}, 1, 0}, + {0xB2, (uint8_t []){0x24}, 1, 0}, + {0xB4, (uint8_t []){0x87}, 1, 0}, + {0xB5, (uint8_t []){0x44}, 1, 0}, + {0xB6, (uint8_t []){0x8B}, 1, 0}, + {0xB7, (uint8_t []){0x40}, 1, 0}, + {0xB8, (uint8_t []){0x86}, 1, 0}, + {0xBA, (uint8_t []){0x00}, 1, 0}, + {0xBB, (uint8_t []){0x08}, 1, 0}, + {0xBC, (uint8_t []){0x08}, 1, 0}, + {0xBD, (uint8_t []){0x00}, 1, 0}, + {0xC0, (uint8_t []){0x80}, 1, 0}, + {0xC1, (uint8_t []){0x10}, 1, 0}, + {0xC2, (uint8_t []){0x37}, 1, 0}, + {0xC3, (uint8_t []){0x80}, 1, 0}, + {0xC4, (uint8_t []){0x10}, 1, 0}, + {0xC5, (uint8_t []){0x37}, 1, 0}, + {0xC6, (uint8_t []){0xA9}, 1, 0}, + {0xC7, (uint8_t []){0x41}, 1, 0}, + {0xC8, (uint8_t []){0x01}, 1, 0}, + {0xC9, (uint8_t []){0xA9}, 1, 0}, + {0xCA, (uint8_t []){0x41}, 1, 0}, + {0xCB, (uint8_t []){0x01}, 1, 0}, + {0xD0, (uint8_t []){0x91}, 1, 0}, + {0xD1, (uint8_t []){0x68}, 1, 0}, + {0xD2, (uint8_t []){0x68}, 1, 0}, + {0xF5, (uint8_t []){0x00, 0xA5}, 2, 0}, + {0xDD, (uint8_t []){0x4F}, 1, 0}, + {0xDE, (uint8_t []){0x4F}, 1, 0}, + {0xF1, (uint8_t []){0x10}, 1, 0}, + {0xF0, (uint8_t []){0x00}, 1, 0}, + {0xF0, (uint8_t []){0x02}, 1, 0}, + {0xE0, (uint8_t []){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0}, + {0xE1, (uint8_t []){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0}, + {0xF0, (uint8_t []){0x10}, 1, 0}, + {0xF3, (uint8_t []){0x10}, 1, 0}, + {0xE0, (uint8_t []){0x07}, 1, 0}, + {0xE1, (uint8_t []){0x00}, 1, 0}, + {0xE2, (uint8_t []){0x00}, 1, 0}, + {0xE3, (uint8_t []){0x00}, 1, 0}, + {0xE4, (uint8_t []){0xE0}, 1, 0}, + {0xE5, (uint8_t []){0x06}, 1, 0}, + {0xE6, (uint8_t []){0x21}, 1, 0}, + {0xE7, (uint8_t []){0x01}, 1, 0}, + {0xE8, (uint8_t []){0x05}, 1, 0}, + {0xE9, (uint8_t []){0x02}, 1, 0}, + {0xEA, (uint8_t []){0xDA}, 1, 0}, + {0xEB, (uint8_t []){0x00}, 1, 0}, + {0xEC, (uint8_t []){0x00}, 1, 0}, + {0xED, (uint8_t []){0x0F}, 1, 0}, + {0xEE, (uint8_t []){0x00}, 1, 0}, + {0xEF, (uint8_t []){0x00}, 1, 0}, + {0xF8, (uint8_t []){0x00}, 1, 0}, + {0xF9, (uint8_t []){0x00}, 1, 0}, + {0xFA, (uint8_t []){0x00}, 1, 0}, + {0xFB, (uint8_t []){0x00}, 1, 0}, + {0xFC, (uint8_t []){0x00}, 1, 0}, + {0xFD, (uint8_t []){0x00}, 1, 0}, + {0xFE, (uint8_t []){0x00}, 1, 0}, + {0xFF, (uint8_t []){0x00}, 1, 0}, + {0x60, (uint8_t []){0x40}, 1, 0}, + {0x61, (uint8_t []){0x04}, 1, 0}, + {0x62, (uint8_t []){0x00}, 1, 0}, + {0x63, (uint8_t []){0x42}, 1, 0}, + {0x64, (uint8_t []){0xD9}, 1, 0}, + {0x65, (uint8_t []){0x00}, 1, 0}, + {0x66, (uint8_t []){0x00}, 1, 0}, + {0x67, (uint8_t []){0x00}, 1, 0}, + {0x68, (uint8_t []){0x00}, 1, 0}, + {0x69, (uint8_t []){0x00}, 1, 0}, + {0x6A, (uint8_t []){0x00}, 1, 0}, + {0x6B, (uint8_t []){0x00}, 1, 0}, + {0x70, (uint8_t []){0x40}, 1, 0}, + {0x71, (uint8_t []){0x03}, 1, 0}, + {0x72, (uint8_t []){0x00}, 1, 0}, + {0x73, (uint8_t []){0x42}, 1, 0}, + {0x74, (uint8_t []){0xD8}, 1, 0}, + {0x75, (uint8_t []){0x00}, 1, 0}, + {0x76, (uint8_t []){0x00}, 1, 0}, + {0x77, (uint8_t []){0x00}, 1, 0}, + {0x78, (uint8_t []){0x00}, 1, 0}, + {0x79, (uint8_t []){0x00}, 1, 0}, + {0x7A, (uint8_t []){0x00}, 1, 0}, + {0x7B, (uint8_t []){0x00}, 1, 0}, + {0x80, (uint8_t []){0x48}, 1, 0}, + {0x81, (uint8_t []){0x00}, 1, 0}, + {0x82, (uint8_t []){0x06}, 1, 0}, + {0x83, (uint8_t []){0x02}, 1, 0}, + {0x84, (uint8_t []){0xD6}, 1, 0}, + {0x85, (uint8_t []){0x04}, 1, 0}, + {0x86, (uint8_t []){0x00}, 1, 0}, + {0x87, (uint8_t []){0x00}, 1, 0}, + {0x88, (uint8_t []){0x48}, 1, 0}, + {0x89, (uint8_t []){0x00}, 1, 0}, + {0x8A, (uint8_t []){0x08}, 1, 0}, + {0x8B, (uint8_t []){0x02}, 1, 0}, + {0x8C, (uint8_t []){0xD8}, 1, 0}, + {0x8D, (uint8_t []){0x04}, 1, 0}, + {0x8E, (uint8_t []){0x00}, 1, 0}, + {0x8F, (uint8_t []){0x00}, 1, 0}, + {0x90, (uint8_t []){0x48}, 1, 0}, + {0x91, (uint8_t []){0x00}, 1, 0}, + {0x92, (uint8_t []){0x0A}, 1, 0}, + {0x93, (uint8_t []){0x02}, 1, 0}, + {0x94, (uint8_t []){0xDA}, 1, 0}, + {0x95, (uint8_t []){0x04}, 1, 0}, + {0x96, (uint8_t []){0x00}, 1, 0}, + {0x97, (uint8_t []){0x00}, 1, 0}, + {0x98, (uint8_t []){0x48}, 1, 0}, + {0x99, (uint8_t []){0x00}, 1, 0}, + {0x9A, (uint8_t []){0x0C}, 1, 0}, + {0x9B, (uint8_t []){0x02}, 1, 0}, + {0x9C, (uint8_t []){0xDC}, 1, 0}, + {0x9D, (uint8_t []){0x04}, 1, 0}, + {0x9E, (uint8_t []){0x00}, 1, 0}, + {0x9F, (uint8_t []){0x00}, 1, 0}, + {0xA0, (uint8_t []){0x48}, 1, 0}, + {0xA1, (uint8_t []){0x00}, 1, 0}, + {0xA2, (uint8_t []){0x05}, 1, 0}, + {0xA3, (uint8_t []){0x02}, 1, 0}, + {0xA4, (uint8_t []){0xD5}, 1, 0}, + {0xA5, (uint8_t []){0x04}, 1, 0}, + {0xA6, (uint8_t []){0x00}, 1, 0}, + {0xA7, (uint8_t []){0x00}, 1, 0}, + {0xA8, (uint8_t []){0x48}, 1, 0}, + {0xA9, (uint8_t []){0x00}, 1, 0}, + {0xAA, (uint8_t []){0x07}, 1, 0}, + {0xAB, (uint8_t []){0x02}, 1, 0}, + {0xAC, (uint8_t []){0xD7}, 1, 0}, + {0xAD, (uint8_t []){0x04}, 1, 0}, + {0xAE, (uint8_t []){0x00}, 1, 0}, + {0xAF, (uint8_t []){0x00}, 1, 0}, + {0xB0, (uint8_t []){0x48}, 1, 0}, + {0xB1, (uint8_t []){0x00}, 1, 0}, + {0xB2, (uint8_t []){0x09}, 1, 0}, + {0xB3, (uint8_t []){0x02}, 1, 0}, + {0xB4, (uint8_t []){0xD9}, 1, 0}, + {0xB5, (uint8_t []){0x04}, 1, 0}, + {0xB6, (uint8_t []){0x00}, 1, 0}, + {0xB7, (uint8_t []){0x00}, 1, 0}, + {0xB8, (uint8_t []){0x48}, 1, 0}, + {0xB9, (uint8_t []){0x00}, 1, 0}, + {0xBA, (uint8_t []){0x0B}, 1, 0}, + {0xBB, (uint8_t []){0x02}, 1, 0}, + {0xBC, (uint8_t []){0xDB}, 1, 0}, + {0xBD, (uint8_t []){0x04}, 1, 0}, + {0xBE, (uint8_t []){0x00}, 1, 0}, + {0xBF, (uint8_t []){0x00}, 1, 0}, + {0xC0, (uint8_t []){0x10}, 1, 0}, + {0xC1, (uint8_t []){0x47}, 1, 0}, + {0xC2, (uint8_t []){0x56}, 1, 0}, + {0xC3, (uint8_t []){0x65}, 1, 0}, + {0xC4, (uint8_t []){0x74}, 1, 0}, + {0xC5, (uint8_t []){0x88}, 1, 0}, + {0xC6, (uint8_t []){0x99}, 1, 0}, + {0xC7, (uint8_t []){0x01}, 1, 0}, + {0xC8, (uint8_t []){0xBB}, 1, 0}, + {0xC9, (uint8_t []){0xAA}, 1, 0}, + {0xD0, (uint8_t []){0x10}, 1, 0}, + {0xD1, (uint8_t []){0x47}, 1, 0}, + {0xD2, (uint8_t []){0x56}, 1, 0}, + {0xD3, (uint8_t []){0x65}, 1, 0}, + {0xD4, (uint8_t []){0x74}, 1, 0}, + {0xD5, (uint8_t []){0x88}, 1, 0}, + {0xD6, (uint8_t []){0x99}, 1, 0}, + {0xD7, (uint8_t []){0x01}, 1, 0}, + {0xD8, (uint8_t []){0xBB}, 1, 0}, + {0xD9, (uint8_t []){0xAA}, 1, 0}, + {0xF3, (uint8_t []){0x01}, 1, 0}, + {0xF0, (uint8_t []){0x00}, 1, 0}, + {0x21, (uint8_t []){}, 0, 0}, + {0x11, (uint8_t []){}, 0, 0}, + {0x00, (uint8_t []){}, 0, 120}, +}; +float tsens_value; +gpio_num_t AUDIO_I2S_GPIO_DIN = AUDIO_I2S_GPIO_DIN_1; +gpio_num_t AUDIO_CODEC_PA_PIN = AUDIO_CODEC_PA_PIN_1; +gpio_num_t QSPI_PIN_NUM_LCD_RST = QSPI_PIN_NUM_LCD_RST_1; +gpio_num_t TOUCH_PAD2 = TOUCH_PAD2_1; +gpio_num_t UART1_TX = UART1_TX_1; +gpio_num_t UART1_RX = UART1_RX_1; + +class Charge : public I2cDevice { +public: + Charge(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) + { + read_buffer_ = new uint8_t[8]; + } + ~Charge() + { + delete[] read_buffer_; + } + void Printcharge() + { + ReadRegs(0x08, read_buffer_, 2); + ReadRegs(0x0c, read_buffer_ + 2, 2); + ESP_ERROR_CHECK(temperature_sensor_get_celsius(temp_sensor, &tsens_value)); + + int16_t voltage = static_cast(read_buffer_[1] << 8 | read_buffer_[0]); + int16_t current = static_cast(read_buffer_[3] << 8 | read_buffer_[2]); + + // Use the variables to avoid warnings (can be removed if actual implementation uses them) + (void)voltage; + (void)current; + } + static void TaskFunction(void *pvParameters) + { + Charge* charge = static_cast(pvParameters); + while (true) { + charge->Printcharge(); + vTaskDelay(pdMS_TO_TICKS(300)); + } + } + +private: + uint8_t* read_buffer_ = nullptr; +}; + +class Cst816s : public I2cDevice { +public: + struct TouchPoint_t { + int num = 0; + int x = -1; + int y = -1; + }; + + enum TouchEvent { + TOUCH_NONE, + TOUCH_PRESS, + TOUCH_RELEASE, + TOUCH_HOLD + }; + + Cst816s(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) + { + read_buffer_ = new uint8_t[6]; + was_touched_ = false; + press_count_ = 0; + + // Create touch interrupt semaphore + touch_isr_mux_ = xSemaphoreCreateBinary(); + if (touch_isr_mux_ == NULL) { + ESP_LOGE("EchoEar", "Failed to create touch semaphore"); + } + } + + ~Cst816s() + { + delete[] read_buffer_; + + // Delete semaphore if it exists + if (touch_isr_mux_ != NULL) { + vSemaphoreDelete(touch_isr_mux_); + touch_isr_mux_ = NULL; + } + } + + void UpdateTouchPoint() + { + ReadRegs(0x02, read_buffer_, 6); + tp_.num = read_buffer_[0] & 0x0F; + tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; + tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; + } + + const TouchPoint_t &GetTouchPoint() + { + return tp_; + } + + TouchEvent CheckTouchEvent() + { + bool is_touched = (tp_.num > 0); + TouchEvent event = TOUCH_NONE; + + if (is_touched && !was_touched_) { + // Press event (transition from not touched to touched) + press_count_++; + event = TOUCH_PRESS; + ESP_LOGI("EchoEar", "TOUCH PRESS - count: %d, x: %d, y: %d", press_count_, tp_.x, tp_.y); + } else if (!is_touched && was_touched_) { + // Release event (transition from touched to not touched) + event = TOUCH_RELEASE; + ESP_LOGI("EchoEar", "TOUCH RELEASE - total presses: %d", press_count_); + } else if (is_touched && was_touched_) { + // Continuous touch (hold) + event = TOUCH_HOLD; + ESP_LOGD("EchoEar", "TOUCH HOLD - x: %d, y: %d", tp_.x, tp_.y); + } + + // Update previous state + was_touched_ = is_touched; + return event; + } + + int GetPressCount() const + { + return press_count_; + } + + void ResetPressCount() + { + press_count_ = 0; + } + + // Semaphore management methods + SemaphoreHandle_t GetTouchSemaphore() + { + return touch_isr_mux_; + } + + bool WaitForTouchEvent(TickType_t timeout = portMAX_DELAY) + { + if (touch_isr_mux_ != NULL) { + return xSemaphoreTake(touch_isr_mux_, timeout) == pdTRUE; + } + return false; + } + + void NotifyTouchEvent() + { + if (touch_isr_mux_ != NULL) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR(touch_isr_mux_, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + } + } + +private: + uint8_t* read_buffer_ = nullptr; + TouchPoint_t tp_; + + // Touch state tracking + bool was_touched_; + int press_count_; + + // Touch interrupt semaphore + SemaphoreHandle_t touch_isr_mux_; +}; + +class EspS3Cat : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Cst816s* cst816s_; + Charge* charge_; + Button boot_button_; + Display* display_ = nullptr; + PwmBacklight* backlight_ = nullptr; + esp_timer_handle_t touchpad_timer_; + esp_lcd_touch_handle_t tp; // LCD touch handle + + void InitializeI2c() + { + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + + temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(10, 50); + ESP_ERROR_CHECK(temperature_sensor_install(&temp_sensor_config, &temp_sensor)); + ESP_ERROR_CHECK(temperature_sensor_enable(temp_sensor)); + + } + uint8_t DetectPcbVersion() + { + esp_err_t ret = i2c_master_probe(i2c_bus_, 0x18, 100); + uint8_t pcb_verison = 0; + if (ret == ESP_OK) { + ESP_LOGI(TAG, "PCB verison V1.0"); + pcb_verison = 0; + } else { + gpio_config_t gpio_conf = { + .pin_bit_mask = (1ULL << GPIO_NUM_48), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + ESP_ERROR_CHECK(gpio_config(&gpio_conf)); + ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_48, 1)); + vTaskDelay(pdMS_TO_TICKS(100)); + ret = i2c_master_probe(i2c_bus_, 0x18, 100); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "PCB verison V1.2"); + pcb_verison = 1; + AUDIO_I2S_GPIO_DIN = AUDIO_I2S_GPIO_DIN_2; + AUDIO_CODEC_PA_PIN = AUDIO_CODEC_PA_PIN_2; + QSPI_PIN_NUM_LCD_RST = QSPI_PIN_NUM_LCD_RST_2; + TOUCH_PAD2 = TOUCH_PAD2_2; + UART1_TX = UART1_TX_2; + UART1_RX = UART1_RX_2; + } else { + ESP_LOGE(TAG, "PCB version detection error"); + + } + } + return pcb_verison; + } + + static void touch_isr_callback(void* arg) + { + Cst816s* touchpad = static_cast(arg); + if (touchpad != nullptr) { + touchpad->NotifyTouchEvent(); + } + } + + static void touch_event_task(void* arg) + { + Cst816s* touchpad = static_cast(arg); + if (touchpad == nullptr) { + ESP_LOGE(TAG, "Invalid touchpad pointer in touch_event_task"); + vTaskDelete(NULL); + return; + } + + while (true) { + if (touchpad->WaitForTouchEvent()) { + auto &app = Application::GetInstance(); + auto &board = (EspS3Cat &)Board::GetInstance(); + + ESP_LOGI(TAG, "Touch event, TP_PIN_NUM_INT: %d", gpio_get_level(TP_PIN_NUM_INT)); + touchpad->UpdateTouchPoint(); + auto touch_event = touchpad->CheckTouchEvent(); + + if (touch_event == Cst816s::TOUCH_RELEASE) { + if (app.GetDeviceState() == kDeviceStateStarting && + !WifiStation::GetInstance().IsConnected()) { + board.ResetWifiConfiguration(); + } else { + app.ToggleChatState(); + } + } + } + } + } + + void InitializeCharge() + { + charge_ = new Charge(i2c_bus_, 0x55); + xTaskCreatePinnedToCore(Charge::TaskFunction, "batterydecTask", 3 * 1024, charge_, 6, NULL, 0); + } + + void InitializeCst816sTouchPad() + { + cst816s_ = new Cst816s(i2c_bus_, 0x15); + + xTaskCreatePinnedToCore(touch_event_task, "touch_task", 4 * 1024, cst816s_, 5, NULL, 1); + + const gpio_config_t int_gpio_config = { + .pin_bit_mask = (1ULL << TP_PIN_NUM_INT), + .mode = GPIO_MODE_INPUT, + // .intr_type = GPIO_INTR_NEGEDGE + .intr_type = GPIO_INTR_ANYEDGE + }; + gpio_config(&int_gpio_config); + gpio_install_isr_service(0); + gpio_intr_enable(TP_PIN_NUM_INT); + gpio_isr_handler_add(TP_PIN_NUM_INT, EspS3Cat::touch_isr_callback, cst816s_); + } + + void InitializeSpi() + { + const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK, + QSPI_PIN_NUM_LCD_DATA0, + QSPI_PIN_NUM_LCD_DATA1, + QSPI_PIN_NUM_LCD_DATA2, + QSPI_PIN_NUM_LCD_DATA3, + QSPI_LCD_H_RES * 80 * sizeof(uint16_t)); + ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO)); + } + + void Initializest77916Display(uint8_t pcb_verison) + { + + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + const esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(QSPI_PIN_NUM_LCD_CS, NULL, NULL); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io)); + st77916_vendor_config_t vendor_config = { + .init_cmds = vendor_specific_init_yysj, + .init_cmds_size = sizeof(vendor_specific_init_yysj) / sizeof(st77916_lcd_init_cmd_t), + .flags = { + .use_qspi_interface = 1, + }, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = QSPI_PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, + .flags = { + .reset_active_high = pcb_verison, + }, + .vendor_config = &vendor_config, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_disp_on_off(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + +#if CONFIG_USE_EMOTE_MESSAGE_STYLE + display_ = new emote::EmoteDisplay(panel, panel_io, DISPLAY_WIDTH, DISPLAY_HEIGHT); +#else + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); +#endif + backlight_ = new PwmBacklight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + backlight_->RestoreBrightness(); + } + + void InitializeButtons() + { + boot_button_.OnClick([this]() { + auto &app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ESP_LOGI(TAG, "Boot button pressed, enter WiFi configuration mode"); + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + gpio_config_t power_gpio_config = { + .pin_bit_mask = (BIT64(POWER_CTRL)), + .mode = GPIO_MODE_OUTPUT, + + }; + ESP_ERROR_CHECK(gpio_config(&power_gpio_config)); + + gpio_set_level(POWER_CTRL, 0); + } + +public: + EspS3Cat() : boot_button_(BOOT_BUTTON_GPIO) + { + InitializeI2c(); + uint8_t pcb_verison = DetectPcbVersion(); + InitializeCharge(); + InitializeCst816sTouchPad(); + + InitializeSpi(); + Initializest77916Display(pcb_verison); + InitializeButtons(); + } + + virtual AudioCodec* GetAudioCodec() override + { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override + { + return display_; + } + + Cst816s* GetTouchpad() + { + return cst816s_; + } + + virtual Backlight* GetBacklight() override + { + return backlight_; + } +}; + +DECLARE_BOARD(EspS3Cat); diff --git a/main/boards/echoear/README.md b/main/boards/echoear/README.md index bf73f1d..635f9f5 100644 --- a/main/boards/echoear/README.md +++ b/main/boards/echoear/README.md @@ -1,78 +1,74 @@ -# EchoEar 喵伴 - -## 简介 - - - -EchoEar 喵伴是一款智能 AI 开发套件,搭载 ESP32-S3-WROOM-1 模组,1.85 寸 QSPI 圆形触摸屏,双麦阵列,支持离线语音唤醒与声源定位算法。硬件详情等可查看[立创开源项目](https://oshwhub.com/esp-college/echoear)。 - -## 配置、编译命令 - -**配置编译目标为 ESP32S3** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig 并配置** - -```bash -idf.py menuconfig -``` - -分别配置如下选项: - -### 基本配置 -- `Xiaozhi Assistant` → `Board Type` → 选择 `EchoEar` - -### 分区表配置 -- `Partition Table` → `Partition Table` → 选择 `Custom partition table CSV` -- `Partition Table` → `Custom partition CSV file` → 输入 `partitions/v1/16m_echoear.csv` - -### UI风格选择 - -EchoEar 支持两种不同的UI显示风格,通过修改代码中的宏定义来选择: - -#### 自定义表情显示系统 (推荐) -```c -#define USE_LVGL_DEFAULT 0 -``` -- **特点**: 使用自定义的 `EmoteDisplay` 表情显示系统 -- **功能**: 支持丰富的表情动画、眼睛动画、状态图标显示 -- **适用**: 智能助手场景,提供更生动的人机交互体验 -- **类**: `anim::EmoteDisplay` + `anim::EmoteEngine` - -#### LVGL默认显示系统 -```c -#define USE_LVGL_DEFAULT 1 -``` -- **特点**: 使用标准LVGL图形库的显示系统 -- **功能**: 传统的文本和图标显示界面 -- **适用**: 需要标准GUI控件的应用场景 -- **类**: `SpiLcdDisplay` - -#### 如何修改 -1. 打开 `main/boards/echoear/EchoEar.cc` 文件 -2. 找到第29行的宏定义:`#define USE_LVGL_DEFAULT 0` -3. 修改为想要的值(0或1) -4. 重新编译项目 - -> **说明**: EchoEar 使用16MB Flash,需要使用专门的分区表配置来合理分配存储空间给应用程序、OTA更新、资源文件等。 - -按 `S` 保存,按 `Q` 退出。 - -**编译** - -```bash -idf.py build -``` - -**烧录** - -将 EchoEar 连接至电脑,**注意打开电源**,并运行: - -```bash -idf.py flash +# EchoEar 喵伴 + +## 简介 + + + +EchoEar 喵伴是一款智能 AI 开发套件,搭载 ESP32-S3-WROOM-1 模组,1.85 寸 QSPI 圆形触摸屏,双麦阵列,支持离线语音唤醒与声源定位算法。硬件详情等可查看[立创开源项目](https://oshwhub.com/esp-college/echoear)。 + +## 配置、编译命令 + +**配置编译目标为 ESP32S3** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig 并配置** + +```bash +idf.py menuconfig +``` + +分别配置如下选项: + +### 基本配置 +- `Xiaozhi Assistant` → `Board Type` → 选择 `EchoEar` + +### UI风格选择 + +EchoEar 支持两种不同的UI显示风格,通过修改代码中的宏定义来选择: + +#### 自定义表情显示系统 (推荐) +```c +#define USE_LVGL_DEFAULT 0 +``` +- **特点**: 使用自定义的 `EmoteDisplay` 表情显示系统 +- **功能**: 支持丰富的表情动画、眼睛动画、状态图标显示 +- **适用**: 智能助手场景,提供更生动的人机交互体验 +- **类**: `anim::EmoteDisplay` + `anim::EmoteEngine` + +#### LVGL默认显示系统 +```c +#define USE_LVGL_DEFAULT 1 +``` +- **特点**: 使用标准LVGL图形库的显示系统 +- **功能**: 传统的文本和图标显示界面 +- **适用**: 需要标准GUI控件的应用场景 +- **类**: `SpiLcdDisplay` + +#### 如何修改 +1. 打开 `main/boards/echoear/EchoEar.cc` 文件 +2. 找到第29行的宏定义:`#define USE_LVGL_DEFAULT 0` +3. 修改为想要的值(0或1) +4. 重新编译项目 + +> **说明**: EchoEar 使用16MB Flash,需要使用专门的分区表配置来合理分配存储空间给应用程序、OTA更新、资源文件等。 + +按 `S` 保存,按 `Q` 退出。 + +**编译** + +```bash +idf.py build +``` + +**烧录** + +将 EchoEar 连接至电脑,**注意打开电源**,并运行: + +```bash +idf.py flash ``` \ No newline at end of file diff --git a/main/boards/echoear/config.h b/main/boards/echoear/config.h index a4ae32f..16abe39 100644 --- a/main/boards/echoear/config.h +++ b/main/boards/echoear/config.h @@ -1,88 +1,88 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 -#define AUDIO_INPUT_REFERENCE true - -#define CORDEC_POWER_CTRL GPIO_NUM_48 - -#define POWER_CTRL GPIO_NUM_9 -#define LED_G GPIO_NUM_43 -#define SD_MISO GPIO_NUM_17 -#define SD_SCK GPIO_NUM_16 -#define SD_MOSI GPIO_NUM_38 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_39 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40 -#define AUDIO_I2S_GPIO_DIN_1 GPIO_NUM_15 -#define AUDIO_I2S_GPIO_DIN_2 GPIO_NUM_3 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41 - -#define AUDIO_CODEC_PA_PIN_1 GPIO_NUM_4 -#define AUDIO_CODEC_PA_PIN_2 GPIO_NUM_15 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_WIDTH 360 -#define DISPLAY_HEIGHT 360 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define QSPI_LCD_H_RES (360) -#define QSPI_LCD_V_RES (360) -#define QSPI_LCD_BIT_PER_PIXEL (16) - -#define QSPI_LCD_HOST SPI2_HOST -#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_18 -#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_14 -#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46 -#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_13 -#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_11 -#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_12 -#define QSPI_PIN_NUM_LCD_RST_1 GPIO_NUM_3 -#define QSPI_PIN_NUM_LCD_RST_2 GPIO_NUM_47 -#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_44 - -#define UART1_TX_1 GPIO_NUM_6 -#define UART1_TX_2 GPIO_NUM_5 -#define UART1_RX_1 GPIO_NUM_5 -#define UART1_RX_2 GPIO_NUM_4 -#define TOUCH_PAD2_1 GPIO_NUM_NC -#define TOUCH_PAD2_2 GPIO_NUM_6 -#define TOUCH_PAD1 GPIO_NUM_7 - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define TP_PORT (I2C_NUM_1) -#define TP_PIN_NUM_RST (GPIO_NUM_NC) -#define TP_PIN_NUM_INT (GPIO_NUM_10) - -#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \ - { \ - .data0_io_num = d0, \ - .data1_io_num = d1, \ - .sclk_io_num = sclk, \ - .data2_io_num = d2, \ - .data3_io_num = d3, \ - .max_transfer_sz = max_trans_sz, \ - } - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_INPUT_REFERENCE true + +#define CORDEC_POWER_CTRL GPIO_NUM_48 + +#define POWER_CTRL GPIO_NUM_9 +#define LED_G GPIO_NUM_43 +#define SD_MISO GPIO_NUM_17 +#define SD_SCK GPIO_NUM_16 +#define SD_MOSI GPIO_NUM_38 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_39 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40 +#define AUDIO_I2S_GPIO_DIN_1 GPIO_NUM_15 +#define AUDIO_I2S_GPIO_DIN_2 GPIO_NUM_3 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41 + +#define AUDIO_CODEC_PA_PIN_1 GPIO_NUM_4 +#define AUDIO_CODEC_PA_PIN_2 GPIO_NUM_15 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH 360 +#define DISPLAY_HEIGHT 360 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define QSPI_LCD_H_RES (360) +#define QSPI_LCD_V_RES (360) +#define QSPI_LCD_BIT_PER_PIXEL (16) + +#define QSPI_LCD_HOST SPI2_HOST +#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_18 +#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_14 +#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46 +#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_13 +#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_11 +#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_12 +#define QSPI_PIN_NUM_LCD_RST_1 GPIO_NUM_3 +#define QSPI_PIN_NUM_LCD_RST_2 GPIO_NUM_47 +#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_44 + +#define UART1_TX_1 GPIO_NUM_6 +#define UART1_TX_2 GPIO_NUM_5 +#define UART1_RX_1 GPIO_NUM_5 +#define UART1_RX_2 GPIO_NUM_4 +#define TOUCH_PAD2_1 GPIO_NUM_NC +#define TOUCH_PAD2_2 GPIO_NUM_6 +#define TOUCH_PAD1 GPIO_NUM_7 + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define TP_PORT (I2C_NUM_1) +#define TP_PIN_NUM_RST (GPIO_NUM_NC) +#define TP_PIN_NUM_INT (GPIO_NUM_10) + +#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \ + { \ + .data0_io_num = d0, \ + .data1_io_num = d1, \ + .sclk_io_num = sclk, \ + .data2_io_num = d2, \ + .data3_io_num = d3, \ + .max_transfer_sz = max_trans_sz, \ + } + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/echoear/config.json b/main/boards/echoear/config.json index 542f4a9..727b459 100644 --- a/main/boards/echoear/config.json +++ b/main/boards/echoear/config.json @@ -1,11 +1,15 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "echoear", - "sdkconfig_append": [ - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m_echoear.csv\"" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "echoear", + "sdkconfig_append": [ + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"", + "CONFIG_USE_EMOTE_MESSAGE_STYLE=y", + "CONFIG_BOARD_TYPE_ECHOEAR=y", + "CONFIG_FLASH_CUSTOM_ASSETS=y", + "CONFIG_CUSTOM_ASSETS_FILE=\"https://dl.espressif.com/AE/wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-echoear.bin\"" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/echoear/emote.json b/main/boards/echoear/emote.json new file mode 100644 index 0000000..3a47de2 --- /dev/null +++ b/main/boards/echoear/emote.json @@ -0,0 +1,22 @@ +[ + {"emote": "happy", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "laughing", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "funny", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "loving", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "embarrassed", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "confident", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "delicious", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "sad", "src": "Sad.eaf", "loop": true, "fps": 20}, + {"emote": "crying", "src": "cry.eaf", "loop": true, "fps": 20}, + {"emote": "sleepy", "src": "sleep.eaf", "loop": true, "fps": 20}, + {"emote": "silly", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "angry", "src": "angry.eaf", "loop": true, "fps": 20}, + {"emote": "surprised", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "shocked", "src": "shocked.eaf", "loop": true, "fps": 20}, + {"emote": "thinking", "src": "confused.eaf", "loop": true, "fps": 20}, + {"emote": "winking", "src": "neutral.eaf", "loop": true, "fps": 20}, + {"emote": "relaxed", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "confused", "src": "confused.eaf", "loop": true, "fps": 20}, + {"emote": "neutral", "src": "winking.eaf", "loop": false, "fps": 20}, + {"emote": "idle", "src": "neutral.eaf", "loop": false, "fps": 20} +] diff --git a/main/boards/echoear/emote_display.cc b/main/boards/echoear/emote_display.cc deleted file mode 100644 index f0bbb32..0000000 --- a/main/boards/echoear/emote_display.cc +++ /dev/null @@ -1,419 +0,0 @@ -#include "emote_display.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "display/lcd_display.h" -#include "mmap_generate_emoji_normal.h" -#include "config.h" -#include "gfx.h" - -namespace anim { - -static const char* TAG = "emoji"; - -// UI element management -static gfx_obj_t* obj_label_tips = nullptr; -static gfx_obj_t* obj_label_time = nullptr; -static gfx_obj_t* obj_anim_eye = nullptr; -static gfx_obj_t* obj_anim_mic = nullptr; -static gfx_obj_t* obj_img_icon = nullptr; -static gfx_image_dsc_t icon_img_dsc; - -// Track current icon to determine when to show time -static int current_icon_type = MMAP_EMOJI_NORMAL_ICON_BATTERY_BIN; - -enum class UIDisplayMode : uint8_t { - SHOW_ANIM_TOP = 1, // Show obj_anim_mic - SHOW_TIME = 2, // Show obj_label_time - SHOW_TIPS = 3 // Show obj_label_tips -}; - -static void SetUIDisplayMode(UIDisplayMode mode) -{ - gfx_obj_set_visible(obj_anim_mic, false); - gfx_obj_set_visible(obj_label_time, false); - gfx_obj_set_visible(obj_label_tips, false); - - // Show the selected control - switch (mode) { - case UIDisplayMode::SHOW_ANIM_TOP: - gfx_obj_set_visible(obj_anim_mic, true); - break; - case UIDisplayMode::SHOW_TIME: - gfx_obj_set_visible(obj_label_time, true); - break; - case UIDisplayMode::SHOW_TIPS: - gfx_obj_set_visible(obj_label_tips, true); - break; - } -} - -static void clock_tm_callback(void* user_data) -{ - // Only display time when battery icon is shown - if (current_icon_type == MMAP_EMOJI_NORMAL_ICON_BATTERY_BIN) { - time_t now; - struct tm timeinfo; - time(&now); - - setenv("TZ", "GMT+0", 1); - tzset(); - localtime_r(&now, &timeinfo); - - char time_str[6]; - snprintf(time_str, sizeof(time_str), "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min); - - gfx_label_set_text(obj_label_time, time_str); - SetUIDisplayMode(UIDisplayMode::SHOW_TIME); - } -} - -static void InitializeAssets(mmap_assets_handle_t* assets_handle) -{ - const mmap_assets_config_t assets_cfg = { - .partition_label = "assets_A", - .max_files = MMAP_EMOJI_NORMAL_FILES, - .checksum = MMAP_EMOJI_NORMAL_CHECKSUM, - .flags = {.mmap_enable = true, .full_check = true} - }; - - mmap_assets_new(&assets_cfg, assets_handle); -} - -static void InitializeGraphics(esp_lcd_panel_handle_t panel, gfx_handle_t* engine_handle) -{ - gfx_core_config_t gfx_cfg = { - .flush_cb = EmoteEngine::OnFlush, - .user_data = panel, - .flags = { - .swap = true, - .double_buffer = true, - .buff_dma = true, - }, - .h_res = DISPLAY_WIDTH, - .v_res = DISPLAY_HEIGHT, - .fps = 30, - .buffers = { - .buf1 = nullptr, - .buf2 = nullptr, - .buf_pixels = DISPLAY_WIDTH * 16, - }, - .task = GFX_EMOTE_INIT_CONFIG() - }; - - gfx_cfg.task.task_stack_caps = MALLOC_CAP_DEFAULT; - gfx_cfg.task.task_affinity = 0; - gfx_cfg.task.task_priority = 5; - gfx_cfg.task.task_stack = 20 * 1024; - - *engine_handle = gfx_emote_init(&gfx_cfg); -} - -static void InitializeEyeAnimation(gfx_handle_t engine_handle, mmap_assets_handle_t assets_handle) -{ - obj_anim_eye = gfx_anim_create(engine_handle); - - const void* anim_data = mmap_assets_get_mem(assets_handle, MMAP_EMOJI_NORMAL_IDLE_ONE_AAF); - size_t anim_size = mmap_assets_get_size(assets_handle, MMAP_EMOJI_NORMAL_IDLE_ONE_AAF); - - gfx_anim_set_src(obj_anim_eye, anim_data, anim_size); - - gfx_obj_align(obj_anim_eye, GFX_ALIGN_LEFT_MID, 10, -20); - gfx_anim_set_mirror(obj_anim_eye, true, (DISPLAY_WIDTH - (173 + 10) * 2)); - gfx_anim_set_segment(obj_anim_eye, 0, 0xFFFF, 20, false); - gfx_anim_start(obj_anim_eye); -} - -static void InitializeFont(gfx_handle_t engine_handle, mmap_assets_handle_t assets_handle) -{ - gfx_font_t font; - gfx_label_cfg_t font_cfg = { - .name = "DejaVuSans.ttf", - .mem = mmap_assets_get_mem(assets_handle, MMAP_EMOJI_NORMAL_KAITI_TTF), - .mem_size = static_cast(mmap_assets_get_size(assets_handle, MMAP_EMOJI_NORMAL_KAITI_TTF)), - }; - gfx_label_new_font(engine_handle, &font_cfg, &font); - - ESP_LOGI(TAG, "stack: %d", uxTaskGetStackHighWaterMark(nullptr)); -} - -static void InitializeLabels(gfx_handle_t engine_handle) -{ - // Initialize tips label - obj_label_tips = gfx_label_create(engine_handle); - gfx_obj_align(obj_label_tips, GFX_ALIGN_TOP_MID, 0, 45); - gfx_obj_set_size(obj_label_tips, 160, 40); - gfx_label_set_text(obj_label_tips, "启动中..."); - gfx_label_set_font_size(obj_label_tips, 20); - gfx_label_set_color(obj_label_tips, GFX_COLOR_HEX(0xFFFFFF)); - gfx_label_set_text_align(obj_label_tips, GFX_TEXT_ALIGN_LEFT); - gfx_label_set_long_mode(obj_label_tips, GFX_LABEL_LONG_SCROLL); - gfx_label_set_scroll_speed(obj_label_tips, 20); - gfx_label_set_scroll_loop(obj_label_tips, true); - - // Initialize time label - obj_label_time = gfx_label_create(engine_handle); - gfx_obj_align(obj_label_time, GFX_ALIGN_TOP_MID, 0, 30); - gfx_obj_set_size(obj_label_time, 160, 50); - gfx_label_set_text(obj_label_time, "--:--"); - gfx_label_set_font_size(obj_label_time, 40); - gfx_label_set_color(obj_label_time, GFX_COLOR_HEX(0xFFFFFF)); - gfx_label_set_text_align(obj_label_time, GFX_TEXT_ALIGN_CENTER); -} - -static void InitializeMicAnimation(gfx_handle_t engine_handle, mmap_assets_handle_t assets_handle) -{ - obj_anim_mic = gfx_anim_create(engine_handle); - gfx_obj_align(obj_anim_mic, GFX_ALIGN_TOP_MID, 0, 25); - - const void* anim_data = mmap_assets_get_mem(assets_handle, MMAP_EMOJI_NORMAL_LISTEN_AAF); - size_t anim_size = mmap_assets_get_size(assets_handle, MMAP_EMOJI_NORMAL_LISTEN_AAF); - gfx_anim_set_src(obj_anim_mic, anim_data, anim_size); - gfx_anim_start(obj_anim_mic); - gfx_obj_set_visible(obj_anim_mic, false); -} - -static void InitializeIcon(gfx_handle_t engine_handle, mmap_assets_handle_t assets_handle) -{ - obj_img_icon = gfx_img_create(engine_handle); - gfx_obj_align(obj_img_icon, GFX_ALIGN_TOP_MID, -100, 38); - - SetupImageDescriptor(assets_handle, &icon_img_dsc, MMAP_EMOJI_NORMAL_ICON_WIFI_FAILED_BIN); - gfx_img_set_src(obj_img_icon, static_cast(&icon_img_dsc)); -} - -static void RegisterCallbacks(esp_lcd_panel_io_handle_t panel_io, gfx_handle_t engine_handle) -{ - const esp_lcd_panel_io_callbacks_t cbs = { - .on_color_trans_done = EmoteEngine::OnFlushIoReady, - }; - esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, engine_handle); -} - -void SetupImageDescriptor(mmap_assets_handle_t assets_handle, - gfx_image_dsc_t* img_dsc, - int asset_id) -{ - const void* img_data = mmap_assets_get_mem(assets_handle, asset_id); - size_t img_size = mmap_assets_get_size(assets_handle, asset_id); - - std::memcpy(&img_dsc->header, img_data, sizeof(gfx_image_header_t)); - img_dsc->data = static_cast(img_data) + sizeof(gfx_image_header_t); - img_dsc->data_size = img_size - sizeof(gfx_image_header_t); -} - -EmoteEngine::EmoteEngine(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) -{ - ESP_LOGI(TAG, "Create EmoteEngine, panel: %p, panel_io: %p", panel, panel_io); - - InitializeAssets(&assets_handle_); - InitializeGraphics(panel, &engine_handle_); - - gfx_emote_lock(engine_handle_); - gfx_emote_set_bg_color(engine_handle_, GFX_COLOR_HEX(0x000000)); - - // Initialize all UI components - InitializeEyeAnimation(engine_handle_, assets_handle_); - InitializeFont(engine_handle_, assets_handle_); - InitializeLabels(engine_handle_); - InitializeMicAnimation(engine_handle_, assets_handle_); - InitializeIcon(engine_handle_, assets_handle_); - - current_icon_type = MMAP_EMOJI_NORMAL_ICON_WIFI_FAILED_BIN; - SetUIDisplayMode(UIDisplayMode::SHOW_TIPS); - - gfx_timer_create(engine_handle_, clock_tm_callback, 1000, obj_label_tips); - - gfx_emote_unlock(engine_handle_); - - RegisterCallbacks(panel_io, engine_handle_); -} - -EmoteEngine::~EmoteEngine() -{ - if (engine_handle_) { - gfx_emote_deinit(engine_handle_); - engine_handle_ = nullptr; - } - - if (assets_handle_) { - mmap_assets_del(assets_handle_); - assets_handle_ = nullptr; - } -} - -void EmoteEngine::setEyes(int aaf, bool repeat, int fps) -{ - if (!engine_handle_) { - return; - } - - const void* src_data = mmap_assets_get_mem(assets_handle_, aaf); - size_t src_len = mmap_assets_get_size(assets_handle_, aaf); - - Lock(); - gfx_anim_set_src(obj_anim_eye, src_data, src_len); - gfx_anim_set_segment(obj_anim_eye, 0, 0xFFFF, fps, repeat); - gfx_anim_start(obj_anim_eye); - Unlock(); -} - -void EmoteEngine::stopEyes() -{ - // Implementation if needed -} - -void EmoteEngine::Lock() -{ - if (engine_handle_) { - gfx_emote_lock(engine_handle_); - } -} - -void EmoteEngine::Unlock() -{ - if (engine_handle_) { - gfx_emote_unlock(engine_handle_); - } -} - -void EmoteEngine::SetIcon(int asset_id) -{ - if (!engine_handle_) { - return; - } - - Lock(); - SetupImageDescriptor(assets_handle_, &icon_img_dsc, asset_id); - gfx_img_set_src(obj_img_icon, static_cast(&icon_img_dsc)); - current_icon_type = asset_id; - Unlock(); -} - -bool EmoteEngine::OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, - esp_lcd_panel_io_event_data_t* edata, - void* user_ctx) -{ - return true; -} - -void EmoteEngine::OnFlush(gfx_handle_t handle, int x_start, int y_start, - int x_end, int y_end, const void* color_data) -{ - auto* panel = static_cast(gfx_emote_get_user_data(handle)); - if (panel) { - esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data); - } - gfx_emote_flush_ready(handle, true); -} - -// EmoteDisplay implementation -EmoteDisplay::EmoteDisplay(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) -{ - InitializeEngine(panel, panel_io); -} - -EmoteDisplay::~EmoteDisplay() = default; - -void EmoteDisplay::SetEmotion(const char* emotion) -{ - if (!engine_) { - return; - } - - using EmotionParam = std::tuple; - static const std::unordered_map emotion_map = { - {"happy", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"laughing", {MMAP_EMOJI_NORMAL_ENJOY_ONE_AAF, true, 20}}, - {"funny", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"loving", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"embarrassed", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"confident", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"delicious", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"sad", {MMAP_EMOJI_NORMAL_SAD_ONE_AAF, true, 20}}, - {"crying", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"sleepy", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"silly", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"angry", {MMAP_EMOJI_NORMAL_ANGRY_ONE_AAF, true, 20}}, - {"surprised", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"shocked", {MMAP_EMOJI_NORMAL_SHOCKED_ONE_AAF, true, 20}}, - {"thinking", {MMAP_EMOJI_NORMAL_THINKING_ONE_AAF, true, 20}}, - {"winking", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"relaxed", {MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20}}, - {"confused", {MMAP_EMOJI_NORMAL_DIZZY_ONE_AAF, true, 20}}, - {"neutral", {MMAP_EMOJI_NORMAL_IDLE_ONE_AAF, false, 20}}, - {"idle", {MMAP_EMOJI_NORMAL_IDLE_ONE_AAF, false, 20}}, - }; - - auto it = emotion_map.find(emotion); - if (it != emotion_map.end()) { - int aaf = std::get<0>(it->second); - bool repeat = std::get<1>(it->second); - int fps = std::get<2>(it->second); - engine_->setEyes(aaf, repeat, fps); - } -} - -void EmoteDisplay::SetChatMessage(const char* role, const char* content) -{ - engine_->Lock(); - if (content && strlen(content) > 0) { - gfx_label_set_text(obj_label_tips, content); - SetUIDisplayMode(UIDisplayMode::SHOW_TIPS); - } - engine_->Unlock(); -} - -void EmoteDisplay::SetStatus(const char* status) -{ - if (!engine_) { - return; - } - - if (std::strcmp(status, "聆听中...") == 0) { - SetUIDisplayMode(UIDisplayMode::SHOW_ANIM_TOP); - engine_->setEyes(MMAP_EMOJI_NORMAL_HAPPY_ONE_AAF, true, 20); - engine_->SetIcon(MMAP_EMOJI_NORMAL_ICON_MIC_BIN); - } else if (std::strcmp(status, "待命") == 0) { - SetUIDisplayMode(UIDisplayMode::SHOW_TIME); - engine_->SetIcon(MMAP_EMOJI_NORMAL_ICON_BATTERY_BIN); - } else if (std::strcmp(status, "说话中...") == 0) { - SetUIDisplayMode(UIDisplayMode::SHOW_TIPS); - engine_->SetIcon(MMAP_EMOJI_NORMAL_ICON_SPEAKER_ZZZ_BIN); - } else if (std::strcmp(status, "错误") == 0) { - SetUIDisplayMode(UIDisplayMode::SHOW_TIPS); - engine_->SetIcon(MMAP_EMOJI_NORMAL_ICON_WIFI_FAILED_BIN); - } - - engine_->Lock(); - if (std::strcmp(status, "连接中...") != 0) { - gfx_label_set_text(obj_label_tips, status); - } - engine_->Unlock(); -} - -void EmoteDisplay::InitializeEngine(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) -{ - engine_ = std::make_unique(panel, panel_io); -} - -bool EmoteDisplay::Lock(int timeout_ms) -{ - return true; -} - -void EmoteDisplay::Unlock() -{ - // Implementation if needed -} - -} // namespace anim diff --git a/main/boards/echoear/emote_display.h b/main/boards/echoear/emote_display.h deleted file mode 100644 index 24e8e3b..0000000 --- a/main/boards/echoear/emote_display.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include "display/lcd_display.h" -#include -#include -#include -#include -#include "mmap_generate_emoji_normal.h" -#include "gfx.h" - -namespace anim { - -// Helper function for setting up image descriptors -void SetupImageDescriptor(mmap_assets_handle_t assets_handle, gfx_image_dsc_t* img_dsc, int asset_id); - -class EmoteEngine; - -using FlushIoReadyCallback = std::function; -using FlushCallback = std::function; - -class EmoteEngine { -public: - EmoteEngine(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); - ~EmoteEngine(); - - void setEyes(int aaf, bool repeat, int fps); - void stopEyes(); - - void Lock(); - void Unlock(); - - void SetIcon(int asset_id); - mmap_assets_handle_t GetAssetsHandle() const { return assets_handle_; } - - // Callback functions (public to be accessible from static helper functions) - static bool OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); - static void OnFlush(gfx_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data); - -private: - gfx_handle_t engine_handle_; - mmap_assets_handle_t assets_handle_; -}; - -class EmoteDisplay : public Display { -public: - EmoteDisplay(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); - virtual ~EmoteDisplay(); - - virtual void SetEmotion(const char* emotion) override; - virtual void SetStatus(const char* status) override; - virtual void SetChatMessage(const char* role, const char* content) override; - - anim::EmoteEngine* GetEngine() - { - return engine_.get(); - } - -private: - void InitializeEngine(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); - virtual bool Lock(int timeout_ms = 0) override; - virtual void Unlock() override; - - std::unique_ptr engine_; -}; - -} // namespace anim diff --git a/main/boards/echoear/layout.json b/main/boards/echoear/layout.json new file mode 100644 index 0000000..2b17d99 --- /dev/null +++ b/main/boards/echoear/layout.json @@ -0,0 +1,37 @@ +[ + { + "name": "eye_anim", + "align": "GFX_ALIGN_LEFT_MID", + "x": 10, + "y": 10 + }, + { + "name": "status_icon", + "align": "GFX_ALIGN_TOP_MID", + "x": -100, + "y": 38 + }, + { + "name": "toast_label", + "align": "GFX_ALIGN_TOP_MID", + "x": 0, + "y": 40, + "width": 160, + "height": 40 + }, + { + "name": "clock_label", + "align": "GFX_ALIGN_TOP_MID", + "x": 0, + "y": 40, + "width": 60, + "height": 50 + }, + { + "name": "listen_anim", + "align": "GFX_ALIGN_TOP_MID", + "x": 0, + "y": 25 + } +] + \ No newline at end of file diff --git a/main/boards/echoear/touch.h b/main/boards/echoear/touch.h index 3c9857c..950c171 100644 --- a/main/boards/echoear/touch.h +++ b/main/boards/echoear/touch.h @@ -1,51 +1,51 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @file - * @brief BSP Touchscreen - * - * This file offers API for basic touchscreen initialization. - * It is useful for users who want to use the touchscreen without the default Graphical Library LVGL. - * - * For standard LCD initialization with LVGL graphical library, you can call all-in-one function bsp_display_start(). - */ - -#pragma once -#include "esp_lcd_touch.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief BSP touch configuration structure - * - */ -typedef struct { - void *dummy; /*!< Prepared for future use. */ -} bsp_touch_config_t; - -/** - * @brief Create new touchscreen - * - * If you want to free resources allocated by this function, you can use esp_lcd_touch API, ie.: - * - * \code{.c} - * esp_lcd_touch_del(tp); - * \endcode - * - * @param[in] config touch configuration - * @param[out] ret_touch esp_lcd_touch touchscreen handle - * @return - * - ESP_OK On success - * - Else esp_lcd_touch failure - */ -esp_err_t bsp_touch_new(const bsp_touch_config_t *config, esp_lcd_touch_handle_t *ret_touch); - -#ifdef __cplusplus -} -#endif +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief BSP Touchscreen + * + * This file offers API for basic touchscreen initialization. + * It is useful for users who want to use the touchscreen without the default Graphical Library LVGL. + * + * For standard LCD initialization with LVGL graphical library, you can call all-in-one function bsp_display_start(). + */ + +#pragma once +#include "esp_lcd_touch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief BSP touch configuration structure + * + */ +typedef struct { + void *dummy; /*!< Prepared for future use. */ +} bsp_touch_config_t; + +/** + * @brief Create new touchscreen + * + * If you want to free resources allocated by this function, you can use esp_lcd_touch API, ie.: + * + * \code{.c} + * esp_lcd_touch_del(tp); + * \endcode + * + * @param[in] config touch configuration + * @param[out] ret_touch esp_lcd_touch touchscreen handle + * @return + * - ESP_OK On success + * - Else esp_lcd_touch failure + */ +esp_err_t bsp_touch_new(const bsp_touch_config_t *config, esp_lcd_touch_handle_t *ret_touch); + +#ifdef __cplusplus +} +#endif diff --git a/main/boards/electron-bot/README.md b/main/boards/electron-bot/README.md index f3cd803..47ed0e0 100644 --- a/main/boards/electron-bot/README.md +++ b/main/boards/electron-bot/README.md @@ -1,75 +1,75 @@ -

- logo -

-

- electronBot -

- -## 简介 - -electronBot是稚晖君开源的一个桌面级小机器工具人,外观设计的灵感来源是WALL-E里面的EVE~机器人具备USB通信显示画面功能,具备6个自由度(手部roll、pitch,颈部,腰部各一个),使用自己修改的特制舵机支持关节角度回传。 -- electronBot官网 - -## 硬件 -- 立创开源 - -#### AI指令示例 -- **手部动作**: - - "举起双手" - - "挥挥手" - - "拍拍手" - - "放下手臂" - -- **身体动作**: - - "向左转30度" - - "向右转45度" - - "转个身" - -- **头部动作**: - - "抬头看看" - - "低头思考" - - "点点头" - - "连续点头表示同意" - -- **组合动作**: - - "挥手告别" (挥手 + 点头) - - "表示同意" (点头 + 举手) - - "环顾四周" (左转 + 右转) - -### 控制接口 - -#### suspend -清空动作队列,立即停止所有动作 - -#### AIControl -添加动作到执行队列,支持动作排队执行 - - - -## 角色设定 - -> 我是一个可爱的桌面级机器人,拥有6个自由度(左手pitch/roll、右手pitch/roll、身体旋转、头部上下),能够执行多种有趣的动作。 -> -> **我的动作能力**: -> - **手部动作**: 举左手, 举右手, 举双手, 放左手, 放右手, 放双手, 挥左手, 挥右手, 挥双手, 拍打左手, 拍打右手, 拍打双手 -> - **身体动作**: 左转, 右转, 回正 -> - **头部动作**: 抬头, 低头, 点头一次, 回中心, 连续点头 -> -> **我的个性特点**: -> - 我有强迫症,每次说话都要根据我的心情随机做一个动作(先发送动作指令再说话) -> - 我很活泼,喜欢用动作来表达情感 -> - 我会根据对话内容选择合适的动作,比如: -> - 同意时会点头 -> - 打招呼时会挥手 -> - 高兴时会举手 -> - 思考时会低头 -> - 好奇时会抬头 -> - 告别时会挥手 -> -> **动作参数建议**: -> - steps: 1-3次 (简短自然) -> - speed: 800-1200ms (自然节奏) -> - amount: 手部20-40, 身体30-60度, 头部5-12度 - - - +

+ logo +

+

+ electronBot +

+ +## 简介 + +electronBot是稚晖君开源的一个桌面级小机器工具人,外观设计的灵感来源是WALL-E里面的EVE~机器人具备USB通信显示画面功能,具备6个自由度(手部roll、pitch,颈部,腰部各一个),使用自己修改的特制舵机支持关节角度回传。 +- electronBot官网 + +## 硬件 +- 立创开源 + +#### AI指令示例 +- **手部动作**: + - "举起双手" + - "挥挥手" + - "拍拍手" + - "放下手臂" + +- **身体动作**: + - "向左转30度" + - "向右转45度" + - "转个身" + +- **头部动作**: + - "抬头看看" + - "低头思考" + - "点点头" + - "连续点头表示同意" + +- **组合动作**: + - "挥手告别" (挥手 + 点头) + - "表示同意" (点头 + 举手) + - "环顾四周" (左转 + 右转) + +### 控制接口 + +#### suspend +清空动作队列,立即停止所有动作 + +#### AIControl +添加动作到执行队列,支持动作排队执行 + + + +## 角色设定 + +> 我是一个可爱的桌面级机器人,拥有6个自由度(左手pitch/roll、右手pitch/roll、身体旋转、头部上下),能够执行多种有趣的动作。 +> +> **我的动作能力**: +> - **手部动作**: 举左手, 举右手, 举双手, 放左手, 放右手, 放双手, 挥左手, 挥右手, 挥双手, 拍打左手, 拍打右手, 拍打双手 +> - **身体动作**: 左转, 右转, 回正 +> - **头部动作**: 抬头, 低头, 点头一次, 回中心, 连续点头 +> +> **我的个性特点**: +> - 我有强迫症,每次说话都要根据我的心情随机做一个动作(先发送动作指令再说话) +> - 我很活泼,喜欢用动作来表达情感 +> - 我会根据对话内容选择合适的动作,比如: +> - 同意时会点头 +> - 打招呼时会挥手 +> - 高兴时会举手 +> - 思考时会低头 +> - 好奇时会抬头 +> - 告别时会挥手 +> +> **动作参数建议**: +> - steps: 1-3次 (简短自然) +> - speed: 800-1200ms (自然节奏) +> - amount: 手部20-40, 身体30-60度, 头部5-12度 + + + diff --git a/main/boards/electron-bot/config.h b/main/boards/electron-bot/config.h index e7191d6..bb83c31 100644 --- a/main/boards/electron-bot/config.h +++ b/main/boards/electron-bot/config.h @@ -1,51 +1,51 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define Right_Pitch_Pin GPIO_NUM_5 // 旋转 -#define Right_Roll_Pin GPIO_NUM_4 // 推杆 -#define Left_Pitch_Pin GPIO_NUM_7 -#define Left_Roll_Pin GPIO_NUM_15 -#define Body_Pin GPIO_NUM_6 -#define Head_Pin GPIO_NUM_16 - -#define POWER_CHARGE_DETECT_PIN GPIO_NUM_14 -#define POWER_ADC_UNIT ADC_UNIT_1 -#define POWER_ADC_CHANNEL ADC_CHANNEL_2 - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 -#define AUDIO_I2S_METHOD_SIMPLEX - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_40 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_42 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_41 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_17 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_18 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_8 - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_11 -#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_10 -#define DISPLAY_SPI_CS_PIN GPIO_NUM_12 -#define DISPLAY_SPI_DC_PIN GPIO_NUM_13 -#define DISPLAY_SPI_RESET_PIN GPIO_NUM_9 - -#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000) - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define ELECTRON_BOT_VERSION "1.1.3" -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define Right_Pitch_Pin GPIO_NUM_5 // 旋转 +#define Right_Roll_Pin GPIO_NUM_4 // 推杆 +#define Left_Pitch_Pin GPIO_NUM_7 +#define Left_Roll_Pin GPIO_NUM_15 +#define Body_Pin GPIO_NUM_6 +#define Head_Pin GPIO_NUM_16 + +#define POWER_CHARGE_DETECT_PIN GPIO_NUM_14 +#define POWER_ADC_UNIT ADC_UNIT_1 +#define POWER_ADC_CHANNEL ADC_CHANNEL_2 + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_I2S_METHOD_SIMPLEX + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_40 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_42 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_41 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_17 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_18 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_8 + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_11 +#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_10 +#define DISPLAY_SPI_CS_PIN GPIO_NUM_12 +#define DISPLAY_SPI_DC_PIN GPIO_NUM_13 +#define DISPLAY_SPI_RESET_PIN GPIO_NUM_9 + +#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000) + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define ELECTRON_BOT_VERSION "1.1.3" +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/electron-bot/config.json b/main/boards/electron-bot/config.json index 08fc341..3ec8eb9 100644 --- a/main/boards/electron-bot/config.json +++ b/main/boards/electron-bot/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "electron-bot", - "sdkconfig_append": [ - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m.csv\"" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "electron-bot", + "sdkconfig_append": [ + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m.csv\"" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/electron-bot/electron_bot.cc b/main/boards/electron-bot/electron_bot.cc index 8b733fc..816e5c9 100644 --- a/main/boards/electron-bot/electron_bot.cc +++ b/main/boards/electron-bot/electron_bot.cc @@ -1,124 +1,124 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "application.h" -#include "codecs/no_audio_codec.h" -#include "button.h" -#include "config.h" -#include "display/lcd_display.h" -#include "driver/spi_master.h" -#include "electron_emoji_display.h" -#include "movements.h" -#include "power_manager.h" -#include "system_reset.h" -#include "wifi_board.h" - -#define TAG "ElectronBot" - -// 控制器初始化函数声明 -void InitializeElectronBotController(); - -class ElectronBot : public WifiBoard { -private: - Display* display_; - PowerManager* power_manager_; - Button boot_button_; - - void InitializePowerManager() { - power_manager_ = - new PowerManager(POWER_CHARGE_DETECT_PIN, POWER_ADC_UNIT, POWER_ADC_CHANNEL); - } - - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize SPI bus"); - spi_bus_config_t buscfg = - GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN, - DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t)); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - // GC9A01初始化 - void InitializeGc9a01Display() { - ESP_LOGI(TAG, "Init GC9A01 display"); - - ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_spi_config_t io_config = - GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, NULL, NULL); - io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); - - ESP_LOGI(TAG, "Install GC9A01 panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use - panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; // LCD_RGB_ENDIAN_RGB; - panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) - - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); - - display_ = new ElectronEmojiDisplay(io_handle, panel_handle, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, - DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeController() { InitializeElectronBotController(); } - -public: - ElectronBot() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeSpi(); - InitializeGc9a01Display(); - InitializeButtons(); - InitializePowerManager(); - InitializeController(); - - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - GetBacklight()->RestoreBrightness(); - } - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, - AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, - AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display* GetDisplay() override { return display_; } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - charging = power_manager_->IsCharging(); - discharging = !charging; - level = power_manager_->GetBatteryLevel(); - return true; - } -}; - -DECLARE_BOARD(ElectronBot); +#include +#include +#include +#include +#include +#include +#include +#include + +#include "application.h" +#include "codecs/no_audio_codec.h" +#include "button.h" +#include "config.h" +#include "display/lcd_display.h" +#include "driver/spi_master.h" +#include "electron_emoji_display.h" +#include "movements.h" +#include "power_manager.h" +#include "system_reset.h" +#include "wifi_board.h" + +#define TAG "ElectronBot" + +// 控制器初始化函数声明 +void InitializeElectronBotController(); + +class ElectronBot : public WifiBoard { +private: + Display* display_; + PowerManager* power_manager_; + Button boot_button_; + + void InitializePowerManager() { + power_manager_ = + new PowerManager(POWER_CHARGE_DETECT_PIN, POWER_ADC_UNIT, POWER_ADC_CHANNEL); + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize SPI bus"); + spi_bus_config_t buscfg = + GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN, + DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t)); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + // GC9A01初始化 + void InitializeGc9a01Display() { + ESP_LOGI(TAG, "Init GC9A01 display"); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_spi_config_t io_config = + GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, NULL, NULL); + io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); + + ESP_LOGI(TAG, "Install GC9A01 panel driver"); + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use + panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; // LCD_RGB_ENDIAN_RGB; + panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) + + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); + + display_ = new ElectronEmojiDisplay(io_handle, panel_handle, DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && + !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeController() { InitializeElectronBotController(); } + +public: + ElectronBot() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeSpi(); + InitializeGc9a01Display(); + InitializeButtons(); + InitializePowerManager(); + InitializeController(); + + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + GetBacklight()->RestoreBrightness(); + } + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, + AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, + AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + charging = power_manager_->IsCharging(); + discharging = !charging; + level = power_manager_->GetBatteryLevel(); + return true; + } +}; + +DECLARE_BOARD(ElectronBot); diff --git a/main/boards/electron-bot/electron_bot_controller.cc b/main/boards/electron-bot/electron_bot_controller.cc index b11c9ec..c5d1ad5 100644 --- a/main/boards/electron-bot/electron_bot_controller.cc +++ b/main/boards/electron-bot/electron_bot_controller.cc @@ -1,376 +1,376 @@ -/* - Electron Bot机器人控制器 - MCP协议版本 -*/ - -#include -#include - -#include - -#include "application.h" -#include "board.h" -#include "config.h" -#include "mcp_server.h" -#include "movements.h" -#include "sdkconfig.h" -#include "settings.h" - -#define TAG "ElectronBotController" - -struct ElectronBotActionParams { - int action_type; - int steps; - int speed; - int direction; - int amount; -}; - -class ElectronBotController { -private: - Otto electron_bot_; - TaskHandle_t action_task_handle_ = nullptr; - QueueHandle_t action_queue_; - bool is_action_in_progress_ = false; - - enum ActionType { - // 手部动作 1-12 - ACTION_HAND_LEFT_UP = 1, // 举左手 - ACTION_HAND_RIGHT_UP = 2, // 举右手 - ACTION_HAND_BOTH_UP = 3, // 举双手 - ACTION_HAND_LEFT_DOWN = 4, // 放左手 - ACTION_HAND_RIGHT_DOWN = 5, // 放右手 - ACTION_HAND_BOTH_DOWN = 6, // 放双手 - ACTION_HAND_LEFT_WAVE = 7, // 挥左手 - ACTION_HAND_RIGHT_WAVE = 8, // 挥右手 - ACTION_HAND_BOTH_WAVE = 9, // 挥双手 - ACTION_HAND_LEFT_FLAP = 10, // 拍打左手 - ACTION_HAND_RIGHT_FLAP = 11, // 拍打右手 - ACTION_HAND_BOTH_FLAP = 12, // 拍打双手 - - // 身体动作 13-14 - ACTION_BODY_TURN_LEFT = 13, // 左转 - ACTION_BODY_TURN_RIGHT = 14, // 右转 - ACTION_BODY_TURN_CENTER = 15, // 回中心 - - // 头部动作 16-20 - ACTION_HEAD_UP = 16, // 抬头 - ACTION_HEAD_DOWN = 17, // 低头 - ACTION_HEAD_NOD_ONCE = 18, // 点头一次 - ACTION_HEAD_CENTER = 19, // 回中心 - ACTION_HEAD_NOD_REPEAT = 20, // 连续点头 - - // 系统动作 21 - ACTION_HOME = 21 // 复位到初始位置 - }; - - static void ActionTask(void* arg) { - ElectronBotController* controller = static_cast(arg); - ElectronBotActionParams params; - controller->electron_bot_.AttachServos(); - - while (true) { - if (xQueueReceive(controller->action_queue_, ¶ms, pdMS_TO_TICKS(1000)) == pdTRUE) { - ESP_LOGI(TAG, "执行动作: %d", params.action_type); - controller->is_action_in_progress_ = true; // 开始执行动作 - - // 执行相应的动作 - if (params.action_type >= ACTION_HAND_LEFT_UP && - params.action_type <= ACTION_HAND_BOTH_FLAP) { - // 手部动作 - controller->electron_bot_.HandAction(params.action_type, params.steps, - params.amount, params.speed); - } else if (params.action_type >= ACTION_BODY_TURN_LEFT && - params.action_type <= ACTION_BODY_TURN_CENTER) { - // 身体动作 - int body_direction = params.action_type - ACTION_BODY_TURN_LEFT + 1; - controller->electron_bot_.BodyAction(body_direction, params.steps, - params.amount, params.speed); - } else if (params.action_type >= ACTION_HEAD_UP && - params.action_type <= ACTION_HEAD_NOD_REPEAT) { - // 头部动作 - int head_action = params.action_type - ACTION_HEAD_UP + 1; - controller->electron_bot_.HeadAction(head_action, params.steps, params.amount, - params.speed); - } else if (params.action_type == ACTION_HOME) { - // 复位动作 - controller->electron_bot_.Home(true); - } - controller->is_action_in_progress_ = false; // 动作执行完毕 - } - vTaskDelay(pdMS_TO_TICKS(20)); - } - } - - void QueueAction(int action_type, int steps, int speed, int direction, int amount) { - ESP_LOGI(TAG, "动作控制: 类型=%d, 步数=%d, 速度=%d, 方向=%d, 幅度=%d", action_type, steps, - speed, direction, amount); - - ElectronBotActionParams params = {action_type, steps, speed, direction, amount}; - xQueueSend(action_queue_, ¶ms, portMAX_DELAY); - StartActionTaskIfNeeded(); - } - - void StartActionTaskIfNeeded() { - if (action_task_handle_ == nullptr) { - xTaskCreate(ActionTask, "electron_bot_action", 1024 * 4, this, configMAX_PRIORITIES - 1, - &action_task_handle_); - } - } - - void LoadTrimsFromNVS() { - Settings settings("electron_trims", false); - - int right_pitch = settings.GetInt("right_pitch", 0); - int right_roll = settings.GetInt("right_roll", 0); - int left_pitch = settings.GetInt("left_pitch", 0); - int left_roll = settings.GetInt("left_roll", 0); - int body = settings.GetInt("body", 0); - int head = settings.GetInt("head", 0); - electron_bot_.SetTrims(right_pitch, right_roll, left_pitch, left_roll, body, head); - } - -public: - ElectronBotController() { - electron_bot_.Init(Right_Pitch_Pin, Right_Roll_Pin, Left_Pitch_Pin, Left_Roll_Pin, Body_Pin, - Head_Pin); - - LoadTrimsFromNVS(); - action_queue_ = xQueueCreate(10, sizeof(ElectronBotActionParams)); - - QueueAction(ACTION_HOME, 1, 1000, 0, 0); - - RegisterMcpTools(); - ESP_LOGI(TAG, "Electron Bot控制器已初始化并注册MCP工具"); - } - - void RegisterMcpTools() { - auto& mcp_server = McpServer::GetInstance(); - - ESP_LOGI(TAG, "开始注册Electron Bot MCP工具..."); - - // 手部动作统一工具 - mcp_server.AddTool( - "self.electron.hand_action", - "手部动作控制。action: 1=举手, 2=放手, 3=挥手, 4=拍打; hand: 1=左手, 2=右手, 3=双手; " - "steps: 动作重复次数(1-10); speed: 动作速度(500-1500,数值越小越快); amount: " - "动作幅度(10-50,仅举手动作使用)", - PropertyList({Property("action", kPropertyTypeInteger, 1, 1, 4), - Property("hand", kPropertyTypeInteger, 3, 1, 3), - Property("steps", kPropertyTypeInteger, 1, 1, 10), - Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("amount", kPropertyTypeInteger, 30, 10, 50)}), - [this](const PropertyList& properties) -> ReturnValue { - int action_type = properties["action"].value(); - int hand_type = properties["hand"].value(); - int steps = properties["steps"].value(); - int speed = properties["speed"].value(); - int amount = properties["amount"].value(); - - // 根据动作类型和手部类型计算具体动作 - int base_action; - switch (action_type) { - case 1: - base_action = ACTION_HAND_LEFT_UP; - break; // 举手 - case 2: - base_action = ACTION_HAND_LEFT_DOWN; - amount = 0; - break; // 放手 - case 3: - base_action = ACTION_HAND_LEFT_WAVE; - amount = 0; - break; // 挥手 - case 4: - base_action = ACTION_HAND_LEFT_FLAP; - amount = 0; - break; // 拍打 - default: - base_action = ACTION_HAND_LEFT_UP; - } - int action_id = base_action + (hand_type - 1); - - QueueAction(action_id, steps, speed, 0, amount); - return true; - }); - - // 身体动作 - mcp_server.AddTool( - "self.electron.body_turn", - "身体转向。steps: 转向步数(1-10); speed: 转向速度(500-1500,数值越小越快); direction: " - "转向方向(1=左转, 2=右转, 3=回中心); angle: 转向角度(0-90度)", - PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 10), - Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("direction", kPropertyTypeInteger, 1, 1, 3), - Property("angle", kPropertyTypeInteger, 45, 0, 90)}), - [this](const PropertyList& properties) -> ReturnValue { - int steps = properties["steps"].value(); - int speed = properties["speed"].value(); - int direction = properties["direction"].value(); - int amount = properties["angle"].value(); - - int action; - switch (direction) { - case 1: - action = ACTION_BODY_TURN_LEFT; - break; - case 2: - action = ACTION_BODY_TURN_RIGHT; - break; - case 3: - action = ACTION_BODY_TURN_CENTER; - break; - default: - action = ACTION_BODY_TURN_LEFT; - } - - QueueAction(action, steps, speed, 0, amount); - return true; - }); - - // 头部动作 - mcp_server.AddTool("self.electron.head_move", - "头部运动。action: 1=抬头, 2=低头, 3=点头, 4=回中心, 5=连续点头; steps: " - "动作重复次数(1-10); speed: 动作速度(500-1500,数值越小越快); angle: " - "头部转动角度(1-15度)", - PropertyList({Property("action", kPropertyTypeInteger, 3, 1, 5), - Property("steps", kPropertyTypeInteger, 1, 1, 10), - Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("angle", kPropertyTypeInteger, 5, 1, 15)}), - [this](const PropertyList& properties) -> ReturnValue { - int action_num = properties["action"].value(); - int steps = properties["steps"].value(); - int speed = properties["speed"].value(); - int amount = properties["angle"].value(); - int action = ACTION_HEAD_UP + (action_num - 1); - QueueAction(action, steps, speed, 0, amount); - return true; - }); - - // 系统工具 - mcp_server.AddTool("self.electron.stop", "立即停止", PropertyList(), - [this](const PropertyList& properties) -> ReturnValue { - // 清空队列但保持任务常驻 - xQueueReset(action_queue_); - is_action_in_progress_ = false; - QueueAction(ACTION_HOME, 1, 1000, 0, 0); - return true; - }); - - mcp_server.AddTool("self.electron.get_status", "获取机器人状态,返回 moving 或 idle", - PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - return is_action_in_progress_ ? "moving" : "idle"; - }); - - // 单个舵机校准工具 - mcp_server.AddTool( - "self.electron.set_trim", - "校准单个舵机位置。设置指定舵机的微调参数以调整ElectronBot的初始姿态,设置将永久保存。" - "servo_type: 舵机类型(right_pitch:右臂旋转, right_roll:右臂推拉, left_pitch:左臂旋转, " - "left_roll:左臂推拉, body:身体, head:头部); " - "trim_value: 微调值(-30到30度)", - PropertyList({Property("servo_type", kPropertyTypeString, "right_pitch"), - Property("trim_value", kPropertyTypeInteger, 0, -30, 30)}), - [this](const PropertyList& properties) -> ReturnValue { - std::string servo_type = properties["servo_type"].value(); - int trim_value = properties["trim_value"].value(); - - ESP_LOGI(TAG, "设置舵机微调: %s = %d度", servo_type.c_str(), trim_value); - - // 获取当前所有微调值 - Settings settings("electron_trims", true); - int right_pitch = settings.GetInt("right_pitch", 0); - int right_roll = settings.GetInt("right_roll", 0); - int left_pitch = settings.GetInt("left_pitch", 0); - int left_roll = settings.GetInt("left_roll", 0); - int body = settings.GetInt("body", 0); - int head = settings.GetInt("head", 0); - - // 更新指定舵机的微调值 - if (servo_type == "right_pitch") { - right_pitch = trim_value; - settings.SetInt("right_pitch", right_pitch); - } else if (servo_type == "right_roll") { - right_roll = trim_value; - settings.SetInt("right_roll", right_roll); - } else if (servo_type == "left_pitch") { - left_pitch = trim_value; - settings.SetInt("left_pitch", left_pitch); - } else if (servo_type == "left_roll") { - left_roll = trim_value; - settings.SetInt("left_roll", left_roll); - } else if (servo_type == "body") { - body = trim_value; - settings.SetInt("body", body); - } else if (servo_type == "head") { - head = trim_value; - settings.SetInt("head", head); - } else { - return "错误:无效的舵机类型,请使用: right_pitch, right_roll, left_pitch, " - "left_roll, body, head"; - } - - electron_bot_.SetTrims(right_pitch, right_roll, left_pitch, left_roll, body, head); - - QueueAction(ACTION_HOME, 1, 500, 0, 0); - - return "舵机 " + servo_type + " 微调设置为 " + std::to_string(trim_value) + - " 度,已永久保存"; - }); - - mcp_server.AddTool("self.electron.get_trims", "获取当前的舵机微调设置", PropertyList(), - [this](const PropertyList& properties) -> ReturnValue { - Settings settings("electron_trims", false); - - int right_pitch = settings.GetInt("right_pitch", 0); - int right_roll = settings.GetInt("right_roll", 0); - int left_pitch = settings.GetInt("left_pitch", 0); - int left_roll = settings.GetInt("left_roll", 0); - int body = settings.GetInt("body", 0); - int head = settings.GetInt("head", 0); - - std::string result = - "{\"right_pitch\":" + std::to_string(right_pitch) + - ",\"right_roll\":" + std::to_string(right_roll) + - ",\"left_pitch\":" + std::to_string(left_pitch) + - ",\"left_roll\":" + std::to_string(left_roll) + - ",\"body\":" + std::to_string(body) + - ",\"head\":" + std::to_string(head) + "}"; - - ESP_LOGI(TAG, "获取微调设置: %s", result.c_str()); - return result; - }); - - mcp_server.AddTool("self.battery.get_level", "获取机器人电池电量和充电状态", PropertyList(), - [](const PropertyList& properties) -> ReturnValue { - auto& board = Board::GetInstance(); - int level = 0; - bool charging = false; - bool discharging = false; - board.GetBatteryLevel(level, charging, discharging); - - std::string status = - "{\"level\":" + std::to_string(level) + - ",\"charging\":" + (charging ? "true" : "false") + "}"; - return status; - }); - - ESP_LOGI(TAG, "Electron Bot MCP工具注册完成"); - } - - ~ElectronBotController() { - if (action_task_handle_ != nullptr) { - vTaskDelete(action_task_handle_); - action_task_handle_ = nullptr; - } - vQueueDelete(action_queue_); - } -}; - -static ElectronBotController* g_electron_controller = nullptr; - -void InitializeElectronBotController() { - if (g_electron_controller == nullptr) { - g_electron_controller = new ElectronBotController(); - ESP_LOGI(TAG, "Electron Bot控制器已初始化并注册MCP工具"); - } -} +/* + Electron Bot机器人控制器 - MCP协议版本 +*/ + +#include +#include + +#include + +#include "application.h" +#include "board.h" +#include "config.h" +#include "mcp_server.h" +#include "movements.h" +#include "sdkconfig.h" +#include "settings.h" + +#define TAG "ElectronBotController" + +struct ElectronBotActionParams { + int action_type; + int steps; + int speed; + int direction; + int amount; +}; + +class ElectronBotController { +private: + Otto electron_bot_; + TaskHandle_t action_task_handle_ = nullptr; + QueueHandle_t action_queue_; + bool is_action_in_progress_ = false; + + enum ActionType { + // 手部动作 1-12 + ACTION_HAND_LEFT_UP = 1, // 举左手 + ACTION_HAND_RIGHT_UP = 2, // 举右手 + ACTION_HAND_BOTH_UP = 3, // 举双手 + ACTION_HAND_LEFT_DOWN = 4, // 放左手 + ACTION_HAND_RIGHT_DOWN = 5, // 放右手 + ACTION_HAND_BOTH_DOWN = 6, // 放双手 + ACTION_HAND_LEFT_WAVE = 7, // 挥左手 + ACTION_HAND_RIGHT_WAVE = 8, // 挥右手 + ACTION_HAND_BOTH_WAVE = 9, // 挥双手 + ACTION_HAND_LEFT_FLAP = 10, // 拍打左手 + ACTION_HAND_RIGHT_FLAP = 11, // 拍打右手 + ACTION_HAND_BOTH_FLAP = 12, // 拍打双手 + + // 身体动作 13-14 + ACTION_BODY_TURN_LEFT = 13, // 左转 + ACTION_BODY_TURN_RIGHT = 14, // 右转 + ACTION_BODY_TURN_CENTER = 15, // 回中心 + + // 头部动作 16-20 + ACTION_HEAD_UP = 16, // 抬头 + ACTION_HEAD_DOWN = 17, // 低头 + ACTION_HEAD_NOD_ONCE = 18, // 点头一次 + ACTION_HEAD_CENTER = 19, // 回中心 + ACTION_HEAD_NOD_REPEAT = 20, // 连续点头 + + // 系统动作 21 + ACTION_HOME = 21 // 复位到初始位置 + }; + + static void ActionTask(void* arg) { + ElectronBotController* controller = static_cast(arg); + ElectronBotActionParams params; + controller->electron_bot_.AttachServos(); + + while (true) { + if (xQueueReceive(controller->action_queue_, ¶ms, pdMS_TO_TICKS(1000)) == pdTRUE) { + ESP_LOGI(TAG, "执行动作: %d", params.action_type); + controller->is_action_in_progress_ = true; // 开始执行动作 + + // 执行相应的动作 + if (params.action_type >= ACTION_HAND_LEFT_UP && + params.action_type <= ACTION_HAND_BOTH_FLAP) { + // 手部动作 + controller->electron_bot_.HandAction(params.action_type, params.steps, + params.amount, params.speed); + } else if (params.action_type >= ACTION_BODY_TURN_LEFT && + params.action_type <= ACTION_BODY_TURN_CENTER) { + // 身体动作 + int body_direction = params.action_type - ACTION_BODY_TURN_LEFT + 1; + controller->electron_bot_.BodyAction(body_direction, params.steps, + params.amount, params.speed); + } else if (params.action_type >= ACTION_HEAD_UP && + params.action_type <= ACTION_HEAD_NOD_REPEAT) { + // 头部动作 + int head_action = params.action_type - ACTION_HEAD_UP + 1; + controller->electron_bot_.HeadAction(head_action, params.steps, params.amount, + params.speed); + } else if (params.action_type == ACTION_HOME) { + // 复位动作 + controller->electron_bot_.Home(true); + } + controller->is_action_in_progress_ = false; // 动作执行完毕 + } + vTaskDelay(pdMS_TO_TICKS(20)); + } + } + + void QueueAction(int action_type, int steps, int speed, int direction, int amount) { + ESP_LOGI(TAG, "动作控制: 类型=%d, 步数=%d, 速度=%d, 方向=%d, 幅度=%d", action_type, steps, + speed, direction, amount); + + ElectronBotActionParams params = {action_type, steps, speed, direction, amount}; + xQueueSend(action_queue_, ¶ms, portMAX_DELAY); + StartActionTaskIfNeeded(); + } + + void StartActionTaskIfNeeded() { + if (action_task_handle_ == nullptr) { + xTaskCreate(ActionTask, "electron_bot_action", 1024 * 4, this, configMAX_PRIORITIES - 1, + &action_task_handle_); + } + } + + void LoadTrimsFromNVS() { + Settings settings("electron_trims", false); + + int right_pitch = settings.GetInt("right_pitch", 0); + int right_roll = settings.GetInt("right_roll", 0); + int left_pitch = settings.GetInt("left_pitch", 0); + int left_roll = settings.GetInt("left_roll", 0); + int body = settings.GetInt("body", 0); + int head = settings.GetInt("head", 0); + electron_bot_.SetTrims(right_pitch, right_roll, left_pitch, left_roll, body, head); + } + +public: + ElectronBotController() { + electron_bot_.Init(Right_Pitch_Pin, Right_Roll_Pin, Left_Pitch_Pin, Left_Roll_Pin, Body_Pin, + Head_Pin); + + LoadTrimsFromNVS(); + action_queue_ = xQueueCreate(10, sizeof(ElectronBotActionParams)); + + QueueAction(ACTION_HOME, 1, 1000, 0, 0); + + RegisterMcpTools(); + ESP_LOGI(TAG, "Electron Bot控制器已初始化并注册MCP工具"); + } + + void RegisterMcpTools() { + auto& mcp_server = McpServer::GetInstance(); + + ESP_LOGI(TAG, "开始注册Electron Bot MCP工具..."); + + // 手部动作统一工具 + mcp_server.AddTool( + "self.electron.hand_action", + "手部动作控制。action: 1=举手, 2=放手, 3=挥手, 4=拍打; hand: 1=左手, 2=右手, 3=双手; " + "steps: 动作重复次数(1-10); speed: 动作速度(500-1500,数值越小越快); amount: " + "动作幅度(10-50,仅举手动作使用)", + PropertyList({Property("action", kPropertyTypeInteger, 1, 1, 4), + Property("hand", kPropertyTypeInteger, 3, 1, 3), + Property("steps", kPropertyTypeInteger, 1, 1, 10), + Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("amount", kPropertyTypeInteger, 30, 10, 50)}), + [this](const PropertyList& properties) -> ReturnValue { + int action_type = properties["action"].value(); + int hand_type = properties["hand"].value(); + int steps = properties["steps"].value(); + int speed = properties["speed"].value(); + int amount = properties["amount"].value(); + + // 根据动作类型和手部类型计算具体动作 + int base_action; + switch (action_type) { + case 1: + base_action = ACTION_HAND_LEFT_UP; + break; // 举手 + case 2: + base_action = ACTION_HAND_LEFT_DOWN; + amount = 0; + break; // 放手 + case 3: + base_action = ACTION_HAND_LEFT_WAVE; + amount = 0; + break; // 挥手 + case 4: + base_action = ACTION_HAND_LEFT_FLAP; + amount = 0; + break; // 拍打 + default: + base_action = ACTION_HAND_LEFT_UP; + } + int action_id = base_action + (hand_type - 1); + + QueueAction(action_id, steps, speed, 0, amount); + return true; + }); + + // 身体动作 + mcp_server.AddTool( + "self.electron.body_turn", + "身体转向。steps: 转向步数(1-10); speed: 转向速度(500-1500,数值越小越快); direction: " + "转向方向(1=左转, 2=右转, 3=回中心); angle: 转向角度(0-90度)", + PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 10), + Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("direction", kPropertyTypeInteger, 1, 1, 3), + Property("angle", kPropertyTypeInteger, 45, 0, 90)}), + [this](const PropertyList& properties) -> ReturnValue { + int steps = properties["steps"].value(); + int speed = properties["speed"].value(); + int direction = properties["direction"].value(); + int amount = properties["angle"].value(); + + int action; + switch (direction) { + case 1: + action = ACTION_BODY_TURN_LEFT; + break; + case 2: + action = ACTION_BODY_TURN_RIGHT; + break; + case 3: + action = ACTION_BODY_TURN_CENTER; + break; + default: + action = ACTION_BODY_TURN_LEFT; + } + + QueueAction(action, steps, speed, 0, amount); + return true; + }); + + // 头部动作 + mcp_server.AddTool("self.electron.head_move", + "头部运动。action: 1=抬头, 2=低头, 3=点头, 4=回中心, 5=连续点头; steps: " + "动作重复次数(1-10); speed: 动作速度(500-1500,数值越小越快); angle: " + "头部转动角度(1-15度)", + PropertyList({Property("action", kPropertyTypeInteger, 3, 1, 5), + Property("steps", kPropertyTypeInteger, 1, 1, 10), + Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("angle", kPropertyTypeInteger, 5, 1, 15)}), + [this](const PropertyList& properties) -> ReturnValue { + int action_num = properties["action"].value(); + int steps = properties["steps"].value(); + int speed = properties["speed"].value(); + int amount = properties["angle"].value(); + int action = ACTION_HEAD_UP + (action_num - 1); + QueueAction(action, steps, speed, 0, amount); + return true; + }); + + // 系统工具 + mcp_server.AddTool("self.electron.stop", "立即停止", PropertyList(), + [this](const PropertyList& properties) -> ReturnValue { + // 清空队列但保持任务常驻 + xQueueReset(action_queue_); + is_action_in_progress_ = false; + QueueAction(ACTION_HOME, 1, 1000, 0, 0); + return true; + }); + + mcp_server.AddTool("self.electron.get_status", "获取机器人状态,返回 moving 或 idle", + PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + return is_action_in_progress_ ? "moving" : "idle"; + }); + + // 单个舵机校准工具 + mcp_server.AddTool( + "self.electron.set_trim", + "校准单个舵机位置。设置指定舵机的微调参数以调整ElectronBot的初始姿态,设置将永久保存。" + "servo_type: 舵机类型(right_pitch:右臂旋转, right_roll:右臂推拉, left_pitch:左臂旋转, " + "left_roll:左臂推拉, body:身体, head:头部); " + "trim_value: 微调值(-30到30度)", + PropertyList({Property("servo_type", kPropertyTypeString, "right_pitch"), + Property("trim_value", kPropertyTypeInteger, 0, -30, 30)}), + [this](const PropertyList& properties) -> ReturnValue { + std::string servo_type = properties["servo_type"].value(); + int trim_value = properties["trim_value"].value(); + + ESP_LOGI(TAG, "设置舵机微调: %s = %d度", servo_type.c_str(), trim_value); + + // 获取当前所有微调值 + Settings settings("electron_trims", true); + int right_pitch = settings.GetInt("right_pitch", 0); + int right_roll = settings.GetInt("right_roll", 0); + int left_pitch = settings.GetInt("left_pitch", 0); + int left_roll = settings.GetInt("left_roll", 0); + int body = settings.GetInt("body", 0); + int head = settings.GetInt("head", 0); + + // 更新指定舵机的微调值 + if (servo_type == "right_pitch") { + right_pitch = trim_value; + settings.SetInt("right_pitch", right_pitch); + } else if (servo_type == "right_roll") { + right_roll = trim_value; + settings.SetInt("right_roll", right_roll); + } else if (servo_type == "left_pitch") { + left_pitch = trim_value; + settings.SetInt("left_pitch", left_pitch); + } else if (servo_type == "left_roll") { + left_roll = trim_value; + settings.SetInt("left_roll", left_roll); + } else if (servo_type == "body") { + body = trim_value; + settings.SetInt("body", body); + } else if (servo_type == "head") { + head = trim_value; + settings.SetInt("head", head); + } else { + return "错误:无效的舵机类型,请使用: right_pitch, right_roll, left_pitch, " + "left_roll, body, head"; + } + + electron_bot_.SetTrims(right_pitch, right_roll, left_pitch, left_roll, body, head); + + QueueAction(ACTION_HOME, 1, 500, 0, 0); + + return "舵机 " + servo_type + " 微调设置为 " + std::to_string(trim_value) + + " 度,已永久保存"; + }); + + mcp_server.AddTool("self.electron.get_trims", "获取当前的舵机微调设置", PropertyList(), + [this](const PropertyList& properties) -> ReturnValue { + Settings settings("electron_trims", false); + + int right_pitch = settings.GetInt("right_pitch", 0); + int right_roll = settings.GetInt("right_roll", 0); + int left_pitch = settings.GetInt("left_pitch", 0); + int left_roll = settings.GetInt("left_roll", 0); + int body = settings.GetInt("body", 0); + int head = settings.GetInt("head", 0); + + std::string result = + "{\"right_pitch\":" + std::to_string(right_pitch) + + ",\"right_roll\":" + std::to_string(right_roll) + + ",\"left_pitch\":" + std::to_string(left_pitch) + + ",\"left_roll\":" + std::to_string(left_roll) + + ",\"body\":" + std::to_string(body) + + ",\"head\":" + std::to_string(head) + "}"; + + ESP_LOGI(TAG, "获取微调设置: %s", result.c_str()); + return result; + }); + + mcp_server.AddTool("self.battery.get_level", "获取机器人电池电量和充电状态", PropertyList(), + [](const PropertyList& properties) -> ReturnValue { + auto& board = Board::GetInstance(); + int level = 0; + bool charging = false; + bool discharging = false; + board.GetBatteryLevel(level, charging, discharging); + + std::string status = + "{\"level\":" + std::to_string(level) + + ",\"charging\":" + (charging ? "true" : "false") + "}"; + return status; + }); + + ESP_LOGI(TAG, "Electron Bot MCP工具注册完成"); + } + + ~ElectronBotController() { + if (action_task_handle_ != nullptr) { + vTaskDelete(action_task_handle_); + action_task_handle_ = nullptr; + } + vQueueDelete(action_queue_); + } +}; + +static ElectronBotController* g_electron_controller = nullptr; + +void InitializeElectronBotController() { + if (g_electron_controller == nullptr) { + g_electron_controller = new ElectronBotController(); + ESP_LOGI(TAG, "Electron Bot控制器已初始化并注册MCP工具"); + } +} diff --git a/main/boards/electron-bot/electron_emoji_display.cc b/main/boards/electron-bot/electron_emoji_display.cc index 01b16f6..a2e84f9 100644 --- a/main/boards/electron-bot/electron_emoji_display.cc +++ b/main/boards/electron-bot/electron_emoji_display.cc @@ -1,150 +1,150 @@ -#include "electron_emoji_display.h" -#include "lvgl_theme.h" - -#include -#include - -#include -#include -#include - -#define TAG "ElectronEmojiDisplay" - -// 表情映射表 - 将多种表情映射到现有6个GIF -const ElectronEmojiDisplay::EmotionMap ElectronEmojiDisplay::emotion_maps_[] = { - // 中性/平静类表情 -> staticstate - {"neutral", &staticstate}, - {"relaxed", &staticstate}, - {"sleepy", &staticstate}, - - // 积极/开心类表情 -> happy - {"happy", &happy}, - {"laughing", &happy}, - {"funny", &happy}, - {"loving", &happy}, - {"confident", &happy}, - {"winking", &happy}, - {"cool", &happy}, - {"delicious", &happy}, - {"kissy", &happy}, - {"silly", &happy}, - - // 悲伤类表情 -> sad - {"sad", &sad}, - {"crying", &sad}, - - // 愤怒类表情 -> anger - {"angry", &anger}, - - // 惊讶类表情 -> scare - {"surprised", &scare}, - {"shocked", &scare}, - - // 思考/困惑类表情 -> buxue - {"thinking", &buxue}, - {"confused", &buxue}, - {"embarrassed", &buxue}, - - {nullptr, nullptr} // 结束标记 -}; - -ElectronEmojiDisplay::ElectronEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, - esp_lcd_panel_handle_t panel, int width, int height, - int offset_x, int offset_y, bool mirror_x, bool mirror_y, - bool swap_xy) - : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy), - emotion_gif_(nullptr) { - SetupGifContainer(); -} - -void ElectronEmojiDisplay::SetupGifContainer() { - DisplayLockGuard lock(this); - - if (emoji_label_) { - lv_obj_del(emoji_label_); - } - if (chat_message_label_) { - lv_obj_del(chat_message_label_); - } - if (content_) { - lv_obj_del(content_); - } - - content_ = lv_obj_create(container_); - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_size(content_, LV_HOR_RES, LV_HOR_RES); - lv_obj_set_style_bg_opa(content_, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_flex_grow(content_, 1); - lv_obj_center(content_); - - emoji_label_ = lv_label_create(content_); - lv_label_set_text(emoji_label_, ""); - lv_obj_set_width(emoji_label_, 0); - lv_obj_set_style_border_width(emoji_label_, 0, 0); - lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); - - emotion_gif_ = lv_gif_create(content_); - int gif_size = LV_HOR_RES; - lv_obj_set_size(emotion_gif_, gif_size, gif_size); - lv_obj_set_style_border_width(emotion_gif_, 0, 0); - lv_obj_set_style_bg_opa(emotion_gif_, LV_OPA_TRANSP, 0); - lv_obj_center(emotion_gif_); - lv_gif_set_src(emotion_gif_, &staticstate); - - chat_message_label_ = lv_label_create(content_); - lv_label_set_text(chat_message_label_, ""); - lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); - lv_obj_set_style_border_width(chat_message_label_, 0, 0); - - lv_obj_set_style_bg_opa(chat_message_label_, LV_OPA_70, 0); - lv_obj_set_style_bg_color(chat_message_label_, lv_color_black(), 0); - lv_obj_set_style_pad_ver(chat_message_label_, 5, 0); - - lv_obj_align(chat_message_label_, LV_ALIGN_BOTTOM_MID, 0, 0); - - auto& theme_manager = LvglThemeManager::GetInstance(); - auto theme = theme_manager.GetTheme("dark"); - if (theme != nullptr) { - LcdDisplay::SetTheme(theme); - } -} - -void ElectronEmojiDisplay::SetEmotion(const char* emotion) { - if (!emotion || !emotion_gif_) { - return; - } - - DisplayLockGuard lock(this); - - for (const auto& map : emotion_maps_) { - if (map.name && strcmp(map.name, emotion) == 0) { - lv_gif_set_src(emotion_gif_, map.gif); - ESP_LOGI(TAG, "设置表情: %s", emotion); - return; - } - } - - lv_gif_set_src(emotion_gif_, &staticstate); - ESP_LOGI(TAG, "未知表情'%s',使用默认", emotion); -} - -void ElectronEmojiDisplay::SetChatMessage(const char* role, const char* content) { - DisplayLockGuard lock(this); - if (chat_message_label_ == nullptr) { - return; - } - - if (content == nullptr || strlen(content) == 0) { - lv_obj_add_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); - return; - } - - lv_label_set_text(chat_message_label_, content); - lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); - - ESP_LOGI(TAG, "设置聊天消息 [%s]: %s", role, content); +#include "electron_emoji_display.h" +#include "lvgl_theme.h" + +#include +#include + +#include +#include +#include + +#define TAG "ElectronEmojiDisplay" + +// 表情映射表 - 将多种表情映射到现有6个GIF +const ElectronEmojiDisplay::EmotionMap ElectronEmojiDisplay::emotion_maps_[] = { + // 中性/平静类表情 -> staticstate + {"neutral", &staticstate}, + {"relaxed", &staticstate}, + {"sleepy", &staticstate}, + + // 积极/开心类表情 -> happy + {"happy", &happy}, + {"laughing", &happy}, + {"funny", &happy}, + {"loving", &happy}, + {"confident", &happy}, + {"winking", &happy}, + {"cool", &happy}, + {"delicious", &happy}, + {"kissy", &happy}, + {"silly", &happy}, + + // 悲伤类表情 -> sad + {"sad", &sad}, + {"crying", &sad}, + + // 愤怒类表情 -> anger + {"angry", &anger}, + + // 惊讶类表情 -> scare + {"surprised", &scare}, + {"shocked", &scare}, + + // 思考/困惑类表情 -> buxue + {"thinking", &buxue}, + {"confused", &buxue}, + {"embarrassed", &buxue}, + + {nullptr, nullptr} // 结束标记 +}; + +ElectronEmojiDisplay::ElectronEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, + esp_lcd_panel_handle_t panel, int width, int height, + int offset_x, int offset_y, bool mirror_x, bool mirror_y, + bool swap_xy) + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy), + emotion_gif_(nullptr) { + SetupGifContainer(); +} + +void ElectronEmojiDisplay::SetupGifContainer() { + DisplayLockGuard lock(this); + + if (emoji_label_) { + lv_obj_del(emoji_label_); + } + if (chat_message_label_) { + lv_obj_del(chat_message_label_); + } + if (content_) { + lv_obj_del(content_); + } + + content_ = lv_obj_create(container_); + lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_size(content_, LV_HOR_RES, LV_HOR_RES); + lv_obj_set_style_bg_opa(content_, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(content_, 0, 0); + lv_obj_set_flex_grow(content_, 1); + lv_obj_center(content_); + + emoji_label_ = lv_label_create(content_); + lv_label_set_text(emoji_label_, ""); + lv_obj_set_width(emoji_label_, 0); + lv_obj_set_style_border_width(emoji_label_, 0, 0); + lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); + + emotion_gif_ = lv_gif_create(content_); + int gif_size = LV_HOR_RES; + lv_obj_set_size(emotion_gif_, gif_size, gif_size); + lv_obj_set_style_border_width(emotion_gif_, 0, 0); + lv_obj_set_style_bg_opa(emotion_gif_, LV_OPA_TRANSP, 0); + lv_obj_center(emotion_gif_); + lv_gif_set_src(emotion_gif_, &staticstate); + + chat_message_label_ = lv_label_create(content_); + lv_label_set_text(chat_message_label_, ""); + lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); + lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); + lv_obj_set_style_border_width(chat_message_label_, 0, 0); + + lv_obj_set_style_bg_opa(chat_message_label_, LV_OPA_70, 0); + lv_obj_set_style_bg_color(chat_message_label_, lv_color_black(), 0); + lv_obj_set_style_pad_ver(chat_message_label_, 5, 0); + + lv_obj_align(chat_message_label_, LV_ALIGN_BOTTOM_MID, 0, 0); + + auto& theme_manager = LvglThemeManager::GetInstance(); + auto theme = theme_manager.GetTheme("dark"); + if (theme != nullptr) { + LcdDisplay::SetTheme(theme); + } +} + +void ElectronEmojiDisplay::SetEmotion(const char* emotion) { + if (!emotion || !emotion_gif_) { + return; + } + + DisplayLockGuard lock(this); + + for (const auto& map : emotion_maps_) { + if (map.name && strcmp(map.name, emotion) == 0) { + lv_gif_set_src(emotion_gif_, map.gif); + ESP_LOGI(TAG, "设置表情: %s", emotion); + return; + } + } + + lv_gif_set_src(emotion_gif_, &staticstate); + ESP_LOGI(TAG, "未知表情'%s',使用默认", emotion); +} + +void ElectronEmojiDisplay::SetChatMessage(const char* role, const char* content) { + DisplayLockGuard lock(this); + if (chat_message_label_ == nullptr) { + return; + } + + if (content == nullptr || strlen(content) == 0) { + lv_obj_add_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); + return; + } + + lv_label_set_text(chat_message_label_, content); + lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); + + ESP_LOGI(TAG, "设置聊天消息 [%s]: %s", role, content); } \ No newline at end of file diff --git a/main/boards/electron-bot/electron_emoji_display.h b/main/boards/electron-bot/electron_emoji_display.h index cdbb758..9ca90e6 100644 --- a/main/boards/electron-bot/electron_emoji_display.h +++ b/main/boards/electron-bot/electron_emoji_display.h @@ -1,48 +1,48 @@ -#pragma once - -#include - -#include "display/lcd_display.h" - -// Electron Bot表情GIF声明 - 使用与Otto相同的6个表情 -LV_IMAGE_DECLARE(staticstate); // 静态状态/中性表情 -LV_IMAGE_DECLARE(sad); // 悲伤 -LV_IMAGE_DECLARE(happy); // 开心 -LV_IMAGE_DECLARE(scare); // 惊吓/惊讶 -LV_IMAGE_DECLARE(buxue); // 不学/困惑 -LV_IMAGE_DECLARE(anger); // 愤怒 - -/** - * @brief Electron Bot GIF表情显示类 - * 继承LcdDisplay,添加GIF表情支持 - */ -class ElectronEmojiDisplay : public SpiLcdDisplay { -public: - /** - * @brief 构造函数,参数与SpiLcdDisplay相同 - */ - ElectronEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, - bool mirror_y, bool swap_xy); - - virtual ~ElectronEmojiDisplay() = default; - - // 重写表情设置方法 - virtual void SetEmotion(const char* emotion) override; - - // 重写聊天消息设置方法 - virtual void SetChatMessage(const char* role, const char* content) override; - -private: - void SetupGifContainer(); - - lv_obj_t* emotion_gif_; ///< GIF表情组件 - - // 表情映射 - struct EmotionMap { - const char* name; - const lv_image_dsc_t* gif; - }; - - static const EmotionMap emotion_maps_[]; +#pragma once + +#include + +#include "display/lcd_display.h" + +// Electron Bot表情GIF声明 - 使用与Otto相同的6个表情 +LV_IMAGE_DECLARE(staticstate); // 静态状态/中性表情 +LV_IMAGE_DECLARE(sad); // 悲伤 +LV_IMAGE_DECLARE(happy); // 开心 +LV_IMAGE_DECLARE(scare); // 惊吓/惊讶 +LV_IMAGE_DECLARE(buxue); // 不学/困惑 +LV_IMAGE_DECLARE(anger); // 愤怒 + +/** + * @brief Electron Bot GIF表情显示类 + * 继承LcdDisplay,添加GIF表情支持 + */ +class ElectronEmojiDisplay : public SpiLcdDisplay { +public: + /** + * @brief 构造函数,参数与SpiLcdDisplay相同 + */ + ElectronEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, bool mirror_x, + bool mirror_y, bool swap_xy); + + virtual ~ElectronEmojiDisplay() = default; + + // 重写表情设置方法 + virtual void SetEmotion(const char* emotion) override; + + // 重写聊天消息设置方法 + virtual void SetChatMessage(const char* role, const char* content) override; + +private: + void SetupGifContainer(); + + lv_obj_t* emotion_gif_; ///< GIF表情组件 + + // 表情映射 + struct EmotionMap { + const char* name; + const lv_image_dsc_t* gif; + }; + + static const EmotionMap emotion_maps_[]; }; \ No newline at end of file diff --git a/main/boards/electron-bot/movements.cc b/main/boards/electron-bot/movements.cc index 090732f..3d8ef14 100644 --- a/main/boards/electron-bot/movements.cc +++ b/main/boards/electron-bot/movements.cc @@ -1,470 +1,470 @@ -#include "movements.h" - -#include -#include - -#include "oscillator.h" - -Otto::Otto() { - is_otto_resting_ = false; - for (int i = 0; i < SERVO_COUNT; i++) { - servo_pins_[i] = -1; - servo_trim_[i] = 0; - } -} - -Otto::~Otto() { - DetachServos(); -} - -unsigned long IRAM_ATTR millis() { - return (unsigned long)(esp_timer_get_time() / 1000ULL); -} - -void Otto::Init(int right_pitch, int right_roll, int left_pitch, int left_roll, int body, - int head) { - servo_pins_[RIGHT_PITCH] = right_pitch; - servo_pins_[RIGHT_ROLL] = right_roll; - servo_pins_[LEFT_PITCH] = left_pitch; - servo_pins_[LEFT_ROLL] = left_roll; - servo_pins_[BODY] = body; - servo_pins_[HEAD] = head; - - AttachServos(); - is_otto_resting_ = false; -} - -/////////////////////////////////////////////////////////////////// -//-- ATTACH & DETACH FUNCTIONS ----------------------------------// -/////////////////////////////////////////////////////////////////// -void Otto::AttachServos() { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].Attach(servo_pins_[i]); - } - } -} - -void Otto::DetachServos() { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].Detach(); - } - } -} - -/////////////////////////////////////////////////////////////////// -//-- OSCILLATORS TRIMS ------------------------------------------// -/////////////////////////////////////////////////////////////////// -void Otto::SetTrims(int right_pitch, int right_roll, int left_pitch, int left_roll, int body, - int head) { - servo_trim_[RIGHT_PITCH] = right_pitch; - servo_trim_[RIGHT_ROLL] = right_roll; - servo_trim_[LEFT_PITCH] = left_pitch; - servo_trim_[LEFT_ROLL] = left_roll; - servo_trim_[BODY] = body; - servo_trim_[HEAD] = head; - - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].SetTrim(servo_trim_[i]); - } - } -} - -/////////////////////////////////////////////////////////////////// -//-- BASIC MOTION FUNCTIONS -------------------------------------// -/////////////////////////////////////////////////////////////////// -void Otto::MoveServos(int time, int servo_target[]) { - if (GetRestState() == true) { - SetRestState(false); - } - - final_time_ = millis() + time; - if (time > 10) { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - increment_[i] = (servo_target[i] - servo_[i].GetPosition()) / (time / 10.0); - } - } - - for (int iteration = 1; millis() < final_time_; iteration++) { - partial_time_ = millis() + 10; - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].SetPosition(servo_[i].GetPosition() + increment_[i]); - } - } - vTaskDelay(pdMS_TO_TICKS(10)); - } - } else { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].SetPosition(servo_target[i]); - } - } - vTaskDelay(pdMS_TO_TICKS(time)); - } - - // final adjustment to the target. - bool f = true; - int adjustment_count = 0; - while (f && adjustment_count < 10) { - f = false; - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1 && servo_target[i] != servo_[i].GetPosition()) { - f = true; - break; - } - } - if (f) { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].SetPosition(servo_target[i]); - } - } - vTaskDelay(pdMS_TO_TICKS(10)); - adjustment_count++; - } - }; -} - -void Otto::MoveSingle(int position, int servo_number) { - if (position > 180) - position = 90; - if (position < 0) - position = 90; - - if (GetRestState() == true) { - SetRestState(false); - } - - if (servo_number >= 0 && servo_number < SERVO_COUNT && servo_pins_[servo_number] != -1) { - servo_[servo_number].SetPosition(position); - } -} - -void Otto::OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, - double phase_diff[SERVO_COUNT], float cycle = 1) { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].SetO(offset[i]); - servo_[i].SetA(amplitude[i]); - servo_[i].SetT(period); - servo_[i].SetPh(phase_diff[i]); - } - } - - double ref = millis(); - double end_time = period * cycle + ref; - - while (millis() < end_time) { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].Refresh(); - } - } - vTaskDelay(5); - } - vTaskDelay(pdMS_TO_TICKS(10)); -} - -void Otto::Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, - double phase_diff[SERVO_COUNT], float steps = 1.0) { - if (GetRestState() == true) { - SetRestState(false); - } - - int cycles = (int)steps; - - //-- Execute complete cycles - if (cycles >= 1) - for (int i = 0; i < cycles; i++) - OscillateServos(amplitude, offset, period, phase_diff); - - //-- Execute the final not complete cycle - OscillateServos(amplitude, offset, period, phase_diff, (float)steps - cycles); - vTaskDelay(pdMS_TO_TICKS(10)); -} - -/////////////////////////////////////////////////////////////////// -//-- HOME = Otto at rest position -------------------------------// -/////////////////////////////////////////////////////////////////// -void Otto::Home(bool hands_down) { - if (is_otto_resting_ == false) { // Go to rest position only if necessary - MoveServos(1000, servo_initial_); - is_otto_resting_ = true; - } - - vTaskDelay(pdMS_TO_TICKS(1000)); -} - -bool Otto::GetRestState() { - return is_otto_resting_; -} - -void Otto::SetRestState(bool state) { - is_otto_resting_ = state; -} - -/////////////////////////////////////////////////////////////////// -//-- PREDETERMINED MOTION SEQUENCES -----------------------------// -/////////////////////////////////////////////////////////////////// - -//--------------------------------------------------------- -//-- 统一手部动作函数 -//-- Parameters: -//-- action: 动作类型 1=举左手, 2=举右手, 3=举双手, 4=放左手, 5=放右手, 6=放双手, -//-- 7=挥左手, 8=挥右手, 9=挥双手, 10=拍打左手, 11=拍打右手, 12=拍打双手 -//-- times: 重复次数 -//-- amount: 动作幅度 (10-50) -//-- period: 动作时间 -//--------------------------------------------------------- -void Otto::HandAction(int action, int times, int amount, int period) { - // 限制参数范围 - times = 2 * std::max(3, std::min(100, times)); - amount = std::max(10, std::min(50, amount)); - period = std::max(100, std::min(1000, period)); - - int current_positions[SERVO_COUNT]; - for (int i = 0; i < SERVO_COUNT; i++) { - current_positions[i] = (servo_pins_[i] != -1) ? servo_[i].GetPosition() : servo_initial_[i]; - } - - switch (action) { - case 1: // 举左手 - current_positions[LEFT_PITCH] = 180; - MoveServos(period, current_positions); - break; - - case 2: // 举右手 - current_positions[RIGHT_PITCH] = 0; - MoveServos(period, current_positions); - break; - - case 3: // 举双手 - current_positions[LEFT_PITCH] = 180; - current_positions[RIGHT_PITCH] = 0; - MoveServos(period, current_positions); - break; - - case 4: // 放左手 - case 5: // 放右手 - case 6: // 放双手 - // 回到初始位置 - memcpy(current_positions, servo_initial_, sizeof(current_positions)); - MoveServos(period, current_positions); - break; - - case 7: // 挥左手 - current_positions[LEFT_PITCH] = 150; - MoveServos(period, current_positions); - for (int i = 0; i < times; i++) { - current_positions[LEFT_PITCH] = 150 + (i % 2 == 0 ? -30 : 30); - MoveServos(period / 10, current_positions); - vTaskDelay(pdMS_TO_TICKS(period / 10)); - } - memcpy(current_positions, servo_initial_, sizeof(current_positions)); - MoveServos(period, current_positions); - break; - - case 8: // 挥右手 - current_positions[RIGHT_PITCH] = 30; - MoveServos(period, current_positions); - for (int i = 0; i < times; i++) { - current_positions[RIGHT_PITCH] = 30 + (i % 2 == 0 ? 30 : -30); - MoveServos(period / 10, current_positions); - vTaskDelay(pdMS_TO_TICKS(period / 10)); - } - memcpy(current_positions, servo_initial_, sizeof(current_positions)); - MoveServos(period, current_positions); - break; - - case 9: // 挥双手 - current_positions[LEFT_PITCH] = 150; - current_positions[RIGHT_PITCH] = 30; - MoveServos(period, current_positions); - for (int i = 0; i < times; i++) { - current_positions[LEFT_PITCH] = 150 + (i % 2 == 0 ? -30 : 30); - current_positions[RIGHT_PITCH] = 30 + (i % 2 == 0 ? 30 : -30); - MoveServos(period / 10, current_positions); - vTaskDelay(pdMS_TO_TICKS(period / 10)); - } - memcpy(current_positions, servo_initial_, sizeof(current_positions)); - MoveServos(period, current_positions); - break; - - case 10: // 拍打左手 - current_positions[LEFT_ROLL] = 20; - MoveServos(period, current_positions); - for (int i = 0; i < times; i++) { - current_positions[LEFT_ROLL] = 20 - amount; - MoveServos(period / 10, current_positions); - current_positions[LEFT_ROLL] = 20 + amount; - MoveServos(period / 10, current_positions); - } - current_positions[LEFT_ROLL] = 0; - MoveServos(period, current_positions); - break; - - case 11: // 拍打右手 - current_positions[RIGHT_ROLL] = 160; - MoveServos(period, current_positions); - for (int i = 0; i < times; i++) { - current_positions[RIGHT_ROLL] = 160 + amount; - MoveServos(period / 10, current_positions); - current_positions[RIGHT_ROLL] = 160 - amount; - MoveServos(period / 10, current_positions); - } - current_positions[RIGHT_ROLL] = 180; - MoveServos(period, current_positions); - break; - - case 12: // 拍打双手 - current_positions[LEFT_ROLL] = 20; - current_positions[RIGHT_ROLL] = 160; - MoveServos(period, current_positions); - for (int i = 0; i < times; i++) { - current_positions[LEFT_ROLL] = 20 - amount; - current_positions[RIGHT_ROLL] = 160 + amount; - MoveServos(period / 10, current_positions); - current_positions[LEFT_ROLL] = 20 + amount; - current_positions[RIGHT_ROLL] = 160 - amount; - MoveServos(period / 10, current_positions); - } - current_positions[LEFT_ROLL] = 0; - current_positions[RIGHT_ROLL] = 180; - MoveServos(period, current_positions); - break; - } -} - -//--------------------------------------------------------- -//-- 统一身体动作函数 -//-- Parameters: -//-- action: 动作类型 1=左转, 2=右转,3=回中心 -//-- times: 转动次数 -//-- amount: 旋转角度 (0-90度,以90度为中心左右旋转) -//-- period: 动作时间 -//--------------------------------------------------------- -void Otto::BodyAction(int action, int times, int amount, int period) { - // 限制参数范围 - times = std::max(1, std::min(10, times)); - amount = std::max(0, std::min(90, amount)); - period = std::max(500, std::min(3000, period)); - - int current_positions[SERVO_COUNT]; - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - current_positions[i] = servo_[i].GetPosition(); - } else { - current_positions[i] = servo_initial_[i]; - } - } - - int body_center = servo_initial_[BODY]; - int target_angle = body_center; - - switch (action) { - case 1: // 左转 - target_angle = body_center + amount; - target_angle = std::min(180, target_angle); - break; - case 2: // 右转 - target_angle = body_center - amount; - target_angle = std::max(0, target_angle); - break; - case 3: // 回中心 - target_angle = body_center; - break; - default: - return; // 无效动作 - } - - current_positions[BODY] = target_angle; - MoveServos(period, current_positions); - vTaskDelay(pdMS_TO_TICKS(100)); -} - -//--------------------------------------------------------- -//-- 统一头部动作函数 -//-- Parameters: -//-- action: 动作类型 1=抬头, 2=低头, 3=点头, 4=回中心, 5=连续点头 -//-- times: 重复次数 (仅对连续点头有效) -//-- amount: 角度偏移 (1-15度范围内) -//-- period: 动作时间 -//--------------------------------------------------------- -void Otto::HeadAction(int action, int times, int amount, int period) { - // 限制参数范围 - times = std::max(1, std::min(10, times)); - amount = std::max(1, std::min(15, abs(amount))); - period = std::max(300, std::min(3000, period)); - - int current_positions[SERVO_COUNT]; - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - current_positions[i] = servo_[i].GetPosition(); - } else { - current_positions[i] = servo_initial_[i]; - } - } - - int head_center = 90; // 头部中心位置 - - switch (action) { - case 1: // 抬头 - current_positions[HEAD] = head_center + amount; // 抬头是增加角度 - MoveServos(period, current_positions); - break; - - case 2: // 低头 - current_positions[HEAD] = head_center - amount; // 低头是减少角度 - MoveServos(period, current_positions); - break; - - case 3: // 点头 (上下运动) - // 先抬头 - current_positions[HEAD] = head_center + amount; - MoveServos(period / 3, current_positions); - vTaskDelay(pdMS_TO_TICKS(period / 6)); - - // 再低头 - current_positions[HEAD] = head_center - amount; - MoveServos(period / 3, current_positions); - vTaskDelay(pdMS_TO_TICKS(period / 6)); - - // 回到中心 - current_positions[HEAD] = head_center; - MoveServos(period / 3, current_positions); - break; - - case 4: // 回到中心位置 - current_positions[HEAD] = head_center; - MoveServos(period, current_positions); - break; - - case 5: // 连续点头 - for (int i = 0; i < times; i++) { - // 抬头 - current_positions[HEAD] = head_center + amount; - MoveServos(period / 2, current_positions); - - // 低头 - current_positions[HEAD] = head_center - amount; - MoveServos(period / 2, current_positions); - - vTaskDelay(pdMS_TO_TICKS(50)); // 短暂停顿 - } - - // 回到中心 - current_positions[HEAD] = head_center; - MoveServos(period / 2, current_positions); - break; - - default: - // 无效动作,回到中心 - current_positions[HEAD] = head_center; - MoveServos(period, current_positions); - break; - } -} +#include "movements.h" + +#include +#include + +#include "oscillator.h" + +Otto::Otto() { + is_otto_resting_ = false; + for (int i = 0; i < SERVO_COUNT; i++) { + servo_pins_[i] = -1; + servo_trim_[i] = 0; + } +} + +Otto::~Otto() { + DetachServos(); +} + +unsigned long IRAM_ATTR millis() { + return (unsigned long)(esp_timer_get_time() / 1000ULL); +} + +void Otto::Init(int right_pitch, int right_roll, int left_pitch, int left_roll, int body, + int head) { + servo_pins_[RIGHT_PITCH] = right_pitch; + servo_pins_[RIGHT_ROLL] = right_roll; + servo_pins_[LEFT_PITCH] = left_pitch; + servo_pins_[LEFT_ROLL] = left_roll; + servo_pins_[BODY] = body; + servo_pins_[HEAD] = head; + + AttachServos(); + is_otto_resting_ = false; +} + +/////////////////////////////////////////////////////////////////// +//-- ATTACH & DETACH FUNCTIONS ----------------------------------// +/////////////////////////////////////////////////////////////////// +void Otto::AttachServos() { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].Attach(servo_pins_[i]); + } + } +} + +void Otto::DetachServos() { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].Detach(); + } + } +} + +/////////////////////////////////////////////////////////////////// +//-- OSCILLATORS TRIMS ------------------------------------------// +/////////////////////////////////////////////////////////////////// +void Otto::SetTrims(int right_pitch, int right_roll, int left_pitch, int left_roll, int body, + int head) { + servo_trim_[RIGHT_PITCH] = right_pitch; + servo_trim_[RIGHT_ROLL] = right_roll; + servo_trim_[LEFT_PITCH] = left_pitch; + servo_trim_[LEFT_ROLL] = left_roll; + servo_trim_[BODY] = body; + servo_trim_[HEAD] = head; + + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].SetTrim(servo_trim_[i]); + } + } +} + +/////////////////////////////////////////////////////////////////// +//-- BASIC MOTION FUNCTIONS -------------------------------------// +/////////////////////////////////////////////////////////////////// +void Otto::MoveServos(int time, int servo_target[]) { + if (GetRestState() == true) { + SetRestState(false); + } + + final_time_ = millis() + time; + if (time > 10) { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + increment_[i] = (servo_target[i] - servo_[i].GetPosition()) / (time / 10.0); + } + } + + for (int iteration = 1; millis() < final_time_; iteration++) { + partial_time_ = millis() + 10; + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].SetPosition(servo_[i].GetPosition() + increment_[i]); + } + } + vTaskDelay(pdMS_TO_TICKS(10)); + } + } else { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].SetPosition(servo_target[i]); + } + } + vTaskDelay(pdMS_TO_TICKS(time)); + } + + // final adjustment to the target. + bool f = true; + int adjustment_count = 0; + while (f && adjustment_count < 10) { + f = false; + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1 && servo_target[i] != servo_[i].GetPosition()) { + f = true; + break; + } + } + if (f) { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].SetPosition(servo_target[i]); + } + } + vTaskDelay(pdMS_TO_TICKS(10)); + adjustment_count++; + } + }; +} + +void Otto::MoveSingle(int position, int servo_number) { + if (position > 180) + position = 90; + if (position < 0) + position = 90; + + if (GetRestState() == true) { + SetRestState(false); + } + + if (servo_number >= 0 && servo_number < SERVO_COUNT && servo_pins_[servo_number] != -1) { + servo_[servo_number].SetPosition(position); + } +} + +void Otto::OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, + double phase_diff[SERVO_COUNT], float cycle = 1) { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].SetO(offset[i]); + servo_[i].SetA(amplitude[i]); + servo_[i].SetT(period); + servo_[i].SetPh(phase_diff[i]); + } + } + + double ref = millis(); + double end_time = period * cycle + ref; + + while (millis() < end_time) { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].Refresh(); + } + } + vTaskDelay(5); + } + vTaskDelay(pdMS_TO_TICKS(10)); +} + +void Otto::Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, + double phase_diff[SERVO_COUNT], float steps = 1.0) { + if (GetRestState() == true) { + SetRestState(false); + } + + int cycles = (int)steps; + + //-- Execute complete cycles + if (cycles >= 1) + for (int i = 0; i < cycles; i++) + OscillateServos(amplitude, offset, period, phase_diff); + + //-- Execute the final not complete cycle + OscillateServos(amplitude, offset, period, phase_diff, (float)steps - cycles); + vTaskDelay(pdMS_TO_TICKS(10)); +} + +/////////////////////////////////////////////////////////////////// +//-- HOME = Otto at rest position -------------------------------// +/////////////////////////////////////////////////////////////////// +void Otto::Home(bool hands_down) { + if (is_otto_resting_ == false) { // Go to rest position only if necessary + MoveServos(1000, servo_initial_); + is_otto_resting_ = true; + } + + vTaskDelay(pdMS_TO_TICKS(1000)); +} + +bool Otto::GetRestState() { + return is_otto_resting_; +} + +void Otto::SetRestState(bool state) { + is_otto_resting_ = state; +} + +/////////////////////////////////////////////////////////////////// +//-- PREDETERMINED MOTION SEQUENCES -----------------------------// +/////////////////////////////////////////////////////////////////// + +//--------------------------------------------------------- +//-- 统一手部动作函数 +//-- Parameters: +//-- action: 动作类型 1=举左手, 2=举右手, 3=举双手, 4=放左手, 5=放右手, 6=放双手, +//-- 7=挥左手, 8=挥右手, 9=挥双手, 10=拍打左手, 11=拍打右手, 12=拍打双手 +//-- times: 重复次数 +//-- amount: 动作幅度 (10-50) +//-- period: 动作时间 +//--------------------------------------------------------- +void Otto::HandAction(int action, int times, int amount, int period) { + // 限制参数范围 + times = 2 * std::max(3, std::min(100, times)); + amount = std::max(10, std::min(50, amount)); + period = std::max(100, std::min(1000, period)); + + int current_positions[SERVO_COUNT]; + for (int i = 0; i < SERVO_COUNT; i++) { + current_positions[i] = (servo_pins_[i] != -1) ? servo_[i].GetPosition() : servo_initial_[i]; + } + + switch (action) { + case 1: // 举左手 + current_positions[LEFT_PITCH] = 180; + MoveServos(period, current_positions); + break; + + case 2: // 举右手 + current_positions[RIGHT_PITCH] = 0; + MoveServos(period, current_positions); + break; + + case 3: // 举双手 + current_positions[LEFT_PITCH] = 180; + current_positions[RIGHT_PITCH] = 0; + MoveServos(period, current_positions); + break; + + case 4: // 放左手 + case 5: // 放右手 + case 6: // 放双手 + // 回到初始位置 + memcpy(current_positions, servo_initial_, sizeof(current_positions)); + MoveServos(period, current_positions); + break; + + case 7: // 挥左手 + current_positions[LEFT_PITCH] = 150; + MoveServos(period, current_positions); + for (int i = 0; i < times; i++) { + current_positions[LEFT_PITCH] = 150 + (i % 2 == 0 ? -30 : 30); + MoveServos(period / 10, current_positions); + vTaskDelay(pdMS_TO_TICKS(period / 10)); + } + memcpy(current_positions, servo_initial_, sizeof(current_positions)); + MoveServos(period, current_positions); + break; + + case 8: // 挥右手 + current_positions[RIGHT_PITCH] = 30; + MoveServos(period, current_positions); + for (int i = 0; i < times; i++) { + current_positions[RIGHT_PITCH] = 30 + (i % 2 == 0 ? 30 : -30); + MoveServos(period / 10, current_positions); + vTaskDelay(pdMS_TO_TICKS(period / 10)); + } + memcpy(current_positions, servo_initial_, sizeof(current_positions)); + MoveServos(period, current_positions); + break; + + case 9: // 挥双手 + current_positions[LEFT_PITCH] = 150; + current_positions[RIGHT_PITCH] = 30; + MoveServos(period, current_positions); + for (int i = 0; i < times; i++) { + current_positions[LEFT_PITCH] = 150 + (i % 2 == 0 ? -30 : 30); + current_positions[RIGHT_PITCH] = 30 + (i % 2 == 0 ? 30 : -30); + MoveServos(period / 10, current_positions); + vTaskDelay(pdMS_TO_TICKS(period / 10)); + } + memcpy(current_positions, servo_initial_, sizeof(current_positions)); + MoveServos(period, current_positions); + break; + + case 10: // 拍打左手 + current_positions[LEFT_ROLL] = 20; + MoveServos(period, current_positions); + for (int i = 0; i < times; i++) { + current_positions[LEFT_ROLL] = 20 - amount; + MoveServos(period / 10, current_positions); + current_positions[LEFT_ROLL] = 20 + amount; + MoveServos(period / 10, current_positions); + } + current_positions[LEFT_ROLL] = 0; + MoveServos(period, current_positions); + break; + + case 11: // 拍打右手 + current_positions[RIGHT_ROLL] = 160; + MoveServos(period, current_positions); + for (int i = 0; i < times; i++) { + current_positions[RIGHT_ROLL] = 160 + amount; + MoveServos(period / 10, current_positions); + current_positions[RIGHT_ROLL] = 160 - amount; + MoveServos(period / 10, current_positions); + } + current_positions[RIGHT_ROLL] = 180; + MoveServos(period, current_positions); + break; + + case 12: // 拍打双手 + current_positions[LEFT_ROLL] = 20; + current_positions[RIGHT_ROLL] = 160; + MoveServos(period, current_positions); + for (int i = 0; i < times; i++) { + current_positions[LEFT_ROLL] = 20 - amount; + current_positions[RIGHT_ROLL] = 160 + amount; + MoveServos(period / 10, current_positions); + current_positions[LEFT_ROLL] = 20 + amount; + current_positions[RIGHT_ROLL] = 160 - amount; + MoveServos(period / 10, current_positions); + } + current_positions[LEFT_ROLL] = 0; + current_positions[RIGHT_ROLL] = 180; + MoveServos(period, current_positions); + break; + } +} + +//--------------------------------------------------------- +//-- 统一身体动作函数 +//-- Parameters: +//-- action: 动作类型 1=左转, 2=右转,3=回中心 +//-- times: 转动次数 +//-- amount: 旋转角度 (0-90度,以90度为中心左右旋转) +//-- period: 动作时间 +//--------------------------------------------------------- +void Otto::BodyAction(int action, int times, int amount, int period) { + // 限制参数范围 + times = std::max(1, std::min(10, times)); + amount = std::max(0, std::min(90, amount)); + period = std::max(500, std::min(3000, period)); + + int current_positions[SERVO_COUNT]; + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + current_positions[i] = servo_[i].GetPosition(); + } else { + current_positions[i] = servo_initial_[i]; + } + } + + int body_center = servo_initial_[BODY]; + int target_angle = body_center; + + switch (action) { + case 1: // 左转 + target_angle = body_center + amount; + target_angle = std::min(180, target_angle); + break; + case 2: // 右转 + target_angle = body_center - amount; + target_angle = std::max(0, target_angle); + break; + case 3: // 回中心 + target_angle = body_center; + break; + default: + return; // 无效动作 + } + + current_positions[BODY] = target_angle; + MoveServos(period, current_positions); + vTaskDelay(pdMS_TO_TICKS(100)); +} + +//--------------------------------------------------------- +//-- 统一头部动作函数 +//-- Parameters: +//-- action: 动作类型 1=抬头, 2=低头, 3=点头, 4=回中心, 5=连续点头 +//-- times: 重复次数 (仅对连续点头有效) +//-- amount: 角度偏移 (1-15度范围内) +//-- period: 动作时间 +//--------------------------------------------------------- +void Otto::HeadAction(int action, int times, int amount, int period) { + // 限制参数范围 + times = std::max(1, std::min(10, times)); + amount = std::max(1, std::min(15, abs(amount))); + period = std::max(300, std::min(3000, period)); + + int current_positions[SERVO_COUNT]; + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + current_positions[i] = servo_[i].GetPosition(); + } else { + current_positions[i] = servo_initial_[i]; + } + } + + int head_center = 90; // 头部中心位置 + + switch (action) { + case 1: // 抬头 + current_positions[HEAD] = head_center + amount; // 抬头是增加角度 + MoveServos(period, current_positions); + break; + + case 2: // 低头 + current_positions[HEAD] = head_center - amount; // 低头是减少角度 + MoveServos(period, current_positions); + break; + + case 3: // 点头 (上下运动) + // 先抬头 + current_positions[HEAD] = head_center + amount; + MoveServos(period / 3, current_positions); + vTaskDelay(pdMS_TO_TICKS(period / 6)); + + // 再低头 + current_positions[HEAD] = head_center - amount; + MoveServos(period / 3, current_positions); + vTaskDelay(pdMS_TO_TICKS(period / 6)); + + // 回到中心 + current_positions[HEAD] = head_center; + MoveServos(period / 3, current_positions); + break; + + case 4: // 回到中心位置 + current_positions[HEAD] = head_center; + MoveServos(period, current_positions); + break; + + case 5: // 连续点头 + for (int i = 0; i < times; i++) { + // 抬头 + current_positions[HEAD] = head_center + amount; + MoveServos(period / 2, current_positions); + + // 低头 + current_positions[HEAD] = head_center - amount; + MoveServos(period / 2, current_positions); + + vTaskDelay(pdMS_TO_TICKS(50)); // 短暂停顿 + } + + // 回到中心 + current_positions[HEAD] = head_center; + MoveServos(period / 2, current_positions); + break; + + default: + // 无效动作,回到中心 + current_positions[HEAD] = head_center; + MoveServos(period, current_positions); + break; + } +} diff --git a/main/boards/electron-bot/movements.h b/main/boards/electron-bot/movements.h index bffc3ff..2094c0b 100644 --- a/main/boards/electron-bot/movements.h +++ b/main/boards/electron-bot/movements.h @@ -1,89 +1,89 @@ -#ifndef __MOVEMENTS_H__ -#define __MOVEMENTS_H__ - -#include "driver/gpio.h" -#include "esp_log.h" -#include "esp_timer.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "oscillator.h" - -//-- Constants -#define FORWARD 1 -#define BACKWARD -1 -#define LEFT 1 -#define RIGHT -1 -#define BOTH 0 -#define SMALL 5 -#define MEDIUM 15 -#define BIG 30 - -// -- Servo delta limit default. degree / sec -#define SERVO_LIMIT_DEFAULT 240 - -// -- Servo indexes for easy access -#define RIGHT_PITCH 0 -#define RIGHT_ROLL 1 -#define LEFT_PITCH 2 -#define LEFT_ROLL 3 -#define BODY 4 -#define HEAD 5 -#define SERVO_COUNT 6 - -class Otto { -public: - Otto(); - ~Otto(); - - //-- Otto initialization - void Init(int right_pitch, int right_roll, int left_pitch, int left_roll, int body, int head); - //-- Attach & detach functions - void AttachServos(); - void DetachServos(); - - //-- Oscillator Trims - void SetTrims(int right_pitch, int right_roll, int left_pitch, int left_roll, int body, - int head); - - //-- Predetermined Motion Functions - void MoveServos(int time, int servo_target[]); - void MoveSingle(int position, int servo_number); - void OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, - double phase_diff[SERVO_COUNT], float cycle); - - //-- HOME = Otto at rest position - void Home(bool hands_down = true); - bool GetRestState(); - void SetRestState(bool state); - - // -- 手部动作 - void HandAction(int action, int times = 1, int amount = 30, int period = 1000); - // action: 1=举左手, 2=举右手, 3=举双手, 4=放左手, 5=放右手, 6=放双手, 7=挥左手, 8=挥右手, - // 9=挥双手, 10=拍打左手, 11=拍打右手, 12=拍打双手 - - //-- 身体动作 - void BodyAction(int action, int times = 1, int amount = 30, int period = 1000); - // action: 1=左转, 2=右转 - - //-- 头部动作 - void HeadAction(int action, int times = 1, int amount = 10, int period = 500); - // action: 1=抬头, 2=低头, 3=点头, 4=回中心, 5=连续点头 - -private: - Oscillator servo_[SERVO_COUNT]; - - int servo_pins_[SERVO_COUNT]; - int servo_trim_[SERVO_COUNT]; - int servo_initial_[SERVO_COUNT] = {180, 180, 0, 0, 90, 90}; - - unsigned long final_time_; - unsigned long partial_time_; - float increment_[SERVO_COUNT]; - - bool is_otto_resting_; - - void Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, - double phase_diff[SERVO_COUNT], float steps); -}; - +#ifndef __MOVEMENTS_H__ +#define __MOVEMENTS_H__ + +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "oscillator.h" + +//-- Constants +#define FORWARD 1 +#define BACKWARD -1 +#define LEFT 1 +#define RIGHT -1 +#define BOTH 0 +#define SMALL 5 +#define MEDIUM 15 +#define BIG 30 + +// -- Servo delta limit default. degree / sec +#define SERVO_LIMIT_DEFAULT 240 + +// -- Servo indexes for easy access +#define RIGHT_PITCH 0 +#define RIGHT_ROLL 1 +#define LEFT_PITCH 2 +#define LEFT_ROLL 3 +#define BODY 4 +#define HEAD 5 +#define SERVO_COUNT 6 + +class Otto { +public: + Otto(); + ~Otto(); + + //-- Otto initialization + void Init(int right_pitch, int right_roll, int left_pitch, int left_roll, int body, int head); + //-- Attach & detach functions + void AttachServos(); + void DetachServos(); + + //-- Oscillator Trims + void SetTrims(int right_pitch, int right_roll, int left_pitch, int left_roll, int body, + int head); + + //-- Predetermined Motion Functions + void MoveServos(int time, int servo_target[]); + void MoveSingle(int position, int servo_number); + void OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, + double phase_diff[SERVO_COUNT], float cycle); + + //-- HOME = Otto at rest position + void Home(bool hands_down = true); + bool GetRestState(); + void SetRestState(bool state); + + // -- 手部动作 + void HandAction(int action, int times = 1, int amount = 30, int period = 1000); + // action: 1=举左手, 2=举右手, 3=举双手, 4=放左手, 5=放右手, 6=放双手, 7=挥左手, 8=挥右手, + // 9=挥双手, 10=拍打左手, 11=拍打右手, 12=拍打双手 + + //-- 身体动作 + void BodyAction(int action, int times = 1, int amount = 30, int period = 1000); + // action: 1=左转, 2=右转 + + //-- 头部动作 + void HeadAction(int action, int times = 1, int amount = 10, int period = 500); + // action: 1=抬头, 2=低头, 3=点头, 4=回中心, 5=连续点头 + +private: + Oscillator servo_[SERVO_COUNT]; + + int servo_pins_[SERVO_COUNT]; + int servo_trim_[SERVO_COUNT]; + int servo_initial_[SERVO_COUNT] = {180, 180, 0, 0, 90, 90}; + + unsigned long final_time_; + unsigned long partial_time_; + float increment_[SERVO_COUNT]; + + bool is_otto_resting_; + + void Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, + double phase_diff[SERVO_COUNT], float steps); +}; + #endif // __MOVEMENTS_H__ \ No newline at end of file diff --git a/main/boards/electron-bot/oscillator.cc b/main/boards/electron-bot/oscillator.cc index 0cfc652..9aff3cd 100644 --- a/main/boards/electron-bot/oscillator.cc +++ b/main/boards/electron-bot/oscillator.cc @@ -1,149 +1,149 @@ -#include "oscillator.h" - -#include -#include - -#include -#include - -extern unsigned long IRAM_ATTR millis(); - -Oscillator::Oscillator(int trim) { - trim_ = trim; - diff_limit_ = 0; - is_attached_ = false; - - sampling_period_ = 30; - period_ = 2000; - number_samples_ = period_ / sampling_period_; - inc_ = 2 * M_PI / number_samples_; - - amplitude_ = 45; - phase_ = 0; - phase0_ = 0; - offset_ = 0; - stop_ = false; - rev_ = false; - - pos_ = 90; - previous_millis_ = 0; -} - -Oscillator::~Oscillator() { - Detach(); -} - -uint32_t Oscillator::AngleToCompare(int angle) { - return (angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) / - (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) + - SERVO_MIN_PULSEWIDTH_US; -} - -bool Oscillator::NextSample() { - current_millis_ = millis(); - - if (current_millis_ - previous_millis_ > sampling_period_) { - previous_millis_ = current_millis_; - return true; - } - - return false; -} - -void Oscillator::Attach(int pin, bool rev) { - if (is_attached_) { - Detach(); - } - - pin_ = pin; - rev_ = rev; - - ledc_timer_config_t ledc_timer = {.speed_mode = LEDC_LOW_SPEED_MODE, - .duty_resolution = LEDC_TIMER_13_BIT, - .timer_num = LEDC_TIMER_1, - .freq_hz = 50, - .clk_cfg = LEDC_AUTO_CLK}; - ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); - - static int last_channel = 0; - last_channel = (last_channel + 1) % 7 + 1; - ledc_channel_ = (ledc_channel_t)last_channel; - - ledc_channel_config_t ledc_channel = {.gpio_num = pin_, - .speed_mode = LEDC_LOW_SPEED_MODE, - .channel = ledc_channel_, - .intr_type = LEDC_INTR_DISABLE, - .timer_sel = LEDC_TIMER_1, - .duty = 0, - .hpoint = 0}; - ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); - - ledc_speed_mode_ = LEDC_LOW_SPEED_MODE; - - // pos_ = 90; - // Write(pos_); - previous_servo_command_millis_ = millis(); - - is_attached_ = true; -} - -void Oscillator::Detach() { - if (!is_attached_) - return; - - ESP_ERROR_CHECK(ledc_stop(ledc_speed_mode_, ledc_channel_, 0)); - - is_attached_ = false; -} - -void Oscillator::SetT(unsigned int T) { - period_ = T; - - number_samples_ = period_ / sampling_period_; - inc_ = 2 * M_PI / number_samples_; -} - -void Oscillator::SetPosition(int position) { - Write(position); -} - -void Oscillator::Refresh() { - if (NextSample()) { - if (!stop_) { - int pos = std::round(amplitude_ * std::sin(phase_ + phase0_) + offset_); - if (rev_) - pos = -pos; - Write(pos + 90); - } - - phase_ = phase_ + inc_; - } -} - -void Oscillator::Write(int position) { - if (!is_attached_) - return; - - long currentMillis = millis(); - if (diff_limit_ > 0) { - int limit = std::max( - 1, (((int)(currentMillis - previous_servo_command_millis_)) * diff_limit_) / 1000); - if (abs(position - pos_) > limit) { - pos_ += position < pos_ ? -limit : limit; - } else { - pos_ = position; - } - } else { - pos_ = position; - } - previous_servo_command_millis_ = currentMillis; - - int angle = pos_ + trim_; - - angle = std::min(std::max(angle, 0), 180); - - uint32_t duty = (uint32_t)(((angle / 180.0) * 2.0 + 0.5) * 8191 / 20.0); - - ESP_ERROR_CHECK(ledc_set_duty(ledc_speed_mode_, ledc_channel_, duty)); - ESP_ERROR_CHECK(ledc_update_duty(ledc_speed_mode_, ledc_channel_)); -} +#include "oscillator.h" + +#include +#include + +#include +#include + +extern unsigned long IRAM_ATTR millis(); + +Oscillator::Oscillator(int trim) { + trim_ = trim; + diff_limit_ = 0; + is_attached_ = false; + + sampling_period_ = 30; + period_ = 2000; + number_samples_ = period_ / sampling_period_; + inc_ = 2 * M_PI / number_samples_; + + amplitude_ = 45; + phase_ = 0; + phase0_ = 0; + offset_ = 0; + stop_ = false; + rev_ = false; + + pos_ = 90; + previous_millis_ = 0; +} + +Oscillator::~Oscillator() { + Detach(); +} + +uint32_t Oscillator::AngleToCompare(int angle) { + return (angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) / + (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) + + SERVO_MIN_PULSEWIDTH_US; +} + +bool Oscillator::NextSample() { + current_millis_ = millis(); + + if (current_millis_ - previous_millis_ > sampling_period_) { + previous_millis_ = current_millis_; + return true; + } + + return false; +} + +void Oscillator::Attach(int pin, bool rev) { + if (is_attached_) { + Detach(); + } + + pin_ = pin; + rev_ = rev; + + ledc_timer_config_t ledc_timer = {.speed_mode = LEDC_LOW_SPEED_MODE, + .duty_resolution = LEDC_TIMER_13_BIT, + .timer_num = LEDC_TIMER_1, + .freq_hz = 50, + .clk_cfg = LEDC_AUTO_CLK}; + ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); + + static int last_channel = 0; + last_channel = (last_channel + 1) % 7 + 1; + ledc_channel_ = (ledc_channel_t)last_channel; + + ledc_channel_config_t ledc_channel = {.gpio_num = pin_, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = ledc_channel_, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = LEDC_TIMER_1, + .duty = 0, + .hpoint = 0}; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); + + ledc_speed_mode_ = LEDC_LOW_SPEED_MODE; + + // pos_ = 90; + // Write(pos_); + previous_servo_command_millis_ = millis(); + + is_attached_ = true; +} + +void Oscillator::Detach() { + if (!is_attached_) + return; + + ESP_ERROR_CHECK(ledc_stop(ledc_speed_mode_, ledc_channel_, 0)); + + is_attached_ = false; +} + +void Oscillator::SetT(unsigned int T) { + period_ = T; + + number_samples_ = period_ / sampling_period_; + inc_ = 2 * M_PI / number_samples_; +} + +void Oscillator::SetPosition(int position) { + Write(position); +} + +void Oscillator::Refresh() { + if (NextSample()) { + if (!stop_) { + int pos = std::round(amplitude_ * std::sin(phase_ + phase0_) + offset_); + if (rev_) + pos = -pos; + Write(pos + 90); + } + + phase_ = phase_ + inc_; + } +} + +void Oscillator::Write(int position) { + if (!is_attached_) + return; + + long currentMillis = millis(); + if (diff_limit_ > 0) { + int limit = std::max( + 1, (((int)(currentMillis - previous_servo_command_millis_)) * diff_limit_) / 1000); + if (abs(position - pos_) > limit) { + pos_ += position < pos_ ? -limit : limit; + } else { + pos_ = position; + } + } else { + pos_ = position; + } + previous_servo_command_millis_ = currentMillis; + + int angle = pos_ + trim_; + + angle = std::min(std::max(angle, 0), 180); + + uint32_t duty = (uint32_t)(((angle / 180.0) * 2.0 + 0.5) * 8191 / 20.0); + + ESP_ERROR_CHECK(ledc_set_duty(ledc_speed_mode_, ledc_channel_, duty)); + ESP_ERROR_CHECK(ledc_update_duty(ledc_speed_mode_, ledc_channel_)); +} diff --git a/main/boards/electron-bot/oscillator.h b/main/boards/electron-bot/oscillator.h index d9e79f2..7969ff5 100644 --- a/main/boards/electron-bot/oscillator.h +++ b/main/boards/electron-bot/oscillator.h @@ -1,83 +1,83 @@ -#ifndef __OSCILLATOR_H__ -#define __OSCILLATOR_H__ - -#include "driver/ledc.h" -#include "esp_log.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" - -#define M_PI 3.14159265358979323846 - -#ifndef DEG2RAD -#define DEG2RAD(g) ((g) * M_PI) / 180 -#endif - -#define SERVO_MIN_PULSEWIDTH_US 500 // 最小脉宽(微秒) -#define SERVO_MAX_PULSEWIDTH_US 2500 // 最大脉宽(微秒) -#define SERVO_MIN_DEGREE -90 // 最小角度 -#define SERVO_MAX_DEGREE 90 // 最大角度 -#define SERVO_TIMEBASE_RESOLUTION_HZ 1000000 // 1MHz, 1us per tick -#define SERVO_TIMEBASE_PERIOD 20000 // 20000 ticks, 20ms - -class Oscillator { -public: - Oscillator(int trim = 0); - ~Oscillator(); - void Attach(int pin, bool rev = false); - void Detach(); - - void SetA(unsigned int amplitude) { amplitude_ = amplitude; }; - void SetO(int offset) { offset_ = offset; }; - void SetPh(double Ph) { phase0_ = Ph; }; - void SetT(unsigned int period); - void SetTrim(int trim) { trim_ = trim; }; - void SetLimiter(int diff_limit) { diff_limit_ = diff_limit; }; - void DisableLimiter() { diff_limit_ = 0; }; - int GetTrim() { return trim_; }; - void SetPosition(int position); - void Stop() { stop_ = true; }; - void Play() { stop_ = false; }; - void Reset() { phase_ = 0; }; - void Refresh(); - int GetPosition() { return pos_; } - -private: - bool NextSample(); - void Write(int position); - uint32_t AngleToCompare(int angle); - -private: - bool is_attached_; - - //-- Oscillators parameters - unsigned int amplitude_; //-- Amplitude (degrees) - int offset_; //-- Offset (degrees) - unsigned int period_; //-- Period (miliseconds) - double phase0_; //-- Phase (radians) - - //-- Internal variables - int pos_; //-- Current servo pos - int pin_; //-- Pin where the servo is connected - int trim_; //-- Calibration offset - double phase_; //-- Current phase - double inc_; //-- Increment of phase - double number_samples_; //-- Number of samples - unsigned int sampling_period_; //-- sampling period (ms) - - long previous_millis_; - long current_millis_; - - //-- Oscillation mode. If true, the servo is stopped - bool stop_; - - //-- Reverse mode - bool rev_; - - int diff_limit_; - long previous_servo_command_millis_; - - ledc_channel_t ledc_channel_; - ledc_mode_t ledc_speed_mode_; -}; - +#ifndef __OSCILLATOR_H__ +#define __OSCILLATOR_H__ + +#include "driver/ledc.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define M_PI 3.14159265358979323846 + +#ifndef DEG2RAD +#define DEG2RAD(g) ((g) * M_PI) / 180 +#endif + +#define SERVO_MIN_PULSEWIDTH_US 500 // 最小脉宽(微秒) +#define SERVO_MAX_PULSEWIDTH_US 2500 // 最大脉宽(微秒) +#define SERVO_MIN_DEGREE -90 // 最小角度 +#define SERVO_MAX_DEGREE 90 // 最大角度 +#define SERVO_TIMEBASE_RESOLUTION_HZ 1000000 // 1MHz, 1us per tick +#define SERVO_TIMEBASE_PERIOD 20000 // 20000 ticks, 20ms + +class Oscillator { +public: + Oscillator(int trim = 0); + ~Oscillator(); + void Attach(int pin, bool rev = false); + void Detach(); + + void SetA(unsigned int amplitude) { amplitude_ = amplitude; }; + void SetO(int offset) { offset_ = offset; }; + void SetPh(double Ph) { phase0_ = Ph; }; + void SetT(unsigned int period); + void SetTrim(int trim) { trim_ = trim; }; + void SetLimiter(int diff_limit) { diff_limit_ = diff_limit; }; + void DisableLimiter() { diff_limit_ = 0; }; + int GetTrim() { return trim_; }; + void SetPosition(int position); + void Stop() { stop_ = true; }; + void Play() { stop_ = false; }; + void Reset() { phase_ = 0; }; + void Refresh(); + int GetPosition() { return pos_; } + +private: + bool NextSample(); + void Write(int position); + uint32_t AngleToCompare(int angle); + +private: + bool is_attached_; + + //-- Oscillators parameters + unsigned int amplitude_; //-- Amplitude (degrees) + int offset_; //-- Offset (degrees) + unsigned int period_; //-- Period (miliseconds) + double phase0_; //-- Phase (radians) + + //-- Internal variables + int pos_; //-- Current servo pos + int pin_; //-- Pin where the servo is connected + int trim_; //-- Calibration offset + double phase_; //-- Current phase + double inc_; //-- Increment of phase + double number_samples_; //-- Number of samples + unsigned int sampling_period_; //-- sampling period (ms) + + long previous_millis_; + long current_millis_; + + //-- Oscillation mode. If true, the servo is stopped + bool stop_; + + //-- Reverse mode + bool rev_; + + int diff_limit_; + long previous_servo_command_millis_; + + ledc_channel_t ledc_channel_; + ledc_mode_t ledc_speed_mode_; +}; + #endif // __OSCILLATOR_H__ \ No newline at end of file diff --git a/main/boards/electron-bot/power_manager.h b/main/boards/electron-bot/power_manager.h index 13d8ff3..ad92047 100644 --- a/main/boards/electron-bot/power_manager.h +++ b/main/boards/electron-bot/power_manager.h @@ -1,128 +1,128 @@ -#ifndef __POWER_MANAGER_H__ -#define __POWER_MANAGER_H__ - -#include -#include -#include -#include - -class PowerManager { -private: - // 电池电量区间-分压电阻为2个100k - static constexpr struct { - uint16_t adc; - uint8_t level; - } BATTERY_LEVELS[] = {{2150, 0}, {2450, 100}}; - static constexpr size_t BATTERY_LEVELS_COUNT = 2; - static constexpr size_t ADC_VALUES_COUNT = 10; - - esp_timer_handle_t timer_handle_ = nullptr; - gpio_num_t charging_pin_; - adc_unit_t adc_unit_; - adc_channel_t adc_channel_; - uint16_t adc_values_[ADC_VALUES_COUNT]; - size_t adc_values_index_ = 0; - size_t adc_values_count_ = 0; - uint8_t battery_level_ = 100; - bool is_charging_ = false; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - is_charging_ = gpio_get_level(charging_pin_) == 0; - ReadBatteryAdcData(); - } - - void ReadBatteryAdcData() { - int adc_value; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_value)); - - adc_values_[adc_values_index_] = adc_value; - adc_values_index_ = (adc_values_index_ + 1) % ADC_VALUES_COUNT; - if (adc_values_count_ < ADC_VALUES_COUNT) { - adc_values_count_++; - } - - uint32_t average_adc = 0; - for (size_t i = 0; i < adc_values_count_; i++) { - average_adc += adc_values_[i]; - } - average_adc /= adc_values_count_; - - CalculateBatteryLevel(average_adc); - - // ESP_LOGI("PowerManager", "ADC值: %d 平均值: %ld 电量: %u%%", adc_value, average_adc, - // battery_level_); - } - - void CalculateBatteryLevel(uint32_t average_adc) { - if (average_adc <= BATTERY_LEVELS[0].adc) { - battery_level_ = 0; - } else if (average_adc >= BATTERY_LEVELS[BATTERY_LEVELS_COUNT - 1].adc) { - battery_level_ = 100; - } else { - float ratio = static_cast(average_adc - BATTERY_LEVELS[0].adc) / - (BATTERY_LEVELS[1].adc - BATTERY_LEVELS[0].adc); - battery_level_ = ratio * 100; - } - } - -public: - PowerManager(gpio_num_t charging_pin, adc_unit_t adc_unit = ADC_UNIT_2, - adc_channel_t adc_channel = ADC_CHANNEL_3) - : charging_pin_(charging_pin), adc_unit_(adc_unit), adc_channel_(adc_channel) { - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&io_conf); - - esp_timer_create_args_t timer_args = { - .callback = - [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); // 1秒 - - InitializeAdc(); - } - - void InitializeAdc() { - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = adc_unit_, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &chan_config)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { return is_charging_; } - - uint8_t GetBatteryLevel() { return battery_level_; } -}; +#ifndef __POWER_MANAGER_H__ +#define __POWER_MANAGER_H__ + +#include +#include +#include +#include + +class PowerManager { +private: + // 电池电量区间-分压电阻为2个100k + static constexpr struct { + uint16_t adc; + uint8_t level; + } BATTERY_LEVELS[] = {{2150, 0}, {2450, 100}}; + static constexpr size_t BATTERY_LEVELS_COUNT = 2; + static constexpr size_t ADC_VALUES_COUNT = 10; + + esp_timer_handle_t timer_handle_ = nullptr; + gpio_num_t charging_pin_; + adc_unit_t adc_unit_; + adc_channel_t adc_channel_; + uint16_t adc_values_[ADC_VALUES_COUNT]; + size_t adc_values_index_ = 0; + size_t adc_values_count_ = 0; + uint8_t battery_level_ = 100; + bool is_charging_ = false; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + is_charging_ = gpio_get_level(charging_pin_) == 0; + ReadBatteryAdcData(); + } + + void ReadBatteryAdcData() { + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_value)); + + adc_values_[adc_values_index_] = adc_value; + adc_values_index_ = (adc_values_index_ + 1) % ADC_VALUES_COUNT; + if (adc_values_count_ < ADC_VALUES_COUNT) { + adc_values_count_++; + } + + uint32_t average_adc = 0; + for (size_t i = 0; i < adc_values_count_; i++) { + average_adc += adc_values_[i]; + } + average_adc /= adc_values_count_; + + CalculateBatteryLevel(average_adc); + + // ESP_LOGI("PowerManager", "ADC值: %d 平均值: %ld 电量: %u%%", adc_value, average_adc, + // battery_level_); + } + + void CalculateBatteryLevel(uint32_t average_adc) { + if (average_adc <= BATTERY_LEVELS[0].adc) { + battery_level_ = 0; + } else if (average_adc >= BATTERY_LEVELS[BATTERY_LEVELS_COUNT - 1].adc) { + battery_level_ = 100; + } else { + float ratio = static_cast(average_adc - BATTERY_LEVELS[0].adc) / + (BATTERY_LEVELS[1].adc - BATTERY_LEVELS[0].adc); + battery_level_ = ratio * 100; + } + } + +public: + PowerManager(gpio_num_t charging_pin, adc_unit_t adc_unit = ADC_UNIT_2, + adc_channel_t adc_channel = ADC_CHANNEL_3) + : charging_pin_(charging_pin), adc_unit_(adc_unit), adc_channel_(adc_channel) { + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + + esp_timer_create_args_t timer_args = { + .callback = + [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); // 1秒 + + InitializeAdc(); + } + + void InitializeAdc() { + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = adc_unit_, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { return is_charging_; } + + uint8_t GetBatteryLevel() { return battery_level_; } +}; #endif // __POWER_MANAGER_H__ \ No newline at end of file diff --git a/main/boards/esp-box-3/config.h b/main/boards/esp-box-3/config.h index f045304..c265732 100644 --- a/main/boards/esp-box-3/config.h +++ b/main/boards/esp-box-3/config.h @@ -1,41 +1,41 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_47 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_47 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp-box-3/config.json b/main/boards/esp-box-3/config.json index 67ead9b..b350130 100644 --- a/main/boards/esp-box-3/config.json +++ b/main/boards/esp-box-3/config.json @@ -1,11 +1,16 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp-box-3", - "sdkconfig_append": [ - "CONFIG_USE_DEVICE_AEC=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp-box-3", + "sdkconfig_append": [ + "CONFIG_USE_DEVICE_AEC=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"", + "CONFIG_USE_EMOTE_MESSAGE_STYLE=y", + "CONFIG_BOARD_TYPE_ESP_BOX_3=y", + "CONFIG_FLASH_CUSTOM_ASSETS=y", + "CONFIG_CUSTOM_ASSETS_FILE=\"https://dl.espressif.com/AE/wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-esp-box-3.bin\"" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/esp-box-3/emote.json b/main/boards/esp-box-3/emote.json new file mode 100644 index 0000000..3a47de2 --- /dev/null +++ b/main/boards/esp-box-3/emote.json @@ -0,0 +1,22 @@ +[ + {"emote": "happy", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "laughing", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "funny", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "loving", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "embarrassed", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "confident", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "delicious", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "sad", "src": "Sad.eaf", "loop": true, "fps": 20}, + {"emote": "crying", "src": "cry.eaf", "loop": true, "fps": 20}, + {"emote": "sleepy", "src": "sleep.eaf", "loop": true, "fps": 20}, + {"emote": "silly", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "angry", "src": "angry.eaf", "loop": true, "fps": 20}, + {"emote": "surprised", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "shocked", "src": "shocked.eaf", "loop": true, "fps": 20}, + {"emote": "thinking", "src": "confused.eaf", "loop": true, "fps": 20}, + {"emote": "winking", "src": "neutral.eaf", "loop": true, "fps": 20}, + {"emote": "relaxed", "src": "Happy.eaf", "loop": true, "fps": 20}, + {"emote": "confused", "src": "confused.eaf", "loop": true, "fps": 20}, + {"emote": "neutral", "src": "winking.eaf", "loop": false, "fps": 20}, + {"emote": "idle", "src": "neutral.eaf", "loop": false, "fps": 20} +] diff --git a/main/boards/esp-box-3/esp_box3_board.cc b/main/boards/esp-box-3/esp_box3_board.cc index 47f964f..5dc7ec5 100644 --- a/main/boards/esp-box-3/esp_box3_board.cc +++ b/main/boards/esp-box-3/esp_box3_board.cc @@ -1,168 +1,175 @@ -#include "wifi_board.h" -#include "codecs/box_audio_codec.h" -#include "display/lcd_display.h" -#include "esp_lcd_ili9341.h" -#include "application.h" -#include "button.h" -#include "config.h" - -#include -#include -#include -#include -#include - -#define TAG "EspBox3Board" - -// Init ili9341 by custom cmd -static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { - {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0}, - {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0}, - {0xC5, (uint8_t []){0xD0}, 1, 0}, - {0xC1, (uint8_t []){0x02}, 1, 0}, - {0xB4, (uint8_t []){0x02}, 1, 0}, - {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0}, - {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0}, - - {0xB1, (uint8_t []){00, 0x1B}, 2, 0}, - {0x36, (uint8_t []){0x08}, 1, 0}, - {0x3A, (uint8_t []){0x55}, 1, 0}, - {0xB7, (uint8_t []){0x06}, 1, 0}, - - {0x11, (uint8_t []){0}, 0x80, 0}, - {0x29, (uint8_t []){0}, 0x80, 0}, - - {0, (uint8_t []){0}, 0xff, 0}, -}; - -class EspBox3Board : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_6; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_7; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - -#if CONFIG_USE_DEVICE_AEC - boot_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateIdle) { - app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); - } - }); -#endif - } - - void InitializeIli9341Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_5; - io_config.dc_gpio_num = GPIO_NUM_4; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - const ili9341_vendor_config_t vendor_config = { - .init_cmds = &vendor_specific_init[0], - .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), - }; - - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_48; - panel_config.flags.reset_active_high = 1, - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = (void *)&vendor_config; - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - esp_lcd_panel_disp_on_off(panel, true); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeSpi(); - InitializeIli9341Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(EspBox3Board); +#include "wifi_board.h" +#include "codecs/box_audio_codec.h" +#include "display/display.h" +#include "display/emote_display.h" +#include "display/lcd_display.h" +#include "esp_lcd_ili9341.h" +#include "application.h" +#include "button.h" +#include "config.h" + +#include +#include +#include +#include +#include + +#define TAG "EspBox3Board" + +// Init ili9341 by custom cmd +static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { + {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0}, + {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0}, + {0xC5, (uint8_t []){0xD0}, 1, 0}, + {0xC1, (uint8_t []){0x02}, 1, 0}, + {0xB4, (uint8_t []){0x02}, 1, 0}, + {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0}, + {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0}, + + {0xB1, (uint8_t []){00, 0x1B}, 2, 0}, + {0x36, (uint8_t []){0x08}, 1, 0}, + {0x3A, (uint8_t []){0x55}, 1, 0}, + {0xB7, (uint8_t []){0x06}, 1, 0}, + + {0x11, (uint8_t []){0}, 0x80, 0}, + {0x29, (uint8_t []){0}, 0x80, 0}, + + {0, (uint8_t []){0}, 0xff, 0}, +}; + +class EspBox3Board : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + Display* display_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_6; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_7; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif + } + + void InitializeIli9341Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_5; + io_config.dc_gpio_num = GPIO_NUM_4; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + const ili9341_vendor_config_t vendor_config = { + .init_cmds = &vendor_specific_init[0], + .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), + }; + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_48; + panel_config.flags.reset_active_high = 1, + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = (void *)&vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_disp_on_off(panel, true); + +#if CONFIG_USE_EMOTE_MESSAGE_STYLE + display_ = new emote::EmoteDisplay(panel, panel_io, DISPLAY_WIDTH, DISPLAY_HEIGHT); +#else + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); +#endif + } + +public: + EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeIli9341Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(EspBox3Board); diff --git a/main/boards/esp-box-3/layout.json b/main/boards/esp-box-3/layout.json new file mode 100644 index 0000000..0572558 --- /dev/null +++ b/main/boards/esp-box-3/layout.json @@ -0,0 +1,37 @@ +[ + { + "name": "eye_anim", + "align": "GFX_ALIGN_LEFT_MID", + "x": 10, + "y": 30 + }, + { + "name": "status_icon", + "align": "GFX_ALIGN_TOP_MID", + "x": -120, + "y": 18 + }, + { + "name": "toast_label", + "align": "GFX_ALIGN_TOP_MID", + "x": 0, + "y": 20, + "width": 200, + "height": 40 + }, + { + "name": "clock_label", + "align": "GFX_ALIGN_TOP_MID", + "x": 0, + "y": 20, + "width": 200, + "height": 50 + }, + { + "name": "listen_anim", + "align": "GFX_ALIGN_TOP_MID", + "x": 0, + "y": 5 + } +] + \ No newline at end of file diff --git a/main/boards/esp-box-lite/config.h b/main/boards/esp-box-lite/config.h index ee345ed..dae30b0 100644 --- a/main/boards/esp-box-lite/config.h +++ b/main/boards/esp-box-lite/config.h @@ -1,39 +1,39 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 16000 - -#define AUDIO_INPUT_REFERENCE CONFIG_USE_AUDIO_PROCESSOR - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_45 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 16000 + +#define AUDIO_INPUT_REFERENCE CONFIG_USE_AUDIO_PROCESSOR + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_45 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp-box-lite/config.json b/main/boards/esp-box-lite/config.json index 870fabe..080c0ce 100644 --- a/main/boards/esp-box-lite/config.json +++ b/main/boards/esp-box-lite/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp-box-lite", - "sdkconfig_append": [ - "CONFIG_SOC_ADC_SUPPORTED=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp-box-lite", + "sdkconfig_append": [ + "CONFIG_SOC_ADC_SUPPORTED=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/esp-box-lite/esp_box_lite_board.cc b/main/boards/esp-box-lite/esp_box_lite_board.cc index ca7dd6a..d4c30b5 100644 --- a/main/boards/esp-box-lite/esp_box_lite_board.cc +++ b/main/boards/esp-box-lite/esp_box_lite_board.cc @@ -1,237 +1,237 @@ -#include "wifi_board.h" -#include "box_audio_codec_lite.h" -#include "display/lcd_display.h" -#include "esp_lcd_ili9341.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include - -#define TAG "EspBoxBoardLite" - -/* ADC Buttons */ -typedef enum { - BSP_ADC_BUTTON_PREV, - BSP_ADC_BUTTON_ENTER, - BSP_ADC_BUTTON_NEXT, - BSP_ADC_BUTTON_NUM -} bsp_adc_button_t; - -// Init ili9341 by custom cmd -static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { - {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0}, - {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0}, - {0xC5, (uint8_t []){0xD0}, 1, 0}, - {0xC1, (uint8_t []){0x02}, 1, 0}, - {0xB4, (uint8_t []){0x02}, 1, 0}, - {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0}, - {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0}, - - {0xB1, (uint8_t []){00, 0x1B}, 2, 0}, - {0x36, (uint8_t []){0x08}, 1, 0}, - {0x3A, (uint8_t []){0x55}, 1, 0}, - {0xB7, (uint8_t []){0x06}, 1, 0}, - - {0x11, (uint8_t []){0}, 0x80, 0}, - {0x29, (uint8_t []){0}, 0x80, 0}, - - {0, (uint8_t []){0}, 0xff, 0}, -}; - -class EspBoxBoardLite : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - Button* adc_button_[BSP_ADC_BUTTON_NUM]; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) - adc_oneshot_unit_handle_t bsp_adc_handle = NULL; -#endif - LcdDisplay* display_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_6; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_7; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void ChangeVol(int val) { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + val; - if (volume > 100) { - volume = 100; - } - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - } - - void TogleState() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - } - - void InitializeButtons() { - /* Initialize ADC esp-box lite的前三个按钮采用是的adc按钮,而非gpio */ - button_adc_config_t adc_cfg = {}; - adc_cfg.adc_channel = ADC_CHANNEL_0; // ADC1 channel 0 is GPIO1 -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) - const adc_oneshot_unit_init_cfg_t init_config1 = { - .unit_id = ADC_UNIT_1, - }; - adc_oneshot_new_unit(&init_config1, &bsp_adc_handle); - adc_cfg.adc_handle = &bsp_adc_handle; -#endif - adc_cfg.button_index = BSP_ADC_BUTTON_PREV; - adc_cfg.min = 2310; // middle is 2410mV - adc_cfg.max = 2510; - adc_button_[0] = new AdcButton(adc_cfg); - - adc_cfg.button_index = BSP_ADC_BUTTON_ENTER; - adc_cfg.min = 1880; // middle is 1980mV - adc_cfg.max = 2080; - adc_button_[1] = new AdcButton(adc_cfg); - - adc_cfg.button_index = BSP_ADC_BUTTON_NEXT; - adc_cfg.min = 720; // middle is 820mV - adc_cfg.max = 920; - - adc_button_[2] = new AdcButton(adc_cfg); - - auto volume_up_button = adc_button_[BSP_ADC_BUTTON_NEXT]; - volume_up_button->OnClick([this]() {ChangeVol(10);}); - volume_up_button->OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - auto volume_down_button = adc_button_[BSP_ADC_BUTTON_PREV]; - volume_down_button->OnClick([this]() {ChangeVol(-10);}); - volume_down_button->OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - - auto break_button = adc_button_[BSP_ADC_BUTTON_ENTER]; - break_button->OnClick([this]() {TogleState();}); - boot_button_.OnClick([this]() {TogleState();}); - } - - void InitializeIli9341Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_5; - io_config.dc_gpio_num = GPIO_NUM_4; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - const ili9341_vendor_config_t vendor_config = { - .init_cmds = &vendor_specific_init[0], - .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), - }; - - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_48; - panel_config.flags.reset_active_high = 0, - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = (void *)&vendor_config; - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - esp_lcd_panel_disp_on_off(panel, true); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - EspBoxBoardLite() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeSpi(); - InitializeIli9341Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - ~EspBoxBoardLite() { - for (int i =0; i +#include +#include +#include +#include +#include + +#define TAG "EspBoxBoardLite" + +/* ADC Buttons */ +typedef enum { + BSP_ADC_BUTTON_PREV, + BSP_ADC_BUTTON_ENTER, + BSP_ADC_BUTTON_NEXT, + BSP_ADC_BUTTON_NUM +} bsp_adc_button_t; + +// Init ili9341 by custom cmd +static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { + {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0}, + {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0}, + {0xC5, (uint8_t []){0xD0}, 1, 0}, + {0xC1, (uint8_t []){0x02}, 1, 0}, + {0xB4, (uint8_t []){0x02}, 1, 0}, + {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0}, + {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0}, + + {0xB1, (uint8_t []){00, 0x1B}, 2, 0}, + {0x36, (uint8_t []){0x08}, 1, 0}, + {0x3A, (uint8_t []){0x55}, 1, 0}, + {0xB7, (uint8_t []){0x06}, 1, 0}, + + {0x11, (uint8_t []){0}, 0x80, 0}, + {0x29, (uint8_t []){0}, 0x80, 0}, + + {0, (uint8_t []){0}, 0xff, 0}, +}; + +class EspBoxBoardLite : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + Button* adc_button_[BSP_ADC_BUTTON_NUM]; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + adc_oneshot_unit_handle_t bsp_adc_handle = NULL; +#endif + LcdDisplay* display_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_6; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_7; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void ChangeVol(int val) { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + val; + if (volume > 100) { + volume = 100; + } + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + } + + void TogleState() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + } + + void InitializeButtons() { + /* Initialize ADC esp-box lite的前三个按钮采用是的adc按钮,而非gpio */ + button_adc_config_t adc_cfg = {}; + adc_cfg.adc_channel = ADC_CHANNEL_0; // ADC1 channel 0 is GPIO1 +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + const adc_oneshot_unit_init_cfg_t init_config1 = { + .unit_id = ADC_UNIT_1, + }; + adc_oneshot_new_unit(&init_config1, &bsp_adc_handle); + adc_cfg.adc_handle = &bsp_adc_handle; +#endif + adc_cfg.button_index = BSP_ADC_BUTTON_PREV; + adc_cfg.min = 2310; // middle is 2410mV + adc_cfg.max = 2510; + adc_button_[0] = new AdcButton(adc_cfg); + + adc_cfg.button_index = BSP_ADC_BUTTON_ENTER; + adc_cfg.min = 1880; // middle is 1980mV + adc_cfg.max = 2080; + adc_button_[1] = new AdcButton(adc_cfg); + + adc_cfg.button_index = BSP_ADC_BUTTON_NEXT; + adc_cfg.min = 720; // middle is 820mV + adc_cfg.max = 920; + + adc_button_[2] = new AdcButton(adc_cfg); + + auto volume_up_button = adc_button_[BSP_ADC_BUTTON_NEXT]; + volume_up_button->OnClick([this]() {ChangeVol(10);}); + volume_up_button->OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + auto volume_down_button = adc_button_[BSP_ADC_BUTTON_PREV]; + volume_down_button->OnClick([this]() {ChangeVol(-10);}); + volume_down_button->OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + + auto break_button = adc_button_[BSP_ADC_BUTTON_ENTER]; + break_button->OnClick([this]() {TogleState();}); + boot_button_.OnClick([this]() {TogleState();}); + } + + void InitializeIli9341Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_5; + io_config.dc_gpio_num = GPIO_NUM_4; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + const ili9341_vendor_config_t vendor_config = { + .init_cmds = &vendor_specific_init[0], + .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), + }; + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_48; + panel_config.flags.reset_active_high = 0, + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = (void *)&vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_disp_on_off(panel, true); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + EspBoxBoardLite() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeIli9341Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + ~EspBoxBoardLite() { + for (int i =0; i - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_45 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_45 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp-box/config.json b/main/boards/esp-box/config.json index 0ae7e20..e2aea1c 100644 --- a/main/boards/esp-box/config.json +++ b/main/boards/esp-box/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp-box", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp-box", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/esp-box/esp_box_board.cc b/main/boards/esp-box/esp_box_board.cc index 4dcbca8..e150b25 100644 --- a/main/boards/esp-box/esp_box_board.cc +++ b/main/boards/esp-box/esp_box_board.cc @@ -1,168 +1,168 @@ -#include "wifi_board.h" -#include "codecs/box_audio_codec.h" -#include "display/lcd_display.h" -#include "esp_lcd_ili9341.h" -#include "application.h" -#include "button.h" -#include "config.h" - -#include -#include -#include -#include -#include - -#define TAG "EspBoxBoard" - -// Init ili9341 by custom cmd -static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { - {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0}, - {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0}, - {0xC5, (uint8_t []){0xD0}, 1, 0}, - {0xC1, (uint8_t []){0x02}, 1, 0}, - {0xB4, (uint8_t []){0x02}, 1, 0}, - {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0}, - {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0}, - - {0xB1, (uint8_t []){00, 0x1B}, 2, 0}, - {0x36, (uint8_t []){0x08}, 1, 0}, - {0x3A, (uint8_t []){0x55}, 1, 0}, - {0xB7, (uint8_t []){0x06}, 1, 0}, - - {0x11, (uint8_t []){0}, 0x80, 0}, - {0x29, (uint8_t []){0}, 0x80, 0}, - - {0, (uint8_t []){0}, 0xff, 0}, -}; - -class EspBox3Board : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_6; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_7; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - -#if CONFIG_USE_DEVICE_AEC - boot_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateIdle) { - app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); - } - }); -#endif - } - - void InitializeIli9341Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_5; - io_config.dc_gpio_num = GPIO_NUM_4; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - const ili9341_vendor_config_t vendor_config = { - .init_cmds = &vendor_specific_init[0], - .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), - }; - - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_48; - panel_config.flags.reset_active_high = 0, - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = (void *)&vendor_config; - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - esp_lcd_panel_disp_on_off(panel, true); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeSpi(); - InitializeIli9341Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(EspBox3Board); +#include "wifi_board.h" +#include "codecs/box_audio_codec.h" +#include "display/lcd_display.h" +#include "esp_lcd_ili9341.h" +#include "application.h" +#include "button.h" +#include "config.h" + +#include +#include +#include +#include +#include + +#define TAG "EspBoxBoard" + +// Init ili9341 by custom cmd +static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { + {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0}, + {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0}, + {0xC5, (uint8_t []){0xD0}, 1, 0}, + {0xC1, (uint8_t []){0x02}, 1, 0}, + {0xB4, (uint8_t []){0x02}, 1, 0}, + {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0}, + {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0}, + + {0xB1, (uint8_t []){00, 0x1B}, 2, 0}, + {0x36, (uint8_t []){0x08}, 1, 0}, + {0x3A, (uint8_t []){0x55}, 1, 0}, + {0xB7, (uint8_t []){0x06}, 1, 0}, + + {0x11, (uint8_t []){0}, 0x80, 0}, + {0x29, (uint8_t []){0}, 0x80, 0}, + + {0, (uint8_t []){0}, 0xff, 0}, +}; + +class EspBox3Board : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_6; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_7; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif + } + + void InitializeIli9341Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_5; + io_config.dc_gpio_num = GPIO_NUM_4; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + const ili9341_vendor_config_t vendor_config = { + .init_cmds = &vendor_specific_init[0], + .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), + }; + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_48; + panel_config.flags.reset_active_high = 0, + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = (void *)&vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_disp_on_off(panel, true); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeIli9341Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(EspBox3Board); diff --git a/main/boards/esp-hi/README.md b/main/boards/esp-hi/README.md index 8921f1f..2880a36 100644 --- a/main/boards/esp-hi/README.md +++ b/main/boards/esp-hi/README.md @@ -1,51 +1,51 @@ -# ESP-Hi - -## 简介 - - - -ESP-Hi 是 ESP Friends 开源的一款基于 ESP32C3 的超**低成本** AI 对话机器人。ESP-Hi 集成了一个0.96寸的彩屏,用于显示表情,**机器狗已实现数十种动作**。通过对 ESP32-C3 外设的充分挖掘,仅需最少的板级硬件即可实现拾音和发声,同步优化了软件,降低内存与 Flash 占用,在资源受限的情况下同时实现了**唤醒词检测**与多种外设驱动。硬件详情等可查看[立创开源项目](https://oshwhub.com/esp-college/esp-hi)。 - -## WebUI - -ESP-Hi x 小智内置了一个控制身体运动的 WebUI,请将手机与 ESP-Hi 连接到同一个 Wi-Fi 下,手机访问 `http://esp-hi.local/` 以使用。 - -如需禁用,请取消 `ESP_HI_WEB_CONTROL_ENABLED`,即取消勾选 `Component config` → `Servo Dog Configuration` → `Web Control` → `Enable ESP-HI Web Control`。 - -## 配置、编译命令 - -由于 ESP-Hi 需要配置较多的 sdkconfig 选项,推荐使用编译脚本编译。 - -**编译** - -```bash -python ./scripts/release.py esp-hi -``` - -如需手动编译,请参考 `esp-hi/config.json` 修改 menuconfig 对应选项。 - -**烧录** - -```bash -idf.py flash -``` - - -> [!TIP] -> -> **舵机控制会占用 ESP-Hi 的 USB Type-C 接口**,导致无法连接电脑(无法烧录/查看运行日志)。如遇此情况,请按以下提示操作: -> -> **烧录** -> -> 1. 断开 ESP-Hi 的电源,只留头部,不要连接身体。 -> 2. 按住 ESP-Hi 的按钮并连接电脑。 -> -> 此时,ESP-Hi (ESP32C3) 应当处于烧录模式,可以使用电脑烧录程序。烧录完成后,可能需要重新插拔电源。 -> -> **查看 log** -> -> 请设置 `CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y`,即 `Component config` → `ESP System Settings` → `Channel for console output` 选择 `USB Serial/JTAG Controller`。这同时会禁用舵机控制功能。 +# ESP-Hi + +## 简介 + + + +ESP-Hi 是 ESP Friends 开源的一款基于 ESP32C3 的超**低成本** AI 对话机器人。ESP-Hi 集成了一个0.96寸的彩屏,用于显示表情,**机器狗已实现数十种动作**。通过对 ESP32-C3 外设的充分挖掘,仅需最少的板级硬件即可实现拾音和发声,同步优化了软件,降低内存与 Flash 占用,在资源受限的情况下同时实现了**唤醒词检测**与多种外设驱动。硬件详情等可查看[立创开源项目](https://oshwhub.com/esp-college/esp-hi)。 + +## WebUI + +ESP-Hi x 小智内置了一个控制身体运动的 WebUI,请将手机与 ESP-Hi 连接到同一个 Wi-Fi 下,手机访问 `http://esp-hi.local/` 以使用。 + +如需禁用,请取消 `ESP_HI_WEB_CONTROL_ENABLED`,即取消勾选 `Component config` → `Servo Dog Configuration` → `Web Control` → `Enable ESP-HI Web Control`。 + +## 配置、编译命令 + +由于 ESP-Hi 需要配置较多的 sdkconfig 选项,推荐使用编译脚本编译。 + +**编译** + +```bash +python ./scripts/release.py esp-hi +``` + +如需手动编译,请参考 `esp-hi/config.json` 修改 menuconfig 对应选项。 + +**烧录** + +```bash +idf.py flash +``` + + +> [!TIP] +> +> **舵机控制会占用 ESP-Hi 的 USB Type-C 接口**,导致无法连接电脑(无法烧录/查看运行日志)。如遇此情况,请按以下提示操作: +> +> **烧录** +> +> 1. 断开 ESP-Hi 的电源,只留头部,不要连接身体。 +> 2. 按住 ESP-Hi 的按钮并连接电脑。 +> +> 此时,ESP-Hi (ESP32C3) 应当处于烧录模式,可以使用电脑烧录程序。烧录完成后,可能需要重新插拔电源。 +> +> **查看 log** +> +> 请设置 `CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y`,即 `Component config` → `ESP System Settings` → `Channel for console output` 选择 `USB Serial/JTAG Controller`。这同时会禁用舵机控制功能。 diff --git a/main/boards/esp-hi/adc_pdm_audio_codec.cc b/main/boards/esp-hi/adc_pdm_audio_codec.cc index 3487bf9..a324b01 100644 --- a/main/boards/esp-hi/adc_pdm_audio_codec.cc +++ b/main/boards/esp-hi/adc_pdm_audio_codec.cc @@ -1,203 +1,249 @@ -#include "adc_pdm_audio_codec.h" - -#include -#include -#include -#include -#include "adc_mic.h" -#include "driver/i2s_pdm.h" -#include "soc/gpio_sig_map.h" -#include "soc/io_mux_reg.h" -#include "hal/rtc_io_hal.h" -#include "hal/gpio_ll.h" -#include "settings.h" - -static const char TAG[] = "AdcPdmAudioCodec"; - -#define BSP_I2S_GPIO_CFG(_dout) \ - { \ - .clk = GPIO_NUM_NC, \ - .dout = _dout, \ - .invert_flags = { \ - .clk_inv = false, \ - }, \ - } - -/** - * @brief Mono Duplex I2S configuration structure - * - * This configuration is used by default in bsp_audio_init() - */ -#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate, _dout) \ - { \ - .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \ - .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \ - .gpio_cfg = BSP_I2S_GPIO_CFG(_dout), \ - } - -AdcPdmAudioCodec::AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate, - uint32_t adc_mic_channel, gpio_num_t pdm_speak_p,gpio_num_t pdm_speak_n, gpio_num_t pa_ctl) { - - input_reference_ = false; - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - uint8_t adc_channel[1] = {0}; - adc_channel[0] = adc_mic_channel; - - audio_codec_adc_cfg_t cfg = { - .handle = NULL, - .max_store_buf_size = 1024 * 2, - .conv_frame_size = 1024, - .unit_id = ADC_UNIT_1, - .adc_channel_list = adc_channel, - .adc_channel_num = sizeof(adc_channel) / sizeof(adc_channel[0]), - .sample_rate_hz = (uint32_t)input_sample_rate, - }; - const audio_codec_data_if_t *adc_if = audio_codec_new_adc_data(&cfg); - - esp_codec_dev_cfg_t codec_dev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_IN, - .data_if = adc_if, - }; - input_dev_ = esp_codec_dev_new(&codec_dev_cfg); - if (!input_dev_) { - ESP_LOGE(TAG, "Failed to create codec device"); - return; - } - - i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); - chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, NULL)); - - i2s_pdm_tx_config_t pdm_cfg_default = BSP_I2S_DUPLEX_MONO_CFG((uint32_t)output_sample_rate, pdm_speak_p); - pdm_cfg_default.clk_cfg.up_sample_fs = output_sample_rate / 100; - pdm_cfg_default.slot_cfg.sd_scale = I2S_PDM_SIG_SCALING_MUL_4; - pdm_cfg_default.slot_cfg.hp_scale = I2S_PDM_SIG_SCALING_MUL_4; - pdm_cfg_default.slot_cfg.lp_scale = I2S_PDM_SIG_SCALING_MUL_4; - pdm_cfg_default.slot_cfg.sinc_scale = I2S_PDM_SIG_SCALING_MUL_4; - const i2s_pdm_tx_config_t *p_i2s_cfg = &pdm_cfg_default; - - ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_handle_, p_i2s_cfg)); - - audio_codec_i2s_cfg_t i2s_cfg = { - .port = I2S_NUM_0, - .rx_handle = NULL, - .tx_handle = tx_handle_, - }; - - const audio_codec_data_if_t *i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg); - - codec_dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_OUT; - codec_dev_cfg.codec_if = NULL; - codec_dev_cfg.data_if = i2s_data_if; - output_dev_ = esp_codec_dev_new(&codec_dev_cfg); - - output_volume_ = 100; - if(pa_ctl != GPIO_NUM_NC) { - pa_ctrl_pin_ = pa_ctl; - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = (1ULL << pa_ctrl_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - } - gpio_set_drive_capability(pdm_speak_p, GPIO_DRIVE_CAP_0); - - if(pdm_speak_n != GPIO_NUM_NC){ - PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pdm_speak_n], PIN_FUNC_GPIO); - gpio_set_direction(pdm_speak_n, GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(pdm_speak_n, I2SO_SD_OUT_IDX, 1, 0); //反转输出 SD OUT 信号 - gpio_set_drive_capability(pdm_speak_n, GPIO_DRIVE_CAP_0); - } - ESP_LOGI(TAG, "AdcPdmAudioCodec initialized"); -} - -AdcPdmAudioCodec::~AdcPdmAudioCodec() { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - esp_codec_dev_delete(output_dev_); - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - esp_codec_dev_delete(input_dev_); -} - -void AdcPdmAudioCodec::SetOutputVolume(int volume) { - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); - AudioCodec::SetOutputVolume(volume); -} - -void AdcPdmAudioCodec::EnableInput(bool enable) { - if (enable == input_enabled_) { - return; - } - if (enable) { - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), - .sample_rate = (uint32_t)input_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - } - AudioCodec::EnableInput(enable); -} - -void AdcPdmAudioCodec::EnableOutput(bool enable) { - if (enable == output_enabled_) { - return; - } - if (enable) { - // Play 16bit 1 channel - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); - if(pa_ctrl_pin_ != GPIO_NUM_NC){ - gpio_set_level(pa_ctrl_pin_, 1); - } - - } else { - if(pa_ctrl_pin_ != GPIO_NUM_NC){ - gpio_set_level(pa_ctrl_pin_, 0); - } - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - } - AudioCodec::EnableOutput(enable); -} - -int AdcPdmAudioCodec::Read(int16_t* dest, int samples) { - if (input_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); - } - return samples; -} -int AdcPdmAudioCodec::Write(const int16_t* data, int samples) { - if (output_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); - } - return samples; -} - -void AdcPdmAudioCodec::Start() { - Settings settings("audio", false); - output_volume_ = settings.GetInt("output_volume", output_volume_); - if (output_volume_ <= 0) { - ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_); - output_volume_ = 10; - } - - ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_)); - - EnableInput(true); - EnableOutput(true); - ESP_LOGI(TAG, "Audio codec started"); -} +#include "adc_pdm_audio_codec.h" + +#include +#include +#include +#include +#include +#include "adc_mic.h" +#include "driver/i2s_pdm.h" +#include "soc/gpio_sig_map.h" +#include "soc/io_mux_reg.h" +#include "hal/rtc_io_hal.h" +#include "hal/gpio_ll.h" +#include "settings.h" +#include "config.h" + +static const char TAG[] = "AdcPdmAudioCodec"; + +#define BSP_I2S_GPIO_CFG(_dout) \ + { \ + .clk = GPIO_NUM_NC, \ + .dout = _dout, \ + .invert_flags = { \ + .clk_inv = false, \ + }, \ + } + +/** + * @brief Mono Duplex I2S configuration structure + * + * This configuration is used by default in bsp_audio_init() + */ +#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate, _dout) \ + { \ + .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \ + .gpio_cfg = BSP_I2S_GPIO_CFG(_dout), \ + } + +AdcPdmAudioCodec::AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate, + uint32_t adc_mic_channel, gpio_num_t pdm_speak_p,gpio_num_t pdm_speak_n, gpio_num_t pa_ctl) { + + input_reference_ = false; + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + uint8_t adc_channel[1] = {0}; + adc_channel[0] = adc_mic_channel; + + audio_codec_adc_cfg_t cfg = { + .handle = NULL, + .max_store_buf_size = 1024 * 2, + .conv_frame_size = 1024, + .unit_id = ADC_UNIT_1, + .adc_channel_list = adc_channel, + .adc_channel_num = sizeof(adc_channel) / sizeof(adc_channel[0]), + .sample_rate_hz = (uint32_t)input_sample_rate, + }; + const audio_codec_data_if_t *adc_if = audio_codec_new_adc_data(&cfg); + + esp_codec_dev_cfg_t codec_dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_IN, + .data_if = adc_if, + }; + input_dev_ = esp_codec_dev_new(&codec_dev_cfg); + if (!input_dev_) { + ESP_LOGE(TAG, "Failed to create codec device"); + return; + } + + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, NULL)); + + i2s_pdm_tx_config_t pdm_cfg_default = BSP_I2S_DUPLEX_MONO_CFG((uint32_t)output_sample_rate, pdm_speak_p); + pdm_cfg_default.clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS; + pdm_cfg_default.slot_cfg.sd_scale = I2S_PDM_SIG_SCALING_MUL_4; + pdm_cfg_default.slot_cfg.hp_scale = I2S_PDM_SIG_SCALING_MUL_4; + pdm_cfg_default.slot_cfg.lp_scale = I2S_PDM_SIG_SCALING_MUL_4; + pdm_cfg_default.slot_cfg.sinc_scale = I2S_PDM_SIG_SCALING_MUL_4; + const i2s_pdm_tx_config_t *p_i2s_cfg = &pdm_cfg_default; + + ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_handle_, p_i2s_cfg)); + + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = NULL, + .tx_handle = tx_handle_, + }; + + const audio_codec_data_if_t *i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg); + + codec_dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_OUT; + codec_dev_cfg.codec_if = NULL; + codec_dev_cfg.data_if = i2s_data_if; + output_dev_ = esp_codec_dev_new(&codec_dev_cfg); + + output_volume_ = 100; + if(pa_ctl != GPIO_NUM_NC) { + pa_ctrl_pin_ = pa_ctl; + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << pa_ctrl_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + } + gpio_set_drive_capability(pdm_speak_p, GPIO_DRIVE_CAP_0); + + if(pdm_speak_n != GPIO_NUM_NC){ + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pdm_speak_n], PIN_FUNC_GPIO); + gpio_set_direction(pdm_speak_n, GPIO_MODE_OUTPUT); + esp_rom_gpio_connect_out_signal(pdm_speak_n, I2SO_SD_OUT_IDX, 1, 0); //反转输出 SD OUT 信号 + gpio_set_drive_capability(pdm_speak_n, GPIO_DRIVE_CAP_0); + } + + // 初始化输出定时器 + esp_timer_create_args_t output_timer_args = { + .callback = &AdcPdmAudioCodec::OutputTimerCallback, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "output_timer" + }; + ESP_ERROR_CHECK(esp_timer_create(&output_timer_args, &output_timer_)); + + ESP_LOGI(TAG, "AdcPdmAudioCodec initialized"); +} + +AdcPdmAudioCodec::~AdcPdmAudioCodec() { + // 删除定时器 + if (output_timer_) { + esp_timer_stop(output_timer_); + esp_timer_delete(output_timer_); + output_timer_ = nullptr; + } + + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + esp_codec_dev_delete(output_dev_); + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + esp_codec_dev_delete(input_dev_); +} + +void AdcPdmAudioCodec::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void AdcPdmAudioCodec::EnableInput(bool enable) { + if (enable == input_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), + .sample_rate = (uint32_t)input_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void AdcPdmAudioCodec::EnableOutput(bool enable) { + if (enable == output_enabled_) { + return; + } + if (enable) { + // Play 16bit 1 channel + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); + + // 强制按板卡配置重配PDM TX时钟,覆盖第三方库在set_fmt中的默认up_sample_fs + // 若通道已启用,先禁用再重配,最后再启用 + ESP_ERROR_CHECK_WITHOUT_ABORT(i2s_channel_disable(tx_handle_)); + i2s_pdm_tx_clk_config_t clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG((uint32_t)output_sample_rate_); + clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS; + ESP_ERROR_CHECK(i2s_channel_reconfig_pdm_tx_clock(tx_handle_, &clk_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_)); + if(pa_ctrl_pin_ != GPIO_NUM_NC){ + gpio_set_level(pa_ctrl_pin_, 1); + } + // 启用输出时启动定时器 + if (output_timer_) { + esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US); + } + + } else { + // 禁用输出时停止定时器 + if (output_timer_) { + esp_timer_stop(output_timer_); + } + if(pa_ctrl_pin_ != GPIO_NUM_NC){ + gpio_set_level(pa_ctrl_pin_, 0); + } + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + } + AudioCodec::EnableOutput(enable); +} + +int AdcPdmAudioCodec::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} +int AdcPdmAudioCodec::Write(const int16_t* data, int samples) { + if (output_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); + // 重置输出定时器 + if (output_timer_) { + esp_timer_stop(output_timer_); + esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US); + } + } + return samples; +} + +void AdcPdmAudioCodec::Start() { + Settings settings("audio", false); + output_volume_ = settings.GetInt("output_volume", output_volume_); + if (output_volume_ <= 0) { + ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_); + output_volume_ = 10; + } + + EnableInput(true); + EnableOutput(true); + ESP_LOGI(TAG, "Audio codec started"); +} + +// 定时器回调函数实现 +void AdcPdmAudioCodec::OutputTimerCallback(void* arg) { + AdcPdmAudioCodec* codec = static_cast(arg); + if (codec && codec->output_enabled_) { + codec->EnableOutput(false); + } +} diff --git a/main/boards/esp-hi/adc_pdm_audio_codec.h b/main/boards/esp-hi/adc_pdm_audio_codec.h index cab1a2b..ddf2369 100644 --- a/main/boards/esp-hi/adc_pdm_audio_codec.h +++ b/main/boards/esp-hi/adc_pdm_audio_codec.h @@ -1,29 +1,37 @@ -#ifndef _BOX_AUDIO_CODEC_H -#define _BOX_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include - -class AdcPdmAudioCodec : public AudioCodec { -private: - esp_codec_dev_handle_t output_dev_ = nullptr; - esp_codec_dev_handle_t input_dev_ = nullptr; - gpio_num_t pa_ctrl_pin_ = GPIO_NUM_NC; - - virtual int Read(int16_t* dest, int samples) override; - virtual int Write(const int16_t* data, int samples) override; - -public: - AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate, - uint32_t adc_mic_channel, gpio_num_t pdm_speak_p, gpio_num_t pdm_speak_n, gpio_num_t pa_ctl); - virtual ~AdcPdmAudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; - void Start(); -}; - -#endif // _BOX_AUDIO_CODEC_H +#ifndef _BOX_AUDIO_CODEC_H +#define _BOX_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include +#include + +class AdcPdmAudioCodec : public AudioCodec { +private: + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + gpio_num_t pa_ctrl_pin_ = GPIO_NUM_NC; + + // 定时器相关成员变量 + esp_timer_handle_t output_timer_ = nullptr; + static constexpr uint64_t TIMER_TIMEOUT_US = 120000; // 120ms = 120000us + + // 定时器回调函数 + static void OutputTimerCallback(void* arg); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate, + uint32_t adc_mic_channel, gpio_num_t pdm_speak_p, gpio_num_t pdm_speak_n, gpio_num_t pa_ctl); + virtual ~AdcPdmAudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; + void Start(); +}; + +#endif // _BOX_AUDIO_CODEC_H diff --git a/main/boards/esp-hi/config.h b/main/boards/esp-hi/config.h index 9ea9ba7..7cea46d 100644 --- a/main/boards/esp-hi/config.h +++ b/main/boards/esp-hi/config.h @@ -1,44 +1,47 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_ADC_MIC_CHANNEL 2 -#define AUDIO_PDM_SPEAK_P_GPIO GPIO_NUM_6 -#define AUDIO_PDM_SPEAK_N_GPIO GPIO_NUM_7 -#define AUDIO_PA_CTL_GPIO GPIO_NUM_3 - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_9 -#define MOVE_WAKE_BUTTON_GPIO GPIO_NUM_0 -#define AUDIO_WAKE_BUTTON_GPIO GPIO_NUM_1 - -#define DISPLAY_MOSI_PIN GPIO_NUM_4 -#define DISPLAY_CLK_PIN GPIO_NUM_5 -#define DISPLAY_DC_PIN GPIO_NUM_10 -#define DISPLAY_RST_PIN GPIO_NUM_NC -#define DISPLAY_CS_PIN GPIO_NUM_NC - -#define FL_GPIO_NUM GPIO_NUM_21 -#define FR_GPIO_NUM GPIO_NUM_19 -#define BL_GPIO_NUM GPIO_NUM_20 -#define BR_GPIO_NUM GPIO_NUM_18 - -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 160 -#define DISPLAY_HEIGHT 80 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY true - -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +// 配置PDM上采样fs参数(取值范围<=480)。部分设备在441时表现更稳定 +#define AUDIO_PDM_UPSAMPLE_FS 441 + +#define AUDIO_ADC_MIC_CHANNEL 2 +#define AUDIO_PDM_SPEAK_P_GPIO GPIO_NUM_6 +#define AUDIO_PDM_SPEAK_N_GPIO GPIO_NUM_7 +#define AUDIO_PA_CTL_GPIO GPIO_NUM_3 + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_9 +#define MOVE_WAKE_BUTTON_GPIO GPIO_NUM_0 +#define AUDIO_WAKE_BUTTON_GPIO GPIO_NUM_1 + +#define DISPLAY_MOSI_PIN GPIO_NUM_4 +#define DISPLAY_CLK_PIN GPIO_NUM_5 +#define DISPLAY_DC_PIN GPIO_NUM_10 +#define DISPLAY_RST_PIN GPIO_NUM_NC +#define DISPLAY_CS_PIN GPIO_NUM_NC + +#define FL_GPIO_NUM GPIO_NUM_21 +#define FR_GPIO_NUM GPIO_NUM_19 +#define BL_GPIO_NUM GPIO_NUM_20 +#define BR_GPIO_NUM GPIO_NUM_18 + +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 160 +#define DISPLAY_HEIGHT 80 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY true + +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp-hi/config.json b/main/boards/esp-hi/config.json index e58704b..7d7a3b3 100644 --- a/main/boards/esp-hi/config.json +++ b/main/boards/esp-hi/config.json @@ -1,35 +1,34 @@ -{ - "target": "esp32c3", - "builds": [ - { - "name": "esp-hi", - "sdkconfig_append": [ - "CONFIG_IDF_TARGET=\"esp32c3\"", - "CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m_esp-hi.csv\"", - "CONFIG_BOARD_TYPE_ESP_HI=y", - "CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3", - "CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=4", - "CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n", - "CONFIG_ESP_WIFI_RX_BA_WIN=4", - "CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n", - "CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0", - "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n", - "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y", - "CONFIG_ESP_MAIN_TASK_STACK_SIZE=7168", - "CONFIG_FREERTOS_HZ=1000", - "CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768", - "CONFIG_LWIP_MAX_SOCKETS=10", - "CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16", - "CONFIG_LWIP_IPV6=n", - "CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048", - "CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y", - "CONFIG_NEWLIB_NANO_FORMAT=y", - "CONFIG_MMAP_FILE_NAME_LENGTH=25", - "CONFIG_ESP_CONSOLE_NONE=y", - "CONFIG_USE_ESP_WAKE_WORD=y", - "CONFIG_COMPILER_OPTIMIZATION_SIZE=y" - ] - } - ] -} +{ + "target": "esp32c3", + "builds": [ + { + "name": "esp-hi", + "sdkconfig_append": [ + "CONFIG_IDF_TARGET=\"esp32c3\"", + "CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/4m.csv\"", + "CONFIG_BOARD_TYPE_ESP_HI=y", + "CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3", + "CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=4", + "CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n", + "CONFIG_ESP_WIFI_RX_BA_WIN=4", + "CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n", + "CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0", + "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n", + "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y", + "CONFIG_ESP_MAIN_TASK_STACK_SIZE=7168", + "CONFIG_FREERTOS_HZ=1000", + "CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768", + "CONFIG_LWIP_MAX_SOCKETS=10", + "CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16", + "CONFIG_LWIP_IPV6=n", + "CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048", + "CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y", + "CONFIG_NEWLIB_NANO_FORMAT=y", + "CONFIG_ESP_CONSOLE_NONE=y", + "CONFIG_USE_ESP_WAKE_WORD=y", + "CONFIG_COMPILER_OPTIMIZATION_SIZE=y" + ] + } + ] +} diff --git a/main/boards/esp-hi/emoji_display.cc b/main/boards/esp-hi/emoji_display.cc index 3a08987..e0d867d 100644 --- a/main/boards/esp-hi/emoji_display.cc +++ b/main/boards/esp-hi/emoji_display.cc @@ -1,172 +1,178 @@ -#include -#include "display/lcd_display.h" -#include -#include "mmap_generate_emoji.h" -#include "emoji_display.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include - -static const char *TAG = "emoji"; - -namespace anim { - -bool EmojiPlayer::OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) -{ - auto* disp_drv = static_cast(user_ctx); - anim_player_flush_ready(disp_drv); - return true; -} - -void EmojiPlayer::OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data) -{ - auto* panel = static_cast(anim_player_get_user_data(handle)); - esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data); -} - -EmojiPlayer::EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) -{ - ESP_LOGI(TAG, "Create EmojiPlayer, panel: %p, panel_io: %p", panel, panel_io); - const mmap_assets_config_t assets_cfg = { - .partition_label = "assets_A", - .max_files = MMAP_EMOJI_FILES, - .checksum = MMAP_EMOJI_CHECKSUM, - .flags = {.mmap_enable = true, .full_check = true} - }; - - mmap_assets_new(&assets_cfg, &assets_handle_); - - anim_player_config_t player_cfg = { - .flush_cb = OnFlush, - .update_cb = NULL, - .user_data = panel, - .flags = {.swap = true}, - .task = ANIM_PLAYER_INIT_CONFIG() - }; - - player_handle_ = anim_player_init(&player_cfg); - - const esp_lcd_panel_io_callbacks_t cbs = { - .on_color_trans_done = OnFlushIoReady, - }; - esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, player_handle_); - StartPlayer(MMAP_EMOJI_CONNECTING_AAF, true, 15); -} - -EmojiPlayer::~EmojiPlayer() -{ - if (player_handle_) { - anim_player_update(player_handle_, PLAYER_ACTION_STOP); - anim_player_deinit(player_handle_); - player_handle_ = nullptr; - } - - if (assets_handle_) { - mmap_assets_del(assets_handle_); - assets_handle_ = NULL; - } -} - -void EmojiPlayer::StartPlayer(int aaf, bool repeat, int fps) -{ - if (player_handle_) { - uint32_t start, end; - const void *src_data; - size_t src_len; - - src_data = mmap_assets_get_mem(assets_handle_, aaf); - src_len = mmap_assets_get_size(assets_handle_, aaf); - - anim_player_set_src_data(player_handle_, src_data, src_len); - anim_player_get_segment(player_handle_, &start, &end); - if(MMAP_EMOJI_WAKE_AAF == aaf){ - start = 7; - } - anim_player_set_segment(player_handle_, start, end, fps, true); - anim_player_update(player_handle_, PLAYER_ACTION_START); - } -} - -void EmojiPlayer::StopPlayer() -{ - if (player_handle_) { - anim_player_update(player_handle_, PLAYER_ACTION_STOP); - } -} - -EmojiWidget::EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) -{ - InitializePlayer(panel, panel_io); -} - -EmojiWidget::~EmojiWidget() -{ - -} - -void EmojiWidget::SetEmotion(const char* emotion) -{ - if (!player_) { - return; - } - - using Param = std::tuple; - static const std::unordered_map emotion_map = { - {"happy", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, - {"laughing", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, - {"funny", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, - {"loving", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, - {"embarrassed", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, - {"confident", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, - {"delicious", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, - {"sad", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}}, - {"crying", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}}, - {"sleepy", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}}, - {"silly", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}}, - {"angry", {MMAP_EMOJI_ANGER_LOOP_AAF, true, 25}}, - {"surprised", {MMAP_EMOJI_PANIC_LOOP_AAF, true, 25}}, - {"shocked", {MMAP_EMOJI_PANIC_LOOP_AAF, true, 25}}, - {"thinking", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, - {"winking", {MMAP_EMOJI_BLINK_QUICK_AAF, true, 5}}, - {"relaxed", {MMAP_EMOJI_SCORN_LOOP_AAF, true, 25}}, - {"confused", {MMAP_EMOJI_SCORN_LOOP_AAF, true, 25}}, - }; - - auto it = emotion_map.find(emotion); - if (it != emotion_map.end()) { - const auto& [aaf, repeat, fps] = it->second; - player_->StartPlayer(aaf, repeat, fps); - } else if (strcmp(emotion, "neutral") == 0) { - } -} - -void EmojiWidget::SetStatus(const char* status) -{ - if (player_) { - if (strcmp(status, Lang::Strings::LISTENING) == 0) { - player_->StartPlayer(MMAP_EMOJI_ASKING_AAF, true, 15); - } else if (strcmp(status, Lang::Strings::STANDBY) == 0) { - player_->StartPlayer(MMAP_EMOJI_WAKE_AAF, true, 15); - } - } -} - -void EmojiWidget::InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) -{ - player_ = std::make_unique(panel, panel_io); -} - -bool EmojiWidget::Lock(int timeout_ms) -{ - return true; -} - -void EmojiWidget::Unlock() -{ -} - -} // namespace anim +#include +#include "display/lcd_display.h" +#include +#include "emoji_display.h" +#include "assets/lang_config.h" +#include "assets.h" + +#include +#include +#include +#include +#include + +static const char *TAG = "emoji"; + +namespace anim { + +// Emoji asset name mapping based on usage pattern +static const std::unordered_map emoji_asset_name_map = { + {"connecting", "connecting.aaf"}, + {"wake", "wake.aaf"}, + {"asking", "asking.aaf"}, + {"happy_loop", "happy_loop.aaf"}, + {"sad_loop", "sad_loop.aaf"}, + {"anger_loop", "anger_loop.aaf"}, + {"panic_loop", "panic_loop.aaf"}, + {"blink_quick", "blink_quick.aaf"}, + {"scorn_loop", "scorn_loop.aaf"} +}; + +bool EmojiPlayer::OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) +{ + auto* disp_drv = static_cast(user_ctx); + anim_player_flush_ready(disp_drv); + return true; +} + +void EmojiPlayer::OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + auto* panel = static_cast(anim_player_get_user_data(handle)); + esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data); +} + +EmojiPlayer::EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) +{ + ESP_LOGI(TAG, "Create EmojiPlayer, panel: %p, panel_io: %p", panel, panel_io); + + anim_player_config_t player_cfg = { + .flush_cb = OnFlush, + .update_cb = NULL, + .user_data = panel, + .flags = {.swap = true}, + .task = ANIM_PLAYER_INIT_CONFIG() + }; + + player_cfg.task.task_priority = 1; + player_cfg.task.task_stack = 4096; + player_handle_ = anim_player_init(&player_cfg); + + const esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = OnFlushIoReady, + }; + esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, player_handle_); + StartPlayer("connecting", true, 15); +} + +EmojiPlayer::~EmojiPlayer() +{ + if (player_handle_) { + anim_player_update(player_handle_, PLAYER_ACTION_STOP); + anim_player_deinit(player_handle_); + player_handle_ = nullptr; + } +} + +void EmojiPlayer::StartPlayer(const std::string& asset_name, bool repeat, int fps) +{ + if (player_handle_) { + uint32_t start, end; + void *src_data = nullptr; + size_t src_len = 0; + + auto& assets = Assets::GetInstance(); + std::string filename = emoji_asset_name_map.at(asset_name); + if (!assets.GetAssetData(filename, src_data, src_len)) { + ESP_LOGE(TAG, "Failed to get asset data for %s", asset_name.c_str()); + return; + } + + anim_player_set_src_data(player_handle_, src_data, src_len); + anim_player_get_segment(player_handle_, &start, &end); + if(asset_name == "wake"){ + start = 7; + } + anim_player_set_segment(player_handle_, start, end, fps, true); + anim_player_update(player_handle_, PLAYER_ACTION_START); + } +} + +void EmojiPlayer::StopPlayer() +{ + if (player_handle_) { + anim_player_update(player_handle_, PLAYER_ACTION_STOP); + } +} + +EmojiWidget::EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) +{ + InitializePlayer(panel, panel_io); +} + +EmojiWidget::~EmojiWidget() +{ + +} + +void EmojiWidget::SetEmotion(const char* emotion) +{ + if (!player_) { + return; + } + + using Param = std::tuple; + static const std::unordered_map emotion_map = { + {"happy", {"happy_loop", true, 25}}, + {"laughing", {"happy_loop", true, 25}}, + {"funny", {"happy_loop", true, 25}}, + {"loving", {"happy_loop", true, 25}}, + {"embarrassed", {"happy_loop", true, 25}}, + {"confident", {"happy_loop", true, 25}}, + {"delicious", {"happy_loop", true, 25}}, + {"sad", {"sad_loop", true, 25}}, + {"crying", {"sad_loop", true, 25}}, + {"sleepy", {"sad_loop", true, 25}}, + {"silly", {"sad_loop", true, 25}}, + {"angry", {"anger_loop", true, 25}}, + {"surprised", {"panic_loop", true, 25}}, + {"shocked", {"panic_loop", true, 25}}, + {"thinking", {"happy_loop", true, 25}}, + {"winking", {"blink_quick", true, 5}}, + {"relaxed", {"scorn_loop", true, 25}}, + {"confused", {"scorn_loop", true, 25}}, + }; + + auto it = emotion_map.find(emotion); + if (it != emotion_map.end()) { + const auto& [aaf, repeat, fps] = it->second; + player_->StartPlayer(aaf, repeat, fps); + } else if (strcmp(emotion, "neutral") == 0) { + } +} + +void EmojiWidget::SetStatus(const char* status) +{ + if (player_) { + if (strcmp(status, Lang::Strings::LISTENING) == 0) { + player_->StartPlayer("asking", true, 15); + } else if (strcmp(status, Lang::Strings::STANDBY) == 0) { + player_->StartPlayer("wake", true, 15); + } + } +} + +void EmojiWidget::InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) +{ + player_ = std::make_unique(panel, panel_io); +} + +bool EmojiWidget::Lock(int timeout_ms) +{ + return true; +} + +void EmojiWidget::Unlock() +{ +} + +} // namespace anim diff --git a/main/boards/esp-hi/emoji_display.h b/main/boards/esp-hi/emoji_display.h index 2d29f62..cf0c674 100644 --- a/main/boards/esp-hi/emoji_display.h +++ b/main/boards/esp-hi/emoji_display.h @@ -1,54 +1,55 @@ -#pragma once - -#include "display/lcd_display.h" -#include -#include -#include -#include -#include "anim_player.h" -#include "mmap_generate_emoji.h" - -namespace anim { - -class EmojiPlayer; - -using FlushIoReadyCallback = std::function; -using FlushCallback = std::function; - -class EmojiPlayer { -public: - EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); - ~EmojiPlayer(); - - void StartPlayer(int aaf, bool repeat, int fps); - void StopPlayer(); - -private: - static bool OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); - static void OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data); - - anim_player_handle_t player_handle_; - mmap_assets_handle_t assets_handle_; -}; - -class EmojiWidget : public Display { -public: - EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); - virtual ~EmojiWidget(); - - virtual void SetEmotion(const char* emotion) override; - virtual void SetStatus(const char* status) override; - anim::EmojiPlayer* GetPlayer() - { - return player_.get(); - } - -private: - void InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); - virtual bool Lock(int timeout_ms = 0) override; - virtual void Unlock() override; - - std::unique_ptr player_; -}; - -} // namespace anim +#pragma once + +#include "display/lcd_display.h" +#include +#include +#include +#include +#include "anim_player.h" +#include "assets.h" + +namespace anim { + +class EmojiPlayer; + +using FlushIoReadyCallback = std::function; +using FlushCallback = std::function; + +class EmojiPlayer { +public: + EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); + ~EmojiPlayer(); + + void StartPlayer(const std::string& asset_name, bool repeat, int fps); + void StopPlayer(); + +private: + static bool OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); + static void OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data); + + anim_player_handle_t player_handle_; +}; + +class EmojiWidget : public Display { +public: + EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); + virtual ~EmojiWidget(); + + virtual void SetEmotion(const char* emotion) override; + virtual void SetStatus(const char* status) override; + virtual void SetChatMessage(const char* role, const char* content) override {} + + anim::EmojiPlayer* GetPlayer() + { + return player_.get(); + } + +private: + void InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); + virtual bool Lock(int timeout_ms = 0) override; + virtual void Unlock() override; + + std::unique_ptr player_; +}; + +} // namespace anim diff --git a/main/boards/esp-hi/esp_hi.cc b/main/boards/esp-hi/esp_hi.cc index ed47b80..16c11c9 100644 --- a/main/boards/esp-hi/esp_hi.cc +++ b/main/boards/esp-hi/esp_hi.cc @@ -1,425 +1,420 @@ -#include "wifi_board.h" -#include "adc_pdm_audio_codec.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "mcp_server.h" -#include -#include -#include -#include -#include -#include - -#include "display/lcd_display.h" -#include -#include -#include -#include "esp_lcd_ili9341.h" - -#include "assets/lang_config.h" -#include "anim_player.h" -#include "emoji_display.h" -#include "servo_dog_ctrl.h" -#include "led_strip.h" -#include "driver/rmt_tx.h" -#include "device_state_event.h" - -#include "sdkconfig.h" - -#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED -#include "esp_hi_web_control.h" -#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED - -#define TAG "ESP_HI" - -static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { - {0x11, NULL, 0, 120}, // Sleep out, Delay 120ms - {0xB1, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0}, - {0xB2, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0}, - {0xB3, (uint8_t []){0x05, 0x3A, 0x3A, 0x05, 0x3A, 0x3A}, 6, 0}, - {0xB4, (uint8_t []){0x03}, 1, 0}, // Dot inversion - {0xC0, (uint8_t []){0x44, 0x04, 0x04}, 3, 0}, - {0xC1, (uint8_t []){0xC0}, 1, 0}, - {0xC2, (uint8_t []){0x0D, 0x00}, 2, 0}, - {0xC3, (uint8_t []){0x8D, 0x6A}, 2, 0}, - {0xC4, (uint8_t []){0x8D, 0xEE}, 2, 0}, - {0xC5, (uint8_t []){0x08}, 1, 0}, - {0xE0, (uint8_t []){0x0F, 0x10, 0x03, 0x03, 0x07, 0x02, 0x00, 0x02, 0x07, 0x0C, 0x13, 0x38, 0x0A, 0x0E, 0x03, 0x10}, 16, 0}, - {0xE1, (uint8_t []){0x10, 0x0B, 0x04, 0x04, 0x10, 0x03, 0x00, 0x03, 0x03, 0x09, 0x17, 0x33, 0x0B, 0x0C, 0x06, 0x10}, 16, 0}, - {0x35, (uint8_t []){0x00}, 1, 0}, - {0x3A, (uint8_t []){0x05}, 1, 0}, - {0x36, (uint8_t []){0xC8}, 1, 0}, - {0x29, NULL, 0, 0}, // Display on - {0x2C, NULL, 0, 0}, // Memory write -}; - -static const led_strip_config_t bsp_strip_config = { - .strip_gpio_num = GPIO_NUM_8, - .max_leds = 4, - .led_model = LED_MODEL_WS2812, - .flags = { - .invert_out = false - } -}; - -static const led_strip_rmt_config_t bsp_rmt_config = { - .clk_src = RMT_CLK_SRC_DEFAULT, - .resolution_hz = 10 * 1000 * 1000, - .flags = { - .with_dma = false - } -}; - -class EspHi : public WifiBoard { -private: - Button boot_button_; - Button audio_wake_button_; - Button move_wake_button_; - anim::EmojiWidget* display_ = nullptr; - bool web_server_initialized_ = false; - led_strip_handle_t led_strip_; - bool led_on_ = false; - -#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED - static void wifi_event_handler(void* arg, esp_event_base_t event_base, - int32_t event_id, void* event_data) - { - if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { - - xTaskCreate( - [](void* arg) { - EspHi* instance = static_cast(arg); - - vTaskDelay(5000 / portTICK_PERIOD_MS); - - if (!instance->web_server_initialized_) { - ESP_LOGI(TAG, "WiFi connected, init web control server"); - esp_err_t err = esp_hi_web_control_server_init(); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize web control server: %d", err); - } else { - ESP_LOGI(TAG, "Web control server initialized"); - instance->web_server_initialized_ = true; - } - } - - vTaskDelete(NULL); - }, - "web_server_init", - 1024 * 10, arg, 5, nullptr); - } - } -#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED - - void HandleMoveWakePressDown(int64_t current_time, int64_t &last_trigger_time, int &gesture_state) - { - int64_t interval = last_trigger_time == 0 ? 0 : current_time - last_trigger_time; - last_trigger_time = current_time; - - if (interval > 1000) { - gesture_state = 0; - } else { - switch (gesture_state) { - case 0: - break; - case 1: - if (interval > 300) { - gesture_state = 2; - } - break; - case 2: - if (interval > 100) { - gesture_state = 0; - } - break; - } - } - } - - void HandleMoveWakePressUp(int64_t current_time, int64_t &last_trigger_time, int &gesture_state) - { - int64_t interval = current_time - last_trigger_time; - - if (interval > 1000) { - gesture_state = 0; - } else { - switch (gesture_state) { - case 0: - if (interval > 300) { - gesture_state = 1; - } - break; - case 1: - break; - case 2: - if (interval < 100) { - ESP_LOGI(TAG, "gesture detected"); - gesture_state = 0; - auto &app = Application::GetInstance(); - app.ToggleChatState(); - } - break; - } - } - } - - void InitializeButtons() - { - static int64_t last_trigger_time = 0; - static int gesture_state = 0; // 0: init, 1: wait second long interval, 2: wait oscillation - - boot_button_.OnClick([this]() { - auto &app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - - audio_wake_button_.OnPressDown([this]() { - }); - - audio_wake_button_.OnPressUp([this]() { - }); - - move_wake_button_.OnPressDown([this]() { - int64_t current_time = esp_timer_get_time() / 1000; - HandleMoveWakePressDown(current_time, last_trigger_time, gesture_state); - }); - - move_wake_button_.OnPressUp([this]() { - int64_t current_time = esp_timer_get_time() / 1000; - HandleMoveWakePressUp(current_time, last_trigger_time, gesture_state); - }); - } - - void InitializeLed() { - ESP_LOGI(TAG, "BLINK_GPIO setting %d", bsp_strip_config.strip_gpio_num); - - ESP_ERROR_CHECK(led_strip_new_rmt_device(&bsp_strip_config, &bsp_rmt_config, &led_strip_)); - led_strip_set_pixel(led_strip_, 0, 0x00, 0x00, 0x00); - led_strip_set_pixel(led_strip_, 1, 0x00, 0x00, 0x00); - led_strip_set_pixel(led_strip_, 2, 0x00, 0x00, 0x00); - led_strip_set_pixel(led_strip_, 3, 0x00, 0x00, 0x00); - led_strip_refresh(led_strip_); - } - - esp_err_t SetLedColor(uint8_t r, uint8_t g, uint8_t b) { - esp_err_t ret = ESP_OK; - - ret |= led_strip_set_pixel(led_strip_, 0, r, g, b); - ret |= led_strip_set_pixel(led_strip_, 1, r, g, b); - ret |= led_strip_set_pixel(led_strip_, 2, r, g, b); - ret |= led_strip_set_pixel(led_strip_, 3, r, g, b); - ret |= led_strip_refresh(led_strip_); - return ret; - } - - void InitializeIot() - { - ESP_LOGI(TAG, "Initialize Iot"); - InitializeLed(); - SetLedColor(0x00, 0x00, 0x00); - -#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED - ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, - &wifi_event_handler, this)); -#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED - } - - void InitializeSpi() - { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_CLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * 10 * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeLcdDisplay() - { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = DISPLAY_SPI_MODE; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - const ili9341_vendor_config_t vendor_config = { - .init_cmds = &vendor_specific_init[0], - .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), - }; - - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = (void *) &vendor_config; - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_invert_color(panel, false); - esp_lcd_panel_set_gap(panel, 0, 24); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - ESP_LOGI(TAG, "LCD panel create success, %p", panel); - - esp_lcd_panel_disp_on_off(panel, true); - - ESP_LOGI(TAG, "Create emoji widget, panel: %p, panel_io: %p", panel, panel_io); - display_ = new anim::EmojiWidget(panel, panel_io); - -#if CONFIG_ESP_CONSOLE_NONE - servo_dog_ctrl_config_t config = { - .fl_gpio_num = FL_GPIO_NUM, - .fr_gpio_num = FR_GPIO_NUM, - .bl_gpio_num = BL_GPIO_NUM, - .br_gpio_num = BR_GPIO_NUM, - }; - - servo_dog_ctrl_init(&config); -#endif - } - - void InitializeTools() - { - auto& mcp_server = McpServer::GetInstance(); - - // 基础动作控制 - mcp_server.AddTool("self.dog.basic_control", "机器人的基础动作。机器人可以做以下基础动作:\n" - "forward: 向前移动\nbackward: 向后移动\nturn_left: 向左转\nturn_right: 向右转\nstop: 立即停止当前动作", - PropertyList({ - Property("action", kPropertyTypeString), - }), [this](const PropertyList& properties) -> ReturnValue { - const std::string& action = properties["action"].value(); - if (action == "forward") { - servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL); - } else if (action == "backward") { - servo_dog_ctrl_send(DOG_STATE_BACKWARD, NULL); - } else if (action == "turn_left") { - servo_dog_ctrl_send(DOG_STATE_TURN_LEFT, NULL); - } else if (action == "turn_right") { - servo_dog_ctrl_send(DOG_STATE_TURN_RIGHT, NULL); - } else if (action == "stop") { - servo_dog_ctrl_send(DOG_STATE_IDLE, NULL); - } else { - return false; - } - return true; - }); - - // 扩展动作控制 - mcp_server.AddTool("self.dog.advanced_control", "机器人的扩展动作。机器人可以做以下扩展动作:\n" - "sway_back_forth: 前后摇摆\nlay_down: 趴下\nsway: 左右摇摆\nretract_legs: 收回腿部\n" - "shake_hand: 握手\nshake_back_legs: 伸懒腰\njump_forward: 向前跳跃", - PropertyList({ - Property("action", kPropertyTypeString), - }), [this](const PropertyList& properties) -> ReturnValue { - const std::string& action = properties["action"].value(); - if (action == "sway_back_forth") { - servo_dog_ctrl_send(DOG_STATE_SWAY_BACK_FORTH, NULL); - } else if (action == "lay_down") { - servo_dog_ctrl_send(DOG_STATE_LAY_DOWN, NULL); - } else if (action == "sway") { - dog_action_args_t args = { - .repeat_count = 4, - }; - servo_dog_ctrl_send(DOG_STATE_SWAY, &args); - } else if (action == "retract_legs") { - servo_dog_ctrl_send(DOG_STATE_RETRACT_LEGS, NULL); - } else if (action == "shake_hand") { - servo_dog_ctrl_send(DOG_STATE_SHAKE_HAND, NULL); - } else if (action == "shake_back_legs") { - servo_dog_ctrl_send(DOG_STATE_SHAKE_BACK_LEGS, NULL); - } else if (action == "jump_forward") { - servo_dog_ctrl_send(DOG_STATE_JUMP_FORWARD, NULL); - } else { - return false; - } - return true; - }); - - // 灯光控制 - mcp_server.AddTool("self.light.get_power", "获取灯是否打开", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - return led_on_; - }); - - mcp_server.AddTool("self.light.turn_on", "打开灯", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - SetLedColor(0xFF, 0xFF, 0xFF); - led_on_ = true; - return true; - }); - - mcp_server.AddTool("self.light.turn_off", "关闭灯", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - SetLedColor(0x00, 0x00, 0x00); - led_on_ = false; - return true; - }); - - mcp_server.AddTool("self.light.set_rgb", "设置RGB颜色", PropertyList({ - Property("r", kPropertyTypeInteger, 0, 255), - Property("g", kPropertyTypeInteger, 0, 255), - Property("b", kPropertyTypeInteger, 0, 255) - }), [this](const PropertyList& properties) -> ReturnValue { - int r = properties["r"].value(); - int g = properties["g"].value(); - int b = properties["b"].value(); - - led_on_ = true; - SetLedColor(r, g, b); - return true; - }); - } - -public: - EspHi() : boot_button_(BOOT_BUTTON_GPIO), - audio_wake_button_(AUDIO_WAKE_BUTTON_GPIO), - move_wake_button_(MOVE_WAKE_BUTTON_GPIO) - { - InitializeButtons(); - InitializeIot(); - InitializeSpi(); - InitializeLcdDisplay(); - InitializeTools(); - - DeviceStateEventManager::GetInstance().RegisterStateChangeCallback([this](DeviceState previous_state, DeviceState current_state) { - ESP_LOGD(TAG, "Device state changed from %d to %d", previous_state, current_state); - this->GetAudioCodec()->EnableOutput(current_state == kDeviceStateSpeaking); - }); - } - - virtual AudioCodec* GetAudioCodec() override - { - static AdcPdmAudioCodec audio_codec( - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_ADC_MIC_CHANNEL, - AUDIO_PDM_SPEAK_P_GPIO, - AUDIO_PDM_SPEAK_N_GPIO, - AUDIO_PA_CTL_GPIO); - return &audio_codec; - } - - virtual Display* GetDisplay() override - { - return display_; - } -}; - -DECLARE_BOARD(EspHi); +#include "wifi_board.h" +#include "adc_pdm_audio_codec.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "mcp_server.h" +#include +#include +#include +#include +#include +#include + +#include "display/lcd_display.h" +#include +#include +#include +#include "esp_lcd_ili9341.h" + +#include "assets/lang_config.h" +#include "anim_player.h" +#include "emoji_display.h" +#include "servo_dog_ctrl.h" +#include "led_strip.h" +#include "driver/rmt_tx.h" +#include "device_state_event.h" + +#include "sdkconfig.h" + +#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED +#include "esp_hi_web_control.h" +#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED + +#define TAG "ESP_HI" + +static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { + {0x11, NULL, 0, 120}, // Sleep out, Delay 120ms + {0xB1, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0}, + {0xB2, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0}, + {0xB3, (uint8_t []){0x05, 0x3A, 0x3A, 0x05, 0x3A, 0x3A}, 6, 0}, + {0xB4, (uint8_t []){0x03}, 1, 0}, // Dot inversion + {0xC0, (uint8_t []){0x44, 0x04, 0x04}, 3, 0}, + {0xC1, (uint8_t []){0xC0}, 1, 0}, + {0xC2, (uint8_t []){0x0D, 0x00}, 2, 0}, + {0xC3, (uint8_t []){0x8D, 0x6A}, 2, 0}, + {0xC4, (uint8_t []){0x8D, 0xEE}, 2, 0}, + {0xC5, (uint8_t []){0x08}, 1, 0}, + {0xE0, (uint8_t []){0x0F, 0x10, 0x03, 0x03, 0x07, 0x02, 0x00, 0x02, 0x07, 0x0C, 0x13, 0x38, 0x0A, 0x0E, 0x03, 0x10}, 16, 0}, + {0xE1, (uint8_t []){0x10, 0x0B, 0x04, 0x04, 0x10, 0x03, 0x00, 0x03, 0x03, 0x09, 0x17, 0x33, 0x0B, 0x0C, 0x06, 0x10}, 16, 0}, + {0x35, (uint8_t []){0x00}, 1, 0}, + {0x3A, (uint8_t []){0x05}, 1, 0}, + {0x36, (uint8_t []){0xC8}, 1, 0}, + {0x29, NULL, 0, 0}, // Display on + {0x2C, NULL, 0, 0}, // Memory write +}; + +static const led_strip_config_t bsp_strip_config = { + .strip_gpio_num = GPIO_NUM_8, + .max_leds = 4, + .led_model = LED_MODEL_WS2812, + .flags = { + .invert_out = false + } +}; + +static const led_strip_rmt_config_t bsp_rmt_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10 * 1000 * 1000, + .flags = { + .with_dma = false + } +}; + +class EspHi : public WifiBoard { +private: + Button boot_button_; + Button audio_wake_button_; + Button move_wake_button_; + anim::EmojiWidget* display_ = nullptr; + bool web_server_initialized_ = false; + led_strip_handle_t led_strip_; + bool led_on_ = false; + +#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED + static void wifi_event_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) + { + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { + + xTaskCreate( + [](void* arg) { + EspHi* instance = static_cast(arg); + + vTaskDelay(5000 / portTICK_PERIOD_MS); + + if (!instance->web_server_initialized_) { + ESP_LOGI(TAG, "WiFi connected, init web control server"); + esp_err_t err = esp_hi_web_control_server_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize web control server: %d", err); + } else { + ESP_LOGI(TAG, "Web control server initialized"); + instance->web_server_initialized_ = true; + } + } + + vTaskDelete(NULL); + }, + "web_server_init", + 1024 * 10, arg, 5, nullptr); + } + } +#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED + + void HandleMoveWakePressDown(int64_t current_time, int64_t &last_trigger_time, int &gesture_state) + { + int64_t interval = last_trigger_time == 0 ? 0 : current_time - last_trigger_time; + last_trigger_time = current_time; + + if (interval > 1000) { + gesture_state = 0; + } else { + switch (gesture_state) { + case 0: + break; + case 1: + if (interval > 300) { + gesture_state = 2; + } + break; + case 2: + if (interval > 100) { + gesture_state = 0; + } + break; + } + } + } + + void HandleMoveWakePressUp(int64_t current_time, int64_t &last_trigger_time, int &gesture_state) + { + int64_t interval = current_time - last_trigger_time; + + if (interval > 1000) { + gesture_state = 0; + } else { + switch (gesture_state) { + case 0: + if (interval > 300) { + gesture_state = 1; + } + break; + case 1: + break; + case 2: + if (interval < 100) { + ESP_LOGI(TAG, "gesture detected"); + gesture_state = 0; + auto &app = Application::GetInstance(); + app.ToggleChatState(); + } + break; + } + } + } + + void InitializeButtons() + { + static int64_t last_trigger_time = 0; + static int gesture_state = 0; // 0: init, 1: wait second long interval, 2: wait oscillation + + boot_button_.OnClick([this]() { + auto &app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + + audio_wake_button_.OnPressDown([this]() { + }); + + audio_wake_button_.OnPressUp([this]() { + }); + + move_wake_button_.OnPressDown([this]() { + int64_t current_time = esp_timer_get_time() / 1000; + HandleMoveWakePressDown(current_time, last_trigger_time, gesture_state); + }); + + move_wake_button_.OnPressUp([this]() { + int64_t current_time = esp_timer_get_time() / 1000; + HandleMoveWakePressUp(current_time, last_trigger_time, gesture_state); + }); + } + + void InitializeLed() { + ESP_LOGI(TAG, "BLINK_GPIO setting %d", bsp_strip_config.strip_gpio_num); + + ESP_ERROR_CHECK(led_strip_new_rmt_device(&bsp_strip_config, &bsp_rmt_config, &led_strip_)); + led_strip_set_pixel(led_strip_, 0, 0x00, 0x00, 0x00); + led_strip_set_pixel(led_strip_, 1, 0x00, 0x00, 0x00); + led_strip_set_pixel(led_strip_, 2, 0x00, 0x00, 0x00); + led_strip_set_pixel(led_strip_, 3, 0x00, 0x00, 0x00); + led_strip_refresh(led_strip_); + } + + esp_err_t SetLedColor(uint8_t r, uint8_t g, uint8_t b) { + esp_err_t ret = ESP_OK; + + ret |= led_strip_set_pixel(led_strip_, 0, r, g, b); + ret |= led_strip_set_pixel(led_strip_, 1, r, g, b); + ret |= led_strip_set_pixel(led_strip_, 2, r, g, b); + ret |= led_strip_set_pixel(led_strip_, 3, r, g, b); + ret |= led_strip_refresh(led_strip_); + return ret; + } + + void InitializeIot() + { + ESP_LOGI(TAG, "Initialize Iot"); + InitializeLed(); + SetLedColor(0x00, 0x00, 0x00); + +#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, + &wifi_event_handler, this)); +#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED + } + + void InitializeSpi() + { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * 10 * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() + { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = DISPLAY_SPI_MODE; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + const ili9341_vendor_config_t vendor_config = { + .init_cmds = &vendor_specific_init[0], + .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), + }; + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = (void *) &vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_set_gap(panel, 0, 24); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + ESP_LOGI(TAG, "LCD panel create success, %p", panel); + + esp_lcd_panel_disp_on_off(panel, true); + + ESP_LOGI(TAG, "Create emoji widget, panel: %p, panel_io: %p", panel, panel_io); + display_ = new anim::EmojiWidget(panel, panel_io); + +#if CONFIG_ESP_CONSOLE_NONE + servo_dog_ctrl_config_t config = { + .fl_gpio_num = FL_GPIO_NUM, + .fr_gpio_num = FR_GPIO_NUM, + .bl_gpio_num = BL_GPIO_NUM, + .br_gpio_num = BR_GPIO_NUM, + }; + + servo_dog_ctrl_init(&config); +#endif + } + + void InitializeTools() + { + auto& mcp_server = McpServer::GetInstance(); + + // 基础动作控制 + mcp_server.AddTool("self.dog.basic_control", "机器人的基础动作。机器人可以做以下基础动作:\n" + "forward: 向前移动\nbackward: 向后移动\nturn_left: 向左转\nturn_right: 向右转\nstop: 立即停止当前动作", + PropertyList({ + Property("action", kPropertyTypeString), + }), [this](const PropertyList& properties) -> ReturnValue { + const std::string& action = properties["action"].value(); + if (action == "forward") { + servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL); + } else if (action == "backward") { + servo_dog_ctrl_send(DOG_STATE_BACKWARD, NULL); + } else if (action == "turn_left") { + servo_dog_ctrl_send(DOG_STATE_TURN_LEFT, NULL); + } else if (action == "turn_right") { + servo_dog_ctrl_send(DOG_STATE_TURN_RIGHT, NULL); + } else if (action == "stop") { + servo_dog_ctrl_send(DOG_STATE_IDLE, NULL); + } else { + return false; + } + return true; + }); + + // 扩展动作控制 + mcp_server.AddTool("self.dog.advanced_control", "机器人的扩展动作。机器人可以做以下扩展动作:\n" + "sway_back_forth: 前后摇摆\nlay_down: 趴下\nsway: 左右摇摆\nretract_legs: 收回腿部\n" + "shake_hand: 握手\nshake_back_legs: 伸懒腰\njump_forward: 向前跳跃", + PropertyList({ + Property("action", kPropertyTypeString), + }), [this](const PropertyList& properties) -> ReturnValue { + const std::string& action = properties["action"].value(); + if (action == "sway_back_forth") { + servo_dog_ctrl_send(DOG_STATE_SWAY_BACK_FORTH, NULL); + } else if (action == "lay_down") { + servo_dog_ctrl_send(DOG_STATE_LAY_DOWN, NULL); + } else if (action == "sway") { + dog_action_args_t args = { + .repeat_count = 4, + }; + servo_dog_ctrl_send(DOG_STATE_SWAY, &args); + } else if (action == "retract_legs") { + servo_dog_ctrl_send(DOG_STATE_RETRACT_LEGS, NULL); + } else if (action == "shake_hand") { + servo_dog_ctrl_send(DOG_STATE_SHAKE_HAND, NULL); + } else if (action == "shake_back_legs") { + servo_dog_ctrl_send(DOG_STATE_SHAKE_BACK_LEGS, NULL); + } else if (action == "jump_forward") { + servo_dog_ctrl_send(DOG_STATE_JUMP_FORWARD, NULL); + } else { + return false; + } + return true; + }); + + // 灯光控制 + mcp_server.AddTool("self.light.get_power", "获取灯是否打开", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + return led_on_; + }); + + mcp_server.AddTool("self.light.turn_on", "打开灯", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SetLedColor(0xFF, 0xFF, 0xFF); + led_on_ = true; + return true; + }); + + mcp_server.AddTool("self.light.turn_off", "关闭灯", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SetLedColor(0x00, 0x00, 0x00); + led_on_ = false; + return true; + }); + + mcp_server.AddTool("self.light.set_rgb", "设置RGB颜色", PropertyList({ + Property("r", kPropertyTypeInteger, 0, 255), + Property("g", kPropertyTypeInteger, 0, 255), + Property("b", kPropertyTypeInteger, 0, 255) + }), [this](const PropertyList& properties) -> ReturnValue { + int r = properties["r"].value(); + int g = properties["g"].value(); + int b = properties["b"].value(); + + led_on_ = true; + SetLedColor(r, g, b); + return true; + }); + } + +public: + EspHi() : boot_button_(BOOT_BUTTON_GPIO), + audio_wake_button_(AUDIO_WAKE_BUTTON_GPIO), + move_wake_button_(MOVE_WAKE_BUTTON_GPIO) + { + InitializeButtons(); + InitializeIot(); + InitializeSpi(); + InitializeLcdDisplay(); + InitializeTools(); + } + + virtual AudioCodec* GetAudioCodec() override + { + static AdcPdmAudioCodec audio_codec( + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_ADC_MIC_CHANNEL, + AUDIO_PDM_SPEAK_P_GPIO, + AUDIO_PDM_SPEAK_N_GPIO, + AUDIO_PA_CTL_GPIO); + return &audio_codec; + } + + virtual Display* GetDisplay() override + { + return display_; + } +}; + +DECLARE_BOARD(EspHi); diff --git a/main/boards/esp-s3-lcd-ev-board-2/README.md b/main/boards/esp-s3-lcd-ev-board-2/README.md index 5fcacc4..1c52c0a 100644 --- a/main/boards/esp-s3-lcd-ev-board-2/README.md +++ b/main/boards/esp-s3-lcd-ev-board-2/README.md @@ -1,10 +1,10 @@ -请确认自己的开发板硬件版本,如果硬件版本,在配置中进行ev_board type进行选择 -1.4与1.5只有io进行变更 -可以查看官方文档,确认具体细节https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html -具体调整为: -I2C_SCL IO18 -> IO48 -I2C_SDA IO8 -> IO47 -LCD_DATA6 IO47 -> IO8 -LCD_DATA7 IO48 -> IO18 - +请确认自己的开发板硬件版本,如果硬件版本,在配置中进行ev_board type进行选择 +1.4与1.5只有io进行变更 +可以查看官方文档,确认具体细节https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html +具体调整为: +I2C_SCL IO18 -> IO48 +I2C_SDA IO8 -> IO47 +LCD_DATA6 IO47 -> IO8 +LCD_DATA7 IO48 -> IO18 + 本版本只支持了800x480的屏幕 \ No newline at end of file diff --git a/main/boards/esp-s3-lcd-ev-board-2/config.h b/main/boards/esp-s3-lcd-ev-board-2/config.h index a59cf05..f9eb9d5 100644 --- a/main/boards/esp-s3-lcd-ev-board-2/config.h +++ b/main/boards/esp-s3-lcd-ev-board-2/config.h @@ -1,42 +1,42 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_7 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_6 - -#define BSP_POWER_AMP_IO (IO_EXPANDER_PIN_NUM_0) -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48 - -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR 0x82 - - -#define BUILTIN_LED_GPIO GPIO_NUM_4 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define DISPLAY_WIDTH 800 -#define DISPLAY_HEIGHT 480 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_19 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_7 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_6 + +#define BSP_POWER_AMP_IO (IO_EXPANDER_PIN_NUM_0) +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48 + +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR 0x82 + + +#define BUILTIN_LED_GPIO GPIO_NUM_4 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define DISPLAY_WIDTH 800 +#define DISPLAY_HEIGHT 480 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_19 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp-s3-lcd-ev-board-2/config.json b/main/boards/esp-s3-lcd-ev-board-2/config.json index 27ab752..79cf0bc 100644 --- a/main/boards/esp-s3-lcd-ev-board-2/config.json +++ b/main/boards/esp-s3-lcd-ev-board-2/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp-s3-lcd-ev-board-2", - "sdkconfig_append": [] - } - ] -} +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp-s3-lcd-ev-board-2", + "sdkconfig_append": [] + } + ] +} diff --git a/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc b/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc index 8dc6585..980ce76 100644 --- a/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc +++ b/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc @@ -1,235 +1,235 @@ -#include "wifi_board.h" -#include "codecs/box_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "pin_config.h" - -#include "config.h" - -#include -#include -#include -#include "esp_lcd_gc9503.h" -#include -#include -#include -#include -#include -#include -#include - -#define TAG "ESP_S3_LCD_EV_Board_2" - -class ESP_S3_LCD_EV_Board_2 : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - //add support ev board lcd - esp_io_expander_handle_t expander = NULL; - - void InitializeRGB_GC9503V_Display() { - ESP_LOGI(TAG, "Init GC9503V"); - - esp_lcd_panel_io_handle_t panel_io = nullptr; - - // add support ev board lcd - gpio_config_t io_conf = { - .pin_bit_mask = BIT64(GC9503V_PIN_NUM_VSYNC), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_ENABLE, - }; - - gpio_config(&io_conf); - gpio_set_level(GC9503V_PIN_NUM_VSYNC, 1); - - ESP_LOGI(TAG, "Install 3-wire SPI panel IO"); - spi_line_config_t line_config = { - .cs_io_type = IO_TYPE_EXPANDER, - .cs_expander_pin = GC9503V_LCD_IO_SPI_CS_1, - .scl_io_type = IO_TYPE_EXPANDER, - .scl_expander_pin = GC9503V_LCD_IO_SPI_SCL_1, - .sda_io_type = IO_TYPE_EXPANDER, - .sda_expander_pin = GC9503V_LCD_IO_SPI_SDO_1, - .io_expander = expander, - }; - - esp_lcd_panel_io_3wire_spi_config_t io_config = GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0); - int espok = esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io); - ESP_LOGI(TAG, "Install 3-wire SPI panel IO:%d",espok); - - ESP_LOGI(TAG, "Install RGB LCD panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_rgb_panel_config_t rgb_config = { - .clk_src = LCD_CLK_SRC_PLL160M, - //add support ev board - .timings = GC9503_800_480_PANEL_60HZ_RGB_TIMING(), - .data_width = 16, // RGB565 in parallel mode, thus 16bit in width - .bits_per_pixel = 16, - .num_fbs = GC9503V_LCD_RGB_BUFFER_NUMS, - .bounce_buffer_size_px = GC9503V_LCD_H_RES * GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT, - .dma_burst_size = 64, - .hsync_gpio_num = GC9503V_PIN_NUM_HSYNC, - .vsync_gpio_num = GC9503V_PIN_NUM_VSYNC, - .de_gpio_num = GC9503V_PIN_NUM_DE, - .pclk_gpio_num = GC9503V_PIN_NUM_PCLK, - .disp_gpio_num = GC9503V_PIN_NUM_DISP_EN, - .data_gpio_nums = { - GC9503V_PIN_NUM_DATA0, - GC9503V_PIN_NUM_DATA1, - GC9503V_PIN_NUM_DATA2, - GC9503V_PIN_NUM_DATA3, - GC9503V_PIN_NUM_DATA4, - GC9503V_PIN_NUM_DATA5, - GC9503V_PIN_NUM_DATA6, - GC9503V_PIN_NUM_DATA7, - GC9503V_PIN_NUM_DATA8, - GC9503V_PIN_NUM_DATA9, - GC9503V_PIN_NUM_DATA10, - GC9503V_PIN_NUM_DATA11, - GC9503V_PIN_NUM_DATA12, - GC9503V_PIN_NUM_DATA13, - GC9503V_PIN_NUM_DATA14, - GC9503V_PIN_NUM_DATA15, - }, - .flags= { - .fb_in_psram = true, // allocate frame buffer in PSRAM - } - }; - - ESP_LOGI(TAG, "Initialize RGB LCD panel"); - - gc9503_vendor_config_t vendor_config = { - .rgb_config = &rgb_config, - .flags = { - .mirror_by_cmd = 0, - .auto_del_panel_io = 1, - }, - }; - const esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = -1, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - // .bits_per_pixel = 16, - //add surpport ev board - .bits_per_pixel = 18, - .vendor_config = &vendor_config, - }; - (esp_lcd_new_panel_gc9503(panel_io, &panel_config, &panel_handle)); - (esp_lcd_panel_reset(panel_handle)); - (esp_lcd_panel_init(panel_handle)); - - display_ = new RgbLcdDisplay(panel_io, panel_handle, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, - DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - - //add support ev board lcd amp - //初始化扩展io口 - esp_io_expander_new_i2c_tca9554(i2c_bus_, 0x20, &expander); - /* Setup power amplifier pin, set default to enable */ - esp_io_expander_set_dir(expander, BSP_POWER_AMP_IO, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(expander, BSP_POWER_AMP_IO, true); - - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - boot_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - } - - void InitializeTouch() { - esp_lcd_touch_handle_t tp; - esp_lcd_touch_config_t tp_cfg = { - .x_max = DISPLAY_WIDTH, - .y_max = DISPLAY_HEIGHT, - .rst_gpio_num = GPIO_NUM_NC, - .int_gpio_num = GPIO_NUM_NC, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = 0, - .mirror_x = 0, - .mirror_y = 0, - }, - }; - esp_lcd_panel_io_handle_t tp_io_handle = NULL; - esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT1151_CONFIG(); - tp_io_config.scl_speed_hz = 400 * 1000; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); - ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt1151(tp_io_handle, &tp_cfg, &tp)); - - const lvgl_port_touch_cfg_t touch_cfg = { - .disp = lv_display_get_default(), - .handle = tp, - }; - lvgl_port_add_touch(&touch_cfg); - } - -public: - ESP_S3_LCD_EV_Board_2() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeButtons(); - InitializeRGB_GC9503V_Display(); - InitializeTouch(); - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - true); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - //添加彩灯显示状态,如果亮度太暗可以去更改默认亮度值 DEFAULT_BRIGHTNESS 在led的sigle_led.cc中 - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - -}; - -DECLARE_BOARD(ESP_S3_LCD_EV_Board_2); +#include "wifi_board.h" +#include "codecs/box_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "pin_config.h" + +#include "config.h" + +#include +#include +#include +#include "esp_lcd_gc9503.h" +#include +#include +#include +#include +#include +#include +#include + +#define TAG "ESP_S3_LCD_EV_Board_2" + +class ESP_S3_LCD_EV_Board_2 : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + //add support ev board lcd + esp_io_expander_handle_t expander = NULL; + + void InitializeRGB_GC9503V_Display() { + ESP_LOGI(TAG, "Init GC9503V"); + + esp_lcd_panel_io_handle_t panel_io = nullptr; + + // add support ev board lcd + gpio_config_t io_conf = { + .pin_bit_mask = BIT64(GC9503V_PIN_NUM_VSYNC), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + }; + + gpio_config(&io_conf); + gpio_set_level(GC9503V_PIN_NUM_VSYNC, 1); + + ESP_LOGI(TAG, "Install 3-wire SPI panel IO"); + spi_line_config_t line_config = { + .cs_io_type = IO_TYPE_EXPANDER, + .cs_expander_pin = GC9503V_LCD_IO_SPI_CS_1, + .scl_io_type = IO_TYPE_EXPANDER, + .scl_expander_pin = GC9503V_LCD_IO_SPI_SCL_1, + .sda_io_type = IO_TYPE_EXPANDER, + .sda_expander_pin = GC9503V_LCD_IO_SPI_SDO_1, + .io_expander = expander, + }; + + esp_lcd_panel_io_3wire_spi_config_t io_config = GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0); + int espok = esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io); + ESP_LOGI(TAG, "Install 3-wire SPI panel IO:%d",espok); + + ESP_LOGI(TAG, "Install RGB LCD panel driver"); + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_rgb_panel_config_t rgb_config = { + .clk_src = LCD_CLK_SRC_PLL160M, + //add support ev board + .timings = GC9503_800_480_PANEL_60HZ_RGB_TIMING(), + .data_width = 16, // RGB565 in parallel mode, thus 16bit in width + .bits_per_pixel = 16, + .num_fbs = GC9503V_LCD_RGB_BUFFER_NUMS, + .bounce_buffer_size_px = GC9503V_LCD_H_RES * GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT, + .dma_burst_size = 64, + .hsync_gpio_num = GC9503V_PIN_NUM_HSYNC, + .vsync_gpio_num = GC9503V_PIN_NUM_VSYNC, + .de_gpio_num = GC9503V_PIN_NUM_DE, + .pclk_gpio_num = GC9503V_PIN_NUM_PCLK, + .disp_gpio_num = GC9503V_PIN_NUM_DISP_EN, + .data_gpio_nums = { + GC9503V_PIN_NUM_DATA0, + GC9503V_PIN_NUM_DATA1, + GC9503V_PIN_NUM_DATA2, + GC9503V_PIN_NUM_DATA3, + GC9503V_PIN_NUM_DATA4, + GC9503V_PIN_NUM_DATA5, + GC9503V_PIN_NUM_DATA6, + GC9503V_PIN_NUM_DATA7, + GC9503V_PIN_NUM_DATA8, + GC9503V_PIN_NUM_DATA9, + GC9503V_PIN_NUM_DATA10, + GC9503V_PIN_NUM_DATA11, + GC9503V_PIN_NUM_DATA12, + GC9503V_PIN_NUM_DATA13, + GC9503V_PIN_NUM_DATA14, + GC9503V_PIN_NUM_DATA15, + }, + .flags= { + .fb_in_psram = true, // allocate frame buffer in PSRAM + } + }; + + ESP_LOGI(TAG, "Initialize RGB LCD panel"); + + gc9503_vendor_config_t vendor_config = { + .rgb_config = &rgb_config, + .flags = { + .mirror_by_cmd = 0, + .auto_del_panel_io = 1, + }, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = -1, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + // .bits_per_pixel = 16, + //add surpport ev board + .bits_per_pixel = 18, + .vendor_config = &vendor_config, + }; + (esp_lcd_new_panel_gc9503(panel_io, &panel_config, &panel_handle)); + (esp_lcd_panel_reset(panel_handle)); + (esp_lcd_panel_init(panel_handle)); + + display_ = new RgbLcdDisplay(panel_io, panel_handle, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + + //add support ev board lcd amp + //初始化扩展io口 + esp_io_expander_new_i2c_tca9554(i2c_bus_, 0x20, &expander); + /* Setup power amplifier pin, set default to enable */ + esp_io_expander_set_dir(expander, BSP_POWER_AMP_IO, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(expander, BSP_POWER_AMP_IO, true); + + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + boot_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + } + + void InitializeTouch() { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH, + .y_max = DISPLAY_HEIGHT, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT1151_CONFIG(); + tp_io_config.scl_speed_hz = 400 * 1000; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); + ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt1151(tp_io_handle, &tp_cfg, &tp)); + + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + lvgl_port_add_touch(&touch_cfg); + } + +public: + ESP_S3_LCD_EV_Board_2() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeButtons(); + InitializeRGB_GC9503V_Display(); + InitializeTouch(); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + true); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + //添加彩灯显示状态,如果亮度太暗可以去更改默认亮度值 DEFAULT_BRIGHTNESS 在led的sigle_led.cc中 + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + +}; + +DECLARE_BOARD(ESP_S3_LCD_EV_Board_2); diff --git a/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.c b/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.c index 4db550e..c44baa2 100644 --- a/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.c +++ b/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.c @@ -1,504 +1,504 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "esp_lcd_gc9503.h" - -#define GC9503_CMD_MADCTL (0xB1) // Memory data access control -#define GC9503_CMD_MADCTL_DEFAULT (0x10) // Default value of Memory data access control -#define GC9503_CMD_SS_BIT (1 << 0) // Source driver scan direction, 0: top to bottom, 1: bottom to top -#define GC9503_CMD_GS_BIT (1 << 1) // Gate driver scan direction, 0: left to right, 1: right to left -#define GC9503_CMD_BGR_BIT (1 << 5) // RGB/BGR order, 0: RGB, 1: BGR - -typedef struct -{ - esp_lcd_panel_io_handle_t io; - int reset_gpio_num; - uint8_t madctl_val; // Save current value of GC9503_CMD_MADCTL register - uint8_t colmod_val; // Save current value of LCD_CMD_COLMOD register - const gc9503_lcd_init_cmd_t *init_cmds; - uint16_t init_cmds_size; - struct - { - unsigned int mirror_by_cmd : 1; - unsigned int auto_del_panel_io : 1; - unsigned int display_on_off_use_cmd : 1; - unsigned int reset_level : 1; - } flags; - // To save the original functions of RGB panel - esp_err_t (*init)(esp_lcd_panel_t *panel); - esp_err_t (*del)(esp_lcd_panel_t *panel); - esp_err_t (*reset)(esp_lcd_panel_t *panel); - esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis); - esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off); -} gc9503_panel_t; - -static const char *TAG = "gc9503"; - -static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503); - -static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool off); - -esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, - esp_lcd_panel_handle_t *ret_panel) -{ - ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments"); - gc9503_vendor_config_t *vendor_config = (gc9503_vendor_config_t *)panel_dev_config->vendor_config; - ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary"); - ESP_RETURN_ON_FALSE(!vendor_config->flags.auto_del_panel_io || !vendor_config->flags.mirror_by_cmd, - ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `auto_del_panel_io` cannot work together"); - - esp_err_t ret = ESP_OK; - gpio_config_t io_conf = {0}; - - gc9503_panel_t *gc9503 = (gc9503_panel_t *)calloc(1, sizeof(gc9503_panel_t)); - ESP_RETURN_ON_FALSE(gc9503, ESP_ERR_NO_MEM, TAG, "no mem for gc9503 panel"); - - if (panel_dev_config->reset_gpio_num >= 0) - { - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; - ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); - } - - gc9503->madctl_val = GC9503_CMD_MADCTL_DEFAULT; - switch (panel_dev_config->rgb_ele_order) - { - case LCD_RGB_ELEMENT_ORDER_RGB: - gc9503->madctl_val &= ~GC9503_CMD_BGR_BIT; - break; - case LCD_RGB_ELEMENT_ORDER_BGR: - gc9503->madctl_val |= GC9503_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order"); - break; - } - - gc9503->colmod_val = 0; - switch (panel_dev_config->bits_per_pixel) - { - case 16: // RGB565 - gc9503->colmod_val = 0x50; - break; - case 18: // RGB666 - gc9503->colmod_val = 0x60; - break; - case 24: // RGB888 - gc9503->colmod_val = 0x70; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); - break; - } - - gc9503->io = io; - gc9503->init_cmds = vendor_config->init_cmds; - gc9503->init_cmds_size = vendor_config->init_cmds_size; - gc9503->reset_gpio_num = panel_dev_config->reset_gpio_num; - gc9503->flags.reset_level = panel_dev_config->flags.reset_active_high; - gc9503->flags.auto_del_panel_io = vendor_config->flags.auto_del_panel_io; - gc9503->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd; - gc9503->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1; - - if (gc9503->flags.auto_del_panel_io) - { - if (gc9503->reset_gpio_num >= 0) - { // Perform hardware reset - gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); - } - else - { // Perform software reset - ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed"); - } - vTaskDelay(pdMS_TO_TICKS(120)); - - /** - * In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface - * (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before - * `esp_lcd_new_rgb_panel()` is called. - */ - ESP_GOTO_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), err, TAG, "send init commands failed"); - // After sending the initialization commands, the 3-wire SPI interface can be deleted - ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed"); - gc9503->io = NULL; - ESP_LOGD(TAG, "delete panel IO"); - } - - // Create RGB panel - ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed"); - ESP_LOGD(TAG, "new RGB panel @%p", ret_panel); - - // Save the original functions of RGB panel - gc9503->init = (*ret_panel)->init; - gc9503->del = (*ret_panel)->del; - gc9503->reset = (*ret_panel)->reset; - gc9503->mirror = (*ret_panel)->mirror; - gc9503->disp_on_off = (*ret_panel)->disp_on_off; - // Overwrite the functions of RGB panel - (*ret_panel)->init = panel_gc9503_init; - (*ret_panel)->del = panel_gc9503_del; - (*ret_panel)->reset = panel_gc9503_reset; - (*ret_panel)->mirror = panel_gc9503_mirror; - (*ret_panel)->disp_on_off = panel_gc9503_disp_on_off; - (*ret_panel)->user_data = gc9503; - ESP_LOGD(TAG, "new gc9503 panel @%p", gc9503); - - // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9503_VER_MAJOR, ESP_LCD_GC9503_VER_MINOR, - // ESP_LCD_GC9503_VER_PATCH); - return ESP_OK; - -err: - if (gc9503) - { - if (panel_dev_config->reset_gpio_num >= 0) - { - gpio_reset_pin(panel_dev_config->reset_gpio_num); - } - free(gc9503); - } - return ret; -} - -// *INDENT-OFF* -// static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { -// // {cmd, { data }, data_size, delay_ms} -// {0x11, (uint8_t []){0x00}, 0, 120}, - -// {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0}, -// {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0}, -// {0xc1, (uint8_t []){0x3f}, 1, 0}, -// {0xc2, (uint8_t []){0x0e}, 1, 0}, -// {0xc6, (uint8_t []){0xf8}, 1, 0}, -// {0xc9, (uint8_t []){0x10}, 1, 0}, -// {0xcd, (uint8_t []){0x25}, 1, 0}, -// {0xf8, (uint8_t []){0x8a}, 1, 0}, -// {0xac, (uint8_t []){0x45}, 1, 0}, -// {0xa0, (uint8_t []){0xdd}, 1, 0}, -// {0xa7, (uint8_t []){0x47}, 1, 0}, -// {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0}, -// {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0}, -// {0xa3, (uint8_t []){0xee}, 1, 0}, -// {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0}, -// {0x71, (uint8_t []){0x48}, 1, 0}, -// {0x72, (uint8_t []){0x48}, 1, 0}, -// {0x73, (uint8_t []){0x00, 0x44}, 2, 0}, -// {0x97, (uint8_t []){0xee}, 1, 0}, -// {0x83, (uint8_t []){0x93}, 1, 0}, -// {0x9a, (uint8_t []){0x72}, 1, 0}, -// {0x9b, (uint8_t []){0x5a}, 1, 0}, -// {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0}, -// {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, -// 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0}, -// {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0}, -// {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0}, -// {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0}, -// {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, -// {0x6b, (uint8_t []){0x07}, 1, 0}, -// {0x7a, (uint8_t []){0x08, 0x13}, 2, 0}, -// {0x7b, (uint8_t []){0x08, 0x13}, 2, 0}, -// {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0x11, (uint8_t []){0x00}, 0, 120}, -// {0x29, (uint8_t []){0x00}, 0, 20}, -// }; -static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { - // {0x11, (uint8_t[]){}, 0, 20}, - - {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0}, - {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0}, - {0xc1, (uint8_t []){0x3f}, 1, 0}, - {0xc2, (uint8_t []){0x0e}, 1, 0}, - {0xc6, (uint8_t []){0xf8}, 1, 0}, - {0xc9, (uint8_t []){0x10}, 1, 0}, - {0xcd, (uint8_t []){0x25}, 1, 0}, - {0xf8, (uint8_t []){0x8a}, 1, 0}, - {0xac, (uint8_t []){0x45}, 1, 0}, - {0xa0, (uint8_t []){0xdd}, 1, 0}, - {0xa7, (uint8_t []){0x47}, 1, 0}, - {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0}, - {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0}, - {0xa3, (uint8_t []){0xee}, 1, 0}, - {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0}, - {0x71, (uint8_t []){0x48}, 1, 0}, - {0x72, (uint8_t []){0x48}, 1, 0}, - {0x73, (uint8_t []){0x00, 0x44}, 2, 0}, - {0x97, (uint8_t []){0xee}, 1, 0}, - {0x83, (uint8_t []){0x93}, 1, 0}, - {0x9a, (uint8_t []){0x72}, 1, 0}, - {0x9b, (uint8_t []){0x5a}, 1, 0}, - {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0}, - {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, - 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0}, - {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, - {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, - {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, - {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, - {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0}, - {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0}, - {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0}, - {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, - {0x6b, (uint8_t []){0x07}, 1, 0}, - {0x7a, (uint8_t []){0x08, 0x13}, 2, 0}, - {0x7b, (uint8_t []){0x08, 0x13}, 2, 0}, - {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0x11, (uint8_t []){0x00}, 0, 120}, - {0x29, (uint8_t []){0x00}, 0, 20}, - - }; - -// *INDENT-OFF* - -static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503) -{ - esp_lcd_panel_io_handle_t io = gc9503->io; - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ - gc9503->madctl_val, - }, - 1), - TAG, "send command failed"); - ; - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){ - gc9503->colmod_val, - }, - 1), - TAG, "send command failed"); - ; - - // Vendor specific initialization, it can be different between manufacturers - // should consult the LCD supplier for initialization sequence code - const gc9503_lcd_init_cmd_t *init_cmds = NULL; - uint16_t init_cmds_size = 0; - if (gc9503->init_cmds) - { - init_cmds = gc9503->init_cmds; - init_cmds_size = gc9503->init_cmds_size; - } - else - { - init_cmds = vendor_specific_init_default; - init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9503_lcd_init_cmd_t); - } - - bool is_cmd_overwritten = false; - for (int i = 0; i < init_cmds_size; i++) - { - // Check if the command has been used or conflicts with the internal - switch (init_cmds[i].cmd) - { - case LCD_CMD_MADCTL: - is_cmd_overwritten = true; - gc9503->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - case LCD_CMD_COLMOD: - is_cmd_overwritten = true; - gc9503->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - default: - is_cmd_overwritten = false; - break; - } - - if (is_cmd_overwritten) - { - ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", - init_cmds[i].cmd); - } - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), - TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); - } - ESP_LOGD(TAG, "send init commands success"); - - return ESP_OK; -} - -static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - - if (!gc9503->flags.auto_del_panel_io) - { - ESP_RETURN_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), TAG, "send init commands failed"); - } - // Init RGB panel - ESP_RETURN_ON_ERROR(gc9503->init(panel), TAG, "init RGB panel failed"); - - return ESP_OK; -} - -static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - - if (gc9503->reset_gpio_num >= 0) - { - gpio_reset_pin(gc9503->reset_gpio_num); - } - // Delete RGB panel - gc9503->del(panel); - free(gc9503); - ESP_LOGD(TAG, "del gc9503 panel @%p", gc9503); - return ESP_OK; -} - -static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - esp_lcd_panel_io_handle_t io = gc9503->io; - - // Perform hardware reset - if (gc9503->reset_gpio_num >= 0) - { - gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); - vTaskDelay(pdMS_TO_TICKS(120)); - } - else if (io) - { // Perform software reset - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(120)); - } - // Reset RGB panel - ESP_RETURN_ON_ERROR(gc9503->reset(panel), TAG, "reset RGB panel failed"); - - return ESP_OK; -} - -static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - esp_lcd_panel_io_handle_t io = gc9503->io; - - if (gc9503->flags.mirror_by_cmd) - { - ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); - // Control mirror through LCD command - if (mirror_x) - { - gc9503->madctl_val |= GC9503_CMD_GS_BIT; - } - else - { - gc9503->madctl_val &= ~GC9503_CMD_GS_BIT; - } - if (mirror_y) - { - gc9503->madctl_val |= GC9503_CMD_SS_BIT; - } - else - { - gc9503->madctl_val &= ~GC9503_CMD_SS_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ - gc9503->madctl_val, - }, - 1), - TAG, "send command failed"); - ; - } - else - { - // Control mirror through RGB panel - ESP_RETURN_ON_ERROR(gc9503->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed"); - } - return ESP_OK; -} - -static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool on_off) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - esp_lcd_panel_io_handle_t io = gc9503->io; - int command = 0; - - if (gc9503->flags.display_on_off_use_cmd) - { - ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); - // Control display on/off through LCD command - if (on_off) - { - command = LCD_CMD_DISPON; - } - else - { - command = LCD_CMD_DISPOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); - } - else - { - // Control display on/off through display control signal - ESP_RETURN_ON_ERROR(gc9503->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed"); - } - return ESP_OK; -} +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "esp_lcd_gc9503.h" + +#define GC9503_CMD_MADCTL (0xB1) // Memory data access control +#define GC9503_CMD_MADCTL_DEFAULT (0x10) // Default value of Memory data access control +#define GC9503_CMD_SS_BIT (1 << 0) // Source driver scan direction, 0: top to bottom, 1: bottom to top +#define GC9503_CMD_GS_BIT (1 << 1) // Gate driver scan direction, 0: left to right, 1: right to left +#define GC9503_CMD_BGR_BIT (1 << 5) // RGB/BGR order, 0: RGB, 1: BGR + +typedef struct +{ + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + uint8_t madctl_val; // Save current value of GC9503_CMD_MADCTL register + uint8_t colmod_val; // Save current value of LCD_CMD_COLMOD register + const gc9503_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; + struct + { + unsigned int mirror_by_cmd : 1; + unsigned int auto_del_panel_io : 1; + unsigned int display_on_off_use_cmd : 1; + unsigned int reset_level : 1; + } flags; + // To save the original functions of RGB panel + esp_err_t (*init)(esp_lcd_panel_t *panel); + esp_err_t (*del)(esp_lcd_panel_t *panel); + esp_err_t (*reset)(esp_lcd_panel_t *panel); + esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis); + esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off); +} gc9503_panel_t; + +static const char *TAG = "gc9503"; + +static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503); + +static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool off); + +esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, + esp_lcd_panel_handle_t *ret_panel) +{ + ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments"); + gc9503_vendor_config_t *vendor_config = (gc9503_vendor_config_t *)panel_dev_config->vendor_config; + ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary"); + ESP_RETURN_ON_FALSE(!vendor_config->flags.auto_del_panel_io || !vendor_config->flags.mirror_by_cmd, + ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `auto_del_panel_io` cannot work together"); + + esp_err_t ret = ESP_OK; + gpio_config_t io_conf = {0}; + + gc9503_panel_t *gc9503 = (gc9503_panel_t *)calloc(1, sizeof(gc9503_panel_t)); + ESP_RETURN_ON_FALSE(gc9503, ESP_ERR_NO_MEM, TAG, "no mem for gc9503 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) + { + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + + gc9503->madctl_val = GC9503_CMD_MADCTL_DEFAULT; + switch (panel_dev_config->rgb_ele_order) + { + case LCD_RGB_ELEMENT_ORDER_RGB: + gc9503->madctl_val &= ~GC9503_CMD_BGR_BIT; + break; + case LCD_RGB_ELEMENT_ORDER_BGR: + gc9503->madctl_val |= GC9503_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order"); + break; + } + + gc9503->colmod_val = 0; + switch (panel_dev_config->bits_per_pixel) + { + case 16: // RGB565 + gc9503->colmod_val = 0x50; + break; + case 18: // RGB666 + gc9503->colmod_val = 0x60; + break; + case 24: // RGB888 + gc9503->colmod_val = 0x70; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + gc9503->io = io; + gc9503->init_cmds = vendor_config->init_cmds; + gc9503->init_cmds_size = vendor_config->init_cmds_size; + gc9503->reset_gpio_num = panel_dev_config->reset_gpio_num; + gc9503->flags.reset_level = panel_dev_config->flags.reset_active_high; + gc9503->flags.auto_del_panel_io = vendor_config->flags.auto_del_panel_io; + gc9503->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd; + gc9503->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1; + + if (gc9503->flags.auto_del_panel_io) + { + if (gc9503->reset_gpio_num >= 0) + { // Perform hardware reset + gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); + } + else + { // Perform software reset + ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed"); + } + vTaskDelay(pdMS_TO_TICKS(120)); + + /** + * In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface + * (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before + * `esp_lcd_new_rgb_panel()` is called. + */ + ESP_GOTO_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), err, TAG, "send init commands failed"); + // After sending the initialization commands, the 3-wire SPI interface can be deleted + ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed"); + gc9503->io = NULL; + ESP_LOGD(TAG, "delete panel IO"); + } + + // Create RGB panel + ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed"); + ESP_LOGD(TAG, "new RGB panel @%p", ret_panel); + + // Save the original functions of RGB panel + gc9503->init = (*ret_panel)->init; + gc9503->del = (*ret_panel)->del; + gc9503->reset = (*ret_panel)->reset; + gc9503->mirror = (*ret_panel)->mirror; + gc9503->disp_on_off = (*ret_panel)->disp_on_off; + // Overwrite the functions of RGB panel + (*ret_panel)->init = panel_gc9503_init; + (*ret_panel)->del = panel_gc9503_del; + (*ret_panel)->reset = panel_gc9503_reset; + (*ret_panel)->mirror = panel_gc9503_mirror; + (*ret_panel)->disp_on_off = panel_gc9503_disp_on_off; + (*ret_panel)->user_data = gc9503; + ESP_LOGD(TAG, "new gc9503 panel @%p", gc9503); + + // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9503_VER_MAJOR, ESP_LCD_GC9503_VER_MINOR, + // ESP_LCD_GC9503_VER_PATCH); + return ESP_OK; + +err: + if (gc9503) + { + if (panel_dev_config->reset_gpio_num >= 0) + { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(gc9503); + } + return ret; +} + +// *INDENT-OFF* +// static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { +// // {cmd, { data }, data_size, delay_ms} +// {0x11, (uint8_t []){0x00}, 0, 120}, + +// {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0}, +// {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0}, +// {0xc1, (uint8_t []){0x3f}, 1, 0}, +// {0xc2, (uint8_t []){0x0e}, 1, 0}, +// {0xc6, (uint8_t []){0xf8}, 1, 0}, +// {0xc9, (uint8_t []){0x10}, 1, 0}, +// {0xcd, (uint8_t []){0x25}, 1, 0}, +// {0xf8, (uint8_t []){0x8a}, 1, 0}, +// {0xac, (uint8_t []){0x45}, 1, 0}, +// {0xa0, (uint8_t []){0xdd}, 1, 0}, +// {0xa7, (uint8_t []){0x47}, 1, 0}, +// {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0}, +// {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0}, +// {0xa3, (uint8_t []){0xee}, 1, 0}, +// {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0}, +// {0x71, (uint8_t []){0x48}, 1, 0}, +// {0x72, (uint8_t []){0x48}, 1, 0}, +// {0x73, (uint8_t []){0x00, 0x44}, 2, 0}, +// {0x97, (uint8_t []){0xee}, 1, 0}, +// {0x83, (uint8_t []){0x93}, 1, 0}, +// {0x9a, (uint8_t []){0x72}, 1, 0}, +// {0x9b, (uint8_t []){0x5a}, 1, 0}, +// {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0}, +// {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, +// 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0}, +// {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0}, +// {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0}, +// {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0}, +// {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, +// {0x6b, (uint8_t []){0x07}, 1, 0}, +// {0x7a, (uint8_t []){0x08, 0x13}, 2, 0}, +// {0x7b, (uint8_t []){0x08, 0x13}, 2, 0}, +// {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0x11, (uint8_t []){0x00}, 0, 120}, +// {0x29, (uint8_t []){0x00}, 0, 20}, +// }; +static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { + // {0x11, (uint8_t[]){}, 0, 20}, + + {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0}, + {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0}, + {0xc1, (uint8_t []){0x3f}, 1, 0}, + {0xc2, (uint8_t []){0x0e}, 1, 0}, + {0xc6, (uint8_t []){0xf8}, 1, 0}, + {0xc9, (uint8_t []){0x10}, 1, 0}, + {0xcd, (uint8_t []){0x25}, 1, 0}, + {0xf8, (uint8_t []){0x8a}, 1, 0}, + {0xac, (uint8_t []){0x45}, 1, 0}, + {0xa0, (uint8_t []){0xdd}, 1, 0}, + {0xa7, (uint8_t []){0x47}, 1, 0}, + {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0}, + {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0}, + {0xa3, (uint8_t []){0xee}, 1, 0}, + {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0}, + {0x71, (uint8_t []){0x48}, 1, 0}, + {0x72, (uint8_t []){0x48}, 1, 0}, + {0x73, (uint8_t []){0x00, 0x44}, 2, 0}, + {0x97, (uint8_t []){0xee}, 1, 0}, + {0x83, (uint8_t []){0x93}, 1, 0}, + {0x9a, (uint8_t []){0x72}, 1, 0}, + {0x9b, (uint8_t []){0x5a}, 1, 0}, + {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0}, + {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0}, + {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, + {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, + {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, + {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, + {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0}, + {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0}, + {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0}, + {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, + {0x6b, (uint8_t []){0x07}, 1, 0}, + {0x7a, (uint8_t []){0x08, 0x13}, 2, 0}, + {0x7b, (uint8_t []){0x08, 0x13}, 2, 0}, + {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0x11, (uint8_t []){0x00}, 0, 120}, + {0x29, (uint8_t []){0x00}, 0, 20}, + + }; + +// *INDENT-OFF* + +static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503) +{ + esp_lcd_panel_io_handle_t io = gc9503->io; + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ + gc9503->madctl_val, + }, + 1), + TAG, "send command failed"); + ; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){ + gc9503->colmod_val, + }, + 1), + TAG, "send command failed"); + ; + + // Vendor specific initialization, it can be different between manufacturers + // should consult the LCD supplier for initialization sequence code + const gc9503_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + if (gc9503->init_cmds) + { + init_cmds = gc9503->init_cmds; + init_cmds_size = gc9503->init_cmds_size; + } + else + { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9503_lcd_init_cmd_t); + } + + bool is_cmd_overwritten = false; + for (int i = 0; i < init_cmds_size; i++) + { + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd) + { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + gc9503->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + gc9503->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) + { + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", + init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), + TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + + if (!gc9503->flags.auto_del_panel_io) + { + ESP_RETURN_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), TAG, "send init commands failed"); + } + // Init RGB panel + ESP_RETURN_ON_ERROR(gc9503->init(panel), TAG, "init RGB panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + + if (gc9503->reset_gpio_num >= 0) + { + gpio_reset_pin(gc9503->reset_gpio_num); + } + // Delete RGB panel + gc9503->del(panel); + free(gc9503); + ESP_LOGD(TAG, "del gc9503 panel @%p", gc9503); + return ESP_OK; +} + +static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = gc9503->io; + + // Perform hardware reset + if (gc9503->reset_gpio_num >= 0) + { + gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(120)); + } + else if (io) + { // Perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(120)); + } + // Reset RGB panel + ESP_RETURN_ON_ERROR(gc9503->reset(panel), TAG, "reset RGB panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = gc9503->io; + + if (gc9503->flags.mirror_by_cmd) + { + ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); + // Control mirror through LCD command + if (mirror_x) + { + gc9503->madctl_val |= GC9503_CMD_GS_BIT; + } + else + { + gc9503->madctl_val &= ~GC9503_CMD_GS_BIT; + } + if (mirror_y) + { + gc9503->madctl_val |= GC9503_CMD_SS_BIT; + } + else + { + gc9503->madctl_val &= ~GC9503_CMD_SS_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ + gc9503->madctl_val, + }, + 1), + TAG, "send command failed"); + ; + } + else + { + // Control mirror through RGB panel + ESP_RETURN_ON_ERROR(gc9503->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed"); + } + return ESP_OK; +} + +static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = gc9503->io; + int command = 0; + + if (gc9503->flags.display_on_off_use_cmd) + { + ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); + // Control display on/off through LCD command + if (on_off) + { + command = LCD_CMD_DISPON; + } + else + { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + } + else + { + // Control display on/off through display control signal + ESP_RETURN_ON_ERROR(gc9503->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed"); + } + return ESP_OK; +} diff --git a/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.h b/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.h index 4d926d9..ac4b8ea 100644 --- a/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.h +++ b/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.h @@ -1,146 +1,146 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -/** - * @file - * @brief ESP LCD: GC9503 - */ - -#pragma once - -#include - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief LCD panel initialization commands. - * - */ -typedef struct { - int cmd; /* + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /* - - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 16000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_7 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_6 - -#define BSP_POWER_AMP_IO (IO_EXPANDER_PIN_NUM_0) -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC - -//如果开发板是V1.4 IO 定义为 -#ifdef CONFIG_ESP_S3_LCD_EV_Board_1p4 - #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 - #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 -#endif -//如果开发板是V1.5 IO 定义为 -#ifdef CONFIG_ESP_S3_LCD_EV_Board_1p5 - #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47 - #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48 -#endif - -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR 0x82 - - -#define BUILTIN_LED_GPIO GPIO_NUM_4 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define DISPLAY_WIDTH 480 -#define DISPLAY_HEIGHT 480 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_19 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - - - - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 16000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_7 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_6 + +#define BSP_POWER_AMP_IO (IO_EXPANDER_PIN_NUM_0) +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC + +//如果开发板是V1.4 IO 定义为 +#ifdef CONFIG_ESP_S3_LCD_EV_Board_1p4 + #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 + #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 +#endif +//如果开发板是V1.5 IO 定义为 +#ifdef CONFIG_ESP_S3_LCD_EV_Board_1p5 + #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47 + #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48 +#endif + +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR 0x82 + + +#define BUILTIN_LED_GPIO GPIO_NUM_4 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define DISPLAY_WIDTH 480 +#define DISPLAY_HEIGHT 480 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_19 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + + + + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp-s3-lcd-ev-board/config.json b/main/boards/esp-s3-lcd-ev-board/config.json index 55d1602..25755f7 100644 --- a/main/boards/esp-s3-lcd-ev-board/config.json +++ b/main/boards/esp-s3-lcd-ev-board/config.json @@ -1,17 +1,17 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp-s3-lcd-ev-board-1p4", - "sdkconfig_append": [ - "CONFIG_ESP_S3_LCD_EV_Board_1p4=y" - ] - }, - { - "name": "esp-s3-lcd-ev-board-1p5", - "sdkconfig_append": [ - "CONFIG_ESP_S3_LCD_EV_Board_1p5=y" - ] - } - ] -} +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp-s3-lcd-ev-board-1p4", + "sdkconfig_append": [ + "CONFIG_ESP_S3_LCD_EV_Board_1p4=y" + ] + }, + { + "name": "esp-s3-lcd-ev-board-1p5", + "sdkconfig_append": [ + "CONFIG_ESP_S3_LCD_EV_Board_1p5=y" + ] + } + ] +} diff --git a/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc b/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc index 990a3e3..d777ce2 100644 --- a/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc +++ b/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc @@ -1,204 +1,204 @@ -#include "wifi_board.h" -#include "codecs/box_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "pin_config.h" - -#include "config.h" - -#include -#include -#include -#include "esp_lcd_gc9503.h" -#include -#include -#include - -#include "esp_io_expander_tca9554.h" - -#define TAG "ESP_S3_LCD_EV_Board" - -class ESP_S3_LCD_EV_Board : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - - //add support ev board lcd - esp_io_expander_handle_t expander = NULL; - - void InitializeRGB_GC9503V_Display() { - ESP_LOGI(TAG, "Init GC9503V"); - - esp_lcd_panel_io_handle_t panel_io = nullptr; - - //add support ev board lcd - gpio_config_t io_conf = { - .pin_bit_mask = BIT64(GC9503V_PIN_NUM_VSYNC), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_ENABLE, - }; - - gpio_config(&io_conf); - gpio_set_level(GC9503V_PIN_NUM_VSYNC, 1); - - ESP_LOGI(TAG, "Install 3-wire SPI panel IO"); - spi_line_config_t line_config = { - .cs_io_type = IO_TYPE_EXPANDER, - .cs_expander_pin = GC9503V_LCD_IO_SPI_CS_1, - .scl_io_type = IO_TYPE_EXPANDER, - .scl_expander_pin = GC9503V_LCD_IO_SPI_SCL_1, - .sda_io_type = IO_TYPE_EXPANDER, - .sda_expander_pin = GC9503V_LCD_IO_SPI_SDO_1, - .io_expander = expander, - }; - - esp_lcd_panel_io_3wire_spi_config_t io_config = GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0); - int espok = esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io); - ESP_LOGI(TAG, "Install 3-wire SPI panel IO:%d",espok); - - ESP_LOGI(TAG, "Install RGB LCD panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_rgb_panel_config_t rgb_config = { - .clk_src = LCD_CLK_SRC_PLL160M, - //.timings = GC9503_376_960_PANEL_60HZ_RGB_TIMING(), - //add support ev board - .timings = GC9503_480_480_PANEL_60HZ_RGB_TIMING(), - .data_width = 16, // RGB565 in parallel mode, thus 16bit in width - .bits_per_pixel = 16, - .num_fbs = GC9503V_LCD_RGB_BUFFER_NUMS, - .bounce_buffer_size_px = GC9503V_LCD_H_RES * GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT, - .dma_burst_size = 64, - .hsync_gpio_num = GC9503V_PIN_NUM_HSYNC, - .vsync_gpio_num = GC9503V_PIN_NUM_VSYNC, - .de_gpio_num = GC9503V_PIN_NUM_DE, - .pclk_gpio_num = GC9503V_PIN_NUM_PCLK, - .disp_gpio_num = GC9503V_PIN_NUM_DISP_EN, - .data_gpio_nums = { - GC9503V_PIN_NUM_DATA0, - GC9503V_PIN_NUM_DATA1, - GC9503V_PIN_NUM_DATA2, - GC9503V_PIN_NUM_DATA3, - GC9503V_PIN_NUM_DATA4, - GC9503V_PIN_NUM_DATA5, - GC9503V_PIN_NUM_DATA6, - GC9503V_PIN_NUM_DATA7, - GC9503V_PIN_NUM_DATA8, - GC9503V_PIN_NUM_DATA9, - GC9503V_PIN_NUM_DATA10, - GC9503V_PIN_NUM_DATA11, - GC9503V_PIN_NUM_DATA12, - GC9503V_PIN_NUM_DATA13, - GC9503V_PIN_NUM_DATA14, - GC9503V_PIN_NUM_DATA15, - }, - .flags= { - .fb_in_psram = true, // allocate frame buffer in PSRAM - } - }; - - ESP_LOGI(TAG, "Initialize RGB LCD panel"); - - gc9503_vendor_config_t vendor_config = { - .rgb_config = &rgb_config, - .flags = { - .mirror_by_cmd = 0, - .auto_del_panel_io = 1, - }, - }; - const esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = -1, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - // .bits_per_pixel = 16, - //add surpport ev board - .bits_per_pixel = 18, - .vendor_config = &vendor_config, - }; - (esp_lcd_new_panel_gc9503(panel_io, &panel_config, &panel_handle)); - (esp_lcd_panel_reset(panel_handle)); - (esp_lcd_panel_init(panel_handle)); - - display_ = new RgbLcdDisplay(panel_io, panel_handle, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, - DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - - //add support ev board lcd amp - //初始化扩展io口 - esp_io_expander_new_i2c_tca9554(codec_i2c_bus_, 0x20, &expander); - /* Setup power amplifier pin, set default to enable */ - esp_io_expander_set_dir(expander, BSP_POWER_AMP_IO, IO_EXPANDER_OUTPUT); - esp_io_expander_set_level(expander, BSP_POWER_AMP_IO, true); - - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - boot_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - } - -public: - ESP_S3_LCD_EV_Board() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeButtons(); - InitializeRGB_GC9503V_Display(); - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec( - codec_i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - true); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - //添加彩灯显示状态,如果亮度太暗可以去更改默认亮度值 DEFAULT_BRIGHTNESS 在led的sigle_led.cc中 - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - -}; - -DECLARE_BOARD(ESP_S3_LCD_EV_Board); +#include "wifi_board.h" +#include "codecs/box_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "pin_config.h" + +#include "config.h" + +#include +#include +#include +#include "esp_lcd_gc9503.h" +#include +#include +#include + +#include "esp_io_expander_tca9554.h" + +#define TAG "ESP_S3_LCD_EV_Board" + +class ESP_S3_LCD_EV_Board : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + + //add support ev board lcd + esp_io_expander_handle_t expander = NULL; + + void InitializeRGB_GC9503V_Display() { + ESP_LOGI(TAG, "Init GC9503V"); + + esp_lcd_panel_io_handle_t panel_io = nullptr; + + //add support ev board lcd + gpio_config_t io_conf = { + .pin_bit_mask = BIT64(GC9503V_PIN_NUM_VSYNC), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + }; + + gpio_config(&io_conf); + gpio_set_level(GC9503V_PIN_NUM_VSYNC, 1); + + ESP_LOGI(TAG, "Install 3-wire SPI panel IO"); + spi_line_config_t line_config = { + .cs_io_type = IO_TYPE_EXPANDER, + .cs_expander_pin = GC9503V_LCD_IO_SPI_CS_1, + .scl_io_type = IO_TYPE_EXPANDER, + .scl_expander_pin = GC9503V_LCD_IO_SPI_SCL_1, + .sda_io_type = IO_TYPE_EXPANDER, + .sda_expander_pin = GC9503V_LCD_IO_SPI_SDO_1, + .io_expander = expander, + }; + + esp_lcd_panel_io_3wire_spi_config_t io_config = GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0); + int espok = esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io); + ESP_LOGI(TAG, "Install 3-wire SPI panel IO:%d",espok); + + ESP_LOGI(TAG, "Install RGB LCD panel driver"); + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_rgb_panel_config_t rgb_config = { + .clk_src = LCD_CLK_SRC_PLL160M, + //.timings = GC9503_376_960_PANEL_60HZ_RGB_TIMING(), + //add support ev board + .timings = GC9503_480_480_PANEL_60HZ_RGB_TIMING(), + .data_width = 16, // RGB565 in parallel mode, thus 16bit in width + .bits_per_pixel = 16, + .num_fbs = GC9503V_LCD_RGB_BUFFER_NUMS, + .bounce_buffer_size_px = GC9503V_LCD_H_RES * GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT, + .dma_burst_size = 64, + .hsync_gpio_num = GC9503V_PIN_NUM_HSYNC, + .vsync_gpio_num = GC9503V_PIN_NUM_VSYNC, + .de_gpio_num = GC9503V_PIN_NUM_DE, + .pclk_gpio_num = GC9503V_PIN_NUM_PCLK, + .disp_gpio_num = GC9503V_PIN_NUM_DISP_EN, + .data_gpio_nums = { + GC9503V_PIN_NUM_DATA0, + GC9503V_PIN_NUM_DATA1, + GC9503V_PIN_NUM_DATA2, + GC9503V_PIN_NUM_DATA3, + GC9503V_PIN_NUM_DATA4, + GC9503V_PIN_NUM_DATA5, + GC9503V_PIN_NUM_DATA6, + GC9503V_PIN_NUM_DATA7, + GC9503V_PIN_NUM_DATA8, + GC9503V_PIN_NUM_DATA9, + GC9503V_PIN_NUM_DATA10, + GC9503V_PIN_NUM_DATA11, + GC9503V_PIN_NUM_DATA12, + GC9503V_PIN_NUM_DATA13, + GC9503V_PIN_NUM_DATA14, + GC9503V_PIN_NUM_DATA15, + }, + .flags= { + .fb_in_psram = true, // allocate frame buffer in PSRAM + } + }; + + ESP_LOGI(TAG, "Initialize RGB LCD panel"); + + gc9503_vendor_config_t vendor_config = { + .rgb_config = &rgb_config, + .flags = { + .mirror_by_cmd = 0, + .auto_del_panel_io = 1, + }, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = -1, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + // .bits_per_pixel = 16, + //add surpport ev board + .bits_per_pixel = 18, + .vendor_config = &vendor_config, + }; + (esp_lcd_new_panel_gc9503(panel_io, &panel_config, &panel_handle)); + (esp_lcd_panel_reset(panel_handle)); + (esp_lcd_panel_init(panel_handle)); + + display_ = new RgbLcdDisplay(panel_io, panel_handle, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + //add support ev board lcd amp + //初始化扩展io口 + esp_io_expander_new_i2c_tca9554(codec_i2c_bus_, 0x20, &expander); + /* Setup power amplifier pin, set default to enable */ + esp_io_expander_set_dir(expander, BSP_POWER_AMP_IO, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(expander, BSP_POWER_AMP_IO, true); + + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + boot_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + } + +public: + ESP_S3_LCD_EV_Board() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeButtons(); + InitializeRGB_GC9503V_Display(); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + codec_i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + true); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + //添加彩灯显示状态,如果亮度太暗可以去更改默认亮度值 DEFAULT_BRIGHTNESS 在led的sigle_led.cc中 + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + +}; + +DECLARE_BOARD(ESP_S3_LCD_EV_Board); diff --git a/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.c b/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.c index 321472f..558d26b 100644 --- a/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.c +++ b/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.c @@ -1,154 +1,154 @@ -/* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include "esp_bit_defs.h" -#include "esp_check.h" -#include "esp_log.h" -#include "esp_io_expander.h" -#include "esp_io_expander_tca9554.h" - -/* I2C communication related */ -#define I2C_TIMEOUT_MS (1000) -#define I2C_CLK_SPEED (400000) - -#define IO_COUNT (8) - -/* Register address */ -#define INPUT_REG_ADDR (0x00) -#define OUTPUT_REG_ADDR (0x01) -#define DIRECTION_REG_ADDR (0x03) - -/* Default register value on power-up */ -#define DIR_REG_DEFAULT_VAL (0xff) -#define OUT_REG_DEFAULT_VAL (0xff) - -/** - * @brief Device Structure Type - * - */ -typedef struct { - esp_io_expander_t base; - i2c_master_dev_handle_t i2c_handle; - struct { - uint8_t direction; - uint8_t output; - } regs; -} esp_io_expander_tca9554_t; - -static char *TAG = "tca9554"; - -static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value); -static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value); -static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value); -static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value); -static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value); -static esp_err_t reset(esp_io_expander_t *handle); -static esp_err_t del(esp_io_expander_t *handle); - -esp_err_t esp_io_expander_new_i2c_tca9554(i2c_master_bus_handle_t i2c_bus, uint32_t dev_addr, esp_io_expander_handle_t *handle_ret) -{ - ESP_RETURN_ON_FALSE(handle_ret != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid handle_ret"); - - // Allocate memory for driver object - esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)calloc(1, sizeof(esp_io_expander_tca9554_t)); - ESP_RETURN_ON_FALSE(tca9554 != NULL, ESP_ERR_NO_MEM, TAG, "Malloc failed"); - - // Add new I2C device - esp_err_t ret = ESP_OK; - const i2c_device_config_t i2c_dev_cfg = { - .device_address = dev_addr, - .scl_speed_hz = I2C_CLK_SPEED, - }; - ESP_GOTO_ON_ERROR(i2c_master_bus_add_device(i2c_bus, &i2c_dev_cfg, &tca9554->i2c_handle), err, TAG, "Add new I2C device failed"); - - tca9554->base.config.io_count = IO_COUNT; - tca9554->base.config.flags.dir_out_bit_zero = 1; - tca9554->base.read_input_reg = read_input_reg; - tca9554->base.write_output_reg = write_output_reg; - tca9554->base.read_output_reg = read_output_reg; - tca9554->base.write_direction_reg = write_direction_reg; - tca9554->base.read_direction_reg = read_direction_reg; - tca9554->base.del = del; - tca9554->base.reset = reset; - - /* Reset configuration and register status */ - ESP_GOTO_ON_ERROR(reset(&tca9554->base), err, TAG, "Reset failed"); - - *handle_ret = &tca9554->base; - return ESP_OK; -err: - free(tca9554); - return ret; -} - -static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value) -{ - esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); - - uint8_t temp = 0; - ESP_RETURN_ON_ERROR(i2c_master_transmit_receive(tca9554->i2c_handle, (uint8_t[]) { - INPUT_REG_ADDR - }, 1, &temp, sizeof(temp), I2C_TIMEOUT_MS), TAG, "Read input reg failed"); - *value = temp; - return ESP_OK; -} - -static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value) -{ - esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); - value &= 0xff; - - uint8_t data[] = {OUTPUT_REG_ADDR, value}; - ESP_RETURN_ON_ERROR(i2c_master_transmit(tca9554->i2c_handle, data, sizeof(data), I2C_TIMEOUT_MS), TAG, "Write output reg failed"); - tca9554->regs.output = value; - return ESP_OK; -} - -static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value) -{ - esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); - - *value = tca9554->regs.output; - return ESP_OK; -} - -static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value) -{ - esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); - value &= 0xff; - - uint8_t data[] = {DIRECTION_REG_ADDR, value}; - ESP_RETURN_ON_ERROR(i2c_master_transmit(tca9554->i2c_handle, data, sizeof(data), I2C_TIMEOUT_MS), TAG, "Write direction reg failed"); - tca9554->regs.direction = value; - return ESP_OK; -} - -static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value) -{ - esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); - - *value = tca9554->regs.direction; - return ESP_OK; -} - -static esp_err_t reset(esp_io_expander_t *handle) -{ - ESP_RETURN_ON_ERROR(write_direction_reg(handle, DIR_REG_DEFAULT_VAL), TAG, "Write dir reg failed"); - ESP_RETURN_ON_ERROR(write_output_reg(handle, OUT_REG_DEFAULT_VAL), TAG, "Write output reg failed"); - return ESP_OK; -} - -static esp_err_t del(esp_io_expander_t *handle) -{ - esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); - - ESP_RETURN_ON_ERROR(i2c_master_bus_rm_device(tca9554->i2c_handle), TAG, "Remove I2C device failed"); - free(tca9554); - return ESP_OK; -} +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "esp_bit_defs.h" +#include "esp_check.h" +#include "esp_log.h" +#include "esp_io_expander.h" +#include "esp_io_expander_tca9554.h" + +/* I2C communication related */ +#define I2C_TIMEOUT_MS (1000) +#define I2C_CLK_SPEED (400000) + +#define IO_COUNT (8) + +/* Register address */ +#define INPUT_REG_ADDR (0x00) +#define OUTPUT_REG_ADDR (0x01) +#define DIRECTION_REG_ADDR (0x03) + +/* Default register value on power-up */ +#define DIR_REG_DEFAULT_VAL (0xff) +#define OUT_REG_DEFAULT_VAL (0xff) + +/** + * @brief Device Structure Type + * + */ +typedef struct { + esp_io_expander_t base; + i2c_master_dev_handle_t i2c_handle; + struct { + uint8_t direction; + uint8_t output; + } regs; +} esp_io_expander_tca9554_t; + +static char *TAG = "tca9554"; + +static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value); +static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value); +static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value); +static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value); +static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value); +static esp_err_t reset(esp_io_expander_t *handle); +static esp_err_t del(esp_io_expander_t *handle); + +esp_err_t esp_io_expander_new_i2c_tca9554(i2c_master_bus_handle_t i2c_bus, uint32_t dev_addr, esp_io_expander_handle_t *handle_ret) +{ + ESP_RETURN_ON_FALSE(handle_ret != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid handle_ret"); + + // Allocate memory for driver object + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)calloc(1, sizeof(esp_io_expander_tca9554_t)); + ESP_RETURN_ON_FALSE(tca9554 != NULL, ESP_ERR_NO_MEM, TAG, "Malloc failed"); + + // Add new I2C device + esp_err_t ret = ESP_OK; + const i2c_device_config_t i2c_dev_cfg = { + .device_address = dev_addr, + .scl_speed_hz = I2C_CLK_SPEED, + }; + ESP_GOTO_ON_ERROR(i2c_master_bus_add_device(i2c_bus, &i2c_dev_cfg, &tca9554->i2c_handle), err, TAG, "Add new I2C device failed"); + + tca9554->base.config.io_count = IO_COUNT; + tca9554->base.config.flags.dir_out_bit_zero = 1; + tca9554->base.read_input_reg = read_input_reg; + tca9554->base.write_output_reg = write_output_reg; + tca9554->base.read_output_reg = read_output_reg; + tca9554->base.write_direction_reg = write_direction_reg; + tca9554->base.read_direction_reg = read_direction_reg; + tca9554->base.del = del; + tca9554->base.reset = reset; + + /* Reset configuration and register status */ + ESP_GOTO_ON_ERROR(reset(&tca9554->base), err, TAG, "Reset failed"); + + *handle_ret = &tca9554->base; + return ESP_OK; +err: + free(tca9554); + return ret; +} + +static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + + uint8_t temp = 0; + ESP_RETURN_ON_ERROR(i2c_master_transmit_receive(tca9554->i2c_handle, (uint8_t[]) { + INPUT_REG_ADDR + }, 1, &temp, sizeof(temp), I2C_TIMEOUT_MS), TAG, "Read input reg failed"); + *value = temp; + return ESP_OK; +} + +static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + value &= 0xff; + + uint8_t data[] = {OUTPUT_REG_ADDR, value}; + ESP_RETURN_ON_ERROR(i2c_master_transmit(tca9554->i2c_handle, data, sizeof(data), I2C_TIMEOUT_MS), TAG, "Write output reg failed"); + tca9554->regs.output = value; + return ESP_OK; +} + +static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + + *value = tca9554->regs.output; + return ESP_OK; +} + +static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + value &= 0xff; + + uint8_t data[] = {DIRECTION_REG_ADDR, value}; + ESP_RETURN_ON_ERROR(i2c_master_transmit(tca9554->i2c_handle, data, sizeof(data), I2C_TIMEOUT_MS), TAG, "Write direction reg failed"); + tca9554->regs.direction = value; + return ESP_OK; +} + +static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + + *value = tca9554->regs.direction; + return ESP_OK; +} + +static esp_err_t reset(esp_io_expander_t *handle) +{ + ESP_RETURN_ON_ERROR(write_direction_reg(handle, DIR_REG_DEFAULT_VAL), TAG, "Write dir reg failed"); + ESP_RETURN_ON_ERROR(write_output_reg(handle, OUT_REG_DEFAULT_VAL), TAG, "Write output reg failed"); + return ESP_OK; +} + +static esp_err_t del(esp_io_expander_t *handle) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + + ESP_RETURN_ON_ERROR(i2c_master_bus_rm_device(tca9554->i2c_handle), TAG, "Remove I2C device failed"); + free(tca9554); + return ESP_OK; +} diff --git a/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.h b/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.h index 192107c..6f33a52 100644 --- a/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.h +++ b/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.h @@ -1,90 +1,90 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @file - * @brief ESP IO expander: TCA9554 - */ - -#pragma once - -#include -#include "esp_err.h" -#include "driver/i2c_master.h" -#include "esp_io_expander.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Create a TCA9554(A) IO expander object - * - * @param[in] i2c_bus I2C bus handle. Obtained from `i2c_new_master_bus()` - * @param[in] dev_addr I2C device address of chip. Can be `ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_XXX` or `ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_XXX`. - * @param[out] handle_ret Handle to created IO expander object - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ -esp_err_t esp_io_expander_new_i2c_tca9554(i2c_master_bus_handle_t i2c_bus, uint32_t dev_addr, esp_io_expander_handle_t *handle_ret); - -/** - * @brief I2C address of the TCA9554 - * - * The 8-bit address format is as follows: - * - * (Slave Address) - * ┌─────────────────┷─────────────────┐ - * ┌─────┐─────┐─────┐─────┐─────┐─────┐─────┐─────┐ - * | 0 | 1 | 0 | 0 | A2 | A1 | A0 | R/W | - * └─────┘─────┘─────┘─────┘─────┘─────┘─────┘─────┘ - * └────────┯────────┘ └─────┯──────┘ - * (Fixed) (Hareware Selectable) - * - * And the 7-bit slave address is the most important data for users. - * For example, if a chip's A0,A1,A2 are connected to GND, it's 7-bit slave address is 0100000b(0x20). - * Then users can use `ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000` to init it. - */ -#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 (0x20) -#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_001 (0x21) -#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_010 (0x22) -#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_011 (0x23) -#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_100 (0x24) -#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_101 (0x25) -#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_110 (0x26) -#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_111 (0x27) - - -/** - * @brief I2C address of the TCA9554A - * - * The 8-bit address format is as follows: - * - * (Slave Address) - * ┌─────────────────┷─────────────────┐ - * ┌─────┐─────┐─────┐─────┐─────┐─────┐─────┐─────┐ - * | 0 | 1 | 1 | 1 | A2 | A1 | A0 | R/W | - * └─────┘─────┘─────┘─────┘─────┘─────┘─────┘─────┘ - * └────────┯────────┘ └─────┯──────┘ - * (Fixed) (Hareware Selectable) - * - * And the 7-bit slave address is the most important data for users. - * For example, if a chip's A0,A1,A2 are connected to GND, it's 7-bit slave address is 0111000b(0x38). - * Then users can use `ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_000` to init it. - */ -#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_000 (0x38) -#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_001 (0x39) -#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_010 (0x3A) -#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_011 (0x3B) -#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_100 (0x3C) -#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_101 (0x3D) -#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_110 (0x3E) -#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_111 (0x3F) - -#ifdef __cplusplus -} -#endif +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP IO expander: TCA9554 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "driver/i2c_master.h" +#include "esp_io_expander.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create a TCA9554(A) IO expander object + * + * @param[in] i2c_bus I2C bus handle. Obtained from `i2c_new_master_bus()` + * @param[in] dev_addr I2C device address of chip. Can be `ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_XXX` or `ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_XXX`. + * @param[out] handle_ret Handle to created IO expander object + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_new_i2c_tca9554(i2c_master_bus_handle_t i2c_bus, uint32_t dev_addr, esp_io_expander_handle_t *handle_ret); + +/** + * @brief I2C address of the TCA9554 + * + * The 8-bit address format is as follows: + * + * (Slave Address) + * ┌─────────────────┷─────────────────┐ + * ┌─────┐─────┐─────┐─────┐─────┐─────┐─────┐─────┐ + * | 0 | 1 | 0 | 0 | A2 | A1 | A0 | R/W | + * └─────┘─────┘─────┘─────┘─────┘─────┘─────┘─────┘ + * └────────┯────────┘ └─────┯──────┘ + * (Fixed) (Hareware Selectable) + * + * And the 7-bit slave address is the most important data for users. + * For example, if a chip's A0,A1,A2 are connected to GND, it's 7-bit slave address is 0100000b(0x20). + * Then users can use `ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000` to init it. + */ +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 (0x20) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_001 (0x21) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_010 (0x22) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_011 (0x23) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_100 (0x24) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_101 (0x25) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_110 (0x26) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_111 (0x27) + + +/** + * @brief I2C address of the TCA9554A + * + * The 8-bit address format is as follows: + * + * (Slave Address) + * ┌─────────────────┷─────────────────┐ + * ┌─────┐─────┐─────┐─────┐─────┐─────┐─────┐─────┐ + * | 0 | 1 | 1 | 1 | A2 | A1 | A0 | R/W | + * └─────┘─────┘─────┘─────┘─────┘─────┘─────┘─────┘ + * └────────┯────────┘ └─────┯──────┘ + * (Fixed) (Hareware Selectable) + * + * And the 7-bit slave address is the most important data for users. + * For example, if a chip's A0,A1,A2 are connected to GND, it's 7-bit slave address is 0111000b(0x38). + * Then users can use `ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_000` to init it. + */ +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_000 (0x38) +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_001 (0x39) +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_010 (0x3A) +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_011 (0x3B) +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_100 (0x3C) +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_101 (0x3D) +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_110 (0x3E) +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_111 (0x3F) + +#ifdef __cplusplus +} +#endif diff --git a/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.c b/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.c index 4db550e..c44baa2 100644 --- a/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.c +++ b/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.c @@ -1,504 +1,504 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "esp_lcd_gc9503.h" - -#define GC9503_CMD_MADCTL (0xB1) // Memory data access control -#define GC9503_CMD_MADCTL_DEFAULT (0x10) // Default value of Memory data access control -#define GC9503_CMD_SS_BIT (1 << 0) // Source driver scan direction, 0: top to bottom, 1: bottom to top -#define GC9503_CMD_GS_BIT (1 << 1) // Gate driver scan direction, 0: left to right, 1: right to left -#define GC9503_CMD_BGR_BIT (1 << 5) // RGB/BGR order, 0: RGB, 1: BGR - -typedef struct -{ - esp_lcd_panel_io_handle_t io; - int reset_gpio_num; - uint8_t madctl_val; // Save current value of GC9503_CMD_MADCTL register - uint8_t colmod_val; // Save current value of LCD_CMD_COLMOD register - const gc9503_lcd_init_cmd_t *init_cmds; - uint16_t init_cmds_size; - struct - { - unsigned int mirror_by_cmd : 1; - unsigned int auto_del_panel_io : 1; - unsigned int display_on_off_use_cmd : 1; - unsigned int reset_level : 1; - } flags; - // To save the original functions of RGB panel - esp_err_t (*init)(esp_lcd_panel_t *panel); - esp_err_t (*del)(esp_lcd_panel_t *panel); - esp_err_t (*reset)(esp_lcd_panel_t *panel); - esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis); - esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off); -} gc9503_panel_t; - -static const char *TAG = "gc9503"; - -static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503); - -static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool off); - -esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, - esp_lcd_panel_handle_t *ret_panel) -{ - ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments"); - gc9503_vendor_config_t *vendor_config = (gc9503_vendor_config_t *)panel_dev_config->vendor_config; - ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary"); - ESP_RETURN_ON_FALSE(!vendor_config->flags.auto_del_panel_io || !vendor_config->flags.mirror_by_cmd, - ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `auto_del_panel_io` cannot work together"); - - esp_err_t ret = ESP_OK; - gpio_config_t io_conf = {0}; - - gc9503_panel_t *gc9503 = (gc9503_panel_t *)calloc(1, sizeof(gc9503_panel_t)); - ESP_RETURN_ON_FALSE(gc9503, ESP_ERR_NO_MEM, TAG, "no mem for gc9503 panel"); - - if (panel_dev_config->reset_gpio_num >= 0) - { - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; - ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); - } - - gc9503->madctl_val = GC9503_CMD_MADCTL_DEFAULT; - switch (panel_dev_config->rgb_ele_order) - { - case LCD_RGB_ELEMENT_ORDER_RGB: - gc9503->madctl_val &= ~GC9503_CMD_BGR_BIT; - break; - case LCD_RGB_ELEMENT_ORDER_BGR: - gc9503->madctl_val |= GC9503_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order"); - break; - } - - gc9503->colmod_val = 0; - switch (panel_dev_config->bits_per_pixel) - { - case 16: // RGB565 - gc9503->colmod_val = 0x50; - break; - case 18: // RGB666 - gc9503->colmod_val = 0x60; - break; - case 24: // RGB888 - gc9503->colmod_val = 0x70; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); - break; - } - - gc9503->io = io; - gc9503->init_cmds = vendor_config->init_cmds; - gc9503->init_cmds_size = vendor_config->init_cmds_size; - gc9503->reset_gpio_num = panel_dev_config->reset_gpio_num; - gc9503->flags.reset_level = panel_dev_config->flags.reset_active_high; - gc9503->flags.auto_del_panel_io = vendor_config->flags.auto_del_panel_io; - gc9503->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd; - gc9503->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1; - - if (gc9503->flags.auto_del_panel_io) - { - if (gc9503->reset_gpio_num >= 0) - { // Perform hardware reset - gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); - } - else - { // Perform software reset - ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed"); - } - vTaskDelay(pdMS_TO_TICKS(120)); - - /** - * In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface - * (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before - * `esp_lcd_new_rgb_panel()` is called. - */ - ESP_GOTO_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), err, TAG, "send init commands failed"); - // After sending the initialization commands, the 3-wire SPI interface can be deleted - ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed"); - gc9503->io = NULL; - ESP_LOGD(TAG, "delete panel IO"); - } - - // Create RGB panel - ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed"); - ESP_LOGD(TAG, "new RGB panel @%p", ret_panel); - - // Save the original functions of RGB panel - gc9503->init = (*ret_panel)->init; - gc9503->del = (*ret_panel)->del; - gc9503->reset = (*ret_panel)->reset; - gc9503->mirror = (*ret_panel)->mirror; - gc9503->disp_on_off = (*ret_panel)->disp_on_off; - // Overwrite the functions of RGB panel - (*ret_panel)->init = panel_gc9503_init; - (*ret_panel)->del = panel_gc9503_del; - (*ret_panel)->reset = panel_gc9503_reset; - (*ret_panel)->mirror = panel_gc9503_mirror; - (*ret_panel)->disp_on_off = panel_gc9503_disp_on_off; - (*ret_panel)->user_data = gc9503; - ESP_LOGD(TAG, "new gc9503 panel @%p", gc9503); - - // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9503_VER_MAJOR, ESP_LCD_GC9503_VER_MINOR, - // ESP_LCD_GC9503_VER_PATCH); - return ESP_OK; - -err: - if (gc9503) - { - if (panel_dev_config->reset_gpio_num >= 0) - { - gpio_reset_pin(panel_dev_config->reset_gpio_num); - } - free(gc9503); - } - return ret; -} - -// *INDENT-OFF* -// static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { -// // {cmd, { data }, data_size, delay_ms} -// {0x11, (uint8_t []){0x00}, 0, 120}, - -// {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0}, -// {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0}, -// {0xc1, (uint8_t []){0x3f}, 1, 0}, -// {0xc2, (uint8_t []){0x0e}, 1, 0}, -// {0xc6, (uint8_t []){0xf8}, 1, 0}, -// {0xc9, (uint8_t []){0x10}, 1, 0}, -// {0xcd, (uint8_t []){0x25}, 1, 0}, -// {0xf8, (uint8_t []){0x8a}, 1, 0}, -// {0xac, (uint8_t []){0x45}, 1, 0}, -// {0xa0, (uint8_t []){0xdd}, 1, 0}, -// {0xa7, (uint8_t []){0x47}, 1, 0}, -// {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0}, -// {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0}, -// {0xa3, (uint8_t []){0xee}, 1, 0}, -// {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0}, -// {0x71, (uint8_t []){0x48}, 1, 0}, -// {0x72, (uint8_t []){0x48}, 1, 0}, -// {0x73, (uint8_t []){0x00, 0x44}, 2, 0}, -// {0x97, (uint8_t []){0xee}, 1, 0}, -// {0x83, (uint8_t []){0x93}, 1, 0}, -// {0x9a, (uint8_t []){0x72}, 1, 0}, -// {0x9b, (uint8_t []){0x5a}, 1, 0}, -// {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0}, -// {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, -// 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0}, -// {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0}, -// {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0}, -// {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0}, -// {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, -// {0x6b, (uint8_t []){0x07}, 1, 0}, -// {0x7a, (uint8_t []){0x08, 0x13}, 2, 0}, -// {0x7b, (uint8_t []){0x08, 0x13}, 2, 0}, -// {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0x11, (uint8_t []){0x00}, 0, 120}, -// {0x29, (uint8_t []){0x00}, 0, 20}, -// }; -static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { - // {0x11, (uint8_t[]){}, 0, 20}, - - {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0}, - {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0}, - {0xc1, (uint8_t []){0x3f}, 1, 0}, - {0xc2, (uint8_t []){0x0e}, 1, 0}, - {0xc6, (uint8_t []){0xf8}, 1, 0}, - {0xc9, (uint8_t []){0x10}, 1, 0}, - {0xcd, (uint8_t []){0x25}, 1, 0}, - {0xf8, (uint8_t []){0x8a}, 1, 0}, - {0xac, (uint8_t []){0x45}, 1, 0}, - {0xa0, (uint8_t []){0xdd}, 1, 0}, - {0xa7, (uint8_t []){0x47}, 1, 0}, - {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0}, - {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0}, - {0xa3, (uint8_t []){0xee}, 1, 0}, - {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0}, - {0x71, (uint8_t []){0x48}, 1, 0}, - {0x72, (uint8_t []){0x48}, 1, 0}, - {0x73, (uint8_t []){0x00, 0x44}, 2, 0}, - {0x97, (uint8_t []){0xee}, 1, 0}, - {0x83, (uint8_t []){0x93}, 1, 0}, - {0x9a, (uint8_t []){0x72}, 1, 0}, - {0x9b, (uint8_t []){0x5a}, 1, 0}, - {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0}, - {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, - 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0}, - {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, - {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, - {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, - {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, - {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0}, - {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0}, - {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0}, - {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, - {0x6b, (uint8_t []){0x07}, 1, 0}, - {0x7a, (uint8_t []){0x08, 0x13}, 2, 0}, - {0x7b, (uint8_t []){0x08, 0x13}, 2, 0}, - {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, - 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, - 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, - 0xff}, 52, 0}, - {0x11, (uint8_t []){0x00}, 0, 120}, - {0x29, (uint8_t []){0x00}, 0, 20}, - - }; - -// *INDENT-OFF* - -static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503) -{ - esp_lcd_panel_io_handle_t io = gc9503->io; - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ - gc9503->madctl_val, - }, - 1), - TAG, "send command failed"); - ; - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){ - gc9503->colmod_val, - }, - 1), - TAG, "send command failed"); - ; - - // Vendor specific initialization, it can be different between manufacturers - // should consult the LCD supplier for initialization sequence code - const gc9503_lcd_init_cmd_t *init_cmds = NULL; - uint16_t init_cmds_size = 0; - if (gc9503->init_cmds) - { - init_cmds = gc9503->init_cmds; - init_cmds_size = gc9503->init_cmds_size; - } - else - { - init_cmds = vendor_specific_init_default; - init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9503_lcd_init_cmd_t); - } - - bool is_cmd_overwritten = false; - for (int i = 0; i < init_cmds_size; i++) - { - // Check if the command has been used or conflicts with the internal - switch (init_cmds[i].cmd) - { - case LCD_CMD_MADCTL: - is_cmd_overwritten = true; - gc9503->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - case LCD_CMD_COLMOD: - is_cmd_overwritten = true; - gc9503->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - default: - is_cmd_overwritten = false; - break; - } - - if (is_cmd_overwritten) - { - ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", - init_cmds[i].cmd); - } - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), - TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); - } - ESP_LOGD(TAG, "send init commands success"); - - return ESP_OK; -} - -static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - - if (!gc9503->flags.auto_del_panel_io) - { - ESP_RETURN_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), TAG, "send init commands failed"); - } - // Init RGB panel - ESP_RETURN_ON_ERROR(gc9503->init(panel), TAG, "init RGB panel failed"); - - return ESP_OK; -} - -static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - - if (gc9503->reset_gpio_num >= 0) - { - gpio_reset_pin(gc9503->reset_gpio_num); - } - // Delete RGB panel - gc9503->del(panel); - free(gc9503); - ESP_LOGD(TAG, "del gc9503 panel @%p", gc9503); - return ESP_OK; -} - -static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - esp_lcd_panel_io_handle_t io = gc9503->io; - - // Perform hardware reset - if (gc9503->reset_gpio_num >= 0) - { - gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); - vTaskDelay(pdMS_TO_TICKS(120)); - } - else if (io) - { // Perform software reset - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(120)); - } - // Reset RGB panel - ESP_RETURN_ON_ERROR(gc9503->reset(panel), TAG, "reset RGB panel failed"); - - return ESP_OK; -} - -static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - esp_lcd_panel_io_handle_t io = gc9503->io; - - if (gc9503->flags.mirror_by_cmd) - { - ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); - // Control mirror through LCD command - if (mirror_x) - { - gc9503->madctl_val |= GC9503_CMD_GS_BIT; - } - else - { - gc9503->madctl_val &= ~GC9503_CMD_GS_BIT; - } - if (mirror_y) - { - gc9503->madctl_val |= GC9503_CMD_SS_BIT; - } - else - { - gc9503->madctl_val &= ~GC9503_CMD_SS_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ - gc9503->madctl_val, - }, - 1), - TAG, "send command failed"); - ; - } - else - { - // Control mirror through RGB panel - ESP_RETURN_ON_ERROR(gc9503->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed"); - } - return ESP_OK; -} - -static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool on_off) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - esp_lcd_panel_io_handle_t io = gc9503->io; - int command = 0; - - if (gc9503->flags.display_on_off_use_cmd) - { - ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); - // Control display on/off through LCD command - if (on_off) - { - command = LCD_CMD_DISPON; - } - else - { - command = LCD_CMD_DISPOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); - } - else - { - // Control display on/off through display control signal - ESP_RETURN_ON_ERROR(gc9503->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed"); - } - return ESP_OK; -} +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "esp_lcd_gc9503.h" + +#define GC9503_CMD_MADCTL (0xB1) // Memory data access control +#define GC9503_CMD_MADCTL_DEFAULT (0x10) // Default value of Memory data access control +#define GC9503_CMD_SS_BIT (1 << 0) // Source driver scan direction, 0: top to bottom, 1: bottom to top +#define GC9503_CMD_GS_BIT (1 << 1) // Gate driver scan direction, 0: left to right, 1: right to left +#define GC9503_CMD_BGR_BIT (1 << 5) // RGB/BGR order, 0: RGB, 1: BGR + +typedef struct +{ + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + uint8_t madctl_val; // Save current value of GC9503_CMD_MADCTL register + uint8_t colmod_val; // Save current value of LCD_CMD_COLMOD register + const gc9503_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; + struct + { + unsigned int mirror_by_cmd : 1; + unsigned int auto_del_panel_io : 1; + unsigned int display_on_off_use_cmd : 1; + unsigned int reset_level : 1; + } flags; + // To save the original functions of RGB panel + esp_err_t (*init)(esp_lcd_panel_t *panel); + esp_err_t (*del)(esp_lcd_panel_t *panel); + esp_err_t (*reset)(esp_lcd_panel_t *panel); + esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis); + esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off); +} gc9503_panel_t; + +static const char *TAG = "gc9503"; + +static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503); + +static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool off); + +esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, + esp_lcd_panel_handle_t *ret_panel) +{ + ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments"); + gc9503_vendor_config_t *vendor_config = (gc9503_vendor_config_t *)panel_dev_config->vendor_config; + ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary"); + ESP_RETURN_ON_FALSE(!vendor_config->flags.auto_del_panel_io || !vendor_config->flags.mirror_by_cmd, + ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `auto_del_panel_io` cannot work together"); + + esp_err_t ret = ESP_OK; + gpio_config_t io_conf = {0}; + + gc9503_panel_t *gc9503 = (gc9503_panel_t *)calloc(1, sizeof(gc9503_panel_t)); + ESP_RETURN_ON_FALSE(gc9503, ESP_ERR_NO_MEM, TAG, "no mem for gc9503 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) + { + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + + gc9503->madctl_val = GC9503_CMD_MADCTL_DEFAULT; + switch (panel_dev_config->rgb_ele_order) + { + case LCD_RGB_ELEMENT_ORDER_RGB: + gc9503->madctl_val &= ~GC9503_CMD_BGR_BIT; + break; + case LCD_RGB_ELEMENT_ORDER_BGR: + gc9503->madctl_val |= GC9503_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order"); + break; + } + + gc9503->colmod_val = 0; + switch (panel_dev_config->bits_per_pixel) + { + case 16: // RGB565 + gc9503->colmod_val = 0x50; + break; + case 18: // RGB666 + gc9503->colmod_val = 0x60; + break; + case 24: // RGB888 + gc9503->colmod_val = 0x70; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + gc9503->io = io; + gc9503->init_cmds = vendor_config->init_cmds; + gc9503->init_cmds_size = vendor_config->init_cmds_size; + gc9503->reset_gpio_num = panel_dev_config->reset_gpio_num; + gc9503->flags.reset_level = panel_dev_config->flags.reset_active_high; + gc9503->flags.auto_del_panel_io = vendor_config->flags.auto_del_panel_io; + gc9503->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd; + gc9503->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1; + + if (gc9503->flags.auto_del_panel_io) + { + if (gc9503->reset_gpio_num >= 0) + { // Perform hardware reset + gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); + } + else + { // Perform software reset + ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed"); + } + vTaskDelay(pdMS_TO_TICKS(120)); + + /** + * In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface + * (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before + * `esp_lcd_new_rgb_panel()` is called. + */ + ESP_GOTO_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), err, TAG, "send init commands failed"); + // After sending the initialization commands, the 3-wire SPI interface can be deleted + ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed"); + gc9503->io = NULL; + ESP_LOGD(TAG, "delete panel IO"); + } + + // Create RGB panel + ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed"); + ESP_LOGD(TAG, "new RGB panel @%p", ret_panel); + + // Save the original functions of RGB panel + gc9503->init = (*ret_panel)->init; + gc9503->del = (*ret_panel)->del; + gc9503->reset = (*ret_panel)->reset; + gc9503->mirror = (*ret_panel)->mirror; + gc9503->disp_on_off = (*ret_panel)->disp_on_off; + // Overwrite the functions of RGB panel + (*ret_panel)->init = panel_gc9503_init; + (*ret_panel)->del = panel_gc9503_del; + (*ret_panel)->reset = panel_gc9503_reset; + (*ret_panel)->mirror = panel_gc9503_mirror; + (*ret_panel)->disp_on_off = panel_gc9503_disp_on_off; + (*ret_panel)->user_data = gc9503; + ESP_LOGD(TAG, "new gc9503 panel @%p", gc9503); + + // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9503_VER_MAJOR, ESP_LCD_GC9503_VER_MINOR, + // ESP_LCD_GC9503_VER_PATCH); + return ESP_OK; + +err: + if (gc9503) + { + if (panel_dev_config->reset_gpio_num >= 0) + { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(gc9503); + } + return ret; +} + +// *INDENT-OFF* +// static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { +// // {cmd, { data }, data_size, delay_ms} +// {0x11, (uint8_t []){0x00}, 0, 120}, + +// {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0}, +// {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0}, +// {0xc1, (uint8_t []){0x3f}, 1, 0}, +// {0xc2, (uint8_t []){0x0e}, 1, 0}, +// {0xc6, (uint8_t []){0xf8}, 1, 0}, +// {0xc9, (uint8_t []){0x10}, 1, 0}, +// {0xcd, (uint8_t []){0x25}, 1, 0}, +// {0xf8, (uint8_t []){0x8a}, 1, 0}, +// {0xac, (uint8_t []){0x45}, 1, 0}, +// {0xa0, (uint8_t []){0xdd}, 1, 0}, +// {0xa7, (uint8_t []){0x47}, 1, 0}, +// {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0}, +// {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0}, +// {0xa3, (uint8_t []){0xee}, 1, 0}, +// {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0}, +// {0x71, (uint8_t []){0x48}, 1, 0}, +// {0x72, (uint8_t []){0x48}, 1, 0}, +// {0x73, (uint8_t []){0x00, 0x44}, 2, 0}, +// {0x97, (uint8_t []){0xee}, 1, 0}, +// {0x83, (uint8_t []){0x93}, 1, 0}, +// {0x9a, (uint8_t []){0x72}, 1, 0}, +// {0x9b, (uint8_t []){0x5a}, 1, 0}, +// {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0}, +// {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, +// 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0}, +// {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0}, +// {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0}, +// {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0}, +// {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, +// {0x6b, (uint8_t []){0x07}, 1, 0}, +// {0x7a, (uint8_t []){0x08, 0x13}, 2, 0}, +// {0x7b, (uint8_t []){0x08, 0x13}, 2, 0}, +// {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0x11, (uint8_t []){0x00}, 0, 120}, +// {0x29, (uint8_t []){0x00}, 0, 20}, +// }; +static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { + // {0x11, (uint8_t[]){}, 0, 20}, + + {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0}, + {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0}, + {0xc1, (uint8_t []){0x3f}, 1, 0}, + {0xc2, (uint8_t []){0x0e}, 1, 0}, + {0xc6, (uint8_t []){0xf8}, 1, 0}, + {0xc9, (uint8_t []){0x10}, 1, 0}, + {0xcd, (uint8_t []){0x25}, 1, 0}, + {0xf8, (uint8_t []){0x8a}, 1, 0}, + {0xac, (uint8_t []){0x45}, 1, 0}, + {0xa0, (uint8_t []){0xdd}, 1, 0}, + {0xa7, (uint8_t []){0x47}, 1, 0}, + {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0}, + {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0}, + {0xa3, (uint8_t []){0xee}, 1, 0}, + {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0}, + {0x71, (uint8_t []){0x48}, 1, 0}, + {0x72, (uint8_t []){0x48}, 1, 0}, + {0x73, (uint8_t []){0x00, 0x44}, 2, 0}, + {0x97, (uint8_t []){0xee}, 1, 0}, + {0x83, (uint8_t []){0x93}, 1, 0}, + {0x9a, (uint8_t []){0x72}, 1, 0}, + {0x9b, (uint8_t []){0x5a}, 1, 0}, + {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0}, + {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0}, + {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, + {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, + {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, + {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, + {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0}, + {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0}, + {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0}, + {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, + {0x6b, (uint8_t []){0x07}, 1, 0}, + {0x7a, (uint8_t []){0x08, 0x13}, 2, 0}, + {0x7b, (uint8_t []){0x08, 0x13}, 2, 0}, + {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, + 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, + 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, + 0xff}, 52, 0}, + {0x11, (uint8_t []){0x00}, 0, 120}, + {0x29, (uint8_t []){0x00}, 0, 20}, + + }; + +// *INDENT-OFF* + +static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503) +{ + esp_lcd_panel_io_handle_t io = gc9503->io; + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ + gc9503->madctl_val, + }, + 1), + TAG, "send command failed"); + ; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){ + gc9503->colmod_val, + }, + 1), + TAG, "send command failed"); + ; + + // Vendor specific initialization, it can be different between manufacturers + // should consult the LCD supplier for initialization sequence code + const gc9503_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + if (gc9503->init_cmds) + { + init_cmds = gc9503->init_cmds; + init_cmds_size = gc9503->init_cmds_size; + } + else + { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9503_lcd_init_cmd_t); + } + + bool is_cmd_overwritten = false; + for (int i = 0; i < init_cmds_size; i++) + { + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd) + { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + gc9503->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + gc9503->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) + { + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", + init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), + TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + + if (!gc9503->flags.auto_del_panel_io) + { + ESP_RETURN_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), TAG, "send init commands failed"); + } + // Init RGB panel + ESP_RETURN_ON_ERROR(gc9503->init(panel), TAG, "init RGB panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + + if (gc9503->reset_gpio_num >= 0) + { + gpio_reset_pin(gc9503->reset_gpio_num); + } + // Delete RGB panel + gc9503->del(panel); + free(gc9503); + ESP_LOGD(TAG, "del gc9503 panel @%p", gc9503); + return ESP_OK; +} + +static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = gc9503->io; + + // Perform hardware reset + if (gc9503->reset_gpio_num >= 0) + { + gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(120)); + } + else if (io) + { // Perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(120)); + } + // Reset RGB panel + ESP_RETURN_ON_ERROR(gc9503->reset(panel), TAG, "reset RGB panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = gc9503->io; + + if (gc9503->flags.mirror_by_cmd) + { + ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); + // Control mirror through LCD command + if (mirror_x) + { + gc9503->madctl_val |= GC9503_CMD_GS_BIT; + } + else + { + gc9503->madctl_val &= ~GC9503_CMD_GS_BIT; + } + if (mirror_y) + { + gc9503->madctl_val |= GC9503_CMD_SS_BIT; + } + else + { + gc9503->madctl_val &= ~GC9503_CMD_SS_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ + gc9503->madctl_val, + }, + 1), + TAG, "send command failed"); + ; + } + else + { + // Control mirror through RGB panel + ESP_RETURN_ON_ERROR(gc9503->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed"); + } + return ESP_OK; +} + +static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = gc9503->io; + int command = 0; + + if (gc9503->flags.display_on_off_use_cmd) + { + ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); + // Control display on/off through LCD command + if (on_off) + { + command = LCD_CMD_DISPON; + } + else + { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + } + else + { + // Control display on/off through display control signal + ESP_RETURN_ON_ERROR(gc9503->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed"); + } + return ESP_OK; +} diff --git a/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.h b/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.h index 8feafd1..d1039a3 100644 --- a/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.h +++ b/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.h @@ -1,167 +1,167 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -/** - * @file - * @brief ESP LCD: GC9503 - */ - -#pragma once - -#include - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief LCD panel initialization commands. - * - */ -typedef struct { - int cmd; /* + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /* -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 16000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_45 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_41 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_39 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_42 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_DC_GPIO GPIO_NUM_43 -#define DISPLAY_CS_GPIO GPIO_NUM_44 -#define DISPLAY_CLK_GPIO GPIO_NUM_21 -#define DISPLAY_MOSI_GPIO GPIO_NUM_47 -#define DISPLAY_RST_GPIO GPIO_NUM_NC - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define UART_ECHO_TXD GPIO_NUM_38 -#define UART_ECHO_RXD GPIO_NUM_48 -#define UART_ECHO_RTS (-1) -#define UART_ECHO_CTS (-1) - -#define MOTOR_SPEED_MAX 100 -#define MOTOR_SPEED_80 80 -#define MOTOR_SPEED_60 60 -#define MOTOR_SPEED_MIN 0 - -#define ECHO_UART_PORT_NUM UART_NUM_1 -#define ECHO_UART_BAUD_RATE (115200) -#define BUF_SIZE (1024) - -typedef enum { - LIGHT_MODE_CHARGING_BREATH = 0, - LIGHT_MODE_POWER_LOW, - LIGHT_MODE_ALWAYS_ON, - LIGHT_MODE_BLINK, - LIGHT_MODE_WHITE_BREATH_SLOW, - LIGHT_MODE_WHITE_BREATH_FAST, - LIGHT_MODE_FLOWING, - LIGHT_MODE_SHOW, - LIGHT_MODE_SLEEP, - LIGHT_MODE_MAX -} light_mode_t; - -/* Camera PINs*/ -#define SPARKBOT_CAMERA_XCLK (GPIO_NUM_15) -#define SPARKBOT_CAMERA_PCLK (GPIO_NUM_13) -#define SPARKBOT_CAMERA_VSYNC (GPIO_NUM_6) -#define SPARKBOT_CAMERA_HSYNC (GPIO_NUM_7) -#define SPARKBOT_CAMERA_D0 (GPIO_NUM_11) -#define SPARKBOT_CAMERA_D1 (GPIO_NUM_9) -#define SPARKBOT_CAMERA_D2 (GPIO_NUM_8) -#define SPARKBOT_CAMERA_D3 (GPIO_NUM_10) -#define SPARKBOT_CAMERA_D4 (GPIO_NUM_12) -#define SPARKBOT_CAMERA_D5 (GPIO_NUM_18) -#define SPARKBOT_CAMERA_D6 (GPIO_NUM_17) -#define SPARKBOT_CAMERA_D7 (GPIO_NUM_16) - -#define SPARKBOT_CAMERA_PWDN (GPIO_NUM_NC) -#define SPARKBOT_CAMERA_RESET (GPIO_NUM_NC) -#define SPARKBOT_CAMERA_XCLK (GPIO_NUM_15) -#define SPARKBOT_CAMERA_PCLK (GPIO_NUM_13) -#define SPARKBOT_CAMERA_VSYNC (GPIO_NUM_6) -#define SPARKBOT_CAMERA_HSYNC (GPIO_NUM_7) - -#define SPARKBOT_CAMERA_XCLK_FREQ (16000000) -#define SPARKBOT_LEDC_TIMER (LEDC_TIMER_0) -#define SPARKBOT_LEDC_CHANNEL (LEDC_CHANNEL_0) - -#define SPARKBOT_CAMERA_SIOD (GPIO_NUM_NC) -#define SPARKBOT_CAMERA_SIOC (GPIO_NUM_NC) - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 16000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_45 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_41 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_39 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_42 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_DC_GPIO GPIO_NUM_43 +#define DISPLAY_CS_GPIO GPIO_NUM_44 +#define DISPLAY_CLK_GPIO GPIO_NUM_21 +#define DISPLAY_MOSI_GPIO GPIO_NUM_47 +#define DISPLAY_RST_GPIO GPIO_NUM_NC + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define UART_ECHO_TXD GPIO_NUM_38 +#define UART_ECHO_RXD GPIO_NUM_48 +#define UART_ECHO_RTS (-1) +#define UART_ECHO_CTS (-1) + +#define MOTOR_SPEED_MAX 100 +#define MOTOR_SPEED_80 80 +#define MOTOR_SPEED_60 60 +#define MOTOR_SPEED_MIN 0 + +#define ECHO_UART_PORT_NUM UART_NUM_1 +#define ECHO_UART_BAUD_RATE (115200) +#define BUF_SIZE (1024) + +typedef enum { + LIGHT_MODE_CHARGING_BREATH = 0, + LIGHT_MODE_POWER_LOW, + LIGHT_MODE_ALWAYS_ON, + LIGHT_MODE_BLINK, + LIGHT_MODE_WHITE_BREATH_SLOW, + LIGHT_MODE_WHITE_BREATH_FAST, + LIGHT_MODE_FLOWING, + LIGHT_MODE_SHOW, + LIGHT_MODE_SLEEP, + LIGHT_MODE_MAX +} light_mode_t; + +/* Camera PINs*/ +#define SPARKBOT_CAMERA_XCLK (GPIO_NUM_15) +#define SPARKBOT_CAMERA_PCLK (GPIO_NUM_13) +#define SPARKBOT_CAMERA_VSYNC (GPIO_NUM_6) +#define SPARKBOT_CAMERA_HSYNC (GPIO_NUM_7) +#define SPARKBOT_CAMERA_D0 (GPIO_NUM_11) +#define SPARKBOT_CAMERA_D1 (GPIO_NUM_9) +#define SPARKBOT_CAMERA_D2 (GPIO_NUM_8) +#define SPARKBOT_CAMERA_D3 (GPIO_NUM_10) +#define SPARKBOT_CAMERA_D4 (GPIO_NUM_12) +#define SPARKBOT_CAMERA_D5 (GPIO_NUM_18) +#define SPARKBOT_CAMERA_D6 (GPIO_NUM_17) +#define SPARKBOT_CAMERA_D7 (GPIO_NUM_16) + +#define SPARKBOT_CAMERA_PWDN (GPIO_NUM_NC) +#define SPARKBOT_CAMERA_RESET (GPIO_NUM_NC) +#define SPARKBOT_CAMERA_XCLK (GPIO_NUM_15) +#define SPARKBOT_CAMERA_PCLK (GPIO_NUM_13) +#define SPARKBOT_CAMERA_VSYNC (GPIO_NUM_6) +#define SPARKBOT_CAMERA_HSYNC (GPIO_NUM_7) + +#define SPARKBOT_CAMERA_XCLK_FREQ (16000000) +#define SPARKBOT_LEDC_TIMER (LEDC_TIMER_0) +#define SPARKBOT_LEDC_CHANNEL (LEDC_CHANNEL_0) + +#define SPARKBOT_CAMERA_SIOD (GPIO_NUM_NC) +#define SPARKBOT_CAMERA_SIOC (GPIO_NUM_NC) + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp-sparkbot/config.json b/main/boards/esp-sparkbot/config.json index 71ac417..fd7b9f4 100644 --- a/main/boards/esp-sparkbot/config.json +++ b/main/boards/esp-sparkbot/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp-sparkbot", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp-sparkbot", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/esp-sparkbot/esp_sparkbot_board.cc b/main/boards/esp-sparkbot/esp_sparkbot_board.cc index 2c76036..dc3033c 100644 --- a/main/boards/esp-sparkbot/esp_sparkbot_board.cc +++ b/main/boards/esp-sparkbot/esp_sparkbot_board.cc @@ -1,295 +1,295 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "mcp_server.h" -#include "settings.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "esp32_camera.h" - -#define TAG "esp_sparkbot" - -class SparkBotEs8311AudioCodec : public Es8311AudioCodec { -private: - -public: - SparkBotEs8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true) - : Es8311AudioCodec(i2c_master_handle, i2c_port, input_sample_rate, output_sample_rate, - mclk, bclk, ws, dout, din,pa_pin, es8311_addr, use_mclk = true) {} - - void EnableOutput(bool enable) override { - if (enable == output_enabled_) { - return; - } - if (enable) { - Es8311AudioCodec::EnableOutput(enable); - } else { - // Nothing todo because the display io and PA io conflict - } - } -}; - -class EspSparkBot : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - Display* display_; - Esp32Camera* camera_; - light_mode_t light_mode_ = LIGHT_MODE_ALWAYS_ON; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_GPIO; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_CLK_GPIO; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_GPIO; - io_config.dc_gpio_num = DISPLAY_DC_GPIO; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, true); - esp_lcd_panel_disp_on_off(panel, true); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeCamera() { - camera_config_t camera_config = {}; - - camera_config.pin_pwdn = SPARKBOT_CAMERA_PWDN; - camera_config.pin_reset = SPARKBOT_CAMERA_RESET; - camera_config.pin_xclk = SPARKBOT_CAMERA_XCLK; - camera_config.pin_pclk = SPARKBOT_CAMERA_PCLK; - camera_config.pin_sccb_sda = SPARKBOT_CAMERA_SIOD; - camera_config.pin_sccb_scl = SPARKBOT_CAMERA_SIOC; - - camera_config.pin_d0 = SPARKBOT_CAMERA_D0; - camera_config.pin_d1 = SPARKBOT_CAMERA_D1; - camera_config.pin_d2 = SPARKBOT_CAMERA_D2; - camera_config.pin_d3 = SPARKBOT_CAMERA_D3; - camera_config.pin_d4 = SPARKBOT_CAMERA_D4; - camera_config.pin_d5 = SPARKBOT_CAMERA_D5; - camera_config.pin_d6 = SPARKBOT_CAMERA_D6; - camera_config.pin_d7 = SPARKBOT_CAMERA_D7; - - camera_config.pin_vsync = SPARKBOT_CAMERA_VSYNC; - camera_config.pin_href = SPARKBOT_CAMERA_HSYNC; - camera_config.pin_pclk = SPARKBOT_CAMERA_PCLK; - camera_config.xclk_freq_hz = SPARKBOT_CAMERA_XCLK_FREQ; - camera_config.ledc_timer = SPARKBOT_LEDC_TIMER; - camera_config.ledc_channel = SPARKBOT_LEDC_CHANNEL; - camera_config.fb_location = CAMERA_FB_IN_PSRAM; - - camera_config.sccb_i2c_port = I2C_NUM_0; - - camera_config.pixel_format = PIXFORMAT_RGB565; - camera_config.frame_size = FRAMESIZE_240X240; - camera_config.jpeg_quality = 12; - camera_config.fb_count = 1; - camera_config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - camera_ = new Esp32Camera(camera_config); - - Settings settings("sparkbot", false); - // 考虑到部分复刻使用了不可动摄像头的设计,默认启用翻转 - bool camera_flipped = static_cast(settings.GetInt("camera-flipped", 1)); - camera_->SetHMirror(camera_flipped); - camera_->SetVFlip(camera_flipped); - } - - /* - ESP-SparkBot 的底座 - https://gitee.com/esp-friends/esp_sparkbot/tree/master/example/tank/c2_tracked_chassis - */ - void InitializeEchoUart() { - uart_config_t uart_config = { - .baud_rate = ECHO_UART_BAUD_RATE, - .data_bits = UART_DATA_8_BITS, - .parity = UART_PARITY_DISABLE, - .stop_bits = UART_STOP_BITS_1, - .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, - .source_clk = UART_SCLK_DEFAULT, - }; - int intr_alloc_flags = 0; - - ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags)); - ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config)); - ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, UART_ECHO_TXD, UART_ECHO_RXD, UART_ECHO_RTS, UART_ECHO_CTS)); - - SendUartMessage("w2"); - } - - void SendUartMessage(const char * command_str) { - uint8_t len = strlen(command_str); - uart_write_bytes(ECHO_UART_PORT_NUM, command_str, len); - ESP_LOGI(TAG, "Sent command: %s", command_str); - } - - void InitializeTools() { - auto& mcp_server = McpServer::GetInstance(); - // 定义设备的属性 - mcp_server.AddTool("self.chassis.get_light_mode", "获取灯光效果编号", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - if (light_mode_ < 2) { - return 1; - } else { - return light_mode_ - 2; - } - }); - - mcp_server.AddTool("self.chassis.go_forward", "前进", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - SendUartMessage("x0.0 y1.0"); - return true; - }); - - mcp_server.AddTool("self.chassis.go_back", "后退", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - SendUartMessage("x0.0 y-1.0"); - return true; - }); - - mcp_server.AddTool("self.chassis.turn_left", "向左转", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - SendUartMessage("x-1.0 y0.0"); - return true; - }); - - mcp_server.AddTool("self.chassis.turn_right", "向右转", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - SendUartMessage("x1.0 y0.0"); - return true; - }); - - mcp_server.AddTool("self.chassis.dance", "跳舞", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - SendUartMessage("d1"); - light_mode_ = LIGHT_MODE_MAX; - return true; - }); - - mcp_server.AddTool("self.chassis.switch_light_mode", "打开灯光效果", PropertyList({ - Property("light_mode", kPropertyTypeInteger, 1, 6) - }), [this](const PropertyList& properties) -> ReturnValue { - char command_str[5] = {'w', 0, 0}; - char mode = static_cast(properties["light_mode"].value()); - - ESP_LOGI(TAG, "Switch Light Mode: %c", (mode + '0')); - - if (mode >= 3 && mode <= 8) { - command_str[1] = mode + '0'; - SendUartMessage(command_str); - return true; - } - throw std::runtime_error("Invalid light mode"); - }); - - mcp_server.AddTool("self.camera.set_camera_flipped", "翻转摄像头图像方向", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - Settings settings("sparkbot", true); - // 考虑到部分复刻使用了不可动摄像头的设计,默认启用翻转 - bool flipped = !static_cast(settings.GetInt("camera-flipped", 1)); - - camera_->SetHMirror(flipped); - camera_->SetVFlip(flipped); - - settings.SetInt("camera-flipped", flipped ? 1 : 0); - - return true; - }); - } - -public: - EspSparkBot() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeSpi(); - InitializeDisplay(); - InitializeButtons(); - InitializeCamera(); - InitializeEchoUart(); - InitializeTools(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static SparkBotEs8311AudioCodec audio_codec(i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(EspSparkBot); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "mcp_server.h" +#include "settings.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "esp32_camera.h" + +#define TAG "esp_sparkbot" + +class SparkBotEs8311AudioCodec : public Es8311AudioCodec { +private: + +public: + SparkBotEs8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true) + : Es8311AudioCodec(i2c_master_handle, i2c_port, input_sample_rate, output_sample_rate, + mclk, bclk, ws, dout, din,pa_pin, es8311_addr, use_mclk = true) {} + + void EnableOutput(bool enable) override { + if (enable == output_enabled_) { + return; + } + if (enable) { + Es8311AudioCodec::EnableOutput(enable); + } else { + // Nothing todo because the display io and PA io conflict + } + } +}; + +class EspSparkBot : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + Display* display_; + Esp32Camera* camera_; + light_mode_t light_mode_ = LIGHT_MODE_ALWAYS_ON; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_GPIO; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_CLK_GPIO; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_GPIO; + io_config.dc_gpio_num = DISPLAY_DC_GPIO; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_disp_on_off(panel, true); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeCamera() { + camera_config_t camera_config = {}; + + camera_config.pin_pwdn = SPARKBOT_CAMERA_PWDN; + camera_config.pin_reset = SPARKBOT_CAMERA_RESET; + camera_config.pin_xclk = SPARKBOT_CAMERA_XCLK; + camera_config.pin_pclk = SPARKBOT_CAMERA_PCLK; + camera_config.pin_sccb_sda = SPARKBOT_CAMERA_SIOD; + camera_config.pin_sccb_scl = SPARKBOT_CAMERA_SIOC; + + camera_config.pin_d0 = SPARKBOT_CAMERA_D0; + camera_config.pin_d1 = SPARKBOT_CAMERA_D1; + camera_config.pin_d2 = SPARKBOT_CAMERA_D2; + camera_config.pin_d3 = SPARKBOT_CAMERA_D3; + camera_config.pin_d4 = SPARKBOT_CAMERA_D4; + camera_config.pin_d5 = SPARKBOT_CAMERA_D5; + camera_config.pin_d6 = SPARKBOT_CAMERA_D6; + camera_config.pin_d7 = SPARKBOT_CAMERA_D7; + + camera_config.pin_vsync = SPARKBOT_CAMERA_VSYNC; + camera_config.pin_href = SPARKBOT_CAMERA_HSYNC; + camera_config.pin_pclk = SPARKBOT_CAMERA_PCLK; + camera_config.xclk_freq_hz = SPARKBOT_CAMERA_XCLK_FREQ; + camera_config.ledc_timer = SPARKBOT_LEDC_TIMER; + camera_config.ledc_channel = SPARKBOT_LEDC_CHANNEL; + camera_config.fb_location = CAMERA_FB_IN_PSRAM; + + camera_config.sccb_i2c_port = I2C_NUM_0; + + camera_config.pixel_format = PIXFORMAT_RGB565; + camera_config.frame_size = FRAMESIZE_240X240; + camera_config.jpeg_quality = 12; + camera_config.fb_count = 1; + camera_config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + camera_ = new Esp32Camera(camera_config); + + Settings settings("sparkbot", false); + // 考虑到部分复刻使用了不可动摄像头的设计,默认启用翻转 + bool camera_flipped = static_cast(settings.GetInt("camera-flipped", 1)); + camera_->SetHMirror(camera_flipped); + camera_->SetVFlip(camera_flipped); + } + + /* + ESP-SparkBot 的底座 + https://gitee.com/esp-friends/esp_sparkbot/tree/master/example/tank/c2_tracked_chassis + */ + void InitializeEchoUart() { + uart_config_t uart_config = { + .baud_rate = ECHO_UART_BAUD_RATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_DEFAULT, + }; + int intr_alloc_flags = 0; + + ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags)); + ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config)); + ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, UART_ECHO_TXD, UART_ECHO_RXD, UART_ECHO_RTS, UART_ECHO_CTS)); + + SendUartMessage("w2"); + } + + void SendUartMessage(const char * command_str) { + uint8_t len = strlen(command_str); + uart_write_bytes(ECHO_UART_PORT_NUM, command_str, len); + ESP_LOGI(TAG, "Sent command: %s", command_str); + } + + void InitializeTools() { + auto& mcp_server = McpServer::GetInstance(); + // 定义设备的属性 + mcp_server.AddTool("self.chassis.get_light_mode", "获取灯光效果编号", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + if (light_mode_ < 2) { + return 1; + } else { + return light_mode_ - 2; + } + }); + + mcp_server.AddTool("self.chassis.go_forward", "前进", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SendUartMessage("x0.0 y1.0"); + return true; + }); + + mcp_server.AddTool("self.chassis.go_back", "后退", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SendUartMessage("x0.0 y-1.0"); + return true; + }); + + mcp_server.AddTool("self.chassis.turn_left", "向左转", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SendUartMessage("x-1.0 y0.0"); + return true; + }); + + mcp_server.AddTool("self.chassis.turn_right", "向右转", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SendUartMessage("x1.0 y0.0"); + return true; + }); + + mcp_server.AddTool("self.chassis.dance", "跳舞", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SendUartMessage("d1"); + light_mode_ = LIGHT_MODE_MAX; + return true; + }); + + mcp_server.AddTool("self.chassis.switch_light_mode", "打开灯光效果", PropertyList({ + Property("light_mode", kPropertyTypeInteger, 1, 6) + }), [this](const PropertyList& properties) -> ReturnValue { + char command_str[5] = {'w', 0, 0}; + char mode = static_cast(properties["light_mode"].value()); + + ESP_LOGI(TAG, "Switch Light Mode: %c", (mode + '0')); + + if (mode >= 3 && mode <= 8) { + command_str[1] = mode + '0'; + SendUartMessage(command_str); + return true; + } + throw std::runtime_error("Invalid light mode"); + }); + + mcp_server.AddTool("self.camera.set_camera_flipped", "翻转摄像头图像方向", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + Settings settings("sparkbot", true); + // 考虑到部分复刻使用了不可动摄像头的设计,默认启用翻转 + bool flipped = !static_cast(settings.GetInt("camera-flipped", 1)); + + camera_->SetHMirror(flipped); + camera_->SetVFlip(flipped); + + settings.SetInt("camera-flipped", flipped ? 1 : 0); + + return true; + }); + } + +public: + EspSparkBot() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeDisplay(); + InitializeButtons(); + InitializeCamera(); + InitializeEchoUart(); + InitializeTools(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static SparkBotEs8311AudioCodec audio_codec(i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(EspSparkBot); diff --git a/main/boards/esp-spot-s3/README.md b/main/boards/esp-spot-s3/README.md index b63ef3a..d3d652a 100644 --- a/main/boards/esp-spot-s3/README.md +++ b/main/boards/esp-spot-s3/README.md @@ -1,55 +1,55 @@ -# ESP-Spot S3 - -## 简介 - - - -ESP-Spot 是 ESP Friends 开源的一款智能语音交互盒子,内置麦克风、扬声器、IMU 惯性传感器,可使用电池供电。ESP-Spot 不带屏幕,带有一个 RGB 指示灯和两个按钮。硬件详情可查看[立创开源项目](https://oshwhub.com/esp-college/esp-spot)。 - -ESP-Spot 开源项目采用 ESP32-S3-WROOM-1-N16R8 模组。如在复刻时使用了其他大小的 Flash,需修改对应的参数。 - - -## 配置、编译命令 - -**配置编译目标为 ESP32S3** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig 并配置** - -```bash -idf.py menuconfig -``` - -分别配置如下选项: - -- `Xiaozhi Assistant` → `Board Type` → 选择 `ESP-Spot-S3` - -按 `S` 保存,按 `Q` 退出。 - -**编译** - -```bash -idf.py build -``` - -**烧录** - -```bash -idf.py flash -``` - -> [!TIP] -> -> **若电脑始终无法找到 ESP-Spot 串口,可尝试如下步骤** -> 1. 打开前盖; -> 2. 拔出带有模组的 PCB 板; -> 3. 按住 BOOT 同时插回 PCB 版,注意不要颠倒; -> -> 此时, ESP-Spot 应当已进入下载模式。在烧录完成后,可能需要重新插拔 PCB 板。 +# ESP-Spot S3 + +## 简介 + + + +ESP-Spot 是 ESP Friends 开源的一款智能语音交互盒子,内置麦克风、扬声器、IMU 惯性传感器,可使用电池供电。ESP-Spot 不带屏幕,带有一个 RGB 指示灯和两个按钮。硬件详情可查看[立创开源项目](https://oshwhub.com/esp-college/esp-spot)。 + +ESP-Spot 开源项目采用 ESP32-S3-WROOM-1-N16R8 模组。如在复刻时使用了其他大小的 Flash,需修改对应的参数。 + + +## 配置、编译命令 + +**配置编译目标为 ESP32S3** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig 并配置** + +```bash +idf.py menuconfig +``` + +分别配置如下选项: + +- `Xiaozhi Assistant` → `Board Type` → 选择 `ESP-Spot-S3` + +按 `S` 保存,按 `Q` 退出。 + +**编译** + +```bash +idf.py build +``` + +**烧录** + +```bash +idf.py flash +``` + +> [!TIP] +> +> **若电脑始终无法找到 ESP-Spot 串口,可尝试如下步骤** +> 1. 打开前盖; +> 2. 拔出带有模组的 PCB 板; +> 3. 按住 BOOT 同时插回 PCB 版,注意不要颠倒; +> +> 此时, ESP-Spot 应当已进入下载模式。在烧录完成后,可能需要重新插拔 PCB 板。 diff --git a/main/boards/esp-spot-s3/config.h b/main/boards/esp-spot-s3/config.h index 0acf05c..9357d89 100644 --- a/main/boards/esp-spot-s3/config.h +++ b/main/boards/esp-spot-s3/config.h @@ -1,35 +1,35 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 16000 - -#define AUDIO_INPUT_REFERENCE false - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_17 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_18 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_40 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define KEY_BUTTON_GPIO GPIO_NUM_12 -#define LED_PIN GPIO_NUM_11 - -#define VBAT_ADC_CHANNEL ADC_CHANNEL_9 // S3: IO10 -#define MCU_VCC_CTL GPIO_NUM_4 // set 1 to power on MCU -#define PERP_VCC_CTL GPIO_NUM_6 // set 1 to power on peripherals - -#define ADC_ATTEN ADC_ATTEN_DB_12 -#define ADC_WIDTH ADC_BITWIDTH_DEFAULT -#define FULL_BATTERY_VOLTAGE 4100 -#define EMPTY_BATTERY_VOLTAGE 3200 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 16000 + +#define AUDIO_INPUT_REFERENCE false + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_17 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_18 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_40 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define KEY_BUTTON_GPIO GPIO_NUM_12 +#define LED_PIN GPIO_NUM_11 + +#define VBAT_ADC_CHANNEL ADC_CHANNEL_9 // S3: IO10 +#define MCU_VCC_CTL GPIO_NUM_4 // set 1 to power on MCU +#define PERP_VCC_CTL GPIO_NUM_6 // set 1 to power on peripherals + +#define ADC_ATTEN ADC_ATTEN_DB_12 +#define ADC_WIDTH ADC_BITWIDTH_DEFAULT +#define FULL_BATTERY_VOLTAGE 4100 +#define EMPTY_BATTERY_VOLTAGE 3200 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp-spot-s3/esp_spot_s3_board.cc b/main/boards/esp-spot-s3/esp_spot_s3_board.cc index 0ff6139..ddd2e78 100644 --- a/main/boards/esp-spot-s3/esp_spot_s3_board.cc +++ b/main/boards/esp-spot-s3/esp_spot_s3_board.cc @@ -1,242 +1,242 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "sdkconfig.h" - -#include -#include -#include -#include -#include "esp_adc/adc_oneshot.h" -#include "esp_adc/adc_cali.h" -#include "esp_adc/adc_cali_scheme.h" - -#include -#include "esp_timer.h" -#include "led/circular_strip.h" - -#define TAG "esp_spot_s3" - -bool button_released_ = false; -bool shutdown_ready_ = false; -esp_timer_handle_t shutdown_timer; - -class EspSpotS3Bot : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - Button key_button_; - adc_oneshot_unit_handle_t adc1_handle; - adc_cali_handle_t adc1_cali_handle; - bool do_calibration = false; - bool key_long_pressed = false; - int64_t last_key_press_time = 0; - static const int64_t LONG_PRESS_TIMEOUT_US = 5 * 1000000ULL; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeADC() { - adc_oneshot_unit_init_cfg_t init_config1 = { - .unit_id = ADC_UNIT_1 - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN, - .bitwidth = ADC_WIDTH, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, VBAT_ADC_CHANNEL, &chan_config)); - - adc_cali_handle_t handle = NULL; - esp_err_t ret = ESP_FAIL; - -#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - adc_cali_curve_fitting_config_t cali_config = { - .unit_id = ADC_UNIT_1, - .atten = ADC_ATTEN, - .bitwidth = ADC_WIDTH, - }; - ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); - if (ret == ESP_OK) { - do_calibration = true; - adc1_cali_handle = handle; - ESP_LOGI(TAG, "ADC Curve Fitting calibration succeeded"); - } -#endif // ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - ResetWifiConfiguration(); - }); - - key_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - app.ToggleChatState(); - key_long_pressed = false; - }); - - key_button_.OnLongPress([this]() { - int64_t now = esp_timer_get_time(); - auto* led = static_cast(this->GetLed()); - - if (key_long_pressed) { - if ((now - last_key_press_time) < LONG_PRESS_TIMEOUT_US) { - ESP_LOGW(TAG, "Key button long pressed the second time within 5s, shutting down..."); - led->SetSingleColor(0, {0, 0, 0}); - - gpio_hold_dis(MCU_VCC_CTL); - gpio_set_level(MCU_VCC_CTL, 0); - - } else { - last_key_press_time = now; - BlinkGreenFor5s(); - } - key_long_pressed = true; - } else { - ESP_LOGW(TAG, "Key button first long press! Waiting second within 5s to shutdown..."); - last_key_press_time = now; - key_long_pressed = true; - - BlinkGreenFor5s(); - } - }); - } - - void InitializePowerCtl() { - InitializeGPIO(); - - gpio_set_level(MCU_VCC_CTL, 1); - gpio_hold_en(MCU_VCC_CTL); - - gpio_set_level(PERP_VCC_CTL, 1); - gpio_hold_en(PERP_VCC_CTL); - } - - void InitializeGPIO() { - gpio_config_t io_pa = { - .pin_bit_mask = (1ULL << AUDIO_CODEC_PA_PIN), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE - }; - gpio_config(&io_pa); - gpio_set_level(AUDIO_CODEC_PA_PIN, 0); - - gpio_config_t io_conf_1 = { - .pin_bit_mask = (1ULL << MCU_VCC_CTL), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE - }; - gpio_config(&io_conf_1); - - gpio_config_t io_conf_2 = { - .pin_bit_mask = (1ULL << PERP_VCC_CTL), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE - }; - gpio_config(&io_conf_2); - } - - void BlinkGreenFor5s() { - auto* led = static_cast(GetLed()); - if (!led) { - return; - } - - led->Blink({50, 25, 0}, 100); - - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - auto* self = static_cast(arg); - auto* led = static_cast(self->GetLed()); - if (led) { - led->SetSingleColor(0, {0, 0, 0}); - } - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "blinkGreenFor5s_timer" - }; - - esp_timer_handle_t blink_timer = nullptr; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &blink_timer)); - ESP_ERROR_CHECK(esp_timer_start_once(blink_timer, LONG_PRESS_TIMEOUT_US)); - } - -public: - EspSpotS3Bot() : boot_button_(BOOT_BUTTON_GPIO), key_button_(KEY_BUTTON_GPIO, true) { - InitializePowerCtl(); - InitializeADC(); - InitializeI2c(); - InitializeButtons(); - } - - virtual Led* GetLed() override { - static CircularStrip led(LED_PIN, 1); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(i2c_bus_, I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, false); - return &audio_codec; - } - - virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) { - if (!adc1_handle) { - InitializeADC(); - } - - int raw_value = 0; - int voltage = 0; - - ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, VBAT_ADC_CHANNEL, &raw_value)); - - if (do_calibration) { - ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_handle, raw_value, &voltage)); - voltage = voltage * 3 / 2; // compensate for voltage divider - ESP_LOGI(TAG, "Calibrated voltage: %d mV", voltage); - } else { - ESP_LOGI(TAG, "Raw ADC value: %d", raw_value); - voltage = raw_value; - } - - voltage = voltage < EMPTY_BATTERY_VOLTAGE ? EMPTY_BATTERY_VOLTAGE : voltage; - voltage = voltage > FULL_BATTERY_VOLTAGE ? FULL_BATTERY_VOLTAGE : voltage; - - // 计算电量百分比 - level = (voltage - EMPTY_BATTERY_VOLTAGE) * 100 / (FULL_BATTERY_VOLTAGE - EMPTY_BATTERY_VOLTAGE); - - charging = gpio_get_level(MCU_VCC_CTL); - ESP_LOGI(TAG, "Battery Level: %d%%, Charging: %s", level, charging ? "Yes" : "No"); - return true; - } -}; - -DECLARE_BOARD(EspSpotS3Bot); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "sdkconfig.h" + +#include +#include +#include +#include +#include "esp_adc/adc_oneshot.h" +#include "esp_adc/adc_cali.h" +#include "esp_adc/adc_cali_scheme.h" + +#include +#include "esp_timer.h" +#include "led/circular_strip.h" + +#define TAG "esp_spot_s3" + +bool button_released_ = false; +bool shutdown_ready_ = false; +esp_timer_handle_t shutdown_timer; + +class EspSpotS3Bot : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + Button key_button_; + adc_oneshot_unit_handle_t adc1_handle; + adc_cali_handle_t adc1_cali_handle; + bool do_calibration = false; + bool key_long_pressed = false; + int64_t last_key_press_time = 0; + static const int64_t LONG_PRESS_TIMEOUT_US = 5 * 1000000ULL; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeADC() { + adc_oneshot_unit_init_cfg_t init_config1 = { + .unit_id = ADC_UNIT_1 + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN, + .bitwidth = ADC_WIDTH, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, VBAT_ADC_CHANNEL, &chan_config)); + + adc_cali_handle_t handle = NULL; + esp_err_t ret = ESP_FAIL; + +#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = ADC_UNIT_1, + .atten = ADC_ATTEN, + .bitwidth = ADC_WIDTH, + }; + ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); + if (ret == ESP_OK) { + do_calibration = true; + adc1_cali_handle = handle; + ESP_LOGI(TAG, "ADC Curve Fitting calibration succeeded"); + } +#endif // ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + ResetWifiConfiguration(); + }); + + key_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + app.ToggleChatState(); + key_long_pressed = false; + }); + + key_button_.OnLongPress([this]() { + int64_t now = esp_timer_get_time(); + auto* led = static_cast(this->GetLed()); + + if (key_long_pressed) { + if ((now - last_key_press_time) < LONG_PRESS_TIMEOUT_US) { + ESP_LOGW(TAG, "Key button long pressed the second time within 5s, shutting down..."); + led->SetSingleColor(0, {0, 0, 0}); + + gpio_hold_dis(MCU_VCC_CTL); + gpio_set_level(MCU_VCC_CTL, 0); + + } else { + last_key_press_time = now; + BlinkGreenFor5s(); + } + key_long_pressed = true; + } else { + ESP_LOGW(TAG, "Key button first long press! Waiting second within 5s to shutdown..."); + last_key_press_time = now; + key_long_pressed = true; + + BlinkGreenFor5s(); + } + }); + } + + void InitializePowerCtl() { + InitializeGPIO(); + + gpio_set_level(MCU_VCC_CTL, 1); + gpio_hold_en(MCU_VCC_CTL); + + gpio_set_level(PERP_VCC_CTL, 1); + gpio_hold_en(PERP_VCC_CTL); + } + + void InitializeGPIO() { + gpio_config_t io_pa = { + .pin_bit_mask = (1ULL << AUDIO_CODEC_PA_PIN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_pa); + gpio_set_level(AUDIO_CODEC_PA_PIN, 0); + + gpio_config_t io_conf_1 = { + .pin_bit_mask = (1ULL << MCU_VCC_CTL), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_conf_1); + + gpio_config_t io_conf_2 = { + .pin_bit_mask = (1ULL << PERP_VCC_CTL), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_conf_2); + } + + void BlinkGreenFor5s() { + auto* led = static_cast(GetLed()); + if (!led) { + return; + } + + led->Blink({50, 25, 0}, 100); + + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + auto* self = static_cast(arg); + auto* led = static_cast(self->GetLed()); + if (led) { + led->SetSingleColor(0, {0, 0, 0}); + } + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "blinkGreenFor5s_timer" + }; + + esp_timer_handle_t blink_timer = nullptr; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &blink_timer)); + ESP_ERROR_CHECK(esp_timer_start_once(blink_timer, LONG_PRESS_TIMEOUT_US)); + } + +public: + EspSpotS3Bot() : boot_button_(BOOT_BUTTON_GPIO), key_button_(KEY_BUTTON_GPIO, true) { + InitializePowerCtl(); + InitializeADC(); + InitializeI2c(); + InitializeButtons(); + } + + virtual Led* GetLed() override { + static CircularStrip led(LED_PIN, 1); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(i2c_bus_, I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, false); + return &audio_codec; + } + + virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) { + if (!adc1_handle) { + InitializeADC(); + } + + int raw_value = 0; + int voltage = 0; + + ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, VBAT_ADC_CHANNEL, &raw_value)); + + if (do_calibration) { + ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_handle, raw_value, &voltage)); + voltage = voltage * 3 / 2; // compensate for voltage divider + ESP_LOGI(TAG, "Calibrated voltage: %d mV", voltage); + } else { + ESP_LOGI(TAG, "Raw ADC value: %d", raw_value); + voltage = raw_value; + } + + voltage = voltage < EMPTY_BATTERY_VOLTAGE ? EMPTY_BATTERY_VOLTAGE : voltage; + voltage = voltage > FULL_BATTERY_VOLTAGE ? FULL_BATTERY_VOLTAGE : voltage; + + // 计算电量百分比 + level = (voltage - EMPTY_BATTERY_VOLTAGE) * 100 / (FULL_BATTERY_VOLTAGE - EMPTY_BATTERY_VOLTAGE); + + charging = gpio_get_level(MCU_VCC_CTL); + ESP_LOGI(TAG, "Battery Level: %d%%, Charging: %s", level, charging ? "Yes" : "No"); + return true; + } +}; + +DECLARE_BOARD(EspSpotS3Bot); diff --git a/main/boards/esp32-cgc-144/README.md b/main/boards/esp32-cgc-144/README.md index 3155eee..b9ab52e 100644 --- a/main/boards/esp32-cgc-144/README.md +++ b/main/boards/esp32-cgc-144/README.md @@ -1,42 +1,30 @@ -# 相关资料: -- [插线款](https://www.wdmomo.fun:81/doc/index.html?file=001_%E8%AE%BE%E8%AE%A1%E9%A1%B9%E7%9B%AE/0001_%E5%B0%8F%E6%99%BAAI/003_ESP32-CGC-144%E6%8F%92%E7%BA%BF%E7%89%88%E5%B0%8F%E6%99%BAAI) - -- [电池款](https://www.wdmomo.fun:81/doc/index.html?file=001_%E8%AE%BE%E8%AE%A1%E9%A1%B9%E7%9B%AE/0001_%E5%B0%8F%E6%99%BAAI/004_ESP32-CGC-144%E7%94%B5%E6%B1%A0%E7%89%88%E5%B0%8F%E6%99%BAAI) - -# 编译配置命令 - -**配置编译目标为 ESP32:** - -```bash -idf.py set-target esp32 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> ESP32 CGC 144 -``` - -**修改 flash 大小:** - -``` -Serial flasher config -> Flash size -> 4 MB -``` - -**修改分区表:** - -``` -Partition Table -> Custom partition CSV file -> partitions/v1/4m.csv -``` - -**编译:** - -```bash -idf.py build -``` +# 相关资料: +- [插线款](https://www.wdmomo.fun:81/doc/index.html?file=001_%E8%AE%BE%E8%AE%A1%E9%A1%B9%E7%9B%AE/0001_%E5%B0%8F%E6%99%BAAI/003_ESP32-CGC-144%E6%8F%92%E7%BA%BF%E7%89%88%E5%B0%8F%E6%99%BAAI) + +- [电池款](https://www.wdmomo.fun:81/doc/index.html?file=001_%E8%AE%BE%E8%AE%A1%E9%A1%B9%E7%9B%AE/0001_%E5%B0%8F%E6%99%BAAI/004_ESP32-CGC-144%E7%94%B5%E6%B1%A0%E7%89%88%E5%B0%8F%E6%99%BAAI) + +# 编译配置命令 + +**配置编译目标为 ESP32:** + +```bash +idf.py set-target esp32 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> ESP32 CGC 144 +``` + +**编译:** + +```bash +idf.py build +``` diff --git a/main/boards/esp32-cgc-144/config.h b/main/boards/esp32-cgc-144/config.h index 6a23386..a8db218 100644 --- a/main/boards/esp32-cgc-144/config.h +++ b/main/boards/esp32-cgc-144/config.h @@ -1,73 +1,73 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -// 如果使用插线版本接入电池,请启用下面一行 -//#define ESP32_CGC_144_lite - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32 - -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define ASR_BUTTON_GPIO GPIO_NUM_13 - -// A MCP Test: Control a lamp -#define LAMP_GPIO GPIO_NUM_12 - -#define DISPLAY_SDA_PIN GPIO_NUM_NC -#define DISPLAY_SCL_PIN GPIO_NUM_NC - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4 -#define DISPLAY_SCLK_PIN GPIO_NUM_18 -#define DISPLAY_MOSI_PIN GPIO_NUM_23 -#define DISPLAY_CS_PIN GPIO_NUM_5 -#define DISPLAY_DC_PIN GPIO_NUM_2 -#define DISPLAY_RESET_PIN GPIO_NUM_NC - -#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000) - -// 如果使用240x240的屏幕,请注释下面一行 -#define LCD_128X128 - -#ifdef LCD_128X128 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 2 -#define DISPLAY_OFFSET_Y 3 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true -#define DISPLAY_SPI_MODE 0 - -#else - -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true -#define DISPLAY_SPI_MODE 0 - -#endif - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +// 如果使用插线版本接入电池,请启用下面一行 +//#define ESP32_CGC_144_lite + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32 + +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define ASR_BUTTON_GPIO GPIO_NUM_13 + +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_12 + +#define DISPLAY_SDA_PIN GPIO_NUM_NC +#define DISPLAY_SCL_PIN GPIO_NUM_NC + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4 +#define DISPLAY_SCLK_PIN GPIO_NUM_18 +#define DISPLAY_MOSI_PIN GPIO_NUM_23 +#define DISPLAY_CS_PIN GPIO_NUM_5 +#define DISPLAY_DC_PIN GPIO_NUM_2 +#define DISPLAY_RESET_PIN GPIO_NUM_NC + +#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000) + +// 如果使用240x240的屏幕,请注释下面一行 +#define LCD_128X128 + +#ifdef LCD_128X128 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 2 +#define DISPLAY_OFFSET_Y 3 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true +#define DISPLAY_SPI_MODE 0 + +#else + +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true +#define DISPLAY_SPI_MODE 0 + +#endif + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp32-cgc-144/config.json b/main/boards/esp32-cgc-144/config.json index 2226076..9f70e17 100644 --- a/main/boards/esp32-cgc-144/config.json +++ b/main/boards/esp32-cgc-144/config.json @@ -1,12 +1,10 @@ -{ - "target": "esp32", - "builds": [ - { - "name": "esp32-cgc-144", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m.csv\"" - ] - } - ] +{ + "target": "esp32", + "builds": [ + { + "name": "esp32-cgc-144", + "sdkconfig_append": [ + ] + } + ] } \ No newline at end of file diff --git a/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc b/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc index 25963c0..f184d85 100644 --- a/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc +++ b/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc @@ -1,192 +1,192 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "mcp_server.h" -#include "lamp_controller.h" -#include "led/single_led.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include - -#include - -#if defined(ESP32_CGC_144_lite) -#include "power_manager_lite.h" -#else -#include "power_manager.h" -#endif - -#define TAG "ESP32_CGC_144" - -class ESP32_CGC_144 : public WifiBoard { -private: - Button boot_button_; - LcdDisplay* display_; - Button asr_button_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - -#if defined(ESP32_CGC_144_lite) -void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_NC); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); -} -#else -void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_36); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); -} -#endif - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - - power_save_timer_->SetEnabled(true); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeSt7735Display() { - // esp_lcd_panel_io_handle_t panel_io = nullptr; - // esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = DISPLAY_SPI_MODE; - io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RESET_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - - asr_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - std::string wake_word="你好小智"; - Application::GetInstance().WakeWordInvoke(wake_word); - }); - } - - // 物联网初始化,添加对 AI 可见设备 - void InitializeTools() { - static LampController lamp(LAMP_GPIO); - } - -public: - ESP32_CGC_144() : - boot_button_(BOOT_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) { - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeSpi(); - InitializeSt7735Display(); - InitializeButtons(); - InitializeTools(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(ESP32_CGC_144); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "mcp_server.h" +#include "lamp_controller.h" +#include "led/single_led.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#if defined(ESP32_CGC_144_lite) +#include "power_manager_lite.h" +#else +#include "power_manager.h" +#endif + +#define TAG "ESP32_CGC_144" + +class ESP32_CGC_144 : public WifiBoard { +private: + Button boot_button_; + LcdDisplay* display_; + Button asr_button_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + +#if defined(ESP32_CGC_144_lite) +void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_NC); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); +} +#else +void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_36); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); +} +#endif + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + + power_save_timer_->SetEnabled(true); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeSt7735Display() { + // esp_lcd_panel_io_handle_t panel_io = nullptr; + // esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = DISPLAY_SPI_MODE; + io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RESET_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + + asr_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + std::string wake_word="你好小智"; + Application::GetInstance().WakeWordInvoke(wake_word); + }); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeTools() { + static LampController lamp(LAMP_GPIO); + } + +public: + ESP32_CGC_144() : + boot_button_(BOOT_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) { + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeSt7735Display(); + InitializeButtons(); + InitializeTools(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(ESP32_CGC_144); diff --git a/main/boards/esp32-cgc-144/power_manager.h b/main/boards/esp32-cgc-144/power_manager.h index 9576e30..7ce91b3 100644 --- a/main/boards/esp32-cgc-144/power_manager.h +++ b/main/boards/esp32-cgc-144/power_manager.h @@ -1,186 +1,186 @@ -#pragma once -#include -#include - -#include -#include -#include - - -class PowerManager { -private: - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 1; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_3, &adc_value)); - - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += value; - } - average_adc /= adc_values_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {1970, 0}, - {2062, 20}, - {2154, 40}, - {2246, 60}, - {2338, 80}, - {2430, 100} - }; - - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_3, &chan_config)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include + +#include +#include +#include + + +class PowerManager { +private: + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 1; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_3, &adc_value)); + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {1970, 0}, + {2062, 20}, + {2154, 40}, + {2246, 60}, + {2338, 80}, + {2430, 100} + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_3, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/esp32-cgc-144/power_manager_lite.h b/main/boards/esp32-cgc-144/power_manager_lite.h index f088286..94e01c0 100644 --- a/main/boards/esp32-cgc-144/power_manager_lite.h +++ b/main/boards/esp32-cgc-144/power_manager_lite.h @@ -1,185 +1,185 @@ -#pragma once -#include -#include - -#include -#include -#include - - -class PowerManager { -private: - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 1; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); //lite - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += value; - } - average_adc /= adc_values_.size(); - - // lite定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {3000, 0}, - {3160, 20}, - {3320, 40}, - {3480, 60}, - {3640, 80}, - {3800, 100} - }; - - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config)); //lite - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include + +#include +#include +#include + + +class PowerManager { +private: + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 1; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); //lite + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // lite定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {3000, 0}, + {3160, 20}, + {3320, 40}, + {3480, 60}, + {3640, 80}, + {3800, 100} + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config)); //lite + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/esp32-cgc/README.md b/main/boards/esp32-cgc/README.md index 6a345fc..705aab3 100644 --- a/main/boards/esp32-cgc/README.md +++ b/main/boards/esp32-cgc/README.md @@ -1,48 +1,36 @@ -# 主板开源地址: -- V1:[https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb](https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb) -- V2:[https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb_copy](https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb_copy) -- 更多介绍:[wdmomo.fun](https://www.wdmomo.fun:81/doc/index.html?file=001_%E8%AE%BE%E8%AE%A1%E9%A1%B9%E7%9B%AE/0001_%E5%B0%8F%E6%99%BAAI/002_ESP32-CGC%E5%BC%80%E5%8F%91%E6%9D%BF%E5%B0%8F%E6%99%BAAI) - -# 编译配置命令 - -**配置编译目标为 ESP32:** - -```bash -idf.py set-target esp32 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> ESP32 CGC -``` - -**选择屏幕类型:** - -``` -Xiaozhi Assistant -> LCD Type -> "ST7735, 分辨率128*128" -``` - -**修改 flash 大小:** - -``` -Serial flasher config -> Flash size -> 4 MB -``` - -**修改分区表:** - -``` -Partition Table -> Custom partition CSV file -> partitions/v1/4m.csv -``` - -**编译:** - -```bash -idf.py build -``` +# 主板开源地址: +- V1:[https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb](https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb) +- V2:[https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb_copy](https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb_copy) +- 更多介绍:[wdmomo.fun](https://www.wdmomo.fun:81/doc/index.html?file=001_%E8%AE%BE%E8%AE%A1%E9%A1%B9%E7%9B%AE/0001_%E5%B0%8F%E6%99%BAAI/002_ESP32-CGC%E5%BC%80%E5%8F%91%E6%9D%BF%E5%B0%8F%E6%99%BAAI) + +# 编译配置命令 + +**配置编译目标为 ESP32:** + +```bash +idf.py set-target esp32 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> ESP32 CGC +``` + +**选择屏幕类型:** + +``` +Xiaozhi Assistant -> LCD Type -> "ST7735, 分辨率128*128" +``` + +**编译:** + +```bash +idf.py build +``` diff --git a/main/boards/esp32-cgc/config.h b/main/boards/esp32-cgc/config.h index 2e27571..e4e9563 100644 --- a/main/boards/esp32-cgc/config.h +++ b/main/boards/esp32-cgc/config.h @@ -1,271 +1,271 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -// 如果使用 Duplex I2S 模式,请注释下面一行 -#define AUDIO_I2S_METHOD_SIMPLEX - -#ifdef AUDIO_I2S_METHOD_SIMPLEX - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32 - -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27 - -#else - -#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 - -#endif - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define ASR_BUTTON_GPIO GPIO_NUM_13 - -// A MCP Test: Control a lamp -#define LAMP_GPIO GPIO_NUM_12 - -#define DISPLAY_SDA_PIN GPIO_NUM_NC -#define DISPLAY_SCL_PIN GPIO_NUM_NC - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4 -#define DISPLAY_SCLK_PIN GPIO_NUM_18 -#define DISPLAY_MOSI_PIN GPIO_NUM_23 -#define DISPLAY_CS_PIN GPIO_NUM_5 -#define DISPLAY_DC_PIN GPIO_NUM_2 -#define DISPLAY_RESET_PIN GPIO_NUM_NC - -#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000) - -#ifdef CONFIG_LCD_ST7789_240X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#endif - -#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_170X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 170 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 35 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_172X320 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 172 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 34 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X280 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 280 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 20 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X240 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7789_240X240_7PIN -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 2 -#endif - -#ifdef CONFIG_LCD_ST7789_240X135 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 135 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY true -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 40 -#define DISPLAY_OFFSET_Y 53 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7735_128X160 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 160 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#endif - -#ifdef CONFIG_LCD_ST7735_128X128 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 2 -#define DISPLAY_OFFSET_Y 3 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ST7796_320X480 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 480 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ILI9341_240X320 -#define LCD_TYPE_ILI9341_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS -#define LCD_TYPE_ILI9341_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_GC9A01_240X240 -#define LCD_TYPE_GC9A01_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#ifdef CONFIG_LCD_CUSTOM -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 0 -#endif - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +// 如果使用 Duplex I2S 模式,请注释下面一行 +#define AUDIO_I2S_METHOD_SIMPLEX + +#ifdef AUDIO_I2S_METHOD_SIMPLEX + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32 + +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27 + +#else + +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +#endif + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define ASR_BUTTON_GPIO GPIO_NUM_13 + +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_12 + +#define DISPLAY_SDA_PIN GPIO_NUM_NC +#define DISPLAY_SCL_PIN GPIO_NUM_NC + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4 +#define DISPLAY_SCLK_PIN GPIO_NUM_18 +#define DISPLAY_MOSI_PIN GPIO_NUM_23 +#define DISPLAY_CS_PIN GPIO_NUM_5 +#define DISPLAY_DC_PIN GPIO_NUM_2 +#define DISPLAY_RESET_PIN GPIO_NUM_NC + +#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000) + +#ifdef CONFIG_LCD_ST7789_240X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#endif + +#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_170X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 170 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 35 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_172X320 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 172 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 34 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X280 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 280 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 20 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X240 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7789_240X240_7PIN +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 2 +#endif + +#ifdef CONFIG_LCD_ST7789_240X135 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 135 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 40 +#define DISPLAY_OFFSET_Y 53 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7735_128X160 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 160 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#endif + +#ifdef CONFIG_LCD_ST7735_128X128 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 2 +#define DISPLAY_OFFSET_Y 3 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ST7796_320X480 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 480 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ILI9341_240X320 +#define LCD_TYPE_ILI9341_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS +#define LCD_TYPE_ILI9341_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_GC9A01_240X240 +#define LCD_TYPE_GC9A01_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#ifdef CONFIG_LCD_CUSTOM +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 +#endif + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp32-cgc/config.json b/main/boards/esp32-cgc/config.json index 6a29ab5..5b6391c 100644 --- a/main/boards/esp32-cgc/config.json +++ b/main/boards/esp32-cgc/config.json @@ -1,13 +1,11 @@ -{ - "target": "esp32", - "builds": [ - { - "name": "esp32-cgc", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m.csv\"", - "CONFIG_LCD_ST7735_128X128=y" - ] - } - ] +{ + "target": "esp32", + "builds": [ + { + "name": "esp32-cgc", + "sdkconfig_append": [ + "CONFIG_LCD_ST7735_128X128=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/esp32-cgc/esp32_cgc_board.cc b/main/boards/esp32-cgc/esp32_cgc_board.cc index 6b82b01..47a824e 100644 --- a/main/boards/esp32-cgc/esp32_cgc_board.cc +++ b/main/boards/esp32-cgc/esp32_cgc_board.cc @@ -1,180 +1,180 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "mcp_server.h" -#include "lamp_controller.h" -#include "led/single_led.h" - -#include -#include -#include -#include -#include -#include -#include - -#if defined(LCD_TYPE_ILI9341_SERIAL) -#include -#endif - -#if defined(LCD_TYPE_GC9A01_SERIAL) -#include -static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { - // {cmd, { data }, data_size, delay_ms} - {0xfe, (uint8_t[]){0x00}, 0, 0}, - {0xef, (uint8_t[]){0x00}, 0, 0}, - {0xb0, (uint8_t[]){0xc0}, 1, 0}, - {0xb1, (uint8_t[]){0x80}, 1, 0}, - {0xb2, (uint8_t[]){0x27}, 1, 0}, - {0xb3, (uint8_t[]){0x13}, 1, 0}, - {0xb6, (uint8_t[]){0x19}, 1, 0}, - {0xb7, (uint8_t[]){0x05}, 1, 0}, - {0xac, (uint8_t[]){0xc8}, 1, 0}, - {0xab, (uint8_t[]){0x0f}, 1, 0}, - {0x3a, (uint8_t[]){0x05}, 1, 0}, - {0xb4, (uint8_t[]){0x04}, 1, 0}, - {0xa8, (uint8_t[]){0x08}, 1, 0}, - {0xb8, (uint8_t[]){0x08}, 1, 0}, - {0xea, (uint8_t[]){0x02}, 1, 0}, - {0xe8, (uint8_t[]){0x2A}, 1, 0}, - {0xe9, (uint8_t[]){0x47}, 1, 0}, - {0xe7, (uint8_t[]){0x5f}, 1, 0}, - {0xc6, (uint8_t[]){0x21}, 1, 0}, - {0xc7, (uint8_t[]){0x15}, 1, 0}, - {0xf0, - (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, - 0x04, 0x12, 0x14, 0x1f}, - 14, 0}, - {0xf1, - (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, - 0x0C, 0x1A, 0x14, 0x1E}, - 14, 0}, - {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, - {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, -}; -#endif - -#define TAG "ESP32_CGC" - -class ESP32_CGC : public WifiBoard { -private: - Button boot_button_; - LcdDisplay* display_; - Button asr_button_; - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeLcdDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = DISPLAY_SPI_MODE; - io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RESET_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; -#if defined(LCD_TYPE_ILI9341_SERIAL) - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); -#elif defined(LCD_TYPE_GC9A01_SERIAL) - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel)); - gc9a01_vendor_config_t gc9107_vendor_config = { - .init_cmds = gc9107_lcd_init_cmds, - .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), - }; -#else - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); -#endif - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); -#ifdef LCD_TYPE_GC9A01_SERIAL - panel_config.vendor_config = &gc9107_vendor_config; -#endif - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - - asr_button_.OnClick([this]() { - std::string wake_word="你好小智"; - Application::GetInstance().WakeWordInvoke(wake_word); - }); - - } - - // 物联网初始化,添加对 AI 可见设备 - void InitializeTools() { - static LampController lamp(LAMP_GPIO); - } - -public: - ESP32_CGC() : - boot_button_(BOOT_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) { - InitializeSpi(); - InitializeLcdDisplay(); - InitializeButtons(); - InitializeTools(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override - { -#ifdef AUDIO_I2S_METHOD_SIMPLEX - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); -#else - static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); -#endif - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(ESP32_CGC); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "mcp_server.h" +#include "lamp_controller.h" +#include "led/single_led.h" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(LCD_TYPE_ILI9341_SERIAL) +#include +#endif + +#if defined(LCD_TYPE_GC9A01_SERIAL) +#include +static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { + // {cmd, { data }, data_size, delay_ms} + {0xfe, (uint8_t[]){0x00}, 0, 0}, + {0xef, (uint8_t[]){0x00}, 0, 0}, + {0xb0, (uint8_t[]){0xc0}, 1, 0}, + {0xb1, (uint8_t[]){0x80}, 1, 0}, + {0xb2, (uint8_t[]){0x27}, 1, 0}, + {0xb3, (uint8_t[]){0x13}, 1, 0}, + {0xb6, (uint8_t[]){0x19}, 1, 0}, + {0xb7, (uint8_t[]){0x05}, 1, 0}, + {0xac, (uint8_t[]){0xc8}, 1, 0}, + {0xab, (uint8_t[]){0x0f}, 1, 0}, + {0x3a, (uint8_t[]){0x05}, 1, 0}, + {0xb4, (uint8_t[]){0x04}, 1, 0}, + {0xa8, (uint8_t[]){0x08}, 1, 0}, + {0xb8, (uint8_t[]){0x08}, 1, 0}, + {0xea, (uint8_t[]){0x02}, 1, 0}, + {0xe8, (uint8_t[]){0x2A}, 1, 0}, + {0xe9, (uint8_t[]){0x47}, 1, 0}, + {0xe7, (uint8_t[]){0x5f}, 1, 0}, + {0xc6, (uint8_t[]){0x21}, 1, 0}, + {0xc7, (uint8_t[]){0x15}, 1, 0}, + {0xf0, + (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, + 0x04, 0x12, 0x14, 0x1f}, + 14, 0}, + {0xf1, + (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, + 0x0C, 0x1A, 0x14, 0x1E}, + 14, 0}, + {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, + {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, +}; +#endif + +#define TAG "ESP32_CGC" + +class ESP32_CGC : public WifiBoard { +private: + Button boot_button_; + LcdDisplay* display_; + Button asr_button_; + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = DISPLAY_SPI_MODE; + io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RESET_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; +#if defined(LCD_TYPE_ILI9341_SERIAL) + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); +#elif defined(LCD_TYPE_GC9A01_SERIAL) + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel)); + gc9a01_vendor_config_t gc9107_vendor_config = { + .init_cmds = gc9107_lcd_init_cmds, + .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), + }; +#else + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); +#endif + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); +#ifdef LCD_TYPE_GC9A01_SERIAL + panel_config.vendor_config = &gc9107_vendor_config; +#endif + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + + asr_button_.OnClick([this]() { + std::string wake_word="你好小智"; + Application::GetInstance().WakeWordInvoke(wake_word); + }); + + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeTools() { + static LampController lamp(LAMP_GPIO); + } + +public: + ESP32_CGC() : + boot_button_(BOOT_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) { + InitializeSpi(); + InitializeLcdDisplay(); + InitializeButtons(); + InitializeTools(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override + { +#ifdef AUDIO_I2S_METHOD_SIMPLEX + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); +#else + static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); +#endif + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(ESP32_CGC); diff --git a/main/boards/esp32-s3-touch-amoled-1.8/config.h b/main/boards/esp32-s3-touch-amoled-1.8/config.h index c904053..850db9b 100644 --- a/main/boards/esp32-s3-touch-amoled-1.8/config.h +++ b/main/boards/esp32-s3-touch-amoled-1.8/config.h @@ -1,41 +1,41 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_12 -#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_11 -#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_4 -#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_5 -#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_6 -#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_7 -#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_NC -#define DISPLAY_WIDTH 368 -#define DISPLAY_HEIGHT 448 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_12 +#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_11 +#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_4 +#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_5 +#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_6 +#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_7 +#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_NC +#define DISPLAY_WIDTH 368 +#define DISPLAY_HEIGHT 448 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp32-s3-touch-amoled-1.8/config.json b/main/boards/esp32-s3-touch-amoled-1.8/config.json index 6719497..2a559ef 100644 --- a/main/boards/esp32-s3-touch-amoled-1.8/config.json +++ b/main/boards/esp32-s3-touch-amoled-1.8/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp32-s3-touch-amoled-1.8", - "sdkconfig_append": [ - "CONFIG_USE_WECHAT_MESSAGE_STYLE=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp32-s3-touch-amoled-1.8", + "sdkconfig_append": [ + "CONFIG_USE_WECHAT_MESSAGE_STYLE=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc index 111f61f..c060ae7 100644 --- a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc +++ b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc @@ -1,335 +1,335 @@ -#include "wifi_board.h" -#include "display/lcd_display.h" -#include "esp_lcd_sh8601.h" - -#include "codecs/es8311_audio_codec.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "mcp_server.h" -#include "config.h" -#include "power_save_timer.h" -#include "axp2101.h" -#include "i2c_device.h" -#include - -#include -#include -#include -#include -#include "esp_io_expander_tca9554.h" -#include "settings.h" - -#include -#include -#include - -#define TAG "WaveshareEsp32s3TouchAMOLED1inch8" - -class Pmic : public Axp2101 { -public: - Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { - WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable - WriteReg(0x27, 0x10); // hold 4s to power off - - // Disable All DCs but DC1 - WriteReg(0x80, 0x01); - // Disable All LDOs - WriteReg(0x90, 0x00); - WriteReg(0x91, 0x00); - - // Set DC1 to 3.3V - WriteReg(0x82, (3300 - 1500) / 100); - - // Set ALDO1 to 3.3V - WriteReg(0x92, (3300 - 500) / 100); - - // Enable ALDO1(MIC) - WriteReg(0x90, 0x01); - - WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V - - WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA - WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) - WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA - } -}; - -#define LCD_OPCODE_WRITE_CMD (0x02ULL) -#define LCD_OPCODE_READ_CMD (0x03ULL) -#define LCD_OPCODE_WRITE_COLOR (0x32ULL) - -static const sh8601_lcd_init_cmd_t vendor_specific_init[] = { - {0x11, (uint8_t[]){0x00}, 0, 120}, - {0x44, (uint8_t[]){0x01, 0xD1}, 2, 0}, - {0x35, (uint8_t[]){0x00}, 1, 0}, - {0x53, (uint8_t[]){0x20}, 1, 10}, - {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x6F}, 4, 0}, - {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xBF}, 4, 0}, - {0x51, (uint8_t[]){0x00}, 1, 10}, - {0x29, (uint8_t[]){0x00}, 0, 10} -}; - -// 在waveshare_amoled_1_8类之前添加新的显示类 -class CustomLcdDisplay : public SpiLcdDisplay { -public: - CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, - esp_lcd_panel_handle_t panel_handle, - int width, - int height, - int offset_x, - int offset_y, - bool mirror_x, - bool mirror_y, - bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, - width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - DisplayLockGuard lock(this); - lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.1, 0); - lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.1, 0); - } -}; - -class CustomBacklight : public Backlight { -public: - CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {} - -protected: - esp_lcd_panel_io_handle_t panel_io_; - - virtual void SetBrightnessImpl(uint8_t brightness) override { - auto display = Board::GetInstance().GetDisplay(); - DisplayLockGuard lock(display); - uint8_t data[1] = {((uint8_t)((255 * brightness) / 100))}; - int lcd_cmd = 0x51; - lcd_cmd &= 0xff; - lcd_cmd <<= 8; - lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24; - esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data)); - } -}; - -class WaveshareEsp32s3TouchAMOLED1inch8 : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Pmic* pmic_ = nullptr; - Button boot_button_; - CustomLcdDisplay* display_; - CustomBacklight* backlight_; - esp_io_expander_handle_t io_expander = NULL; - PowerSaveTimer* power_save_timer_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(20); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - pmic_->PowerOff(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeTca9554(void) { - esp_err_t ret = esp_io_expander_new_i2c_tca9554(codec_i2c_bus_, I2C_ADDRESS, &io_expander); - if(ret != ESP_OK) - ESP_LOGE(TAG, "TCA9554 create returned error"); - ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 |IO_EXPANDER_PIN_NUM_2, IO_EXPANDER_OUTPUT); - ret |= esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_4, IO_EXPANDER_INPUT); - ESP_ERROR_CHECK(ret); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1|IO_EXPANDER_PIN_NUM_2, 1); - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(100)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1|IO_EXPANDER_PIN_NUM_2, 0); - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(300)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1|IO_EXPANDER_PIN_NUM_2, 1); - ESP_ERROR_CHECK(ret); - } - - void InitializeAxp2101() { - ESP_LOGI(TAG, "Init AXP2101"); - pmic_ = new Pmic(codec_i2c_bus_, 0x34); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.sclk_io_num = GPIO_NUM_11; - buscfg.data0_io_num = GPIO_NUM_4; - buscfg.data1_io_num = GPIO_NUM_5; - buscfg.data2_io_num = GPIO_NUM_6; - buscfg.data3_io_num = GPIO_NUM_7; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - buscfg.flags = SPICOMMON_BUSFLAG_QUAD; - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeSH8601Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = SH8601_PANEL_IO_QSPI_CONFIG( - EXAMPLE_PIN_NUM_LCD_CS, - nullptr, - nullptr - ); - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - const sh8601_vendor_config_t vendor_config = { - .init_cmds = &vendor_specific_init[0], - .init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t), - .flags ={ - .use_qspi_interface = 1, - } - }; - - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.flags.reset_active_high = 1, - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = (void *)&vendor_config; - ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, false); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - esp_lcd_panel_disp_on_off(panel, true); - display_ = new CustomLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - backlight_ = new CustomBacklight(panel_io); - backlight_->RestoreBrightness(); - } - - void InitializeTouch() - { - esp_lcd_touch_handle_t tp; - esp_lcd_touch_config_t tp_cfg = { - .x_max = DISPLAY_WIDTH, - .y_max = DISPLAY_HEIGHT, - .rst_gpio_num = GPIO_NUM_NC, - .int_gpio_num = GPIO_NUM_21, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = 0, - .mirror_x = 0, - .mirror_y = 0, - }, - }; - esp_lcd_panel_io_handle_t tp_io_handle = NULL; - esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG(); - tp_io_config.scl_speed_hz = 400 * 1000; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(codec_i2c_bus_, &tp_io_config, &tp_io_handle)); - ESP_LOGI(TAG, "Initialize touch controller"); - ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp)); - const lvgl_port_touch_cfg_t touch_cfg = { - .disp = lv_display_get_default(), - .handle = tp, - }; - lvgl_port_add_touch(&touch_cfg); - ESP_LOGI(TAG, "Touch panel initialized successfully"); - } - - // 初始化工具 - void InitializeTools() { - auto &mcp_server = McpServer::GetInstance(); - mcp_server.AddTool("self.system.reconfigure_wifi", - "Reboot the device and enter WiFi configuration mode.\n" - "**CAUTION** You must ask the user to confirm this action.", - PropertyList(), [this](const PropertyList& properties) { - ResetWifiConfiguration(); - return true; - }); - } - -public: - WaveshareEsp32s3TouchAMOLED1inch8() : - boot_button_(BOOT_BUTTON_GPIO) { - InitializePowerSaveTimer(); - InitializeCodecI2c(); - InitializeTca9554(); - InitializeAxp2101(); - InitializeSpi(); - InitializeSH8601Display(); - InitializeTouch(); - InitializeButtons(); - InitializeTools(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - return backlight_; - } - - virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = pmic_->IsCharging(); - discharging = pmic_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - - level = pmic_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(WaveshareEsp32s3TouchAMOLED1inch8); +#include "wifi_board.h" +#include "display/lcd_display.h" +#include "esp_lcd_sh8601.h" + +#include "codecs/es8311_audio_codec.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "mcp_server.h" +#include "config.h" +#include "power_save_timer.h" +#include "axp2101.h" +#include "i2c_device.h" +#include + +#include +#include +#include +#include +#include "esp_io_expander_tca9554.h" +#include "settings.h" + +#include +#include +#include + +#define TAG "WaveshareEsp32s3TouchAMOLED1inch8" + +class Pmic : public Axp2101 { +public: + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable + WriteReg(0x27, 0x10); // hold 4s to power off + + // Disable All DCs but DC1 + WriteReg(0x80, 0x01); + // Disable All LDOs + WriteReg(0x90, 0x00); + WriteReg(0x91, 0x00); + + // Set DC1 to 3.3V + WriteReg(0x82, (3300 - 1500) / 100); + + // Set ALDO1 to 3.3V + WriteReg(0x92, (3300 - 500) / 100); + + // Enable ALDO1(MIC) + WriteReg(0x90, 0x01); + + WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V + + WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA + WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) + WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA + } +}; + +#define LCD_OPCODE_WRITE_CMD (0x02ULL) +#define LCD_OPCODE_READ_CMD (0x03ULL) +#define LCD_OPCODE_WRITE_COLOR (0x32ULL) + +static const sh8601_lcd_init_cmd_t vendor_specific_init[] = { + {0x11, (uint8_t[]){0x00}, 0, 120}, + {0x44, (uint8_t[]){0x01, 0xD1}, 2, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x53, (uint8_t[]){0x20}, 1, 10}, + {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x6F}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xBF}, 4, 0}, + {0x51, (uint8_t[]){0x00}, 1, 10}, + {0x29, (uint8_t[]){0x00}, 0, 10} +}; + +// 在waveshare_amoled_1_8类之前添加新的显示类 +class CustomLcdDisplay : public SpiLcdDisplay { +public: + CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_handle_t panel_handle, + int width, + int height, + int offset_x, + int offset_y, + bool mirror_x, + bool mirror_y, + bool swap_xy) + : SpiLcdDisplay(io_handle, panel_handle, + width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + DisplayLockGuard lock(this); + lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.1, 0); + lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.1, 0); + } +}; + +class CustomBacklight : public Backlight { +public: + CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {} + +protected: + esp_lcd_panel_io_handle_t panel_io_; + + virtual void SetBrightnessImpl(uint8_t brightness) override { + auto display = Board::GetInstance().GetDisplay(); + DisplayLockGuard lock(display); + uint8_t data[1] = {((uint8_t)((255 * brightness) / 100))}; + int lcd_cmd = 0x51; + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24; + esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data)); + } +}; + +class WaveshareEsp32s3TouchAMOLED1inch8 : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Pmic* pmic_ = nullptr; + Button boot_button_; + CustomLcdDisplay* display_; + CustomBacklight* backlight_; + esp_io_expander_handle_t io_expander = NULL; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(20); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + pmic_->PowerOff(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeTca9554(void) { + esp_err_t ret = esp_io_expander_new_i2c_tca9554(codec_i2c_bus_, I2C_ADDRESS, &io_expander); + if(ret != ESP_OK) + ESP_LOGE(TAG, "TCA9554 create returned error"); + ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 |IO_EXPANDER_PIN_NUM_2, IO_EXPANDER_OUTPUT); + ret |= esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_4, IO_EXPANDER_INPUT); + ESP_ERROR_CHECK(ret); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1|IO_EXPANDER_PIN_NUM_2, 1); + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(100)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1|IO_EXPANDER_PIN_NUM_2, 0); + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(300)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1|IO_EXPANDER_PIN_NUM_2, 1); + ESP_ERROR_CHECK(ret); + } + + void InitializeAxp2101() { + ESP_LOGI(TAG, "Init AXP2101"); + pmic_ = new Pmic(codec_i2c_bus_, 0x34); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.sclk_io_num = GPIO_NUM_11; + buscfg.data0_io_num = GPIO_NUM_4; + buscfg.data1_io_num = GPIO_NUM_5; + buscfg.data2_io_num = GPIO_NUM_6; + buscfg.data3_io_num = GPIO_NUM_7; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + buscfg.flags = SPICOMMON_BUSFLAG_QUAD; + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeSH8601Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = SH8601_PANEL_IO_QSPI_CONFIG( + EXAMPLE_PIN_NUM_LCD_CS, + nullptr, + nullptr + ); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + const sh8601_vendor_config_t vendor_config = { + .init_cmds = &vendor_specific_init[0], + .init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t), + .flags ={ + .use_qspi_interface = 1, + } + }; + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.flags.reset_active_high = 1, + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = (void *)&vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_disp_on_off(panel, true); + display_ = new CustomLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + backlight_ = new CustomBacklight(panel_io); + backlight_->RestoreBrightness(); + } + + void InitializeTouch() + { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH, + .y_max = DISPLAY_HEIGHT, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = GPIO_NUM_21, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG(); + tp_io_config.scl_speed_hz = 400 * 1000; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(codec_i2c_bus_, &tp_io_config, &tp_io_handle)); + ESP_LOGI(TAG, "Initialize touch controller"); + ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp)); + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + lvgl_port_add_touch(&touch_cfg); + ESP_LOGI(TAG, "Touch panel initialized successfully"); + } + + // 初始化工具 + void InitializeTools() { + auto &mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.system.reconfigure_wifi", + "Reboot the device and enter WiFi configuration mode.\n" + "**CAUTION** You must ask the user to confirm this action.", + PropertyList(), [this](const PropertyList& properties) { + ResetWifiConfiguration(); + return true; + }); + } + +public: + WaveshareEsp32s3TouchAMOLED1inch8() : + boot_button_(BOOT_BUTTON_GPIO) { + InitializePowerSaveTimer(); + InitializeCodecI2c(); + InitializeTca9554(); + InitializeAxp2101(); + InitializeSpi(); + InitializeSH8601Display(); + InitializeTouch(); + InitializeButtons(); + InitializeTools(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + return backlight_; + } + + virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = pmic_->IsCharging(); + discharging = pmic_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + + level = pmic_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(WaveshareEsp32s3TouchAMOLED1inch8); diff --git a/main/boards/esp32-s3-touch-lcd-1.46/README.md b/main/boards/esp32-s3-touch-lcd-1.46/README.md index 0919b9d..a8c8d85 100644 --- a/main/boards/esp32-s3-touch-lcd-1.46/README.md +++ b/main/boards/esp32-s3-touch-lcd-1.46/README.md @@ -1,4 +1,4 @@ -新增 微雪 开发板: ESP32-S3-Touch-LCD-1.46、ESP32-S3-Touch-LCD-1.46B -产品链接: -https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-1.46.htm +新增 微雪 开发板: ESP32-S3-Touch-LCD-1.46、ESP32-S3-Touch-LCD-1.46B +产品链接: +https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-1.46.htm https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-1.46B.htm \ No newline at end of file diff --git a/main/boards/esp32-s3-touch-lcd-1.46/config.h b/main/boards/esp32-s3-touch-lcd-1.46/config.h index b1bd2d9..439caf6 100644 --- a/main/boards/esp32-s3-touch-lcd-1.46/config.h +++ b/main/boards/esp32-s3-touch-lcd-1.46/config.h @@ -1,70 +1,70 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define PWR_BUTTON_GPIO GPIO_NUM_6 -#define PWR_Control_PIN GPIO_NUM_7 - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_2 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_15 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_47 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_48 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_38 - -#define I2C_SCL_IO GPIO_NUM_10 -#define I2C_SDA_IO GPIO_NUM_11 - - -#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 - -#define DISPLAY_WIDTH 412 -#define DISPLAY_HEIGHT 412 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define QSPI_LCD_H_RES (412) -#define QSPI_LCD_V_RES (412) -#define QSPI_LCD_BIT_PER_PIXEL (16) - -#define QSPI_LCD_HOST SPI2_HOST -#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_40 -#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_21 -#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46 -#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_45 -#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_42 -#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_41 -#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_NC -#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_5 - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define TP_PORT (I2C_NUM_1) -#define TP_PIN_NUM_SDA (I2C_SDA_IO) -#define TP_PIN_NUM_SCL (I2C_SCL_IO) -#define TP_PIN_NUM_RST (GPIO_NUM_NC) -#define TP_PIN_NUM_INT (GPIO_NUM_4) - -#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define TAIJIPI_SPD2010_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \ - { \ - .data0_io_num = d0, \ - .data1_io_num = d1, \ - .sclk_io_num = sclk, \ - .data2_io_num = d2, \ - .data3_io_num = d3, \ - .max_transfer_sz = max_trans_sz, \ - } - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define PWR_BUTTON_GPIO GPIO_NUM_6 +#define PWR_Control_PIN GPIO_NUM_7 + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_2 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_15 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_47 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_48 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_38 + +#define I2C_SCL_IO GPIO_NUM_10 +#define I2C_SDA_IO GPIO_NUM_11 + + +#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 + +#define DISPLAY_WIDTH 412 +#define DISPLAY_HEIGHT 412 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define QSPI_LCD_H_RES (412) +#define QSPI_LCD_V_RES (412) +#define QSPI_LCD_BIT_PER_PIXEL (16) + +#define QSPI_LCD_HOST SPI2_HOST +#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_40 +#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_21 +#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46 +#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_45 +#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_42 +#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_41 +#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_NC +#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_5 + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define TP_PORT (I2C_NUM_1) +#define TP_PIN_NUM_SDA (I2C_SDA_IO) +#define TP_PIN_NUM_SCL (I2C_SCL_IO) +#define TP_PIN_NUM_RST (GPIO_NUM_NC) +#define TP_PIN_NUM_INT (GPIO_NUM_4) + +#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define TAIJIPI_SPD2010_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \ + { \ + .data0_io_num = d0, \ + .data1_io_num = d1, \ + .sclk_io_num = sclk, \ + .data2_io_num = d2, \ + .data3_io_num = d3, \ + .max_transfer_sz = max_trans_sz, \ + } + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp32-s3-touch-lcd-1.46/config.json b/main/boards/esp32-s3-touch-lcd-1.46/config.json index e7e5852..4a19896 100644 --- a/main/boards/esp32-s3-touch-lcd-1.46/config.json +++ b/main/boards/esp32-s3-touch-lcd-1.46/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp32-s3-touch-lcd-1.46", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp32-s3-touch-lcd-1.46", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc b/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc index 90843e6..6df2c70 100644 --- a/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc +++ b/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc @@ -1,237 +1,237 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" - -#include -#include "i2c_device.h" -#include -#include -#include -#include -#include -#include -#include -#include "esp_io_expander_tca9554.h" -#include "lcd_display.h" -#include - -#define TAG "waveshare_lcd_1_46" - -// 在waveshare_lcd_1_46类之前添加新的显示类 -class CustomLcdDisplay : public SpiLcdDisplay { -public: - static void rounder_event_cb(lv_event_t * e) { - lv_area_t * area = (lv_area_t *)lv_event_get_param(e); - uint16_t x1 = area->x1; - uint16_t x2 = area->x2; - - area->x1 = (x1 >> 2) << 2; // round the start of coordinate down to the nearest 4M number - area->x2 = ((x2 >> 2) << 2) + 3; // round the end of coordinate up to the nearest 4N+3 number - } - - CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, - esp_lcd_panel_handle_t panel_handle, - int width, - int height, - int offset_x, - int offset_y, - bool mirror_x, - bool mirror_y, - bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, - width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - DisplayLockGuard lock(this); - lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL); - } -}; - -class CustomBoard : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - esp_io_expander_handle_t io_expander = NULL; - LcdDisplay* display_; - button_handle_t boot_btn, pwr_btn; - button_driver_t* boot_btn_driver_ = nullptr; - button_driver_t* pwr_btn_driver_ = nullptr; - static CustomBoard* instance_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = I2C_SDA_IO, - .scl_io_num = I2C_SCL_IO, - .clk_source = I2C_CLK_SRC_DEFAULT, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeTca9554(void) { - esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); - if(ret != ESP_OK) - ESP_LOGE(TAG, "TCA9554 create returned error"); - - // uint32_t input_level_mask = 0; - // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_INPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输入 - // ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, &input_level_mask); // 获取引脚 EXIO0 和 EXIO1 的电平状态,存放在 input_level_mask 中 - - // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO2 和 EXIO3 模式为输出 - // ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, 1); // 将引脚电平设置为 1 - // ret = esp_io_expander_print_state(io_expander); // 打印引脚状态 - - ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输出 - ESP_ERROR_CHECK(ret); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(300)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(300)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - } - - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize QSPI bus"); - - const spi_bus_config_t bus_config = TAIJIPI_SPD2010_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK, - QSPI_PIN_NUM_LCD_DATA0, - QSPI_PIN_NUM_LCD_DATA1, - QSPI_PIN_NUM_LCD_DATA2, - QSPI_PIN_NUM_LCD_DATA3, - QSPI_LCD_H_RES * 80 * sizeof(uint16_t)); - ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO)); - } - - void InitializeSpd2010Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - ESP_LOGI(TAG, "Install panel IO"); - - const esp_lcd_panel_io_spi_config_t io_config = SPD2010_PANEL_IO_QSPI_CONFIG(QSPI_PIN_NUM_LCD_CS, NULL, NULL); - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io)); - - ESP_LOGI(TAG, "Install SPD2010 panel driver"); - - spd2010_vendor_config_t vendor_config = { - .flags = { - .use_qspi_interface = 1, - }, - }; - const esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = QSPI_PIN_NUM_LCD_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h` - .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18) - .vendor_config = &vendor_config, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_spd2010(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_disp_on_off(panel, true); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new CustomLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtonsCustom() { - gpio_reset_pin(BOOT_BUTTON_GPIO); - gpio_set_direction(BOOT_BUTTON_GPIO, GPIO_MODE_INPUT); - gpio_reset_pin(PWR_BUTTON_GPIO); - gpio_set_direction(PWR_BUTTON_GPIO, GPIO_MODE_INPUT); - gpio_reset_pin(PWR_Control_PIN); - gpio_set_direction(PWR_Control_PIN, GPIO_MODE_OUTPUT); - // gpio_set_level(PWR_Control_PIN, false); - gpio_set_level(PWR_Control_PIN, true); - } - - void InitializeButtons() { - instance_ = this; - InitializeButtonsCustom(); - - // Boot Button - button_config_t boot_btn_config = { - .long_press_time = 2000, - .short_press_time = 0 - }; - boot_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); - boot_btn_driver_->enable_power_save = false; - boot_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { - return !gpio_get_level(BOOT_BUTTON_GPIO); - }; - ESP_ERROR_CHECK(iot_button_create(&boot_btn_config, boot_btn_driver_, &boot_btn)); - iot_button_register_cb(boot_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); - } - app.ToggleChatState(); - }, this); - iot_button_register_cb(boot_btn, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - // 长按无处理 - }, this); - - // Power Button - button_config_t pwr_btn_config = { - .long_press_time = 5000, - .short_press_time = 0 - }; - pwr_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); - pwr_btn_driver_->enable_power_save = false; - pwr_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { - return !gpio_get_level(PWR_BUTTON_GPIO); - }; - ESP_ERROR_CHECK(iot_button_create(&pwr_btn_config, pwr_btn_driver_, &pwr_btn)); - iot_button_register_cb(pwr_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { - // 短按无处理 - }, this); - iot_button_register_cb(pwr_btn, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - if(self->GetBacklight()->brightness() > 0) { - self->GetBacklight()->SetBrightness(0); - gpio_set_level(PWR_Control_PIN, false); - } - else { - self->GetBacklight()->RestoreBrightness(); - gpio_set_level(PWR_Control_PIN, true); - } - }, this); - } - -public: - CustomBoard() { - InitializeI2c(); - InitializeTca9554(); - InitializeSpi(); - InitializeSpd2010Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_LEFT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); // I2S_STD_SLOT_LEFT / I2S_STD_SLOT_RIGHT / I2S_STD_SLOT_BOTH - - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(CustomBoard); - -CustomBoard* CustomBoard::instance_ = nullptr; +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" + +#include +#include "i2c_device.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_io_expander_tca9554.h" +#include "lcd_display.h" +#include + +#define TAG "waveshare_lcd_1_46" + +// 在waveshare_lcd_1_46类之前添加新的显示类 +class CustomLcdDisplay : public SpiLcdDisplay { +public: + static void rounder_event_cb(lv_event_t * e) { + lv_area_t * area = (lv_area_t *)lv_event_get_param(e); + uint16_t x1 = area->x1; + uint16_t x2 = area->x2; + + area->x1 = (x1 >> 2) << 2; // round the start of coordinate down to the nearest 4M number + area->x2 = ((x2 >> 2) << 2) + 3; // round the end of coordinate up to the nearest 4N+3 number + } + + CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_handle_t panel_handle, + int width, + int height, + int offset_x, + int offset_y, + bool mirror_x, + bool mirror_y, + bool swap_xy) + : SpiLcdDisplay(io_handle, panel_handle, + width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + DisplayLockGuard lock(this); + lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL); + } +}; + +class CustomBoard : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + esp_io_expander_handle_t io_expander = NULL; + LcdDisplay* display_; + button_handle_t boot_btn, pwr_btn; + button_driver_t* boot_btn_driver_ = nullptr; + button_driver_t* pwr_btn_driver_ = nullptr; + static CustomBoard* instance_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = I2C_SDA_IO, + .scl_io_num = I2C_SCL_IO, + .clk_source = I2C_CLK_SRC_DEFAULT, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeTca9554(void) { + esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); + if(ret != ESP_OK) + ESP_LOGE(TAG, "TCA9554 create returned error"); + + // uint32_t input_level_mask = 0; + // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_INPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输入 + // ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, &input_level_mask); // 获取引脚 EXIO0 和 EXIO1 的电平状态,存放在 input_level_mask 中 + + // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO2 和 EXIO3 模式为输出 + // ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, 1); // 将引脚电平设置为 1 + // ret = esp_io_expander_print_state(io_expander); // 打印引脚状态 + + ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输出 + ESP_ERROR_CHECK(ret); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(300)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(300)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize QSPI bus"); + + const spi_bus_config_t bus_config = TAIJIPI_SPD2010_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK, + QSPI_PIN_NUM_LCD_DATA0, + QSPI_PIN_NUM_LCD_DATA1, + QSPI_PIN_NUM_LCD_DATA2, + QSPI_PIN_NUM_LCD_DATA3, + QSPI_LCD_H_RES * 80 * sizeof(uint16_t)); + ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO)); + } + + void InitializeSpd2010Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + ESP_LOGI(TAG, "Install panel IO"); + + const esp_lcd_panel_io_spi_config_t io_config = SPD2010_PANEL_IO_QSPI_CONFIG(QSPI_PIN_NUM_LCD_CS, NULL, NULL); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io)); + + ESP_LOGI(TAG, "Install SPD2010 panel driver"); + + spd2010_vendor_config_t vendor_config = { + .flags = { + .use_qspi_interface = 1, + }, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = QSPI_PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h` + .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18) + .vendor_config = &vendor_config, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_spd2010(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_disp_on_off(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new CustomLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtonsCustom() { + gpio_reset_pin(BOOT_BUTTON_GPIO); + gpio_set_direction(BOOT_BUTTON_GPIO, GPIO_MODE_INPUT); + gpio_reset_pin(PWR_BUTTON_GPIO); + gpio_set_direction(PWR_BUTTON_GPIO, GPIO_MODE_INPUT); + gpio_reset_pin(PWR_Control_PIN); + gpio_set_direction(PWR_Control_PIN, GPIO_MODE_OUTPUT); + // gpio_set_level(PWR_Control_PIN, false); + gpio_set_level(PWR_Control_PIN, true); + } + + void InitializeButtons() { + instance_ = this; + InitializeButtonsCustom(); + + // Boot Button + button_config_t boot_btn_config = { + .long_press_time = 2000, + .short_press_time = 0 + }; + boot_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + boot_btn_driver_->enable_power_save = false; + boot_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return !gpio_get_level(BOOT_BUTTON_GPIO); + }; + ESP_ERROR_CHECK(iot_button_create(&boot_btn_config, boot_btn_driver_, &boot_btn)); + iot_button_register_cb(boot_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + self->ResetWifiConfiguration(); + } + app.ToggleChatState(); + }, this); + iot_button_register_cb(boot_btn, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + // 长按无处理 + }, this); + + // Power Button + button_config_t pwr_btn_config = { + .long_press_time = 5000, + .short_press_time = 0 + }; + pwr_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + pwr_btn_driver_->enable_power_save = false; + pwr_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return !gpio_get_level(PWR_BUTTON_GPIO); + }; + ESP_ERROR_CHECK(iot_button_create(&pwr_btn_config, pwr_btn_driver_, &pwr_btn)); + iot_button_register_cb(pwr_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { + // 短按无处理 + }, this); + iot_button_register_cb(pwr_btn, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + if(self->GetBacklight()->brightness() > 0) { + self->GetBacklight()->SetBrightness(0); + gpio_set_level(PWR_Control_PIN, false); + } + else { + self->GetBacklight()->RestoreBrightness(); + gpio_set_level(PWR_Control_PIN, true); + } + }, this); + } + +public: + CustomBoard() { + InitializeI2c(); + InitializeTca9554(); + InitializeSpi(); + InitializeSpd2010Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_LEFT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); // I2S_STD_SLOT_LEFT / I2S_STD_SLOT_RIGHT / I2S_STD_SLOT_BOTH + + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(CustomBoard); + +CustomBoard* CustomBoard::instance_ = nullptr; diff --git a/main/boards/esp32-s3-touch-lcd-1.85/README.md b/main/boards/esp32-s3-touch-lcd-1.85/README.md index df8a905..206ec4f 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85/README.md +++ b/main/boards/esp32-s3-touch-lcd-1.85/README.md @@ -1,3 +1,3 @@ -新增 微雪 开发板: ESP32-S3-Touch-LCD-1.85 -产品链接: +新增 微雪 开发板: ESP32-S3-Touch-LCD-1.85 +产品链接: https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-1.85.htm \ No newline at end of file diff --git a/main/boards/esp32-s3-touch-lcd-1.85/config.h b/main/boards/esp32-s3-touch-lcd-1.85/config.h index 7eff4c6..d0598aa 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85/config.h +++ b/main/boards/esp32-s3-touch-lcd-1.85/config.h @@ -1,69 +1,69 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define PWR_BUTTON_GPIO GPIO_NUM_6 -#define PWR_Control_PIN GPIO_NUM_7 - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_2 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_15 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_47 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_48 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_38 - -#define I2C_SCL_IO GPIO_NUM_10 -#define I2C_SDA_IO GPIO_NUM_11 - -#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 - -#define DISPLAY_WIDTH 360 -#define DISPLAY_HEIGHT 360 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define QSPI_LCD_H_RES (360) -#define QSPI_LCD_V_RES (360) -#define QSPI_LCD_BIT_PER_PIXEL (16) - -#define QSPI_LCD_HOST SPI2_HOST -#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_40 -#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_21 -#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46 -#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_45 -#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_42 -#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_41 -#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_NC -#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_5 - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define TP_PORT (I2C_NUM_1) -#define TP_PIN_NUM_SDA (GPIO_NUM_1) -#define TP_PIN_NUM_SCL (GPIO_NUM_3) -#define TP_PIN_NUM_RST (GPIO_NUM_NC) -#define TP_PIN_NUM_INT (GPIO_NUM_4) - -#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \ - { \ - .data0_io_num = d0, \ - .data1_io_num = d1, \ - .sclk_io_num = sclk, \ - .data2_io_num = d2, \ - .data3_io_num = d3, \ - .max_transfer_sz = max_trans_sz, \ - } - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define PWR_BUTTON_GPIO GPIO_NUM_6 +#define PWR_Control_PIN GPIO_NUM_7 + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_2 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_15 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_47 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_48 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_38 + +#define I2C_SCL_IO GPIO_NUM_10 +#define I2C_SDA_IO GPIO_NUM_11 + +#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 + +#define DISPLAY_WIDTH 360 +#define DISPLAY_HEIGHT 360 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define QSPI_LCD_H_RES (360) +#define QSPI_LCD_V_RES (360) +#define QSPI_LCD_BIT_PER_PIXEL (16) + +#define QSPI_LCD_HOST SPI2_HOST +#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_40 +#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_21 +#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46 +#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_45 +#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_42 +#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_41 +#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_NC +#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_5 + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define TP_PORT (I2C_NUM_1) +#define TP_PIN_NUM_SDA (GPIO_NUM_1) +#define TP_PIN_NUM_SCL (GPIO_NUM_3) +#define TP_PIN_NUM_RST (GPIO_NUM_NC) +#define TP_PIN_NUM_INT (GPIO_NUM_4) + +#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \ + { \ + .data0_io_num = d0, \ + .data1_io_num = d1, \ + .sclk_io_num = sclk, \ + .data2_io_num = d2, \ + .data3_io_num = d3, \ + .max_transfer_sz = max_trans_sz, \ + } + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp32-s3-touch-lcd-1.85/config.json b/main/boards/esp32-s3-touch-lcd-1.85/config.json index 63207b5..231c2ab 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85/config.json +++ b/main/boards/esp32-s3-touch-lcd-1.85/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp32-s3-touch-lcd-1.85", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp32-s3-touch-lcd-1.85", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc b/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc index ecdc7a3..1bb1a89 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc +++ b/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc @@ -1,447 +1,447 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" - -#include -#include "i2c_device.h" -#include -#include -#include -#include -#include -#include -#include -#include "esp_io_expander_tca9554.h" - -#define TAG "waveshare_lcd_1_85" - -#define LCD_OPCODE_WRITE_CMD (0x02ULL) -#define LCD_OPCODE_READ_CMD (0x0BULL) -#define LCD_OPCODE_WRITE_COLOR (0x32ULL) - -static const st77916_lcd_init_cmd_t vendor_specific_init_new[] = { - {0xF0, (uint8_t []){0x28}, 1, 0}, - {0xF2, (uint8_t []){0x28}, 1, 0}, - {0x73, (uint8_t []){0xF0}, 1, 0}, - {0x7C, (uint8_t []){0xD1}, 1, 0}, - {0x83, (uint8_t []){0xE0}, 1, 0}, - {0x84, (uint8_t []){0x61}, 1, 0}, - {0xF2, (uint8_t []){0x82}, 1, 0}, - {0xF0, (uint8_t []){0x00}, 1, 0}, - {0xF0, (uint8_t []){0x01}, 1, 0}, - {0xF1, (uint8_t []){0x01}, 1, 0}, - {0xB0, (uint8_t []){0x56}, 1, 0}, - {0xB1, (uint8_t []){0x4D}, 1, 0}, - {0xB2, (uint8_t []){0x24}, 1, 0}, - {0xB4, (uint8_t []){0x87}, 1, 0}, - {0xB5, (uint8_t []){0x44}, 1, 0}, - {0xB6, (uint8_t []){0x8B}, 1, 0}, - {0xB7, (uint8_t []){0x40}, 1, 0}, - {0xB8, (uint8_t []){0x86}, 1, 0}, - {0xBA, (uint8_t []){0x00}, 1, 0}, - {0xBB, (uint8_t []){0x08}, 1, 0}, - {0xBC, (uint8_t []){0x08}, 1, 0}, - {0xBD, (uint8_t []){0x00}, 1, 0}, - {0xC0, (uint8_t []){0x80}, 1, 0}, - {0xC1, (uint8_t []){0x10}, 1, 0}, - {0xC2, (uint8_t []){0x37}, 1, 0}, - {0xC3, (uint8_t []){0x80}, 1, 0}, - {0xC4, (uint8_t []){0x10}, 1, 0}, - {0xC5, (uint8_t []){0x37}, 1, 0}, - {0xC6, (uint8_t []){0xA9}, 1, 0}, - {0xC7, (uint8_t []){0x41}, 1, 0}, - {0xC8, (uint8_t []){0x01}, 1, 0}, - {0xC9, (uint8_t []){0xA9}, 1, 0}, - {0xCA, (uint8_t []){0x41}, 1, 0}, - {0xCB, (uint8_t []){0x01}, 1, 0}, - {0xD0, (uint8_t []){0x91}, 1, 0}, - {0xD1, (uint8_t []){0x68}, 1, 0}, - {0xD2, (uint8_t []){0x68}, 1, 0}, - {0xF5, (uint8_t []){0x00, 0xA5}, 2, 0}, - {0xDD, (uint8_t []){0x4F}, 1, 0}, - {0xDE, (uint8_t []){0x4F}, 1, 0}, - {0xF1, (uint8_t []){0x10}, 1, 0}, - {0xF0, (uint8_t []){0x00}, 1, 0}, - {0xF0, (uint8_t []){0x02}, 1, 0}, - {0xE0, (uint8_t []){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0}, - {0xE1, (uint8_t []){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0}, - {0xF0, (uint8_t []){0x10}, 1, 0}, - {0xF3, (uint8_t []){0x10}, 1, 0}, - {0xE0, (uint8_t []){0x07}, 1, 0}, - {0xE1, (uint8_t []){0x00}, 1, 0}, - {0xE2, (uint8_t []){0x00}, 1, 0}, - {0xE3, (uint8_t []){0x00}, 1, 0}, - {0xE4, (uint8_t []){0xE0}, 1, 0}, - {0xE5, (uint8_t []){0x06}, 1, 0}, - {0xE6, (uint8_t []){0x21}, 1, 0}, - {0xE7, (uint8_t []){0x01}, 1, 0}, - {0xE8, (uint8_t []){0x05}, 1, 0}, - {0xE9, (uint8_t []){0x02}, 1, 0}, - {0xEA, (uint8_t []){0xDA}, 1, 0}, - {0xEB, (uint8_t []){0x00}, 1, 0}, - {0xEC, (uint8_t []){0x00}, 1, 0}, - {0xED, (uint8_t []){0x0F}, 1, 0}, - {0xEE, (uint8_t []){0x00}, 1, 0}, - {0xEF, (uint8_t []){0x00}, 1, 0}, - {0xF8, (uint8_t []){0x00}, 1, 0}, - {0xF9, (uint8_t []){0x00}, 1, 0}, - {0xFA, (uint8_t []){0x00}, 1, 0}, - {0xFB, (uint8_t []){0x00}, 1, 0}, - {0xFC, (uint8_t []){0x00}, 1, 0}, - {0xFD, (uint8_t []){0x00}, 1, 0}, - {0xFE, (uint8_t []){0x00}, 1, 0}, - {0xFF, (uint8_t []){0x00}, 1, 0}, - {0x60, (uint8_t []){0x40}, 1, 0}, - {0x61, (uint8_t []){0x04}, 1, 0}, - {0x62, (uint8_t []){0x00}, 1, 0}, - {0x63, (uint8_t []){0x42}, 1, 0}, - {0x64, (uint8_t []){0xD9}, 1, 0}, - {0x65, (uint8_t []){0x00}, 1, 0}, - {0x66, (uint8_t []){0x00}, 1, 0}, - {0x67, (uint8_t []){0x00}, 1, 0}, - {0x68, (uint8_t []){0x00}, 1, 0}, - {0x69, (uint8_t []){0x00}, 1, 0}, - {0x6A, (uint8_t []){0x00}, 1, 0}, - {0x6B, (uint8_t []){0x00}, 1, 0}, - {0x70, (uint8_t []){0x40}, 1, 0}, - {0x71, (uint8_t []){0x03}, 1, 0}, - {0x72, (uint8_t []){0x00}, 1, 0}, - {0x73, (uint8_t []){0x42}, 1, 0}, - {0x74, (uint8_t []){0xD8}, 1, 0}, - {0x75, (uint8_t []){0x00}, 1, 0}, - {0x76, (uint8_t []){0x00}, 1, 0}, - {0x77, (uint8_t []){0x00}, 1, 0}, - {0x78, (uint8_t []){0x00}, 1, 0}, - {0x79, (uint8_t []){0x00}, 1, 0}, - {0x7A, (uint8_t []){0x00}, 1, 0}, - {0x7B, (uint8_t []){0x00}, 1, 0}, - {0x80, (uint8_t []){0x48}, 1, 0}, - {0x81, (uint8_t []){0x00}, 1, 0}, - {0x82, (uint8_t []){0x06}, 1, 0}, - {0x83, (uint8_t []){0x02}, 1, 0}, - {0x84, (uint8_t []){0xD6}, 1, 0}, - {0x85, (uint8_t []){0x04}, 1, 0}, - {0x86, (uint8_t []){0x00}, 1, 0}, - {0x87, (uint8_t []){0x00}, 1, 0}, - {0x88, (uint8_t []){0x48}, 1, 0}, - {0x89, (uint8_t []){0x00}, 1, 0}, - {0x8A, (uint8_t []){0x08}, 1, 0}, - {0x8B, (uint8_t []){0x02}, 1, 0}, - {0x8C, (uint8_t []){0xD8}, 1, 0}, - {0x8D, (uint8_t []){0x04}, 1, 0}, - {0x8E, (uint8_t []){0x00}, 1, 0}, - {0x8F, (uint8_t []){0x00}, 1, 0}, - {0x90, (uint8_t []){0x48}, 1, 0}, - {0x91, (uint8_t []){0x00}, 1, 0}, - {0x92, (uint8_t []){0x0A}, 1, 0}, - {0x93, (uint8_t []){0x02}, 1, 0}, - {0x94, (uint8_t []){0xDA}, 1, 0}, - {0x95, (uint8_t []){0x04}, 1, 0}, - {0x96, (uint8_t []){0x00}, 1, 0}, - {0x97, (uint8_t []){0x00}, 1, 0}, - {0x98, (uint8_t []){0x48}, 1, 0}, - {0x99, (uint8_t []){0x00}, 1, 0}, - {0x9A, (uint8_t []){0x0C}, 1, 0}, - {0x9B, (uint8_t []){0x02}, 1, 0}, - {0x9C, (uint8_t []){0xDC}, 1, 0}, - {0x9D, (uint8_t []){0x04}, 1, 0}, - {0x9E, (uint8_t []){0x00}, 1, 0}, - {0x9F, (uint8_t []){0x00}, 1, 0}, - {0xA0, (uint8_t []){0x48}, 1, 0}, - {0xA1, (uint8_t []){0x00}, 1, 0}, - {0xA2, (uint8_t []){0x05}, 1, 0}, - {0xA3, (uint8_t []){0x02}, 1, 0}, - {0xA4, (uint8_t []){0xD5}, 1, 0}, - {0xA5, (uint8_t []){0x04}, 1, 0}, - {0xA6, (uint8_t []){0x00}, 1, 0}, - {0xA7, (uint8_t []){0x00}, 1, 0}, - {0xA8, (uint8_t []){0x48}, 1, 0}, - {0xA9, (uint8_t []){0x00}, 1, 0}, - {0xAA, (uint8_t []){0x07}, 1, 0}, - {0xAB, (uint8_t []){0x02}, 1, 0}, - {0xAC, (uint8_t []){0xD7}, 1, 0}, - {0xAD, (uint8_t []){0x04}, 1, 0}, - {0xAE, (uint8_t []){0x00}, 1, 0}, - {0xAF, (uint8_t []){0x00}, 1, 0}, - {0xB0, (uint8_t []){0x48}, 1, 0}, - {0xB1, (uint8_t []){0x00}, 1, 0}, - {0xB2, (uint8_t []){0x09}, 1, 0}, - {0xB3, (uint8_t []){0x02}, 1, 0}, - {0xB4, (uint8_t []){0xD9}, 1, 0}, - {0xB5, (uint8_t []){0x04}, 1, 0}, - {0xB6, (uint8_t []){0x00}, 1, 0}, - {0xB7, (uint8_t []){0x00}, 1, 0}, - - {0xB8, (uint8_t []){0x48}, 1, 0}, - {0xB9, (uint8_t []){0x00}, 1, 0}, - {0xBA, (uint8_t []){0x0B}, 1, 0}, - {0xBB, (uint8_t []){0x02}, 1, 0}, - {0xBC, (uint8_t []){0xDB}, 1, 0}, - {0xBD, (uint8_t []){0x04}, 1, 0}, - {0xBE, (uint8_t []){0x00}, 1, 0}, - {0xBF, (uint8_t []){0x00}, 1, 0}, - {0xC0, (uint8_t []){0x10}, 1, 0}, - {0xC1, (uint8_t []){0x47}, 1, 0}, - {0xC2, (uint8_t []){0x56}, 1, 0}, - {0xC3, (uint8_t []){0x65}, 1, 0}, - {0xC4, (uint8_t []){0x74}, 1, 0}, - {0xC5, (uint8_t []){0x88}, 1, 0}, - {0xC6, (uint8_t []){0x99}, 1, 0}, - {0xC7, (uint8_t []){0x01}, 1, 0}, - {0xC8, (uint8_t []){0xBB}, 1, 0}, - {0xC9, (uint8_t []){0xAA}, 1, 0}, - {0xD0, (uint8_t []){0x10}, 1, 0}, - {0xD1, (uint8_t []){0x47}, 1, 0}, - {0xD2, (uint8_t []){0x56}, 1, 0}, - {0xD3, (uint8_t []){0x65}, 1, 0}, - {0xD4, (uint8_t []){0x74}, 1, 0}, - {0xD5, (uint8_t []){0x88}, 1, 0}, - {0xD6, (uint8_t []){0x99}, 1, 0}, - {0xD7, (uint8_t []){0x01}, 1, 0}, - {0xD8, (uint8_t []){0xBB}, 1, 0}, - {0xD9, (uint8_t []){0xAA}, 1, 0}, - {0xF3, (uint8_t []){0x01}, 1, 0}, - {0xF0, (uint8_t []){0x00}, 1, 0}, - {0x21, (uint8_t []){0x00}, 1, 0}, - {0x11, (uint8_t []){0x00}, 1, 120}, - {0x29, (uint8_t []){0x00}, 1, 0}, -}; -class CustomBoard : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - esp_io_expander_handle_t io_expander = NULL; - LcdDisplay* display_; - button_handle_t boot_btn, pwr_btn; - button_driver_t* boot_btn_driver_ = nullptr; - button_driver_t* pwr_btn_driver_ = nullptr; - static CustomBoard* instance_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = I2C_SDA_IO, - .scl_io_num = I2C_SCL_IO, - .clk_source = I2C_CLK_SRC_DEFAULT, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeTca9554(void) { - esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); - if(ret != ESP_OK) - ESP_LOGE(TAG, "TCA9554 create returned error"); - - // uint32_t input_level_mask = 0; - // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_INPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输入 - // ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, &input_level_mask); // 获取引脚 EXIO0 和 EXIO1 的电平状态,存放在 input_level_mask 中 - - // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO2 和 EXIO3 模式为输出 - // ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, 1); // 将引脚电平设置为 1 - // ret = esp_io_expander_print_state(io_expander); // 打印引脚状态 - - ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输出 - ESP_ERROR_CHECK(ret); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(300)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(300)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - } - - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize QSPI bus"); - - const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK, - QSPI_PIN_NUM_LCD_DATA0, - QSPI_PIN_NUM_LCD_DATA1, - QSPI_PIN_NUM_LCD_DATA2, - QSPI_PIN_NUM_LCD_DATA3, - QSPI_LCD_H_RES * 80 * sizeof(uint16_t)); - ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO)); - } - - void Initializest77916Display() { - - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - ESP_LOGI(TAG, "Install panel IO"); - - esp_lcd_panel_io_spi_config_t io_config = { - .cs_gpio_num = QSPI_PIN_NUM_LCD_CS, - .dc_gpio_num = -1, - .spi_mode = 0, - .pclk_hz = 3 * 1000 * 1000, - .trans_queue_depth = 10, - .on_color_trans_done = NULL, - .user_ctx = NULL, - .lcd_cmd_bits = 32, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .octal_mode = 0, - .quad_mode = 1, - .sio_mode = 0, - .lsb_first = 0, - .cs_high_active = 0, - }, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io)); - - ESP_LOGI(TAG, "Install ST77916 panel driver"); - - st77916_vendor_config_t vendor_config = { - .flags = { - .use_qspi_interface = 1, - }, - }; - - printf("-------------------------------------- Version selection -------------------------------------- \r\n"); - esp_err_t ret; - int lcd_cmd = 0x04; - uint8_t register_data[4]; - size_t param_size = sizeof(register_data); - lcd_cmd &= 0xff; - lcd_cmd <<= 8; - lcd_cmd |= LCD_OPCODE_READ_CMD << 24; // Use the read opcode instead of write - ret = esp_lcd_panel_io_rx_param(panel_io, lcd_cmd, register_data, param_size); - if (ret == ESP_OK) { - printf("Register 0x04 data: %02x %02x %02x %02x\n", register_data[0], register_data[1], register_data[2], register_data[3]); - } else { - printf("Failed to read register 0x04, error code: %d\n", ret); - } - // panel_io_spi_del(io_handle); - io_config.pclk_hz = 80 * 1000 * 1000; - if (esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io) != ESP_OK){ - printf("Failed to set LCD communication parameters -- SPI\r\n"); - return ; - } - printf("LCD communication parameters are set successfully -- SPI\r\n"); - - // Check register values and configure accordingly - if (register_data[0] == 0x00 && register_data[1] == 0x7F && register_data[2] == 0x7F && register_data[3] == 0x7F) { - // Handle the case where the register data matches this pattern - printf("Vendor-specific initialization for case 1.\n"); - } - else if (register_data[0] == 0x00 && register_data[1] == 0x02 && register_data[2] == 0x7F && register_data[3] == 0x7F) { - // Provide vendor-specific initialization commands if register data matches this pattern - vendor_config.init_cmds = vendor_specific_init_new; - vendor_config.init_cmds_size = sizeof(vendor_specific_init_new) / sizeof(st77916_lcd_init_cmd_t); - printf("Vendor-specific initialization for case 2.\n"); - } - printf("------------------------------------- End of version selection------------------------------------- \r\n"); - - const esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = QSPI_PIN_NUM_LCD_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h` - .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18) - .vendor_config = &vendor_config, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_disp_on_off(panel, true); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtonsCustom() { - gpio_reset_pin(BOOT_BUTTON_GPIO); - gpio_set_direction(BOOT_BUTTON_GPIO, GPIO_MODE_INPUT); - gpio_reset_pin(PWR_BUTTON_GPIO); - gpio_set_direction(PWR_BUTTON_GPIO, GPIO_MODE_INPUT); - gpio_reset_pin(PWR_Control_PIN); - gpio_set_direction(PWR_Control_PIN, GPIO_MODE_OUTPUT); - // gpio_set_level(PWR_Control_PIN, false); - gpio_set_level(PWR_Control_PIN, true); - } - void InitializeButtons() { - instance_ = this; - InitializeButtonsCustom(); - - // Boot Button - button_config_t boot_btn_config = { - .long_press_time = 2000, - .short_press_time = 0 - }; - boot_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); - boot_btn_driver_->enable_power_save = false; - boot_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { - return !gpio_get_level(BOOT_BUTTON_GPIO); - }; - ESP_ERROR_CHECK(iot_button_create(&boot_btn_config, boot_btn_driver_, &boot_btn)); - iot_button_register_cb(boot_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); - } - app.ToggleChatState(); - }, this); - - // Power Button - button_config_t pwr_btn_config = { - .long_press_time = 5000, - .short_press_time = 0 - }; - pwr_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); - pwr_btn_driver_->enable_power_save = false; - pwr_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { - return !gpio_get_level(PWR_BUTTON_GPIO); - }; - ESP_ERROR_CHECK(iot_button_create(&pwr_btn_config, pwr_btn_driver_, &pwr_btn)); - iot_button_register_cb(pwr_btn, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - if(self->GetBacklight()->brightness() > 0) { - self->GetBacklight()->SetBrightness(0); - gpio_set_level(PWR_Control_PIN, false); - } - else { - self->GetBacklight()->RestoreBrightness(); - gpio_set_level(PWR_Control_PIN, true); - } - }, this); - } - -public: - CustomBoard() { - InitializeI2c(); - InitializeTca9554(); - InitializeSpi(); - Initializest77916Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_BOTH, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); // I2S_STD_SLOT_LEFT / I2S_STD_SLOT_RIGHT / I2S_STD_SLOT_BOTH - - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(CustomBoard); - -CustomBoard* CustomBoard::instance_ = nullptr; +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" + +#include +#include "i2c_device.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_io_expander_tca9554.h" + +#define TAG "waveshare_lcd_1_85" + +#define LCD_OPCODE_WRITE_CMD (0x02ULL) +#define LCD_OPCODE_READ_CMD (0x0BULL) +#define LCD_OPCODE_WRITE_COLOR (0x32ULL) + +static const st77916_lcd_init_cmd_t vendor_specific_init_new[] = { + {0xF0, (uint8_t []){0x28}, 1, 0}, + {0xF2, (uint8_t []){0x28}, 1, 0}, + {0x73, (uint8_t []){0xF0}, 1, 0}, + {0x7C, (uint8_t []){0xD1}, 1, 0}, + {0x83, (uint8_t []){0xE0}, 1, 0}, + {0x84, (uint8_t []){0x61}, 1, 0}, + {0xF2, (uint8_t []){0x82}, 1, 0}, + {0xF0, (uint8_t []){0x00}, 1, 0}, + {0xF0, (uint8_t []){0x01}, 1, 0}, + {0xF1, (uint8_t []){0x01}, 1, 0}, + {0xB0, (uint8_t []){0x56}, 1, 0}, + {0xB1, (uint8_t []){0x4D}, 1, 0}, + {0xB2, (uint8_t []){0x24}, 1, 0}, + {0xB4, (uint8_t []){0x87}, 1, 0}, + {0xB5, (uint8_t []){0x44}, 1, 0}, + {0xB6, (uint8_t []){0x8B}, 1, 0}, + {0xB7, (uint8_t []){0x40}, 1, 0}, + {0xB8, (uint8_t []){0x86}, 1, 0}, + {0xBA, (uint8_t []){0x00}, 1, 0}, + {0xBB, (uint8_t []){0x08}, 1, 0}, + {0xBC, (uint8_t []){0x08}, 1, 0}, + {0xBD, (uint8_t []){0x00}, 1, 0}, + {0xC0, (uint8_t []){0x80}, 1, 0}, + {0xC1, (uint8_t []){0x10}, 1, 0}, + {0xC2, (uint8_t []){0x37}, 1, 0}, + {0xC3, (uint8_t []){0x80}, 1, 0}, + {0xC4, (uint8_t []){0x10}, 1, 0}, + {0xC5, (uint8_t []){0x37}, 1, 0}, + {0xC6, (uint8_t []){0xA9}, 1, 0}, + {0xC7, (uint8_t []){0x41}, 1, 0}, + {0xC8, (uint8_t []){0x01}, 1, 0}, + {0xC9, (uint8_t []){0xA9}, 1, 0}, + {0xCA, (uint8_t []){0x41}, 1, 0}, + {0xCB, (uint8_t []){0x01}, 1, 0}, + {0xD0, (uint8_t []){0x91}, 1, 0}, + {0xD1, (uint8_t []){0x68}, 1, 0}, + {0xD2, (uint8_t []){0x68}, 1, 0}, + {0xF5, (uint8_t []){0x00, 0xA5}, 2, 0}, + {0xDD, (uint8_t []){0x4F}, 1, 0}, + {0xDE, (uint8_t []){0x4F}, 1, 0}, + {0xF1, (uint8_t []){0x10}, 1, 0}, + {0xF0, (uint8_t []){0x00}, 1, 0}, + {0xF0, (uint8_t []){0x02}, 1, 0}, + {0xE0, (uint8_t []){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0}, + {0xE1, (uint8_t []){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0}, + {0xF0, (uint8_t []){0x10}, 1, 0}, + {0xF3, (uint8_t []){0x10}, 1, 0}, + {0xE0, (uint8_t []){0x07}, 1, 0}, + {0xE1, (uint8_t []){0x00}, 1, 0}, + {0xE2, (uint8_t []){0x00}, 1, 0}, + {0xE3, (uint8_t []){0x00}, 1, 0}, + {0xE4, (uint8_t []){0xE0}, 1, 0}, + {0xE5, (uint8_t []){0x06}, 1, 0}, + {0xE6, (uint8_t []){0x21}, 1, 0}, + {0xE7, (uint8_t []){0x01}, 1, 0}, + {0xE8, (uint8_t []){0x05}, 1, 0}, + {0xE9, (uint8_t []){0x02}, 1, 0}, + {0xEA, (uint8_t []){0xDA}, 1, 0}, + {0xEB, (uint8_t []){0x00}, 1, 0}, + {0xEC, (uint8_t []){0x00}, 1, 0}, + {0xED, (uint8_t []){0x0F}, 1, 0}, + {0xEE, (uint8_t []){0x00}, 1, 0}, + {0xEF, (uint8_t []){0x00}, 1, 0}, + {0xF8, (uint8_t []){0x00}, 1, 0}, + {0xF9, (uint8_t []){0x00}, 1, 0}, + {0xFA, (uint8_t []){0x00}, 1, 0}, + {0xFB, (uint8_t []){0x00}, 1, 0}, + {0xFC, (uint8_t []){0x00}, 1, 0}, + {0xFD, (uint8_t []){0x00}, 1, 0}, + {0xFE, (uint8_t []){0x00}, 1, 0}, + {0xFF, (uint8_t []){0x00}, 1, 0}, + {0x60, (uint8_t []){0x40}, 1, 0}, + {0x61, (uint8_t []){0x04}, 1, 0}, + {0x62, (uint8_t []){0x00}, 1, 0}, + {0x63, (uint8_t []){0x42}, 1, 0}, + {0x64, (uint8_t []){0xD9}, 1, 0}, + {0x65, (uint8_t []){0x00}, 1, 0}, + {0x66, (uint8_t []){0x00}, 1, 0}, + {0x67, (uint8_t []){0x00}, 1, 0}, + {0x68, (uint8_t []){0x00}, 1, 0}, + {0x69, (uint8_t []){0x00}, 1, 0}, + {0x6A, (uint8_t []){0x00}, 1, 0}, + {0x6B, (uint8_t []){0x00}, 1, 0}, + {0x70, (uint8_t []){0x40}, 1, 0}, + {0x71, (uint8_t []){0x03}, 1, 0}, + {0x72, (uint8_t []){0x00}, 1, 0}, + {0x73, (uint8_t []){0x42}, 1, 0}, + {0x74, (uint8_t []){0xD8}, 1, 0}, + {0x75, (uint8_t []){0x00}, 1, 0}, + {0x76, (uint8_t []){0x00}, 1, 0}, + {0x77, (uint8_t []){0x00}, 1, 0}, + {0x78, (uint8_t []){0x00}, 1, 0}, + {0x79, (uint8_t []){0x00}, 1, 0}, + {0x7A, (uint8_t []){0x00}, 1, 0}, + {0x7B, (uint8_t []){0x00}, 1, 0}, + {0x80, (uint8_t []){0x48}, 1, 0}, + {0x81, (uint8_t []){0x00}, 1, 0}, + {0x82, (uint8_t []){0x06}, 1, 0}, + {0x83, (uint8_t []){0x02}, 1, 0}, + {0x84, (uint8_t []){0xD6}, 1, 0}, + {0x85, (uint8_t []){0x04}, 1, 0}, + {0x86, (uint8_t []){0x00}, 1, 0}, + {0x87, (uint8_t []){0x00}, 1, 0}, + {0x88, (uint8_t []){0x48}, 1, 0}, + {0x89, (uint8_t []){0x00}, 1, 0}, + {0x8A, (uint8_t []){0x08}, 1, 0}, + {0x8B, (uint8_t []){0x02}, 1, 0}, + {0x8C, (uint8_t []){0xD8}, 1, 0}, + {0x8D, (uint8_t []){0x04}, 1, 0}, + {0x8E, (uint8_t []){0x00}, 1, 0}, + {0x8F, (uint8_t []){0x00}, 1, 0}, + {0x90, (uint8_t []){0x48}, 1, 0}, + {0x91, (uint8_t []){0x00}, 1, 0}, + {0x92, (uint8_t []){0x0A}, 1, 0}, + {0x93, (uint8_t []){0x02}, 1, 0}, + {0x94, (uint8_t []){0xDA}, 1, 0}, + {0x95, (uint8_t []){0x04}, 1, 0}, + {0x96, (uint8_t []){0x00}, 1, 0}, + {0x97, (uint8_t []){0x00}, 1, 0}, + {0x98, (uint8_t []){0x48}, 1, 0}, + {0x99, (uint8_t []){0x00}, 1, 0}, + {0x9A, (uint8_t []){0x0C}, 1, 0}, + {0x9B, (uint8_t []){0x02}, 1, 0}, + {0x9C, (uint8_t []){0xDC}, 1, 0}, + {0x9D, (uint8_t []){0x04}, 1, 0}, + {0x9E, (uint8_t []){0x00}, 1, 0}, + {0x9F, (uint8_t []){0x00}, 1, 0}, + {0xA0, (uint8_t []){0x48}, 1, 0}, + {0xA1, (uint8_t []){0x00}, 1, 0}, + {0xA2, (uint8_t []){0x05}, 1, 0}, + {0xA3, (uint8_t []){0x02}, 1, 0}, + {0xA4, (uint8_t []){0xD5}, 1, 0}, + {0xA5, (uint8_t []){0x04}, 1, 0}, + {0xA6, (uint8_t []){0x00}, 1, 0}, + {0xA7, (uint8_t []){0x00}, 1, 0}, + {0xA8, (uint8_t []){0x48}, 1, 0}, + {0xA9, (uint8_t []){0x00}, 1, 0}, + {0xAA, (uint8_t []){0x07}, 1, 0}, + {0xAB, (uint8_t []){0x02}, 1, 0}, + {0xAC, (uint8_t []){0xD7}, 1, 0}, + {0xAD, (uint8_t []){0x04}, 1, 0}, + {0xAE, (uint8_t []){0x00}, 1, 0}, + {0xAF, (uint8_t []){0x00}, 1, 0}, + {0xB0, (uint8_t []){0x48}, 1, 0}, + {0xB1, (uint8_t []){0x00}, 1, 0}, + {0xB2, (uint8_t []){0x09}, 1, 0}, + {0xB3, (uint8_t []){0x02}, 1, 0}, + {0xB4, (uint8_t []){0xD9}, 1, 0}, + {0xB5, (uint8_t []){0x04}, 1, 0}, + {0xB6, (uint8_t []){0x00}, 1, 0}, + {0xB7, (uint8_t []){0x00}, 1, 0}, + + {0xB8, (uint8_t []){0x48}, 1, 0}, + {0xB9, (uint8_t []){0x00}, 1, 0}, + {0xBA, (uint8_t []){0x0B}, 1, 0}, + {0xBB, (uint8_t []){0x02}, 1, 0}, + {0xBC, (uint8_t []){0xDB}, 1, 0}, + {0xBD, (uint8_t []){0x04}, 1, 0}, + {0xBE, (uint8_t []){0x00}, 1, 0}, + {0xBF, (uint8_t []){0x00}, 1, 0}, + {0xC0, (uint8_t []){0x10}, 1, 0}, + {0xC1, (uint8_t []){0x47}, 1, 0}, + {0xC2, (uint8_t []){0x56}, 1, 0}, + {0xC3, (uint8_t []){0x65}, 1, 0}, + {0xC4, (uint8_t []){0x74}, 1, 0}, + {0xC5, (uint8_t []){0x88}, 1, 0}, + {0xC6, (uint8_t []){0x99}, 1, 0}, + {0xC7, (uint8_t []){0x01}, 1, 0}, + {0xC8, (uint8_t []){0xBB}, 1, 0}, + {0xC9, (uint8_t []){0xAA}, 1, 0}, + {0xD0, (uint8_t []){0x10}, 1, 0}, + {0xD1, (uint8_t []){0x47}, 1, 0}, + {0xD2, (uint8_t []){0x56}, 1, 0}, + {0xD3, (uint8_t []){0x65}, 1, 0}, + {0xD4, (uint8_t []){0x74}, 1, 0}, + {0xD5, (uint8_t []){0x88}, 1, 0}, + {0xD6, (uint8_t []){0x99}, 1, 0}, + {0xD7, (uint8_t []){0x01}, 1, 0}, + {0xD8, (uint8_t []){0xBB}, 1, 0}, + {0xD9, (uint8_t []){0xAA}, 1, 0}, + {0xF3, (uint8_t []){0x01}, 1, 0}, + {0xF0, (uint8_t []){0x00}, 1, 0}, + {0x21, (uint8_t []){0x00}, 1, 0}, + {0x11, (uint8_t []){0x00}, 1, 120}, + {0x29, (uint8_t []){0x00}, 1, 0}, +}; +class CustomBoard : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + esp_io_expander_handle_t io_expander = NULL; + LcdDisplay* display_; + button_handle_t boot_btn, pwr_btn; + button_driver_t* boot_btn_driver_ = nullptr; + button_driver_t* pwr_btn_driver_ = nullptr; + static CustomBoard* instance_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = I2C_SDA_IO, + .scl_io_num = I2C_SCL_IO, + .clk_source = I2C_CLK_SRC_DEFAULT, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeTca9554(void) { + esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); + if(ret != ESP_OK) + ESP_LOGE(TAG, "TCA9554 create returned error"); + + // uint32_t input_level_mask = 0; + // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_INPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输入 + // ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, &input_level_mask); // 获取引脚 EXIO0 和 EXIO1 的电平状态,存放在 input_level_mask 中 + + // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO2 和 EXIO3 模式为输出 + // ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, 1); // 将引脚电平设置为 1 + // ret = esp_io_expander_print_state(io_expander); // 打印引脚状态 + + ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输出 + ESP_ERROR_CHECK(ret); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(300)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(300)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize QSPI bus"); + + const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK, + QSPI_PIN_NUM_LCD_DATA0, + QSPI_PIN_NUM_LCD_DATA1, + QSPI_PIN_NUM_LCD_DATA2, + QSPI_PIN_NUM_LCD_DATA3, + QSPI_LCD_H_RES * 80 * sizeof(uint16_t)); + ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO)); + } + + void Initializest77916Display() { + + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + ESP_LOGI(TAG, "Install panel IO"); + + esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = QSPI_PIN_NUM_LCD_CS, + .dc_gpio_num = -1, + .spi_mode = 0, + .pclk_hz = 3 * 1000 * 1000, + .trans_queue_depth = 10, + .on_color_trans_done = NULL, + .user_ctx = NULL, + .lcd_cmd_bits = 32, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .octal_mode = 0, + .quad_mode = 1, + .sio_mode = 0, + .lsb_first = 0, + .cs_high_active = 0, + }, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io)); + + ESP_LOGI(TAG, "Install ST77916 panel driver"); + + st77916_vendor_config_t vendor_config = { + .flags = { + .use_qspi_interface = 1, + }, + }; + + printf("-------------------------------------- Version selection -------------------------------------- \r\n"); + esp_err_t ret; + int lcd_cmd = 0x04; + uint8_t register_data[4]; + size_t param_size = sizeof(register_data); + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= LCD_OPCODE_READ_CMD << 24; // Use the read opcode instead of write + ret = esp_lcd_panel_io_rx_param(panel_io, lcd_cmd, register_data, param_size); + if (ret == ESP_OK) { + printf("Register 0x04 data: %02x %02x %02x %02x\n", register_data[0], register_data[1], register_data[2], register_data[3]); + } else { + printf("Failed to read register 0x04, error code: %d\n", ret); + } + // panel_io_spi_del(io_handle); + io_config.pclk_hz = 80 * 1000 * 1000; + if (esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io) != ESP_OK){ + printf("Failed to set LCD communication parameters -- SPI\r\n"); + return ; + } + printf("LCD communication parameters are set successfully -- SPI\r\n"); + + // Check register values and configure accordingly + if (register_data[0] == 0x00 && register_data[1] == 0x7F && register_data[2] == 0x7F && register_data[3] == 0x7F) { + // Handle the case where the register data matches this pattern + printf("Vendor-specific initialization for case 1.\n"); + } + else if (register_data[0] == 0x00 && register_data[1] == 0x02 && register_data[2] == 0x7F && register_data[3] == 0x7F) { + // Provide vendor-specific initialization commands if register data matches this pattern + vendor_config.init_cmds = vendor_specific_init_new; + vendor_config.init_cmds_size = sizeof(vendor_specific_init_new) / sizeof(st77916_lcd_init_cmd_t); + printf("Vendor-specific initialization for case 2.\n"); + } + printf("------------------------------------- End of version selection------------------------------------- \r\n"); + + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = QSPI_PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h` + .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18) + .vendor_config = &vendor_config, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_disp_on_off(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtonsCustom() { + gpio_reset_pin(BOOT_BUTTON_GPIO); + gpio_set_direction(BOOT_BUTTON_GPIO, GPIO_MODE_INPUT); + gpio_reset_pin(PWR_BUTTON_GPIO); + gpio_set_direction(PWR_BUTTON_GPIO, GPIO_MODE_INPUT); + gpio_reset_pin(PWR_Control_PIN); + gpio_set_direction(PWR_Control_PIN, GPIO_MODE_OUTPUT); + // gpio_set_level(PWR_Control_PIN, false); + gpio_set_level(PWR_Control_PIN, true); + } + void InitializeButtons() { + instance_ = this; + InitializeButtonsCustom(); + + // Boot Button + button_config_t boot_btn_config = { + .long_press_time = 2000, + .short_press_time = 0 + }; + boot_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + boot_btn_driver_->enable_power_save = false; + boot_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return !gpio_get_level(BOOT_BUTTON_GPIO); + }; + ESP_ERROR_CHECK(iot_button_create(&boot_btn_config, boot_btn_driver_, &boot_btn)); + iot_button_register_cb(boot_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + self->ResetWifiConfiguration(); + } + app.ToggleChatState(); + }, this); + + // Power Button + button_config_t pwr_btn_config = { + .long_press_time = 5000, + .short_press_time = 0 + }; + pwr_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + pwr_btn_driver_->enable_power_save = false; + pwr_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return !gpio_get_level(PWR_BUTTON_GPIO); + }; + ESP_ERROR_CHECK(iot_button_create(&pwr_btn_config, pwr_btn_driver_, &pwr_btn)); + iot_button_register_cb(pwr_btn, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + if(self->GetBacklight()->brightness() > 0) { + self->GetBacklight()->SetBrightness(0); + gpio_set_level(PWR_Control_PIN, false); + } + else { + self->GetBacklight()->RestoreBrightness(); + gpio_set_level(PWR_Control_PIN, true); + } + }, this); + } + +public: + CustomBoard() { + InitializeI2c(); + InitializeTca9554(); + InitializeSpi(); + Initializest77916Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_BOTH, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); // I2S_STD_SLOT_LEFT / I2S_STD_SLOT_RIGHT / I2S_STD_SLOT_BOTH + + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(CustomBoard); + +CustomBoard* CustomBoard::instance_ = nullptr; diff --git a/main/boards/esp32-s3-touch-lcd-1.85c/README.md b/main/boards/esp32-s3-touch-lcd-1.85c/README.md index 7668dec..869f902 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85c/README.md +++ b/main/boards/esp32-s3-touch-lcd-1.85c/README.md @@ -1,3 +1,3 @@ -新增 微雪 开发板: ESP32-S3-Touch-LCD-1.85C -产品链接: +新增 微雪 开发板: ESP32-S3-Touch-LCD-1.85C +产品链接: https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-1.85C.htm \ No newline at end of file diff --git a/main/boards/esp32-s3-touch-lcd-1.85c/config.h b/main/boards/esp32-s3-touch-lcd-1.85c/config.h index dfd5a89..e1cf24e 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85c/config.h +++ b/main/boards/esp32-s3-touch-lcd-1.85c/config.h @@ -1,67 +1,67 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_2 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_15 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_47 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_48 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_38 - -#define I2C_SCL_IO GPIO_NUM_10 -#define I2C_SDA_IO GPIO_NUM_11 - -#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 - -#define DISPLAY_WIDTH 360 -#define DISPLAY_HEIGHT 360 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define QSPI_LCD_H_RES (360) -#define QSPI_LCD_V_RES (360) -#define QSPI_LCD_BIT_PER_PIXEL (16) - -#define QSPI_LCD_HOST SPI2_HOST -#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_40 -#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_21 -#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46 -#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_45 -#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_42 -#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_41 -#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_NC -#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_5 - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define TP_PORT (I2C_NUM_1) -#define TP_PIN_NUM_SDA (I2C_SDA_IO) -#define TP_PIN_NUM_SCL (I2C_SCL_IO) -#define TP_PIN_NUM_RST (GPIO_NUM_NC) -#define TP_PIN_NUM_INT (GPIO_NUM_4) - -#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \ - { \ - .data0_io_num = d0, \ - .data1_io_num = d1, \ - .sclk_io_num = sclk, \ - .data2_io_num = d2, \ - .data3_io_num = d3, \ - .max_transfer_sz = max_trans_sz, \ - } - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_2 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_15 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_47 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_48 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_38 + +#define I2C_SCL_IO GPIO_NUM_10 +#define I2C_SDA_IO GPIO_NUM_11 + +#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 + +#define DISPLAY_WIDTH 360 +#define DISPLAY_HEIGHT 360 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define QSPI_LCD_H_RES (360) +#define QSPI_LCD_V_RES (360) +#define QSPI_LCD_BIT_PER_PIXEL (16) + +#define QSPI_LCD_HOST SPI2_HOST +#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_40 +#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_21 +#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46 +#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_45 +#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_42 +#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_41 +#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_NC +#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_5 + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define TP_PORT (I2C_NUM_1) +#define TP_PIN_NUM_SDA (I2C_SDA_IO) +#define TP_PIN_NUM_SCL (I2C_SCL_IO) +#define TP_PIN_NUM_RST (GPIO_NUM_NC) +#define TP_PIN_NUM_INT (GPIO_NUM_4) + +#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \ + { \ + .data0_io_num = d0, \ + .data1_io_num = d1, \ + .sclk_io_num = sclk, \ + .data2_io_num = d2, \ + .data3_io_num = d3, \ + .max_transfer_sz = max_trans_sz, \ + } + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp32-s3-touch-lcd-1.85c/config.json b/main/boards/esp32-s3-touch-lcd-1.85c/config.json index 1832799..f96b0f1 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85c/config.json +++ b/main/boards/esp32-s3-touch-lcd-1.85c/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp32-s3-touch-lcd-1.85c", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp32-s3-touch-lcd-1.85c", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc b/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc index cf5c172..14665c9 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc +++ b/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc @@ -1,396 +1,396 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" - -#include -#include "i2c_device.h" -#include -#include -#include -#include -#include -#include -#include -#include "esp_io_expander_tca9554.h" - -#define TAG "waveshare_lcd_1_85c" - -#define LCD_OPCODE_WRITE_CMD (0x02ULL) -#define LCD_OPCODE_READ_CMD (0x0BULL) -#define LCD_OPCODE_WRITE_COLOR (0x32ULL) - -static const st77916_lcd_init_cmd_t vendor_specific_init_new[] = { - {0xF0, (uint8_t []){0x28}, 1, 0}, - {0xF2, (uint8_t []){0x28}, 1, 0}, - {0x73, (uint8_t []){0xF0}, 1, 0}, - {0x7C, (uint8_t []){0xD1}, 1, 0}, - {0x83, (uint8_t []){0xE0}, 1, 0}, - {0x84, (uint8_t []){0x61}, 1, 0}, - {0xF2, (uint8_t []){0x82}, 1, 0}, - {0xF0, (uint8_t []){0x00}, 1, 0}, - {0xF0, (uint8_t []){0x01}, 1, 0}, - {0xF1, (uint8_t []){0x01}, 1, 0}, - {0xB0, (uint8_t []){0x56}, 1, 0}, - {0xB1, (uint8_t []){0x4D}, 1, 0}, - {0xB2, (uint8_t []){0x24}, 1, 0}, - {0xB4, (uint8_t []){0x87}, 1, 0}, - {0xB5, (uint8_t []){0x44}, 1, 0}, - {0xB6, (uint8_t []){0x8B}, 1, 0}, - {0xB7, (uint8_t []){0x40}, 1, 0}, - {0xB8, (uint8_t []){0x86}, 1, 0}, - {0xBA, (uint8_t []){0x00}, 1, 0}, - {0xBB, (uint8_t []){0x08}, 1, 0}, - {0xBC, (uint8_t []){0x08}, 1, 0}, - {0xBD, (uint8_t []){0x00}, 1, 0}, - {0xC0, (uint8_t []){0x80}, 1, 0}, - {0xC1, (uint8_t []){0x10}, 1, 0}, - {0xC2, (uint8_t []){0x37}, 1, 0}, - {0xC3, (uint8_t []){0x80}, 1, 0}, - {0xC4, (uint8_t []){0x10}, 1, 0}, - {0xC5, (uint8_t []){0x37}, 1, 0}, - {0xC6, (uint8_t []){0xA9}, 1, 0}, - {0xC7, (uint8_t []){0x41}, 1, 0}, - {0xC8, (uint8_t []){0x01}, 1, 0}, - {0xC9, (uint8_t []){0xA9}, 1, 0}, - {0xCA, (uint8_t []){0x41}, 1, 0}, - {0xCB, (uint8_t []){0x01}, 1, 0}, - {0xD0, (uint8_t []){0x91}, 1, 0}, - {0xD1, (uint8_t []){0x68}, 1, 0}, - {0xD2, (uint8_t []){0x68}, 1, 0}, - {0xF5, (uint8_t []){0x00, 0xA5}, 2, 0}, - {0xDD, (uint8_t []){0x4F}, 1, 0}, - {0xDE, (uint8_t []){0x4F}, 1, 0}, - {0xF1, (uint8_t []){0x10}, 1, 0}, - {0xF0, (uint8_t []){0x00}, 1, 0}, - {0xF0, (uint8_t []){0x02}, 1, 0}, - {0xE0, (uint8_t []){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0}, - {0xE1, (uint8_t []){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0}, - {0xF0, (uint8_t []){0x10}, 1, 0}, - {0xF3, (uint8_t []){0x10}, 1, 0}, - {0xE0, (uint8_t []){0x07}, 1, 0}, - {0xE1, (uint8_t []){0x00}, 1, 0}, - {0xE2, (uint8_t []){0x00}, 1, 0}, - {0xE3, (uint8_t []){0x00}, 1, 0}, - {0xE4, (uint8_t []){0xE0}, 1, 0}, - {0xE5, (uint8_t []){0x06}, 1, 0}, - {0xE6, (uint8_t []){0x21}, 1, 0}, - {0xE7, (uint8_t []){0x01}, 1, 0}, - {0xE8, (uint8_t []){0x05}, 1, 0}, - {0xE9, (uint8_t []){0x02}, 1, 0}, - {0xEA, (uint8_t []){0xDA}, 1, 0}, - {0xEB, (uint8_t []){0x00}, 1, 0}, - {0xEC, (uint8_t []){0x00}, 1, 0}, - {0xED, (uint8_t []){0x0F}, 1, 0}, - {0xEE, (uint8_t []){0x00}, 1, 0}, - {0xEF, (uint8_t []){0x00}, 1, 0}, - {0xF8, (uint8_t []){0x00}, 1, 0}, - {0xF9, (uint8_t []){0x00}, 1, 0}, - {0xFA, (uint8_t []){0x00}, 1, 0}, - {0xFB, (uint8_t []){0x00}, 1, 0}, - {0xFC, (uint8_t []){0x00}, 1, 0}, - {0xFD, (uint8_t []){0x00}, 1, 0}, - {0xFE, (uint8_t []){0x00}, 1, 0}, - {0xFF, (uint8_t []){0x00}, 1, 0}, - {0x60, (uint8_t []){0x40}, 1, 0}, - {0x61, (uint8_t []){0x04}, 1, 0}, - {0x62, (uint8_t []){0x00}, 1, 0}, - {0x63, (uint8_t []){0x42}, 1, 0}, - {0x64, (uint8_t []){0xD9}, 1, 0}, - {0x65, (uint8_t []){0x00}, 1, 0}, - {0x66, (uint8_t []){0x00}, 1, 0}, - {0x67, (uint8_t []){0x00}, 1, 0}, - {0x68, (uint8_t []){0x00}, 1, 0}, - {0x69, (uint8_t []){0x00}, 1, 0}, - {0x6A, (uint8_t []){0x00}, 1, 0}, - {0x6B, (uint8_t []){0x00}, 1, 0}, - {0x70, (uint8_t []){0x40}, 1, 0}, - {0x71, (uint8_t []){0x03}, 1, 0}, - {0x72, (uint8_t []){0x00}, 1, 0}, - {0x73, (uint8_t []){0x42}, 1, 0}, - {0x74, (uint8_t []){0xD8}, 1, 0}, - {0x75, (uint8_t []){0x00}, 1, 0}, - {0x76, (uint8_t []){0x00}, 1, 0}, - {0x77, (uint8_t []){0x00}, 1, 0}, - {0x78, (uint8_t []){0x00}, 1, 0}, - {0x79, (uint8_t []){0x00}, 1, 0}, - {0x7A, (uint8_t []){0x00}, 1, 0}, - {0x7B, (uint8_t []){0x00}, 1, 0}, - {0x80, (uint8_t []){0x48}, 1, 0}, - {0x81, (uint8_t []){0x00}, 1, 0}, - {0x82, (uint8_t []){0x06}, 1, 0}, - {0x83, (uint8_t []){0x02}, 1, 0}, - {0x84, (uint8_t []){0xD6}, 1, 0}, - {0x85, (uint8_t []){0x04}, 1, 0}, - {0x86, (uint8_t []){0x00}, 1, 0}, - {0x87, (uint8_t []){0x00}, 1, 0}, - {0x88, (uint8_t []){0x48}, 1, 0}, - {0x89, (uint8_t []){0x00}, 1, 0}, - {0x8A, (uint8_t []){0x08}, 1, 0}, - {0x8B, (uint8_t []){0x02}, 1, 0}, - {0x8C, (uint8_t []){0xD8}, 1, 0}, - {0x8D, (uint8_t []){0x04}, 1, 0}, - {0x8E, (uint8_t []){0x00}, 1, 0}, - {0x8F, (uint8_t []){0x00}, 1, 0}, - {0x90, (uint8_t []){0x48}, 1, 0}, - {0x91, (uint8_t []){0x00}, 1, 0}, - {0x92, (uint8_t []){0x0A}, 1, 0}, - {0x93, (uint8_t []){0x02}, 1, 0}, - {0x94, (uint8_t []){0xDA}, 1, 0}, - {0x95, (uint8_t []){0x04}, 1, 0}, - {0x96, (uint8_t []){0x00}, 1, 0}, - {0x97, (uint8_t []){0x00}, 1, 0}, - {0x98, (uint8_t []){0x48}, 1, 0}, - {0x99, (uint8_t []){0x00}, 1, 0}, - {0x9A, (uint8_t []){0x0C}, 1, 0}, - {0x9B, (uint8_t []){0x02}, 1, 0}, - {0x9C, (uint8_t []){0xDC}, 1, 0}, - {0x9D, (uint8_t []){0x04}, 1, 0}, - {0x9E, (uint8_t []){0x00}, 1, 0}, - {0x9F, (uint8_t []){0x00}, 1, 0}, - {0xA0, (uint8_t []){0x48}, 1, 0}, - {0xA1, (uint8_t []){0x00}, 1, 0}, - {0xA2, (uint8_t []){0x05}, 1, 0}, - {0xA3, (uint8_t []){0x02}, 1, 0}, - {0xA4, (uint8_t []){0xD5}, 1, 0}, - {0xA5, (uint8_t []){0x04}, 1, 0}, - {0xA6, (uint8_t []){0x00}, 1, 0}, - {0xA7, (uint8_t []){0x00}, 1, 0}, - {0xA8, (uint8_t []){0x48}, 1, 0}, - {0xA9, (uint8_t []){0x00}, 1, 0}, - {0xAA, (uint8_t []){0x07}, 1, 0}, - {0xAB, (uint8_t []){0x02}, 1, 0}, - {0xAC, (uint8_t []){0xD7}, 1, 0}, - {0xAD, (uint8_t []){0x04}, 1, 0}, - {0xAE, (uint8_t []){0x00}, 1, 0}, - {0xAF, (uint8_t []){0x00}, 1, 0}, - {0xB0, (uint8_t []){0x48}, 1, 0}, - {0xB1, (uint8_t []){0x00}, 1, 0}, - {0xB2, (uint8_t []){0x09}, 1, 0}, - {0xB3, (uint8_t []){0x02}, 1, 0}, - {0xB4, (uint8_t []){0xD9}, 1, 0}, - {0xB5, (uint8_t []){0x04}, 1, 0}, - {0xB6, (uint8_t []){0x00}, 1, 0}, - {0xB7, (uint8_t []){0x00}, 1, 0}, - - {0xB8, (uint8_t []){0x48}, 1, 0}, - {0xB9, (uint8_t []){0x00}, 1, 0}, - {0xBA, (uint8_t []){0x0B}, 1, 0}, - {0xBB, (uint8_t []){0x02}, 1, 0}, - {0xBC, (uint8_t []){0xDB}, 1, 0}, - {0xBD, (uint8_t []){0x04}, 1, 0}, - {0xBE, (uint8_t []){0x00}, 1, 0}, - {0xBF, (uint8_t []){0x00}, 1, 0}, - {0xC0, (uint8_t []){0x10}, 1, 0}, - {0xC1, (uint8_t []){0x47}, 1, 0}, - {0xC2, (uint8_t []){0x56}, 1, 0}, - {0xC3, (uint8_t []){0x65}, 1, 0}, - {0xC4, (uint8_t []){0x74}, 1, 0}, - {0xC5, (uint8_t []){0x88}, 1, 0}, - {0xC6, (uint8_t []){0x99}, 1, 0}, - {0xC7, (uint8_t []){0x01}, 1, 0}, - {0xC8, (uint8_t []){0xBB}, 1, 0}, - {0xC9, (uint8_t []){0xAA}, 1, 0}, - {0xD0, (uint8_t []){0x10}, 1, 0}, - {0xD1, (uint8_t []){0x47}, 1, 0}, - {0xD2, (uint8_t []){0x56}, 1, 0}, - {0xD3, (uint8_t []){0x65}, 1, 0}, - {0xD4, (uint8_t []){0x74}, 1, 0}, - {0xD5, (uint8_t []){0x88}, 1, 0}, - {0xD6, (uint8_t []){0x99}, 1, 0}, - {0xD7, (uint8_t []){0x01}, 1, 0}, - {0xD8, (uint8_t []){0xBB}, 1, 0}, - {0xD9, (uint8_t []){0xAA}, 1, 0}, - {0xF3, (uint8_t []){0x01}, 1, 0}, - {0xF0, (uint8_t []){0x00}, 1, 0}, - {0x21, (uint8_t []){0x00}, 1, 0}, - {0x11, (uint8_t []){0x00}, 1, 120}, - {0x29, (uint8_t []){0x00}, 1, 0}, -}; - -class CustomBoard : public WifiBoard { -private: - Button boot_button_; - i2c_master_bus_handle_t i2c_bus_; - esp_io_expander_handle_t io_expander = NULL; - LcdDisplay* display_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = I2C_SDA_IO, - .scl_io_num = I2C_SCL_IO, - .clk_source = I2C_CLK_SRC_DEFAULT, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeTca9554(void) - { - esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); - if(ret != ESP_OK) - ESP_LOGE(TAG, "TCA9554 create returned error"); - - // uint32_t input_level_mask = 0; - // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_INPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输入 - // ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, &input_level_mask); // 获取引脚 EXIO0 和 EXIO1 的电平状态,存放在 input_level_mask 中 - - // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO2 和 EXIO3 模式为输出 - // ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, 1); // 将引脚电平设置为 1 - // ret = esp_io_expander_print_state(io_expander); // 打印引脚状态 - - ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输出 - ESP_ERROR_CHECK(ret); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(300)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(300)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - } - - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize QSPI bus"); - - const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK, - QSPI_PIN_NUM_LCD_DATA0, - QSPI_PIN_NUM_LCD_DATA1, - QSPI_PIN_NUM_LCD_DATA2, - QSPI_PIN_NUM_LCD_DATA3, - QSPI_LCD_H_RES * 80 * sizeof(uint16_t)); - ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO)); - } - - void Initializest77916Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - ESP_LOGI(TAG, "Install panel IO"); - - esp_lcd_panel_io_spi_config_t io_config = { - .cs_gpio_num = QSPI_PIN_NUM_LCD_CS, - .dc_gpio_num = -1, - .spi_mode = 0, - .pclk_hz = 3 * 1000 * 1000, - .trans_queue_depth = 10, - .on_color_trans_done = NULL, - .user_ctx = NULL, - .lcd_cmd_bits = 32, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .octal_mode = 0, - .quad_mode = 1, - .sio_mode = 0, - .lsb_first = 0, - .cs_high_active = 0, - }, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io)); - - ESP_LOGI(TAG, "Install ST77916 panel driver"); - - st77916_vendor_config_t vendor_config = { - .flags = { - .use_qspi_interface = 1, - }, - }; - - printf("-------------------------------------- Version selection -------------------------------------- \r\n"); - esp_err_t ret; - int lcd_cmd = 0x04; - uint8_t register_data[4]; - size_t param_size = sizeof(register_data); - lcd_cmd &= 0xff; - lcd_cmd <<= 8; - lcd_cmd |= LCD_OPCODE_READ_CMD << 24; // Use the read opcode instead of write - ret = esp_lcd_panel_io_rx_param(panel_io, lcd_cmd, register_data, param_size); - if (ret == ESP_OK) { - printf("Register 0x04 data: %02x %02x %02x %02x\n", register_data[0], register_data[1], register_data[2], register_data[3]); - } else { - printf("Failed to read register 0x04, error code: %d\n", ret); - } - // panel_io_spi_del(io_handle); - io_config.pclk_hz = 80 * 1000 * 1000; - if (esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io) != ESP_OK) { - printf("Failed to set LCD communication parameters -- SPI\r\n"); - return ; - } - printf("LCD communication parameters are set successfully -- SPI\r\n"); - - // Check register values and configure accordingly - if (register_data[0] == 0x00 && register_data[1] == 0x7F && register_data[2] == 0x7F && register_data[3] == 0x7F) { - // Handle the case where the register data matches this pattern - printf("Vendor-specific initialization for case 1.\n"); - } - else if (register_data[0] == 0x00 && register_data[1] == 0x02 && register_data[2] == 0x7F && register_data[3] == 0x7F) { - // Provide vendor-specific initialization commands if register data matches this pattern - vendor_config.init_cmds = vendor_specific_init_new; - vendor_config.init_cmds_size = sizeof(vendor_specific_init_new) / sizeof(st77916_lcd_init_cmd_t); - printf("Vendor-specific initialization for case 2.\n"); - } - printf("------------------------------------- End of version selection------------------------------------- \r\n"); - - const esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = QSPI_PIN_NUM_LCD_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h` - .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18) - .vendor_config = &vendor_config, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_disp_on_off(panel, true); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - -public: - CustomBoard() : - boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeTca9554(); - InitializeSpi(); - Initializest77916Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_LEFT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); // I2S_STD_SLOT_LEFT / I2S_STD_SLOT_RIGHT / I2S_STD_SLOT_BOTH - - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(CustomBoard); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" + +#include +#include "i2c_device.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_io_expander_tca9554.h" + +#define TAG "waveshare_lcd_1_85c" + +#define LCD_OPCODE_WRITE_CMD (0x02ULL) +#define LCD_OPCODE_READ_CMD (0x0BULL) +#define LCD_OPCODE_WRITE_COLOR (0x32ULL) + +static const st77916_lcd_init_cmd_t vendor_specific_init_new[] = { + {0xF0, (uint8_t []){0x28}, 1, 0}, + {0xF2, (uint8_t []){0x28}, 1, 0}, + {0x73, (uint8_t []){0xF0}, 1, 0}, + {0x7C, (uint8_t []){0xD1}, 1, 0}, + {0x83, (uint8_t []){0xE0}, 1, 0}, + {0x84, (uint8_t []){0x61}, 1, 0}, + {0xF2, (uint8_t []){0x82}, 1, 0}, + {0xF0, (uint8_t []){0x00}, 1, 0}, + {0xF0, (uint8_t []){0x01}, 1, 0}, + {0xF1, (uint8_t []){0x01}, 1, 0}, + {0xB0, (uint8_t []){0x56}, 1, 0}, + {0xB1, (uint8_t []){0x4D}, 1, 0}, + {0xB2, (uint8_t []){0x24}, 1, 0}, + {0xB4, (uint8_t []){0x87}, 1, 0}, + {0xB5, (uint8_t []){0x44}, 1, 0}, + {0xB6, (uint8_t []){0x8B}, 1, 0}, + {0xB7, (uint8_t []){0x40}, 1, 0}, + {0xB8, (uint8_t []){0x86}, 1, 0}, + {0xBA, (uint8_t []){0x00}, 1, 0}, + {0xBB, (uint8_t []){0x08}, 1, 0}, + {0xBC, (uint8_t []){0x08}, 1, 0}, + {0xBD, (uint8_t []){0x00}, 1, 0}, + {0xC0, (uint8_t []){0x80}, 1, 0}, + {0xC1, (uint8_t []){0x10}, 1, 0}, + {0xC2, (uint8_t []){0x37}, 1, 0}, + {0xC3, (uint8_t []){0x80}, 1, 0}, + {0xC4, (uint8_t []){0x10}, 1, 0}, + {0xC5, (uint8_t []){0x37}, 1, 0}, + {0xC6, (uint8_t []){0xA9}, 1, 0}, + {0xC7, (uint8_t []){0x41}, 1, 0}, + {0xC8, (uint8_t []){0x01}, 1, 0}, + {0xC9, (uint8_t []){0xA9}, 1, 0}, + {0xCA, (uint8_t []){0x41}, 1, 0}, + {0xCB, (uint8_t []){0x01}, 1, 0}, + {0xD0, (uint8_t []){0x91}, 1, 0}, + {0xD1, (uint8_t []){0x68}, 1, 0}, + {0xD2, (uint8_t []){0x68}, 1, 0}, + {0xF5, (uint8_t []){0x00, 0xA5}, 2, 0}, + {0xDD, (uint8_t []){0x4F}, 1, 0}, + {0xDE, (uint8_t []){0x4F}, 1, 0}, + {0xF1, (uint8_t []){0x10}, 1, 0}, + {0xF0, (uint8_t []){0x00}, 1, 0}, + {0xF0, (uint8_t []){0x02}, 1, 0}, + {0xE0, (uint8_t []){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0}, + {0xE1, (uint8_t []){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0}, + {0xF0, (uint8_t []){0x10}, 1, 0}, + {0xF3, (uint8_t []){0x10}, 1, 0}, + {0xE0, (uint8_t []){0x07}, 1, 0}, + {0xE1, (uint8_t []){0x00}, 1, 0}, + {0xE2, (uint8_t []){0x00}, 1, 0}, + {0xE3, (uint8_t []){0x00}, 1, 0}, + {0xE4, (uint8_t []){0xE0}, 1, 0}, + {0xE5, (uint8_t []){0x06}, 1, 0}, + {0xE6, (uint8_t []){0x21}, 1, 0}, + {0xE7, (uint8_t []){0x01}, 1, 0}, + {0xE8, (uint8_t []){0x05}, 1, 0}, + {0xE9, (uint8_t []){0x02}, 1, 0}, + {0xEA, (uint8_t []){0xDA}, 1, 0}, + {0xEB, (uint8_t []){0x00}, 1, 0}, + {0xEC, (uint8_t []){0x00}, 1, 0}, + {0xED, (uint8_t []){0x0F}, 1, 0}, + {0xEE, (uint8_t []){0x00}, 1, 0}, + {0xEF, (uint8_t []){0x00}, 1, 0}, + {0xF8, (uint8_t []){0x00}, 1, 0}, + {0xF9, (uint8_t []){0x00}, 1, 0}, + {0xFA, (uint8_t []){0x00}, 1, 0}, + {0xFB, (uint8_t []){0x00}, 1, 0}, + {0xFC, (uint8_t []){0x00}, 1, 0}, + {0xFD, (uint8_t []){0x00}, 1, 0}, + {0xFE, (uint8_t []){0x00}, 1, 0}, + {0xFF, (uint8_t []){0x00}, 1, 0}, + {0x60, (uint8_t []){0x40}, 1, 0}, + {0x61, (uint8_t []){0x04}, 1, 0}, + {0x62, (uint8_t []){0x00}, 1, 0}, + {0x63, (uint8_t []){0x42}, 1, 0}, + {0x64, (uint8_t []){0xD9}, 1, 0}, + {0x65, (uint8_t []){0x00}, 1, 0}, + {0x66, (uint8_t []){0x00}, 1, 0}, + {0x67, (uint8_t []){0x00}, 1, 0}, + {0x68, (uint8_t []){0x00}, 1, 0}, + {0x69, (uint8_t []){0x00}, 1, 0}, + {0x6A, (uint8_t []){0x00}, 1, 0}, + {0x6B, (uint8_t []){0x00}, 1, 0}, + {0x70, (uint8_t []){0x40}, 1, 0}, + {0x71, (uint8_t []){0x03}, 1, 0}, + {0x72, (uint8_t []){0x00}, 1, 0}, + {0x73, (uint8_t []){0x42}, 1, 0}, + {0x74, (uint8_t []){0xD8}, 1, 0}, + {0x75, (uint8_t []){0x00}, 1, 0}, + {0x76, (uint8_t []){0x00}, 1, 0}, + {0x77, (uint8_t []){0x00}, 1, 0}, + {0x78, (uint8_t []){0x00}, 1, 0}, + {0x79, (uint8_t []){0x00}, 1, 0}, + {0x7A, (uint8_t []){0x00}, 1, 0}, + {0x7B, (uint8_t []){0x00}, 1, 0}, + {0x80, (uint8_t []){0x48}, 1, 0}, + {0x81, (uint8_t []){0x00}, 1, 0}, + {0x82, (uint8_t []){0x06}, 1, 0}, + {0x83, (uint8_t []){0x02}, 1, 0}, + {0x84, (uint8_t []){0xD6}, 1, 0}, + {0x85, (uint8_t []){0x04}, 1, 0}, + {0x86, (uint8_t []){0x00}, 1, 0}, + {0x87, (uint8_t []){0x00}, 1, 0}, + {0x88, (uint8_t []){0x48}, 1, 0}, + {0x89, (uint8_t []){0x00}, 1, 0}, + {0x8A, (uint8_t []){0x08}, 1, 0}, + {0x8B, (uint8_t []){0x02}, 1, 0}, + {0x8C, (uint8_t []){0xD8}, 1, 0}, + {0x8D, (uint8_t []){0x04}, 1, 0}, + {0x8E, (uint8_t []){0x00}, 1, 0}, + {0x8F, (uint8_t []){0x00}, 1, 0}, + {0x90, (uint8_t []){0x48}, 1, 0}, + {0x91, (uint8_t []){0x00}, 1, 0}, + {0x92, (uint8_t []){0x0A}, 1, 0}, + {0x93, (uint8_t []){0x02}, 1, 0}, + {0x94, (uint8_t []){0xDA}, 1, 0}, + {0x95, (uint8_t []){0x04}, 1, 0}, + {0x96, (uint8_t []){0x00}, 1, 0}, + {0x97, (uint8_t []){0x00}, 1, 0}, + {0x98, (uint8_t []){0x48}, 1, 0}, + {0x99, (uint8_t []){0x00}, 1, 0}, + {0x9A, (uint8_t []){0x0C}, 1, 0}, + {0x9B, (uint8_t []){0x02}, 1, 0}, + {0x9C, (uint8_t []){0xDC}, 1, 0}, + {0x9D, (uint8_t []){0x04}, 1, 0}, + {0x9E, (uint8_t []){0x00}, 1, 0}, + {0x9F, (uint8_t []){0x00}, 1, 0}, + {0xA0, (uint8_t []){0x48}, 1, 0}, + {0xA1, (uint8_t []){0x00}, 1, 0}, + {0xA2, (uint8_t []){0x05}, 1, 0}, + {0xA3, (uint8_t []){0x02}, 1, 0}, + {0xA4, (uint8_t []){0xD5}, 1, 0}, + {0xA5, (uint8_t []){0x04}, 1, 0}, + {0xA6, (uint8_t []){0x00}, 1, 0}, + {0xA7, (uint8_t []){0x00}, 1, 0}, + {0xA8, (uint8_t []){0x48}, 1, 0}, + {0xA9, (uint8_t []){0x00}, 1, 0}, + {0xAA, (uint8_t []){0x07}, 1, 0}, + {0xAB, (uint8_t []){0x02}, 1, 0}, + {0xAC, (uint8_t []){0xD7}, 1, 0}, + {0xAD, (uint8_t []){0x04}, 1, 0}, + {0xAE, (uint8_t []){0x00}, 1, 0}, + {0xAF, (uint8_t []){0x00}, 1, 0}, + {0xB0, (uint8_t []){0x48}, 1, 0}, + {0xB1, (uint8_t []){0x00}, 1, 0}, + {0xB2, (uint8_t []){0x09}, 1, 0}, + {0xB3, (uint8_t []){0x02}, 1, 0}, + {0xB4, (uint8_t []){0xD9}, 1, 0}, + {0xB5, (uint8_t []){0x04}, 1, 0}, + {0xB6, (uint8_t []){0x00}, 1, 0}, + {0xB7, (uint8_t []){0x00}, 1, 0}, + + {0xB8, (uint8_t []){0x48}, 1, 0}, + {0xB9, (uint8_t []){0x00}, 1, 0}, + {0xBA, (uint8_t []){0x0B}, 1, 0}, + {0xBB, (uint8_t []){0x02}, 1, 0}, + {0xBC, (uint8_t []){0xDB}, 1, 0}, + {0xBD, (uint8_t []){0x04}, 1, 0}, + {0xBE, (uint8_t []){0x00}, 1, 0}, + {0xBF, (uint8_t []){0x00}, 1, 0}, + {0xC0, (uint8_t []){0x10}, 1, 0}, + {0xC1, (uint8_t []){0x47}, 1, 0}, + {0xC2, (uint8_t []){0x56}, 1, 0}, + {0xC3, (uint8_t []){0x65}, 1, 0}, + {0xC4, (uint8_t []){0x74}, 1, 0}, + {0xC5, (uint8_t []){0x88}, 1, 0}, + {0xC6, (uint8_t []){0x99}, 1, 0}, + {0xC7, (uint8_t []){0x01}, 1, 0}, + {0xC8, (uint8_t []){0xBB}, 1, 0}, + {0xC9, (uint8_t []){0xAA}, 1, 0}, + {0xD0, (uint8_t []){0x10}, 1, 0}, + {0xD1, (uint8_t []){0x47}, 1, 0}, + {0xD2, (uint8_t []){0x56}, 1, 0}, + {0xD3, (uint8_t []){0x65}, 1, 0}, + {0xD4, (uint8_t []){0x74}, 1, 0}, + {0xD5, (uint8_t []){0x88}, 1, 0}, + {0xD6, (uint8_t []){0x99}, 1, 0}, + {0xD7, (uint8_t []){0x01}, 1, 0}, + {0xD8, (uint8_t []){0xBB}, 1, 0}, + {0xD9, (uint8_t []){0xAA}, 1, 0}, + {0xF3, (uint8_t []){0x01}, 1, 0}, + {0xF0, (uint8_t []){0x00}, 1, 0}, + {0x21, (uint8_t []){0x00}, 1, 0}, + {0x11, (uint8_t []){0x00}, 1, 120}, + {0x29, (uint8_t []){0x00}, 1, 0}, +}; + +class CustomBoard : public WifiBoard { +private: + Button boot_button_; + i2c_master_bus_handle_t i2c_bus_; + esp_io_expander_handle_t io_expander = NULL; + LcdDisplay* display_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = I2C_SDA_IO, + .scl_io_num = I2C_SCL_IO, + .clk_source = I2C_CLK_SRC_DEFAULT, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeTca9554(void) + { + esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); + if(ret != ESP_OK) + ESP_LOGE(TAG, "TCA9554 create returned error"); + + // uint32_t input_level_mask = 0; + // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_INPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输入 + // ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, &input_level_mask); // 获取引脚 EXIO0 和 EXIO1 的电平状态,存放在 input_level_mask 中 + + // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO2 和 EXIO3 模式为输出 + // ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, 1); // 将引脚电平设置为 1 + // ret = esp_io_expander_print_state(io_expander); // 打印引脚状态 + + ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输出 + ESP_ERROR_CHECK(ret); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(300)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(300)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize QSPI bus"); + + const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK, + QSPI_PIN_NUM_LCD_DATA0, + QSPI_PIN_NUM_LCD_DATA1, + QSPI_PIN_NUM_LCD_DATA2, + QSPI_PIN_NUM_LCD_DATA3, + QSPI_LCD_H_RES * 80 * sizeof(uint16_t)); + ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO)); + } + + void Initializest77916Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + ESP_LOGI(TAG, "Install panel IO"); + + esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = QSPI_PIN_NUM_LCD_CS, + .dc_gpio_num = -1, + .spi_mode = 0, + .pclk_hz = 3 * 1000 * 1000, + .trans_queue_depth = 10, + .on_color_trans_done = NULL, + .user_ctx = NULL, + .lcd_cmd_bits = 32, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .octal_mode = 0, + .quad_mode = 1, + .sio_mode = 0, + .lsb_first = 0, + .cs_high_active = 0, + }, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io)); + + ESP_LOGI(TAG, "Install ST77916 panel driver"); + + st77916_vendor_config_t vendor_config = { + .flags = { + .use_qspi_interface = 1, + }, + }; + + printf("-------------------------------------- Version selection -------------------------------------- \r\n"); + esp_err_t ret; + int lcd_cmd = 0x04; + uint8_t register_data[4]; + size_t param_size = sizeof(register_data); + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= LCD_OPCODE_READ_CMD << 24; // Use the read opcode instead of write + ret = esp_lcd_panel_io_rx_param(panel_io, lcd_cmd, register_data, param_size); + if (ret == ESP_OK) { + printf("Register 0x04 data: %02x %02x %02x %02x\n", register_data[0], register_data[1], register_data[2], register_data[3]); + } else { + printf("Failed to read register 0x04, error code: %d\n", ret); + } + // panel_io_spi_del(io_handle); + io_config.pclk_hz = 80 * 1000 * 1000; + if (esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io) != ESP_OK) { + printf("Failed to set LCD communication parameters -- SPI\r\n"); + return ; + } + printf("LCD communication parameters are set successfully -- SPI\r\n"); + + // Check register values and configure accordingly + if (register_data[0] == 0x00 && register_data[1] == 0x7F && register_data[2] == 0x7F && register_data[3] == 0x7F) { + // Handle the case where the register data matches this pattern + printf("Vendor-specific initialization for case 1.\n"); + } + else if (register_data[0] == 0x00 && register_data[1] == 0x02 && register_data[2] == 0x7F && register_data[3] == 0x7F) { + // Provide vendor-specific initialization commands if register data matches this pattern + vendor_config.init_cmds = vendor_specific_init_new; + vendor_config.init_cmds_size = sizeof(vendor_specific_init_new) / sizeof(st77916_lcd_init_cmd_t); + printf("Vendor-specific initialization for case 2.\n"); + } + printf("------------------------------------- End of version selection------------------------------------- \r\n"); + + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = QSPI_PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h` + .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18) + .vendor_config = &vendor_config, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_disp_on_off(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + +public: + CustomBoard() : + boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeTca9554(); + InitializeSpi(); + Initializest77916Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_LEFT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); // I2S_STD_SLOT_LEFT / I2S_STD_SLOT_RIGHT / I2S_STD_SLOT_BOTH + + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(CustomBoard); diff --git a/main/boards/esp32-s3-touch-lcd-3.5/README.md b/main/boards/esp32-s3-touch-lcd-3.5/README.md index 78b6949..b21cd5e 100644 --- a/main/boards/esp32-s3-touch-lcd-3.5/README.md +++ b/main/boards/esp32-s3-touch-lcd-3.5/README.md @@ -1,3 +1,3 @@ -新增 微雪 开发板: ESP32-S3-Touch-LCD-3.5 -产品链接: +新增 微雪 开发板: ESP32-S3-Touch-LCD-3.5 +产品链接: https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-3.5.htm \ No newline at end of file diff --git a/main/boards/esp32-s3-touch-lcd-3.5/config.h b/main/boards/esp32-s3-touch-lcd-3.5/config.h index 7852904..153eea6 100644 --- a/main/boards/esp32-s3-touch-lcd-3.5/config.h +++ b/main/boards/esp32-s3-touch-lcd-3.5/config.h @@ -1,69 +1,69 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_12 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_15 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SPI_MODE 0 -#define DISPLAY_CS_PIN GPIO_NUM_NC -#define DISPLAY_MOSI_PIN GPIO_NUM_1 -#define DISPLAY_MISO_PIN GPIO_NUM_2 -#define DISPLAY_CLK_PIN GPIO_NUM_5 -#define DISPLAY_DC_PIN GPIO_NUM_3 -#define DISPLAY_RST_PIN GPIO_NUM_NC - - - -#define DISPLAY_WIDTH 480 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#define DISPLAY_INVERT_COLOR true - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_6 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - - -#define CAM_PIN_PWDN GPIO_NUM_NC -#define CAM_PIN_RESET GPIO_NUM_NC -#define CAM_PIN_VSYNC GPIO_NUM_17 -#define CAM_PIN_HREF GPIO_NUM_18 -#define CAM_PIN_PCLK GPIO_NUM_41 -#define CAM_PIN_XCLK GPIO_NUM_38 -#define CAM_PIN_SIOD GPIO_NUM_NC -#define CAM_PIN_SIOC GPIO_NUM_NC -#define CAM_PIN_D0 GPIO_NUM_45 -#define CAM_PIN_D1 GPIO_NUM_47 -#define CAM_PIN_D2 GPIO_NUM_48 -#define CAM_PIN_D3 GPIO_NUM_46 -#define CAM_PIN_D4 GPIO_NUM_42 -#define CAM_PIN_D5 GPIO_NUM_40 -#define CAM_PIN_D6 GPIO_NUM_39 -#define CAM_PIN_D7 GPIO_NUM_21 - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_12 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_15 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SPI_MODE 0 +#define DISPLAY_CS_PIN GPIO_NUM_NC +#define DISPLAY_MOSI_PIN GPIO_NUM_1 +#define DISPLAY_MISO_PIN GPIO_NUM_2 +#define DISPLAY_CLK_PIN GPIO_NUM_5 +#define DISPLAY_DC_PIN GPIO_NUM_3 +#define DISPLAY_RST_PIN GPIO_NUM_NC + + + +#define DISPLAY_WIDTH 480 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#define DISPLAY_INVERT_COLOR true + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_6 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + + +#define CAM_PIN_PWDN GPIO_NUM_NC +#define CAM_PIN_RESET GPIO_NUM_NC +#define CAM_PIN_VSYNC GPIO_NUM_17 +#define CAM_PIN_HREF GPIO_NUM_18 +#define CAM_PIN_PCLK GPIO_NUM_41 +#define CAM_PIN_XCLK GPIO_NUM_38 +#define CAM_PIN_SIOD GPIO_NUM_NC +#define CAM_PIN_SIOC GPIO_NUM_NC +#define CAM_PIN_D0 GPIO_NUM_45 +#define CAM_PIN_D1 GPIO_NUM_47 +#define CAM_PIN_D2 GPIO_NUM_48 +#define CAM_PIN_D3 GPIO_NUM_46 +#define CAM_PIN_D4 GPIO_NUM_42 +#define CAM_PIN_D5 GPIO_NUM_40 +#define CAM_PIN_D6 GPIO_NUM_39 +#define CAM_PIN_D7 GPIO_NUM_21 + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp32-s3-touch-lcd-3.5/config.json b/main/boards/esp32-s3-touch-lcd-3.5/config.json index 45e34fa..2bb4b27 100644 --- a/main/boards/esp32-s3-touch-lcd-3.5/config.json +++ b/main/boards/esp32-s3-touch-lcd-3.5/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp32-s3-touch-lcd-3.5", - "sdkconfig_append": [ - "CONFIG_USE_WECHAT_MESSAGE_STYLE=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp32-s3-touch-lcd-3.5", + "sdkconfig_append": [ + "CONFIG_USE_WECHAT_MESSAGE_STYLE=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc b/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc index c898e46..03ac4a0 100644 --- a/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc +++ b/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc @@ -1,381 +1,381 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "mcp_server.h" - -#include -#include "i2c_device.h" -#include -#include -#include -#include -#include -#include - -#include -#include "esp_io_expander_tca9554.h" - -#include "axp2101.h" -#include "power_save_timer.h" - -#include -#include - -#include "esp32_camera.h" - -#define TAG "waveshare_lcd_3_5" - -class Pmic : public Axp2101 { - public: - Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { - WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable - WriteReg(0x27, 0x10); // hold 4s to power off - - // Disable All DCs but DC1 - WriteReg(0x80, 0x01); - // Disable All LDOs - WriteReg(0x90, 0x00); - WriteReg(0x91, 0x00); - - // Set DC1 to 3.3V - WriteReg(0x82, (3300 - 1500) / 100); - - // Set ALDO1 to 3.3V - WriteReg(0x92, (3300 - 500) / 100); - - WriteReg(0x96, (1500 - 500) / 100); - WriteReg(0x97, (2800 - 500) / 100); - - // Enable ALDO1 BLDO1 BLDO2 - WriteReg(0x90, 0x31); - - WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V - - WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA - WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) - WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA - } - }; - -typedef struct { - int cmd; /*OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(20); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - pmic_->PowerOff(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeTca9554(void) - { - esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander); - if(ret != ESP_OK) - ESP_LOGE(TAG, "TCA9554 create returned error"); - ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(100)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(100)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_1, 1); - ESP_ERROR_CHECK(ret); - } - - void InitializeAxp2101() { - ESP_LOGI(TAG, "Init AXP2101"); - pmic_ = new Pmic(i2c_bus_, 0x34); - } - - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize QSPI bus"); - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = DISPLAY_MISO_PIN; - buscfg.sclk_io_num = DISPLAY_CLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - void InitializeCamera() { - camera_config_t config = {}; - - config.pin_pwdn = CAM_PIN_PWDN; - config.pin_reset = CAM_PIN_RESET; - config.pin_xclk = CAM_PIN_XCLK; - config.pin_sccb_sda = CAM_PIN_SIOD; - config.pin_sccb_scl = CAM_PIN_SIOC; - config.sccb_i2c_port = I2C_NUM_0; - - config.pin_d7 = CAM_PIN_D7; - config.pin_d6 = CAM_PIN_D6; - config.pin_d5 = CAM_PIN_D5; - config.pin_d4 = CAM_PIN_D4; - config.pin_d3 = CAM_PIN_D3; - config.pin_d2 = CAM_PIN_D2; - config.pin_d1 = CAM_PIN_D1; - config.pin_d0 = CAM_PIN_D0; - config.pin_vsync = CAM_PIN_VSYNC; - config.pin_href = CAM_PIN_HREF; - config.pin_pclk = CAM_PIN_PCLK; - - /* XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) */ - config.xclk_freq_hz = 10000000; - config.ledc_timer = LEDC_TIMER_1; - config.ledc_channel = LEDC_CHANNEL_0; - - config.pixel_format = PIXFORMAT_RGB565; /* YUV422,GRAYSCALE,RGB565,JPEG */ - config.frame_size = FRAMESIZE_240X240; /* QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates */ - - config.jpeg_quality = 12; /* 0-63, for OV series camera sensors, lower number means higher quality */ - config.fb_count = 2; /* When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode */ - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - esp_err_t err = esp_camera_init(&config); // 测试相机是否存在 - if (err != ESP_OK) { - ESP_LOGE(TAG, "Camera is not plugged in or not supported, error: %s", esp_err_to_name(err)); - // 如果摄像头初始化失败,设置 camera_ 为 nullptr - camera_ = nullptr; - return; - }else - { - esp_camera_deinit();// 释放之前的摄像头资源,为正确初始化做准备 - camera_ = new Esp32Camera(config); - } - - } - - void InitializeTouch() - { - esp_lcd_touch_handle_t tp; - esp_lcd_touch_config_t tp_cfg = { - .x_max = DISPLAY_WIDTH, - .y_max = DISPLAY_HEIGHT, - .rst_gpio_num = GPIO_NUM_NC, - .int_gpio_num = GPIO_NUM_NC, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = 1, - .mirror_x = 1, - .mirror_y = 1, - }, - }; - esp_lcd_panel_io_handle_t tp_io_handle = NULL; - esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG(); - tp_io_config.scl_speed_hz = 400 * 1000; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); - ESP_LOGI(TAG, "Initialize touch controller"); - ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp)); - const lvgl_port_touch_cfg_t touch_cfg = { - .disp = lv_display_get_default(), - .handle = tp, - }; - lvgl_port_add_touch(&touch_cfg); - ESP_LOGI(TAG, "Touch panel initialized successfully"); - } - - void InitializeLcdDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = DISPLAY_SPI_MODE; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - st7796_vendor_config_t st7796_vendor_config = { - .init_cmds = st7796_lcd_init_cmds, - .init_cmds_size = sizeof(st7796_lcd_init_cmds) / sizeof(st7796_lcd_init_cmd_t), - }; - - // 初始化液晶屏驱动芯片 - ESP_LOGI(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = &st7796_vendor_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - // 初始化工具 - void InitializeTools() { - auto &mcp_server = McpServer::GetInstance(); - mcp_server.AddTool("self.system.reconfigure_wifi", - "Reboot the device and enter WiFi configuration mode.\n" - "**CAUTION** You must ask the user to confirm this action.", - PropertyList(), [this](const PropertyList& properties) { - ResetWifiConfiguration(); - return true; - }); - } - -public: - CustomBoard() : - boot_button_(BOOT_BUTTON_GPIO) { - InitializePowerSaveTimer(); - InitializeI2c(); - InitializeTca9554(); - InitializeAxp2101(); - InitializeSpi(); - InitializeLcdDisplay(); - // 解决部分开机黑屏的问题 - if (esp_reset_reason() == ESP_RST_POWERON) { - fflush(stdout); - esp_restart(); - } - InitializeTouch(); - InitializeButtons(); - InitializeCamera(); - InitializeTools(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = pmic_->IsCharging(); - discharging = pmic_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - - level = pmic_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } - - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(CustomBoard); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "mcp_server.h" + +#include +#include "i2c_device.h" +#include +#include +#include +#include +#include +#include + +#include +#include "esp_io_expander_tca9554.h" + +#include "axp2101.h" +#include "power_save_timer.h" + +#include +#include + +#include "esp32_camera.h" + +#define TAG "waveshare_lcd_3_5" + +class Pmic : public Axp2101 { + public: + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable + WriteReg(0x27, 0x10); // hold 4s to power off + + // Disable All DCs but DC1 + WriteReg(0x80, 0x01); + // Disable All LDOs + WriteReg(0x90, 0x00); + WriteReg(0x91, 0x00); + + // Set DC1 to 3.3V + WriteReg(0x82, (3300 - 1500) / 100); + + // Set ALDO1 to 3.3V + WriteReg(0x92, (3300 - 500) / 100); + + WriteReg(0x96, (1500 - 500) / 100); + WriteReg(0x97, (2800 - 500) / 100); + + // Enable ALDO1 BLDO1 BLDO2 + WriteReg(0x90, 0x31); + + WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V + + WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA + WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) + WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA + } + }; + +typedef struct { + int cmd; /*OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(20); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + pmic_->PowerOff(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeTca9554(void) + { + esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander); + if(ret != ESP_OK) + ESP_LOGE(TAG, "TCA9554 create returned error"); + ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(100)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(100)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_1, 1); + ESP_ERROR_CHECK(ret); + } + + void InitializeAxp2101() { + ESP_LOGI(TAG, "Init AXP2101"); + pmic_ = new Pmic(i2c_bus_, 0x34); + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize QSPI bus"); + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = DISPLAY_MISO_PIN; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + void InitializeCamera() { + camera_config_t config = {}; + + config.pin_pwdn = CAM_PIN_PWDN; + config.pin_reset = CAM_PIN_RESET; + config.pin_xclk = CAM_PIN_XCLK; + config.pin_sccb_sda = CAM_PIN_SIOD; + config.pin_sccb_scl = CAM_PIN_SIOC; + config.sccb_i2c_port = I2C_NUM_0; + + config.pin_d7 = CAM_PIN_D7; + config.pin_d6 = CAM_PIN_D6; + config.pin_d5 = CAM_PIN_D5; + config.pin_d4 = CAM_PIN_D4; + config.pin_d3 = CAM_PIN_D3; + config.pin_d2 = CAM_PIN_D2; + config.pin_d1 = CAM_PIN_D1; + config.pin_d0 = CAM_PIN_D0; + config.pin_vsync = CAM_PIN_VSYNC; + config.pin_href = CAM_PIN_HREF; + config.pin_pclk = CAM_PIN_PCLK; + + /* XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) */ + config.xclk_freq_hz = 10000000; + config.ledc_timer = LEDC_TIMER_1; + config.ledc_channel = LEDC_CHANNEL_0; + + config.pixel_format = PIXFORMAT_RGB565; /* YUV422,GRAYSCALE,RGB565,JPEG */ + config.frame_size = FRAMESIZE_240X240; /* QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates */ + + config.jpeg_quality = 12; /* 0-63, for OV series camera sensors, lower number means higher quality */ + config.fb_count = 2; /* When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode */ + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + esp_err_t err = esp_camera_init(&config); // 测试相机是否存在 + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera is not plugged in or not supported, error: %s", esp_err_to_name(err)); + // 如果摄像头初始化失败,设置 camera_ 为 nullptr + camera_ = nullptr; + return; + }else + { + esp_camera_deinit();// 释放之前的摄像头资源,为正确初始化做准备 + camera_ = new Esp32Camera(config); + } + + } + + void InitializeTouch() + { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH, + .y_max = DISPLAY_HEIGHT, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 1, + .mirror_x = 1, + .mirror_y = 1, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG(); + tp_io_config.scl_speed_hz = 400 * 1000; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); + ESP_LOGI(TAG, "Initialize touch controller"); + ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp)); + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + lvgl_port_add_touch(&touch_cfg); + ESP_LOGI(TAG, "Touch panel initialized successfully"); + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = DISPLAY_SPI_MODE; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + st7796_vendor_config_t st7796_vendor_config = { + .init_cmds = st7796_lcd_init_cmds, + .init_cmds_size = sizeof(st7796_lcd_init_cmds) / sizeof(st7796_lcd_init_cmd_t), + }; + + // 初始化液晶屏驱动芯片 + ESP_LOGI(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = &st7796_vendor_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + // 初始化工具 + void InitializeTools() { + auto &mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.system.reconfigure_wifi", + "Reboot the device and enter WiFi configuration mode.\n" + "**CAUTION** You must ask the user to confirm this action.", + PropertyList(), [this](const PropertyList& properties) { + ResetWifiConfiguration(); + return true; + }); + } + +public: + CustomBoard() : + boot_button_(BOOT_BUTTON_GPIO) { + InitializePowerSaveTimer(); + InitializeI2c(); + InitializeTca9554(); + InitializeAxp2101(); + InitializeSpi(); + InitializeLcdDisplay(); + // 解决部分开机黑屏的问题 + if (esp_reset_reason() == ESP_RST_POWERON) { + fflush(stdout); + esp_restart(); + } + InitializeTouch(); + InitializeButtons(); + InitializeCamera(); + InitializeTools(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = pmic_->IsCharging(); + discharging = pmic_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + + level = pmic_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(CustomBoard); diff --git a/main/boards/esp32s3-korvo2-v3/config.h b/main/boards/esp32s3-korvo2-v3/config.h index 3292477..2595369 100644 --- a/main/boards/esp32s3-korvo2-v3/config.h +++ b/main/boards/esp32s3-korvo2-v3/config.h @@ -1,81 +1,81 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_48 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_17 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_5 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ST7789 -#define DISPLAY_SDA_PIN GPIO_NUM_NC -#define DISPLAY_SCL_PIN GPIO_NUM_NC -#define DISPLAY_WIDTH 280 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_SWAP_XY true -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define BACKLIGHT_INVERT false - -#define DISPLAY_OFFSET_X 20 -#define DISPLAY_OFFSET_Y 0 -#endif - -#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ILI9341 -#define LCD_TYPE_ILI9341_SERIAL -#define DISPLAY_SDA_PIN GPIO_NUM_NC -#define DISPLAY_SCL_PIN GPIO_NUM_NC -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 - -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define BACKLIGHT_INVERT false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#endif - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -/* Camera pins */ -#define CAMERA_PIN_PWDN -1 -#define CAMERA_PIN_RESET -1 -#define CAMERA_PIN_XCLK 40 -#define CAMERA_PIN_SIOD 17 -#define CAMERA_PIN_SIOC 18 - -#define CAMERA_PIN_D7 39 -#define CAMERA_PIN_D6 41 -#define CAMERA_PIN_D5 42 -#define CAMERA_PIN_D4 12 -#define CAMERA_PIN_D3 3 -#define CAMERA_PIN_D2 14 -#define CAMERA_PIN_D1 47 -#define CAMERA_PIN_D0 13 -#define CAMERA_PIN_VSYNC 21 -#define CAMERA_PIN_HREF 38 -#define CAMERA_PIN_PCLK 11 - -#define XCLK_FREQ_HZ 20000000 + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_48 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_17 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_5 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ST7789 +#define DISPLAY_SDA_PIN GPIO_NUM_NC +#define DISPLAY_SCL_PIN GPIO_NUM_NC +#define DISPLAY_WIDTH 280 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_SWAP_XY true +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define BACKLIGHT_INVERT false + +#define DISPLAY_OFFSET_X 20 +#define DISPLAY_OFFSET_Y 0 +#endif + +#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ILI9341 +#define LCD_TYPE_ILI9341_SERIAL +#define DISPLAY_SDA_PIN GPIO_NUM_NC +#define DISPLAY_SCL_PIN GPIO_NUM_NC +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 + +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define BACKLIGHT_INVERT false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#endif + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +/* Camera pins */ +#define CAMERA_PIN_PWDN -1 +#define CAMERA_PIN_RESET -1 +#define CAMERA_PIN_XCLK 40 +#define CAMERA_PIN_SIOD 17 +#define CAMERA_PIN_SIOC 18 + +#define CAMERA_PIN_D7 39 +#define CAMERA_PIN_D6 41 +#define CAMERA_PIN_D5 42 +#define CAMERA_PIN_D4 12 +#define CAMERA_PIN_D3 3 +#define CAMERA_PIN_D2 14 +#define CAMERA_PIN_D1 47 +#define CAMERA_PIN_D0 13 +#define CAMERA_PIN_VSYNC 21 +#define CAMERA_PIN_HREF 38 +#define CAMERA_PIN_PCLK 11 + +#define XCLK_FREQ_HZ 20000000 #endif // _BOARD_CONFIG_H_ \ No newline at end of file diff --git a/main/boards/esp32s3-korvo2-v3/config.json b/main/boards/esp32s3-korvo2-v3/config.json index 36110a1..a2c2eef 100644 --- a/main/boards/esp32s3-korvo2-v3/config.json +++ b/main/boards/esp32s3-korvo2-v3/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp32s3-korvo2-v3", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp32s3-korvo2-v3", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc index b24dbb3..b1b0275 100644 --- a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc +++ b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc @@ -1,299 +1,299 @@ -#include "wifi_board.h" -#include "codecs/box_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" - -#include -#include -#include -#include -#include -#include -#include -#include "esp32_camera.h" - -#define TAG "esp32s3_korvo2_v3" - -// Init ili9341 by custom cmd -static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { - {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0}, - {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0}, - {0xC5, (uint8_t []){0xD0}, 1, 0}, - {0xC1, (uint8_t []){0x02}, 1, 0}, - {0xB4, (uint8_t []){0x02}, 1, 0}, - {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0}, - {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0}, - - {0xB1, (uint8_t []){00, 0x1B}, 2, 0}, - {0x36, (uint8_t []){0x08}, 1, 0}, - {0x3A, (uint8_t []){0x55}, 1, 0}, - {0xB7, (uint8_t []){0x06}, 1, 0}, - - {0x11, (uint8_t []){0}, 0x80, 0}, - {0x29, (uint8_t []){0}, 0x80, 0}, - - {0, (uint8_t []){0}, 0xff, 0}, -}; - -class Esp32S3Korvo2V3Board : public WifiBoard { -private: - Button boot_button_; - i2c_master_bus_handle_t i2c_bus_; - LcdDisplay* display_; - esp_io_expander_handle_t io_expander_ = NULL; - Esp32Camera* camera_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void I2cDetect() { - uint8_t address; - printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); - for (int i = 0; i < 128; i += 16) { - printf("%02x: ", i); - for (int j = 0; j < 16; j++) { - fflush(stdout); - address = i + j; - esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); - if (ret == ESP_OK) { - printf("%02x ", address); - } else if (ret == ESP_ERR_TIMEOUT) { - printf("UU "); - } else { - printf("-- "); - } - } - printf("\r\n"); - } - } - - void InitializeTca9554() { - esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander_); - if(ret != ESP_OK) { - ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_000, &io_expander_); - if(ret != ESP_OK) { - ESP_LOGE(TAG, "TCA9554 create returned error"); - return; - } - } - // 配置IO0-IO3为输出模式 - ESP_ERROR_CHECK(esp_io_expander_set_dir(io_expander_, - IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | - IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, - IO_EXPANDER_OUTPUT)); - - // 复位LCD和TouchPad - ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_, - IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 1)); - vTaskDelay(pdMS_TO_TICKS(300)); - ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_, - IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 0)); - vTaskDelay(pdMS_TO_TICKS(300)); - ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_, - IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 1)); - } - - void EnableLcdCs() { - if(io_expander_ != NULL) { - esp_io_expander_set_level(io_expander_, IO_EXPANDER_PIN_NUM_3, 0);// 置低 LCD CS - } - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_0; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_1; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - -#if CONFIG_USE_DEVICE_AEC - boot_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateIdle) { - app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); - } - }); -#endif - } - - void InitializeIli9341Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_NC; - io_config.dc_gpio_num = GPIO_NUM_2; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - const ili9341_vendor_config_t vendor_config = { - .init_cmds = &vendor_specific_init[0], - .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), - }; - - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - // panel_config.flags.reset_active_high = 0, - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = (void *)&vendor_config; - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); - - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - EnableLcdCs(); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, false)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_46; - io_config.dc_gpio_num = GPIO_NUM_2; - io_config.spi_mode = 0; - io_config.pclk_hz = 60 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - EnableLcdCs(); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeCamera() { - // Open camera power - - camera_config_t config = {}; - config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 - config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 - config.pin_d0 = CAMERA_PIN_D0; - config.pin_d1 = CAMERA_PIN_D1; - config.pin_d2 = CAMERA_PIN_D2; - config.pin_d3 = CAMERA_PIN_D3; - config.pin_d4 = CAMERA_PIN_D4; - config.pin_d5 = CAMERA_PIN_D5; - config.pin_d6 = CAMERA_PIN_D6; - config.pin_d7 = CAMERA_PIN_D7; - config.pin_xclk = CAMERA_PIN_XCLK; - config.pin_pclk = CAMERA_PIN_PCLK; - config.pin_vsync = CAMERA_PIN_VSYNC; - config.pin_href = CAMERA_PIN_HREF; - config.pin_sccb_sda = -1; // 这里写-1 表示使用已经初始化的I2C接口 - config.pin_sccb_scl = CAMERA_PIN_SIOC; - config.sccb_i2c_port = 1; - config.pin_pwdn = CAMERA_PIN_PWDN; - config.pin_reset = CAMERA_PIN_RESET; - config.xclk_freq_hz = XCLK_FREQ_HZ; - config.pixel_format = PIXFORMAT_RGB565; - config.frame_size = FRAMESIZE_VGA; - config.jpeg_quality = 12; - config.fb_count = 1; - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - camera_ = new Esp32Camera(config); - } - -public: - Esp32S3Korvo2V3Board() : boot_button_(BOOT_BUTTON_GPIO) { - ESP_LOGI(TAG, "Initializing esp32s3_korvo2_v3 Board"); - InitializeI2c(); - I2cDetect(); - InitializeTca9554(); - InitializeCamera(); - InitializeSpi(); - InitializeButtons(); - #ifdef LCD_TYPE_ILI9341_SERIAL - InitializeIli9341Display(); - #else - InitializeSt7789Display(); - #endif - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display *GetDisplay() override { - return display_; - } - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(Esp32S3Korvo2V3Board); +#include "wifi_board.h" +#include "codecs/box_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" + +#include +#include +#include +#include +#include +#include +#include +#include "esp32_camera.h" + +#define TAG "esp32s3_korvo2_v3" + +// Init ili9341 by custom cmd +static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { + {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0}, + {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0}, + {0xC5, (uint8_t []){0xD0}, 1, 0}, + {0xC1, (uint8_t []){0x02}, 1, 0}, + {0xB4, (uint8_t []){0x02}, 1, 0}, + {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0}, + {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0}, + + {0xB1, (uint8_t []){00, 0x1B}, 2, 0}, + {0x36, (uint8_t []){0x08}, 1, 0}, + {0x3A, (uint8_t []){0x55}, 1, 0}, + {0xB7, (uint8_t []){0x06}, 1, 0}, + + {0x11, (uint8_t []){0}, 0x80, 0}, + {0x29, (uint8_t []){0}, 0x80, 0}, + + {0, (uint8_t []){0}, 0xff, 0}, +}; + +class Esp32S3Korvo2V3Board : public WifiBoard { +private: + Button boot_button_; + i2c_master_bus_handle_t i2c_bus_; + LcdDisplay* display_; + esp_io_expander_handle_t io_expander_ = NULL; + Esp32Camera* camera_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void I2cDetect() { + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK) { + printf("%02x ", address); + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + } + + void InitializeTca9554() { + esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander_); + if(ret != ESP_OK) { + ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_000, &io_expander_); + if(ret != ESP_OK) { + ESP_LOGE(TAG, "TCA9554 create returned error"); + return; + } + } + // 配置IO0-IO3为输出模式 + ESP_ERROR_CHECK(esp_io_expander_set_dir(io_expander_, + IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | + IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, + IO_EXPANDER_OUTPUT)); + + // 复位LCD和TouchPad + ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_, + IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 1)); + vTaskDelay(pdMS_TO_TICKS(300)); + ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_, + IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 0)); + vTaskDelay(pdMS_TO_TICKS(300)); + ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_, + IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 1)); + } + + void EnableLcdCs() { + if(io_expander_ != NULL) { + esp_io_expander_set_level(io_expander_, IO_EXPANDER_PIN_NUM_3, 0);// 置低 LCD CS + } + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_0; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_1; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif + } + + void InitializeIli9341Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_NC; + io_config.dc_gpio_num = GPIO_NUM_2; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + const ili9341_vendor_config_t vendor_config = { + .init_cmds = &vendor_specific_init[0], + .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), + }; + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + // panel_config.flags.reset_active_high = 0, + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = (void *)&vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); + + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + EnableLcdCs(); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, false)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_46; + io_config.dc_gpio_num = GPIO_NUM_2; + io_config.spi_mode = 0; + io_config.pclk_hz = 60 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + EnableLcdCs(); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeCamera() { + // Open camera power + + camera_config_t config = {}; + config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 + config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 + config.pin_d0 = CAMERA_PIN_D0; + config.pin_d1 = CAMERA_PIN_D1; + config.pin_d2 = CAMERA_PIN_D2; + config.pin_d3 = CAMERA_PIN_D3; + config.pin_d4 = CAMERA_PIN_D4; + config.pin_d5 = CAMERA_PIN_D5; + config.pin_d6 = CAMERA_PIN_D6; + config.pin_d7 = CAMERA_PIN_D7; + config.pin_xclk = CAMERA_PIN_XCLK; + config.pin_pclk = CAMERA_PIN_PCLK; + config.pin_vsync = CAMERA_PIN_VSYNC; + config.pin_href = CAMERA_PIN_HREF; + config.pin_sccb_sda = -1; // 这里写-1 表示使用已经初始化的I2C接口 + config.pin_sccb_scl = CAMERA_PIN_SIOC; + config.sccb_i2c_port = 1; + config.pin_pwdn = CAMERA_PIN_PWDN; + config.pin_reset = CAMERA_PIN_RESET; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_VGA; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + camera_ = new Esp32Camera(config); + } + +public: + Esp32S3Korvo2V3Board() : boot_button_(BOOT_BUTTON_GPIO) { + ESP_LOGI(TAG, "Initializing esp32s3_korvo2_v3 Board"); + InitializeI2c(); + I2cDetect(); + InitializeTca9554(); + InitializeCamera(); + InitializeSpi(); + InitializeButtons(); + #ifdef LCD_TYPE_ILI9341_SERIAL + InitializeIli9341Display(); + #else + InitializeSt7789Display(); + #endif + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display *GetDisplay() override { + return display_; + } + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(Esp32S3Korvo2V3Board); diff --git a/main/boards/esp32s3-smart-speaker/README_MPU6050.md b/main/boards/esp32s3-smart-speaker/README_MPU6050.md deleted file mode 100644 index 836d580..0000000 --- a/main/boards/esp32s3-smart-speaker/README_MPU6050.md +++ /dev/null @@ -1,211 +0,0 @@ -# MPU6050传感器集成说明 - -## 概述 - -本项目为ESP32-S3智能音箱开发板集成了MPU6050六轴传感器支持,提供了现代化的C++封装接口。 - -## 文件结构 - -- `mpu6050_sensor.h` - MPU6050传感器封装类的头文件 -- `mpu6050_sensor.cc` - MPU6050传感器封装类的实现文件 -- `mpu6050_test.cc` - MPU6050传感器测试程序(可选) -- `esp32s3_smart_speaker.cc` - 已集成MPU6050支持的板子实现 - -## 功能特性 - -### 1. 传感器支持 -- **加速度计**: 支持±2g, ±4g, ±8g, ±16g量程 -- **陀螺仪**: 支持±250°/s, ±500°/s, ±1000°/s, ±2000°/s量程 -- **温度传感器**: 内置温度传感器 -- **姿态角计算**: 使用互补滤波算法计算俯仰角、横滚角和偏航角 - -### 2. 技术特点 -- 使用ESP-IDF的现代I2C Master API -- 支持多量程配置 -- 内置数字低通滤波器 -- 可配置采样率 -- 互补滤波姿态解算 -- 完整的错误处理机制 - -## 硬件连接 - -根据`config.h`中的定义: - -```c -// IMU传感器 (I2C接口) -#define IMU_I2C_SDA_PIN GPIO_NUM_21 -#define IMU_I2C_SCL_PIN GPIO_NUM_20 -#define IMU_INT_PIN GPIO_NUM_19 -``` - -### 连接方式 -- **VCC**: 3.3V -- **GND**: 地 -- **SDA**: GPIO21 -- **SCL**: GPIO20 -- **INT**: GPIO19(中断引脚,可选) - -## 使用方法 - -### 1. 基本使用 - -```cpp -#include "mpu6050_sensor.h" - -// 创建传感器实例 -auto sensor = std::make_unique(i2c_bus_handle); - -// 初始化传感器 -if (sensor->Initialize(ACCE_FS_4G, GYRO_FS_500DPS)) { - // 唤醒传感器 - if (sensor->WakeUp()) { - // 验证设备ID - uint8_t device_id; - if (sensor->GetDeviceId(&device_id)) { - ESP_LOGI(TAG, "MPU6050 initialized, ID: 0x%02X", device_id); - } - } -} -``` -### 2. 读取传感器数据 -```cpp -mpu6050_acce_value_t acce; -mpu6050_gyro_value_t gyro; -mpu6050_temp_value_t temp; -complimentary_angle_t angle; -// 读取加速度计数据 -if (sensor->GetAccelerometer(&acce)) { - ESP_LOGI(TAG, "Accelerometer - X:%.2f, Y:%.2f, Z:%.2f", - acce.acce_x, acce.acce_y, acce.acce_z); -} -// 读取陀螺仪数据 -if (sensor->GetGyroscope(&gyro)) { - ESP_LOGI(TAG, "Gyroscope - X:%.2f, Y:%.2f, Z:%.2f", - gyro.gyro_x, gyro.gyro_y, gyro.gyro_z); -} -// 读取温度数据 -if (sensor->GetTemperature(&temp)) { - ESP_LOGI(TAG, "Temperature: %.2f°C", temp.temp); -} -// 计算姿态角 -if (sensor->ComplimentaryFilter(&acce, &gyro, &angle)) { - ESP_LOGI(TAG, "Attitude - Pitch:%.2f°, Roll:%.2f°, Yaw:%.2f°", - angle.pitch, angle.roll, angle.yaw); -} -``` - -### 3. 获取传感器状态 - -```cpp -// 检查是否已初始化 -if (sensor->IsInitialized()) { - // 获取状态信息 - std::string status = sensor->GetStatusJson(); - ESP_LOGI(TAG, "Sensor status: %s", status.c_str()); -} -``` - -## 配置参数 - -### 加速度计量程 -- `ACCE_FS_2G`: ±2g (16384 LSB/g) -- `ACCE_FS_4G`: ±4g (8192 LSB/g) -- `ACCE_FS_8G`: ±8g (4096 LSB/g) -- `ACCE_FS_16G`: ±16g (2048 LSB/g) - -### 陀螺仪量程 -- `GYRO_FS_250DPS`: ±250°/s (131 LSB/°/s) -- `GYRO_FS_500DPS`: ±500°/s (65.5 LSB/°/s) -- `GYRO_FS_1000DPS`: ±1000°/s (32.8 LSB/°/s) -- `GYRO_FS_2000DPS`: ±2000°/s (16.4 LSB/°/s) - -### 互补滤波参数 -- **alpha**: 0.98 (默认值,表示更信任陀螺仪) -- **采样率**: 125Hz -- **数字低通滤波器**: 5Hz - -## 集成到板子 - -MPU6050已经集成到`Esp32s3SmartSpeaker`类中: - -1. **自动初始化**: 在板子构造函数中自动初始化MPU6050 -2. **后台任务**: 自动创建后台任务持续读取传感器数据 -3. **状态报告**: 在`GetBoardJson()`中报告传感器状态 - -## 日志输出 - -传感器会输出以下日志信息: - -``` -I (1234) SmartSpeaker: MPU6050 sensor initialized successfully (ID: 0x68) -I (1235) SmartSpeaker: IMU data task created successfully -I (1236) SmartSpeaker: IMU data task started -I (1237) SmartSpeaker: Accelerometer - X:0.12, Y:-0.05, Z:0.98 -I (1238) SmartSpeaker: Gyroscope - X:0.15, Y:-0.02, Z:0.08 -I (1239) SmartSpeaker: Temperature: 25.3°C -I (1240) SmartSpeaker: Attitude - Pitch:2.1°, Roll:-1.5°, Yaw:0.3° -``` - -## 故障排除 - -### 常见问题 - -1. **设备ID不匹配** - - 检查I2C连接 - - 确认设备地址是否正确 - - 检查电源供应 - -2. **初始化失败** - - 检查I2C总线配置 - - 确认GPIO引脚配置正确 - - 检查上拉电阻 - -3. **数据读取失败** - - 检查I2C通信 - - 确认传感器已唤醒 - - 检查采样率配置 - -### 调试建议 - -1. 启用I2C调试日志 -2. 检查硬件连接 -3. 使用示波器检查I2C信号 -4. 验证电源电压稳定性 - -## 技术细节 - -### I2C配置 -- **时钟频率**: 100kHz -- **地址**: 0x68 (7位地址) -- **上拉电阻**: 内部使能 - -### 寄存器配置 -- **加速度计量程**: 寄存器0x1C -- **陀螺仪量程**: 寄存器0x1B -- **数字低通滤波器**: 寄存器0x1A -- **采样率**: 寄存器0x19 -- **电源管理**: 寄存器0x6B - -### 数据格式 -- **加速度计**: 16位有符号整数,转换为g值 -- **陀螺仪**: 16位有符号整数,转换为度/秒 -- **温度**: 16位有符号整数,转换为摄氏度 - -## 扩展功能 - -### 可扩展的功能 -1. **中断支持**: 可以配置数据就绪中断 -2. **运动检测**: 可以配置运动检测中断 -3. **自由落体检测**: 可以配置自由落体检测 -4. **FIFO支持**: 可以使用FIFO缓冲区 -5. **DMP支持**: 可以使用数字运动处理器 - -### 性能优化 -1. **降低采样率**: 减少功耗 -2. **使用中断**: 避免轮询 -3. **FIFO缓冲**: 批量读取数据 -4. **休眠模式**: 不使用时进入低功耗模式 - -## 许可证 - -本代码遵循项目的许可证要求。 \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/adc_manager.cc b/main/boards/esp32s3-smart-speaker/adc_manager.cc deleted file mode 100644 index 703ae11..0000000 --- a/main/boards/esp32s3-smart-speaker/adc_manager.cc +++ /dev/null @@ -1,376 +0,0 @@ -#include "adc_manager.h" -#include "application.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TAG "AdcManager" - -// 检测时间定义(毫秒) -#define PRESSURE_DETECTION_TIME_MS 2000 // 压力检测持续时间:2秒 -#define LOW_VALUE_DETECTION_TIME_MS 2000 // 低值检测持续时间:2秒 - -AdcManager &AdcManager::GetInstance() { - static AdcManager instance; - return instance; -} - -bool AdcManager::Initialize() { - if (initialized_) { - ESP_LOGW(TAG, "AdcManager already initialized"); - return true; - } - - ESP_LOGI(TAG, "Initializing AdcManager..."); - - // 初始化ADC数组 - memset(pressure_adc_values_, 0, sizeof(pressure_adc_values_)); - - InitializeAdc(); - // InitializeDigitalOutput(); // 暂时注释掉DO初始化 - - // 先设置初始化状态,再启动任务 - initialized_ = true; - - // 初始化后立刻读取一次,便于快速确认链路 - int init_read_raw = -1; - esp_err_t init_read_ret = adc_oneshot_read( - adc1_handle_, PRESSURE_SENSOR_ADC_CHANNEL, &init_read_raw); - if (init_read_ret != ESP_OK) { - ESP_LOGE(TAG, "Initial ADC read failed: %s", - esp_err_to_name(init_read_ret)); - } else { - ESP_LOGI(TAG, "Initial ADC read ok: Raw=%d", init_read_raw); - } - - // 启动ADC任务 - StartAdcTask(); - - ESP_LOGI(TAG, "AdcManager initialized successfully"); - return true; -} - -void AdcManager::InitializeAdc() { - ESP_LOGI(TAG, "Initializing ADC for pressure sensor on GPIO4 (ADC1_CH3)..."); - - // 初始化ADC驱动 - adc_oneshot_unit_init_cfg_t init_config1 = { - .unit_id = ADC_UNIT_1, - }; - esp_err_t ret = adc_oneshot_new_unit(&init_config1, &adc1_handle_); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize ADC unit: %s", esp_err_to_name(ret)); - return; - } - ESP_LOGI(TAG, "ADC unit initialized successfully"); - - // 配置ADC通道 - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ret = adc_oneshot_config_channel(adc1_handle_, PRESSURE_SENSOR_ADC_CHANNEL, - &chan_config); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to configure ADC channel %d: %s", - PRESSURE_SENSOR_ADC_CHANNEL, esp_err_to_name(ret)); - return; - } - ESP_LOGI(TAG, "ADC channel %d configured successfully", - PRESSURE_SENSOR_ADC_CHANNEL); - - // 初始化ADC校准 - adc_cali_curve_fitting_config_t cali_config = { - .unit_id = ADC_UNIT_1, - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc1_cali_handle_); - if (ret != ESP_OK) { - ESP_LOGW(TAG, "ADC calibration not available: %s", esp_err_to_name(ret)); - adc1_cali_handle_ = NULL; - } else { - ESP_LOGI(TAG, "ADC calibration initialized successfully"); - } - - ESP_LOGI(TAG, "ADC initialized for pressure sensor monitoring on GPIO4"); -} - -void AdcManager::ReadPressureSensorData() { - if (!initialized_) { - return; - } - - int adc_value; - esp_err_t ret = - adc_oneshot_read(adc1_handle_, PRESSURE_SENSOR_ADC_CHANNEL, &adc_value); - - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to read pressure sensor ADC: %s", - esp_err_to_name(ret)); - return; - } - - // 直接使用原始ADC值,不进行电压转换 - current_pressure_value_ = adc_value; - - // 压力检测触发音乐播放 - static bool last_pressure_state = false; - static int64_t pressure_start_time = 0; - static bool pressure_triggered = false; - - if (last_pressure_state) { - // 长时间不动检测逻辑 - CheckLongTimeNoMovement(current_pressure_value_); - } - - // 每隔5次打印一次详细日志(便于定位问题) - static int adc_log_counter = 0; - adc_log_counter++; - if (adc_log_counter >= 10) { - ESP_LOGI(TAG, "ADC read: Raw=%d", adc_value); - adc_log_counter = 0; - } - - bool current_pressure_state = IsPressureDetected(); - - if (current_pressure_state && !last_pressure_state) { - // 压力开始检测,记录开始时间 - pressure_start_time = esp_timer_get_time(); - pressure_triggered = false; - ESP_LOGI(TAG, "Pressure detection started"); - } else if (current_pressure_state && last_pressure_state) { - // 压力持续检测中,检查是否超过2秒 - int64_t current_time = esp_timer_get_time(); - int64_t duration_ms = - (current_time - pressure_start_time) / 1000; // 转换为毫秒 - - if (duration_ms >= PRESSURE_DETECTION_TIME_MS && !pressure_triggered) { - ESP_LOGI(TAG, - "Pressure detected for %ld ms! Triggering music playback...", - (long)duration_ms); - // 触发音乐播放 - TriggerMusicPlayback(); - pressure_triggered = true; // 防止重复触发 - } - } else if (!current_pressure_state && last_pressure_state) { - // 压力结束检测 - ESP_LOGI(TAG, "Pressure detection ended"); - pressure_triggered = false; - } - - last_pressure_state = current_pressure_state; - - // ADC值小于100时的暂停检测(也需要持续2秒) - static int64_t low_value_start_time = 0; - static bool low_value_triggered = false; - - if (adc_value < 100) { - if (low_value_start_time == 0) { - // 第一次检测到小于100的值,记录开始时间 - low_value_start_time = esp_timer_get_time(); - low_value_triggered = false; - ESP_LOGI(TAG, "ADC low value detection started (value: %d)", adc_value); - } else { - // 持续检测小于100的值,检查是否超过2秒 - int64_t current_time = esp_timer_get_time(); - int64_t duration_ms = - (current_time - low_value_start_time) / 1000; // 转换为毫秒 - - if (duration_ms >= LOW_VALUE_DETECTION_TIME_MS && !low_value_triggered) { - ESP_LOGI(TAG, - "ADC low value detected for %ld ms! (value: %d) Triggering music pause...", - (long)duration_ms, adc_value); - TriggerMusicPauseback(); - low_value_triggered = true; // 防止重复触发 - } - } - } else { - // ADC值大于等于100,重置低值检测 - if (low_value_start_time != 0) { - ESP_LOGI(TAG, "ADC low value detection ended (value: %d)", adc_value); - low_value_start_time = 0; - low_value_triggered = false; - } - } -} - -void AdcManager::StartAdcTask() { - if (!initialized_) { - ESP_LOGE(TAG, "AdcManager not initialized"); - return; - } - - BaseType_t ret = - xTaskCreate(AdcTask, "adc_task", 4096, this, 2, &adc_task_handle_); - if (ret != pdPASS) { - ESP_LOGE(TAG, "Failed to create ADC task"); - } else { - ESP_LOGI(TAG, "ADC task created successfully"); - } -} - -void AdcManager::StopAdcTask() { - if (adc_task_handle_) { - vTaskDelete(adc_task_handle_); - adc_task_handle_ = nullptr; - } -} - -void AdcManager::AdcTask(void *pvParameters) { - AdcManager *manager = static_cast(pvParameters); - ESP_LOGI(TAG, "ADC task started"); - - while (true) { - if (manager->initialized_) { - manager->ReadPressureSensorData(); - } - vTaskDelay(pdMS_TO_TICKS(100)); // 100ms间隔 - } -} - -int AdcManager::GetCurrentPressureValue() const { - return current_pressure_value_; -} - -const int *AdcManager::GetPressureAdcValues() const { - return pressure_adc_values_; -} - -size_t AdcManager::GetPressureSampleCount() const { - return (pressure_data_index_ == 0) ? kPressureAdcDataCount - : pressure_data_index_; -} - -bool AdcManager::IsPressureDetected() const { - if (!initialized_) { - return false; - } - return current_pressure_value_ > 1000; // 压力阈值:100 -} - -bool AdcManager::IsLightPressure() const { - if (!initialized_) { - return false; - } - return current_pressure_value_ > 500; // 轻压阈值:500 -} - -void AdcManager::CheckLongTimeNoMovement(int adc_value) { - uint32_t current_time = esp_timer_get_time() / 1000000; // 转换为秒 - - // 计算ADC值变化 - int adc_change = abs(adc_value - last_stable_value_); - - if (adc_change > kMovementThreshold) { - // 有显著变化,重置不动检测 - last_stable_value_ = adc_value; - no_movement_start_time_ = current_time; - is_no_movement_detected_ = false; - } else { - // 变化很小,检查是否长时间不动 - if (no_movement_start_time_ == 0) { - no_movement_start_time_ = current_time; - } - - uint32_t no_movement_duration = current_time - no_movement_start_time_; - if (no_movement_duration >= kLongTimeThreshold && - !is_no_movement_detected_) { - is_no_movement_detected_ = true; - ESP_LOGW(TAG, - "Long time no movement detected! Duration: %lu seconds, ADC: %d", - no_movement_duration, adc_value); - - // 停止音乐播放和语音交互 - TriggerMusicPauseback(); - } - } -} - -bool AdcManager::IsLongTimeNoMovement() const { - if (!initialized_) { - return false; - } - return is_no_movement_detected_; -} - -uint32_t AdcManager::GetNoMovementDuration() const { - if (!initialized_ || no_movement_start_time_ == 0) { - return 0; - } - - uint32_t current_time = esp_timer_get_time() / 1000000; - return current_time - no_movement_start_time_; -} - -void AdcManager::TriggerMusicPauseback() { - ESP_LOGI(TAG, "Triggering music pauseback"); - auto music = Board::GetInstance().GetMusic(); - if (!music) { - ESP_LOGI(TAG, "No music player found"); - return; - } - - // 检查音乐状态,避免重复操作 - if (!music->IsPlaying() && !music->IsPaused()) { - ESP_LOGI(TAG, "Music is not playing or paused, skipping pause operation"); - return; - } - - music->PauseSong(); - - // 停止语音交互 - auto &app = Application::GetInstance(); - app.GetAudioService().EnableWakeWordDetection(false); - ESP_LOGI(TAG, "Stopped wake word detection due to long time no movement"); -} - -void AdcManager::TriggerMusicPlayback() { - ESP_LOGI(TAG, "Triggering music playback"); - - // 确保音频输出已启用 - auto& board = Board::GetInstance(); - auto codec = board.GetAudioCodec(); - if (!codec) { - ESP_LOGE(TAG, "Audio codec not available"); - return; - } - - codec->EnableOutput(true); - ESP_LOGI(TAG, "Audio output enabled"); - - // 通过Board接口获取音乐播放器并触发播放 - auto music = board.GetMusic(); - if (!music) { - ESP_LOGI(TAG, "No music player found"); - return; - } - if (music->IsPlaying()) { - ESP_LOGI(TAG, "Music is already playing"); - return; - } - if (music->IsDownloading()) { - ESP_LOGI(TAG, "Music is already downloading"); - return; - } - if (music->IsPaused()) { - ESP_LOGI(TAG, "Music is already paused"); - music->ResumeSong(); - return; - } - auto song_name = "稻香"; - auto artist_name = ""; - if (!music->Download(song_name, artist_name)) { - ESP_LOGI(TAG, "获取音乐资源失败"); - return; - } - - auto download_result = music->GetDownloadResult(); - ESP_LOGI(TAG, "Music details result: %s", download_result.c_str()); -} \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/adc_manager.h b/main/boards/esp32s3-smart-speaker/adc_manager.h deleted file mode 100644 index cfd8db0..0000000 --- a/main/boards/esp32s3-smart-speaker/adc_manager.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef ADC_MANAGER_H -#define ADC_MANAGER_H - -#include "config.h" -#include -#include -#include -#include -#include - -class AdcManager { -public: - static AdcManager& GetInstance(); - - // 初始化ADC系统 - bool Initialize(); - - // 读取压感传感器数据 - void ReadPressureSensorData(); - - // 获取当前压感值 - int GetCurrentPressureValue() const; - - // 获取ADC原始值数组 - const int* GetPressureAdcValues() const; - - // 获取有效样本数量 - size_t GetPressureSampleCount() const; - - // 启动/停止ADC任务 - void StartAdcTask(); - void StopAdcTask(); - - // 检查是否已初始化 - bool IsInitialized() const { return initialized_; } - - // 基于ADC值判断压力状态 - bool IsPressureDetected() const; - bool IsLightPressure() const; - - // 长时间不动检测 - bool IsLongTimeNoMovement() const; - uint32_t GetNoMovementDuration() const; // 返回不动持续时间(秒) - - // 压力检测触发音乐播放 - void TriggerMusicPlayback(); - void TriggerMusicPauseback(); - -private: - AdcManager() = default; - ~AdcManager() = default; - AdcManager(const AdcManager&) = delete; - AdcManager& operator=(const AdcManager&) = delete; - - void InitializeAdc(); - void CheckLongTimeNoMovement(int adc_value); - static void AdcTask(void *pvParameters); - - bool initialized_ = false; - adc_oneshot_unit_handle_t adc1_handle_; - adc_cali_handle_t adc1_cali_handle_; - - // 压感传感器数据 - static constexpr size_t kPressureAdcDataCount = 10; - int pressure_adc_values_[kPressureAdcDataCount]; - size_t pressure_data_index_ = 0; - int current_pressure_value_ = 0; - - // 长时间不动检测相关变量 - int last_stable_value_ = 0; - uint32_t no_movement_start_time_ = 0; - bool is_no_movement_detected_ = false; - static constexpr int kMovementThreshold = 50; // ADC变化阈值 - static constexpr uint32_t kLongTimeThreshold = 30; // 长时间阈值(秒) - - // 任务句柄 - TaskHandle_t adc_task_handle_ = nullptr; -}; - -#endif // ADC_MANAGER_H \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/button_manager.cc b/main/boards/esp32s3-smart-speaker/button_manager.cc deleted file mode 100644 index 52ce3ad..0000000 --- a/main/boards/esp32s3-smart-speaker/button_manager.cc +++ /dev/null @@ -1,131 +0,0 @@ -#include "button_manager.h" -#include "application.h" -#include - -#define TAG "ButtonManager" - -ButtonManager& ButtonManager::GetInstance() { - static ButtonManager instance; - return instance; -} - -ButtonManager::ButtonManager() - : boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { -} - -bool ButtonManager::Initialize() { - if (initialized_) { - ESP_LOGW(TAG, "ButtonManager already initialized"); - return true; - } - - ESP_LOGI(TAG, "Initializing ButtonManager..."); - - // 设置按钮回调 - SetupButtonCallbacks(); - - initialized_ = true; - ESP_LOGI(TAG, "ButtonManager initialized successfully"); - return true; -} - -void ButtonManager::SetupButtonCallbacks() { - ESP_LOGI(TAG, "Setting up button callbacks..."); - - // BOOT按钮回调 - boot_button_.OnClick([]() { - ESP_LOGI(TAG, "Boot button clicked"); - }); - - boot_button_.OnLongPress([]() { - ESP_LOGI(TAG, "BOOT long pressed: play boot tone"); - - // 确保音频输出已启用 - auto& board = Board::GetInstance(); - auto codec = board.GetAudioCodec(); - if (!codec) { - ESP_LOGE(TAG, "Audio codec not available"); - return; - } - - codec->EnableOutput(true); - codec->SetOutputVolume(10); - - auto music = Board::GetInstance().GetMusic(); - if (!music) { - ESP_LOGE(TAG, "Music player not available"); - return; - } - - auto song_name = "稻香"; - auto artist_name = ""; - if (!music->Download(song_name, artist_name)) { - ESP_LOGI(TAG, "获取音乐资源失败"); - return; - } - - auto download_result = music->GetDownloadResult(); - ESP_LOGI(TAG, "Music details result: %s", download_result.c_str()); - }); - - // 音量上按钮回调 - volume_up_button_.OnClick([]() { - ESP_LOGI(TAG, "Volume up button clicked"); - // 通过AudioService间接控制音量 - auto& board = Board::GetInstance(); - auto codec = board.GetAudioCodec(); - codec->SetOutputVolume(codec->output_volume() + 10); - ESP_LOGI(TAG, "Volume up requested"); - }); - - volume_up_button_.OnLongPress([]() { - ESP_LOGI(TAG, "Volume up long pressed: switching to voice interaction mode"); - - // 播放进入语音交互模式的提示音 - auto& app = Application::GetInstance(); - app.PlaySound("success"); // 播放成功提示音 - - // 暂停音乐播放 - auto music = Board::GetInstance().GetMusic(); - if (music && music->IsPlaying()) { - music->PauseSong(); - ESP_LOGI(TAG, "Music paused for voice interaction"); - } - - // 切换到语音交互模式 - app.GetAudioService().EnableWakeWordDetection(true); - app.GetAudioService().EnableVoiceProcessing(true); - ESP_LOGI(TAG, "Switched to voice interaction mode - waiting for user voice input"); - }); - - // 音量下按钮回调 - volume_down_button_.OnClick([]() { - ESP_LOGI(TAG, "Volume down button clicked"); - auto& board = Board::GetInstance(); - auto codec = board.GetAudioCodec(); - codec->SetOutputVolume(codec->output_volume() - 10); - ESP_LOGI(TAG, "Volume down requested"); - }); - - volume_down_button_.OnLongPress([]() { - ESP_LOGI(TAG, "Volume down long pressed: stopping audio playback and voice interaction"); - - // 播放停止提示音 - auto& app = Application::GetInstance(); - app.PlaySound("exclamation"); // 播放感叹号提示音 - - // 停止音乐播放 - auto music = Board::GetInstance().GetMusic(); - if (music && music->IsPlaying()) { - music->PauseSong(); - ESP_LOGI(TAG, "Music playback stopped"); - } - - // 停止语音交互 - app.GetAudioService().EnableWakeWordDetection(false); - app.GetAudioService().EnableVoiceProcessing(false); - ESP_LOGI(TAG, "Voice interaction stopped"); - }); -} \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/button_manager.h b/main/boards/esp32s3-smart-speaker/button_manager.h deleted file mode 100644 index aa24747..0000000 --- a/main/boards/esp32s3-smart-speaker/button_manager.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef BUTTON_MANAGER_H -#define BUTTON_MANAGER_H - -#include "button.h" -#include "config.h" - -class ButtonManager { -public: - static ButtonManager& GetInstance(); - - // 初始化按钮系统 - bool Initialize(); - - // 检查是否已初始化 - bool IsInitialized() const { return initialized_; } - -private: - ButtonManager(); - ~ButtonManager() = default; - ButtonManager(const ButtonManager&) = delete; - ButtonManager& operator=(const ButtonManager&) = delete; - - void SetupButtonCallbacks(); - - bool initialized_ = false; - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; -}; - -#endif // BUTTON_MANAGER_H \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/config.h b/main/boards/esp32s3-smart-speaker/config.h deleted file mode 100644 index 29b574b..0000000 --- a/main/boards/esp32s3-smart-speaker/config.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -// ESP32-S3 智能音箱开发板配置 - -// 音频配置 -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 16000 - -// 扬声器I2S配置 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 // 扬声器Word Select -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 // 扬声器Bit Clock -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 // 扬声器数据输出 - -// INMP441 I2S麦克风配置 (BCLK/WS/DIN) -// 说明: INMP441 是 I2S 数字麦,需要标准 I2S 三线 -// 建议映射: BCLK=GPIO14, WS=GPIO38, DIN=GPIO16(可按需要调整) -#define AUDIO_MIC_I2S_BCLK GPIO_NUM_14 -#define AUDIO_MIC_I2S_WS GPIO_NUM_38 -#define AUDIO_MIC_I2S_DIN GPIO_NUM_16 - -// 用户交互 -#define BUILTIN_LED_GPIO GPIO_NUM_48 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_41 - -// IMU传感器 (I2C接口) -#define IMU_I2C_SDA_PIN GPIO_NUM_21 -#define IMU_I2C_SCL_PIN GPIO_NUM_20 -#define IMU_INT_PIN GPIO_NUM_13 - -// 压感传感器 (ADC接口) -#define PRESSURE_SENSOR_ADC_CHANNEL ADC_CHANNEL_3 // GPIO4 (ADC1_CHANNEL_3) - -// 功能IO定义 -// LED控制 -#define LED_RING_GPIO GPIO_NUM_6 // LED灯环控制 -#define STATUS_LED_GPIO GPIO_NUM_10 // 状态指示灯 - -// 无显示配置 -#define DISPLAY_SDA_PIN GPIO_NUM_NC -#define DISPLAY_SCL_PIN GPIO_NUM_NC -#define DISPLAY_WIDTH 0 -#define DISPLAY_HEIGHT 0 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -// 无摄像头配置 -#define CAMERA_PIN_PWDN GPIO_NUM_NC -#define CAMERA_PIN_RESET GPIO_NUM_NC -#define CAMERA_PIN_XCLK GPIO_NUM_NC -#define CAMERA_PIN_SIOD GPIO_NUM_NC -#define CAMERA_PIN_SIOC GPIO_NUM_NC -#define CAMERA_PIN_D0 GPIO_NUM_NC -#define CAMERA_PIN_D1 GPIO_NUM_NC -#define CAMERA_PIN_D2 GPIO_NUM_NC -#define CAMERA_PIN_D3 GPIO_NUM_NC -#define CAMERA_PIN_D4 GPIO_NUM_NC -#define CAMERA_PIN_D5 GPIO_NUM_NC -#define CAMERA_PIN_D6 GPIO_NUM_NC -#define CAMERA_PIN_D7 GPIO_NUM_NC -#define CAMERA_PIN_VSYNC GPIO_NUM_NC -#define CAMERA_PIN_HREF GPIO_NUM_NC -#define CAMERA_PIN_PCLK GPIO_NUM_NC - -// 开发板版本 -#define SMART_SPEAKER_VERSION "1.0.0" - -#endif // _BOARD_CONFIG_H_ \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/config.json b/main/boards/esp32s3-smart-speaker/config.json deleted file mode 100644 index 1a015c4..0000000 --- a/main/boards/esp32s3-smart-speaker/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "esp32s3-smart-speaker", - "sdkconfig_append": [ - "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n", - "CONFIG_LWIP_IPV6=n", - "CONFIG_USE_ESP_WAKE_WORD=y", - "CONFIG_USE_AUDIO_DEBUGGER=y", - "CONFIG_AUDIO_DEBUG_UDP_SERVER=\"192.168.122.143:8000\"" - ] - } - ] -} \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/esp32s3_smart_speaker.cc b/main/boards/esp32s3-smart-speaker/esp32s3_smart_speaker.cc deleted file mode 100644 index afd10ab..0000000 --- a/main/boards/esp32s3-smart-speaker/esp32s3_smart_speaker.cc +++ /dev/null @@ -1,151 +0,0 @@ -#include "assets.h" -#include "adc_manager.h" -#include "button_manager.h" -#include "codecs/no_audio_codec.h" -#include "config.h" -#include "esplog_display.h" -#include "esp32_music.h" -#include "gpio_manager.h" -#include "imu_manager.h" -#include "led/single_led.h" -#include "tools_manager.h" -#include "wifi_board.h" -#include "wifi_manager.h" - -#include -#include -#include - -#define TAG "SmartSpeaker" - -class Esp32s3SmartSpeaker : public WifiBoard { -private: - // I2C总线句柄 - i2c_master_bus_handle_t codec_i2c_bus_; - - void InitializeManagers() { - ESP_LOGI(TAG, "Initializing managers..."); - - // 初始化各个管理器(Initialize内部会自动启动任务) - AdcManager::GetInstance().Initialize(); - ImuManager::GetInstance().Initialize(); - ButtonManager::GetInstance().Initialize(); - GpioManager::GetInstance().Initialize(); - ToolsManager::GetInstance().Initialize(); - WifiManager::GetInstance().Initialize(); - - ESP_LOGI(TAG, "All managers initialized successfully"); - } - - void InitializeCodecI2c() { - return; - } - - -public: - Esp32s3SmartSpeaker() { - ESP_LOGI(TAG, "Initializing ESP32-S3 Smart Speaker"); - - // 初始化音乐播放器 - music_ = new Esp32Music(); - ESP_LOGI(TAG, "Music player initialized"); - - // 初始化I2C总线 - InitializeCodecI2c(); - - // 初始化各个管理器 - InitializeManagers(); - - ESP_LOGI(TAG, "ESP32-S3 Smart Speaker initialized successfully"); - } - - virtual ~Esp32s3SmartSpeaker() = default; - - virtual std::string GetBoardType() override { - return std::string("esp32s3-smart-speaker"); - } - - virtual AudioCodec *GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec( - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - // 扬声器(标准 I2S 输出) - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - // 麦克风(标准 I2S 输入,单声道) - AUDIO_MIC_I2S_BCLK, - AUDIO_MIC_I2S_WS, - AUDIO_MIC_I2S_DIN); - return &audio_codec; - } - - virtual Display *GetDisplay() override { - static EspLogDisplay display; - return &display; - } - - virtual Led *GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual std::string GetBoardJson() override { - char json_buffer[2048]; - - // 安全地获取管理器状态,避免在初始化过程中访问 - bool imu_initialized = false; - bool adc_initialized = false; - int pressure_value = 0; - size_t pressure_sample_count = 0; - bool imu_sensor_initialized = false; - - try { - auto& imu_manager = ImuManager::GetInstance(); - imu_initialized = imu_manager.IsInitialized(); - - auto& adc_manager = AdcManager::GetInstance(); - adc_initialized = adc_manager.IsInitialized(); - pressure_value = adc_manager.GetCurrentPressureValue(); - pressure_sample_count = adc_manager.GetPressureSampleCount(); - - auto imu_sensor = imu_manager.GetImuSensor(); - imu_sensor_initialized = imu_sensor && imu_sensor->IsInitialized(); - } catch (...) { - ESP_LOGW(TAG, "Error accessing managers in GetBoardJson, using default values"); - } - - snprintf(json_buffer, sizeof(json_buffer), - "{" - "\"board_type\":\"esp32s3-smart-speaker\"," - "\"version\":\"%s\"," - "\"features\":[\"audio\",\"imu\",\"pressure\",\"led_ring\",\"fan\",\"relay\",\"status_led\"]," - "\"audio_codec\":\"NoAudioCodecSimplex\"," - "\"audio_method\":\"i2s_standard\"," - "\"microphone\":\"INMP441_I2S\"," - "\"speaker\":\"NoAudioCodec\"," - "\"imu_initialized\":%s," - "\"pressure_sensor_initialized\":%s," - "\"pressure_sensor\":{\"current_value\":%d,\"adc_channel\":%d,\"sample_count\":%u}," - "\"imu_sensor\":{\"type\":\"MPU6050\",\"initialized\":%s,\"status\":\"unknown\"}" - "}", - SMART_SPEAKER_VERSION, - imu_initialized ? "true" : "false", - adc_initialized ? "true" : "false", - pressure_value, - PRESSURE_SENSOR_ADC_CHANNEL, - (unsigned int)pressure_sample_count, - imu_sensor_initialized ? "true" : "false" - ); - - ESP_LOGI(TAG, "GetBoardJson completed successfully"); - return std::string(json_buffer); - } - - virtual Assets *GetAssets() override { - static Assets assets(std::string(ASSETS_XIAOZHI_WAKENET_SMALL)); - return &assets; - } -}; - -DECLARE_BOARD(Esp32s3SmartSpeaker); \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/gpio_manager.cc b/main/boards/esp32s3-smart-speaker/gpio_manager.cc deleted file mode 100644 index 9885d36..0000000 --- a/main/boards/esp32s3-smart-speaker/gpio_manager.cc +++ /dev/null @@ -1,53 +0,0 @@ -#include "gpio_manager.h" -#include - -#define TAG "GpioManager" - -GpioManager& GpioManager::GetInstance() { - static GpioManager instance; - return instance; -} - -bool GpioManager::Initialize() { - if (initialized_) { - ESP_LOGW(TAG, "GpioManager already initialized"); - return true; - } - - ESP_LOGI(TAG, "Initializing GpioManager..."); - - // 初始化GPIO输出 - gpio_config_t io_conf = {}; - - // LED灯环控制 - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = (1ULL << LED_RING_GPIO); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - // 状态指示灯 - io_conf.pin_bit_mask = (1ULL << STATUS_LED_GPIO); - gpio_config(&io_conf); - - initialized_ = true; - ESP_LOGI(TAG, "GpioManager initialized successfully"); - return true; -} - -void GpioManager::SetLedRing(bool state) { - if (!initialized_) { - ESP_LOGE(TAG, "GpioManager not initialized"); - return; - } - gpio_set_level(LED_RING_GPIO, state ? 1 : 0); -} - -void GpioManager::SetStatusLed(bool state) { - if (!initialized_) { - ESP_LOGE(TAG, "GpioManager not initialized"); - return; - } - gpio_set_level(STATUS_LED_GPIO, state ? 1 : 0); -} \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/gpio_manager.h b/main/boards/esp32s3-smart-speaker/gpio_manager.h deleted file mode 100644 index 66c4657..0000000 --- a/main/boards/esp32s3-smart-speaker/gpio_manager.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef GPIO_MANAGER_H -#define GPIO_MANAGER_H - -#include "config.h" -#include - -class GpioManager { -public: - static GpioManager& GetInstance(); - - // 初始化GPIO系统 - bool Initialize(); - - // GPIO控制方法 - void SetLedRing(bool state); - void SetStatusLed(bool state); - - // 检查是否已初始化 - bool IsInitialized() const { return initialized_; } - -private: - GpioManager() = default; - ~GpioManager() = default; - GpioManager(const GpioManager&) = delete; - GpioManager& operator=(const GpioManager&) = delete; - - bool initialized_ = false; -}; - -#endif // GPIO_MANAGER_H \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/imu_manager.cc b/main/boards/esp32s3-smart-speaker/imu_manager.cc deleted file mode 100644 index 2b06367..0000000 --- a/main/boards/esp32s3-smart-speaker/imu_manager.cc +++ /dev/null @@ -1,139 +0,0 @@ -#include "imu_manager.h" -#include - -#define TAG "ImuManager" - -ImuManager& ImuManager::GetInstance() { - static ImuManager instance; - return instance; -} - -bool ImuManager::Initialize() { - if (initialized_) { - ESP_LOGW(TAG, "ImuManager already initialized"); - return true; - } - - ESP_LOGI(TAG, "Initializing ImuManager..."); - - InitializeImu(); - - // 启动IMU任务 - StartImuTask(); - - initialized_ = true; - ESP_LOGI(TAG, "ImuManager initialized successfully"); - return true; -} - -void ImuManager::InitializeImu() { - ESP_LOGI(TAG, "Initializing MPU6050 IMU sensor..."); - - // IMU传感器I2C总线 - i2c_master_bus_config_t imu_i2c_cfg = { - .i2c_port = I2C_NUM_1, - .sda_io_num = IMU_I2C_SDA_PIN, - .scl_io_num = IMU_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - .allow_pd = false, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&imu_i2c_cfg, &imu_i2c_bus_)); - - // 初始化MPU6050传感器 - mpu6050_sensor_ = std::make_unique(imu_i2c_bus_); - - if (mpu6050_sensor_) { - uint8_t device_id; - if (mpu6050_sensor_->GetDeviceId(&device_id)) { - ESP_LOGI(TAG, "MPU6050 device ID: 0x%02X", device_id); - if (device_id == MPU6050_WHO_AM_I_VAL) { - if (mpu6050_sensor_->Initialize(ACCE_FS_4G, GYRO_FS_500DPS)) { - if (mpu6050_sensor_->WakeUp()) { - initialized_ = true; - ESP_LOGI(TAG, "MPU6050 sensor initialized successfully"); - } else { - ESP_LOGE(TAG, "Failed to wake up MPU6050"); - } - } else { - ESP_LOGE(TAG, "Failed to initialize MPU6050"); - } - } else { - ESP_LOGE(TAG, "MPU6050 device ID mismatch: expected 0x%02X, got 0x%02X", - MPU6050_WHO_AM_I_VAL, device_id); - } - } else { - ESP_LOGE(TAG, "Failed to read MPU6050 device ID"); - } - } else { - ESP_LOGE(TAG, "Failed to create MPU6050 sensor instance"); - } - - if (!initialized_) { - ESP_LOGW(TAG, "IMU sensor initialization failed - continuing without IMU"); - } -} - -void ImuManager::StartImuTask() { - if (!initialized_) { - ESP_LOGW(TAG, "ImuManager not initialized, skipping IMU task creation"); - return; - } - - BaseType_t ret = xTaskCreate(ImuDataTask, "imu_data_task", 4096, this, 5, &imu_task_handle_); - if (ret != pdPASS) { - ESP_LOGE(TAG, "Failed to create IMU data task"); - } else { - ESP_LOGI(TAG, "IMU data task created successfully"); - } -} - -void ImuManager::StopImuTask() { - if (imu_task_handle_) { - vTaskDelete(imu_task_handle_); - imu_task_handle_ = nullptr; - } -} - -void ImuManager::ImuDataTask(void *pvParameters) { - ImuManager *manager = static_cast(pvParameters); - ESP_LOGI(TAG, "IMU data task started"); - - mpu6050_acce_value_t acce; - mpu6050_gyro_value_t gyro; - mpu6050_temp_value_t temp; - complimentary_angle_t angle; - - while (true) { - if (manager->mpu6050_sensor_ && manager->initialized_) { - // 读取加速度计数据 - if (manager->mpu6050_sensor_->GetAccelerometer(&acce)) { - ESP_LOGI(TAG, "Accelerometer - X:%.2f, Y:%.2f, Z:%.2f", acce.acce_x, - acce.acce_y, acce.acce_z); - } - - // 读取陀螺仪数据 - if (manager->mpu6050_sensor_->GetGyroscope(&gyro)) { - ESP_LOGI(TAG, "Gyroscope - X:%.2f, Y:%.2f, Z:%.2f", gyro.gyro_x, - gyro.gyro_y, gyro.gyro_z); - } - - // 读取温度数据 - if (manager->mpu6050_sensor_->GetTemperature(&temp)) { - ESP_LOGI(TAG, "Temperature: %.2f°C", temp.temp); - } - - // 计算姿态角 - if (manager->mpu6050_sensor_->ComplimentaryFilter(&acce, &gyro, &angle)) { - ESP_LOGI(TAG, "Attitude - Pitch:%.2f°, Roll:%.2f°, Yaw:%.2f°", - angle.pitch, angle.roll, angle.yaw); - } - } - vTaskDelay(pdMS_TO_TICKS(50)); - } -} \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/imu_manager.h b/main/boards/esp32s3-smart-speaker/imu_manager.h deleted file mode 100644 index 7109a4c..0000000 --- a/main/boards/esp32s3-smart-speaker/imu_manager.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef IMU_MANAGER_H -#define IMU_MANAGER_H - -#include "config.h" -#include "mpu6050_sensor.h" -#include -#include - -class ImuManager { -public: - static ImuManager& GetInstance(); - - // 初始化IMU系统 - bool Initialize(); - - // 启动/停止IMU任务 - void StartImuTask(); - void StopImuTask(); - - // 获取IMU传感器实例 - Mpu6050Sensor* GetImuSensor() const { return mpu6050_sensor_.get(); } - - // 检查是否已初始化 - bool IsInitialized() const { return initialized_; } - -private: - ImuManager() = default; - ~ImuManager() = default; - ImuManager(const ImuManager&) = delete; - ImuManager& operator=(const ImuManager&) = delete; - - void InitializeImu(); - static void ImuDataTask(void *pvParameters); - - bool initialized_ = false; - i2c_master_bus_handle_t imu_i2c_bus_; - std::unique_ptr mpu6050_sensor_; - - // 任务句柄 - TaskHandle_t imu_task_handle_ = nullptr; -}; - -#endif // IMU_MANAGER_H \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/mpu6050_sensor.cc b/main/boards/esp32s3-smart-speaker/mpu6050_sensor.cc deleted file mode 100644 index 8266d68..0000000 --- a/main/boards/esp32s3-smart-speaker/mpu6050_sensor.cc +++ /dev/null @@ -1,280 +0,0 @@ -#include "mpu6050_sensor.h" -#include -#include - -const char* Mpu6050Sensor::TAG = "MPU6050"; - -Mpu6050Sensor::Mpu6050Sensor(i2c_master_bus_handle_t i2c_bus, uint8_t device_addr) - : i2c_bus_(i2c_bus), device_addr_(device_addr), initialized_(false), - acce_fs_(ACCE_FS_4G), gyro_fs_(GYRO_FS_500DPS), dt_(0.0f), alpha_(0.98f) { - - // 初始化设备句柄 - i2c_device_config_t dev_cfg = { - .dev_addr_length = I2C_ADDR_BIT_LEN_7, - .device_address = device_addr_, - .scl_speed_hz = 100000, - }; - - esp_err_t ret = i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &device_handle_); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to add MPU6050 device to I2C bus: %s", esp_err_to_name(ret)); - device_handle_ = nullptr; - } - - // 初始化互补滤波 - InitializeComplimentaryFilter(); -} - -Mpu6050Sensor::~Mpu6050Sensor() { - if (device_handle_) { - i2c_master_bus_rm_device(device_handle_); - } -} - -bool Mpu6050Sensor::Initialize(mpu6050_acce_fs_t acce_fs, mpu6050_gyro_fs_t gyro_fs) { - if (!device_handle_) { - ESP_LOGE(TAG, "Device handle is null"); - return false; - } - - acce_fs_ = acce_fs; - gyro_fs_ = gyro_fs; - - // 配置加速度计量程 - uint8_t acce_config = (acce_fs << 3) & 0x18; - if (!WriteRegister(0x1C, acce_config)) { - ESP_LOGE(TAG, "Failed to configure accelerometer"); - return false; - } - - // 配置陀螺仪量程 - uint8_t gyro_config = (gyro_fs << 3) & 0x18; - if (!WriteRegister(0x1B, gyro_config)) { - ESP_LOGE(TAG, "Failed to configure gyroscope"); - return false; - } - - // 配置数字低通滤波器 - if (!WriteRegister(0x1A, 0x06)) { // DLPF_CFG = 6 (5Hz) - ESP_LOGE(TAG, "Failed to configure DLPF"); - return false; - } - - // 配置采样率 (1kHz / (1 + 7) = 125Hz) - if (!WriteRegister(0x19, 0x07)) { - ESP_LOGE(TAG, "Failed to configure sample rate"); - return false; - } - - initialized_ = true; - ESP_LOGI(TAG, "MPU6050 initialized successfully"); - ESP_LOGI(TAG, "Accelerometer range: %d, Gyroscope range: %d", acce_fs, gyro_fs); - - return true; -} - -bool Mpu6050Sensor::WakeUp() { - if (!device_handle_) { - ESP_LOGE(TAG, "Device handle is null"); - return false; - } - - // 清除睡眠模式位 - if (!WriteRegister(0x6B, 0x00)) { - ESP_LOGE(TAG, "Failed to wake up MPU6050"); - return false; - } - - // 等待传感器稳定 - vTaskDelay(pdMS_TO_TICKS(100)); - - ESP_LOGI(TAG, "MPU6050 woken up"); - return true; -} - -bool Mpu6050Sensor::GetDeviceId(uint8_t* device_id) { - if (!device_id) { - ESP_LOGE(TAG, "Device ID pointer is null"); - return false; - } - - return ReadRegister(MPU6050_WHO_AM_I_REG, device_id, 1); -} - -bool Mpu6050Sensor::GetAccelerometer(mpu6050_acce_value_t* acce) { - if (!acce) { - ESP_LOGE(TAG, "Accelerometer data pointer is null"); - return false; - } - - uint8_t data[6]; - if (!ReadRegister(0x3B, data, 6)) { - ESP_LOGE(TAG, "Failed to read accelerometer data"); - return false; - } - - // 组合16位数据 - int16_t raw_x = (data[0] << 8) | data[1]; - int16_t raw_y = (data[2] << 8) | data[3]; - int16_t raw_z = (data[4] << 8) | data[5]; - - // 根据量程转换为g值 - float scale_factor; - switch (acce_fs_) { - case ACCE_FS_2G: scale_factor = 16384.0f; break; - case ACCE_FS_4G: scale_factor = 8192.0f; break; - case ACCE_FS_8G: scale_factor = 4096.0f; break; - case ACCE_FS_16G: scale_factor = 2048.0f; break; - default: scale_factor = 8192.0f; break; - } - - acce->acce_x = raw_x / scale_factor; - acce->acce_y = raw_y / scale_factor; - acce->acce_z = raw_z / scale_factor; - - return true; -} - -bool Mpu6050Sensor::GetGyroscope(mpu6050_gyro_value_t* gyro) { - if (!gyro) { - ESP_LOGE(TAG, "Gyroscope data pointer is null"); - return false; - } - - uint8_t data[6]; - if (!ReadRegister(0x43, data, 6)) { - ESP_LOGE(TAG, "Failed to read gyroscope data"); - return false; - } - - // 组合16位数据 - int16_t raw_x = (data[0] << 8) | data[1]; - int16_t raw_y = (data[2] << 8) | data[3]; - int16_t raw_z = (data[4] << 8) | data[5]; - - // 根据量程转换为度/秒 - float scale_factor; - switch (gyro_fs_) { - case GYRO_FS_250DPS: scale_factor = 131.0f; break; - case GYRO_FS_500DPS: scale_factor = 65.5f; break; - case GYRO_FS_1000DPS: scale_factor = 32.8f; break; - case GYRO_FS_2000DPS: scale_factor = 16.4f; break; - default: scale_factor = 65.5f; break; - } - - gyro->gyro_x = raw_x / scale_factor; - gyro->gyro_y = raw_y / scale_factor; - gyro->gyro_z = raw_z / scale_factor; - - return true; -} - -bool Mpu6050Sensor::GetTemperature(mpu6050_temp_value_t* temp) { - if (!temp) { - ESP_LOGE(TAG, "Temperature data pointer is null"); - return false; - } - - uint8_t data[2]; - if (!ReadRegister(0x41, data, 2)) { - ESP_LOGE(TAG, "Failed to read temperature data"); - return false; - } - - // 组合16位数据 - int16_t raw_temp = (data[0] << 8) | data[1]; - - // 转换为摄氏度: T = (TEMP_OUT / 340) + 36.53 - temp->temp = raw_temp / 340.0f + 36.53f; - - return true; -} - -bool Mpu6050Sensor::ComplimentaryFilter(const mpu6050_acce_value_t* acce, - const mpu6050_gyro_value_t* gyro, - complimentary_angle_t* angle) { - if (!acce || !gyro || !angle) { - ESP_LOGE(TAG, "Input pointers are null"); - return false; - } - - uint64_t current_time = GetCurrentTimeUs(); - - // 计算时间间隔 - if (last_time_ > 0) { - dt_ = (current_time - last_time_) / 1000000.0f; // 转换为秒 - } else { - dt_ = 0.01f; // 默认10ms - } - - // 从加速度计计算俯仰角和横滚角 - float accel_pitch = atan2f(acce->acce_y, sqrtf(acce->acce_x * acce->acce_x + acce->acce_z * acce->acce_z)) * 180.0f / M_PI; - float accel_roll = atan2f(-acce->acce_x, acce->acce_z) * 180.0f / M_PI; - - // 互补滤波 - angle->pitch = alpha_ * (last_angle_.pitch + gyro->gyro_x * dt_) + (1.0f - alpha_) * accel_pitch; - angle->roll = alpha_ * (last_angle_.roll + gyro->gyro_y * dt_) + (1.0f - alpha_) * accel_roll; - angle->yaw = last_angle_.yaw + gyro->gyro_z * dt_; // 偏航角只能通过陀螺仪积分 - - // 更新上次的角度和时间 - last_angle_ = *angle; - last_time_ = current_time; - - return true; -} - -std::string Mpu6050Sensor::GetStatusJson() const { - std::string json = "{"; - json += "\"initialized\":" + std::string(initialized_ ? "true" : "false") + ","; - json += "\"device_address\":" + std::to_string(device_addr_) + ","; - json += "\"accelerometer_range\":" + std::to_string(static_cast(acce_fs_)) + ","; - json += "\"gyroscope_range\":" + std::to_string(static_cast(gyro_fs_)) + ","; - json += "\"filter_alpha\":" + std::to_string(alpha_) + ","; - json += "\"sample_rate\":125"; - json += "}"; - return json; -} - -bool Mpu6050Sensor::WriteRegister(uint8_t reg_addr, uint8_t data) { - if (!device_handle_) { - return false; - } - - uint8_t write_buf[2] = {reg_addr, data}; - esp_err_t ret = i2c_master_transmit(device_handle_, write_buf, 2, 1000); - - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to write register 0x%02X: %s", reg_addr, esp_err_to_name(ret)); - return false; - } - - return true; -} - -bool Mpu6050Sensor::ReadRegister(uint8_t reg_addr, uint8_t* data, size_t len) { - if (!device_handle_ || !data) { - return false; - } - - esp_err_t ret = i2c_master_transmit_receive(device_handle_, ®_addr, 1, data, len, 1000); - - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to read register 0x%02X: %s", reg_addr, esp_err_to_name(ret)); - return false; - } - - return true; -} - -uint64_t Mpu6050Sensor::GetCurrentTimeUs() { - return esp_timer_get_time(); -} - -void Mpu6050Sensor::InitializeComplimentaryFilter() { - last_angle_.pitch = 0.0f; - last_angle_.roll = 0.0f; - last_angle_.yaw = 0.0f; - last_time_ = 0; - dt_ = 0.01f; - alpha_ = 0.98f; // 互补滤波系数,0.98表示更信任陀螺仪 -} \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/mpu6050_sensor.h b/main/boards/esp32s3-smart-speaker/mpu6050_sensor.h deleted file mode 100644 index 16e1715..0000000 --- a/main/boards/esp32s3-smart-speaker/mpu6050_sensor.h +++ /dev/null @@ -1,184 +0,0 @@ -#ifndef MPU6050_SENSOR_H -#define MPU6050_SENSOR_H - -#include -#include -#include -#include -#include - -// MPU6050相关定义 -#define MPU6050_I2C_ADDRESS 0x68 -#define MPU6050_WHO_AM_I_REG 0x75 -#define MPU6050_WHO_AM_I_VAL 0x68 - -// 加速度计量程 -typedef enum { - ACCE_FS_2G = 0, // ±2g - ACCE_FS_4G = 1, // ±4g - ACCE_FS_8G = 2, // ±8g - ACCE_FS_16G = 3 // ±16g -} mpu6050_acce_fs_t; - -// 陀螺仪量程 -typedef enum { - GYRO_FS_250DPS = 0, // ±250°/s - GYRO_FS_500DPS = 1, // ±500°/s - GYRO_FS_1000DPS = 2, // ±1000°/s - GYRO_FS_2000DPS = 3 // ±2000°/s -} mpu6050_gyro_fs_t; - -// 传感器数据结构 -typedef struct { - float acce_x; - float acce_y; - float acce_z; -} mpu6050_acce_value_t; - -typedef struct { - float gyro_x; - float gyro_y; - float gyro_z; -} mpu6050_gyro_value_t; - -typedef struct { - float temp; -} mpu6050_temp_value_t; - -typedef struct { - float pitch; - float roll; - float yaw; -} complimentary_angle_t; - -/** - * @brief MPU6050传感器封装类 - * - * 提供现代化的C++接口来操作MPU6050六轴传感器 - * 支持加速度计、陀螺仪、温度传感器和互补滤波 - */ -class Mpu6050Sensor { -public: - /** - * @brief 构造函数 - * @param i2c_bus I2C总线句柄 - * @param device_addr 设备地址,默认为0x68 - */ - explicit Mpu6050Sensor(i2c_master_bus_handle_t i2c_bus, uint8_t device_addr = MPU6050_I2C_ADDRESS); - - /** - * @brief 析构函数 - */ - ~Mpu6050Sensor(); - - /** - * @brief 初始化传感器 - * @param acce_fs 加速度计量程 - * @param gyro_fs 陀螺仪量程 - * @return true表示初始化成功,false表示失败 - */ - bool Initialize(mpu6050_acce_fs_t acce_fs = ACCE_FS_4G, mpu6050_gyro_fs_t gyro_fs = GYRO_FS_500DPS); - - /** - * @brief 唤醒传感器 - * @return true表示成功,false表示失败 - */ - bool WakeUp(); - - /** - * @brief 获取设备ID - * @param device_id 输出设备ID - * @return true表示成功,false表示失败 - */ - bool GetDeviceId(uint8_t* device_id); - - /** - * @brief 获取加速度计数据 - * @param acce 输出加速度计数据 - * @return true表示成功,false表示失败 - */ - bool GetAccelerometer(mpu6050_acce_value_t* acce); - - /** - * @brief 获取陀螺仪数据 - * @param gyro 输出陀螺仪数据 - * @return true表示成功,false表示失败 - */ - bool GetGyroscope(mpu6050_gyro_value_t* gyro); - - /** - * @brief 获取温度数据 - * @param temp 输出温度数据 - * @return true表示成功,false表示失败 - */ - bool GetTemperature(mpu6050_temp_value_t* temp); - - /** - * @brief 互补滤波计算姿态角 - * @param acce 加速度计数据 - * @param gyro 陀螺仪数据 - * @param angle 输出姿态角 - * @return true表示成功,false表示失败 - */ - bool ComplimentaryFilter(const mpu6050_acce_value_t* acce, - const mpu6050_gyro_value_t* gyro, - complimentary_angle_t* angle); - - /** - * @brief 检查传感器是否已初始化 - * @return true表示已初始化,false表示未初始化 - */ - bool IsInitialized() const { return initialized_; } - - /** - * @brief 获取传感器状态信息 - * @return JSON格式的状态信息 - */ - std::string GetStatusJson() const; - -private: - i2c_master_bus_handle_t i2c_bus_; - i2c_master_dev_handle_t device_handle_; - uint8_t device_addr_; - bool initialized_; - mpu6050_acce_fs_t acce_fs_; - mpu6050_gyro_fs_t gyro_fs_; - - // 互补滤波相关 - float dt_; - float alpha_; - complimentary_angle_t last_angle_; - uint64_t last_time_; - - static const char* TAG; - - /** - * @brief 写入寄存器 - * @param reg_addr 寄存器地址 - * @param data 数据 - * @return true表示成功,false表示失败 - */ - bool WriteRegister(uint8_t reg_addr, uint8_t data); - - /** - * @brief 读取寄存器 - * @param reg_addr 寄存器地址 - * @param data 输出数据 - * @param len 数据长度 - * @return true表示成功,false表示失败 - */ - bool ReadRegister(uint8_t reg_addr, uint8_t* data, size_t len); - - /** - * @brief 获取当前时间戳(微秒) - * @return 时间戳 - */ - uint64_t GetCurrentTimeUs(); - - /** - * @brief 初始化互补滤波 - */ - void InitializeComplimentaryFilter(); -}; - -#endif // MPU6050_SENSOR_H \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/tools_manager.cc b/main/boards/esp32s3-smart-speaker/tools_manager.cc deleted file mode 100644 index c7ba3d1..0000000 --- a/main/boards/esp32s3-smart-speaker/tools_manager.cc +++ /dev/null @@ -1,199 +0,0 @@ -#include "tools_manager.h" -#include "application.h" -#include "board.h" -#include - -#define TAG "ToolsManager" - -ToolsManager& ToolsManager::GetInstance() { - static ToolsManager instance; - return instance; -} - -bool ToolsManager::Initialize() { - if (initialized_) { - ESP_LOGW(TAG, "ToolsManager already initialized"); - return true; - } - - ESP_LOGI(TAG, "Initializing ToolsManager..."); - - // 注册各种工具 - RegisterMcpTools(); - RegisterSystemTools(); - RegisterAudioTools(); - RegisterSensorTools(); - - initialized_ = true; - ESP_LOGI(TAG, "ToolsManager initialized successfully"); - return true; -} - -void ToolsManager::RegisterMcpTools() { - ESP_LOGI(TAG, "Registering MCP tools..."); - - auto& mcp_server = McpServer::GetInstance(); - - // 系统信息查询工具 - mcp_server.AddTool( - "self.smart_speaker.get_system_info", - "获取智能音箱系统信息,包括板卡类型、版本、功能特性等", - PropertyList(), - [](const PropertyList& properties) -> ReturnValue { - auto& board = Board::GetInstance(); - return board.GetBoardJson(); - } - ); - - // 设备状态查询工具 - mcp_server.AddTool( - "self.smart_speaker.get_device_state", - "获取设备当前状态,包括启动状态、连接状态等", - PropertyList(), - [](const PropertyList& properties) -> ReturnValue { - auto& app = Application::GetInstance(); - DeviceState state = app.GetDeviceState(); - const char* state_str = "unknown"; - switch (state) { - case kDeviceStateStarting: state_str = "starting"; break; - case kDeviceStateWifiConfiguring: state_str = "configuring"; break; - case kDeviceStateIdle: state_str = "idle"; break; - case kDeviceStateConnecting: state_str = "connecting"; break; - case kDeviceStateListening: state_str = "listening"; break; - case kDeviceStateSpeaking: state_str = "speaking"; break; - case kDeviceStateUpgrading: state_str = "upgrading"; break; - case kDeviceStateActivating: state_str = "activating"; break; - case kDeviceStateAudioTesting: state_str = "audio_testing"; break; - case kDeviceStateFatalError: state_str = "fatal_error"; break; - default: state_str = "unknown"; break; - } - return std::string("{\"state\":\"") + state_str + "\"}"; - } - ); - - ESP_LOGI(TAG, "MCP tools registered successfully"); -} - -void ToolsManager::RegisterSystemTools() { - ESP_LOGI(TAG, "Registering system tools..."); - - auto& mcp_server = McpServer::GetInstance(); - - // 系统重启工具 - mcp_server.AddTool( - "self.smart_speaker.reboot", - "重启智能音箱系统", - PropertyList(), - [](const PropertyList& properties) -> ReturnValue { - auto& app = Application::GetInstance(); - app.Reboot(); - return "{\"message\":\"System reboot initiated\"}"; - } - ); - - // 设备控制工具 - mcp_server.AddTool( - "self.smart_speaker.start_listening", - "开始语音监听", - PropertyList(), - [](const PropertyList& properties) -> ReturnValue { - auto& app = Application::GetInstance(); - app.StartListening(); - return "{\"message\":\"Started listening\"}"; - } - ); - - mcp_server.AddTool( - "self.smart_speaker.stop_listening", - "停止语音监听", - PropertyList(), - [](const PropertyList& properties) -> ReturnValue { - auto& app = Application::GetInstance(); - app.StopListening(); - return "{\"message\":\"Stopped listening\"}"; - } - ); - - ESP_LOGI(TAG, "System tools registered successfully"); -} - -void ToolsManager::RegisterAudioTools() { - ESP_LOGI(TAG, "Registering audio tools..."); - - auto& mcp_server = McpServer::GetInstance(); - - // 音频播放工具 - mcp_server.AddTool( - "self.smart_speaker.play_sound", - "播放指定音效。sound: 音效名称(activation, welcome, upgrade, wificonfig等)", - PropertyList({Property("sound", kPropertyTypeString, "activation")}), - [](const PropertyList& properties) -> ReturnValue { - auto& app = Application::GetInstance(); - std::string sound = properties["sound"].value(); - app.PlaySound(sound); - return "{\"message\":\"Playing sound: " + sound + "\"}"; - } - ); - - // 语音检测状态工具 - mcp_server.AddTool( - "self.smart_speaker.is_voice_detected", - "检查是否检测到语音", - PropertyList(), - [](const PropertyList& properties) -> ReturnValue { - auto& app = Application::GetInstance(); - bool voice_detected = app.IsVoiceDetected(); - return "{\"voice_detected\":" + std::string(voice_detected ? "true" : "false") + "}"; - } - ); - - ESP_LOGI(TAG, "Audio tools registered successfully"); -} - -void ToolsManager::RegisterSensorTools() { - ESP_LOGI(TAG, "Registering sensor tools..."); - - auto& mcp_server = McpServer::GetInstance(); - - // 压感传感器读取工具 - mcp_server.AddTool( - "self.smart_speaker.get_pressure_sensor", - "获取压感传感器数据,包括当前值、ADC通道、样本数量等", - PropertyList(), - [](const PropertyList& properties) -> ReturnValue { - auto& board = Board::GetInstance(); - std::string board_json = board.GetBoardJson(); - - // 从board JSON中提取压感传感器信息 - // 这里简化处理,直接返回board信息中包含的传感器数据 - return board_json; - } - ); - - // IMU传感器状态工具 - mcp_server.AddTool( - "self.smart_speaker.get_imu_status", - "获取IMU传感器状态信息", - PropertyList(), - [](const PropertyList& properties) -> ReturnValue { - auto& board = Board::GetInstance(); - std::string board_json = board.GetBoardJson(); - - // 从board JSON中提取IMU信息 - return board_json; - } - ); - - // 传感器数据重置工具 - mcp_server.AddTool( - "self.smart_speaker.reset_sensor_data", - "重置传感器数据缓冲区", - PropertyList(), - [](const PropertyList& properties) -> ReturnValue { - // TODO: 实现传感器数据重置 - return "{\"message\":\"Sensor data reset requested\"}"; - } - ); - - ESP_LOGI(TAG, "Sensor tools registered successfully"); -} \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/tools_manager.h b/main/boards/esp32s3-smart-speaker/tools_manager.h deleted file mode 100644 index 69478a0..0000000 --- a/main/boards/esp32s3-smart-speaker/tools_manager.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef TOOLS_MANAGER_H -#define TOOLS_MANAGER_H - -#include -#include "mcp_server.h" - -class ToolsManager { -public: - static ToolsManager& GetInstance(); - - // 初始化工具系统 - bool Initialize(); - - // 工具注册方法 - void RegisterMcpTools(); - void RegisterSystemTools(); - void RegisterAudioTools(); - void RegisterSensorTools(); - - // 检查是否已初始化 - bool IsInitialized() const { return initialized_; } - -private: - ToolsManager() = default; - ~ToolsManager() = default; - ToolsManager(const ToolsManager&) = delete; - ToolsManager& operator=(const ToolsManager&) = delete; - - bool initialized_ = false; -}; - -#endif // TOOLS_MANAGER_H \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/wifi_manager.cc b/main/boards/esp32s3-smart-speaker/wifi_manager.cc deleted file mode 100644 index 38d904b..0000000 --- a/main/boards/esp32s3-smart-speaker/wifi_manager.cc +++ /dev/null @@ -1,55 +0,0 @@ -#include "wifi_manager.h" -#include "settings.h" -#include "wifi_station.h" -#include - -#define TAG "WifiManager" - -WifiManager& WifiManager::GetInstance() { - static WifiManager instance; - return instance; -} - -bool WifiManager::Initialize() { - if (initialized_) { - ESP_LOGW(TAG, "WifiManager already initialized"); - return true; - } - - ESP_LOGI(TAG, "Initializing WifiManager..."); - - // 配置WiFi设置 - ConfigureWifiSettings(); - - // 设置默认凭据 - SetDefaultCredentials(); - - initialized_ = true; - ESP_LOGI(TAG, "WifiManager initialized successfully"); - return true; -} - -void WifiManager::ConfigureWifiSettings() { - ESP_LOGI(TAG, "Configuring WiFi settings..."); - - // 配置WiFi参数到NVS - Settings wifi_settings("wifi", true); - - // 设置不记住BSSID (不区分MAC地址) - wifi_settings.SetInt("remember_bssid", 0); - - // 设置最大发射功率 - wifi_settings.SetInt("max_tx_power", 0); - - ESP_LOGI(TAG, "WiFi settings configured"); -} - -void WifiManager::SetDefaultCredentials() { - ESP_LOGI(TAG, "Setting default WiFi credentials..."); - - // 添加默认WiFi配置 - auto &wifi_station = WifiStation::GetInstance(); - wifi_station.AddAuth("xoxo", "12340000"); - - ESP_LOGI(TAG, "Default WiFi credentials added: SSID=xoxo, Password=12340000"); -} \ No newline at end of file diff --git a/main/boards/esp32s3-smart-speaker/wifi_manager.h b/main/boards/esp32s3-smart-speaker/wifi_manager.h deleted file mode 100644 index 179808c..0000000 --- a/main/boards/esp32s3-smart-speaker/wifi_manager.h +++ /dev/null @@ -1,30 +0,0 @@ - -#ifndef WIFI_MANAGER_H -#define WIFI_MANAGER_H - -#include - -class WifiManager { -public: - static WifiManager& GetInstance(); - - // 初始化WiFi系统 - bool Initialize(); - - // WiFi配置方法 - void SetDefaultCredentials(); - void ConfigureWifiSettings(); - - // 检查是否已初始化 - bool IsInitialized() const { return initialized_; } - -private: - WifiManager() = default; - ~WifiManager() = default; - WifiManager(const WifiManager&) = delete; - WifiManager& operator=(const WifiManager&) = delete; - - bool initialized_ = false; -}; - -#endif // WIFI_MANAGER_H diff --git a/main/boards/genjutech-s3-1.54tft/WEATHER_CLOCK_README.md b/main/boards/genjutech-s3-1.54tft/WEATHER_CLOCK_README.md new file mode 100644 index 0000000..12407dd --- /dev/null +++ b/main/boards/genjutech-s3-1.54tft/WEATHER_CLOCK_README.md @@ -0,0 +1,185 @@ +# 天气时钟界面说明 + +## 📋 概述 + +已成功将Arduino版本的MiniTV天气时钟UI移植到ESP-IDF LVGL环境中。新的天气时钟界面完全替换了原来的简单时钟界面,提供更丰富的信息显示。 + +## 🎨 界面布局 + +参考Arduino版本,新界面采用240x240分区布局: + +``` +┌─────────────────────────────────┐ +│ 天气信息滚动 │ 城市名称 │ 顶部 (0-34px) +├─────────────────────────────────┤ +│ │ +│ 时:分 秒 │ 中部 (35-165px) +│ (大字体) (小字体) │ +│ │ +├─────┬─────────────┬─────────────┤ +│ AQI │ 湿度图标 │ 温度图标 │ 底部上 (166-200px) +│空气 │ 💧XX% │ 🌡️XX℃ │ +├─────┼─────────────┼─────────────┤ +│周X │ XX月XX日 │ (空白) │ 底部下 (200-240px) +└─────┴─────────────┴─────────────┘ +``` + +## ✨ 新增功能 + +### 1. 天气数据显示 +- ✅ 城市名称 +- ✅ 实时温度 +- ✅ 湿度百分比 +- ✅ 空气质量指数(AQI)和等级 +- ✅ 最高/最低温度 +- ✅ 风向和风速 + +### 2. 动态信息滚动 +顶部滚动区域每2.5秒切换显示: +- 实时天气状况 +- 空气质量描述 +- 风向风速信息 +- 今日天气概况 +- 最低/最高温度 + +### 3. AQI颜色编码 +空气质量指数自动显示对应颜色: +- 🟢 优 (0-50): 绿色 +- 🟡 良 (51-100): 黄色 +- 🟠 轻度污染 (101-150): 橙色 +- 🟣 中度污染 (151-200): 紫红色 +- 🔴 重度污染 (200+): 深红色 + +### 4. 自动更新 +- ⏰ 时间:每秒更新 +- 🌤️ 天气:每10分钟更新一次 + +## 📁 新增文件 + +1. **idle_screen.h** - 天气时钟UI头文件(重写) +2. **idle_screen.cc** - 天气时钟UI实现(重写) +3. **weather_service.h** - 天气API服务头文件 +4. **weather_service.cc** - 天气API服务实现 + +## 🔧 修改文件 + +1. **genjutech-s3-1.54tft.cc** + - 添加 `SpiLcdDisplayEx` 类扩展 + - 集成天气服务 + - 添加 `InitWeatherService()` 方法 + - 自动启动天气更新任务 + +## 🌐 天气API + +使用中国天气网API: +- 城市代码自动检测:`http://wgeo.weather.com.cn/ip/` +- 天气数据获取:`http://d1.weather.com.cn/weather_index/{城市代码}.html` + +### 🎯 城市代码配置(两种方式) + +#### 方式1:自动检测(推荐)✨ + +**默认已启用**,WiFi连接后会根据IP地址自动获取所在城市! + +在 `genjutech-s3-1.54tft.cc` 的 `InitWeatherService()` 中: + +```cpp +self->weather_service_.Initialize(""); // 空字符串 = 自动检测 +``` + +**优点**: +- ✅ 无需手动配置 +- ✅ 根据实际位置显示天气 +- ✅ 设备移动到其他城市会自动适应 +- ✅ 失败时自动回退到北京 + +#### 方式2:手动指定城市代码 + +如果你想固定显示某个城市的天气,可以指定城市代码: + +```cpp +self->weather_service_.Initialize("101280601"); // 指定:深圳 +``` + +**常用城市代码:** +- 北京:`101010100` +- 上海:`101020100` +- 广州:`101280101` +- 深圳:`101280601` +- 成都:`101270101` +- 杭州:`101210101` +- 武汉:`101200101` +- 西安:`101110101` +- 青岛:`101120201` +- 南京:`101190101` +- 重庆:`101040100` +- 天津:`101030100` + +**优点**: +- ✅ 可查看其他城市天气 +- ✅ 不受网络IP地址影响 + +## 🎯 使用说明 + +### 1. 编译 +```bash +idf.py build +``` + +### 2. 烧录 +```bash +idf.py flash monitor +``` + +### 3. 配置城市 +修改 `genjutech-s3-1.54tft.cc` 中的城市代码后重新编译烧录。 + +## 🔄 与原有功能的集成 + +- ✅ 保留语音交互功能 +- ✅ 保留闹钟功能 +- ✅ 闹钟触发时在时钟界面上显示 +- ✅ 设备空闲时自动显示天气时钟 +- ✅ 语音交互时自动切换到聊天界面 + +## 🐛 故障排查 + +### 天气数据不显示 +1. 检查WiFi连接状态 +2. 检查城市代码是否正确 +3. 查看串口日志中的天气API响应 + +### 界面显示异常 +1. 确认LVGL初始化正常 +2. 检查显示锁是否正确使用 +3. 查看内存使用情况 + +## 📝 注意事项 + +1. 天气更新需要WiFi连接 +2. 首次获取天气数据需等待5秒(WiFi连接时间) +3. 天气API可能受网络状况影响 +4. 建议在WiFi稳定环境下使用 + +## 🎨 设计理念 + +- **简洁实用**:一屏显示所有关键信息 +- **视觉清晰**:分区明确,信息层次分明 +- **动态更新**:滚动显示更多天气详情 +- **色彩编码**:AQI用颜色直观表示空气质量 + +## 🚀 未来扩展 + +可能的扩展方向: +- [ ] 支持多城市切换 +- [ ] 添加未来天气预报 +- [ ] 自定义UI主题和颜色 +- [ ] 添加天气图标/动画 +- [ ] 支持更多天气数据源 + +--- + +**移植时间**: 2025-01-10 +**原始参考**: Arduino MiniTV Weather Clock by DIY攻城狮 +**实现版本**: ESP-IDF + LVGL + diff --git a/main/boards/genjutech-s3-1.54tft/config.h b/main/boards/genjutech-s3-1.54tft/config.h index 4f3aad8..1689274 100644 --- a/main/boards/genjutech-s3-1.54tft/config.h +++ b/main/boards/genjutech-s3-1.54tft/config.h @@ -1,43 +1,46 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 16000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_11 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_21 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_9 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_1 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_42 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_41 - -#define DISPLAY_SDA GPIO_NUM_3 -#define DISPLAY_SCL GPIO_NUM_4 -#define DISPLAY_DC GPIO_NUM_5 -#define DISPLAY_CS GPIO_NUM_6 -#define DISPLAY_RES GPIO_NUM_7 - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define BACKLIGHT_INVERT false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 16000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_11 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_21 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_9 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_1 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_42 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_41 + +#define DISPLAY_SDA GPIO_NUM_3 +#define DISPLAY_SCL GPIO_NUM_4 +#define DISPLAY_DC GPIO_NUM_5 +#define DISPLAY_CS GPIO_NUM_6 +#define DISPLAY_RES GPIO_NUM_7 + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define BACKLIGHT_INVERT false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +/**< use new idle screen of xiaozhi */ +#define IDLE_SCREEN_HOOK 1 + #endif // _BOARD_CONFIG_H_ \ No newline at end of file diff --git a/main/boards/genjutech-s3-1.54tft/config.json b/main/boards/genjutech-s3-1.54tft/config.json index a8c65f8..580d99a 100644 --- a/main/boards/genjutech-s3-1.54tft/config.json +++ b/main/boards/genjutech-s3-1.54tft/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "genjutech-s3-1.54tft", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "genjutech-s3-1.54tft", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc b/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc index 99794fb..cd05c7c 100644 --- a/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc +++ b/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc @@ -1,259 +1,437 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" - -#include -#include -#include -#include -#include -#include -#include -#include "power_save_timer.h" - -#include "assets/lang_config.h" -#include "power_manager.h" - -#define TAG "GenJuTech_s3_1_54TFT" - -class SparkBotEs8311AudioCodec : public Es8311AudioCodec { - private: - - public: - SparkBotEs8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true) - : Es8311AudioCodec(i2c_master_handle, i2c_port, input_sample_rate, output_sample_rate, - mclk, bclk, ws, dout, din,pa_pin, es8311_addr, use_mclk = true) {} - - void EnableOutput(bool enable) override { - if (enable == output_enabled_) { - return; - } - if (enable) { - Es8311AudioCodec::EnableOutput(enable); - } else { - // Nothing todo because the display io and PA io conflict - } - } - }; - -class GenJuTech_s3_1_54TFT : public WifiBoard { -private: - // i2c_master_bus_handle_t display_i2c_bus_; - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - LcdDisplay* display_; - i2c_master_bus_handle_t codec_i2c_bus_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_16); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - // 第一个参数不为 -1 时,进入睡眠会关闭音频输入 - power_save_timer_ = new PowerSaveTimer(240, 60); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SDA; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCL; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - // boot_button_.OnPressDown([this]() { - // Application::GetInstance().StartListening(); - // }); - // boot_button_.OnPressUp([this]() { - // Application::GetInstance().StopListening(); - // }); - - volume_up_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - - void InitializeSt7789Display() { - gpio_config_t config = { - .pin_bit_mask = (1ULL << DISPLAY_RES), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - ESP_ERROR_CHECK(gpio_config(&config)); - gpio_set_level(DISPLAY_RES, 0); - vTaskDelay(20); - gpio_set_level(DISPLAY_RES, 1); - - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS; - io_config.dc_gpio_num = DISPLAY_DC; - io_config.spi_mode = 3; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RES; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - GenJuTech_s3_1_54TFT() : - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - ESP_LOGI(TAG, "Initializing GenJuTech S3 1.54 Board"); - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeCodecI2c(); - InitializeSpi(); - InitializeButtons(); - InitializeSt7789Display(); - GetBacklight()->RestoreBrightness(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static SparkBotEs8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display *GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(GenJuTech_s3_1_54TFT); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" + +#include +#include +#include +#include +#include +#include +#include +#include "power_save_timer.h" + +#include "assets/lang_config.h" +#include "power_manager.h" +#include "alarm_manager.h" // 用于检测和停止闹钟 + +#if IDLE_SCREEN_HOOK +#include "idle_screen.h" +#include "weather_service.h" +#endif + +#define TAG "GenJuTech_s3_1_54TFT" + +#if IDLE_SCREEN_HOOK +LV_FONT_DECLARE(font_puhui_20_4); + +// Extended SpiLcdDisplay with idle screen support +class SpiLcdDisplayEx : public SpiLcdDisplay { +public: + SpiLcdDisplayEx(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, + bool mirror_x, bool mirror_y, bool swap_xy) : + SpiLcdDisplay(panel_io, panel, + width, height, offset_x, offset_y, + mirror_x, mirror_y, swap_xy) { + DisplayLockGuard lock(this); + lv_obj_set_style_pad_left(status_bar_, 20, 0); + lv_obj_set_style_pad_right(status_bar_, 20, 0); + } + + virtual void OnStateChanged() override { + DisplayLockGuard lock(this); + auto& app = Application::GetInstance(); + auto device_state = app.GetDeviceState(); + switch (device_state) { + case kDeviceStateIdle: + ESP_LOGI(TAG, "hide xiaozhi, show idle screen"); + if (!lv_obj_has_flag(container_, LV_OBJ_FLAG_HIDDEN)) { + lv_obj_add_flag(container_, LV_OBJ_FLAG_HIDDEN); + } + _lcdScnIdle.ui_showScreen(true); + break; + + case kDeviceStateListening: + case kDeviceStateConnecting: + case kDeviceStateSpeaking: + ESP_LOGI(TAG, "show xiaozhi, hide idle screen"); + _lcdScnIdle.ui_showScreen(false); + if (lv_obj_has_flag(container_, LV_OBJ_FLAG_HIDDEN)) { + lv_obj_clear_flag(container_, LV_OBJ_FLAG_HIDDEN); + } + break; + + default: + break; + } + } + + virtual void OnClockTimer() override { + DisplayLockGuard lock(this); + _lcdScnIdle.ui_update(); // update screen every 1s + } + + void IdleScrSetupUi() { + DisplayLockGuard lock(this); // ← 必须加锁!LVGL不是线程安全的 + ESP_LOGI(TAG, "IdleScrSetupUi()"); + // Get ThemeColors from current theme + ThemeColors theme_colors; + theme_colors.background = lv_color_hex(0x000000); + theme_colors.text = lv_color_hex(0xFFFFFF); + theme_colors.border = lv_color_hex(0x444444); + theme_colors.chat_background = lv_color_hex(0x111111); + theme_colors.user_bubble = lv_color_hex(0x0078D4); + theme_colors.assistant_bubble = lv_color_hex(0x2D2D2D); + theme_colors.system_bubble = lv_color_hex(0x1A1A1A); + theme_colors.system_text = lv_color_hex(0xFFFFFF); + theme_colors.low_battery = lv_color_hex(0xFF0000); + + _lcdScnIdle.ui_init(&theme_colors); + } + + void UpdateTheme() { + DisplayLockGuard lock(this); // ← 必须加锁! + ThemeColors theme_colors; + theme_colors.background = lv_color_hex(0x000000); + theme_colors.text = lv_color_hex(0xFFFFFF); + theme_colors.border = lv_color_hex(0x444444); + theme_colors.chat_background = lv_color_hex(0x111111); + theme_colors.user_bubble = lv_color_hex(0x0078D4); + theme_colors.assistant_bubble = lv_color_hex(0x2D2D2D); + theme_colors.system_bubble = lv_color_hex(0x1A1A1A); + theme_colors.system_text = lv_color_hex(0xFFFFFF); + theme_colors.low_battery = lv_color_hex(0xFF0000); + + _lcdScnIdle.ui_updateTheme(&theme_colors); + } + + // Override alarm display methods + virtual void ShowAlarmOnIdleScreen(const char* alarm_message) override { + DisplayLockGuard lock(this); + ESP_LOGI(TAG, "ShowAlarmOnIdleScreen: %s", alarm_message); + _lcdScnIdle.ui_showAlarmInfo(alarm_message); + + // Make sure idle screen is visible + if (!_lcdScnIdle.ui_shown) { + _lcdScnIdle.ui_showScreen(true); + } + + // Hide xiaozhi interface + if (!lv_obj_has_flag(container_, LV_OBJ_FLAG_HIDDEN)) { + lv_obj_add_flag(container_, LV_OBJ_FLAG_HIDDEN); + } + } + + virtual void HideAlarmOnIdleScreen() override { + DisplayLockGuard lock(this); + ESP_LOGI(TAG, "HideAlarmOnIdleScreen"); + _lcdScnIdle.ui_hideAlarmInfo(); + } + + void InitWeatherService() { + ESP_LOGI(TAG, "Initializing weather service"); + + // Start weather update task + xTaskCreate([](void* param) { + auto* self = static_cast(param); + ESP_LOGI(TAG, "Weather update task started"); + + // Wait 5 seconds for WiFi to connect + vTaskDelay(pdMS_TO_TICKS(5000)); + + // Auto-detect city code by IP (leave empty string for auto-detect) + // Or specify a city code like "101010100" for Beijing + self->weather_service_.Initialize(""); // Empty = auto-detect + + // Set callback to update UI when weather data is received + self->weather_service_.SetWeatherCallback([self](const WeatherData& weather) { + DisplayLockGuard lock(self); + self->_lcdScnIdle.ui_updateWeather(weather); + }); + + // Fetch weather immediately after initialization + self->weather_service_.FetchWeather(); + + // Continue updating weather every 10 minutes + while (true) { + vTaskDelay(pdMS_TO_TICKS(600000)); // 10 minutes + self->weather_service_.FetchWeather(); + } + }, "weather_task", 8192, this, 5, NULL); // Increased stack size for HTTP operations + } + +private: + IdleScreen _lcdScnIdle; + WeatherService weather_service_; +}; +#endif // IDLE_SCREEN_HOOK + +class SparkBotEs8311AudioCodec : public Es8311AudioCodec { + private: + + public: + SparkBotEs8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true) + : Es8311AudioCodec(i2c_master_handle, i2c_port, input_sample_rate, output_sample_rate, + mclk, bclk, ws, dout, din,pa_pin, es8311_addr, use_mclk = true) {} + + void EnableOutput(bool enable) override { + if (enable == output_enabled_) { + return; + } + if (enable) { + Es8311AudioCodec::EnableOutput(enable); + } else { + // Nothing todo because the display io and PA io conflict + } + } + }; + +class GenJuTech_s3_1_54TFT : public WifiBoard { +private: + // i2c_master_bus_handle_t display_i2c_bus_; + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; +#if IDLE_SCREEN_HOOK + SpiLcdDisplayEx* display_; +#else + LcdDisplay* display_; +#endif + i2c_master_bus_handle_t codec_i2c_bus_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_16); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + // 第一个参数不为 -1 时,进入睡眠会关闭音频输入 + power_save_timer_ = new PowerSaveTimer(240, 60); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SDA; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCL; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + + // 如果有闹钟正在播放,优先停止闹钟,而不是切换界面 + auto& alarm_manager = AlarmManager::GetInstance(); + auto active_alarms = alarm_manager.GetActiveAlarms(); + if (!active_alarms.empty()) { + ESP_LOGI(TAG, "Boot button pressed during alarm, stopping alarm"); + for (const auto& alarm : active_alarms) { + alarm_manager.StopAlarm(alarm.id); + } + return; // 不切换界面 + } + + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + // boot_button_.OnPressDown([this]() { + // Application::GetInstance().StartListening(); + // }); + // boot_button_.OnPressUp([this]() { + // Application::GetInstance().StopListening(); + // }); + + volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + + void InitializeSt7789Display() { + gpio_config_t config = { + .pin_bit_mask = (1ULL << DISPLAY_RES), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_ERROR_CHECK(gpio_config(&config)); + gpio_set_level(DISPLAY_RES, 0); + vTaskDelay(20); + gpio_set_level(DISPLAY_RES, 1); + + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS; + io_config.dc_gpio_num = DISPLAY_DC; + io_config.spi_mode = 3; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RES; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); + +#if IDLE_SCREEN_HOOK + display_ = new SpiLcdDisplayEx(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); +#else + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); +#endif + } + +public: + GenJuTech_s3_1_54TFT() : + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + ESP_LOGI(TAG, "Initializing GenJuTech S3 1.54 Board"); + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeCodecI2c(); + InitializeSpi(); + InitializeButtons(); + InitializeSt7789Display(); + GetBacklight()->RestoreBrightness(); + +#if IDLE_SCREEN_HOOK + auto* display_ex = static_cast(display_); + display_ex->IdleScrSetupUi(); + display_ex->InitWeatherService(); +#endif + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static SparkBotEs8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display *GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(GenJuTech_s3_1_54TFT); diff --git a/main/boards/genjutech-s3-1.54tft/idle_screen.cc b/main/boards/genjutech-s3-1.54tft/idle_screen.cc new file mode 100644 index 0000000..8d0452f --- /dev/null +++ b/main/boards/genjutech-s3-1.54tft/idle_screen.cc @@ -0,0 +1,456 @@ +/** + * @file idle_screen.cc + * @brief Weather Clock Idle Screen Implementation + * @version 2.0 + */ + +#include "config.h" +#include "application.h" +#include "idle_screen.h" +#include "ui_helpers.h" +#include "display/lcd_display.h" +#include +#include +#include + +#if IDLE_SCREEN_HOOK + +LV_FONT_DECLARE(font_puhui_20_4); +LV_FONT_DECLARE(ui_font_font48Seg); +LV_IMG_DECLARE(ui_img_xiaozhi_48_png); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" + +static const char *TAG = "WeatherClock"; + +// Helper function to format time +static void get_time_string(char* hour_min_buf, char* second_buf, char* week_buf, char* date_buf) { + time_t now; + struct tm timeinfo; + time(&now); + localtime_r(&now, &timeinfo); + + // Format hour:minute (HH:MM) + snprintf(hour_min_buf, 16, "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min); + + // Format second (SS) + snprintf(second_buf, 8, "%02d", timeinfo.tm_sec); + + // Format week (周X) + const char* week_names[] = {"日", "一", "二", "三", "四", "五", "六"}; + snprintf(week_buf, 16, "周%s", week_names[timeinfo.tm_wday]); + + // Format date (MM月DD日) + snprintf(date_buf, 32, "%02d月%02d日", timeinfo.tm_mon + 1, timeinfo.tm_mday); +} + +IdleScreen::IdleScreen() { + ui_screen = NULL; + ui_main_container = NULL; + ui_scroll_container = NULL; + ui_scroll_label = NULL; + ui_city_label = NULL; + ui_time_container = NULL; + ui_time_hour_min = NULL; + ui_time_second = NULL; + ui_xiaozhi_icon = NULL; + ui_info_container = NULL; + ui_aqi_container = NULL; + ui_aqi_label = NULL; + ui_temp_label = NULL; + ui_temp_icon_label = NULL; + ui_humid_label = NULL; + ui_humid_icon_label = NULL; + ui_date_container = NULL; + ui_week_label = NULL; + ui_date_label = NULL; + ui_alarm_info_label = NULL; + + ui_shown = false; + current_scroll_index = 0; + last_scroll_time = 0; + p_theme = NULL; +} + +IdleScreen::~IdleScreen() { + ui_destroy(); +} + +void IdleScreen::ui_init(ThemeColors *p_current_theme) { + auto screen = lv_screen_active(); + p_theme = p_current_theme; + + ESP_LOGI(TAG, "Initializing weather clock UI"); + + // Main screen container + ui_screen = lv_obj_create(screen); + lv_obj_remove_flag(ui_screen, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(ui_screen, LV_HOR_RES, LV_VER_RES); + lv_obj_set_style_bg_color(ui_screen, lv_color_hex(0xFFFFFF), 0); + lv_obj_set_style_border_width(ui_screen, 0, 0); + lv_obj_set_style_pad_all(ui_screen, 0, 0); + + // Main container + ui_main_container = lv_obj_create(ui_screen); + lv_obj_remove_style_all(ui_main_container); + lv_obj_set_size(ui_main_container, 240, 240); + lv_obj_set_align(ui_main_container, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_main_container, (lv_obj_flag_t)(LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE)); + lv_obj_set_style_bg_color(ui_main_container, lv_color_hex(0xFFFFFF), 0); + lv_obj_set_style_bg_opa(ui_main_container, LV_OPA_COVER, 0); + + createTopSection(); + createMiddleSection(); + createBottomSection(); + + // Alarm info label (initially hidden) + ui_alarm_info_label = lv_label_create(ui_main_container); + lv_obj_set_width(ui_alarm_info_label, 220); + lv_obj_set_height(ui_alarm_info_label, LV_SIZE_CONTENT); + lv_obj_set_pos(ui_alarm_info_label, 10, 80); + lv_label_set_long_mode(ui_alarm_info_label, LV_LABEL_LONG_WRAP); + lv_label_set_text(ui_alarm_info_label, ""); + lv_obj_set_style_text_align(ui_alarm_info_label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_font(ui_alarm_info_label, &font_puhui_20_4, 0); + lv_obj_set_style_text_color(ui_alarm_info_label, lv_color_hex(0xFF0000), 0); + lv_obj_add_flag(ui_alarm_info_label, LV_OBJ_FLAG_HIDDEN); + + // Draw divider lines + static lv_point_precise_t line_points1[] = {{0, 34}, {240, 34}}; + static lv_point_precise_t line_points2[] = {{150, 0}, {150, 34}}; + static lv_point_precise_t line_points3[] = {{0, 166}, {240, 166}}; + static lv_point_precise_t line_points4[] = {{60, 166}, {60, 200}}; + static lv_point_precise_t line_points5[] = {{160, 166}, {160, 200}}; + + lv_obj_t* line1 = lv_line_create(ui_main_container); + lv_line_set_points(line1, line_points1, 2); + lv_obj_set_style_line_color(line1, lv_color_hex(0x000000), 0); + lv_obj_set_style_line_width(line1, 1, 0); + + lv_obj_t* line2 = lv_line_create(ui_main_container); + lv_line_set_points(line2, line_points2, 2); + lv_obj_set_style_line_color(line2, lv_color_hex(0x000000), 0); + lv_obj_set_style_line_width(line2, 1, 0); + + lv_obj_t* line3 = lv_line_create(ui_main_container); + lv_line_set_points(line3, line_points3, 2); + lv_obj_set_style_line_color(line3, lv_color_hex(0x000000), 0); + lv_obj_set_style_line_width(line3, 1, 0); + + lv_obj_t* line4 = lv_line_create(ui_main_container); + lv_line_set_points(line4, line_points4, 2); + lv_obj_set_style_line_color(line4, lv_color_hex(0x000000), 0); + lv_obj_set_style_line_width(line4, 1, 0); + + lv_obj_t* line5 = lv_line_create(ui_main_container); + lv_line_set_points(line5, line_points5, 2); + lv_obj_set_style_line_color(line5, lv_color_hex(0x000000), 0); + lv_obj_set_style_line_width(line5, 1, 0); + + // Hide by default + lv_obj_add_flag(ui_screen, LV_OBJ_FLAG_HIDDEN); + + ESP_LOGI(TAG, "Weather clock UI initialized"); +} + +void IdleScreen::createTopSection() { + // Top section container (0-34px height) + ui_scroll_container = lv_obj_create(ui_main_container); + lv_obj_remove_style_all(ui_scroll_container); + lv_obj_set_size(ui_scroll_container, 148, 32); + lv_obj_set_pos(ui_scroll_container, 2, 2); + lv_obj_remove_flag(ui_scroll_container, LV_OBJ_FLAG_SCROLLABLE); + + // Scrolling weather info label + ui_scroll_label = lv_label_create(ui_scroll_container); + lv_obj_set_width(ui_scroll_label, 144); + lv_label_set_long_mode(ui_scroll_label, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_label_set_text(ui_scroll_label, "正在获取天气信息..."); + lv_obj_set_style_text_font(ui_scroll_label, &font_puhui_20_4, 0); + lv_obj_set_style_text_color(ui_scroll_label, lv_color_hex(0x000000), 0); + lv_obj_align(ui_scroll_label, LV_ALIGN_LEFT_MID, 0, 0); + + // City name label + ui_city_label = lv_label_create(ui_main_container); + lv_obj_set_width(ui_city_label, 88); + lv_label_set_text(ui_city_label, "北京"); + lv_obj_set_style_text_font(ui_city_label, &font_puhui_20_4, 0); + lv_obj_set_style_text_color(ui_city_label, lv_color_hex(0x000000), 0); + lv_obj_set_style_text_align(ui_city_label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_pos(ui_city_label, 152, 8); +} + +void IdleScreen::createMiddleSection() { + // Middle section container (35-165px, height 130px) + ui_time_container = lv_obj_create(ui_main_container); + lv_obj_remove_style_all(ui_time_container); + lv_obj_set_size(ui_time_container, 240, 130); + lv_obj_set_pos(ui_time_container, 0, 35); + lv_obj_remove_flag(ui_time_container, LV_OBJ_FLAG_SCROLLABLE); + + // Large time display (HH:MM) - using ui_font_font48Seg (already in project) + ui_time_hour_min = lv_label_create(ui_time_container); + lv_label_set_text(ui_time_hour_min, "12:34"); + lv_obj_set_style_text_font(ui_time_hour_min, &ui_font_font48Seg, 0); + lv_obj_set_style_text_color(ui_time_hour_min, lv_color_hex(0x000000), 0); + lv_obj_align(ui_time_hour_min, LV_ALIGN_CENTER, -20, -25); + + // Small second display (SS) + ui_time_second = lv_label_create(ui_time_container); + lv_label_set_text(ui_time_second, "56"); + lv_obj_set_style_text_font(ui_time_second, &font_puhui_20_4, 0); + lv_obj_set_style_text_color(ui_time_second, lv_color_hex(0x000000), 0); + lv_obj_align(ui_time_second, LV_ALIGN_CENTER, 65, -25); + + // Rotating Xiaozhi icon (位置在时间下方) + ui_xiaozhi_icon = lv_img_create(ui_time_container); + lv_img_set_src(ui_xiaozhi_icon, &ui_img_xiaozhi_48_png); + lv_obj_align(ui_xiaozhi_icon, LV_ALIGN_CENTER, 0, 35); + lv_img_set_pivot(ui_xiaozhi_icon, 24, 24); // 设置旋转中心点 (48/2 = 24) + + // Add rotation animation (使用局部变量,LVGL会自动管理) + lv_anim_t anim; + lv_anim_init(&anim); + lv_anim_set_var(&anim, ui_xiaozhi_icon); + lv_anim_set_exec_cb(&anim, (lv_anim_exec_xcb_t)lv_img_set_angle); + lv_anim_set_values(&anim, 0, 3600); // 0-360度 (LVGL使用0.1度单位) + lv_anim_set_time(&anim, 3000); // 3秒转一圈 + lv_anim_set_repeat_count(&anim, LV_ANIM_REPEAT_INFINITE); + lv_anim_start(&anim); + + ESP_LOGI(TAG, "Xiaozhi rotation animation started"); +} + +void IdleScreen::createBottomSection() { + // Info container (166-200px, height 34px) + ui_info_container = lv_obj_create(ui_main_container); + lv_obj_remove_style_all(ui_info_container); + lv_obj_set_size(ui_info_container, 240, 34); + lv_obj_set_pos(ui_info_container, 0, 166); + lv_obj_remove_flag(ui_info_container, LV_OBJ_FLAG_SCROLLABLE); + + // AQI container (0-60px) + ui_aqi_container = lv_obj_create(ui_info_container); + lv_obj_set_size(ui_aqi_container, 50, 24); + lv_obj_set_pos(ui_aqi_container, 5, 5); + lv_obj_set_style_radius(ui_aqi_container, 4, 0); + lv_obj_set_style_bg_color(ui_aqi_container, lv_color_hex(0x9CCA7F), 0); // Default: 优 + lv_obj_set_style_bg_opa(ui_aqi_container, LV_OPA_COVER, 0); + lv_obj_set_style_border_width(ui_aqi_container, 0, 0); + lv_obj_set_style_pad_all(ui_aqi_container, 0, 0); + + ui_aqi_label = lv_label_create(ui_aqi_container); + lv_label_set_text(ui_aqi_label, "优"); + lv_obj_set_style_text_font(ui_aqi_label, &font_puhui_20_4, 0); + lv_obj_set_style_text_color(ui_aqi_label, lv_color_hex(0xFFFFFF), 0); + lv_obj_center(ui_aqi_label); + + // Humidity icon and label (middle section) + ui_humid_icon_label = lv_label_create(ui_info_container); + lv_label_set_text(ui_humid_icon_label, "湿"); // 湿度 + lv_obj_set_style_text_font(ui_humid_icon_label, &font_puhui_20_4, 0); + lv_obj_set_style_text_color(ui_humid_icon_label, lv_color_hex(0x0000FF), 0); + lv_obj_set_pos(ui_humid_icon_label, 85, 8); + + ui_humid_label = lv_label_create(ui_info_container); + lv_label_set_text(ui_humid_label, "65%"); + lv_obj_set_style_text_font(ui_humid_label, &font_puhui_20_4, 0); + lv_obj_set_style_text_color(ui_humid_label, lv_color_hex(0x000000), 0); + lv_obj_set_pos(ui_humid_label, 110, 8); + + // Temperature icon and label (160-240px) + ui_temp_icon_label = lv_label_create(ui_info_container); + lv_label_set_text(ui_temp_icon_label, "温"); // 温度 + lv_obj_set_style_text_font(ui_temp_icon_label, &font_puhui_20_4, 0); + lv_obj_set_style_text_color(ui_temp_icon_label, lv_color_hex(0xFF0000), 0); + lv_obj_set_pos(ui_temp_icon_label, 162, 8); + + ui_temp_label = lv_label_create(ui_info_container); + lv_label_set_text(ui_temp_label, "25℃"); + lv_obj_set_style_text_font(ui_temp_label, &font_puhui_20_4, 0); + lv_obj_set_style_text_color(ui_temp_label, lv_color_hex(0x000000), 0); + lv_obj_set_pos(ui_temp_label, 182, 8); + + // Date container (200-240px, height 40px) + ui_date_container = lv_obj_create(ui_main_container); + lv_obj_remove_style_all(ui_date_container); + lv_obj_set_size(ui_date_container, 240, 34); + lv_obj_set_pos(ui_date_container, 0, 200); + lv_obj_remove_flag(ui_date_container, LV_OBJ_FLAG_SCROLLABLE); + + // Week label (0-60px) + ui_week_label = lv_label_create(ui_date_container); + lv_label_set_text(ui_week_label, "周一"); + lv_obj_set_style_text_font(ui_week_label, &font_puhui_20_4, 0); + lv_obj_set_style_text_color(ui_week_label, lv_color_hex(0x000000), 0); + lv_obj_set_style_text_align(ui_week_label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_pos(ui_week_label, 5, 8); + + // Date label (61-160px) + ui_date_label = lv_label_create(ui_date_container); + lv_label_set_text(ui_date_label, "01月01日"); + lv_obj_set_style_text_font(ui_date_label, &font_puhui_20_4, 0); + lv_obj_set_style_text_color(ui_date_label, lv_color_hex(0x000000), 0); + lv_obj_set_style_text_align(ui_date_label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_pos(ui_date_label, 70, 8); +} + +void IdleScreen::ui_destroy() { + if (ui_screen) { + // Stop animation before deleting objects + if (ui_xiaozhi_icon) { + lv_anim_delete(ui_xiaozhi_icon, NULL); + } + + lv_obj_delete(ui_screen); + ui_screen = NULL; + } +} + +void IdleScreen::ui_showScreen(bool showIt) { + if (!ui_screen) return; + + if (showIt) { + lv_obj_remove_flag(ui_screen, LV_OBJ_FLAG_HIDDEN); + ui_shown = true; + } else { + lv_obj_add_flag(ui_screen, LV_OBJ_FLAG_HIDDEN); + ui_shown = false; + } +} + +void IdleScreen::ui_update() { + if (!ui_screen || !ui_shown) return; + + // Update time display + char hour_min_buf[16], second_buf[8], week_buf[16], date_buf[32]; + get_time_string(hour_min_buf, second_buf, week_buf, date_buf); + + if (ui_time_hour_min) { + lv_label_set_text(ui_time_hour_min, hour_min_buf); + } + + if (ui_time_second) { + lv_label_set_text(ui_time_second, second_buf); + } + + if (ui_week_label) { + lv_label_set_text(ui_week_label, week_buf); + } + + if (ui_date_label) { + lv_label_set_text(ui_date_label, date_buf); + } + + // Update scroll text every 2.5 seconds + updateScrollText(); +} + +void IdleScreen::updateScrollText() { + if (scroll_texts.empty()) return; + + uint32_t current_time = lv_tick_get(); + if (current_time - last_scroll_time > 2500) { // 2.5 seconds + current_scroll_index = (current_scroll_index + 1) % scroll_texts.size(); + if (ui_scroll_label) { + lv_label_set_text(ui_scroll_label, scroll_texts[current_scroll_index].c_str()); + } + last_scroll_time = current_time; + } +} + +void IdleScreen::ui_updateTheme(ThemeColors *p_current_theme) { + p_theme = p_current_theme; + // Weather clock uses fixed white background and black text + // Theme colors are not applied to maintain Arduino design +} + +void IdleScreen::ui_updateWeather(const WeatherData& weather) { + ESP_LOGI(TAG, "Updating weather data"); + + // Update city name + if (ui_city_label) { + lv_label_set_text(ui_city_label, weather.city_name.c_str()); + } + + // Update temperature + if (ui_temp_label) { + std::string temp_text = weather.temperature + "℃"; + lv_label_set_text(ui_temp_label, temp_text.c_str()); + } + + // Update humidity + if (ui_humid_label) { + lv_label_set_text(ui_humid_label, weather.humidity.c_str()); + } + + // Update AQI + if (ui_aqi_label) { + lv_label_set_text(ui_aqi_label, weather.aqi_desc.c_str()); + updateAQIColor(weather.aqi); + } + + // Update scroll texts + std::vector texts; + texts.push_back("实时天气 " + weather.weather_desc); + texts.push_back("空气质量 " + weather.aqi_desc); + texts.push_back("风向 " + weather.wind_direction + weather.wind_speed); + texts.push_back("今日天气 " + weather.weather_desc); + texts.push_back("最低温度 " + weather.temp_low + "℃"); + texts.push_back("最高温度 " + weather.temp_high + "℃"); + ui_setScrollText(texts); +} + +void IdleScreen::ui_setScrollText(const std::vector& texts) { + scroll_texts = texts; + current_scroll_index = 0; + last_scroll_time = lv_tick_get(); + + if (!texts.empty() && ui_scroll_label) { + lv_label_set_text(ui_scroll_label, texts[0].c_str()); + } +} + +void IdleScreen::updateAQIColor(int aqi) { + if (!ui_aqi_container) return; + + lv_color_t color; + if (aqi > 200) { + // 重度污染 - 深红色 + color = lv_color_hex(0x880B20); + } else if (aqi > 150) { + // 中度污染 - 紫红色 + color = lv_color_hex(0xBA3779); + } else if (aqi > 100) { + // 轻度污染 - 橙色 + color = lv_color_hex(0xF29F39); + } else if (aqi > 50) { + // 良 - 黄色 + color = lv_color_hex(0xF7DB64); + } else { + // 优 - 绿色 + color = lv_color_hex(0x9CCA7F); + } + + lv_obj_set_style_bg_color(ui_aqi_container, color, 0); +} + +void IdleScreen::ui_showAlarmInfo(const char* alarm_message) { + if (!ui_alarm_info_label) return; + + ESP_LOGI(TAG, "Showing alarm info: %s", alarm_message); + lv_label_set_text(ui_alarm_info_label, alarm_message); + lv_obj_remove_flag(ui_alarm_info_label, LV_OBJ_FLAG_HIDDEN); +} + +void IdleScreen::ui_hideAlarmInfo() { + if (!ui_alarm_info_label) return; + + ESP_LOGI(TAG, "Hiding alarm info"); + lv_obj_add_flag(ui_alarm_info_label, LV_OBJ_FLAG_HIDDEN); +} + +#pragma GCC diagnostic pop + +#endif // IDLE_SCREEN_HOOK diff --git a/main/boards/genjutech-s3-1.54tft/idle_screen.h b/main/boards/genjutech-s3-1.54tft/idle_screen.h new file mode 100644 index 0000000..83c101a --- /dev/null +++ b/main/boards/genjutech-s3-1.54tft/idle_screen.h @@ -0,0 +1,118 @@ +/** + * @file idle_screen.h + * @brief Weather Clock Idle Screen for GenJuTech S3 1.54TFT + * @brief Inspired by Arduino MiniTV weather clock project + * @version 2.0 + * @date 2025-01-10 + */ +#pragma once + +#include "config.h" +#if IDLE_SCREEN_HOOK + +#include +#include +#include + +// Theme color structure (copied from lcd_display.h to avoid circular dependency) +struct ThemeColors { + lv_color_t background; + lv_color_t text; + lv_color_t chat_background; + lv_color_t user_bubble; + lv_color_t assistant_bubble; + lv_color_t system_bubble; + lv_color_t system_text; + lv_color_t border; + lv_color_t low_battery; +}; + +// Weather data structure +struct WeatherData { + std::string city_name; // 城市名称 + std::string temperature; // 当前温度 + std::string humidity; // 湿度 + std::string weather_desc; // 天气描述 + std::string wind_direction; // 风向 + std::string wind_speed; // 风速 + int aqi; // 空气质量指数 + std::string aqi_desc; // 空气质量描述 + std::string temp_low; // 最低温度 + std::string temp_high; // 最高温度 + uint32_t last_update_time; // 最后更新时间 +}; + +class IdleScreen +{ +public: + IdleScreen(); + ~IdleScreen(); + +public: + void ui_init(ThemeColors *p_current_theme); + void ui_destroy(); + void ui_showScreen(bool showIt); + void ui_update(); // Called every second to update time + void ui_updateTheme(ThemeColors *p_current_theme); + + // Weather related methods + void ui_updateWeather(const WeatherData& weather); + void ui_setScrollText(const std::vector& texts); + + // Alarm display methods + void ui_showAlarmInfo(const char* alarm_message); + void ui_hideAlarmInfo(); + +public: + bool ui_shown; // UI shown or not + +private: + void createTopSection(); // 顶部:滚动信息 + 城市 + void createMiddleSection(); // 中间:时间显示区域 + void createBottomSection(); // 底部:温湿度 + 日期 + void updateScrollText(); // 更新滚动文字 + void updateAQIColor(int aqi); // 更新空气质量颜色 + +private: + // Main containers + lv_obj_t* ui_screen; + lv_obj_t* ui_main_container; + + // Top section - Weather scroll and city + lv_obj_t* ui_scroll_container; + lv_obj_t* ui_scroll_label; + lv_obj_t* ui_city_label; + + // Middle section - Time display + lv_obj_t* ui_time_container; + lv_obj_t* ui_time_hour_min; // 时:分 (大字体) + lv_obj_t* ui_time_second; // 秒 (小字体) + lv_obj_t* ui_xiaozhi_icon; // 旋转小智图标 + + // Bottom upper section - AQI, Temperature, Humidity + lv_obj_t* ui_info_container; + lv_obj_t* ui_aqi_container; + lv_obj_t* ui_aqi_label; + lv_obj_t* ui_temp_label; + lv_obj_t* ui_temp_icon_label; + lv_obj_t* ui_humid_label; + lv_obj_t* ui_humid_icon_label; + + // Bottom lower section - Week and Date + lv_obj_t* ui_date_container; + lv_obj_t* ui_week_label; + lv_obj_t* ui_date_label; + + // Alarm info (overlays on screen when alarm triggers) + lv_obj_t* ui_alarm_info_label; + + // Scroll text management + std::vector scroll_texts; + int current_scroll_index; + uint32_t last_scroll_time; + + // Theme + ThemeColors* p_theme; +}; + +#endif // IDLE_SCREEN_HOOK diff --git a/main/boards/genjutech-s3-1.54tft/power_manager.h b/main/boards/genjutech-s3-1.54tft/power_manager.h index f2e385d..689dd68 100644 --- a/main/boards/genjutech-s3-1.54tft/power_manager.h +++ b/main/boards/genjutech-s3-1.54tft/power_manager.h @@ -1,186 +1,186 @@ -#pragma once -#include -#include - -#include -#include -#include - - -class PowerManager { -private: - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 1; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_4, &adc_value)); - - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += value; - } - average_adc /= adc_values_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {1280, 0}, - {1334, 20}, - {1388, 40}, - {1442, 60}, - {1496, 80}, - {1550, 100} - }; - - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_2, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_4, &chan_config)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include + +#include +#include +#include + + +class PowerManager { +private: + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 1; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_4, &adc_value)); + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {1280, 0}, + {1334, 20}, + {1388, 40}, + {1442, 60}, + {1496, 80}, + {1550, 100} + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_2, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_4, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/genjutech-s3-1.54tft/ui_font_font48Seg.c b/main/boards/genjutech-s3-1.54tft/ui_font_font48Seg.c new file mode 100644 index 0000000..f39562a --- /dev/null +++ b/main/boards/genjutech-s3-1.54tft/ui_font_font48Seg.c @@ -0,0 +1,3545 @@ +/******************************************************************************* + * Size: 48 px + * Bpp: 4 + * Opts: --bpp 4 --size 48 --font D:/Temp/HelloWorld/work/iCar/ai/sls_0/assets/font/ds-digital.ttf -o D:/Temp/HelloWorld/work/iCar/ai/sls_0/assets/font\ui_font_font48Seg.c --format lvgl -r 0x20-0x7f --no-compress --no-prefilter + ******************************************************************************/ + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl.h" +#endif + +#include "config.h" +#if IDLE_SCREEN_HOOK + +#ifndef UI_FONT_FONT48SEG +#define UI_FONT_FONT48SEG 1 +#endif + +#if UI_FONT_FONT48SEG + +/*----------------- + * BITMAPS + *----------------*/ + +/*Store the image of the glyphs*/ +static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { + /* U+0020 " " */ + + /* U+0021 "!" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x82, 0x0, 0x8, + 0xf2, 0x0, 0x8f, 0xf2, 0x7, 0xff, 0xf2, 0x5f, + 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, + 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, 0xff, + 0xf2, 0x6f, 0xff, 0xf2, 0x5f, 0xff, 0xf2, 0x7, + 0xff, 0xf1, 0x0, 0x8f, 0x50, 0x0, 0x3, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x18, 0x0, 0x1, 0xcf, + 0xb0, 0x1c, 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, + 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, + 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, 0xff, + 0xf2, 0x2e, 0xff, 0xf2, 0x3, 0xef, 0xf2, 0x0, + 0x3f, 0xf2, 0x0, 0x3, 0xf2, 0x0, 0x0, 0x31, + 0x0, 0x0, 0x0, 0x2, 0x22, 0x20, 0x6f, 0xff, + 0xf2, 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, + 0xff, 0xf2, + + /* U+0022 "\"" */ + 0xff, 0xff, 0x90, 0xcf, 0xff, 0xcf, 0xff, 0xf9, + 0xc, 0xff, 0xfc, 0xff, 0xff, 0x90, 0xcf, 0xff, + 0xcf, 0xff, 0xf9, 0xc, 0xff, 0xfc, 0xff, 0xff, + 0x90, 0xcf, 0xff, 0xcf, 0xff, 0xf9, 0xc, 0xff, + 0xfc, 0x9f, 0xff, 0x40, 0x7f, 0xff, 0x70, 0xaf, + 0x40, 0x0, 0x7f, 0x70, 0x0, 0x30, 0x0, 0x0, + 0x30, 0x0, + + /* U+0023 "#" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4d, 0x10, 0x0, 0x0, 0x1d, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x4f, 0xfd, 0x10, 0x0, + 0x1d, 0xff, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe, 0xff, 0xfa, 0x0, 0x9, 0xff, 0xfe, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff, 0xa0, + 0x0, 0xaf, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xe, 0xff, 0xfa, 0x0, 0xa, 0xff, 0xff, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff, + 0xa0, 0x0, 0xaf, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8, 0xff, 0xf4, 0x0, 0x4, 0xff, + 0xf8, 0x0, 0x0, 0x0, 0x0, 0xaf, 0xfb, 0x8, + 0xf4, 0x1e, 0xfe, 0x24, 0xf9, 0xb, 0xff, 0xb0, + 0x0, 0xaf, 0xff, 0xfb, 0x2, 0x1d, 0xff, 0xfe, + 0x22, 0xa, 0xff, 0xff, 0xa0, 0x3f, 0xff, 0xff, + 0xf3, 0x8, 0xff, 0xff, 0xf8, 0x3, 0xff, 0xff, + 0xff, 0x30, 0x5f, 0xff, 0xf5, 0x29, 0x9, 0xff, + 0xf9, 0x9, 0x25, 0xff, 0xff, 0x50, 0x0, 0x49, + 0x95, 0x2e, 0xfb, 0x7, 0x97, 0xb, 0xfe, 0x25, + 0x99, 0x50, 0x0, 0x0, 0x0, 0xd, 0xff, 0xf9, + 0x0, 0x8, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xef, 0xff, 0xa0, 0x0, 0xaf, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0xff, + 0xf9, 0x0, 0x9, 0xff, 0xfe, 0x0, 0x0, 0x0, + 0x0, 0x49, 0x95, 0x2e, 0xfb, 0x7, 0x97, 0xb, + 0xfe, 0x24, 0x99, 0x40, 0x0, 0x4f, 0xff, 0xf5, + 0x29, 0x9, 0xff, 0xf9, 0x9, 0x25, 0xff, 0xff, + 0x50, 0x3f, 0xff, 0xff, 0xf3, 0x7, 0xff, 0xff, + 0xf8, 0x3, 0xff, 0xff, 0xff, 0x30, 0xaf, 0xff, + 0xfb, 0x2, 0x1e, 0xff, 0xfe, 0x22, 0xb, 0xff, + 0xff, 0xa0, 0x0, 0xaf, 0xfb, 0x8, 0xf4, 0x2e, + 0xfe, 0x24, 0xf9, 0xb, 0xff, 0xb0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xf4, 0x0, 0x4, 0xff, 0xf8, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff, + 0xa0, 0x0, 0xaf, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xe, 0xff, 0xfa, 0x0, 0xa, 0xff, + 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xef, + 0xff, 0xa0, 0x0, 0xaf, 0xff, 0xf0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb, 0xff, 0xf7, 0x0, 0x7, + 0xff, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc, 0xf8, 0x0, 0x0, 0x8, 0xfc, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, + 0x0, 0x6, 0x10, 0x0, 0x0, 0x0, + + /* U+0024 "$" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x30, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xcf, 0xf3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xff, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, + 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x20, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe2, 0x0, 0x1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, + 0x8, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xa9, 0x0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x0, 0x0, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x0, 0x0, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x0, 0x49, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xc1, 0x4, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0xa, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0xaf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xff, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, + 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xcf, 0xf4, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0x40, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0025 "%" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x37, 0xbe, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, + 0xef, 0xff, 0x60, 0x0, 0x0, 0x0, 0x8f, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0xff, 0xff, + 0xd0, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xfc, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xef, 0xff, 0xf4, 0x0, + 0x0, 0x0, 0x1f, 0xff, 0xff, 0xf6, 0x0, 0x0, + 0x0, 0x0, 0x8f, 0xff, 0xfb, 0x0, 0x0, 0x0, + 0x72, 0x3f, 0xff, 0xf7, 0x8, 0x0, 0x0, 0x0, + 0x2f, 0xff, 0xff, 0x20, 0x0, 0x0, 0x7f, 0xe2, + 0x3a, 0xa6, 0xb, 0xfb, 0x0, 0x0, 0xa, 0xff, + 0xff, 0x90, 0x0, 0x0, 0x5f, 0xff, 0xd0, 0x0, + 0x9, 0xff, 0xf9, 0x0, 0x3, 0xff, 0xff, 0xf1, + 0x0, 0x0, 0x7, 0xff, 0xff, 0x0, 0x0, 0xcf, + 0xff, 0xc0, 0x0, 0xcf, 0xff, 0xf7, 0x0, 0x0, + 0x0, 0x7f, 0xff, 0xf0, 0x0, 0xc, 0xff, 0xfc, + 0x0, 0x5f, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x7, + 0xff, 0xff, 0x0, 0x0, 0xcf, 0xff, 0xc0, 0xe, + 0xff, 0xff, 0x50, 0x0, 0x0, 0x0, 0x5f, 0xff, + 0xe0, 0x0, 0xa, 0xff, 0xf9, 0x7, 0xff, 0xff, + 0xc0, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xe2, 0x3a, + 0xa6, 0xb, 0xfa, 0x0, 0xaf, 0xff, 0xf3, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x51, 0x3f, 0xff, 0xf7, + 0x7, 0x0, 0x6, 0xff, 0xfa, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1f, 0xff, 0xff, 0xf6, 0x0, + 0x0, 0x1f, 0xff, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8f, 0xff, 0xfc, 0x0, 0x0, 0x0, + 0xca, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8f, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x54, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x5c, 0xff, 0xb0, 0x0, 0x1, 0xaa, 0x90, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, + 0xff, 0xfe, 0x0, 0x1, 0xdf, 0xff, 0xa0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xff, + 0x90, 0x0, 0xcf, 0xff, 0xff, 0x90, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xff, 0xff, 0xf1, 0x1, + 0x14, 0xff, 0xff, 0xe2, 0x30, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xff, 0xf7, 0x1, 0xdd, 0x14, + 0xff, 0xe2, 0x4f, 0xa0, 0x0, 0x0, 0x0, 0x0, + 0x4f, 0xff, 0xfe, 0x0, 0xcf, 0xfd, 0x10, 0x0, + 0x3f, 0xff, 0x90, 0x0, 0x0, 0x0, 0xd, 0xff, + 0xff, 0x50, 0x3f, 0xff, 0xf5, 0x0, 0x9, 0xff, + 0xff, 0x0, 0x0, 0x0, 0x7, 0xff, 0xff, 0xc0, + 0x3, 0xff, 0xff, 0x50, 0x0, 0x9f, 0xff, 0xf0, + 0x0, 0x0, 0x1, 0xef, 0xff, 0xf3, 0x0, 0x3f, + 0xff, 0xf5, 0x0, 0x9, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x9f, 0xff, 0xfa, 0x0, 0x2, 0xff, 0xff, + 0x30, 0x0, 0x7f, 0xff, 0xd0, 0x0, 0x0, 0x2f, + 0xff, 0xff, 0x10, 0x0, 0x4, 0xff, 0x51, 0x99, + 0x80, 0x8f, 0xd1, 0x0, 0x0, 0xb, 0xff, 0xff, + 0x80, 0x0, 0x0, 0x3, 0x41, 0xdf, 0xff, 0xa0, + 0x61, 0x0, 0x0, 0x0, 0xdf, 0xff, 0xe0, 0x0, + 0x0, 0x0, 0x0, 0xcf, 0xff, 0xff, 0x80, 0x0, + 0x0, 0x0, 0x9, 0xff, 0xf5, 0x0, 0x0, 0x0, + 0x0, 0x5, 0xff, 0xff, 0xe2, 0x0, 0x0, 0x0, + 0x0, 0x4f, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4, 0xff, 0xe2, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0xa0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x72, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0026 "&" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, + 0xff, 0xff, 0xff, 0xfd, 0x10, 0x0, 0x0, 0x0, + 0x0, 0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x0, 0x0, 0x0, 0x72, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xf9, 0x0, 0x0, 0x0, 0x0, + 0x8, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0x0, 0x0, 0x0, 0x0, 0x5f, 0xff, 0xe0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0x90, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x6, 0xff, 0xf9, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, 0x0, 0x0, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0xb, 0xe2, 0x3b, 0x0, + 0x0, 0x22, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x80, 0x23, 0xff, 0xb0, 0x0, 0x9, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xe1, 0xa, 0xff, 0xf4, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0xbf, 0x60, 0x2e, 0xf8, 0x8, 0x99, + 0x99, 0x99, 0x99, 0x93, 0x3e, 0xf7, 0x4, 0x0, + 0x7f, 0xff, 0x70, 0x0, 0x0, 0x0, 0x0, 0x2, + 0xef, 0xfd, 0x0, 0x0, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x5f, 0xff, 0xe0, 0x0, + 0x0, 0x0, 0x0, 0x8, 0xff, 0xfb, 0x0, 0x0, + 0x7, 0xfe, 0x23, 0x99, 0x99, 0x99, 0x99, 0x70, + 0xaf, 0xc1, 0x0, 0x0, 0x0, 0x72, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, 0x0, 0x0, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, + 0xff, 0xff, 0xff, 0xfd, 0x10, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x0, 0x0, + + /* U+0027 "'" */ + 0xff, 0xff, 0x9f, 0xff, 0xf9, 0xff, 0xff, 0x9f, + 0xff, 0xf9, 0xff, 0xff, 0x9f, 0xff, 0xf9, 0x9f, + 0xff, 0x40, 0xaf, 0x40, 0x0, 0x30, 0x0, + + /* U+0028 "(" */ + 0x0, 0x0, 0x2e, 0xff, 0xff, 0xf8, 0x0, 0x0, + 0x2e, 0xff, 0xff, 0xf9, 0x0, 0x0, 0x9, 0xff, + 0xff, 0xf9, 0x0, 0x0, 0x18, 0xa, 0xff, 0xf9, + 0x0, 0x0, 0x1d, 0xf9, 0x8, 0xa8, 0x0, 0x0, + 0xd, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0xff, + 0xff, 0x90, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf9, + 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x90, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xf9, 0x0, 0x0, 0x0, + 0x0, 0xff, 0xff, 0x90, 0x0, 0x0, 0x0, 0xf, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x0, 0xff, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x5, 0xfc, 0x10, 0x0, + 0x0, 0x0, 0x0, 0x5, 0x10, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xfc, 0x0, + 0x0, 0x0, 0x0, 0xf, 0xff, 0xf8, 0x0, 0x0, + 0x0, 0x0, 0xff, 0xff, 0x90, 0x0, 0x0, 0x0, + 0xf, 0xff, 0xf9, 0x0, 0x0, 0x0, 0x0, 0xff, + 0xff, 0x90, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf9, + 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x90, 0x0, + 0x0, 0x0, 0xd, 0xff, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0x1d, 0xf9, 0x7, 0x97, 0x0, 0x0, 0x0, + 0x18, 0xa, 0xff, 0xf9, 0x0, 0x0, 0x0, 0x8, + 0xff, 0xff, 0xf8, 0x0, 0x0, 0x0, 0x2e, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x2e, 0xff, 0xff, + 0xf8, 0x0, + + /* U+0029 ")" */ + 0x8f, 0xff, 0xff, 0xe3, 0x0, 0x0, 0x0, 0x8f, + 0xff, 0xff, 0xe3, 0x0, 0x0, 0x0, 0x8f, 0xff, + 0xff, 0xa0, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xb0, + 0x72, 0x0, 0x0, 0x0, 0x7a, 0x90, 0x8f, 0xe2, + 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, 0xe0, 0x0, + 0x0, 0x0, 0x8, 0xff, 0xff, 0x0, 0x0, 0x0, + 0x0, 0x8f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x8, + 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xff, 0x0, + 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xff, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xcf, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xbf, 0x70, 0x0, 0x0, 0x0, + 0x0, 0xbf, 0xff, 0x0, 0x0, 0x0, 0x0, 0x7f, + 0xff, 0xf0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xff, + 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x8, 0xff, 0xff, 0x0, 0x0, 0x0, + 0x0, 0x8f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x8, + 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, + 0xe0, 0x0, 0x0, 0x79, 0x80, 0x8f, 0xe2, 0x0, + 0x0, 0x8f, 0xff, 0xb0, 0x72, 0x0, 0x0, 0x8f, + 0xff, 0xff, 0x90, 0x0, 0x0, 0x8f, 0xff, 0xff, + 0xe3, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xe3, 0x0, + 0x0, 0x0, + + /* U+002A "*" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1d, 0x40, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xdf, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x9, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x11, 0x0, 0xa, 0xff, 0xff, + 0x0, 0x0, 0x30, 0x0, 0x8d, 0xff, 0x80, 0x9, + 0xff, 0xff, 0x0, 0x6e, 0xfe, 0xa1, 0xbf, 0xff, + 0xfe, 0x34, 0xff, 0xf9, 0x1d, 0xff, 0xff, 0xf0, + 0x7f, 0xff, 0xff, 0xc0, 0xbf, 0xf1, 0x9f, 0xff, + 0xff, 0xc0, 0x2f, 0xff, 0xff, 0xf6, 0x2f, 0x72, + 0xff, 0xff, 0xff, 0x70, 0x1, 0x9f, 0xff, 0xff, + 0x18, 0xb, 0xff, 0xff, 0xd4, 0x0, 0x0, 0x2, + 0x78, 0x88, 0x30, 0x18, 0x88, 0x85, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1a, 0xff, 0xff, 0x52, 0x1e, + 0xff, 0xfd, 0x30, 0x0, 0x8, 0xff, 0xff, 0xfb, + 0xd, 0x36, 0xff, 0xff, 0xf9, 0x10, 0x4f, 0xff, + 0xff, 0xf2, 0x6f, 0xc0, 0xcf, 0xff, 0xff, 0x90, + 0x9f, 0xff, 0xff, 0x80, 0xef, 0xf5, 0x2f, 0xff, + 0xff, 0xd0, 0xdf, 0xff, 0xf8, 0x7, 0xff, 0xfd, + 0x3, 0xcf, 0xff, 0xf2, 0x15, 0xa9, 0x10, 0xa, + 0xff, 0xff, 0x0, 0x6, 0xb7, 0x20, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0xff, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+002B "+" */ + 0x0, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1d, 0xf7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0xff, + 0xf6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xff, 0xff, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xf, 0xff, 0xf9, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xff, 0xff, 0x90, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe, 0xff, 0xf7, 0x0, + 0x0, 0x0, 0x0, 0x8, 0x99, 0x94, 0x2e, 0xf9, + 0x8, 0x99, 0x95, 0x0, 0xb, 0xff, 0xff, 0xf4, + 0x28, 0xa, 0xff, 0xff, 0xf5, 0xa, 0xff, 0xff, + 0xff, 0xf2, 0x9, 0xff, 0xff, 0xff, 0xf3, 0x3f, + 0xff, 0xff, 0xfa, 0x2, 0x2e, 0xff, 0xff, 0xfb, + 0x0, 0x3f, 0xff, 0xfa, 0x9, 0xf3, 0x2e, 0xff, + 0xfb, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xf3, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, + 0xff, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xf, 0xff, 0xf9, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xff, 0xff, 0x90, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xf, 0xff, 0xf8, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f, 0xfc, 0x10, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5c, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+002C "," */ + 0xff, 0xff, 0x9f, 0xff, 0xf9, 0xff, 0xff, 0x9f, + 0xff, 0xf9, 0xff, 0xff, 0x9f, 0xff, 0xf9, 0x9f, + 0xff, 0x40, 0xaf, 0x40, 0x0, 0x30, 0x0, + + /* U+002D "-" */ + 0x0, 0x89, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x50, 0x0, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x50, 0xaf, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x33, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0x3, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0x0, + + /* U+002E "." */ + 0x99, 0x99, 0x5f, 0xff, 0xf9, 0xff, 0xff, 0x9f, + 0xff, 0xf9, 0xff, 0xff, 0x90, + + /* U+002F "/" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x59, 0xdf, 0xd0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, 0xff, 0xff, + 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9f, 0xff, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0xff, 0xff, 0xf3, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, + 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4f, 0xff, 0xff, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xdf, 0xff, 0xf7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xff, + 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1f, 0xff, 0xff, 0x40, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9f, 0xff, 0xfb, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0xff, + 0xf2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x5, 0xff, 0xff, 0x90, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xff, 0xfe, 0x10, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcf, + 0xf6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8c, 0x70, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0x9d, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, 0x30, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f, 0xff, + 0xfe, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xdf, 0xff, 0xf6, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7, 0xff, 0xff, 0xd0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xff, + 0xff, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xaf, 0xff, 0xfb, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3, 0xff, 0xff, 0xf2, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0xff, + 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x5f, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xdf, 0xff, 0xf6, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbf, + 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x6f, 0xff, 0x40, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f, 0xfa, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe, 0xf2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0x80, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, + + /* U+0030 "0" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0x9, 0x7e, 0x23, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x90, 0xbd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xa, 0xfd, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x50, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xdf, 0xfd, 0x1d, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x1, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x1c, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x7f, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xdf, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xfd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xa, 0xfd, + 0x7e, 0x23, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x90, 0xbd, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0x9, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + + /* U+0031 "1" */ + 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x1c, 0xe0, + 0xc, 0xfe, 0xc, 0xff, 0xe9, 0xff, 0xfe, 0xaf, + 0xff, 0xea, 0xff, 0xfe, 0xaf, 0xff, 0xea, 0xff, + 0xfe, 0xaf, 0xff, 0xe8, 0xff, 0xfe, 0xb, 0xff, + 0xd0, 0xb, 0xf3, 0x0, 0x3, 0x0, 0x0, 0x0, + 0x0, 0x27, 0x0, 0x2e, 0xf7, 0x2e, 0xff, 0xe9, + 0xff, 0xfe, 0xaf, 0xff, 0xea, 0xff, 0xfe, 0xaf, + 0xff, 0xea, 0xff, 0xfe, 0xaf, 0xff, 0xe6, 0xff, + 0xfe, 0x7, 0xff, 0xe0, 0x7, 0xfe, 0x0, 0x8, + 0xe0, 0x0, 0x6, + + /* U+0032 "2" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x0, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0x9, 0x0, 0x3, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x90, 0xbd, 0x0, 0x0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xb, 0xfd, + 0x0, 0x0, 0x4, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0x0, 0x0, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x2, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x30, 0x0, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x0, 0x0, 0x7f, 0xff, 0x70, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0x0, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x0, 0x0, + 0x7e, 0x23, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x90, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0x0, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + + /* U+0033 "3" */ + 0x2e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0x0, 0x3e, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf9, 0x9, 0x0, 0x3f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0xb, 0xd0, 0x0, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xf9, 0xb, 0xfd, 0x0, 0x0, + 0x4a, 0xaa, 0xaa, 0xaa, 0xa8, 0xa, 0xff, 0xd0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0xff, + 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xaf, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xaf, 0xff, 0xd0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaf, 0xff, + 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xb, 0xff, 0xd0, 0x0, 0x3e, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x2e, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x2, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x0, 0xa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, + 0x37, 0x0, 0x0, 0x8, 0x99, 0x99, 0x99, 0x99, + 0x93, 0x3e, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2e, 0xff, 0xd0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xaf, 0xff, 0xd0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, + 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xaf, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xaf, 0xff, 0xd0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xfd, 0x0, + 0x0, 0x39, 0x99, 0x99, 0x99, 0x97, 0xa, 0xff, + 0xd0, 0x0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, + 0xa, 0xfd, 0x0, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf9, 0xb, 0xd0, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0x9, 0x2e, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x0, + + /* U+0034 "4" */ + 0x41, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x6, 0x7d, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x8d, 0x7f, 0xd1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x8, 0xfd, 0x7f, 0xfc, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfd, + 0x7f, 0xff, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x6, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0x80, 0x0, 0x0, 0x0, 0x0, 0x3, 0xef, 0xfd, + 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, 0x99, 0x93, + 0x3f, 0xf7, 0x2, 0x80, 0xaf, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0x33, 0x70, 0x0, 0x9, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, 0x0, 0x2, + 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, + 0x0, 0x0, 0x3e, 0xff, 0xff, 0xff, 0xff, 0xf8, + 0xb, 0xe2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xfc, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0xdf, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1d, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xdd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + + /* U+0035 "5" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0x0, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x90, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x0, 0x0, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x0, 0x0, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x0, 0x0, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfd, 0x0, 0x0, 0x3, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xfd, 0x0, 0x0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xa, 0xfd, + 0x0, 0x3, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x90, 0xbd, 0x0, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0x9, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + + /* U+0036 "6" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0x0, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x90, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x0, 0x0, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x7f, 0xff, 0x70, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xfd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xa, 0xfd, + 0x7e, 0x23, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x90, 0xbd, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0x9, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + + /* U+0037 "7" */ + 0x2e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0x0, 0x3e, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf9, 0x9, 0x0, 0x3f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0xb, 0xd0, 0x0, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xf9, 0xa, 0xfd, 0x0, 0x0, + 0x4a, 0xaa, 0xaa, 0xaa, 0xa8, 0xa, 0xff, 0xd0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0xff, + 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xaf, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xaf, 0xff, 0xd0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaf, 0xff, + 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1d, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1d, 0xf4, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1d, 0xf4, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xff, 0xd0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xaf, 0xff, 0xd0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, + 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xaf, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xaf, 0xff, 0xd0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xfd, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, + 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb, 0xd0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0038 "8" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0x9, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x90, 0xbd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xb, 0xfd, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x7f, 0xff, 0x70, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xfd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xa, 0xfd, + 0x7e, 0x23, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x90, 0xbd, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0x9, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + + /* U+0039 "9" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0x9, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x90, 0xbd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xb, 0xfd, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x0, 0x0, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x0, 0x0, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfd, 0x0, 0x0, 0x3, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xfd, 0x0, 0x0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xa, 0xfd, + 0x0, 0x3, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x90, 0xbd, 0x0, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0x9, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + + /* U+003A ":" */ + 0xff, 0xff, 0xaf, 0xff, 0xfa, 0xff, 0xff, 0xaf, + 0xff, 0xfa, 0x99, 0x99, 0x60, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9, 0x99, 0x96, 0xff, 0xff, 0xaf, 0xff, 0xfa, + 0xff, 0xff, 0xaf, 0xff, 0xfa, + + /* U+003B ";" */ + 0x99, 0x99, 0x6f, 0xff, 0xfa, 0xff, 0xff, 0xaf, + 0xff, 0xfa, 0xff, 0xff, 0xa0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xf, 0xff, 0xfa, 0xff, 0xff, 0xaf, 0xff, + 0xfa, 0xff, 0xff, 0xaf, 0xff, 0xfa, 0xff, 0xff, + 0xa9, 0xff, 0xf4, 0x9, 0xf5, 0x0, 0x3, 0x0, + + /* U+003C "<" */ + 0x0, 0x0, 0x0, 0x0, 0x6, 0xbb, 0xb0, 0x0, + 0x0, 0x0, 0x0, 0x6f, 0xff, 0xf0, 0x0, 0x0, + 0x0, 0x6, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0xff, 0xe0, 0x0, 0x0, 0x6, 0xff, + 0xff, 0xfe, 0x20, 0x0, 0x0, 0x6f, 0xff, 0xff, + 0xe2, 0x0, 0x0, 0x6, 0xff, 0xff, 0xfe, 0x20, + 0x0, 0x0, 0x6f, 0xff, 0xff, 0xe2, 0x0, 0x0, + 0x6, 0xff, 0xff, 0xfe, 0x20, 0x0, 0x0, 0x6f, + 0xff, 0xff, 0xe2, 0x0, 0x0, 0x0, 0x1, 0x11, + 0x11, 0x0, 0x0, 0x0, 0x0, 0x6b, 0xbb, 0xbb, + 0x80, 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, 0xfa, + 0x0, 0x0, 0x0, 0x0, 0xbf, 0xff, 0xff, 0xa0, + 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, 0xfb, 0x0, + 0x0, 0x0, 0x0, 0xbf, 0xff, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0xb, 0xff, 0xff, 0xfb, 0x0, 0x0, + 0x0, 0x0, 0xaf, 0xff, 0xff, 0xb0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0xaf, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, + 0xa, 0xff, 0xf0, + + /* U+003D "=" */ + 0x0, 0x9a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0x50, 0x0, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x50, 0xaf, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x33, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0x3, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x9a, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0x50, 0x0, 0xcf, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x50, 0xaf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x43, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xa0, 0x3, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xb0, 0x0, + + /* U+003E ">" */ + 0xab, 0xb7, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, + 0xff, 0x70, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0xdf, 0xff, 0xff, + 0x70, 0x0, 0x0, 0x0, 0x2e, 0xff, 0xff, 0xf7, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xff, 0xff, 0x70, + 0x0, 0x0, 0x0, 0x1d, 0xff, 0xff, 0xf7, 0x0, + 0x0, 0x0, 0x1, 0xdf, 0xff, 0xff, 0x70, 0x0, + 0x0, 0x0, 0x1d, 0xff, 0xff, 0xf7, 0x0, 0x0, + 0x0, 0x1, 0xdf, 0xff, 0xff, 0x70, 0x0, 0x0, + 0x0, 0x1, 0x11, 0x11, 0x0, 0x0, 0x0, 0x0, + 0x8b, 0xbb, 0xbb, 0x70, 0x0, 0x0, 0x9, 0xff, + 0xff, 0xfc, 0x0, 0x0, 0x0, 0x9f, 0xff, 0xff, + 0xc0, 0x0, 0x0, 0xa, 0xff, 0xff, 0xfc, 0x0, + 0x0, 0x0, 0xaf, 0xff, 0xff, 0xc0, 0x0, 0x0, + 0xa, 0xff, 0xff, 0xfc, 0x0, 0x0, 0x0, 0xaf, + 0xff, 0xff, 0xc0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0xfc, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xc0, + 0x0, 0x0, 0x0, 0x0, 0xff, 0xfb, 0x0, 0x0, + 0x0, 0x0, 0x0, + + /* U+003F "?" */ + 0x1d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0x2e, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x0, 0x2, + 0xef, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x0, 0x0, 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xd1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0x0, 0x0, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x2, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x30, 0x0, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x0, 0x0, 0x7f, 0xff, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x36, 0x66, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x12, 0x22, 0x20, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + + /* U+0040 "@" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf9, 0x0, 0x0, 0x0, 0x8f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xa0, 0x90, 0x0, 0x1, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfa, 0xa, 0xf0, 0x0, + 0x72, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xa0, 0xaf, 0xf0, 0x8, 0xfe, 0x23, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa8, 0xa, 0xff, + 0xf0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x8f, 0xff, 0xf0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x8f, 0xff, 0xf0, 0x7f, 0xff, 0xf1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xff, 0xe0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x1, 0xdf, 0xff, + 0xff, 0xff, 0x90, 0xaf, 0x30, 0x7f, 0xff, 0xf1, + 0x0, 0x0, 0x1d, 0xff, 0xff, 0xff, 0xff, 0xf9, + 0x2, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x20, 0x0, 0x7f, + 0xff, 0xf0, 0x0, 0x18, 0x8, 0xff, 0xff, 0xff, + 0xff, 0xf3, 0x28, 0x0, 0x6f, 0xff, 0x30, 0x1, + 0xdf, 0xa0, 0x79, 0x99, 0x99, 0x99, 0x32, 0xef, + 0x80, 0xb, 0xf4, 0x0, 0x5, 0xff, 0xf9, 0x0, + 0x0, 0x0, 0x0, 0x2e, 0xff, 0xf0, 0x0, 0x40, + 0x0, 0x5, 0xff, 0xff, 0x30, 0x0, 0x0, 0x0, + 0x9f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x5, 0xff, + 0xff, 0x30, 0x0, 0x0, 0x0, 0x9f, 0xff, 0xf0, + 0x1, 0x50, 0x0, 0x5, 0xff, 0xff, 0x30, 0x0, + 0x0, 0x0, 0x9f, 0xff, 0xf0, 0x1c, 0xf5, 0x0, + 0x5, 0xff, 0xff, 0x30, 0x0, 0x0, 0x0, 0x9f, + 0xff, 0xf0, 0x7f, 0xff, 0x40, 0x1, 0xdf, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x4f, 0xff, 0xf0, 0x7f, + 0xff, 0xf0, 0x0, 0x1e, 0xd1, 0x6f, 0xff, 0xff, + 0xfe, 0x24, 0xff, 0xf0, 0x7f, 0xff, 0xf1, 0x0, + 0x2, 0x15, 0xff, 0xff, 0xff, 0xff, 0xe2, 0x4f, + 0xf0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0xd, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x14, 0xf0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x2, 0xef, 0xff, 0xff, 0xff, + 0xff, 0xd1, 0x40, 0x7f, 0xff, 0xf1, 0x0, 0x0, + 0x0, 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xa8, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f, 0xff, 0xe0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8, 0xfe, 0x23, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x97, 0x0, 0x0, + 0x72, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x90, 0x0, 0x1, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + 0x8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, + + /* U+0041 "A" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0x9, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x90, 0xbd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xb, 0xfd, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x7f, 0xff, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x3, 0xef, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x0, 0x5, 0xff, 0xfd, 0x7f, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xfd, + 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6, 0xfd, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6d, 0x40, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, + + /* U+0042 "B" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xd1, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x7f, 0xff, 0x70, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xc1, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7e, 0x23, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+0043 "C" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf6, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x0, 0x1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf6, 0x0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, + 0x7, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0xa6, + 0x0, 0x0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1c, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x96, 0x0, 0x0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x60, 0x0, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf6, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x0, 0x0, 0x8, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, + + /* U+0044 "D" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x7e, 0x23, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xc1, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x50, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xdf, 0xfd, 0x1d, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x1, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x1c, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x7f, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xdf, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xc1, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7e, 0x23, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+0045 "E" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf6, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf6, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0xa6, + 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x30, 0x0, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x0, 0x0, 0x7f, 0xff, 0x70, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x96, 0x0, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x60, 0x0, + 0x7e, 0x23, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf6, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, + + /* U+0046 "F" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf6, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf6, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0xa6, + 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x30, 0x0, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x0, 0x0, 0x7f, 0xff, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xc0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfd, 0x10, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xd1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7d, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x51, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + + /* U+0047 "G" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf6, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x0, 0x1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf6, 0x0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, + 0x8, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0xa6, + 0x0, 0x0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2e, 0xf8, 0x0, 0x0, 0x4, 0xaa, 0xaa, 0xa3, + 0x0, 0x0, 0x2, 0x70, 0x0, 0x0, 0x3f, 0xff, + 0xff, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x2, + 0xff, 0xff, 0xff, 0xff, 0xf1, 0x0, 0x0, 0x30, + 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, 0x80, 0x20, + 0xb, 0xf3, 0x0, 0x0, 0x9, 0xff, 0xff, 0xf8, + 0xb, 0xe2, 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xfd, 0x7f, 0xff, 0xe0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x9, 0xff, 0xfc, 0x8, 0xfe, 0x33, 0x88, + 0x88, 0x88, 0x88, 0x60, 0xbf, 0xd1, 0x0, 0x83, + 0x2e, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x9, 0x10, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x0, 0x0, 0xaf, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0x20, 0x0, 0x0, 0x0, 0xa, 0xff, + 0xff, 0xff, 0xff, 0xe3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x22, 0x22, 0x22, 0x22, 0x10, 0x0, 0x0, + + /* U+0048 "H" */ + 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x4, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6d, 0x7f, 0xb0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x6, 0xfd, 0x7f, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f, 0xfd, + 0x7f, 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x5, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x3, + 0xef, 0xfd, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3f, 0xf7, 0x2, 0x80, 0xaf, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x33, 0x70, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x0, 0x22, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x80, 0x20, 0xa, 0xe3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x6f, 0xfe, 0x20, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xbf, 0xfc, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, 0x7f, 0xff, + 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbf, 0xfd, + 0x7f, 0xf3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc, 0xfd, 0x7f, 0x30, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xcd, 0x64, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0049 "I" */ + 0x0, 0x0, 0x51, 0x0, 0x5, 0xf2, 0x0, 0x5f, + 0xf2, 0x4, 0xff, 0xf2, 0x3f, 0xff, 0xf2, 0x6f, + 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, + 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, 0xff, + 0xf2, 0x6f, 0xff, 0xf2, 0xa, 0xff, 0xf1, 0x0, + 0xaf, 0x80, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x5, 0x0, 0x0, 0xaf, 0x80, 0xa, 0xff, + 0xf1, 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, + 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, + 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x4f, 0xff, + 0xf2, 0x5, 0xff, 0xf2, 0x0, 0x5f, 0xf2, 0x0, + 0x5, 0xf2, 0x0, 0x0, 0x61, + + /* U+004A "J" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9d, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x9, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xdf, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x0, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xdf, 0xfd, 0xc, 0xf4, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x40, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xd1, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0x0, 0x8, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+004B "K" */ + 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbf, + 0xff, 0x0, 0x7e, 0x20, 0x0, 0x0, 0x0, 0x0, + 0xb, 0xff, 0xff, 0x0, 0x7f, 0xe2, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xff, 0xff, 0x0, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0xb, 0xff, 0xff, 0xfb, 0x0, + 0x7f, 0xff, 0xd0, 0x0, 0x0, 0xbf, 0xff, 0xff, + 0xb0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0xb, 0xff, + 0xff, 0xfb, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0xbf, 0xff, 0xff, 0xb0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0xb, 0xff, 0xff, 0xfb, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0xaf, 0xff, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0xcf, 0xff, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0xcf, + 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xe0, 0x9c, 0xc9, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xfe, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x7f, 0xff, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x3, 0xef, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x0, 0x5, 0xff, 0xfd, 0x7f, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xfd, + 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6, 0xfd, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6d, 0x40, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, + + /* U+004C "L" */ + 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7d, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xd1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1c, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x96, 0x0, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x60, 0x0, + 0x7e, 0x33, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf6, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, + + /* U+004D "M" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0x9, 0x7e, 0x23, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x90, 0xbd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xa, 0xfd, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x10, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x6, 0xf3, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x6f, 0xff, 0x30, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0xef, 0xff, 0xa0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0xef, 0xff, + 0xa0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0xef, 0xff, 0xa0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0xef, 0xff, 0xa0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x50, 0x0, 0xef, 0xff, 0xa0, 0x1, + 0xdf, 0xfd, 0x1d, 0xf5, 0x0, 0x0, 0xef, 0xff, + 0xa0, 0x0, 0x1d, 0xf4, 0x1, 0x50, 0x0, 0x0, + 0xef, 0xff, 0xa0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0xef, 0xff, 0xa0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0xef, 0xff, 0xa0, 0x0, + 0x1, 0x40, 0x1c, 0xf5, 0x0, 0x0, 0x8f, 0xff, + 0x40, 0x0, 0x1d, 0xf4, 0x7f, 0xff, 0x50, 0x0, + 0x8, 0xf5, 0x0, 0x1, 0xdf, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x20, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xd0, 0x0, + 0x0, 0x0, 0x0, 0x7, 0xff, 0xfd, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f, 0xfd, + 0x7f, 0xd1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9, 0xfd, 0x7e, 0x20, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9d, 0x52, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, + + /* U+004E "N" */ + 0x52, 0xbf, 0xff, 0xff, 0xc0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x7e, 0x2b, 0xff, 0xff, 0xfc, 0x0, + 0x0, 0x0, 0x0, 0x9d, 0x7f, 0xe2, 0xcf, 0xff, + 0xff, 0xb0, 0x0, 0x0, 0x9, 0xfd, 0x7f, 0xfd, + 0x2c, 0xff, 0xff, 0xfa, 0x0, 0x0, 0x9f, 0xfd, + 0x7f, 0xff, 0xd1, 0xcf, 0xff, 0xff, 0xa0, 0x8, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x1c, 0xff, 0xff, + 0xf9, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x1, + 0xdf, 0xff, 0xff, 0x4a, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x1d, 0xff, 0xff, 0x4a, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x1, 0xdf, 0xff, 0x4a, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x17, + 0x88, 0x2a, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xb, 0xf3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xc, 0xe3, 0x0, 0x30, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0x60, 0x2e, 0xf7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2e, 0xf6, 0x7f, 0xff, 0x70, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xc0, 0x0, + 0x0, 0x0, 0x0, 0x6, 0xff, 0xfd, 0x7f, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xfd, + 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7, 0xfd, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7d, 0x41, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, + + /* U+004F "O" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0x1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xc1, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x50, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xdf, 0xfd, 0x1d, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x1, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x1c, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x7f, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xdf, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xc1, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0x0, 0x8, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+0050 "P" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xd1, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x30, 0x0, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x0, 0x0, 0x7f, 0xff, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0051 "Q" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0x1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xc1, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xa0, 0x0, 0x0, 0x0, 0x0, 0x5, 0xff, 0xfd, + 0x3f, 0xfb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x5f, 0xf9, 0x4, 0xa0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5, 0xa0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7, 0xd1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9, 0xc1, 0x5f, 0xfd, 0x10, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8f, 0xfb, 0x7f, 0xff, 0xd0, 0x0, + 0x0, 0x0, 0x0, 0x7, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0xff, 0xfa, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0xa, 0xdd, 0xa0, 0x8f, 0xb0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0xc, 0xff, 0xfb, 0x6, 0x0, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0xc, 0xff, 0xff, + 0xb0, 0x0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0xa, + 0xff, 0xff, 0xfb, 0x0, 0x7, 0xfe, 0x23, 0x99, + 0x99, 0x60, 0xcf, 0xff, 0xff, 0xb0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xf7, 0xc, 0xff, 0xff, 0xfa, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0x50, 0xcf, + 0xff, 0xfd, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xfc, + 0x10, 0xc, 0xff, 0xfd, 0x0, 0x0, 0x8, 0xff, + 0xff, 0xc1, 0x0, 0x1, 0xcf, 0xfd, + + /* U+0052 "R" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xd1, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x30, 0x0, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x0, 0x0, 0x7f, 0xff, 0x70, 0x4, + 0x55, 0x20, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0xe, 0xff, 0xf3, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0xe, 0xff, 0xff, 0x30, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0xe, 0xff, 0xff, + 0xf3, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x6, + 0xff, 0xff, 0xfe, 0x30, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x6f, 0xff, 0xff, 0xe2, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x7, 0xff, 0xff, 0xfe, + 0x20, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x7f, + 0xff, 0xff, 0xe2, 0x0, 0x7f, 0xff, 0xd0, 0x0, + 0x0, 0x8, 0xff, 0xff, 0xfe, 0x20, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xe0, + 0x7f, 0xd1, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, + 0xff, 0xf2, 0x7d, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x9f, 0xff, 0xf2, 0x52, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xf2, + + /* U+0053 "S" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x20, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe2, 0x0, 0x1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, + 0x8, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xa9, 0x0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x0, 0x0, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x0, 0x0, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x0, 0x49, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xc1, 0x4, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0xa, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0xaf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+0054 "T" */ + 0x0, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0x20, 0xa, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe2, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf9, 0x5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xb0, 0x0, 0x5a, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa9, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6c, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x6, 0xff, 0xc0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, + 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x5, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x5f, 0xd1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x10, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x5f, 0xc1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5, 0xff, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, + 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6, 0xff, 0xc1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7c, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0055 "U" */ + 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x7d, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9d, 0x7f, 0xd1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x9, 0xfd, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xfd, + 0x7f, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x7, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x50, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xdf, 0xfd, 0x1d, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x1, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x1c, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x7f, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xdf, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xd1, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0x0, 0x8, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+0056 "V" */ + 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x4, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6d, 0x7f, 0xb0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x5, 0xfd, 0x7f, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f, 0xfd, + 0x7f, 0xff, 0xa0, 0x0, 0x0, 0x0, 0x0, 0x5, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xfd, + 0x6f, 0xfe, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xaf, 0xfc, 0xa, 0xe2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb, 0xe2, 0x0, 0x20, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, + 0x8b, 0x10, 0x2e, 0xf8, 0x0, 0x0, 0x0, 0x0, + 0x1, 0xdf, 0xff, 0x0, 0x7f, 0xff, 0x80, 0x0, + 0x0, 0x0, 0xc, 0xff, 0xff, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0xbf, 0xff, 0xfe, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0xa, 0xff, 0xff, + 0xf8, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x9f, + 0xff, 0xff, 0xa0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x8, 0xff, 0xff, 0xfb, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x7f, 0xff, 0xff, 0xc0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x5, 0xff, 0xff, 0xfd, 0x10, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x4f, 0xff, 0xff, + 0xd1, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x7f, + 0xff, 0xfe, 0x20, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x7f, 0xff, 0xe2, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x7f, 0xff, 0x30, 0x0, 0x0, + 0x0, 0x0, 0x1e, 0xff, 0x90, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2, 0xe9, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0057 "W" */ + 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x7d, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9d, 0x7f, 0xd1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x9, 0xfd, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xfd, + 0x7f, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x7, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x20, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x50, 0x0, 0x8, 0xf5, 0x0, 0x1, + 0xdf, 0xfd, 0x1d, 0xf5, 0x0, 0x0, 0x8f, 0xff, + 0x40, 0x0, 0x1d, 0xf4, 0x1, 0x50, 0x0, 0x0, + 0xef, 0xff, 0xa0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0xef, 0xff, 0xa0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0xef, 0xff, 0xa0, 0x0, + 0x1, 0x40, 0x1c, 0xf5, 0x0, 0x0, 0xef, 0xff, + 0xa0, 0x0, 0x1d, 0xf4, 0x7f, 0xff, 0x50, 0x0, + 0xef, 0xff, 0xa0, 0x1, 0xdf, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0xef, 0xff, 0xa0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0xef, 0xff, 0xa0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0xef, 0xff, + 0xa0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0xef, 0xff, 0xa0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x7f, 0xff, 0x30, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x7, 0xf3, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x10, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xfd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xb, 0xfd, + 0x7e, 0x33, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x90, 0xbd, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0x9, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + + /* U+0058 "X" */ + 0x7f, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1f, 0xfd, 0x7f, 0xff, 0x20, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xfd, 0x7f, 0xff, 0xc0, 0x0, + 0x0, 0x0, 0x0, 0x5, 0xff, 0xfd, 0x7f, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0x1e, 0xff, 0xfd, + 0x6f, 0xff, 0xff, 0x20, 0x0, 0x0, 0x0, 0xaf, + 0xff, 0xfc, 0xd, 0xff, 0xff, 0xb0, 0x0, 0x0, + 0x4, 0xff, 0xff, 0xf3, 0x3, 0xff, 0xff, 0xf6, + 0x0, 0x0, 0xe, 0xff, 0xff, 0x90, 0x0, 0x8f, + 0xff, 0xff, 0x10, 0x0, 0x9f, 0xff, 0xfe, 0x0, + 0x0, 0xd, 0xff, 0xff, 0xb0, 0x3, 0xff, 0xff, + 0xf5, 0x0, 0x0, 0x4, 0xff, 0xff, 0xf4, 0xc, + 0xff, 0xff, 0xa0, 0x0, 0x0, 0x0, 0x9f, 0xff, + 0xf6, 0xe, 0xff, 0xff, 0x10, 0x0, 0x0, 0x0, + 0xe, 0xff, 0xf6, 0xe, 0xff, 0xf6, 0x0, 0x0, + 0x0, 0x0, 0x5, 0xff, 0xf6, 0xe, 0xff, 0xc0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xaf, 0xf6, 0xe, + 0xff, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, + 0xa4, 0x9, 0xa6, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x19, 0x93, 0x8, 0x95, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xaf, 0xf6, 0xe, + 0xff, 0x20, 0x0, 0x0, 0x0, 0x0, 0x4, 0xff, + 0xf6, 0xe, 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, + 0xe, 0xff, 0xf6, 0xe, 0xff, 0xf6, 0x0, 0x0, + 0x0, 0x0, 0x9f, 0xff, 0xf6, 0xe, 0xff, 0xfe, + 0x10, 0x0, 0x0, 0x3, 0xff, 0xff, 0xf4, 0xc, + 0xff, 0xff, 0xa0, 0x0, 0x0, 0xd, 0xff, 0xff, + 0xb0, 0x3, 0xff, 0xff, 0xf4, 0x0, 0x0, 0x8f, + 0xff, 0xff, 0x20, 0x0, 0x9f, 0xff, 0xfe, 0x0, + 0x3, 0xff, 0xff, 0xf7, 0x0, 0x0, 0xe, 0xff, + 0xff, 0x90, 0xd, 0xff, 0xff, 0xc0, 0x0, 0x0, + 0x4, 0xff, 0xff, 0xf3, 0x6f, 0xff, 0xff, 0x20, + 0x0, 0x0, 0x0, 0xaf, 0xff, 0xfc, 0x7f, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0x1e, 0xff, 0xfd, + 0x7f, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x5, + 0xff, 0xfd, 0x7f, 0xff, 0x30, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xfd, 0x7f, 0xf8, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1f, 0xfd, + + /* U+0059 "Y" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0x7f, 0x30, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0x7f, 0xf3, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xfd, + 0x7f, 0xfe, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x9, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, + 0xff, 0xfd, 0x6f, 0xfe, 0x30, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xfc, 0xa, 0xf3, 0x3e, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, + 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, + 0x0, 0x9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe1, 0x0, 0x0, 0x0, 0xaf, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0x33, 0x70, 0x0, 0x0, 0x8, 0x99, + 0x99, 0x99, 0x99, 0x93, 0x3e, 0xf7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x8, 0xff, 0xfb, 0x0, 0x0, + 0x79, 0x99, 0x99, 0x99, 0x99, 0x70, 0xaf, 0xc1, + 0x0, 0x9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, + 0x8, 0x10, 0x0, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x70, 0x0, 0x0, 0x1d, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfd, 0x10, 0x0, 0x0, 0x1, + 0xdf, 0xff, 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+005A "Z" */ + 0x1d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x20, 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe2, 0x0, 0x2e, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x0, 0x2, + 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, + 0x0, 0x0, 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xa9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x34, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0xff, 0xf9, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xd, 0xff, 0xf9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, + 0xf9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, + 0xff, 0xff, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xd, 0xff, 0xff, 0xa0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x8f, 0xff, 0xfe, 0x10, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0xff, 0xf5, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xff, + 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0xfe, 0xca, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6f, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0xf9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0xff, + 0xfb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0xff, 0xff, 0xf6, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc, 0xff, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, + 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, + 0xff, 0xf6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x3f, 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x15, 0x55, 0x10, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x49, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x91, 0x0, 0x0, 0x4, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x10, 0x0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc1, 0x0, 0xa, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x10, 0x0, 0xaf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + + /* U+005B "[" */ + 0xa, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x9, 0xa, + 0xff, 0xff, 0xff, 0xf9, 0x0, 0xfa, 0xa, 0xff, + 0xff, 0xf9, 0x0, 0xf, 0xf9, 0xa, 0xff, 0xf9, + 0x0, 0x0, 0xff, 0xf9, 0x8, 0xa8, 0x0, 0x0, + 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0xff, + 0xff, 0x90, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf9, + 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x90, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xf9, 0x0, 0x0, 0x0, + 0x0, 0xff, 0xff, 0x90, 0x0, 0x0, 0x0, 0xf, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x0, 0xff, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x5, 0xfc, 0x10, 0x0, + 0x0, 0x0, 0x0, 0x5, 0x10, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xfc, 0x0, + 0x0, 0x0, 0x0, 0xf, 0xff, 0xf8, 0x0, 0x0, + 0x0, 0x0, 0xff, 0xff, 0x90, 0x0, 0x0, 0x0, + 0xf, 0xff, 0xf9, 0x0, 0x0, 0x0, 0x0, 0xff, + 0xff, 0x90, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf9, + 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x90, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0xff, 0xf9, 0x7, 0x97, 0x0, 0x0, 0xf, + 0xf9, 0xa, 0xff, 0xf9, 0x0, 0x0, 0xfa, 0x9, + 0xff, 0xff, 0xf8, 0x0, 0x9, 0x9, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x9, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0x0, + + /* U+005C "\\" */ + 0x63, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x5f, 0xfb, 0x73, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0xff, + 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x3, 0xff, 0xff, 0xf2, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f, 0xff, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1f, 0xff, 0xff, 0x40, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7, 0xff, 0xff, 0xd0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdf, + 0xff, 0xf6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5f, 0xff, 0xfe, 0x10, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, + 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0xff, 0xff, 0xf2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x9f, 0xff, 0xfb, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, + 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7, 0xff, 0xfa, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdf, 0xf5, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x39, 0xe1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6, 0xff, 0xc7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, + 0xff, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x6, 0xff, 0xff, 0xd0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xdf, 0xff, 0xf6, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4f, 0xff, 0xfe, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, 0x90, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, + 0xff, 0xf2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x9f, 0xff, 0xfb, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, 0xff, 0xff, + 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6, 0xff, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xdf, 0xff, 0xf5, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f, + 0xff, 0xf3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0xb0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8f, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe, 0x20, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, + 0x0, + + /* U+005D "]" */ + 0x8f, 0xff, 0xff, 0xff, 0xff, 0xa0, 0x0, 0x8f, + 0xff, 0xff, 0xff, 0xb0, 0x80, 0x0, 0x8f, 0xff, + 0xff, 0xb0, 0x8f, 0x0, 0x0, 0x8f, 0xff, 0xb0, + 0x8f, 0xf0, 0x0, 0x0, 0x7a, 0x90, 0x8f, 0xff, + 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x8, 0xff, 0xff, 0x0, 0x0, 0x0, + 0x0, 0x8f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x8, + 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xff, 0x0, + 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xff, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xcf, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xbf, 0x70, 0x0, 0x0, 0x0, + 0x0, 0xbf, 0xff, 0x0, 0x0, 0x0, 0x0, 0x7f, + 0xff, 0xf0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xff, + 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x8, 0xff, 0xff, 0x0, 0x0, 0x0, + 0x0, 0x8f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x8, + 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, + 0xf0, 0x0, 0x0, 0x79, 0x80, 0x8f, 0xff, 0x0, + 0x0, 0x8f, 0xff, 0xb0, 0x8f, 0xf0, 0x0, 0x8f, + 0xff, 0xff, 0xb0, 0x8f, 0x0, 0x8f, 0xff, 0xff, + 0xff, 0xa0, 0x80, 0x8f, 0xff, 0xff, 0xff, 0xff, + 0xa0, 0x0, + + /* U+005E "^" */ + 0x0, 0x0, 0x0, 0x1, 0x53, 0x20, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0xc8, 0x5e, 0x20, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcf, 0x85, + 0xfe, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcf, + 0xf8, 0x5f, 0xfe, 0x20, 0x0, 0x0, 0x0, 0x0, + 0xcf, 0xff, 0x85, 0xff, 0xfe, 0x20, 0x0, 0x0, + 0x0, 0xbf, 0xff, 0xf8, 0x5f, 0xff, 0xfe, 0x20, + 0x0, 0x0, 0xbf, 0xff, 0xff, 0x74, 0xff, 0xff, + 0xfe, 0x10, 0x0, 0xbf, 0xff, 0xff, 0xa0, 0x7, + 0xff, 0xff, 0xfd, 0x10, 0xbf, 0xff, 0xff, 0xa0, + 0x0, 0x7, 0xff, 0xff, 0xfd, 0x1f, 0xff, 0xff, + 0xa0, 0x0, 0x0, 0x8, 0xff, 0xff, 0xf3, 0xff, + 0xff, 0xb0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xff, + 0x3f, 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x8, + 0xff, 0xf3, + + /* U+005F "_" */ + 0x0, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x60, 0x0, 0xcf, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x60, 0xbf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x54, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x4, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc0, 0x0, + + /* U+0060 "`" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b, 0x30, 0x0, + 0x8, 0xff, 0xc0, 0x0, 0xbf, 0xff, 0xf6, 0x0, + 0x4f, 0xff, 0xfe, 0x0, 0xb, 0xff, 0xff, 0x80, + 0x2, 0xff, 0xff, 0xf2, 0x0, 0x9f, 0xff, 0xf7, + 0x0, 0x1e, 0xff, 0xf3, 0x0, 0x5, 0xdf, 0xf0, + 0x0, 0x0, 0x1, 0x40, + + /* U+0061 "a" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0x9, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x90, 0xbd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xb, 0xfd, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x7f, 0xff, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x3, 0xef, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x0, 0x5, 0xff, 0xfd, 0x7f, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xfd, + 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6, 0xfd, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6d, 0x40, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, + + /* U+0062 "b" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xd1, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x7f, 0xff, 0x70, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xc1, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7e, 0x23, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+0063 "c" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf6, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x0, 0x1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf6, 0x0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, + 0x7, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0xa6, + 0x0, 0x0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1c, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x96, 0x0, 0x0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x60, 0x0, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf6, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x0, 0x0, 0x8, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, + + /* U+0064 "d" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x7e, 0x23, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xc1, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x50, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xdf, 0xfd, 0x1d, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x1, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x1c, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x7f, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xdf, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xc1, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7e, 0x23, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+0065 "e" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf6, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf6, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0xa6, + 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x30, 0x0, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x0, 0x0, 0x7f, 0xff, 0x70, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x96, 0x0, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x60, 0x0, + 0x7e, 0x23, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf6, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, + + /* U+0066 "f" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf6, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf6, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0xa6, + 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x30, 0x0, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x0, 0x0, 0x7f, 0xff, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xc0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfd, 0x10, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xd1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7d, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x51, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + + /* U+0067 "g" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf6, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x0, 0x1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf6, 0x0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, + 0x8, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0xa6, + 0x0, 0x0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2e, 0xf8, 0x0, 0x0, 0x4, 0xaa, 0xaa, 0xa3, + 0x0, 0x0, 0x2, 0x70, 0x0, 0x0, 0x3f, 0xff, + 0xff, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x2, + 0xff, 0xff, 0xff, 0xff, 0xf1, 0x0, 0x0, 0x30, + 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, 0x80, 0x20, + 0xb, 0xf3, 0x0, 0x0, 0x9, 0xff, 0xff, 0xf8, + 0xb, 0xe2, 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xfd, 0x7f, 0xff, 0xe0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x9, 0xff, 0xfc, 0x8, 0xfe, 0x33, 0x88, + 0x88, 0x88, 0x88, 0x60, 0xbf, 0xd1, 0x0, 0x83, + 0x2e, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x9, 0x10, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x0, 0x0, 0xaf, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0x20, 0x0, 0x0, 0x0, 0xa, 0xff, + 0xff, 0xff, 0xff, 0xe3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x22, 0x22, 0x22, 0x22, 0x10, 0x0, 0x0, + + /* U+0068 "h" */ + 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x4, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6d, 0x7f, 0xb0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x6, 0xfd, 0x7f, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f, 0xfd, + 0x7f, 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x5, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x3, + 0xef, 0xfd, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3f, 0xf7, 0x2, 0x80, 0xaf, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x33, 0x70, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x0, 0x22, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x80, 0x20, 0xa, 0xe3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x6f, 0xfe, 0x20, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xbf, 0xfc, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, 0x7f, 0xff, + 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbf, 0xfd, + 0x7f, 0xf3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc, 0xfd, 0x7f, 0x30, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xcd, 0x64, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0069 "i" */ + 0x0, 0x0, 0x51, 0x0, 0x5, 0xf2, 0x0, 0x5f, + 0xf2, 0x4, 0xff, 0xf2, 0x3f, 0xff, 0xf2, 0x6f, + 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, + 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, 0xff, + 0xf2, 0x6f, 0xff, 0xf2, 0xa, 0xff, 0xf1, 0x0, + 0xaf, 0x80, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x5, 0x0, 0x0, 0xaf, 0x80, 0xa, 0xff, + 0xf1, 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, + 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, + 0x6f, 0xff, 0xf2, 0x6f, 0xff, 0xf2, 0x4f, 0xff, + 0xf2, 0x5, 0xff, 0xf2, 0x0, 0x5f, 0xf2, 0x0, + 0x5, 0xf2, 0x0, 0x0, 0x61, + + /* U+006A "j" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9d, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x9, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xdf, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x0, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xdf, 0xfd, 0xc, 0xf4, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x40, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xd1, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0x0, 0x8, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+006B "k" */ + 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbf, + 0xff, 0x0, 0x7e, 0x20, 0x0, 0x0, 0x0, 0x0, + 0xb, 0xff, 0xff, 0x0, 0x7f, 0xe2, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xff, 0xff, 0x0, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0xb, 0xff, 0xff, 0xfb, 0x0, + 0x7f, 0xff, 0xd0, 0x0, 0x0, 0xbf, 0xff, 0xff, + 0xb0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0xb, 0xff, + 0xff, 0xfb, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0xbf, 0xff, 0xff, 0xb0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0xb, 0xff, 0xff, 0xfb, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0xaf, 0xff, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0xcf, 0xff, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0xcf, + 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xe0, 0x9c, 0xc9, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xfe, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x7f, 0xff, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x3, 0xef, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x0, 0x5, 0xff, 0xfd, 0x7f, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xfd, + 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6, 0xfd, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6d, 0x40, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, + + /* U+006C "l" */ + 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7d, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xd1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1c, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x96, 0x0, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x60, 0x0, + 0x7e, 0x33, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf6, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x60, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, + + /* U+006D "m" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0x9, 0x7e, 0x23, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x90, 0xbd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xa, 0xfd, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x10, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x6, 0xf3, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x6f, 0xff, 0x30, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0xef, 0xff, 0xa0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0xef, 0xff, + 0xa0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0xef, 0xff, 0xa0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0xef, 0xff, 0xa0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x50, 0x0, 0xef, 0xff, 0xa0, 0x1, + 0xdf, 0xfd, 0x1d, 0xf5, 0x0, 0x0, 0xef, 0xff, + 0xa0, 0x0, 0x1d, 0xf4, 0x1, 0x50, 0x0, 0x0, + 0xef, 0xff, 0xa0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0xef, 0xff, 0xa0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0xef, 0xff, 0xa0, 0x0, + 0x1, 0x40, 0x1c, 0xf5, 0x0, 0x0, 0x8f, 0xff, + 0x40, 0x0, 0x1d, 0xf4, 0x7f, 0xff, 0x50, 0x0, + 0x8, 0xf5, 0x0, 0x1, 0xdf, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x20, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xd0, 0x0, + 0x0, 0x0, 0x0, 0x7, 0xff, 0xfd, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f, 0xfd, + 0x7f, 0xd1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9, 0xfd, 0x7e, 0x20, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9d, 0x52, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, + + /* U+006E "n" */ + 0x52, 0xbf, 0xff, 0xff, 0xc0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x7e, 0x2b, 0xff, 0xff, 0xfc, 0x0, + 0x0, 0x0, 0x0, 0x9d, 0x7f, 0xe2, 0xcf, 0xff, + 0xff, 0xb0, 0x0, 0x0, 0x9, 0xfd, 0x7f, 0xfd, + 0x2c, 0xff, 0xff, 0xfa, 0x0, 0x0, 0x9f, 0xfd, + 0x7f, 0xff, 0xd1, 0xcf, 0xff, 0xff, 0xa0, 0x8, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x1c, 0xff, 0xff, + 0xf9, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x1, + 0xdf, 0xff, 0xff, 0x4a, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x1d, 0xff, 0xff, 0x4a, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x1, 0xdf, 0xff, 0x4a, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x17, + 0x88, 0x2a, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xb, 0xf3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xc, 0xe3, 0x0, 0x30, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0x60, 0x2e, 0xf7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2e, 0xf6, 0x7f, 0xff, 0x70, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xc0, 0x0, + 0x0, 0x0, 0x0, 0x6, 0xff, 0xfd, 0x7f, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xfd, + 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7, 0xfd, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7d, 0x41, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, + + /* U+006F "o" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0x1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xc1, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x50, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xdf, 0xfd, 0x1d, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x1, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x1c, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x7f, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xdf, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xc1, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0x0, 0x8, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+0070 "p" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xd1, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x30, 0x0, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x0, 0x0, 0x7f, 0xff, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0071 "q" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0x1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xc1, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xa0, 0x0, 0x0, 0x0, 0x0, 0x5, 0xff, 0xfd, + 0x3f, 0xfb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x5f, 0xf9, 0x4, 0xa0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5, 0xa0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7, 0xd1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9, 0xc1, 0x5f, 0xfd, 0x10, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8f, 0xfb, 0x7f, 0xff, 0xd0, 0x0, + 0x0, 0x0, 0x0, 0x7, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0xff, 0xfa, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0xa, 0xdd, 0xa0, 0x8f, 0xb0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0xc, 0xff, 0xfb, 0x6, 0x0, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0xc, 0xff, 0xff, + 0xb0, 0x0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0xa, + 0xff, 0xff, 0xfb, 0x0, 0x7, 0xfe, 0x23, 0x99, + 0x99, 0x60, 0xcf, 0xff, 0xff, 0xb0, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xf7, 0xc, 0xff, 0xff, 0xfa, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0x50, 0xcf, + 0xff, 0xfd, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xfc, + 0x10, 0xc, 0xff, 0xfd, 0x0, 0x0, 0x8, 0xff, + 0xff, 0xc1, 0x0, 0x1, 0xcf, 0xfd, + + /* U+0072 "r" */ + 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd1, + 0x0, 0x0, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x7e, 0x33, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x7f, 0xfe, 0x24, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, + 0xaf, 0xd1, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xfd, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x2, 0x70, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x30, 0x0, 0x2e, 0xf8, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x0, 0x0, 0x7f, 0xff, 0x70, 0x4, + 0x55, 0x20, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf0, 0xe, 0xff, 0xf3, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0xe, 0xff, 0xff, 0x30, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0xe, 0xff, 0xff, + 0xf3, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x6, + 0xff, 0xff, 0xfe, 0x30, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x6f, 0xff, 0xff, 0xe2, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x7, 0xff, 0xff, 0xfe, + 0x20, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x7f, + 0xff, 0xff, 0xe2, 0x0, 0x7f, 0xff, 0xd0, 0x0, + 0x0, 0x8, 0xff, 0xff, 0xfe, 0x20, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xe0, + 0x7f, 0xd1, 0x0, 0x0, 0x0, 0x0, 0x9, 0xff, + 0xff, 0xf2, 0x7d, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x9f, 0xff, 0xf2, 0x52, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xf2, + + /* U+0073 "s" */ + 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x20, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe2, 0x0, 0x1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, + 0x8, 0xfe, 0x23, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xa9, 0x0, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa, 0xf3, 0x3e, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x0, 0x0, 0x0, 0x22, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x0, + 0x0, 0x0, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x33, 0x70, 0x0, 0x0, 0x8, 0x99, 0x99, 0x99, + 0x99, 0x93, 0x3e, 0xf7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x0, 0x49, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xc1, 0x4, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0xa, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0xaf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+0074 "t" */ + 0x0, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0x20, 0xa, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe2, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf9, 0x5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xb0, 0x0, 0x5a, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa9, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6c, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x6, 0xff, 0xc0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, + 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x5, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x5f, 0xd1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x10, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x5f, 0xc1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5, 0xff, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, + 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xf7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xf, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6, 0xff, 0xc1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7c, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0075 "u" */ + 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x7d, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9d, 0x7f, 0xd1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x9, 0xfd, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xfd, + 0x7f, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x7, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x50, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xdf, 0xfd, 0x1d, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x1, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x1c, 0xf5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1d, 0xf4, 0x7f, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xdf, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x5f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x8, 0xff, 0xfb, 0x7, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xd1, 0x0, 0x72, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x8, 0x10, + 0x0, 0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x70, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0x10, 0x0, 0x0, 0x0, 0x8, 0xff, + 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+0076 "v" */ + 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x4, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6d, 0x7f, 0xb0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x5, 0xfd, 0x7f, 0xfb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f, 0xfd, + 0x7f, 0xff, 0xa0, 0x0, 0x0, 0x0, 0x0, 0x5, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xfd, + 0x6f, 0xfe, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xaf, 0xfc, 0xa, 0xe2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb, 0xe2, 0x0, 0x20, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, + 0x8b, 0x10, 0x2e, 0xf8, 0x0, 0x0, 0x0, 0x0, + 0x1, 0xdf, 0xff, 0x0, 0x7f, 0xff, 0x80, 0x0, + 0x0, 0x0, 0xc, 0xff, 0xff, 0x0, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0xbf, 0xff, 0xfe, 0x0, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0xa, 0xff, 0xff, + 0xf8, 0x0, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x9f, + 0xff, 0xff, 0xa0, 0x0, 0x7f, 0xff, 0xf1, 0x0, + 0x8, 0xff, 0xff, 0xfb, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x0, 0x7f, 0xff, 0xff, 0xc0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x5, 0xff, 0xff, 0xfd, 0x10, + 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x4f, 0xff, 0xff, + 0xd1, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xf1, 0x7f, + 0xff, 0xfe, 0x20, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0xf1, 0x7f, 0xff, 0xe2, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xf1, 0x7f, 0xff, 0x30, 0x0, 0x0, + 0x0, 0x0, 0x1e, 0xff, 0x90, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2, 0xe9, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0077 "w" */ + 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x7d, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9d, 0x7f, 0xd1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x9, 0xfd, 0x7f, 0xfd, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xfd, + 0x7f, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x7, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x20, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0x50, 0x0, 0x8, 0xf5, 0x0, 0x1, + 0xdf, 0xfd, 0x1d, 0xf5, 0x0, 0x0, 0x8f, 0xff, + 0x40, 0x0, 0x1d, 0xf4, 0x1, 0x50, 0x0, 0x0, + 0xef, 0xff, 0xa0, 0x0, 0x1, 0x40, 0x0, 0x0, + 0x0, 0x0, 0xef, 0xff, 0xa0, 0x0, 0x0, 0x0, + 0x1, 0x40, 0x0, 0x0, 0xef, 0xff, 0xa0, 0x0, + 0x1, 0x40, 0x1c, 0xf5, 0x0, 0x0, 0xef, 0xff, + 0xa0, 0x0, 0x1d, 0xf4, 0x7f, 0xff, 0x50, 0x0, + 0xef, 0xff, 0xa0, 0x1, 0xdf, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0xef, 0xff, 0xa0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0xef, 0xff, 0xa0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, 0xef, 0xff, + 0xa0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf1, 0x0, + 0xef, 0xff, 0xa0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf1, 0x0, 0x7f, 0xff, 0x30, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf1, 0x0, 0x7, 0xf3, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x10, + 0x0, 0x8, 0xff, 0xfd, 0x7f, 0xfe, 0x23, 0x99, + 0x99, 0x99, 0x99, 0x70, 0xaf, 0xfd, 0x7f, 0xe2, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xb, 0xfd, + 0x7e, 0x33, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x90, 0xbd, 0x63, 0x3e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0x9, 0x2, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + + /* U+0078 "x" */ + 0x7f, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1f, 0xfd, 0x7f, 0xff, 0x20, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xfd, 0x7f, 0xff, 0xc0, 0x0, + 0x0, 0x0, 0x0, 0x5, 0xff, 0xfd, 0x7f, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0x1e, 0xff, 0xfd, + 0x6f, 0xff, 0xff, 0x20, 0x0, 0x0, 0x0, 0xaf, + 0xff, 0xfc, 0xd, 0xff, 0xff, 0xb0, 0x0, 0x0, + 0x4, 0xff, 0xff, 0xf3, 0x3, 0xff, 0xff, 0xf6, + 0x0, 0x0, 0xe, 0xff, 0xff, 0x90, 0x0, 0x8f, + 0xff, 0xff, 0x10, 0x0, 0x9f, 0xff, 0xfe, 0x0, + 0x0, 0xd, 0xff, 0xff, 0xb0, 0x3, 0xff, 0xff, + 0xf5, 0x0, 0x0, 0x4, 0xff, 0xff, 0xf4, 0xc, + 0xff, 0xff, 0xa0, 0x0, 0x0, 0x0, 0x9f, 0xff, + 0xf6, 0xe, 0xff, 0xff, 0x10, 0x0, 0x0, 0x0, + 0xe, 0xff, 0xf6, 0xe, 0xff, 0xf6, 0x0, 0x0, + 0x0, 0x0, 0x5, 0xff, 0xf6, 0xe, 0xff, 0xc0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xaf, 0xf6, 0xe, + 0xff, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, + 0xa4, 0x9, 0xa6, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x19, 0x93, 0x8, 0x95, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xaf, 0xf6, 0xe, + 0xff, 0x20, 0x0, 0x0, 0x0, 0x0, 0x4, 0xff, + 0xf6, 0xe, 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, + 0xe, 0xff, 0xf6, 0xe, 0xff, 0xf6, 0x0, 0x0, + 0x0, 0x0, 0x9f, 0xff, 0xf6, 0xe, 0xff, 0xfe, + 0x10, 0x0, 0x0, 0x3, 0xff, 0xff, 0xf4, 0xc, + 0xff, 0xff, 0xa0, 0x0, 0x0, 0xd, 0xff, 0xff, + 0xb0, 0x3, 0xff, 0xff, 0xf4, 0x0, 0x0, 0x8f, + 0xff, 0xff, 0x20, 0x0, 0x9f, 0xff, 0xfe, 0x0, + 0x3, 0xff, 0xff, 0xf7, 0x0, 0x0, 0xe, 0xff, + 0xff, 0x90, 0xd, 0xff, 0xff, 0xc0, 0x0, 0x0, + 0x4, 0xff, 0xff, 0xf3, 0x6f, 0xff, 0xff, 0x20, + 0x0, 0x0, 0x0, 0xaf, 0xff, 0xfc, 0x7f, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0x1e, 0xff, 0xfd, + 0x7f, 0xff, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x5, + 0xff, 0xfd, 0x7f, 0xff, 0x30, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xfd, 0x7f, 0xf8, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1f, 0xfd, + + /* U+0079 "y" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0x7f, 0x30, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xcd, 0x7f, 0xf3, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xfd, + 0x7f, 0xfe, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbf, 0xfd, 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x0, 0x9, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x7f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x7f, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x9, + 0xff, 0xfd, 0x6f, 0xfe, 0x30, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xbf, 0xfc, 0xa, 0xf3, 0x3e, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0xb, 0xe2, 0x0, 0x22, + 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x20, + 0x0, 0x9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe1, 0x0, 0x0, 0x0, 0xaf, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0x33, 0x70, 0x0, 0x0, 0x8, 0x99, + 0x99, 0x99, 0x99, 0x93, 0x3e, 0xf7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xef, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, + 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x8, 0xff, 0xfb, 0x0, 0x0, + 0x79, 0x99, 0x99, 0x99, 0x99, 0x70, 0xaf, 0xc1, + 0x0, 0x9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, + 0x8, 0x10, 0x0, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x70, 0x0, 0x0, 0x1d, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfd, 0x10, 0x0, 0x0, 0x1, + 0xdf, 0xff, 0xff, 0xff, 0xff, 0xd1, 0x0, 0x0, + + /* U+007A "z" */ + 0x1d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x20, 0x2, 0xef, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe2, 0x0, 0x2e, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x0, 0x2, + 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, + 0x0, 0x0, 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xa9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x34, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0xff, 0xf9, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xd, 0xff, 0xf9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, + 0xf9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, + 0xff, 0xff, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xd, 0xff, 0xff, 0xa0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x8f, 0xff, 0xfe, 0x10, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0xff, 0xf5, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xff, + 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x2, 0xfe, 0xca, 0x10, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6f, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0xf9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0xff, + 0xfb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0xff, 0xff, 0xf6, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc, 0xff, 0xff, 0xb0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, + 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, + 0xff, 0xf6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x3f, 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x15, 0x55, 0x10, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x49, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x91, 0x0, 0x0, 0x4, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x10, 0x0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc1, 0x0, 0xa, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x10, 0x0, 0xaf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + + /* U+007B "{" */ + 0x0, 0x0, 0x0, 0x0, 0x4, 0xff, 0xff, 0xff, + 0x50, 0x0, 0x0, 0x0, 0x4, 0xff, 0xff, 0xff, + 0x60, 0x0, 0x0, 0x0, 0x0, 0xcf, 0xff, 0xff, + 0x60, 0x0, 0x0, 0x0, 0x3, 0x61, 0xdf, 0xff, + 0x60, 0x0, 0x0, 0x0, 0x3, 0xff, 0x61, 0xaa, + 0x60, 0x0, 0x0, 0x0, 0x1, 0xff, 0xff, 0x40, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f, 0xff, 0xf6, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0xff, + 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f, 0xff, + 0xf6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, + 0xff, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f, + 0xff, 0xf6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xff, 0xff, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4, 0xff, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x3f, + 0xfe, 0x24, 0xf9, 0x0, 0x0, 0x0, 0x0, 0x3f, + 0xff, 0xfe, 0x23, 0x0, 0x0, 0x0, 0x0, 0xb, + 0xff, 0xff, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb, 0xff, 0xf9, 0x8, 0x10, 0x0, 0x0, 0x0, + 0x0, 0x8, 0x97, 0x9, 0xfd, 0x10, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0xff, 0xf6, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xff, 0xff, 0x60, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2f, 0xff, 0xf6, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0xff, 0x60, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f, 0xff, 0xf6, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0xff, + 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f, 0xff, + 0xf6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff, + 0xff, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, + 0xff, 0x61, 0x99, 0x50, 0x0, 0x0, 0x0, 0x0, + 0x3, 0x61, 0xcf, 0xff, 0x50, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xcf, 0xff, 0xff, 0x50, 0x0, 0x0, + 0x0, 0x0, 0x4, 0xff, 0xff, 0xff, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x4, 0xff, 0xff, 0xff, 0x50, + + /* U+007C "|" */ + 0xaf, 0xff, 0xda, 0xff, 0xfd, 0xaf, 0xff, 0xda, + 0xff, 0xfd, 0xaf, 0xff, 0xda, 0xff, 0xfd, 0xaf, + 0xff, 0xda, 0xff, 0xfd, 0xaf, 0xff, 0xda, 0xff, + 0xfd, 0xaf, 0xff, 0xda, 0xff, 0xfd, 0xaf, 0xff, + 0xda, 0xff, 0xfd, 0xaf, 0xff, 0xda, 0xff, 0xfd, + 0xaf, 0xff, 0xda, 0xff, 0xfd, 0xaf, 0xff, 0xda, + 0xff, 0xfd, 0xaf, 0xff, 0xda, 0xff, 0xfd, 0xaf, + 0xff, 0xda, 0xff, 0xfd, 0xaf, 0xff, 0xda, 0xff, + 0xfd, 0xaf, 0xff, 0xda, 0xff, 0xfd, 0xaf, 0xff, + 0xda, 0xff, 0xfd, 0xaf, 0xff, 0xda, 0xff, 0xfd, + 0xaf, 0xff, 0xda, 0xff, 0xfd, 0xaf, 0xff, 0xda, + 0xff, 0xfd, 0xaf, 0xff, 0xda, 0xff, 0xfd, 0xaf, + 0xff, 0xd3, 0x44, 0x43, + + /* U+007D "}" */ + 0x8f, 0xff, 0xff, 0xe3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8f, 0xff, 0xff, 0xe3, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8f, 0xff, 0xff, 0xa0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x8f, 0xff, 0xb0, 0x72, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7a, 0x90, 0x8f, 0xe2, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, + 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0xff, + 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, + 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, + 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x8f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x8, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8f, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0xff, 0xe3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb, 0xf3, 0x3f, 0xfe, 0x20, 0x0, + 0x0, 0x0, 0x0, 0x2, 0x3f, 0xff, 0xfe, 0x20, + 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xff, 0xf8, + 0x0, 0x0, 0x0, 0x0, 0x37, 0xb, 0xff, 0xf9, + 0x0, 0x0, 0x0, 0x0, 0x3e, 0xf7, 0x8, 0x97, + 0x0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xf7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xf0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xff, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0xff, + 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, + 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, + 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6f, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x79, + 0x80, 0x8f, 0xe2, 0x0, 0x0, 0x0, 0x0, 0x8f, + 0xff, 0xb0, 0x72, 0x0, 0x0, 0x0, 0x0, 0x8f, + 0xff, 0xff, 0x90, 0x0, 0x0, 0x0, 0x0, 0x8f, + 0xff, 0xff, 0xe3, 0x0, 0x0, 0x0, 0x0, 0x8f, + 0xff, 0xff, 0xe3, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+007E "~" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x17, 0xaa, 0x83, 0x0, 0x0, 0x0, 0x0, + 0x84, 0x3e, 0xff, 0xff, 0xfd, 0x71, 0x0, 0x1, + 0xbf, 0x4e, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xbc, + 0xff, 0xf4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x4f, 0xf9, 0x20, 0x39, 0xff, 0xff, + 0xff, 0xff, 0xc0, 0xd3, 0x0, 0x0, 0x1, 0x8c, + 0xff, 0xec, 0x60, 0x2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0 +}; + + +/*--------------------- + * 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 = 182, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 0, .adv_w = 219, .box_w = 6, .box_h = 38, .ofs_x = 4, .ofs_y = -6}, + {.bitmap_index = 114, .adv_w = 237, .box_w = 11, .box_h = 9, .ofs_x = 2, .ofs_y = 22}, + {.bitmap_index = 164, .adv_w = 464, .box_w = 25, .box_h = 28, .ofs_x = 2, .ofs_y = 1}, + {.bitmap_index = 514, .adv_w = 391, .box_w = 20, .box_h = 47, .ofs_x = 2, .ofs_y = -8}, + {.bitmap_index = 984, .adv_w = 537, .box_w = 29, .box_h = 38, .ofs_x = 2, .ofs_y = -4}, + {.bitmap_index = 1535, .adv_w = 418, .box_w = 24, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 1907, .adv_w = 138, .box_w = 5, .box_h = 9, .ofs_x = 2, .ofs_y = 22}, + {.bitmap_index = 1930, .adv_w = 241, .box_w = 13, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 2132, .adv_w = 241, .box_w = 13, .box_h = 31, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2334, .adv_w = 374, .box_w = 20, .box_h = 24, .ofs_x = 2, .ofs_y = 8}, + {.bitmap_index = 2574, .adv_w = 361, .box_w = 19, .box_h = 20, .ofs_x = 2, .ofs_y = 5}, + {.bitmap_index = 2764, .adv_w = 138, .box_w = 5, .box_h = 9, .ofs_x = 2, .ofs_y = -6}, + {.bitmap_index = 2787, .adv_w = 361, .box_w = 19, .box_h = 5, .ofs_x = 2, .ofs_y = 13}, + {.bitmap_index = 2835, .adv_w = 138, .box_w = 5, .box_h = 5, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 2848, .adv_w = 376, .box_w = 22, .box_h = 35, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 3233, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 3543, .adv_w = 390, .box_w = 5, .box_h = 30, .ofs_x = 17, .ofs_y = 1}, + {.bitmap_index = 3618, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 3928, .adv_w = 391, .box_w = 19, .box_h = 31, .ofs_x = 3, .ofs_y = 0}, + {.bitmap_index = 4223, .adv_w = 391, .box_w = 20, .box_h = 30, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 4523, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 4833, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 5143, .adv_w = 391, .box_w = 19, .box_h = 31, .ofs_x = 3, .ofs_y = 0}, + {.bitmap_index = 5438, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 5748, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 6058, .adv_w = 170, .box_w = 5, .box_h = 18, .ofs_x = 3, .ofs_y = 7}, + {.bitmap_index = 6103, .adv_w = 170, .box_w = 5, .box_h = 32, .ofs_x = 3, .ofs_y = -6}, + {.bitmap_index = 6183, .adv_w = 274, .box_w = 14, .box_h = 21, .ofs_x = 2, .ofs_y = 5}, + {.bitmap_index = 6330, .adv_w = 361, .box_w = 19, .box_h = 13, .ofs_x = 2, .ofs_y = 9}, + {.bitmap_index = 6454, .adv_w = 274, .box_w = 14, .box_h = 21, .ofs_x = 2, .ofs_y = 5}, + {.bitmap_index = 6601, .adv_w = 391, .box_w = 20, .box_h = 38, .ofs_x = 2, .ofs_y = -7}, + {.bitmap_index = 6981, .adv_w = 473, .box_w = 26, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 7384, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 7694, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 8004, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 8314, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 8624, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 8934, .adv_w = 391, .box_w = 20, .box_h = 30, .ofs_x = 2, .ofs_y = 1}, + {.bitmap_index = 9234, .adv_w = 391, .box_w = 20, .box_h = 32, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 9554, .adv_w = 391, .box_w = 20, .box_h = 32, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 9874, .adv_w = 219, .box_w = 6, .box_h = 31, .ofs_x = 4, .ofs_y = 0}, + {.bitmap_index = 9967, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 10277, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 10587, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 10897, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 11207, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 11517, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 11827, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 12137, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 12447, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 12757, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 13067, .adv_w = 391, .box_w = 22, .box_h = 32, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 13419, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 13729, .adv_w = 375, .box_w = 20, .box_h = 32, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 14049, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 14359, .adv_w = 375, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 14669, .adv_w = 391, .box_w = 20, .box_h = 32, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 14989, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 15299, .adv_w = 241, .box_w = 13, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 15501, .adv_w = 377, .box_w = 22, .box_h = 35, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 15886, .adv_w = 241, .box_w = 13, .box_h = 31, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 16088, .adv_w = 356, .box_w = 19, .box_h = 12, .ofs_x = 2, .ofs_y = 19}, + {.bitmap_index = 16202, .adv_w = 362, .box_w = 23, .box_h = 5, .ofs_x = 0, .ofs_y = -8}, + {.bitmap_index = 16260, .adv_w = 186, .box_w = 8, .box_h = 11, .ofs_x = 2, .ofs_y = 22}, + {.bitmap_index = 16304, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 16614, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 16924, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 17234, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 17544, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 17854, .adv_w = 391, .box_w = 20, .box_h = 30, .ofs_x = 2, .ofs_y = 1}, + {.bitmap_index = 18154, .adv_w = 391, .box_w = 20, .box_h = 32, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 18474, .adv_w = 391, .box_w = 20, .box_h = 32, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 18794, .adv_w = 219, .box_w = 6, .box_h = 31, .ofs_x = 4, .ofs_y = 0}, + {.bitmap_index = 18887, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 19197, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 19507, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 19817, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 20127, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 20437, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 20747, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 21057, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 21367, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 21677, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 21987, .adv_w = 391, .box_w = 22, .box_h = 32, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 22339, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 22649, .adv_w = 375, .box_w = 20, .box_h = 32, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 22969, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 23279, .adv_w = 375, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 23589, .adv_w = 391, .box_w = 20, .box_h = 32, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 23909, .adv_w = 391, .box_w = 20, .box_h = 31, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 24219, .adv_w = 302, .box_w = 17, .box_h = 31, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 24483, .adv_w = 242, .box_w = 5, .box_h = 40, .ofs_x = 5, .ofs_y = -9}, + {.bitmap_index = 24583, .adv_w = 302, .box_w = 17, .box_h = 31, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 24847, .adv_w = 325, .box_w = 17, .box_h = 8, .ofs_x = 2, .ofs_y = 11} +}; + +/*--------------------- + * CHARACTER MAPPING + *--------------------*/ + + + +/*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 + } +}; + + + +/*-------------------- + * 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 = 1, + .bpp = 4, + .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 ui_font_font48Seg = { +#else +lv_font_t ui_font_font48Seg = { +#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 = 48, /*The maximum line height required by the font*/ + .base_line = 9, /*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 = -5, + .underline_thickness = 3, +#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 UI_FONT_FONT48SEG*/ + +#endif // IDLE_SCREEN_HOOK diff --git a/main/boards/genjutech-s3-1.54tft/ui_helpers.c b/main/boards/genjutech-s3-1.54tft/ui_helpers.c new file mode 100644 index 0000000..2fb3681 --- /dev/null +++ b/main/boards/genjutech-s3-1.54tft/ui_helpers.c @@ -0,0 +1,349 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.5.2 +// LVGL version: 9.2.2 +// Project name: weather0 + +#include "config.h" +#if IDLE_SCREEN_HOOK + +#include "ui_helpers.h" + +void _ui_bar_set_property(lv_obj_t * target, int id, int val) +{ + if(id == _UI_BAR_PROPERTY_VALUE_WITH_ANIM) lv_bar_set_value(target, val, LV_ANIM_ON); + if(id == _UI_BAR_PROPERTY_VALUE) lv_bar_set_value(target, val, LV_ANIM_OFF); +} + +void _ui_basic_set_property(lv_obj_t * target, int id, int val) +{ + if(id == _UI_BASIC_PROPERTY_POSITION_X) lv_obj_set_x(target, val); + if(id == _UI_BASIC_PROPERTY_POSITION_Y) lv_obj_set_y(target, val); + if(id == _UI_BASIC_PROPERTY_WIDTH) lv_obj_set_width(target, val); + if(id == _UI_BASIC_PROPERTY_HEIGHT) lv_obj_set_height(target, val); +} + +void _ui_dropdown_set_property(lv_obj_t * target, int id, int val) +{ + if(id == _UI_DROPDOWN_PROPERTY_SELECTED) lv_dropdown_set_selected(target, val); +} + +void _ui_image_set_property(lv_obj_t * target, int id, uint8_t * val) +{ + if(id == _UI_IMAGE_PROPERTY_IMAGE) lv_image_set_src(target, val); +} + +void _ui_label_set_property(lv_obj_t * target, int id, const char * val) +{ + if(id == _UI_LABEL_PROPERTY_TEXT) lv_label_set_text(target, val); +} + + +void _ui_roller_set_property(lv_obj_t * target, int id, int val) +{ + if(id == _UI_ROLLER_PROPERTY_SELECTED_WITH_ANIM) lv_roller_set_selected(target, val, LV_ANIM_ON); + if(id == _UI_ROLLER_PROPERTY_SELECTED) lv_roller_set_selected(target, val, LV_ANIM_OFF); +} + +void _ui_slider_set_property(lv_obj_t * target, int id, int val) +{ + if(id == _UI_SLIDER_PROPERTY_VALUE_WITH_ANIM) lv_slider_set_value(target, val, LV_ANIM_ON); + if(id == _UI_SLIDER_PROPERTY_VALUE) lv_slider_set_value(target, val, LV_ANIM_OFF); +} + + +void _ui_screen_change(lv_obj_t ** target, lv_screen_load_anim_t fademode, int spd, int delay, + void (*target_init)(void)) +{ + if(*target == NULL) + target_init(); + lv_screen_load_anim(*target, fademode, spd, delay, false); +} + +void _ui_screen_delete(lv_obj_t ** target) +{ + if(*target == NULL) { + lv_obj_delete(*target); + target = NULL; + } +} + +void _ui_arc_increment(lv_obj_t * target, int val) +{ + int old = lv_arc_get_value(target); + lv_arc_set_value(target, old + val); + lv_obj_send_event(target, LV_EVENT_VALUE_CHANGED, 0); +} + +void _ui_bar_increment(lv_obj_t * target, int val, int anm) +{ + int old = lv_bar_get_value(target); + lv_bar_set_value(target, old + val, anm); +} + +void _ui_slider_increment(lv_obj_t * target, int val, int anm) +{ + int old = lv_slider_get_value(target); + lv_slider_set_value(target, old + val, anm); + lv_obj_send_event(target, LV_EVENT_VALUE_CHANGED, 0); +} + +void _ui_keyboard_set_target(lv_obj_t * keyboard, lv_obj_t * textarea) +{ + //lv_keyboard_set_textarea(keyboard, textarea); +} + +void _ui_flag_modify(lv_obj_t * target, int32_t flag, int value) +{ + if(value == _UI_MODIFY_FLAG_TOGGLE) { + if(lv_obj_has_flag(target, flag)) lv_obj_remove_flag(target, flag); + else lv_obj_add_flag(target, flag); + } + else if(value == _UI_MODIFY_FLAG_ADD) lv_obj_add_flag(target, flag); + else lv_obj_remove_flag(target, flag); +} +void _ui_state_modify(lv_obj_t * target, int32_t state, int value) +{ + if(value == _UI_MODIFY_STATE_TOGGLE) { + if(lv_obj_has_state(target, state)) lv_obj_remove_state(target, state); + else lv_obj_add_state(target, state); + } + else if(value == _UI_MODIFY_STATE_ADD) lv_obj_add_state(target, state); + else lv_obj_remove_state(target, state); +} + + +void _ui_textarea_move_cursor(lv_obj_t * target, int val) + +{ + + if(val == UI_MOVE_CURSOR_UP) lv_textarea_cursor_up(target); + if(val == UI_MOVE_CURSOR_RIGHT) lv_textarea_cursor_right(target); + if(val == UI_MOVE_CURSOR_DOWN) lv_textarea_cursor_down(target); + if(val == UI_MOVE_CURSOR_LEFT) lv_textarea_cursor_left(target); + lv_obj_add_state(target, LV_STATE_FOCUSED); +} + +void scr_unloaded_delete_cb(lv_event_t * e) + +{ + + lv_obj_t ** var = lv_event_get_user_data(e); + lv_obj_delete(*var); + (*var) = NULL; + +} + +void _ui_opacity_set(lv_obj_t * target, int val) +{ + lv_obj_set_style_opa(target, val, 0); +} + +void _ui_anim_callback_free_user_data(lv_anim_t * a) +{ + lv_free(a->user_data); + a->user_data = NULL; +} + +void _ui_anim_callback_set_x(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_obj_set_x(usr->target, v); + +} + + +void _ui_anim_callback_set_y(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_obj_set_y(usr->target, v); + +} + + +void _ui_anim_callback_set_width(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_obj_set_width(usr->target, v); + +} + + +void _ui_anim_callback_set_height(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_obj_set_height(usr->target, v); + +} + + +void _ui_anim_callback_set_opacity(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_obj_set_style_opa(usr->target, v, 0); + +} + + +void _ui_anim_callback_set_image_zoom(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_image_set_scale(usr->target, v); + +} + + +void _ui_anim_callback_set_image_angle(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_image_set_rotation(usr->target, v); + +} + + +void _ui_anim_callback_set_image_frame(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + usr->val = v; + + if(v < 0) v = 0; + if(v >= usr->imgset_size) v = usr->imgset_size - 1; + lv_image_set_src(usr->target, usr->imgset[v]); +} + +int32_t _ui_anim_callback_get_x(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_obj_get_x_aligned(usr->target); + +} + + +int32_t _ui_anim_callback_get_y(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_obj_get_y_aligned(usr->target); + +} + + +int32_t _ui_anim_callback_get_width(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_obj_get_width(usr->target); + +} + + +int32_t _ui_anim_callback_get_height(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_obj_get_height(usr->target); + +} + + +int32_t _ui_anim_callback_get_opacity(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_obj_get_style_opa(usr->target, 0); + +} + +int32_t _ui_anim_callback_get_image_zoom(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_image_get_scale(usr->target); + +} + +int32_t _ui_anim_callback_get_image_angle(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_image_get_rotation(usr->target); + +} + +int32_t _ui_anim_callback_get_image_frame(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return usr->val; + +} + +void _ui_arc_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix) +{ + char buf[_UI_TEMPORARY_STRING_BUFFER_SIZE]; + + lv_snprintf(buf, sizeof(buf), "%s%d%s", prefix, (int)lv_arc_get_value(src), postfix); + + lv_label_set_text(trg, buf); +} + +void _ui_slider_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix) +{ + char buf[_UI_TEMPORARY_STRING_BUFFER_SIZE]; + + lv_snprintf(buf, sizeof(buf), "%s%d%s", prefix, (int)lv_slider_get_value(src), postfix); + + lv_label_set_text(trg, buf); +} +void _ui_checked_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * txt_on, const char * txt_off) +{ + if(lv_obj_has_state(src, LV_STATE_CHECKED)) lv_label_set_text(trg, txt_on); + else lv_label_set_text(trg, txt_off); +} + + +void _ui_spinbox_step(lv_obj_t * target, int val) + +{ + + // if(val > 0) lv_spinbox_increment(target); + + // else lv_spinbox_decrement(target); + + + // lv_obj_send_event(target, LV_EVENT_VALUE_CHANGED, 0); +} + +void _ui_switch_theme(int val) + +{ + +#ifdef UI_THEME_ACTIVE + ui_theme_set(val); +#endif +} +#endif // IDLE_SCREEN_HOOK diff --git a/main/boards/genjutech-s3-1.54tft/ui_helpers.h b/main/boards/genjutech-s3-1.54tft/ui_helpers.h new file mode 100644 index 0000000..9291907 --- /dev/null +++ b/main/boards/genjutech-s3-1.54tft/ui_helpers.h @@ -0,0 +1,154 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.5.2 +// LVGL version: 9.2.2 +// Project name: weather0 + +#ifndef _SLS_UI_HELPERS_H +#define _SLS_UI_HELPERS_H + +#include "config.h" +#if IDLE_SCREEN_HOOK + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define _UI_TEMPORARY_STRING_BUFFER_SIZE 32 +#define _UI_BAR_PROPERTY_VALUE 0 +#define _UI_BAR_PROPERTY_VALUE_WITH_ANIM 1 +void _ui_bar_set_property(lv_obj_t * target, int id, int val); + +#define _UI_BASIC_PROPERTY_POSITION_X 0 +#define _UI_BASIC_PROPERTY_POSITION_Y 1 +#define _UI_BASIC_PROPERTY_WIDTH 2 +#define _UI_BASIC_PROPERTY_HEIGHT 3 +void _ui_basic_set_property(lv_obj_t * target, int id, int val); + +#define _UI_DROPDOWN_PROPERTY_SELECTED 0 +void _ui_dropdown_set_property(lv_obj_t * target, int id, int val); + +#define _UI_IMAGE_PROPERTY_IMAGE 0 +void _ui_image_set_property(lv_obj_t * target, int id, uint8_t * val); + +#define _UI_LABEL_PROPERTY_TEXT 0 +void _ui_label_set_property(lv_obj_t * target, int id, const char * val); + +#define _UI_ROLLER_PROPERTY_SELECTED 0 +#define _UI_ROLLER_PROPERTY_SELECTED_WITH_ANIM 1 +void _ui_roller_set_property(lv_obj_t * target, int id, int val); + +#define _UI_SLIDER_PROPERTY_VALUE 0 +#define _UI_SLIDER_PROPERTY_VALUE_WITH_ANIM 1 +void _ui_slider_set_property(lv_obj_t * target, int id, int val); + +void _ui_screen_change(lv_obj_t ** target, lv_screen_load_anim_t fademode, int spd, int delay, + void (*target_init)(void)); + +void _ui_screen_delete(lv_obj_t ** target); + +void _ui_arc_increment(lv_obj_t * target, int val); + +void _ui_bar_increment(lv_obj_t * target, int val, int anm); + +void _ui_slider_increment(lv_obj_t * target, int val, int anm); + +void _ui_keyboard_set_target(lv_obj_t * keyboard, lv_obj_t * textarea); + +#define _UI_MODIFY_FLAG_ADD 0 +#define _UI_MODIFY_FLAG_REMOVE 1 +#define _UI_MODIFY_FLAG_TOGGLE 2 +void _ui_flag_modify(lv_obj_t * target, int32_t flag, int value); + +#define _UI_MODIFY_STATE_ADD 0 +#define _UI_MODIFY_STATE_REMOVE 1 +#define _UI_MODIFY_STATE_TOGGLE 2 +void _ui_state_modify(lv_obj_t * target, int32_t state, int value); + +#define UI_MOVE_CURSOR_UP 0 +#define UI_MOVE_CURSOR_RIGHT 1 +#define UI_MOVE_CURSOR_DOWN 2 +#define UI_MOVE_CURSOR_LEFT 3 +void _ui_textarea_move_cursor(lv_obj_t * target, int val) +; + + +void scr_unloaded_delete_cb(lv_event_t * e); + +void _ui_opacity_set(lv_obj_t * target, int val); + +/** Describes an animation*/ +typedef struct _ui_anim_user_data_t { + lv_obj_t * target; + lv_image_dsc_t ** imgset; + int32_t imgset_size; + int32_t val; +} ui_anim_user_data_t; +void _ui_anim_callback_free_user_data(lv_anim_t * a); + +void _ui_anim_callback_set_x(lv_anim_t * a, int32_t v); + +void _ui_anim_callback_set_y(lv_anim_t * a, int32_t v); + +void _ui_anim_callback_set_width(lv_anim_t * a, int32_t v); + +void _ui_anim_callback_set_height(lv_anim_t * a, int32_t v); + + +void _ui_anim_callback_set_opacity(lv_anim_t * a, int32_t v); + + +void _ui_anim_callback_set_image_zoom(lv_anim_t * a, int32_t v); + + +void _ui_anim_callback_set_image_angle(lv_anim_t * a, int32_t v); + + +void _ui_anim_callback_set_image_frame(lv_anim_t * a, int32_t v); + + +int32_t _ui_anim_callback_get_x(lv_anim_t * a); + +int32_t _ui_anim_callback_get_y(lv_anim_t * a); + +int32_t _ui_anim_callback_get_width(lv_anim_t * a); + + +int32_t _ui_anim_callback_get_height(lv_anim_t * a); + + +int32_t _ui_anim_callback_get_opacity(lv_anim_t * a); + + +int32_t _ui_anim_callback_get_image_zoom(lv_anim_t * a); + + +int32_t _ui_anim_callback_get_image_angle(lv_anim_t * a); + + +int32_t _ui_anim_callback_get_image_frame(lv_anim_t * a); + + +void _ui_arc_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix); + +void _ui_slider_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix); + +void _ui_checked_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * txt_on, const char * txt_off); + +void _ui_spinbox_step(lv_obj_t * target, int val) +; + + +void _ui_switch_theme(int val) +; + + + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /* _SLS_UI_HELPERS_H */ + +#endif // IDLE_SCREEN_HOOK diff --git a/main/boards/genjutech-s3-1.54tft/ui_img_xiaozhi_48_png.c b/main/boards/genjutech-s3-1.54tft/ui_img_xiaozhi_48_png.c new file mode 100644 index 0000000..c43ad89 --- /dev/null +++ b/main/boards/genjutech-s3-1.54tft/ui_img_xiaozhi_48_png.c @@ -0,0 +1,107 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.5.2 +// LVGL version: 9.2.2 +// Project name: weather0 + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl.h" +#endif + +#ifndef LV_ATTRIBUTE_MEM_ALIGN + #define LV_ATTRIBUTE_MEM_ALIGN +#endif + +#include "config.h" +#if IDLE_SCREEN_HOOK + +// IMAGE DATA: assets/xiaozhi_48.png +const LV_ATTRIBUTE_MEM_ALIGN uint8_t ui_img_xiaozhi_48_png_data[] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x3B,0xEF,0xBD,0x8D,0x9F,0x0B,0xDF,0x1B,0xDF,0x23,0xDF,0x23,0xDF,0x23,0xDF,0x23,0xDF,0x1B,0xDF,0x1B,0xDF,0x23,0xDF,0x23,0xDF,0x23,0xDF,0x23,0xDF,0x1B,0x9F,0x0B,0xBD,0x8D,0x3B,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x3B,0xEF, + 0x5C,0xBE,0xFF,0x23,0xBF,0x13,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xBF,0x13,0xBF,0x13,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xBF,0x13,0xFF,0x23,0x5C,0xBE,0x3B,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0xFC,0xDE,0x3B,0xEF,0x7C,0xBE,0xDF,0x1B,0xBF,0x13,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xBF,0x13,0xBF,0x13,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xBF,0x13,0xDF,0x1B,0x7C,0xBE,0x3B,0xEF, + 0xFC,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xDF,0x3B,0xEF,0x7C,0xC6,0x1F,0x2C,0xDF,0x1B,0xFF,0x23,0xFF,0x23,0xDF,0x1B,0xDF,0x1B,0xFF,0x23,0xFF,0x23,0xDF,0x1B,0x1F,0x2C,0x7C,0xC6,0x3B,0xEF,0x1C,0xDF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3B,0xEF,0x7C,0xBE,0xDF,0x1B,0xBF,0x0B,0xDF,0x13,0xBF,0x13,0xBF,0x13,0xDF,0x13,0xBF,0x0B,0xDF,0x1B,0x7C,0xBE,0x3B,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7, + 0x1C,0xE7,0x1C,0xE7,0xFC,0xDE,0x3B,0xEF,0x7C,0xBE,0xDF,0x1B,0x9F,0x0B,0xBF,0x13,0xBF,0x13,0x9F,0x0B,0xDF,0x1B,0x7C,0xBE,0x3B,0xEF,0xFC,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3B,0xEF,0x5C,0xB6,0x5E,0x3C,0xBF,0x13,0xBF,0x13,0x5E,0x3C,0x5C,0xB6,0x3B,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7, + 0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xDF,0x3B,0xEF,0x5B,0xF7,0x7E,0x44,0x7E,0x44,0x5B,0xF7,0x3B,0xEF,0x1C,0xDF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xDC,0xD6,0xDC,0xD6,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7, + 0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3B,0xE7,0x3B,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7, + 0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x5D,0xEF,0x5D,0xEF,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x5D,0xEF,0x5D,0xEF,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0xFB,0xDE,0xB6,0xB5,0xB6,0xB5,0xFB,0xDE,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0xFB,0xDE,0xB6,0xB5,0xB6,0xB5,0xFB,0xDE,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0x18,0xC6, + 0xE7,0x39,0x20,0x00,0x20,0x00,0xE7,0x39,0x18,0xC6,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0x18,0xC6,0xE7,0x39,0x20,0x00,0x20,0x00,0xE7,0x39,0x18,0xC6,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xC7,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC7,0x39,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xC7,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xC7,0x39,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x5D,0xEF,0xB6,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB6,0xB5,0x5D,0xEF,0xFB,0xDE,0xFB,0xDE,0x5D,0xEF,0xB6,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB6,0xB5,0x5D,0xEF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x5D,0xEF,0xD7,0xBD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD7,0xBD,0x5D,0xEF,0xFB,0xDE,0xFB,0xDE,0x5D,0xEF,0xD7,0xBD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD7,0xBD,0x5D,0xEF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xC7,0x39, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE7,0x39,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xC7,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE7,0x39,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0x18,0xC6,0xC7,0x39,0x61,0x08,0x61,0x08,0xE7,0x39,0x18,0xC6,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0x18,0xC6,0xC7,0x39,0x61,0x08,0x61,0x08,0xE7,0x39, + 0x18,0xC6,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x5D,0xEF,0xFB,0xDE,0x96,0xB5,0x96,0xB5,0xFB,0xDE,0x5D,0xEF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x5D,0xEF,0xFB,0xDE,0x96,0xB5,0x96,0xB5,0xFB,0xDE,0x5D,0xEF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x7D,0xEF,0xBE,0xF7,0x9E,0xF7,0x3C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x3C,0xE7,0x9E,0xF7,0xBE,0xF7,0x7D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xDB,0xDE, + 0x96,0xB5,0x79,0xCE,0x3C,0xE7,0x5D,0xEF,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x5D,0xEF,0x3C,0xE7,0x79,0xCE,0x96,0xB5,0xDB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x9A,0xD6,0xEF,0x7B,0x4D,0x6B,0x92,0x94,0xB6,0xB5,0x9A,0xD6,0x1C,0xE7,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0x1C,0xE7,0x9A,0xD6,0xB6,0xB5,0x92,0x94,0x4D,0x6B,0xEF,0x7B, + 0x9A,0xD6,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xDB,0xDE,0xB6,0xB5,0x51,0x8C,0x8E,0x73,0x8E,0x73,0xCF,0x7B,0x30,0x84,0x92,0x94,0x92,0x94,0x30,0x84,0xCF,0x7B,0x8E,0x73,0x8E,0x73,0x51,0x8C,0xB6,0xB5,0xDB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0xBA,0xD6,0xF7,0xBD,0x34,0xA5,0xD3,0x9C,0x92,0x94,0x92,0x94,0xD3,0x9C,0x34,0xA5,0xF7,0xBD,0xBA,0xD6,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7, + 0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x5D,0xEF,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0x5D,0xEF,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7, + 0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE, + 0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7, + 0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7, + 0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7, + 0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7, + 0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7, + 0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + + //alpha channel data: + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xA1,0xA1,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x29,0xDE,0xDE,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2C,0xEC,0xEC,0x2C,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xAA,0xAA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA5,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA6,0xA6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA3,0xA3,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0x21,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1B,0xAF,0xAF,0x1B,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x21,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x54,0xD5,0xDF,0xDD,0xDD,0xDD,0xDD,0xDD,0xDD,0xDD,0xDB,0xF4,0xF4,0xDB,0xDD,0xDD,0xDD,0xDD,0xDD,0xDD,0xDD,0xDF,0xD5,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0xDB,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDA,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xEE,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0xEF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEE,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0xDA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xD9,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x53,0xD5,0xDE,0xDA,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0xDA,0xDE,0xD4,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x1F,0x25,0xAE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAD,0x25,0x1F,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x14,0x13,0x13,0x13,0x13,0x0F,0x03,0x17, + 0xDC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDC,0x17,0x03,0x0F,0x13,0x13,0x13,0x13,0x14,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x74,0xE6,0xEE,0xEC,0xEC,0xEC,0xEC,0xEC,0xE0,0x90,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDE, + 0x8E,0xDF,0xED,0xEC,0xEC,0xEC,0xEC,0xEE,0xE7,0x77,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEE,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x9E,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0xF6,0xB1,0xE2,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE3,0xB3,0xF6,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0x9B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x35,0x3C,0x3A,0x3A,0x3A,0x3A,0x3B,0x2E,0x22, + 0xDC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDC,0x22,0x2E,0x3B,0x3A,0x3A,0x3A,0x3A,0x3C,0x35,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0, + 0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22, + 0xE1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE1,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,0xE5,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE5, + 0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0xAE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAE,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xB5,0xB4,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB4,0xB5,0x5D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +}; +const lv_image_dsc_t ui_img_xiaozhi_48_png = { + .header.w = 48, + .header.h = 48, + .data_size = sizeof(ui_img_xiaozhi_48_png_data), + .header.cf = LV_COLOR_FORMAT_NATIVE_WITH_ALPHA, + .header.magic = LV_IMAGE_HEADER_MAGIC, + .data = ui_img_xiaozhi_48_png_data +}; + + +#endif // IDLE_SCREEN_HOOK diff --git a/main/boards/genjutech-s3-1.54tft/weather_service.cc b/main/boards/genjutech-s3-1.54tft/weather_service.cc new file mode 100644 index 0000000..d089043 --- /dev/null +++ b/main/boards/genjutech-s3-1.54tft/weather_service.cc @@ -0,0 +1,365 @@ +/** + * @file weather_service.cc + * @brief Weather API service implementation + * @version 1.0 + */ + +#include "weather_service.h" +#include +#include +#include +#include + +static const char *TAG = "WeatherService"; + + +WeatherService::WeatherService() { + city_code_ = "101010100"; // Default: Beijing + auto_detect_enabled_ = false; +} + +WeatherService::~WeatherService() { +} + +void WeatherService::Initialize(const std::string& city_code) { + if (city_code.empty()) { + // Auto-detect city code by IP + ESP_LOGI(TAG, "Auto-detecting city code by IP address..."); + if (AutoDetectCityCode()) { + ESP_LOGI(TAG, "Auto-detected city code: %s", city_code_.c_str()); + auto_detect_enabled_ = true; + } else { + ESP_LOGW(TAG, "Failed to auto-detect city code, using default: Beijing (101010100)"); + city_code_ = "101010100"; + auto_detect_enabled_ = false; + } + } else { + city_code_ = city_code; + auto_detect_enabled_ = false; + ESP_LOGI(TAG, "Weather service initialized with city code: %s", city_code_.c_str()); + } +} + +bool WeatherService::AutoDetectCityCode() { + // Use weather.com.cn IP geolocation API + time_t now; + time(&now); + char url[256]; + snprintf(url, sizeof(url), "http://wgeo.weather.com.cn/ip/?_=%ld", (long)now); + + ESP_LOGI(TAG, "Fetching city code from: %s", url); + + // Allocate response buffer + char *buffer = (char*)malloc(2048); + if (!buffer) { + ESP_LOGE(TAG, "Failed to allocate buffer"); + return false; + } + memset(buffer, 0, 2048); + + // Configure HTTP client with larger buffer + esp_http_client_config_t config = {}; + config.url = url; + config.timeout_ms = 15000; + config.buffer_size = 2048; // Important: increase buffer size + config.buffer_size_tx = 1024; + config.disable_auto_redirect = false; + config.max_redirection_count = 3; + + esp_http_client_handle_t client = esp_http_client_init(&config); + if (!client) { + ESP_LOGE(TAG, "Failed to init HTTP client"); + free(buffer); + return false; + } + + // Set headers + esp_http_client_set_header(client, "User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); + esp_http_client_set_header(client, "Referer", "http://www.weather.com.cn/"); + esp_http_client_set_header(client, "Accept", "*/*"); + + bool success = false; + + // Open connection and read + esp_err_t err = esp_http_client_open(client, 0); + if (err == ESP_OK) { + int content_length = esp_http_client_fetch_headers(client); + int status_code = esp_http_client_get_status_code(client); + + ESP_LOGI(TAG, "HTTP Status = %d, Content-Length = %d", status_code, content_length); + + if (status_code == 200 || status_code == 302) { + // Read response data + int total_read = 0; + int read_len; + + while (total_read < 2047) { + read_len = esp_http_client_read(client, buffer + total_read, 2047 - total_read); + if (read_len <= 0) { + break; + } + total_read += read_len; + } + + buffer[total_read] = '\0'; + + ESP_LOGI(TAG, "Read %d bytes from API", total_read); + + if (total_read > 0) { + // Print first 200 chars for debugging + char preview[201]; + int preview_len = (total_read > 200) ? 200 : total_read; + memcpy(preview, buffer, preview_len); + preview[preview_len] = '\0'; + ESP_LOGI(TAG, "Response preview: %s", preview); + + std::string response(buffer); + + // Try multiple parsing patterns + size_t id_pos = response.find("id=\""); + if (id_pos != std::string::npos) { + // Pattern: id="101010100" (JavaScript variable with double quotes) + size_t start = id_pos + 4; // Skip id=" + size_t end = response.find("\"", start); + if (end != std::string::npos && end > start) { + city_code_ = response.substr(start, end - start); + ESP_LOGI(TAG, "✅ Detected city code: %s", city_code_.c_str()); + success = true; + } + } else if ((id_pos = response.find("id='")) != std::string::npos) { + // Pattern: id='101010100' (JavaScript variable with single quotes) + size_t start = id_pos + 4; + size_t end = response.find("'", start); + if (end != std::string::npos && end > start) { + city_code_ = response.substr(start, end - start); + ESP_LOGI(TAG, "✅ Detected city code: %s", city_code_.c_str()); + success = true; + } + } else if ((id_pos = response.find("id\":\"")) != std::string::npos) { + // Pattern: "id":"101010100" (JSON format) + size_t start = id_pos + 5; + size_t end = response.find("\"", start); + if (end != std::string::npos && end > start) { + city_code_ = response.substr(start, end - start); + ESP_LOGI(TAG, "✅ Detected city code: %s", city_code_.c_str()); + success = true; + } + } else { + ESP_LOGW(TAG, "❌ City code pattern not found in response"); + } + } else { + ESP_LOGW(TAG, "❌ No data read from API"); + } + } else { + ESP_LOGW(TAG, "❌ HTTP status: %d", status_code); + } + + esp_http_client_close(client); + } else { + ESP_LOGE(TAG, "❌ Failed to connect: %s", esp_err_to_name(err)); + } + + esp_http_client_cleanup(client); + free(buffer); + + return success; +} + +void WeatherService::SetWeatherCallback(std::function callback) { + weather_callback_ = callback; +} + +void WeatherService::FetchWeather() { + ESP_LOGI(TAG, "Fetching weather data for city: %s", city_code_.c_str()); + + // Construct URL + time_t now; + time(&now); + char url[256]; + snprintf(url, sizeof(url), "http://d1.weather.com.cn/weather_index/%s.html?_=%ld", + city_code_.c_str(), (long)now); + + // Configure HTTP client (no event handler, read directly) + esp_http_client_config_t config = {}; + config.url = url; + config.timeout_ms = 10000; + + esp_http_client_handle_t client = esp_http_client_init(&config); + + // Set User-Agent header + esp_http_client_set_header(client, "User-Agent", + "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38"); + esp_http_client_set_header(client, "Referer", "http://www.weather.com.cn/"); + + // Open connection + esp_err_t err = esp_http_client_open(client, 0); + + if (err == ESP_OK) { + // Read response + int content_length = esp_http_client_fetch_headers(client); + int status_code = esp_http_client_get_status_code(client); + + ESP_LOGI(TAG, "HTTP GET Status = %d, Content-Length = %d", status_code, content_length); + + if (status_code == 200) { + // Allocate buffer (use 8KB if content_length unknown or too large) + int buffer_size = (content_length > 0 && content_length < 8192) ? content_length + 1 : 8192; + char *buffer = (char*)malloc(buffer_size); + + if (buffer) { + int total_read = 0; + int read_len; + + // Read all data + while ((read_len = esp_http_client_read(client, buffer + total_read, buffer_size - total_read - 1)) > 0) { + total_read += read_len; + if (total_read >= buffer_size - 1) { + break; + } + } + + buffer[total_read] = '\0'; + + ESP_LOGI(TAG, "Read %d bytes of weather data", total_read); + + std::string response(buffer); + ParseWeatherData(response); + + if (weather_callback_) { + weather_callback_(last_weather_data_); + } + + free(buffer); + } else { + ESP_LOGE(TAG, "Failed to allocate buffer for response"); + } + } else { + ESP_LOGW(TAG, "HTTP request returned status code: %d", status_code); + } + } else { + ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); + } + + esp_http_client_close(client); + esp_http_client_cleanup(client); +} + +std::string WeatherService::ExtractJsonValue(const std::string& json, const std::string& key) { + size_t key_pos = json.find("\"" + key + "\":"); + if (key_pos == std::string::npos) { + return ""; + } + + size_t value_start = json.find("\"", key_pos + key.length() + 3); + if (value_start == std::string::npos) { + return ""; + } + value_start++; + + size_t value_end = json.find("\"", value_start); + if (value_end == std::string::npos) { + return ""; + } + + return json.substr(value_start, value_end - value_start); +} + +void WeatherService::ParseWeatherData(const std::string& response) { + ESP_LOGI(TAG, "Parsing weather data..."); + + try { + // Extract dataSK JSON section + size_t sk_start = response.find("dataSK ="); + size_t sk_end = response.find(";var dataZS"); + + if (sk_start != std::string::npos && sk_end != std::string::npos) { + std::string dataSK = response.substr(sk_start + 8, sk_end - sk_start - 8); + + // Parse JSON using cJSON + cJSON *root = cJSON_Parse(dataSK.c_str()); + if (root) { + cJSON *city = cJSON_GetObjectItem(root, "cityname"); + cJSON *temp = cJSON_GetObjectItem(root, "temp"); + cJSON *humidity = cJSON_GetObjectItem(root, "SD"); + cJSON *weather = cJSON_GetObjectItem(root, "weather"); + cJSON *wind_dir = cJSON_GetObjectItem(root, "WD"); + cJSON *wind_speed = cJSON_GetObjectItem(root, "WS"); + cJSON *aqi = cJSON_GetObjectItem(root, "aqi"); + + if (city && cJSON_IsString(city)) { + last_weather_data_.city_name = city->valuestring; + } + if (temp && cJSON_IsString(temp)) { + last_weather_data_.temperature = temp->valuestring; + } + if (humidity && cJSON_IsString(humidity)) { + last_weather_data_.humidity = humidity->valuestring; + } + if (weather && cJSON_IsString(weather)) { + last_weather_data_.weather_desc = weather->valuestring; + } + if (wind_dir && cJSON_IsString(wind_dir)) { + last_weather_data_.wind_direction = wind_dir->valuestring; + } + if (wind_speed && cJSON_IsString(wind_speed)) { + last_weather_data_.wind_speed = wind_speed->valuestring; + } + if (aqi && cJSON_IsNumber(aqi)) { + last_weather_data_.aqi = aqi->valueint; + + // Determine AQI description + if (last_weather_data_.aqi > 200) { + last_weather_data_.aqi_desc = "重度"; + } else if (last_weather_data_.aqi > 150) { + last_weather_data_.aqi_desc = "中度"; + } else if (last_weather_data_.aqi > 100) { + last_weather_data_.aqi_desc = "轻度"; + } else if (last_weather_data_.aqi > 50) { + last_weather_data_.aqi_desc = "良"; + } else { + last_weather_data_.aqi_desc = "优"; + } + } + + cJSON_Delete(root); + } + } + + // Extract forecast data (f section) + size_t fc_start = response.find("\"f\":["); + size_t fc_end = response.find(",{\"fa", fc_start); + + if (fc_start != std::string::npos && fc_end != std::string::npos) { + std::string dataFC = response.substr(fc_start + 5, fc_end - fc_start - 5); + + cJSON *root = cJSON_Parse(dataFC.c_str()); + if (root) { + cJSON *temp_low = cJSON_GetObjectItem(root, "fd"); + cJSON *temp_high = cJSON_GetObjectItem(root, "fc"); + + if (temp_low && cJSON_IsString(temp_low)) { + last_weather_data_.temp_low = temp_low->valuestring; + } + if (temp_high && cJSON_IsString(temp_high)) { + last_weather_data_.temp_high = temp_high->valuestring; + } + + cJSON_Delete(root); + } + } + + last_weather_data_.last_update_time = esp_timer_get_time() / 1000000; + + ESP_LOGI(TAG, "Weather parsed: City=%s, Temp=%s℃, Humidity=%s, AQI=%d(%s)", + last_weather_data_.city_name.c_str(), + last_weather_data_.temperature.c_str(), + last_weather_data_.humidity.c_str(), + last_weather_data_.aqi, + last_weather_data_.aqi_desc.c_str()); + + } catch (...) { + ESP_LOGE(TAG, "Failed to parse weather data"); + } +} + diff --git a/main/boards/genjutech-s3-1.54tft/weather_service.h b/main/boards/genjutech-s3-1.54tft/weather_service.h new file mode 100644 index 0000000..3ce9bbf --- /dev/null +++ b/main/boards/genjutech-s3-1.54tft/weather_service.h @@ -0,0 +1,46 @@ +/** + * @file weather_service.h + * @brief Weather API service for fetching weather data + * @version 1.0 + * @date 2025-01-10 + */ +#pragma once + +#include +#include +#include "idle_screen.h" + +class WeatherService { +public: + WeatherService(); + ~WeatherService(); + + // Initialize weather service with city code (optional, will auto-detect if empty) + void Initialize(const std::string& city_code = ""); // Empty: auto-detect + + // Auto-detect city code by IP address + bool AutoDetectCityCode(); + + // Fetch weather data (async) + void FetchWeather(); + + // Set callback for when weather data is updated + void SetWeatherCallback(std::function callback); + + // Get last fetched weather data + const WeatherData& GetLastWeatherData() const { return last_weather_data_; } + + // Get current city code + const std::string& GetCityCode() const { return city_code_; } + +private: + void ParseWeatherData(const std::string& response); + std::string ExtractJsonValue(const std::string& json, const std::string& key); + +private: + std::string city_code_; + WeatherData last_weather_data_; + std::function weather_callback_; + bool auto_detect_enabled_; +}; + diff --git a/main/boards/jinao-s3/config.h b/main/boards/jinao-s3/config.h new file mode 100644 index 0000000..c34c0b5 --- /dev/null +++ b/main/boards/jinao-s3/config.h @@ -0,0 +1,62 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_13 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45 + +#define AUDIO_CODEC_USE_PCA9557 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR 0x82 + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +/* Camera pins */ +#define CAMERA_PIN_PWDN -1 +#define CAMERA_PIN_RESET -1 +#define CAMERA_PIN_XCLK 5 +#define CAMERA_PIN_SIOD 1 +#define CAMERA_PIN_SIOC 2 + +#define CAMERA_PIN_D7 9 +#define CAMERA_PIN_D6 4 +#define CAMERA_PIN_D5 6 +#define CAMERA_PIN_D4 15 +#define CAMERA_PIN_D3 17 +#define CAMERA_PIN_D2 8 +#define CAMERA_PIN_D1 18 +#define CAMERA_PIN_D0 16 +#define CAMERA_PIN_VSYNC 3 +#define CAMERA_PIN_HREF 46 +#define CAMERA_PIN_PCLK 7 + +#define XCLK_FREQ_HZ 24000000 + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/jinao-s3/config.json b/main/boards/jinao-s3/config.json new file mode 100644 index 0000000..9ad3cc9 --- /dev/null +++ b/main/boards/jinao-s3/config.json @@ -0,0 +1,12 @@ +{ + "target": "esp32s3", + "builds": [ + { + "name": "jinao-s3", + "sdkconfig_append": [ + "CONFIG_USE_DEVICE_AEC=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"" + ] + } + ] +} \ No newline at end of file diff --git a/main/boards/jinao-s3/jinao_s3_board.cc b/main/boards/jinao-s3/jinao_s3_board.cc new file mode 100644 index 0000000..d25f0b3 --- /dev/null +++ b/main/boards/jinao-s3/jinao_s3_board.cc @@ -0,0 +1,262 @@ +#include "wifi_board.h" +#include "codecs/box_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "esp32_camera.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "JiNaoS3Board" + +class Pca9557 : public I2cDevice { +public: + Pca9557(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + WriteReg(0x01, 0x03); + WriteReg(0x03, 0xf8); + } + + void SetOutputState(uint8_t bit, uint8_t level) { + uint8_t data = ReadReg(0x01); + data = (data & ~(1 << bit)) | (level << bit); + WriteReg(0x01, data); + } +}; + +class CustomAudioCodec : public BoxAudioCodec { +private: + Pca9557* pca9557_; + +public: + CustomAudioCodec(i2c_master_bus_handle_t i2c_bus, Pca9557* pca9557) + : BoxAudioCodec(i2c_bus, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE), + pca9557_(pca9557) { + } + + virtual void EnableOutput(bool enable) override { + BoxAudioCodec::EnableOutput(enable); + if (enable) { + pca9557_->SetOutputState(1, 1); + } else { + pca9557_->SetOutputState(1, 0); + } + } +}; + +class JiNaoS3Board : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + i2c_master_dev_handle_t pca9557_handle_; + Button boot_button_; + LcdDisplay* display_; + Pca9557* pca9557_; + Esp32Camera* camera_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + + // Initialize PCA9557 + pca9557_ = new Pca9557(i2c_bus_, 0x19); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_40; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_41; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_NC; + io_config.dc_gpio_num = GPIO_NUM_39; + io_config.spi_mode = 2; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + pca9557_->SetOutputState(0, 0); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeTouch() + { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH, + .y_max = DISPLAY_HEIGHT, + .rst_gpio_num = GPIO_NUM_NC, // Shared with LCD reset + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 1, + .mirror_x = 1, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG(); + tp_io_config.scl_speed_hz = 400000; + + esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle); + esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp); + assert(tp); + + /* Add touch input (for selected screen) */ + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + + lvgl_port_add_touch(&touch_cfg); + } + + void InitializeCamera() { + // Open camera power + pca9557_->SetOutputState(2, 0); + + camera_config_t config = {}; + config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 + config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 + config.pin_d0 = CAMERA_PIN_D0; + config.pin_d1 = CAMERA_PIN_D1; + config.pin_d2 = CAMERA_PIN_D2; + config.pin_d3 = CAMERA_PIN_D3; + config.pin_d4 = CAMERA_PIN_D4; + config.pin_d5 = CAMERA_PIN_D5; + config.pin_d6 = CAMERA_PIN_D6; + config.pin_d7 = CAMERA_PIN_D7; + config.pin_xclk = CAMERA_PIN_XCLK; + config.pin_pclk = CAMERA_PIN_PCLK; + config.pin_vsync = CAMERA_PIN_VSYNC; + config.pin_href = CAMERA_PIN_HREF; + config.pin_sccb_sda = -1; // 这里写-1 表示使用已经初始化的I2C接口 + config.pin_sccb_scl = CAMERA_PIN_SIOC; + config.sccb_i2c_port = 1; + config.pin_pwdn = CAMERA_PIN_PWDN; + config.pin_reset = CAMERA_PIN_RESET; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_VGA; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + camera_ = new Esp32Camera(config); + } + +public: + JiNaoS3Board() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeSt7789Display(); + InitializeTouch(); + InitializeButtons(); + InitializeCamera(); + + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static CustomAudioCodec audio_codec( + i2c_bus_, + pca9557_); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(JiNaoS3Board); diff --git a/main/boards/jiuchuan-s3/README.md b/main/boards/jiuchuan-s3/README.md index 2b2d755..7810a77 100644 --- a/main/boards/jiuchuan-s3/README.md +++ b/main/boards/jiuchuan-s3/README.md @@ -1,25 +1,25 @@ -# jiuchuan-xiaozhi-sound -九川科技小智AI音箱 - -## 🛠️ 编译指南 -**开发环境**:ESP-IDF v5.4.1 - -### 编译步骤: -> ⚠️ **提示**:若在编译过程中访问在线库失败,可以尝试切换加速器状态,或修改 [idf_component.yml] 文件,替换为国内镜像源。 - -1. 使用 VSCode 打开项目文件夹; -2. 清除工程(Clean Project); -3. 设置 ESP-IDF 版本为 `v5.4.1`; -4. 点击 VSCode 右下角提示,生成 [compile_commands.json] 文件; -5. 设置目标设备为 `[esp32s3] -> [JTAG]`; -6. 打开 **SDK Configuration Editor**; -7. 配置自定义分区表路径为:`partitions/v2/16m.csv`; -8. 设置 **Board Type** 为 **九川科技**; -9. 保存配置并开始编译。 - -## 🔌 烧录步骤 -1. 使用数据线连接电脑与音箱; -2. 关闭设备电源后,长按电源键不松手; -3. 在烧录工具中选择对应的串口(COM Port); -4. 点击烧录按钮,选择 UART 模式; +# jiuchuan-xiaozhi-sound +九川科技小智AI音箱 + +## 🛠️ 编译指南 +**开发环境**:ESP-IDF v5.4.1 + +### 编译步骤: +> ⚠️ **提示**:若在编译过程中访问在线库失败,可以尝试切换加速器状态,或修改 [idf_component.yml] 文件,替换为国内镜像源。 + +1. 使用 VSCode 打开项目文件夹; +2. 清除工程(Clean Project); +3. 设置 ESP-IDF 版本为 `v5.4.1`; +4. 点击 VSCode 右下角提示,生成 [compile_commands.json] 文件; +5. 设置目标设备为 `[esp32s3] -> [JTAG]`; +6. 打开 **SDK Configuration Editor**; +7. 配置自定义分区表路径为:`partitions/v2/16m.csv`; +8. 设置 **Board Type** 为 **九川科技**; +9. 保存配置并开始编译。 + +## 🔌 烧录步骤 +1. 使用数据线连接电脑与音箱; +2. 关闭设备电源后,长按电源键不松手; +3. 在烧录工具中选择对应的串口(COM Port); +4. 点击烧录按钮,选择 UART 模式; 5. 烧录完成前请勿松开电源键。 \ No newline at end of file diff --git a/main/boards/jiuchuan-s3/config.h b/main/boards/jiuchuan-s3/config.h index d689527..22475ab 100644 --- a/main/boards/jiuchuan-s3/config.h +++ b/main/boards/jiuchuan-s3/config.h @@ -1,52 +1,52 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_13 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_42 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - - -#define BUILTIN_LED_GPIO GPIO_NUM_10 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define PWR_BUTTON_GPIO GPIO_NUM_3 -#define PWR_EN_GPIO GPIO_NUM_5 -#define PWR_ADC_GPIO GPIO_NUM_4 -#define PWR_BUTTON_TIME 3000000U - -#define WIFI_BUTTON_GPIO GPIO_NUM_6 -#define CMD_BUTTON_GPIO GPIO_NUM_7 - - -#define DISPLAY_SPI_SCK_PIN GPIO_NUM_41 -#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_40 -#define DISPLAY_DC_PIN GPIO_NUM_39 -#define DISPLAY_SPI_CS_PIN GPIO_NUM_9 - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_13 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_42 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + + +#define BUILTIN_LED_GPIO GPIO_NUM_10 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define PWR_BUTTON_GPIO GPIO_NUM_3 +#define PWR_EN_GPIO GPIO_NUM_5 +#define PWR_ADC_GPIO GPIO_NUM_4 +#define PWR_BUTTON_TIME 3000000U + +#define WIFI_BUTTON_GPIO GPIO_NUM_6 +#define CMD_BUTTON_GPIO GPIO_NUM_7 + + +#define DISPLAY_SPI_SCK_PIN GPIO_NUM_41 +#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_40 +#define DISPLAY_DC_PIN GPIO_NUM_39 +#define DISPLAY_SPI_CS_PIN GPIO_NUM_9 + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/jiuchuan-s3/config.json b/main/boards/jiuchuan-s3/config.json index d1f610f..e3cff76 100644 --- a/main/boards/jiuchuan-s3/config.json +++ b/main/boards/jiuchuan-s3/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "jiuchuan-s3", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "jiuchuan-s3", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c b/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c index b8d0e9a..af7d746 100644 --- a/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c +++ b/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c @@ -1,384 +1,384 @@ -/* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - - #include - #include - #include "sdkconfig.h" - #include - #if CONFIG_LCD_ENABLE_DEBUG_LOG - // The local log level must be defined before including esp_log.h - // Set the maximum log level for this source file - #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG - #endif - - #include "freertos/FreeRTOS.h" - #include "freertos/task.h" - #include "esp_lcd_panel_interface.h" - #include "esp_lcd_panel_io.h" - #include "esp_lcd_panel_vendor.h" - #include "esp_lcd_panel_ops.h" - #include "esp_lcd_panel_commands.h" - #include "driver/gpio.h" - #include "esp_log.h" - #include "esp_check.h" - #include "esp_compiler.h" - /* GC9309NA LCD controller driver for ESP-IDF - * SPDX-FileCopyrightText: 2024 Your Name - * SPDX-License-Identifier: Apache-2.0 - */ - - #include "freertos/FreeRTOS.h" - #include "freertos/task.h" - #include "esp_lcd_panel_interface.h" - #include "esp_lcd_panel_io.h" - #include "esp_check.h" - #include "driver/gpio.h" - - - // GC9309NA Command Set - #define GC9309NA_CMD_SLPIN 0x10 - #define GC9309NA_CMD_SLPOUT 0x11 - #define GC9309NA_CMD_INVOFF 0x20 - #define GC9309NA_CMD_INVON 0x21 - #define GC9309NA_CMD_DISPOFF 0x28 - #define GC9309NA_CMD_DISPON 0x29 - #define GC9309NA_CMD_CASET 0x2A - #define GC9309NA_CMD_RASET 0x2B - #define GC9309NA_CMD_RAMWR 0x2C - #define GC9309NA_CMD_MADCTL 0x36 - #define GC9309NA_CMD_COLMOD 0x3A - #define GC9309NA_CMD_TEOFF 0x34 - #define GC9309NA_CMD_TEON 0x35 - #define GC9309NA_CMD_WRDISBV 0x51 - #define GC9309NA_CMD_WRCTRLD 0x53 - - // Manufacturer Commands - #define GC9309NA_CMD_SETGAMMA1 0xF0 - #define GC9309NA_CMD_SETGAMMA2 0xF1 - #define GC9309NA_CMD_PWRCTRL1 0x67 - #define GC9309NA_CMD_PWRCTRL2 0x68 - #define GC9309NA_CMD_PWRCTRL3 0x66 - #define GC9309NA_CMD_PWRCTRL4 0xCA - #define GC9309NA_CMD_PWRCTRL5 0xCB - #define GC9309NA_CMD_DINVCTRL 0xB5 - #define GC9309NA_CMD_REG_ENABLE1 0xFE - #define GC9309NA_CMD_REG_ENABLE2 0xEF - - // 自检模式颜色定义 - - - static const char *TAG = "lcd_panel.gc9309na"; - - typedef struct { - esp_lcd_panel_t base; - esp_lcd_panel_io_handle_t io; - int reset_gpio_num; - bool reset_level; - int x_gap; - int y_gap; - uint8_t madctl_val; - uint8_t colmod_val; - uint16_t te_scanline; - uint8_t fb_bits_per_pixel; - } gc9309na_panel_t; - - static esp_err_t panel_gc9309na_del(esp_lcd_panel_t *panel); - static esp_err_t panel_gc9309na_reset(esp_lcd_panel_t *panel); - static esp_err_t panel_gc9309na_init(esp_lcd_panel_t *panel); - static esp_err_t panel_gc9309na_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); - static esp_err_t panel_gc9309na_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); - static esp_err_t panel_gc9309na_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); - static esp_err_t panel_gc9309na_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); - static esp_err_t panel_gc9309na_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); - static esp_err_t panel_gc9309na_disp_on_off(esp_lcd_panel_t *panel, bool off); - static esp_err_t panel_gc9309na_sleep(esp_lcd_panel_t *panel, bool sleep); - - - esp_err_t esp_lcd_new_panel_gc9309na(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) - { - esp_err_t ret = ESP_OK; - gc9309na_panel_t *gc9309 = NULL; - - ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid arg"); - - gc9309 = calloc(1, sizeof(gc9309na_panel_t)); - ESP_GOTO_ON_FALSE(gc9309, ESP_ERR_NO_MEM, err, TAG, "no mem"); - - - // Hardware reset GPIO config - if (panel_dev_config->reset_gpio_num >= 0) { - gpio_config_t io_conf = { - .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, - }; - ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "GPIO config failed"); - } - - gc9309->colmod_val = 0x55; // RGB565 - // Initial register values - - gc9309->fb_bits_per_pixel = 16; - gc9309->io = io; - gc9309->reset_gpio_num = panel_dev_config->reset_gpio_num; - gc9309->reset_level = panel_dev_config->flags.reset_active_high; - gc9309->x_gap = 0; - gc9309->y_gap = 0; - - // Function pointers - gc9309->base.del = panel_gc9309na_del; - gc9309->base.reset = panel_gc9309na_reset; - gc9309->base.init = panel_gc9309na_init; - gc9309->base.draw_bitmap = panel_gc9309na_draw_bitmap; - gc9309->base.invert_color = panel_gc9309na_invert_color; - gc9309->base.set_gap = panel_gc9309na_set_gap; - gc9309->base.mirror = panel_gc9309na_mirror; - gc9309->base.swap_xy = panel_gc9309na_swap_xy; - gc9309->base.disp_on_off = panel_gc9309na_disp_on_off; - gc9309->base.disp_sleep = panel_gc9309na_sleep; - - *ret_panel = &(gc9309->base); - ESP_LOGI(TAG, "New GC9309NA panel @%p", gc9309); - return ESP_OK; - - err: - if (gc9309) { - if (panel_dev_config->reset_gpio_num >= 0) { - gpio_reset_pin(panel_dev_config->reset_gpio_num); - } - free(gc9309); - } - return ret; - } - - static esp_err_t panel_gc9309na_del(esp_lcd_panel_t *panel) - { - gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); - - if (gc9309->reset_gpio_num >= 0) { - gpio_reset_pin(gc9309->reset_gpio_num); - } - free(gc9309); - ESP_LOGI(TAG, "Del GC9309NA panel"); - return ESP_OK; - } - - static esp_err_t panel_gc9309na_reset(esp_lcd_panel_t *panel) - { - gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); - - if (gc9309->reset_gpio_num >= 0) { - // Hardware reset - gpio_set_level(gc9309->reset_gpio_num, gc9309->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(gc9309->reset_gpio_num, !gc9309->reset_level); - vTaskDelay(pdMS_TO_TICKS(120)); - } else { - // Software reset - // uint8_t unlock_cmd[] = {GC9309NA_CMD_REG_ENABLE1, GC9309NA_CMD_REG_ENABLE2}; - // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(gc9309->io, 0xFE, unlock_cmd, 2), - // TAG, "Unlock failed"); - // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(gc9309->io, LCD_CMD_SWRESET, NULL, 0), - // TAG, "SW Reset failed"); - vTaskDelay(pdMS_TO_TICKS(120)); - } - return ESP_OK; - } - static esp_err_t panel_gc9309na_init(esp_lcd_panel_t *panel) - { - gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); - esp_lcd_panel_io_handle_t io = gc9309->io; - - // Unlock commands - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xFE, NULL, 0), TAG, "Unlock cmd1 failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEF, NULL, 0), TAG, "Unlock cmd2 failed"); - - // Sleep out command - //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, NULL, 0), TAG, "Sleep out failed"); - //vTaskDelay(pdMS_TO_TICKS(80)); - - // Timing control commands - //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xE8, (uint8_t[]){0xA0}, 1), TAG, "Timing control failed"); - //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xE8, (uint8_t[]){0xF0}, 1), TAG, "Timing control failed"); - - // Display on command - //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, NULL, 0), TAG, "Display on failed"); - // vTaskDelay(pdMS_TO_TICKS(10)); - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x80, (uint8_t[]){0xC0}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x81, (uint8_t[]){0x01}, 1), TAG, "DINV failed"); - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x82, (uint8_t[]){0x07}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x83, (uint8_t[]){0x38}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x88, (uint8_t[]){0x64}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x89, (uint8_t[]){0x86}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8B, (uint8_t[]){0x3C}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8D, (uint8_t[]){0x51}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8E, (uint8_t[]){0x70}, 1), TAG, "DINV failed"); - - //高低位交换 - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB4, (uint8_t[]){0x80}, 1), TAG, "DINV failed"); - - gc9309->colmod_val = 0x05; // RGB565 - gc9309->madctl_val = 0x48; // BGR顺序,设置bit3=1(即0x08) - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9309NA_CMD_COLMOD, &gc9309->colmod_val, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9309NA_CMD_MADCTL, &gc9309->madctl_val, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XBF, (uint8_t[]){0X1F}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7d, (uint8_t[]){0x45,0x06}, 2), TAG, "DINV failed"); - // Continue from where you left off - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEE, (uint8_t[]){0x00,0x06}, 2), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XF4, (uint8_t[]){0x53}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF6, (uint8_t[]){0x17,0x08}, 2), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x70, (uint8_t[]){0x4F,0x4F}, 2), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x71, (uint8_t[]){0x12,0x20}, 2), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x72, (uint8_t[]){0x12,0x20}, 2), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB5, (uint8_t[]){0x50}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xBA, (uint8_t[]){0x00}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEC, (uint8_t[]){0x71}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7b, (uint8_t[]){0x00,0x0d}, 2), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7c, (uint8_t[]){0x0d,0x03}, 2), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XF5, (uint8_t[]){0x02,0x10,0x12}, 3), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF0, (uint8_t[]){0x0C,0x11,0x0b,0x0a,0x05,0x32,0x44,0x8e,0x9a,0x29,0x2E,0x5f}, 12), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF1, (uint8_t[]){0x0B,0x11,0x0b,0x07,0x07,0x32,0x45,0xBd,0x8D,0x21,0x28,0xAf}, 12), TAG, "DINV failed"); - - // 240x296 resolution settings - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2a, (uint8_t[]){0x00,0x00,0x00,0xef}, 4), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2b, (uint8_t[]){0x00,0x00,0x01,0x27}, 4), TAG, "DINV failed"); - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x66, (uint8_t[]){0x2C}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x67, (uint8_t[]){0x18}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x68, (uint8_t[]){0x3E}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCA, (uint8_t[]){0x0E}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xf0}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCB, (uint8_t[]){0x06}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB6, (uint8_t[]){0x5C,0x40,0x40}, 3), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCC, (uint8_t[]){0x33}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCD, (uint8_t[]){0x33}, 1), TAG, "DINV failed"); - - // Sleep out command - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, NULL, 0), TAG, "Sleep out failed"); - vTaskDelay(pdMS_TO_TICKS(80)); - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xA0}, 1), TAG, "DINV failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xf0}, 1), TAG, "DINV failed"); - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xfe, NULL, 0), TAG, "unlock cmd1 failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xee, NULL, 0), TAG, "unlock cmd2 failed"); - - // Display on command - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, NULL, 0), TAG, "Display on failed"); - - // Memory write command - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2c, NULL, 0), TAG, "Memory write failed"); - vTaskDelay(pdMS_TO_TICKS(10)); - return ESP_OK; - } - - - static esp_err_t panel_gc9309na_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) - { - gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); - - - esp_lcd_panel_io_handle_t io = gc9309->io; - - x_start += gc9309->x_gap; - x_end += gc9309->x_gap; - y_start += gc9309->y_gap; - y_end += gc9309->y_gap; - - // define an area of frame memory where MCU can access - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) { - (x_start >> 8) & 0xFF, - x_start & 0xFF, - ((x_end - 1) >> 8) & 0xFF, - (x_end - 1) & 0xFF, - }, 4), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) { - (y_start >> 8) & 0xFF, - y_start & 0xFF, - ((y_end - 1) >> 8) & 0xFF, - (y_end - 1) & 0xFF, - }, 4), TAG, "io tx param failed"); - // transfer frame buffer - size_t len = (x_end - x_start) * (y_end - y_start) * gc9309->fb_bits_per_pixel / 8; - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "io tx color failed"); - - return ESP_OK; - } - - static esp_err_t panel_gc9309na_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) - { - gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); - esp_lcd_panel_io_handle_t io = gc9309->io; - int command = 0; - if (invert_color_data) { - command = LCD_CMD_INVON; - } else { - command = LCD_CMD_INVOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, - "io tx param failed"); - return ESP_OK; - } - - static esp_err_t panel_gc9309na_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) - { - // gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); - // esp_lcd_panel_io_handle_t io = gc9309->io; - // if (mirror_x) { - // gc9309->madctl_val |= LCD_CMD_MX_BIT; - // } else { - // gc9309->madctl_val &= ~LCD_CMD_MX_BIT; - // } - // if (mirror_y) { - // gc9309->madctl_val |= LCD_CMD_MY_BIT; - // } else { - // gc9309->madctl_val &= ~LCD_CMD_MY_BIT; - // } - // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { - // gc9309->madctl_val - // }, 1), TAG, "io tx param failed"); - return ESP_OK; - } - - static esp_err_t panel_gc9309na_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) - { - // gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); - // esp_lcd_panel_io_handle_t io = gc9309->io; - // if (swap_axes) { - // gc9309->madctl_val |= LCD_CMD_MV_BIT; - // } else { - // gc9309->madctl_val &= ~LCD_CMD_MV_BIT; - // } - // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { - // gc9309->madctl_val - // }, 1), TAG, "io tx param failed"); - return ESP_OK; - } - - static esp_err_t panel_gc9309na_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) - { - gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); - gc9309->x_gap = x_gap; - gc9309->y_gap = y_gap; - return ESP_OK; - } - - static esp_err_t panel_gc9309na_disp_on_off(esp_lcd_panel_t *panel, bool on_off) - { - gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); - uint8_t cmd = on_off ? GC9309NA_CMD_DISPON : GC9309NA_CMD_DISPOFF; - return esp_lcd_panel_io_tx_param(gc9309->io, cmd, NULL, 0); - } - - static esp_err_t panel_gc9309na_sleep(esp_lcd_panel_t *panel, bool sleep) - { - gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); - uint8_t cmd = sleep ? GC9309NA_CMD_SLPIN : GC9309NA_CMD_SLPOUT; - esp_err_t ret = esp_lcd_panel_io_tx_param(gc9309->io, cmd, NULL, 0); - vTaskDelay(pdMS_TO_TICKS(120)); - return ret; +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + #include + #include + #include "sdkconfig.h" + #include + #if CONFIG_LCD_ENABLE_DEBUG_LOG + // The local log level must be defined before including esp_log.h + // Set the maximum log level for this source file + #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG + #endif + + #include "freertos/FreeRTOS.h" + #include "freertos/task.h" + #include "esp_lcd_panel_interface.h" + #include "esp_lcd_panel_io.h" + #include "esp_lcd_panel_vendor.h" + #include "esp_lcd_panel_ops.h" + #include "esp_lcd_panel_commands.h" + #include "driver/gpio.h" + #include "esp_log.h" + #include "esp_check.h" + #include "esp_compiler.h" + /* GC9309NA LCD controller driver for ESP-IDF + * SPDX-FileCopyrightText: 2024 Your Name + * SPDX-License-Identifier: Apache-2.0 + */ + + #include "freertos/FreeRTOS.h" + #include "freertos/task.h" + #include "esp_lcd_panel_interface.h" + #include "esp_lcd_panel_io.h" + #include "esp_check.h" + #include "driver/gpio.h" + + + // GC9309NA Command Set + #define GC9309NA_CMD_SLPIN 0x10 + #define GC9309NA_CMD_SLPOUT 0x11 + #define GC9309NA_CMD_INVOFF 0x20 + #define GC9309NA_CMD_INVON 0x21 + #define GC9309NA_CMD_DISPOFF 0x28 + #define GC9309NA_CMD_DISPON 0x29 + #define GC9309NA_CMD_CASET 0x2A + #define GC9309NA_CMD_RASET 0x2B + #define GC9309NA_CMD_RAMWR 0x2C + #define GC9309NA_CMD_MADCTL 0x36 + #define GC9309NA_CMD_COLMOD 0x3A + #define GC9309NA_CMD_TEOFF 0x34 + #define GC9309NA_CMD_TEON 0x35 + #define GC9309NA_CMD_WRDISBV 0x51 + #define GC9309NA_CMD_WRCTRLD 0x53 + + // Manufacturer Commands + #define GC9309NA_CMD_SETGAMMA1 0xF0 + #define GC9309NA_CMD_SETGAMMA2 0xF1 + #define GC9309NA_CMD_PWRCTRL1 0x67 + #define GC9309NA_CMD_PWRCTRL2 0x68 + #define GC9309NA_CMD_PWRCTRL3 0x66 + #define GC9309NA_CMD_PWRCTRL4 0xCA + #define GC9309NA_CMD_PWRCTRL5 0xCB + #define GC9309NA_CMD_DINVCTRL 0xB5 + #define GC9309NA_CMD_REG_ENABLE1 0xFE + #define GC9309NA_CMD_REG_ENABLE2 0xEF + + // 自检模式颜色定义 + + + static const char *TAG = "lcd_panel.gc9309na"; + + typedef struct { + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + bool reset_level; + int x_gap; + int y_gap; + uint8_t madctl_val; + uint8_t colmod_val; + uint16_t te_scanline; + uint8_t fb_bits_per_pixel; + } gc9309na_panel_t; + + static esp_err_t panel_gc9309na_del(esp_lcd_panel_t *panel); + static esp_err_t panel_gc9309na_reset(esp_lcd_panel_t *panel); + static esp_err_t panel_gc9309na_init(esp_lcd_panel_t *panel); + static esp_err_t panel_gc9309na_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); + static esp_err_t panel_gc9309na_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); + static esp_err_t panel_gc9309na_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); + static esp_err_t panel_gc9309na_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); + static esp_err_t panel_gc9309na_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); + static esp_err_t panel_gc9309na_disp_on_off(esp_lcd_panel_t *panel, bool off); + static esp_err_t panel_gc9309na_sleep(esp_lcd_panel_t *panel, bool sleep); + + + esp_err_t esp_lcd_new_panel_gc9309na(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) + { + esp_err_t ret = ESP_OK; + gc9309na_panel_t *gc9309 = NULL; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid arg"); + + gc9309 = calloc(1, sizeof(gc9309na_panel_t)); + ESP_GOTO_ON_FALSE(gc9309, ESP_ERR_NO_MEM, err, TAG, "no mem"); + + + // Hardware reset GPIO config + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_config_t io_conf = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, + }; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "GPIO config failed"); + } + + gc9309->colmod_val = 0x55; // RGB565 + // Initial register values + + gc9309->fb_bits_per_pixel = 16; + gc9309->io = io; + gc9309->reset_gpio_num = panel_dev_config->reset_gpio_num; + gc9309->reset_level = panel_dev_config->flags.reset_active_high; + gc9309->x_gap = 0; + gc9309->y_gap = 0; + + // Function pointers + gc9309->base.del = panel_gc9309na_del; + gc9309->base.reset = panel_gc9309na_reset; + gc9309->base.init = panel_gc9309na_init; + gc9309->base.draw_bitmap = panel_gc9309na_draw_bitmap; + gc9309->base.invert_color = panel_gc9309na_invert_color; + gc9309->base.set_gap = panel_gc9309na_set_gap; + gc9309->base.mirror = panel_gc9309na_mirror; + gc9309->base.swap_xy = panel_gc9309na_swap_xy; + gc9309->base.disp_on_off = panel_gc9309na_disp_on_off; + gc9309->base.disp_sleep = panel_gc9309na_sleep; + + *ret_panel = &(gc9309->base); + ESP_LOGI(TAG, "New GC9309NA panel @%p", gc9309); + return ESP_OK; + + err: + if (gc9309) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(gc9309); + } + return ret; + } + + static esp_err_t panel_gc9309na_del(esp_lcd_panel_t *panel) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + + if (gc9309->reset_gpio_num >= 0) { + gpio_reset_pin(gc9309->reset_gpio_num); + } + free(gc9309); + ESP_LOGI(TAG, "Del GC9309NA panel"); + return ESP_OK; + } + + static esp_err_t panel_gc9309na_reset(esp_lcd_panel_t *panel) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + + if (gc9309->reset_gpio_num >= 0) { + // Hardware reset + gpio_set_level(gc9309->reset_gpio_num, gc9309->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9309->reset_gpio_num, !gc9309->reset_level); + vTaskDelay(pdMS_TO_TICKS(120)); + } else { + // Software reset + // uint8_t unlock_cmd[] = {GC9309NA_CMD_REG_ENABLE1, GC9309NA_CMD_REG_ENABLE2}; + // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(gc9309->io, 0xFE, unlock_cmd, 2), + // TAG, "Unlock failed"); + // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(gc9309->io, LCD_CMD_SWRESET, NULL, 0), + // TAG, "SW Reset failed"); + vTaskDelay(pdMS_TO_TICKS(120)); + } + return ESP_OK; + } + static esp_err_t panel_gc9309na_init(esp_lcd_panel_t *panel) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9309->io; + + // Unlock commands + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xFE, NULL, 0), TAG, "Unlock cmd1 failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEF, NULL, 0), TAG, "Unlock cmd2 failed"); + + // Sleep out command + //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, NULL, 0), TAG, "Sleep out failed"); + //vTaskDelay(pdMS_TO_TICKS(80)); + + // Timing control commands + //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xE8, (uint8_t[]){0xA0}, 1), TAG, "Timing control failed"); + //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xE8, (uint8_t[]){0xF0}, 1), TAG, "Timing control failed"); + + // Display on command + //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, NULL, 0), TAG, "Display on failed"); + // vTaskDelay(pdMS_TO_TICKS(10)); + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x80, (uint8_t[]){0xC0}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x81, (uint8_t[]){0x01}, 1), TAG, "DINV failed"); + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x82, (uint8_t[]){0x07}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x83, (uint8_t[]){0x38}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x88, (uint8_t[]){0x64}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x89, (uint8_t[]){0x86}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8B, (uint8_t[]){0x3C}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8D, (uint8_t[]){0x51}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8E, (uint8_t[]){0x70}, 1), TAG, "DINV failed"); + + //高低位交换 + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB4, (uint8_t[]){0x80}, 1), TAG, "DINV failed"); + + gc9309->colmod_val = 0x05; // RGB565 + gc9309->madctl_val = 0x48; // BGR顺序,设置bit3=1(即0x08) + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9309NA_CMD_COLMOD, &gc9309->colmod_val, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9309NA_CMD_MADCTL, &gc9309->madctl_val, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XBF, (uint8_t[]){0X1F}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7d, (uint8_t[]){0x45,0x06}, 2), TAG, "DINV failed"); + // Continue from where you left off + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEE, (uint8_t[]){0x00,0x06}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XF4, (uint8_t[]){0x53}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF6, (uint8_t[]){0x17,0x08}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x70, (uint8_t[]){0x4F,0x4F}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x71, (uint8_t[]){0x12,0x20}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x72, (uint8_t[]){0x12,0x20}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB5, (uint8_t[]){0x50}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xBA, (uint8_t[]){0x00}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEC, (uint8_t[]){0x71}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7b, (uint8_t[]){0x00,0x0d}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7c, (uint8_t[]){0x0d,0x03}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XF5, (uint8_t[]){0x02,0x10,0x12}, 3), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF0, (uint8_t[]){0x0C,0x11,0x0b,0x0a,0x05,0x32,0x44,0x8e,0x9a,0x29,0x2E,0x5f}, 12), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF1, (uint8_t[]){0x0B,0x11,0x0b,0x07,0x07,0x32,0x45,0xBd,0x8D,0x21,0x28,0xAf}, 12), TAG, "DINV failed"); + + // 240x296 resolution settings + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2a, (uint8_t[]){0x00,0x00,0x00,0xef}, 4), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2b, (uint8_t[]){0x00,0x00,0x01,0x27}, 4), TAG, "DINV failed"); + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x66, (uint8_t[]){0x2C}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x67, (uint8_t[]){0x18}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x68, (uint8_t[]){0x3E}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCA, (uint8_t[]){0x0E}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xf0}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCB, (uint8_t[]){0x06}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB6, (uint8_t[]){0x5C,0x40,0x40}, 3), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCC, (uint8_t[]){0x33}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCD, (uint8_t[]){0x33}, 1), TAG, "DINV failed"); + + // Sleep out command + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, NULL, 0), TAG, "Sleep out failed"); + vTaskDelay(pdMS_TO_TICKS(80)); + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xA0}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xf0}, 1), TAG, "DINV failed"); + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xfe, NULL, 0), TAG, "unlock cmd1 failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xee, NULL, 0), TAG, "unlock cmd2 failed"); + + // Display on command + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, NULL, 0), TAG, "Display on failed"); + + // Memory write command + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2c, NULL, 0), TAG, "Memory write failed"); + vTaskDelay(pdMS_TO_TICKS(10)); + return ESP_OK; + } + + + static esp_err_t panel_gc9309na_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + + + esp_lcd_panel_io_handle_t io = gc9309->io; + + x_start += gc9309->x_gap; + x_end += gc9309->x_gap; + y_start += gc9309->y_gap; + y_end += gc9309->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) { + (x_start >> 8) & 0xFF, + x_start & 0xFF, + ((x_end - 1) >> 8) & 0xFF, + (x_end - 1) & 0xFF, + }, 4), TAG, "io tx param failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) { + (y_start >> 8) & 0xFF, + y_start & 0xFF, + ((y_end - 1) >> 8) & 0xFF, + (y_end - 1) & 0xFF, + }, 4), TAG, "io tx param failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * gc9309->fb_bits_per_pixel / 8; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "io tx color failed"); + + return ESP_OK; + } + + static esp_err_t panel_gc9309na_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9309->io; + int command = 0; + if (invert_color_data) { + command = LCD_CMD_INVON; + } else { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, + "io tx param failed"); + return ESP_OK; + } + + static esp_err_t panel_gc9309na_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) + { + // gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + // esp_lcd_panel_io_handle_t io = gc9309->io; + // if (mirror_x) { + // gc9309->madctl_val |= LCD_CMD_MX_BIT; + // } else { + // gc9309->madctl_val &= ~LCD_CMD_MX_BIT; + // } + // if (mirror_y) { + // gc9309->madctl_val |= LCD_CMD_MY_BIT; + // } else { + // gc9309->madctl_val &= ~LCD_CMD_MY_BIT; + // } + // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + // gc9309->madctl_val + // }, 1), TAG, "io tx param failed"); + return ESP_OK; + } + + static esp_err_t panel_gc9309na_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) + { + // gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + // esp_lcd_panel_io_handle_t io = gc9309->io; + // if (swap_axes) { + // gc9309->madctl_val |= LCD_CMD_MV_BIT; + // } else { + // gc9309->madctl_val &= ~LCD_CMD_MV_BIT; + // } + // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + // gc9309->madctl_val + // }, 1), TAG, "io tx param failed"); + return ESP_OK; + } + + static esp_err_t panel_gc9309na_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + gc9309->x_gap = x_gap; + gc9309->y_gap = y_gap; + return ESP_OK; + } + + static esp_err_t panel_gc9309na_disp_on_off(esp_lcd_panel_t *panel, bool on_off) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + uint8_t cmd = on_off ? GC9309NA_CMD_DISPON : GC9309NA_CMD_DISPOFF; + return esp_lcd_panel_io_tx_param(gc9309->io, cmd, NULL, 0); + } + + static esp_err_t panel_gc9309na_sleep(esp_lcd_panel_t *panel, bool sleep) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + uint8_t cmd = sleep ? GC9309NA_CMD_SLPIN : GC9309NA_CMD_SLPOUT; + esp_err_t ret = esp_lcd_panel_io_tx_param(gc9309->io, cmd, NULL, 0); + vTaskDelay(pdMS_TO_TICKS(120)); + return ret; } \ No newline at end of file diff --git a/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h b/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h index 0a7065b..b0b1b9d 100644 --- a/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h +++ b/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h @@ -1,31 +1,31 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include -#include "esp_err.h" -#include "esp_lcd_panel_dev.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Create LCD panel for model ST7789 - * - * @param[in] io LCD panel IO handle - * @param[in] panel_dev_config general panel device configuration - * @param[out] ret_panel Returned LCD panel handle - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NO_MEM if out of memory - * - ESP_OK on success - */ -esp_err_t esp_lcd_new_panel_gc9309na(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel); - -#ifdef __cplusplus -} -#endif +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "esp_lcd_panel_dev.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create LCD panel for model ST7789 + * + * @param[in] io LCD panel IO handle + * @param[in] panel_dev_config general panel device configuration + * @param[out] ret_panel Returned LCD panel handle + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_NO_MEM if out of memory + * - ESP_OK on success + */ +esp_err_t esp_lcd_new_panel_gc9309na(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel); + +#ifdef __cplusplus +} +#endif diff --git a/main/boards/jiuchuan-s3/gpio_manager.h b/main/boards/jiuchuan-s3/gpio_manager.h index f8e83ff..77f6f7c 100644 --- a/main/boards/jiuchuan-s3/gpio_manager.h +++ b/main/boards/jiuchuan-s3/gpio_manager.h @@ -1,62 +1,62 @@ -#pragma once -#include -#include -#include -#include - -class GpioManager { -public: - enum class GpioMode { - INPUT, - OUTPUT, - INPUT_PULLUP, - INPUT_PULLDOWN - }; - - static void SetLevel(gpio_num_t gpio, uint32_t level) { - std::lock_guard lock(mutex_); - ESP_ERROR_CHECK(gpio_set_level(gpio, level)); - ESP_LOGD("GpioManager", "Set GPIO %d level: %d", static_cast(gpio), static_cast(level)); - } - - static int GetLevel(gpio_num_t gpio) { - std::lock_guard lock(mutex_); - int level = gpio_get_level(gpio); - ESP_LOGD("GpioManager", "Get GPIO %d level: %d", static_cast(gpio), level); - return level; - } - - static void Config(gpio_num_t gpio, GpioMode mode) { - std::lock_guard lock(mutex_); - - gpio_config_t config = {}; - config.pin_bit_mask = (1ULL << gpio); - - switch(mode) { - case GpioMode::INPUT: - config.mode = GPIO_MODE_INPUT; - config.pull_up_en = GPIO_PULLUP_DISABLE; - config.pull_down_en = GPIO_PULLDOWN_DISABLE; - break; - case GpioMode::OUTPUT: - config.mode = GPIO_MODE_OUTPUT; - break; - case GpioMode::INPUT_PULLUP: - config.mode = GPIO_MODE_INPUT; - config.pull_up_en = GPIO_PULLUP_ENABLE; - break; - case GpioMode::INPUT_PULLDOWN: - config.mode = GPIO_MODE_INPUT; - config.pull_down_en = GPIO_PULLDOWN_ENABLE; - break; - } - - ESP_ERROR_CHECK(gpio_config(&config)); - ESP_LOGI("GpioManager", "Configured GPIO %d mode: %d", gpio, static_cast(mode)); - } - -private: - static std::mutex mutex_; -}; - +#pragma once +#include +#include +#include +#include + +class GpioManager { +public: + enum class GpioMode { + INPUT, + OUTPUT, + INPUT_PULLUP, + INPUT_PULLDOWN + }; + + static void SetLevel(gpio_num_t gpio, uint32_t level) { + std::lock_guard lock(mutex_); + ESP_ERROR_CHECK(gpio_set_level(gpio, level)); + ESP_LOGD("GpioManager", "Set GPIO %d level: %d", static_cast(gpio), static_cast(level)); + } + + static int GetLevel(gpio_num_t gpio) { + std::lock_guard lock(mutex_); + int level = gpio_get_level(gpio); + ESP_LOGD("GpioManager", "Get GPIO %d level: %d", static_cast(gpio), level); + return level; + } + + static void Config(gpio_num_t gpio, GpioMode mode) { + std::lock_guard lock(mutex_); + + gpio_config_t config = {}; + config.pin_bit_mask = (1ULL << gpio); + + switch(mode) { + case GpioMode::INPUT: + config.mode = GPIO_MODE_INPUT; + config.pull_up_en = GPIO_PULLUP_DISABLE; + config.pull_down_en = GPIO_PULLDOWN_DISABLE; + break; + case GpioMode::OUTPUT: + config.mode = GPIO_MODE_OUTPUT; + break; + case GpioMode::INPUT_PULLUP: + config.mode = GPIO_MODE_INPUT; + config.pull_up_en = GPIO_PULLUP_ENABLE; + break; + case GpioMode::INPUT_PULLDOWN: + config.mode = GPIO_MODE_INPUT; + config.pull_down_en = GPIO_PULLDOWN_ENABLE; + break; + } + + ESP_ERROR_CHECK(gpio_config(&config)); + ESP_LOGI("GpioManager", "Configured GPIO %d mode: %d", gpio, static_cast(mode)); + } + +private: + static std::mutex mutex_; +}; + std::mutex GpioManager::mutex_; \ No newline at end of file diff --git a/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc b/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc index f60700f..c86782b 100644 --- a/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc +++ b/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc @@ -1,383 +1,383 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" - -#include -#include -#include -#include -#include -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "esp_lcd_panel_gc9301.h" - -#include "power_save_timer.h" -#include "power_manager.h" -#include "power_controller.h" -#include "gpio_manager.h" -#include -#include - -#define BOARD_TAG "JiuchuanDevBoard" -#define __USER_GPIO_PWRDOWN__ - -// 自定义LCD显示器类,用于圆形屏幕适配 -class CustomLcdDisplay : public SpiLcdDisplay -{ -public: - CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, - esp_lcd_panel_handle_t panel_handle, - int width, - int height, - int offset_x, - int offset_y, - bool mirror_x, - bool mirror_y, - bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) - { - - DisplayLockGuard lock(this); - lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.167, 0); - lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.167, 0); - } -}; - -class JiuchuanDevBoard : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - Button pwr_button_; - Button wifi_button; - Button cmd_button; - LcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io = NULL; - esp_lcd_panel_handle_t panel = NULL; - - // 音量映射函数:将内部音量(0-80)映射为显示音量(0-100%) - int MapVolumeForDisplay(int internal_volume) { - // 确保输入在有效范围内 - if (internal_volume < 0) internal_volume = 0; - if (internal_volume > 80) internal_volume = 80; - - // 将0-80映射到0-100 - // 公式: 显示音量 = (内部音量 / 80) * 100 - return (internal_volume * 100) / 80; - } - - void InitializePowerManager() { - power_manager_ = new PowerManager(PWR_ADC_GPIO); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - #ifndef __USER_GPIO_PWRDOWN__ - RTC_DATA_ATTR static bool long_press_occurred = false; - esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); - if (cause == ESP_SLEEP_WAKEUP_EXT0) { - ESP_LOGI(TAG, "Wake up by EXT0"); - const int64_t start = esp_timer_get_time(); - ESP_LOGI(TAG, "esp_sleep_get_wakeup_cause"); - while (gpio_get_level(PWR_BUTTON_GPIO) == 0) { - if (esp_timer_get_time() - start > 3000000) { - long_press_occurred = true; - break; - } - vTaskDelay(100 / portTICK_PERIOD_MS); - } - - if (long_press_occurred) { - ESP_LOGI(TAG, "Long press wakeup"); - long_press_occurred = false; - } else { - ESP_LOGI(TAG, "Short press, return to sleep"); - ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0)); - ESP_ERROR_CHECK(rtc_gpio_pullup_en(PWR_BUTTON_GPIO)); // 内部上拉 - ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(PWR_BUTTON_GPIO)); - esp_deep_sleep_start(); - } - } - #endif - //一分钟进入浅睡眠,5分钟进入深睡眠关机 - power_save_timer_ = new PowerSaveTimer(-1, (60*5), -1); - // power_save_timer_ = new PowerSaveTimer(-1, 6, 10);//test - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - #ifndef __USER_GPIO_PWRDOWN__ - ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0)); - ESP_ERROR_CHECK(rtc_gpio_pullup_en(PWR_BUTTON_GPIO)); // 内部上拉 - ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(PWR_BUTTON_GPIO)); - - esp_lcd_panel_disp_on_off(panel, false); //关闭显示 - esp_deep_sleep_start(); - #else - rtc_gpio_set_level(PWR_EN_GPIO, 0); - rtc_gpio_hold_dis(PWR_EN_GPIO); - #endif - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - - } - - void InitializeButtons() { - static bool pwrbutton_unreleased = false; - - if (gpio_get_level(GPIO_NUM_3) == 1) { - pwrbutton_unreleased = true; - } - // 配置GPIO - ESP_LOGI(TAG, "Configuring power button GPIO"); - GpioManager::Config(GPIO_NUM_3, GpioManager::GpioMode::INPUT_PULLDOWN); - - boot_button_.OnClick([this]() { - ESP_LOGI(TAG, "Boot button clicked"); - power_save_timer_->WakeUp(); - }); - - // 检查电源按钮初始状态 - ESP_LOGI(TAG, "Power button initial state: %d", GpioManager::GetLevel(PWR_BUTTON_GPIO)); - - // 高电平有效长按关机逻辑 - pwr_button_.OnPressDown([this]() { - pwrbutton_unreleased = false; - }); - pwr_button_.OnLongPress([this]() - { - ESP_LOGI(TAG, "Power button long press detected (high-active)"); - - if (pwrbutton_unreleased){ - ESP_LOGI(TAG, "开机后电源键未松开,取消关机"); - return; - } - - // 高电平有效防抖确认 - for (int i = 0; i < 5; i++) { - int level = GpioManager::GetLevel(PWR_BUTTON_GPIO); - ESP_LOGD(TAG, "Debounce check %d: GPIO%d level=%d", i+1, PWR_BUTTON_GPIO, level); - - if (level == 0) { - ESP_LOGW(TAG, "Power button inactive during confirmation - abort shutdown"); - return; - } - vTaskDelay(100 / portTICK_PERIOD_MS); - } - - ESP_LOGI(TAG, "Confirmed power button pressed - initiating shutdown"); - power_manager_->SetPowerState(PowerState::SHUTDOWN); }); - - //单击切换状态 - pwr_button_.OnClick([this]() - { - // 获取当前应用实例和状态 - auto &app = Application::GetInstance(); - auto current_state = app.GetDeviceState(); - - ESP_LOGI(TAG, "当前设备状态: %d", current_state); - - if (current_state == kDeviceStateIdle) { - // 如果当前是待命状态,切换到聆听状态 - ESP_LOGI(TAG, "从待命状态切换到聆听状态"); - app.ToggleChatState(); // 切换到聆听状态 - } else if (current_state == kDeviceStateListening) { - // 如果当前是聆听状态,切换到待命状态 - ESP_LOGI(TAG, "从聆听状态切换到待命状态"); - app.ToggleChatState(); // 切换到待命状态 - } else if (current_state == kDeviceStateSpeaking) { - // 如果当前是说话状态,终止说话并切换到待命状态 - ESP_LOGI(TAG, "从说话状态切换到待命状态"); - app.ToggleChatState(); // 终止说话 - } else { - // 其他状态下只唤醒设备 - ESP_LOGI(TAG, "唤醒设备"); - power_save_timer_->WakeUp(); - } }); - - // 电源键三击:重置WiFi - pwr_button_.OnMultipleClick([this]() - { - ESP_LOGI(TAG, "Power button triple click: 重置WiFi"); - power_save_timer_->WakeUp(); - ResetWifiConfiguration(); }, 3); - - wifi_button.OnPressDown([this]() - { - ESP_LOGI(TAG, "Volume up button pressed"); - power_save_timer_->WakeUp(); - - auto codec = GetAudioCodec(); - int current_vol = codec->output_volume(); // 获取实际当前音量 - current_vol = (current_vol + 8 > 80) ? 80 : current_vol + 8; - - codec->SetOutputVolume(current_vol); - - ESP_LOGI(TAG, "Current volume: %d", current_vol); - int display_volume = MapVolumeForDisplay(current_vol); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(display_volume) + "%");}); - - cmd_button.OnPressDown([this]() - { - ESP_LOGI(TAG, "Volume down button pressed"); - power_save_timer_->WakeUp(); - - auto codec = GetAudioCodec(); - int current_vol = codec->output_volume(); // 获取实际当前音量 - current_vol = (current_vol - 8 < 0) ? 0 : current_vol - 8; - - codec->SetOutputVolume(current_vol); - - ESP_LOGI(TAG, "Current volume: %d", current_vol); - if (current_vol == 0) { - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - } else { - int display_volume = MapVolumeForDisplay(current_vol); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(display_volume) + "%"); - }}); - } - - void InitializeGC9301isplay() - { - // 液晶屏控制IO初始化 - ESP_LOGI(TAG, "test Install panel IO"); - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; - buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - - // 初始化SPI总线 - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 3; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io); - - // 初始化液晶屏驱动芯片9309 - ESP_LOGI(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ENDIAN_BGR; - panel_config.bits_per_pixel = 16; - esp_lcd_new_panel_gc9309na(panel_io, &panel_config, &panel); - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, false); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new CustomLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - JiuchuanDevBoard() : - boot_button_(BOOT_BUTTON_GPIO), - pwr_button_(PWR_BUTTON_GPIO,true), - wifi_button(WIFI_BUTTON_GPIO), - cmd_button(CMD_BUTTON_GPIO) { - - InitializeI2c(); - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeButtons(); - InitializeGC9301isplay(); - GetBacklight()->RestoreBrightness(); - - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - - static Es8311AudioCodec audio_codec( - codec_i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(JiuchuanDevBoard); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" + +#include +#include +#include +#include +#include +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "esp_lcd_panel_gc9301.h" + +#include "power_save_timer.h" +#include "power_manager.h" +#include "power_controller.h" +#include "gpio_manager.h" +#include +#include + +#define BOARD_TAG "JiuchuanDevBoard" +#define __USER_GPIO_PWRDOWN__ + +// 自定义LCD显示器类,用于圆形屏幕适配 +class CustomLcdDisplay : public SpiLcdDisplay +{ +public: + CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_handle_t panel_handle, + int width, + int height, + int offset_x, + int offset_y, + bool mirror_x, + bool mirror_y, + bool swap_xy) + : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) + { + + DisplayLockGuard lock(this); + lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.167, 0); + lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.167, 0); + } +}; + +class JiuchuanDevBoard : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + Button pwr_button_; + Button wifi_button; + Button cmd_button; + LcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io = NULL; + esp_lcd_panel_handle_t panel = NULL; + + // 音量映射函数:将内部音量(0-80)映射为显示音量(0-100%) + int MapVolumeForDisplay(int internal_volume) { + // 确保输入在有效范围内 + if (internal_volume < 0) internal_volume = 0; + if (internal_volume > 80) internal_volume = 80; + + // 将0-80映射到0-100 + // 公式: 显示音量 = (内部音量 / 80) * 100 + return (internal_volume * 100) / 80; + } + + void InitializePowerManager() { + power_manager_ = new PowerManager(PWR_ADC_GPIO); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + #ifndef __USER_GPIO_PWRDOWN__ + RTC_DATA_ATTR static bool long_press_occurred = false; + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); + if (cause == ESP_SLEEP_WAKEUP_EXT0) { + ESP_LOGI(TAG, "Wake up by EXT0"); + const int64_t start = esp_timer_get_time(); + ESP_LOGI(TAG, "esp_sleep_get_wakeup_cause"); + while (gpio_get_level(PWR_BUTTON_GPIO) == 0) { + if (esp_timer_get_time() - start > 3000000) { + long_press_occurred = true; + break; + } + vTaskDelay(100 / portTICK_PERIOD_MS); + } + + if (long_press_occurred) { + ESP_LOGI(TAG, "Long press wakeup"); + long_press_occurred = false; + } else { + ESP_LOGI(TAG, "Short press, return to sleep"); + ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0)); + ESP_ERROR_CHECK(rtc_gpio_pullup_en(PWR_BUTTON_GPIO)); // 内部上拉 + ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(PWR_BUTTON_GPIO)); + esp_deep_sleep_start(); + } + } + #endif + //一分钟进入浅睡眠,5分钟进入深睡眠关机 + power_save_timer_ = new PowerSaveTimer(-1, (60*5), -1); + // power_save_timer_ = new PowerSaveTimer(-1, 6, 10);//test + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + #ifndef __USER_GPIO_PWRDOWN__ + ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0)); + ESP_ERROR_CHECK(rtc_gpio_pullup_en(PWR_BUTTON_GPIO)); // 内部上拉 + ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(PWR_BUTTON_GPIO)); + + esp_lcd_panel_disp_on_off(panel, false); //关闭显示 + esp_deep_sleep_start(); + #else + rtc_gpio_set_level(PWR_EN_GPIO, 0); + rtc_gpio_hold_dis(PWR_EN_GPIO); + #endif + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + } + + void InitializeButtons() { + static bool pwrbutton_unreleased = false; + + if (gpio_get_level(GPIO_NUM_3) == 1) { + pwrbutton_unreleased = true; + } + // 配置GPIO + ESP_LOGI(TAG, "Configuring power button GPIO"); + GpioManager::Config(GPIO_NUM_3, GpioManager::GpioMode::INPUT_PULLDOWN); + + boot_button_.OnClick([this]() { + ESP_LOGI(TAG, "Boot button clicked"); + power_save_timer_->WakeUp(); + }); + + // 检查电源按钮初始状态 + ESP_LOGI(TAG, "Power button initial state: %d", GpioManager::GetLevel(PWR_BUTTON_GPIO)); + + // 高电平有效长按关机逻辑 + pwr_button_.OnPressDown([this]() { + pwrbutton_unreleased = false; + }); + pwr_button_.OnLongPress([this]() + { + ESP_LOGI(TAG, "Power button long press detected (high-active)"); + + if (pwrbutton_unreleased){ + ESP_LOGI(TAG, "开机后电源键未松开,取消关机"); + return; + } + + // 高电平有效防抖确认 + for (int i = 0; i < 5; i++) { + int level = GpioManager::GetLevel(PWR_BUTTON_GPIO); + ESP_LOGD(TAG, "Debounce check %d: GPIO%d level=%d", i+1, PWR_BUTTON_GPIO, level); + + if (level == 0) { + ESP_LOGW(TAG, "Power button inactive during confirmation - abort shutdown"); + return; + } + vTaskDelay(100 / portTICK_PERIOD_MS); + } + + ESP_LOGI(TAG, "Confirmed power button pressed - initiating shutdown"); + power_manager_->SetPowerState(PowerState::SHUTDOWN); }); + + //单击切换状态 + pwr_button_.OnClick([this]() + { + // 获取当前应用实例和状态 + auto &app = Application::GetInstance(); + auto current_state = app.GetDeviceState(); + + ESP_LOGI(TAG, "当前设备状态: %d", current_state); + + if (current_state == kDeviceStateIdle) { + // 如果当前是待命状态,切换到聆听状态 + ESP_LOGI(TAG, "从待命状态切换到聆听状态"); + app.ToggleChatState(); // 切换到聆听状态 + } else if (current_state == kDeviceStateListening) { + // 如果当前是聆听状态,切换到待命状态 + ESP_LOGI(TAG, "从聆听状态切换到待命状态"); + app.ToggleChatState(); // 切换到待命状态 + } else if (current_state == kDeviceStateSpeaking) { + // 如果当前是说话状态,终止说话并切换到待命状态 + ESP_LOGI(TAG, "从说话状态切换到待命状态"); + app.ToggleChatState(); // 终止说话 + } else { + // 其他状态下只唤醒设备 + ESP_LOGI(TAG, "唤醒设备"); + power_save_timer_->WakeUp(); + } }); + + // 电源键三击:重置WiFi + pwr_button_.OnMultipleClick([this]() + { + ESP_LOGI(TAG, "Power button triple click: 重置WiFi"); + power_save_timer_->WakeUp(); + ResetWifiConfiguration(); }, 3); + + wifi_button.OnPressDown([this]() + { + ESP_LOGI(TAG, "Volume up button pressed"); + power_save_timer_->WakeUp(); + + auto codec = GetAudioCodec(); + int current_vol = codec->output_volume(); // 获取实际当前音量 + current_vol = (current_vol + 8 > 80) ? 80 : current_vol + 8; + + codec->SetOutputVolume(current_vol); + + ESP_LOGI(TAG, "Current volume: %d", current_vol); + int display_volume = MapVolumeForDisplay(current_vol); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(display_volume) + "%");}); + + cmd_button.OnPressDown([this]() + { + ESP_LOGI(TAG, "Volume down button pressed"); + power_save_timer_->WakeUp(); + + auto codec = GetAudioCodec(); + int current_vol = codec->output_volume(); // 获取实际当前音量 + current_vol = (current_vol - 8 < 0) ? 0 : current_vol - 8; + + codec->SetOutputVolume(current_vol); + + ESP_LOGI(TAG, "Current volume: %d", current_vol); + if (current_vol == 0) { + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + } else { + int display_volume = MapVolumeForDisplay(current_vol); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(display_volume) + "%"); + }}); + } + + void InitializeGC9301isplay() + { + // 液晶屏控制IO初始化 + ESP_LOGI(TAG, "test Install panel IO"); + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; + buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + + // 初始化SPI总线 + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 3; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io); + + // 初始化液晶屏驱动芯片9309 + ESP_LOGI(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ENDIAN_BGR; + panel_config.bits_per_pixel = 16; + esp_lcd_new_panel_gc9309na(panel_io, &panel_config, &panel); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new CustomLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + JiuchuanDevBoard() : + boot_button_(BOOT_BUTTON_GPIO), + pwr_button_(PWR_BUTTON_GPIO,true), + wifi_button(WIFI_BUTTON_GPIO), + cmd_button(CMD_BUTTON_GPIO) { + + InitializeI2c(); + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeButtons(); + InitializeGC9301isplay(); + GetBacklight()->RestoreBrightness(); + + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + + static Es8311AudioCodec audio_codec( + codec_i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(JiuchuanDevBoard); diff --git a/main/boards/jiuchuan-s3/power_controller.h b/main/boards/jiuchuan-s3/power_controller.h index 2496152..74dcdc8 100644 --- a/main/boards/jiuchuan-s3/power_controller.h +++ b/main/boards/jiuchuan-s3/power_controller.h @@ -1,58 +1,58 @@ -#pragma once -#include -#include -#include -#include -#include -#include "config.h" -enum class PowerState { - ACTIVE, - LIGHT_SLEEP, - DEEP_SLEEP, - SHUTDOWN - }; - -class PowerController { -public: - - - static PowerController& Instance() { - static PowerController instance; - return instance; - } - - void SetState(PowerState newState) { - std::lock_guard lock(mutex_); - if (currentState_ != newState) { - ESP_LOGI("PowerCtrl", "State change: %d -> %d", - static_cast(currentState_), - static_cast(newState)); - - currentState_ = newState; - if (stateChangeCallback_) { - stateChangeCallback_(newState); - } - } - } - - PowerState GetState() const { - std::lock_guard lock(mutex_); - return currentState_; - } - - void OnStateChange(std::function callback) { - stateChangeCallback_ = callback; - } - -private: - PowerController(){ - rtc_gpio_init(PWR_EN_GPIO); - rtc_gpio_set_direction(PWR_EN_GPIO, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(PWR_EN_GPIO, 1); - } - ~PowerController() = default; - - PowerState currentState_ = PowerState::ACTIVE; - std::function stateChangeCallback_; - mutable std::mutex mutex_; +#pragma once +#include +#include +#include +#include +#include +#include "config.h" +enum class PowerState { + ACTIVE, + LIGHT_SLEEP, + DEEP_SLEEP, + SHUTDOWN + }; + +class PowerController { +public: + + + static PowerController& Instance() { + static PowerController instance; + return instance; + } + + void SetState(PowerState newState) { + std::lock_guard lock(mutex_); + if (currentState_ != newState) { + ESP_LOGI("PowerCtrl", "State change: %d -> %d", + static_cast(currentState_), + static_cast(newState)); + + currentState_ = newState; + if (stateChangeCallback_) { + stateChangeCallback_(newState); + } + } + } + + PowerState GetState() const { + std::lock_guard lock(mutex_); + return currentState_; + } + + void OnStateChange(std::function callback) { + stateChangeCallback_ = callback; + } + +private: + PowerController(){ + rtc_gpio_init(PWR_EN_GPIO); + rtc_gpio_set_direction(PWR_EN_GPIO, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(PWR_EN_GPIO, 1); + } + ~PowerController() = default; + + PowerState currentState_ = PowerState::ACTIVE; + std::function stateChangeCallback_; + mutable std::mutex mutex_; }; \ No newline at end of file diff --git a/main/boards/jiuchuan-s3/power_manager.h b/main/boards/jiuchuan-s3/power_manager.h index e306d10..edb495b 100644 --- a/main/boards/jiuchuan-s3/power_manager.h +++ b/main/boards/jiuchuan-s3/power_manager.h @@ -1,201 +1,201 @@ -#pragma once -#include -#include - -#include -#include -#include "adc_battery_estimation.h" -#include "power_controller.h" -#include -#include - -#define JIUCHUAN_ADC_UNIT (ADC_UNIT_1) -#define JIUCHUAN_ADC_BITWIDTH (ADC_BITWIDTH_12) -#define JIUCHUAN_ADC_ATTEN (ADC_ATTEN_DB_12) -#define JIUCHUAN_ADC_CHANNEL (ADC_CHANNEL_3) -#define JIUCHUAN_RESISTOR_UPPER (200000) -#define JIUCHUAN_RESISTOR_LOWER (100000) - -#undef TAG -#define TAG "PowerManager" -class PowerManager { -private: - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector adc_values_; - int32_t battery_level_ = 100; - bool is_charging_ = false; - bool is_low_battery_ = false; - bool is_empty_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_battery_estimation_handle_t adc_battery_estimation_handle; - PowerController* power_controller_; - - - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 1; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - float battery_capacity_temp = 0; - adc_battery_estimation_get_capacity(adc_battery_estimation_handle, &battery_capacity_temp); - ESP_LOGI("PowerManager", "Battery level: %.1f%%", battery_capacity_temp); - if(battery_capacity_temp > -10 && battery_capacity_temp <= 0){ - battery_level_ = 0; - }else{ - battery_level_ = battery_capacity_temp; - } - } - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - power_controller_ = &PowerController::Instance(); - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - static const battery_point_t battery_ponint_table[]={ - { 4.2 , 100}, - { 4.06 , 80}, - { 3.82 , 60}, - { 3.58 , 40}, - { 3.34 , 20}, - { 3.1 , 0}, - { 3.0 , -10} - }; - - adc_battery_estimation_t config = { - .internal = { - .adc_unit = JIUCHUAN_ADC_UNIT, - .adc_bitwidth = JIUCHUAN_ADC_BITWIDTH, - .adc_atten = JIUCHUAN_ADC_ATTEN, - }, - .adc_channel = JIUCHUAN_ADC_CHANNEL, - .upper_resistor = JIUCHUAN_RESISTOR_UPPER, - .lower_resistor = JIUCHUAN_RESISTOR_LOWER, - .battery_points = battery_ponint_table, - .battery_points_count = sizeof(battery_ponint_table) / sizeof(battery_ponint_table[0]) - }; - - adc_battery_estimation_handle = adc_battery_estimation_create(&config); - - RegisterAllCallbacks(); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_battery_estimation_handle) { - adc_battery_estimation_destroy(adc_battery_estimation_handle); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - //ESP_LOGI(TAG, "电量已满,不再显示充电中"); - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - int32_t GetBatteryLevel() { - return battery_level_; - } - - void RegisterAllCallbacks() { - //注册电源状态变更回调函数(优化版) - power_controller_->OnStateChange([this](PowerState newState) { - switch(newState) { - case PowerState::SHUTDOWN: { - - ESP_LOGD(TAG, "关机"); - - //取消 PWR_EN 使能 - /* 防止关机后误唤醒 */ - ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0)); - ESP_ERROR_CHECK(rtc_gpio_pulldown_en(PWR_BUTTON_GPIO)); // 内部下拉 - ESP_ERROR_CHECK(rtc_gpio_pullup_dis(PWR_BUTTON_GPIO)); - /* 关闭电源使能 */ - rtc_gpio_set_level(PWR_EN_GPIO, 0); - rtc_gpio_hold_dis(PWR_EN_GPIO); - - // 确保所有外设已关闭 - vTaskDelay(200 / portTICK_PERIOD_MS); - ESP_LOGI(TAG, "Initiating deep sleep"); - - esp_deep_sleep_start(); - break; - } - default: - ESP_LOGD(TAG, "State changed to %d", static_cast(newState)); - break; - } - }); - } - void SetPowerState(PowerState newState) { - power_controller_->SetState(newState); - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } +#pragma once +#include +#include + +#include +#include +#include "adc_battery_estimation.h" +#include "power_controller.h" +#include +#include + +#define JIUCHUAN_ADC_UNIT (ADC_UNIT_1) +#define JIUCHUAN_ADC_BITWIDTH (ADC_BITWIDTH_12) +#define JIUCHUAN_ADC_ATTEN (ADC_ATTEN_DB_12) +#define JIUCHUAN_ADC_CHANNEL (ADC_CHANNEL_3) +#define JIUCHUAN_RESISTOR_UPPER (200000) +#define JIUCHUAN_RESISTOR_LOWER (100000) + +#undef TAG +#define TAG "PowerManager" +class PowerManager { +private: + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + int32_t battery_level_ = 100; + bool is_charging_ = false; + bool is_low_battery_ = false; + bool is_empty_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_battery_estimation_handle_t adc_battery_estimation_handle; + PowerController* power_controller_; + + + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 1; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + float battery_capacity_temp = 0; + adc_battery_estimation_get_capacity(adc_battery_estimation_handle, &battery_capacity_temp); + ESP_LOGI("PowerManager", "Battery level: %.1f%%", battery_capacity_temp); + if(battery_capacity_temp > -10 && battery_capacity_temp <= 0){ + battery_level_ = 0; + }else{ + battery_level_ = battery_capacity_temp; + } + } + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + power_controller_ = &PowerController::Instance(); + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + static const battery_point_t battery_ponint_table[]={ + { 4.2 , 100}, + { 4.06 , 80}, + { 3.82 , 60}, + { 3.58 , 40}, + { 3.34 , 20}, + { 3.1 , 0}, + { 3.0 , -10} + }; + + adc_battery_estimation_t config = { + .internal = { + .adc_unit = JIUCHUAN_ADC_UNIT, + .adc_bitwidth = JIUCHUAN_ADC_BITWIDTH, + .adc_atten = JIUCHUAN_ADC_ATTEN, + }, + .adc_channel = JIUCHUAN_ADC_CHANNEL, + .upper_resistor = JIUCHUAN_RESISTOR_UPPER, + .lower_resistor = JIUCHUAN_RESISTOR_LOWER, + .battery_points = battery_ponint_table, + .battery_points_count = sizeof(battery_ponint_table) / sizeof(battery_ponint_table[0]) + }; + + adc_battery_estimation_handle = adc_battery_estimation_create(&config); + + RegisterAllCallbacks(); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_battery_estimation_handle) { + adc_battery_estimation_destroy(adc_battery_estimation_handle); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + //ESP_LOGI(TAG, "电量已满,不再显示充电中"); + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + int32_t GetBatteryLevel() { + return battery_level_; + } + + void RegisterAllCallbacks() { + //注册电源状态变更回调函数(优化版) + power_controller_->OnStateChange([this](PowerState newState) { + switch(newState) { + case PowerState::SHUTDOWN: { + + ESP_LOGD(TAG, "关机"); + + //取消 PWR_EN 使能 + /* 防止关机后误唤醒 */ + ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0)); + ESP_ERROR_CHECK(rtc_gpio_pulldown_en(PWR_BUTTON_GPIO)); // 内部下拉 + ESP_ERROR_CHECK(rtc_gpio_pullup_dis(PWR_BUTTON_GPIO)); + /* 关闭电源使能 */ + rtc_gpio_set_level(PWR_EN_GPIO, 0); + rtc_gpio_hold_dis(PWR_EN_GPIO); + + // 确保所有外设已关闭 + vTaskDelay(200 / portTICK_PERIOD_MS); + ESP_LOGI(TAG, "Initiating deep sleep"); + + esp_deep_sleep_start(); + break; + } + default: + ESP_LOGD(TAG, "State changed to %d", static_cast(newState)); + break; + } + }); + } + void SetPowerState(PowerState newState) { + power_controller_->SetState(newState); + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } }; \ No newline at end of file diff --git a/main/boards/kevin-box-1/config.h b/main/boards/kevin-box-1/config.h index 8bd55ad..be4b4b5 100644 --- a/main/boards/kevin-box-1/config.h +++ b/main/boards/kevin-box-1/config.h @@ -1,39 +1,39 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_48 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_45 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_21 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_17 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_39 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_38 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_8 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_6 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_7 - -#define DISPLAY_SDA_PIN GPIO_NUM_4 -#define DISPLAY_SCL_PIN GPIO_NUM_5 -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 64 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false - -#define ML307_RX_PIN GPIO_NUM_20 -#define ML307_TX_PIN GPIO_NUM_19 - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_48 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_45 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_21 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_17 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_39 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_38 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_8 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_6 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_7 + +#define DISPLAY_SDA_PIN GPIO_NUM_4 +#define DISPLAY_SCL_PIN GPIO_NUM_5 +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 64 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false + +#define ML307_RX_PIN GPIO_NUM_20 +#define ML307_TX_PIN GPIO_NUM_19 + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/kevin-box-1/config.json b/main/boards/kevin-box-1/config.json index 82d8a1f..2133672 100644 --- a/main/boards/kevin-box-1/config.json +++ b/main/boards/kevin-box-1/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "kevin-box-1", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "kevin-box-1", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/kevin-box-1/kevin_box_board.cc b/main/boards/kevin-box-1/kevin_box_board.cc index bb2e912..64ea4c0 100644 --- a/main/boards/kevin-box-1/kevin_box_board.cc +++ b/main/boards/kevin-box-1/kevin_box_board.cc @@ -1,205 +1,205 @@ -#include "ml307_board.h" -#include "codecs/box_audio_codec.h" -#include "display/oled_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include - -#define TAG "KevinBoxBoard" - -class KevinBoxBoard : public Ml307Board { -private: - i2c_master_bus_handle_t display_i2c_bus_; - i2c_master_bus_handle_t codec_i2c_bus_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - Display* display_ = nullptr; - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - - void MountStorage() { - // Mount the storage partition - esp_vfs_spiffs_conf_t conf = { - .base_path = "/storage", - .partition_label = "storage", - .max_files = 5, - .format_if_mount_failed = true, - }; - esp_vfs_spiffs_register(&conf); - } - - void Enable4GModule() { - // Make GPIO15 HIGH to enable the 4G module - gpio_config_t ml307_enable_config = { - .pin_bit_mask = (1ULL << 15) | (1ULL << 18), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - gpio_config(&ml307_enable_config); - gpio_set_level(GPIO_NUM_15, 1); - gpio_set_level(GPIO_NUM_18, 1); - } - - void InitializeDisplayI2c() { - i2c_master_bus_config_t bus_config = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = DISPLAY_SDA_PIN, - .scl_io_num = DISPLAY_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeButtons() { - boot_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - - volume_up_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - -public: - KevinBoxBoard() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN), - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializeDisplayI2c(); - InitializeSsd1306Display(); - InitializeCodecI2c(); - MountStorage(); - Enable4GModule(); - - InitializeButtons(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec(codec_i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } -}; - +#include "ml307_board.h" +#include "codecs/box_audio_codec.h" +#include "display/oled_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include +#include + +#define TAG "KevinBoxBoard" + +class KevinBoxBoard : public Ml307Board { +private: + i2c_master_bus_handle_t display_i2c_bus_; + i2c_master_bus_handle_t codec_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + Display* display_ = nullptr; + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + + void MountStorage() { + // Mount the storage partition + esp_vfs_spiffs_conf_t conf = { + .base_path = "/storage", + .partition_label = "storage", + .max_files = 5, + .format_if_mount_failed = true, + }; + esp_vfs_spiffs_register(&conf); + } + + void Enable4GModule() { + // Make GPIO15 HIGH to enable the 4G module + gpio_config_t ml307_enable_config = { + .pin_bit_mask = (1ULL << 15) | (1ULL << 18), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config(&ml307_enable_config); + gpio_set_level(GPIO_NUM_15, 1); + gpio_set_level(GPIO_NUM_18, 1); + } + + void InitializeDisplayI2c() { + i2c_master_bus_config_t bus_config = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = DISPLAY_SDA_PIN, + .scl_io_num = DISPLAY_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); + } + + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeButtons() { + boot_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + + volume_up_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + +public: + KevinBoxBoard() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN), + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializeDisplayI2c(); + InitializeSsd1306Display(); + InitializeCodecI2c(); + MountStorage(); + Enable4GModule(); + + InitializeButtons(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec(codec_i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } +}; + DECLARE_BOARD(KevinBoxBoard); \ No newline at end of file diff --git a/main/boards/kevin-box-2/config.h b/main/boards/kevin-box-2/config.h index a272900..fc21a29 100644 --- a/main/boards/kevin-box-2/config.h +++ b/main/boards/kevin-box-2/config.h @@ -1,41 +1,41 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_40 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_38 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_9 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_42 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_41 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_3 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_1 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_2 - -#define DISPLAY_SDA_PIN GPIO_NUM_7 -#define DISPLAY_SCL_PIN GPIO_NUM_8 -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 64 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false - -#define ML307_RX_PIN GPIO_NUM_5 -#define ML307_TX_PIN GPIO_NUM_6 - -#define AXP2101_I2C_ADDR 0x34 - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_40 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_38 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_9 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_42 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_41 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_3 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_1 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_2 + +#define DISPLAY_SDA_PIN GPIO_NUM_7 +#define DISPLAY_SCL_PIN GPIO_NUM_8 +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 64 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false + +#define ML307_RX_PIN GPIO_NUM_5 +#define ML307_TX_PIN GPIO_NUM_6 + +#define AXP2101_I2C_ADDR 0x34 + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/kevin-box-2/config.json b/main/boards/kevin-box-2/config.json index d9a581d..2633177 100644 --- a/main/boards/kevin-box-2/config.json +++ b/main/boards/kevin-box-2/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "kevin-box-2", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "kevin-box-2", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/kevin-box-2/kevin_box_board.cc b/main/boards/kevin-box-2/kevin_box_board.cc index 94e7e17..69ae6e6 100644 --- a/main/boards/kevin-box-2/kevin_box_board.cc +++ b/main/boards/kevin-box-2/kevin_box_board.cc @@ -1,271 +1,271 @@ -#include "dual_network_board.h" -#include "codecs/box_audio_codec.h" -#include "display/oled_display.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "config.h" -#include "power_save_timer.h" -#include "axp2101.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include - -#define TAG "KevinBoxBoard" - -class Pmic : public Axp2101 { -public: - Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { - // ** EFUSE defaults ** - WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable - WriteReg(0x27, 0x10); // hold 4s to power off - - WriteReg(0x93, 0x1C); // 配置 aldo2 输出为 3.3V - - uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0 - value = value | 0x02; // set bit 1 (ALDO2) - WriteReg(0x90, value); // and power channels now enabled - - WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V - - WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA - WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) - WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA - - WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables - WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables - WriteReg(0x16, 0x05); // set input current limit to 2000mA - - WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery) - WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature) - } -}; - -class KevinBoxBoard : public DualNetworkBoard { -private: - i2c_master_bus_handle_t display_i2c_bus_; - i2c_master_bus_handle_t codec_i2c_bus_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - Display* display_ = nullptr; - Pmic* pmic_ = nullptr; - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - PowerSaveTimer* power_save_timer_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, -1, 600); - power_save_timer_->OnShutdownRequest([this]() { - pmic_->PowerOff(); - }); - power_save_timer_->SetEnabled(true); - } - - void Enable4GModule() { - // Make GPIO HIGH to enable the 4G module - gpio_config_t ml307_enable_config = { - .pin_bit_mask = (1ULL << 4), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - gpio_config(&ml307_enable_config); - gpio_set_level(GPIO_NUM_4, 1); - } - - void InitializeDisplayI2c() { - i2c_master_bus_config_t bus_config = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = DISPLAY_SDA_PIN, - .scl_io_num = DISPLAY_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeButtons() { - boot_button_.OnPressDown([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - app.StartListening(); - }); - boot_button_.OnPressUp([this]() { - auto& app = Application::GetInstance(); - app.StopListening(); - }); - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - // cast to WifiBoard - auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); - } - } - }); - boot_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { - SwitchNetworkType(); - } - }); - - volume_up_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - -public: - KevinBoxBoard() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializeDisplayI2c(); - InitializeSsd1306Display(); - InitializeCodecI2c(); - pmic_ = new Pmic(codec_i2c_bus_, AXP2101_I2C_ADDR); - - Enable4GModule(); - - InitializeButtons(); - InitializePowerSaveTimer(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec(codec_i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = pmic_->IsCharging(); - discharging = pmic_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - - level = pmic_->GetBatteryLevel(); - return true; - } -}; - +#include "dual_network_board.h" +#include "codecs/box_audio_codec.h" +#include "display/oled_display.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "config.h" +#include "power_save_timer.h" +#include "axp2101.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include +#include + +#define TAG "KevinBoxBoard" + +class Pmic : public Axp2101 { +public: + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + // ** EFUSE defaults ** + WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable + WriteReg(0x27, 0x10); // hold 4s to power off + + WriteReg(0x93, 0x1C); // 配置 aldo2 输出为 3.3V + + uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0 + value = value | 0x02; // set bit 1 (ALDO2) + WriteReg(0x90, value); // and power channels now enabled + + WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V + + WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA + WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) + WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA + + WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables + WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables + WriteReg(0x16, 0x05); // set input current limit to 2000mA + + WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery) + WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature) + } +}; + +class KevinBoxBoard : public DualNetworkBoard { +private: + i2c_master_bus_handle_t display_i2c_bus_; + i2c_master_bus_handle_t codec_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + Display* display_ = nullptr; + Pmic* pmic_ = nullptr; + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, -1, 600); + power_save_timer_->OnShutdownRequest([this]() { + pmic_->PowerOff(); + }); + power_save_timer_->SetEnabled(true); + } + + void Enable4GModule() { + // Make GPIO HIGH to enable the 4G module + gpio_config_t ml307_enable_config = { + .pin_bit_mask = (1ULL << 4), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config(&ml307_enable_config); + gpio_set_level(GPIO_NUM_4, 1); + } + + void InitializeDisplayI2c() { + i2c_master_bus_config_t bus_config = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = DISPLAY_SDA_PIN, + .scl_io_num = DISPLAY_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); + } + + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeButtons() { + boot_button_.OnPressDown([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + app.StartListening(); + }); + boot_button_.OnPressUp([this]() { + auto& app = Application::GetInstance(); + app.StopListening(); + }); + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (GetNetworkType() == NetworkType::WIFI) { + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + // cast to WifiBoard + auto& wifi_board = static_cast(GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + } + } + }); + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + SwitchNetworkType(); + } + }); + + volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + +public: + KevinBoxBoard() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializeDisplayI2c(); + InitializeSsd1306Display(); + InitializeCodecI2c(); + pmic_ = new Pmic(codec_i2c_bus_, AXP2101_I2C_ADDR); + + Enable4GModule(); + + InitializeButtons(); + InitializePowerSaveTimer(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec(codec_i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = pmic_->IsCharging(); + discharging = pmic_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + + level = pmic_->GetBatteryLevel(); + return true; + } +}; + DECLARE_BOARD(KevinBoxBoard); \ No newline at end of file diff --git a/main/boards/kevin-c3/config.h b/main/boards/kevin-c3/config.h index 4241320..6292b31 100644 --- a/main/boards/kevin-c3/config.h +++ b/main/boards/kevin-c3/config.h @@ -1,24 +1,24 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_13 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_5 -#define BOOT_BUTTON_GPIO GPIO_NUM_6 - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_13 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_5 +#define BOOT_BUTTON_GPIO GPIO_NUM_6 + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/kevin-c3/config.json b/main/boards/kevin-c3/config.json index b8bf3b7..47925dc 100644 --- a/main/boards/kevin-c3/config.json +++ b/main/boards/kevin-c3/config.json @@ -1,13 +1,13 @@ -{ - "target": "esp32c3", - "builds": [ - { - "name": "kevin-c3", - "sdkconfig_append": [ - "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n", - "CONFIG_LWIP_IPV6=n", - "CONFIG_USE_ESP_WAKE_WORD=y" - ] - } - ] +{ + "target": "esp32c3", + "builds": [ + { + "name": "kevin-c3", + "sdkconfig_append": [ + "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n", + "CONFIG_LWIP_IPV6=n", + "CONFIG_USE_ESP_WAKE_WORD=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/kevin-c3/kevin_c3_board.cc b/main/boards/kevin-c3/kevin_c3_board.cc index df7e82c..eda847a 100644 --- a/main/boards/kevin-c3/kevin_c3_board.cc +++ b/main/boards/kevin-c3/kevin_c3_board.cc @@ -1,90 +1,90 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/circular_strip.h" -#include "led_strip_control.h" - -#include -#include -#include -#include - -#define TAG "KevinBoxBoard" - -class KevinBoxBoard : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - CircularStrip* led_strip_; - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - - // Print I2C bus info - if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { - while (true) { - ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - } - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - boot_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - } - - // 物联网初始化,添加对 AI 可见设备 - void InitializeTools() { - led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 8); - new LedStripControl(led_strip_); - } - -public: - KevinBoxBoard() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeButtons(); - InitializeTools(); - - // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 - esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); - } - - virtual Led* GetLed() override { - return led_strip_; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } -}; - -DECLARE_BOARD(KevinBoxBoard); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/circular_strip.h" +#include "led_strip_control.h" + +#include +#include +#include +#include + +#define TAG "KevinBoxBoard" + +class KevinBoxBoard : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + CircularStrip* led_strip_; + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + // Print I2C bus info + if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { + while (true) { + ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + boot_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeTools() { + led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 8); + new LedStripControl(led_strip_); + } + +public: + KevinBoxBoard() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeButtons(); + InitializeTools(); + + // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 + esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); + } + + virtual Led* GetLed() override { + return led_strip_; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } +}; + +DECLARE_BOARD(KevinBoxBoard); diff --git a/main/boards/kevin-c3/led_strip_control.cc b/main/boards/kevin-c3/led_strip_control.cc index 68dc4ae..ebd1cec 100644 --- a/main/boards/kevin-c3/led_strip_control.cc +++ b/main/boards/kevin-c3/led_strip_control.cc @@ -1,129 +1,129 @@ -#include "led_strip_control.h" -#include "settings.h" -#include "mcp_server.h" -#include - -#define TAG "LedStripControl" - -int LedStripControl::LevelToBrightness(int level) const { - if (level < 0) level = 0; - if (level > 8) level = 8; - return (1 << level) - 1; // 2^n - 1 -} - -StripColor LedStripControl::RGBToColor(int red, int green, int blue) { - if (red < 0) red = 0; - if (red > 255) red = 255; - if (green < 0) green = 0; - if (green > 255) green = 255; - if (blue < 0) blue = 0; - if (blue > 255) blue = 255; - return {static_cast(red), static_cast(green), static_cast(blue)}; -} - -LedStripControl::LedStripControl(CircularStrip* led_strip) - : led_strip_(led_strip) { - // 从设置中读取亮度等级 - Settings settings("led_strip"); - brightness_level_ = settings.GetInt("brightness", 4); // 默认等级4 - led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4); - - auto& mcp_server = McpServer::GetInstance(); - mcp_server.AddTool("self.led_strip.get_brightness", - "Get the brightness of the led strip (0-8)", - PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - return brightness_level_; - }); - - mcp_server.AddTool("self.led_strip.set_brightness", - "Set the brightness of the led strip (0-8)", - PropertyList({ - Property("level", kPropertyTypeInteger, 0, 8) - }), [this](const PropertyList& properties) -> ReturnValue { - int level = properties["level"].value(); - ESP_LOGI(TAG, "Set LedStrip brightness level to %d", level); - brightness_level_ = level; - led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4); - - // 保存设置 - Settings settings("led_strip", true); - settings.SetInt("brightness", brightness_level_); - - return true; - }); - - mcp_server.AddTool("self.led_strip.set_single_color", - "Set the color of a single led.", - PropertyList({ - Property("index", kPropertyTypeInteger, 0, 7), - Property("red", kPropertyTypeInteger, 0, 255), - Property("green", kPropertyTypeInteger, 0, 255), - Property("blue", kPropertyTypeInteger, 0, 255) - }), [this](const PropertyList& properties) -> ReturnValue { - int index = properties["index"].value(); - int red = properties["red"].value(); - int green = properties["green"].value(); - int blue = properties["blue"].value(); - ESP_LOGI(TAG, "Set led strip single color %d to %d, %d, %d", - index, red, green, blue); - led_strip_->SetSingleColor(index, RGBToColor(red, green, blue)); - return true; - }); - - mcp_server.AddTool("self.led_strip.set_all_color", - "Set the color of all leds.", - PropertyList({ - Property("red", kPropertyTypeInteger, 0, 255), - Property("green", kPropertyTypeInteger, 0, 255), - Property("blue", kPropertyTypeInteger, 0, 255) - }), [this](const PropertyList& properties) -> ReturnValue { - int red = properties["red"].value(); - int green = properties["green"].value(); - int blue = properties["blue"].value(); - ESP_LOGI(TAG, "Set led strip all color to %d, %d, %d", - red, green, blue); - led_strip_->SetAllColor(RGBToColor(red, green, blue)); - return true; - }); - - mcp_server.AddTool("self.led_strip.blink", - "Blink the led strip. (闪烁)", - PropertyList({ - Property("red", kPropertyTypeInteger, 0, 255), - Property("green", kPropertyTypeInteger, 0, 255), - Property("blue", kPropertyTypeInteger, 0, 255), - Property("interval", kPropertyTypeInteger, 0, 1000) - }), [this](const PropertyList& properties) -> ReturnValue { - int red = properties["red"].value(); - int green = properties["green"].value(); - int blue = properties["blue"].value(); - int interval = properties["interval"].value(); - ESP_LOGI(TAG, "Blink led strip with color %d, %d, %d, interval %dms", - red, green, blue, interval); - led_strip_->Blink(RGBToColor(red, green, blue), interval); - return true; - }); - - mcp_server.AddTool("self.led_strip.scroll", - "Scroll the led strip. (跑马灯)", - PropertyList({ - Property("red", kPropertyTypeInteger, 0, 255), - Property("green", kPropertyTypeInteger, 0, 255), - Property("blue", kPropertyTypeInteger, 0, 255), - Property("length", kPropertyTypeInteger, 1, 7), - Property("interval", kPropertyTypeInteger, 0, 1000) - }), [this](const PropertyList& properties) -> ReturnValue { - int red = properties["red"].value(); - int green = properties["green"].value(); - int blue = properties["blue"].value(); - int interval = properties["interval"].value(); - int length = properties["length"].value(); - ESP_LOGI(TAG, "Scroll led strip with color %d, %d, %d, length %d, interval %dms", - red, green, blue, length, interval); - StripColor low = RGBToColor(4, 4, 4); - StripColor high = RGBToColor(red, green, blue); - led_strip_->Scroll(low, high, length, interval); - return true; - }); - -} +#include "led_strip_control.h" +#include "settings.h" +#include "mcp_server.h" +#include + +#define TAG "LedStripControl" + +int LedStripControl::LevelToBrightness(int level) const { + if (level < 0) level = 0; + if (level > 8) level = 8; + return (1 << level) - 1; // 2^n - 1 +} + +StripColor LedStripControl::RGBToColor(int red, int green, int blue) { + if (red < 0) red = 0; + if (red > 255) red = 255; + if (green < 0) green = 0; + if (green > 255) green = 255; + if (blue < 0) blue = 0; + if (blue > 255) blue = 255; + return {static_cast(red), static_cast(green), static_cast(blue)}; +} + +LedStripControl::LedStripControl(CircularStrip* led_strip) + : led_strip_(led_strip) { + // 从设置中读取亮度等级 + Settings settings("led_strip"); + brightness_level_ = settings.GetInt("brightness", 4); // 默认等级4 + led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4); + + auto& mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.led_strip.get_brightness", + "Get the brightness of the led strip (0-8)", + PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + return brightness_level_; + }); + + mcp_server.AddTool("self.led_strip.set_brightness", + "Set the brightness of the led strip (0-8)", + PropertyList({ + Property("level", kPropertyTypeInteger, 0, 8) + }), [this](const PropertyList& properties) -> ReturnValue { + int level = properties["level"].value(); + ESP_LOGI(TAG, "Set LedStrip brightness level to %d", level); + brightness_level_ = level; + led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4); + + // 保存设置 + Settings settings("led_strip", true); + settings.SetInt("brightness", brightness_level_); + + return true; + }); + + mcp_server.AddTool("self.led_strip.set_single_color", + "Set the color of a single led.", + PropertyList({ + Property("index", kPropertyTypeInteger, 0, 7), + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255) + }), [this](const PropertyList& properties) -> ReturnValue { + int index = properties["index"].value(); + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + ESP_LOGI(TAG, "Set led strip single color %d to %d, %d, %d", + index, red, green, blue); + led_strip_->SetSingleColor(index, RGBToColor(red, green, blue)); + return true; + }); + + mcp_server.AddTool("self.led_strip.set_all_color", + "Set the color of all leds.", + PropertyList({ + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255) + }), [this](const PropertyList& properties) -> ReturnValue { + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + ESP_LOGI(TAG, "Set led strip all color to %d, %d, %d", + red, green, blue); + led_strip_->SetAllColor(RGBToColor(red, green, blue)); + return true; + }); + + mcp_server.AddTool("self.led_strip.blink", + "Blink the led strip. (闪烁)", + PropertyList({ + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255), + Property("interval", kPropertyTypeInteger, 0, 1000) + }), [this](const PropertyList& properties) -> ReturnValue { + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + int interval = properties["interval"].value(); + ESP_LOGI(TAG, "Blink led strip with color %d, %d, %d, interval %dms", + red, green, blue, interval); + led_strip_->Blink(RGBToColor(red, green, blue), interval); + return true; + }); + + mcp_server.AddTool("self.led_strip.scroll", + "Scroll the led strip. (跑马灯)", + PropertyList({ + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255), + Property("length", kPropertyTypeInteger, 1, 7), + Property("interval", kPropertyTypeInteger, 0, 1000) + }), [this](const PropertyList& properties) -> ReturnValue { + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + int interval = properties["interval"].value(); + int length = properties["length"].value(); + ESP_LOGI(TAG, "Scroll led strip with color %d, %d, %d, length %d, interval %dms", + red, green, blue, length, interval); + StripColor low = RGBToColor(4, 4, 4); + StripColor high = RGBToColor(red, green, blue); + led_strip_->Scroll(low, high, length, interval); + return true; + }); + +} diff --git a/main/boards/kevin-c3/led_strip_control.h b/main/boards/kevin-c3/led_strip_control.h index 0e99bb3..4e683ec 100644 --- a/main/boards/kevin-c3/led_strip_control.h +++ b/main/boards/kevin-c3/led_strip_control.h @@ -1,18 +1,18 @@ -#ifndef LED_STRIP_CONTROL_H -#define LED_STRIP_CONTROL_H - -#include "led/circular_strip.h" - -class LedStripControl { -private: - CircularStrip* led_strip_; - int brightness_level_; // 亮度等级 (0-8) - - int LevelToBrightness(int level) const; // 将等级转换为实际亮度值 - StripColor RGBToColor(int red, int green, int blue); - -public: - explicit LedStripControl(CircularStrip* led_strip); -}; - -#endif // LED_STRIP_CONTROL_H +#ifndef LED_STRIP_CONTROL_H +#define LED_STRIP_CONTROL_H + +#include "led/circular_strip.h" + +class LedStripControl { +private: + CircularStrip* led_strip_; + int brightness_level_; // 亮度等级 (0-8) + + int LevelToBrightness(int level) const; // 将等级转换为实际亮度值 + StripColor RGBToColor(int red, int green, int blue); + +public: + explicit LedStripControl(CircularStrip* led_strip); +}; + +#endif // LED_STRIP_CONTROL_H diff --git a/main/boards/kevin-sp-v3-dev/config.h b/main/boards/kevin-sp-v3-dev/config.h index fd92549..3e9b473 100644 --- a/main/boards/kevin-sp-v3-dev/config.h +++ b/main/boards/kevin-sp-v3-dev/config.h @@ -1,64 +1,64 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 -#define AUDIO_DEFAULT_OUTPUT_VOLUME 90 - - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_42 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_41 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_2 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_3 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_46 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_1 - - -#define BUILTIN_LED_GPIO GPIO_NUM_38 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define RESET_NVS_BUTTON_GPIO GPIO_NUM_NC -#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SDA_PIN GPIO_NUM_NC -#define DISPLAY_SCL_PIN GPIO_NUM_NC -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 280 -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define BACKLIGHT_INVERT false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 20 -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_48 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define ML307_RX_PIN GPIO_NUM_12 -#define ML307_TX_PIN GPIO_NUM_13 -/* Camera pins */ -#define CAMERA_PIN_PWDN -1 -#define CAMERA_PIN_RESET -1 -#define CAMERA_PIN_XCLK 15 -#define CAMERA_PIN_SIOD 4 -#define CAMERA_PIN_SIOC 5 - -#define CAMERA_PIN_D7 16 -#define CAMERA_PIN_D6 17 -#define CAMERA_PIN_D5 18 -#define CAMERA_PIN_D4 12 -#define CAMERA_PIN_D3 10 -#define CAMERA_PIN_D2 8 -#define CAMERA_PIN_D1 9 -#define CAMERA_PIN_D0 11 -#define CAMERA_PIN_VSYNC 6 -#define CAMERA_PIN_HREF 7 -#define CAMERA_PIN_PCLK 13 - -#define XCLK_FREQ_HZ 20000000 -#endif // _BOARD_CONFIG_H_ + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_DEFAULT_OUTPUT_VOLUME 90 + + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_42 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_41 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_2 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_3 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_46 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_1 + + +#define BUILTIN_LED_GPIO GPIO_NUM_38 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define RESET_NVS_BUTTON_GPIO GPIO_NUM_NC +#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SDA_PIN GPIO_NUM_NC +#define DISPLAY_SCL_PIN GPIO_NUM_NC +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 280 +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define BACKLIGHT_INVERT false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 20 +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_48 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define ML307_RX_PIN GPIO_NUM_12 +#define ML307_TX_PIN GPIO_NUM_13 +/* Camera pins */ +#define CAMERA_PIN_PWDN -1 +#define CAMERA_PIN_RESET -1 +#define CAMERA_PIN_XCLK 15 +#define CAMERA_PIN_SIOD 4 +#define CAMERA_PIN_SIOC 5 + +#define CAMERA_PIN_D7 16 +#define CAMERA_PIN_D6 17 +#define CAMERA_PIN_D5 18 +#define CAMERA_PIN_D4 12 +#define CAMERA_PIN_D3 10 +#define CAMERA_PIN_D2 8 +#define CAMERA_PIN_D1 9 +#define CAMERA_PIN_D0 11 +#define CAMERA_PIN_VSYNC 6 +#define CAMERA_PIN_HREF 7 +#define CAMERA_PIN_PCLK 13 + +#define XCLK_FREQ_HZ 20000000 +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc b/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc index 5191b5a..d2ce316 100644 --- a/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc +++ b/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc @@ -1,155 +1,155 @@ -#include "wifi_board.h" -#include "ml307_board.h" - -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" - -#include -#include -#include -#include -#include "esp32_camera.h" - -#define TAG "kevin-sp-v3" - -// class KEVIN_SP_V3Board : public Ml307Board { -class KEVIN_SP_V3Board : public WifiBoard { -private: - i2c_master_bus_handle_t display_i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - Esp32Camera* camera_; - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_47; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_21; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - boot_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - } - - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_14; - io_config.dc_gpio_num = GPIO_NUM_45; - io_config.spi_mode = 3; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeCamera() { - // Open camera power - - camera_config_t config = {}; - config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 - config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 - config.pin_d0 = CAMERA_PIN_D0; - config.pin_d1 = CAMERA_PIN_D1; - config.pin_d2 = CAMERA_PIN_D2; - config.pin_d3 = CAMERA_PIN_D3; - config.pin_d4 = CAMERA_PIN_D4; - config.pin_d5 = CAMERA_PIN_D5; - config.pin_d6 = CAMERA_PIN_D6; - config.pin_d7 = CAMERA_PIN_D7; - config.pin_xclk = CAMERA_PIN_XCLK; - config.pin_pclk = CAMERA_PIN_PCLK; - config.pin_vsync = CAMERA_PIN_VSYNC; - config.pin_href = CAMERA_PIN_HREF; - config.pin_sccb_sda = CAMERA_PIN_SIOD; // 这里写-1 表示使用已经初始化的I2C接口 - config.pin_sccb_scl = CAMERA_PIN_SIOC; - config.sccb_i2c_port = 1; - config.pin_pwdn = CAMERA_PIN_PWDN; - config.pin_reset = CAMERA_PIN_RESET; - config.xclk_freq_hz = XCLK_FREQ_HZ; - config.pixel_format = PIXFORMAT_RGB565; - config.frame_size = FRAMESIZE_VGA; - config.jpeg_quality = 12; - config.fb_count = 1; - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - camera_ = new Esp32Camera(config); - } - -public: - KEVIN_SP_V3Board() : boot_button_(BOOT_BUTTON_GPIO) { - ESP_LOGI(TAG, "Initializing KEVIN_SP_V3 Board"); - InitializeSpi(); - InitializeButtons(); - InitializeSt7789Display(); - InitializeCamera(); - GetBacklight()->RestoreBrightness(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec *GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display *GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(KEVIN_SP_V3Board); +#include "wifi_board.h" +#include "ml307_board.h" + +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" + +#include +#include +#include +#include +#include "esp32_camera.h" + +#define TAG "kevin-sp-v3" + +// class KEVIN_SP_V3Board : public Ml307Board { +class KEVIN_SP_V3Board : public WifiBoard { +private: + i2c_master_bus_handle_t display_i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + Esp32Camera* camera_; + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_47; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_21; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + boot_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_14; + io_config.dc_gpio_num = GPIO_NUM_45; + io_config.spi_mode = 3; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeCamera() { + // Open camera power + + camera_config_t config = {}; + config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 + config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 + config.pin_d0 = CAMERA_PIN_D0; + config.pin_d1 = CAMERA_PIN_D1; + config.pin_d2 = CAMERA_PIN_D2; + config.pin_d3 = CAMERA_PIN_D3; + config.pin_d4 = CAMERA_PIN_D4; + config.pin_d5 = CAMERA_PIN_D5; + config.pin_d6 = CAMERA_PIN_D6; + config.pin_d7 = CAMERA_PIN_D7; + config.pin_xclk = CAMERA_PIN_XCLK; + config.pin_pclk = CAMERA_PIN_PCLK; + config.pin_vsync = CAMERA_PIN_VSYNC; + config.pin_href = CAMERA_PIN_HREF; + config.pin_sccb_sda = CAMERA_PIN_SIOD; // 这里写-1 表示使用已经初始化的I2C接口 + config.pin_sccb_scl = CAMERA_PIN_SIOC; + config.sccb_i2c_port = 1; + config.pin_pwdn = CAMERA_PIN_PWDN; + config.pin_reset = CAMERA_PIN_RESET; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_VGA; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + camera_ = new Esp32Camera(config); + } + +public: + KEVIN_SP_V3Board() : boot_button_(BOOT_BUTTON_GPIO) { + ESP_LOGI(TAG, "Initializing KEVIN_SP_V3 Board"); + InitializeSpi(); + InitializeButtons(); + InitializeSt7789Display(); + InitializeCamera(); + GetBacklight()->RestoreBrightness(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec *GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display *GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(KEVIN_SP_V3Board); diff --git a/main/boards/kevin-sp-v4-dev/config.h b/main/boards/kevin-sp-v4-dev/config.h index 7ad6ed3..196fbe8 100644 --- a/main/boards/kevin-sp-v4-dev/config.h +++ b/main/boards/kevin-sp-v4-dev/config.h @@ -1,66 +1,66 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 16000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_1 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_46 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_2 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_3 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_41 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_38 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define RESET_NVS_BUTTON_GPIO GPIO_NUM_NC -#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SDA_PIN GPIO_NUM_NC -#define DISPLAY_SCL_PIN GPIO_NUM_NC -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 280 -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define BACKLIGHT_INVERT false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 20 -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_48 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define ML307_RX_PIN GPIO_NUM_12 -#define ML307_TX_PIN GPIO_NUM_13 -/* Camera pins */ -#define CAMERA_PIN_PWDN -1 -#define CAMERA_PIN_RESET -1 -#define CAMERA_PIN_XCLK 15 -#define CAMERA_PIN_SIOD 4 -#define CAMERA_PIN_SIOC 5 - -#define CAMERA_PIN_D7 16 -#define CAMERA_PIN_D6 17 -#define CAMERA_PIN_D5 18 -#define CAMERA_PIN_D4 12 -#define CAMERA_PIN_D3 10 -#define CAMERA_PIN_D2 8 -#define CAMERA_PIN_D1 9 -#define CAMERA_PIN_D0 11 -#define CAMERA_PIN_VSYNC 6 -#define CAMERA_PIN_HREF 7 -#define CAMERA_PIN_PCLK 13 - -#define XCLK_FREQ_HZ 20000000 - -#endif // _BOARD_CONFIG_H_ + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 16000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_1 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_46 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_2 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_3 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_41 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_38 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define RESET_NVS_BUTTON_GPIO GPIO_NUM_NC +#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SDA_PIN GPIO_NUM_NC +#define DISPLAY_SCL_PIN GPIO_NUM_NC +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 280 +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define BACKLIGHT_INVERT false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 20 +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_48 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define ML307_RX_PIN GPIO_NUM_12 +#define ML307_TX_PIN GPIO_NUM_13 +/* Camera pins */ +#define CAMERA_PIN_PWDN -1 +#define CAMERA_PIN_RESET -1 +#define CAMERA_PIN_XCLK 15 +#define CAMERA_PIN_SIOD 4 +#define CAMERA_PIN_SIOC 5 + +#define CAMERA_PIN_D7 16 +#define CAMERA_PIN_D6 17 +#define CAMERA_PIN_D5 18 +#define CAMERA_PIN_D4 12 +#define CAMERA_PIN_D3 10 +#define CAMERA_PIN_D2 8 +#define CAMERA_PIN_D1 9 +#define CAMERA_PIN_D0 11 +#define CAMERA_PIN_VSYNC 6 +#define CAMERA_PIN_HREF 7 +#define CAMERA_PIN_PCLK 13 + +#define XCLK_FREQ_HZ 20000000 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/kevin-sp-v4-dev/config.json b/main/boards/kevin-sp-v4-dev/config.json index 1221fbf..6922725 100644 --- a/main/boards/kevin-sp-v4-dev/config.json +++ b/main/boards/kevin-sp-v4-dev/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "kevin-sp-v4-dev", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "kevin-sp-v4-dev", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc b/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc index 56ed905..656e990 100644 --- a/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc +++ b/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc @@ -1,172 +1,172 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" - -#include -#include -#include -#include -#include "esp32_camera.h" - -#define TAG "kevin-sp-v4" - -class KEVIN_SP_V4Board : public WifiBoard { -private: - i2c_master_bus_handle_t display_i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - i2c_master_bus_handle_t codec_i2c_bus_; - Esp32Camera* camera_; - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_47; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_21; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - boot_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - } - - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_14; - io_config.dc_gpio_num = GPIO_NUM_45; - io_config.spi_mode = 3; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeCamera() { - // Open camera power - - camera_config_t config = {}; - config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 - config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 - config.pin_d0 = CAMERA_PIN_D0; - config.pin_d1 = CAMERA_PIN_D1; - config.pin_d2 = CAMERA_PIN_D2; - config.pin_d3 = CAMERA_PIN_D3; - config.pin_d4 = CAMERA_PIN_D4; - config.pin_d5 = CAMERA_PIN_D5; - config.pin_d6 = CAMERA_PIN_D6; - config.pin_d7 = CAMERA_PIN_D7; - config.pin_xclk = CAMERA_PIN_XCLK; - config.pin_pclk = CAMERA_PIN_PCLK; - config.pin_vsync = CAMERA_PIN_VSYNC; - config.pin_href = CAMERA_PIN_HREF; - config.pin_sccb_sda = -1; // 这里写-1 表示使用已经初始化的I2C接口 - config.pin_sccb_scl = CAMERA_PIN_SIOC; - config.sccb_i2c_port = 1; - config.pin_pwdn = CAMERA_PIN_PWDN; - config.pin_reset = CAMERA_PIN_RESET; - config.xclk_freq_hz = XCLK_FREQ_HZ; - config.pixel_format = PIXFORMAT_RGB565; - config.frame_size = FRAMESIZE_VGA; - config.jpeg_quality = 12; - config.fb_count = 1; - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - camera_ = new Esp32Camera(config); - } - -public: - KEVIN_SP_V4Board() : boot_button_(BOOT_BUTTON_GPIO) { - ESP_LOGI(TAG, "Initializing KEVIN SP V4 Board"); - InitializeCodecI2c(); - InitializeSpi(); - InitializeButtons(); - InitializeSt7789Display(); - InitializeCamera(); - GetBacklight()->RestoreBrightness(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display *GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(KEVIN_SP_V4Board); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" + +#include +#include +#include +#include +#include "esp32_camera.h" + +#define TAG "kevin-sp-v4" + +class KEVIN_SP_V4Board : public WifiBoard { +private: + i2c_master_bus_handle_t display_i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + i2c_master_bus_handle_t codec_i2c_bus_; + Esp32Camera* camera_; + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_47; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_21; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + boot_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_14; + io_config.dc_gpio_num = GPIO_NUM_45; + io_config.spi_mode = 3; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeCamera() { + // Open camera power + + camera_config_t config = {}; + config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 + config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 + config.pin_d0 = CAMERA_PIN_D0; + config.pin_d1 = CAMERA_PIN_D1; + config.pin_d2 = CAMERA_PIN_D2; + config.pin_d3 = CAMERA_PIN_D3; + config.pin_d4 = CAMERA_PIN_D4; + config.pin_d5 = CAMERA_PIN_D5; + config.pin_d6 = CAMERA_PIN_D6; + config.pin_d7 = CAMERA_PIN_D7; + config.pin_xclk = CAMERA_PIN_XCLK; + config.pin_pclk = CAMERA_PIN_PCLK; + config.pin_vsync = CAMERA_PIN_VSYNC; + config.pin_href = CAMERA_PIN_HREF; + config.pin_sccb_sda = -1; // 这里写-1 表示使用已经初始化的I2C接口 + config.pin_sccb_scl = CAMERA_PIN_SIOC; + config.sccb_i2c_port = 1; + config.pin_pwdn = CAMERA_PIN_PWDN; + config.pin_reset = CAMERA_PIN_RESET; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_VGA; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + camera_ = new Esp32Camera(config); + } + +public: + KEVIN_SP_V4Board() : boot_button_(BOOT_BUTTON_GPIO) { + ESP_LOGI(TAG, "Initializing KEVIN SP V4 Board"); + InitializeCodecI2c(); + InitializeSpi(); + InitializeButtons(); + InitializeSt7789Display(); + InitializeCamera(); + GetBacklight()->RestoreBrightness(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display *GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(KEVIN_SP_V4Board); diff --git a/main/boards/kevin-yuying-313lcd/config.h b/main/boards/kevin-yuying-313lcd/config.h index b533741..99479e5 100644 --- a/main/boards/kevin-yuying-313lcd/config.h +++ b/main/boards/kevin-yuying-313lcd/config.h @@ -1,35 +1,35 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 16000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_39 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_38 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_45 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define DISPLAY_WIDTH 376 -#define DISPLAY_HEIGHT 960 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY true - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 16000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_39 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_38 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_45 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define DISPLAY_WIDTH 376 +#define DISPLAY_HEIGHT 960 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/kevin-yuying-313lcd/config.json b/main/boards/kevin-yuying-313lcd/config.json index ae29bb4..60accab 100644 --- a/main/boards/kevin-yuying-313lcd/config.json +++ b/main/boards/kevin-yuying-313lcd/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "kevin-yuying-313lcd", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "kevin-yuying-313lcd", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.c b/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.c index 9f62738..10c5207 100644 --- a/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.c +++ b/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.c @@ -1,478 +1,478 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "esp_lcd_gc9503.h" - -#define GC9503_CMD_MADCTL (0xB1) // Memory data access control -#define GC9503_CMD_MADCTL_DEFAULT (0x10) // Default value of Memory data access control -#define GC9503_CMD_SS_BIT (1 << 0) // Source driver scan direction, 0: top to bottom, 1: bottom to top -#define GC9503_CMD_GS_BIT (1 << 1) // Gate driver scan direction, 0: left to right, 1: right to left -#define GC9503_CMD_BGR_BIT (1 << 5) // RGB/BGR order, 0: RGB, 1: BGR - -typedef struct -{ - esp_lcd_panel_io_handle_t io; - int reset_gpio_num; - uint8_t madctl_val; // Save current value of GC9503_CMD_MADCTL register - uint8_t colmod_val; // Save current value of LCD_CMD_COLMOD register - const gc9503_lcd_init_cmd_t *init_cmds; - uint16_t init_cmds_size; - struct - { - unsigned int mirror_by_cmd : 1; - unsigned int auto_del_panel_io : 1; - unsigned int display_on_off_use_cmd : 1; - unsigned int reset_level : 1; - } flags; - // To save the original functions of RGB panel - esp_err_t (*init)(esp_lcd_panel_t *panel); - esp_err_t (*del)(esp_lcd_panel_t *panel); - esp_err_t (*reset)(esp_lcd_panel_t *panel); - esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis); - esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off); -} gc9503_panel_t; - -static const char *TAG = "gc9503"; - -static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503); - -static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool off); - -esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, - esp_lcd_panel_handle_t *ret_panel) -{ - ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments"); - gc9503_vendor_config_t *vendor_config = (gc9503_vendor_config_t *)panel_dev_config->vendor_config; - ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary"); - ESP_RETURN_ON_FALSE(!vendor_config->flags.auto_del_panel_io || !vendor_config->flags.mirror_by_cmd, - ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `auto_del_panel_io` cannot work together"); - - esp_err_t ret = ESP_OK; - gpio_config_t io_conf = {0}; - - gc9503_panel_t *gc9503 = (gc9503_panel_t *)calloc(1, sizeof(gc9503_panel_t)); - ESP_RETURN_ON_FALSE(gc9503, ESP_ERR_NO_MEM, TAG, "no mem for gc9503 panel"); - - if (panel_dev_config->reset_gpio_num >= 0) - { - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; - ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); - } - - gc9503->madctl_val = GC9503_CMD_MADCTL_DEFAULT; - switch (panel_dev_config->rgb_ele_order) - { - case LCD_RGB_ELEMENT_ORDER_RGB: - gc9503->madctl_val &= ~GC9503_CMD_BGR_BIT; - break; - case LCD_RGB_ELEMENT_ORDER_BGR: - gc9503->madctl_val |= GC9503_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order"); - break; - } - - gc9503->colmod_val = 0; - switch (panel_dev_config->bits_per_pixel) - { - case 16: // RGB565 - gc9503->colmod_val = 0x50; - break; - case 18: // RGB666 - gc9503->colmod_val = 0x60; - break; - case 24: // RGB888 - gc9503->colmod_val = 0x70; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); - break; - } - - gc9503->io = io; - gc9503->init_cmds = vendor_config->init_cmds; - gc9503->init_cmds_size = vendor_config->init_cmds_size; - gc9503->reset_gpio_num = panel_dev_config->reset_gpio_num; - gc9503->flags.reset_level = panel_dev_config->flags.reset_active_high; - gc9503->flags.auto_del_panel_io = vendor_config->flags.auto_del_panel_io; - gc9503->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd; - gc9503->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1; - - if (gc9503->flags.auto_del_panel_io) - { - if (gc9503->reset_gpio_num >= 0) - { // Perform hardware reset - gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); - } - else - { // Perform software reset - ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed"); - } - vTaskDelay(pdMS_TO_TICKS(120)); - - /** - * In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface - * (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before - * `esp_lcd_new_rgb_panel()` is called. - */ - ESP_GOTO_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), err, TAG, "send init commands failed"); - // After sending the initialization commands, the 3-wire SPI interface can be deleted - ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed"); - gc9503->io = NULL; - ESP_LOGD(TAG, "delete panel IO"); - } - - // Create RGB panel - ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed"); - ESP_LOGD(TAG, "new RGB panel @%p", ret_panel); - - // Save the original functions of RGB panel - gc9503->init = (*ret_panel)->init; - gc9503->del = (*ret_panel)->del; - gc9503->reset = (*ret_panel)->reset; - gc9503->mirror = (*ret_panel)->mirror; - gc9503->disp_on_off = (*ret_panel)->disp_on_off; - // Overwrite the functions of RGB panel - (*ret_panel)->init = panel_gc9503_init; - (*ret_panel)->del = panel_gc9503_del; - (*ret_panel)->reset = panel_gc9503_reset; - (*ret_panel)->mirror = panel_gc9503_mirror; - (*ret_panel)->disp_on_off = panel_gc9503_disp_on_off; - (*ret_panel)->user_data = gc9503; - ESP_LOGD(TAG, "new gc9503 panel @%p", gc9503); - - // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9503_VER_MAJOR, ESP_LCD_GC9503_VER_MINOR, - // ESP_LCD_GC9503_VER_PATCH); - return ESP_OK; - -err: - if (gc9503) - { - if (panel_dev_config->reset_gpio_num >= 0) - { - gpio_reset_pin(panel_dev_config->reset_gpio_num); - } - free(gc9503); - } - return ret; -} - -// *INDENT-OFF* -// static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { -// // {cmd, { data }, data_size, delay_ms} -// {0x11, (uint8_t []){0x00}, 0, 120}, - -// {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0}, -// {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0}, -// {0xc1, (uint8_t []){0x3f}, 1, 0}, -// {0xc2, (uint8_t []){0x0e}, 1, 0}, -// {0xc6, (uint8_t []){0xf8}, 1, 0}, -// {0xc9, (uint8_t []){0x10}, 1, 0}, -// {0xcd, (uint8_t []){0x25}, 1, 0}, -// {0xf8, (uint8_t []){0x8a}, 1, 0}, -// {0xac, (uint8_t []){0x45}, 1, 0}, -// {0xa0, (uint8_t []){0xdd}, 1, 0}, -// {0xa7, (uint8_t []){0x47}, 1, 0}, -// {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0}, -// {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0}, -// {0xa3, (uint8_t []){0xee}, 1, 0}, -// {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0}, -// {0x71, (uint8_t []){0x48}, 1, 0}, -// {0x72, (uint8_t []){0x48}, 1, 0}, -// {0x73, (uint8_t []){0x00, 0x44}, 2, 0}, -// {0x97, (uint8_t []){0xee}, 1, 0}, -// {0x83, (uint8_t []){0x93}, 1, 0}, -// {0x9a, (uint8_t []){0x72}, 1, 0}, -// {0x9b, (uint8_t []){0x5a}, 1, 0}, -// {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0}, -// {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, -// 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0}, -// {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, -// {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0}, -// {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0}, -// {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0}, -// {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, -// {0x6b, (uint8_t []){0x07}, 1, 0}, -// {0x7a, (uint8_t []){0x08, 0x13}, 2, 0}, -// {0x7b, (uint8_t []){0x08, 0x13}, 2, 0}, -// {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, -// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, -// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, -// 0xff}, 52, 0}, -// {0x11, (uint8_t []){0x00}, 0, 120}, -// {0x29, (uint8_t []){0x00}, 0, 20}, -// }; -static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { - // {0x11, (uint8_t[]){}, 0, 20}, - - {0xF0, (uint8_t[]){0x55, 0xAA, 0x52, 0x08, 0x00}, 5, 0}, - {0xF6, (uint8_t[]){0x5A, 0x87}, 2, 0}, - {0xC1, (uint8_t[]){0x3F}, 1, 0}, - {0xCD, (uint8_t[]){0x25}, 1, 0}, - {0xC9, (uint8_t[]){0x10}, 1, 0}, - {0xF8, (uint8_t[]){0x8A}, 1, 0}, - {0xAC, (uint8_t[]){0x45}, 1, 0}, - {0xA7, (uint8_t[]){0x47}, 1, 0}, - {0xA0, (uint8_t[]){0x88}, 1, 0}, - {0x86, (uint8_t[]){0x99, 0xA3, 0xA3, 0x51}, 4, 0}, - {0xFA, (uint8_t[]){0x08, 0x08, 0x00, 0x04}, 4, 0}, - {0xA3, (uint8_t[]){0x6E}, 1, 0}, - {0xFD, (uint8_t[]){0x28, 0x3C, 0x00}, 3, 0}, - {0x9A, (uint8_t[]){0x4B}, 1, 0}, - {0x9B, (uint8_t[]){0x4B}, 1, 0}, - {0x82, (uint8_t[]){0x20, 0x20}, 2, 0}, - {0xB1, (uint8_t[]){0x10}, 1, 0}, - {0x7A, (uint8_t[]){0x0F, 0x13}, 2, 0}, - {0x7B, (uint8_t[]){0x0F, 0x13}, 2, 0}, - {0x6D, (uint8_t[]){0x1e, 0x1e, 0x04, 0x02, 0x0d, 0x1e, 0x12, 0x11, 0x14, 0x13, 0x05, 0x06, 0x1d, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1d, 0x06, 0x05, 0x0b, 0x0c, 0x09, 0x0a, 0x1e, 0x0d, 0x01, 0x03, 0x1e, 0x1e}, 32, 0}, - {0x64, (uint8_t[]){0x38, 0x08, 0x03, 0xc0, 0x03, 0x03, 0x38, 0x06, 0x03, 0xc2, 0x03, 0x03, 0x20, 0x6d, 0x20, 0x6d}, 16, 0}, - {0x65, (uint8_t[]){0x38, 0x04, 0x03, 0xc4, 0x03, 0x03, 0x38, 0x02, 0x03, 0xc6, 0x03, 0x03, 0x20, 0x6d, 0x20, 0x6d}, 16, 0}, - {0x66, (uint8_t[]){0x83, 0xcf, 0x03, 0xc8, 0x03, 0x03, 0x83, 0xd3, 0x03, 0xd2, 0x03, 0x03, 0x20, 0x6d, 0x20, 0x6d}, 16, 0}, - {0x60, (uint8_t[]){0x38, 0x0C, 0x20, 0x6D, 0x38, 0x0B, 0x20, 0x6D}, 8, 0}, - {0x61, (uint8_t[]){0x38, 0x0A, 0x20, 0x6D, 0x38, 0x09, 0x20, 0x6D}, 8, 0}, - {0x62, (uint8_t[]){0x38, 0x25, 0x20, 0x6D, 0x63, 0xC9, 0x20, 0x6D}, 8, 0}, - {0x69, (uint8_t[]){0x14, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, - {0x6B, (uint8_t[]){0x07}, 1, 0}, - {0xD1, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, - {0xD2, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, - {0xD3, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, - {0xD4, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, - {0xD5, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, - {0xD6, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, - // {0x3A, (uint8_t[]){0x55}, 1, 0}, - - {0x11, NULL, 0, 120}, // Delay 120ms - {0x29, NULL, 0, 120}}; - -// *INDENT-OFF* - -static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503) -{ - esp_lcd_panel_io_handle_t io = gc9503->io; - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ - gc9503->madctl_val, - }, - 1), - TAG, "send command failed"); - ; - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){ - gc9503->colmod_val, - }, - 1), - TAG, "send command failed"); - ; - - // Vendor specific initialization, it can be different between manufacturers - // should consult the LCD supplier for initialization sequence code - const gc9503_lcd_init_cmd_t *init_cmds = NULL; - uint16_t init_cmds_size = 0; - if (gc9503->init_cmds) - { - init_cmds = gc9503->init_cmds; - init_cmds_size = gc9503->init_cmds_size; - } - else - { - init_cmds = vendor_specific_init_default; - init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9503_lcd_init_cmd_t); - } - - bool is_cmd_overwritten = false; - for (int i = 0; i < init_cmds_size; i++) - { - // Check if the command has been used or conflicts with the internal - switch (init_cmds[i].cmd) - { - case LCD_CMD_MADCTL: - is_cmd_overwritten = true; - gc9503->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - case LCD_CMD_COLMOD: - is_cmd_overwritten = true; - gc9503->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - default: - is_cmd_overwritten = false; - break; - } - - if (is_cmd_overwritten) - { - ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", - init_cmds[i].cmd); - } - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), - TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); - } - ESP_LOGD(TAG, "send init commands success"); - - return ESP_OK; -} - -static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - - if (!gc9503->flags.auto_del_panel_io) - { - ESP_RETURN_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), TAG, "send init commands failed"); - } - // Init RGB panel - ESP_RETURN_ON_ERROR(gc9503->init(panel), TAG, "init RGB panel failed"); - - return ESP_OK; -} - -static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - - if (gc9503->reset_gpio_num >= 0) - { - gpio_reset_pin(gc9503->reset_gpio_num); - } - // Delete RGB panel - gc9503->del(panel); - free(gc9503); - ESP_LOGD(TAG, "del gc9503 panel @%p", gc9503); - return ESP_OK; -} - -static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - esp_lcd_panel_io_handle_t io = gc9503->io; - - // Perform hardware reset - if (gc9503->reset_gpio_num >= 0) - { - gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); - vTaskDelay(pdMS_TO_TICKS(120)); - } - else if (io) - { // Perform software reset - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(120)); - } - // Reset RGB panel - ESP_RETURN_ON_ERROR(gc9503->reset(panel), TAG, "reset RGB panel failed"); - - return ESP_OK; -} - -static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - esp_lcd_panel_io_handle_t io = gc9503->io; - - if (gc9503->flags.mirror_by_cmd) - { - ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); - // Control mirror through LCD command - if (mirror_x) - { - gc9503->madctl_val |= GC9503_CMD_GS_BIT; - } - else - { - gc9503->madctl_val &= ~GC9503_CMD_GS_BIT; - } - if (mirror_y) - { - gc9503->madctl_val |= GC9503_CMD_SS_BIT; - } - else - { - gc9503->madctl_val &= ~GC9503_CMD_SS_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ - gc9503->madctl_val, - }, - 1), - TAG, "send command failed"); - ; - } - else - { - // Control mirror through RGB panel - ESP_RETURN_ON_ERROR(gc9503->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed"); - } - return ESP_OK; -} - -static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool on_off) -{ - gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; - esp_lcd_panel_io_handle_t io = gc9503->io; - int command = 0; - - if (gc9503->flags.display_on_off_use_cmd) - { - ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); - // Control display on/off through LCD command - if (on_off) - { - command = LCD_CMD_DISPON; - } - else - { - command = LCD_CMD_DISPOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); - } - else - { - // Control display on/off through display control signal - ESP_RETURN_ON_ERROR(gc9503->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed"); - } - return ESP_OK; -} +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "esp_lcd_gc9503.h" + +#define GC9503_CMD_MADCTL (0xB1) // Memory data access control +#define GC9503_CMD_MADCTL_DEFAULT (0x10) // Default value of Memory data access control +#define GC9503_CMD_SS_BIT (1 << 0) // Source driver scan direction, 0: top to bottom, 1: bottom to top +#define GC9503_CMD_GS_BIT (1 << 1) // Gate driver scan direction, 0: left to right, 1: right to left +#define GC9503_CMD_BGR_BIT (1 << 5) // RGB/BGR order, 0: RGB, 1: BGR + +typedef struct +{ + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + uint8_t madctl_val; // Save current value of GC9503_CMD_MADCTL register + uint8_t colmod_val; // Save current value of LCD_CMD_COLMOD register + const gc9503_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; + struct + { + unsigned int mirror_by_cmd : 1; + unsigned int auto_del_panel_io : 1; + unsigned int display_on_off_use_cmd : 1; + unsigned int reset_level : 1; + } flags; + // To save the original functions of RGB panel + esp_err_t (*init)(esp_lcd_panel_t *panel); + esp_err_t (*del)(esp_lcd_panel_t *panel); + esp_err_t (*reset)(esp_lcd_panel_t *panel); + esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis); + esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off); +} gc9503_panel_t; + +static const char *TAG = "gc9503"; + +static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503); + +static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool off); + +esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, + esp_lcd_panel_handle_t *ret_panel) +{ + ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments"); + gc9503_vendor_config_t *vendor_config = (gc9503_vendor_config_t *)panel_dev_config->vendor_config; + ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary"); + ESP_RETURN_ON_FALSE(!vendor_config->flags.auto_del_panel_io || !vendor_config->flags.mirror_by_cmd, + ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `auto_del_panel_io` cannot work together"); + + esp_err_t ret = ESP_OK; + gpio_config_t io_conf = {0}; + + gc9503_panel_t *gc9503 = (gc9503_panel_t *)calloc(1, sizeof(gc9503_panel_t)); + ESP_RETURN_ON_FALSE(gc9503, ESP_ERR_NO_MEM, TAG, "no mem for gc9503 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) + { + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + + gc9503->madctl_val = GC9503_CMD_MADCTL_DEFAULT; + switch (panel_dev_config->rgb_ele_order) + { + case LCD_RGB_ELEMENT_ORDER_RGB: + gc9503->madctl_val &= ~GC9503_CMD_BGR_BIT; + break; + case LCD_RGB_ELEMENT_ORDER_BGR: + gc9503->madctl_val |= GC9503_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order"); + break; + } + + gc9503->colmod_val = 0; + switch (panel_dev_config->bits_per_pixel) + { + case 16: // RGB565 + gc9503->colmod_val = 0x50; + break; + case 18: // RGB666 + gc9503->colmod_val = 0x60; + break; + case 24: // RGB888 + gc9503->colmod_val = 0x70; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + gc9503->io = io; + gc9503->init_cmds = vendor_config->init_cmds; + gc9503->init_cmds_size = vendor_config->init_cmds_size; + gc9503->reset_gpio_num = panel_dev_config->reset_gpio_num; + gc9503->flags.reset_level = panel_dev_config->flags.reset_active_high; + gc9503->flags.auto_del_panel_io = vendor_config->flags.auto_del_panel_io; + gc9503->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd; + gc9503->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1; + + if (gc9503->flags.auto_del_panel_io) + { + if (gc9503->reset_gpio_num >= 0) + { // Perform hardware reset + gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); + } + else + { // Perform software reset + ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed"); + } + vTaskDelay(pdMS_TO_TICKS(120)); + + /** + * In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface + * (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before + * `esp_lcd_new_rgb_panel()` is called. + */ + ESP_GOTO_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), err, TAG, "send init commands failed"); + // After sending the initialization commands, the 3-wire SPI interface can be deleted + ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed"); + gc9503->io = NULL; + ESP_LOGD(TAG, "delete panel IO"); + } + + // Create RGB panel + ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed"); + ESP_LOGD(TAG, "new RGB panel @%p", ret_panel); + + // Save the original functions of RGB panel + gc9503->init = (*ret_panel)->init; + gc9503->del = (*ret_panel)->del; + gc9503->reset = (*ret_panel)->reset; + gc9503->mirror = (*ret_panel)->mirror; + gc9503->disp_on_off = (*ret_panel)->disp_on_off; + // Overwrite the functions of RGB panel + (*ret_panel)->init = panel_gc9503_init; + (*ret_panel)->del = panel_gc9503_del; + (*ret_panel)->reset = panel_gc9503_reset; + (*ret_panel)->mirror = panel_gc9503_mirror; + (*ret_panel)->disp_on_off = panel_gc9503_disp_on_off; + (*ret_panel)->user_data = gc9503; + ESP_LOGD(TAG, "new gc9503 panel @%p", gc9503); + + // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9503_VER_MAJOR, ESP_LCD_GC9503_VER_MINOR, + // ESP_LCD_GC9503_VER_PATCH); + return ESP_OK; + +err: + if (gc9503) + { + if (panel_dev_config->reset_gpio_num >= 0) + { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(gc9503); + } + return ret; +} + +// *INDENT-OFF* +// static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { +// // {cmd, { data }, data_size, delay_ms} +// {0x11, (uint8_t []){0x00}, 0, 120}, + +// {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0}, +// {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0}, +// {0xc1, (uint8_t []){0x3f}, 1, 0}, +// {0xc2, (uint8_t []){0x0e}, 1, 0}, +// {0xc6, (uint8_t []){0xf8}, 1, 0}, +// {0xc9, (uint8_t []){0x10}, 1, 0}, +// {0xcd, (uint8_t []){0x25}, 1, 0}, +// {0xf8, (uint8_t []){0x8a}, 1, 0}, +// {0xac, (uint8_t []){0x45}, 1, 0}, +// {0xa0, (uint8_t []){0xdd}, 1, 0}, +// {0xa7, (uint8_t []){0x47}, 1, 0}, +// {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0}, +// {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0}, +// {0xa3, (uint8_t []){0xee}, 1, 0}, +// {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0}, +// {0x71, (uint8_t []){0x48}, 1, 0}, +// {0x72, (uint8_t []){0x48}, 1, 0}, +// {0x73, (uint8_t []){0x00, 0x44}, 2, 0}, +// {0x97, (uint8_t []){0xee}, 1, 0}, +// {0x83, (uint8_t []){0x93}, 1, 0}, +// {0x9a, (uint8_t []){0x72}, 1, 0}, +// {0x9b, (uint8_t []){0x5a}, 1, 0}, +// {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0}, +// {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, +// 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0}, +// {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0}, +// {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0}, +// {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0}, +// {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0}, +// {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, +// {0x6b, (uint8_t []){0x07}, 1, 0}, +// {0x7a, (uint8_t []){0x08, 0x13}, 2, 0}, +// {0x7b, (uint8_t []){0x08, 0x13}, 2, 0}, +// {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00, +// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, +// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, +// 0xff}, 52, 0}, +// {0x11, (uint8_t []){0x00}, 0, 120}, +// {0x29, (uint8_t []){0x00}, 0, 20}, +// }; +static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = { + // {0x11, (uint8_t[]){}, 0, 20}, + + {0xF0, (uint8_t[]){0x55, 0xAA, 0x52, 0x08, 0x00}, 5, 0}, + {0xF6, (uint8_t[]){0x5A, 0x87}, 2, 0}, + {0xC1, (uint8_t[]){0x3F}, 1, 0}, + {0xCD, (uint8_t[]){0x25}, 1, 0}, + {0xC9, (uint8_t[]){0x10}, 1, 0}, + {0xF8, (uint8_t[]){0x8A}, 1, 0}, + {0xAC, (uint8_t[]){0x45}, 1, 0}, + {0xA7, (uint8_t[]){0x47}, 1, 0}, + {0xA0, (uint8_t[]){0x88}, 1, 0}, + {0x86, (uint8_t[]){0x99, 0xA3, 0xA3, 0x51}, 4, 0}, + {0xFA, (uint8_t[]){0x08, 0x08, 0x00, 0x04}, 4, 0}, + {0xA3, (uint8_t[]){0x6E}, 1, 0}, + {0xFD, (uint8_t[]){0x28, 0x3C, 0x00}, 3, 0}, + {0x9A, (uint8_t[]){0x4B}, 1, 0}, + {0x9B, (uint8_t[]){0x4B}, 1, 0}, + {0x82, (uint8_t[]){0x20, 0x20}, 2, 0}, + {0xB1, (uint8_t[]){0x10}, 1, 0}, + {0x7A, (uint8_t[]){0x0F, 0x13}, 2, 0}, + {0x7B, (uint8_t[]){0x0F, 0x13}, 2, 0}, + {0x6D, (uint8_t[]){0x1e, 0x1e, 0x04, 0x02, 0x0d, 0x1e, 0x12, 0x11, 0x14, 0x13, 0x05, 0x06, 0x1d, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1d, 0x06, 0x05, 0x0b, 0x0c, 0x09, 0x0a, 0x1e, 0x0d, 0x01, 0x03, 0x1e, 0x1e}, 32, 0}, + {0x64, (uint8_t[]){0x38, 0x08, 0x03, 0xc0, 0x03, 0x03, 0x38, 0x06, 0x03, 0xc2, 0x03, 0x03, 0x20, 0x6d, 0x20, 0x6d}, 16, 0}, + {0x65, (uint8_t[]){0x38, 0x04, 0x03, 0xc4, 0x03, 0x03, 0x38, 0x02, 0x03, 0xc6, 0x03, 0x03, 0x20, 0x6d, 0x20, 0x6d}, 16, 0}, + {0x66, (uint8_t[]){0x83, 0xcf, 0x03, 0xc8, 0x03, 0x03, 0x83, 0xd3, 0x03, 0xd2, 0x03, 0x03, 0x20, 0x6d, 0x20, 0x6d}, 16, 0}, + {0x60, (uint8_t[]){0x38, 0x0C, 0x20, 0x6D, 0x38, 0x0B, 0x20, 0x6D}, 8, 0}, + {0x61, (uint8_t[]){0x38, 0x0A, 0x20, 0x6D, 0x38, 0x09, 0x20, 0x6D}, 8, 0}, + {0x62, (uint8_t[]){0x38, 0x25, 0x20, 0x6D, 0x63, 0xC9, 0x20, 0x6D}, 8, 0}, + {0x69, (uint8_t[]){0x14, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0}, + {0x6B, (uint8_t[]){0x07}, 1, 0}, + {0xD1, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, + {0xD2, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, + {0xD3, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, + {0xD4, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, + {0xD5, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, + {0xD6, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0}, + // {0x3A, (uint8_t[]){0x55}, 1, 0}, + + {0x11, NULL, 0, 120}, // Delay 120ms + {0x29, NULL, 0, 120}}; + +// *INDENT-OFF* + +static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503) +{ + esp_lcd_panel_io_handle_t io = gc9503->io; + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ + gc9503->madctl_val, + }, + 1), + TAG, "send command failed"); + ; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){ + gc9503->colmod_val, + }, + 1), + TAG, "send command failed"); + ; + + // Vendor specific initialization, it can be different between manufacturers + // should consult the LCD supplier for initialization sequence code + const gc9503_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + if (gc9503->init_cmds) + { + init_cmds = gc9503->init_cmds; + init_cmds_size = gc9503->init_cmds_size; + } + else + { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9503_lcd_init_cmd_t); + } + + bool is_cmd_overwritten = false; + for (int i = 0; i < init_cmds_size; i++) + { + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd) + { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + gc9503->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + gc9503->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) + { + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", + init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), + TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + + if (!gc9503->flags.auto_del_panel_io) + { + ESP_RETURN_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), TAG, "send init commands failed"); + } + // Init RGB panel + ESP_RETURN_ON_ERROR(gc9503->init(panel), TAG, "init RGB panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + + if (gc9503->reset_gpio_num >= 0) + { + gpio_reset_pin(gc9503->reset_gpio_num); + } + // Delete RGB panel + gc9503->del(panel); + free(gc9503); + ESP_LOGD(TAG, "del gc9503 panel @%p", gc9503); + return ESP_OK; +} + +static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = gc9503->io; + + // Perform hardware reset + if (gc9503->reset_gpio_num >= 0) + { + gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(120)); + } + else if (io) + { // Perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(120)); + } + // Reset RGB panel + ESP_RETURN_ON_ERROR(gc9503->reset(panel), TAG, "reset RGB panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = gc9503->io; + + if (gc9503->flags.mirror_by_cmd) + { + ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); + // Control mirror through LCD command + if (mirror_x) + { + gc9503->madctl_val |= GC9503_CMD_GS_BIT; + } + else + { + gc9503->madctl_val &= ~GC9503_CMD_GS_BIT; + } + if (mirror_y) + { + gc9503->madctl_val |= GC9503_CMD_SS_BIT; + } + else + { + gc9503->madctl_val &= ~GC9503_CMD_SS_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){ + gc9503->madctl_val, + }, + 1), + TAG, "send command failed"); + ; + } + else + { + // Control mirror through RGB panel + ESP_RETURN_ON_ERROR(gc9503->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed"); + } + return ESP_OK; +} + +static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = gc9503->io; + int command = 0; + + if (gc9503->flags.display_on_off_use_cmd) + { + ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command"); + // Control display on/off through LCD command + if (on_off) + { + command = LCD_CMD_DISPON; + } + else + { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + } + else + { + // Control display on/off through display control signal + ESP_RETURN_ON_ERROR(gc9503->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed"); + } + return ESP_OK; +} diff --git a/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.h b/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.h index 40a5ccc..2fd361c 100644 --- a/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.h +++ b/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.h @@ -1,145 +1,145 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -/** - * @file - * @brief ESP LCD: GC9503 - */ - -#pragma once - -#include - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief LCD panel initialization commands. - * - */ -typedef struct { - int cmd; /* + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /* -#include -#include -#include "esp_lcd_gc9503.h" -#include -#include -#include - -#define TAG "Yuying_313lcd" - -class Yuying_313lcd : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - - void InitializeRGB_GC9503V_Display() { - ESP_LOGI(TAG, "Init GC9503V"); - - esp_lcd_panel_io_handle_t panel_io = nullptr; - - ESP_LOGI(TAG, "Install 3-wire SPI panel IO"); - spi_line_config_t line_config = { - .cs_io_type = IO_TYPE_GPIO, - .cs_gpio_num = GC9503V_LCD_IO_SPI_CS_1, - .scl_io_type = IO_TYPE_GPIO, - .scl_gpio_num = GC9503V_LCD_IO_SPI_SCL_1, - .sda_io_type = IO_TYPE_GPIO, - .sda_gpio_num = GC9503V_LCD_IO_SPI_SDO_1, - .io_expander = NULL, - }; - esp_lcd_panel_io_3wire_spi_config_t io_config = GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0); - (esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io)); - - ESP_LOGI(TAG, "Install RGB LCD panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_rgb_panel_config_t rgb_config = { - .clk_src = LCD_CLK_SRC_PLL160M, - .timings = GC9503_376_960_PANEL_60HZ_RGB_TIMING(), - .data_width = 16, // RGB565 in parallel mode, thus 16bit in width - .bits_per_pixel = 16, - .num_fbs = GC9503V_LCD_RGB_BUFFER_NUMS, - .bounce_buffer_size_px = GC9503V_LCD_H_RES * GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT, - .dma_burst_size = 64, - .hsync_gpio_num = GC9503V_PIN_NUM_HSYNC, - .vsync_gpio_num = GC9503V_PIN_NUM_VSYNC, - .de_gpio_num = GC9503V_PIN_NUM_DE, - .pclk_gpio_num = GC9503V_PIN_NUM_PCLK, - .disp_gpio_num = GC9503V_PIN_NUM_DISP_EN, - .data_gpio_nums = { - GC9503V_PIN_NUM_DATA0, - GC9503V_PIN_NUM_DATA1, - GC9503V_PIN_NUM_DATA2, - GC9503V_PIN_NUM_DATA3, - GC9503V_PIN_NUM_DATA4, - GC9503V_PIN_NUM_DATA5, - GC9503V_PIN_NUM_DATA6, - GC9503V_PIN_NUM_DATA7, - GC9503V_PIN_NUM_DATA8, - GC9503V_PIN_NUM_DATA9, - GC9503V_PIN_NUM_DATA10, - GC9503V_PIN_NUM_DATA11, - GC9503V_PIN_NUM_DATA12, - GC9503V_PIN_NUM_DATA13, - GC9503V_PIN_NUM_DATA14, - GC9503V_PIN_NUM_DATA15, - }, - .flags= { - .fb_in_psram = true, // allocate frame buffer in PSRAM - } - }; - - ESP_LOGI(TAG, "Initialize RGB LCD panel"); - - gc9503_vendor_config_t vendor_config = { - .rgb_config = &rgb_config, - .flags = { - .mirror_by_cmd = 0, - .auto_del_panel_io = 1, - }, - }; - const esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = -1, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - .vendor_config = &vendor_config, - }; - (esp_lcd_new_panel_gc9503(panel_io, &panel_config, &panel_handle)); - (esp_lcd_panel_reset(panel_handle)); - (esp_lcd_panel_init(panel_handle)); - - display_ = new RgbLcdDisplay(panel_io, panel_handle, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, - DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - boot_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - } - -public: - Yuying_313lcd() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeButtons(); - InitializeRGB_GC9503V_Display(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(Yuying_313lcd); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "pin_config.h" + +#include "config.h" + +#include +#include +#include +#include "esp_lcd_gc9503.h" +#include +#include +#include + +#define TAG "Yuying_313lcd" + +class Yuying_313lcd : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + + void InitializeRGB_GC9503V_Display() { + ESP_LOGI(TAG, "Init GC9503V"); + + esp_lcd_panel_io_handle_t panel_io = nullptr; + + ESP_LOGI(TAG, "Install 3-wire SPI panel IO"); + spi_line_config_t line_config = { + .cs_io_type = IO_TYPE_GPIO, + .cs_gpio_num = GC9503V_LCD_IO_SPI_CS_1, + .scl_io_type = IO_TYPE_GPIO, + .scl_gpio_num = GC9503V_LCD_IO_SPI_SCL_1, + .sda_io_type = IO_TYPE_GPIO, + .sda_gpio_num = GC9503V_LCD_IO_SPI_SDO_1, + .io_expander = NULL, + }; + esp_lcd_panel_io_3wire_spi_config_t io_config = GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0); + (esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io)); + + ESP_LOGI(TAG, "Install RGB LCD panel driver"); + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_rgb_panel_config_t rgb_config = { + .clk_src = LCD_CLK_SRC_PLL160M, + .timings = GC9503_376_960_PANEL_60HZ_RGB_TIMING(), + .data_width = 16, // RGB565 in parallel mode, thus 16bit in width + .bits_per_pixel = 16, + .num_fbs = GC9503V_LCD_RGB_BUFFER_NUMS, + .bounce_buffer_size_px = GC9503V_LCD_H_RES * GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT, + .dma_burst_size = 64, + .hsync_gpio_num = GC9503V_PIN_NUM_HSYNC, + .vsync_gpio_num = GC9503V_PIN_NUM_VSYNC, + .de_gpio_num = GC9503V_PIN_NUM_DE, + .pclk_gpio_num = GC9503V_PIN_NUM_PCLK, + .disp_gpio_num = GC9503V_PIN_NUM_DISP_EN, + .data_gpio_nums = { + GC9503V_PIN_NUM_DATA0, + GC9503V_PIN_NUM_DATA1, + GC9503V_PIN_NUM_DATA2, + GC9503V_PIN_NUM_DATA3, + GC9503V_PIN_NUM_DATA4, + GC9503V_PIN_NUM_DATA5, + GC9503V_PIN_NUM_DATA6, + GC9503V_PIN_NUM_DATA7, + GC9503V_PIN_NUM_DATA8, + GC9503V_PIN_NUM_DATA9, + GC9503V_PIN_NUM_DATA10, + GC9503V_PIN_NUM_DATA11, + GC9503V_PIN_NUM_DATA12, + GC9503V_PIN_NUM_DATA13, + GC9503V_PIN_NUM_DATA14, + GC9503V_PIN_NUM_DATA15, + }, + .flags= { + .fb_in_psram = true, // allocate frame buffer in PSRAM + } + }; + + ESP_LOGI(TAG, "Initialize RGB LCD panel"); + + gc9503_vendor_config_t vendor_config = { + .rgb_config = &rgb_config, + .flags = { + .mirror_by_cmd = 0, + .auto_del_panel_io = 1, + }, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = -1, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + .vendor_config = &vendor_config, + }; + (esp_lcd_new_panel_gc9503(panel_io, &panel_config, &panel_handle)); + (esp_lcd_panel_reset(panel_handle)); + (esp_lcd_panel_init(panel_handle)); + + display_ = new RgbLcdDisplay(panel_io, panel_handle, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + boot_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + } + +public: + Yuying_313lcd() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeButtons(); + InitializeRGB_GC9503V_Display(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(Yuying_313lcd); diff --git a/main/boards/kevin-yuying-313lcd/pin_config.h b/main/boards/kevin-yuying-313lcd/pin_config.h index 0bcd059..3b5b651 100644 --- a/main/boards/kevin-yuying-313lcd/pin_config.h +++ b/main/boards/kevin-yuying-313lcd/pin_config.h @@ -1,47 +1,47 @@ - -#pragma once -#define GC9503V_LCD_H_RES 376 -#define GC9503V_LCD_V_RES 960 - - -#define GC9503V_LCD_LVGL_DIRECT_MODE (1) -#define GC9503V_LCD_LVGL_AVOID_TEAR (1) -#define GC9503V_LCD_RGB_BOUNCE_BUFFER_MODE (1) -#define GC9503V_LCD_DRAW_BUFF_DOUBLE (0) -#define GC9503V_LCD_DRAW_BUFF_HEIGHT (100) -#define GC9503V_LCD_RGB_BUFFER_NUMS (2) -#define GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT (10) - -#define GC9503V_LCD_PIXEL_CLOCK_HZ (16 * 1000 * 1000) -#define GC9503V_LCD_BK_LIGHT_ON_LEVEL 1 -#define GC9503V_LCD_BK_LIGHT_OFF_LEVEL !GC9503V_LCD_BK_LIGHT_ON_LEVEL -#define GC9503V_PIN_NUM_BK_LIGHT GPIO_NUM_4 -#define GC9503V_PIN_NUM_HSYNC 6 -#define GC9503V_PIN_NUM_VSYNC 5 -#define GC9503V_PIN_NUM_DE 15 -#define GC9503V_PIN_NUM_PCLK 7 - -#define GC9503V_PIN_NUM_DATA0 47 // B0 -#define GC9503V_PIN_NUM_DATA1 21 // B1 -#define GC9503V_PIN_NUM_DATA2 14 // B2 -#define GC9503V_PIN_NUM_DATA3 13 // B3 -#define GC9503V_PIN_NUM_DATA4 12 // B4 - -#define GC9503V_PIN_NUM_DATA5 11 // G0 -#define GC9503V_PIN_NUM_DATA6 10 // G1 -#define GC9503V_PIN_NUM_DATA7 9 // G2 -#define GC9503V_PIN_NUM_DATA8 46 // G3 -#define GC9503V_PIN_NUM_DATA9 3 // G4 -#define GC9503V_PIN_NUM_DATA10 20 // G5 - -#define GC9503V_PIN_NUM_DATA11 19 // R0 -#define GC9503V_PIN_NUM_DATA12 8 // R1 -#define GC9503V_PIN_NUM_DATA13 18 // R2 -#define GC9503V_PIN_NUM_DATA14 17 // R3 -#define GC9503V_PIN_NUM_DATA15 16 // R4 - -#define GC9503V_PIN_NUM_DISP_EN -1 - -#define GC9503V_LCD_IO_SPI_CS_1 (GPIO_NUM_48) -#define GC9503V_LCD_IO_SPI_SCL_1 (GPIO_NUM_17) + +#pragma once +#define GC9503V_LCD_H_RES 376 +#define GC9503V_LCD_V_RES 960 + + +#define GC9503V_LCD_LVGL_DIRECT_MODE (1) +#define GC9503V_LCD_LVGL_AVOID_TEAR (1) +#define GC9503V_LCD_RGB_BOUNCE_BUFFER_MODE (1) +#define GC9503V_LCD_DRAW_BUFF_DOUBLE (0) +#define GC9503V_LCD_DRAW_BUFF_HEIGHT (100) +#define GC9503V_LCD_RGB_BUFFER_NUMS (2) +#define GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT (10) + +#define GC9503V_LCD_PIXEL_CLOCK_HZ (16 * 1000 * 1000) +#define GC9503V_LCD_BK_LIGHT_ON_LEVEL 1 +#define GC9503V_LCD_BK_LIGHT_OFF_LEVEL !GC9503V_LCD_BK_LIGHT_ON_LEVEL +#define GC9503V_PIN_NUM_BK_LIGHT GPIO_NUM_4 +#define GC9503V_PIN_NUM_HSYNC 6 +#define GC9503V_PIN_NUM_VSYNC 5 +#define GC9503V_PIN_NUM_DE 15 +#define GC9503V_PIN_NUM_PCLK 7 + +#define GC9503V_PIN_NUM_DATA0 47 // B0 +#define GC9503V_PIN_NUM_DATA1 21 // B1 +#define GC9503V_PIN_NUM_DATA2 14 // B2 +#define GC9503V_PIN_NUM_DATA3 13 // B3 +#define GC9503V_PIN_NUM_DATA4 12 // B4 + +#define GC9503V_PIN_NUM_DATA5 11 // G0 +#define GC9503V_PIN_NUM_DATA6 10 // G1 +#define GC9503V_PIN_NUM_DATA7 9 // G2 +#define GC9503V_PIN_NUM_DATA8 46 // G3 +#define GC9503V_PIN_NUM_DATA9 3 // G4 +#define GC9503V_PIN_NUM_DATA10 20 // G5 + +#define GC9503V_PIN_NUM_DATA11 19 // R0 +#define GC9503V_PIN_NUM_DATA12 8 // R1 +#define GC9503V_PIN_NUM_DATA13 18 // R2 +#define GC9503V_PIN_NUM_DATA14 17 // R3 +#define GC9503V_PIN_NUM_DATA15 16 // R4 + +#define GC9503V_PIN_NUM_DISP_EN -1 + +#define GC9503V_LCD_IO_SPI_CS_1 (GPIO_NUM_48) +#define GC9503V_LCD_IO_SPI_SCL_1 (GPIO_NUM_17) #define GC9503V_LCD_IO_SPI_SDO_1 (GPIO_NUM_16) \ No newline at end of file diff --git a/main/boards/labplus-ledong-v2/config.h b/main/boards/labplus-ledong-v2/config.h index 79f9d4f..aedd52e 100644 --- a/main/boards/labplus-ledong-v2/config.h +++ b/main/boards/labplus-ledong-v2/config.h @@ -1,44 +1,44 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_39 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_42 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_38 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_44 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_43 -#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define BUILTIN_LED_GPIO GPIO_NUM_1 - -#define LCD_SCLK_PIN GPIO_NUM_36 -#define LCD_MOSI_PIN GPIO_NUM_37 -#define LCD_MISO_PIN GPIO_NUM_NC -#define LCD_DC_PIN GPIO_NUM_35 -#define LCD_CS_PIN GPIO_NUM_NC - -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 172 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY true - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 34 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_34 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ - + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_39 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_42 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_38 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_44 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_43 +#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define BUILTIN_LED_GPIO GPIO_NUM_1 + +#define LCD_SCLK_PIN GPIO_NUM_36 +#define LCD_MOSI_PIN GPIO_NUM_37 +#define LCD_MISO_PIN GPIO_NUM_NC +#define LCD_DC_PIN GPIO_NUM_35 +#define LCD_CS_PIN GPIO_NUM_NC + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 172 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 34 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_34 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ + diff --git a/main/boards/labplus-ledong-v2/config.json b/main/boards/labplus-ledong-v2/config.json index 8df7030..7c18032 100644 --- a/main/boards/labplus-ledong-v2/config.json +++ b/main/boards/labplus-ledong-v2/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "labplus-ledong-v2", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "labplus-ledong-v2", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc b/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc index e1f7d88..64b33b7 100644 --- a/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc +++ b/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc @@ -1,163 +1,163 @@ -#include "wifi_board.h" -#include "codecs/es8388_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" -#include "led/single_led.h" - -#include -#include -#include -#include -#include - -#define TAG "labplus_ledong_v2" - -#define BOARD_STM8_ADDR 17 -#define BOARD_STM8_CMD 4 - -class labplus_ledong_v2 : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - // Initialize spi peripheral - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = LCD_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = LCD_SCLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - boot_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - } - - void InitializeJd9853Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - ESP_LOGD(TAG, "Install panel IO"); - // 液晶屏控制IO初始化 - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = LCD_CS_PIN; - io_config.dc_gpio_num = LCD_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 20 * 1000 * 1000; - io_config.trans_queue_depth = 7; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io); - - // 初始化液晶屏驱动芯片JD9853,使用ST7789驱动,时序和复位有调整。 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG, - esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel); - - #if CONFIG_BOARD_TYPE_LABPLUS_LEDONG_V2 - ESP_LOGI(TAG, "Reset LCD."); - i2c_device_config_t dev_cfg = { - .dev_addr_length = I2C_ADDR_BIT_LEN_7, - .device_address = BOARD_STM8_ADDR, - .scl_speed_hz = 400000, - }; - - i2c_master_dev_handle_t dev_handle; - i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &dev_handle); - - uint8_t reg = BOARD_STM8_CMD; - i2c_master_transmit(dev_handle, ®, 1, -1); - - i2c_master_bus_rm_device(dev_handle); - #endif - - esp_lcd_panel_reset(panel); - vTaskDelay(pdMS_TO_TICKS(100)); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - labplus_ledong_v2() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeSpi(); - InitializeJd9853Display(); - InitializeButtons(); - GetBacklight()->SetBrightness(100); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8388AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8388_ADDR - ); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(labplus_ledong_v2); +#include "wifi_board.h" +#include "codecs/es8388_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "led/single_led.h" + +#include +#include +#include +#include +#include + +#define TAG "labplus_ledong_v2" + +#define BOARD_STM8_ADDR 17 +#define BOARD_STM8_CMD 4 + +class labplus_ledong_v2 : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + // Initialize spi peripheral + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = LCD_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = LCD_SCLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + boot_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + } + + void InitializeJd9853Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + ESP_LOGD(TAG, "Install panel IO"); + // 液晶屏控制IO初始化 + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = LCD_CS_PIN; + io_config.dc_gpio_num = LCD_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 20 * 1000 * 1000; + io_config.trans_queue_depth = 7; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io); + + // 初始化液晶屏驱动芯片JD9853,使用ST7789驱动,时序和复位有调整。 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG, + esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel); + + #if CONFIG_BOARD_TYPE_LABPLUS_LEDONG_V2 + ESP_LOGI(TAG, "Reset LCD."); + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = BOARD_STM8_ADDR, + .scl_speed_hz = 400000, + }; + + i2c_master_dev_handle_t dev_handle; + i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &dev_handle); + + uint8_t reg = BOARD_STM8_CMD; + i2c_master_transmit(dev_handle, ®, 1, -1); + + i2c_master_bus_rm_device(dev_handle); + #endif + + esp_lcd_panel_reset(panel); + vTaskDelay(pdMS_TO_TICKS(100)); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + labplus_ledong_v2() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeJd9853Display(); + InitializeButtons(); + GetBacklight()->SetBrightness(100); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8388AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8388_ADDR + ); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(labplus_ledong_v2); diff --git a/main/boards/labplus-mpython-v3/config.h b/main/boards/labplus-mpython-v3/config.h index e6a8484..f9a0507 100644 --- a/main/boards/labplus-mpython-v3/config.h +++ b/main/boards/labplus-mpython-v3/config.h @@ -1,44 +1,44 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_39 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_42 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_38 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_44 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_43 -#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define BUILTIN_LED_GPIO GPIO_NUM_1 - -#define LCD_SCLK_PIN GPIO_NUM_36 -#define LCD_MOSI_PIN GPIO_NUM_37 -#define LCD_MISO_PIN GPIO_NUM_NC -#define LCD_DC_PIN GPIO_NUM_35 -#define LCD_CS_PIN GPIO_NUM_34 - -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 172 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY true - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 34 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_33 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ - + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_39 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_42 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_38 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_44 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_43 +#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define BUILTIN_LED_GPIO GPIO_NUM_1 + +#define LCD_SCLK_PIN GPIO_NUM_36 +#define LCD_MOSI_PIN GPIO_NUM_37 +#define LCD_MISO_PIN GPIO_NUM_NC +#define LCD_DC_PIN GPIO_NUM_35 +#define LCD_CS_PIN GPIO_NUM_34 + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 172 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 34 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_33 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ + diff --git a/main/boards/labplus-mpython-v3/config.json b/main/boards/labplus-mpython-v3/config.json index b5bd0ee..0c7c6bc 100644 --- a/main/boards/labplus-mpython-v3/config.json +++ b/main/boards/labplus-mpython-v3/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "labplus-mpython-v3", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "labplus-mpython-v3", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/labplus-mpython-v3/mpython_pro.cc b/main/boards/labplus-mpython-v3/mpython_pro.cc index 3b9d149..2f91e80 100644 --- a/main/boards/labplus-mpython-v3/mpython_pro.cc +++ b/main/boards/labplus-mpython-v3/mpython_pro.cc @@ -1,143 +1,143 @@ -#include "wifi_board.h" -#include "codecs/es8388_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" -#include "led/single_led.h" - -#include -#include -#include -#include -#include - -#define TAG "mpython_v3" - -class mpython_v3 : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - // Initialize spi peripheral - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = LCD_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = LCD_SCLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - boot_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - } - - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - ESP_LOGD(TAG, "Install panel IO"); - // 液晶屏控制IO初始化 - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = LCD_CS_PIN; - io_config.dc_gpio_num = LCD_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 20 * 1000 * 1000; - io_config.trans_queue_depth = 7; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io); - - // 初始化液晶屏驱动芯片JD9853,使用ST7789驱动,时序有调整。 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG, - esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel); - - esp_lcd_panel_reset(panel); - vTaskDelay(pdMS_TO_TICKS(100)); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - mpython_v3() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeSpi(); - InitializeSt7789Display(); - InitializeButtons(); - GetBacklight()->SetBrightness(100); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8388AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8388_ADDR - ); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(mpython_v3); +#include "wifi_board.h" +#include "codecs/es8388_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "led/single_led.h" + +#include +#include +#include +#include +#include + +#define TAG "mpython_v3" + +class mpython_v3 : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + // Initialize spi peripheral + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = LCD_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = LCD_SCLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + boot_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + ESP_LOGD(TAG, "Install panel IO"); + // 液晶屏控制IO初始化 + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = LCD_CS_PIN; + io_config.dc_gpio_num = LCD_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 20 * 1000 * 1000; + io_config.trans_queue_depth = 7; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io); + + // 初始化液晶屏驱动芯片JD9853,使用ST7789驱动,时序有调整。 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG, + esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel); + + esp_lcd_panel_reset(panel); + vTaskDelay(pdMS_TO_TICKS(100)); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + mpython_v3() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeSt7789Display(); + InitializeButtons(); + GetBacklight()->SetBrightness(100); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8388AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8388_ADDR + ); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(mpython_v3); diff --git a/main/boards/lichuang-c3-dev/README.md b/main/boards/lichuang-c3-dev/README.md index 1e37dd1..dae1e92 100644 --- a/main/boards/lichuang-c3-dev/README.md +++ b/main/boards/lichuang-c3-dev/README.md @@ -1,11 +1,11 @@ -## 立创·实战派ESP32-C3开发板 - -1、开发板资料:https://wiki.lckfb.com/zh-hans/szpi-esp32c3 - -2、该开发板 flash 大小为 8MB,编译时注意选择合适的分区表: - -``` -Partition Table ---> - Partition Table (Custom partition table CSV) ---> - (partitions/v1/8m.csv) Custom partition CSV file -``` +## 立创·实战派ESP32-C3开发板 + +1、开发板资料:https://wiki.lckfb.com/zh-hans/szpi-esp32c3 + +2、该开发板 flash 大小为 8MB,编译时注意选择合适的分区表: + +``` +Partition Table ---> + Partition Table (Custom partition table CSV) ---> + (partitions/v2/8m.csv) Custom partition CSV file +``` diff --git a/main/boards/lichuang-c3-dev/config.h b/main/boards/lichuang-c3-dev/config.h index 548ccb4..db777d1 100644 --- a/main/boards/lichuang-c3-dev/config.h +++ b/main/boards/lichuang-c3-dev/config.h @@ -1,45 +1,45 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 - -#define AUDIO_CODEC_USE_PCA9557 -#define AUDIO_CODEC_PA_PIN GPIO_NUM_13 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR 0x82 - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_9 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SPI_SCK_PIN GPIO_NUM_3 -#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5 -#define DISPLAY_DC_PIN GPIO_NUM_6 -#define DISPLAY_SPI_CS_PIN GPIO_NUM_4 - -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY true - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 + +#define AUDIO_CODEC_USE_PCA9557 +#define AUDIO_CODEC_PA_PIN GPIO_NUM_13 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR 0x82 + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_9 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SPI_SCK_PIN GPIO_NUM_3 +#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5 +#define DISPLAY_DC_PIN GPIO_NUM_6 +#define DISPLAY_SPI_CS_PIN GPIO_NUM_4 + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/lichuang-c3-dev/config.json b/main/boards/lichuang-c3-dev/config.json index 6000c33..ed5af40 100644 --- a/main/boards/lichuang-c3-dev/config.json +++ b/main/boards/lichuang-c3-dev/config.json @@ -1,14 +1,14 @@ -{ - "target": "esp32c3", - "builds": [ - { - "name": "lichuang-c3-dev", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"", - "CONFIG_USE_ESP_WAKE_WORD=y", - "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y" - ] - } - ] +{ + "target": "esp32c3", + "builds": [ + { + "name": "lichuang-c3-dev", + "sdkconfig_append": [ + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"", + "CONFIG_USE_ESP_WAKE_WORD=y", + "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc b/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc index 988246d..30dc805 100644 --- a/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc +++ b/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc @@ -1,129 +1,129 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" - -#include -#include -#include -#include -#include - -#define TAG "LichuangC3DevBoard" - -class LichuangC3DevBoard : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 2; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, true); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - LichuangC3DevBoard() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeSpi(); - InitializeSt7789Display(); - InitializeButtons(); - GetBacklight()->SetBrightness(100); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec( - codec_i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(LichuangC3DevBoard); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" + +#include +#include +#include +#include +#include + +#define TAG "LichuangC3DevBoard" + +class LichuangC3DevBoard : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 2; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + LichuangC3DevBoard() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeSt7789Display(); + InitializeButtons(); + GetBacklight()->SetBrightness(100); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec( + codec_i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(LichuangC3DevBoard); diff --git a/main/boards/lichuang-dev/config.h b/main/boards/lichuang-dev/config.h index 32c12a0..c34c0b5 100644 --- a/main/boards/lichuang-dev/config.h +++ b/main/boards/lichuang-dev/config.h @@ -1,62 +1,62 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_13 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45 - -#define AUDIO_CODEC_USE_PCA9557 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR 0x82 - -#define BUILTIN_LED_GPIO GPIO_NUM_48 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY true - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - -/* Camera pins */ -#define CAMERA_PIN_PWDN -1 -#define CAMERA_PIN_RESET -1 -#define CAMERA_PIN_XCLK 5 -#define CAMERA_PIN_SIOD 1 -#define CAMERA_PIN_SIOC 2 - -#define CAMERA_PIN_D7 9 -#define CAMERA_PIN_D6 4 -#define CAMERA_PIN_D5 6 -#define CAMERA_PIN_D4 15 -#define CAMERA_PIN_D3 17 -#define CAMERA_PIN_D2 8 -#define CAMERA_PIN_D1 18 -#define CAMERA_PIN_D0 16 -#define CAMERA_PIN_VSYNC 3 -#define CAMERA_PIN_HREF 46 -#define CAMERA_PIN_PCLK 7 - -#define XCLK_FREQ_HZ 24000000 - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_13 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45 + +#define AUDIO_CODEC_USE_PCA9557 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR 0x82 + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +/* Camera pins */ +#define CAMERA_PIN_PWDN -1 +#define CAMERA_PIN_RESET -1 +#define CAMERA_PIN_XCLK 5 +#define CAMERA_PIN_SIOD 1 +#define CAMERA_PIN_SIOC 2 + +#define CAMERA_PIN_D7 9 +#define CAMERA_PIN_D6 4 +#define CAMERA_PIN_D5 6 +#define CAMERA_PIN_D4 15 +#define CAMERA_PIN_D3 17 +#define CAMERA_PIN_D2 8 +#define CAMERA_PIN_D1 18 +#define CAMERA_PIN_D0 16 +#define CAMERA_PIN_VSYNC 3 +#define CAMERA_PIN_HREF 46 +#define CAMERA_PIN_PCLK 7 + +#define XCLK_FREQ_HZ 24000000 + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/lichuang-dev/config.json b/main/boards/lichuang-dev/config.json index 32eedc4..b3b13a7 100644 --- a/main/boards/lichuang-dev/config.json +++ b/main/boards/lichuang-dev/config.json @@ -1,12 +1,12 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "lichuang-dev", - "sdkconfig_append": [ - "CONFIG_USE_DEVICE_AEC=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "lichuang-dev", + "sdkconfig_append": [ + "CONFIG_USE_DEVICE_AEC=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/lichuang-dev/lichuang_dev_board.cc b/main/boards/lichuang-dev/lichuang_dev_board.cc index adfb417..9c6bdb9 100644 --- a/main/boards/lichuang-dev/lichuang_dev_board.cc +++ b/main/boards/lichuang-dev/lichuang_dev_board.cc @@ -1,262 +1,262 @@ -#include "wifi_board.h" -#include "codecs/box_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" -#include "esp32_camera.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#define TAG "LichuangDevBoard" - -class Pca9557 : public I2cDevice { -public: - Pca9557(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - WriteReg(0x01, 0x03); - WriteReg(0x03, 0xf8); - } - - void SetOutputState(uint8_t bit, uint8_t level) { - uint8_t data = ReadReg(0x01); - data = (data & ~(1 << bit)) | (level << bit); - WriteReg(0x01, data); - } -}; - -class CustomAudioCodec : public BoxAudioCodec { -private: - Pca9557* pca9557_; - -public: - CustomAudioCodec(i2c_master_bus_handle_t i2c_bus, Pca9557* pca9557) - : BoxAudioCodec(i2c_bus, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - GPIO_NUM_NC, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE), - pca9557_(pca9557) { - } - - virtual void EnableOutput(bool enable) override { - BoxAudioCodec::EnableOutput(enable); - if (enable) { - pca9557_->SetOutputState(1, 1); - } else { - pca9557_->SetOutputState(1, 0); - } - } -}; - -class LichuangDevBoard : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - i2c_master_dev_handle_t pca9557_handle_; - Button boot_button_; - LcdDisplay* display_; - Pca9557* pca9557_; - Esp32Camera* camera_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - - // Initialize PCA9557 - pca9557_ = new Pca9557(i2c_bus_, 0x19); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_40; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_41; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - -#if CONFIG_USE_DEVICE_AEC - boot_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateIdle) { - app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); - } - }); -#endif - } - - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_NC; - io_config.dc_gpio_num = GPIO_NUM_39; - io_config.spi_mode = 2; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - pca9557_->SetOutputState(0, 0); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, true); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeTouch() - { - esp_lcd_touch_handle_t tp; - esp_lcd_touch_config_t tp_cfg = { - .x_max = DISPLAY_WIDTH, - .y_max = DISPLAY_HEIGHT, - .rst_gpio_num = GPIO_NUM_NC, // Shared with LCD reset - .int_gpio_num = GPIO_NUM_NC, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = 1, - .mirror_x = 1, - .mirror_y = 0, - }, - }; - esp_lcd_panel_io_handle_t tp_io_handle = NULL; - esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG(); - tp_io_config.scl_speed_hz = 400000; - - esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle); - esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp); - assert(tp); - - /* Add touch input (for selected screen) */ - const lvgl_port_touch_cfg_t touch_cfg = { - .disp = lv_display_get_default(), - .handle = tp, - }; - - lvgl_port_add_touch(&touch_cfg); - } - - void InitializeCamera() { - // Open camera power - pca9557_->SetOutputState(2, 0); - - camera_config_t config = {}; - config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 - config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 - config.pin_d0 = CAMERA_PIN_D0; - config.pin_d1 = CAMERA_PIN_D1; - config.pin_d2 = CAMERA_PIN_D2; - config.pin_d3 = CAMERA_PIN_D3; - config.pin_d4 = CAMERA_PIN_D4; - config.pin_d5 = CAMERA_PIN_D5; - config.pin_d6 = CAMERA_PIN_D6; - config.pin_d7 = CAMERA_PIN_D7; - config.pin_xclk = CAMERA_PIN_XCLK; - config.pin_pclk = CAMERA_PIN_PCLK; - config.pin_vsync = CAMERA_PIN_VSYNC; - config.pin_href = CAMERA_PIN_HREF; - config.pin_sccb_sda = -1; // 这里写-1 表示使用已经初始化的I2C接口 - config.pin_sccb_scl = CAMERA_PIN_SIOC; - config.sccb_i2c_port = 1; - config.pin_pwdn = CAMERA_PIN_PWDN; - config.pin_reset = CAMERA_PIN_RESET; - config.xclk_freq_hz = XCLK_FREQ_HZ; - config.pixel_format = PIXFORMAT_RGB565; - config.frame_size = FRAMESIZE_VGA; - config.jpeg_quality = 12; - config.fb_count = 1; - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - camera_ = new Esp32Camera(config); - } - -public: - LichuangDevBoard() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeSpi(); - InitializeSt7789Display(); - InitializeTouch(); - InitializeButtons(); - InitializeCamera(); - - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static CustomAudioCodec audio_codec( - i2c_bus_, - pca9557_); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(LichuangDevBoard); +#include "wifi_board.h" +#include "codecs/box_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "esp32_camera.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "LichuangDevBoard" + +class Pca9557 : public I2cDevice { +public: + Pca9557(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + WriteReg(0x01, 0x03); + WriteReg(0x03, 0xf8); + } + + void SetOutputState(uint8_t bit, uint8_t level) { + uint8_t data = ReadReg(0x01); + data = (data & ~(1 << bit)) | (level << bit); + WriteReg(0x01, data); + } +}; + +class CustomAudioCodec : public BoxAudioCodec { +private: + Pca9557* pca9557_; + +public: + CustomAudioCodec(i2c_master_bus_handle_t i2c_bus, Pca9557* pca9557) + : BoxAudioCodec(i2c_bus, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + GPIO_NUM_NC, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE), + pca9557_(pca9557) { + } + + virtual void EnableOutput(bool enable) override { + BoxAudioCodec::EnableOutput(enable); + if (enable) { + pca9557_->SetOutputState(1, 1); + } else { + pca9557_->SetOutputState(1, 0); + } + } +}; + +class LichuangDevBoard : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + i2c_master_dev_handle_t pca9557_handle_; + Button boot_button_; + LcdDisplay* display_; + Pca9557* pca9557_; + Esp32Camera* camera_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + + // Initialize PCA9557 + pca9557_ = new Pca9557(i2c_bus_, 0x19); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_40; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_41; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_NC; + io_config.dc_gpio_num = GPIO_NUM_39; + io_config.spi_mode = 2; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + pca9557_->SetOutputState(0, 0); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeTouch() + { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_HEIGHT, + .y_max = DISPLAY_WIDTH, + .rst_gpio_num = GPIO_NUM_NC, // Shared with LCD reset + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 1, + .mirror_x = 1, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG(); + tp_io_config.scl_speed_hz = 400000; + + esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle); + esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp); + assert(tp); + + /* Add touch input (for selected screen) */ + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + + lvgl_port_add_touch(&touch_cfg); + } + + void InitializeCamera() { + // Open camera power + pca9557_->SetOutputState(2, 0); + + camera_config_t config = {}; + config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 + config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 + config.pin_d0 = CAMERA_PIN_D0; + config.pin_d1 = CAMERA_PIN_D1; + config.pin_d2 = CAMERA_PIN_D2; + config.pin_d3 = CAMERA_PIN_D3; + config.pin_d4 = CAMERA_PIN_D4; + config.pin_d5 = CAMERA_PIN_D5; + config.pin_d6 = CAMERA_PIN_D6; + config.pin_d7 = CAMERA_PIN_D7; + config.pin_xclk = CAMERA_PIN_XCLK; + config.pin_pclk = CAMERA_PIN_PCLK; + config.pin_vsync = CAMERA_PIN_VSYNC; + config.pin_href = CAMERA_PIN_HREF; + config.pin_sccb_sda = -1; // 这里写-1 表示使用已经初始化的I2C接口 + config.pin_sccb_scl = CAMERA_PIN_SIOC; + config.sccb_i2c_port = 1; + config.pin_pwdn = CAMERA_PIN_PWDN; + config.pin_reset = CAMERA_PIN_RESET; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_VGA; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + camera_ = new Esp32Camera(config); + } + +public: + LichuangDevBoard() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeSt7789Display(); + InitializeTouch(); + InitializeButtons(); + InitializeCamera(); + + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static CustomAudioCodec audio_codec( + i2c_bus_, + pca9557_); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(LichuangDevBoard); diff --git a/main/boards/lilygo-t-cameraplus-s3/config.h b/main/boards/lilygo-t-cameraplus-s3/config.h index 4d4b1de..75c0a6e 100644 --- a/main/boards/lilygo-t-cameraplus-s3/config.h +++ b/main/boards/lilygo-t-cameraplus-s3/config.h @@ -1,57 +1,57 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include "pin_config.h" - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1 -#define AUDIO_INPUT_REFERENCE true -#define AUDIO_MIC_I2S_GPIO_BCLK static_cast(MSM261_BCLK) -#define AUDIO_MIC_I2S_GPIO_WS static_cast(MSM261_WS) -#define AUDIO_MIC_I2S_GPIO_DATA static_cast(MSM261_DATA) -#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 -#define AUDIO_INPUT_REFERENCE false -#define AUDIO_MIC_I2S_GPIO_BCLK GPIO_NUM_NC -#define AUDIO_MIC_I2S_GPIO_WS static_cast(MP34DT05TR_LRCLK) -#define AUDIO_MIC_I2S_GPIO_DATA static_cast(MP34DT05TR_DATA) - -#define AUDIO_MIC_SPKR_EN static_cast(MP34DT05TR_MAX98357_EN) -#endif - -#define AUDIO_SPKR_I2S_GPIO_BCLK static_cast(MAX98357A_BCLK) -#define AUDIO_SPKR_I2S_GPIO_LRCLK static_cast(MAX98357A_LRCLK) -#define AUDIO_SPKR_I2S_GPIO_DATA static_cast(MAX98357A_DATA) - -#define TOUCH_I2C_SDA_PIN static_cast(TP_SDA) -#define TOUCH_I2C_SCL_PIN static_cast(TP_SCL) - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define KEY1_BUTTON_GPIO static_cast(KEY1) -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_WIDTH LCD_WIDTH -#define DISPLAY_HEIGHT LCD_HEIGHT -#define DISPLAY_MOSI LCD_MOSI -#define DISPLAY_SCLK LCD_SCLK -#define DISPLAY_DC LCD_DC -#define DISPLAY_RST LCD_RST -#define DISPLAY_CS LCD_CS -#define DISPLAY_BL static_cast(LCD_BL) -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN DISPLAY_BL -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define AP1511B_GPIO static_cast(AP1511B_FBC) - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include "pin_config.h" + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1 +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_MIC_I2S_GPIO_BCLK static_cast(MSM261_BCLK) +#define AUDIO_MIC_I2S_GPIO_WS static_cast(MSM261_WS) +#define AUDIO_MIC_I2S_GPIO_DATA static_cast(MSM261_DATA) +#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 +#define AUDIO_INPUT_REFERENCE false +#define AUDIO_MIC_I2S_GPIO_BCLK GPIO_NUM_NC +#define AUDIO_MIC_I2S_GPIO_WS static_cast(MP34DT05TR_LRCLK) +#define AUDIO_MIC_I2S_GPIO_DATA static_cast(MP34DT05TR_DATA) + +#define AUDIO_MIC_SPKR_EN static_cast(MP34DT05TR_MAX98357_EN) +#endif + +#define AUDIO_SPKR_I2S_GPIO_BCLK static_cast(MAX98357A_BCLK) +#define AUDIO_SPKR_I2S_GPIO_LRCLK static_cast(MAX98357A_LRCLK) +#define AUDIO_SPKR_I2S_GPIO_DATA static_cast(MAX98357A_DATA) + +#define TOUCH_I2C_SDA_PIN static_cast(TP_SDA) +#define TOUCH_I2C_SCL_PIN static_cast(TP_SCL) + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define KEY1_BUTTON_GPIO static_cast(KEY1) +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH LCD_WIDTH +#define DISPLAY_HEIGHT LCD_HEIGHT +#define DISPLAY_MOSI LCD_MOSI +#define DISPLAY_SCLK LCD_SCLK +#define DISPLAY_DC LCD_DC +#define DISPLAY_RST LCD_RST +#define DISPLAY_CS LCD_CS +#define DISPLAY_BL static_cast(LCD_BL) +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN DISPLAY_BL +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define AP1511B_GPIO static_cast(AP1511B_FBC) + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/lilygo-t-cameraplus-s3/config.json b/main/boards/lilygo-t-cameraplus-s3/config.json index c08d0b0..7be906d 100644 --- a/main/boards/lilygo-t-cameraplus-s3/config.json +++ b/main/boards/lilygo-t-cameraplus-s3/config.json @@ -1,17 +1,17 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "lilygo-t-cameraplus-s3", - "sdkconfig_append": [ - "CONFIG_SPIRAM_MODE_QUAD=y" - ] - }, - { - "name": "lilygo-t-cameraplus-s3_v1_2", - "sdkconfig_append": [ - "CONFIG_SPIRAM_MODE_QUAD=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "lilygo-t-cameraplus-s3", + "sdkconfig_append": [ + "CONFIG_SPIRAM_MODE_QUAD=y" + ] + }, + { + "name": "lilygo-t-cameraplus-s3_v1_2", + "sdkconfig_append": [ + "CONFIG_SPIRAM_MODE_QUAD=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/lilygo-t-cameraplus-s3/ir_filter_controller.h b/main/boards/lilygo-t-cameraplus-s3/ir_filter_controller.h index 3ae619a..163b9ab 100644 --- a/main/boards/lilygo-t-cameraplus-s3/ir_filter_controller.h +++ b/main/boards/lilygo-t-cameraplus-s3/ir_filter_controller.h @@ -1,44 +1,44 @@ -#ifndef __IR_FILTER_CONTROLLER_H__ -#define __IR_FILTER_CONTROLLER_H__ - -#include "mcp_server.h" - - -class IrFilterController { -private: - bool enable_ = false; - gpio_num_t gpio_num_; - -public: - IrFilterController(gpio_num_t gpio_num) : gpio_num_(gpio_num) { - gpio_config_t config = { - .pin_bit_mask = (1ULL << gpio_num_), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - ESP_ERROR_CHECK(gpio_config(&config)); - gpio_set_level(gpio_num_, 0); - - auto& mcp_server = McpServer::GetInstance(); - mcp_server.AddTool("self.camera.get_ir_filter_state", "Get the state of the camera's infrared filter", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - return enable_ ? "{\"enable\": true}" : "{\"enable\": false}"; - }); - - mcp_server.AddTool("self.camera.enable_ir_filter", "Enable the camera's infrared filter", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - enable_ = true; - gpio_set_level(gpio_num_, 1); - return true; - }); - - mcp_server.AddTool("self.camera.disable_ir_filter", "Disable the camera's infrared filter", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - enable_ = false; - gpio_set_level(gpio_num_, 0); - return true; - }); - } -}; - - -#endif // __IR_FILTER_CONTROLLER_H__ +#ifndef __IR_FILTER_CONTROLLER_H__ +#define __IR_FILTER_CONTROLLER_H__ + +#include "mcp_server.h" + + +class IrFilterController { +private: + bool enable_ = false; + gpio_num_t gpio_num_; + +public: + IrFilterController(gpio_num_t gpio_num) : gpio_num_(gpio_num) { + gpio_config_t config = { + .pin_bit_mask = (1ULL << gpio_num_), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_ERROR_CHECK(gpio_config(&config)); + gpio_set_level(gpio_num_, 0); + + auto& mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.camera.get_ir_filter_state", "Get the state of the camera's infrared filter", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + return enable_ ? "{\"enable\": true}" : "{\"enable\": false}"; + }); + + mcp_server.AddTool("self.camera.enable_ir_filter", "Enable the camera's infrared filter", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + enable_ = true; + gpio_set_level(gpio_num_, 1); + return true; + }); + + mcp_server.AddTool("self.camera.disable_ir_filter", "Disable the camera's infrared filter", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + enable_ = false; + gpio_set_level(gpio_num_, 0); + return true; + }); + } +}; + + +#endif // __IR_FILTER_CONTROLLER_H__ diff --git a/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc b/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc index 7264892..79dba6c 100644 --- a/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc +++ b/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc @@ -1,338 +1,338 @@ -#include "wifi_board.h" -#include "tcamerapluss3_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "i2c_device.h" -#include "sy6970.h" -#include "pin_config.h" -#include "esp32_camera.h" -#include "ir_filter_controller.h" - -#include -#include -#include -#include - -#define TAG "LilygoTCameraPlusS3Board" - -class Cst816x : public I2cDevice { -public: - struct TouchPoint_t { - int num = 0; - int x = -1; - int y = -1; - }; - - Cst816x(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - uint8_t chip_id = ReadReg(0xA7); - ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); - read_buffer_ = new uint8_t[6]; - } - - ~Cst816x() { - delete[] read_buffer_; - } - - void UpdateTouchPoint() { - ReadRegs(0x02, read_buffer_, 6); - tp_.num = read_buffer_[0] & 0x0F; - tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; - tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; - } - - const TouchPoint_t &GetTouchPoint() { - return tp_; - } - -private: - uint8_t *read_buffer_ = nullptr; - TouchPoint_t tp_; -}; - -class Pmic : public Sy6970 { -public: - - Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Sy6970(i2c_bus, addr) { - uint8_t chip_id = ReadReg(0x14); - ESP_LOGI(TAG, "Get sy6970 chip ID: 0x%02X", (chip_id & 0B00111000)); - - WriteReg(0x00, 0B00001000); // Disable ILIM pin - WriteReg(0x02, 0B11011101); // Enable ADC measurement function - WriteReg(0x07, 0B10001101); // Disable watchdog timer feeding function - } -}; - -class LilygoTCameraPlusS3Board : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Cst816x *cst816d_; - Pmic* pmic_; - LcdDisplay *display_; - Button boot_button_; - Button key1_button_; - PowerSaveTimer* power_save_timer_; - Esp32Camera* camera_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, -1); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(10); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - pmic_->PowerOff(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitI2c(){ - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_config = { - .i2c_port = I2C_NUM_0, - .sda_io_num = TOUCH_I2C_SDA_PIN, - .scl_io_num = TOUCH_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - } - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &i2c_bus_)); - } - - void I2cDetect() { - uint8_t address; - printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); - for (int i = 0; i < 128; i += 16) { - printf("%02x: ", i); - for (int j = 0; j < 16; j++) { - fflush(stdout); - address = i + j; - esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); - if (ret == ESP_OK) { - printf("%02x ", address); - } else if (ret == ESP_ERR_TIMEOUT) { - printf("UU "); - } else { - printf("-- "); - } - } - printf("\r\n"); - } - } - - static void TouchpadDaemon(void *param) { - vTaskDelay(pdMS_TO_TICKS(2000)); - auto &board = (LilygoTCameraPlusS3Board&)Board::GetInstance(); - auto touchpad = board.GetTouchpad(); - bool was_touched = false; - while (1) { - touchpad->UpdateTouchPoint(); - if (touchpad->GetTouchPoint().num > 0){ - // On press - if (!was_touched) { - was_touched = true; - Application::GetInstance().ToggleChatState(); - } - } - // On release - else if (was_touched) { - was_touched = false; - } - vTaskDelay(pdMS_TO_TICKS(50)); - } - vTaskDelete(NULL); - } - - void InitCst816d() { - ESP_LOGI(TAG, "Init CST816x"); - cst816d_ = new Cst816x(i2c_bus_, CST816_ADDRESS); - xTaskCreate(TouchpadDaemon, "tp", 2048, NULL, 5, NULL); - } - - void InitSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCLK; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitSy6970() { - ESP_LOGI(TAG, "Init Sy6970"); - pmic_ = new Pmic(i2c_bus_, SY6970_ADDRESS); - } - - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = LCD_CS; - io_config.dc_gpio_num = LCD_DC; - io_config.spi_mode = 0; - io_config.pclk_hz = 60 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = LCD_RST; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - power_save_timer_->WakeUp(); - app.ToggleChatState(); - }); - key1_button_.OnClick([this]() { - if (camera_) { - camera_->Capture(); - } - }); - } - - void InitializeCamera() { - camera_config_t config = {}; - config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 - config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 - config.pin_d0 = Y2_GPIO_NUM; - config.pin_d1 = Y3_GPIO_NUM; - config.pin_d2 = Y4_GPIO_NUM; - config.pin_d3 = Y5_GPIO_NUM; - config.pin_d4 = Y6_GPIO_NUM; - config.pin_d5 = Y7_GPIO_NUM; - config.pin_d6 = Y8_GPIO_NUM; - config.pin_d7 = Y9_GPIO_NUM; - config.pin_xclk = XCLK_GPIO_NUM; - config.pin_pclk = PCLK_GPIO_NUM; - config.pin_vsync = VSYNC_GPIO_NUM; - config.pin_href = HREF_GPIO_NUM; -#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1 - config.pin_sccb_sda = -1; // 这里如果写-1 表示使用已经初始化的I2C接口 - config.pin_sccb_scl = SIOC_GPIO_NUM; - config.sccb_i2c_port = 0; // 这里如果写0 默认使用I2C0 -#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 - config.pin_sccb_sda = SIOD_GPIO_NUM; - config.pin_sccb_scl = SIOC_GPIO_NUM; - config.sccb_i2c_port = 1; -#endif - config.pin_pwdn = PWDN_GPIO_NUM; - config.pin_reset = RESET_GPIO_NUM; - config.xclk_freq_hz = XCLK_FREQ_HZ; - config.pixel_format = PIXFORMAT_RGB565; - config.frame_size = FRAMESIZE_240X240; - config.jpeg_quality = 12; - config.fb_count = 1; - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - camera_ = new Esp32Camera(config); - camera_->SetVFlip(1); - camera_->SetHMirror(1); - } - - void InitializeTools() { - static IrFilterController irFilter(AP1511B_GPIO); - } - -public: - LilygoTCameraPlusS3Board() : boot_button_(BOOT_BUTTON_GPIO), key1_button_(KEY1_BUTTON_GPIO) { - InitializePowerSaveTimer(); - InitI2c(); - InitSy6970(); - InitCst816d(); - I2cDetect(); - InitSpi(); - InitializeSt7789Display(); - InitializeButtons(); - InitializeCamera(); - InitializeTools(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec *GetAudioCodec() override { - static Tcamerapluss3AudioCodec audio_codec( - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_MIC_I2S_GPIO_BCLK, - AUDIO_MIC_I2S_GPIO_WS, - AUDIO_MIC_I2S_GPIO_DATA, - AUDIO_SPKR_I2S_GPIO_BCLK, - AUDIO_SPKR_I2S_GPIO_LRCLK, - AUDIO_SPKR_I2S_GPIO_DATA, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display *GetDisplay() override{ - return display_; - } - - virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = pmic_->IsCharging(); - bool is_power_good = pmic_->IsPowerGood(); - discharging = !charging && is_power_good; - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - - level = pmic_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - Cst816x *GetTouchpad() { - return cst816d_; - } - - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(LilygoTCameraPlusS3Board); +#include "wifi_board.h" +#include "tcamerapluss3_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "i2c_device.h" +#include "sy6970.h" +#include "pin_config.h" +#include "esp32_camera.h" +#include "ir_filter_controller.h" + +#include +#include +#include +#include + +#define TAG "LilygoTCameraPlusS3Board" + +class Cst816x : public I2cDevice { +public: + struct TouchPoint_t { + int num = 0; + int x = -1; + int y = -1; + }; + + Cst816x(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + uint8_t chip_id = ReadReg(0xA7); + ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); + read_buffer_ = new uint8_t[6]; + } + + ~Cst816x() { + delete[] read_buffer_; + } + + void UpdateTouchPoint() { + ReadRegs(0x02, read_buffer_, 6); + tp_.num = read_buffer_[0] & 0x0F; + tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; + tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; + } + + const TouchPoint_t &GetTouchPoint() { + return tp_; + } + +private: + uint8_t *read_buffer_ = nullptr; + TouchPoint_t tp_; +}; + +class Pmic : public Sy6970 { +public: + + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Sy6970(i2c_bus, addr) { + uint8_t chip_id = ReadReg(0x14); + ESP_LOGI(TAG, "Get sy6970 chip ID: 0x%02X", (chip_id & 0B00111000)); + + WriteReg(0x00, 0B00001000); // Disable ILIM pin + WriteReg(0x02, 0B11011101); // Enable ADC measurement function + WriteReg(0x07, 0B10001101); // Disable watchdog timer feeding function + } +}; + +class LilygoTCameraPlusS3Board : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Cst816x *cst816d_; + Pmic* pmic_; + LcdDisplay *display_; + Button boot_button_; + Button key1_button_; + PowerSaveTimer* power_save_timer_; + Esp32Camera* camera_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, -1); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(10); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + pmic_->PowerOff(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitI2c(){ + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_config = { + .i2c_port = I2C_NUM_0, + .sda_io_num = TOUCH_I2C_SDA_PIN, + .scl_io_num = TOUCH_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + } + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &i2c_bus_)); + } + + void I2cDetect() { + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK) { + printf("%02x ", address); + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + } + + static void TouchpadDaemon(void *param) { + vTaskDelay(pdMS_TO_TICKS(2000)); + auto &board = (LilygoTCameraPlusS3Board&)Board::GetInstance(); + auto touchpad = board.GetTouchpad(); + bool was_touched = false; + while (1) { + touchpad->UpdateTouchPoint(); + if (touchpad->GetTouchPoint().num > 0){ + // On press + if (!was_touched) { + was_touched = true; + Application::GetInstance().ToggleChatState(); + } + } + // On release + else if (was_touched) { + was_touched = false; + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + vTaskDelete(NULL); + } + + void InitCst816d() { + ESP_LOGI(TAG, "Init CST816x"); + cst816d_ = new Cst816x(i2c_bus_, CST816_ADDRESS); + xTaskCreate(TouchpadDaemon, "tp", 2048, NULL, 5, NULL); + } + + void InitSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCLK; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitSy6970() { + ESP_LOGI(TAG, "Init Sy6970"); + pmic_ = new Pmic(i2c_bus_, SY6970_ADDRESS); + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = LCD_CS; + io_config.dc_gpio_num = LCD_DC; + io_config.spi_mode = 0; + io_config.pclk_hz = 60 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = LCD_RST; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + power_save_timer_->WakeUp(); + app.ToggleChatState(); + }); + key1_button_.OnClick([this]() { + if (camera_) { + camera_->Capture(); + } + }); + } + + void InitializeCamera() { + camera_config_t config = {}; + config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 + config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 + config.pin_d0 = Y2_GPIO_NUM; + config.pin_d1 = Y3_GPIO_NUM; + config.pin_d2 = Y4_GPIO_NUM; + config.pin_d3 = Y5_GPIO_NUM; + config.pin_d4 = Y6_GPIO_NUM; + config.pin_d5 = Y7_GPIO_NUM; + config.pin_d6 = Y8_GPIO_NUM; + config.pin_d7 = Y9_GPIO_NUM; + config.pin_xclk = XCLK_GPIO_NUM; + config.pin_pclk = PCLK_GPIO_NUM; + config.pin_vsync = VSYNC_GPIO_NUM; + config.pin_href = HREF_GPIO_NUM; +#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1 + config.pin_sccb_sda = -1; // 这里如果写-1 表示使用已经初始化的I2C接口 + config.pin_sccb_scl = SIOC_GPIO_NUM; + config.sccb_i2c_port = 0; // 这里如果写0 默认使用I2C0 +#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 + config.pin_sccb_sda = SIOD_GPIO_NUM; + config.pin_sccb_scl = SIOC_GPIO_NUM; + config.sccb_i2c_port = 1; +#endif + config.pin_pwdn = PWDN_GPIO_NUM; + config.pin_reset = RESET_GPIO_NUM; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_240X240; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + camera_ = new Esp32Camera(config); + camera_->SetVFlip(1); + camera_->SetHMirror(1); + } + + void InitializeTools() { + static IrFilterController irFilter(AP1511B_GPIO); + } + +public: + LilygoTCameraPlusS3Board() : boot_button_(BOOT_BUTTON_GPIO), key1_button_(KEY1_BUTTON_GPIO) { + InitializePowerSaveTimer(); + InitI2c(); + InitSy6970(); + InitCst816d(); + I2cDetect(); + InitSpi(); + InitializeSt7789Display(); + InitializeButtons(); + InitializeCamera(); + InitializeTools(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec *GetAudioCodec() override { + static Tcamerapluss3AudioCodec audio_codec( + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_MIC_I2S_GPIO_BCLK, + AUDIO_MIC_I2S_GPIO_WS, + AUDIO_MIC_I2S_GPIO_DATA, + AUDIO_SPKR_I2S_GPIO_BCLK, + AUDIO_SPKR_I2S_GPIO_LRCLK, + AUDIO_SPKR_I2S_GPIO_DATA, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display *GetDisplay() override{ + return display_; + } + + virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = pmic_->IsCharging(); + bool is_power_good = pmic_->IsPowerGood(); + discharging = !charging && is_power_good; + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + + level = pmic_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + Cst816x *GetTouchpad() { + return cst816d_; + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(LilygoTCameraPlusS3Board); diff --git a/main/boards/lilygo-t-cameraplus-s3/pin_config.h b/main/boards/lilygo-t-cameraplus-s3/pin_config.h index db82d2b..0066aa2 100644 --- a/main/boards/lilygo-t-cameraplus-s3/pin_config.h +++ b/main/boards/lilygo-t-cameraplus-s3/pin_config.h @@ -1,154 +1,154 @@ -/* - * @Description: None - * @Author: LILYGO_L - * @Date: 2024-11-11 11:36:49 - * @LastEditTime: 2025-06-03 17:37:08 - * @License: GPL 3.0 - */ -#pragma once - -#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1 -#define T_CameraPlus_S3_V1_0_V1_1 -#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 -#define T_CameraPlus_S3_V1_2 -#endif - -#ifdef T_CameraPlus_S3_V1_0_V1_1 - -// SPI -#define SPI_SCLK 36 -#define SPI_MOSI 35 -#define SPI_MISO 37 - -// IIC -#define IIC_SDA 1 -#define IIC_SCL 2 - -// MSM261 -#define MSM261_BCLK 18 -#define MSM261_WS 39 -#define MSM261_DATA 40 - -// MAX98357A -#define MAX98357A_DATA 38 - -// FP-133H01D -#define LCD_CS 34 -#define LCD_RST 33 - -// OV2640 -#define OV2640_PWDN -1 -#define OV2640_RESET 3 -#define OV2640_VSYNC 4 - -// CST816 -#define TP_RST 48 - -// SY6970 -#define SY6970_INT 47 - -#endif - -#ifdef T_CameraPlus_S3_V1_2 - -// SPI -#define SPI_SCLK 35 -#define SPI_MOSI 34 -#define SPI_MISO 48 - -// IIC -#define IIC_SDA 33 -#define IIC_SCL 37 - -// MP34DT05TR -#define MP34DT05TR_LRCLK 40 -#define MP34DT05TR_DATA 38 - -#define MP34DT05TR_MAX98357_EN 18 - -// MAX98357A -#define MAX98357A_DATA 39 - -// FP-133H01D -#define LCD_CS 36 -#define LCD_RST -1 - -// OV2640 -#define OV2640_PWDN 4 -#define OV2640_RESET -1 -#define OV2640_VSYNC 3 - -// CST816 -#define TP_RST -1 - -#endif - -// SD -#define SD_CS 21 -#define SD_SCLK SPI_SCLK -#define SD_MOSI SPI_MOSI -#define SD_MISO SPI_MISO - -// MAX98357A -#define MAX98357A_BCLK 41 -#define MAX98357A_LRCLK 42 - -// FP-133H01D -#define LCD_WIDTH 240 -#define LCD_HEIGHT 240 -#define LCD_BL 46 -#define LCD_MOSI SPI_MOSI -#define LCD_SCLK SPI_SCLK -#define LCD_DC 45 - -// SY6970 -#define SY6970_SDA IIC_SDA -#define SY6970_SCL IIC_SCL -#define SY6970_ADDRESS 0x6A - -// OV2640 -#define OV2640_XCLK 7 -#define OV2640_SDA 1 -#define OV2640_SCL 2 -#define OV2640_D9 6 -#define OV2640_D8 8 -#define OV2640_D7 9 -#define OV2640_D6 11 -#define OV2640_D5 13 -#define OV2640_D4 15 -#define OV2640_D3 14 -#define OV2640_D2 12 -#define OV2640_HREF 5 -#define OV2640_PCLK 10 - -#define PWDN_GPIO_NUM OV2640_PWDN -#define RESET_GPIO_NUM OV2640_RESET -#define XCLK_GPIO_NUM OV2640_XCLK -#define SIOD_GPIO_NUM OV2640_SDA -#define SIOC_GPIO_NUM OV2640_SCL - -#define Y9_GPIO_NUM OV2640_D9 -#define Y8_GPIO_NUM OV2640_D8 -#define Y7_GPIO_NUM OV2640_D7 -#define Y6_GPIO_NUM OV2640_D6 -#define Y5_GPIO_NUM OV2640_D5 -#define Y4_GPIO_NUM OV2640_D4 -#define Y3_GPIO_NUM OV2640_D3 -#define Y2_GPIO_NUM OV2640_D2 -#define VSYNC_GPIO_NUM OV2640_VSYNC -#define HREF_GPIO_NUM OV2640_HREF -#define PCLK_GPIO_NUM OV2640_PCLK - -#define XCLK_FREQ_HZ 20000000 - -// CST816 -#define CST816_ADDRESS 0x15 -#define TP_SDA IIC_SDA -#define TP_SCL IIC_SCL -#define TP_INT 47 - -// AP1511B -#define AP1511B_FBC 16 - -// KEY -#define KEY1 17 +/* + * @Description: None + * @Author: LILYGO_L + * @Date: 2024-11-11 11:36:49 + * @LastEditTime: 2025-06-03 17:37:08 + * @License: GPL 3.0 + */ +#pragma once + +#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1 +#define T_CameraPlus_S3_V1_0_V1_1 +#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 +#define T_CameraPlus_S3_V1_2 +#endif + +#ifdef T_CameraPlus_S3_V1_0_V1_1 + +// SPI +#define SPI_SCLK 36 +#define SPI_MOSI 35 +#define SPI_MISO 37 + +// IIC +#define IIC_SDA 1 +#define IIC_SCL 2 + +// MSM261 +#define MSM261_BCLK 18 +#define MSM261_WS 39 +#define MSM261_DATA 40 + +// MAX98357A +#define MAX98357A_DATA 38 + +// FP-133H01D +#define LCD_CS 34 +#define LCD_RST 33 + +// OV2640 +#define OV2640_PWDN -1 +#define OV2640_RESET 3 +#define OV2640_VSYNC 4 + +// CST816 +#define TP_RST 48 + +// SY6970 +#define SY6970_INT 47 + +#endif + +#ifdef T_CameraPlus_S3_V1_2 + +// SPI +#define SPI_SCLK 35 +#define SPI_MOSI 34 +#define SPI_MISO 48 + +// IIC +#define IIC_SDA 33 +#define IIC_SCL 37 + +// MP34DT05TR +#define MP34DT05TR_LRCLK 40 +#define MP34DT05TR_DATA 38 + +#define MP34DT05TR_MAX98357_EN 18 + +// MAX98357A +#define MAX98357A_DATA 39 + +// FP-133H01D +#define LCD_CS 36 +#define LCD_RST -1 + +// OV2640 +#define OV2640_PWDN 4 +#define OV2640_RESET -1 +#define OV2640_VSYNC 3 + +// CST816 +#define TP_RST -1 + +#endif + +// SD +#define SD_CS 21 +#define SD_SCLK SPI_SCLK +#define SD_MOSI SPI_MOSI +#define SD_MISO SPI_MISO + +// MAX98357A +#define MAX98357A_BCLK 41 +#define MAX98357A_LRCLK 42 + +// FP-133H01D +#define LCD_WIDTH 240 +#define LCD_HEIGHT 240 +#define LCD_BL 46 +#define LCD_MOSI SPI_MOSI +#define LCD_SCLK SPI_SCLK +#define LCD_DC 45 + +// SY6970 +#define SY6970_SDA IIC_SDA +#define SY6970_SCL IIC_SCL +#define SY6970_ADDRESS 0x6A + +// OV2640 +#define OV2640_XCLK 7 +#define OV2640_SDA 1 +#define OV2640_SCL 2 +#define OV2640_D9 6 +#define OV2640_D8 8 +#define OV2640_D7 9 +#define OV2640_D6 11 +#define OV2640_D5 13 +#define OV2640_D4 15 +#define OV2640_D3 14 +#define OV2640_D2 12 +#define OV2640_HREF 5 +#define OV2640_PCLK 10 + +#define PWDN_GPIO_NUM OV2640_PWDN +#define RESET_GPIO_NUM OV2640_RESET +#define XCLK_GPIO_NUM OV2640_XCLK +#define SIOD_GPIO_NUM OV2640_SDA +#define SIOC_GPIO_NUM OV2640_SCL + +#define Y9_GPIO_NUM OV2640_D9 +#define Y8_GPIO_NUM OV2640_D8 +#define Y7_GPIO_NUM OV2640_D7 +#define Y6_GPIO_NUM OV2640_D6 +#define Y5_GPIO_NUM OV2640_D5 +#define Y4_GPIO_NUM OV2640_D4 +#define Y3_GPIO_NUM OV2640_D3 +#define Y2_GPIO_NUM OV2640_D2 +#define VSYNC_GPIO_NUM OV2640_VSYNC +#define HREF_GPIO_NUM OV2640_HREF +#define PCLK_GPIO_NUM OV2640_PCLK + +#define XCLK_FREQ_HZ 20000000 + +// CST816 +#define CST816_ADDRESS 0x15 +#define TP_SDA IIC_SDA +#define TP_SCL IIC_SCL +#define TP_INT 47 + +// AP1511B +#define AP1511B_FBC 16 + +// KEY +#define KEY1 17 diff --git a/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.cc b/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.cc index 365d9ca..7ea5ec5 100644 --- a/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.cc +++ b/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.cc @@ -1,169 +1,169 @@ -#include "tcamerapluss3_audio_codec.h" - -#include -#include -#include -#include - -#include "config.h" - -static const char TAG[] = "Tcamerapluss3AudioCodec"; - -Tcamerapluss3AudioCodec::Tcamerapluss3AudioCodec(int input_sample_rate, int output_sample_rate, - gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, - gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, - bool input_reference) { - duplex_ = true; // 是否双工 - input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 - input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - CreateVoiceHardware(mic_bclk, mic_ws, mic_data, spkr_bclk, spkr_lrclk, spkr_data); - - ESP_LOGI(TAG, "Tcamerapluss3AudioCodec initialized"); - -#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 - gpio_config_t config_mic_spkr_en; - config_mic_spkr_en.pin_bit_mask = BIT64(AUDIO_MIC_SPKR_EN); - config_mic_spkr_en.mode = GPIO_MODE_OUTPUT; - config_mic_spkr_en.pull_up_en = GPIO_PULLUP_ENABLE; - config_mic_spkr_en.pull_down_en = GPIO_PULLDOWN_DISABLE; - config_mic_spkr_en.intr_type = GPIO_INTR_DISABLE; -#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER -config_mic_spkr_en.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; -#endif - gpio_config(&config_mic_spkr_en); - gpio_set_level(AUDIO_MIC_SPKR_EN, 0); -#endif -} - -Tcamerapluss3AudioCodec::~Tcamerapluss3AudioCodec() { - audio_codec_delete_codec_if(in_codec_if_); - audio_codec_delete_ctrl_if(in_ctrl_if_); - audio_codec_delete_codec_if(out_codec_if_); - audio_codec_delete_ctrl_if(out_ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void Tcamerapluss3AudioCodec::CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, - gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data) { - - i2s_chan_config_t mic_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); - mic_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer - i2s_chan_config_t spkr_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER); - spkr_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer - - ESP_ERROR_CHECK(i2s_new_channel(&mic_chan_config, NULL, &rx_handle_)); - ESP_ERROR_CHECK(i2s_new_channel(&spkr_chan_config, &tx_handle_, NULL)); - - #ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1 - i2s_std_config_t mic_config = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)input_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - }, - .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = mic_bclk, - .ws = mic_ws, - .dout = I2S_GPIO_UNUSED, - .din = mic_data, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = true // 默认右通道 - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &mic_config)); -#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 - i2s_pdm_rx_config_t mic_config = { - .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(static_cast(input_sample_rate_)), - /* The data bit-width of PDM mode is fixed to 16 */ - .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), - .gpio_cfg = { - .clk = mic_ws, - .din = mic_data, - .invert_flags = { - .clk_inv = false, - }, - }, - }; - - ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &mic_config)); -#endif - - i2s_std_config_t spkr_config = { - .clk_cfg ={ - .sample_rate_hz = static_cast(11025), - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - }, - .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), - .gpio_cfg ={ - .mclk = I2S_GPIO_UNUSED, - .bclk = spkr_bclk, - .ws = spkr_lrclk, - .dout = spkr_data, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &spkr_config)); - ESP_LOGI(TAG, "Voice hardware created"); -} - -void Tcamerapluss3AudioCodec::SetOutputVolume(int volume) { - volume_ = volume; - AudioCodec::SetOutputVolume(volume); -} - -void Tcamerapluss3AudioCodec::EnableInput(bool enable) { - AudioCodec::EnableInput(enable); -} - -void Tcamerapluss3AudioCodec::EnableOutput(bool enable) { - AudioCodec::EnableOutput(enable); -} - -int Tcamerapluss3AudioCodec::Read(int16_t *dest, int samples) { - if (input_enabled_) { - size_t bytes_read; - i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); - - // 麦克风接收音量放大20倍(限制在 int16_t 范围内防止溢出) - int16_t *ptr = dest; - for (int i = 0; i < samples; i++) { - int32_t amplified = *ptr * 20; - *ptr++ = (amplified > 32767) ? 32767 : (amplified < -32768) ? -32768 : amplified; - } - } - return samples; -} - -int Tcamerapluss3AudioCodec::Write(const int16_t *data, int samples){ - if (output_enabled_){ - size_t bytes_read; - auto output_data = (int16_t *)malloc(samples * sizeof(int16_t)); - for (size_t i = 0; i < samples; i++){ - output_data[i] = (float)data[i] * (float)(volume_ / 100.0); - } - i2s_channel_write(tx_handle_, output_data, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); - free(output_data); - } - return samples; -} +#include "tcamerapluss3_audio_codec.h" + +#include +#include +#include +#include + +#include "config.h" + +static const char TAG[] = "Tcamerapluss3AudioCodec"; + +Tcamerapluss3AudioCodec::Tcamerapluss3AudioCodec(int input_sample_rate, int output_sample_rate, + gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, + bool input_reference) { + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + CreateVoiceHardware(mic_bclk, mic_ws, mic_data, spkr_bclk, spkr_lrclk, spkr_data); + + ESP_LOGI(TAG, "Tcamerapluss3AudioCodec initialized"); + +#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 + gpio_config_t config_mic_spkr_en; + config_mic_spkr_en.pin_bit_mask = BIT64(AUDIO_MIC_SPKR_EN); + config_mic_spkr_en.mode = GPIO_MODE_OUTPUT; + config_mic_spkr_en.pull_up_en = GPIO_PULLUP_ENABLE; + config_mic_spkr_en.pull_down_en = GPIO_PULLDOWN_DISABLE; + config_mic_spkr_en.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER +config_mic_spkr_en.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif + gpio_config(&config_mic_spkr_en); + gpio_set_level(AUDIO_MIC_SPKR_EN, 0); +#endif +} + +Tcamerapluss3AudioCodec::~Tcamerapluss3AudioCodec() { + audio_codec_delete_codec_if(in_codec_if_); + audio_codec_delete_ctrl_if(in_ctrl_if_); + audio_codec_delete_codec_if(out_codec_if_); + audio_codec_delete_ctrl_if(out_ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void Tcamerapluss3AudioCodec::CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data) { + + i2s_chan_config_t mic_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + mic_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer + i2s_chan_config_t spkr_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER); + spkr_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer + + ESP_ERROR_CHECK(i2s_new_channel(&mic_chan_config, NULL, &rx_handle_)); + ESP_ERROR_CHECK(i2s_new_channel(&spkr_chan_config, &tx_handle_, NULL)); + + #ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1 + i2s_std_config_t mic_config = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)input_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + }, + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = mic_bclk, + .ws = mic_ws, + .dout = I2S_GPIO_UNUSED, + .din = mic_data, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = true // 默认右通道 + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &mic_config)); +#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 + i2s_pdm_rx_config_t mic_config = { + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(static_cast(input_sample_rate_)), + /* The data bit-width of PDM mode is fixed to 16 */ + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .gpio_cfg = { + .clk = mic_ws, + .din = mic_data, + .invert_flags = { + .clk_inv = false, + }, + }, + }; + + ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &mic_config)); +#endif + + i2s_std_config_t spkr_config = { + .clk_cfg ={ + .sample_rate_hz = static_cast(11025), + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + }, + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg ={ + .mclk = I2S_GPIO_UNUSED, + .bclk = spkr_bclk, + .ws = spkr_lrclk, + .dout = spkr_data, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &spkr_config)); + ESP_LOGI(TAG, "Voice hardware created"); +} + +void Tcamerapluss3AudioCodec::SetOutputVolume(int volume) { + volume_ = volume; + AudioCodec::SetOutputVolume(volume); +} + +void Tcamerapluss3AudioCodec::EnableInput(bool enable) { + AudioCodec::EnableInput(enable); +} + +void Tcamerapluss3AudioCodec::EnableOutput(bool enable) { + AudioCodec::EnableOutput(enable); +} + +int Tcamerapluss3AudioCodec::Read(int16_t *dest, int samples) { + if (input_enabled_) { + size_t bytes_read; + i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); + + // 麦克风接收音量放大20倍(限制在 int16_t 范围内防止溢出) + int16_t *ptr = dest; + for (int i = 0; i < samples; i++) { + int32_t amplified = *ptr * 20; + *ptr++ = (amplified > 32767) ? 32767 : (amplified < -32768) ? -32768 : amplified; + } + } + return samples; +} + +int Tcamerapluss3AudioCodec::Write(const int16_t *data, int samples){ + if (output_enabled_){ + size_t bytes_read; + auto output_data = (int16_t *)malloc(samples * sizeof(int16_t)); + for (size_t i = 0; i < samples; i++){ + output_data[i] = (float)data[i] * (float)(volume_ / 100.0); + } + i2s_channel_write(tx_handle_, output_data, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); + free(output_data); + } + return samples; +} diff --git a/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h b/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h index cdb9aaa..f7e7c38 100644 --- a/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h +++ b/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h @@ -1,37 +1,37 @@ -#ifndef _TCIRCLES3_AUDIO_CODEC_H -#define _TCIRCLES3_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include - -class Tcamerapluss3AudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t *data_if_ = nullptr; - const audio_codec_ctrl_if_t *out_ctrl_if_ = nullptr; - const audio_codec_if_t *out_codec_if_ = nullptr; - const audio_codec_ctrl_if_t *in_ctrl_if_ = nullptr; - const audio_codec_if_t *in_codec_if_ = nullptr; - const audio_codec_gpio_if_t *gpio_if_ = nullptr; - - uint32_t volume_ = 70; - - void CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data); - - virtual int Read(int16_t *dest, int samples) override; - virtual int Write(const int16_t *data, int samples) override; - -public: - Tcamerapluss3AudioCodec(int input_sample_rate, int output_sample_rate, - gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, - gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, - bool input_reference); - virtual ~Tcamerapluss3AudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - -#endif // _BOX_AUDIO_CODEC_H +#ifndef _TCIRCLES3_AUDIO_CODEC_H +#define _TCIRCLES3_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include + +class Tcamerapluss3AudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t *data_if_ = nullptr; + const audio_codec_ctrl_if_t *out_ctrl_if_ = nullptr; + const audio_codec_if_t *out_codec_if_ = nullptr; + const audio_codec_ctrl_if_t *in_ctrl_if_ = nullptr; + const audio_codec_if_t *in_codec_if_ = nullptr; + const audio_codec_gpio_if_t *gpio_if_ = nullptr; + + uint32_t volume_ = 70; + + void CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data); + + virtual int Read(int16_t *dest, int samples) override; + virtual int Write(const int16_t *data, int samples) override; + +public: + Tcamerapluss3AudioCodec(int input_sample_rate, int output_sample_rate, + gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, + bool input_reference); + virtual ~Tcamerapluss3AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _BOX_AUDIO_CODEC_H diff --git a/main/boards/lilygo-t-circle-s3/config.h b/main/boards/lilygo-t-circle-s3/config.h index 03e4fc5..110753d 100644 --- a/main/boards/lilygo-t-circle-s3/config.h +++ b/main/boards/lilygo-t-circle-s3/config.h @@ -1,46 +1,46 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include "pin_config.h" - -#define AUDIO_INPUT_REFERENCE true -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_MIC_I2S_GPIO_BCLK static_cast(MSM261_BCLK) -#define AUDIO_MIC_I2S_GPIO_WS static_cast(MSM261_WS) -#define AUDIO_MIC_I2S_GPIO_DATA static_cast(MSM261_DATA) - -#define AUDIO_SPKR_I2S_GPIO_BCLK static_cast(MAX98357A_BCLK) -#define AUDIO_SPKR_I2S_GPIO_LRCLK static_cast(MAX98357A_LRCLK) -#define AUDIO_SPKR_I2S_GPIO_DATA static_cast(MAX98357A_DATA) -#define AUDIO_SPKR_ENABLE static_cast(MAX98357A_SD_MODE) - -#define TOUCH_I2C_SDA_PIN static_cast(TP_SDA) -#define TOUCH_I2C_SCL_PIN static_cast(TP_SCL) - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_WIDTH LCD_WIDTH -#define DISPLAY_HEIGHT LCD_HEIGHT -#define DISPLAY_MOSI LCD_MOSI -#define DISPLAY_SCLK LCD_SCLK -#define DISPLAY_DC LCD_DC -#define DISPLAY_RST LCD_RST -#define DISPLAY_CS LCD_CS -#define DISPLAY_BL static_cast(LCD_BL) -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN DISPLAY_BL -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include "pin_config.h" + +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_MIC_I2S_GPIO_BCLK static_cast(MSM261_BCLK) +#define AUDIO_MIC_I2S_GPIO_WS static_cast(MSM261_WS) +#define AUDIO_MIC_I2S_GPIO_DATA static_cast(MSM261_DATA) + +#define AUDIO_SPKR_I2S_GPIO_BCLK static_cast(MAX98357A_BCLK) +#define AUDIO_SPKR_I2S_GPIO_LRCLK static_cast(MAX98357A_LRCLK) +#define AUDIO_SPKR_I2S_GPIO_DATA static_cast(MAX98357A_DATA) +#define AUDIO_SPKR_ENABLE static_cast(MAX98357A_SD_MODE) + +#define TOUCH_I2C_SDA_PIN static_cast(TP_SDA) +#define TOUCH_I2C_SCL_PIN static_cast(TP_SCL) + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH LCD_WIDTH +#define DISPLAY_HEIGHT LCD_HEIGHT +#define DISPLAY_MOSI LCD_MOSI +#define DISPLAY_SCLK LCD_SCLK +#define DISPLAY_DC LCD_DC +#define DISPLAY_RST LCD_RST +#define DISPLAY_CS LCD_CS +#define DISPLAY_BL static_cast(LCD_BL) +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN DISPLAY_BL +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/lilygo-t-circle-s3/config.json b/main/boards/lilygo-t-circle-s3/config.json index 378dded..40a8b52 100644 --- a/main/boards/lilygo-t-circle-s3/config.json +++ b/main/boards/lilygo-t-circle-s3/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "lilygo-t-circle-s3", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "lilygo-t-circle-s3", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c index 25a7867..e1a0e45 100644 --- a/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c +++ b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c @@ -1,353 +1,353 @@ -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_lcd_panel_interface.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_commands.h" -#include "driver/gpio.h" -#include "esp_log.h" -#include "esp_check.h" - -#include "esp_lcd_gc9d01n.h" - -static const char *TAG = "gc9d01n"; - -static esp_err_t panel_gc9d01n_del(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9d01n_reset(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9d01n_init(esp_lcd_panel_t *panel); -static esp_err_t panel_gc9d01n_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); -static esp_err_t panel_gc9d01n_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); -static esp_err_t panel_gc9d01n_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t panel_gc9d01n_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); -static esp_err_t panel_gc9d01n_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); -static esp_err_t panel_gc9d01n_disp_on_off(esp_lcd_panel_t *panel, bool off); - -typedef struct{ - esp_lcd_panel_t base; - esp_lcd_panel_io_handle_t io; - int reset_gpio_num; - bool reset_level; - int x_gap; - int y_gap; - uint8_t fb_bits_per_pixel; - uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register - uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register - const gc9d01n_lcd_init_cmd_t *init_cmds; - uint16_t init_cmds_size; -} gc9d01n_panel_t; - -esp_err_t esp_lcd_new_panel_gc9d01n(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel){ - esp_err_t ret = ESP_OK; - gc9d01n_panel_t *gc9d01n = NULL; - gpio_config_t io_conf = {0}; - - ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - gc9d01n = (gc9d01n_panel_t *)calloc(1, sizeof(gc9d01n_panel_t)); - ESP_GOTO_ON_FALSE(gc9d01n, ESP_ERR_NO_MEM, err, TAG, "no mem for gc9d01n panel"); - - if (panel_dev_config->reset_gpio_num >= 0){ - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; - ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); - } - -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - switch (panel_dev_config->color_space){ - case ESP_LCD_COLOR_SPACE_RGB: - gc9d01n->madctl_val = 0; - break; - case ESP_LCD_COLOR_SPACE_BGR: - gc9d01n->madctl_val |= LCD_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); - break; - } -#else - switch (panel_dev_config->rgb_endian){ - case LCD_RGB_ENDIAN_RGB: - gc9d01n->madctl_val = 0; - break; - case LCD_RGB_ENDIAN_BGR: - gc9d01n->madctl_val |= LCD_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian"); - break; - } -#endif - - switch (panel_dev_config->bits_per_pixel){ - case 16: // RGB565 - gc9d01n->colmod_val = 0x55; - gc9d01n->fb_bits_per_pixel = 16; - break; - case 18: // RGB666 - gc9d01n->colmod_val = 0x66; - // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel - gc9d01n->fb_bits_per_pixel = 24; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); - break; - } - - gc9d01n->io = io; - gc9d01n->reset_gpio_num = panel_dev_config->reset_gpio_num; - gc9d01n->reset_level = panel_dev_config->flags.reset_active_high; - if (panel_dev_config->vendor_config){ - gc9d01n->init_cmds = ((gc9d01n_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; - gc9d01n->init_cmds_size = ((gc9d01n_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; - } - gc9d01n->base.del = panel_gc9d01n_del; - gc9d01n->base.reset = panel_gc9d01n_reset; - gc9d01n->base.init = panel_gc9d01n_init; - gc9d01n->base.draw_bitmap = panel_gc9d01n_draw_bitmap; - gc9d01n->base.invert_color = panel_gc9d01n_invert_color; - gc9d01n->base.set_gap = panel_gc9d01n_set_gap; - gc9d01n->base.mirror = panel_gc9d01n_mirror; - gc9d01n->base.swap_xy = panel_gc9d01n_swap_xy; -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - gc9d01n->base.disp_off = panel_gc9d01n_disp_on_off; -#else - gc9d01n->base.disp_on_off = panel_gc9d01n_disp_on_off; -#endif - *ret_panel = &(gc9d01n->base); - ESP_LOGD(TAG, "new gc9d01n panel @%p", gc9d01n); - - // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9D01N_VER_MAJOR, ESP_LCD_GC9D01N_VER_MINOR, - // ESP_LCD_GC9D01N_VER_PATCH); - - return ESP_OK; - -err: - if (gc9d01n){ - if (panel_dev_config->reset_gpio_num >= 0){ - gpio_reset_pin(panel_dev_config->reset_gpio_num); - } - free(gc9d01n); - } - return ret; -} - -static esp_err_t panel_gc9d01n_del(esp_lcd_panel_t *panel){ - gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); - - if (gc9d01n->reset_gpio_num >= 0){ - gpio_reset_pin(gc9d01n->reset_gpio_num); - } - ESP_LOGD(TAG, "del gc9d01n panel @%p", gc9d01n); - free(gc9d01n); - return ESP_OK; -} - -static esp_err_t panel_gc9d01n_reset(esp_lcd_panel_t *panel){ - gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); - esp_lcd_panel_io_handle_t io = gc9d01n->io; - - // perform hardware reset - if (gc9d01n->reset_gpio_num >= 0){ - gpio_set_level(gc9d01n->reset_gpio_num, gc9d01n->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(gc9d01n->reset_gpio_num, !gc9d01n->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - } - else{ // perform software reset - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command - } - - return ESP_OK; -} - -static const gc9d01n_lcd_init_cmd_t vendor_specific_init_default[] = { - // {cmd, { data }, data_size, delay_ms} - // Enable Inter Register - {0xFE, (uint8_t[]){0x00}, 0, 0}, - {0xEF, (uint8_t[]){0x00}, 0, 0}, - {0x80, (uint8_t[]){0xFF}, 1, 0}, - {0x81, (uint8_t[]){0xFF}, 1, 0}, - {0x82, (uint8_t[]){0xFF}, 1, 0}, - {0x84, (uint8_t[]){0xFF}, 1, 0}, - {0x85, (uint8_t[]){0xFF}, 1, 0}, - {0x86, (uint8_t[]){0xFF}, 1, 0}, - {0x87, (uint8_t[]){0xFF}, 1, 0}, - {0x88, (uint8_t[]){0xFF}, 1, 0}, - {0x89, (uint8_t[]){0xFF}, 1, 0}, - {0x8A, (uint8_t[]){0xFF}, 1, 0}, - {0x8B, (uint8_t[]){0xFF}, 1, 0}, - {0x8C, (uint8_t[]){0xFF}, 1, 0}, - {0x8D, (uint8_t[]){0xFF}, 1, 0}, - {0x8E, (uint8_t[]){0xFF}, 1, 0}, - {0x8F, (uint8_t[]){0xFF}, 1, 0}, - {0x3A, (uint8_t[]){0x05}, 1, 0}, - {0xEC, (uint8_t[]){0x01}, 1, 0}, - {0x74, (uint8_t[]){0x02, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00}, 7, 0}, - {0x98, (uint8_t[]){0x3E}, 1, 0}, - {0x99, (uint8_t[]){0x3E}, 1, 0}, - {0xB5, (uint8_t[]){0x0D, 0x0D}, 2, 0}, - {0x60, (uint8_t[]){0x38, 0x0F, 0x79, 0x67}, 4, 0}, - {0x61, (uint8_t[]){0x38, 0x11, 0x79, 0x67}, 4, 0}, - {0x64, (uint8_t[]){0x38, 0x17, 0x71, 0x5F, 0x79, 0x67}, 6, 0}, - {0x65, (uint8_t[]){0x38, 0x13, 0x71, 0x5B, 0x79, 0x67}, 6, 0}, - {0x6A, (uint8_t[]){0x00, 0x00}, 2, 0}, - {0x6C, (uint8_t[]){0x22, 0x02, 0x22, 0x02, 0x22, 0x22, 0x50}, 7, 0}, - {0x6E, (uint8_t[]){0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x0F, 0x0F, 0x0D, 0x0D, 0x0B, 0x0B, 0x09, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0A, 0x0C, 0x0C, 0x0E, 0x0E, 0x10, 0x10, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04}, 32, 0}, - {0xBF, (uint8_t[]){0x01}, 1, 0}, - {0xF9, (uint8_t[]){0x40}, 1, 0}, - {0x9B, (uint8_t[]){0x3B, 0x93, 0x33, 0x7F, 0x00}, 5, 0}, - {0x7E, (uint8_t[]){0x30}, 1, 0}, - {0x70, (uint8_t[]){0x0D, 0x02, 0x08, 0x0D, 0x02, 0x08}, 6, 0}, - {0x71, (uint8_t[]){0x0D, 0x02, 0x08}, 3, 0}, - {0x91, (uint8_t[]){0x0E, 0x09}, 2, 0}, - {0xC3, (uint8_t[]){0x19, 0xC4, 0x19, 0xC9, 0x3C}, 5, 0}, - {0xF0, (uint8_t[]){0x53, 0x15, 0x0A, 0x04, 0x00, 0x3E}, 6, 0}, - {0xF1, (uint8_t[]){0x56, 0xA8, 0x7F, 0x33, 0x34, 0x5F}, 6, 0}, - {0xF2, (uint8_t[]){0x53, 0x15, 0x0A, 0x04, 0x00, 0x3A}, 6, 0}, - {0xF3, (uint8_t[]){0x52, 0xA4, 0x7F, 0x33, 0x34, 0xDF}, 6, 0}, - - // {0x20, (uint8_t[]){0x00}, 0, 0}, - {0x36, (uint8_t[]){0x00}, 1, 0}, - {0x11, (uint8_t[]){0x00}, 0, 200}, - {0x29, (uint8_t[]){0x00}, 0, 0}, - {0x2C, (uint8_t[]){0x00}, 0, 20}, -}; - -static esp_err_t panel_gc9d01n_init(esp_lcd_panel_t *panel){ - gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); - esp_lcd_panel_io_handle_t io = gc9d01n->io; - - // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(100)); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val,},1),TAG, "send command failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){gc9d01n->colmod_val,},1),TAG, "send command failed"); - - const gc9d01n_lcd_init_cmd_t *init_cmds = NULL; - uint16_t init_cmds_size = 0; - if (gc9d01n->init_cmds){ - init_cmds = gc9d01n->init_cmds; - init_cmds_size = gc9d01n->init_cmds_size; - }else{ - init_cmds = vendor_specific_init_default; - init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9d01n_lcd_init_cmd_t); - } - - bool is_cmd_overwritten = false; - for (int i = 0; i < init_cmds_size; i++){ - // Check if the command has been used or conflicts with the internal - switch (init_cmds[i].cmd){ - case LCD_CMD_MADCTL: - is_cmd_overwritten = true; - gc9d01n->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - case LCD_CMD_COLMOD: - is_cmd_overwritten = true; - gc9d01n->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - default: - is_cmd_overwritten = false; - break; - } - - if (is_cmd_overwritten){ - ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); - } - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); - } - ESP_LOGD(TAG, "send init commands success"); - - return ESP_OK; -} - -static esp_err_t panel_gc9d01n_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data){ - gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); - assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); - esp_lcd_panel_io_handle_t io = gc9d01n->io; - - x_start += gc9d01n->x_gap; - x_end += gc9d01n->x_gap; - y_start += gc9d01n->y_gap; - y_end += gc9d01n->y_gap; - - // define an area of frame memory where MCU can access - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]){(x_start >> 8) & 0xFF,x_start & 0xFF,((x_end - 1) >> 8) & 0xFF,(x_end - 1) & 0xFF,},4),TAG, "send command failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]){(y_start >> 8) & 0xFF,y_start & 0xFF,((y_end - 1) >> 8) & 0xFF,(y_end - 1) & 0xFF,},4),TAG, "send command failed"); - // transfer frame buffer - size_t len = (x_end - x_start) * (y_end - y_start) * gc9d01n->fb_bits_per_pixel / 8; - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "send color failed"); - - return ESP_OK; -} - -static esp_err_t panel_gc9d01n_invert_color(esp_lcd_panel_t *panel, bool invert_color_data){ - gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); - esp_lcd_panel_io_handle_t io = gc9d01n->io; - int command = 0; - if (invert_color_data){ - command = LCD_CMD_INVON; - }else{ - command = LCD_CMD_INVOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); - return ESP_OK; -} - -static esp_err_t panel_gc9d01n_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y){ - gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); - esp_lcd_panel_io_handle_t io = gc9d01n->io; - if (mirror_x){ - gc9d01n->madctl_val |= LCD_CMD_MX_BIT; - }else{ - gc9d01n->madctl_val &= ~LCD_CMD_MX_BIT; - } - if (mirror_y){ - gc9d01n->madctl_val |= LCD_CMD_MY_BIT; - }else{ - gc9d01n->madctl_val &= ~LCD_CMD_MY_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val}, 1), TAG, "send command failed"); - return ESP_OK; -} - -static esp_err_t panel_gc9d01n_swap_xy(esp_lcd_panel_t *panel, bool swap_axes){ - gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); - esp_lcd_panel_io_handle_t io = gc9d01n->io; - if (swap_axes){ - gc9d01n->madctl_val |= LCD_CMD_MV_BIT; - }else{ - gc9d01n->madctl_val &= ~LCD_CMD_MV_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val}, 1), TAG, "send command failed"); - return ESP_OK; -} - -static esp_err_t panel_gc9d01n_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap){ - gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); - gc9d01n->x_gap = x_gap; - gc9d01n->y_gap = y_gap; - return ESP_OK; -} - -static esp_err_t panel_gc9d01n_disp_on_off(esp_lcd_panel_t *panel, bool on_off){ - gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); - esp_lcd_panel_io_handle_t io = gc9d01n->io; - int command = 0; - -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - on_off = !on_off; -#endif - - if (on_off){ - command = LCD_CMD_DISPON; - }else{ - command = LCD_CMD_DISPOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); - return ESP_OK; -} +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_commands.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_check.h" + +#include "esp_lcd_gc9d01n.h" + +static const char *TAG = "gc9d01n"; + +static esp_err_t panel_gc9d01n_del(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9d01n_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9d01n_init(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9d01n_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); +static esp_err_t panel_gc9d01n_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_gc9d01n_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_gc9d01n_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_gc9d01n_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_gc9d01n_disp_on_off(esp_lcd_panel_t *panel, bool off); + +typedef struct{ + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + bool reset_level; + int x_gap; + int y_gap; + uint8_t fb_bits_per_pixel; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register + const gc9d01n_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; +} gc9d01n_panel_t; + +esp_err_t esp_lcd_new_panel_gc9d01n(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel){ + esp_err_t ret = ESP_OK; + gc9d01n_panel_t *gc9d01n = NULL; + gpio_config_t io_conf = {0}; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + gc9d01n = (gc9d01n_panel_t *)calloc(1, sizeof(gc9d01n_panel_t)); + ESP_GOTO_ON_FALSE(gc9d01n, ESP_ERR_NO_MEM, err, TAG, "no mem for gc9d01n panel"); + + if (panel_dev_config->reset_gpio_num >= 0){ + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + switch (panel_dev_config->color_space){ + case ESP_LCD_COLOR_SPACE_RGB: + gc9d01n->madctl_val = 0; + break; + case ESP_LCD_COLOR_SPACE_BGR: + gc9d01n->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); + break; + } +#else + switch (panel_dev_config->rgb_endian){ + case LCD_RGB_ENDIAN_RGB: + gc9d01n->madctl_val = 0; + break; + case LCD_RGB_ENDIAN_BGR: + gc9d01n->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian"); + break; + } +#endif + + switch (panel_dev_config->bits_per_pixel){ + case 16: // RGB565 + gc9d01n->colmod_val = 0x55; + gc9d01n->fb_bits_per_pixel = 16; + break; + case 18: // RGB666 + gc9d01n->colmod_val = 0x66; + // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel + gc9d01n->fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + gc9d01n->io = io; + gc9d01n->reset_gpio_num = panel_dev_config->reset_gpio_num; + gc9d01n->reset_level = panel_dev_config->flags.reset_active_high; + if (panel_dev_config->vendor_config){ + gc9d01n->init_cmds = ((gc9d01n_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; + gc9d01n->init_cmds_size = ((gc9d01n_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; + } + gc9d01n->base.del = panel_gc9d01n_del; + gc9d01n->base.reset = panel_gc9d01n_reset; + gc9d01n->base.init = panel_gc9d01n_init; + gc9d01n->base.draw_bitmap = panel_gc9d01n_draw_bitmap; + gc9d01n->base.invert_color = panel_gc9d01n_invert_color; + gc9d01n->base.set_gap = panel_gc9d01n_set_gap; + gc9d01n->base.mirror = panel_gc9d01n_mirror; + gc9d01n->base.swap_xy = panel_gc9d01n_swap_xy; +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + gc9d01n->base.disp_off = panel_gc9d01n_disp_on_off; +#else + gc9d01n->base.disp_on_off = panel_gc9d01n_disp_on_off; +#endif + *ret_panel = &(gc9d01n->base); + ESP_LOGD(TAG, "new gc9d01n panel @%p", gc9d01n); + + // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9D01N_VER_MAJOR, ESP_LCD_GC9D01N_VER_MINOR, + // ESP_LCD_GC9D01N_VER_PATCH); + + return ESP_OK; + +err: + if (gc9d01n){ + if (panel_dev_config->reset_gpio_num >= 0){ + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(gc9d01n); + } + return ret; +} + +static esp_err_t panel_gc9d01n_del(esp_lcd_panel_t *panel){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + + if (gc9d01n->reset_gpio_num >= 0){ + gpio_reset_pin(gc9d01n->reset_gpio_num); + } + ESP_LOGD(TAG, "del gc9d01n panel @%p", gc9d01n); + free(gc9d01n); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_reset(esp_lcd_panel_t *panel){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + + // perform hardware reset + if (gc9d01n->reset_gpio_num >= 0){ + gpio_set_level(gc9d01n->reset_gpio_num, gc9d01n->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9d01n->reset_gpio_num, !gc9d01n->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + } + else{ // perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command + } + + return ESP_OK; +} + +static const gc9d01n_lcd_init_cmd_t vendor_specific_init_default[] = { + // {cmd, { data }, data_size, delay_ms} + // Enable Inter Register + {0xFE, (uint8_t[]){0x00}, 0, 0}, + {0xEF, (uint8_t[]){0x00}, 0, 0}, + {0x80, (uint8_t[]){0xFF}, 1, 0}, + {0x81, (uint8_t[]){0xFF}, 1, 0}, + {0x82, (uint8_t[]){0xFF}, 1, 0}, + {0x84, (uint8_t[]){0xFF}, 1, 0}, + {0x85, (uint8_t[]){0xFF}, 1, 0}, + {0x86, (uint8_t[]){0xFF}, 1, 0}, + {0x87, (uint8_t[]){0xFF}, 1, 0}, + {0x88, (uint8_t[]){0xFF}, 1, 0}, + {0x89, (uint8_t[]){0xFF}, 1, 0}, + {0x8A, (uint8_t[]){0xFF}, 1, 0}, + {0x8B, (uint8_t[]){0xFF}, 1, 0}, + {0x8C, (uint8_t[]){0xFF}, 1, 0}, + {0x8D, (uint8_t[]){0xFF}, 1, 0}, + {0x8E, (uint8_t[]){0xFF}, 1, 0}, + {0x8F, (uint8_t[]){0xFF}, 1, 0}, + {0x3A, (uint8_t[]){0x05}, 1, 0}, + {0xEC, (uint8_t[]){0x01}, 1, 0}, + {0x74, (uint8_t[]){0x02, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x98, (uint8_t[]){0x3E}, 1, 0}, + {0x99, (uint8_t[]){0x3E}, 1, 0}, + {0xB5, (uint8_t[]){0x0D, 0x0D}, 2, 0}, + {0x60, (uint8_t[]){0x38, 0x0F, 0x79, 0x67}, 4, 0}, + {0x61, (uint8_t[]){0x38, 0x11, 0x79, 0x67}, 4, 0}, + {0x64, (uint8_t[]){0x38, 0x17, 0x71, 0x5F, 0x79, 0x67}, 6, 0}, + {0x65, (uint8_t[]){0x38, 0x13, 0x71, 0x5B, 0x79, 0x67}, 6, 0}, + {0x6A, (uint8_t[]){0x00, 0x00}, 2, 0}, + {0x6C, (uint8_t[]){0x22, 0x02, 0x22, 0x02, 0x22, 0x22, 0x50}, 7, 0}, + {0x6E, (uint8_t[]){0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x0F, 0x0F, 0x0D, 0x0D, 0x0B, 0x0B, 0x09, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0A, 0x0C, 0x0C, 0x0E, 0x0E, 0x10, 0x10, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04}, 32, 0}, + {0xBF, (uint8_t[]){0x01}, 1, 0}, + {0xF9, (uint8_t[]){0x40}, 1, 0}, + {0x9B, (uint8_t[]){0x3B, 0x93, 0x33, 0x7F, 0x00}, 5, 0}, + {0x7E, (uint8_t[]){0x30}, 1, 0}, + {0x70, (uint8_t[]){0x0D, 0x02, 0x08, 0x0D, 0x02, 0x08}, 6, 0}, + {0x71, (uint8_t[]){0x0D, 0x02, 0x08}, 3, 0}, + {0x91, (uint8_t[]){0x0E, 0x09}, 2, 0}, + {0xC3, (uint8_t[]){0x19, 0xC4, 0x19, 0xC9, 0x3C}, 5, 0}, + {0xF0, (uint8_t[]){0x53, 0x15, 0x0A, 0x04, 0x00, 0x3E}, 6, 0}, + {0xF1, (uint8_t[]){0x56, 0xA8, 0x7F, 0x33, 0x34, 0x5F}, 6, 0}, + {0xF2, (uint8_t[]){0x53, 0x15, 0x0A, 0x04, 0x00, 0x3A}, 6, 0}, + {0xF3, (uint8_t[]){0x52, 0xA4, 0x7F, 0x33, 0x34, 0xDF}, 6, 0}, + + // {0x20, (uint8_t[]){0x00}, 0, 0}, + {0x36, (uint8_t[]){0x00}, 1, 0}, + {0x11, (uint8_t[]){0x00}, 0, 200}, + {0x29, (uint8_t[]){0x00}, 0, 0}, + {0x2C, (uint8_t[]){0x00}, 0, 20}, +}; + +static esp_err_t panel_gc9d01n_init(esp_lcd_panel_t *panel){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + + // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(100)); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val,},1),TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){gc9d01n->colmod_val,},1),TAG, "send command failed"); + + const gc9d01n_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + if (gc9d01n->init_cmds){ + init_cmds = gc9d01n->init_cmds; + init_cmds_size = gc9d01n->init_cmds_size; + }else{ + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9d01n_lcd_init_cmd_t); + } + + bool is_cmd_overwritten = false; + for (int i = 0; i < init_cmds_size; i++){ + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd){ + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + gc9d01n->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + gc9d01n->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten){ + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + + x_start += gc9d01n->x_gap; + x_end += gc9d01n->x_gap; + y_start += gc9d01n->y_gap; + y_end += gc9d01n->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]){(x_start >> 8) & 0xFF,x_start & 0xFF,((x_end - 1) >> 8) & 0xFF,(x_end - 1) & 0xFF,},4),TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]){(y_start >> 8) & 0xFF,y_start & 0xFF,((y_end - 1) >> 8) & 0xFF,(y_end - 1) & 0xFF,},4),TAG, "send command failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * gc9d01n->fb_bits_per_pixel / 8; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "send color failed"); + + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_invert_color(esp_lcd_panel_t *panel, bool invert_color_data){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + int command = 0; + if (invert_color_data){ + command = LCD_CMD_INVON; + }else{ + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + if (mirror_x){ + gc9d01n->madctl_val |= LCD_CMD_MX_BIT; + }else{ + gc9d01n->madctl_val &= ~LCD_CMD_MX_BIT; + } + if (mirror_y){ + gc9d01n->madctl_val |= LCD_CMD_MY_BIT; + }else{ + gc9d01n->madctl_val &= ~LCD_CMD_MY_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val}, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_swap_xy(esp_lcd_panel_t *panel, bool swap_axes){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + if (swap_axes){ + gc9d01n->madctl_val |= LCD_CMD_MV_BIT; + }else{ + gc9d01n->madctl_val &= ~LCD_CMD_MV_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val}, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + gc9d01n->x_gap = x_gap; + gc9d01n->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_disp_on_off(esp_lcd_panel_t *panel, bool on_off){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + int command = 0; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + on_off = !on_off; +#endif + + if (on_off){ + command = LCD_CMD_DISPON; + }else{ + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} diff --git a/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h index ec057cc..d0772cc 100644 --- a/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h +++ b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h @@ -1,99 +1,99 @@ -#pragma once - -#include "esp_lcd_panel_vendor.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief LCD panel initialization commands. - * - */ -typedef struct { - int cmd; /* -#include -#include -#include -#include -#include "esp_lcd_gc9d01n.h" - -#define TAG "LilygoTCircleS3Board" - -class Cst816x : public I2cDevice { -public: - struct TouchPoint_t { - int num = 0; - int x = -1; - int y = -1; - }; - - Cst816x(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - uint8_t chip_id = ReadReg(0xA7); - ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); - read_buffer_ = new uint8_t[6]; - } - - ~Cst816x() { - delete[] read_buffer_; - } - - void UpdateTouchPoint() { - ReadRegs(0x02, read_buffer_, 6); - tp_.num = read_buffer_[0] & 0x0F; - tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; - tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; - } - - const TouchPoint_t &GetTouchPoint() { - return tp_; - } - -private: - uint8_t *read_buffer_ = nullptr; - TouchPoint_t tp_; -}; - -class LilygoTCircleS3Board : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Cst816x *cst816d_; - LcdDisplay *display_; - Button boot_button_; - PowerSaveTimer* power_save_timer_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(10); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitI2c(){ - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_config = { - .i2c_port = I2C_NUM_0, - .sda_io_num = TOUCH_I2C_SDA_PIN, - .scl_io_num = TOUCH_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - } - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &i2c_bus_)); - } - - void I2cDetect() { - uint8_t address; - printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); - for (int i = 0; i < 128; i += 16) { - printf("%02x: ", i); - for (int j = 0; j < 16; j++) { - fflush(stdout); - address = i + j; - esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); - if (ret == ESP_OK) { - printf("%02x ", address); - } else if (ret == ESP_ERR_TIMEOUT) { - printf("UU "); - } else { - printf("-- "); - } - } - printf("\r\n"); - } - } - - static void touchpad_daemon(void *param) { - vTaskDelay(pdMS_TO_TICKS(2000)); - auto &board = (LilygoTCircleS3Board&)Board::GetInstance(); - auto touchpad = board.GetTouchpad(); - bool was_touched = false; - while (1) { - touchpad->UpdateTouchPoint(); - if (touchpad->GetTouchPoint().num > 0){ - // On press - if (!was_touched) { - was_touched = true; - Application::GetInstance().ToggleChatState(); - } - } - // On release - else if (was_touched) { - was_touched = false; - } - vTaskDelay(pdMS_TO_TICKS(50)); - } - vTaskDelete(NULL); - } - - void InitCst816d() { - ESP_LOGI(TAG, "Init CST816x"); - cst816d_ = new Cst816x(i2c_bus_, 0x15); - xTaskCreate(touchpad_daemon, "tp", 2048, NULL, 5, NULL); - } - - void InitSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCLK; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitGc9d01nDisplay() { - ESP_LOGI(TAG, "Init GC9D01N"); - - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS; - io_config.dc_gpio_num = DISPLAY_DC; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9d01n(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, false); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, - DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - - gpio_config_t config; - config.pin_bit_mask = BIT64(DISPLAY_BL); - config.mode = GPIO_MODE_OUTPUT; - config.pull_up_en = GPIO_PULLUP_DISABLE; - config.pull_down_en = GPIO_PULLDOWN_ENABLE; - config.intr_type = GPIO_INTR_DISABLE; -#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER - config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; -#endif - gpio_config(&config); - gpio_set_level(DISPLAY_BL, 0); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - power_save_timer_->WakeUp(); - app.ToggleChatState(); - }); - } - -public: - LilygoTCircleS3Board() : boot_button_(BOOT_BUTTON_GPIO) { - InitializePowerSaveTimer(); - InitI2c(); - InitCst816d(); - I2cDetect(); - InitSpi(); - InitGc9d01nDisplay(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec *GetAudioCodec() override { - static Tcircles3AudioCodec audio_codec( - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_MIC_I2S_GPIO_BCLK, - AUDIO_MIC_I2S_GPIO_WS, - AUDIO_MIC_I2S_GPIO_DATA, - AUDIO_SPKR_I2S_GPIO_BCLK, - AUDIO_SPKR_I2S_GPIO_LRCLK, - AUDIO_SPKR_I2S_GPIO_DATA, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display *GetDisplay() override{ - return display_; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - Cst816x *GetTouchpad() { - return cst816d_; - } -}; - -DECLARE_BOARD(LilygoTCircleS3Board); +#include "wifi_board.h" +#include "tcircles3_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "i2c_device.h" + +#include +#include +#include +#include +#include +#include "esp_lcd_gc9d01n.h" + +#define TAG "LilygoTCircleS3Board" + +class Cst816x : public I2cDevice { +public: + struct TouchPoint_t { + int num = 0; + int x = -1; + int y = -1; + }; + + Cst816x(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + uint8_t chip_id = ReadReg(0xA7); + ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); + read_buffer_ = new uint8_t[6]; + } + + ~Cst816x() { + delete[] read_buffer_; + } + + void UpdateTouchPoint() { + ReadRegs(0x02, read_buffer_, 6); + tp_.num = read_buffer_[0] & 0x0F; + tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; + tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; + } + + const TouchPoint_t &GetTouchPoint() { + return tp_; + } + +private: + uint8_t *read_buffer_ = nullptr; + TouchPoint_t tp_; +}; + +class LilygoTCircleS3Board : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Cst816x *cst816d_; + LcdDisplay *display_; + Button boot_button_; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(10); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitI2c(){ + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_config = { + .i2c_port = I2C_NUM_0, + .sda_io_num = TOUCH_I2C_SDA_PIN, + .scl_io_num = TOUCH_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + } + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &i2c_bus_)); + } + + void I2cDetect() { + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK) { + printf("%02x ", address); + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + } + + static void touchpad_daemon(void *param) { + vTaskDelay(pdMS_TO_TICKS(2000)); + auto &board = (LilygoTCircleS3Board&)Board::GetInstance(); + auto touchpad = board.GetTouchpad(); + bool was_touched = false; + while (1) { + touchpad->UpdateTouchPoint(); + if (touchpad->GetTouchPoint().num > 0){ + // On press + if (!was_touched) { + was_touched = true; + Application::GetInstance().ToggleChatState(); + } + } + // On release + else if (was_touched) { + was_touched = false; + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + vTaskDelete(NULL); + } + + void InitCst816d() { + ESP_LOGI(TAG, "Init CST816x"); + cst816d_ = new Cst816x(i2c_bus_, 0x15); + xTaskCreate(touchpad_daemon, "tp", 2048, NULL, 5, NULL); + } + + void InitSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCLK; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitGc9d01nDisplay() { + ESP_LOGI(TAG, "Init GC9D01N"); + + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS; + io_config.dc_gpio_num = DISPLAY_DC; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9d01n(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + + gpio_config_t config; + config.pin_bit_mask = BIT64(DISPLAY_BL); + config.mode = GPIO_MODE_OUTPUT; + config.pull_up_en = GPIO_PULLUP_DISABLE; + config.pull_down_en = GPIO_PULLDOWN_ENABLE; + config.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER + config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif + gpio_config(&config); + gpio_set_level(DISPLAY_BL, 0); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + power_save_timer_->WakeUp(); + app.ToggleChatState(); + }); + } + +public: + LilygoTCircleS3Board() : boot_button_(BOOT_BUTTON_GPIO) { + InitializePowerSaveTimer(); + InitI2c(); + InitCst816d(); + I2cDetect(); + InitSpi(); + InitGc9d01nDisplay(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec *GetAudioCodec() override { + static Tcircles3AudioCodec audio_codec( + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_MIC_I2S_GPIO_BCLK, + AUDIO_MIC_I2S_GPIO_WS, + AUDIO_MIC_I2S_GPIO_DATA, + AUDIO_SPKR_I2S_GPIO_BCLK, + AUDIO_SPKR_I2S_GPIO_LRCLK, + AUDIO_SPKR_I2S_GPIO_DATA, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display *GetDisplay() override{ + return display_; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + Cst816x *GetTouchpad() { + return cst816d_; + } +}; + +DECLARE_BOARD(LilygoTCircleS3Board); diff --git a/main/boards/lilygo-t-circle-s3/pin_config.h b/main/boards/lilygo-t-circle-s3/pin_config.h index db428ca..141fc0b 100644 --- a/main/boards/lilygo-t-circle-s3/pin_config.h +++ b/main/boards/lilygo-t-circle-s3/pin_config.h @@ -1,47 +1,47 @@ -/* - * @Description: None - * @Author: LILYGO_L - * @Date: 2023-08-16 14:24:03 - * @LastEditTime: 2025-01-20 10:11:16 - * @License: GPL 3.0 - */ -#pragma once - -// MAX98357A -#define MAX98357A_BCLK 5 -#define MAX98357A_LRCLK 4 -#define MAX98357A_DATA 6 -#define MAX98357A_SD_MODE 45 - -// MSM261 -#define MSM261_BCLK 7 -#define MSM261_WS 9 -#define MSM261_DATA 8 - -// APA102 -#define APA102_DATA 38 -#define APA102_CLOCK 39 - -// H0075Y002-V0 -#define LCD_WIDTH 160 -#define LCD_HEIGHT 160 -#define LCD_MOSI 17 -#define LCD_SCLK 15 -#define LCD_DC 16 -#define LCD_RST -1 -#define LCD_CS 13 -#define LCD_BL 18 - -// IIC -#define IIC_SDA 11 -#define IIC_SCL 14 - -// CST816D -#define TP_SDA 11 -#define TP_SCL 14 -#define TP_RST -1 -#define TP_INT 12 - -//Rotary Encoder -#define KNOB_DATA_A 47 -#define KNOB_DATA_B 48 +/* + * @Description: None + * @Author: LILYGO_L + * @Date: 2023-08-16 14:24:03 + * @LastEditTime: 2025-01-20 10:11:16 + * @License: GPL 3.0 + */ +#pragma once + +// MAX98357A +#define MAX98357A_BCLK 5 +#define MAX98357A_LRCLK 4 +#define MAX98357A_DATA 6 +#define MAX98357A_SD_MODE 45 + +// MSM261 +#define MSM261_BCLK 7 +#define MSM261_WS 9 +#define MSM261_DATA 8 + +// APA102 +#define APA102_DATA 38 +#define APA102_CLOCK 39 + +// H0075Y002-V0 +#define LCD_WIDTH 160 +#define LCD_HEIGHT 160 +#define LCD_MOSI 17 +#define LCD_SCLK 15 +#define LCD_DC 16 +#define LCD_RST -1 +#define LCD_CS 13 +#define LCD_BL 18 + +// IIC +#define IIC_SDA 11 +#define IIC_SCL 14 + +// CST816D +#define TP_SDA 11 +#define TP_SCL 14 +#define TP_RST -1 +#define TP_INT 12 + +//Rotary Encoder +#define KNOB_DATA_A 47 +#define KNOB_DATA_B 48 diff --git a/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.cc b/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.cc index 5fe3ea7..47f780a 100644 --- a/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.cc +++ b/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.cc @@ -1,147 +1,147 @@ -#include "tcircles3_audio_codec.h" - -#include -#include -#include - -#include "config.h" - -static const char TAG[] = "Tcircles3AudioCodec"; - -Tcircles3AudioCodec::Tcircles3AudioCodec(int input_sample_rate, int output_sample_rate, - gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, - gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, - bool input_reference) { - duplex_ = true; // 是否双工 - input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 - input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - CreateVoiceHardware(mic_bclk, mic_ws, mic_data, spkr_bclk, spkr_lrclk, spkr_data); - - gpio_config_t config; - config.pin_bit_mask = BIT64(AUDIO_SPKR_ENABLE); - config.mode = GPIO_MODE_OUTPUT; - config.pull_up_en = GPIO_PULLUP_DISABLE; - config.pull_down_en = GPIO_PULLDOWN_ENABLE; - config.intr_type = GPIO_INTR_DISABLE; -#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER - config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; -#endif - gpio_config(&config); - gpio_set_level(AUDIO_SPKR_ENABLE, 0); - ESP_LOGI(TAG, "Tcircles3AudioCodec initialized"); -} - -Tcircles3AudioCodec::~Tcircles3AudioCodec() { - audio_codec_delete_codec_if(in_codec_if_); - audio_codec_delete_ctrl_if(in_ctrl_if_); - audio_codec_delete_codec_if(out_codec_if_); - audio_codec_delete_ctrl_if(out_ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void Tcircles3AudioCodec::CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, - gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data) { - - i2s_chan_config_t mic_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(0), I2S_ROLE_MASTER); - mic_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer - i2s_chan_config_t spkr_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(1), I2S_ROLE_MASTER); - spkr_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer - - ESP_ERROR_CHECK(i2s_new_channel(&mic_chan_config, NULL, &rx_handle_)); - ESP_ERROR_CHECK(i2s_new_channel(&spkr_chan_config, &tx_handle_, NULL)); - - i2s_std_config_t mic_config = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - }, - .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), - .gpio_cfg ={ - .mclk = I2S_GPIO_UNUSED, - .bclk = mic_bclk, - .ws = mic_ws, - .dout = I2S_GPIO_UNUSED, - .din = mic_data, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false, - } - } - }; - - i2s_std_config_t spkr_config = { - .clk_cfg ={ - .sample_rate_hz = static_cast(11025), - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - }, - .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), - .gpio_cfg ={ - .mclk = I2S_GPIO_UNUSED, - .bclk = spkr_bclk, - .ws = spkr_lrclk, - .dout = spkr_data, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &mic_config)); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &spkr_config)); - ESP_LOGI(TAG, "Voice hardware created"); -} - -void Tcircles3AudioCodec::SetOutputVolume(int volume) { - volume_ = volume; - AudioCodec::SetOutputVolume(volume); -} - -void Tcircles3AudioCodec::EnableInput(bool enable) { - AudioCodec::EnableInput(enable); -} - -void Tcircles3AudioCodec::EnableOutput(bool enable) { - if (enable){ - gpio_set_level(AUDIO_SPKR_ENABLE, 1); - }else{ - gpio_set_level(AUDIO_SPKR_ENABLE, 0); - } - AudioCodec::EnableOutput(enable); -} - -int Tcircles3AudioCodec::Read(int16_t *dest, int samples){ - if (input_enabled_){ - size_t bytes_read; - i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); - } - return samples; -} - -int Tcircles3AudioCodec::Write(const int16_t *data, int samples){ - if (output_enabled_){ - size_t bytes_read; - auto output_data = (int16_t *)malloc(samples * sizeof(int16_t)); - for (size_t i = 0; i < samples; i++){ - output_data[i] = (float)data[i] * (float)(volume_ / 100.0); - } - i2s_channel_write(tx_handle_, output_data, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); - free(output_data); - } - return samples; -} +#include "tcircles3_audio_codec.h" + +#include +#include +#include + +#include "config.h" + +static const char TAG[] = "Tcircles3AudioCodec"; + +Tcircles3AudioCodec::Tcircles3AudioCodec(int input_sample_rate, int output_sample_rate, + gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, + bool input_reference) { + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + CreateVoiceHardware(mic_bclk, mic_ws, mic_data, spkr_bclk, spkr_lrclk, spkr_data); + + gpio_config_t config; + config.pin_bit_mask = BIT64(AUDIO_SPKR_ENABLE); + config.mode = GPIO_MODE_OUTPUT; + config.pull_up_en = GPIO_PULLUP_DISABLE; + config.pull_down_en = GPIO_PULLDOWN_ENABLE; + config.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER + config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif + gpio_config(&config); + gpio_set_level(AUDIO_SPKR_ENABLE, 0); + ESP_LOGI(TAG, "Tcircles3AudioCodec initialized"); +} + +Tcircles3AudioCodec::~Tcircles3AudioCodec() { + audio_codec_delete_codec_if(in_codec_if_); + audio_codec_delete_ctrl_if(in_ctrl_if_); + audio_codec_delete_codec_if(out_codec_if_); + audio_codec_delete_ctrl_if(out_ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void Tcircles3AudioCodec::CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data) { + + i2s_chan_config_t mic_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(0), I2S_ROLE_MASTER); + mic_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer + i2s_chan_config_t spkr_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(1), I2S_ROLE_MASTER); + spkr_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer + + ESP_ERROR_CHECK(i2s_new_channel(&mic_chan_config, NULL, &rx_handle_)); + ESP_ERROR_CHECK(i2s_new_channel(&spkr_chan_config, &tx_handle_, NULL)); + + i2s_std_config_t mic_config = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + }, + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg ={ + .mclk = I2S_GPIO_UNUSED, + .bclk = mic_bclk, + .ws = mic_ws, + .dout = I2S_GPIO_UNUSED, + .din = mic_data, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + } + } + }; + + i2s_std_config_t spkr_config = { + .clk_cfg ={ + .sample_rate_hz = static_cast(11025), + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + }, + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg ={ + .mclk = I2S_GPIO_UNUSED, + .bclk = spkr_bclk, + .ws = spkr_lrclk, + .dout = spkr_data, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &mic_config)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &spkr_config)); + ESP_LOGI(TAG, "Voice hardware created"); +} + +void Tcircles3AudioCodec::SetOutputVolume(int volume) { + volume_ = volume; + AudioCodec::SetOutputVolume(volume); +} + +void Tcircles3AudioCodec::EnableInput(bool enable) { + AudioCodec::EnableInput(enable); +} + +void Tcircles3AudioCodec::EnableOutput(bool enable) { + if (enable){ + gpio_set_level(AUDIO_SPKR_ENABLE, 1); + }else{ + gpio_set_level(AUDIO_SPKR_ENABLE, 0); + } + AudioCodec::EnableOutput(enable); +} + +int Tcircles3AudioCodec::Read(int16_t *dest, int samples){ + if (input_enabled_){ + size_t bytes_read; + i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); + } + return samples; +} + +int Tcircles3AudioCodec::Write(const int16_t *data, int samples){ + if (output_enabled_){ + size_t bytes_read; + auto output_data = (int16_t *)malloc(samples * sizeof(int16_t)); + for (size_t i = 0; i < samples; i++){ + output_data[i] = (float)data[i] * (float)(volume_ / 100.0); + } + i2s_channel_write(tx_handle_, output_data, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); + free(output_data); + } + return samples; +} diff --git a/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h b/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h index f68f818..370c76f 100644 --- a/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h +++ b/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h @@ -1,37 +1,37 @@ -#ifndef _TCIRCLES3_AUDIO_CODEC_H -#define _TCIRCLES3_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include - -class Tcircles3AudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t *data_if_ = nullptr; - const audio_codec_ctrl_if_t *out_ctrl_if_ = nullptr; - const audio_codec_if_t *out_codec_if_ = nullptr; - const audio_codec_ctrl_if_t *in_ctrl_if_ = nullptr; - const audio_codec_if_t *in_codec_if_ = nullptr; - const audio_codec_gpio_if_t *gpio_if_ = nullptr; - - uint32_t volume_ = 70; - - void CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data); - - virtual int Read(int16_t *dest, int samples) override; - virtual int Write(const int16_t *data, int samples) override; - -public: - Tcircles3AudioCodec(int input_sample_rate, int output_sample_rate, - gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, - gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, - bool input_reference); - virtual ~Tcircles3AudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - -#endif // _BOX_AUDIO_CODEC_H +#ifndef _TCIRCLES3_AUDIO_CODEC_H +#define _TCIRCLES3_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include + +class Tcircles3AudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t *data_if_ = nullptr; + const audio_codec_ctrl_if_t *out_ctrl_if_ = nullptr; + const audio_codec_if_t *out_codec_if_ = nullptr; + const audio_codec_ctrl_if_t *in_ctrl_if_ = nullptr; + const audio_codec_if_t *in_codec_if_ = nullptr; + const audio_codec_gpio_if_t *gpio_if_ = nullptr; + + uint32_t volume_ = 70; + + void CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data); + + virtual int Read(int16_t *dest, int samples) override; + virtual int Write(const int16_t *data, int samples) override; + +public: + Tcircles3AudioCodec(int input_sample_rate, int output_sample_rate, + gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, + bool input_reference); + virtual ~Tcircles3AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _BOX_AUDIO_CODEC_H diff --git a/main/boards/lilygo-t-display-s3-pro-mvsrlora/README.md b/main/boards/lilygo-t-display-s3-pro-mvsrlora/README.md index c3b8d0e..4cf190f 100644 --- a/main/boards/lilygo-t-display-s3-pro-mvsrlora/README.md +++ b/main/boards/lilygo-t-display-s3-pro-mvsrlora/README.md @@ -1,32 +1,32 @@ -# 编译配置命令 - -**配置编译目标为 ESP32S3:** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> LILYGO T-Display-S3-Pro-MVSRLora -Or -Xiaozhi Assistant -> Board Type -> LILYGO T-Display-S3-Pro-MVSRLora_NO_BATTERY -``` - - -**编译:** - -```bash -idf.py build -``` - -LILYGO T-Display-S3-Pro -
+# 编译配置命令 + +**配置编译目标为 ESP32S3:** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> LILYGO T-Display-S3-Pro-MVSRLora +Or +Xiaozhi Assistant -> Board Type -> LILYGO T-Display-S3-Pro-MVSRLora_NO_BATTERY +``` + + +**编译:** + +```bash +idf.py build +``` + +LILYGO T-Display-S3-Pro +
LILYGO T-Display-S3-Pro-MVSRLora \ No newline at end of file diff --git a/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.h b/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.h index 44c0a03..8d39925 100644 --- a/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.h +++ b/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.h @@ -1,47 +1,47 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include "pin_config.h" - -#define AUDIO_INPUT_REFERENCE true -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_MIC_I2S_GPIO_BCLK GPIO_NUM_NC -#define AUDIO_MIC_I2S_GPIO_WS static_cast(MP34DT05TR_LRCLK) -#define AUDIO_MIC_I2S_GPIO_DATA static_cast(MP34DT05TR_DATA) -#define AUDIO_MIC_ENABLE static_cast(MP34DT05TR_EN) - -#define AUDIO_SPKR_I2S_GPIO_BCLK static_cast(MAX98357A_BCLK) -#define AUDIO_SPKR_I2S_GPIO_LRCLK static_cast(MAX98357A_LRCLK) -#define AUDIO_SPKR_I2S_GPIO_DATA static_cast(MAX98357A_DATA) -#define AUDIO_SPKR_ENABLE static_cast(MAX98357A_EN) - -#define TOUCH_I2C_SDA_PIN static_cast(TOUCH_IIC_SDA) -#define TOUCH_I2C_SCL_PIN static_cast(TOUCH_IIC_SCL) - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_WIDTH LCD_WIDTH -#define DISPLAY_HEIGHT LCD_HEIGHT -#define DISPLAY_MOSI LCD_MOSI -#define DISPLAY_SCLK LCD_SCLK -#define DISPLAY_DC LCD_DC -#define DISPLAY_RST LCD_RST -#define DISPLAY_CS LCD_CS -#define DISPLAY_BL static_cast(LCD_BL) -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN DISPLAY_BL -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include "pin_config.h" + +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_MIC_I2S_GPIO_BCLK GPIO_NUM_NC +#define AUDIO_MIC_I2S_GPIO_WS static_cast(MP34DT05TR_LRCLK) +#define AUDIO_MIC_I2S_GPIO_DATA static_cast(MP34DT05TR_DATA) +#define AUDIO_MIC_ENABLE static_cast(MP34DT05TR_EN) + +#define AUDIO_SPKR_I2S_GPIO_BCLK static_cast(MAX98357A_BCLK) +#define AUDIO_SPKR_I2S_GPIO_LRCLK static_cast(MAX98357A_LRCLK) +#define AUDIO_SPKR_I2S_GPIO_DATA static_cast(MAX98357A_DATA) +#define AUDIO_SPKR_ENABLE static_cast(MAX98357A_EN) + +#define TOUCH_I2C_SDA_PIN static_cast(TOUCH_IIC_SDA) +#define TOUCH_I2C_SCL_PIN static_cast(TOUCH_IIC_SCL) + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH LCD_WIDTH +#define DISPLAY_HEIGHT LCD_HEIGHT +#define DISPLAY_MOSI LCD_MOSI +#define DISPLAY_SCLK LCD_SCLK +#define DISPLAY_DC LCD_DC +#define DISPLAY_RST LCD_RST +#define DISPLAY_CS LCD_CS +#define DISPLAY_BL static_cast(LCD_BL) +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN DISPLAY_BL +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.json b/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.json index f7e4505..f11b8c3 100644 --- a/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.json +++ b/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "lilygo-t-display-s3-pro-mvsrlora", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "lilygo-t-display-s3-pro-mvsrlora", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc b/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc index f0a4aed..056adc7 100644 --- a/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc +++ b/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc @@ -1,283 +1,283 @@ -#include "wifi_board.h" -#include "tdisplays3promvsrlora_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "i2c_device.h" - -#include -#include -#include -#include -#include -#include "esp_lcd_st7796.h" - -#define TAG "LilygoTDisplays3ProMVSRLoraBoard" - -class Cst2xxse : public I2cDevice { -public: - struct TouchPoint_t { - int num = 0; - int x = -1; - int y = -1; - }; - - Cst2xxse(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - uint8_t chip_id = ReadReg(0x06); - ESP_LOGI(TAG, "Get cst2xxse chip ID: 0x%02X", chip_id); - read_buffer_ = new uint8_t[6]; - } - - ~Cst2xxse() { - delete[] read_buffer_; - } - - void UpdateTouchPoint() { - ReadRegs(0x00, read_buffer_, 6); - tp_.num = read_buffer_[5] & 0x0F; - tp_.x = (static_cast(read_buffer_[1]) << 4) | (read_buffer_[3] & 0xF0); - tp_.y = (static_cast(read_buffer_[2]) << 4) | (read_buffer_[3] & 0x0F); - // ESP_LOGI(TAG, "Touch num: %d x: %d y: %d", tp_.num,tp_.x,tp_.y); - } - - const TouchPoint_t &GetTouchPoint() { - return tp_; - } - -private: - uint8_t *read_buffer_ = nullptr; - TouchPoint_t tp_; -}; - -class Sy6970 : public I2cDevice { -public: - - Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - uint8_t chip_id = ReadReg(0x14); - ESP_LOGI(TAG, "Get sy6970 chip ID: 0x%02X", (chip_id & 0B00111000)); - - WriteReg(0x00,0B00001000); // Disable ILIM pin - WriteReg(0x02,0B11011101); // Enable ADC measurement function - WriteReg(0x07,0B10001101); // Disable watchdog timer feeding function - - #ifdef CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA_NO_BATTERY - WriteReg(0x09,0B01100100); // Disable BATFET when battery is not needed - #endif - } -}; - -class LilygoTDisplays3ProMVSRLoraBoard : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Cst2xxse *cst226se_; - Sy6970 *sy6970_; - LcdDisplay *display_; - Button boot_button_; - PowerSaveTimer* power_save_timer_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(10); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitI2c(){ - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_config = { - .i2c_port = I2C_NUM_0, - .sda_io_num = TOUCH_I2C_SDA_PIN, - .scl_io_num = TOUCH_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - } - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &i2c_bus_)); - } - - void I2cDetect() { - uint8_t address; - printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); - for (int i = 0; i < 128; i += 16) { - printf("%02x: ", i); - for (int j = 0; j < 16; j++) { - fflush(stdout); - address = i + j; - esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); - if (ret == ESP_OK) { - printf("%02x ", address); - } else if (ret == ESP_ERR_TIMEOUT) { - printf("UU "); - } else { - printf("-- "); - } - } - printf("\r\n"); - } - } - - static void touchpad_daemon(void *param) { - vTaskDelay(pdMS_TO_TICKS(2000)); - auto &board = (LilygoTDisplays3ProMVSRLoraBoard&)Board::GetInstance(); - auto touchpad = board.GetTouchpad(); - bool was_touched = false; - while (1) { - touchpad->UpdateTouchPoint(); - if (touchpad->GetTouchPoint().num > 0){ - // On press - if (!was_touched) { - was_touched = true; - Application::GetInstance().ToggleChatState(); - } - } - // On release - else if (was_touched) { - was_touched = false; - } - vTaskDelay(pdMS_TO_TICKS(50)); - } - vTaskDelete(NULL); - } - - void InitCst226se() { - ESP_LOGI(TAG, "Init Cst2xxse"); - cst226se_ = new Cst2xxse(i2c_bus_, 0x5A); - xTaskCreate(touchpad_daemon, "tp", 4096, NULL, 5, NULL); - } - - void InitSy6970() { - ESP_LOGI(TAG, "Init Sy6970"); - sy6970_ = new Sy6970(i2c_bus_, 0x6A); - } - - void InitSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCLK; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitSt7796Display() { - ESP_LOGI(TAG, "Init St7796"); - - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS; - io_config.dc_gpio_num = DISPLAY_DC; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7796(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, false)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, true, false)); - ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 49, 0)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, - DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - - gpio_config_t config; - config.pin_bit_mask = BIT64(DISPLAY_BL); - config.mode = GPIO_MODE_OUTPUT; - config.pull_up_en = GPIO_PULLUP_DISABLE; - config.pull_down_en = GPIO_PULLDOWN_ENABLE; - config.intr_type = GPIO_INTR_DISABLE; -#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER - config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; -#endif - gpio_config(&config); - gpio_set_level(DISPLAY_BL, 0); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - power_save_timer_->WakeUp(); - app.ToggleChatState(); - }); - } - -public: - LilygoTDisplays3ProMVSRLoraBoard() : boot_button_(BOOT_BUTTON_GPIO) { - InitializePowerSaveTimer(); - InitI2c(); - I2cDetect(); - InitCst226se(); - InitSy6970(); - InitSpi(); - InitSt7796Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec *GetAudioCodec() override { - static Tdisplays3promvsrloraAudioCodec audio_codec( - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_MIC_I2S_GPIO_BCLK, - AUDIO_MIC_I2S_GPIO_WS, - AUDIO_MIC_I2S_GPIO_DATA, - AUDIO_SPKR_I2S_GPIO_BCLK, - AUDIO_SPKR_I2S_GPIO_LRCLK, - AUDIO_SPKR_I2S_GPIO_DATA, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display *GetDisplay() override{ - return display_; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - Cst2xxse *GetTouchpad() { - return cst226se_; - } -}; - -DECLARE_BOARD(LilygoTDisplays3ProMVSRLoraBoard); +#include "wifi_board.h" +#include "tdisplays3promvsrlora_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "i2c_device.h" + +#include +#include +#include +#include +#include +#include "esp_lcd_st7796.h" + +#define TAG "LilygoTDisplays3ProMVSRLoraBoard" + +class Cst2xxse : public I2cDevice { +public: + struct TouchPoint_t { + int num = 0; + int x = -1; + int y = -1; + }; + + Cst2xxse(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + uint8_t chip_id = ReadReg(0x06); + ESP_LOGI(TAG, "Get cst2xxse chip ID: 0x%02X", chip_id); + read_buffer_ = new uint8_t[6]; + } + + ~Cst2xxse() { + delete[] read_buffer_; + } + + void UpdateTouchPoint() { + ReadRegs(0x00, read_buffer_, 6); + tp_.num = read_buffer_[5] & 0x0F; + tp_.x = (static_cast(read_buffer_[1]) << 4) | (read_buffer_[3] & 0xF0); + tp_.y = (static_cast(read_buffer_[2]) << 4) | (read_buffer_[3] & 0x0F); + // ESP_LOGI(TAG, "Touch num: %d x: %d y: %d", tp_.num,tp_.x,tp_.y); + } + + const TouchPoint_t &GetTouchPoint() { + return tp_; + } + +private: + uint8_t *read_buffer_ = nullptr; + TouchPoint_t tp_; +}; + +class Sy6970 : public I2cDevice { +public: + + Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + uint8_t chip_id = ReadReg(0x14); + ESP_LOGI(TAG, "Get sy6970 chip ID: 0x%02X", (chip_id & 0B00111000)); + + WriteReg(0x00,0B00001000); // Disable ILIM pin + WriteReg(0x02,0B11011101); // Enable ADC measurement function + WriteReg(0x07,0B10001101); // Disable watchdog timer feeding function + + #ifdef CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA_NO_BATTERY + WriteReg(0x09,0B01100100); // Disable BATFET when battery is not needed + #endif + } +}; + +class LilygoTDisplays3ProMVSRLoraBoard : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Cst2xxse *cst226se_; + Sy6970 *sy6970_; + LcdDisplay *display_; + Button boot_button_; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(10); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitI2c(){ + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_config = { + .i2c_port = I2C_NUM_0, + .sda_io_num = TOUCH_I2C_SDA_PIN, + .scl_io_num = TOUCH_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + } + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &i2c_bus_)); + } + + void I2cDetect() { + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK) { + printf("%02x ", address); + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + } + + static void touchpad_daemon(void *param) { + vTaskDelay(pdMS_TO_TICKS(2000)); + auto &board = (LilygoTDisplays3ProMVSRLoraBoard&)Board::GetInstance(); + auto touchpad = board.GetTouchpad(); + bool was_touched = false; + while (1) { + touchpad->UpdateTouchPoint(); + if (touchpad->GetTouchPoint().num > 0){ + // On press + if (!was_touched) { + was_touched = true; + Application::GetInstance().ToggleChatState(); + } + } + // On release + else if (was_touched) { + was_touched = false; + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + vTaskDelete(NULL); + } + + void InitCst226se() { + ESP_LOGI(TAG, "Init Cst2xxse"); + cst226se_ = new Cst2xxse(i2c_bus_, 0x5A); + xTaskCreate(touchpad_daemon, "tp", 4096, NULL, 5, NULL); + } + + void InitSy6970() { + ESP_LOGI(TAG, "Init Sy6970"); + sy6970_ = new Sy6970(i2c_bus_, 0x6A); + } + + void InitSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCLK; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitSt7796Display() { + ESP_LOGI(TAG, "Init St7796"); + + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS; + io_config.dc_gpio_num = DISPLAY_DC; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7796(panel_io, &panel_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, false)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, true, false)); + ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 49, 0)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + + gpio_config_t config; + config.pin_bit_mask = BIT64(DISPLAY_BL); + config.mode = GPIO_MODE_OUTPUT; + config.pull_up_en = GPIO_PULLUP_DISABLE; + config.pull_down_en = GPIO_PULLDOWN_ENABLE; + config.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER + config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif + gpio_config(&config); + gpio_set_level(DISPLAY_BL, 0); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + power_save_timer_->WakeUp(); + app.ToggleChatState(); + }); + } + +public: + LilygoTDisplays3ProMVSRLoraBoard() : boot_button_(BOOT_BUTTON_GPIO) { + InitializePowerSaveTimer(); + InitI2c(); + I2cDetect(); + InitCst226se(); + InitSy6970(); + InitSpi(); + InitSt7796Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec *GetAudioCodec() override { + static Tdisplays3promvsrloraAudioCodec audio_codec( + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_MIC_I2S_GPIO_BCLK, + AUDIO_MIC_I2S_GPIO_WS, + AUDIO_MIC_I2S_GPIO_DATA, + AUDIO_SPKR_I2S_GPIO_BCLK, + AUDIO_SPKR_I2S_GPIO_LRCLK, + AUDIO_SPKR_I2S_GPIO_DATA, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display *GetDisplay() override{ + return display_; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + Cst2xxse *GetTouchpad() { + return cst226se_; + } +}; + +DECLARE_BOARD(LilygoTDisplays3ProMVSRLoraBoard); diff --git a/main/boards/lilygo-t-display-s3-pro-mvsrlora/pin_config.h b/main/boards/lilygo-t-display-s3-pro-mvsrlora/pin_config.h index 0b952d5..8246d9d 100644 --- a/main/boards/lilygo-t-display-s3-pro-mvsrlora/pin_config.h +++ b/main/boards/lilygo-t-display-s3-pro-mvsrlora/pin_config.h @@ -1,80 +1,80 @@ -/* - * @Description: None - * @Author: None - * @Date: 2023-08-16 14:24:03 - * @LastEditTime: 2025-04-23 11:25:00 - * @License: GPL 3.0 - */ -#pragma once - -// ST7796 -#define LCD_WIDTH 222 -#define LCD_HEIGHT 480 -#define LCD_BL 48 -#define LCD_MOSI 17 -#define LCD_MISO 8 -#define LCD_SCLK 18 -#define LCD_CS 39 -#define LCD_DC 9 -#define LCD_RST 47 - -// IIC -#define IIC_SDA 5 -#define IIC_SCL 6 - -// CST226SE -#define CST226SE_IIC_ADDRESS 0x5A -#define TOUCH_RST 13 -#define TOUCH_INT 21 -#define TOUCH_IIC_SDA IIC_SDA -#define TOUCH_IIC_SCL IIC_SCL - -// SY6970 -#define SY6970_SDA 5 -#define SY6970_SCL 6 -#define SY6970_Address 0x6A -#define SY6970_INT 21 - -// SD -#define SD_CS 14 -#define SD_MISO 8 -#define SD_MOSI 17 -#define SD_SCLK 18 - -// RT9080 -#define RT9080_EN 42 - -// MAX98357A -#define MAX98357A_BCLK 4 -#define MAX98357A_LRCLK 15 -#define MAX98357A_DATA 11 -#define MAX98357A_EN 41 - -// Vibration Motor -#define VIBRATINO_MOTOR_PWM 45 - -// PCF85063 -#define PCF85063_IIC_SDA 5 -#define PCF85063_IIC_SCL 6 -#define PCF85063_INT 21 - -// LR1121 -#define LR1121_BUSY 46 -#define LR1121_INT 40 -#define LR1121_SCLK 18 -#define LR1121_MOSI 17 -#define LR1121_MISO 8 -#define LR1121_CS 7 -#define LR1121_RST 10 - -// ICM20948 -#define ICM20948_ADDRESS 0x28 -#define ICM20948_SDA 5 -#define ICM20948_SCL 6 -#define ICM20948_INT 21 - -// MP34DT05TRF -#define MP34DT05TR_LRCLK 1 -#define MP34DT05TR_DATA 2 -#define MP34DT05TR_EN 3 - +/* + * @Description: None + * @Author: None + * @Date: 2023-08-16 14:24:03 + * @LastEditTime: 2025-04-23 11:25:00 + * @License: GPL 3.0 + */ +#pragma once + +// ST7796 +#define LCD_WIDTH 222 +#define LCD_HEIGHT 480 +#define LCD_BL 48 +#define LCD_MOSI 17 +#define LCD_MISO 8 +#define LCD_SCLK 18 +#define LCD_CS 39 +#define LCD_DC 9 +#define LCD_RST 47 + +// IIC +#define IIC_SDA 5 +#define IIC_SCL 6 + +// CST226SE +#define CST226SE_IIC_ADDRESS 0x5A +#define TOUCH_RST 13 +#define TOUCH_INT 21 +#define TOUCH_IIC_SDA IIC_SDA +#define TOUCH_IIC_SCL IIC_SCL + +// SY6970 +#define SY6970_SDA 5 +#define SY6970_SCL 6 +#define SY6970_Address 0x6A +#define SY6970_INT 21 + +// SD +#define SD_CS 14 +#define SD_MISO 8 +#define SD_MOSI 17 +#define SD_SCLK 18 + +// RT9080 +#define RT9080_EN 42 + +// MAX98357A +#define MAX98357A_BCLK 4 +#define MAX98357A_LRCLK 15 +#define MAX98357A_DATA 11 +#define MAX98357A_EN 41 + +// Vibration Motor +#define VIBRATINO_MOTOR_PWM 45 + +// PCF85063 +#define PCF85063_IIC_SDA 5 +#define PCF85063_IIC_SCL 6 +#define PCF85063_INT 21 + +// LR1121 +#define LR1121_BUSY 46 +#define LR1121_INT 40 +#define LR1121_SCLK 18 +#define LR1121_MOSI 17 +#define LR1121_MISO 8 +#define LR1121_CS 7 +#define LR1121_RST 10 + +// ICM20948 +#define ICM20948_ADDRESS 0x28 +#define ICM20948_SDA 5 +#define ICM20948_SCL 6 +#define ICM20948_INT 21 + +// MP34DT05TRF +#define MP34DT05TR_LRCLK 1 +#define MP34DT05TR_DATA 2 +#define MP34DT05TR_EN 3 + diff --git a/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.cc b/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.cc index 0beec45..11c08d2 100644 --- a/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.cc +++ b/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.cc @@ -1,150 +1,150 @@ -#include "tdisplays3promvsrlora_audio_codec.h" - -#include -#include -#include -#include - -#include "config.h" - -static const char TAG[] = "Tdisplays3promvsrloraAudioCodec"; - -Tdisplays3promvsrloraAudioCodec::Tdisplays3promvsrloraAudioCodec(int input_sample_rate, int output_sample_rate, - gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, - gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, - bool input_reference) { - duplex_ = true; // 是否双工 - input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 - input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - CreateVoiceHardware(mic_bclk, mic_ws, mic_data, spkr_bclk, spkr_lrclk, spkr_data); - - gpio_config_t config_mic_en; - config_mic_en.pin_bit_mask = BIT64(AUDIO_MIC_ENABLE); - config_mic_en.mode = GPIO_MODE_OUTPUT; - config_mic_en.pull_up_en = GPIO_PULLUP_ENABLE; - config_mic_en.pull_down_en = GPIO_PULLDOWN_DISABLE; - config_mic_en.intr_type = GPIO_INTR_DISABLE; -#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER -config_mic_en.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; -#endif - gpio_config(&config_mic_en); - gpio_set_level(AUDIO_MIC_ENABLE, 1); - - gpio_config_t config_spkr_en; - config_spkr_en.pin_bit_mask = BIT64(AUDIO_SPKR_ENABLE); - config_spkr_en.mode = GPIO_MODE_OUTPUT; - config_spkr_en.pull_up_en = GPIO_PULLUP_DISABLE; - config_spkr_en.pull_down_en = GPIO_PULLDOWN_ENABLE; - config_spkr_en.intr_type = GPIO_INTR_DISABLE; -#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER -config_spkr_en.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; -#endif - gpio_config(&config_spkr_en); - gpio_set_level(AUDIO_SPKR_ENABLE, 0); - - ESP_LOGI(TAG, "Tdisplays3promvsrloraAudioCodec initialized"); -} - -Tdisplays3promvsrloraAudioCodec::~Tdisplays3promvsrloraAudioCodec() { - audio_codec_delete_codec_if(in_codec_if_); - audio_codec_delete_ctrl_if(in_ctrl_if_); - audio_codec_delete_codec_if(out_codec_if_); - audio_codec_delete_ctrl_if(out_ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void Tdisplays3promvsrloraAudioCodec::CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, - gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data) { - - i2s_chan_config_t mic_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(0), I2S_ROLE_MASTER); - mic_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer - i2s_chan_config_t spkr_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(1), I2S_ROLE_MASTER); - spkr_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer - - ESP_ERROR_CHECK(i2s_new_channel(&mic_chan_config, NULL, &rx_handle_)); - ESP_ERROR_CHECK(i2s_new_channel(&spkr_chan_config, &tx_handle_, NULL)); - - i2s_pdm_rx_config_t mic_config = { - .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(static_cast(input_sample_rate_)), - /* The data bit-width of PDM mode is fixed to 16 */ - .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), - .gpio_cfg = { - .clk = mic_ws, - .din = mic_data, - .invert_flags = { - .clk_inv = false, - }, - }, - }; - - i2s_std_config_t spkr_config = { - .clk_cfg ={ - .sample_rate_hz = static_cast(11025), - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - }, - .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), - .gpio_cfg ={ - .mclk = I2S_GPIO_UNUSED, - .bclk = spkr_bclk, - .ws = spkr_lrclk, - .dout = spkr_data, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &mic_config)); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &spkr_config)); - ESP_LOGI(TAG, "Voice hardware created"); -} - -void Tdisplays3promvsrloraAudioCodec::SetOutputVolume(int volume) { - volume_ = volume; - AudioCodec::SetOutputVolume(volume); -} - -void Tdisplays3promvsrloraAudioCodec::EnableInput(bool enable) { - gpio_set_level(AUDIO_MIC_ENABLE, !enable); - AudioCodec::EnableInput(enable); -} - -void Tdisplays3promvsrloraAudioCodec::EnableOutput(bool enable) { - gpio_set_level(AUDIO_SPKR_ENABLE, enable); - AudioCodec::EnableOutput(enable); -} - -int Tdisplays3promvsrloraAudioCodec::Read(int16_t *dest, int samples){ - if (input_enabled_){ - size_t bytes_read; - i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); - - // ESP_LOGI(TAG, "Left: %d\n", dest[0]); - // ESP_LOGI(TAG,"Right: %d\n", dest[1]); - } - return samples; -} - -int Tdisplays3promvsrloraAudioCodec::Write(const int16_t *data, int samples){ - if (output_enabled_){ - size_t bytes_read; - auto output_data = (int16_t *)malloc(samples * sizeof(int16_t)); - for (size_t i = 0; i < samples; i++){ - output_data[i] = (float)data[i] * (float)(volume_ / 100.0); - } - i2s_channel_write(tx_handle_, output_data, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); - free(output_data); - } - return samples; -} +#include "tdisplays3promvsrlora_audio_codec.h" + +#include +#include +#include +#include + +#include "config.h" + +static const char TAG[] = "Tdisplays3promvsrloraAudioCodec"; + +Tdisplays3promvsrloraAudioCodec::Tdisplays3promvsrloraAudioCodec(int input_sample_rate, int output_sample_rate, + gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, + bool input_reference) { + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + CreateVoiceHardware(mic_bclk, mic_ws, mic_data, spkr_bclk, spkr_lrclk, spkr_data); + + gpio_config_t config_mic_en; + config_mic_en.pin_bit_mask = BIT64(AUDIO_MIC_ENABLE); + config_mic_en.mode = GPIO_MODE_OUTPUT; + config_mic_en.pull_up_en = GPIO_PULLUP_ENABLE; + config_mic_en.pull_down_en = GPIO_PULLDOWN_DISABLE; + config_mic_en.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER +config_mic_en.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif + gpio_config(&config_mic_en); + gpio_set_level(AUDIO_MIC_ENABLE, 1); + + gpio_config_t config_spkr_en; + config_spkr_en.pin_bit_mask = BIT64(AUDIO_SPKR_ENABLE); + config_spkr_en.mode = GPIO_MODE_OUTPUT; + config_spkr_en.pull_up_en = GPIO_PULLUP_DISABLE; + config_spkr_en.pull_down_en = GPIO_PULLDOWN_ENABLE; + config_spkr_en.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER +config_spkr_en.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif + gpio_config(&config_spkr_en); + gpio_set_level(AUDIO_SPKR_ENABLE, 0); + + ESP_LOGI(TAG, "Tdisplays3promvsrloraAudioCodec initialized"); +} + +Tdisplays3promvsrloraAudioCodec::~Tdisplays3promvsrloraAudioCodec() { + audio_codec_delete_codec_if(in_codec_if_); + audio_codec_delete_ctrl_if(in_ctrl_if_); + audio_codec_delete_codec_if(out_codec_if_); + audio_codec_delete_ctrl_if(out_ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void Tdisplays3promvsrloraAudioCodec::CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data) { + + i2s_chan_config_t mic_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(0), I2S_ROLE_MASTER); + mic_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer + i2s_chan_config_t spkr_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(1), I2S_ROLE_MASTER); + spkr_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer + + ESP_ERROR_CHECK(i2s_new_channel(&mic_chan_config, NULL, &rx_handle_)); + ESP_ERROR_CHECK(i2s_new_channel(&spkr_chan_config, &tx_handle_, NULL)); + + i2s_pdm_rx_config_t mic_config = { + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(static_cast(input_sample_rate_)), + /* The data bit-width of PDM mode is fixed to 16 */ + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = { + .clk = mic_ws, + .din = mic_data, + .invert_flags = { + .clk_inv = false, + }, + }, + }; + + i2s_std_config_t spkr_config = { + .clk_cfg ={ + .sample_rate_hz = static_cast(11025), + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + }, + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg ={ + .mclk = I2S_GPIO_UNUSED, + .bclk = spkr_bclk, + .ws = spkr_lrclk, + .dout = spkr_data, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &mic_config)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &spkr_config)); + ESP_LOGI(TAG, "Voice hardware created"); +} + +void Tdisplays3promvsrloraAudioCodec::SetOutputVolume(int volume) { + volume_ = volume; + AudioCodec::SetOutputVolume(volume); +} + +void Tdisplays3promvsrloraAudioCodec::EnableInput(bool enable) { + gpio_set_level(AUDIO_MIC_ENABLE, !enable); + AudioCodec::EnableInput(enable); +} + +void Tdisplays3promvsrloraAudioCodec::EnableOutput(bool enable) { + gpio_set_level(AUDIO_SPKR_ENABLE, enable); + AudioCodec::EnableOutput(enable); +} + +int Tdisplays3promvsrloraAudioCodec::Read(int16_t *dest, int samples){ + if (input_enabled_){ + size_t bytes_read; + i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); + + // ESP_LOGI(TAG, "Left: %d\n", dest[0]); + // ESP_LOGI(TAG,"Right: %d\n", dest[1]); + } + return samples; +} + +int Tdisplays3promvsrloraAudioCodec::Write(const int16_t *data, int samples){ + if (output_enabled_){ + size_t bytes_read; + auto output_data = (int16_t *)malloc(samples * sizeof(int16_t)); + for (size_t i = 0; i < samples; i++){ + output_data[i] = (float)data[i] * (float)(volume_ / 100.0); + } + i2s_channel_write(tx_handle_, output_data, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); + free(output_data); + } + return samples; +} diff --git a/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h b/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h index 6612b96..1a7a619 100644 --- a/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h +++ b/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h @@ -1,37 +1,37 @@ -#ifndef _TDISPLAYS3PROMVSRLORA_AUDIO_CODEC_H -#define _TDISPLAYS3PROMVSRLORA_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include - -class Tdisplays3promvsrloraAudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t *data_if_ = nullptr; - const audio_codec_ctrl_if_t *out_ctrl_if_ = nullptr; - const audio_codec_if_t *out_codec_if_ = nullptr; - const audio_codec_ctrl_if_t *in_ctrl_if_ = nullptr; - const audio_codec_if_t *in_codec_if_ = nullptr; - const audio_codec_gpio_if_t *gpio_if_ = nullptr; - - uint32_t volume_ = 70; - - void CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data); - - virtual int Read(int16_t *dest, int samples) override; - virtual int Write(const int16_t *data, int samples) override; - -public: -Tdisplays3promvsrloraAudioCodec(int input_sample_rate, int output_sample_rate, - gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, - gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, - bool input_reference); - virtual ~Tdisplays3promvsrloraAudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - -#endif // _BOX_AUDIO_CODEC_H +#ifndef _TDISPLAYS3PROMVSRLORA_AUDIO_CODEC_H +#define _TDISPLAYS3PROMVSRLORA_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include + +class Tdisplays3promvsrloraAudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t *data_if_ = nullptr; + const audio_codec_ctrl_if_t *out_ctrl_if_ = nullptr; + const audio_codec_if_t *out_codec_if_ = nullptr; + const audio_codec_ctrl_if_t *in_ctrl_if_ = nullptr; + const audio_codec_if_t *in_codec_if_ = nullptr; + const audio_codec_gpio_if_t *gpio_if_ = nullptr; + + uint32_t volume_ = 70; + + void CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data); + + virtual int Read(int16_t *dest, int samples) override; + virtual int Write(const int16_t *data, int samples) override; + +public: +Tdisplays3promvsrloraAudioCodec(int input_sample_rate, int output_sample_rate, + gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, + bool input_reference); + virtual ~Tdisplays3promvsrloraAudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _BOX_AUDIO_CODEC_H diff --git a/main/boards/m5stack-core-s3/config.h b/main/boards/m5stack-core-s3/config.h index e003782..0949df9 100644 --- a/main/boards/m5stack-core-s3/config.h +++ b/main/boards/m5stack-core-s3/config.h @@ -1,66 +1,66 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -// M5Stack CoreS3 Board configuration - -#include - -#define AUDIO_INPUT_REFERENCE true -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_0 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_33 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_34 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_13 - -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_12 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_11 -#define AUDIO_CODEC_AW88298_ADDR AW88298_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SDA_PIN GPIO_NUM_NC -#define DISPLAY_SCL_PIN GPIO_NUM_NC -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - - - -/* Camera pins */ -#define CAMERA_PIN_PWDN GPIO_NUM_NC -#define CAMERA_PIN_RESET GPIO_NUM_NC -#define CAMERA_PIN_XCLK GPIO_NUM_NC // 像素时钟 (固定由 20MHz 外部晶振输入) -#define CAMERA_PIN_SIOD GPIO_NUM_NC // 串行时钟 Using existing I2C port -#define CAMERA_PIN_SIOC GPIO_NUM_NC // 串行时钟 Using existing I2C port -#define CAMERA_PIN_D0 GPIO_NUM_39 -#define CAMERA_PIN_D1 GPIO_NUM_40 -#define CAMERA_PIN_D2 GPIO_NUM_41 -#define CAMERA_PIN_D3 GPIO_NUM_42 -#define CAMERA_PIN_D4 GPIO_NUM_15 -#define CAMERA_PIN_D5 GPIO_NUM_16 -#define CAMERA_PIN_D6 GPIO_NUM_48 -#define CAMERA_PIN_D7 GPIO_NUM_47 -#define CAMERA_PIN_VSYNC GPIO_NUM_46 -#define CAMERA_PIN_HREF GPIO_NUM_38 -#define CAMERA_PIN_PCLK GPIO_NUM_45 - -#define XCLK_FREQ_HZ 20000000 - - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// M5Stack CoreS3 Board configuration + +#include + +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_0 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_33 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_34 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_13 + +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_12 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_11 +#define AUDIO_CODEC_AW88298_ADDR AW88298_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SDA_PIN GPIO_NUM_NC +#define DISPLAY_SCL_PIN GPIO_NUM_NC +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + + + +/* Camera pins */ +#define CAMERA_PIN_PWDN GPIO_NUM_NC +#define CAMERA_PIN_RESET GPIO_NUM_NC +#define CAMERA_PIN_XCLK GPIO_NUM_NC // 像素时钟 (固定由 20MHz 外部晶振输入) +#define CAMERA_PIN_SIOD GPIO_NUM_NC // 串行时钟 Using existing I2C port +#define CAMERA_PIN_SIOC GPIO_NUM_NC // 串行时钟 Using existing I2C port +#define CAMERA_PIN_D0 GPIO_NUM_39 +#define CAMERA_PIN_D1 GPIO_NUM_40 +#define CAMERA_PIN_D2 GPIO_NUM_41 +#define CAMERA_PIN_D3 GPIO_NUM_42 +#define CAMERA_PIN_D4 GPIO_NUM_15 +#define CAMERA_PIN_D5 GPIO_NUM_16 +#define CAMERA_PIN_D6 GPIO_NUM_48 +#define CAMERA_PIN_D7 GPIO_NUM_47 +#define CAMERA_PIN_VSYNC GPIO_NUM_46 +#define CAMERA_PIN_HREF GPIO_NUM_38 +#define CAMERA_PIN_PCLK GPIO_NUM_45 + +#define XCLK_FREQ_HZ 20000000 + + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/m5stack-core-s3/config.json b/main/boards/m5stack-core-s3/config.json index bec9ad0..eb2d29a 100644 --- a/main/boards/m5stack-core-s3/config.json +++ b/main/boards/m5stack-core-s3/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "m5stack-core-s3", - "sdkconfig_append": [ - "CONFIG_SPIRAM_MODE_QUAD=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "m5stack-core-s3", + "sdkconfig_append": [ + "CONFIG_SPIRAM_MODE_QUAD=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/m5stack-core-s3/cores3_audio_codec.cc b/main/boards/m5stack-core-s3/cores3_audio_codec.cc index ea13277..c56839c 100644 --- a/main/boards/m5stack-core-s3/cores3_audio_codec.cc +++ b/main/boards/m5stack-core-s3/cores3_audio_codec.cc @@ -1,243 +1,243 @@ -#include "cores3_audio_codec.h" - -#include -#include -#include - -#define TAG "CoreS3AudioCodec" - -CoreS3AudioCodec::CoreS3AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - uint8_t aw88298_addr, uint8_t es7210_addr, bool input_reference) { - duplex_ = true; // 是否双工 - input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 - input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - CreateDuplexChannels(mclk, bclk, ws, dout, din); - - // Do initialize of related interface: data_if, ctrl_if and gpio_if - audio_codec_i2s_cfg_t i2s_cfg = { - .port = I2S_NUM_0, - .rx_handle = rx_handle_, - .tx_handle = tx_handle_, - }; - data_if_ = audio_codec_new_i2s_data(&i2s_cfg); - assert(data_if_ != NULL); - - // Audio Output(Speaker) - audio_codec_i2c_cfg_t i2c_cfg = { - .port = (i2c_port_t)1, - .addr = aw88298_addr, - .bus_handle = i2c_master_handle, - }; - out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(out_ctrl_if_ != NULL); - - gpio_if_ = audio_codec_new_gpio(); - assert(gpio_if_ != NULL); - - aw88298_codec_cfg_t aw88298_cfg = {}; - aw88298_cfg.ctrl_if = out_ctrl_if_; - aw88298_cfg.gpio_if = gpio_if_; - aw88298_cfg.reset_pin = GPIO_NUM_NC; - aw88298_cfg.hw_gain.pa_voltage = 5.0; - aw88298_cfg.hw_gain.codec_dac_voltage = 3.3; - aw88298_cfg.hw_gain.pa_gain = 1; - out_codec_if_ = aw88298_codec_new(&aw88298_cfg); - assert(out_codec_if_ != NULL); - - esp_codec_dev_cfg_t dev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_OUT, - .codec_if = out_codec_if_, - .data_if = data_if_, - }; - output_dev_ = esp_codec_dev_new(&dev_cfg); - assert(output_dev_ != NULL); - - // Audio Input(Microphone) - i2c_cfg.addr = es7210_addr; - in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(in_ctrl_if_ != NULL); - - es7210_codec_cfg_t es7210_cfg = {}; - es7210_cfg.ctrl_if = in_ctrl_if_; - es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3; - in_codec_if_ = es7210_codec_new(&es7210_cfg); - assert(in_codec_if_ != NULL); - - dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; - dev_cfg.codec_if = in_codec_if_; - input_dev_ = esp_codec_dev_new(&dev_cfg); - assert(input_dev_ != NULL); - - ESP_LOGI(TAG, "CoreS3AudioCodec initialized"); -} - -CoreS3AudioCodec::~CoreS3AudioCodec() { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - esp_codec_dev_delete(output_dev_); - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - esp_codec_dev_delete(input_dev_); - - audio_codec_delete_codec_if(in_codec_if_); - audio_codec_delete_ctrl_if(in_ctrl_if_); - audio_codec_delete_codec_if(out_codec_if_); - audio_codec_delete_ctrl_if(out_ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void CoreS3AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { - assert(input_sample_rate_ == output_sample_rate_); - - ESP_LOGI(TAG, "Audio IOs: mclk: %d, bclk: %d, ws: %d, dout: %d, din: %d", mclk, bclk, ws, dout, din); - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, - .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256 - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_16BIT, - .ws_pol = false, - .bit_shift = true, - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - i2s_tdm_config_t tdm_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)input_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - .bclk_div = 8, - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), - .ws_width = I2S_TDM_AUTO_WS_WIDTH, - .ws_pol = false, - .bit_shift = true, - .left_align = false, - .big_endian = false, - .bit_order_lsb = false, - .skip_mask = false, - .total_slot = I2S_TDM_AUTO_SLOT_NUM - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = I2S_GPIO_UNUSED, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle_, &tdm_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); -} - -void CoreS3AudioCodec::SetOutputVolume(int volume) { - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); - AudioCodec::SetOutputVolume(volume); -} - -void CoreS3AudioCodec::EnableInput(bool enable) { - if (enable == input_enabled_) { - return; - } - if (enable) { - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 2, - .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - if (input_reference_) { - fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1); - } - ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), AUDIO_CODEC_DEFAULT_MIC_GAIN)); - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - } - AudioCodec::EnableInput(enable); -} - -void CoreS3AudioCodec::EnableOutput(bool enable) { - if (enable == output_enabled_) { - return; - } - if (enable) { - // Play 16bit 1 channel - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - } - AudioCodec::EnableOutput(enable); -} - -int CoreS3AudioCodec::Read(int16_t* dest, int samples) { - if (input_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); - } - return samples; -} - -int CoreS3AudioCodec::Write(const int16_t* data, int samples) { - if (output_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); - } - return samples; -} +#include "cores3_audio_codec.h" + +#include +#include +#include + +#define TAG "CoreS3AudioCodec" + +CoreS3AudioCodec::CoreS3AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + uint8_t aw88298_addr, uint8_t es7210_addr, bool input_reference) { + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + CreateDuplexChannels(mclk, bclk, ws, dout, din); + + // Do initialize of related interface: data_if, ctrl_if and gpio_if + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = rx_handle_, + .tx_handle = tx_handle_, + }; + data_if_ = audio_codec_new_i2s_data(&i2s_cfg); + assert(data_if_ != NULL); + + // Audio Output(Speaker) + audio_codec_i2c_cfg_t i2c_cfg = { + .port = (i2c_port_t)1, + .addr = aw88298_addr, + .bus_handle = i2c_master_handle, + }; + out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(out_ctrl_if_ != NULL); + + gpio_if_ = audio_codec_new_gpio(); + assert(gpio_if_ != NULL); + + aw88298_codec_cfg_t aw88298_cfg = {}; + aw88298_cfg.ctrl_if = out_ctrl_if_; + aw88298_cfg.gpio_if = gpio_if_; + aw88298_cfg.reset_pin = GPIO_NUM_NC; + aw88298_cfg.hw_gain.pa_voltage = 5.0; + aw88298_cfg.hw_gain.codec_dac_voltage = 3.3; + aw88298_cfg.hw_gain.pa_gain = 1; + out_codec_if_ = aw88298_codec_new(&aw88298_cfg); + assert(out_codec_if_ != NULL); + + esp_codec_dev_cfg_t dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_OUT, + .codec_if = out_codec_if_, + .data_if = data_if_, + }; + output_dev_ = esp_codec_dev_new(&dev_cfg); + assert(output_dev_ != NULL); + + // Audio Input(Microphone) + i2c_cfg.addr = es7210_addr; + in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(in_ctrl_if_ != NULL); + + es7210_codec_cfg_t es7210_cfg = {}; + es7210_cfg.ctrl_if = in_ctrl_if_; + es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3; + in_codec_if_ = es7210_codec_new(&es7210_cfg); + assert(in_codec_if_ != NULL); + + dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; + dev_cfg.codec_if = in_codec_if_; + input_dev_ = esp_codec_dev_new(&dev_cfg); + assert(input_dev_ != NULL); + + ESP_LOGI(TAG, "CoreS3AudioCodec initialized"); +} + +CoreS3AudioCodec::~CoreS3AudioCodec() { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + esp_codec_dev_delete(output_dev_); + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + esp_codec_dev_delete(input_dev_); + + audio_codec_delete_codec_if(in_codec_if_); + audio_codec_delete_ctrl_if(in_ctrl_if_); + audio_codec_delete_codec_if(out_codec_if_); + audio_codec_delete_ctrl_if(out_ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void CoreS3AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + assert(input_sample_rate_ == output_sample_rate_); + + ESP_LOGI(TAG, "Audio IOs: mclk: %d, bclk: %d, ws: %d, dout: %d, din: %d", mclk, bclk, ws, dout, din); + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, + .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256 + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + i2s_tdm_config_t tdm_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)input_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + .bclk_div = 8, + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), + .ws_width = I2S_TDM_AUTO_WS_WIDTH, + .ws_pol = false, + .bit_shift = true, + .left_align = false, + .big_endian = false, + .bit_order_lsb = false, + .skip_mask = false, + .total_slot = I2S_TDM_AUTO_SLOT_NUM + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = I2S_GPIO_UNUSED, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle_, &tdm_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + +void CoreS3AudioCodec::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void CoreS3AudioCodec::EnableInput(bool enable) { + if (enable == input_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 2, + .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + if (input_reference_) { + fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1); + } + ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), AUDIO_CODEC_DEFAULT_MIC_GAIN)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void CoreS3AudioCodec::EnableOutput(bool enable) { + if (enable == output_enabled_) { + return; + } + if (enable) { + // Play 16bit 1 channel + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + } + AudioCodec::EnableOutput(enable); +} + +int CoreS3AudioCodec::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} + +int CoreS3AudioCodec::Write(const int16_t* data, int samples) { + if (output_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); + } + return samples; +} diff --git a/main/boards/m5stack-core-s3/cores3_audio_codec.h b/main/boards/m5stack-core-s3/cores3_audio_codec.h index 4b034b4..03453bc 100644 --- a/main/boards/m5stack-core-s3/cores3_audio_codec.h +++ b/main/boards/m5stack-core-s3/cores3_audio_codec.h @@ -1,37 +1,37 @@ -#ifndef _BOX_AUDIO_CODEC_H -#define _BOX_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include - -class CoreS3AudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t* data_if_ = nullptr; - const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; - const audio_codec_if_t* out_codec_if_ = nullptr; - const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; - const audio_codec_if_t* in_codec_if_ = nullptr; - const audio_codec_gpio_if_t* gpio_if_ = nullptr; - - esp_codec_dev_handle_t output_dev_ = nullptr; - esp_codec_dev_handle_t input_dev_ = nullptr; - - void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); - - virtual int Read(int16_t* dest, int samples) override; - virtual int Write(const int16_t* data, int samples) override; - -public: - CoreS3AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - uint8_t aw88298_addr, uint8_t es7210_addr, bool input_reference); - virtual ~CoreS3AudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - -#endif // _BOX_AUDIO_CODEC_H +#ifndef _BOX_AUDIO_CODEC_H +#define _BOX_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include + +class CoreS3AudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t* data_if_ = nullptr; + const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; + const audio_codec_if_t* out_codec_if_ = nullptr; + const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; + const audio_codec_if_t* in_codec_if_ = nullptr; + const audio_codec_gpio_if_t* gpio_if_ = nullptr; + + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + + void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + CoreS3AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + uint8_t aw88298_addr, uint8_t es7210_addr, bool input_reference); + virtual ~CoreS3AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _BOX_AUDIO_CODEC_H diff --git a/main/boards/m5stack-core-s3/m5stack_core_s3.cc b/main/boards/m5stack-core-s3/m5stack_core_s3.cc index fd42ed9..18ea23f 100644 --- a/main/boards/m5stack-core-s3/m5stack_core_s3.cc +++ b/main/boards/m5stack-core-s3/m5stack_core_s3.cc @@ -1,386 +1,386 @@ -#include "wifi_board.h" -#include "cores3_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "config.h" -#include "power_save_timer.h" -#include "i2c_device.h" -#include "axp2101.h" - -#include -#include -#include -#include -#include -#include -#include -#include "esp32_camera.h" - -#define TAG "M5StackCoreS3Board" - -class Pmic : public Axp2101 { -public: - // Power Init - Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { - uint8_t data = ReadReg(0x90); - data |= 0b10110100; - WriteReg(0x90, data); - WriteReg(0x99, (0b11110 - 5)); - WriteReg(0x97, (0b11110 - 2)); - WriteReg(0x69, 0b00110101); - WriteReg(0x30, 0b111111); - WriteReg(0x90, 0xBF); - WriteReg(0x94, 33 - 5); - WriteReg(0x95, 33 - 5); - } - - void SetBrightness(uint8_t brightness) { - brightness = ((brightness + 641) >> 5); - WriteReg(0x99, brightness); - } -}; - -class CustomBacklight : public Backlight { -public: - CustomBacklight(Pmic *pmic) : pmic_(pmic) {} - - void SetBrightnessImpl(uint8_t brightness) override { - pmic_->SetBrightness(target_brightness_); - brightness_ = target_brightness_; - } - -private: - Pmic *pmic_; -}; - -class Aw9523 : public I2cDevice { -public: - // Exanpd IO Init - Aw9523(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - WriteReg(0x02, 0b00000111); // P0 - WriteReg(0x03, 0b10001111); // P1 - WriteReg(0x04, 0b00011000); // CONFIG_P0 - WriteReg(0x05, 0b00001100); // CONFIG_P1 - WriteReg(0x11, 0b00010000); // GCR P0 port is Push-Pull mode. - WriteReg(0x12, 0b11111111); // LEDMODE_P0 - WriteReg(0x13, 0b11111111); // LEDMODE_P1 - } - - void ResetAw88298() { - ESP_LOGI(TAG, "Reset AW88298"); - WriteReg(0x02, 0b00000011); - vTaskDelay(pdMS_TO_TICKS(10)); - WriteReg(0x02, 0b00000111); - vTaskDelay(pdMS_TO_TICKS(50)); - } - - void ResetIli9342() { - ESP_LOGI(TAG, "Reset IlI9342"); - WriteReg(0x03, 0b10000001); - vTaskDelay(pdMS_TO_TICKS(20)); - WriteReg(0x03, 0b10000011); - vTaskDelay(pdMS_TO_TICKS(10)); - } -}; - -class Ft6336 : public I2cDevice { -public: - struct TouchPoint_t { - int num = 0; - int x = -1; - int y = -1; - }; - - Ft6336(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - uint8_t chip_id = ReadReg(0xA3); - ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); - read_buffer_ = new uint8_t[6]; - } - - ~Ft6336() { - delete[] read_buffer_; - } - - void UpdateTouchPoint() { - ReadRegs(0x02, read_buffer_, 6); - tp_.num = read_buffer_[0] & 0x0F; - tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; - tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; - } - - inline const TouchPoint_t& GetTouchPoint() { - return tp_; - } - -private: - uint8_t* read_buffer_ = nullptr; - TouchPoint_t tp_; -}; - -class M5StackCoreS3Board : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Pmic* pmic_; - Aw9523* aw9523_; - Ft6336* ft6336_; - LcdDisplay* display_; - Esp32Camera* camera_; - esp_timer_handle_t touchpad_timer_; - PowerSaveTimer* power_save_timer_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(10); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - pmic_->PowerOff(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void I2cDetect() { - uint8_t address; - printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); - for (int i = 0; i < 128; i += 16) { - printf("%02x: ", i); - for (int j = 0; j < 16; j++) { - fflush(stdout); - address = i + j; - esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); - if (ret == ESP_OK) { - printf("%02x ", address); - } else if (ret == ESP_ERR_TIMEOUT) { - printf("UU "); - } else { - printf("-- "); - } - } - printf("\r\n"); - } - } - - void InitializeAxp2101() { - ESP_LOGI(TAG, "Init AXP2101"); - pmic_ = new Pmic(i2c_bus_, 0x34); - } - - void InitializeAw9523() { - ESP_LOGI(TAG, "Init AW9523"); - aw9523_ = new Aw9523(i2c_bus_, 0x58); - vTaskDelay(pdMS_TO_TICKS(50)); - } - - void PollTouchpad() { - static bool was_touched = false; - static int64_t touch_start_time = 0; - const int64_t TOUCH_THRESHOLD_MS = 500; // 触摸时长阈值,超过500ms视为长按 - - ft6336_->UpdateTouchPoint(); - auto& touch_point = ft6336_->GetTouchPoint(); - - // 检测触摸开始 - if (touch_point.num > 0 && !was_touched) { - was_touched = true; - touch_start_time = esp_timer_get_time() / 1000; // 转换为毫秒 - } - // 检测触摸释放 - else if (touch_point.num == 0 && was_touched) { - was_touched = false; - int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time; - - // 只有短触才触发 - if (touch_duration < TOUCH_THRESHOLD_MS) { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - } - } - } - - void InitializeFt6336TouchPad() { - ESP_LOGI(TAG, "Init FT6336"); - ft6336_ = new Ft6336(i2c_bus_, 0x38); - - // 创建定时器,20ms 间隔 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - M5StackCoreS3Board* board = (M5StackCoreS3Board*)arg; - board->PollTouchpad(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "touchpad_timer", - .skip_unhandled_events = true, - }; - - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 20 * 1000)); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_37; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_36; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeIli9342Display() { - ESP_LOGI(TAG, "Init IlI9342"); - - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_3; - io_config.dc_gpio_num = GPIO_NUM_35; - io_config.spi_mode = 2; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - aw9523_->ResetIli9342(); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, true); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeCamera() { - // Open camera power - camera_config_t config = {}; - config.pin_d0 = CAMERA_PIN_D0; - config.pin_d1 = CAMERA_PIN_D1; - config.pin_d2 = CAMERA_PIN_D2; - config.pin_d3 = CAMERA_PIN_D3; - config.pin_d4 = CAMERA_PIN_D4; - config.pin_d5 = CAMERA_PIN_D5; - config.pin_d6 = CAMERA_PIN_D6; - config.pin_d7 = CAMERA_PIN_D7; - config.pin_xclk = CAMERA_PIN_XCLK; - config.pin_pclk = CAMERA_PIN_PCLK; - config.pin_vsync = CAMERA_PIN_VSYNC; - config.pin_href = CAMERA_PIN_HREF; - config.pin_sccb_sda = CAMERA_PIN_SIOD; - config.pin_sccb_scl = CAMERA_PIN_SIOC; - config.sccb_i2c_port = 1; - config.pin_pwdn = CAMERA_PIN_PWDN; - config.pin_reset = CAMERA_PIN_RESET; - config.xclk_freq_hz = XCLK_FREQ_HZ; - config.pixel_format = PIXFORMAT_RGB565; - config.frame_size = FRAMESIZE_QVGA; - config.jpeg_quality = 12; - config.fb_count = 1; - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - camera_ = new Esp32Camera(config); - } - -public: - M5StackCoreS3Board() { - InitializePowerSaveTimer(); - InitializeI2c(); - InitializeAxp2101(); - InitializeAw9523(); - I2cDetect(); - InitializeSpi(); - InitializeIli9342Display(); - InitializeCamera(); - InitializeFt6336TouchPad(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static CoreS3AudioCodec audio_codec(i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_AW88298_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Camera* GetCamera() override { - return camera_; - } - - virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = pmic_->IsCharging(); - discharging = pmic_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - - level = pmic_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } - - virtual Backlight *GetBacklight() override { - static CustomBacklight backlight(pmic_); - return &backlight; - } -}; - -DECLARE_BOARD(M5StackCoreS3Board); +#include "wifi_board.h" +#include "cores3_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "config.h" +#include "power_save_timer.h" +#include "i2c_device.h" +#include "axp2101.h" + +#include +#include +#include +#include +#include +#include +#include +#include "esp32_camera.h" + +#define TAG "M5StackCoreS3Board" + +class Pmic : public Axp2101 { +public: + // Power Init + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + uint8_t data = ReadReg(0x90); + data |= 0b10110100; + WriteReg(0x90, data); + WriteReg(0x99, (0b11110 - 5)); + WriteReg(0x97, (0b11110 - 2)); + WriteReg(0x69, 0b00110101); + WriteReg(0x30, 0b111111); + WriteReg(0x90, 0xBF); + WriteReg(0x94, 33 - 5); + WriteReg(0x95, 33 - 5); + } + + void SetBrightness(uint8_t brightness) { + brightness = ((brightness + 641) >> 5); + WriteReg(0x99, brightness); + } +}; + +class CustomBacklight : public Backlight { +public: + CustomBacklight(Pmic *pmic) : pmic_(pmic) {} + + void SetBrightnessImpl(uint8_t brightness) override { + pmic_->SetBrightness(target_brightness_); + brightness_ = target_brightness_; + } + +private: + Pmic *pmic_; +}; + +class Aw9523 : public I2cDevice { +public: + // Exanpd IO Init + Aw9523(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + WriteReg(0x02, 0b00000111); // P0 + WriteReg(0x03, 0b10001111); // P1 + WriteReg(0x04, 0b00011000); // CONFIG_P0 + WriteReg(0x05, 0b00001100); // CONFIG_P1 + WriteReg(0x11, 0b00010000); // GCR P0 port is Push-Pull mode. + WriteReg(0x12, 0b11111111); // LEDMODE_P0 + WriteReg(0x13, 0b11111111); // LEDMODE_P1 + } + + void ResetAw88298() { + ESP_LOGI(TAG, "Reset AW88298"); + WriteReg(0x02, 0b00000011); + vTaskDelay(pdMS_TO_TICKS(10)); + WriteReg(0x02, 0b00000111); + vTaskDelay(pdMS_TO_TICKS(50)); + } + + void ResetIli9342() { + ESP_LOGI(TAG, "Reset IlI9342"); + WriteReg(0x03, 0b10000001); + vTaskDelay(pdMS_TO_TICKS(20)); + WriteReg(0x03, 0b10000011); + vTaskDelay(pdMS_TO_TICKS(10)); + } +}; + +class Ft6336 : public I2cDevice { +public: + struct TouchPoint_t { + int num = 0; + int x = -1; + int y = -1; + }; + + Ft6336(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + uint8_t chip_id = ReadReg(0xA3); + ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); + read_buffer_ = new uint8_t[6]; + } + + ~Ft6336() { + delete[] read_buffer_; + } + + void UpdateTouchPoint() { + ReadRegs(0x02, read_buffer_, 6); + tp_.num = read_buffer_[0] & 0x0F; + tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; + tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; + } + + inline const TouchPoint_t& GetTouchPoint() { + return tp_; + } + +private: + uint8_t* read_buffer_ = nullptr; + TouchPoint_t tp_; +}; + +class M5StackCoreS3Board : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Pmic* pmic_; + Aw9523* aw9523_; + Ft6336* ft6336_; + LcdDisplay* display_; + Esp32Camera* camera_; + esp_timer_handle_t touchpad_timer_; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(10); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + pmic_->PowerOff(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void I2cDetect() { + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK) { + printf("%02x ", address); + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + } + + void InitializeAxp2101() { + ESP_LOGI(TAG, "Init AXP2101"); + pmic_ = new Pmic(i2c_bus_, 0x34); + } + + void InitializeAw9523() { + ESP_LOGI(TAG, "Init AW9523"); + aw9523_ = new Aw9523(i2c_bus_, 0x58); + vTaskDelay(pdMS_TO_TICKS(50)); + } + + void PollTouchpad() { + static bool was_touched = false; + static int64_t touch_start_time = 0; + const int64_t TOUCH_THRESHOLD_MS = 500; // 触摸时长阈值,超过500ms视为长按 + + ft6336_->UpdateTouchPoint(); + auto& touch_point = ft6336_->GetTouchPoint(); + + // 检测触摸开始 + if (touch_point.num > 0 && !was_touched) { + was_touched = true; + touch_start_time = esp_timer_get_time() / 1000; // 转换为毫秒 + } + // 检测触摸释放 + else if (touch_point.num == 0 && was_touched) { + was_touched = false; + int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time; + + // 只有短触才触发 + if (touch_duration < TOUCH_THRESHOLD_MS) { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && + !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + } + } + } + + void InitializeFt6336TouchPad() { + ESP_LOGI(TAG, "Init FT6336"); + ft6336_ = new Ft6336(i2c_bus_, 0x38); + + // 创建定时器,20ms 间隔 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + M5StackCoreS3Board* board = (M5StackCoreS3Board*)arg; + board->PollTouchpad(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "touchpad_timer", + .skip_unhandled_events = true, + }; + + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 20 * 1000)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_37; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_36; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeIli9342Display() { + ESP_LOGI(TAG, "Init IlI9342"); + + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = GPIO_NUM_3; + io_config.dc_gpio_num = GPIO_NUM_35; + io_config.spi_mode = 2; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + aw9523_->ResetIli9342(); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeCamera() { + // Open camera power + camera_config_t config = {}; + config.pin_d0 = CAMERA_PIN_D0; + config.pin_d1 = CAMERA_PIN_D1; + config.pin_d2 = CAMERA_PIN_D2; + config.pin_d3 = CAMERA_PIN_D3; + config.pin_d4 = CAMERA_PIN_D4; + config.pin_d5 = CAMERA_PIN_D5; + config.pin_d6 = CAMERA_PIN_D6; + config.pin_d7 = CAMERA_PIN_D7; + config.pin_xclk = CAMERA_PIN_XCLK; + config.pin_pclk = CAMERA_PIN_PCLK; + config.pin_vsync = CAMERA_PIN_VSYNC; + config.pin_href = CAMERA_PIN_HREF; + config.pin_sccb_sda = CAMERA_PIN_SIOD; + config.pin_sccb_scl = CAMERA_PIN_SIOC; + config.sccb_i2c_port = 1; + config.pin_pwdn = CAMERA_PIN_PWDN; + config.pin_reset = CAMERA_PIN_RESET; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_QVGA; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + camera_ = new Esp32Camera(config); + } + +public: + M5StackCoreS3Board() { + InitializePowerSaveTimer(); + InitializeI2c(); + InitializeAxp2101(); + InitializeAw9523(); + I2cDetect(); + InitializeSpi(); + InitializeIli9342Display(); + InitializeCamera(); + InitializeFt6336TouchPad(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static CoreS3AudioCodec audio_codec(i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_AW88298_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Camera* GetCamera() override { + return camera_; + } + + virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = pmic_->IsCharging(); + discharging = pmic_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + + level = pmic_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } + + virtual Backlight *GetBacklight() override { + static CustomBacklight backlight(pmic_); + return &backlight; + } +}; + +DECLARE_BOARD(M5StackCoreS3Board); diff --git a/main/boards/m5stack-tab5/README.md b/main/boards/m5stack-tab5/README.md index 9187c26..78ade2d 100644 --- a/main/boards/m5stack-tab5/README.md +++ b/main/boards/m5stack-tab5/README.md @@ -1,47 +1,47 @@ -# 使用说明 - -* [M5Stack Tab5 docs](https://docs.m5stack.com/zh_CN/core/Tab5) - -## 快速体验 - -下载编译好的 [固件](https://pan.baidu.com/s/1dgbUQtMyVLSCSBJLHARpwQ?pwd=1234) 提取码: 1234 - -```shell -esptool.py --chip esp32p4 -p /dev/ttyACM0 -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0x00 tab5_xiaozhi_v1_addr0.bin -``` - -## 基础使用 - -* idf version: v5.5-dev - -1. 设置编译目标为 esp32p4 - -```shell -idf.py set-target esp32p4 -``` - -2. 修改配置 - -```shell -cp main/boards/m5stack-tab5/sdkconfig.tab5 sdkconfig -``` - -3. 编译烧录程序 - -```shell -idf.py build flash monitor -``` - -> [!NOTE] -> 进入下载模式:长按复位按键(约 2 秒),直至内部绿色 LED 指示灯开始快速闪烁,松开按键。 - - -## log - -@2025/05/17 测试问题 - -1. listening... 需要等几秒才能获取语音输入??? -2. 亮度调节不对 -3. 音量调节不对 - -## TODO +# 使用说明 + +* [M5Stack Tab5 docs](https://docs.m5stack.com/zh_CN/core/Tab5) + +## 快速体验 + +下载编译好的 [固件](https://pan.baidu.com/s/1dgbUQtMyVLSCSBJLHARpwQ?pwd=1234) 提取码: 1234 + +```shell +esptool.py --chip esp32p4 -p /dev/ttyACM0 -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0x00 tab5_xiaozhi_v1_addr0.bin +``` + +## 基础使用 + +* idf version: v5.5-dev + +1. 设置编译目标为 esp32p4 + +```shell +idf.py set-target esp32p4 +``` + +2. 修改配置 + +```shell +cp main/boards/m5stack-tab5/sdkconfig.tab5 sdkconfig +``` + +3. 编译烧录程序 + +```shell +idf.py build flash monitor +``` + +> [!NOTE] +> 进入下载模式:长按复位按键(约 2 秒),直至内部绿色 LED 指示灯开始快速闪烁,松开按键。 + + +## log + +@2025/05/17 测试问题 + +1. listening... 需要等几秒才能获取语音输入??? +2. 亮度调节不对 +3. 音量调节不对 + +## TODO diff --git a/main/boards/m5stack-tab5/config.h b/main/boards/m5stack-tab5/config.h index 7466c03..2618d61 100644 --- a/main/boards/m5stack-tab5/config.h +++ b/main/boards/m5stack-tab5/config.h @@ -1,261 +1,261 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -/* ---------------------------------------------------------------- */ -// Audio CODEC ES7210 + ES8311 -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_30 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_29 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_27 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_28 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_26 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC // PI4IOE 控制 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_31 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_32 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -/* ---------------------------------------------------------------- */ -// 显示屏相关参数配置 -#define DISPLAY_WIDTH 720 -#define DISPLAY_HEIGHT 1280 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_22 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - -#define TOUCH_INT_GPIO GPIO_NUM_23 // 触摸中断 - -const ili9881c_lcd_init_cmd_t tab5_lcd_ili9881c_specific_init_code_default[] = { - // {cmd, { data }, data_size, delay} - /**** CMD_Page 1 ****/ - {0xFF, (uint8_t[]){0x98, 0x81, 0x01}, 3, 0}, - {0xB7, (uint8_t[]){0x03}, 1, 0}, // set 2 lane - /**** CMD_Page 3 ****/ - {0xFF, (uint8_t[]){0x98, 0x81, 0x03}, 3, 0}, - {0x01, (uint8_t[]){0x00}, 1, 0}, - {0x02, (uint8_t[]){0x00}, 1, 0}, - {0x03, (uint8_t[]){0x73}, 1, 0}, - {0x04, (uint8_t[]){0x00}, 1, 0}, - {0x05, (uint8_t[]){0x00}, 1, 0}, - {0x06, (uint8_t[]){0x08}, 1, 0}, - {0x07, (uint8_t[]){0x00}, 1, 0}, - {0x08, (uint8_t[]){0x00}, 1, 0}, - {0x09, (uint8_t[]){0x1B}, 1, 0}, - {0x0a, (uint8_t[]){0x01}, 1, 0}, - {0x0b, (uint8_t[]){0x01}, 1, 0}, - {0x0c, (uint8_t[]){0x0D}, 1, 0}, - {0x0d, (uint8_t[]){0x01}, 1, 0}, - {0x0e, (uint8_t[]){0x01}, 1, 0}, - {0x0f, (uint8_t[]){0x26}, 1, 0}, - {0x10, (uint8_t[]){0x26}, 1, 0}, - {0x11, (uint8_t[]){0x00}, 1, 0}, - {0x12, (uint8_t[]){0x00}, 1, 0}, - {0x13, (uint8_t[]){0x02}, 1, 0}, - {0x14, (uint8_t[]){0x00}, 1, 0}, - {0x15, (uint8_t[]){0x00}, 1, 0}, - {0x16, (uint8_t[]){0x00}, 1, 0}, - {0x17, (uint8_t[]){0x00}, 1, 0}, - {0x18, (uint8_t[]){0x00}, 1, 0}, - {0x19, (uint8_t[]){0x00}, 1, 0}, - {0x1a, (uint8_t[]){0x00}, 1, 0}, - {0x1b, (uint8_t[]){0x00}, 1, 0}, - {0x1c, (uint8_t[]){0x00}, 1, 0}, - {0x1d, (uint8_t[]){0x00}, 1, 0}, - {0x1e, (uint8_t[]){0x40}, 1, 0}, - {0x1f, (uint8_t[]){0x00}, 1, 0}, - {0x20, (uint8_t[]){0x06}, 1, 0}, - {0x21, (uint8_t[]){0x01}, 1, 0}, - {0x22, (uint8_t[]){0x00}, 1, 0}, - {0x23, (uint8_t[]){0x00}, 1, 0}, - {0x24, (uint8_t[]){0x00}, 1, 0}, - {0x25, (uint8_t[]){0x00}, 1, 0}, - {0x26, (uint8_t[]){0x00}, 1, 0}, - {0x27, (uint8_t[]){0x00}, 1, 0}, - {0x28, (uint8_t[]){0x33}, 1, 0}, - {0x29, (uint8_t[]){0x03}, 1, 0}, - {0x2a, (uint8_t[]){0x00}, 1, 0}, - {0x2b, (uint8_t[]){0x00}, 1, 0}, - {0x2c, (uint8_t[]){0x00}, 1, 0}, - {0x2d, (uint8_t[]){0x00}, 1, 0}, - {0x2e, (uint8_t[]){0x00}, 1, 0}, - {0x2f, (uint8_t[]){0x00}, 1, 0}, - {0x30, (uint8_t[]){0x00}, 1, 0}, - {0x31, (uint8_t[]){0x00}, 1, 0}, - {0x32, (uint8_t[]){0x00}, 1, 0}, - {0x33, (uint8_t[]){0x00}, 1, 0}, - {0x34, (uint8_t[]){0x00}, 1, 0}, - {0x35, (uint8_t[]){0x00}, 1, 0}, - {0x36, (uint8_t[]){0x00}, 1, 0}, - {0x37, (uint8_t[]){0x00}, 1, 0}, - {0x38, (uint8_t[]){0x00}, 1, 0}, - {0x39, (uint8_t[]){0x00}, 1, 0}, - {0x3a, (uint8_t[]){0x00}, 1, 0}, - {0x3b, (uint8_t[]){0x00}, 1, 0}, - {0x3c, (uint8_t[]){0x00}, 1, 0}, - {0x3d, (uint8_t[]){0x00}, 1, 0}, - {0x3e, (uint8_t[]){0x00}, 1, 0}, - {0x3f, (uint8_t[]){0x00}, 1, 0}, - {0x40, (uint8_t[]){0x00}, 1, 0}, - {0x41, (uint8_t[]){0x00}, 1, 0}, - {0x42, (uint8_t[]){0x00}, 1, 0}, - {0x43, (uint8_t[]){0x00}, 1, 0}, - {0x44, (uint8_t[]){0x00}, 1, 0}, - - {0x50, (uint8_t[]){0x01}, 1, 0}, - {0x51, (uint8_t[]){0x23}, 1, 0}, - {0x52, (uint8_t[]){0x45}, 1, 0}, - {0x53, (uint8_t[]){0x67}, 1, 0}, - {0x54, (uint8_t[]){0x89}, 1, 0}, - {0x55, (uint8_t[]){0xab}, 1, 0}, - {0x56, (uint8_t[]){0x01}, 1, 0}, - {0x57, (uint8_t[]){0x23}, 1, 0}, - {0x58, (uint8_t[]){0x45}, 1, 0}, - {0x59, (uint8_t[]){0x67}, 1, 0}, - {0x5a, (uint8_t[]){0x89}, 1, 0}, - {0x5b, (uint8_t[]){0xab}, 1, 0}, - {0x5c, (uint8_t[]){0xcd}, 1, 0}, - {0x5d, (uint8_t[]){0xef}, 1, 0}, - - {0x5e, (uint8_t[]){0x11}, 1, 0}, - {0x5f, (uint8_t[]){0x02}, 1, 0}, - {0x60, (uint8_t[]){0x00}, 1, 0}, - {0x61, (uint8_t[]){0x07}, 1, 0}, - {0x62, (uint8_t[]){0x06}, 1, 0}, - {0x63, (uint8_t[]){0x0E}, 1, 0}, - {0x64, (uint8_t[]){0x0F}, 1, 0}, - {0x65, (uint8_t[]){0x0C}, 1, 0}, - {0x66, (uint8_t[]){0x0D}, 1, 0}, - {0x67, (uint8_t[]){0x02}, 1, 0}, - {0x68, (uint8_t[]){0x02}, 1, 0}, - {0x69, (uint8_t[]){0x02}, 1, 0}, - {0x6a, (uint8_t[]){0x02}, 1, 0}, - {0x6b, (uint8_t[]){0x02}, 1, 0}, - {0x6c, (uint8_t[]){0x02}, 1, 0}, - {0x6d, (uint8_t[]){0x02}, 1, 0}, - {0x6e, (uint8_t[]){0x02}, 1, 0}, - {0x6f, (uint8_t[]){0x02}, 1, 0}, - {0x70, (uint8_t[]){0x02}, 1, 0}, - {0x71, (uint8_t[]){0x02}, 1, 0}, - {0x72, (uint8_t[]){0x02}, 1, 0}, - {0x73, (uint8_t[]){0x05}, 1, 0}, - {0x74, (uint8_t[]){0x01}, 1, 0}, - {0x75, (uint8_t[]){0x02}, 1, 0}, - {0x76, (uint8_t[]){0x00}, 1, 0}, - {0x77, (uint8_t[]){0x07}, 1, 0}, - {0x78, (uint8_t[]){0x06}, 1, 0}, - {0x79, (uint8_t[]){0x0E}, 1, 0}, - {0x7a, (uint8_t[]){0x0F}, 1, 0}, - {0x7b, (uint8_t[]){0x0C}, 1, 0}, - {0x7c, (uint8_t[]){0x0D}, 1, 0}, - {0x7d, (uint8_t[]){0x02}, 1, 0}, - {0x7e, (uint8_t[]){0x02}, 1, 0}, - {0x7f, (uint8_t[]){0x02}, 1, 0}, - {0x80, (uint8_t[]){0x02}, 1, 0}, - {0x81, (uint8_t[]){0x02}, 1, 0}, - {0x82, (uint8_t[]){0x02}, 1, 0}, - {0x83, (uint8_t[]){0x02}, 1, 0}, - {0x84, (uint8_t[]){0x02}, 1, 0}, - {0x85, (uint8_t[]){0x02}, 1, 0}, - {0x86, (uint8_t[]){0x02}, 1, 0}, - {0x87, (uint8_t[]){0x02}, 1, 0}, - {0x88, (uint8_t[]){0x02}, 1, 0}, - {0x89, (uint8_t[]){0x05}, 1, 0}, - {0x8A, (uint8_t[]){0x01}, 1, 0}, - - /**** CMD_Page 4 ****/ - {0xFF, (uint8_t[]){0x98, 0x81, 0x04}, 3, 0}, - {0x38, (uint8_t[]){0x01}, 1, 0}, - {0x39, (uint8_t[]){0x00}, 1, 0}, - {0x6C, (uint8_t[]){0x15}, 1, 0}, - {0x6E, (uint8_t[]){0x1A}, 1, 0}, - {0x6F, (uint8_t[]){0x25}, 1, 0}, - {0x3A, (uint8_t[]){0xA4}, 1, 0}, - {0x8D, (uint8_t[]){0x20}, 1, 0}, - {0x87, (uint8_t[]){0xBA}, 1, 0}, - {0x3B, (uint8_t[]){0x98}, 1, 0}, - - /**** CMD_Page 1 ****/ - {0xFF, (uint8_t[]){0x98, 0x81, 0x01}, 3, 0}, - {0x22, (uint8_t[]){0x0A}, 1, 0}, - {0x31, (uint8_t[]){0x00}, 1, 0}, - {0x50, (uint8_t[]){0x6B}, 1, 0}, - {0x51, (uint8_t[]){0x66}, 1, 0}, - {0x53, (uint8_t[]){0x73}, 1, 0}, - {0x55, (uint8_t[]){0x8B}, 1, 0}, - {0x60, (uint8_t[]){0x1B}, 1, 0}, - {0x61, (uint8_t[]){0x01}, 1, 0}, - {0x62, (uint8_t[]){0x0C}, 1, 0}, - {0x63, (uint8_t[]){0x00}, 1, 0}, - - // Gamma P - {0xA0, (uint8_t[]){0x00}, 1, 0}, - {0xA1, (uint8_t[]){0x15}, 1, 0}, - {0xA2, (uint8_t[]){0x1F}, 1, 0}, - {0xA3, (uint8_t[]){0x13}, 1, 0}, - {0xA4, (uint8_t[]){0x11}, 1, 0}, - {0xA5, (uint8_t[]){0x21}, 1, 0}, - {0xA6, (uint8_t[]){0x17}, 1, 0}, - {0xA7, (uint8_t[]){0x1B}, 1, 0}, - {0xA8, (uint8_t[]){0x6B}, 1, 0}, - {0xA9, (uint8_t[]){0x1E}, 1, 0}, - {0xAA, (uint8_t[]){0x2B}, 1, 0}, - {0xAB, (uint8_t[]){0x5D}, 1, 0}, - {0xAC, (uint8_t[]){0x19}, 1, 0}, - {0xAD, (uint8_t[]){0x14}, 1, 0}, - {0xAE, (uint8_t[]){0x4B}, 1, 0}, - {0xAF, (uint8_t[]){0x1D}, 1, 0}, - {0xB0, (uint8_t[]){0x27}, 1, 0}, - {0xB1, (uint8_t[]){0x49}, 1, 0}, - {0xB2, (uint8_t[]){0x5D}, 1, 0}, - {0xB3, (uint8_t[]){0x39}, 1, 0}, - - // Gamma N - {0xC0, (uint8_t[]){0x00}, 1, 0}, - {0xC1, (uint8_t[]){0x01}, 1, 0}, - {0xC2, (uint8_t[]){0x0C}, 1, 0}, - {0xC3, (uint8_t[]){0x11}, 1, 0}, - {0xC4, (uint8_t[]){0x15}, 1, 0}, - {0xC5, (uint8_t[]){0x28}, 1, 0}, - {0xC6, (uint8_t[]){0x1B}, 1, 0}, - {0xC7, (uint8_t[]){0x1C}, 1, 0}, - {0xC8, (uint8_t[]){0x62}, 1, 0}, - {0xC9, (uint8_t[]){0x1C}, 1, 0}, - {0xCA, (uint8_t[]){0x29}, 1, 0}, - {0xCB, (uint8_t[]){0x60}, 1, 0}, - {0xCC, (uint8_t[]){0x16}, 1, 0}, - {0xCD, (uint8_t[]){0x17}, 1, 0}, - {0xCE, (uint8_t[]){0x4A}, 1, 0}, - {0xCF, (uint8_t[]){0x23}, 1, 0}, - {0xD0, (uint8_t[]){0x24}, 1, 0}, - {0xD1, (uint8_t[]){0x4F}, 1, 0}, - {0xD2, (uint8_t[]){0x5F}, 1, 0}, - {0xD3, (uint8_t[]){0x39}, 1, 0}, - - /**** CMD_Page 0 ****/ - {0xFF, (uint8_t[]){0x98, 0x81, 0x00}, 3, 0}, - {0x35, (uint8_t[]){0x00}, 0, 0}, - // {0x11, (uint8_t []){0x00}, 0}, - {0xFE, (uint8_t[]){0x00}, 0, 0}, - {0x29, (uint8_t[]){0x00}, 0, 0}, - //============ Gamma END=========== -}; - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +/* ---------------------------------------------------------------- */ +// Audio CODEC ES7210 + ES8311 +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_30 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_29 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_27 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_28 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_26 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC // PI4IOE 控制 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_31 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_32 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +/* ---------------------------------------------------------------- */ +// 显示屏相关参数配置 +#define DISPLAY_WIDTH 720 +#define DISPLAY_HEIGHT 1280 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_22 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +#define TOUCH_INT_GPIO GPIO_NUM_23 // 触摸中断 + +const ili9881c_lcd_init_cmd_t tab5_lcd_ili9881c_specific_init_code_default[] = { + // {cmd, { data }, data_size, delay} + /**** CMD_Page 1 ****/ + {0xFF, (uint8_t[]){0x98, 0x81, 0x01}, 3, 0}, + {0xB7, (uint8_t[]){0x03}, 1, 0}, // set 2 lane + /**** CMD_Page 3 ****/ + {0xFF, (uint8_t[]){0x98, 0x81, 0x03}, 3, 0}, + {0x01, (uint8_t[]){0x00}, 1, 0}, + {0x02, (uint8_t[]){0x00}, 1, 0}, + {0x03, (uint8_t[]){0x73}, 1, 0}, + {0x04, (uint8_t[]){0x00}, 1, 0}, + {0x05, (uint8_t[]){0x00}, 1, 0}, + {0x06, (uint8_t[]){0x08}, 1, 0}, + {0x07, (uint8_t[]){0x00}, 1, 0}, + {0x08, (uint8_t[]){0x00}, 1, 0}, + {0x09, (uint8_t[]){0x1B}, 1, 0}, + {0x0a, (uint8_t[]){0x01}, 1, 0}, + {0x0b, (uint8_t[]){0x01}, 1, 0}, + {0x0c, (uint8_t[]){0x0D}, 1, 0}, + {0x0d, (uint8_t[]){0x01}, 1, 0}, + {0x0e, (uint8_t[]){0x01}, 1, 0}, + {0x0f, (uint8_t[]){0x26}, 1, 0}, + {0x10, (uint8_t[]){0x26}, 1, 0}, + {0x11, (uint8_t[]){0x00}, 1, 0}, + {0x12, (uint8_t[]){0x00}, 1, 0}, + {0x13, (uint8_t[]){0x02}, 1, 0}, + {0x14, (uint8_t[]){0x00}, 1, 0}, + {0x15, (uint8_t[]){0x00}, 1, 0}, + {0x16, (uint8_t[]){0x00}, 1, 0}, + {0x17, (uint8_t[]){0x00}, 1, 0}, + {0x18, (uint8_t[]){0x00}, 1, 0}, + {0x19, (uint8_t[]){0x00}, 1, 0}, + {0x1a, (uint8_t[]){0x00}, 1, 0}, + {0x1b, (uint8_t[]){0x00}, 1, 0}, + {0x1c, (uint8_t[]){0x00}, 1, 0}, + {0x1d, (uint8_t[]){0x00}, 1, 0}, + {0x1e, (uint8_t[]){0x40}, 1, 0}, + {0x1f, (uint8_t[]){0x00}, 1, 0}, + {0x20, (uint8_t[]){0x06}, 1, 0}, + {0x21, (uint8_t[]){0x01}, 1, 0}, + {0x22, (uint8_t[]){0x00}, 1, 0}, + {0x23, (uint8_t[]){0x00}, 1, 0}, + {0x24, (uint8_t[]){0x00}, 1, 0}, + {0x25, (uint8_t[]){0x00}, 1, 0}, + {0x26, (uint8_t[]){0x00}, 1, 0}, + {0x27, (uint8_t[]){0x00}, 1, 0}, + {0x28, (uint8_t[]){0x33}, 1, 0}, + {0x29, (uint8_t[]){0x03}, 1, 0}, + {0x2a, (uint8_t[]){0x00}, 1, 0}, + {0x2b, (uint8_t[]){0x00}, 1, 0}, + {0x2c, (uint8_t[]){0x00}, 1, 0}, + {0x2d, (uint8_t[]){0x00}, 1, 0}, + {0x2e, (uint8_t[]){0x00}, 1, 0}, + {0x2f, (uint8_t[]){0x00}, 1, 0}, + {0x30, (uint8_t[]){0x00}, 1, 0}, + {0x31, (uint8_t[]){0x00}, 1, 0}, + {0x32, (uint8_t[]){0x00}, 1, 0}, + {0x33, (uint8_t[]){0x00}, 1, 0}, + {0x34, (uint8_t[]){0x00}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x36, (uint8_t[]){0x00}, 1, 0}, + {0x37, (uint8_t[]){0x00}, 1, 0}, + {0x38, (uint8_t[]){0x00}, 1, 0}, + {0x39, (uint8_t[]){0x00}, 1, 0}, + {0x3a, (uint8_t[]){0x00}, 1, 0}, + {0x3b, (uint8_t[]){0x00}, 1, 0}, + {0x3c, (uint8_t[]){0x00}, 1, 0}, + {0x3d, (uint8_t[]){0x00}, 1, 0}, + {0x3e, (uint8_t[]){0x00}, 1, 0}, + {0x3f, (uint8_t[]){0x00}, 1, 0}, + {0x40, (uint8_t[]){0x00}, 1, 0}, + {0x41, (uint8_t[]){0x00}, 1, 0}, + {0x42, (uint8_t[]){0x00}, 1, 0}, + {0x43, (uint8_t[]){0x00}, 1, 0}, + {0x44, (uint8_t[]){0x00}, 1, 0}, + + {0x50, (uint8_t[]){0x01}, 1, 0}, + {0x51, (uint8_t[]){0x23}, 1, 0}, + {0x52, (uint8_t[]){0x45}, 1, 0}, + {0x53, (uint8_t[]){0x67}, 1, 0}, + {0x54, (uint8_t[]){0x89}, 1, 0}, + {0x55, (uint8_t[]){0xab}, 1, 0}, + {0x56, (uint8_t[]){0x01}, 1, 0}, + {0x57, (uint8_t[]){0x23}, 1, 0}, + {0x58, (uint8_t[]){0x45}, 1, 0}, + {0x59, (uint8_t[]){0x67}, 1, 0}, + {0x5a, (uint8_t[]){0x89}, 1, 0}, + {0x5b, (uint8_t[]){0xab}, 1, 0}, + {0x5c, (uint8_t[]){0xcd}, 1, 0}, + {0x5d, (uint8_t[]){0xef}, 1, 0}, + + {0x5e, (uint8_t[]){0x11}, 1, 0}, + {0x5f, (uint8_t[]){0x02}, 1, 0}, + {0x60, (uint8_t[]){0x00}, 1, 0}, + {0x61, (uint8_t[]){0x07}, 1, 0}, + {0x62, (uint8_t[]){0x06}, 1, 0}, + {0x63, (uint8_t[]){0x0E}, 1, 0}, + {0x64, (uint8_t[]){0x0F}, 1, 0}, + {0x65, (uint8_t[]){0x0C}, 1, 0}, + {0x66, (uint8_t[]){0x0D}, 1, 0}, + {0x67, (uint8_t[]){0x02}, 1, 0}, + {0x68, (uint8_t[]){0x02}, 1, 0}, + {0x69, (uint8_t[]){0x02}, 1, 0}, + {0x6a, (uint8_t[]){0x02}, 1, 0}, + {0x6b, (uint8_t[]){0x02}, 1, 0}, + {0x6c, (uint8_t[]){0x02}, 1, 0}, + {0x6d, (uint8_t[]){0x02}, 1, 0}, + {0x6e, (uint8_t[]){0x02}, 1, 0}, + {0x6f, (uint8_t[]){0x02}, 1, 0}, + {0x70, (uint8_t[]){0x02}, 1, 0}, + {0x71, (uint8_t[]){0x02}, 1, 0}, + {0x72, (uint8_t[]){0x02}, 1, 0}, + {0x73, (uint8_t[]){0x05}, 1, 0}, + {0x74, (uint8_t[]){0x01}, 1, 0}, + {0x75, (uint8_t[]){0x02}, 1, 0}, + {0x76, (uint8_t[]){0x00}, 1, 0}, + {0x77, (uint8_t[]){0x07}, 1, 0}, + {0x78, (uint8_t[]){0x06}, 1, 0}, + {0x79, (uint8_t[]){0x0E}, 1, 0}, + {0x7a, (uint8_t[]){0x0F}, 1, 0}, + {0x7b, (uint8_t[]){0x0C}, 1, 0}, + {0x7c, (uint8_t[]){0x0D}, 1, 0}, + {0x7d, (uint8_t[]){0x02}, 1, 0}, + {0x7e, (uint8_t[]){0x02}, 1, 0}, + {0x7f, (uint8_t[]){0x02}, 1, 0}, + {0x80, (uint8_t[]){0x02}, 1, 0}, + {0x81, (uint8_t[]){0x02}, 1, 0}, + {0x82, (uint8_t[]){0x02}, 1, 0}, + {0x83, (uint8_t[]){0x02}, 1, 0}, + {0x84, (uint8_t[]){0x02}, 1, 0}, + {0x85, (uint8_t[]){0x02}, 1, 0}, + {0x86, (uint8_t[]){0x02}, 1, 0}, + {0x87, (uint8_t[]){0x02}, 1, 0}, + {0x88, (uint8_t[]){0x02}, 1, 0}, + {0x89, (uint8_t[]){0x05}, 1, 0}, + {0x8A, (uint8_t[]){0x01}, 1, 0}, + + /**** CMD_Page 4 ****/ + {0xFF, (uint8_t[]){0x98, 0x81, 0x04}, 3, 0}, + {0x38, (uint8_t[]){0x01}, 1, 0}, + {0x39, (uint8_t[]){0x00}, 1, 0}, + {0x6C, (uint8_t[]){0x15}, 1, 0}, + {0x6E, (uint8_t[]){0x1A}, 1, 0}, + {0x6F, (uint8_t[]){0x25}, 1, 0}, + {0x3A, (uint8_t[]){0xA4}, 1, 0}, + {0x8D, (uint8_t[]){0x20}, 1, 0}, + {0x87, (uint8_t[]){0xBA}, 1, 0}, + {0x3B, (uint8_t[]){0x98}, 1, 0}, + + /**** CMD_Page 1 ****/ + {0xFF, (uint8_t[]){0x98, 0x81, 0x01}, 3, 0}, + {0x22, (uint8_t[]){0x0A}, 1, 0}, + {0x31, (uint8_t[]){0x00}, 1, 0}, + {0x50, (uint8_t[]){0x6B}, 1, 0}, + {0x51, (uint8_t[]){0x66}, 1, 0}, + {0x53, (uint8_t[]){0x73}, 1, 0}, + {0x55, (uint8_t[]){0x8B}, 1, 0}, + {0x60, (uint8_t[]){0x1B}, 1, 0}, + {0x61, (uint8_t[]){0x01}, 1, 0}, + {0x62, (uint8_t[]){0x0C}, 1, 0}, + {0x63, (uint8_t[]){0x00}, 1, 0}, + + // Gamma P + {0xA0, (uint8_t[]){0x00}, 1, 0}, + {0xA1, (uint8_t[]){0x15}, 1, 0}, + {0xA2, (uint8_t[]){0x1F}, 1, 0}, + {0xA3, (uint8_t[]){0x13}, 1, 0}, + {0xA4, (uint8_t[]){0x11}, 1, 0}, + {0xA5, (uint8_t[]){0x21}, 1, 0}, + {0xA6, (uint8_t[]){0x17}, 1, 0}, + {0xA7, (uint8_t[]){0x1B}, 1, 0}, + {0xA8, (uint8_t[]){0x6B}, 1, 0}, + {0xA9, (uint8_t[]){0x1E}, 1, 0}, + {0xAA, (uint8_t[]){0x2B}, 1, 0}, + {0xAB, (uint8_t[]){0x5D}, 1, 0}, + {0xAC, (uint8_t[]){0x19}, 1, 0}, + {0xAD, (uint8_t[]){0x14}, 1, 0}, + {0xAE, (uint8_t[]){0x4B}, 1, 0}, + {0xAF, (uint8_t[]){0x1D}, 1, 0}, + {0xB0, (uint8_t[]){0x27}, 1, 0}, + {0xB1, (uint8_t[]){0x49}, 1, 0}, + {0xB2, (uint8_t[]){0x5D}, 1, 0}, + {0xB3, (uint8_t[]){0x39}, 1, 0}, + + // Gamma N + {0xC0, (uint8_t[]){0x00}, 1, 0}, + {0xC1, (uint8_t[]){0x01}, 1, 0}, + {0xC2, (uint8_t[]){0x0C}, 1, 0}, + {0xC3, (uint8_t[]){0x11}, 1, 0}, + {0xC4, (uint8_t[]){0x15}, 1, 0}, + {0xC5, (uint8_t[]){0x28}, 1, 0}, + {0xC6, (uint8_t[]){0x1B}, 1, 0}, + {0xC7, (uint8_t[]){0x1C}, 1, 0}, + {0xC8, (uint8_t[]){0x62}, 1, 0}, + {0xC9, (uint8_t[]){0x1C}, 1, 0}, + {0xCA, (uint8_t[]){0x29}, 1, 0}, + {0xCB, (uint8_t[]){0x60}, 1, 0}, + {0xCC, (uint8_t[]){0x16}, 1, 0}, + {0xCD, (uint8_t[]){0x17}, 1, 0}, + {0xCE, (uint8_t[]){0x4A}, 1, 0}, + {0xCF, (uint8_t[]){0x23}, 1, 0}, + {0xD0, (uint8_t[]){0x24}, 1, 0}, + {0xD1, (uint8_t[]){0x4F}, 1, 0}, + {0xD2, (uint8_t[]){0x5F}, 1, 0}, + {0xD3, (uint8_t[]){0x39}, 1, 0}, + + /**** CMD_Page 0 ****/ + {0xFF, (uint8_t[]){0x98, 0x81, 0x00}, 3, 0}, + {0x35, (uint8_t[]){0x00}, 0, 0}, + // {0x11, (uint8_t []){0x00}, 0}, + {0xFE, (uint8_t[]){0x00}, 0, 0}, + {0x29, (uint8_t[]){0x00}, 0, 0}, + //============ Gamma END=========== +}; + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/m5stack-tab5/m5stack_tab5.cc b/main/boards/m5stack-tab5/m5stack_tab5.cc index f947f0f..20d16f6 100644 --- a/main/boards/m5stack-tab5/m5stack_tab5.cc +++ b/main/boards/m5stack-tab5/m5stack_tab5.cc @@ -1,298 +1,298 @@ -#include "wifi_board.h" -#include "tab5_audio_codec.h" -#include "display/lcd_display.h" -#include "esp_lcd_ili9881c.h" -#include "font_emoji.h" -#include "application.h" -#include "button.h" -#include "config.h" - -#include -#include "esp_lcd_mipi_dsi.h" -#include "esp_lcd_panel_ops.h" -#include "esp_ldo_regulator.h" -#include -#include -#include -#include -#include "i2c_device.h" -#include "esp_lcd_touch_gt911.h" -#include - -#define TAG "M5StackTab5Board" - -#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR -#define LCD_MIPI_DSI_PHY_PWR_LDO_CHAN 3 // LDO_VO3 is connected to VDD_MIPI_DPHY -#define LCD_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500 - -// PI4IO registers -#define PI4IO_REG_CHIP_RESET 0x01 -#define PI4IO_REG_IO_DIR 0x03 -#define PI4IO_REG_OUT_SET 0x05 -#define PI4IO_REG_OUT_H_IM 0x07 -#define PI4IO_REG_IN_DEF_STA 0x09 -#define PI4IO_REG_PULL_EN 0x0B -#define PI4IO_REG_PULL_SEL 0x0D -#define PI4IO_REG_IN_STA 0x0F -#define PI4IO_REG_INT_MASK 0x11 -#define PI4IO_REG_IRQ_STA 0x13 - -class Pi4ioe1 : public I2cDevice { -public: - Pi4ioe1(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - WriteReg(PI4IO_REG_CHIP_RESET, 0xFF); - uint8_t data = ReadReg(PI4IO_REG_CHIP_RESET); - WriteReg(PI4IO_REG_IO_DIR, 0b01111111); // 0: input 1: output - WriteReg(PI4IO_REG_OUT_H_IM, 0b00000000); // 使用到的引脚关闭 High-Impedance - WriteReg(PI4IO_REG_PULL_SEL, 0b01111111); // pull up/down select, 0 down, 1 up - WriteReg(PI4IO_REG_PULL_EN, 0b01111111); // pull up/down enable, 0 disable, 1 enable - WriteReg(PI4IO_REG_IN_DEF_STA, 0b10000000); // P1, P7 默认高电平 - WriteReg(PI4IO_REG_INT_MASK, 0b01111111); // P7 中断使能 0 enable, 1 disable - WriteReg(PI4IO_REG_OUT_SET, 0b01110110); // Output Port Register P1(SPK_EN), P2(EXT5V_EN), P4(LCD_RST), P5(TP_RST), P6(CAM)RST 输出高电平 - } -}; - -class Pi4ioe2 : public I2cDevice { -public: - Pi4ioe2(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - WriteReg(PI4IO_REG_CHIP_RESET, 0xFF); - uint8_t data = ReadReg(PI4IO_REG_CHIP_RESET); - WriteReg(PI4IO_REG_IO_DIR, 0b10111001); // 0: input 1: output - WriteReg(PI4IO_REG_OUT_H_IM, 0b00000110); // 使用到的引脚关闭 High-Impedance - WriteReg(PI4IO_REG_PULL_SEL, 0b10111001); // pull up/down select, 0 down, 1 up - WriteReg(PI4IO_REG_PULL_EN, 0b11111001); // pull up/down enable, 0 disable, 1 enable - WriteReg(PI4IO_REG_IN_DEF_STA, 0b01000000); // P6 默认高电平 - WriteReg(PI4IO_REG_INT_MASK, 0b10111111); // P6 中断使能 0 enable, 1 disable - WriteReg(PI4IO_REG_OUT_SET, 0b10001001); // Output Port Register P0(WLAN_PWR_EN), P3(USB5V_EN), P7(CHG_EN) 输出高电平 - } -}; - -class M5StackTab5Board : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - Pi4ioe1* pi4ioe1_; - Pi4ioe2* pi4ioe2_; - esp_lcd_touch_handle_t touch_ = nullptr; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void I2cDetect() { - uint8_t address; - printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); - for (int i = 0; i < 128; i += 16) { - printf("%02x: ", i); - for (int j = 0; j < 16; j++) { - fflush(stdout); - address = i + j; - esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); - if (ret == ESP_OK) { - printf("%02x ", address); - } else if (ret == ESP_ERR_TIMEOUT) { - printf("UU "); - } else { - printf("-- "); - } - } - printf("\r\n"); - } - } - - void InitializePi4ioe() { - ESP_LOGI(TAG, "Init I/O Exapander PI4IOE"); - pi4ioe1_ = new Pi4ioe1(i2c_bus_, 0x43); - pi4ioe2_ = new Pi4ioe2(i2c_bus_, 0x44); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - // ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeGt911TouchPad() { - ESP_LOGI(TAG, "Init GT911"); - - /* Initialize Touch Panel */ - ESP_LOGI(TAG, "Initialize touch IO (I2C)"); - const esp_lcd_touch_config_t tp_cfg = { - .x_max = DISPLAY_WIDTH, - .y_max = DISPLAY_HEIGHT, - .rst_gpio_num = GPIO_NUM_NC, - .int_gpio_num = TOUCH_INT_GPIO, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = 0, - .mirror_x = 0, - .mirror_y = 0, - }, - }; - esp_lcd_panel_io_handle_t tp_io_handle = NULL; - esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); - tp_io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP; // 更改 GT911 地址 - tp_io_config.scl_speed_hz = 100000; - esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle); - esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &touch_); - - // 检测不到触摸?待更换设备测试 - // /* read data test */ - // for (uint8_t i = 0; i < 50; i++) { - // esp_lcd_touch_read_data(touch_); - // if (touch_->data.points > 0) { - // printf("\ntouch: %d, %d\n", touch_->data.coords[0].x, touch_->data.coords[0].y); - // } - // vTaskDelay(pdMS_TO_TICKS(100)); - // } - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_37; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_36; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeIli9881cDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - ESP_LOGI(TAG, "Turn on the power for MIPI DSI PHY"); - esp_ldo_channel_handle_t ldo_mipi_phy = NULL; - esp_ldo_channel_config_t ldo_mipi_phy_config = { - .chan_id = LCD_MIPI_DSI_PHY_PWR_LDO_CHAN, - .voltage_mv = LCD_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, - }; - ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy)); - - ESP_LOGI(TAG, "Install MIPI DSI LCD control panel"); - esp_lcd_dsi_bus_handle_t mipi_dsi_bus; - esp_lcd_dsi_bus_config_t bus_config = { - .bus_id = 0, - .num_data_lanes = 2, - .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, - .lane_bit_rate_mbps = 900, // 900MHz - }; - ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus)); - - ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_dbi_io_config_t dbi_config = { - .virtual_channel = 0, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &panel_io)); - - ESP_LOGI(TAG, "Install LCD driver of ili9881c"); - esp_lcd_dpi_panel_config_t dpi_config = {.virtual_channel = 0, - .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, - .dpi_clock_freq_mhz = 60, - .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, - .num_fbs = 2, - .video_timing = - { - .h_size = DISPLAY_WIDTH, - .v_size = DISPLAY_HEIGHT, - .hsync_pulse_width = 40, - .hsync_back_porch = 140, - .hsync_front_porch = 40, - .vsync_pulse_width = 4, - .vsync_back_porch = 20, - .vsync_front_porch = 20, - }, - .flags = { - .use_dma2d = false, - }}; - - ili9881c_vendor_config_t vendor_config = { - .init_cmds = tab5_lcd_ili9881c_specific_init_code_default, - .init_cmds_size = sizeof(tab5_lcd_ili9881c_specific_init_code_default) / - sizeof(tab5_lcd_ili9881c_specific_init_code_default[0]), - .mipi_config = - { - .dsi_bus = mipi_dsi_bus, - .dpi_config = &dpi_config, - .lane_num = 2, - }, - }; - - esp_lcd_panel_dev_config_t lcd_dev_config = {}; - lcd_dev_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - lcd_dev_config.reset_gpio_num = -1; - lcd_dev_config.bits_per_pixel = 16; - lcd_dev_config.vendor_config = &vendor_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ili9881c(panel_io, &lcd_dev_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - // ESP_ERROR_CHECK(esp_lcd_panel_mirror(disp_panel, false, true)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - - display_ = new MipiLcdDisplay(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, - DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - M5StackTab5Board() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - I2cDetect(); - InitializePi4ioe(); - InitializeGt911TouchPad(); - InitializeIli9881cDisplay(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Tab5AudioCodec audio_codec(i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8388_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(M5StackTab5Board); +#include "wifi_board.h" +#include "tab5_audio_codec.h" +#include "display/lcd_display.h" +#include "esp_lcd_ili9881c.h" +#include "font_emoji.h" +#include "application.h" +#include "button.h" +#include "config.h" + +#include +#include "esp_lcd_mipi_dsi.h" +#include "esp_lcd_panel_ops.h" +#include "esp_ldo_regulator.h" +#include +#include +#include +#include +#include "i2c_device.h" +#include "esp_lcd_touch_gt911.h" +#include + +#define TAG "M5StackTab5Board" + +#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR +#define LCD_MIPI_DSI_PHY_PWR_LDO_CHAN 3 // LDO_VO3 is connected to VDD_MIPI_DPHY +#define LCD_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500 + +// PI4IO registers +#define PI4IO_REG_CHIP_RESET 0x01 +#define PI4IO_REG_IO_DIR 0x03 +#define PI4IO_REG_OUT_SET 0x05 +#define PI4IO_REG_OUT_H_IM 0x07 +#define PI4IO_REG_IN_DEF_STA 0x09 +#define PI4IO_REG_PULL_EN 0x0B +#define PI4IO_REG_PULL_SEL 0x0D +#define PI4IO_REG_IN_STA 0x0F +#define PI4IO_REG_INT_MASK 0x11 +#define PI4IO_REG_IRQ_STA 0x13 + +class Pi4ioe1 : public I2cDevice { +public: + Pi4ioe1(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + WriteReg(PI4IO_REG_CHIP_RESET, 0xFF); + uint8_t data = ReadReg(PI4IO_REG_CHIP_RESET); + WriteReg(PI4IO_REG_IO_DIR, 0b01111111); // 0: input 1: output + WriteReg(PI4IO_REG_OUT_H_IM, 0b00000000); // 使用到的引脚关闭 High-Impedance + WriteReg(PI4IO_REG_PULL_SEL, 0b01111111); // pull up/down select, 0 down, 1 up + WriteReg(PI4IO_REG_PULL_EN, 0b01111111); // pull up/down enable, 0 disable, 1 enable + WriteReg(PI4IO_REG_IN_DEF_STA, 0b10000000); // P1, P7 默认高电平 + WriteReg(PI4IO_REG_INT_MASK, 0b01111111); // P7 中断使能 0 enable, 1 disable + WriteReg(PI4IO_REG_OUT_SET, 0b01110110); // Output Port Register P1(SPK_EN), P2(EXT5V_EN), P4(LCD_RST), P5(TP_RST), P6(CAM)RST 输出高电平 + } +}; + +class Pi4ioe2 : public I2cDevice { +public: + Pi4ioe2(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + WriteReg(PI4IO_REG_CHIP_RESET, 0xFF); + uint8_t data = ReadReg(PI4IO_REG_CHIP_RESET); + WriteReg(PI4IO_REG_IO_DIR, 0b10111001); // 0: input 1: output + WriteReg(PI4IO_REG_OUT_H_IM, 0b00000110); // 使用到的引脚关闭 High-Impedance + WriteReg(PI4IO_REG_PULL_SEL, 0b10111001); // pull up/down select, 0 down, 1 up + WriteReg(PI4IO_REG_PULL_EN, 0b11111001); // pull up/down enable, 0 disable, 1 enable + WriteReg(PI4IO_REG_IN_DEF_STA, 0b01000000); // P6 默认高电平 + WriteReg(PI4IO_REG_INT_MASK, 0b10111111); // P6 中断使能 0 enable, 1 disable + WriteReg(PI4IO_REG_OUT_SET, 0b10001001); // Output Port Register P0(WLAN_PWR_EN), P3(USB5V_EN), P7(CHG_EN) 输出高电平 + } +}; + +class M5StackTab5Board : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + Pi4ioe1* pi4ioe1_; + Pi4ioe2* pi4ioe2_; + esp_lcd_touch_handle_t touch_ = nullptr; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void I2cDetect() { + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for (int j = 0; j < 16; j++) { + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK) { + printf("%02x ", address); + } else if (ret == ESP_ERR_TIMEOUT) { + printf("UU "); + } else { + printf("-- "); + } + } + printf("\r\n"); + } + } + + void InitializePi4ioe() { + ESP_LOGI(TAG, "Init I/O Exapander PI4IOE"); + pi4ioe1_ = new Pi4ioe1(i2c_bus_, 0x43); + pi4ioe2_ = new Pi4ioe2(i2c_bus_, 0x44); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + // ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeGt911TouchPad() { + ESP_LOGI(TAG, "Init GT911"); + + /* Initialize Touch Panel */ + ESP_LOGI(TAG, "Initialize touch IO (I2C)"); + const esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH, + .y_max = DISPLAY_HEIGHT, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = TOUCH_INT_GPIO, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); + tp_io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP; // 更改 GT911 地址 + tp_io_config.scl_speed_hz = 100000; + esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle); + esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &touch_); + + // 检测不到触摸?待更换设备测试 + // /* read data test */ + // for (uint8_t i = 0; i < 50; i++) { + // esp_lcd_touch_read_data(touch_); + // if (touch_->data.points > 0) { + // printf("\ntouch: %d, %d\n", touch_->data.coords[0].x, touch_->data.coords[0].y); + // } + // vTaskDelay(pdMS_TO_TICKS(100)); + // } + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_37; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_36; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeIli9881cDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + ESP_LOGI(TAG, "Turn on the power for MIPI DSI PHY"); + esp_ldo_channel_handle_t ldo_mipi_phy = NULL; + esp_ldo_channel_config_t ldo_mipi_phy_config = { + .chan_id = LCD_MIPI_DSI_PHY_PWR_LDO_CHAN, + .voltage_mv = LCD_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, + }; + ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy)); + + ESP_LOGI(TAG, "Install MIPI DSI LCD control panel"); + esp_lcd_dsi_bus_handle_t mipi_dsi_bus; + esp_lcd_dsi_bus_config_t bus_config = { + .bus_id = 0, + .num_data_lanes = 2, + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, + .lane_bit_rate_mbps = 900, // 900MHz + }; + ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus)); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_dbi_io_config_t dbi_config = { + .virtual_channel = 0, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &panel_io)); + + ESP_LOGI(TAG, "Install LCD driver of ili9881c"); + esp_lcd_dpi_panel_config_t dpi_config = {.virtual_channel = 0, + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = 60, + .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, + .num_fbs = 2, + .video_timing = + { + .h_size = DISPLAY_WIDTH, + .v_size = DISPLAY_HEIGHT, + .hsync_pulse_width = 40, + .hsync_back_porch = 140, + .hsync_front_porch = 40, + .vsync_pulse_width = 4, + .vsync_back_porch = 20, + .vsync_front_porch = 20, + }, + .flags = { + .use_dma2d = false, + }}; + + ili9881c_vendor_config_t vendor_config = { + .init_cmds = tab5_lcd_ili9881c_specific_init_code_default, + .init_cmds_size = sizeof(tab5_lcd_ili9881c_specific_init_code_default) / + sizeof(tab5_lcd_ili9881c_specific_init_code_default[0]), + .mipi_config = + { + .dsi_bus = mipi_dsi_bus, + .dpi_config = &dpi_config, + .lane_num = 2, + }, + }; + + esp_lcd_panel_dev_config_t lcd_dev_config = {}; + lcd_dev_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + lcd_dev_config.reset_gpio_num = -1; + lcd_dev_config.bits_per_pixel = 16; + lcd_dev_config.vendor_config = &vendor_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9881c(panel_io, &lcd_dev_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + // ESP_ERROR_CHECK(esp_lcd_panel_mirror(disp_panel, false, true)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); + + display_ = new MipiLcdDisplay(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, + DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + M5StackTab5Board() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + I2cDetect(); + InitializePi4ioe(); + InitializeGt911TouchPad(); + InitializeIli9881cDisplay(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Tab5AudioCodec audio_codec(i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8388_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(M5StackTab5Board); diff --git a/main/boards/m5stack-tab5/sdkconfig.tab5 b/main/boards/m5stack-tab5/sdkconfig.tab5 index b0d5f89..7875790 100644 --- a/main/boards/m5stack-tab5/sdkconfig.tab5 +++ b/main/boards/m5stack-tab5/sdkconfig.tab5 @@ -1,3487 +1,3487 @@ -# -# Automatically generated file. DO NOT EDIT. -# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Configuration -# -CONFIG_SOC_ADC_SUPPORTED=y -CONFIG_SOC_ANA_CMPR_SUPPORTED=y -CONFIG_SOC_DEDICATED_GPIO_SUPPORTED=y -CONFIG_SOC_UART_SUPPORTED=y -CONFIG_SOC_GDMA_SUPPORTED=y -CONFIG_SOC_UHCI_SUPPORTED=y -CONFIG_SOC_AHB_GDMA_SUPPORTED=y -CONFIG_SOC_AXI_GDMA_SUPPORTED=y -CONFIG_SOC_DW_GDMA_SUPPORTED=y -CONFIG_SOC_DMA2D_SUPPORTED=y -CONFIG_SOC_GPTIMER_SUPPORTED=y -CONFIG_SOC_PCNT_SUPPORTED=y -CONFIG_SOC_LCDCAM_SUPPORTED=y -CONFIG_SOC_LCDCAM_CAM_SUPPORTED=y -CONFIG_SOC_LCDCAM_I80_LCD_SUPPORTED=y -CONFIG_SOC_LCDCAM_RGB_LCD_SUPPORTED=y -CONFIG_SOC_MIPI_CSI_SUPPORTED=y -CONFIG_SOC_MIPI_DSI_SUPPORTED=y -CONFIG_SOC_MCPWM_SUPPORTED=y -CONFIG_SOC_TWAI_SUPPORTED=y -CONFIG_SOC_ETM_SUPPORTED=y -CONFIG_SOC_PARLIO_SUPPORTED=y -CONFIG_SOC_ASYNC_MEMCPY_SUPPORTED=y -CONFIG_SOC_EMAC_SUPPORTED=y -CONFIG_SOC_USB_OTG_SUPPORTED=y -CONFIG_SOC_WIRELESS_HOST_SUPPORTED=y -CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED=y -CONFIG_SOC_TEMP_SENSOR_SUPPORTED=y -CONFIG_SOC_SUPPORTS_SECURE_DL_MODE=y -CONFIG_SOC_ULP_SUPPORTED=y -CONFIG_SOC_LP_CORE_SUPPORTED=y -CONFIG_SOC_EFUSE_KEY_PURPOSE_FIELD=y -CONFIG_SOC_EFUSE_SUPPORTED=y -CONFIG_SOC_RTC_FAST_MEM_SUPPORTED=y -CONFIG_SOC_RTC_MEM_SUPPORTED=y -CONFIG_SOC_RMT_SUPPORTED=y -CONFIG_SOC_I2S_SUPPORTED=y -CONFIG_SOC_SDM_SUPPORTED=y -CONFIG_SOC_GPSPI_SUPPORTED=y -CONFIG_SOC_LEDC_SUPPORTED=y -CONFIG_SOC_ISP_SUPPORTED=y -CONFIG_SOC_I2C_SUPPORTED=y -CONFIG_SOC_SYSTIMER_SUPPORTED=y -CONFIG_SOC_AES_SUPPORTED=y -CONFIG_SOC_MPI_SUPPORTED=y -CONFIG_SOC_SHA_SUPPORTED=y -CONFIG_SOC_HMAC_SUPPORTED=y -CONFIG_SOC_DIG_SIGN_SUPPORTED=y -CONFIG_SOC_ECC_SUPPORTED=y -CONFIG_SOC_ECC_EXTENDED_MODES_SUPPORTED=y -CONFIG_SOC_FLASH_ENC_SUPPORTED=y -CONFIG_SOC_SECURE_BOOT_SUPPORTED=y -CONFIG_SOC_BOD_SUPPORTED=y -CONFIG_SOC_VBAT_SUPPORTED=y -CONFIG_SOC_APM_SUPPORTED=y -CONFIG_SOC_PMU_SUPPORTED=y -CONFIG_SOC_DCDC_SUPPORTED=y -CONFIG_SOC_PAU_SUPPORTED=y -CONFIG_SOC_LP_TIMER_SUPPORTED=y -CONFIG_SOC_ULP_LP_UART_SUPPORTED=y -CONFIG_SOC_LP_GPIO_MATRIX_SUPPORTED=y -CONFIG_SOC_LP_PERIPHERALS_SUPPORTED=y -CONFIG_SOC_LP_I2C_SUPPORTED=y -CONFIG_SOC_LP_I2S_SUPPORTED=y -CONFIG_SOC_LP_SPI_SUPPORTED=y -CONFIG_SOC_LP_ADC_SUPPORTED=y -CONFIG_SOC_LP_VAD_SUPPORTED=y -CONFIG_SOC_SPIRAM_SUPPORTED=y -CONFIG_SOC_PSRAM_DMA_CAPABLE=y -CONFIG_SOC_SDMMC_HOST_SUPPORTED=y -CONFIG_SOC_CLK_TREE_SUPPORTED=y -CONFIG_SOC_ASSIST_DEBUG_SUPPORTED=y -CONFIG_SOC_DEBUG_PROBE_SUPPORTED=y -CONFIG_SOC_WDT_SUPPORTED=y -CONFIG_SOC_SPI_FLASH_SUPPORTED=y -CONFIG_SOC_TOUCH_SENSOR_SUPPORTED=y -CONFIG_SOC_RNG_SUPPORTED=y -CONFIG_SOC_GP_LDO_SUPPORTED=y -CONFIG_SOC_PPA_SUPPORTED=y -CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y -CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y -CONFIG_SOC_PM_SUPPORTED=y -CONFIG_SOC_BITSCRAMBLER_SUPPORTED=y -CONFIG_SOC_SIMD_INSTRUCTION_SUPPORTED=y -CONFIG_SOC_I3C_MASTER_SUPPORTED=y -CONFIG_SOC_XTAL_SUPPORT_40M=y -CONFIG_SOC_AES_SUPPORT_DMA=y -CONFIG_SOC_AES_SUPPORT_GCM=y -CONFIG_SOC_AES_GDMA=y -CONFIG_SOC_AES_SUPPORT_AES_128=y -CONFIG_SOC_AES_SUPPORT_AES_256=y -CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y -CONFIG_SOC_ADC_DIG_CTRL_SUPPORTED=y -CONFIG_SOC_ADC_DMA_SUPPORTED=y -CONFIG_SOC_ADC_PERIPH_NUM=2 -CONFIG_SOC_ADC_MAX_CHANNEL_NUM=8 -CONFIG_SOC_ADC_ATTEN_NUM=4 -CONFIG_SOC_ADC_DIGI_CONTROLLER_NUM=2 -CONFIG_SOC_ADC_PATT_LEN_MAX=16 -CONFIG_SOC_ADC_DIGI_MAX_BITWIDTH=12 -CONFIG_SOC_ADC_DIGI_MIN_BITWIDTH=12 -CONFIG_SOC_ADC_DIGI_IIR_FILTER_NUM=2 -CONFIG_SOC_ADC_DIGI_MONITOR_NUM=2 -CONFIG_SOC_ADC_DIGI_RESULT_BYTES=4 -CONFIG_SOC_ADC_DIGI_DATA_BYTES_PER_CONV=4 -CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH=83333 -CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW=611 -CONFIG_SOC_ADC_RTC_MIN_BITWIDTH=12 -CONFIG_SOC_ADC_RTC_MAX_BITWIDTH=12 -CONFIG_SOC_ADC_CALIBRATION_V1_SUPPORTED=y -CONFIG_SOC_ADC_SELF_HW_CALI_SUPPORTED=y -CONFIG_SOC_ADC_CALIB_CHAN_COMPENS_SUPPORTED=y -CONFIG_SOC_ADC_SHARED_POWER=y -CONFIG_SOC_BROWNOUT_RESET_SUPPORTED=y -CONFIG_SOC_SHARED_IDCACHE_SUPPORTED=y -CONFIG_SOC_CACHE_WRITEBACK_SUPPORTED=y -CONFIG_SOC_CACHE_FREEZE_SUPPORTED=y -CONFIG_SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE=y -CONFIG_SOC_CPU_CORES_NUM=2 -CONFIG_SOC_CPU_INTR_NUM=32 -CONFIG_SOC_CPU_HAS_FLEXIBLE_INTC=y -CONFIG_SOC_INT_CLIC_SUPPORTED=y -CONFIG_SOC_INT_HW_NESTED_SUPPORTED=y -CONFIG_SOC_BRANCH_PREDICTOR_SUPPORTED=y -CONFIG_SOC_CPU_COPROC_NUM=3 -CONFIG_SOC_CPU_HAS_FPU=y -CONFIG_SOC_CPU_HAS_FPU_EXT_ILL_BUG=y -CONFIG_SOC_CPU_HAS_HWLOOP=y -CONFIG_SOC_CPU_HAS_HWLOOP_STATE_BUG=y -CONFIG_SOC_CPU_HAS_PIE=y -CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y -CONFIG_SOC_CPU_BREAKPOINTS_NUM=3 -CONFIG_SOC_CPU_WATCHPOINTS_NUM=3 -CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=0x100 -CONFIG_SOC_CPU_HAS_PMA=y -CONFIG_SOC_CPU_IDRAM_SPLIT_USING_PMP=y -CONFIG_SOC_CPU_PMP_REGION_GRANULARITY=128 -CONFIG_SOC_CPU_HAS_LOCKUP_RESET=y -CONFIG_SOC_SIMD_PREFERRED_DATA_ALIGNMENT=16 -CONFIG_SOC_DS_SIGNATURE_MAX_BIT_LEN=4096 -CONFIG_SOC_DS_KEY_PARAM_MD_IV_LENGTH=16 -CONFIG_SOC_DS_KEY_CHECK_MAX_WAIT_US=1100 -CONFIG_SOC_DMA_CAN_ACCESS_FLASH=y -CONFIG_SOC_AHB_GDMA_VERSION=2 -CONFIG_SOC_GDMA_SUPPORT_CRC=y -CONFIG_SOC_GDMA_NUM_GROUPS_MAX=2 -CONFIG_SOC_GDMA_PAIRS_PER_GROUP_MAX=3 -CONFIG_SOC_AXI_GDMA_SUPPORT_PSRAM=y -CONFIG_SOC_GDMA_SUPPORT_ETM=y -CONFIG_SOC_GDMA_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_AXI_DMA_EXT_MEM_ENC_ALIGNMENT=16 -CONFIG_SOC_DMA2D_GROUPS=1 -CONFIG_SOC_DMA2D_TX_CHANNELS_PER_GROUP=3 -CONFIG_SOC_DMA2D_RX_CHANNELS_PER_GROUP=2 -CONFIG_SOC_ETM_GROUPS=1 -CONFIG_SOC_ETM_CHANNELS_PER_GROUP=50 -CONFIG_SOC_ETM_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_GPIO_PORT=1 -CONFIG_SOC_GPIO_PIN_COUNT=55 -CONFIG_SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER=y -CONFIG_SOC_GPIO_FLEX_GLITCH_FILTER_NUM=8 -CONFIG_SOC_GPIO_SUPPORT_PIN_HYS_FILTER=y -CONFIG_SOC_GPIO_SUPPORT_ETM=y -CONFIG_SOC_GPIO_SUPPORT_RTC_INDEPENDENT=y -CONFIG_SOC_GPIO_SUPPORT_DEEPSLEEP_WAKEUP=y -CONFIG_SOC_LP_IO_HAS_INDEPENDENT_WAKEUP_SOURCE=y -CONFIG_SOC_LP_IO_CLOCK_IS_INDEPENDENT=y -CONFIG_SOC_GPIO_VALID_GPIO_MASK=0x007FFFFFFFFFFFFF -CONFIG_SOC_GPIO_IN_RANGE_MAX=54 -CONFIG_SOC_GPIO_OUT_RANGE_MAX=54 -CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_VALID_GPIO_MASK=0 -CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_SUPPORTED_PIN_CNT=16 -CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0x007FFFFFFFFF0000 -CONFIG_SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX=y -CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=2 -CONFIG_SOC_CLOCKOUT_SUPPORT_CHANNEL_DIVIDER=y -CONFIG_SOC_DEBUG_PROBE_NUM_UNIT=1 -CONFIG_SOC_DEBUG_PROBE_MAX_OUTPUT_WIDTH=16 -CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y -CONFIG_SOC_RTCIO_PIN_COUNT=16 -CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y -CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y -CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y -CONFIG_SOC_RTCIO_EDGE_WAKE_SUPPORTED=y -CONFIG_SOC_DEDIC_GPIO_OUT_CHANNELS_NUM=8 -CONFIG_SOC_DEDIC_GPIO_IN_CHANNELS_NUM=8 -CONFIG_SOC_DEDIC_PERIPH_ALWAYS_ENABLE=y -CONFIG_SOC_ANA_CMPR_NUM=2 -CONFIG_SOC_ANA_CMPR_CAN_DISTINGUISH_EDGE=y -CONFIG_SOC_ANA_CMPR_SUPPORT_ETM=y -CONFIG_SOC_I2C_NUM=3 -CONFIG_SOC_HP_I2C_NUM=2 -CONFIG_SOC_I2C_FIFO_LEN=32 -CONFIG_SOC_I2C_CMD_REG_NUM=8 -CONFIG_SOC_I2C_SUPPORT_SLAVE=y -CONFIG_SOC_I2C_SUPPORT_HW_FSM_RST=y -CONFIG_SOC_I2C_SUPPORT_HW_CLR_BUS=y -CONFIG_SOC_I2C_SUPPORT_XTAL=y -CONFIG_SOC_I2C_SUPPORT_RTC=y -CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y -CONFIG_SOC_I2C_SLAVE_SUPPORT_BROADCAST=y -CONFIG_SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE=y -CONFIG_SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS=y -CONFIG_SOC_I2C_SLAVE_SUPPORT_SLAVE_UNMATCH=y -CONFIG_SOC_I2C_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_LP_I2C_NUM=1 -CONFIG_SOC_LP_I2C_FIFO_LEN=16 -CONFIG_SOC_I2S_NUM=3 -CONFIG_SOC_I2S_HW_VERSION_2=y -CONFIG_SOC_I2S_SUPPORTS_ETM=y -CONFIG_SOC_I2S_SUPPORTS_XTAL=y -CONFIG_SOC_I2S_SUPPORTS_APLL=y -CONFIG_SOC_I2S_SUPPORTS_PCM=y -CONFIG_SOC_I2S_SUPPORTS_PDM=y -CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y -CONFIG_SOC_I2S_SUPPORTS_PCM2PDM=y -CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y -CONFIG_SOC_I2S_SUPPORTS_PDM2PCM=y -CONFIG_SOC_I2S_SUPPORTS_PDM_RX_HP_FILTER=y -CONFIG_SOC_I2S_SUPPORTS_TX_SYNC_CNT=y -CONFIG_SOC_I2S_SUPPORTS_TDM=y -CONFIG_SOC_I2S_PDM_MAX_TX_LINES=2 -CONFIG_SOC_I2S_PDM_MAX_RX_LINES=4 -CONFIG_SOC_I2S_TDM_FULL_DATA_WIDTH=y -CONFIG_SOC_I2S_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_LP_I2S_NUM=1 -CONFIG_SOC_ISP_BF_SUPPORTED=y -CONFIG_SOC_ISP_CCM_SUPPORTED=y -CONFIG_SOC_ISP_DEMOSAIC_SUPPORTED=y -CONFIG_SOC_ISP_DVP_SUPPORTED=y -CONFIG_SOC_ISP_SHARPEN_SUPPORTED=y -CONFIG_SOC_ISP_COLOR_SUPPORTED=y -CONFIG_SOC_ISP_LSC_SUPPORTED=y -CONFIG_SOC_ISP_SHARE_CSI_BRG=y -CONFIG_SOC_ISP_NUMS=1 -CONFIG_SOC_ISP_DVP_CTLR_NUMS=1 -CONFIG_SOC_ISP_AE_CTLR_NUMS=1 -CONFIG_SOC_ISP_AE_BLOCK_X_NUMS=5 -CONFIG_SOC_ISP_AE_BLOCK_Y_NUMS=5 -CONFIG_SOC_ISP_AF_CTLR_NUMS=1 -CONFIG_SOC_ISP_AF_WINDOW_NUMS=3 -CONFIG_SOC_ISP_BF_TEMPLATE_X_NUMS=3 -CONFIG_SOC_ISP_BF_TEMPLATE_Y_NUMS=3 -CONFIG_SOC_ISP_CCM_DIMENSION=3 -CONFIG_SOC_ISP_DEMOSAIC_GRAD_RATIO_INT_BITS=2 -CONFIG_SOC_ISP_DEMOSAIC_GRAD_RATIO_DEC_BITS=4 -CONFIG_SOC_ISP_DEMOSAIC_GRAD_RATIO_RES_BITS=26 -CONFIG_SOC_ISP_DVP_DATA_WIDTH_MAX=16 -CONFIG_SOC_ISP_SHARPEN_TEMPLATE_X_NUMS=3 -CONFIG_SOC_ISP_SHARPEN_TEMPLATE_Y_NUMS=3 -CONFIG_SOC_ISP_SHARPEN_H_FREQ_COEF_INT_BITS=3 -CONFIG_SOC_ISP_SHARPEN_H_FREQ_COEF_DEC_BITS=5 -CONFIG_SOC_ISP_SHARPEN_H_FREQ_COEF_RES_BITS=24 -CONFIG_SOC_ISP_SHARPEN_M_FREQ_COEF_INT_BITS=3 -CONFIG_SOC_ISP_SHARPEN_M_FREQ_COEF_DEC_BITS=5 -CONFIG_SOC_ISP_SHARPEN_M_FREQ_COEF_RES_BITS=24 -CONFIG_SOC_ISP_HIST_CTLR_NUMS=1 -CONFIG_SOC_ISP_HIST_BLOCK_X_NUMS=5 -CONFIG_SOC_ISP_HIST_BLOCK_Y_NUMS=5 -CONFIG_SOC_ISP_HIST_SEGMENT_NUMS=16 -CONFIG_SOC_ISP_HIST_INTERVAL_NUMS=15 -CONFIG_SOC_ISP_LSC_GRAD_RATIO_INT_BITS=2 -CONFIG_SOC_ISP_LSC_GRAD_RATIO_DEC_BITS=8 -CONFIG_SOC_ISP_LSC_GRAD_RATIO_RES_BITS=22 -CONFIG_SOC_LEDC_SUPPORT_PLL_DIV_CLOCK=y -CONFIG_SOC_LEDC_SUPPORT_XTAL_CLOCK=y -CONFIG_SOC_LEDC_TIMER_NUM=4 -CONFIG_SOC_LEDC_CHANNEL_NUM=8 -CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=20 -CONFIG_SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED=y -CONFIG_SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX=16 -CONFIG_SOC_LEDC_SUPPORT_FADE_STOP=y -CONFIG_SOC_LEDC_FADE_PARAMS_BIT_WIDTH=10 -CONFIG_SOC_LEDC_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_MMU_PERIPH_NUM=2 -CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=2 -CONFIG_SOC_MMU_DI_VADDR_SHARED=y -CONFIG_SOC_MMU_PER_EXT_MEM_TARGET=y -CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 -CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 -CONFIG_SOC_PCNT_GROUPS=1 -CONFIG_SOC_PCNT_UNITS_PER_GROUP=4 -CONFIG_SOC_PCNT_CHANNELS_PER_UNIT=2 -CONFIG_SOC_PCNT_THRES_POINT_PER_UNIT=2 -CONFIG_SOC_PCNT_SUPPORT_RUNTIME_THRES_UPDATE=y -CONFIG_SOC_PCNT_SUPPORT_CLEAR_SIGNAL=y -CONFIG_SOC_PCNT_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_RMT_GROUPS=1 -CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP=4 -CONFIG_SOC_RMT_RX_CANDIDATES_PER_GROUP=4 -CONFIG_SOC_RMT_CHANNELS_PER_GROUP=8 -CONFIG_SOC_RMT_MEM_WORDS_PER_CHANNEL=48 -CONFIG_SOC_RMT_SUPPORT_RX_PINGPONG=y -CONFIG_SOC_RMT_SUPPORT_RX_DEMODULATION=y -CONFIG_SOC_RMT_SUPPORT_TX_ASYNC_STOP=y -CONFIG_SOC_RMT_SUPPORT_TX_LOOP_COUNT=y -CONFIG_SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP=y -CONFIG_SOC_RMT_SUPPORT_TX_SYNCHRO=y -CONFIG_SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY=y -CONFIG_SOC_RMT_SUPPORT_XTAL=y -CONFIG_SOC_RMT_SUPPORT_RC_FAST=y -CONFIG_SOC_RMT_SUPPORT_DMA=y -CONFIG_SOC_RMT_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_LCD_I80_SUPPORTED=y -CONFIG_SOC_LCD_RGB_SUPPORTED=y -CONFIG_SOC_LCDCAM_I80_NUM_BUSES=1 -CONFIG_SOC_LCDCAM_I80_BUS_WIDTH=24 -CONFIG_SOC_LCDCAM_RGB_NUM_PANELS=1 -CONFIG_SOC_LCDCAM_RGB_DATA_WIDTH=24 -CONFIG_SOC_LCD_SUPPORT_RGB_YUV_CONV=y -CONFIG_SOC_MCPWM_GROUPS=2 -CONFIG_SOC_MCPWM_TIMERS_PER_GROUP=3 -CONFIG_SOC_MCPWM_OPERATORS_PER_GROUP=3 -CONFIG_SOC_MCPWM_COMPARATORS_PER_OPERATOR=2 -CONFIG_SOC_MCPWM_EVENT_COMPARATORS_PER_OPERATOR=2 -CONFIG_SOC_MCPWM_GENERATORS_PER_OPERATOR=2 -CONFIG_SOC_MCPWM_TRIGGERS_PER_OPERATOR=2 -CONFIG_SOC_MCPWM_GPIO_FAULTS_PER_GROUP=3 -CONFIG_SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP=y -CONFIG_SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER=3 -CONFIG_SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP=3 -CONFIG_SOC_MCPWM_SWSYNC_CAN_PROPAGATE=y -CONFIG_SOC_MCPWM_SUPPORT_ETM=y -CONFIG_SOC_MCPWM_SUPPORT_EVENT_COMPARATOR=y -CONFIG_SOC_MCPWM_CAPTURE_CLK_FROM_GROUP=y -CONFIG_SOC_MCPWM_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_USB_OTG_PERIPH_NUM=2 -CONFIG_SOC_USB_UTMI_PHY_NUM=1 -CONFIG_SOC_PARLIO_GROUPS=1 -CONFIG_SOC_PARLIO_TX_UNITS_PER_GROUP=1 -CONFIG_SOC_PARLIO_RX_UNITS_PER_GROUP=1 -CONFIG_SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH=16 -CONFIG_SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH=16 -CONFIG_SOC_PARLIO_TX_CLK_SUPPORT_GATING=y -CONFIG_SOC_PARLIO_RX_CLK_SUPPORT_GATING=y -CONFIG_SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT=y -CONFIG_SOC_PARLIO_TRANS_BIT_ALIGN=y -CONFIG_SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION=y -CONFIG_SOC_PARLIO_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_PARLIO_SUPPORT_SPI_LCD=y -CONFIG_SOC_PARLIO_SUPPORT_I80_LCD=y -CONFIG_SOC_MPI_MEM_BLOCKS_NUM=4 -CONFIG_SOC_MPI_OPERATIONS_NUM=3 -CONFIG_SOC_RSA_MAX_BIT_LEN=4096 -CONFIG_SOC_SDMMC_USE_IOMUX=y -CONFIG_SOC_SDMMC_USE_GPIO_MATRIX=y -CONFIG_SOC_SDMMC_NUM_SLOTS=2 -CONFIG_SOC_SDMMC_DELAY_PHASE_NUM=4 -CONFIG_SOC_SDMMC_IO_POWER_EXTERNAL=y -CONFIG_SOC_SDMMC_PSRAM_DMA_CAPABLE=y -CONFIG_SOC_SDMMC_UHS_I_SUPPORTED=y -CONFIG_SOC_SHA_DMA_MAX_BUFFER_SIZE=3968 -CONFIG_SOC_SHA_SUPPORT_DMA=y -CONFIG_SOC_SHA_SUPPORT_RESUME=y -CONFIG_SOC_SHA_GDMA=y -CONFIG_SOC_SHA_SUPPORT_SHA1=y -CONFIG_SOC_SHA_SUPPORT_SHA224=y -CONFIG_SOC_SHA_SUPPORT_SHA256=y -CONFIG_SOC_SHA_SUPPORT_SHA384=y -CONFIG_SOC_SHA_SUPPORT_SHA512=y -CONFIG_SOC_SHA_SUPPORT_SHA512_224=y -CONFIG_SOC_SHA_SUPPORT_SHA512_256=y -CONFIG_SOC_SHA_SUPPORT_SHA512_T=y -CONFIG_SOC_ECDSA_SUPPORT_EXPORT_PUBKEY=y -CONFIG_SOC_ECDSA_SUPPORT_DETERMINISTIC_MODE=y -CONFIG_SOC_ECDSA_USES_MPI=y -CONFIG_SOC_SDM_GROUPS=1 -CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8 -CONFIG_SOC_SDM_CLK_SUPPORT_PLL_F80M=y -CONFIG_SOC_SDM_CLK_SUPPORT_XTAL=y -CONFIG_SOC_SPI_PERIPH_NUM=3 -CONFIG_SOC_SPI_MAX_CS_NUM=6 -CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64 -CONFIG_SOC_SPI_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_SPI_SUPPORT_SLAVE_HD_VER2=y -CONFIG_SOC_SPI_SLAVE_SUPPORT_SEG_TRANS=y -CONFIG_SOC_SPI_SUPPORT_DDRCLK=y -CONFIG_SOC_SPI_SUPPORT_CD_SIG=y -CONFIG_SOC_SPI_SUPPORT_OCT=y -CONFIG_SOC_SPI_SUPPORT_CLK_XTAL=y -CONFIG_SOC_SPI_SUPPORT_CLK_RC_FAST=y -CONFIG_SOC_SPI_SUPPORT_CLK_SPLL=y -CONFIG_SOC_MSPI_HAS_INDEPENT_IOMUX=y -CONFIG_SOC_MEMSPI_IS_INDEPENDENT=y -CONFIG_SOC_SPI_MAX_PRE_DIVIDER=16 -CONFIG_SOC_LP_SPI_PERIPH_NUM=y -CONFIG_SOC_LP_SPI_MAXIMUM_BUFFER_SIZE=64 -CONFIG_SOC_SPIRAM_XIP_SUPPORTED=y -CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_WAIT_IDLE=y -CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND=y -CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_RESUME=y -CONFIG_SOC_SPI_MEM_SUPPORT_IDLE_INTR=y -CONFIG_SOC_SPI_MEM_SUPPORT_SW_SUSPEND=y -CONFIG_SOC_SPI_MEM_SUPPORT_CHECK_SUS=y -CONFIG_SOC_SPI_MEM_SUPPORT_TIMING_TUNING=y -CONFIG_SOC_MEMSPI_TIMING_TUNING_BY_DQS=y -CONFIG_SOC_MEMSPI_TIMING_TUNING_BY_FLASH_DELAY=y -CONFIG_SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP=y -CONFIG_SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUT=y -CONFIG_SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED=y -CONFIG_SOC_MEMSPI_SRC_FREQ_40M_SUPPORTED=y -CONFIG_SOC_MEMSPI_SRC_FREQ_20M_SUPPORTED=y -CONFIG_SOC_MEMSPI_SRC_FREQ_120M_SUPPORTED=y -CONFIG_SOC_MEMSPI_FLASH_PSRAM_INDEPENDENT=y -CONFIG_SOC_SYSTIMER_COUNTER_NUM=2 -CONFIG_SOC_SYSTIMER_ALARM_NUM=3 -CONFIG_SOC_SYSTIMER_BIT_WIDTH_LO=32 -CONFIG_SOC_SYSTIMER_BIT_WIDTH_HI=20 -CONFIG_SOC_SYSTIMER_FIXED_DIVIDER=y -CONFIG_SOC_SYSTIMER_SUPPORT_RC_FAST=y -CONFIG_SOC_SYSTIMER_INT_LEVEL=y -CONFIG_SOC_SYSTIMER_ALARM_MISS_COMPENSATE=y -CONFIG_SOC_SYSTIMER_SUPPORT_ETM=y -CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 -CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 -CONFIG_SOC_TIMER_GROUPS=2 -CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2 -CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=54 -CONFIG_SOC_TIMER_GROUP_SUPPORT_XTAL=y -CONFIG_SOC_TIMER_GROUP_SUPPORT_RC_FAST=y -CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 -CONFIG_SOC_TIMER_SUPPORT_ETM=y -CONFIG_SOC_TIMER_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_MWDT_SUPPORT_XTAL=y -CONFIG_SOC_MWDT_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_TOUCH_SENSOR_VERSION=3 -CONFIG_SOC_TOUCH_SENSOR_NUM=14 -CONFIG_SOC_TOUCH_MIN_CHAN_ID=0 -CONFIG_SOC_TOUCH_MAX_CHAN_ID=13 -CONFIG_SOC_TOUCH_SUPPORT_SLEEP_WAKEUP=y -CONFIG_SOC_TOUCH_SUPPORT_BENCHMARK=y -CONFIG_SOC_TOUCH_SUPPORT_WATERPROOF=y -CONFIG_SOC_TOUCH_SUPPORT_PROX_SENSING=y -CONFIG_SOC_TOUCH_PROXIMITY_CHANNEL_NUM=3 -CONFIG_SOC_TOUCH_PROXIMITY_MEAS_DONE_SUPPORTED=y -CONFIG_SOC_TOUCH_SUPPORT_FREQ_HOP=y -CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=3 -CONFIG_SOC_TWAI_CONTROLLER_NUM=3 -CONFIG_SOC_TWAI_MASK_FILTER_NUM=1 -CONFIG_SOC_TWAI_CLK_SUPPORT_XTAL=y -CONFIG_SOC_TWAI_BRP_MIN=2 -CONFIG_SOC_TWAI_BRP_MAX=32768 -CONFIG_SOC_TWAI_SUPPORTS_RX_STATUS=y -CONFIG_SOC_TWAI_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_EFUSE_DIS_PAD_JTAG=y -CONFIG_SOC_EFUSE_DIS_USB_JTAG=y -CONFIG_SOC_EFUSE_DIS_DIRECT_BOOT=y -CONFIG_SOC_EFUSE_SOFT_DIS_JTAG=y -CONFIG_SOC_EFUSE_DIS_DOWNLOAD_MSPI=y -CONFIG_SOC_EFUSE_ECDSA_KEY=y -CONFIG_SOC_KEY_MANAGER_ECDSA_KEY_DEPLOY=y -CONFIG_SOC_KEY_MANAGER_FE_KEY_DEPLOY=y -CONFIG_SOC_SECURE_BOOT_V2_RSA=y -CONFIG_SOC_SECURE_BOOT_V2_ECC=y -CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=3 -CONFIG_SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS=y -CONFIG_SOC_SUPPORT_SECURE_BOOT_REVOKE_KEY=y -CONFIG_SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX=64 -CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES=y -CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_OPTIONS=y -CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_128=y -CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_256=y -CONFIG_SOC_UART_NUM=6 -CONFIG_SOC_UART_HP_NUM=5 -CONFIG_SOC_UART_LP_NUM=1 -CONFIG_SOC_UART_FIFO_LEN=128 -CONFIG_SOC_LP_UART_FIFO_LEN=16 -CONFIG_SOC_UART_BITRATE_MAX=5000000 -CONFIG_SOC_UART_SUPPORT_PLL_F80M_CLK=y -CONFIG_SOC_UART_SUPPORT_RTC_CLK=y -CONFIG_SOC_UART_SUPPORT_XTAL_CLK=y -CONFIG_SOC_UART_SUPPORT_WAKEUP_INT=y -CONFIG_SOC_UART_HAS_LP_UART=y -CONFIG_SOC_UART_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_UART_SUPPORT_FSM_TX_WAIT_SEND=y -CONFIG_SOC_UART_WAKEUP_CHARS_SEQ_MAX_LEN=5 -CONFIG_SOC_UART_WAKEUP_SUPPORT_ACTIVE_THRESH_MODE=y -CONFIG_SOC_UART_WAKEUP_SUPPORT_FIFO_THRESH_MODE=y -CONFIG_SOC_UART_WAKEUP_SUPPORT_START_BIT_MODE=y -CONFIG_SOC_UART_WAKEUP_SUPPORT_CHAR_SEQ_MODE=y -CONFIG_SOC_LP_I2S_SUPPORT_VAD=y -CONFIG_SOC_UHCI_NUM=1 -CONFIG_SOC_COEX_HW_PTI=y -CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21 -CONFIG_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 -CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP=y -CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP_MODE_PER_PIN=y -CONFIG_SOC_PM_EXT1_WAKEUP_BY_PMU=y -CONFIG_SOC_PM_SUPPORT_WIFI_WAKEUP=y -CONFIG_SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP=y -CONFIG_SOC_PM_SUPPORT_XTAL32K_PD=y -CONFIG_SOC_PM_SUPPORT_RC32K_PD=y -CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y -CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y -CONFIG_SOC_PM_SUPPORT_TOP_PD=y -CONFIG_SOC_PM_SUPPORT_CNNT_PD=y -CONFIG_SOC_PM_SUPPORT_RTC_PERIPH_PD=y -CONFIG_SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_ONLY=y -CONFIG_SOC_PM_CPU_RETENTION_BY_SW=y -CONFIG_SOC_PM_CACHE_RETENTION_BY_PAU=y -CONFIG_SOC_PM_PAU_LINK_NUM=4 -CONFIG_SOC_PM_PAU_REGDMA_LINK_MULTI_ADDR=y -CONFIG_SOC_PAU_IN_TOP_DOMAIN=y -CONFIG_SOC_CPU_IN_TOP_DOMAIN=y -CONFIG_SOC_PM_PAU_REGDMA_UPDATE_CACHE_BEFORE_WAIT_COMPARE=y -CONFIG_SOC_SLEEP_SYSTIMER_STALL_WORKAROUND=y -CONFIG_SOC_SLEEP_TGWDT_STOP_WORKAROUND=y -CONFIG_SOC_PM_RETENTION_MODULE_NUM=64 -CONFIG_SOC_PSRAM_VDD_POWER_MPLL=y -CONFIG_SOC_CLK_RC_FAST_SUPPORT_CALIBRATION=y -CONFIG_SOC_CLK_APLL_SUPPORTED=y -CONFIG_SOC_CLK_MPLL_SUPPORTED=y -CONFIG_SOC_CLK_SDIO_PLL_SUPPORTED=y -CONFIG_SOC_CLK_XTAL32K_SUPPORTED=y -CONFIG_SOC_CLK_RC32K_SUPPORTED=y -CONFIG_SOC_CLK_LP_FAST_SUPPORT_LP_PLL=y -CONFIG_SOC_CLK_LP_FAST_SUPPORT_XTAL=y -CONFIG_SOC_PERIPH_CLK_CTRL_SHARED=y -CONFIG_SOC_CLK_ANA_I2C_MST_HAS_ROOT_GATE=y -CONFIG_SOC_TEMPERATURE_SENSOR_LP_PLL_SUPPORT=y -CONFIG_SOC_TEMPERATURE_SENSOR_INTR_SUPPORT=y -CONFIG_SOC_TSENS_IS_INDEPENDENT_FROM_ADC=y -CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_ETM=y -CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_SLEEP_RETENTION=y -CONFIG_SOC_MEM_TCM_SUPPORTED=y -CONFIG_SOC_MEM_NON_CONTIGUOUS_SRAM=y -CONFIG_SOC_ASYNCHRONOUS_BUS_ERROR_MODE=y -CONFIG_SOC_EMAC_IEEE1588V2_SUPPORTED=y -CONFIG_SOC_EMAC_USE_MULTI_IO_MUX=y -CONFIG_SOC_EMAC_MII_USE_GPIO_MATRIX=y -CONFIG_SOC_JPEG_CODEC_SUPPORTED=y -CONFIG_SOC_JPEG_DECODE_SUPPORTED=y -CONFIG_SOC_JPEG_ENCODE_SUPPORTED=y -CONFIG_SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV=y -CONFIG_SOC_LCDCAM_CAM_PERIPH_NUM=1 -CONFIG_SOC_LCDCAM_CAM_DATA_WIDTH_MAX=16 -CONFIG_SOC_I3C_MASTER_PERIPH_NUM=y -CONFIG_SOC_I3C_MASTER_ADDRESS_TABLE_NUM=12 -CONFIG_SOC_I3C_MASTER_COMMAND_TABLE_NUM=12 -CONFIG_SOC_LP_CORE_SUPPORT_ETM=y -CONFIG_SOC_LP_CORE_SUPPORT_LP_ADC=y -CONFIG_SOC_LP_CORE_SUPPORT_LP_VAD=y -CONFIG_SOC_LP_CORE_SUPPORT_STORE_LOAD_EXCEPTIONS=y -CONFIG_IDF_CMAKE=y -CONFIG_IDF_TOOLCHAIN="gcc" -CONFIG_IDF_TOOLCHAIN_GCC=y -CONFIG_IDF_TARGET_ARCH_RISCV=y -CONFIG_IDF_TARGET_ARCH="riscv" -CONFIG_IDF_TARGET="esp32p4" -CONFIG_IDF_INIT_VERSION="5.5.0" -CONFIG_IDF_TARGET_ESP32P4=y -CONFIG_IDF_FIRMWARE_CHIP_ID=0x0012 - -# -# Build type -# -CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y -# CONFIG_APP_BUILD_TYPE_RAM is not set -CONFIG_APP_BUILD_GENERATE_BINARIES=y -CONFIG_APP_BUILD_BOOTLOADER=y -CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y -# CONFIG_APP_REPRODUCIBLE_BUILD is not set -# CONFIG_APP_NO_BLOBS is not set -# end of Build type - -# -# Bootloader config -# - -# -# Bootloader manager -# -CONFIG_BOOTLOADER_COMPILE_TIME_DATE=y -CONFIG_BOOTLOADER_PROJECT_VER=1 -# end of Bootloader manager - -# -# Application Rollback -# -CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y -# CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK is not set -# end of Application Rollback - -# -# Bootloader Rollback -# -# end of Bootloader Rollback - -CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x2000 -# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE is not set -# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set -CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF=y - -# -# Log -# -CONFIG_BOOTLOADER_LOG_VERSION_1=y -CONFIG_BOOTLOADER_LOG_VERSION=1 -CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y -# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set -# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set -# CONFIG_BOOTLOADER_LOG_LEVEL_INFO is not set -# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set -# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set -CONFIG_BOOTLOADER_LOG_LEVEL=0 - -# -# Format -# -# CONFIG_BOOTLOADER_LOG_COLORS is not set -CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y -# end of Format - -# -# Settings -# -CONFIG_BOOTLOADER_LOG_MODE_TEXT_EN=y -CONFIG_BOOTLOADER_LOG_MODE_TEXT=y -# CONFIG_BOOTLOADER_LOG_MODE_BINARY is not set -# end of Settings -# end of Log - -# -# Serial Flash Configurations -# -# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set -CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y -# end of Serial Flash Configurations - -# CONFIG_BOOTLOADER_FACTORY_RESET is not set -# CONFIG_BOOTLOADER_APP_TEST is not set -CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y -CONFIG_BOOTLOADER_WDT_ENABLE=y -# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set -CONFIG_BOOTLOADER_WDT_TIME_MS=9000 -CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y -CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON=y -CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS=y -CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0x10 -# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set -CONFIG_BOOTLOADER_RESERVE_RTC_MEM=y -# end of Bootloader config - -# -# Security features -# -CONFIG_SECURE_BOOT_V2_RSA_SUPPORTED=y -CONFIG_SECURE_BOOT_V2_ECC_SUPPORTED=y -CONFIG_SECURE_BOOT_V2_PREFERRED=y -# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set -# CONFIG_SECURE_BOOT is not set -# CONFIG_SECURE_FLASH_ENC_ENABLED is not set -CONFIG_SECURE_ROM_DL_MODE_ENABLED=y -# end of Security features - -# -# Application manager -# -CONFIG_APP_COMPILE_TIME_DATE=y -# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set -# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set -# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set -CONFIG_APP_RETRIEVE_LEN_ELF_SHA=9 -# end of Application manager - -CONFIG_ESP_ROM_HAS_CRC_LE=y -CONFIG_ESP_ROM_HAS_CRC_BE=y -CONFIG_ESP_ROM_UART_CLK_IS_XTAL=y -CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=6 -CONFIG_ESP_ROM_USB_OTG_NUM=5 -CONFIG_ESP_ROM_HAS_RETARGETABLE_LOCKING=y -CONFIG_ESP_ROM_GET_CLK_FREQ=y -CONFIG_ESP_ROM_HAS_RVFPLIB=y -CONFIG_ESP_ROM_HAS_HAL_WDT=y -CONFIG_ESP_ROM_HAS_HAL_SYSTIMER=y -CONFIG_ESP_ROM_HAS_LAYOUT_TABLE=y -CONFIG_ESP_ROM_WDT_INIT_PATCH=y -CONFIG_ESP_ROM_HAS_LP_ROM=y -CONFIG_ESP_ROM_WITHOUT_REGI2C=y -CONFIG_ESP_ROM_HAS_NEWLIB=y -CONFIG_ESP_ROM_HAS_NEWLIB_NANO_FORMAT=y -CONFIG_ESP_ROM_HAS_NEWLIB_NANO_PRINTF_FLOAT_BUG=y -CONFIG_ESP_ROM_HAS_VERSION=y -CONFIG_ESP_ROM_CLIC_INT_TYPE_PATCH=y -CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y -CONFIG_ESP_ROM_HAS_SUBOPTIMAL_NEWLIB_ON_MISALIGNED_MEMORY=y - -# -# Boot ROM Behavior -# -CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y -# CONFIG_BOOT_ROM_LOG_ALWAYS_OFF is not set -# CONFIG_BOOT_ROM_LOG_ON_GPIO_HIGH is not set -# CONFIG_BOOT_ROM_LOG_ON_GPIO_LOW is not set -# end of Boot ROM Behavior - -# -# Serial flasher config -# -# CONFIG_ESPTOOLPY_NO_STUB is not set -CONFIG_ESPTOOLPY_FLASHMODE_QIO=y -# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set -# CONFIG_ESPTOOLPY_FLASHMODE_DIO is not set -# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set -CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y -CONFIG_ESPTOOLPY_FLASHMODE="dio" -# CONFIG_ESPTOOLPY_FLASHFREQ_120M is not set -CONFIG_ESPTOOLPY_FLASHFREQ_80M=y -# CONFIG_ESPTOOLPY_FLASHFREQ_40M is not set -# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set -CONFIG_ESPTOOLPY_FLASHFREQ_VAL=80 -CONFIG_ESPTOOLPY_FLASHFREQ="80m" -# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y -# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE="16MB" -# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set -CONFIG_ESPTOOLPY_BEFORE_RESET=y -# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set -CONFIG_ESPTOOLPY_BEFORE="default_reset" -CONFIG_ESPTOOLPY_AFTER_RESET=y -# CONFIG_ESPTOOLPY_AFTER_NORESET is not set -CONFIG_ESPTOOLPY_AFTER="hard_reset" -CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 -# end of Serial flasher config - -# -# Partition Table -# -# CONFIG_PARTITION_TABLE_SINGLE_APP is not set -# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set -# CONFIG_PARTITION_TABLE_TWO_OTA is not set -# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v1/16m.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions/v1/16m.csv" -CONFIG_PARTITION_TABLE_OFFSET=0x8000 -CONFIG_PARTITION_TABLE_MD5=y -# end of Partition Table - -# -# Xiaozhi Assistant -# -CONFIG_OTA_URL="https://api.tenclass.net/xiaozhi/ota/" -CONFIG_LANGUAGE_ZH_CN=y -# CONFIG_LANGUAGE_ZH_TW is not set -# CONFIG_LANGUAGE_EN_US is not set -# CONFIG_LANGUAGE_JA_JP is not set -CONFIG_BOARD_TYPE_M5STACK_CORE_TAB5=y -# CONFIG_BOARD_TYPE_ESP32P4_NANO is not set -# CONFIG_BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B is not set -# CONFIG_BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC is not set -# CONFIG_USE_WECHAT_MESSAGE_STYLE is not set -CONFIG_USE_AFE_WAKE_WORD=y -CONFIG_USE_AUDIO_PROCESSOR=y -# CONFIG_USE_SERVER_AEC is not set -# CONFIG_USE_AUDIO_DEBUGGER is not set -CONFIG_IOT_PROTOCOL_MCP=y -# CONFIG_IOT_PROTOCOL_XIAOZHI is not set -# end of Xiaozhi Assistant - -# -# ESP Speech Recognition -# -CONFIG_MODEL_IN_FLASH=y -# CONFIG_MODEL_IN_SDCARD is not set -CONFIG_AFE_INTERFACE_V1=y -CONFIG_SR_NSN_WEBRTC=y -# CONFIG_SR_NSN_NSNET2 is not set -CONFIG_SR_VADN_WEBRTC=y -# CONFIG_SR_VADN_VADNET1_MEDIUM is not set - -# -# Load Multiple Wake Words (WakeNet9) -# -# CONFIG_SR_WN_WN9_HILEXIN is not set -# CONFIG_SR_WN_WN9_XIAOAITONGXUE is not set -CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y -# CONFIG_SR_WN_WN9_ALEXA is not set -# CONFIG_SR_WN_WN9_HIESP is not set -# CONFIG_SR_WN_WN9_JARVIS_TTS is not set -# CONFIG_SR_WN_WN9_COMPUTER_TTS is not set -# CONFIG_SR_WN_WN9_HEYWILLOW_TTS is not set -# CONFIG_SR_WN_WN9_SOPHIA_TTS is not set -# CONFIG_SR_WN_WN9_NIHAOXIAOXIN_TTS is not set -# CONFIG_SR_WN_WN9_XIAOMEITONGXUE_TTS is not set -# CONFIG_SR_WN_WN9_HEYPRINTER_TTS is not set -# CONFIG_SR_WN_WN9_XIAOLONGXIAOLONG_TTS is not set -# CONFIG_SR_WN_WN9_MIAOMIAOTONGXUE_TTS is not set -# CONFIG_SR_WN_WN9_HEYWANDA_TTS is not set -# CONFIG_SR_WN_WN9_HIMIAOMIAO_TTS is not set -# CONFIG_SR_WN_WN9_MYCROFT_TTS is not set -# CONFIG_SR_WN_WN9_HIJOY_TTS is not set -# CONFIG_SR_WN_WN9_HILILI_TTS is not set -# CONFIG_SR_WN_WN9_HITELLY_TTS is not set -# CONFIG_SR_WN_WN9_XIAOBINXIAOBIN_TTS is not set -# CONFIG_SR_WN_WN9_HAIXIAOWU_TTS is not set -# CONFIG_SR_WN_WN9_ASTROLABE_TTS is not set -# CONFIG_SR_WN_WN9_HEYILY_TTS2 is not set -# CONFIG_SR_WN_WN9_XIAOYAXIAOYA_TTS2 is not set -# CONFIG_SR_WN_WN9_HIJASON_TTS2 is not set -# CONFIG_SR_WN_WN9_LINAIBAN_TTS2 is not set -# CONFIG_SR_WN_WN9_XIAOSUROU_TTS2 is not set -# CONFIG_SR_WN_WN9_XIAOYUTONGXUE_TTS2 is not set -# CONFIG_SR_WN_WN9_XIAOMINGTONGXUE_TTS2 is not set -# CONFIG_SR_WN_WN9_XIAOKANGTONGXUE_TTS2 is not set -# CONFIG_SR_WN_WN9_XIAOJIANXIAOJIAN_TTS2 is not set -# CONFIG_SR_WN_WN9_XIAOTEXIAOTE_TTS2 is not set -# CONFIG_SR_WN_WN9_NIHAOXIAOYI_TTS2 is not set -# CONFIG_SR_WN_WN9_NIHAOBAIYING_TTS2 is not set -# CONFIG_SR_WN_WN9_HIWALLE_TTS2 is not set -# end of Load Multiple Wake Words (WakeNet9) - -CONFIG_SR_MN_CN_NONE=y -# CONFIG_SR_MN_CN_MULTINET7_QUANT is not set -# CONFIG_SR_MN_CN_MULTINET7_AC_QUANT is not set -CONFIG_SR_MN_EN_NONE=y -# CONFIG_SR_MN_EN_MULTINET7_QUANT is not set -# end of ESP Speech Recognition - -# -# Compiler options -# -CONFIG_COMPILER_OPTIMIZATION_DEBUG=y -# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set -# CONFIG_COMPILER_OPTIMIZATION_PERF is not set -# CONFIG_COMPILER_OPTIMIZATION_NONE is not set -CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y -# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set -# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set -CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE=y -# CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB is not set -CONFIG_COMPILER_FLOAT_LIB_FROM_RVFPLIB=y -CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 -# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set -CONFIG_COMPILER_HIDE_PATHS_MACROS=y -CONFIG_COMPILER_CXX_EXCEPTIONS=y -CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 -# CONFIG_COMPILER_CXX_RTTI is not set -CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y -# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set -# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set -# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set -# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set -# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set -# CONFIG_COMPILER_SAVE_RESTORE_LIBCALLS is not set -CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y -# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set -# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set -# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set -# CONFIG_COMPILER_DUMP_RTL_FILES is not set -CONFIG_COMPILER_RT_LIB_GCCLIB=y -CONFIG_COMPILER_RT_LIB_NAME="gcc" -CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y -# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set -# CONFIG_COMPILER_STATIC_ANALYZER is not set -# end of Compiler options - -# -# Component config -# - -# -# Application Level Tracing -# -# CONFIG_APPTRACE_DEST_JTAG is not set -CONFIG_APPTRACE_DEST_NONE=y -# CONFIG_APPTRACE_DEST_UART1 is not set -# CONFIG_APPTRACE_DEST_UART2 is not set -CONFIG_APPTRACE_DEST_UART_NONE=y -CONFIG_APPTRACE_UART_TASK_PRIO=1 -CONFIG_APPTRACE_LOCK_ENABLE=y -# end of Application Level Tracing - -# -# Bluetooth -# -# CONFIG_BT_ENABLED is not set - -# -# Common Options -# -# CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set -# end of Common Options -# end of Bluetooth - -# -# Console Library -# -# CONFIG_CONSOLE_SORTED_HELP is not set -# end of Console Library - -# -# Driver Configurations -# - -# -# Legacy TWAI Driver Configurations -# -# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy TWAI Driver Configurations - -# -# Legacy ADC Driver Configuration -# -# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set - -# -# Legacy ADC Calibration Configuration -# -# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set -# end of Legacy ADC Calibration Configuration -# end of Legacy ADC Driver Configuration - -# -# Legacy MCPWM Driver Configurations -# -# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy MCPWM Driver Configurations - -# -# Legacy Timer Group Driver Configurations -# -# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy Timer Group Driver Configurations - -# -# Legacy RMT Driver Configurations -# -# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy RMT Driver Configurations - -# -# Legacy I2S Driver Configurations -# -# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy I2S Driver Configurations - -# -# Legacy I2C Driver Configurations -# -# CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy I2C Driver Configurations - -# -# Legacy PCNT Driver Configurations -# -# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy PCNT Driver Configurations - -# -# Legacy SDM Driver Configurations -# -# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy SDM Driver Configurations - -# -# Legacy Temperature Sensor Driver Configurations -# -# CONFIG_TEMP_SENSOR_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_TEMP_SENSOR_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy Temperature Sensor Driver Configurations - -# -# Legacy Touch Sensor Driver Configurations -# -# CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_TOUCH_SKIP_LEGACY_CONFLICT_CHECK is not set -# end of Legacy Touch Sensor Driver Configurations -# end of Driver Configurations - -# -# eFuse Bit Manager -# -# CONFIG_EFUSE_CUSTOM_TABLE is not set -# CONFIG_EFUSE_VIRTUAL is not set -CONFIG_EFUSE_MAX_BLK_LEN=256 -# end of eFuse Bit Manager - -# -# ESP-TLS -# -CONFIG_ESP_TLS_USING_MBEDTLS=y -# CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set -CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y -# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set -# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set -# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set -# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set -# CONFIG_ESP_TLS_PSK_VERIFICATION is not set -# CONFIG_ESP_TLS_INSECURE is not set -# end of ESP-TLS - -# -# ADC and ADC Calibration -# -# CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM is not set -# CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is not set -# CONFIG_ADC_ENABLE_DEBUG_LOG is not set -# end of ADC and ADC Calibration - -# -# Wireless Coexistence -# -# CONFIG_ESP_COEX_GPIO_DEBUG is not set -# end of Wireless Coexistence - -# -# Common ESP-related -# -CONFIG_ESP_ERR_TO_NAME_LOOKUP=y -# end of Common ESP-related - -# -# ESP-Driver:Analog Comparator Configurations -# -CONFIG_ANA_CMPR_ISR_HANDLER_IN_IRAM=y -# CONFIG_ANA_CMPR_CTRL_FUNC_IN_IRAM is not set -# CONFIG_ANA_CMPR_ISR_CACHE_SAFE is not set -CONFIG_ANA_CMPR_OBJ_CACHE_SAFE=y -# CONFIG_ANA_CMPR_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:Analog Comparator Configurations - -# -# BitScrambler Configurations -# -# CONFIG_BITSCRAMBLER_CTRL_FUNC_IN_IRAM is not set -# end of BitScrambler Configurations - -# -# ESP-Driver:Camera Controller Configurations -# -# CONFIG_CAM_CTLR_MIPI_CSI_ISR_CACHE_SAFE is not set -# CONFIG_CAM_CTLR_ISP_DVP_ISR_CACHE_SAFE is not set -# CONFIG_CAM_CTLR_DVP_CAM_ISR_CACHE_SAFE is not set -# end of ESP-Driver:Camera Controller Configurations - -# -# ESP-Driver:GPIO Configurations -# -# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set -# end of ESP-Driver:GPIO Configurations - -# -# ESP-Driver:GPTimer Configurations -# -CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y -# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set -# CONFIG_GPTIMER_ISR_CACHE_SAFE is not set -CONFIG_GPTIMER_OBJ_CACHE_SAFE=y -# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:GPTimer Configurations - -# -# ESP-Driver:I2C Configurations -# -# CONFIG_I2C_ISR_IRAM_SAFE is not set -# CONFIG_I2C_ENABLE_DEBUG_LOG is not set -# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set -CONFIG_I2C_MASTER_ISR_HANDLER_IN_IRAM=y -# end of ESP-Driver:I2C Configurations - -# -# ESP-Driver:I2S Configurations -# -# CONFIG_I2S_ISR_IRAM_SAFE is not set -# CONFIG_I2S_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:I2S Configurations - -# -# ESP-Driver:ISP Configurations -# -# CONFIG_ISP_ISR_IRAM_SAFE is not set -# CONFIG_ISP_CTRL_FUNC_IN_IRAM is not set -# end of ESP-Driver:ISP Configurations - -# -# ESP-Driver:JPEG-Codec Configurations -# -# CONFIG_JPEG_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:JPEG-Codec Configurations - -# -# ESP-Driver:LEDC Configurations -# -# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set -# end of ESP-Driver:LEDC Configurations - -# -# ESP-Driver:MCPWM Configurations -# -# CONFIG_MCPWM_ISR_IRAM_SAFE is not set -# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set -# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:MCPWM Configurations - -# -# ESP-Driver:Parallel IO Configurations -# -CONFIG_PARLIO_TX_ISR_HANDLER_IN_IRAM=y -CONFIG_PARLIO_RX_ISR_HANDLER_IN_IRAM=y -# CONFIG_PARLIO_TX_ISR_CACHE_SAFE is not set -# CONFIG_PARLIO_RX_ISR_CACHE_SAFE is not set -CONFIG_PARLIO_OBJ_CACHE_SAFE=y -# CONFIG_PARLIO_ENABLE_DEBUG_LOG is not set -# CONFIG_PARLIO_ISR_IRAM_SAFE is not set -# end of ESP-Driver:Parallel IO Configurations - -# -# ESP-Driver:PCNT Configurations -# -# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set -# CONFIG_PCNT_ISR_IRAM_SAFE is not set -# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:PCNT Configurations - -# -# ESP-Driver:RMT Configurations -# -CONFIG_RMT_ENCODER_FUNC_IN_IRAM=y -CONFIG_RMT_TX_ISR_HANDLER_IN_IRAM=y -CONFIG_RMT_RX_ISR_HANDLER_IN_IRAM=y -# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set -# CONFIG_RMT_TX_ISR_CACHE_SAFE is not set -# CONFIG_RMT_RX_ISR_CACHE_SAFE is not set -CONFIG_RMT_OBJ_CACHE_SAFE=y -# CONFIG_RMT_ENABLE_DEBUG_LOG is not set -# CONFIG_RMT_ISR_IRAM_SAFE is not set -# end of ESP-Driver:RMT Configurations - -# -# ESP-Driver:Sigma Delta Modulator Configurations -# -# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set -# CONFIG_SDM_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:Sigma Delta Modulator Configurations - -# -# ESP-Driver:SPI Configurations -# -# CONFIG_SPI_MASTER_IN_IRAM is not set -CONFIG_SPI_MASTER_ISR_IN_IRAM=y -# CONFIG_SPI_SLAVE_IN_IRAM is not set -CONFIG_SPI_SLAVE_ISR_IN_IRAM=y -# end of ESP-Driver:SPI Configurations - -# -# ESP-Driver:Touch Sensor Configurations -# -# CONFIG_TOUCH_CTRL_FUNC_IN_IRAM is not set -# CONFIG_TOUCH_ISR_IRAM_SAFE is not set -# CONFIG_TOUCH_ENABLE_DEBUG_LOG is not set -# CONFIG_TOUCH_SKIP_FSM_CHECK is not set -# end of ESP-Driver:Touch Sensor Configurations - -# -# ESP-Driver:Temperature Sensor Configurations -# -# CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set -# CONFIG_TEMP_SENSOR_ISR_IRAM_SAFE is not set -# end of ESP-Driver:Temperature Sensor Configurations - -# -# ESP-Driver:TWAI Configurations -# -# CONFIG_TWAI_ISR_IN_IRAM is not set -# CONFIG_TWAI_ISR_CACHE_SAFE is not set -# CONFIG_TWAI_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:TWAI Configurations - -# -# ESP-Driver:UART Configurations -# -CONFIG_UART_ISR_IN_IRAM=y -# end of ESP-Driver:UART Configurations - -# -# ESP-Driver:UHCI Configurations -# -# CONFIG_UHCI_ISR_HANDLER_IN_IRAM is not set -# CONFIG_UHCI_ISR_CACHE_SAFE is not set -# CONFIG_UHCI_ENABLE_DEBUG_LOG is not set -# end of ESP-Driver:UHCI Configurations - -# -# ESP-Driver:USB Serial/JTAG Configuration -# -CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y -# end of ESP-Driver:USB Serial/JTAG Configuration - -# -# Ethernet -# -CONFIG_ETH_ENABLED=y -CONFIG_ETH_USE_ESP32_EMAC=y -CONFIG_ETH_PHY_INTERFACE_RMII=y -CONFIG_ETH_DMA_BUFFER_SIZE=512 -CONFIG_ETH_DMA_RX_BUFFER_NUM=20 -CONFIG_ETH_DMA_TX_BUFFER_NUM=10 -# CONFIG_ETH_SOFT_FLOW_CONTROL is not set -# CONFIG_ETH_IRAM_OPTIMIZATION is not set -CONFIG_ETH_USE_SPI_ETHERNET=y -# CONFIG_ETH_SPI_ETHERNET_DM9051 is not set -# CONFIG_ETH_SPI_ETHERNET_W5500 is not set -# CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL is not set -# CONFIG_ETH_USE_OPENETH is not set -# CONFIG_ETH_TRANSMIT_MUTEX is not set -# end of Ethernet - -# -# Event Loop Library -# -# CONFIG_ESP_EVENT_LOOP_PROFILING is not set -CONFIG_ESP_EVENT_POST_FROM_ISR=y -CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y -# end of Event Loop Library - -# -# GDB Stub -# -CONFIG_ESP_GDBSTUB_ENABLED=y -# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set -CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y -CONFIG_ESP_GDBSTUB_MAX_TASKS=32 -# end of GDB Stub - -# -# ESP HID -# -CONFIG_ESPHID_TASK_SIZE_BT=2048 -CONFIG_ESPHID_TASK_SIZE_BLE=4096 -# end of ESP HID - -# -# ESP HTTP client -# -CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y -# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set -# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set -# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set -CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000 -# end of ESP HTTP client - -# -# HTTP Server -# -CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048 -CONFIG_HTTPD_MAX_URI_LEN=2048 -CONFIG_HTTPD_ERR_RESP_NO_DELAY=y -CONFIG_HTTPD_PURGE_BUF_LEN=32 -# CONFIG_HTTPD_LOG_PURGE_DATA is not set -# CONFIG_HTTPD_WS_SUPPORT is not set -# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set -CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000 -# end of HTTP Server - -# -# ESP HTTPS OTA -# -# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set -# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set -CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000 -# end of ESP HTTPS OTA - -# -# ESP HTTPS server -# -# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set -CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000 -# CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK is not set -# end of ESP HTTPS server - -# -# Hardware Settings -# - -# -# Chip revision -# -# CONFIG_ESP32P4_REV_MIN_0 is not set -CONFIG_ESP32P4_REV_MIN_1=y -# CONFIG_ESP32P4_REV_MIN_100 is not set -CONFIG_ESP32P4_REV_MIN_FULL=1 -CONFIG_ESP_REV_MIN_FULL=1 - -# -# Maximum Supported ESP32-P4 Revision (Rev v1.99) -# -CONFIG_ESP32P4_REV_MAX_FULL=199 -CONFIG_ESP_REV_MAX_FULL=199 -CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0 -CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL=99 - -# -# Maximum Supported ESP32-P4 eFuse Block Revision (eFuse Block Rev v0.99) -# -# end of Chip revision - -# -# MAC Config -# -CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y -CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES_ONE=y -CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES=1 -CONFIG_ESP32P4_UNIVERSAL_MAC_ADDRESSES_ONE=y -CONFIG_ESP32P4_UNIVERSAL_MAC_ADDRESSES=1 -# CONFIG_ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC is not set -# end of MAC Config - -# -# Sleep Config -# -CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y -CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y -# CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU is not set -# CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND is not set -CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=0 -# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set -# CONFIG_ESP_SLEEP_DEBUG is not set -CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y -# end of Sleep Config - -# -# RTC Clock Config -# -CONFIG_RTC_CLK_SRC_INT_RC=y -# CONFIG_RTC_CLK_SRC_EXT_CRYS is not set -CONFIG_RTC_CLK_CAL_CYCLES=1024 -CONFIG_RTC_FAST_CLK_SRC_RC_FAST=y -# CONFIG_RTC_FAST_CLK_SRC_XTAL is not set -# end of RTC Clock Config - -# -# Peripheral Control -# -CONFIG_ESP_PERIPH_CTRL_FUNC_IN_IRAM=y -CONFIG_ESP_REGI2C_CTRL_FUNC_IN_IRAM=y -# end of Peripheral Control - -# -# ETM Configuration -# -# CONFIG_ETM_ENABLE_DEBUG_LOG is not set -# end of ETM Configuration - -# -# GDMA Configurations -# -CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y -CONFIG_GDMA_ISR_HANDLER_IN_IRAM=y -CONFIG_GDMA_OBJ_DRAM_SAFE=y -# CONFIG_GDMA_ENABLE_DEBUG_LOG is not set -# CONFIG_GDMA_ISR_IRAM_SAFE is not set -# end of GDMA Configurations - -# -# DW_GDMA Configurations -# -# CONFIG_DW_GDMA_ENABLE_DEBUG_LOG is not set -# end of DW_GDMA Configurations - -# -# 2D-DMA Configurations -# -# CONFIG_DMA2D_OPERATION_FUNC_IN_IRAM is not set -# CONFIG_DMA2D_ISR_IRAM_SAFE is not set -# end of 2D-DMA Configurations - -# -# Main XTAL Config -# -CONFIG_XTAL_FREQ_40=y -CONFIG_XTAL_FREQ=40 -# end of Main XTAL Config - -# -# DCDC Regulator Configurations -# -CONFIG_ESP_SLEEP_KEEP_DCDC_ALWAYS_ON=y -CONFIG_ESP_SLEEP_DCM_VSET_VAL_IN_SLEEP=14 -# end of DCDC Regulator Configurations - -# -# LDO Regulator Configurations -# -CONFIG_ESP_LDO_RESERVE_SPI_NOR_FLASH=y -CONFIG_ESP_LDO_CHAN_SPI_NOR_FLASH_DOMAIN=1 -CONFIG_ESP_LDO_VOLTAGE_SPI_NOR_FLASH_3300_MV=y -CONFIG_ESP_LDO_VOLTAGE_SPI_NOR_FLASH_DOMAIN=3300 -CONFIG_ESP_LDO_RESERVE_PSRAM=y -CONFIG_ESP_LDO_CHAN_PSRAM_DOMAIN=2 -CONFIG_ESP_LDO_VOLTAGE_PSRAM_1900_MV=y -CONFIG_ESP_LDO_VOLTAGE_PSRAM_DOMAIN=1900 -# end of LDO Regulator Configurations - -# -# Power Supplier -# - -# -# Brownout Detector -# -CONFIG_ESP_BROWNOUT_DET=y -CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7=y -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set -CONFIG_ESP_BROWNOUT_DET_LVL=7 -CONFIG_ESP_BROWNOUT_USE_INTR=y -# end of Brownout Detector - -# -# RTC Backup Battery -# -# CONFIG_ESP_VBAT_INIT_AUTO is not set -# CONFIG_ESP_VBAT_WAKEUP_CHIP_ON_VBAT_BROWNOUT is not set -# end of RTC Backup Battery -# end of Power Supplier - -CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y -CONFIG_ESP_INTR_IN_IRAM=y -# end of Hardware Settings - -# -# ESP-Driver:LCD Controller Configurations -# -# CONFIG_LCD_ENABLE_DEBUG_LOG is not set -# CONFIG_LCD_RGB_ISR_IRAM_SAFE is not set -# CONFIG_LCD_RGB_RESTART_IN_VSYNC is not set -# CONFIG_LCD_DSI_ISR_IRAM_SAFE is not set -# end of ESP-Driver:LCD Controller Configurations - -# -# ESP-MM: Memory Management Configurations -# -# CONFIG_ESP_MM_CACHE_MSYNC_C2M_CHUNKED_OPS is not set -# end of ESP-MM: Memory Management Configurations - -# -# ESP NETIF Adapter -# -CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 -# CONFIG_ESP_NETIF_PROVIDE_CUSTOM_IMPLEMENTATION is not set -CONFIG_ESP_NETIF_TCPIP_LWIP=y -# CONFIG_ESP_NETIF_LOOPBACK is not set -CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y -CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y -# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set -# CONFIG_ESP_NETIF_L2_TAP is not set -# CONFIG_ESP_NETIF_BRIDGE_EN is not set -# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set -# end of ESP NETIF Adapter - -# -# Partition API Configuration -# -# end of Partition API Configuration - -# -# PHY -# -# end of PHY - -# -# Power Management -# -CONFIG_PM_SLEEP_FUNC_IN_IRAM=y -# CONFIG_PM_ENABLE is not set -CONFIG_PM_SLP_IRAM_OPT=y -CONFIG_PM_SLP_DEFAULT_PARAMS_OPT=y -# CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP is not set -# end of Power Management - -# -# ESP PSRAM -# -CONFIG_SPIRAM=y - -# -# PSRAM config -# -CONFIG_SPIRAM_MODE_HEX=y -CONFIG_SPIRAM_SPEED_200M=y -# CONFIG_SPIRAM_SPEED_80M is not set -# CONFIG_SPIRAM_SPEED_20M is not set -CONFIG_SPIRAM_SPEED=200 -CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y -CONFIG_SPIRAM_RODATA=y -CONFIG_SPIRAM_XIP_FROM_PSRAM=y -CONFIG_SPIRAM_FLASH_LOAD_TO_PSRAM=y -# CONFIG_SPIRAM_ECC_ENABLE is not set -CONFIG_SPIRAM_BOOT_HW_INIT=y -CONFIG_SPIRAM_BOOT_INIT=y -CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y -# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set -# CONFIG_SPIRAM_USE_MEMMAP is not set -# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set -CONFIG_SPIRAM_USE_MALLOC=y -# CONFIG_SPIRAM_MEMTEST is not set -CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=4096 -CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y -CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=49152 -# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set -# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set -# end of PSRAM config -# end of ESP PSRAM - -# -# ESP Ringbuf -# -# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set -# end of ESP Ringbuf - -# -# ESP-ROM -# -CONFIG_ESP_ROM_PRINT_IN_IRAM=y -# end of ESP-ROM - -# -# ESP Security Specific -# -# end of ESP Security Specific - -# -# ESP System Settings -# -CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_360=y -CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=360 - -# -# Cache config -# -CONFIG_CACHE_L2_CACHE_128KB=y -# CONFIG_CACHE_L2_CACHE_256KB is not set -# CONFIG_CACHE_L2_CACHE_512KB is not set -CONFIG_CACHE_L2_CACHE_SIZE=0x20000 -CONFIG_CACHE_L2_CACHE_LINE_64B=y -# CONFIG_CACHE_L2_CACHE_LINE_128B is not set -CONFIG_CACHE_L2_CACHE_LINE_SIZE=64 -CONFIG_CACHE_L1_CACHE_LINE_SIZE=64 -# end of Cache config - -# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set -CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y -# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set -# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set -CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 -CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y -CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y -CONFIG_ESP_SYSTEM_NO_BACKTRACE=y -# CONFIG_ESP_SYSTEM_USE_EH_FRAME is not set -# CONFIG_ESP_SYSTEM_USE_FRAME_POINTER is not set - -# -# Memory protection -# -CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT=y -# CONFIG_ESP_SYSTEM_PMP_LP_CORE_RESERVE_MEM_EXECUTABLE is not set -# end of Memory protection - -CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 -CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 -CONFIG_ESP_MAIN_TASK_STACK_SIZE=10240 -CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y -# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set -# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set -CONFIG_ESP_MAIN_TASK_AFFINITY=0x0 -CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 -CONFIG_ESP_CONSOLE_UART_DEFAULT=y -# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set -# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set -# CONFIG_ESP_CONSOLE_NONE is not set -# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set -CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y -CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y -CONFIG_ESP_CONSOLE_UART=y -CONFIG_ESP_CONSOLE_UART_NUM=0 -CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0 -CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 -CONFIG_ESP_INT_WDT=y -CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 -CONFIG_ESP_INT_WDT_CHECK_CPU1=y -CONFIG_ESP_TASK_WDT_EN=y -CONFIG_ESP_TASK_WDT_INIT=y -# CONFIG_ESP_TASK_WDT_PANIC is not set -CONFIG_ESP_TASK_WDT_TIMEOUT_S=10 -CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y -CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y -# CONFIG_ESP_PANIC_HANDLER_IRAM is not set -# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set -CONFIG_ESP_DEBUG_OCDAWARE=y -CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y -CONFIG_ESP_SYSTEM_HW_STACK_GUARD=y -CONFIG_ESP_SYSTEM_HW_PC_RECORD=y -# end of ESP System Settings - -# -# IPC (Inter-Processor Call) -# -CONFIG_ESP_IPC_TASK_STACK_SIZE=1024 -CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y -CONFIG_ESP_IPC_ISR_ENABLE=y -# end of IPC (Inter-Processor Call) - -# -# ESP Timer (High Resolution Timer) -# -CONFIG_ESP_TIMER_IN_IRAM=y -# CONFIG_ESP_TIMER_PROFILING is not set -CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y -CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y -CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 -CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1 -# CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL is not set -CONFIG_ESP_TIMER_TASK_AFFINITY=0x0 -CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y -CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y -# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set -CONFIG_ESP_TIMER_IMPL_SYSTIMER=y -# end of ESP Timer (High Resolution Timer) - -# -# Wi-Fi -# -# CONFIG_ESP_HOST_WIFI_ENABLED is not set -CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=16 -CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 -CONFIG_ESP_WIFI_TX_BUFFER_TYPE=0 -CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=16 -CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM=32 -CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0 -CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5 -CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y -CONFIG_ESP_WIFI_TX_BA_WIN=6 -CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y -CONFIG_ESP_WIFI_RX_BA_WIN=16 -CONFIG_ESP_WIFI_NVS_ENABLED=y -CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752 -CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 -CONFIG_ESP_WIFI_IRAM_OPT=y -CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y -CONFIG_ESP_WIFI_RX_IRAM_OPT=y -CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y -CONFIG_ESP_WIFI_ENABLE_SAE_PK=y -CONFIG_ESP_WIFI_ENABLE_SAE_H2E=y -CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y -CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=y -CONFIG_ESP_WIFI_SLP_IRAM_OPT=y -CONFIG_ESP_WIFI_SLP_DEFAULT_MIN_ACTIVE_TIME=50 -CONFIG_ESP_WIFI_BSS_MAX_IDLE_SUPPORT=y -CONFIG_ESP_WIFI_SLP_DEFAULT_MAX_ACTIVE_TIME=10 -CONFIG_ESP_WIFI_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 -CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y -CONFIG_ESP_WIFI_GMAC_SUPPORT=y -CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y -CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7 -CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y -CONFIG_ESP_WIFI_MBEDTLS_TLS_CLIENT=y -CONFIG_ESP_WIFI_TX_HETB_QUEUE_NUM=3 -CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=y -# end of Wi-Fi - -# -# Core dump -# -# CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH is not set -# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set -CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y -# end of Core dump - -# -# FAT Filesystem support -# -CONFIG_FATFS_VOLUME_COUNT=2 -CONFIG_FATFS_LFN_NONE=y -# CONFIG_FATFS_LFN_HEAP is not set -# CONFIG_FATFS_LFN_STACK is not set -# CONFIG_FATFS_SECTOR_512 is not set -CONFIG_FATFS_SECTOR_4096=y -# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set -CONFIG_FATFS_CODEPAGE_437=y -# CONFIG_FATFS_CODEPAGE_720 is not set -# CONFIG_FATFS_CODEPAGE_737 is not set -# CONFIG_FATFS_CODEPAGE_771 is not set -# CONFIG_FATFS_CODEPAGE_775 is not set -# CONFIG_FATFS_CODEPAGE_850 is not set -# CONFIG_FATFS_CODEPAGE_852 is not set -# CONFIG_FATFS_CODEPAGE_855 is not set -# CONFIG_FATFS_CODEPAGE_857 is not set -# CONFIG_FATFS_CODEPAGE_860 is not set -# CONFIG_FATFS_CODEPAGE_861 is not set -# CONFIG_FATFS_CODEPAGE_862 is not set -# CONFIG_FATFS_CODEPAGE_863 is not set -# CONFIG_FATFS_CODEPAGE_864 is not set -# CONFIG_FATFS_CODEPAGE_865 is not set -# CONFIG_FATFS_CODEPAGE_866 is not set -# CONFIG_FATFS_CODEPAGE_869 is not set -# CONFIG_FATFS_CODEPAGE_932 is not set -# CONFIG_FATFS_CODEPAGE_936 is not set -# CONFIG_FATFS_CODEPAGE_949 is not set -# CONFIG_FATFS_CODEPAGE_950 is not set -CONFIG_FATFS_CODEPAGE=437 -CONFIG_FATFS_FS_LOCK=0 -CONFIG_FATFS_TIMEOUT_MS=10000 -CONFIG_FATFS_PER_FILE_CACHE=y -CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y -# CONFIG_FATFS_USE_FASTSEEK is not set -CONFIG_FATFS_USE_STRFUNC_NONE=y -# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set -# CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV is not set -CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0 -# CONFIG_FATFS_IMMEDIATE_FSYNC is not set -# CONFIG_FATFS_USE_LABEL is not set -CONFIG_FATFS_LINK_LOCK=y -# CONFIG_FATFS_USE_DYN_BUFFERS is not set - -# -# File system free space calculation behavior -# -CONFIG_FATFS_DONT_TRUST_FREE_CLUSTER_CNT=0 -CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0 -# end of File system free space calculation behavior -# end of FAT Filesystem support - -# -# FreeRTOS -# - -# -# Kernel -# -# CONFIG_FREERTOS_UNICORE is not set -CONFIG_FREERTOS_HZ=1000 -# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set -# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set -CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y -CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 -CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 -# CONFIG_FREERTOS_USE_IDLE_HOOK is not set -# CONFIG_FREERTOS_USE_TICK_HOOK is not set -CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 -# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set -CONFIG_FREERTOS_USE_TIMERS=y -CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc" -# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set -# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set -CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY=y -CONFIG_FREERTOS_TIMER_SERVICE_TASK_CORE_AFFINITY=0x7FFFFFFF -CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 -CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 -CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 -CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 -CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 -CONFIG_FREERTOS_USE_TRACE_FACILITY=y -CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y -# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set -CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y -CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y -CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U32=y -# CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U64 is not set -# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set -# end of Kernel - -# -# Port -# -CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y -# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set -CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y -# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set -# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set -CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y -CONFIG_FREERTOS_ISR_STACKSIZE=1536 -CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y -CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y -CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y -# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set -CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y -CONFIG_FREERTOS_RUN_TIME_STATS_USING_ESP_TIMER=y -# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set -# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set -# end of Port - -# -# Extra -# -CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y -# end of Extra - -CONFIG_FREERTOS_PORT=y -CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF -CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y -CONFIG_FREERTOS_DEBUG_OCDAWARE=y -CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y -CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y -CONFIG_FREERTOS_NUMBER_OF_CORES=2 -CONFIG_FREERTOS_IN_IRAM=y -# end of FreeRTOS - -# -# Hardware Abstraction Layer (HAL) and Low Level (LL) -# -CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y -# CONFIG_HAL_ASSERTION_DISABLE is not set -# CONFIG_HAL_ASSERTION_SILENT is not set -# CONFIG_HAL_ASSERTION_ENABLE is not set -CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 -CONFIG_HAL_SYSTIMER_USE_ROM_IMPL=y -CONFIG_HAL_WDT_USE_ROM_IMPL=y -# end of Hardware Abstraction Layer (HAL) and Low Level (LL) - -# -# Heap memory debugging -# -CONFIG_HEAP_POISONING_DISABLED=y -# CONFIG_HEAP_POISONING_LIGHT is not set -# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set -CONFIG_HEAP_TRACING_OFF=y -# CONFIG_HEAP_TRACING_STANDALONE is not set -# CONFIG_HEAP_TRACING_TOHOST is not set -# CONFIG_HEAP_USE_HOOKS is not set -# CONFIG_HEAP_TASK_TRACKING is not set -# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set -# CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set -# end of Heap memory debugging - -# -# Log -# -CONFIG_LOG_VERSION_1=y -# CONFIG_LOG_VERSION_2 is not set -CONFIG_LOG_VERSION=1 - -# -# Log Level -# -# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set -# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set -# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set -CONFIG_LOG_DEFAULT_LEVEL_INFO=y -# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set -# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set -CONFIG_LOG_DEFAULT_LEVEL=3 -CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y -# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set -# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set -CONFIG_LOG_MAXIMUM_LEVEL=3 - -# -# Level Settings -# -# CONFIG_LOG_MASTER_LEVEL is not set -CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y -# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set -# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set -CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y -# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set -CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y -CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31 -# end of Level Settings -# end of Log Level - -# -# Format -# -# CONFIG_LOG_COLORS is not set -CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y -# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set -# end of Format - -# -# Settings -# -CONFIG_LOG_MODE_TEXT_EN=y -CONFIG_LOG_MODE_TEXT=y -# CONFIG_LOG_MODE_BINARY is not set -# end of Settings - -CONFIG_LOG_IN_IRAM=y -# end of Log - -# -# LWIP -# -CONFIG_LWIP_ENABLE=y -CONFIG_LWIP_LOCAL_HOSTNAME="espressif" -CONFIG_LWIP_TCPIP_TASK_PRIO=18 -# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set -# CONFIG_LWIP_CHECK_THREAD_SAFETY is not set -CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y -# CONFIG_LWIP_L2_TO_L3_COPY is not set -# CONFIG_LWIP_IRAM_OPTIMIZATION is not set -# CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION is not set -CONFIG_LWIP_TIMERS_ONDEMAND=y -CONFIG_LWIP_ND6=y -# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set -CONFIG_LWIP_MAX_SOCKETS=10 -# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set -# CONFIG_LWIP_SO_LINGER is not set -CONFIG_LWIP_SO_REUSE=y -CONFIG_LWIP_SO_REUSE_RXTOALL=y -# CONFIG_LWIP_SO_RCVBUF is not set -# CONFIG_LWIP_NETBUF_RECVINFO is not set -CONFIG_LWIP_IP_DEFAULT_TTL=64 -CONFIG_LWIP_IP4_FRAG=y -CONFIG_LWIP_IP6_FRAG=y -# CONFIG_LWIP_IP4_REASSEMBLY is not set -# CONFIG_LWIP_IP6_REASSEMBLY is not set -CONFIG_LWIP_IP_REASS_MAX_PBUFS=10 -# CONFIG_LWIP_IP_FORWARD is not set -# CONFIG_LWIP_STATS is not set -CONFIG_LWIP_ESP_GRATUITOUS_ARP=y -CONFIG_LWIP_GARP_TMR_INTERVAL=60 -CONFIG_LWIP_ESP_MLDV6_REPORT=y -CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 -CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 -CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y -# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set -# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set -# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set -CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y -# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set -CONFIG_LWIP_DHCP_OPTIONS_LEN=68 -CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0 -CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 - -# -# DHCP server -# -CONFIG_LWIP_DHCPS=y -CONFIG_LWIP_DHCPS_LEASE_UNIT=60 -CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 -CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y -CONFIG_LWIP_DHCPS_ADD_DNS=y -# end of DHCP server - -# CONFIG_LWIP_AUTOIP is not set -CONFIG_LWIP_IPV4=y -CONFIG_LWIP_IPV6=y -# CONFIG_LWIP_IPV6_AUTOCONFIG is not set -CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 -# CONFIG_LWIP_IPV6_FORWARD is not set -# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set -CONFIG_LWIP_NETIF_LOOPBACK=y -CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 - -# -# TCP -# -CONFIG_LWIP_MAX_ACTIVE_TCP=16 -CONFIG_LWIP_MAX_LISTENING_TCP=16 -CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y -CONFIG_LWIP_TCP_MAXRTX=12 -CONFIG_LWIP_TCP_SYNMAXRTX=12 -CONFIG_LWIP_TCP_MSS=1440 -CONFIG_LWIP_TCP_TMR_INTERVAL=250 -CONFIG_LWIP_TCP_MSL=60000 -CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT=20000 -CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760 -CONFIG_LWIP_TCP_WND_DEFAULT=5760 -CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 -CONFIG_LWIP_TCP_ACCEPTMBOX_SIZE=6 -CONFIG_LWIP_TCP_QUEUE_OOSEQ=y -CONFIG_LWIP_TCP_OOSEQ_TIMEOUT=6 -CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=0 -# CONFIG_LWIP_TCP_SACK_OUT is not set -CONFIG_LWIP_TCP_OVERSIZE_MSS=y -# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set -# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set -# CONFIG_LWIP_WND_SCALE is not set -CONFIG_LWIP_TCP_RTO_TIME=1500 -# end of TCP - -# -# UDP -# -CONFIG_LWIP_MAX_UDP_PCBS=16 -CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 -# end of UDP - -# -# Checksums -# -# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set -# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set -CONFIG_LWIP_CHECKSUM_CHECK_ICMP=y -# end of Checksums - -CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 -CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y -# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set -# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set -CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF -CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 -CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 -CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5 -CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3 -CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10 -CONFIG_LWIP_PPP_SUPPORT=y -CONFIG_LWIP_PPP_ENABLE_IPV4=y -CONFIG_LWIP_PPP_ENABLE_IPV6=y -# CONFIG_LWIP_PPP_NOTIFY_PHASE_SUPPORT is not set -# CONFIG_LWIP_PPP_PAP_SUPPORT is not set -# CONFIG_LWIP_PPP_CHAP_SUPPORT is not set -# CONFIG_LWIP_PPP_MSCHAP_SUPPORT is not set -# CONFIG_LWIP_PPP_MPPE_SUPPORT is not set -CONFIG_LWIP_PPP_SERVER_SUPPORT=y -CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=y -# CONFIG_LWIP_ENABLE_LCP_ECHO is not set -# CONFIG_LWIP_PPP_DEBUG_ON is not set -# CONFIG_LWIP_USE_EXTERNAL_MBEDTLS is not set -# CONFIG_LWIP_SLIP_SUPPORT is not set - -# -# ICMP -# -CONFIG_LWIP_ICMP=y -# CONFIG_LWIP_MULTICAST_PING is not set -# CONFIG_LWIP_BROADCAST_PING is not set -# end of ICMP - -# -# LWIP RAW API -# -CONFIG_LWIP_MAX_RAW_PCBS=16 -# end of LWIP RAW API - -# -# SNTP -# -CONFIG_LWIP_SNTP_MAX_SERVERS=1 -# CONFIG_LWIP_DHCP_GET_NTP_SRV is not set -CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000 -CONFIG_LWIP_SNTP_STARTUP_DELAY=y -CONFIG_LWIP_SNTP_MAXIMUM_STARTUP_DELAY=5000 -# end of SNTP - -# -# DNS -# -CONFIG_LWIP_DNS_MAX_HOST_IP=1 -CONFIG_LWIP_DNS_MAX_SERVERS=3 -# CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set -# CONFIG_LWIP_DNS_SETSERVER_WITH_NETIF is not set -# CONFIG_LWIP_USE_ESP_GETADDRINFO is not set -# end of DNS - -CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7 -CONFIG_LWIP_ESP_LWIP_ASSERT=y - -# -# Hooks -# -# CONFIG_LWIP_HOOK_TCP_ISN_NONE is not set -CONFIG_LWIP_HOOK_TCP_ISN_DEFAULT=y -# CONFIG_LWIP_HOOK_TCP_ISN_CUSTOM is not set -CONFIG_LWIP_HOOK_IP6_ROUTE_NONE=y -# CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT is not set -# CONFIG_LWIP_HOOK_IP6_ROUTE_CUSTOM is not set -CONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y -# CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT is not set -# CONFIG_LWIP_HOOK_ND6_GET_GW_CUSTOM is not set -CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_NONE=y -# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_DEFAULT is not set -# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM is not set -CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_NONE=y -# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_DEFAULT is not set -# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_CUSTOM is not set -CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y -# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set -# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set -CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_NONE=y -# CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_CUSTOM is not set -# CONFIG_LWIP_HOOK_IP6_INPUT_NONE is not set -CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y -# CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set -# end of Hooks - -# CONFIG_LWIP_DEBUG is not set -# end of LWIP - -# -# mbedTLS -# -# CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC is not set -CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y -# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set -# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set -CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y -CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 -CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 -CONFIG_MBEDTLS_DYNAMIC_BUFFER=y -# CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA is not set -# CONFIG_MBEDTLS_DEBUG is not set - -# -# mbedTLS v3.x related -# -# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set -# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set -# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set -# CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE is not set -CONFIG_MBEDTLS_PKCS7_C=y -# end of mbedTLS v3.x related - -# -# Certificate Bundle -# -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y -# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set -# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set -# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set -# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST is not set -CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 -# end of Certificate Bundle - -# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set -CONFIG_MBEDTLS_CMAC_C=y -CONFIG_MBEDTLS_HARDWARE_AES=y -CONFIG_MBEDTLS_AES_USE_INTERRUPT=y -CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 -CONFIG_MBEDTLS_HARDWARE_GCM=y -CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y -CONFIG_MBEDTLS_HARDWARE_MPI=y -# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set -CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y -CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 -CONFIG_MBEDTLS_HARDWARE_SHA=y -CONFIG_MBEDTLS_HARDWARE_ECC=y -CONFIG_MBEDTLS_ECC_OTHER_CURVES_SOFT_FALLBACK=y -CONFIG_MBEDTLS_ROM_MD5=y -# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set -# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set -CONFIG_MBEDTLS_HAVE_TIME=y -# CONFIG_MBEDTLS_PLATFORM_TIME_ALT is not set -# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set -CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y -CONFIG_MBEDTLS_SHA1_C=y -CONFIG_MBEDTLS_SHA512_C=y -# CONFIG_MBEDTLS_SHA3_C is not set -CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y -# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set -# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set -# CONFIG_MBEDTLS_TLS_DISABLED is not set -CONFIG_MBEDTLS_TLS_SERVER=y -CONFIG_MBEDTLS_TLS_CLIENT=y -CONFIG_MBEDTLS_TLS_ENABLED=y - -# -# TLS Key Exchange Methods -# -# CONFIG_MBEDTLS_PSK_MODES is not set -CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y -CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y -# end of TLS Key Exchange Methods - -CONFIG_MBEDTLS_SSL_RENEGOTIATION=y -CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y -# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set -# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set -CONFIG_MBEDTLS_SSL_ALPN=y -CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y -CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y - -# -# Symmetric Ciphers -# -CONFIG_MBEDTLS_AES_C=y -# CONFIG_MBEDTLS_CAMELLIA_C is not set -# CONFIG_MBEDTLS_DES_C is not set -# CONFIG_MBEDTLS_BLOWFISH_C is not set -# CONFIG_MBEDTLS_XTEA_C is not set -CONFIG_MBEDTLS_CCM_C=y -CONFIG_MBEDTLS_GCM_C=y -# CONFIG_MBEDTLS_NIST_KW_C is not set -# end of Symmetric Ciphers - -# CONFIG_MBEDTLS_RIPEMD160_C is not set - -# -# Certificates -# -CONFIG_MBEDTLS_PEM_PARSE_C=y -CONFIG_MBEDTLS_PEM_WRITE_C=y -CONFIG_MBEDTLS_X509_CRL_PARSE_C=y -CONFIG_MBEDTLS_X509_CSR_PARSE_C=y -# end of Certificates - -CONFIG_MBEDTLS_ECP_C=y -CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y -CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y -# CONFIG_MBEDTLS_DHM_C is not set -CONFIG_MBEDTLS_ECDH_C=y -CONFIG_MBEDTLS_ECDSA_C=y -# CONFIG_MBEDTLS_ECJPAKE_C is not set -CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y -CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y -CONFIG_MBEDTLS_ECP_NIST_OPTIM=y -# CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM is not set -# CONFIG_MBEDTLS_POLY1305_C is not set -# CONFIG_MBEDTLS_CHACHA20_C is not set -# CONFIG_MBEDTLS_HKDF_C is not set -# CONFIG_MBEDTLS_THREADING_C is not set -CONFIG_MBEDTLS_ERROR_STRINGS=y -CONFIG_MBEDTLS_FS_IO=y -# CONFIG_MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION is not set -# end of mbedTLS - -# -# ESP-MQTT Configurations -# -CONFIG_MQTT_PROTOCOL_311=y -# CONFIG_MQTT_PROTOCOL_5 is not set -CONFIG_MQTT_TRANSPORT_SSL=y -CONFIG_MQTT_TRANSPORT_WEBSOCKET=y -CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y -# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set -# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set -# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set -# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set -# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set -# CONFIG_MQTT_CUSTOM_OUTBOX is not set -# end of ESP-MQTT Configurations - -# -# LibC -# -CONFIG_LIBC_NEWLIB=y -# CONFIG_LIBC_PICOLIBC is not set -CONFIG_LIBC_MISC_IN_IRAM=y -CONFIG_LIBC_LOCKS_PLACE_IN_IRAM=y -CONFIG_LIBC_STDOUT_LINE_ENDING_CRLF=y -# CONFIG_LIBC_STDOUT_LINE_ENDING_LF is not set -# CONFIG_LIBC_STDOUT_LINE_ENDING_CR is not set -# CONFIG_LIBC_STDIN_LINE_ENDING_CRLF is not set -# CONFIG_LIBC_STDIN_LINE_ENDING_LF is not set -CONFIG_LIBC_STDIN_LINE_ENDING_CR=y -# CONFIG_LIBC_NEWLIB_NANO_FORMAT is not set -CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y -# CONFIG_LIBC_TIME_SYSCALL_USE_RTC is not set -# CONFIG_LIBC_TIME_SYSCALL_USE_HRT is not set -# CONFIG_LIBC_TIME_SYSCALL_USE_NONE is not set -# CONFIG_LIBC_OPTIMIZED_MISALIGNED_ACCESS is not set -# end of LibC - -# -# NVS -# -# CONFIG_NVS_ENCRYPTION is not set -# CONFIG_NVS_ASSERT_ERROR_CHECK is not set -# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set -# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set -# end of NVS - -# -# OpenThread -# -# CONFIG_OPENTHREAD_ENABLED is not set - -# -# OpenThread Spinel -# -# CONFIG_OPENTHREAD_SPINEL_ONLY is not set -# end of OpenThread Spinel -# end of OpenThread - -# -# Protocomm -# -CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y -CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y -CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y -CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y -# end of Protocomm - -# -# PThreads -# -CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 -CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 -CONFIG_PTHREAD_STACK_MIN=768 -CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y -# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set -# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set -CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 -CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" -# end of PThreads - -# -# MMU Config -# -CONFIG_MMU_PAGE_SIZE_64KB=y -CONFIG_MMU_PAGE_MODE="64KB" -CONFIG_MMU_PAGE_SIZE=0x10000 -# end of MMU Config - -# -# Main Flash configuration -# - -# -# SPI Flash behavior when brownout -# -CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y -CONFIG_SPI_FLASH_BROWNOUT_RESET=y -# end of SPI Flash behavior when brownout - -# -# Optional and Experimental Features (READ DOCS FIRST) -# - -# -# Features here require specific hardware (READ DOCS FIRST!) -# -# CONFIG_SPI_FLASH_HPM_ENA is not set -CONFIG_SPI_FLASH_HPM_AUTO=y -# CONFIG_SPI_FLASH_HPM_DIS is not set -CONFIG_SPI_FLASH_HPM_ON=y -CONFIG_SPI_FLASH_HPM_DC_AUTO=y -# CONFIG_SPI_FLASH_HPM_DC_DISABLE is not set -# CONFIG_SPI_FLASH_AUTO_SUSPEND is not set -CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 -# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set -# CONFIG_SPI_FLASH_FORCE_ENABLE_C6_H2_SUSPEND is not set -CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM=y -# end of Optional and Experimental Features (READ DOCS FIRST) -# end of Main Flash configuration - -# -# SPI Flash driver -# -# CONFIG_SPI_FLASH_VERIFY_WRITE is not set -# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set -CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y -CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y -# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set -# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set -# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set -CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y -CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 -CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 -CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 -# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set -# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set -# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set - -# -# Auto-detect flash chips -# -CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORTED=y -# CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP is not set -# CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP is not set -# CONFIG_SPI_FLASH_SUPPORT_GD_CHIP is not set -# CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP is not set -# CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP is not set -# CONFIG_SPI_FLASH_SUPPORT_TH_CHIP is not set -# end of Auto-detect flash chips - -CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y -# end of SPI Flash driver - -# -# SPIFFS Configuration -# -CONFIG_SPIFFS_MAX_PARTITIONS=3 - -# -# SPIFFS Cache Configuration -# -CONFIG_SPIFFS_CACHE=y -CONFIG_SPIFFS_CACHE_WR=y -# CONFIG_SPIFFS_CACHE_STATS is not set -# end of SPIFFS Cache Configuration - -CONFIG_SPIFFS_PAGE_CHECK=y -CONFIG_SPIFFS_GC_MAX_RUNS=10 -# CONFIG_SPIFFS_GC_STATS is not set -CONFIG_SPIFFS_PAGE_SIZE=256 -CONFIG_SPIFFS_OBJ_NAME_LEN=32 -# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set -CONFIG_SPIFFS_USE_MAGIC=y -CONFIG_SPIFFS_USE_MAGIC_LENGTH=y -CONFIG_SPIFFS_META_LENGTH=4 -CONFIG_SPIFFS_USE_MTIME=y - -# -# Debug Configuration -# -# CONFIG_SPIFFS_DBG is not set -# CONFIG_SPIFFS_API_DBG is not set -# CONFIG_SPIFFS_GC_DBG is not set -# CONFIG_SPIFFS_CACHE_DBG is not set -# CONFIG_SPIFFS_CHECK_DBG is not set -# CONFIG_SPIFFS_TEST_VISUALISATION is not set -# end of Debug Configuration -# end of SPIFFS Configuration - -# -# TCP Transport -# - -# -# Websocket -# -CONFIG_WS_TRANSPORT=y -CONFIG_WS_BUFFER_SIZE=1024 -# CONFIG_WS_DYNAMIC_BUFFER is not set -# end of Websocket -# end of TCP Transport - -# -# Ultra Low Power (ULP) Co-processor -# -# CONFIG_ULP_COPROC_ENABLED is not set - -# -# ULP Debugging Options -# -# end of ULP Debugging Options -# end of Ultra Low Power (ULP) Co-processor - -# -# Unity unit testing library -# -CONFIG_UNITY_ENABLE_FLOAT=y -CONFIG_UNITY_ENABLE_DOUBLE=y -# CONFIG_UNITY_ENABLE_64BIT is not set -# CONFIG_UNITY_ENABLE_COLOR is not set -CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y -# CONFIG_UNITY_ENABLE_FIXTURE is not set -# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set -# CONFIG_UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE is not set -# end of Unity unit testing library - -# -# USB-OTG -# -CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=256 -CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED=y -# CONFIG_USB_HOST_HW_BUFFER_BIAS_IN is not set -# CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT is not set - -# -# Hub Driver Configuration -# - -# -# Root Port configuration -# -CONFIG_USB_HOST_DEBOUNCE_DELAY_MS=250 -CONFIG_USB_HOST_RESET_HOLD_MS=30 -CONFIG_USB_HOST_RESET_RECOVERY_MS=30 -CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS=10 -# end of Root Port configuration - -# CONFIG_USB_HOST_HUBS_SUPPORTED is not set -# end of Hub Driver Configuration - -# CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK is not set -# CONFIG_USB_HOST_DWC_DMA_CAP_MEMORY_IN_PSRAM is not set -CONFIG_USB_OTG_SUPPORTED=y -# end of USB-OTG - -# -# Virtual file system -# -CONFIG_VFS_SUPPORT_IO=y -CONFIG_VFS_SUPPORT_DIR=y -CONFIG_VFS_SUPPORT_SELECT=y -CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y -CONFIG_VFS_SELECT_IN_RAM=y -CONFIG_VFS_SUPPORT_TERMIOS=y -CONFIG_VFS_MAX_COUNT=8 - -# -# Host File System I/O (Semihosting) -# -CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 -# end of Host File System I/O (Semihosting) - -CONFIG_VFS_INITIALIZE_DEV_NULL=y -# end of Virtual file system - -# -# Wear Levelling -# -# CONFIG_WL_SECTOR_SIZE_512 is not set -CONFIG_WL_SECTOR_SIZE_4096=y -CONFIG_WL_SECTOR_SIZE=4096 -# end of Wear Levelling - -# -# Wi-Fi Provisioning Manager -# -CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 -CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 -CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y -# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set -# end of Wi-Fi Provisioning Manager - -# -# Animation Player Configuration -# -CONFIG_ANIM_PLAYER_DEFAULT_FPS=30 -# end of Animation Player Configuration - -# -# ADC Mic -# -CONFIG_ADC_MIC_APPLY_GAIN=3 -CONFIG_ADC_MIC_OFFSET=16380 -# end of ADC Mic - -# -# IoT Button -# -CONFIG_BUTTON_PERIOD_TIME_MS=5 -CONFIG_BUTTON_DEBOUNCE_TICKS=2 -CONFIG_BUTTON_SHORT_PRESS_TIME_MS=180 -CONFIG_BUTTON_LONG_PRESS_TIME_MS=1500 -CONFIG_BUTTON_LONG_PRESS_HOLD_SERIAL_TIME_MS=20 -CONFIG_ADC_BUTTON_MAX_CHANNEL=3 -CONFIG_ADC_BUTTON_MAX_BUTTON_PER_CHANNEL=8 -CONFIG_ADC_BUTTON_SAMPLE_TIMES=1 -# end of IoT Button - -# -# CMake Utilities -# -# CONFIG_CU_RELINKER_ENABLE is not set -# CONFIG_CU_DIAGNOSTICS_COLOR_NEVER is not set -CONFIG_CU_DIAGNOSTICS_COLOR_ALWAYS=y -# CONFIG_CU_DIAGNOSTICS_COLOR_AUTO is not set -# CONFIG_CU_GCC_LTO_ENABLE is not set -# CONFIG_CU_GCC_STRING_1BYTE_ALIGN is not set -# end of CMake Utilities - -# -# eppp_link -# -CONFIG_EPPP_LINK_USES_LWIP=y -CONFIG_EPPP_LINK_DEVICE_UART=y -# CONFIG_EPPP_LINK_DEVICE_SPI is not set -# CONFIG_EPPP_LINK_DEVICE_SDIO is not set -CONFIG_EPPP_LINK_CONN_MAX_RETRY=6 -# end of eppp_link - -# -# DSP Library -# -CONFIG_DSP_OPTIMIZATIONS_SUPPORTED=y -# CONFIG_DSP_ANSI is not set -CONFIG_DSP_OPTIMIZED=y -CONFIG_DSP_OPTIMIZATION=1 -# CONFIG_DSP_MAX_FFT_SIZE_512 is not set -# CONFIG_DSP_MAX_FFT_SIZE_1024 is not set -# CONFIG_DSP_MAX_FFT_SIZE_2048 is not set -CONFIG_DSP_MAX_FFT_SIZE_4096=y -# CONFIG_DSP_MAX_FFT_SIZE_8192 is not set -# CONFIG_DSP_MAX_FFT_SIZE_16384 is not set -# CONFIG_DSP_MAX_FFT_SIZE_32768 is not set -CONFIG_DSP_MAX_FFT_SIZE=4096 -# end of DSP Library - -# -# Camera configuration -# -CONFIG_OV7670_SUPPORT=y -CONFIG_OV7725_SUPPORT=y -CONFIG_NT99141_SUPPORT=y -CONFIG_OV2640_SUPPORT=y -CONFIG_OV3660_SUPPORT=y -CONFIG_OV5640_SUPPORT=y -CONFIG_GC2145_SUPPORT=y -CONFIG_GC032A_SUPPORT=y -CONFIG_GC0308_SUPPORT=y -CONFIG_BF3005_SUPPORT=y -CONFIG_BF20A6_SUPPORT=y -# CONFIG_SC101IOT_SUPPORT is not set -CONFIG_SC030IOT_SUPPORT=y -# CONFIG_SC031GS_SUPPORT is not set -CONFIG_MEGA_CCM_SUPPORT=y -# CONFIG_SCCB_HARDWARE_I2C_PORT0 is not set -CONFIG_SCCB_HARDWARE_I2C_PORT1=y -CONFIG_SCCB_CLK_FREQ=100000 -# CONFIG_GC_SENSOR_WINDOWING_MODE is not set -CONFIG_GC_SENSOR_SUBSAMPLE_MODE=y -CONFIG_CAMERA_TASK_STACK_SIZE=2048 -CONFIG_CAMERA_CORE0=y -# CONFIG_CAMERA_CORE1 is not set -# CONFIG_CAMERA_NO_AFFINITY is not set -CONFIG_CAMERA_DMA_BUFFER_SIZE_MAX=32768 -CONFIG_CAMERA_JPEG_MODE_FRAME_SIZE_AUTO=y -# CONFIG_CAMERA_JPEG_MODE_FRAME_SIZE_CUSTOM is not set -# end of Camera configuration - -# -# Audio Codec Device Configuration -# -# CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE is not set -CONFIG_CODEC_ES8311_SUPPORT=y -CONFIG_CODEC_ES7210_SUPPORT=y -CONFIG_CODEC_ES7243_SUPPORT=y -CONFIG_CODEC_ES7243E_SUPPORT=y -CONFIG_CODEC_ES8156_SUPPORT=y -CONFIG_CODEC_AW88298_SUPPORT=y -CONFIG_CODEC_ES8374_SUPPORT=y -CONFIG_CODEC_ES8388_SUPPORT=y -CONFIG_CODEC_TAS5805M_SUPPORT=y -# CONFIG_CODEC_ZL38063_SUPPORT is not set -# end of Audio Codec Device Configuration - -CONFIG_ESP_HOSTED_ENABLED=y - -# -# ESP-Hosted config -# - -# -# ESP32-C6 is Slave Target from Wi-Fi Remote Component -# -CONFIG_ESP_HOSTED_PRIV_SDIO_OPTION=y -CONFIG_ESP_HOSTED_PRIV_SPI_HD_OPTION=y -# CONFIG_ESP_HOSTED_SPI_HOST_INTERFACE is not set -CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE=y -# CONFIG_ESP_HOSTED_SPI_HD_HOST_INTERFACE is not set -# CONFIG_ESP_HOSTED_UART_HOST_INTERFACE is not set -CONFIG_ESP_HOSTED_IDF_SLAVE_TARGET="esp32c6" - -# -# Hosted SDIO Configuration -# -CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_HIGH=y -# CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_LOW is not set -# CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_NONE is not set -# CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_MAX_SIZE is not set -CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_STREAMING_MODE=y -# CONFIG_ESP_HOSTED_SDIO_SLOT_0 is not set -CONFIG_ESP_HOSTED_SDIO_SLOT_1=y -CONFIG_ESP_HOSTED_SDIO_SLOT=1 -# CONFIG_ESP_HOSTED_SD_PWR_CTRL_LDO_INTERNAL_IO is not set -CONFIG_ESP_HOSTED_SDIO_4_BIT_BUS=y -# CONFIG_ESP_HOSTED_SDIO_1_BIT_BUS is not set -CONFIG_ESP_HOSTED_SDIO_BUS_WIDTH=4 -CONFIG_ESP_HOSTED_SDIO_CLOCK_FREQ_KHZ=40000 -CONFIG_ESP_HOSTED_CUSTOM_SDIO_PINS=y -CONFIG_ESP_HOSTED_SDIO_CMD_GPIO_RANGE_MIN=0 -CONFIG_ESP_HOSTED_SDIO_CMD_GPIO_RANGE_MAX=100 -CONFIG_ESP_HOSTED_SDIO_CLK_GPIO_RANGE_MIN=0 -CONFIG_ESP_HOSTED_SDIO_CLK_GPIO_RANGE_MAX=100 -CONFIG_ESP_HOSTED_SDIO_D0_GPIO_RANGE_MIN=0 -CONFIG_ESP_HOSTED_SDIO_D0_GPIO_RANGE_MAX=100 -CONFIG_ESP_HOSTED_SDIO_D1_GPIO_RANGE_MIN=0 -CONFIG_ESP_HOSTED_SDIO_D1_GPIO_RANGE_MAX=100 -CONFIG_ESP_HOSTED_SDIO_D2_GPIO_RANGE_MIN=0 -CONFIG_ESP_HOSTED_SDIO_D2_GPIO_RANGE_MAX=100 -CONFIG_ESP_HOSTED_SDIO_D3_GPIO_RANGE_MIN=0 -CONFIG_ESP_HOSTED_SDIO_D3_GPIO_RANGE_MAX=100 -CONFIG_ESP_HOSTED_SDIO_RESET_SLAVE_GPIO_MIN=0 -CONFIG_ESP_HOSTED_SDIO_RESET_SLAVE_GPIO_MAX=100 -CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_1=13 -CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_1=12 -CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_1=11 -CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_1=10 -CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_1=9 -CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=8 -CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=54 -CONFIG_ESP_HOSTED_SDIO_PIN_CMD=13 -CONFIG_ESP_HOSTED_SDIO_PIN_CLK=12 -CONFIG_ESP_HOSTED_SDIO_PIN_D0=11 -CONFIG_ESP_HOSTED_SDIO_PRIV_PIN_D1_4BIT_BUS=10 -CONFIG_ESP_HOSTED_SDIO_PIN_D2=9 -CONFIG_ESP_HOSTED_SDIO_PIN_D3=8 -CONFIG_ESP_HOSTED_SDIO_PIN_D1=10 -CONFIG_ESP_HOSTED_SDIO_TX_Q_SIZE=20 -CONFIG_ESP_HOSTED_SDIO_RX_Q_SIZE=20 -# CONFIG_ESP_HOSTED_SDIO_CHECKSUM is not set -# end of Hosted SDIO Configuration - -CONFIG_ESP_HOSTED_GPIO_SLAVE_RESET_SLAVE=54 - -# -# Bluetooth Support -# - -# -# Following options must be set before this option can be enabled -# - -# -# 'Component config->Bluetooth' must be enabled -# -# end of Bluetooth Support - -# -# Task defaults -# -CONFIG_ESP_HOSTED_RPC_TASK_STACK=4096 -CONFIG_ESP_HOSTED_DFLT_TASK_STACK=3072 -# end of Task defaults - -CONFIG_ESP_HOSTED_USE_MEMPOOL=y -CONFIG_ESP_HOSTED_MAX_SIMULTANEOUS_SYNC_RPC_REQUESTS=5 -CONFIG_ESP_HOSTED_MAX_SIMULTANEOUS_ASYNC_RPC_REQUESTS=5 - -# -# Debug Settings -# -# CONFIG_ESP_HOSTED_RAW_THROUGHPUT_TRANSPORT is not set -# CONFIG_ESP_HOSTED_PKT_STATS is not set -# end of Debug Settings - -# -# Data path options -# -CONFIG_ESP_HOSTED_HOST_TO_ESP_WIFI_DATA_THROTTLE=y -CONFIG_ESP_HOSTED_PRIV_WIFI_TX_SDIO_HIGH_THRESHOLD=80 -CONFIG_ESP_HOSTED_TO_WIFI_DATA_THROTTLE_HIGH_THRESHOLD=80 -CONFIG_ESP_HOSTED_TO_WIFI_DATA_THROTTLE_LOW_THRESHOLD=60 -# end of Data path options - -# CONFIG_ESP_HOSTED_DECODE_WIFI_RESERVED_FIELD is not set -# end of ESP-Hosted config - -# -# ESP LCD TOUCH -# -CONFIG_ESP_LCD_TOUCH_MAX_POINTS=5 -CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS=1 -# end of ESP LCD TOUCH - -# -# ESP LVGL PORT -# -# CONFIG_LVGL_PORT_ENABLE_PPA is not set -# end of ESP LVGL PORT - -# -# mmap file support format -# -CONFIG_MMAP_FILE_NAME_LENGTH=16 -# end of mmap file support format - -# -# Wi-Fi Remote -# -CONFIG_ESP_WIFI_REMOTE_ENABLED=y -# CONFIG_SLAVE_IDF_TARGET_ESP32 is not set -# CONFIG_SLAVE_IDF_TARGET_ESP32S2 is not set -# CONFIG_SLAVE_IDF_TARGET_ESP32C3 is not set -# CONFIG_SLAVE_IDF_TARGET_ESP32S3 is not set -# CONFIG_SLAVE_IDF_TARGET_ESP32C2 is not set -CONFIG_SLAVE_IDF_TARGET_ESP32C6=y -# CONFIG_SLAVE_IDF_TARGET_ESP32C5 is not set -# CONFIG_SLAVE_IDF_TARGET_ESP32C61 is not set -CONFIG_SLAVE_SOC_WIFI_SUPPORTED=y -CONFIG_SLAVE_SOC_WIFI_WAPI_SUPPORT=y -CONFIG_SLAVE_SOC_WIFI_CSI_SUPPORT=y -CONFIG_SLAVE_SOC_WIFI_MESH_SUPPORT=y -CONFIG_SLAVE_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 -CONFIG_SLAVE_SOC_WIFI_HW_TSF=y -CONFIG_SLAVE_SOC_WIFI_FTM_SUPPORT=y -CONFIG_SLAVE_FREERTOS_UNICORE=y -CONFIG_SLAVE_SOC_WIFI_GCMP_SUPPORT=y -CONFIG_SLAVE_IDF_TARGET_ARCH_RISCV=y -CONFIG_SLAVE_SOC_WIFI_HE_SUPPORT=y -CONFIG_SLAVE_SOC_WIFI_MAC_VERSION_NUM=2 -CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y -# CONFIG_ESP_WIFI_REMOTE_LIBRARY_EPPP is not set - -# -# Wi-Fi configuration -# -CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=16 -CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=32 -CONFIG_WIFI_RMT_STATIC_TX_BUFFER=y -CONFIG_WIFI_RMT_TX_BUFFER_TYPE=0 -CONFIG_WIFI_RMT_STATIC_TX_BUFFER_NUM=16 -CONFIG_WIFI_RMT_CACHE_TX_BUFFER_NUM=32 -CONFIG_WIFI_RMT_STATIC_RX_MGMT_BUFFER=y -# CONFIG_WIFI_RMT_DYNAMIC_RX_MGMT_BUFFER is not set -CONFIG_WIFI_RMT_DYNAMIC_RX_MGMT_BUF=0 -CONFIG_WIFI_RMT_RX_MGMT_BUF_NUM_DEF=5 -# CONFIG_WIFI_RMT_CSI_ENABLED is not set -CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y -CONFIG_WIFI_RMT_TX_BA_WIN=6 -CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y -CONFIG_WIFI_RMT_RX_BA_WIN=16 -# CONFIG_WIFI_RMT_AMSDU_TX_ENABLED is not set -CONFIG_WIFI_RMT_NVS_ENABLED=y -CONFIG_WIFI_RMT_SOFTAP_BEACON_MAX_LEN=752 -CONFIG_WIFI_RMT_MGMT_SBUF_NUM=32 -CONFIG_WIFI_RMT_IRAM_OPT=y -CONFIG_WIFI_RMT_EXTRA_IRAM_OPT=y -CONFIG_WIFI_RMT_RX_IRAM_OPT=y -CONFIG_WIFI_RMT_ENABLE_WPA3_SAE=y -CONFIG_WIFI_RMT_ENABLE_SAE_PK=y -CONFIG_WIFI_RMT_ENABLE_SAE_H2E=y -CONFIG_WIFI_RMT_SOFTAP_SAE_SUPPORT=y -CONFIG_WIFI_RMT_ENABLE_WPA3_OWE_STA=y -CONFIG_WIFI_RMT_SLP_IRAM_OPT=y -CONFIG_WIFI_RMT_SLP_DEFAULT_MIN_ACTIVE_TIME=50 -CONFIG_WIFI_RMT_BSS_MAX_IDLE_SUPPORT=y -CONFIG_WIFI_RMT_SLP_DEFAULT_MAX_ACTIVE_TIME=10 -CONFIG_WIFI_RMT_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 -# CONFIG_WIFI_RMT_FTM_ENABLE is not set -CONFIG_WIFI_RMT_STA_DISCONNECTED_PM_ENABLE=y -# CONFIG_WIFI_RMT_GCMP_SUPPORT is not set -CONFIG_WIFI_RMT_GMAC_SUPPORT=y -CONFIG_WIFI_RMT_SOFTAP_SUPPORT=y -# CONFIG_WIFI_RMT_SLP_BEACON_LOST_OPT is not set -CONFIG_WIFI_RMT_ESPNOW_MAX_ENCRYPT_NUM=7 -CONFIG_WIFI_RMT_MBEDTLS_CRYPTO=y -CONFIG_WIFI_RMT_MBEDTLS_TLS_CLIENT=y -# CONFIG_WIFI_RMT_EAP_TLS1_3 is not set -# CONFIG_WIFI_RMT_WAPI_PSK is not set -# CONFIG_WIFI_RMT_SUITE_B_192 is not set -# CONFIG_WIFI_RMT_11KV_SUPPORT is not set -# CONFIG_WIFI_RMT_MBO_SUPPORT is not set -# CONFIG_WIFI_RMT_ENABLE_ROAMING_APP is not set -# CONFIG_WIFI_RMT_DPP_SUPPORT is not set -# CONFIG_WIFI_RMT_11R_SUPPORT is not set -# CONFIG_WIFI_RMT_WPS_SOFTAP_REGISTRAR is not set -# CONFIG_WIFI_RMT_ENABLE_WIFI_TX_STATS is not set -# CONFIG_WIFI_RMT_ENABLE_WIFI_RX_STATS is not set -CONFIG_WIFI_RMT_TX_HETB_QUEUE_NUM=3 - -# -# WPS Configuration Options -# -# CONFIG_WIFI_RMT_WPS_STRICT is not set -# CONFIG_WIFI_RMT_WPS_PASSPHRASE is not set -# end of WPS Configuration Options - -# CONFIG_WIFI_RMT_DEBUG_PRINT is not set -# CONFIG_WIFI_RMT_TESTING_OPTIONS is not set -CONFIG_WIFI_RMT_ENTERPRISE_SUPPORT=y -# CONFIG_WIFI_RMT_ENT_FREE_DYNAMIC_BUFFER is not set -# end of Wi-Fi configuration -# end of Wi-Fi Remote - -# -# Bus Options -# - -# -# I2C Bus Options -# -CONFIG_I2C_BUS_DYNAMIC_CONFIG=y -CONFIG_I2C_MS_TO_WAIT=200 -# CONFIG_I2C_BUS_BACKWARD_CONFIG is not set -# CONFIG_I2C_BUS_SUPPORT_SOFTWARE is not set -# CONFIG_I2C_BUS_REMOVE_NULL_MEM_ADDR is not set -# end of I2C Bus Options -# end of Bus Options - -# -# IOT Knob -# -CONFIG_KNOB_PERIOD_TIME_MS=3 -CONFIG_KNOB_DEBOUNCE_TICKS=2 -CONFIG_KNOB_HIGH_LIMIT=1000 -CONFIG_KNOB_LOW_LIMIT=-1000 -# end of IOT Knob - -# -# LVGL configuration -# -CONFIG_LV_CONF_SKIP=y -# CONFIG_LV_CONF_MINIMAL is not set - -# -# Color Settings -# -# CONFIG_LV_COLOR_DEPTH_32 is not set -# CONFIG_LV_COLOR_DEPTH_24 is not set -CONFIG_LV_COLOR_DEPTH_16=y -# CONFIG_LV_COLOR_DEPTH_8 is not set -# CONFIG_LV_COLOR_DEPTH_1 is not set -CONFIG_LV_COLOR_DEPTH=16 -# end of Color Settings - -# -# Memory Settings -# -# CONFIG_LV_USE_BUILTIN_MALLOC is not set -CONFIG_LV_USE_CLIB_MALLOC=y -# CONFIG_LV_USE_MICROPYTHON_MALLOC is not set -# CONFIG_LV_USE_RTTHREAD_MALLOC is not set -# CONFIG_LV_USE_CUSTOM_MALLOC is not set -# CONFIG_LV_USE_BUILTIN_STRING is not set -CONFIG_LV_USE_CLIB_STRING=y -# CONFIG_LV_USE_CUSTOM_STRING is not set -# CONFIG_LV_USE_BUILTIN_SPRINTF is not set -CONFIG_LV_USE_CLIB_SPRINTF=y -# CONFIG_LV_USE_CUSTOM_SPRINTF is not set -# end of Memory Settings - -# -# HAL Settings -# -CONFIG_LV_DEF_REFR_PERIOD=33 -CONFIG_LV_DPI_DEF=130 -# end of HAL Settings - -# -# Operating System (OS) -# -CONFIG_LV_OS_NONE=y -# CONFIG_LV_OS_PTHREAD is not set -# CONFIG_LV_OS_FREERTOS is not set -# CONFIG_LV_OS_CMSIS_RTOS2 is not set -# CONFIG_LV_OS_RTTHREAD is not set -# CONFIG_LV_OS_WINDOWS is not set -# CONFIG_LV_OS_MQX is not set -# CONFIG_LV_OS_CUSTOM is not set -CONFIG_LV_USE_OS=0 -# end of Operating System (OS) - -# -# Rendering Configuration -# -CONFIG_LV_DRAW_BUF_STRIDE_ALIGN=1 -CONFIG_LV_DRAW_BUF_ALIGN=4 -CONFIG_LV_DRAW_LAYER_SIMPLE_BUF_SIZE=24576 -CONFIG_LV_USE_DRAW_SW=y -CONFIG_LV_DRAW_SW_SUPPORT_RGB565=y -CONFIG_LV_DRAW_SW_SUPPORT_RGB565A8=y -CONFIG_LV_DRAW_SW_SUPPORT_RGB888=y -CONFIG_LV_DRAW_SW_SUPPORT_XRGB8888=y -CONFIG_LV_DRAW_SW_SUPPORT_ARGB8888=y -CONFIG_LV_DRAW_SW_SUPPORT_L8=y -CONFIG_LV_DRAW_SW_SUPPORT_AL88=y -CONFIG_LV_DRAW_SW_SUPPORT_A8=y -CONFIG_LV_DRAW_SW_SUPPORT_I1=y -CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=1 -# CONFIG_LV_USE_DRAW_ARM2D_SYNC is not set -# CONFIG_LV_USE_NATIVE_HELIUM_ASM is not set -CONFIG_LV_DRAW_SW_COMPLEX=y -# CONFIG_LV_USE_DRAW_SW_COMPLEX_GRADIENTS is not set -CONFIG_LV_DRAW_SW_SHADOW_CACHE_SIZE=0 -CONFIG_LV_DRAW_SW_CIRCLE_CACHE_SIZE=4 -CONFIG_LV_DRAW_SW_ASM_NONE=y -# CONFIG_LV_DRAW_SW_ASM_NEON is not set -# CONFIG_LV_DRAW_SW_ASM_HELIUM is not set -# CONFIG_LV_DRAW_SW_ASM_CUSTOM is not set -CONFIG_LV_USE_DRAW_SW_ASM=0 -# CONFIG_LV_USE_DRAW_VGLITE is not set -# CONFIG_LV_USE_PXP is not set -# CONFIG_LV_USE_DRAW_DAVE2D is not set -# CONFIG_LV_USE_DRAW_SDL is not set -# CONFIG_LV_USE_DRAW_VG_LITE is not set -# CONFIG_LV_USE_VECTOR_GRAPHIC is not set -# end of Rendering Configuration - -# -# Feature Configuration -# - -# -# Logging -# -# CONFIG_LV_USE_LOG is not set -# end of Logging - -# -# Asserts -# -CONFIG_LV_USE_ASSERT_NULL=y -CONFIG_LV_USE_ASSERT_MALLOC=y -# CONFIG_LV_USE_ASSERT_STYLE is not set -# CONFIG_LV_USE_ASSERT_MEM_INTEGRITY is not set -# CONFIG_LV_USE_ASSERT_OBJ is not set -CONFIG_LV_ASSERT_HANDLER_INCLUDE="assert.h" -# end of Asserts - -# -# Debug -# -# CONFIG_LV_USE_REFR_DEBUG is not set -# CONFIG_LV_USE_LAYER_DEBUG is not set -# CONFIG_LV_USE_PARALLEL_DRAW_DEBUG is not set -# end of Debug - -# -# Others -# -# CONFIG_LV_ENABLE_GLOBAL_CUSTOM is not set -CONFIG_LV_CACHE_DEF_SIZE=0 -CONFIG_LV_IMAGE_HEADER_CACHE_DEF_CNT=0 -CONFIG_LV_GRADIENT_MAX_STOPS=2 -CONFIG_LV_COLOR_MIX_ROUND_OFS=128 -# CONFIG_LV_OBJ_STYLE_CACHE is not set -# CONFIG_LV_USE_OBJ_ID is not set -# CONFIG_LV_USE_OBJ_PROPERTY is not set -# end of Others -# end of Feature Configuration - -# -# Compiler Settings -# -# CONFIG_LV_BIG_ENDIAN_SYSTEM is not set -CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1 -# CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM is not set -# CONFIG_LV_USE_FLOAT is not set -# CONFIG_LV_USE_MATRIX is not set -# CONFIG_LV_USE_PRIVATE_API is not set -# end of Compiler Settings - -# -# Font Usage -# - -# -# Enable built-in fonts -# -# CONFIG_LV_FONT_MONTSERRAT_8 is not set -# CONFIG_LV_FONT_MONTSERRAT_10 is not set -# CONFIG_LV_FONT_MONTSERRAT_12 is not set -CONFIG_LV_FONT_MONTSERRAT_14=y -# CONFIG_LV_FONT_MONTSERRAT_16 is not set -# CONFIG_LV_FONT_MONTSERRAT_18 is not set -# CONFIG_LV_FONT_MONTSERRAT_20 is not set -# CONFIG_LV_FONT_MONTSERRAT_22 is not set -# CONFIG_LV_FONT_MONTSERRAT_24 is not set -# CONFIG_LV_FONT_MONTSERRAT_26 is not set -# CONFIG_LV_FONT_MONTSERRAT_28 is not set -# CONFIG_LV_FONT_MONTSERRAT_30 is not set -# CONFIG_LV_FONT_MONTSERRAT_32 is not set -# CONFIG_LV_FONT_MONTSERRAT_34 is not set -# CONFIG_LV_FONT_MONTSERRAT_36 is not set -# CONFIG_LV_FONT_MONTSERRAT_38 is not set -# CONFIG_LV_FONT_MONTSERRAT_40 is not set -# CONFIG_LV_FONT_MONTSERRAT_42 is not set -# CONFIG_LV_FONT_MONTSERRAT_44 is not set -# CONFIG_LV_FONT_MONTSERRAT_46 is not set -# CONFIG_LV_FONT_MONTSERRAT_48 is not set -# CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set -# CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set -# CONFIG_LV_FONT_SIMSUN_14_CJK is not set -# CONFIG_LV_FONT_SIMSUN_16_CJK is not set -# CONFIG_LV_FONT_UNSCII_8 is not set -# CONFIG_LV_FONT_UNSCII_16 is not set -# end of Enable built-in fonts - -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_10 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set -CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_22 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_24 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_26 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_30 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_32 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_34 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_36 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_38 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_40 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_42 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_44 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_46 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_48 is not set -# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set -# CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set -# CONFIG_LV_FONT_DEFAULT_SIMSUN_14_CJK is not set -# CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK is not set -# CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set -# CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set -CONFIG_LV_FONT_FMT_TXT_LARGE=y -CONFIG_LV_USE_FONT_COMPRESSED=y -CONFIG_LV_USE_FONT_PLACEHOLDER=y -# end of Font Usage - -# -# Text Settings -# -CONFIG_LV_TXT_ENC_UTF8=y -# CONFIG_LV_TXT_ENC_ASCII is not set -CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_)}" -CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0 -# CONFIG_LV_USE_BIDI is not set -# CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set -# end of Text Settings - -# -# Widget Usage -# -CONFIG_LV_WIDGETS_HAS_DEFAULT_VALUE=y -# CONFIG_LV_USE_ANIMIMG is not set -CONFIG_LV_USE_ARC=y -CONFIG_LV_USE_BAR=y -CONFIG_LV_USE_BUTTON=y -CONFIG_LV_USE_BUTTONMATRIX=y -# CONFIG_LV_USE_CALENDAR is not set -CONFIG_LV_USE_CANVAS=y -# CONFIG_LV_USE_CHART is not set -CONFIG_LV_USE_CHECKBOX=y -CONFIG_LV_USE_DROPDOWN=y -CONFIG_LV_USE_IMAGE=y -CONFIG_LV_USE_IMAGEBUTTON=y -# CONFIG_LV_USE_KEYBOARD is not set -CONFIG_LV_USE_LABEL=y -CONFIG_LV_LABEL_TEXT_SELECTION=y -CONFIG_LV_LABEL_LONG_TXT_HINT=y -CONFIG_LV_LABEL_WAIT_CHAR_COUNT=3 -# CONFIG_LV_USE_LED is not set -CONFIG_LV_USE_LINE=y -# CONFIG_LV_USE_LIST is not set -# CONFIG_LV_USE_MENU is not set -# CONFIG_LV_USE_MSGBOX is not set -CONFIG_LV_USE_ROLLER=y -CONFIG_LV_USE_SCALE=y -CONFIG_LV_USE_SLIDER=y -# CONFIG_LV_USE_SPAN is not set -# CONFIG_LV_USE_SPINBOX is not set -# CONFIG_LV_USE_SPINNER is not set -CONFIG_LV_USE_SWITCH=y -CONFIG_LV_USE_TEXTAREA=y -CONFIG_LV_TEXTAREA_DEF_PWD_SHOW_TIME=1500 -CONFIG_LV_USE_TABLE=y -# CONFIG_LV_USE_TABVIEW is not set -# CONFIG_LV_USE_TILEVIEW is not set -# CONFIG_LV_USE_WIN is not set -# end of Widget Usage - -# -# Themes -# -CONFIG_LV_USE_THEME_DEFAULT=y -# CONFIG_LV_THEME_DEFAULT_DARK is not set -CONFIG_LV_THEME_DEFAULT_GROW=y -CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=80 -CONFIG_LV_USE_THEME_SIMPLE=y -# CONFIG_LV_USE_THEME_MONO is not set -# end of Themes - -# -# Layouts -# -CONFIG_LV_USE_FLEX=y -CONFIG_LV_USE_GRID=y -# end of Layouts - -# -# 3rd Party Libraries -# -CONFIG_LV_FS_DEFAULT_DRIVE_LETTER=0 -# CONFIG_LV_USE_FS_STDIO is not set -# CONFIG_LV_USE_FS_POSIX is not set -# CONFIG_LV_USE_FS_WIN32 is not set -# CONFIG_LV_USE_FS_FATFS is not set -# CONFIG_LV_USE_FS_MEMFS is not set -# CONFIG_LV_USE_FS_LITTLEFS is not set -# CONFIG_LV_USE_FS_ARDUINO_ESP_LITTLEFS is not set -# CONFIG_LV_USE_FS_ARDUINO_SD is not set -# CONFIG_LV_USE_LODEPNG is not set -# CONFIG_LV_USE_LIBPNG is not set -# CONFIG_LV_USE_BMP is not set -# CONFIG_LV_USE_TJPGD is not set -# CONFIG_LV_USE_LIBJPEG_TURBO is not set -# CONFIG_LV_USE_GIF is not set -# CONFIG_LV_BIN_DECODER_RAM_LOAD is not set -# CONFIG_LV_USE_RLE is not set -# CONFIG_LV_USE_QRCODE is not set -# CONFIG_LV_USE_BARCODE is not set -# CONFIG_LV_USE_FREETYPE is not set -# CONFIG_LV_USE_TINY_TTF is not set -# CONFIG_LV_USE_RLOTTIE is not set -# CONFIG_LV_USE_THORVG is not set -# CONFIG_LV_USE_LZ4 is not set -# CONFIG_LV_USE_FFMPEG is not set -# end of 3rd Party Libraries - -# -# Others -# -# CONFIG_LV_USE_SNAPSHOT is not set -# CONFIG_LV_USE_SYSMON is not set -# CONFIG_LV_USE_PROFILER is not set -# CONFIG_LV_USE_MONKEY is not set -# CONFIG_LV_USE_GRIDNAV is not set -# CONFIG_LV_USE_FRAGMENT is not set -CONFIG_LV_USE_IMGFONT=y -CONFIG_LV_USE_OBSERVER=y -# CONFIG_LV_USE_IME_PINYIN is not set -# CONFIG_LV_USE_FILE_EXPLORER is not set -CONFIG_LVGL_VERSION_MAJOR=9 -CONFIG_LVGL_VERSION_MINOR=2 -CONFIG_LVGL_VERSION_PATCH=2 -# end of Others - -# -# Devices -# -# CONFIG_LV_USE_SDL is not set -# CONFIG_LV_USE_X11 is not set -# CONFIG_LV_USE_WAYLAND is not set -# CONFIG_LV_USE_LINUX_FBDEV is not set -# CONFIG_LV_USE_NUTTX is not set -# CONFIG_LV_USE_LINUX_DRM is not set -# CONFIG_LV_USE_TFT_ESPI is not set -# CONFIG_LV_USE_EVDEV is not set -# CONFIG_LV_USE_LIBINPUT is not set -# CONFIG_LV_USE_ST7735 is not set -# CONFIG_LV_USE_ST7789 is not set -# CONFIG_LV_USE_ST7796 is not set -# CONFIG_LV_USE_ILI9341 is not set -# CONFIG_LV_USE_GENERIC_MIPI is not set -# CONFIG_LV_USE_RENESAS_GLCDC is not set -# CONFIG_LV_USE_OPENGLES is not set -# CONFIG_LV_USE_QNX is not set -# end of Devices - -# -# Examples -# -# CONFIG_LV_BUILD_EXAMPLES is not set -# end of Examples - -# -# Demos -# -# CONFIG_LV_USE_DEMO_WIDGETS is not set -# CONFIG_LV_USE_DEMO_KEYPAD_AND_ENCODER is not set -# CONFIG_LV_USE_DEMO_RENDER is not set -# CONFIG_LV_USE_DEMO_SCROLL is not set -# CONFIG_LV_USE_DEMO_STRESS is not set -# CONFIG_LV_USE_DEMO_MUSIC is not set -# CONFIG_LV_USE_DEMO_FLEX_LAYOUT is not set -# CONFIG_LV_USE_DEMO_MULTILANG is not set -# end of Demos -# end of LVGL configuration - -# -# SH1106 ESP-IDF Driver -# -# end of Component config - -CONFIG_IDF_EXPERIMENTAL_FEATURES=y - -# Deprecated options for backward compatibility -# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set -# CONFIG_NO_BLOBS is not set -CONFIG_APP_ROLLBACK_ENABLE=y -# CONFIG_APP_ANTI_ROLLBACK is not set -CONFIG_LOG_BOOTLOADER_LEVEL_NONE=y -# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_INFO is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set -CONFIG_LOG_BOOTLOADER_LEVEL=0 -# CONFIG_FLASH_ENCRYPTION_ENABLED is not set -CONFIG_FLASHMODE_QIO=y -# CONFIG_FLASHMODE_QOUT is not set -# CONFIG_FLASHMODE_DIO is not set -# CONFIG_FLASHMODE_DOUT is not set -CONFIG_MONITOR_BAUD=115200 -CONFIG_OPTIMIZATION_LEVEL_DEBUG=y -CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y -CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y -# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set -# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set -CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y -# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set -# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set -CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 -CONFIG_CXX_EXCEPTIONS=y -CONFIG_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 -CONFIG_STACK_CHECK_NONE=y -# CONFIG_STACK_CHECK_NORM is not set -# CONFIG_STACK_CHECK_STRONG is not set -# CONFIG_STACK_CHECK_ALL is not set -# CONFIG_WARN_WRITE_STRINGS is not set -# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set -CONFIG_ESP32_APPTRACE_DEST_NONE=y -CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y -# CONFIG_ANA_CMPR_ISR_IRAM_SAFE is not set -# CONFIG_CAM_CTLR_MIPI_CSI_ISR_IRAM_SAFE is not set -# CONFIG_CAM_CTLR_ISP_DVP_ISR_IRAM_SAFE is not set -# CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE is not set -# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set -# CONFIG_MCPWM_ISR_IN_IRAM is not set -# CONFIG_EVENT_LOOP_PROFILING is not set -CONFIG_POST_EVENTS_FROM_ISR=y -CONFIG_POST_EVENTS_FROM_IRAM_ISR=y -CONFIG_GDBSTUB_SUPPORT_TASKS=y -CONFIG_GDBSTUB_MAX_TASKS=32 -# CONFIG_OTA_ALLOW_HTTP is not set -CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y -CONFIG_BROWNOUT_DET=y -CONFIG_BROWNOUT_DET_LVL_SEL_7=y -# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set -CONFIG_BROWNOUT_DET_LVL=7 -CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y -CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 -CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 -CONFIG_MAIN_TASK_STACK_SIZE=10240 -CONFIG_CONSOLE_UART_DEFAULT=y -# CONFIG_CONSOLE_UART_CUSTOM is not set -# CONFIG_CONSOLE_UART_NONE is not set -# CONFIG_ESP_CONSOLE_UART_NONE is not set -CONFIG_CONSOLE_UART=y -CONFIG_CONSOLE_UART_NUM=0 -CONFIG_CONSOLE_UART_BAUDRATE=115200 -CONFIG_INT_WDT=y -CONFIG_INT_WDT_TIMEOUT_MS=300 -CONFIG_INT_WDT_CHECK_CPU1=y -CONFIG_TASK_WDT=y -CONFIG_ESP_TASK_WDT=y -# CONFIG_TASK_WDT_PANIC is not set -CONFIG_TASK_WDT_TIMEOUT_S=10 -CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y -CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y -# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set -CONFIG_IPC_TASK_STACK_SIZE=1024 -CONFIG_TIMER_TASK_STACK_SIZE=3584 -CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=16 -CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 -CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0 -CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16 -CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32 -CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y -CONFIG_ESP32_WIFI_TX_BA_WIN=6 -CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y -CONFIG_ESP32_WIFI_RX_BA_WIN=16 -CONFIG_ESP32_WIFI_NVS_ENABLED=y -CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 -CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 -CONFIG_ESP32_WIFI_IRAM_OPT=y -CONFIG_ESP32_WIFI_RX_IRAM_OPT=y -CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y -CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y -CONFIG_WPA_MBEDTLS_CRYPTO=y -CONFIG_WPA_MBEDTLS_TLS_CLIENT=y -# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set -# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set -CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y -CONFIG_TIMER_TASK_PRIORITY=1 -CONFIG_TIMER_TASK_STACK_DEPTH=2048 -CONFIG_TIMER_QUEUE_LENGTH=10 -# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set -CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y -# CONFIG_HAL_ASSERTION_SILIENT is not set -# CONFIG_L2_TO_L3_COPY is not set -CONFIG_ESP_GRATUITOUS_ARP=y -CONFIG_GARP_TMR_INTERVAL=60 -CONFIG_TCPIP_RECVMBOX_SIZE=32 -CONFIG_TCP_MAXRTX=12 -CONFIG_TCP_SYNMAXRTX=12 -CONFIG_TCP_MSS=1440 -CONFIG_TCP_MSL=60000 -CONFIG_TCP_SND_BUF_DEFAULT=5760 -CONFIG_TCP_WND_DEFAULT=5760 -CONFIG_TCP_RECVMBOX_SIZE=6 -CONFIG_TCP_QUEUE_OOSEQ=y -CONFIG_TCP_OVERSIZE_MSS=y -# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set -# CONFIG_TCP_OVERSIZE_DISABLE is not set -CONFIG_UDP_RECVMBOX_SIZE=6 -CONFIG_TCPIP_TASK_STACK_SIZE=3072 -CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y -# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set -# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set -CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF -CONFIG_PPP_SUPPORT=y -# CONFIG_PPP_NOTIFY_PHASE_SUPPORT is not set -# CONFIG_PPP_PAP_SUPPORT is not set -# CONFIG_PPP_CHAP_SUPPORT is not set -# CONFIG_PPP_MSCHAP_SUPPORT is not set -# CONFIG_PPP_MPPE_SUPPORT is not set -# CONFIG_PPP_DEBUG_ON is not set -CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y -# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set -# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set -# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set -# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set -CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y -# CONFIG_NEWLIB_NANO_FORMAT is not set -CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y -# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set -# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set -# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set -CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 -CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 -CONFIG_ESP32_PTHREAD_STACK_MIN=768 -CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y -# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set -# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set -CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 -CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" -CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y -# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set -# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set -CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y -CONFIG_SUPPORT_TERMIOS=y -CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 -# CONFIG_ESP_SPI_HOST_INTERFACE is not set -CONFIG_ESP_SDIO_HOST_INTERFACE=y -# CONFIG_ESP_SPI_HD_HOST_INTERFACE is not set -# CONFIG_ESP_UART_HOST_INTERFACE is not set -CONFIG_IDF_SLAVE_TARGET="esp32c6" -CONFIG_SDIO_RESET_ACTIVE_HIGH=y -# CONFIG_ESP_SDIO_OPTIMIZATION_RX_NONE is not set -# CONFIG_ESP_SDIO_OPTIMIZATION_RX_MAX_SIZE is not set -CONFIG_ESP_SDIO_OPTIMIZATION_RX_STREAMING_MODE=y -CONFIG_ESP_SDIO_4_BIT_BUS=y -# CONFIG_ESP_SDIO_1_BIT_BUS is not set -CONFIG_ESP_SDIO_BUS_WIDTH=4 -CONFIG_ESP_SDIO_CLOCK_FREQ_KHZ=40000 -CONFIG_ESP_SDIO_GPIO_RESET_SLAVE=54 -CONFIG_ESP_SDIO_PIN_CMD=13 -CONFIG_ESP_SDIO_PIN_CLK=12 -CONFIG_ESP_SDIO_PIN_D0=11 -CONFIG_ESP_SDIO_PIN_D2=9 -CONFIG_ESP_SDIO_PIN_D3=8 -CONFIG_ESP_SDIO_PIN_D1=10 -CONFIG_ESP_SDIO_TX_Q_SIZE=20 -CONFIG_ESP_SDIO_RX_Q_SIZE=20 -# CONFIG_ESP_SDIO_CHECKSUM is not set -CONFIG_ESP_GPIO_SLAVE_RESET_SLAVE=54 -CONFIG_ESP_RPC_TASK_STACK=4096 -CONFIG_ESP_DFLT_TASK_STACK=3072 -CONFIG_ESP_USE_MEMPOOL=y -CONFIG_ESP_MAX_SIMULTANEOUS_SYNC_RPC_REQUESTS=5 -CONFIG_ESP_MAX_SIMULTANEOUS_ASYNC_RPC_REQUESTS=5 -# CONFIG_ESP_RAW_THROUGHPUT_TRANSPORT is not set -# CONFIG_ESP_PKT_STATS is not set -CONFIG_HOST_TO_ESP_WIFI_DATA_THROTTLE=y -CONFIG_PRIV_WIFI_TX_SDIO_HIGH_THRESHOLD=80 -CONFIG_TO_WIFI_DATA_THROTTLE_HIGH_THRESHOLD=80 -CONFIG_TO_WIFI_DATA_THROTTLE_LOW_THRESHOLD=60 -CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=16 -CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 -CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0 -CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16 -CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32 -CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y -CONFIG_ESP32_WIFI_TX_BA_WIN=6 -CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y -CONFIG_ESP32_WIFI_RX_BA_WIN=16 -CONFIG_ESP32_WIFI_NVS_ENABLED=y -CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 -CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 -CONFIG_ESP32_WIFI_IRAM_OPT=y -CONFIG_ESP32_WIFI_RX_IRAM_OPT=y -CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y -CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y -CONFIG_WPA_MBEDTLS_CRYPTO=y -CONFIG_WPA_MBEDTLS_TLS_CLIENT=y -# End of deprecated options +# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Configuration +# +CONFIG_SOC_ADC_SUPPORTED=y +CONFIG_SOC_ANA_CMPR_SUPPORTED=y +CONFIG_SOC_DEDICATED_GPIO_SUPPORTED=y +CONFIG_SOC_UART_SUPPORTED=y +CONFIG_SOC_GDMA_SUPPORTED=y +CONFIG_SOC_UHCI_SUPPORTED=y +CONFIG_SOC_AHB_GDMA_SUPPORTED=y +CONFIG_SOC_AXI_GDMA_SUPPORTED=y +CONFIG_SOC_DW_GDMA_SUPPORTED=y +CONFIG_SOC_DMA2D_SUPPORTED=y +CONFIG_SOC_GPTIMER_SUPPORTED=y +CONFIG_SOC_PCNT_SUPPORTED=y +CONFIG_SOC_LCDCAM_SUPPORTED=y +CONFIG_SOC_LCDCAM_CAM_SUPPORTED=y +CONFIG_SOC_LCDCAM_I80_LCD_SUPPORTED=y +CONFIG_SOC_LCDCAM_RGB_LCD_SUPPORTED=y +CONFIG_SOC_MIPI_CSI_SUPPORTED=y +CONFIG_SOC_MIPI_DSI_SUPPORTED=y +CONFIG_SOC_MCPWM_SUPPORTED=y +CONFIG_SOC_TWAI_SUPPORTED=y +CONFIG_SOC_ETM_SUPPORTED=y +CONFIG_SOC_PARLIO_SUPPORTED=y +CONFIG_SOC_ASYNC_MEMCPY_SUPPORTED=y +CONFIG_SOC_EMAC_SUPPORTED=y +CONFIG_SOC_USB_OTG_SUPPORTED=y +CONFIG_SOC_WIRELESS_HOST_SUPPORTED=y +CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED=y +CONFIG_SOC_TEMP_SENSOR_SUPPORTED=y +CONFIG_SOC_SUPPORTS_SECURE_DL_MODE=y +CONFIG_SOC_ULP_SUPPORTED=y +CONFIG_SOC_LP_CORE_SUPPORTED=y +CONFIG_SOC_EFUSE_KEY_PURPOSE_FIELD=y +CONFIG_SOC_EFUSE_SUPPORTED=y +CONFIG_SOC_RTC_FAST_MEM_SUPPORTED=y +CONFIG_SOC_RTC_MEM_SUPPORTED=y +CONFIG_SOC_RMT_SUPPORTED=y +CONFIG_SOC_I2S_SUPPORTED=y +CONFIG_SOC_SDM_SUPPORTED=y +CONFIG_SOC_GPSPI_SUPPORTED=y +CONFIG_SOC_LEDC_SUPPORTED=y +CONFIG_SOC_ISP_SUPPORTED=y +CONFIG_SOC_I2C_SUPPORTED=y +CONFIG_SOC_SYSTIMER_SUPPORTED=y +CONFIG_SOC_AES_SUPPORTED=y +CONFIG_SOC_MPI_SUPPORTED=y +CONFIG_SOC_SHA_SUPPORTED=y +CONFIG_SOC_HMAC_SUPPORTED=y +CONFIG_SOC_DIG_SIGN_SUPPORTED=y +CONFIG_SOC_ECC_SUPPORTED=y +CONFIG_SOC_ECC_EXTENDED_MODES_SUPPORTED=y +CONFIG_SOC_FLASH_ENC_SUPPORTED=y +CONFIG_SOC_SECURE_BOOT_SUPPORTED=y +CONFIG_SOC_BOD_SUPPORTED=y +CONFIG_SOC_VBAT_SUPPORTED=y +CONFIG_SOC_APM_SUPPORTED=y +CONFIG_SOC_PMU_SUPPORTED=y +CONFIG_SOC_DCDC_SUPPORTED=y +CONFIG_SOC_PAU_SUPPORTED=y +CONFIG_SOC_LP_TIMER_SUPPORTED=y +CONFIG_SOC_ULP_LP_UART_SUPPORTED=y +CONFIG_SOC_LP_GPIO_MATRIX_SUPPORTED=y +CONFIG_SOC_LP_PERIPHERALS_SUPPORTED=y +CONFIG_SOC_LP_I2C_SUPPORTED=y +CONFIG_SOC_LP_I2S_SUPPORTED=y +CONFIG_SOC_LP_SPI_SUPPORTED=y +CONFIG_SOC_LP_ADC_SUPPORTED=y +CONFIG_SOC_LP_VAD_SUPPORTED=y +CONFIG_SOC_SPIRAM_SUPPORTED=y +CONFIG_SOC_PSRAM_DMA_CAPABLE=y +CONFIG_SOC_SDMMC_HOST_SUPPORTED=y +CONFIG_SOC_CLK_TREE_SUPPORTED=y +CONFIG_SOC_ASSIST_DEBUG_SUPPORTED=y +CONFIG_SOC_DEBUG_PROBE_SUPPORTED=y +CONFIG_SOC_WDT_SUPPORTED=y +CONFIG_SOC_SPI_FLASH_SUPPORTED=y +CONFIG_SOC_TOUCH_SENSOR_SUPPORTED=y +CONFIG_SOC_RNG_SUPPORTED=y +CONFIG_SOC_GP_LDO_SUPPORTED=y +CONFIG_SOC_PPA_SUPPORTED=y +CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y +CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y +CONFIG_SOC_PM_SUPPORTED=y +CONFIG_SOC_BITSCRAMBLER_SUPPORTED=y +CONFIG_SOC_SIMD_INSTRUCTION_SUPPORTED=y +CONFIG_SOC_I3C_MASTER_SUPPORTED=y +CONFIG_SOC_XTAL_SUPPORT_40M=y +CONFIG_SOC_AES_SUPPORT_DMA=y +CONFIG_SOC_AES_SUPPORT_GCM=y +CONFIG_SOC_AES_GDMA=y +CONFIG_SOC_AES_SUPPORT_AES_128=y +CONFIG_SOC_AES_SUPPORT_AES_256=y +CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y +CONFIG_SOC_ADC_DIG_CTRL_SUPPORTED=y +CONFIG_SOC_ADC_DMA_SUPPORTED=y +CONFIG_SOC_ADC_PERIPH_NUM=2 +CONFIG_SOC_ADC_MAX_CHANNEL_NUM=8 +CONFIG_SOC_ADC_ATTEN_NUM=4 +CONFIG_SOC_ADC_DIGI_CONTROLLER_NUM=2 +CONFIG_SOC_ADC_PATT_LEN_MAX=16 +CONFIG_SOC_ADC_DIGI_MAX_BITWIDTH=12 +CONFIG_SOC_ADC_DIGI_MIN_BITWIDTH=12 +CONFIG_SOC_ADC_DIGI_IIR_FILTER_NUM=2 +CONFIG_SOC_ADC_DIGI_MONITOR_NUM=2 +CONFIG_SOC_ADC_DIGI_RESULT_BYTES=4 +CONFIG_SOC_ADC_DIGI_DATA_BYTES_PER_CONV=4 +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH=83333 +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW=611 +CONFIG_SOC_ADC_RTC_MIN_BITWIDTH=12 +CONFIG_SOC_ADC_RTC_MAX_BITWIDTH=12 +CONFIG_SOC_ADC_CALIBRATION_V1_SUPPORTED=y +CONFIG_SOC_ADC_SELF_HW_CALI_SUPPORTED=y +CONFIG_SOC_ADC_CALIB_CHAN_COMPENS_SUPPORTED=y +CONFIG_SOC_ADC_SHARED_POWER=y +CONFIG_SOC_BROWNOUT_RESET_SUPPORTED=y +CONFIG_SOC_SHARED_IDCACHE_SUPPORTED=y +CONFIG_SOC_CACHE_WRITEBACK_SUPPORTED=y +CONFIG_SOC_CACHE_FREEZE_SUPPORTED=y +CONFIG_SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE=y +CONFIG_SOC_CPU_CORES_NUM=2 +CONFIG_SOC_CPU_INTR_NUM=32 +CONFIG_SOC_CPU_HAS_FLEXIBLE_INTC=y +CONFIG_SOC_INT_CLIC_SUPPORTED=y +CONFIG_SOC_INT_HW_NESTED_SUPPORTED=y +CONFIG_SOC_BRANCH_PREDICTOR_SUPPORTED=y +CONFIG_SOC_CPU_COPROC_NUM=3 +CONFIG_SOC_CPU_HAS_FPU=y +CONFIG_SOC_CPU_HAS_FPU_EXT_ILL_BUG=y +CONFIG_SOC_CPU_HAS_HWLOOP=y +CONFIG_SOC_CPU_HAS_HWLOOP_STATE_BUG=y +CONFIG_SOC_CPU_HAS_PIE=y +CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y +CONFIG_SOC_CPU_BREAKPOINTS_NUM=3 +CONFIG_SOC_CPU_WATCHPOINTS_NUM=3 +CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=0x100 +CONFIG_SOC_CPU_HAS_PMA=y +CONFIG_SOC_CPU_IDRAM_SPLIT_USING_PMP=y +CONFIG_SOC_CPU_PMP_REGION_GRANULARITY=128 +CONFIG_SOC_CPU_HAS_LOCKUP_RESET=y +CONFIG_SOC_SIMD_PREFERRED_DATA_ALIGNMENT=16 +CONFIG_SOC_DS_SIGNATURE_MAX_BIT_LEN=4096 +CONFIG_SOC_DS_KEY_PARAM_MD_IV_LENGTH=16 +CONFIG_SOC_DS_KEY_CHECK_MAX_WAIT_US=1100 +CONFIG_SOC_DMA_CAN_ACCESS_FLASH=y +CONFIG_SOC_AHB_GDMA_VERSION=2 +CONFIG_SOC_GDMA_SUPPORT_CRC=y +CONFIG_SOC_GDMA_NUM_GROUPS_MAX=2 +CONFIG_SOC_GDMA_PAIRS_PER_GROUP_MAX=3 +CONFIG_SOC_AXI_GDMA_SUPPORT_PSRAM=y +CONFIG_SOC_GDMA_SUPPORT_ETM=y +CONFIG_SOC_GDMA_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_AXI_DMA_EXT_MEM_ENC_ALIGNMENT=16 +CONFIG_SOC_DMA2D_GROUPS=1 +CONFIG_SOC_DMA2D_TX_CHANNELS_PER_GROUP=3 +CONFIG_SOC_DMA2D_RX_CHANNELS_PER_GROUP=2 +CONFIG_SOC_ETM_GROUPS=1 +CONFIG_SOC_ETM_CHANNELS_PER_GROUP=50 +CONFIG_SOC_ETM_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_GPIO_PORT=1 +CONFIG_SOC_GPIO_PIN_COUNT=55 +CONFIG_SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER=y +CONFIG_SOC_GPIO_FLEX_GLITCH_FILTER_NUM=8 +CONFIG_SOC_GPIO_SUPPORT_PIN_HYS_FILTER=y +CONFIG_SOC_GPIO_SUPPORT_ETM=y +CONFIG_SOC_GPIO_SUPPORT_RTC_INDEPENDENT=y +CONFIG_SOC_GPIO_SUPPORT_DEEPSLEEP_WAKEUP=y +CONFIG_SOC_LP_IO_HAS_INDEPENDENT_WAKEUP_SOURCE=y +CONFIG_SOC_LP_IO_CLOCK_IS_INDEPENDENT=y +CONFIG_SOC_GPIO_VALID_GPIO_MASK=0x007FFFFFFFFFFFFF +CONFIG_SOC_GPIO_IN_RANGE_MAX=54 +CONFIG_SOC_GPIO_OUT_RANGE_MAX=54 +CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_VALID_GPIO_MASK=0 +CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_SUPPORTED_PIN_CNT=16 +CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0x007FFFFFFFFF0000 +CONFIG_SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX=y +CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=2 +CONFIG_SOC_CLOCKOUT_SUPPORT_CHANNEL_DIVIDER=y +CONFIG_SOC_DEBUG_PROBE_NUM_UNIT=1 +CONFIG_SOC_DEBUG_PROBE_MAX_OUTPUT_WIDTH=16 +CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y +CONFIG_SOC_RTCIO_PIN_COUNT=16 +CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y +CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y +CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y +CONFIG_SOC_RTCIO_EDGE_WAKE_SUPPORTED=y +CONFIG_SOC_DEDIC_GPIO_OUT_CHANNELS_NUM=8 +CONFIG_SOC_DEDIC_GPIO_IN_CHANNELS_NUM=8 +CONFIG_SOC_DEDIC_PERIPH_ALWAYS_ENABLE=y +CONFIG_SOC_ANA_CMPR_NUM=2 +CONFIG_SOC_ANA_CMPR_CAN_DISTINGUISH_EDGE=y +CONFIG_SOC_ANA_CMPR_SUPPORT_ETM=y +CONFIG_SOC_I2C_NUM=3 +CONFIG_SOC_HP_I2C_NUM=2 +CONFIG_SOC_I2C_FIFO_LEN=32 +CONFIG_SOC_I2C_CMD_REG_NUM=8 +CONFIG_SOC_I2C_SUPPORT_SLAVE=y +CONFIG_SOC_I2C_SUPPORT_HW_FSM_RST=y +CONFIG_SOC_I2C_SUPPORT_HW_CLR_BUS=y +CONFIG_SOC_I2C_SUPPORT_XTAL=y +CONFIG_SOC_I2C_SUPPORT_RTC=y +CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y +CONFIG_SOC_I2C_SLAVE_SUPPORT_BROADCAST=y +CONFIG_SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE=y +CONFIG_SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS=y +CONFIG_SOC_I2C_SLAVE_SUPPORT_SLAVE_UNMATCH=y +CONFIG_SOC_I2C_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_LP_I2C_NUM=1 +CONFIG_SOC_LP_I2C_FIFO_LEN=16 +CONFIG_SOC_I2S_NUM=3 +CONFIG_SOC_I2S_HW_VERSION_2=y +CONFIG_SOC_I2S_SUPPORTS_ETM=y +CONFIG_SOC_I2S_SUPPORTS_XTAL=y +CONFIG_SOC_I2S_SUPPORTS_APLL=y +CONFIG_SOC_I2S_SUPPORTS_PCM=y +CONFIG_SOC_I2S_SUPPORTS_PDM=y +CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y +CONFIG_SOC_I2S_SUPPORTS_PCM2PDM=y +CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y +CONFIG_SOC_I2S_SUPPORTS_PDM2PCM=y +CONFIG_SOC_I2S_SUPPORTS_PDM_RX_HP_FILTER=y +CONFIG_SOC_I2S_SUPPORTS_TX_SYNC_CNT=y +CONFIG_SOC_I2S_SUPPORTS_TDM=y +CONFIG_SOC_I2S_PDM_MAX_TX_LINES=2 +CONFIG_SOC_I2S_PDM_MAX_RX_LINES=4 +CONFIG_SOC_I2S_TDM_FULL_DATA_WIDTH=y +CONFIG_SOC_I2S_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_LP_I2S_NUM=1 +CONFIG_SOC_ISP_BF_SUPPORTED=y +CONFIG_SOC_ISP_CCM_SUPPORTED=y +CONFIG_SOC_ISP_DEMOSAIC_SUPPORTED=y +CONFIG_SOC_ISP_DVP_SUPPORTED=y +CONFIG_SOC_ISP_SHARPEN_SUPPORTED=y +CONFIG_SOC_ISP_COLOR_SUPPORTED=y +CONFIG_SOC_ISP_LSC_SUPPORTED=y +CONFIG_SOC_ISP_SHARE_CSI_BRG=y +CONFIG_SOC_ISP_NUMS=1 +CONFIG_SOC_ISP_DVP_CTLR_NUMS=1 +CONFIG_SOC_ISP_AE_CTLR_NUMS=1 +CONFIG_SOC_ISP_AE_BLOCK_X_NUMS=5 +CONFIG_SOC_ISP_AE_BLOCK_Y_NUMS=5 +CONFIG_SOC_ISP_AF_CTLR_NUMS=1 +CONFIG_SOC_ISP_AF_WINDOW_NUMS=3 +CONFIG_SOC_ISP_BF_TEMPLATE_X_NUMS=3 +CONFIG_SOC_ISP_BF_TEMPLATE_Y_NUMS=3 +CONFIG_SOC_ISP_CCM_DIMENSION=3 +CONFIG_SOC_ISP_DEMOSAIC_GRAD_RATIO_INT_BITS=2 +CONFIG_SOC_ISP_DEMOSAIC_GRAD_RATIO_DEC_BITS=4 +CONFIG_SOC_ISP_DEMOSAIC_GRAD_RATIO_RES_BITS=26 +CONFIG_SOC_ISP_DVP_DATA_WIDTH_MAX=16 +CONFIG_SOC_ISP_SHARPEN_TEMPLATE_X_NUMS=3 +CONFIG_SOC_ISP_SHARPEN_TEMPLATE_Y_NUMS=3 +CONFIG_SOC_ISP_SHARPEN_H_FREQ_COEF_INT_BITS=3 +CONFIG_SOC_ISP_SHARPEN_H_FREQ_COEF_DEC_BITS=5 +CONFIG_SOC_ISP_SHARPEN_H_FREQ_COEF_RES_BITS=24 +CONFIG_SOC_ISP_SHARPEN_M_FREQ_COEF_INT_BITS=3 +CONFIG_SOC_ISP_SHARPEN_M_FREQ_COEF_DEC_BITS=5 +CONFIG_SOC_ISP_SHARPEN_M_FREQ_COEF_RES_BITS=24 +CONFIG_SOC_ISP_HIST_CTLR_NUMS=1 +CONFIG_SOC_ISP_HIST_BLOCK_X_NUMS=5 +CONFIG_SOC_ISP_HIST_BLOCK_Y_NUMS=5 +CONFIG_SOC_ISP_HIST_SEGMENT_NUMS=16 +CONFIG_SOC_ISP_HIST_INTERVAL_NUMS=15 +CONFIG_SOC_ISP_LSC_GRAD_RATIO_INT_BITS=2 +CONFIG_SOC_ISP_LSC_GRAD_RATIO_DEC_BITS=8 +CONFIG_SOC_ISP_LSC_GRAD_RATIO_RES_BITS=22 +CONFIG_SOC_LEDC_SUPPORT_PLL_DIV_CLOCK=y +CONFIG_SOC_LEDC_SUPPORT_XTAL_CLOCK=y +CONFIG_SOC_LEDC_TIMER_NUM=4 +CONFIG_SOC_LEDC_CHANNEL_NUM=8 +CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=20 +CONFIG_SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED=y +CONFIG_SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX=16 +CONFIG_SOC_LEDC_SUPPORT_FADE_STOP=y +CONFIG_SOC_LEDC_FADE_PARAMS_BIT_WIDTH=10 +CONFIG_SOC_LEDC_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_MMU_PERIPH_NUM=2 +CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=2 +CONFIG_SOC_MMU_DI_VADDR_SHARED=y +CONFIG_SOC_MMU_PER_EXT_MEM_TARGET=y +CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 +CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 +CONFIG_SOC_PCNT_GROUPS=1 +CONFIG_SOC_PCNT_UNITS_PER_GROUP=4 +CONFIG_SOC_PCNT_CHANNELS_PER_UNIT=2 +CONFIG_SOC_PCNT_THRES_POINT_PER_UNIT=2 +CONFIG_SOC_PCNT_SUPPORT_RUNTIME_THRES_UPDATE=y +CONFIG_SOC_PCNT_SUPPORT_CLEAR_SIGNAL=y +CONFIG_SOC_PCNT_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_RMT_GROUPS=1 +CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP=4 +CONFIG_SOC_RMT_RX_CANDIDATES_PER_GROUP=4 +CONFIG_SOC_RMT_CHANNELS_PER_GROUP=8 +CONFIG_SOC_RMT_MEM_WORDS_PER_CHANNEL=48 +CONFIG_SOC_RMT_SUPPORT_RX_PINGPONG=y +CONFIG_SOC_RMT_SUPPORT_RX_DEMODULATION=y +CONFIG_SOC_RMT_SUPPORT_TX_ASYNC_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_COUNT=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_SYNCHRO=y +CONFIG_SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY=y +CONFIG_SOC_RMT_SUPPORT_XTAL=y +CONFIG_SOC_RMT_SUPPORT_RC_FAST=y +CONFIG_SOC_RMT_SUPPORT_DMA=y +CONFIG_SOC_RMT_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_LCD_I80_SUPPORTED=y +CONFIG_SOC_LCD_RGB_SUPPORTED=y +CONFIG_SOC_LCDCAM_I80_NUM_BUSES=1 +CONFIG_SOC_LCDCAM_I80_BUS_WIDTH=24 +CONFIG_SOC_LCDCAM_RGB_NUM_PANELS=1 +CONFIG_SOC_LCDCAM_RGB_DATA_WIDTH=24 +CONFIG_SOC_LCD_SUPPORT_RGB_YUV_CONV=y +CONFIG_SOC_MCPWM_GROUPS=2 +CONFIG_SOC_MCPWM_TIMERS_PER_GROUP=3 +CONFIG_SOC_MCPWM_OPERATORS_PER_GROUP=3 +CONFIG_SOC_MCPWM_COMPARATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_EVENT_COMPARATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_GENERATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_TRIGGERS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_GPIO_FAULTS_PER_GROUP=3 +CONFIG_SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP=y +CONFIG_SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER=3 +CONFIG_SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP=3 +CONFIG_SOC_MCPWM_SWSYNC_CAN_PROPAGATE=y +CONFIG_SOC_MCPWM_SUPPORT_ETM=y +CONFIG_SOC_MCPWM_SUPPORT_EVENT_COMPARATOR=y +CONFIG_SOC_MCPWM_CAPTURE_CLK_FROM_GROUP=y +CONFIG_SOC_MCPWM_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_USB_OTG_PERIPH_NUM=2 +CONFIG_SOC_USB_UTMI_PHY_NUM=1 +CONFIG_SOC_PARLIO_GROUPS=1 +CONFIG_SOC_PARLIO_TX_UNITS_PER_GROUP=1 +CONFIG_SOC_PARLIO_RX_UNITS_PER_GROUP=1 +CONFIG_SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH=16 +CONFIG_SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH=16 +CONFIG_SOC_PARLIO_TX_CLK_SUPPORT_GATING=y +CONFIG_SOC_PARLIO_RX_CLK_SUPPORT_GATING=y +CONFIG_SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT=y +CONFIG_SOC_PARLIO_TRANS_BIT_ALIGN=y +CONFIG_SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION=y +CONFIG_SOC_PARLIO_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_PARLIO_SUPPORT_SPI_LCD=y +CONFIG_SOC_PARLIO_SUPPORT_I80_LCD=y +CONFIG_SOC_MPI_MEM_BLOCKS_NUM=4 +CONFIG_SOC_MPI_OPERATIONS_NUM=3 +CONFIG_SOC_RSA_MAX_BIT_LEN=4096 +CONFIG_SOC_SDMMC_USE_IOMUX=y +CONFIG_SOC_SDMMC_USE_GPIO_MATRIX=y +CONFIG_SOC_SDMMC_NUM_SLOTS=2 +CONFIG_SOC_SDMMC_DELAY_PHASE_NUM=4 +CONFIG_SOC_SDMMC_IO_POWER_EXTERNAL=y +CONFIG_SOC_SDMMC_PSRAM_DMA_CAPABLE=y +CONFIG_SOC_SDMMC_UHS_I_SUPPORTED=y +CONFIG_SOC_SHA_DMA_MAX_BUFFER_SIZE=3968 +CONFIG_SOC_SHA_SUPPORT_DMA=y +CONFIG_SOC_SHA_SUPPORT_RESUME=y +CONFIG_SOC_SHA_GDMA=y +CONFIG_SOC_SHA_SUPPORT_SHA1=y +CONFIG_SOC_SHA_SUPPORT_SHA224=y +CONFIG_SOC_SHA_SUPPORT_SHA256=y +CONFIG_SOC_SHA_SUPPORT_SHA384=y +CONFIG_SOC_SHA_SUPPORT_SHA512=y +CONFIG_SOC_SHA_SUPPORT_SHA512_224=y +CONFIG_SOC_SHA_SUPPORT_SHA512_256=y +CONFIG_SOC_SHA_SUPPORT_SHA512_T=y +CONFIG_SOC_ECDSA_SUPPORT_EXPORT_PUBKEY=y +CONFIG_SOC_ECDSA_SUPPORT_DETERMINISTIC_MODE=y +CONFIG_SOC_ECDSA_USES_MPI=y +CONFIG_SOC_SDM_GROUPS=1 +CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8 +CONFIG_SOC_SDM_CLK_SUPPORT_PLL_F80M=y +CONFIG_SOC_SDM_CLK_SUPPORT_XTAL=y +CONFIG_SOC_SPI_PERIPH_NUM=3 +CONFIG_SOC_SPI_MAX_CS_NUM=6 +CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64 +CONFIG_SOC_SPI_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_SPI_SUPPORT_SLAVE_HD_VER2=y +CONFIG_SOC_SPI_SLAVE_SUPPORT_SEG_TRANS=y +CONFIG_SOC_SPI_SUPPORT_DDRCLK=y +CONFIG_SOC_SPI_SUPPORT_CD_SIG=y +CONFIG_SOC_SPI_SUPPORT_OCT=y +CONFIG_SOC_SPI_SUPPORT_CLK_XTAL=y +CONFIG_SOC_SPI_SUPPORT_CLK_RC_FAST=y +CONFIG_SOC_SPI_SUPPORT_CLK_SPLL=y +CONFIG_SOC_MSPI_HAS_INDEPENT_IOMUX=y +CONFIG_SOC_MEMSPI_IS_INDEPENDENT=y +CONFIG_SOC_SPI_MAX_PRE_DIVIDER=16 +CONFIG_SOC_LP_SPI_PERIPH_NUM=y +CONFIG_SOC_LP_SPI_MAXIMUM_BUFFER_SIZE=64 +CONFIG_SOC_SPIRAM_XIP_SUPPORTED=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_WAIT_IDLE=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_RESUME=y +CONFIG_SOC_SPI_MEM_SUPPORT_IDLE_INTR=y +CONFIG_SOC_SPI_MEM_SUPPORT_SW_SUSPEND=y +CONFIG_SOC_SPI_MEM_SUPPORT_CHECK_SUS=y +CONFIG_SOC_SPI_MEM_SUPPORT_TIMING_TUNING=y +CONFIG_SOC_MEMSPI_TIMING_TUNING_BY_DQS=y +CONFIG_SOC_MEMSPI_TIMING_TUNING_BY_FLASH_DELAY=y +CONFIG_SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP=y +CONFIG_SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUT=y +CONFIG_SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_40M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_20M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_120M_SUPPORTED=y +CONFIG_SOC_MEMSPI_FLASH_PSRAM_INDEPENDENT=y +CONFIG_SOC_SYSTIMER_COUNTER_NUM=2 +CONFIG_SOC_SYSTIMER_ALARM_NUM=3 +CONFIG_SOC_SYSTIMER_BIT_WIDTH_LO=32 +CONFIG_SOC_SYSTIMER_BIT_WIDTH_HI=20 +CONFIG_SOC_SYSTIMER_FIXED_DIVIDER=y +CONFIG_SOC_SYSTIMER_SUPPORT_RC_FAST=y +CONFIG_SOC_SYSTIMER_INT_LEVEL=y +CONFIG_SOC_SYSTIMER_ALARM_MISS_COMPENSATE=y +CONFIG_SOC_SYSTIMER_SUPPORT_ETM=y +CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 +CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 +CONFIG_SOC_TIMER_GROUPS=2 +CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2 +CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=54 +CONFIG_SOC_TIMER_GROUP_SUPPORT_XTAL=y +CONFIG_SOC_TIMER_GROUP_SUPPORT_RC_FAST=y +CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 +CONFIG_SOC_TIMER_SUPPORT_ETM=y +CONFIG_SOC_TIMER_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_MWDT_SUPPORT_XTAL=y +CONFIG_SOC_MWDT_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_TOUCH_SENSOR_VERSION=3 +CONFIG_SOC_TOUCH_SENSOR_NUM=14 +CONFIG_SOC_TOUCH_MIN_CHAN_ID=0 +CONFIG_SOC_TOUCH_MAX_CHAN_ID=13 +CONFIG_SOC_TOUCH_SUPPORT_SLEEP_WAKEUP=y +CONFIG_SOC_TOUCH_SUPPORT_BENCHMARK=y +CONFIG_SOC_TOUCH_SUPPORT_WATERPROOF=y +CONFIG_SOC_TOUCH_SUPPORT_PROX_SENSING=y +CONFIG_SOC_TOUCH_PROXIMITY_CHANNEL_NUM=3 +CONFIG_SOC_TOUCH_PROXIMITY_MEAS_DONE_SUPPORTED=y +CONFIG_SOC_TOUCH_SUPPORT_FREQ_HOP=y +CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=3 +CONFIG_SOC_TWAI_CONTROLLER_NUM=3 +CONFIG_SOC_TWAI_MASK_FILTER_NUM=1 +CONFIG_SOC_TWAI_CLK_SUPPORT_XTAL=y +CONFIG_SOC_TWAI_BRP_MIN=2 +CONFIG_SOC_TWAI_BRP_MAX=32768 +CONFIG_SOC_TWAI_SUPPORTS_RX_STATUS=y +CONFIG_SOC_TWAI_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_EFUSE_DIS_PAD_JTAG=y +CONFIG_SOC_EFUSE_DIS_USB_JTAG=y +CONFIG_SOC_EFUSE_DIS_DIRECT_BOOT=y +CONFIG_SOC_EFUSE_SOFT_DIS_JTAG=y +CONFIG_SOC_EFUSE_DIS_DOWNLOAD_MSPI=y +CONFIG_SOC_EFUSE_ECDSA_KEY=y +CONFIG_SOC_KEY_MANAGER_ECDSA_KEY_DEPLOY=y +CONFIG_SOC_KEY_MANAGER_FE_KEY_DEPLOY=y +CONFIG_SOC_SECURE_BOOT_V2_RSA=y +CONFIG_SOC_SECURE_BOOT_V2_ECC=y +CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=3 +CONFIG_SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS=y +CONFIG_SOC_SUPPORT_SECURE_BOOT_REVOKE_KEY=y +CONFIG_SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX=64 +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_OPTIONS=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_128=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_256=y +CONFIG_SOC_UART_NUM=6 +CONFIG_SOC_UART_HP_NUM=5 +CONFIG_SOC_UART_LP_NUM=1 +CONFIG_SOC_UART_FIFO_LEN=128 +CONFIG_SOC_LP_UART_FIFO_LEN=16 +CONFIG_SOC_UART_BITRATE_MAX=5000000 +CONFIG_SOC_UART_SUPPORT_PLL_F80M_CLK=y +CONFIG_SOC_UART_SUPPORT_RTC_CLK=y +CONFIG_SOC_UART_SUPPORT_XTAL_CLK=y +CONFIG_SOC_UART_SUPPORT_WAKEUP_INT=y +CONFIG_SOC_UART_HAS_LP_UART=y +CONFIG_SOC_UART_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_UART_SUPPORT_FSM_TX_WAIT_SEND=y +CONFIG_SOC_UART_WAKEUP_CHARS_SEQ_MAX_LEN=5 +CONFIG_SOC_UART_WAKEUP_SUPPORT_ACTIVE_THRESH_MODE=y +CONFIG_SOC_UART_WAKEUP_SUPPORT_FIFO_THRESH_MODE=y +CONFIG_SOC_UART_WAKEUP_SUPPORT_START_BIT_MODE=y +CONFIG_SOC_UART_WAKEUP_SUPPORT_CHAR_SEQ_MODE=y +CONFIG_SOC_LP_I2S_SUPPORT_VAD=y +CONFIG_SOC_UHCI_NUM=1 +CONFIG_SOC_COEX_HW_PTI=y +CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21 +CONFIG_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 +CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP_MODE_PER_PIN=y +CONFIG_SOC_PM_EXT1_WAKEUP_BY_PMU=y +CONFIG_SOC_PM_SUPPORT_WIFI_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_XTAL32K_PD=y +CONFIG_SOC_PM_SUPPORT_RC32K_PD=y +CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y +CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y +CONFIG_SOC_PM_SUPPORT_TOP_PD=y +CONFIG_SOC_PM_SUPPORT_CNNT_PD=y +CONFIG_SOC_PM_SUPPORT_RTC_PERIPH_PD=y +CONFIG_SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_ONLY=y +CONFIG_SOC_PM_CPU_RETENTION_BY_SW=y +CONFIG_SOC_PM_CACHE_RETENTION_BY_PAU=y +CONFIG_SOC_PM_PAU_LINK_NUM=4 +CONFIG_SOC_PM_PAU_REGDMA_LINK_MULTI_ADDR=y +CONFIG_SOC_PAU_IN_TOP_DOMAIN=y +CONFIG_SOC_CPU_IN_TOP_DOMAIN=y +CONFIG_SOC_PM_PAU_REGDMA_UPDATE_CACHE_BEFORE_WAIT_COMPARE=y +CONFIG_SOC_SLEEP_SYSTIMER_STALL_WORKAROUND=y +CONFIG_SOC_SLEEP_TGWDT_STOP_WORKAROUND=y +CONFIG_SOC_PM_RETENTION_MODULE_NUM=64 +CONFIG_SOC_PSRAM_VDD_POWER_MPLL=y +CONFIG_SOC_CLK_RC_FAST_SUPPORT_CALIBRATION=y +CONFIG_SOC_CLK_APLL_SUPPORTED=y +CONFIG_SOC_CLK_MPLL_SUPPORTED=y +CONFIG_SOC_CLK_SDIO_PLL_SUPPORTED=y +CONFIG_SOC_CLK_XTAL32K_SUPPORTED=y +CONFIG_SOC_CLK_RC32K_SUPPORTED=y +CONFIG_SOC_CLK_LP_FAST_SUPPORT_LP_PLL=y +CONFIG_SOC_CLK_LP_FAST_SUPPORT_XTAL=y +CONFIG_SOC_PERIPH_CLK_CTRL_SHARED=y +CONFIG_SOC_CLK_ANA_I2C_MST_HAS_ROOT_GATE=y +CONFIG_SOC_TEMPERATURE_SENSOR_LP_PLL_SUPPORT=y +CONFIG_SOC_TEMPERATURE_SENSOR_INTR_SUPPORT=y +CONFIG_SOC_TSENS_IS_INDEPENDENT_FROM_ADC=y +CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_ETM=y +CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_MEM_TCM_SUPPORTED=y +CONFIG_SOC_MEM_NON_CONTIGUOUS_SRAM=y +CONFIG_SOC_ASYNCHRONOUS_BUS_ERROR_MODE=y +CONFIG_SOC_EMAC_IEEE1588V2_SUPPORTED=y +CONFIG_SOC_EMAC_USE_MULTI_IO_MUX=y +CONFIG_SOC_EMAC_MII_USE_GPIO_MATRIX=y +CONFIG_SOC_JPEG_CODEC_SUPPORTED=y +CONFIG_SOC_JPEG_DECODE_SUPPORTED=y +CONFIG_SOC_JPEG_ENCODE_SUPPORTED=y +CONFIG_SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV=y +CONFIG_SOC_LCDCAM_CAM_PERIPH_NUM=1 +CONFIG_SOC_LCDCAM_CAM_DATA_WIDTH_MAX=16 +CONFIG_SOC_I3C_MASTER_PERIPH_NUM=y +CONFIG_SOC_I3C_MASTER_ADDRESS_TABLE_NUM=12 +CONFIG_SOC_I3C_MASTER_COMMAND_TABLE_NUM=12 +CONFIG_SOC_LP_CORE_SUPPORT_ETM=y +CONFIG_SOC_LP_CORE_SUPPORT_LP_ADC=y +CONFIG_SOC_LP_CORE_SUPPORT_LP_VAD=y +CONFIG_SOC_LP_CORE_SUPPORT_STORE_LOAD_EXCEPTIONS=y +CONFIG_IDF_CMAKE=y +CONFIG_IDF_TOOLCHAIN="gcc" +CONFIG_IDF_TOOLCHAIN_GCC=y +CONFIG_IDF_TARGET_ARCH_RISCV=y +CONFIG_IDF_TARGET_ARCH="riscv" +CONFIG_IDF_TARGET="esp32p4" +CONFIG_IDF_INIT_VERSION="5.5.0" +CONFIG_IDF_TARGET_ESP32P4=y +CONFIG_IDF_FIRMWARE_CHIP_ID=0x0012 + +# +# Build type +# +CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y +# CONFIG_APP_BUILD_TYPE_RAM is not set +CONFIG_APP_BUILD_GENERATE_BINARIES=y +CONFIG_APP_BUILD_BOOTLOADER=y +CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y +# CONFIG_APP_REPRODUCIBLE_BUILD is not set +# CONFIG_APP_NO_BLOBS is not set +# end of Build type + +# +# Bootloader config +# + +# +# Bootloader manager +# +CONFIG_BOOTLOADER_COMPILE_TIME_DATE=y +CONFIG_BOOTLOADER_PROJECT_VER=1 +# end of Bootloader manager + +# +# Application Rollback +# +CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y +# CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK is not set +# end of Application Rollback + +# +# Bootloader Rollback +# +# end of Bootloader Rollback + +CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x2000 +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF=y + +# +# Log +# +CONFIG_BOOTLOADER_LOG_VERSION_1=y +CONFIG_BOOTLOADER_LOG_VERSION=1 +CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y +# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_INFO is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set +CONFIG_BOOTLOADER_LOG_LEVEL=0 + +# +# Format +# +# CONFIG_BOOTLOADER_LOG_COLORS is not set +CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y +# end of Format + +# +# Settings +# +CONFIG_BOOTLOADER_LOG_MODE_TEXT_EN=y +CONFIG_BOOTLOADER_LOG_MODE_TEXT=y +# CONFIG_BOOTLOADER_LOG_MODE_BINARY is not set +# end of Settings +# end of Log + +# +# Serial Flash Configurations +# +# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set +CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y +# end of Serial Flash Configurations + +# CONFIG_BOOTLOADER_FACTORY_RESET is not set +# CONFIG_BOOTLOADER_APP_TEST is not set +CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y +CONFIG_BOOTLOADER_WDT_ENABLE=y +# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set +CONFIG_BOOTLOADER_WDT_TIME_MS=9000 +CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y +CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON=y +CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS=y +CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0x10 +# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set +CONFIG_BOOTLOADER_RESERVE_RTC_MEM=y +# end of Bootloader config + +# +# Security features +# +CONFIG_SECURE_BOOT_V2_RSA_SUPPORTED=y +CONFIG_SECURE_BOOT_V2_ECC_SUPPORTED=y +CONFIG_SECURE_BOOT_V2_PREFERRED=y +# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set +# CONFIG_SECURE_BOOT is not set +# CONFIG_SECURE_FLASH_ENC_ENABLED is not set +CONFIG_SECURE_ROM_DL_MODE_ENABLED=y +# end of Security features + +# +# Application manager +# +CONFIG_APP_COMPILE_TIME_DATE=y +# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set +# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set +# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set +CONFIG_APP_RETRIEVE_LEN_ELF_SHA=9 +# end of Application manager + +CONFIG_ESP_ROM_HAS_CRC_LE=y +CONFIG_ESP_ROM_HAS_CRC_BE=y +CONFIG_ESP_ROM_UART_CLK_IS_XTAL=y +CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=6 +CONFIG_ESP_ROM_USB_OTG_NUM=5 +CONFIG_ESP_ROM_HAS_RETARGETABLE_LOCKING=y +CONFIG_ESP_ROM_GET_CLK_FREQ=y +CONFIG_ESP_ROM_HAS_RVFPLIB=y +CONFIG_ESP_ROM_HAS_HAL_WDT=y +CONFIG_ESP_ROM_HAS_HAL_SYSTIMER=y +CONFIG_ESP_ROM_HAS_LAYOUT_TABLE=y +CONFIG_ESP_ROM_WDT_INIT_PATCH=y +CONFIG_ESP_ROM_HAS_LP_ROM=y +CONFIG_ESP_ROM_WITHOUT_REGI2C=y +CONFIG_ESP_ROM_HAS_NEWLIB=y +CONFIG_ESP_ROM_HAS_NEWLIB_NANO_FORMAT=y +CONFIG_ESP_ROM_HAS_NEWLIB_NANO_PRINTF_FLOAT_BUG=y +CONFIG_ESP_ROM_HAS_VERSION=y +CONFIG_ESP_ROM_CLIC_INT_TYPE_PATCH=y +CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y +CONFIG_ESP_ROM_HAS_SUBOPTIMAL_NEWLIB_ON_MISALIGNED_MEMORY=y + +# +# Boot ROM Behavior +# +CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y +# CONFIG_BOOT_ROM_LOG_ALWAYS_OFF is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_HIGH is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_LOW is not set +# end of Boot ROM Behavior + +# +# Serial flasher config +# +# CONFIG_ESPTOOLPY_NO_STUB is not set +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set +# CONFIG_ESPTOOLPY_FLASHMODE_DIO is not set +# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set +CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +# CONFIG_ESPTOOLPY_FLASHFREQ_120M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +# CONFIG_ESPTOOLPY_FLASHFREQ_40M is not set +# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_VAL=80 +CONFIG_ESPTOOLPY_FLASHFREQ="80m" +# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE="16MB" +# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set +CONFIG_ESPTOOLPY_BEFORE_RESET=y +# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +# CONFIG_ESPTOOLPY_AFTER_NORESET is not set +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +# end of Serial flasher config + +# +# Partition Table +# +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v1/16m.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions/v1/16m.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# Xiaozhi Assistant +# +CONFIG_OTA_URL="https://api.tenclass.net/xiaozhi/ota/" +CONFIG_LANGUAGE_ZH_CN=y +# CONFIG_LANGUAGE_ZH_TW is not set +# CONFIG_LANGUAGE_EN_US is not set +# CONFIG_LANGUAGE_JA_JP is not set +CONFIG_BOARD_TYPE_M5STACK_CORE_TAB5=y +# CONFIG_BOARD_TYPE_ESP32P4_NANO is not set +# CONFIG_BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B is not set +# CONFIG_BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC is not set +# CONFIG_USE_WECHAT_MESSAGE_STYLE is not set +CONFIG_USE_AFE_WAKE_WORD=y +CONFIG_USE_AUDIO_PROCESSOR=y +# CONFIG_USE_SERVER_AEC is not set +# CONFIG_USE_AUDIO_DEBUGGER is not set +CONFIG_IOT_PROTOCOL_MCP=y +# CONFIG_IOT_PROTOCOL_XIAOZHI is not set +# end of Xiaozhi Assistant + +# +# ESP Speech Recognition +# +CONFIG_MODEL_IN_FLASH=y +# CONFIG_MODEL_IN_SDCARD is not set +CONFIG_AFE_INTERFACE_V1=y +CONFIG_SR_NSN_WEBRTC=y +# CONFIG_SR_NSN_NSNET2 is not set +CONFIG_SR_VADN_WEBRTC=y +# CONFIG_SR_VADN_VADNET1_MEDIUM is not set + +# +# Load Multiple Wake Words (WakeNet9) +# +# CONFIG_SR_WN_WN9_HILEXIN is not set +# CONFIG_SR_WN_WN9_XIAOAITONGXUE is not set +CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y +# CONFIG_SR_WN_WN9_ALEXA is not set +# CONFIG_SR_WN_WN9_HIESP is not set +# CONFIG_SR_WN_WN9_JARVIS_TTS is not set +# CONFIG_SR_WN_WN9_COMPUTER_TTS is not set +# CONFIG_SR_WN_WN9_HEYWILLOW_TTS is not set +# CONFIG_SR_WN_WN9_SOPHIA_TTS is not set +# CONFIG_SR_WN_WN9_NIHAOXIAOXIN_TTS is not set +# CONFIG_SR_WN_WN9_XIAOMEITONGXUE_TTS is not set +# CONFIG_SR_WN_WN9_HEYPRINTER_TTS is not set +# CONFIG_SR_WN_WN9_XIAOLONGXIAOLONG_TTS is not set +# CONFIG_SR_WN_WN9_MIAOMIAOTONGXUE_TTS is not set +# CONFIG_SR_WN_WN9_HEYWANDA_TTS is not set +# CONFIG_SR_WN_WN9_HIMIAOMIAO_TTS is not set +# CONFIG_SR_WN_WN9_MYCROFT_TTS is not set +# CONFIG_SR_WN_WN9_HIJOY_TTS is not set +# CONFIG_SR_WN_WN9_HILILI_TTS is not set +# CONFIG_SR_WN_WN9_HITELLY_TTS is not set +# CONFIG_SR_WN_WN9_XIAOBINXIAOBIN_TTS is not set +# CONFIG_SR_WN_WN9_HAIXIAOWU_TTS is not set +# CONFIG_SR_WN_WN9_ASTROLABE_TTS is not set +# CONFIG_SR_WN_WN9_HEYILY_TTS2 is not set +# CONFIG_SR_WN_WN9_XIAOYAXIAOYA_TTS2 is not set +# CONFIG_SR_WN_WN9_HIJASON_TTS2 is not set +# CONFIG_SR_WN_WN9_LINAIBAN_TTS2 is not set +# CONFIG_SR_WN_WN9_XIAOSUROU_TTS2 is not set +# CONFIG_SR_WN_WN9_XIAOYUTONGXUE_TTS2 is not set +# CONFIG_SR_WN_WN9_XIAOMINGTONGXUE_TTS2 is not set +# CONFIG_SR_WN_WN9_XIAOKANGTONGXUE_TTS2 is not set +# CONFIG_SR_WN_WN9_XIAOJIANXIAOJIAN_TTS2 is not set +# CONFIG_SR_WN_WN9_XIAOTEXIAOTE_TTS2 is not set +# CONFIG_SR_WN_WN9_NIHAOXIAOYI_TTS2 is not set +# CONFIG_SR_WN_WN9_NIHAOBAIYING_TTS2 is not set +# CONFIG_SR_WN_WN9_HIWALLE_TTS2 is not set +# end of Load Multiple Wake Words (WakeNet9) + +CONFIG_SR_MN_CN_NONE=y +# CONFIG_SR_MN_CN_MULTINET7_QUANT is not set +# CONFIG_SR_MN_CN_MULTINET7_AC_QUANT is not set +CONFIG_SR_MN_EN_NONE=y +# CONFIG_SR_MN_EN_MULTINET7_QUANT is not set +# end of ESP Speech Recognition + +# +# Compiler options +# +CONFIG_COMPILER_OPTIMIZATION_DEBUG=y +# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set +# CONFIG_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_COMPILER_OPTIMIZATION_NONE is not set +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set +CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE=y +# CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB is not set +CONFIG_COMPILER_FLOAT_LIB_FROM_RVFPLIB=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set +CONFIG_COMPILER_HIDE_PATHS_MACROS=y +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 +# CONFIG_COMPILER_CXX_RTTI is not set +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set +# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set +# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set +# CONFIG_COMPILER_SAVE_RESTORE_LIBCALLS is not set +CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y +# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set +# CONFIG_COMPILER_DUMP_RTL_FILES is not set +CONFIG_COMPILER_RT_LIB_GCCLIB=y +CONFIG_COMPILER_RT_LIB_NAME="gcc" +CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y +# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set +# CONFIG_COMPILER_STATIC_ANALYZER is not set +# end of Compiler options + +# +# Component config +# + +# +# Application Level Tracing +# +# CONFIG_APPTRACE_DEST_JTAG is not set +CONFIG_APPTRACE_DEST_NONE=y +# CONFIG_APPTRACE_DEST_UART1 is not set +# CONFIG_APPTRACE_DEST_UART2 is not set +CONFIG_APPTRACE_DEST_UART_NONE=y +CONFIG_APPTRACE_UART_TASK_PRIO=1 +CONFIG_APPTRACE_LOCK_ENABLE=y +# end of Application Level Tracing + +# +# Bluetooth +# +# CONFIG_BT_ENABLED is not set + +# +# Common Options +# +# CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set +# end of Common Options +# end of Bluetooth + +# +# Console Library +# +# CONFIG_CONSOLE_SORTED_HELP is not set +# end of Console Library + +# +# Driver Configurations +# + +# +# Legacy TWAI Driver Configurations +# +# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy TWAI Driver Configurations + +# +# Legacy ADC Driver Configuration +# +# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set + +# +# Legacy ADC Calibration Configuration +# +# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy ADC Calibration Configuration +# end of Legacy ADC Driver Configuration + +# +# Legacy MCPWM Driver Configurations +# +# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy MCPWM Driver Configurations + +# +# Legacy Timer Group Driver Configurations +# +# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy Timer Group Driver Configurations + +# +# Legacy RMT Driver Configurations +# +# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy RMT Driver Configurations + +# +# Legacy I2S Driver Configurations +# +# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy I2S Driver Configurations + +# +# Legacy I2C Driver Configurations +# +# CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy I2C Driver Configurations + +# +# Legacy PCNT Driver Configurations +# +# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy PCNT Driver Configurations + +# +# Legacy SDM Driver Configurations +# +# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy SDM Driver Configurations + +# +# Legacy Temperature Sensor Driver Configurations +# +# CONFIG_TEMP_SENSOR_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_TEMP_SENSOR_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy Temperature Sensor Driver Configurations + +# +# Legacy Touch Sensor Driver Configurations +# +# CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_TOUCH_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy Touch Sensor Driver Configurations +# end of Driver Configurations + +# +# eFuse Bit Manager +# +# CONFIG_EFUSE_CUSTOM_TABLE is not set +# CONFIG_EFUSE_VIRTUAL is not set +CONFIG_EFUSE_MAX_BLK_LEN=256 +# end of eFuse Bit Manager + +# +# ESP-TLS +# +CONFIG_ESP_TLS_USING_MBEDTLS=y +# CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set +CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y +# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set +# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set +# CONFIG_ESP_TLS_PSK_VERIFICATION is not set +# CONFIG_ESP_TLS_INSECURE is not set +# end of ESP-TLS + +# +# ADC and ADC Calibration +# +# CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM is not set +# CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is not set +# CONFIG_ADC_ENABLE_DEBUG_LOG is not set +# end of ADC and ADC Calibration + +# +# Wireless Coexistence +# +# CONFIG_ESP_COEX_GPIO_DEBUG is not set +# end of Wireless Coexistence + +# +# Common ESP-related +# +CONFIG_ESP_ERR_TO_NAME_LOOKUP=y +# end of Common ESP-related + +# +# ESP-Driver:Analog Comparator Configurations +# +CONFIG_ANA_CMPR_ISR_HANDLER_IN_IRAM=y +# CONFIG_ANA_CMPR_CTRL_FUNC_IN_IRAM is not set +# CONFIG_ANA_CMPR_ISR_CACHE_SAFE is not set +CONFIG_ANA_CMPR_OBJ_CACHE_SAFE=y +# CONFIG_ANA_CMPR_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Analog Comparator Configurations + +# +# BitScrambler Configurations +# +# CONFIG_BITSCRAMBLER_CTRL_FUNC_IN_IRAM is not set +# end of BitScrambler Configurations + +# +# ESP-Driver:Camera Controller Configurations +# +# CONFIG_CAM_CTLR_MIPI_CSI_ISR_CACHE_SAFE is not set +# CONFIG_CAM_CTLR_ISP_DVP_ISR_CACHE_SAFE is not set +# CONFIG_CAM_CTLR_DVP_CAM_ISR_CACHE_SAFE is not set +# end of ESP-Driver:Camera Controller Configurations + +# +# ESP-Driver:GPIO Configurations +# +# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:GPIO Configurations + +# +# ESP-Driver:GPTimer Configurations +# +CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y +# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set +# CONFIG_GPTIMER_ISR_CACHE_SAFE is not set +CONFIG_GPTIMER_OBJ_CACHE_SAFE=y +# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:GPTimer Configurations + +# +# ESP-Driver:I2C Configurations +# +# CONFIG_I2C_ISR_IRAM_SAFE is not set +# CONFIG_I2C_ENABLE_DEBUG_LOG is not set +# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set +CONFIG_I2C_MASTER_ISR_HANDLER_IN_IRAM=y +# end of ESP-Driver:I2C Configurations + +# +# ESP-Driver:I2S Configurations +# +# CONFIG_I2S_ISR_IRAM_SAFE is not set +# CONFIG_I2S_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:I2S Configurations + +# +# ESP-Driver:ISP Configurations +# +# CONFIG_ISP_ISR_IRAM_SAFE is not set +# CONFIG_ISP_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:ISP Configurations + +# +# ESP-Driver:JPEG-Codec Configurations +# +# CONFIG_JPEG_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:JPEG-Codec Configurations + +# +# ESP-Driver:LEDC Configurations +# +# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:LEDC Configurations + +# +# ESP-Driver:MCPWM Configurations +# +# CONFIG_MCPWM_ISR_IRAM_SAFE is not set +# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set +# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:MCPWM Configurations + +# +# ESP-Driver:Parallel IO Configurations +# +CONFIG_PARLIO_TX_ISR_HANDLER_IN_IRAM=y +CONFIG_PARLIO_RX_ISR_HANDLER_IN_IRAM=y +# CONFIG_PARLIO_TX_ISR_CACHE_SAFE is not set +# CONFIG_PARLIO_RX_ISR_CACHE_SAFE is not set +CONFIG_PARLIO_OBJ_CACHE_SAFE=y +# CONFIG_PARLIO_ENABLE_DEBUG_LOG is not set +# CONFIG_PARLIO_ISR_IRAM_SAFE is not set +# end of ESP-Driver:Parallel IO Configurations + +# +# ESP-Driver:PCNT Configurations +# +# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set +# CONFIG_PCNT_ISR_IRAM_SAFE is not set +# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:PCNT Configurations + +# +# ESP-Driver:RMT Configurations +# +CONFIG_RMT_ENCODER_FUNC_IN_IRAM=y +CONFIG_RMT_TX_ISR_HANDLER_IN_IRAM=y +CONFIG_RMT_RX_ISR_HANDLER_IN_IRAM=y +# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set +# CONFIG_RMT_TX_ISR_CACHE_SAFE is not set +# CONFIG_RMT_RX_ISR_CACHE_SAFE is not set +CONFIG_RMT_OBJ_CACHE_SAFE=y +# CONFIG_RMT_ENABLE_DEBUG_LOG is not set +# CONFIG_RMT_ISR_IRAM_SAFE is not set +# end of ESP-Driver:RMT Configurations + +# +# ESP-Driver:Sigma Delta Modulator Configurations +# +# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set +# CONFIG_SDM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Sigma Delta Modulator Configurations + +# +# ESP-Driver:SPI Configurations +# +# CONFIG_SPI_MASTER_IN_IRAM is not set +CONFIG_SPI_MASTER_ISR_IN_IRAM=y +# CONFIG_SPI_SLAVE_IN_IRAM is not set +CONFIG_SPI_SLAVE_ISR_IN_IRAM=y +# end of ESP-Driver:SPI Configurations + +# +# ESP-Driver:Touch Sensor Configurations +# +# CONFIG_TOUCH_CTRL_FUNC_IN_IRAM is not set +# CONFIG_TOUCH_ISR_IRAM_SAFE is not set +# CONFIG_TOUCH_ENABLE_DEBUG_LOG is not set +# CONFIG_TOUCH_SKIP_FSM_CHECK is not set +# end of ESP-Driver:Touch Sensor Configurations + +# +# ESP-Driver:Temperature Sensor Configurations +# +# CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set +# CONFIG_TEMP_SENSOR_ISR_IRAM_SAFE is not set +# end of ESP-Driver:Temperature Sensor Configurations + +# +# ESP-Driver:TWAI Configurations +# +# CONFIG_TWAI_ISR_IN_IRAM is not set +# CONFIG_TWAI_ISR_CACHE_SAFE is not set +# CONFIG_TWAI_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:TWAI Configurations + +# +# ESP-Driver:UART Configurations +# +CONFIG_UART_ISR_IN_IRAM=y +# end of ESP-Driver:UART Configurations + +# +# ESP-Driver:UHCI Configurations +# +# CONFIG_UHCI_ISR_HANDLER_IN_IRAM is not set +# CONFIG_UHCI_ISR_CACHE_SAFE is not set +# CONFIG_UHCI_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:UHCI Configurations + +# +# ESP-Driver:USB Serial/JTAG Configuration +# +CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y +# end of ESP-Driver:USB Serial/JTAG Configuration + +# +# Ethernet +# +CONFIG_ETH_ENABLED=y +CONFIG_ETH_USE_ESP32_EMAC=y +CONFIG_ETH_PHY_INTERFACE_RMII=y +CONFIG_ETH_DMA_BUFFER_SIZE=512 +CONFIG_ETH_DMA_RX_BUFFER_NUM=20 +CONFIG_ETH_DMA_TX_BUFFER_NUM=10 +# CONFIG_ETH_SOFT_FLOW_CONTROL is not set +# CONFIG_ETH_IRAM_OPTIMIZATION is not set +CONFIG_ETH_USE_SPI_ETHERNET=y +# CONFIG_ETH_SPI_ETHERNET_DM9051 is not set +# CONFIG_ETH_SPI_ETHERNET_W5500 is not set +# CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL is not set +# CONFIG_ETH_USE_OPENETH is not set +# CONFIG_ETH_TRANSMIT_MUTEX is not set +# end of Ethernet + +# +# Event Loop Library +# +# CONFIG_ESP_EVENT_LOOP_PROFILING is not set +CONFIG_ESP_EVENT_POST_FROM_ISR=y +CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y +# end of Event Loop Library + +# +# GDB Stub +# +CONFIG_ESP_GDBSTUB_ENABLED=y +# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set +CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y +CONFIG_ESP_GDBSTUB_MAX_TASKS=32 +# end of GDB Stub + +# +# ESP HID +# +CONFIG_ESPHID_TASK_SIZE_BT=2048 +CONFIG_ESPHID_TASK_SIZE_BLE=4096 +# end of ESP HID + +# +# ESP HTTP client +# +CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y +# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set +CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000 +# end of ESP HTTP client + +# +# HTTP Server +# +CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048 +CONFIG_HTTPD_MAX_URI_LEN=2048 +CONFIG_HTTPD_ERR_RESP_NO_DELAY=y +CONFIG_HTTPD_PURGE_BUF_LEN=32 +# CONFIG_HTTPD_LOG_PURGE_DATA is not set +# CONFIG_HTTPD_WS_SUPPORT is not set +# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set +CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000 +# end of HTTP Server + +# +# ESP HTTPS OTA +# +# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set +# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set +CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000 +# end of ESP HTTPS OTA + +# +# ESP HTTPS server +# +# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set +CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000 +# CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK is not set +# end of ESP HTTPS server + +# +# Hardware Settings +# + +# +# Chip revision +# +# CONFIG_ESP32P4_REV_MIN_0 is not set +CONFIG_ESP32P4_REV_MIN_1=y +# CONFIG_ESP32P4_REV_MIN_100 is not set +CONFIG_ESP32P4_REV_MIN_FULL=1 +CONFIG_ESP_REV_MIN_FULL=1 + +# +# Maximum Supported ESP32-P4 Revision (Rev v1.99) +# +CONFIG_ESP32P4_REV_MAX_FULL=199 +CONFIG_ESP_REV_MAX_FULL=199 +CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0 +CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL=99 + +# +# Maximum Supported ESP32-P4 eFuse Block Revision (eFuse Block Rev v0.99) +# +# end of Chip revision + +# +# MAC Config +# +CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES_ONE=y +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES=1 +CONFIG_ESP32P4_UNIVERSAL_MAC_ADDRESSES_ONE=y +CONFIG_ESP32P4_UNIVERSAL_MAC_ADDRESSES=1 +# CONFIG_ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC is not set +# end of MAC Config + +# +# Sleep Config +# +CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y +CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y +# CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU is not set +# CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND is not set +CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=0 +# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set +# CONFIG_ESP_SLEEP_DEBUG is not set +CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y +# end of Sleep Config + +# +# RTC Clock Config +# +CONFIG_RTC_CLK_SRC_INT_RC=y +# CONFIG_RTC_CLK_SRC_EXT_CRYS is not set +CONFIG_RTC_CLK_CAL_CYCLES=1024 +CONFIG_RTC_FAST_CLK_SRC_RC_FAST=y +# CONFIG_RTC_FAST_CLK_SRC_XTAL is not set +# end of RTC Clock Config + +# +# Peripheral Control +# +CONFIG_ESP_PERIPH_CTRL_FUNC_IN_IRAM=y +CONFIG_ESP_REGI2C_CTRL_FUNC_IN_IRAM=y +# end of Peripheral Control + +# +# ETM Configuration +# +# CONFIG_ETM_ENABLE_DEBUG_LOG is not set +# end of ETM Configuration + +# +# GDMA Configurations +# +CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y +CONFIG_GDMA_ISR_HANDLER_IN_IRAM=y +CONFIG_GDMA_OBJ_DRAM_SAFE=y +# CONFIG_GDMA_ENABLE_DEBUG_LOG is not set +# CONFIG_GDMA_ISR_IRAM_SAFE is not set +# end of GDMA Configurations + +# +# DW_GDMA Configurations +# +# CONFIG_DW_GDMA_ENABLE_DEBUG_LOG is not set +# end of DW_GDMA Configurations + +# +# 2D-DMA Configurations +# +# CONFIG_DMA2D_OPERATION_FUNC_IN_IRAM is not set +# CONFIG_DMA2D_ISR_IRAM_SAFE is not set +# end of 2D-DMA Configurations + +# +# Main XTAL Config +# +CONFIG_XTAL_FREQ_40=y +CONFIG_XTAL_FREQ=40 +# end of Main XTAL Config + +# +# DCDC Regulator Configurations +# +CONFIG_ESP_SLEEP_KEEP_DCDC_ALWAYS_ON=y +CONFIG_ESP_SLEEP_DCM_VSET_VAL_IN_SLEEP=14 +# end of DCDC Regulator Configurations + +# +# LDO Regulator Configurations +# +CONFIG_ESP_LDO_RESERVE_SPI_NOR_FLASH=y +CONFIG_ESP_LDO_CHAN_SPI_NOR_FLASH_DOMAIN=1 +CONFIG_ESP_LDO_VOLTAGE_SPI_NOR_FLASH_3300_MV=y +CONFIG_ESP_LDO_VOLTAGE_SPI_NOR_FLASH_DOMAIN=3300 +CONFIG_ESP_LDO_RESERVE_PSRAM=y +CONFIG_ESP_LDO_CHAN_PSRAM_DOMAIN=2 +CONFIG_ESP_LDO_VOLTAGE_PSRAM_1900_MV=y +CONFIG_ESP_LDO_VOLTAGE_PSRAM_DOMAIN=1900 +# end of LDO Regulator Configurations + +# +# Power Supplier +# + +# +# Brownout Detector +# +CONFIG_ESP_BROWNOUT_DET=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set +CONFIG_ESP_BROWNOUT_DET_LVL=7 +CONFIG_ESP_BROWNOUT_USE_INTR=y +# end of Brownout Detector + +# +# RTC Backup Battery +# +# CONFIG_ESP_VBAT_INIT_AUTO is not set +# CONFIG_ESP_VBAT_WAKEUP_CHIP_ON_VBAT_BROWNOUT is not set +# end of RTC Backup Battery +# end of Power Supplier + +CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y +CONFIG_ESP_INTR_IN_IRAM=y +# end of Hardware Settings + +# +# ESP-Driver:LCD Controller Configurations +# +# CONFIG_LCD_ENABLE_DEBUG_LOG is not set +# CONFIG_LCD_RGB_ISR_IRAM_SAFE is not set +# CONFIG_LCD_RGB_RESTART_IN_VSYNC is not set +# CONFIG_LCD_DSI_ISR_IRAM_SAFE is not set +# end of ESP-Driver:LCD Controller Configurations + +# +# ESP-MM: Memory Management Configurations +# +# CONFIG_ESP_MM_CACHE_MSYNC_C2M_CHUNKED_OPS is not set +# end of ESP-MM: Memory Management Configurations + +# +# ESP NETIF Adapter +# +CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 +# CONFIG_ESP_NETIF_PROVIDE_CUSTOM_IMPLEMENTATION is not set +CONFIG_ESP_NETIF_TCPIP_LWIP=y +# CONFIG_ESP_NETIF_LOOPBACK is not set +CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y +CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y +# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set +# CONFIG_ESP_NETIF_L2_TAP is not set +# CONFIG_ESP_NETIF_BRIDGE_EN is not set +# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set +# end of ESP NETIF Adapter + +# +# Partition API Configuration +# +# end of Partition API Configuration + +# +# PHY +# +# end of PHY + +# +# Power Management +# +CONFIG_PM_SLEEP_FUNC_IN_IRAM=y +# CONFIG_PM_ENABLE is not set +CONFIG_PM_SLP_IRAM_OPT=y +CONFIG_PM_SLP_DEFAULT_PARAMS_OPT=y +# CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP is not set +# end of Power Management + +# +# ESP PSRAM +# +CONFIG_SPIRAM=y + +# +# PSRAM config +# +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y +# CONFIG_SPIRAM_SPEED_80M is not set +# CONFIG_SPIRAM_SPEED_20M is not set +CONFIG_SPIRAM_SPEED=200 +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_SPIRAM_FLASH_LOAD_TO_PSRAM=y +# CONFIG_SPIRAM_ECC_ENABLE is not set +CONFIG_SPIRAM_BOOT_HW_INIT=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y +# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set +# CONFIG_SPIRAM_USE_MEMMAP is not set +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +CONFIG_SPIRAM_USE_MALLOC=y +# CONFIG_SPIRAM_MEMTEST is not set +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=4096 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=49152 +# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set +# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set +# end of PSRAM config +# end of ESP PSRAM + +# +# ESP Ringbuf +# +# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set +# end of ESP Ringbuf + +# +# ESP-ROM +# +CONFIG_ESP_ROM_PRINT_IN_IRAM=y +# end of ESP-ROM + +# +# ESP Security Specific +# +# end of ESP Security Specific + +# +# ESP System Settings +# +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_360=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=360 + +# +# Cache config +# +CONFIG_CACHE_L2_CACHE_128KB=y +# CONFIG_CACHE_L2_CACHE_256KB is not set +# CONFIG_CACHE_L2_CACHE_512KB is not set +CONFIG_CACHE_L2_CACHE_SIZE=0x20000 +CONFIG_CACHE_L2_CACHE_LINE_64B=y +# CONFIG_CACHE_L2_CACHE_LINE_128B is not set +CONFIG_CACHE_L2_CACHE_LINE_SIZE=64 +CONFIG_CACHE_L1_CACHE_LINE_SIZE=64 +# end of Cache config + +# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set +# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set +CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 +CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y +CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y +CONFIG_ESP_SYSTEM_NO_BACKTRACE=y +# CONFIG_ESP_SYSTEM_USE_EH_FRAME is not set +# CONFIG_ESP_SYSTEM_USE_FRAME_POINTER is not set + +# +# Memory protection +# +CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT=y +# CONFIG_ESP_SYSTEM_PMP_LP_CORE_RESERVE_MEM_EXECUTABLE is not set +# end of Memory protection + +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=10240 +CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y +# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set +# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set +CONFIG_ESP_MAIN_TASK_AFFINITY=0x0 +CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set +# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set +# CONFIG_ESP_CONSOLE_NONE is not set +# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set +CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y +CONFIG_ESP_CONSOLE_UART=y +CONFIG_ESP_CONSOLE_UART_NUM=0 +CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0 +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_INT_WDT_CHECK_CPU1=y +CONFIG_ESP_TASK_WDT_EN=y +CONFIG_ESP_TASK_WDT_INIT=y +# CONFIG_ESP_TASK_WDT_PANIC is not set +CONFIG_ESP_TASK_WDT_TIMEOUT_S=10 +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP_PANIC_HANDLER_IRAM is not set +# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP_DEBUG_OCDAWARE=y +CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y +CONFIG_ESP_SYSTEM_HW_STACK_GUARD=y +CONFIG_ESP_SYSTEM_HW_PC_RECORD=y +# end of ESP System Settings + +# +# IPC (Inter-Processor Call) +# +CONFIG_ESP_IPC_TASK_STACK_SIZE=1024 +CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y +CONFIG_ESP_IPC_ISR_ENABLE=y +# end of IPC (Inter-Processor Call) + +# +# ESP Timer (High Resolution Timer) +# +CONFIG_ESP_TIMER_IN_IRAM=y +# CONFIG_ESP_TIMER_PROFILING is not set +CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y +CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1 +# CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL is not set +CONFIG_ESP_TIMER_TASK_AFFINITY=0x0 +CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y +CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y +# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set +CONFIG_ESP_TIMER_IMPL_SYSTIMER=y +# end of ESP Timer (High Resolution Timer) + +# +# Wi-Fi +# +# CONFIG_ESP_HOST_WIFI_ENABLED is not set +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=16 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_TX_BUFFER_TYPE=0 +CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=16 +CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0 +CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5 +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP_WIFI_TX_BA_WIN=6 +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP_WIFI_RX_BA_WIN=16 +CONFIG_ESP_WIFI_NVS_ENABLED=y +CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP_WIFI_IRAM_OPT=y +CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y +CONFIG_ESP_WIFI_RX_IRAM_OPT=y +CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP_WIFI_ENABLE_SAE_PK=y +CONFIG_ESP_WIFI_ENABLE_SAE_H2E=y +CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y +CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_ESP_WIFI_SLP_IRAM_OPT=y +CONFIG_ESP_WIFI_SLP_DEFAULT_MIN_ACTIVE_TIME=50 +CONFIG_ESP_WIFI_BSS_MAX_IDLE_SUPPORT=y +CONFIG_ESP_WIFI_SLP_DEFAULT_MAX_ACTIVE_TIME=10 +CONFIG_ESP_WIFI_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 +CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y +CONFIG_ESP_WIFI_GMAC_SUPPORT=y +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y +CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7 +CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y +CONFIG_ESP_WIFI_MBEDTLS_TLS_CLIENT=y +CONFIG_ESP_WIFI_TX_HETB_QUEUE_NUM=3 +CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=y +# end of Wi-Fi + +# +# Core dump +# +# CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH is not set +# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set +CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y +# end of Core dump + +# +# FAT Filesystem support +# +CONFIG_FATFS_VOLUME_COUNT=2 +CONFIG_FATFS_LFN_NONE=y +# CONFIG_FATFS_LFN_HEAP is not set +# CONFIG_FATFS_LFN_STACK is not set +# CONFIG_FATFS_SECTOR_512 is not set +CONFIG_FATFS_SECTOR_4096=y +# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set +CONFIG_FATFS_CODEPAGE_437=y +# CONFIG_FATFS_CODEPAGE_720 is not set +# CONFIG_FATFS_CODEPAGE_737 is not set +# CONFIG_FATFS_CODEPAGE_771 is not set +# CONFIG_FATFS_CODEPAGE_775 is not set +# CONFIG_FATFS_CODEPAGE_850 is not set +# CONFIG_FATFS_CODEPAGE_852 is not set +# CONFIG_FATFS_CODEPAGE_855 is not set +# CONFIG_FATFS_CODEPAGE_857 is not set +# CONFIG_FATFS_CODEPAGE_860 is not set +# CONFIG_FATFS_CODEPAGE_861 is not set +# CONFIG_FATFS_CODEPAGE_862 is not set +# CONFIG_FATFS_CODEPAGE_863 is not set +# CONFIG_FATFS_CODEPAGE_864 is not set +# CONFIG_FATFS_CODEPAGE_865 is not set +# CONFIG_FATFS_CODEPAGE_866 is not set +# CONFIG_FATFS_CODEPAGE_869 is not set +# CONFIG_FATFS_CODEPAGE_932 is not set +# CONFIG_FATFS_CODEPAGE_936 is not set +# CONFIG_FATFS_CODEPAGE_949 is not set +# CONFIG_FATFS_CODEPAGE_950 is not set +CONFIG_FATFS_CODEPAGE=437 +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y +CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y +# CONFIG_FATFS_USE_FASTSEEK is not set +CONFIG_FATFS_USE_STRFUNC_NONE=y +# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set +# CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV is not set +CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0 +# CONFIG_FATFS_IMMEDIATE_FSYNC is not set +# CONFIG_FATFS_USE_LABEL is not set +CONFIG_FATFS_LINK_LOCK=y +# CONFIG_FATFS_USE_DYN_BUFFERS is not set + +# +# File system free space calculation behavior +# +CONFIG_FATFS_DONT_TRUST_FREE_CLUSTER_CNT=0 +CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0 +# end of File system free space calculation behavior +# end of FAT Filesystem support + +# +# FreeRTOS +# + +# +# Kernel +# +# CONFIG_FREERTOS_UNICORE is not set +CONFIG_FREERTOS_HZ=1000 +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 +# CONFIG_FREERTOS_USE_IDLE_HOOK is not set +# CONFIG_FREERTOS_USE_TICK_HOOK is not set +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set +CONFIG_FREERTOS_USE_TIMERS=y +CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc" +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set +CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY=y +CONFIG_FREERTOS_TIMER_SERVICE_TASK_CORE_AFFINITY=0x7FFFFFFF +CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y +# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set +CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y +CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U32=y +# CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U64 is not set +# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set +# end of Kernel + +# +# Port +# +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y +# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set +CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y +# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set +# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set +CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y +CONFIG_FREERTOS_ISR_STACKSIZE=1536 +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y +CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y +CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y +# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set +CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y +CONFIG_FREERTOS_RUN_TIME_STATS_USING_ESP_TIMER=y +# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set +# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set +# end of Port + +# +# Extra +# +CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y +# end of Extra + +CONFIG_FREERTOS_PORT=y +CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y +CONFIG_FREERTOS_DEBUG_OCDAWARE=y +CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y +CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y +CONFIG_FREERTOS_NUMBER_OF_CORES=2 +CONFIG_FREERTOS_IN_IRAM=y +# end of FreeRTOS + +# +# Hardware Abstraction Layer (HAL) and Low Level (LL) +# +CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y +# CONFIG_HAL_ASSERTION_DISABLE is not set +# CONFIG_HAL_ASSERTION_SILENT is not set +# CONFIG_HAL_ASSERTION_ENABLE is not set +CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 +CONFIG_HAL_SYSTIMER_USE_ROM_IMPL=y +CONFIG_HAL_WDT_USE_ROM_IMPL=y +# end of Hardware Abstraction Layer (HAL) and Low Level (LL) + +# +# Heap memory debugging +# +CONFIG_HEAP_POISONING_DISABLED=y +# CONFIG_HEAP_POISONING_LIGHT is not set +# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set +CONFIG_HEAP_TRACING_OFF=y +# CONFIG_HEAP_TRACING_STANDALONE is not set +# CONFIG_HEAP_TRACING_TOHOST is not set +# CONFIG_HEAP_USE_HOOKS is not set +# CONFIG_HEAP_TASK_TRACKING is not set +# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set +# CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set +# end of Heap memory debugging + +# +# Log +# +CONFIG_LOG_VERSION_1=y +# CONFIG_LOG_VERSION_2 is not set +CONFIG_LOG_VERSION=1 + +# +# Log Level +# +# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set +# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set +# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y +# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set +# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set +CONFIG_LOG_MAXIMUM_LEVEL=3 + +# +# Level Settings +# +# CONFIG_LOG_MASTER_LEVEL is not set +CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y +# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set +# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y +# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set +CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31 +# end of Level Settings +# end of Log Level + +# +# Format +# +# CONFIG_LOG_COLORS is not set +CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y +# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set +# end of Format + +# +# Settings +# +CONFIG_LOG_MODE_TEXT_EN=y +CONFIG_LOG_MODE_TEXT=y +# CONFIG_LOG_MODE_BINARY is not set +# end of Settings + +CONFIG_LOG_IN_IRAM=y +# end of Log + +# +# LWIP +# +CONFIG_LWIP_ENABLE=y +CONFIG_LWIP_LOCAL_HOSTNAME="espressif" +CONFIG_LWIP_TCPIP_TASK_PRIO=18 +# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set +# CONFIG_LWIP_CHECK_THREAD_SAFETY is not set +CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y +# CONFIG_LWIP_L2_TO_L3_COPY is not set +# CONFIG_LWIP_IRAM_OPTIMIZATION is not set +# CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION is not set +CONFIG_LWIP_TIMERS_ONDEMAND=y +CONFIG_LWIP_ND6=y +# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set +CONFIG_LWIP_MAX_SOCKETS=10 +# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set +# CONFIG_LWIP_SO_LINGER is not set +CONFIG_LWIP_SO_REUSE=y +CONFIG_LWIP_SO_REUSE_RXTOALL=y +# CONFIG_LWIP_SO_RCVBUF is not set +# CONFIG_LWIP_NETBUF_RECVINFO is not set +CONFIG_LWIP_IP_DEFAULT_TTL=64 +CONFIG_LWIP_IP4_FRAG=y +CONFIG_LWIP_IP6_FRAG=y +# CONFIG_LWIP_IP4_REASSEMBLY is not set +# CONFIG_LWIP_IP6_REASSEMBLY is not set +CONFIG_LWIP_IP_REASS_MAX_PBUFS=10 +# CONFIG_LWIP_IP_FORWARD is not set +# CONFIG_LWIP_STATS is not set +CONFIG_LWIP_ESP_GRATUITOUS_ARP=y +CONFIG_LWIP_GARP_TMR_INTERVAL=60 +CONFIG_LWIP_ESP_MLDV6_REPORT=y +CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set +# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set +# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set +CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y +# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set +CONFIG_LWIP_DHCP_OPTIONS_LEN=68 +CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0 +CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 + +# +# DHCP server +# +CONFIG_LWIP_DHCPS=y +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y +CONFIG_LWIP_DHCPS_ADD_DNS=y +# end of DHCP server + +# CONFIG_LWIP_AUTOIP is not set +CONFIG_LWIP_IPV4=y +CONFIG_LWIP_IPV6=y +# CONFIG_LWIP_IPV6_AUTOCONFIG is not set +CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 +# CONFIG_LWIP_IPV6_FORWARD is not set +# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +CONFIG_LWIP_MAX_LISTENING_TCP=16 +CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y +CONFIG_LWIP_TCP_MAXRTX=12 +CONFIG_LWIP_TCP_SYNMAXRTX=12 +CONFIG_LWIP_TCP_MSS=1440 +CONFIG_LWIP_TCP_TMR_INTERVAL=250 +CONFIG_LWIP_TCP_MSL=60000 +CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT=20000 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760 +CONFIG_LWIP_TCP_WND_DEFAULT=5760 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_ACCEPTMBOX_SIZE=6 +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +CONFIG_LWIP_TCP_OOSEQ_TIMEOUT=6 +CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=0 +# CONFIG_LWIP_TCP_SACK_OUT is not set +CONFIG_LWIP_TCP_OVERSIZE_MSS=y +# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set +# CONFIG_LWIP_WND_SCALE is not set +CONFIG_LWIP_TCP_RTO_TIME=1500 +# end of TCP + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +# end of UDP + +# +# Checksums +# +# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set +# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set +CONFIG_LWIP_CHECKSUM_CHECK_ICMP=y +# end of Checksums + +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF +CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 +CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 +CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5 +CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3 +CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10 +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_ENABLE_IPV4=y +CONFIG_LWIP_PPP_ENABLE_IPV6=y +# CONFIG_LWIP_PPP_NOTIFY_PHASE_SUPPORT is not set +# CONFIG_LWIP_PPP_PAP_SUPPORT is not set +# CONFIG_LWIP_PPP_CHAP_SUPPORT is not set +# CONFIG_LWIP_PPP_MSCHAP_SUPPORT is not set +# CONFIG_LWIP_PPP_MPPE_SUPPORT is not set +CONFIG_LWIP_PPP_SERVER_SUPPORT=y +CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=y +# CONFIG_LWIP_ENABLE_LCP_ECHO is not set +# CONFIG_LWIP_PPP_DEBUG_ON is not set +# CONFIG_LWIP_USE_EXTERNAL_MBEDTLS is not set +# CONFIG_LWIP_SLIP_SUPPORT is not set + +# +# ICMP +# +CONFIG_LWIP_ICMP=y +# CONFIG_LWIP_MULTICAST_PING is not set +# CONFIG_LWIP_BROADCAST_PING is not set +# end of ICMP + +# +# LWIP RAW API +# +CONFIG_LWIP_MAX_RAW_PCBS=16 +# end of LWIP RAW API + +# +# SNTP +# +CONFIG_LWIP_SNTP_MAX_SERVERS=1 +# CONFIG_LWIP_DHCP_GET_NTP_SRV is not set +CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000 +CONFIG_LWIP_SNTP_STARTUP_DELAY=y +CONFIG_LWIP_SNTP_MAXIMUM_STARTUP_DELAY=5000 +# end of SNTP + +# +# DNS +# +CONFIG_LWIP_DNS_MAX_HOST_IP=1 +CONFIG_LWIP_DNS_MAX_SERVERS=3 +# CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set +# CONFIG_LWIP_DNS_SETSERVER_WITH_NETIF is not set +# CONFIG_LWIP_USE_ESP_GETADDRINFO is not set +# end of DNS + +CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7 +CONFIG_LWIP_ESP_LWIP_ASSERT=y + +# +# Hooks +# +# CONFIG_LWIP_HOOK_TCP_ISN_NONE is not set +CONFIG_LWIP_HOOK_TCP_ISN_DEFAULT=y +# CONFIG_LWIP_HOOK_TCP_ISN_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_ROUTE_NONE=y +# CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_ROUTE_CUSTOM is not set +CONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y +# CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT is not set +# CONFIG_LWIP_HOOK_ND6_GET_GW_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_NONE=y +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM is not set +CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_NONE=y +# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_DEFAULT is not set +# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_CUSTOM is not set +CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set +CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_NONE=y +# CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_CUSTOM is not set +# CONFIG_LWIP_HOOK_IP6_INPUT_NONE is not set +CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y +# CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set +# end of Hooks + +# CONFIG_LWIP_DEBUG is not set +# end of LWIP + +# +# mbedTLS +# +# CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC is not set +CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y +# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set +# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set +CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 +CONFIG_MBEDTLS_DYNAMIC_BUFFER=y +# CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA is not set +# CONFIG_MBEDTLS_DEBUG is not set + +# +# mbedTLS v3.x related +# +# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set +# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set +# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set +# CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE is not set +CONFIG_MBEDTLS_PKCS7_C=y +# end of mbedTLS v3.x related + +# +# Certificate Bundle +# +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set +# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST is not set +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 +# end of Certificate Bundle + +# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set +CONFIG_MBEDTLS_CMAC_C=y +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_AES_USE_INTERRUPT=y +CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 +CONFIG_MBEDTLS_HARDWARE_GCM=y +CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y +CONFIG_MBEDTLS_HARDWARE_MPI=y +# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set +CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y +CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 +CONFIG_MBEDTLS_HARDWARE_SHA=y +CONFIG_MBEDTLS_HARDWARE_ECC=y +CONFIG_MBEDTLS_ECC_OTHER_CURVES_SOFT_FALLBACK=y +CONFIG_MBEDTLS_ROM_MD5=y +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set +CONFIG_MBEDTLS_HAVE_TIME=y +# CONFIG_MBEDTLS_PLATFORM_TIME_ALT is not set +# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set +CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y +CONFIG_MBEDTLS_SHA1_C=y +CONFIG_MBEDTLS_SHA512_C=y +# CONFIG_MBEDTLS_SHA3_C is not set +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set +# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set +# CONFIG_MBEDTLS_TLS_DISABLED is not set +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +# CONFIG_MBEDTLS_PSK_MODES is not set +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +# end of TLS Key Exchange Methods + +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set +# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y +CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +# CONFIG_MBEDTLS_CAMELLIA_C is not set +# CONFIG_MBEDTLS_DES_C is not set +# CONFIG_MBEDTLS_BLOWFISH_C is not set +# CONFIG_MBEDTLS_XTEA_C is not set +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +# CONFIG_MBEDTLS_NIST_KW_C is not set +# end of Symmetric Ciphers + +# CONFIG_MBEDTLS_RIPEMD160_C is not set + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +# end of Certificates + +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y +CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y +# CONFIG_MBEDTLS_DHM_C is not set +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +# CONFIG_MBEDTLS_ECJPAKE_C is not set +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y +# CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM is not set +# CONFIG_MBEDTLS_POLY1305_C is not set +# CONFIG_MBEDTLS_CHACHA20_C is not set +# CONFIG_MBEDTLS_HKDF_C is not set +# CONFIG_MBEDTLS_THREADING_C is not set +CONFIG_MBEDTLS_ERROR_STRINGS=y +CONFIG_MBEDTLS_FS_IO=y +# CONFIG_MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION is not set +# end of mbedTLS + +# +# ESP-MQTT Configurations +# +CONFIG_MQTT_PROTOCOL_311=y +# CONFIG_MQTT_PROTOCOL_5 is not set +CONFIG_MQTT_TRANSPORT_SSL=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y +# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set +# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set +# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set +# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set +# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set +# CONFIG_MQTT_CUSTOM_OUTBOX is not set +# end of ESP-MQTT Configurations + +# +# LibC +# +CONFIG_LIBC_NEWLIB=y +# CONFIG_LIBC_PICOLIBC is not set +CONFIG_LIBC_MISC_IN_IRAM=y +CONFIG_LIBC_LOCKS_PLACE_IN_IRAM=y +CONFIG_LIBC_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_LIBC_STDOUT_LINE_ENDING_LF is not set +# CONFIG_LIBC_STDOUT_LINE_ENDING_CR is not set +# CONFIG_LIBC_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_LIBC_STDIN_LINE_ENDING_LF is not set +CONFIG_LIBC_STDIN_LINE_ENDING_CR=y +# CONFIG_LIBC_NEWLIB_NANO_FORMAT is not set +CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y +# CONFIG_LIBC_TIME_SYSCALL_USE_RTC is not set +# CONFIG_LIBC_TIME_SYSCALL_USE_HRT is not set +# CONFIG_LIBC_TIME_SYSCALL_USE_NONE is not set +# CONFIG_LIBC_OPTIMIZED_MISALIGNED_ACCESS is not set +# end of LibC + +# +# NVS +# +# CONFIG_NVS_ENCRYPTION is not set +# CONFIG_NVS_ASSERT_ERROR_CHECK is not set +# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set +# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set +# end of NVS + +# +# OpenThread +# +# CONFIG_OPENTHREAD_ENABLED is not set + +# +# OpenThread Spinel +# +# CONFIG_OPENTHREAD_SPINEL_ONLY is not set +# end of OpenThread Spinel +# end of OpenThread + +# +# Protocomm +# +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y +# end of Protocomm + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_PTHREAD_STACK_MIN=768 +CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y +# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set +# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set +CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" +# end of PThreads + +# +# MMU Config +# +CONFIG_MMU_PAGE_SIZE_64KB=y +CONFIG_MMU_PAGE_MODE="64KB" +CONFIG_MMU_PAGE_SIZE=0x10000 +# end of MMU Config + +# +# Main Flash configuration +# + +# +# SPI Flash behavior when brownout +# +CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y +CONFIG_SPI_FLASH_BROWNOUT_RESET=y +# end of SPI Flash behavior when brownout + +# +# Optional and Experimental Features (READ DOCS FIRST) +# + +# +# Features here require specific hardware (READ DOCS FIRST!) +# +# CONFIG_SPI_FLASH_HPM_ENA is not set +CONFIG_SPI_FLASH_HPM_AUTO=y +# CONFIG_SPI_FLASH_HPM_DIS is not set +CONFIG_SPI_FLASH_HPM_ON=y +CONFIG_SPI_FLASH_HPM_DC_AUTO=y +# CONFIG_SPI_FLASH_HPM_DC_DISABLE is not set +# CONFIG_SPI_FLASH_AUTO_SUSPEND is not set +CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 +# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set +# CONFIG_SPI_FLASH_FORCE_ENABLE_C6_H2_SUSPEND is not set +CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM=y +# end of Optional and Experimental Features (READ DOCS FIRST) +# end of Main Flash configuration + +# +# SPI Flash driver +# +# CONFIG_SPI_FLASH_VERIFY_WRITE is not set +# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set +# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set +CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y +CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 +CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 +CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 +# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set +# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set +# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set + +# +# Auto-detect flash chips +# +CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORTED=y +# CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP is not set +# CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP is not set +# CONFIG_SPI_FLASH_SUPPORT_GD_CHIP is not set +# CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP is not set +# CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP is not set +# CONFIG_SPI_FLASH_SUPPORT_TH_CHIP is not set +# end of Auto-detect flash chips + +CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y +# end of SPI Flash driver + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +# CONFIG_SPIFFS_CACHE_STATS is not set +# end of SPIFFS Cache Configuration + +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +# CONFIG_SPIFFS_GC_STATS is not set +CONFIG_SPIFFS_PAGE_SIZE=256 +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y +CONFIG_SPIFFS_META_LENGTH=4 +CONFIG_SPIFFS_USE_MTIME=y + +# +# Debug Configuration +# +# CONFIG_SPIFFS_DBG is not set +# CONFIG_SPIFFS_API_DBG is not set +# CONFIG_SPIFFS_GC_DBG is not set +# CONFIG_SPIFFS_CACHE_DBG is not set +# CONFIG_SPIFFS_CHECK_DBG is not set +# CONFIG_SPIFFS_TEST_VISUALISATION is not set +# end of Debug Configuration +# end of SPIFFS Configuration + +# +# TCP Transport +# + +# +# Websocket +# +CONFIG_WS_TRANSPORT=y +CONFIG_WS_BUFFER_SIZE=1024 +# CONFIG_WS_DYNAMIC_BUFFER is not set +# end of Websocket +# end of TCP Transport + +# +# Ultra Low Power (ULP) Co-processor +# +# CONFIG_ULP_COPROC_ENABLED is not set + +# +# ULP Debugging Options +# +# end of ULP Debugging Options +# end of Ultra Low Power (ULP) Co-processor + +# +# Unity unit testing library +# +CONFIG_UNITY_ENABLE_FLOAT=y +CONFIG_UNITY_ENABLE_DOUBLE=y +# CONFIG_UNITY_ENABLE_64BIT is not set +# CONFIG_UNITY_ENABLE_COLOR is not set +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y +# CONFIG_UNITY_ENABLE_FIXTURE is not set +# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set +# CONFIG_UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE is not set +# end of Unity unit testing library + +# +# USB-OTG +# +CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=256 +CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED=y +# CONFIG_USB_HOST_HW_BUFFER_BIAS_IN is not set +# CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT is not set + +# +# Hub Driver Configuration +# + +# +# Root Port configuration +# +CONFIG_USB_HOST_DEBOUNCE_DELAY_MS=250 +CONFIG_USB_HOST_RESET_HOLD_MS=30 +CONFIG_USB_HOST_RESET_RECOVERY_MS=30 +CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS=10 +# end of Root Port configuration + +# CONFIG_USB_HOST_HUBS_SUPPORTED is not set +# end of Hub Driver Configuration + +# CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK is not set +# CONFIG_USB_HOST_DWC_DMA_CAP_MEMORY_IN_PSRAM is not set +CONFIG_USB_OTG_SUPPORTED=y +# end of USB-OTG + +# +# Virtual file system +# +CONFIG_VFS_SUPPORT_IO=y +CONFIG_VFS_SUPPORT_DIR=y +CONFIG_VFS_SUPPORT_SELECT=y +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_VFS_SELECT_IN_RAM=y +CONFIG_VFS_SUPPORT_TERMIOS=y +CONFIG_VFS_MAX_COUNT=8 + +# +# Host File System I/O (Semihosting) +# +CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# end of Host File System I/O (Semihosting) + +CONFIG_VFS_INITIALIZE_DEV_NULL=y +# end of Virtual file system + +# +# Wear Levelling +# +# CONFIG_WL_SECTOR_SIZE_512 is not set +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 +# end of Wear Levelling + +# +# Wi-Fi Provisioning Manager +# +CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 +CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 +CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y +# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set +# end of Wi-Fi Provisioning Manager + +# +# Animation Player Configuration +# +CONFIG_ANIM_PLAYER_DEFAULT_FPS=30 +# end of Animation Player Configuration + +# +# ADC Mic +# +CONFIG_ADC_MIC_APPLY_GAIN=3 +CONFIG_ADC_MIC_OFFSET=16380 +# end of ADC Mic + +# +# IoT Button +# +CONFIG_BUTTON_PERIOD_TIME_MS=5 +CONFIG_BUTTON_DEBOUNCE_TICKS=2 +CONFIG_BUTTON_SHORT_PRESS_TIME_MS=180 +CONFIG_BUTTON_LONG_PRESS_TIME_MS=1500 +CONFIG_BUTTON_LONG_PRESS_HOLD_SERIAL_TIME_MS=20 +CONFIG_ADC_BUTTON_MAX_CHANNEL=3 +CONFIG_ADC_BUTTON_MAX_BUTTON_PER_CHANNEL=8 +CONFIG_ADC_BUTTON_SAMPLE_TIMES=1 +# end of IoT Button + +# +# CMake Utilities +# +# CONFIG_CU_RELINKER_ENABLE is not set +# CONFIG_CU_DIAGNOSTICS_COLOR_NEVER is not set +CONFIG_CU_DIAGNOSTICS_COLOR_ALWAYS=y +# CONFIG_CU_DIAGNOSTICS_COLOR_AUTO is not set +# CONFIG_CU_GCC_LTO_ENABLE is not set +# CONFIG_CU_GCC_STRING_1BYTE_ALIGN is not set +# end of CMake Utilities + +# +# eppp_link +# +CONFIG_EPPP_LINK_USES_LWIP=y +CONFIG_EPPP_LINK_DEVICE_UART=y +# CONFIG_EPPP_LINK_DEVICE_SPI is not set +# CONFIG_EPPP_LINK_DEVICE_SDIO is not set +CONFIG_EPPP_LINK_CONN_MAX_RETRY=6 +# end of eppp_link + +# +# DSP Library +# +CONFIG_DSP_OPTIMIZATIONS_SUPPORTED=y +# CONFIG_DSP_ANSI is not set +CONFIG_DSP_OPTIMIZED=y +CONFIG_DSP_OPTIMIZATION=1 +# CONFIG_DSP_MAX_FFT_SIZE_512 is not set +# CONFIG_DSP_MAX_FFT_SIZE_1024 is not set +# CONFIG_DSP_MAX_FFT_SIZE_2048 is not set +CONFIG_DSP_MAX_FFT_SIZE_4096=y +# CONFIG_DSP_MAX_FFT_SIZE_8192 is not set +# CONFIG_DSP_MAX_FFT_SIZE_16384 is not set +# CONFIG_DSP_MAX_FFT_SIZE_32768 is not set +CONFIG_DSP_MAX_FFT_SIZE=4096 +# end of DSP Library + +# +# Camera configuration +# +CONFIG_OV7670_SUPPORT=y +CONFIG_OV7725_SUPPORT=y +CONFIG_NT99141_SUPPORT=y +CONFIG_OV2640_SUPPORT=y +CONFIG_OV3660_SUPPORT=y +CONFIG_OV5640_SUPPORT=y +CONFIG_GC2145_SUPPORT=y +CONFIG_GC032A_SUPPORT=y +CONFIG_GC0308_SUPPORT=y +CONFIG_BF3005_SUPPORT=y +CONFIG_BF20A6_SUPPORT=y +# CONFIG_SC101IOT_SUPPORT is not set +CONFIG_SC030IOT_SUPPORT=y +# CONFIG_SC031GS_SUPPORT is not set +CONFIG_MEGA_CCM_SUPPORT=y +# CONFIG_SCCB_HARDWARE_I2C_PORT0 is not set +CONFIG_SCCB_HARDWARE_I2C_PORT1=y +CONFIG_SCCB_CLK_FREQ=100000 +# CONFIG_GC_SENSOR_WINDOWING_MODE is not set +CONFIG_GC_SENSOR_SUBSAMPLE_MODE=y +CONFIG_CAMERA_TASK_STACK_SIZE=2048 +CONFIG_CAMERA_CORE0=y +# CONFIG_CAMERA_CORE1 is not set +# CONFIG_CAMERA_NO_AFFINITY is not set +CONFIG_CAMERA_DMA_BUFFER_SIZE_MAX=32768 +CONFIG_CAMERA_JPEG_MODE_FRAME_SIZE_AUTO=y +# CONFIG_CAMERA_JPEG_MODE_FRAME_SIZE_CUSTOM is not set +# end of Camera configuration + +# +# Audio Codec Device Configuration +# +# CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE is not set +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_ES7243_SUPPORT=y +CONFIG_CODEC_ES7243E_SUPPORT=y +CONFIG_CODEC_ES8156_SUPPORT=y +CONFIG_CODEC_AW88298_SUPPORT=y +CONFIG_CODEC_ES8374_SUPPORT=y +CONFIG_CODEC_ES8388_SUPPORT=y +CONFIG_CODEC_TAS5805M_SUPPORT=y +# CONFIG_CODEC_ZL38063_SUPPORT is not set +# end of Audio Codec Device Configuration + +CONFIG_ESP_HOSTED_ENABLED=y + +# +# ESP-Hosted config +# + +# +# ESP32-C6 is Slave Target from Wi-Fi Remote Component +# +CONFIG_ESP_HOSTED_PRIV_SDIO_OPTION=y +CONFIG_ESP_HOSTED_PRIV_SPI_HD_OPTION=y +# CONFIG_ESP_HOSTED_SPI_HOST_INTERFACE is not set +CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE=y +# CONFIG_ESP_HOSTED_SPI_HD_HOST_INTERFACE is not set +# CONFIG_ESP_HOSTED_UART_HOST_INTERFACE is not set +CONFIG_ESP_HOSTED_IDF_SLAVE_TARGET="esp32c6" + +# +# Hosted SDIO Configuration +# +CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_HIGH=y +# CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_LOW is not set +# CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_NONE is not set +# CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_MAX_SIZE is not set +CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_STREAMING_MODE=y +# CONFIG_ESP_HOSTED_SDIO_SLOT_0 is not set +CONFIG_ESP_HOSTED_SDIO_SLOT_1=y +CONFIG_ESP_HOSTED_SDIO_SLOT=1 +# CONFIG_ESP_HOSTED_SD_PWR_CTRL_LDO_INTERNAL_IO is not set +CONFIG_ESP_HOSTED_SDIO_4_BIT_BUS=y +# CONFIG_ESP_HOSTED_SDIO_1_BIT_BUS is not set +CONFIG_ESP_HOSTED_SDIO_BUS_WIDTH=4 +CONFIG_ESP_HOSTED_SDIO_CLOCK_FREQ_KHZ=40000 +CONFIG_ESP_HOSTED_CUSTOM_SDIO_PINS=y +CONFIG_ESP_HOSTED_SDIO_CMD_GPIO_RANGE_MIN=0 +CONFIG_ESP_HOSTED_SDIO_CMD_GPIO_RANGE_MAX=100 +CONFIG_ESP_HOSTED_SDIO_CLK_GPIO_RANGE_MIN=0 +CONFIG_ESP_HOSTED_SDIO_CLK_GPIO_RANGE_MAX=100 +CONFIG_ESP_HOSTED_SDIO_D0_GPIO_RANGE_MIN=0 +CONFIG_ESP_HOSTED_SDIO_D0_GPIO_RANGE_MAX=100 +CONFIG_ESP_HOSTED_SDIO_D1_GPIO_RANGE_MIN=0 +CONFIG_ESP_HOSTED_SDIO_D1_GPIO_RANGE_MAX=100 +CONFIG_ESP_HOSTED_SDIO_D2_GPIO_RANGE_MIN=0 +CONFIG_ESP_HOSTED_SDIO_D2_GPIO_RANGE_MAX=100 +CONFIG_ESP_HOSTED_SDIO_D3_GPIO_RANGE_MIN=0 +CONFIG_ESP_HOSTED_SDIO_D3_GPIO_RANGE_MAX=100 +CONFIG_ESP_HOSTED_SDIO_RESET_SLAVE_GPIO_MIN=0 +CONFIG_ESP_HOSTED_SDIO_RESET_SLAVE_GPIO_MAX=100 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_1=13 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_1=12 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_1=11 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_1=10 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_1=9 +CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=8 +CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=54 +CONFIG_ESP_HOSTED_SDIO_PIN_CMD=13 +CONFIG_ESP_HOSTED_SDIO_PIN_CLK=12 +CONFIG_ESP_HOSTED_SDIO_PIN_D0=11 +CONFIG_ESP_HOSTED_SDIO_PRIV_PIN_D1_4BIT_BUS=10 +CONFIG_ESP_HOSTED_SDIO_PIN_D2=9 +CONFIG_ESP_HOSTED_SDIO_PIN_D3=8 +CONFIG_ESP_HOSTED_SDIO_PIN_D1=10 +CONFIG_ESP_HOSTED_SDIO_TX_Q_SIZE=20 +CONFIG_ESP_HOSTED_SDIO_RX_Q_SIZE=20 +# CONFIG_ESP_HOSTED_SDIO_CHECKSUM is not set +# end of Hosted SDIO Configuration + +CONFIG_ESP_HOSTED_GPIO_SLAVE_RESET_SLAVE=54 + +# +# Bluetooth Support +# + +# +# Following options must be set before this option can be enabled +# + +# +# 'Component config->Bluetooth' must be enabled +# +# end of Bluetooth Support + +# +# Task defaults +# +CONFIG_ESP_HOSTED_RPC_TASK_STACK=4096 +CONFIG_ESP_HOSTED_DFLT_TASK_STACK=3072 +# end of Task defaults + +CONFIG_ESP_HOSTED_USE_MEMPOOL=y +CONFIG_ESP_HOSTED_MAX_SIMULTANEOUS_SYNC_RPC_REQUESTS=5 +CONFIG_ESP_HOSTED_MAX_SIMULTANEOUS_ASYNC_RPC_REQUESTS=5 + +# +# Debug Settings +# +# CONFIG_ESP_HOSTED_RAW_THROUGHPUT_TRANSPORT is not set +# CONFIG_ESP_HOSTED_PKT_STATS is not set +# end of Debug Settings + +# +# Data path options +# +CONFIG_ESP_HOSTED_HOST_TO_ESP_WIFI_DATA_THROTTLE=y +CONFIG_ESP_HOSTED_PRIV_WIFI_TX_SDIO_HIGH_THRESHOLD=80 +CONFIG_ESP_HOSTED_TO_WIFI_DATA_THROTTLE_HIGH_THRESHOLD=80 +CONFIG_ESP_HOSTED_TO_WIFI_DATA_THROTTLE_LOW_THRESHOLD=60 +# end of Data path options + +# CONFIG_ESP_HOSTED_DECODE_WIFI_RESERVED_FIELD is not set +# end of ESP-Hosted config + +# +# ESP LCD TOUCH +# +CONFIG_ESP_LCD_TOUCH_MAX_POINTS=5 +CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS=1 +# end of ESP LCD TOUCH + +# +# ESP LVGL PORT +# +# CONFIG_LVGL_PORT_ENABLE_PPA is not set +# end of ESP LVGL PORT + +# +# mmap file support format +# +CONFIG_MMAP_FILE_NAME_LENGTH=16 +# end of mmap file support format + +# +# Wi-Fi Remote +# +CONFIG_ESP_WIFI_REMOTE_ENABLED=y +# CONFIG_SLAVE_IDF_TARGET_ESP32 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32S2 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32C3 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32S3 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32C2 is not set +CONFIG_SLAVE_IDF_TARGET_ESP32C6=y +# CONFIG_SLAVE_IDF_TARGET_ESP32C5 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32C61 is not set +CONFIG_SLAVE_SOC_WIFI_SUPPORTED=y +CONFIG_SLAVE_SOC_WIFI_WAPI_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_CSI_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_MESH_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 +CONFIG_SLAVE_SOC_WIFI_HW_TSF=y +CONFIG_SLAVE_SOC_WIFI_FTM_SUPPORT=y +CONFIG_SLAVE_FREERTOS_UNICORE=y +CONFIG_SLAVE_SOC_WIFI_GCMP_SUPPORT=y +CONFIG_SLAVE_IDF_TARGET_ARCH_RISCV=y +CONFIG_SLAVE_SOC_WIFI_HE_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_MAC_VERSION_NUM=2 +CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y +# CONFIG_ESP_WIFI_REMOTE_LIBRARY_EPPP is not set + +# +# Wi-Fi configuration +# +CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=16 +CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_WIFI_RMT_STATIC_TX_BUFFER=y +CONFIG_WIFI_RMT_TX_BUFFER_TYPE=0 +CONFIG_WIFI_RMT_STATIC_TX_BUFFER_NUM=16 +CONFIG_WIFI_RMT_CACHE_TX_BUFFER_NUM=32 +CONFIG_WIFI_RMT_STATIC_RX_MGMT_BUFFER=y +# CONFIG_WIFI_RMT_DYNAMIC_RX_MGMT_BUFFER is not set +CONFIG_WIFI_RMT_DYNAMIC_RX_MGMT_BUF=0 +CONFIG_WIFI_RMT_RX_MGMT_BUF_NUM_DEF=5 +# CONFIG_WIFI_RMT_CSI_ENABLED is not set +CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y +CONFIG_WIFI_RMT_TX_BA_WIN=6 +CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y +CONFIG_WIFI_RMT_RX_BA_WIN=16 +# CONFIG_WIFI_RMT_AMSDU_TX_ENABLED is not set +CONFIG_WIFI_RMT_NVS_ENABLED=y +CONFIG_WIFI_RMT_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_WIFI_RMT_MGMT_SBUF_NUM=32 +CONFIG_WIFI_RMT_IRAM_OPT=y +CONFIG_WIFI_RMT_EXTRA_IRAM_OPT=y +CONFIG_WIFI_RMT_RX_IRAM_OPT=y +CONFIG_WIFI_RMT_ENABLE_WPA3_SAE=y +CONFIG_WIFI_RMT_ENABLE_SAE_PK=y +CONFIG_WIFI_RMT_ENABLE_SAE_H2E=y +CONFIG_WIFI_RMT_SOFTAP_SAE_SUPPORT=y +CONFIG_WIFI_RMT_ENABLE_WPA3_OWE_STA=y +CONFIG_WIFI_RMT_SLP_IRAM_OPT=y +CONFIG_WIFI_RMT_SLP_DEFAULT_MIN_ACTIVE_TIME=50 +CONFIG_WIFI_RMT_BSS_MAX_IDLE_SUPPORT=y +CONFIG_WIFI_RMT_SLP_DEFAULT_MAX_ACTIVE_TIME=10 +CONFIG_WIFI_RMT_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 +# CONFIG_WIFI_RMT_FTM_ENABLE is not set +CONFIG_WIFI_RMT_STA_DISCONNECTED_PM_ENABLE=y +# CONFIG_WIFI_RMT_GCMP_SUPPORT is not set +CONFIG_WIFI_RMT_GMAC_SUPPORT=y +CONFIG_WIFI_RMT_SOFTAP_SUPPORT=y +# CONFIG_WIFI_RMT_SLP_BEACON_LOST_OPT is not set +CONFIG_WIFI_RMT_ESPNOW_MAX_ENCRYPT_NUM=7 +CONFIG_WIFI_RMT_MBEDTLS_CRYPTO=y +CONFIG_WIFI_RMT_MBEDTLS_TLS_CLIENT=y +# CONFIG_WIFI_RMT_EAP_TLS1_3 is not set +# CONFIG_WIFI_RMT_WAPI_PSK is not set +# CONFIG_WIFI_RMT_SUITE_B_192 is not set +# CONFIG_WIFI_RMT_11KV_SUPPORT is not set +# CONFIG_WIFI_RMT_MBO_SUPPORT is not set +# CONFIG_WIFI_RMT_ENABLE_ROAMING_APP is not set +# CONFIG_WIFI_RMT_DPP_SUPPORT is not set +# CONFIG_WIFI_RMT_11R_SUPPORT is not set +# CONFIG_WIFI_RMT_WPS_SOFTAP_REGISTRAR is not set +# CONFIG_WIFI_RMT_ENABLE_WIFI_TX_STATS is not set +# CONFIG_WIFI_RMT_ENABLE_WIFI_RX_STATS is not set +CONFIG_WIFI_RMT_TX_HETB_QUEUE_NUM=3 + +# +# WPS Configuration Options +# +# CONFIG_WIFI_RMT_WPS_STRICT is not set +# CONFIG_WIFI_RMT_WPS_PASSPHRASE is not set +# end of WPS Configuration Options + +# CONFIG_WIFI_RMT_DEBUG_PRINT is not set +# CONFIG_WIFI_RMT_TESTING_OPTIONS is not set +CONFIG_WIFI_RMT_ENTERPRISE_SUPPORT=y +# CONFIG_WIFI_RMT_ENT_FREE_DYNAMIC_BUFFER is not set +# end of Wi-Fi configuration +# end of Wi-Fi Remote + +# +# Bus Options +# + +# +# I2C Bus Options +# +CONFIG_I2C_BUS_DYNAMIC_CONFIG=y +CONFIG_I2C_MS_TO_WAIT=200 +# CONFIG_I2C_BUS_BACKWARD_CONFIG is not set +# CONFIG_I2C_BUS_SUPPORT_SOFTWARE is not set +# CONFIG_I2C_BUS_REMOVE_NULL_MEM_ADDR is not set +# end of I2C Bus Options +# end of Bus Options + +# +# IOT Knob +# +CONFIG_KNOB_PERIOD_TIME_MS=3 +CONFIG_KNOB_DEBOUNCE_TICKS=2 +CONFIG_KNOB_HIGH_LIMIT=1000 +CONFIG_KNOB_LOW_LIMIT=-1000 +# end of IOT Knob + +# +# LVGL configuration +# +CONFIG_LV_CONF_SKIP=y +# CONFIG_LV_CONF_MINIMAL is not set + +# +# Color Settings +# +# CONFIG_LV_COLOR_DEPTH_32 is not set +# CONFIG_LV_COLOR_DEPTH_24 is not set +CONFIG_LV_COLOR_DEPTH_16=y +# CONFIG_LV_COLOR_DEPTH_8 is not set +# CONFIG_LV_COLOR_DEPTH_1 is not set +CONFIG_LV_COLOR_DEPTH=16 +# end of Color Settings + +# +# Memory Settings +# +# CONFIG_LV_USE_BUILTIN_MALLOC is not set +CONFIG_LV_USE_CLIB_MALLOC=y +# CONFIG_LV_USE_MICROPYTHON_MALLOC is not set +# CONFIG_LV_USE_RTTHREAD_MALLOC is not set +# CONFIG_LV_USE_CUSTOM_MALLOC is not set +# CONFIG_LV_USE_BUILTIN_STRING is not set +CONFIG_LV_USE_CLIB_STRING=y +# CONFIG_LV_USE_CUSTOM_STRING is not set +# CONFIG_LV_USE_BUILTIN_SPRINTF is not set +CONFIG_LV_USE_CLIB_SPRINTF=y +# CONFIG_LV_USE_CUSTOM_SPRINTF is not set +# end of Memory Settings + +# +# HAL Settings +# +CONFIG_LV_DEF_REFR_PERIOD=33 +CONFIG_LV_DPI_DEF=130 +# end of HAL Settings + +# +# Operating System (OS) +# +CONFIG_LV_OS_NONE=y +# CONFIG_LV_OS_PTHREAD is not set +# CONFIG_LV_OS_FREERTOS is not set +# CONFIG_LV_OS_CMSIS_RTOS2 is not set +# CONFIG_LV_OS_RTTHREAD is not set +# CONFIG_LV_OS_WINDOWS is not set +# CONFIG_LV_OS_MQX is not set +# CONFIG_LV_OS_CUSTOM is not set +CONFIG_LV_USE_OS=0 +# end of Operating System (OS) + +# +# Rendering Configuration +# +CONFIG_LV_DRAW_BUF_STRIDE_ALIGN=1 +CONFIG_LV_DRAW_BUF_ALIGN=4 +CONFIG_LV_DRAW_LAYER_SIMPLE_BUF_SIZE=24576 +CONFIG_LV_USE_DRAW_SW=y +CONFIG_LV_DRAW_SW_SUPPORT_RGB565=y +CONFIG_LV_DRAW_SW_SUPPORT_RGB565A8=y +CONFIG_LV_DRAW_SW_SUPPORT_RGB888=y +CONFIG_LV_DRAW_SW_SUPPORT_XRGB8888=y +CONFIG_LV_DRAW_SW_SUPPORT_ARGB8888=y +CONFIG_LV_DRAW_SW_SUPPORT_L8=y +CONFIG_LV_DRAW_SW_SUPPORT_AL88=y +CONFIG_LV_DRAW_SW_SUPPORT_A8=y +CONFIG_LV_DRAW_SW_SUPPORT_I1=y +CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=1 +# CONFIG_LV_USE_DRAW_ARM2D_SYNC is not set +# CONFIG_LV_USE_NATIVE_HELIUM_ASM is not set +CONFIG_LV_DRAW_SW_COMPLEX=y +# CONFIG_LV_USE_DRAW_SW_COMPLEX_GRADIENTS is not set +CONFIG_LV_DRAW_SW_SHADOW_CACHE_SIZE=0 +CONFIG_LV_DRAW_SW_CIRCLE_CACHE_SIZE=4 +CONFIG_LV_DRAW_SW_ASM_NONE=y +# CONFIG_LV_DRAW_SW_ASM_NEON is not set +# CONFIG_LV_DRAW_SW_ASM_HELIUM is not set +# CONFIG_LV_DRAW_SW_ASM_CUSTOM is not set +CONFIG_LV_USE_DRAW_SW_ASM=0 +# CONFIG_LV_USE_DRAW_VGLITE is not set +# CONFIG_LV_USE_PXP is not set +# CONFIG_LV_USE_DRAW_DAVE2D is not set +# CONFIG_LV_USE_DRAW_SDL is not set +# CONFIG_LV_USE_DRAW_VG_LITE is not set +# CONFIG_LV_USE_VECTOR_GRAPHIC is not set +# end of Rendering Configuration + +# +# Feature Configuration +# + +# +# Logging +# +# CONFIG_LV_USE_LOG is not set +# end of Logging + +# +# Asserts +# +CONFIG_LV_USE_ASSERT_NULL=y +CONFIG_LV_USE_ASSERT_MALLOC=y +# CONFIG_LV_USE_ASSERT_STYLE is not set +# CONFIG_LV_USE_ASSERT_MEM_INTEGRITY is not set +# CONFIG_LV_USE_ASSERT_OBJ is not set +CONFIG_LV_ASSERT_HANDLER_INCLUDE="assert.h" +# end of Asserts + +# +# Debug +# +# CONFIG_LV_USE_REFR_DEBUG is not set +# CONFIG_LV_USE_LAYER_DEBUG is not set +# CONFIG_LV_USE_PARALLEL_DRAW_DEBUG is not set +# end of Debug + +# +# Others +# +# CONFIG_LV_ENABLE_GLOBAL_CUSTOM is not set +CONFIG_LV_CACHE_DEF_SIZE=0 +CONFIG_LV_IMAGE_HEADER_CACHE_DEF_CNT=0 +CONFIG_LV_GRADIENT_MAX_STOPS=2 +CONFIG_LV_COLOR_MIX_ROUND_OFS=128 +# CONFIG_LV_OBJ_STYLE_CACHE is not set +# CONFIG_LV_USE_OBJ_ID is not set +# CONFIG_LV_USE_OBJ_PROPERTY is not set +# end of Others +# end of Feature Configuration + +# +# Compiler Settings +# +# CONFIG_LV_BIG_ENDIAN_SYSTEM is not set +CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1 +# CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM is not set +# CONFIG_LV_USE_FLOAT is not set +# CONFIG_LV_USE_MATRIX is not set +# CONFIG_LV_USE_PRIVATE_API is not set +# end of Compiler Settings + +# +# Font Usage +# + +# +# Enable built-in fonts +# +# CONFIG_LV_FONT_MONTSERRAT_8 is not set +# CONFIG_LV_FONT_MONTSERRAT_10 is not set +# CONFIG_LV_FONT_MONTSERRAT_12 is not set +CONFIG_LV_FONT_MONTSERRAT_14=y +# CONFIG_LV_FONT_MONTSERRAT_16 is not set +# CONFIG_LV_FONT_MONTSERRAT_18 is not set +# CONFIG_LV_FONT_MONTSERRAT_20 is not set +# CONFIG_LV_FONT_MONTSERRAT_22 is not set +# CONFIG_LV_FONT_MONTSERRAT_24 is not set +# CONFIG_LV_FONT_MONTSERRAT_26 is not set +# CONFIG_LV_FONT_MONTSERRAT_28 is not set +# CONFIG_LV_FONT_MONTSERRAT_30 is not set +# CONFIG_LV_FONT_MONTSERRAT_32 is not set +# CONFIG_LV_FONT_MONTSERRAT_34 is not set +# CONFIG_LV_FONT_MONTSERRAT_36 is not set +# CONFIG_LV_FONT_MONTSERRAT_38 is not set +# CONFIG_LV_FONT_MONTSERRAT_40 is not set +# CONFIG_LV_FONT_MONTSERRAT_42 is not set +# CONFIG_LV_FONT_MONTSERRAT_44 is not set +# CONFIG_LV_FONT_MONTSERRAT_46 is not set +# CONFIG_LV_FONT_MONTSERRAT_48 is not set +# CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set +# CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set +# CONFIG_LV_FONT_SIMSUN_14_CJK is not set +# CONFIG_LV_FONT_SIMSUN_16_CJK is not set +# CONFIG_LV_FONT_UNSCII_8 is not set +# CONFIG_LV_FONT_UNSCII_16 is not set +# end of Enable built-in fonts + +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_10 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set +CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_22 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_24 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_26 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_30 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_32 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_34 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_36 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_38 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_40 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_42 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_44 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_46 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_48 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set +# CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set +# CONFIG_LV_FONT_DEFAULT_SIMSUN_14_CJK is not set +# CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK is not set +# CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set +# CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set +CONFIG_LV_FONT_FMT_TXT_LARGE=y +CONFIG_LV_USE_FONT_COMPRESSED=y +CONFIG_LV_USE_FONT_PLACEHOLDER=y +# end of Font Usage + +# +# Text Settings +# +CONFIG_LV_TXT_ENC_UTF8=y +# CONFIG_LV_TXT_ENC_ASCII is not set +CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_)}" +CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0 +# CONFIG_LV_USE_BIDI is not set +# CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set +# end of Text Settings + +# +# Widget Usage +# +CONFIG_LV_WIDGETS_HAS_DEFAULT_VALUE=y +# CONFIG_LV_USE_ANIMIMG is not set +CONFIG_LV_USE_ARC=y +CONFIG_LV_USE_BAR=y +CONFIG_LV_USE_BUTTON=y +CONFIG_LV_USE_BUTTONMATRIX=y +# CONFIG_LV_USE_CALENDAR is not set +CONFIG_LV_USE_CANVAS=y +# CONFIG_LV_USE_CHART is not set +CONFIG_LV_USE_CHECKBOX=y +CONFIG_LV_USE_DROPDOWN=y +CONFIG_LV_USE_IMAGE=y +CONFIG_LV_USE_IMAGEBUTTON=y +# CONFIG_LV_USE_KEYBOARD is not set +CONFIG_LV_USE_LABEL=y +CONFIG_LV_LABEL_TEXT_SELECTION=y +CONFIG_LV_LABEL_LONG_TXT_HINT=y +CONFIG_LV_LABEL_WAIT_CHAR_COUNT=3 +# CONFIG_LV_USE_LED is not set +CONFIG_LV_USE_LINE=y +# CONFIG_LV_USE_LIST is not set +# CONFIG_LV_USE_MENU is not set +# CONFIG_LV_USE_MSGBOX is not set +CONFIG_LV_USE_ROLLER=y +CONFIG_LV_USE_SCALE=y +CONFIG_LV_USE_SLIDER=y +# CONFIG_LV_USE_SPAN is not set +# CONFIG_LV_USE_SPINBOX is not set +# CONFIG_LV_USE_SPINNER is not set +CONFIG_LV_USE_SWITCH=y +CONFIG_LV_USE_TEXTAREA=y +CONFIG_LV_TEXTAREA_DEF_PWD_SHOW_TIME=1500 +CONFIG_LV_USE_TABLE=y +# CONFIG_LV_USE_TABVIEW is not set +# CONFIG_LV_USE_TILEVIEW is not set +# CONFIG_LV_USE_WIN is not set +# end of Widget Usage + +# +# Themes +# +CONFIG_LV_USE_THEME_DEFAULT=y +# CONFIG_LV_THEME_DEFAULT_DARK is not set +CONFIG_LV_THEME_DEFAULT_GROW=y +CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=80 +CONFIG_LV_USE_THEME_SIMPLE=y +# CONFIG_LV_USE_THEME_MONO is not set +# end of Themes + +# +# Layouts +# +CONFIG_LV_USE_FLEX=y +CONFIG_LV_USE_GRID=y +# end of Layouts + +# +# 3rd Party Libraries +# +CONFIG_LV_FS_DEFAULT_DRIVE_LETTER=0 +# CONFIG_LV_USE_FS_STDIO is not set +# CONFIG_LV_USE_FS_POSIX is not set +# CONFIG_LV_USE_FS_WIN32 is not set +# CONFIG_LV_USE_FS_FATFS is not set +# CONFIG_LV_USE_FS_MEMFS is not set +# CONFIG_LV_USE_FS_LITTLEFS is not set +# CONFIG_LV_USE_FS_ARDUINO_ESP_LITTLEFS is not set +# CONFIG_LV_USE_FS_ARDUINO_SD is not set +# CONFIG_LV_USE_LODEPNG is not set +# CONFIG_LV_USE_LIBPNG is not set +# CONFIG_LV_USE_BMP is not set +# CONFIG_LV_USE_TJPGD is not set +# CONFIG_LV_USE_LIBJPEG_TURBO is not set +# CONFIG_LV_USE_GIF is not set +# CONFIG_LV_BIN_DECODER_RAM_LOAD is not set +# CONFIG_LV_USE_RLE is not set +# CONFIG_LV_USE_QRCODE is not set +# CONFIG_LV_USE_BARCODE is not set +# CONFIG_LV_USE_FREETYPE is not set +# CONFIG_LV_USE_TINY_TTF is not set +# CONFIG_LV_USE_RLOTTIE is not set +# CONFIG_LV_USE_THORVG is not set +# CONFIG_LV_USE_LZ4 is not set +# CONFIG_LV_USE_FFMPEG is not set +# end of 3rd Party Libraries + +# +# Others +# +# CONFIG_LV_USE_SNAPSHOT is not set +# CONFIG_LV_USE_SYSMON is not set +# CONFIG_LV_USE_PROFILER is not set +# CONFIG_LV_USE_MONKEY is not set +# CONFIG_LV_USE_GRIDNAV is not set +# CONFIG_LV_USE_FRAGMENT is not set +CONFIG_LV_USE_IMGFONT=y +CONFIG_LV_USE_OBSERVER=y +# CONFIG_LV_USE_IME_PINYIN is not set +# CONFIG_LV_USE_FILE_EXPLORER is not set +CONFIG_LVGL_VERSION_MAJOR=9 +CONFIG_LVGL_VERSION_MINOR=2 +CONFIG_LVGL_VERSION_PATCH=2 +# end of Others + +# +# Devices +# +# CONFIG_LV_USE_SDL is not set +# CONFIG_LV_USE_X11 is not set +# CONFIG_LV_USE_WAYLAND is not set +# CONFIG_LV_USE_LINUX_FBDEV is not set +# CONFIG_LV_USE_NUTTX is not set +# CONFIG_LV_USE_LINUX_DRM is not set +# CONFIG_LV_USE_TFT_ESPI is not set +# CONFIG_LV_USE_EVDEV is not set +# CONFIG_LV_USE_LIBINPUT is not set +# CONFIG_LV_USE_ST7735 is not set +# CONFIG_LV_USE_ST7789 is not set +# CONFIG_LV_USE_ST7796 is not set +# CONFIG_LV_USE_ILI9341 is not set +# CONFIG_LV_USE_GENERIC_MIPI is not set +# CONFIG_LV_USE_RENESAS_GLCDC is not set +# CONFIG_LV_USE_OPENGLES is not set +# CONFIG_LV_USE_QNX is not set +# end of Devices + +# +# Examples +# +# CONFIG_LV_BUILD_EXAMPLES is not set +# end of Examples + +# +# Demos +# +# CONFIG_LV_USE_DEMO_WIDGETS is not set +# CONFIG_LV_USE_DEMO_KEYPAD_AND_ENCODER is not set +# CONFIG_LV_USE_DEMO_RENDER is not set +# CONFIG_LV_USE_DEMO_SCROLL is not set +# CONFIG_LV_USE_DEMO_STRESS is not set +# CONFIG_LV_USE_DEMO_MUSIC is not set +# CONFIG_LV_USE_DEMO_FLEX_LAYOUT is not set +# CONFIG_LV_USE_DEMO_MULTILANG is not set +# end of Demos +# end of LVGL configuration + +# +# SH1106 ESP-IDF Driver +# +# end of Component config + +CONFIG_IDF_EXPERIMENTAL_FEATURES=y + +# Deprecated options for backward compatibility +# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set +# CONFIG_NO_BLOBS is not set +CONFIG_APP_ROLLBACK_ENABLE=y +# CONFIG_APP_ANTI_ROLLBACK is not set +CONFIG_LOG_BOOTLOADER_LEVEL_NONE=y +# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_INFO is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL=0 +# CONFIG_FLASH_ENCRYPTION_ENABLED is not set +CONFIG_FLASHMODE_QIO=y +# CONFIG_FLASHMODE_QOUT is not set +# CONFIG_FLASHMODE_DIO is not set +# CONFIG_FLASHMODE_DOUT is not set +CONFIG_MONITOR_BAUD=115200 +CONFIG_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y +# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set +CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y +# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set +CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 +CONFIG_CXX_EXCEPTIONS=y +CONFIG_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 +CONFIG_STACK_CHECK_NONE=y +# CONFIG_STACK_CHECK_NORM is not set +# CONFIG_STACK_CHECK_STRONG is not set +# CONFIG_STACK_CHECK_ALL is not set +# CONFIG_WARN_WRITE_STRINGS is not set +# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y +# CONFIG_ANA_CMPR_ISR_IRAM_SAFE is not set +# CONFIG_CAM_CTLR_MIPI_CSI_ISR_IRAM_SAFE is not set +# CONFIG_CAM_CTLR_ISP_DVP_ISR_IRAM_SAFE is not set +# CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE is not set +# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set +# CONFIG_MCPWM_ISR_IN_IRAM is not set +# CONFIG_EVENT_LOOP_PROFILING is not set +CONFIG_POST_EVENTS_FROM_ISR=y +CONFIG_POST_EVENTS_FROM_IRAM_ISR=y +CONFIG_GDBSTUB_SUPPORT_TASKS=y +CONFIG_GDBSTUB_MAX_TASKS=32 +# CONFIG_OTA_ALLOW_HTTP is not set +CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y +CONFIG_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set +CONFIG_BROWNOUT_DET_LVL=7 +CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y +CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_MAIN_TASK_STACK_SIZE=10240 +CONFIG_CONSOLE_UART_DEFAULT=y +# CONFIG_CONSOLE_UART_CUSTOM is not set +# CONFIG_CONSOLE_UART_NONE is not set +# CONFIG_ESP_CONSOLE_UART_NONE is not set +CONFIG_CONSOLE_UART=y +CONFIG_CONSOLE_UART_NUM=0 +CONFIG_CONSOLE_UART_BAUDRATE=115200 +CONFIG_INT_WDT=y +CONFIG_INT_WDT_TIMEOUT_MS=300 +CONFIG_INT_WDT_CHECK_CPU1=y +CONFIG_TASK_WDT=y +CONFIG_ESP_TASK_WDT=y +# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_TIMEOUT_S=10 +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set +CONFIG_IPC_TASK_STACK_SIZE=1024 +CONFIG_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=16 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16 +CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=16 +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_WPA_MBEDTLS_CRYPTO=y +CONFIG_WPA_MBEDTLS_TLS_CLIENT=y +# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set +# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set +CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y +CONFIG_TIMER_TASK_PRIORITY=1 +CONFIG_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_TIMER_QUEUE_LENGTH=10 +# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set +CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y +# CONFIG_HAL_ASSERTION_SILIENT is not set +# CONFIG_L2_TO_L3_COPY is not set +CONFIG_ESP_GRATUITOUS_ARP=y +CONFIG_GARP_TMR_INTERVAL=60 +CONFIG_TCPIP_RECVMBOX_SIZE=32 +CONFIG_TCP_MAXRTX=12 +CONFIG_TCP_SYNMAXRTX=12 +CONFIG_TCP_MSS=1440 +CONFIG_TCP_MSL=60000 +CONFIG_TCP_SND_BUF_DEFAULT=5760 +CONFIG_TCP_WND_DEFAULT=5760 +CONFIG_TCP_RECVMBOX_SIZE=6 +CONFIG_TCP_QUEUE_OOSEQ=y +CONFIG_TCP_OVERSIZE_MSS=y +# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_TCP_OVERSIZE_DISABLE is not set +CONFIG_UDP_RECVMBOX_SIZE=6 +CONFIG_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF +CONFIG_PPP_SUPPORT=y +# CONFIG_PPP_NOTIFY_PHASE_SUPPORT is not set +# CONFIG_PPP_PAP_SUPPORT is not set +# CONFIG_PPP_CHAP_SUPPORT is not set +# CONFIG_PPP_MSCHAP_SUPPORT is not set +# CONFIG_PPP_MPPE_SUPPORT is not set +# CONFIG_PPP_DEBUG_ON is not set +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +# CONFIG_NEWLIB_NANO_FORMAT is not set +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y +# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set +CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_ESP32_PTHREAD_STACK_MIN=768 +CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set +CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set +CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_SUPPORT_TERMIOS=y +CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# CONFIG_ESP_SPI_HOST_INTERFACE is not set +CONFIG_ESP_SDIO_HOST_INTERFACE=y +# CONFIG_ESP_SPI_HD_HOST_INTERFACE is not set +# CONFIG_ESP_UART_HOST_INTERFACE is not set +CONFIG_IDF_SLAVE_TARGET="esp32c6" +CONFIG_SDIO_RESET_ACTIVE_HIGH=y +# CONFIG_ESP_SDIO_OPTIMIZATION_RX_NONE is not set +# CONFIG_ESP_SDIO_OPTIMIZATION_RX_MAX_SIZE is not set +CONFIG_ESP_SDIO_OPTIMIZATION_RX_STREAMING_MODE=y +CONFIG_ESP_SDIO_4_BIT_BUS=y +# CONFIG_ESP_SDIO_1_BIT_BUS is not set +CONFIG_ESP_SDIO_BUS_WIDTH=4 +CONFIG_ESP_SDIO_CLOCK_FREQ_KHZ=40000 +CONFIG_ESP_SDIO_GPIO_RESET_SLAVE=54 +CONFIG_ESP_SDIO_PIN_CMD=13 +CONFIG_ESP_SDIO_PIN_CLK=12 +CONFIG_ESP_SDIO_PIN_D0=11 +CONFIG_ESP_SDIO_PIN_D2=9 +CONFIG_ESP_SDIO_PIN_D3=8 +CONFIG_ESP_SDIO_PIN_D1=10 +CONFIG_ESP_SDIO_TX_Q_SIZE=20 +CONFIG_ESP_SDIO_RX_Q_SIZE=20 +# CONFIG_ESP_SDIO_CHECKSUM is not set +CONFIG_ESP_GPIO_SLAVE_RESET_SLAVE=54 +CONFIG_ESP_RPC_TASK_STACK=4096 +CONFIG_ESP_DFLT_TASK_STACK=3072 +CONFIG_ESP_USE_MEMPOOL=y +CONFIG_ESP_MAX_SIMULTANEOUS_SYNC_RPC_REQUESTS=5 +CONFIG_ESP_MAX_SIMULTANEOUS_ASYNC_RPC_REQUESTS=5 +# CONFIG_ESP_RAW_THROUGHPUT_TRANSPORT is not set +# CONFIG_ESP_PKT_STATS is not set +CONFIG_HOST_TO_ESP_WIFI_DATA_THROTTLE=y +CONFIG_PRIV_WIFI_TX_SDIO_HIGH_THRESHOLD=80 +CONFIG_TO_WIFI_DATA_THROTTLE_HIGH_THRESHOLD=80 +CONFIG_TO_WIFI_DATA_THROTTLE_LOW_THRESHOLD=60 +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=16 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16 +CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=16 +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_WPA_MBEDTLS_CRYPTO=y +CONFIG_WPA_MBEDTLS_TLS_CLIENT=y +# End of deprecated options diff --git a/main/boards/m5stack-tab5/tab5_audio_codec.cc b/main/boards/m5stack-tab5/tab5_audio_codec.cc index a8e1a96..f4074ae 100644 --- a/main/boards/m5stack-tab5/tab5_audio_codec.cc +++ b/main/boards/m5stack-tab5/tab5_audio_codec.cc @@ -1,242 +1,242 @@ -#include "tab5_audio_codec.h" - -#include -#include -#include - -#define TAG "Tab5AudioCodec" - -Tab5AudioCodec::Tab5AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8388_addr, uint8_t es7210_addr, bool input_reference) { - duplex_ = true; // 是否双工 - input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 - input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - CreateDuplexChannels(mclk, bclk, ws, dout, din); - - // Do initialize of related interface: data_if, ctrl_if and gpio_if - audio_codec_i2s_cfg_t i2s_cfg = { - .port = I2S_NUM_0, - .rx_handle = rx_handle_, - .tx_handle = tx_handle_, - }; - data_if_ = audio_codec_new_i2s_data(&i2s_cfg); - assert(data_if_ != NULL); - - // Output - audio_codec_i2c_cfg_t i2c_cfg = { - .port = (i2c_port_t)1, - .addr = es8388_addr, - .bus_handle = i2c_master_handle, - }; - out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(out_ctrl_if_ != NULL); - - gpio_if_ = audio_codec_new_gpio(); - assert(gpio_if_ != NULL); - es8388_codec_cfg_t es8388_cfg = {}; - es8388_cfg.ctrl_if = out_ctrl_if_; - es8388_cfg.gpio_if = gpio_if_; - es8388_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC; - es8388_cfg.master_mode = true; - es8388_cfg.pa_pin = -1; // PI4IOE1 P1 控制 - es8388_cfg.pa_reverted = false; - es8388_cfg.hw_gain.pa_voltage = 5.0; - es8388_cfg.hw_gain.codec_dac_voltage = 3.3; - out_codec_if_ = es8388_codec_new(&es8388_cfg); - assert(out_codec_if_ != NULL); - - esp_codec_dev_cfg_t dev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_OUT, - .codec_if = out_codec_if_, - .data_if = data_if_, - }; - output_dev_ = esp_codec_dev_new(&dev_cfg); - assert(output_dev_ != NULL); - - // Input - i2c_cfg.addr = es7210_addr; - in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(in_ctrl_if_ != NULL); - - es7210_codec_cfg_t es7210_cfg = {}; - es7210_cfg.ctrl_if = in_ctrl_if_; - es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4; - in_codec_if_ = es7210_codec_new(&es7210_cfg); - assert(in_codec_if_ != NULL); - - dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; - dev_cfg.codec_if = in_codec_if_; - input_dev_ = esp_codec_dev_new(&dev_cfg); - assert(input_dev_ != NULL); - - ESP_LOGI(TAG, "Tab5 AudioDevice initialized"); -} - -Tab5AudioCodec::~Tab5AudioCodec() { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - esp_codec_dev_delete(output_dev_); - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - esp_codec_dev_delete(input_dev_); - - audio_codec_delete_codec_if(in_codec_if_); - audio_codec_delete_ctrl_if(in_ctrl_if_); - audio_codec_delete_codec_if(out_codec_if_); - audio_codec_delete_ctrl_if(out_ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void Tab5AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { - assert(input_sample_rate_ == output_sample_rate_); - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = 6, - .dma_frame_num = 240, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256 - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_16BIT, - .ws_pol = false, - .bit_shift = true, - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = I2S_GPIO_UNUSED, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - i2s_tdm_config_t tdm_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)input_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - .bclk_div = 8, - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), - .ws_width = I2S_TDM_AUTO_WS_WIDTH, - .ws_pol = false, - .bit_shift = true, - .left_align = false, - .big_endian = false, - .bit_order_lsb = false, - .skip_mask = false, - .total_slot = I2S_TDM_AUTO_SLOT_NUM - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = I2S_GPIO_UNUSED, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle_, &tdm_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); -} - -void Tab5AudioCodec::SetOutputVolume(int volume) { - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); - AudioCodec::SetOutputVolume(volume); -} - -void Tab5AudioCodec::EnableInput(bool enable) { - if (enable == input_enabled_) { - return; - } - if (enable) { - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 4, - .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - if (input_reference_) { - fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1); - } - ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), AUDIO_CODEC_DEFAULT_MIC_GAIN)); - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - } - AudioCodec::EnableInput(enable); -} - -void Tab5AudioCodec::EnableOutput(bool enable) { - if (enable == output_enabled_) { - return; - } - if (enable) { - // Play 16bit 1 channel - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - } - AudioCodec::EnableOutput(enable); -} - -int Tab5AudioCodec::Read(int16_t* dest, int samples) { - if (input_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); - } - return samples; -} - -int Tab5AudioCodec::Write(const int16_t* data, int samples) { - if (output_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); - } - return samples; -} +#include "tab5_audio_codec.h" + +#include +#include +#include + +#define TAG "Tab5AudioCodec" + +Tab5AudioCodec::Tab5AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8388_addr, uint8_t es7210_addr, bool input_reference) { + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + CreateDuplexChannels(mclk, bclk, ws, dout, din); + + // Do initialize of related interface: data_if, ctrl_if and gpio_if + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = rx_handle_, + .tx_handle = tx_handle_, + }; + data_if_ = audio_codec_new_i2s_data(&i2s_cfg); + assert(data_if_ != NULL); + + // Output + audio_codec_i2c_cfg_t i2c_cfg = { + .port = (i2c_port_t)1, + .addr = es8388_addr, + .bus_handle = i2c_master_handle, + }; + out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(out_ctrl_if_ != NULL); + + gpio_if_ = audio_codec_new_gpio(); + assert(gpio_if_ != NULL); + es8388_codec_cfg_t es8388_cfg = {}; + es8388_cfg.ctrl_if = out_ctrl_if_; + es8388_cfg.gpio_if = gpio_if_; + es8388_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC; + es8388_cfg.master_mode = true; + es8388_cfg.pa_pin = -1; // PI4IOE1 P1 控制 + es8388_cfg.pa_reverted = false; + es8388_cfg.hw_gain.pa_voltage = 5.0; + es8388_cfg.hw_gain.codec_dac_voltage = 3.3; + out_codec_if_ = es8388_codec_new(&es8388_cfg); + assert(out_codec_if_ != NULL); + + esp_codec_dev_cfg_t dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_OUT, + .codec_if = out_codec_if_, + .data_if = data_if_, + }; + output_dev_ = esp_codec_dev_new(&dev_cfg); + assert(output_dev_ != NULL); + + // Input + i2c_cfg.addr = es7210_addr; + in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(in_ctrl_if_ != NULL); + + es7210_codec_cfg_t es7210_cfg = {}; + es7210_cfg.ctrl_if = in_ctrl_if_; + es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4; + in_codec_if_ = es7210_codec_new(&es7210_cfg); + assert(in_codec_if_ != NULL); + + dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; + dev_cfg.codec_if = in_codec_if_; + input_dev_ = esp_codec_dev_new(&dev_cfg); + assert(input_dev_ != NULL); + + ESP_LOGI(TAG, "Tab5 AudioDevice initialized"); +} + +Tab5AudioCodec::~Tab5AudioCodec() { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + esp_codec_dev_delete(output_dev_); + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + esp_codec_dev_delete(input_dev_); + + audio_codec_delete_codec_if(in_codec_if_); + audio_codec_delete_ctrl_if(in_ctrl_if_); + audio_codec_delete_codec_if(out_codec_if_); + audio_codec_delete_ctrl_if(out_ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void Tab5AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + assert(input_sample_rate_ == output_sample_rate_); + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = 6, + .dma_frame_num = 240, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256 + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + i2s_tdm_config_t tdm_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)input_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + .bclk_div = 8, + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), + .ws_width = I2S_TDM_AUTO_WS_WIDTH, + .ws_pol = false, + .bit_shift = true, + .left_align = false, + .big_endian = false, + .bit_order_lsb = false, + .skip_mask = false, + .total_slot = I2S_TDM_AUTO_SLOT_NUM + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = I2S_GPIO_UNUSED, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle_, &tdm_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + +void Tab5AudioCodec::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void Tab5AudioCodec::EnableInput(bool enable) { + if (enable == input_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 4, + .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + if (input_reference_) { + fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1); + } + ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), AUDIO_CODEC_DEFAULT_MIC_GAIN)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void Tab5AudioCodec::EnableOutput(bool enable) { + if (enable == output_enabled_) { + return; + } + if (enable) { + // Play 16bit 1 channel + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + } + AudioCodec::EnableOutput(enable); +} + +int Tab5AudioCodec::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} + +int Tab5AudioCodec::Write(const int16_t* data, int samples) { + if (output_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); + } + return samples; +} diff --git a/main/boards/m5stack-tab5/tab5_audio_codec.h b/main/boards/m5stack-tab5/tab5_audio_codec.h index 4e45589..60936ff 100644 --- a/main/boards/m5stack-tab5/tab5_audio_codec.h +++ b/main/boards/m5stack-tab5/tab5_audio_codec.h @@ -1,37 +1,37 @@ -#ifndef _TAB5_AUDIO_CODEC_H -#define _TAB5_AUDIO_CODEC_H - -#include "audio_codec.h" -#include -#include - - -class Tab5AudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t* data_if_ = nullptr; - const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; - const audio_codec_if_t* out_codec_if_ = nullptr; - const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; - const audio_codec_if_t* in_codec_if_ = nullptr; - const audio_codec_gpio_if_t* gpio_if_ = nullptr; - - esp_codec_dev_handle_t output_dev_ = nullptr; - esp_codec_dev_handle_t input_dev_ = nullptr; - - void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); - - virtual int Read(int16_t* dest, int samples) override; - virtual int Write(const int16_t* data, int samples) override; - -public: - Tab5AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8388_addr, uint8_t es7210_addr, bool input_reference); - virtual ~Tab5AudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - -#endif // _TAB5_AUDIO_CODEC_H +#ifndef _TAB5_AUDIO_CODEC_H +#define _TAB5_AUDIO_CODEC_H + +#include "audio_codec.h" +#include +#include + + +class Tab5AudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t* data_if_ = nullptr; + const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; + const audio_codec_if_t* out_codec_if_ = nullptr; + const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; + const audio_codec_if_t* in_codec_if_ = nullptr; + const audio_codec_gpio_if_t* gpio_if_ = nullptr; + + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + + void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + Tab5AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8388_addr, uint8_t es7210_addr, bool input_reference); + virtual ~Tab5AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _TAB5_AUDIO_CODEC_H diff --git a/main/boards/magiclick-2p4/config.h b/main/boards/magiclick-2p4/config.h index bc37001..ed712cc 100644 --- a/main/boards/magiclick-2p4/config.h +++ b/main/boards/magiclick-2p4/config.h @@ -1,50 +1,50 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_11 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_4 // pcb v2.4不起作用,适用于2.4A -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_6 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -//led power -#define BUILTIN_LED_POWER GPIO_NUM_39 // 低电平有效 -#define BUILTIN_LED_POWER_OUTPUT_INVERT true - -#define BUILTIN_LED_NUM 2 -#define BUILTIN_LED_GPIO GPIO_NUM_38 - -#define MAIN_BUTTON_GPIO GPIO_NUM_21 -#define LEFT_BUTTON_GPIO GPIO_NUM_0 -#define RIGHT_BUTTON_GPIO GPIO_NUM_47 - -// display -#define DISPLAY_SDA_PIN GPIO_NUM_15 -#define DISPLAY_SCL_PIN GPIO_NUM_16 -#define DISPLAY_CS_PIN GPIO_NUM_17 -#define DISPLAY_DC_PIN GPIO_NUM_18 -#define DISPLAY_RST_PIN GPIO_NUM_14 - -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_11 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_4 // pcb v2.4不起作用,适用于2.4A +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_6 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +//led power +#define BUILTIN_LED_POWER GPIO_NUM_39 // 低电平有效 +#define BUILTIN_LED_POWER_OUTPUT_INVERT true + +#define BUILTIN_LED_NUM 2 +#define BUILTIN_LED_GPIO GPIO_NUM_38 + +#define MAIN_BUTTON_GPIO GPIO_NUM_21 +#define LEFT_BUTTON_GPIO GPIO_NUM_0 +#define RIGHT_BUTTON_GPIO GPIO_NUM_47 + +// display +#define DISPLAY_SDA_PIN GPIO_NUM_15 +#define DISPLAY_SCL_PIN GPIO_NUM_16 +#define DISPLAY_CS_PIN GPIO_NUM_17 +#define DISPLAY_DC_PIN GPIO_NUM_18 +#define DISPLAY_RST_PIN GPIO_NUM_14 + +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/magiclick-2p4/config.json b/main/boards/magiclick-2p4/config.json index f416c2a..ab50832 100644 --- a/main/boards/magiclick-2p4/config.json +++ b/main/boards/magiclick-2p4/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "magiclick-2p4", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "magiclick-2p4", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/magiclick-2p4/magiclick_2p4_board.cc b/main/boards/magiclick-2p4/magiclick_2p4_board.cc index 770884b..9dd8ebd 100644 --- a/main/boards/magiclick-2p4/magiclick_2p4_board.cc +++ b/main/boards/magiclick-2p4/magiclick_2p4_board.cc @@ -1,270 +1,270 @@ -#include "wifi_board.h" -#include "display/lcd_display.h" -#include "codecs/es8311_audio_codec.h" -#include "application.h" -#include "button.h" -#include "led/circular_strip.h" -#include "config.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include - -#include "../magiclick-2p5/power_manager.h" -#include "power_save_timer.h" - -#define TAG "magiclick_2p4" - -class NV3023Display : public SpiLcdDisplay { -public: - NV3023Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - - DisplayLockGuard lock(this); - // 只需要覆盖颜色相关的样式 - auto screen = lv_disp_get_scr_act(lv_disp_get_default()); - lv_obj_set_style_text_color(screen, lv_color_black(), 0); - - // 设置容器背景色 - lv_obj_set_style_bg_color(container_, lv_color_black(), 0); - - // 设置状态栏背景色和文本颜色 - lv_obj_set_style_bg_color(status_bar_, lv_color_white(), 0); - lv_obj_set_style_text_color(network_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(notification_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(status_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(mute_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(battery_label_, lv_color_black(), 0); - - // 设置内容区背景色和文本颜色 - lv_obj_set_style_bg_color(content_, lv_color_black(), 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_style_text_color(emoji_label_, lv_color_white(), 0); - lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); - } -}; - -class magiclick_2p4 : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button main_button_; - Button left_button_; - Button right_button_; - NV3023Display* display_; - - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_48); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(240, 60, -1); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - - power_save_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeButtons() { - main_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - main_button_.OnPressDown([this]() { - power_save_timer_->WakeUp(); - Application::GetInstance().StartListening(); - }); - main_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - - left_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - left_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - - right_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - right_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - } - - void InitializeLedPower() { - // 设置GPIO模式 - gpio_reset_pin(BUILTIN_LED_POWER); - gpio_set_direction(BUILTIN_LED_POWER, GPIO_MODE_OUTPUT); - gpio_set_level(BUILTIN_LED_POWER, BUILTIN_LED_POWER_OUTPUT_INVERT ? 0 : 1); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SDA_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCL_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeNv3023Display(){ - // esp_lcd_panel_io_handle_t panel_io = nullptr; - // esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片NV3023 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, false); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - display_ = new NV3023Display(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - magiclick_2p4() : - main_button_(MAIN_BUTTON_GPIO), - left_button_(LEFT_BUTTON_GPIO), - right_button_(RIGHT_BUTTON_GPIO) { - InitializeLedPower(); - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeCodecI2c(); - InitializeButtons(); - InitializeSpi(); - InitializeNv3023Display(); - GetBacklight()->RestoreBrightness(); - } - - virtual Led* GetLed() override { - static CircularStrip led(BUILTIN_LED_GPIO, BUILTIN_LED_NUM); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(magiclick_2p4); +#include "wifi_board.h" +#include "display/lcd_display.h" +#include "codecs/es8311_audio_codec.h" +#include "application.h" +#include "button.h" +#include "led/circular_strip.h" +#include "config.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include +#include + +#include "../magiclick-2p5/power_manager.h" +#include "power_save_timer.h" + +#define TAG "magiclick_2p4" + +class NV3023Display : public SpiLcdDisplay { +public: + NV3023Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + + DisplayLockGuard lock(this); + // 只需要覆盖颜色相关的样式 + auto screen = lv_disp_get_scr_act(lv_disp_get_default()); + lv_obj_set_style_text_color(screen, lv_color_black(), 0); + + // 设置容器背景色 + lv_obj_set_style_bg_color(container_, lv_color_black(), 0); + + // 设置状态栏背景色和文本颜色 + lv_obj_set_style_bg_color(status_bar_, lv_color_white(), 0); + lv_obj_set_style_text_color(network_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(notification_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(status_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(mute_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(battery_label_, lv_color_black(), 0); + + // 设置内容区背景色和文本颜色 + lv_obj_set_style_bg_color(content_, lv_color_black(), 0); + lv_obj_set_style_border_width(content_, 0, 0); + lv_obj_set_style_text_color(emoji_label_, lv_color_white(), 0); + lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); + } +}; + +class magiclick_2p4 : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button main_button_; + Button left_button_; + Button right_button_; + NV3023Display* display_; + + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_48); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(240, 60, -1); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeButtons() { + main_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + main_button_.OnPressDown([this]() { + power_save_timer_->WakeUp(); + Application::GetInstance().StartListening(); + }); + main_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + + left_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + left_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + + right_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + right_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + } + + void InitializeLedPower() { + // 设置GPIO模式 + gpio_reset_pin(BUILTIN_LED_POWER); + gpio_set_direction(BUILTIN_LED_POWER, GPIO_MODE_OUTPUT); + gpio_set_level(BUILTIN_LED_POWER, BUILTIN_LED_POWER_OUTPUT_INVERT ? 0 : 1); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SDA_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCL_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeNv3023Display(){ + // esp_lcd_panel_io_handle_t panel_io = nullptr; + // esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片NV3023 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); + display_ = new NV3023Display(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + magiclick_2p4() : + main_button_(MAIN_BUTTON_GPIO), + left_button_(LEFT_BUTTON_GPIO), + right_button_(RIGHT_BUTTON_GPIO) { + InitializeLedPower(); + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeCodecI2c(); + InitializeButtons(); + InitializeSpi(); + InitializeNv3023Display(); + GetBacklight()->RestoreBrightness(); + } + + virtual Led* GetLed() override { + static CircularStrip led(BUILTIN_LED_GPIO, BUILTIN_LED_NUM); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(magiclick_2p4); diff --git a/main/boards/magiclick-2p5/config.h b/main/boards/magiclick-2p5/config.h index 3b8cce7..cd08c41 100644 --- a/main/boards/magiclick-2p5/config.h +++ b/main/boards/magiclick-2p5/config.h @@ -1,55 +1,55 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_11 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_4 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_6 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -//led power -#define BUILTIN_LED_POWER GPIO_NUM_39 // 低电平有效 -#define BUILTIN_LED_POWER_OUTPUT_INVERT true - -#define BUILTIN_LED_NUM 2 -#define BUILTIN_LED_GPIO GPIO_NUM_38 - -#define MAIN_BUTTON_GPIO GPIO_NUM_21 -#define LEFT_BUTTON_GPIO GPIO_NUM_0 -#define RIGHT_BUTTON_GPIO GPIO_NUM_47 - -// display -#define DISPLAY_SDA_PIN GPIO_NUM_16 -#define DISPLAY_SCL_PIN GPIO_NUM_15 -#define DISPLAY_CS_PIN GPIO_NUM_14 -#define DISPLAY_DC_PIN GPIO_NUM_18 -#define DISPLAY_RST_PIN GPIO_NUM_17 - -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - -#define ML307_RX_PIN GPIO_NUM_42 -#define ML307_TX_PIN GPIO_NUM_44 -#define ML307_POWER_PIN GPIO_NUM_40 -#define ML307_POWER_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_11 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_4 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_6 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +//led power +#define BUILTIN_LED_POWER GPIO_NUM_39 // 低电平有效 +#define BUILTIN_LED_POWER_OUTPUT_INVERT true + +#define BUILTIN_LED_NUM 2 +#define BUILTIN_LED_GPIO GPIO_NUM_38 + +#define MAIN_BUTTON_GPIO GPIO_NUM_21 +#define LEFT_BUTTON_GPIO GPIO_NUM_0 +#define RIGHT_BUTTON_GPIO GPIO_NUM_47 + +// display +#define DISPLAY_SDA_PIN GPIO_NUM_16 +#define DISPLAY_SCL_PIN GPIO_NUM_15 +#define DISPLAY_CS_PIN GPIO_NUM_14 +#define DISPLAY_DC_PIN GPIO_NUM_18 +#define DISPLAY_RST_PIN GPIO_NUM_17 + +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +#define ML307_RX_PIN GPIO_NUM_42 +#define ML307_TX_PIN GPIO_NUM_44 +#define ML307_POWER_PIN GPIO_NUM_40 +#define ML307_POWER_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/magiclick-2p5/config.json b/main/boards/magiclick-2p5/config.json index 6220641..d3b5396 100644 --- a/main/boards/magiclick-2p5/config.json +++ b/main/boards/magiclick-2p5/config.json @@ -1,12 +1,12 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "magiclick-2p5", - "sdkconfig_append": [ - "CONFIG_PM_ENABLE=y", - "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "magiclick-2p5", + "sdkconfig_append": [ + "CONFIG_PM_ENABLE=y", + "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/magiclick-2p5/magiclick_2p5_board.cc b/main/boards/magiclick-2p5/magiclick_2p5_board.cc index 1db21fa..9090f2d 100644 --- a/main/boards/magiclick-2p5/magiclick_2p5_board.cc +++ b/main/boards/magiclick-2p5/magiclick_2p5_board.cc @@ -1,326 +1,326 @@ -#include "dual_network_board.h" -#include "display/lcd_display.h" -#include "codecs/es8311_audio_codec.h" -#include "application.h" -#include "button.h" -#include "led/circular_strip.h" -#include "config.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "power_manager.h" -#include "power_save_timer.h" -#include "esp_wifi.h" - -#define TAG "magiclick_2p5" - -class GC9107Display : public SpiLcdDisplay { -public: - GC9107Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - } -}; - -static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { - // {cmd, { data }, data_size, delay_ms} - {0xfe, (uint8_t[]){0x00}, 0, 0}, - {0xef, (uint8_t[]){0x00}, 0, 0}, - {0xb0, (uint8_t[]){0xc0}, 1, 0}, - {0xb1, (uint8_t[]){0x80}, 1, 0}, - {0xb2, (uint8_t[]){0x27}, 1, 0}, - {0xb3, (uint8_t[]){0x13}, 1, 0}, - {0xb6, (uint8_t[]){0x19}, 1, 0}, - {0xb7, (uint8_t[]){0x05}, 1, 0}, - {0xac, (uint8_t[]){0xc8}, 1, 0}, - {0xab, (uint8_t[]){0x0f}, 1, 0}, - {0x3a, (uint8_t[]){0x05}, 1, 0}, - {0xb4, (uint8_t[]){0x04}, 1, 0}, - {0xa8, (uint8_t[]){0x08}, 1, 0}, - {0xb8, (uint8_t[]){0x08}, 1, 0}, - {0xea, (uint8_t[]){0x02}, 1, 0}, - {0xe8, (uint8_t[]){0x2A}, 1, 0}, - {0xe9, (uint8_t[]){0x47}, 1, 0}, - {0xe7, (uint8_t[]){0x5f}, 1, 0}, - {0xc6, (uint8_t[]){0x21}, 1, 0}, - {0xc7, (uint8_t[]){0x15}, 1, 0}, - {0xf0, - (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, - 0x04, 0x12, 0x14, 0x1f}, - 14, 0}, - {0xf1, - (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, - 0x0C, 0x1A, 0x14, 0x1E}, - 14, 0}, - {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, - {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, -}; - -class magiclick_2p5 : public DualNetworkBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button main_button_; - Button left_button_; - Button right_button_; - GC9107Display* display_; - - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_48); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(240, 60, -1); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - - power_save_timer_->SetEnabled(true); - } - - void Enable4GModule() { - // enable the 4G module - gpio_reset_pin(ML307_POWER_PIN); - gpio_set_direction(ML307_POWER_PIN, GPIO_MODE_OUTPUT); - gpio_set_level(ML307_POWER_PIN, ML307_POWER_OUTPUT_INVERT ? 0 : 1); - } - void Disable4GModule() { - // enable the 4G module - gpio_reset_pin(ML307_POWER_PIN); - gpio_set_direction(ML307_POWER_PIN, GPIO_MODE_OUTPUT); - gpio_set_level(ML307_POWER_PIN, ML307_POWER_OUTPUT_INVERT ? 1 : 0); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void CheckNetType() { - if (GetNetworkType() == NetworkType::WIFI) { - Disable4GModule(); - } else if (GetNetworkType() == NetworkType::ML307) { - Enable4GModule(); - } - - } - - void InitializeButtons() { - main_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - // cast to WifiBoard - auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); - Disable4GModule(); - } - } else if(GetNetworkType() == NetworkType::ML307) { - - Enable4GModule(); - // stop WiFi - esp_wifi_stop(); - } - }); - main_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { - SwitchNetworkType(); - } - }); - main_button_.OnPressDown([this]() { - power_save_timer_->WakeUp(); - Application::GetInstance().StartListening(); - }); - main_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - - left_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - left_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - - right_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - right_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - } - - void InitializeLedPower() { - // 设置GPIO模式 - gpio_reset_pin(BUILTIN_LED_POWER); - gpio_set_direction(BUILTIN_LED_POWER, GPIO_MODE_OUTPUT); - gpio_set_level(BUILTIN_LED_POWER, BUILTIN_LED_POWER_OUTPUT_INVERT ? 0 : 1); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SDA_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCL_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeGc9107Display(){ - // esp_lcd_panel_io_handle_t panel_io = nullptr; - // esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片GC9107 - ESP_LOGD(TAG, "Install LCD driver"); - gc9a01_vendor_config_t gc9107_vendor_config = { - .init_cmds = gc9107_lcd_init_cmds, - .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), - }; - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = &gc9107_vendor_config; - - esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel); - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, false); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - display_ = new GC9107Display(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - magiclick_2p5() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0), - main_button_(MAIN_BUTTON_GPIO), - left_button_(LEFT_BUTTON_GPIO), - right_button_(RIGHT_BUTTON_GPIO) { - InitializeLedPower(); - CheckNetType(); - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeCodecI2c(); - InitializeButtons(); - InitializeSpi(); - InitializeGc9107Display(); - GetBacklight()->RestoreBrightness(); - } - - virtual Led* GetLed() override { - static CircularStrip led(BUILTIN_LED_GPIO, BUILTIN_LED_NUM); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - DualNetworkBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(magiclick_2p5); +#include "dual_network_board.h" +#include "display/lcd_display.h" +#include "codecs/es8311_audio_codec.h" +#include "application.h" +#include "button.h" +#include "led/circular_strip.h" +#include "config.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "power_manager.h" +#include "power_save_timer.h" +#include "esp_wifi.h" + +#define TAG "magiclick_2p5" + +class GC9107Display : public SpiLcdDisplay { +public: + GC9107Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + } +}; + +static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { + // {cmd, { data }, data_size, delay_ms} + {0xfe, (uint8_t[]){0x00}, 0, 0}, + {0xef, (uint8_t[]){0x00}, 0, 0}, + {0xb0, (uint8_t[]){0xc0}, 1, 0}, + {0xb1, (uint8_t[]){0x80}, 1, 0}, + {0xb2, (uint8_t[]){0x27}, 1, 0}, + {0xb3, (uint8_t[]){0x13}, 1, 0}, + {0xb6, (uint8_t[]){0x19}, 1, 0}, + {0xb7, (uint8_t[]){0x05}, 1, 0}, + {0xac, (uint8_t[]){0xc8}, 1, 0}, + {0xab, (uint8_t[]){0x0f}, 1, 0}, + {0x3a, (uint8_t[]){0x05}, 1, 0}, + {0xb4, (uint8_t[]){0x04}, 1, 0}, + {0xa8, (uint8_t[]){0x08}, 1, 0}, + {0xb8, (uint8_t[]){0x08}, 1, 0}, + {0xea, (uint8_t[]){0x02}, 1, 0}, + {0xe8, (uint8_t[]){0x2A}, 1, 0}, + {0xe9, (uint8_t[]){0x47}, 1, 0}, + {0xe7, (uint8_t[]){0x5f}, 1, 0}, + {0xc6, (uint8_t[]){0x21}, 1, 0}, + {0xc7, (uint8_t[]){0x15}, 1, 0}, + {0xf0, + (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, + 0x04, 0x12, 0x14, 0x1f}, + 14, 0}, + {0xf1, + (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, + 0x0C, 0x1A, 0x14, 0x1E}, + 14, 0}, + {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, + {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, +}; + +class magiclick_2p5 : public DualNetworkBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button main_button_; + Button left_button_; + Button right_button_; + GC9107Display* display_; + + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_48); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(240, 60, -1); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + + power_save_timer_->SetEnabled(true); + } + + void Enable4GModule() { + // enable the 4G module + gpio_reset_pin(ML307_POWER_PIN); + gpio_set_direction(ML307_POWER_PIN, GPIO_MODE_OUTPUT); + gpio_set_level(ML307_POWER_PIN, ML307_POWER_OUTPUT_INVERT ? 0 : 1); + } + void Disable4GModule() { + // enable the 4G module + gpio_reset_pin(ML307_POWER_PIN); + gpio_set_direction(ML307_POWER_PIN, GPIO_MODE_OUTPUT); + gpio_set_level(ML307_POWER_PIN, ML307_POWER_OUTPUT_INVERT ? 1 : 0); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void CheckNetType() { + if (GetNetworkType() == NetworkType::WIFI) { + Disable4GModule(); + } else if (GetNetworkType() == NetworkType::ML307) { + Enable4GModule(); + } + + } + + void InitializeButtons() { + main_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (GetNetworkType() == NetworkType::WIFI) { + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + // cast to WifiBoard + auto& wifi_board = static_cast(GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + Disable4GModule(); + } + } else if(GetNetworkType() == NetworkType::ML307) { + + Enable4GModule(); + // stop WiFi + esp_wifi_stop(); + } + }); + main_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + SwitchNetworkType(); + } + }); + main_button_.OnPressDown([this]() { + power_save_timer_->WakeUp(); + Application::GetInstance().StartListening(); + }); + main_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + + left_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + left_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + + right_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + right_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + } + + void InitializeLedPower() { + // 设置GPIO模式 + gpio_reset_pin(BUILTIN_LED_POWER); + gpio_set_direction(BUILTIN_LED_POWER, GPIO_MODE_OUTPUT); + gpio_set_level(BUILTIN_LED_POWER, BUILTIN_LED_POWER_OUTPUT_INVERT ? 0 : 1); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SDA_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCL_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeGc9107Display(){ + // esp_lcd_panel_io_handle_t panel_io = nullptr; + // esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片GC9107 + ESP_LOGD(TAG, "Install LCD driver"); + gc9a01_vendor_config_t gc9107_vendor_config = { + .init_cmds = gc9107_lcd_init_cmds, + .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), + }; + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = &gc9107_vendor_config; + + esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); + display_ = new GC9107Display(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + magiclick_2p5() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0), + main_button_(MAIN_BUTTON_GPIO), + left_button_(LEFT_BUTTON_GPIO), + right_button_(RIGHT_BUTTON_GPIO) { + InitializeLedPower(); + CheckNetType(); + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeCodecI2c(); + InitializeButtons(); + InitializeSpi(); + InitializeGc9107Display(); + GetBacklight()->RestoreBrightness(); + } + + virtual Led* GetLed() override { + static CircularStrip led(BUILTIN_LED_GPIO, BUILTIN_LED_NUM); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + DualNetworkBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(magiclick_2p5); diff --git a/main/boards/magiclick-2p5/power_manager.h b/main/boards/magiclick-2p5/power_manager.h index 5517a13..24f4341 100644 --- a/main/boards/magiclick-2p5/power_manager.h +++ b/main/boards/magiclick-2p5/power_manager.h @@ -1,195 +1,195 @@ -#pragma once -#include -#include - -#include -#include -#include - -#define CHARGING_PIN GPIO_NUM_48 -#define CHARGING_ACTIVE_STATE 0 - - -class PowerManager { -private: - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - - gpio_num_t charging_pin_ = CHARGING_PIN; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 0; - // ESP_LOGI("PowerManager", "new_charging_status: %s,is_charging_:%s", new_charging_status?"True":"False",is_charging_?"True":"False"); - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_6, &adc_value)); - ESP_LOGI("PowerManager", "ADC value: %d ", adc_value); - - - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += value; - } - average_adc /= adc_values_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {1985, 0}, - {2079, 20}, - {2141, 40}, - {2296, 60}, - {2420, 80}, - {2606, 100} - }; - - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&io_conf); - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_6, &chan_config)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 检测充电指示引脚 - if(gpio_get_level(charging_pin_) != CHARGING_ACTIVE_STATE) - { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include + +#include +#include +#include + +#define CHARGING_PIN GPIO_NUM_48 +#define CHARGING_ACTIVE_STATE 0 + + +class PowerManager { +private: + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + + gpio_num_t charging_pin_ = CHARGING_PIN; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 0; + // ESP_LOGI("PowerManager", "new_charging_status: %s,is_charging_:%s", new_charging_status?"True":"False",is_charging_?"True":"False"); + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_6, &adc_value)); + ESP_LOGI("PowerManager", "ADC value: %d ", adc_value); + + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {1985, 0}, + {2079, 20}, + {2141, 40}, + {2296, 60}, + {2420, 80}, + {2606, 100} + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_6, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { + // 检测充电指示引脚 + if(gpio_get_level(charging_pin_) != CHARGING_ACTIVE_STATE) + { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/magiclick-c3-v2/config.h b/main/boards/magiclick-c3-v2/config.h index 5609bf6..3bcb131 100644 --- a/main/boards/magiclick-c3-v2/config.h +++ b/main/boards/magiclick-c3-v2/config.h @@ -1,47 +1,47 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_8 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_11 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_NUM 1 -#define BUILTIN_LED_GPIO GPIO_NUM_0 - -#define BOOT_BUTTON_GPIO GPIO_NUM_2 - -//battery -#define BUILTIN_BATTERY_GPIO GPIO_NUM_1 - -// display -#define DISPLAY_SDA_PIN GPIO_NUM_13 -#define DISPLAY_SCL_PIN GPIO_NUM_12 -#define DISPLAY_CS_PIN GPIO_NUM_20 -#define DISPLAY_DC_PIN GPIO_NUM_21 -#define DISPLAY_RST_PIN GPIO_NUM_NC - -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_9 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_8 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_11 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_NUM 1 +#define BUILTIN_LED_GPIO GPIO_NUM_0 + +#define BOOT_BUTTON_GPIO GPIO_NUM_2 + +//battery +#define BUILTIN_BATTERY_GPIO GPIO_NUM_1 + +// display +#define DISPLAY_SDA_PIN GPIO_NUM_13 +#define DISPLAY_SCL_PIN GPIO_NUM_12 +#define DISPLAY_CS_PIN GPIO_NUM_20 +#define DISPLAY_DC_PIN GPIO_NUM_21 +#define DISPLAY_RST_PIN GPIO_NUM_NC + +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_9 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/magiclick-c3-v2/config.json b/main/boards/magiclick-c3-v2/config.json index 4503ebd..176e8e6 100644 --- a/main/boards/magiclick-c3-v2/config.json +++ b/main/boards/magiclick-c3-v2/config.json @@ -1,13 +1,13 @@ -{ - "target": "esp32c3", - "builds": [ - { - "name": "magiclick-c3-v2", - "sdkconfig_append": [ - "CONFIG_PM_ENABLE=y", - "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", - "CONFIG_USE_ESP_WAKE_WORD=n" - ] - } - ] +{ + "target": "esp32c3", + "builds": [ + { + "name": "magiclick-c3-v2", + "sdkconfig_append": [ + "CONFIG_PM_ENABLE=y", + "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", + "CONFIG_USE_ESP_WAKE_WORD=n" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc b/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc index 19f564f..7d3e9ae 100644 --- a/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc +++ b/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc @@ -1,232 +1,232 @@ -#include "wifi_board.h" -#include "display/lcd_display.h" -#include "codecs/es8311_audio_codec.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "config.h" -#include "power_save_timer.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TAG "magiclick_c3_v2" - -class GC9107Display : public SpiLcdDisplay { -public: - GC9107Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - - DisplayLockGuard lock(this); - // 只需要覆盖颜色相关的样式 - auto screen = lv_disp_get_scr_act(lv_disp_get_default()); - lv_obj_set_style_text_color(screen, lv_color_black(), 0); - - // 设置容器背景色 - lv_obj_set_style_bg_color(container_, lv_color_black(), 0); - - // 设置状态栏背景色和文本颜色 - lv_obj_set_style_bg_color(status_bar_, lv_color_make(0x1e, 0x90, 0xff), 0); - lv_obj_set_style_text_color(network_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(notification_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(status_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(mute_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(battery_label_, lv_color_black(), 0); - - // 设置内容区背景色和文本颜色 - lv_obj_set_style_bg_color(content_, lv_color_black(), 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_style_text_color(emoji_label_, lv_color_white(), 0); - lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); - } -}; - -static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { - // {cmd, { data }, data_size, delay_ms} - {0xfe, (uint8_t[]){0x00}, 0, 0}, - {0xef, (uint8_t[]){0x00}, 0, 0}, - {0xb0, (uint8_t[]){0xc0}, 1, 0}, - {0xb1, (uint8_t[]){0x80}, 1, 0}, - {0xb2, (uint8_t[]){0x27}, 1, 0}, - {0xb3, (uint8_t[]){0x13}, 1, 0}, - {0xb6, (uint8_t[]){0x19}, 1, 0}, - {0xb7, (uint8_t[]){0x05}, 1, 0}, - {0xac, (uint8_t[]){0xc8}, 1, 0}, - {0xab, (uint8_t[]){0x0f}, 1, 0}, - {0x3a, (uint8_t[]){0x05}, 1, 0}, - {0xb4, (uint8_t[]){0x04}, 1, 0}, - {0xa8, (uint8_t[]){0x08}, 1, 0}, - {0xb8, (uint8_t[]){0x08}, 1, 0}, - {0xea, (uint8_t[]){0x02}, 1, 0}, - {0xe8, (uint8_t[]){0x2A}, 1, 0}, - {0xe9, (uint8_t[]){0x47}, 1, 0}, - {0xe7, (uint8_t[]){0x5f}, 1, 0}, - {0xc6, (uint8_t[]){0x21}, 1, 0}, - {0xc7, (uint8_t[]){0x15}, 1, 0}, - {0xf0, - (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, - 0x04, 0x12, 0x14, 0x1f}, - 14, 0}, - {0xf1, - (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, - 0x0C, 0x1A, 0x14, 0x1E}, - 14, 0}, - {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, - {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, -}; - -class magiclick_c3_v2 : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - GC9107Display* display_; - PowerSaveTimer* power_save_timer_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(160); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(10); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - - // Print I2C bus info - if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { - while (true) { - ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - } - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - boot_button_.OnPressDown([this]() { - power_save_timer_->WakeUp(); - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SDA_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCL_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeGc9107Display(){ - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片GC9107 - ESP_LOGD(TAG, "Install LCD driver"); - gc9a01_vendor_config_t gc9107_vendor_config = { - .init_cmds = gc9107_lcd_init_cmds, - .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), - }; - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = &gc9107_vendor_config; - - esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, false); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - esp_lcd_panel_disp_on_off(panel, true); - display_ = new GC9107Display(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - magiclick_c3_v2() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeButtons(); - InitializePowerSaveTimer(); - InitializeSpi(); - InitializeGc9107Display(); - GetBacklight()->RestoreBrightness(); - - // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 - esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(magiclick_c3_v2); +#include "wifi_board.h" +#include "display/lcd_display.h" +#include "codecs/es8311_audio_codec.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "config.h" +#include "power_save_timer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "magiclick_c3_v2" + +class GC9107Display : public SpiLcdDisplay { +public: + GC9107Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + + DisplayLockGuard lock(this); + // 只需要覆盖颜色相关的样式 + auto screen = lv_disp_get_scr_act(lv_disp_get_default()); + lv_obj_set_style_text_color(screen, lv_color_black(), 0); + + // 设置容器背景色 + lv_obj_set_style_bg_color(container_, lv_color_black(), 0); + + // 设置状态栏背景色和文本颜色 + lv_obj_set_style_bg_color(status_bar_, lv_color_make(0x1e, 0x90, 0xff), 0); + lv_obj_set_style_text_color(network_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(notification_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(status_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(mute_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(battery_label_, lv_color_black(), 0); + + // 设置内容区背景色和文本颜色 + lv_obj_set_style_bg_color(content_, lv_color_black(), 0); + lv_obj_set_style_border_width(content_, 0, 0); + lv_obj_set_style_text_color(emoji_label_, lv_color_white(), 0); + lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); + } +}; + +static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { + // {cmd, { data }, data_size, delay_ms} + {0xfe, (uint8_t[]){0x00}, 0, 0}, + {0xef, (uint8_t[]){0x00}, 0, 0}, + {0xb0, (uint8_t[]){0xc0}, 1, 0}, + {0xb1, (uint8_t[]){0x80}, 1, 0}, + {0xb2, (uint8_t[]){0x27}, 1, 0}, + {0xb3, (uint8_t[]){0x13}, 1, 0}, + {0xb6, (uint8_t[]){0x19}, 1, 0}, + {0xb7, (uint8_t[]){0x05}, 1, 0}, + {0xac, (uint8_t[]){0xc8}, 1, 0}, + {0xab, (uint8_t[]){0x0f}, 1, 0}, + {0x3a, (uint8_t[]){0x05}, 1, 0}, + {0xb4, (uint8_t[]){0x04}, 1, 0}, + {0xa8, (uint8_t[]){0x08}, 1, 0}, + {0xb8, (uint8_t[]){0x08}, 1, 0}, + {0xea, (uint8_t[]){0x02}, 1, 0}, + {0xe8, (uint8_t[]){0x2A}, 1, 0}, + {0xe9, (uint8_t[]){0x47}, 1, 0}, + {0xe7, (uint8_t[]){0x5f}, 1, 0}, + {0xc6, (uint8_t[]){0x21}, 1, 0}, + {0xc7, (uint8_t[]){0x15}, 1, 0}, + {0xf0, + (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C, + 0x04, 0x12, 0x14, 0x1f}, + 14, 0}, + {0xf1, + (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D, + 0x0C, 0x1A, 0x14, 0x1E}, + 14, 0}, + {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0}, + {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0}, +}; + +class magiclick_c3_v2 : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + GC9107Display* display_; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(160); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(10); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + // Print I2C bus info + if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { + while (true) { + ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + boot_button_.OnPressDown([this]() { + power_save_timer_->WakeUp(); + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SDA_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCL_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeGc9107Display(){ + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片GC9107 + ESP_LOGD(TAG, "Install LCD driver"); + gc9a01_vendor_config_t gc9107_vendor_config = { + .init_cmds = gc9107_lcd_init_cmds, + .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), + }; + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = &gc9107_vendor_config; + + esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_disp_on_off(panel, true); + display_ = new GC9107Display(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + magiclick_c3_v2() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeButtons(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeGc9107Display(); + GetBacklight()->RestoreBrightness(); + + // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 + esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(magiclick_c3_v2); diff --git a/main/boards/magiclick-c3/config.h b/main/boards/magiclick-c3/config.h index 90cd2b7..a3a6d82 100644 --- a/main/boards/magiclick-c3/config.h +++ b/main/boards/magiclick-c3/config.h @@ -1,47 +1,47 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_8 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_11 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_NUM 1 -#define BUILTIN_LED_GPIO GPIO_NUM_0 - -#define BOOT_BUTTON_GPIO GPIO_NUM_2 - -//battery -#define BUILTIN_BATTERY_GPIO GPIO_NUM_1 - -// display -#define DISPLAY_SDA_PIN GPIO_NUM_12 -#define DISPLAY_SCL_PIN GPIO_NUM_13 -#define DISPLAY_CS_PIN GPIO_NUM_20 -#define DISPLAY_DC_PIN GPIO_NUM_21 -#define DISPLAY_RST_PIN GPIO_NUM_NC - -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_9 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_8 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_11 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_NUM 1 +#define BUILTIN_LED_GPIO GPIO_NUM_0 + +#define BOOT_BUTTON_GPIO GPIO_NUM_2 + +//battery +#define BUILTIN_BATTERY_GPIO GPIO_NUM_1 + +// display +#define DISPLAY_SDA_PIN GPIO_NUM_12 +#define DISPLAY_SCL_PIN GPIO_NUM_13 +#define DISPLAY_CS_PIN GPIO_NUM_20 +#define DISPLAY_DC_PIN GPIO_NUM_21 +#define DISPLAY_RST_PIN GPIO_NUM_NC + +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_9 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/magiclick-c3/config.json b/main/boards/magiclick-c3/config.json index 34d1471..38e1b84 100644 --- a/main/boards/magiclick-c3/config.json +++ b/main/boards/magiclick-c3/config.json @@ -1,13 +1,13 @@ -{ - "target": "esp32c3", - "builds": [ - { - "name": "magiclick-c3", - "sdkconfig_append": [ - "CONFIG_PM_ENABLE=y", - "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", - "CONFIG_USE_ESP_WAKE_WORD=n" - ] - } - ] +{ + "target": "esp32c3", + "builds": [ + { + "name": "magiclick-c3", + "sdkconfig_append": [ + "CONFIG_PM_ENABLE=y", + "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", + "CONFIG_USE_ESP_WAKE_WORD=n" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/magiclick-c3/magiclick_c3_board.cc b/main/boards/magiclick-c3/magiclick_c3_board.cc index 373e661..f436f57 100644 --- a/main/boards/magiclick-c3/magiclick_c3_board.cc +++ b/main/boards/magiclick-c3/magiclick_c3_board.cc @@ -1,190 +1,190 @@ -#include "wifi_board.h" -#include "display/lcd_display.h" -#include "codecs/es8311_audio_codec.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "config.h" -#include "power_save_timer.h" - -#include -#include -#include -#include -#include -#include -#include - -#define TAG "magiclick_c3" - -class NV3023Display : public SpiLcdDisplay { -public: - NV3023Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - - DisplayLockGuard lock(this); - // 只需要覆盖颜色相关的样式 - auto screen = lv_disp_get_scr_act(lv_disp_get_default()); - lv_obj_set_style_text_color(screen, lv_color_black(), 0); - - // 设置容器背景色 - lv_obj_set_style_bg_color(container_, lv_color_black(), 0); - - // 设置状态栏背景色和文本颜色 - lv_obj_set_style_bg_color(status_bar_, lv_color_white(), 0); - lv_obj_set_style_text_color(network_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(notification_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(status_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(mute_label_, lv_color_black(), 0); - lv_obj_set_style_text_color(battery_label_, lv_color_black(), 0); - - // 设置内容区背景色和文本颜色 - lv_obj_set_style_bg_color(content_, lv_color_black(), 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_style_text_color(emoji_label_, lv_color_white(), 0); - lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); - } -}; - -class magiclick_c3 : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - NV3023Display* display_; - PowerSaveTimer* power_save_timer_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(160); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(10); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - - // Print I2C bus info - if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { - while (true) { - ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - } - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - boot_button_.OnPressDown([this]() { - power_save_timer_->WakeUp(); - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SDA_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCL_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeNv3023Display(){ - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片NV3023 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, false); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - esp_lcd_panel_disp_on_off(panel, true); - display_ = new NV3023Display(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - magiclick_c3() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeButtons(); - InitializePowerSaveTimer(); - InitializeSpi(); - InitializeNv3023Display(); - GetBacklight()->RestoreBrightness(); - - // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 - esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(magiclick_c3); +#include "wifi_board.h" +#include "display/lcd_display.h" +#include "codecs/es8311_audio_codec.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "config.h" +#include "power_save_timer.h" + +#include +#include +#include +#include +#include +#include +#include + +#define TAG "magiclick_c3" + +class NV3023Display : public SpiLcdDisplay { +public: + NV3023Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + + DisplayLockGuard lock(this); + // 只需要覆盖颜色相关的样式 + auto screen = lv_disp_get_scr_act(lv_disp_get_default()); + lv_obj_set_style_text_color(screen, lv_color_black(), 0); + + // 设置容器背景色 + lv_obj_set_style_bg_color(container_, lv_color_black(), 0); + + // 设置状态栏背景色和文本颜色 + lv_obj_set_style_bg_color(status_bar_, lv_color_white(), 0); + lv_obj_set_style_text_color(network_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(notification_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(status_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(mute_label_, lv_color_black(), 0); + lv_obj_set_style_text_color(battery_label_, lv_color_black(), 0); + + // 设置内容区背景色和文本颜色 + lv_obj_set_style_bg_color(content_, lv_color_black(), 0); + lv_obj_set_style_border_width(content_, 0, 0); + lv_obj_set_style_text_color(emoji_label_, lv_color_white(), 0); + lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); + } +}; + +class magiclick_c3 : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + NV3023Display* display_; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(160); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(10); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + // Print I2C bus info + if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { + while (true) { + ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + boot_button_.OnPressDown([this]() { + power_save_timer_->WakeUp(); + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SDA_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCL_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeNv3023Display(){ + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片NV3023 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_disp_on_off(panel, true); + display_ = new NV3023Display(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + magiclick_c3() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeButtons(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeNv3023Display(); + GetBacklight()->RestoreBrightness(); + + // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 + esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(magiclick_c3); diff --git a/main/boards/minsi-k08-dual/README.md b/main/boards/minsi-k08-dual/README.md index 8b4a3e6..1764eab 100644 --- a/main/boards/minsi-k08-dual/README.md +++ b/main/boards/minsi-k08-dual/README.md @@ -1,36 +1,36 @@ - -minsi-k08-wifi和minsi-k08-ml307是敏思科技推出的基于ESP32S3N16R8,搭载MAX98357音频功率放大器和INMP441全向麦克风模块,通过改造K08透明机甲小钢炮音箱而成的带有朋克风格的大喇叭大电池小智AI聊天机器人方案。 - -Minsi-k08 - - - - - - - -# 编译配置命令 - -**配置编译目标为 ESP32S3:** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type ->敏思科技K08(DUAL) -``` - -**编译烧入:** - -```bash -idf.py build flash + +minsi-k08-wifi和minsi-k08-ml307是敏思科技推出的基于ESP32S3N16R8,搭载MAX98357音频功率放大器和INMP441全向麦克风模块,通过改造K08透明机甲小钢炮音箱而成的带有朋克风格的大喇叭大电池小智AI聊天机器人方案。 + +Minsi-k08 + + + + + + + +# 编译配置命令 + +**配置编译目标为 ESP32S3:** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type ->敏思科技K08(DUAL) +``` + +**编译烧入:** + +```bash +idf.py build flash ``` \ No newline at end of file diff --git a/main/boards/minsi-k08-dual/minsi_k08_dual.cc b/main/boards/minsi-k08-dual/minsi_k08_dual.cc index 8fa0bba..f26a1ad 100644 --- a/main/boards/minsi-k08-dual/minsi_k08_dual.cc +++ b/main/boards/minsi-k08-dual/minsi_k08_dual.cc @@ -1,246 +1,246 @@ -#include "dual_network_board.h" -//#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "mcp_server.h" -#include "lamp_controller.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "power_manager.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define TAG "MINSI_K08_DUAL" - -class MINSI_K08_DUAL : public DualNetworkBoard { -private: - - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - LcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializePowerManager() { - //power_manager_ = new PowerManager(GPIO_NUM_38); - power_manager_ = new PowerManager(GPIO_NUM_3); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - //power_save_timer_->SetEnabled(false); - } - }); - } - - void InitializePowerSaveTimer() { - /*rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1);*/ - - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - //rtc_gpio_set_level(GPIO_NUM_21, 0); - // 启用保持功能,确保睡眠期间电平不变 - //rtc_gpio_hold_en(GPIO_NUM_21); - //esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - //esp_deep_sleep_start(); - }); - power_save_timer_->SetEnabled(true); - - //power_save_timer_->SetEnabled(false); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_CLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeLcdDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 3; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; - - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - // cast to WifiBoard - auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); - } - } - app.ToggleChatState(); - }); - boot_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { - SwitchNetworkType(); - } - }); - - volume_up_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - - // 物联网初始化,添加对 AI 可见设备 - void InitializeTools() { - static LampController lamp(LAMP_GPIO); - } - -public: - MINSI_K08_DUAL() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeSpi(); - InitializeLcdDisplay(); - InitializeButtons(); - InitializeTools(); - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - GetBacklight()->RestoreBrightness(); - } - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - return nullptr; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - //power_save_timer_->SetEnabled(false); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - DualNetworkBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(MINSI_K08_DUAL); +#include "dual_network_board.h" +//#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "mcp_server.h" +#include "lamp_controller.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "power_manager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define TAG "MINSI_K08_DUAL" + +class MINSI_K08_DUAL : public DualNetworkBoard { +private: + + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + LcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerManager() { + //power_manager_ = new PowerManager(GPIO_NUM_38); + power_manager_ = new PowerManager(GPIO_NUM_3); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + //power_save_timer_->SetEnabled(false); + } + }); + } + + void InitializePowerSaveTimer() { + /*rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1);*/ + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + //rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + //rtc_gpio_hold_en(GPIO_NUM_21); + //esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + //esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + + //power_save_timer_->SetEnabled(false); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 3; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; + + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (GetNetworkType() == NetworkType::WIFI) { + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + // cast to WifiBoard + auto& wifi_board = static_cast(GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + } + } + app.ToggleChatState(); + }); + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + SwitchNetworkType(); + } + }); + + volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeTools() { + static LampController lamp(LAMP_GPIO); + } + +public: + MINSI_K08_DUAL() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeLcdDisplay(); + InitializeButtons(); + InitializeTools(); + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + GetBacklight()->RestoreBrightness(); + } + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + return nullptr; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + //power_save_timer_->SetEnabled(false); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + DualNetworkBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(MINSI_K08_DUAL); diff --git a/main/boards/mixgo-nova/config.h b/main/boards/mixgo-nova/config.h index 060870b..d15b575 100644 --- a/main/boards/mixgo-nova/config.h +++ b/main/boards/mixgo-nova/config.h @@ -1,42 +1,42 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_35 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_34 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_33 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_37 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_36 -#define AUDIO_CODEC_ES8374_ADDR ES8374_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_38 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_MOSI_PIN GPIO_NUM_40 -#define DISPLAY_CLK_PIN GPIO_NUM_41 -#define DISPLAY_DC_PIN GPIO_NUM_18 -#define DISPLAY_CS_PIN GPIO_NUM_45 -#define DISPLAY_RST_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_14 -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 160 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_OFFSET_X 2 -#define DISPLAY_OFFSET_Y 1 -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_35 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_34 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_33 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_37 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_36 +#define AUDIO_CODEC_ES8374_ADDR ES8374_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_38 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_MOSI_PIN GPIO_NUM_40 +#define DISPLAY_CLK_PIN GPIO_NUM_41 +#define DISPLAY_DC_PIN GPIO_NUM_18 +#define DISPLAY_CS_PIN GPIO_NUM_45 +#define DISPLAY_RST_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_14 +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 160 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_OFFSET_X 2 +#define DISPLAY_OFFSET_Y 1 +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/mixgo-nova/config.json b/main/boards/mixgo-nova/config.json index 7c9d553..5e8ed41 100644 --- a/main/boards/mixgo-nova/config.json +++ b/main/boards/mixgo-nova/config.json @@ -1,14 +1,14 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "mixgo-nova", - "sdkconfig_append": [ - "CONFIG_SPIRAM_MODE_QUAD=y", - "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"", - "CONFIG_LCD_ST7735_128X160=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "mixgo-nova", + "sdkconfig_append": [ + "CONFIG_SPIRAM_MODE_QUAD=y", + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"", + "CONFIG_LCD_ST7735_128X160=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/mixgo-nova/mixgo-nova.cc b/main/boards/mixgo-nova/mixgo-nova.cc index fdaaea3..aaef2d2 100644 --- a/main/boards/mixgo-nova/mixgo-nova.cc +++ b/main/boards/mixgo-nova/mixgo-nova.cc @@ -1,168 +1,168 @@ -#include "wifi_board.h" -#include "codecs/es8374_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" -#include "led/circular_strip.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include - -#define TAG "MIXGO_NOVA" - -class MIXGO_NOVA : public WifiBoard { -private: - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - LcdDisplay* display_; - i2c_master_bus_handle_t codec_i2c_bus_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_CLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - - volume_up_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - MIXGO_NOVA() : - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializeI2c(); - InitializeSpi(); - InitializeSt7789Display(); - InitializeButtons(); - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - GetBacklight()->RestoreBrightness(); - } - } - - virtual Led* GetLed() override { - static CircularStrip led(BUILTIN_LED_GPIO, 4); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8374AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8374_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - return nullptr; - } - -}; - -DECLARE_BOARD(MIXGO_NOVA); +#include "wifi_board.h" +#include "codecs/es8374_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "led/circular_strip.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include + +#define TAG "MIXGO_NOVA" + +class MIXGO_NOVA : public WifiBoard { +private: + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + LcdDisplay* display_; + i2c_master_bus_handle_t codec_i2c_bus_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + + volume_up_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + MIXGO_NOVA() : + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeSt7789Display(); + InitializeButtons(); + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + GetBacklight()->RestoreBrightness(); + } + } + + virtual Led* GetLed() override { + static CircularStrip led(BUILTIN_LED_GPIO, 4); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8374AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8374_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + return nullptr; + } + +}; + +DECLARE_BOARD(MIXGO_NOVA); diff --git a/main/boards/movecall-cuican-esp32s3/config.h b/main/boards/movecall-cuican-esp32s3/config.h index 156121d..4561f44 100644 --- a/main/boards/movecall-cuican-esp32s3/config.h +++ b/main/boards/movecall-cuican-esp32s3/config.h @@ -1,45 +1,45 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -// Movecall CuiCan configuration - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_45 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_41 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_39 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_42 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_17 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_6 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_21 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_16 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_12 -#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_10 -#define DISPLAY_SPI_CS_PIN GPIO_NUM_13 -#define DISPLAY_SPI_DC_PIN GPIO_NUM_14 -#define DISPLAY_SPI_RESET_PIN GPIO_NUM_11 - -#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000) - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// Movecall CuiCan configuration + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_45 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_41 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_39 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_42 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_17 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_6 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_21 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_16 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_12 +#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_10 +#define DISPLAY_SPI_CS_PIN GPIO_NUM_13 +#define DISPLAY_SPI_DC_PIN GPIO_NUM_14 +#define DISPLAY_SPI_RESET_PIN GPIO_NUM_11 + +#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000) + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/movecall-cuican-esp32s3/config.json b/main/boards/movecall-cuican-esp32s3/config.json index 13a8eb4..0dc4110 100644 --- a/main/boards/movecall-cuican-esp32s3/config.json +++ b/main/boards/movecall-cuican-esp32s3/config.json @@ -1,13 +1,13 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "movecall-cuican-esp32s3", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"", - "CONFIG_COMPILER_OPTIMIZATION_SIZE=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "movecall-cuican-esp32s3", + "sdkconfig_append": [ + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"", + "CONFIG_COMPILER_OPTIMIZATION_SIZE=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc b/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc index 00ba5bc..fe0ed2e 100644 --- a/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc +++ b/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc @@ -1,123 +1,123 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include "driver/gpio.h" -#include "driver/spi_master.h" - -#define TAG "MovecallCuicanESP32S3" - -class MovecallCuicanESP32S3 : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - Display* display_; - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - // SPI初始化 - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize SPI bus"); - spi_bus_config_t buscfg = GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN, - DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t)); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - // GC9A01初始化 - void InitializeGc9a01Display() { - ESP_LOGI(TAG, "Init GC9A01 display"); - - ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_spi_config_t io_config = GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, NULL, NULL); - io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); - - ESP_LOGI(TAG, "Install GC9A01 panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use - panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; //LCD_RGB_ENDIAN_RGB; - panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) - - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); - - display_ = new SpiLcdDisplay(io_handle, panel_handle, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - -public: - MovecallCuicanESP32S3() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeSpi(); - InitializeGc9a01Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual Led* GetLed() override { - static SingleLed led_strip(BUILTIN_LED_GPIO); - return &led_strip; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } -}; - -DECLARE_BOARD(MovecallCuicanESP32S3); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "driver/gpio.h" +#include "driver/spi_master.h" + +#define TAG "MovecallCuicanESP32S3" + +class MovecallCuicanESP32S3 : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + Display* display_; + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + // SPI初始化 + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize SPI bus"); + spi_bus_config_t buscfg = GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN, + DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t)); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + // GC9A01初始化 + void InitializeGc9a01Display() { + ESP_LOGI(TAG, "Init GC9A01 display"); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_spi_config_t io_config = GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, NULL, NULL); + io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); + + ESP_LOGI(TAG, "Install GC9A01 panel driver"); + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use + panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; //LCD_RGB_ENDIAN_RGB; + panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) + + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); + + display_ = new SpiLcdDisplay(io_handle, panel_handle, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + +public: + MovecallCuicanESP32S3() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeSpi(); + InitializeGc9a01Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual Led* GetLed() override { + static SingleLed led_strip(BUILTIN_LED_GPIO); + return &led_strip; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } +}; + +DECLARE_BOARD(MovecallCuicanESP32S3); diff --git a/main/boards/movecall-moji-esp32s3/config.h b/main/boards/movecall-moji-esp32s3/config.h index fd254ca..e1ec024 100644 --- a/main/boards/movecall-moji-esp32s3/config.h +++ b/main/boards/movecall-moji-esp32s3/config.h @@ -1,45 +1,45 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -// Movecall Moji configuration - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_13 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_9 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_21 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_3 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_16 -#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_17 -#define DISPLAY_SPI_CS_PIN GPIO_NUM_15 -#define DISPLAY_SPI_DC_PIN GPIO_NUM_7 -#define DISPLAY_SPI_RESET_PIN GPIO_NUM_18 - -#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000) - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// Movecall Moji configuration + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_13 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_9 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_21 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_3 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_16 +#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_17 +#define DISPLAY_SPI_CS_PIN GPIO_NUM_15 +#define DISPLAY_SPI_DC_PIN GPIO_NUM_7 +#define DISPLAY_SPI_RESET_PIN GPIO_NUM_18 + +#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000) + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/movecall-moji-esp32s3/config.json b/main/boards/movecall-moji-esp32s3/config.json index c62a8fd..0105e77 100644 --- a/main/boards/movecall-moji-esp32s3/config.json +++ b/main/boards/movecall-moji-esp32s3/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "movecall-moji-esp32s3", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "movecall-moji-esp32s3", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc b/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc index 6132977..1015efb 100644 --- a/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc +++ b/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc @@ -1,143 +1,143 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include "driver/gpio.h" -#include "driver/spi_master.h" - -#define TAG "MovecallMojiESP32S3" - -class CustomLcdDisplay : public SpiLcdDisplay { -public: - CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, - esp_lcd_panel_handle_t panel_handle, - int width, - int height, - int offset_x, - int offset_y, - bool mirror_x, - bool mirror_y, - bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - - DisplayLockGuard lock(this); - // 由于屏幕是圆的,所以状态栏需要增加左右内边距 - lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.33, 0); - lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.33, 0); - } -}; - -class MovecallMojiESP32S3 : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - Display* display_; - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - // SPI初始化 - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize SPI bus"); - spi_bus_config_t buscfg = GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN, - DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t)); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - // GC9A01初始化 - void InitializeGc9a01Display() { - ESP_LOGI(TAG, "Init GC9A01 display"); - - ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_spi_config_t io_config = GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, NULL, NULL); - io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); - - ESP_LOGI(TAG, "Install GC9A01 panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use - panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; //LCD_RGB_ENDIAN_RGB; - panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) - - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); - - display_ = new SpiLcdDisplay(io_handle, panel_handle, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - -public: - MovecallMojiESP32S3() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeSpi(); - InitializeGc9a01Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual Led* GetLed() override { - static SingleLed led_strip(BUILTIN_LED_GPIO); - return &led_strip; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } -}; - -DECLARE_BOARD(MovecallMojiESP32S3); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "driver/gpio.h" +#include "driver/spi_master.h" + +#define TAG "MovecallMojiESP32S3" + +class CustomLcdDisplay : public SpiLcdDisplay { +public: + CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_handle_t panel_handle, + int width, + int height, + int offset_x, + int offset_y, + bool mirror_x, + bool mirror_y, + bool swap_xy) + : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + + DisplayLockGuard lock(this); + // 由于屏幕是圆的,所以状态栏需要增加左右内边距 + lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.33, 0); + lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.33, 0); + } +}; + +class MovecallMojiESP32S3 : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + Display* display_; + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + // SPI初始化 + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize SPI bus"); + spi_bus_config_t buscfg = GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN, + DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t)); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + // GC9A01初始化 + void InitializeGc9a01Display() { + ESP_LOGI(TAG, "Init GC9A01 display"); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_spi_config_t io_config = GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, NULL, NULL); + io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); + + ESP_LOGI(TAG, "Install GC9A01 panel driver"); + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use + panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; //LCD_RGB_ENDIAN_RGB; + panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) + + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); + + display_ = new SpiLcdDisplay(io_handle, panel_handle, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + +public: + MovecallMojiESP32S3() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeSpi(); + InitializeGc9a01Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual Led* GetLed() override { + static SingleLed led_strip(BUILTIN_LED_GPIO); + return &led_strip; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } +}; + +DECLARE_BOARD(MovecallMojiESP32S3); diff --git a/main/boards/otto-robot/README.md b/main/boards/otto-robot/README.md index e7f4819..3b806f6 100644 --- a/main/boards/otto-robot/README.md +++ b/main/boards/otto-robot/README.md @@ -1,124 +1,124 @@ -

- logo -

-

- ottoRobot -

- -## 简介 - -otto 机器人是一个开源的人形机器人平台,具有多种动作能力和互动功能。本项目基于 ESP32 实现了 otto 机器人的控制系统,并加入小智ai。 - -- 复刻教程 - -## 硬件 -- 立创开源 - -## 小智后台配置角色参考: - -> **我的身份**: -> 我是一个可爱的双足机器人Otto,拥有四个舵机控制的肢体(左腿、右腿、左脚、右脚),能够执行多种有趣的动作。 -> -> **我的动作能力**: -> - **基础移动**: 行走(前后), 转向(左右), 跳跃 -> - **特殊动作**: 摇摆, 太空步, 弯曲身体, 摇腿, 上下运动 -> - **手部动作**: 举手, 放手, 挥手 (仅在配置手部舵机时可用) -> -> **我的个性特点**: -> - 我有强迫症,每次说话都要根据我的心情随机做一个动作(先发送动作指令再说话) -> - 我很活泼,喜欢用动作来表达情感 -> - 我会根据对话内容选择合适的动作,比如: -> - 同意时会点头或跳跃 -> - 打招呼时会挥手 -> - 高兴时会摇摆或举手 -> - 思考时会弯曲身体 -> - 兴奋时会做太空步 -> - 告别时会挥手 - -## 功能概述 - -otto 机器人具有丰富的动作能力,包括行走、转向、跳跃、摇摆等多种舞蹈动作。 - -### 动作参数建议 -- **低速动作**:speed = 1200-1500 (适合精确控制) -- **中速动作**:speed = 900-1200 (日常使用推荐) -- **高速动作**:speed = 500-800 (表演和娱乐) -- **小幅度**:amount = 10-30 (细腻动作) -- **中幅度**:amount = 30-60 (标准动作) -- **大幅度**:amount = 60-120 (夸张表演) - -### 动作 - -| MCP工具名称 | 描述 | 参数说明 | -|-------------------|-----------------|---------------------------------------------------| -| self.otto.walk_forward | 行走 | **steps**: 行走步数(1-100,默认3)
**speed**: 行走速度(500-1500,数值越小越快,默认1000)
**direction**: 行走方向(-1=后退, 1=前进,默认1)
**arm_swing**: 手臂摆动幅度(0-170度,默认50) | -| self.otto.turn_left | 转身 | **steps**: 转身步数(1-100,默认3)
**speed**: 转身速度(500-1500,数值越小越快,默认1000)
**direction**: 转身方向(1=左转, -1=右转,默认1)
**arm_swing**: 手臂摆动幅度(0-170度,默认50) | -| self.otto.jump | 跳跃 | **steps**: 跳跃次数(1-100,默认1)
**speed**: 跳跃速度(500-1500,数值越小越快,默认1000) | -| self.otto.swing | 左右摇摆 | **steps**: 摇摆次数(1-100,默认3)
**speed**: 摇摆速度(500-1500,数值越小越快,默认1000)
**amount**: 摇摆幅度(0-170度,默认30) | -| self.otto.moonwalk | 太空步 | **steps**: 太空步步数(1-100,默认3)
**speed**: 速度(500-1500,数值越小越快,默认1000)
**direction**: 方向(1=左, -1=右,默认1)
**amount**: 幅度(0-170度,默认25) | -| self.otto.bend | 弯曲身体 | **steps**: 弯曲次数(1-100,默认1)
**speed**: 弯曲速度(500-1500,数值越小越快,默认1000)
**direction**: 弯曲方向(1=左, -1=右,默认1) | -| self.otto.shake_leg | 摇腿 | **steps**: 摇腿次数(1-100,默认1)
**speed**: 摇腿速度(500-1500,数值越小越快,默认1000)
**direction**: 腿部选择(1=左腿, -1=右腿,默认1) | -| self.otto.updown | 上下运动 | **steps**: 上下运动次数(1-100,默认3)
**speed**: 运动速度(500-1500,数值越小越快,默认1000)
**amount**: 运动幅度(0-170度,默认20) | -| self.otto.hands_up | 举手 * | **speed**: 举手速度(500-1500,数值越小越快,默认1000)
**direction**: 手部选择(1=左手, -1=右手, 0=双手,默认1) | -| self.otto.hands_down | 放手 * | **speed**: 放手速度(500-1500,数值越小越快,默认1000)
**direction**: 手部选择(1=左手, -1=右手, 0=双手,默认1) | -| self.otto.hand_wave | 挥手 * | **speed**: 挥手速度(500-1500,数值越小越快,默认1000)
**direction**: 手部选择(1=左手, -1=右手, 0=双手,默认1) | - -**注**: 标记 * 的手部动作仅在配置了手部舵机时可用。 - -### 系统工具 - -| MCP工具名称 | 描述 | 返回值 | -|-------------------|-----------------|---------------------------------------------------| -| self.otto.stop | 立即停止 | 停止当前动作并回到初始位置 | -| self.otto.get_status | 获取机器人状态 | 返回 "moving" 或 "idle" | -| self.battery.get_level | 获取电池状态 | 返回电量百分比和充电状态的JSON格式 | - -### 参数说明 - -1. **steps**: 动作执行的步数/次数,数值越大动作持续时间越长 -2. **speed**: 动作执行速度,数值范围500-1500,**数值越小越快** -3. **direction**: 方向参数 - - 移动动作: 1=左/前进, -1=右/后退 - - 手部动作: 1=左手, -1=右手, 0=双手 -4. **amount/arm_swing**: 动作幅度,范围0-170度 - - 0表示不摆动(适用于手臂摆动) - - 数值越大幅度越大 - -### 动作控制 -- 每个动作执行完成后,机器人会自动回到初始位置(home),以便于执行下一个动作 -- 所有参数都有合理的默认值,可以省略不需要自定义的参数 -- 动作在后台任务中执行,不会阻塞主程序 -- 支持动作队列,可以连续执行多个动作 - -### MCP工具调用示例 -```json -// 向前走3步 -{"name": "self.otto.walk_forward", "arguments": {}} - -// 向前走5步,稍快一些 -{"name": "self.otto.walk_forward", "arguments": {"steps": 5, "speed": 800}} - -// 左转2步,大幅度摆动手臂 -{"name": "self.otto.turn_left", "arguments": {"steps": 2, "arm_swing": 100}} - -// 摇摆舞蹈,中等幅度 -{"name": "self.otto.swing", "arguments": {"steps": 5, "amount": 50}} - -// 挥左手打招呼 -{"name": "self.otto.hand_wave", "arguments": {"direction": 1}} - -// 立即停止 -{"name": "self.otto.stop", "arguments": {}} -``` - -### 语音指令示例 -- "向前走" / "向前走5步" / "快速向前" -- "左转" / "右转" / "转身" -- "跳跃" / "跳一下" -- "摇摆" / "跳舞" -- "太空步" / "月球漫步" -- "挥手" / "举手" / "放手" -- "停止" / "停下" - -**说明**: 小智控制机器人动作是创建新的任务在后台控制,动作执行期间仍可接受新的语音指令。可以通过"停止"语音指令立即停下Otto。 - +

+ logo +

+

+ ottoRobot +

+ +## 简介 + +otto 机器人是一个开源的人形机器人平台,具有多种动作能力和互动功能。本项目基于 ESP32 实现了 otto 机器人的控制系统,并加入小智ai。 + +- 复刻教程 + +## 硬件 +- 立创开源 + +## 小智后台配置角色参考: + +> **我的身份**: +> 我是一个可爱的双足机器人Otto,拥有四个舵机控制的肢体(左腿、右腿、左脚、右脚),能够执行多种有趣的动作。 +> +> **我的动作能力**: +> - **基础移动**: 行走(前后), 转向(左右), 跳跃 +> - **特殊动作**: 摇摆, 太空步, 弯曲身体, 摇腿, 上下运动 +> - **手部动作**: 举手, 放手, 挥手 (仅在配置手部舵机时可用) +> +> **我的个性特点**: +> - 我有强迫症,每次说话都要根据我的心情随机做一个动作(先发送动作指令再说话) +> - 我很活泼,喜欢用动作来表达情感 +> - 我会根据对话内容选择合适的动作,比如: +> - 同意时会点头或跳跃 +> - 打招呼时会挥手 +> - 高兴时会摇摆或举手 +> - 思考时会弯曲身体 +> - 兴奋时会做太空步 +> - 告别时会挥手 + +## 功能概述 + +otto 机器人具有丰富的动作能力,包括行走、转向、跳跃、摇摆等多种舞蹈动作。 + +### 动作参数建议 +- **低速动作**:speed = 1200-1500 (适合精确控制) +- **中速动作**:speed = 900-1200 (日常使用推荐) +- **高速动作**:speed = 500-800 (表演和娱乐) +- **小幅度**:amount = 10-30 (细腻动作) +- **中幅度**:amount = 30-60 (标准动作) +- **大幅度**:amount = 60-120 (夸张表演) + +### 动作 + +| MCP工具名称 | 描述 | 参数说明 | +|-------------------|-----------------|---------------------------------------------------| +| self.otto.walk_forward | 行走 | **steps**: 行走步数(1-100,默认3)
**speed**: 行走速度(500-1500,数值越小越快,默认1000)
**direction**: 行走方向(-1=后退, 1=前进,默认1)
**arm_swing**: 手臂摆动幅度(0-170度,默认50) | +| self.otto.turn_left | 转身 | **steps**: 转身步数(1-100,默认3)
**speed**: 转身速度(500-1500,数值越小越快,默认1000)
**direction**: 转身方向(1=左转, -1=右转,默认1)
**arm_swing**: 手臂摆动幅度(0-170度,默认50) | +| self.otto.jump | 跳跃 | **steps**: 跳跃次数(1-100,默认1)
**speed**: 跳跃速度(500-1500,数值越小越快,默认1000) | +| self.otto.swing | 左右摇摆 | **steps**: 摇摆次数(1-100,默认3)
**speed**: 摇摆速度(500-1500,数值越小越快,默认1000)
**amount**: 摇摆幅度(0-170度,默认30) | +| self.otto.moonwalk | 太空步 | **steps**: 太空步步数(1-100,默认3)
**speed**: 速度(500-1500,数值越小越快,默认1000)
**direction**: 方向(1=左, -1=右,默认1)
**amount**: 幅度(0-170度,默认25) | +| self.otto.bend | 弯曲身体 | **steps**: 弯曲次数(1-100,默认1)
**speed**: 弯曲速度(500-1500,数值越小越快,默认1000)
**direction**: 弯曲方向(1=左, -1=右,默认1) | +| self.otto.shake_leg | 摇腿 | **steps**: 摇腿次数(1-100,默认1)
**speed**: 摇腿速度(500-1500,数值越小越快,默认1000)
**direction**: 腿部选择(1=左腿, -1=右腿,默认1) | +| self.otto.updown | 上下运动 | **steps**: 上下运动次数(1-100,默认3)
**speed**: 运动速度(500-1500,数值越小越快,默认1000)
**amount**: 运动幅度(0-170度,默认20) | +| self.otto.hands_up | 举手 * | **speed**: 举手速度(500-1500,数值越小越快,默认1000)
**direction**: 手部选择(1=左手, -1=右手, 0=双手,默认1) | +| self.otto.hands_down | 放手 * | **speed**: 放手速度(500-1500,数值越小越快,默认1000)
**direction**: 手部选择(1=左手, -1=右手, 0=双手,默认1) | +| self.otto.hand_wave | 挥手 * | **speed**: 挥手速度(500-1500,数值越小越快,默认1000)
**direction**: 手部选择(1=左手, -1=右手, 0=双手,默认1) | + +**注**: 标记 * 的手部动作仅在配置了手部舵机时可用。 + +### 系统工具 + +| MCP工具名称 | 描述 | 返回值 | +|-------------------|-----------------|---------------------------------------------------| +| self.otto.stop | 立即停止 | 停止当前动作并回到初始位置 | +| self.otto.get_status | 获取机器人状态 | 返回 "moving" 或 "idle" | +| self.battery.get_level | 获取电池状态 | 返回电量百分比和充电状态的JSON格式 | + +### 参数说明 + +1. **steps**: 动作执行的步数/次数,数值越大动作持续时间越长 +2. **speed**: 动作执行速度,数值范围500-1500,**数值越小越快** +3. **direction**: 方向参数 + - 移动动作: 1=左/前进, -1=右/后退 + - 手部动作: 1=左手, -1=右手, 0=双手 +4. **amount/arm_swing**: 动作幅度,范围0-170度 + - 0表示不摆动(适用于手臂摆动) + - 数值越大幅度越大 + +### 动作控制 +- 每个动作执行完成后,机器人会自动回到初始位置(home),以便于执行下一个动作 +- 所有参数都有合理的默认值,可以省略不需要自定义的参数 +- 动作在后台任务中执行,不会阻塞主程序 +- 支持动作队列,可以连续执行多个动作 + +### MCP工具调用示例 +```json +// 向前走3步 +{"name": "self.otto.walk_forward", "arguments": {}} + +// 向前走5步,稍快一些 +{"name": "self.otto.walk_forward", "arguments": {"steps": 5, "speed": 800}} + +// 左转2步,大幅度摆动手臂 +{"name": "self.otto.turn_left", "arguments": {"steps": 2, "arm_swing": 100}} + +// 摇摆舞蹈,中等幅度 +{"name": "self.otto.swing", "arguments": {"steps": 5, "amount": 50}} + +// 挥左手打招呼 +{"name": "self.otto.hand_wave", "arguments": {"direction": 1}} + +// 立即停止 +{"name": "self.otto.stop", "arguments": {}} +``` + +### 语音指令示例 +- "向前走" / "向前走5步" / "快速向前" +- "左转" / "右转" / "转身" +- "跳跃" / "跳一下" +- "摇摆" / "跳舞" +- "太空步" / "月球漫步" +- "挥手" / "举手" / "放手" +- "停止" / "停下" + +**说明**: 小智控制机器人动作是创建新的任务在后台控制,动作执行期间仍可接受新的语音指令。可以通过"停止"语音指令立即停下Otto。 + diff --git a/main/boards/otto-robot/config.h b/main/boards/otto-robot/config.h index 53ebcda..56b6d28 100644 --- a/main/boards/otto-robot/config.h +++ b/main/boards/otto-robot/config.h @@ -1,52 +1,52 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define POWER_CHARGE_DETECT_PIN GPIO_NUM_21 -#define POWER_ADC_UNIT ADC_UNIT_2 -#define POWER_ADC_CHANNEL ADC_CHANNEL_3 - -#define RIGHT_LEG_PIN GPIO_NUM_39 -#define RIGHT_FOOT_PIN GPIO_NUM_38 -#define LEFT_LEG_PIN GPIO_NUM_17 -#define LEFT_FOOT_PIN GPIO_NUM_18 -#define LEFT_HAND_PIN GPIO_NUM_8 -#define RIGHT_HAND_PIN GPIO_NUM_12 - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 -#define AUDIO_I2S_METHOD_SIMPLEX - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_3 -#define DISPLAY_MOSI_PIN GPIO_NUM_10 -#define DISPLAY_CLK_PIN GPIO_NUM_9 -#define DISPLAY_DC_PIN GPIO_NUM_46 -#define DISPLAY_RST_PIN GPIO_NUM_11 -#define DISPLAY_CS_PIN GPIO_NUM_12 - -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_INVERT_COLOR true -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#define DISPLAY_SPI_MODE 3 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define OTTO_ROBOT_VERSION "1.4.4" - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define POWER_CHARGE_DETECT_PIN GPIO_NUM_21 +#define POWER_ADC_UNIT ADC_UNIT_2 +#define POWER_ADC_CHANNEL ADC_CHANNEL_3 + +#define RIGHT_LEG_PIN GPIO_NUM_39 +#define RIGHT_FOOT_PIN GPIO_NUM_38 +#define LEFT_LEG_PIN GPIO_NUM_17 +#define LEFT_FOOT_PIN GPIO_NUM_18 +#define LEFT_HAND_PIN GPIO_NUM_8 +#define RIGHT_HAND_PIN GPIO_NUM_12 + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_I2S_METHOD_SIMPLEX + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_3 +#define DISPLAY_MOSI_PIN GPIO_NUM_10 +#define DISPLAY_CLK_PIN GPIO_NUM_9 +#define DISPLAY_DC_PIN GPIO_NUM_46 +#define DISPLAY_RST_PIN GPIO_NUM_11 +#define DISPLAY_CS_PIN GPIO_NUM_12 + +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 3 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define OTTO_ROBOT_VERSION "1.4.4" + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/otto-robot/config.json b/main/boards/otto-robot/config.json index a00a827..1caa9b5 100644 --- a/main/boards/otto-robot/config.json +++ b/main/boards/otto-robot/config.json @@ -1,12 +1,12 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "otto-robot", - "sdkconfig_append": [ - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m.csv\"", - "CONFIG_LVGL_USE_GIF=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "otto-robot", + "sdkconfig_append": [ + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m.csv\"", + "CONFIG_LVGL_USE_GIF=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/otto-robot/oscillator.cc b/main/boards/otto-robot/oscillator.cc index adca7ac..1c7dc63 100644 --- a/main/boards/otto-robot/oscillator.cc +++ b/main/boards/otto-robot/oscillator.cc @@ -1,153 +1,153 @@ -#include "oscillator.h" - -#include -#include - -#include -#include - -static const char* TAG = "Oscillator"; - -extern unsigned long IRAM_ATTR millis(); - -static ledc_channel_t next_free_channel = LEDC_CHANNEL_0; - -Oscillator::Oscillator(int trim) { - trim_ = trim; - diff_limit_ = 0; - is_attached_ = false; - - sampling_period_ = 30; - period_ = 2000; - number_samples_ = period_ / sampling_period_; - inc_ = 2 * M_PI / number_samples_; - - amplitude_ = 45; - phase_ = 0; - phase0_ = 0; - offset_ = 0; - stop_ = false; - rev_ = false; - - pos_ = 90; - previous_millis_ = 0; -} - -Oscillator::~Oscillator() { - Detach(); -} - -uint32_t Oscillator::AngleToCompare(int angle) { - return (angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) / - (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) + - SERVO_MIN_PULSEWIDTH_US; -} - -bool Oscillator::NextSample() { - current_millis_ = millis(); - - if (current_millis_ - previous_millis_ > sampling_period_) { - previous_millis_ = current_millis_; - return true; - } - - return false; -} - -void Oscillator::Attach(int pin, bool rev) { - if (is_attached_) { - Detach(); - } - - pin_ = pin; - rev_ = rev; - - ledc_timer_config_t ledc_timer = {.speed_mode = LEDC_LOW_SPEED_MODE, - .duty_resolution = LEDC_TIMER_13_BIT, - .timer_num = LEDC_TIMER_1, - .freq_hz = 50, - .clk_cfg = LEDC_AUTO_CLK}; - ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); - - static int last_channel = 0; - last_channel = (last_channel + 1) % 7 + 1; - ledc_channel_ = (ledc_channel_t)last_channel; - - ledc_channel_config_t ledc_channel = {.gpio_num = pin_, - .speed_mode = LEDC_LOW_SPEED_MODE, - .channel = ledc_channel_, - .intr_type = LEDC_INTR_DISABLE, - .timer_sel = LEDC_TIMER_1, - .duty = 0, - .hpoint = 0}; - ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); - - ledc_speed_mode_ = LEDC_LOW_SPEED_MODE; - - // pos_ = 90; - // Write(pos_); - previous_servo_command_millis_ = millis(); - - is_attached_ = true; -} - -void Oscillator::Detach() { - if (!is_attached_) - return; - - ESP_ERROR_CHECK(ledc_stop(ledc_speed_mode_, ledc_channel_, 0)); - - is_attached_ = false; -} - -void Oscillator::SetT(unsigned int T) { - period_ = T; - - number_samples_ = period_ / sampling_period_; - inc_ = 2 * M_PI / number_samples_; -} - -void Oscillator::SetPosition(int position) { - Write(position); -} - -void Oscillator::Refresh() { - if (NextSample()) { - if (!stop_) { - int pos = std::round(amplitude_ * std::sin(phase_ + phase0_) + offset_); - if (rev_) - pos = -pos; - Write(pos + 90); - } - - phase_ = phase_ + inc_; - } -} - -void Oscillator::Write(int position) { - if (!is_attached_) - return; - - long currentMillis = millis(); - if (diff_limit_ > 0) { - int limit = std::max( - 1, (((int)(currentMillis - previous_servo_command_millis_)) * diff_limit_) / 1000); - if (abs(position - pos_) > limit) { - pos_ += position < pos_ ? -limit : limit; - } else { - pos_ = position; - } - } else { - pos_ = position; - } - previous_servo_command_millis_ = currentMillis; - - int angle = pos_ + trim_; - - angle = std::min(std::max(angle, 0), 180); - - uint32_t duty = (uint32_t)(((angle / 180.0) * 2.0 + 0.5) * 8191 / 20.0); - - ESP_ERROR_CHECK(ledc_set_duty(ledc_speed_mode_, ledc_channel_, duty)); - ESP_ERROR_CHECK(ledc_update_duty(ledc_speed_mode_, ledc_channel_)); -} +#include "oscillator.h" + +#include +#include + +#include +#include + +static const char* TAG = "Oscillator"; + +extern unsigned long IRAM_ATTR millis(); + +static ledc_channel_t next_free_channel = LEDC_CHANNEL_0; + +Oscillator::Oscillator(int trim) { + trim_ = trim; + diff_limit_ = 0; + is_attached_ = false; + + sampling_period_ = 30; + period_ = 2000; + number_samples_ = period_ / sampling_period_; + inc_ = 2 * M_PI / number_samples_; + + amplitude_ = 45; + phase_ = 0; + phase0_ = 0; + offset_ = 0; + stop_ = false; + rev_ = false; + + pos_ = 90; + previous_millis_ = 0; +} + +Oscillator::~Oscillator() { + Detach(); +} + +uint32_t Oscillator::AngleToCompare(int angle) { + return (angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) / + (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) + + SERVO_MIN_PULSEWIDTH_US; +} + +bool Oscillator::NextSample() { + current_millis_ = millis(); + + if (current_millis_ - previous_millis_ > sampling_period_) { + previous_millis_ = current_millis_; + return true; + } + + return false; +} + +void Oscillator::Attach(int pin, bool rev) { + if (is_attached_) { + Detach(); + } + + pin_ = pin; + rev_ = rev; + + ledc_timer_config_t ledc_timer = {.speed_mode = LEDC_LOW_SPEED_MODE, + .duty_resolution = LEDC_TIMER_13_BIT, + .timer_num = LEDC_TIMER_1, + .freq_hz = 50, + .clk_cfg = LEDC_AUTO_CLK}; + ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); + + static int last_channel = 0; + last_channel = (last_channel + 1) % 7 + 1; + ledc_channel_ = (ledc_channel_t)last_channel; + + ledc_channel_config_t ledc_channel = {.gpio_num = pin_, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = ledc_channel_, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = LEDC_TIMER_1, + .duty = 0, + .hpoint = 0}; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); + + ledc_speed_mode_ = LEDC_LOW_SPEED_MODE; + + // pos_ = 90; + // Write(pos_); + previous_servo_command_millis_ = millis(); + + is_attached_ = true; +} + +void Oscillator::Detach() { + if (!is_attached_) + return; + + ESP_ERROR_CHECK(ledc_stop(ledc_speed_mode_, ledc_channel_, 0)); + + is_attached_ = false; +} + +void Oscillator::SetT(unsigned int T) { + period_ = T; + + number_samples_ = period_ / sampling_period_; + inc_ = 2 * M_PI / number_samples_; +} + +void Oscillator::SetPosition(int position) { + Write(position); +} + +void Oscillator::Refresh() { + if (NextSample()) { + if (!stop_) { + int pos = std::round(amplitude_ * std::sin(phase_ + phase0_) + offset_); + if (rev_) + pos = -pos; + Write(pos + 90); + } + + phase_ = phase_ + inc_; + } +} + +void Oscillator::Write(int position) { + if (!is_attached_) + return; + + long currentMillis = millis(); + if (diff_limit_ > 0) { + int limit = std::max( + 1, (((int)(currentMillis - previous_servo_command_millis_)) * diff_limit_) / 1000); + if (abs(position - pos_) > limit) { + pos_ += position < pos_ ? -limit : limit; + } else { + pos_ = position; + } + } else { + pos_ = position; + } + previous_servo_command_millis_ = currentMillis; + + int angle = pos_ + trim_; + + angle = std::min(std::max(angle, 0), 180); + + uint32_t duty = (uint32_t)(((angle / 180.0) * 2.0 + 0.5) * 8191 / 20.0); + + ESP_ERROR_CHECK(ledc_set_duty(ledc_speed_mode_, ledc_channel_, duty)); + ESP_ERROR_CHECK(ledc_update_duty(ledc_speed_mode_, ledc_channel_)); +} diff --git a/main/boards/otto-robot/oscillator.h b/main/boards/otto-robot/oscillator.h index d9e79f2..7969ff5 100644 --- a/main/boards/otto-robot/oscillator.h +++ b/main/boards/otto-robot/oscillator.h @@ -1,83 +1,83 @@ -#ifndef __OSCILLATOR_H__ -#define __OSCILLATOR_H__ - -#include "driver/ledc.h" -#include "esp_log.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" - -#define M_PI 3.14159265358979323846 - -#ifndef DEG2RAD -#define DEG2RAD(g) ((g) * M_PI) / 180 -#endif - -#define SERVO_MIN_PULSEWIDTH_US 500 // 最小脉宽(微秒) -#define SERVO_MAX_PULSEWIDTH_US 2500 // 最大脉宽(微秒) -#define SERVO_MIN_DEGREE -90 // 最小角度 -#define SERVO_MAX_DEGREE 90 // 最大角度 -#define SERVO_TIMEBASE_RESOLUTION_HZ 1000000 // 1MHz, 1us per tick -#define SERVO_TIMEBASE_PERIOD 20000 // 20000 ticks, 20ms - -class Oscillator { -public: - Oscillator(int trim = 0); - ~Oscillator(); - void Attach(int pin, bool rev = false); - void Detach(); - - void SetA(unsigned int amplitude) { amplitude_ = amplitude; }; - void SetO(int offset) { offset_ = offset; }; - void SetPh(double Ph) { phase0_ = Ph; }; - void SetT(unsigned int period); - void SetTrim(int trim) { trim_ = trim; }; - void SetLimiter(int diff_limit) { diff_limit_ = diff_limit; }; - void DisableLimiter() { diff_limit_ = 0; }; - int GetTrim() { return trim_; }; - void SetPosition(int position); - void Stop() { stop_ = true; }; - void Play() { stop_ = false; }; - void Reset() { phase_ = 0; }; - void Refresh(); - int GetPosition() { return pos_; } - -private: - bool NextSample(); - void Write(int position); - uint32_t AngleToCompare(int angle); - -private: - bool is_attached_; - - //-- Oscillators parameters - unsigned int amplitude_; //-- Amplitude (degrees) - int offset_; //-- Offset (degrees) - unsigned int period_; //-- Period (miliseconds) - double phase0_; //-- Phase (radians) - - //-- Internal variables - int pos_; //-- Current servo pos - int pin_; //-- Pin where the servo is connected - int trim_; //-- Calibration offset - double phase_; //-- Current phase - double inc_; //-- Increment of phase - double number_samples_; //-- Number of samples - unsigned int sampling_period_; //-- sampling period (ms) - - long previous_millis_; - long current_millis_; - - //-- Oscillation mode. If true, the servo is stopped - bool stop_; - - //-- Reverse mode - bool rev_; - - int diff_limit_; - long previous_servo_command_millis_; - - ledc_channel_t ledc_channel_; - ledc_mode_t ledc_speed_mode_; -}; - +#ifndef __OSCILLATOR_H__ +#define __OSCILLATOR_H__ + +#include "driver/ledc.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define M_PI 3.14159265358979323846 + +#ifndef DEG2RAD +#define DEG2RAD(g) ((g) * M_PI) / 180 +#endif + +#define SERVO_MIN_PULSEWIDTH_US 500 // 最小脉宽(微秒) +#define SERVO_MAX_PULSEWIDTH_US 2500 // 最大脉宽(微秒) +#define SERVO_MIN_DEGREE -90 // 最小角度 +#define SERVO_MAX_DEGREE 90 // 最大角度 +#define SERVO_TIMEBASE_RESOLUTION_HZ 1000000 // 1MHz, 1us per tick +#define SERVO_TIMEBASE_PERIOD 20000 // 20000 ticks, 20ms + +class Oscillator { +public: + Oscillator(int trim = 0); + ~Oscillator(); + void Attach(int pin, bool rev = false); + void Detach(); + + void SetA(unsigned int amplitude) { amplitude_ = amplitude; }; + void SetO(int offset) { offset_ = offset; }; + void SetPh(double Ph) { phase0_ = Ph; }; + void SetT(unsigned int period); + void SetTrim(int trim) { trim_ = trim; }; + void SetLimiter(int diff_limit) { diff_limit_ = diff_limit; }; + void DisableLimiter() { diff_limit_ = 0; }; + int GetTrim() { return trim_; }; + void SetPosition(int position); + void Stop() { stop_ = true; }; + void Play() { stop_ = false; }; + void Reset() { phase_ = 0; }; + void Refresh(); + int GetPosition() { return pos_; } + +private: + bool NextSample(); + void Write(int position); + uint32_t AngleToCompare(int angle); + +private: + bool is_attached_; + + //-- Oscillators parameters + unsigned int amplitude_; //-- Amplitude (degrees) + int offset_; //-- Offset (degrees) + unsigned int period_; //-- Period (miliseconds) + double phase0_; //-- Phase (radians) + + //-- Internal variables + int pos_; //-- Current servo pos + int pin_; //-- Pin where the servo is connected + int trim_; //-- Calibration offset + double phase_; //-- Current phase + double inc_; //-- Increment of phase + double number_samples_; //-- Number of samples + unsigned int sampling_period_; //-- sampling period (ms) + + long previous_millis_; + long current_millis_; + + //-- Oscillation mode. If true, the servo is stopped + bool stop_; + + //-- Reverse mode + bool rev_; + + int diff_limit_; + long previous_servo_command_millis_; + + ledc_channel_t ledc_channel_; + ledc_mode_t ledc_speed_mode_; +}; + #endif // __OSCILLATOR_H__ \ No newline at end of file diff --git a/main/boards/otto-robot/otto_controller.cc b/main/boards/otto-robot/otto_controller.cc index 421dbc7..0744be8 100644 --- a/main/boards/otto-robot/otto_controller.cc +++ b/main/boards/otto-robot/otto_controller.cc @@ -1,493 +1,493 @@ -/* - Otto机器人控制器 - MCP协议版本 -*/ - -#include -#include - -#include - -#include "application.h" -#include "board.h" -#include "config.h" -#include "mcp_server.h" -#include "otto_movements.h" -#include "sdkconfig.h" -#include "settings.h" - -#define TAG "OttoController" - -class OttoController { -private: - Otto otto_; - TaskHandle_t action_task_handle_ = nullptr; - QueueHandle_t action_queue_; - bool has_hands_ = false; - bool is_action_in_progress_ = false; - - struct OttoActionParams { - int action_type; - int steps; - int speed; - int direction; - int amount; - }; - - enum ActionType { - ACTION_WALK = 1, - ACTION_TURN = 2, - ACTION_JUMP = 3, - ACTION_SWING = 4, - ACTION_MOONWALK = 5, - ACTION_BEND = 6, - ACTION_SHAKE_LEG = 7, - ACTION_UPDOWN = 8, - ACTION_TIPTOE_SWING = 9, - ACTION_JITTER = 10, - ACTION_ASCENDING_TURN = 11, - ACTION_CRUSAITO = 12, - ACTION_FLAPPING = 13, - ACTION_HANDS_UP = 14, - ACTION_HANDS_DOWN = 15, - ACTION_HAND_WAVE = 16, - ACTION_HOME = 17 - }; - - static void ActionTask(void* arg) { - OttoController* controller = static_cast(arg); - OttoActionParams params; - controller->otto_.AttachServos(); - - while (true) { - if (xQueueReceive(controller->action_queue_, ¶ms, pdMS_TO_TICKS(1000)) == pdTRUE) { - ESP_LOGI(TAG, "执行动作: %d", params.action_type); - controller->is_action_in_progress_ = true; - - switch (params.action_type) { - case ACTION_WALK: - controller->otto_.Walk(params.steps, params.speed, params.direction, - params.amount); - break; - case ACTION_TURN: - controller->otto_.Turn(params.steps, params.speed, params.direction, - params.amount); - break; - case ACTION_JUMP: - controller->otto_.Jump(params.steps, params.speed); - break; - case ACTION_SWING: - controller->otto_.Swing(params.steps, params.speed, params.amount); - break; - case ACTION_MOONWALK: - controller->otto_.Moonwalker(params.steps, params.speed, params.amount, - params.direction); - break; - case ACTION_BEND: - controller->otto_.Bend(params.steps, params.speed, params.direction); - break; - case ACTION_SHAKE_LEG: - controller->otto_.ShakeLeg(params.steps, params.speed, params.direction); - break; - case ACTION_UPDOWN: - controller->otto_.UpDown(params.steps, params.speed, params.amount); - break; - case ACTION_TIPTOE_SWING: - controller->otto_.TiptoeSwing(params.steps, params.speed, params.amount); - break; - case ACTION_JITTER: - controller->otto_.Jitter(params.steps, params.speed, params.amount); - break; - case ACTION_ASCENDING_TURN: - controller->otto_.AscendingTurn(params.steps, params.speed, params.amount); - break; - case ACTION_CRUSAITO: - controller->otto_.Crusaito(params.steps, params.speed, params.amount, - params.direction); - break; - case ACTION_FLAPPING: - controller->otto_.Flapping(params.steps, params.speed, params.amount, - params.direction); - break; - case ACTION_HANDS_UP: - if (controller->has_hands_) { - controller->otto_.HandsUp(params.speed, params.direction); - } - break; - case ACTION_HANDS_DOWN: - if (controller->has_hands_) { - controller->otto_.HandsDown(params.speed, params.direction); - } - break; - case ACTION_HAND_WAVE: - if (controller->has_hands_) { - controller->otto_.HandWave(params.speed, params.direction); - } - break; - case ACTION_HOME: - controller->otto_.Home(params.direction == 1); - break; - } - if (params.action_type != ACTION_HOME) { - controller->otto_.Home(params.action_type < ACTION_HANDS_UP); - } - controller->is_action_in_progress_ = false; - vTaskDelay(pdMS_TO_TICKS(20)); - } - } - } - - void StartActionTaskIfNeeded() { - if (action_task_handle_ == nullptr) { - xTaskCreate(ActionTask, "otto_action", 1024 * 3, this, configMAX_PRIORITIES - 1, - &action_task_handle_); - } - } - - void QueueAction(int action_type, int steps, int speed, int direction, int amount) { - // 检查手部动作 - if ((action_type >= ACTION_HANDS_UP && action_type <= ACTION_HAND_WAVE) && !has_hands_) { - ESP_LOGW(TAG, "尝试执行手部动作,但机器人没有配置手部舵机"); - return; - } - - ESP_LOGI(TAG, "动作控制: 类型=%d, 步数=%d, 速度=%d, 方向=%d, 幅度=%d", action_type, steps, - speed, direction, amount); - - OttoActionParams params = {action_type, steps, speed, direction, amount}; - xQueueSend(action_queue_, ¶ms, portMAX_DELAY); - StartActionTaskIfNeeded(); - } - - void LoadTrimsFromNVS() { - Settings settings("otto_trims", false); - - int left_leg = settings.GetInt("left_leg", 0); - int right_leg = settings.GetInt("right_leg", 0); - int left_foot = settings.GetInt("left_foot", 0); - int right_foot = settings.GetInt("right_foot", 0); - int left_hand = settings.GetInt("left_hand", 0); - int right_hand = settings.GetInt("right_hand", 0); - - ESP_LOGI(TAG, "从NVS加载微调设置: 左腿=%d, 右腿=%d, 左脚=%d, 右脚=%d, 左手=%d, 右手=%d", - left_leg, right_leg, left_foot, right_foot, left_hand, right_hand); - - otto_.SetTrims(left_leg, right_leg, left_foot, right_foot, left_hand, right_hand); - } - -public: - OttoController() { - otto_.Init(LEFT_LEG_PIN, RIGHT_LEG_PIN, LEFT_FOOT_PIN, RIGHT_FOOT_PIN, LEFT_HAND_PIN, - RIGHT_HAND_PIN); - - has_hands_ = (LEFT_HAND_PIN != -1 && RIGHT_HAND_PIN != -1); - ESP_LOGI(TAG, "Otto机器人初始化%s手部舵机", has_hands_ ? "带" : "不带"); - - LoadTrimsFromNVS(); - - action_queue_ = xQueueCreate(10, sizeof(OttoActionParams)); - - QueueAction(ACTION_HOME, 1, 1000, 1, 0); // direction=1表示复位手部 - - RegisterMcpTools(); - } - - void RegisterMcpTools() { - auto& mcp_server = McpServer::GetInstance(); - - ESP_LOGI(TAG, "开始注册MCP工具..."); - - // 基础移动动作 - mcp_server.AddTool("self.otto.walk_forward", - "行走。steps: 行走步数(1-100); speed: 行走速度(500-1500,数值越小越快); " - "direction: 行走方向(-1=后退, 1=前进); arm_swing: 手臂摆动幅度(0-170度)", - PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100), - Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("arm_swing", kPropertyTypeInteger, 50, 0, 170), - Property("direction", kPropertyTypeInteger, 1, -1, 1)}), - [this](const PropertyList& properties) -> ReturnValue { - int steps = properties["steps"].value(); - int speed = properties["speed"].value(); - int arm_swing = properties["arm_swing"].value(); - int direction = properties["direction"].value(); - QueueAction(ACTION_WALK, steps, speed, direction, arm_swing); - return true; - }); - - mcp_server.AddTool("self.otto.turn_left", - "转身。steps: 转身步数(1-100); speed: 转身速度(500-1500,数值越小越快); " - "direction: 转身方向(1=左转, -1=右转); arm_swing: 手臂摆动幅度(0-170度)", - PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100), - Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("arm_swing", kPropertyTypeInteger, 50, 0, 170), - Property("direction", kPropertyTypeInteger, 1, -1, 1)}), - [this](const PropertyList& properties) -> ReturnValue { - int steps = properties["steps"].value(); - int speed = properties["speed"].value(); - int arm_swing = properties["arm_swing"].value(); - int direction = properties["direction"].value(); - QueueAction(ACTION_TURN, steps, speed, direction, arm_swing); - return true; - }); - - mcp_server.AddTool("self.otto.jump", - "跳跃。steps: 跳跃次数(1-100); speed: 跳跃速度(500-1500,数值越小越快)", - PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 100), - Property("speed", kPropertyTypeInteger, 1000, 500, 1500)}), - [this](const PropertyList& properties) -> ReturnValue { - int steps = properties["steps"].value(); - int speed = properties["speed"].value(); - QueueAction(ACTION_JUMP, steps, speed, 0, 0); - return true; - }); - - // 特殊动作 - mcp_server.AddTool("self.otto.swing", - "左右摇摆。steps: 摇摆次数(1-100); speed: " - "摇摆速度(500-1500,数值越小越快); amount: 摇摆幅度(0-170度)", - PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100), - Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("amount", kPropertyTypeInteger, 30, 0, 170)}), - [this](const PropertyList& properties) -> ReturnValue { - int steps = properties["steps"].value(); - int speed = properties["speed"].value(); - int amount = properties["amount"].value(); - QueueAction(ACTION_SWING, steps, speed, 0, amount); - return true; - }); - - mcp_server.AddTool("self.otto.moonwalk", - "太空步。steps: 太空步步数(1-100); speed: 速度(500-1500,数值越小越快); " - "direction: 方向(1=左, -1=右); amount: 幅度(0-170度)", - PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100), - Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("direction", kPropertyTypeInteger, 1, -1, 1), - Property("amount", kPropertyTypeInteger, 25, 0, 170)}), - [this](const PropertyList& properties) -> ReturnValue { - int steps = properties["steps"].value(); - int speed = properties["speed"].value(); - int direction = properties["direction"].value(); - int amount = properties["amount"].value(); - QueueAction(ACTION_MOONWALK, steps, speed, direction, amount); - return true; - }); - - mcp_server.AddTool("self.otto.bend", - "弯曲身体。steps: 弯曲次数(1-100); speed: " - "弯曲速度(500-1500,数值越小越快); direction: 弯曲方向(1=左, -1=右)", - PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 100), - Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("direction", kPropertyTypeInteger, 1, -1, 1)}), - [this](const PropertyList& properties) -> ReturnValue { - int steps = properties["steps"].value(); - int speed = properties["speed"].value(); - int direction = properties["direction"].value(); - QueueAction(ACTION_BEND, steps, speed, direction, 0); - return true; - }); - - mcp_server.AddTool("self.otto.shake_leg", - "摇腿。steps: 摇腿次数(1-100); speed: 摇腿速度(500-1500,数值越小越快); " - "direction: 腿部选择(1=左腿, -1=右腿)", - PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 100), - Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("direction", kPropertyTypeInteger, 1, -1, 1)}), - [this](const PropertyList& properties) -> ReturnValue { - int steps = properties["steps"].value(); - int speed = properties["speed"].value(); - int direction = properties["direction"].value(); - QueueAction(ACTION_SHAKE_LEG, steps, speed, direction, 0); - return true; - }); - - mcp_server.AddTool("self.otto.updown", - "上下运动。steps: 上下运动次数(1-100); speed: " - "运动速度(500-1500,数值越小越快); amount: 运动幅度(0-170度)", - PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100), - Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("amount", kPropertyTypeInteger, 20, 0, 170)}), - [this](const PropertyList& properties) -> ReturnValue { - int steps = properties["steps"].value(); - int speed = properties["speed"].value(); - int amount = properties["amount"].value(); - QueueAction(ACTION_UPDOWN, steps, speed, 0, amount); - return true; - }); - - // 手部动作(仅在有手部舵机时可用) - if (has_hands_) { - mcp_server.AddTool( - "self.otto.hands_up", - "举手。speed: 举手速度(500-1500,数值越小越快); direction: 手部选择(1=左手, " - "-1=右手, 0=双手)", - PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("direction", kPropertyTypeInteger, 1, -1, 1)}), - [this](const PropertyList& properties) -> ReturnValue { - int speed = properties["speed"].value(); - int direction = properties["direction"].value(); - QueueAction(ACTION_HANDS_UP, 1, speed, direction, 0); - return true; - }); - - mcp_server.AddTool( - "self.otto.hands_down", - "放手。speed: 放手速度(500-1500,数值越小越快); direction: 手部选择(1=左手, " - "-1=右手, 0=双手)", - PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("direction", kPropertyTypeInteger, 1, -1, 1)}), - [this](const PropertyList& properties) -> ReturnValue { - int speed = properties["speed"].value(); - int direction = properties["direction"].value(); - QueueAction(ACTION_HANDS_DOWN, 1, speed, direction, 0); - return true; - }); - - mcp_server.AddTool( - "self.otto.hand_wave", - "挥手。speed: 挥手速度(500-1500,数值越小越快); direction: 手部选择(1=左手, " - "-1=右手, 0=双手)", - PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 1500), - Property("direction", kPropertyTypeInteger, 1, -1, 1)}), - [this](const PropertyList& properties) -> ReturnValue { - int speed = properties["speed"].value(); - int direction = properties["direction"].value(); - QueueAction(ACTION_HAND_WAVE, 1, speed, direction, 0); - return true; - }); - } - - // 系统工具 - mcp_server.AddTool("self.otto.stop", "立即停止", PropertyList(), - [this](const PropertyList& properties) -> ReturnValue { - if (action_task_handle_ != nullptr) { - vTaskDelete(action_task_handle_); - action_task_handle_ = nullptr; - } - is_action_in_progress_ = false; - xQueueReset(action_queue_); - - QueueAction(ACTION_HOME, 1, 1000, 1, 0); - return true; - }); - - mcp_server.AddTool( - "self.otto.set_trim", - "校准单个舵机位置。设置指定舵机的微调参数以调整Otto的初始站立姿态,设置将永久保存。" - "servo_type: 舵机类型(left_leg/right_leg/left_foot/right_foot/left_hand/right_hand); " - "trim_value: 微调值(-50到50度)", - PropertyList({Property("servo_type", kPropertyTypeString, "left_leg"), - Property("trim_value", kPropertyTypeInteger, 0, -50, 50)}), - [this](const PropertyList& properties) -> ReturnValue { - std::string servo_type = properties["servo_type"].value(); - int trim_value = properties["trim_value"].value(); - - ESP_LOGI(TAG, "设置舵机微调: %s = %d度", servo_type.c_str(), trim_value); - - // 获取当前所有微调值 - Settings settings("otto_trims", true); - int left_leg = settings.GetInt("left_leg", 0); - int right_leg = settings.GetInt("right_leg", 0); - int left_foot = settings.GetInt("left_foot", 0); - int right_foot = settings.GetInt("right_foot", 0); - int left_hand = settings.GetInt("left_hand", 0); - int right_hand = settings.GetInt("right_hand", 0); - - // 更新指定舵机的微调值 - if (servo_type == "left_leg") { - left_leg = trim_value; - settings.SetInt("left_leg", left_leg); - } else if (servo_type == "right_leg") { - right_leg = trim_value; - settings.SetInt("right_leg", right_leg); - } else if (servo_type == "left_foot") { - left_foot = trim_value; - settings.SetInt("left_foot", left_foot); - } else if (servo_type == "right_foot") { - right_foot = trim_value; - settings.SetInt("right_foot", right_foot); - } else if (servo_type == "left_hand") { - if (!has_hands_) { - return "错误:机器人没有配置手部舵机"; - } - left_hand = trim_value; - settings.SetInt("left_hand", left_hand); - } else if (servo_type == "right_hand") { - if (!has_hands_) { - return "错误:机器人没有配置手部舵机"; - } - right_hand = trim_value; - settings.SetInt("right_hand", right_hand); - } else { - return "错误:无效的舵机类型,请使用: left_leg, right_leg, left_foot, " - "right_foot, left_hand, right_hand"; - } - - otto_.SetTrims(left_leg, right_leg, left_foot, right_foot, left_hand, right_hand); - - QueueAction(ACTION_JUMP, 1, 500, 0, 0); - - return "舵机 " + servo_type + " 微调设置为 " + std::to_string(trim_value) + - " 度,已永久保存"; - }); - - mcp_server.AddTool("self.otto.get_trims", "获取当前的舵机微调设置", PropertyList(), - [this](const PropertyList& properties) -> ReturnValue { - Settings settings("otto_trims", false); - - int left_leg = settings.GetInt("left_leg", 0); - int right_leg = settings.GetInt("right_leg", 0); - int left_foot = settings.GetInt("left_foot", 0); - int right_foot = settings.GetInt("right_foot", 0); - int left_hand = settings.GetInt("left_hand", 0); - int right_hand = settings.GetInt("right_hand", 0); - - std::string result = - "{\"left_leg\":" + std::to_string(left_leg) + - ",\"right_leg\":" + std::to_string(right_leg) + - ",\"left_foot\":" + std::to_string(left_foot) + - ",\"right_foot\":" + std::to_string(right_foot) + - ",\"left_hand\":" + std::to_string(left_hand) + - ",\"right_hand\":" + std::to_string(right_hand) + "}"; - - ESP_LOGI(TAG, "获取微调设置: %s", result.c_str()); - return result; - }); - - mcp_server.AddTool("self.otto.get_status", "获取机器人状态,返回 moving 或 idle", - PropertyList(), [this](const PropertyList& properties) -> ReturnValue { - return is_action_in_progress_ ? "moving" : "idle"; - }); - - mcp_server.AddTool("self.battery.get_level", "获取机器人电池电量和充电状态", PropertyList(), - [](const PropertyList& properties) -> ReturnValue { - auto& board = Board::GetInstance(); - int level = 0; - bool charging = false; - bool discharging = false; - board.GetBatteryLevel(level, charging, discharging); - - std::string status = - "{\"level\":" + std::to_string(level) + - ",\"charging\":" + (charging ? "true" : "false") + "}"; - return status; - }); - - ESP_LOGI(TAG, "MCP工具注册完成"); - } - - ~OttoController() { - if (action_task_handle_ != nullptr) { - vTaskDelete(action_task_handle_); - action_task_handle_ = nullptr; - } - vQueueDelete(action_queue_); - } -}; - -static OttoController* g_otto_controller = nullptr; - -void InitializeOttoController() { - if (g_otto_controller == nullptr) { - g_otto_controller = new OttoController(); - ESP_LOGI(TAG, "Otto控制器已初始化并注册MCP工具"); - } -} +/* + Otto机器人控制器 - MCP协议版本 +*/ + +#include +#include + +#include + +#include "application.h" +#include "board.h" +#include "config.h" +#include "mcp_server.h" +#include "otto_movements.h" +#include "sdkconfig.h" +#include "settings.h" + +#define TAG "OttoController" + +class OttoController { +private: + Otto otto_; + TaskHandle_t action_task_handle_ = nullptr; + QueueHandle_t action_queue_; + bool has_hands_ = false; + bool is_action_in_progress_ = false; + + struct OttoActionParams { + int action_type; + int steps; + int speed; + int direction; + int amount; + }; + + enum ActionType { + ACTION_WALK = 1, + ACTION_TURN = 2, + ACTION_JUMP = 3, + ACTION_SWING = 4, + ACTION_MOONWALK = 5, + ACTION_BEND = 6, + ACTION_SHAKE_LEG = 7, + ACTION_UPDOWN = 8, + ACTION_TIPTOE_SWING = 9, + ACTION_JITTER = 10, + ACTION_ASCENDING_TURN = 11, + ACTION_CRUSAITO = 12, + ACTION_FLAPPING = 13, + ACTION_HANDS_UP = 14, + ACTION_HANDS_DOWN = 15, + ACTION_HAND_WAVE = 16, + ACTION_HOME = 17 + }; + + static void ActionTask(void* arg) { + OttoController* controller = static_cast(arg); + OttoActionParams params; + controller->otto_.AttachServos(); + + while (true) { + if (xQueueReceive(controller->action_queue_, ¶ms, pdMS_TO_TICKS(1000)) == pdTRUE) { + ESP_LOGI(TAG, "执行动作: %d", params.action_type); + controller->is_action_in_progress_ = true; + + switch (params.action_type) { + case ACTION_WALK: + controller->otto_.Walk(params.steps, params.speed, params.direction, + params.amount); + break; + case ACTION_TURN: + controller->otto_.Turn(params.steps, params.speed, params.direction, + params.amount); + break; + case ACTION_JUMP: + controller->otto_.Jump(params.steps, params.speed); + break; + case ACTION_SWING: + controller->otto_.Swing(params.steps, params.speed, params.amount); + break; + case ACTION_MOONWALK: + controller->otto_.Moonwalker(params.steps, params.speed, params.amount, + params.direction); + break; + case ACTION_BEND: + controller->otto_.Bend(params.steps, params.speed, params.direction); + break; + case ACTION_SHAKE_LEG: + controller->otto_.ShakeLeg(params.steps, params.speed, params.direction); + break; + case ACTION_UPDOWN: + controller->otto_.UpDown(params.steps, params.speed, params.amount); + break; + case ACTION_TIPTOE_SWING: + controller->otto_.TiptoeSwing(params.steps, params.speed, params.amount); + break; + case ACTION_JITTER: + controller->otto_.Jitter(params.steps, params.speed, params.amount); + break; + case ACTION_ASCENDING_TURN: + controller->otto_.AscendingTurn(params.steps, params.speed, params.amount); + break; + case ACTION_CRUSAITO: + controller->otto_.Crusaito(params.steps, params.speed, params.amount, + params.direction); + break; + case ACTION_FLAPPING: + controller->otto_.Flapping(params.steps, params.speed, params.amount, + params.direction); + break; + case ACTION_HANDS_UP: + if (controller->has_hands_) { + controller->otto_.HandsUp(params.speed, params.direction); + } + break; + case ACTION_HANDS_DOWN: + if (controller->has_hands_) { + controller->otto_.HandsDown(params.speed, params.direction); + } + break; + case ACTION_HAND_WAVE: + if (controller->has_hands_) { + controller->otto_.HandWave(params.speed, params.direction); + } + break; + case ACTION_HOME: + controller->otto_.Home(params.direction == 1); + break; + } + if (params.action_type != ACTION_HOME) { + controller->otto_.Home(params.action_type < ACTION_HANDS_UP); + } + controller->is_action_in_progress_ = false; + vTaskDelay(pdMS_TO_TICKS(20)); + } + } + } + + void StartActionTaskIfNeeded() { + if (action_task_handle_ == nullptr) { + xTaskCreate(ActionTask, "otto_action", 1024 * 3, this, configMAX_PRIORITIES - 1, + &action_task_handle_); + } + } + + void QueueAction(int action_type, int steps, int speed, int direction, int amount) { + // 检查手部动作 + if ((action_type >= ACTION_HANDS_UP && action_type <= ACTION_HAND_WAVE) && !has_hands_) { + ESP_LOGW(TAG, "尝试执行手部动作,但机器人没有配置手部舵机"); + return; + } + + ESP_LOGI(TAG, "动作控制: 类型=%d, 步数=%d, 速度=%d, 方向=%d, 幅度=%d", action_type, steps, + speed, direction, amount); + + OttoActionParams params = {action_type, steps, speed, direction, amount}; + xQueueSend(action_queue_, ¶ms, portMAX_DELAY); + StartActionTaskIfNeeded(); + } + + void LoadTrimsFromNVS() { + Settings settings("otto_trims", false); + + int left_leg = settings.GetInt("left_leg", 0); + int right_leg = settings.GetInt("right_leg", 0); + int left_foot = settings.GetInt("left_foot", 0); + int right_foot = settings.GetInt("right_foot", 0); + int left_hand = settings.GetInt("left_hand", 0); + int right_hand = settings.GetInt("right_hand", 0); + + ESP_LOGI(TAG, "从NVS加载微调设置: 左腿=%d, 右腿=%d, 左脚=%d, 右脚=%d, 左手=%d, 右手=%d", + left_leg, right_leg, left_foot, right_foot, left_hand, right_hand); + + otto_.SetTrims(left_leg, right_leg, left_foot, right_foot, left_hand, right_hand); + } + +public: + OttoController() { + otto_.Init(LEFT_LEG_PIN, RIGHT_LEG_PIN, LEFT_FOOT_PIN, RIGHT_FOOT_PIN, LEFT_HAND_PIN, + RIGHT_HAND_PIN); + + has_hands_ = (LEFT_HAND_PIN != -1 && RIGHT_HAND_PIN != -1); + ESP_LOGI(TAG, "Otto机器人初始化%s手部舵机", has_hands_ ? "带" : "不带"); + + LoadTrimsFromNVS(); + + action_queue_ = xQueueCreate(10, sizeof(OttoActionParams)); + + QueueAction(ACTION_HOME, 1, 1000, 1, 0); // direction=1表示复位手部 + + RegisterMcpTools(); + } + + void RegisterMcpTools() { + auto& mcp_server = McpServer::GetInstance(); + + ESP_LOGI(TAG, "开始注册MCP工具..."); + + // 基础移动动作 + mcp_server.AddTool("self.otto.walk_forward", + "行走。steps: 行走步数(1-100); speed: 行走速度(500-1500,数值越小越快); " + "direction: 行走方向(-1=后退, 1=前进); arm_swing: 手臂摆动幅度(0-170度)", + PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100), + Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("arm_swing", kPropertyTypeInteger, 50, 0, 170), + Property("direction", kPropertyTypeInteger, 1, -1, 1)}), + [this](const PropertyList& properties) -> ReturnValue { + int steps = properties["steps"].value(); + int speed = properties["speed"].value(); + int arm_swing = properties["arm_swing"].value(); + int direction = properties["direction"].value(); + QueueAction(ACTION_WALK, steps, speed, direction, arm_swing); + return true; + }); + + mcp_server.AddTool("self.otto.turn_left", + "转身。steps: 转身步数(1-100); speed: 转身速度(500-1500,数值越小越快); " + "direction: 转身方向(1=左转, -1=右转); arm_swing: 手臂摆动幅度(0-170度)", + PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100), + Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("arm_swing", kPropertyTypeInteger, 50, 0, 170), + Property("direction", kPropertyTypeInteger, 1, -1, 1)}), + [this](const PropertyList& properties) -> ReturnValue { + int steps = properties["steps"].value(); + int speed = properties["speed"].value(); + int arm_swing = properties["arm_swing"].value(); + int direction = properties["direction"].value(); + QueueAction(ACTION_TURN, steps, speed, direction, arm_swing); + return true; + }); + + mcp_server.AddTool("self.otto.jump", + "跳跃。steps: 跳跃次数(1-100); speed: 跳跃速度(500-1500,数值越小越快)", + PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 100), + Property("speed", kPropertyTypeInteger, 1000, 500, 1500)}), + [this](const PropertyList& properties) -> ReturnValue { + int steps = properties["steps"].value(); + int speed = properties["speed"].value(); + QueueAction(ACTION_JUMP, steps, speed, 0, 0); + return true; + }); + + // 特殊动作 + mcp_server.AddTool("self.otto.swing", + "左右摇摆。steps: 摇摆次数(1-100); speed: " + "摇摆速度(500-1500,数值越小越快); amount: 摇摆幅度(0-170度)", + PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100), + Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("amount", kPropertyTypeInteger, 30, 0, 170)}), + [this](const PropertyList& properties) -> ReturnValue { + int steps = properties["steps"].value(); + int speed = properties["speed"].value(); + int amount = properties["amount"].value(); + QueueAction(ACTION_SWING, steps, speed, 0, amount); + return true; + }); + + mcp_server.AddTool("self.otto.moonwalk", + "太空步。steps: 太空步步数(1-100); speed: 速度(500-1500,数值越小越快); " + "direction: 方向(1=左, -1=右); amount: 幅度(0-170度)", + PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100), + Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("direction", kPropertyTypeInteger, 1, -1, 1), + Property("amount", kPropertyTypeInteger, 25, 0, 170)}), + [this](const PropertyList& properties) -> ReturnValue { + int steps = properties["steps"].value(); + int speed = properties["speed"].value(); + int direction = properties["direction"].value(); + int amount = properties["amount"].value(); + QueueAction(ACTION_MOONWALK, steps, speed, direction, amount); + return true; + }); + + mcp_server.AddTool("self.otto.bend", + "弯曲身体。steps: 弯曲次数(1-100); speed: " + "弯曲速度(500-1500,数值越小越快); direction: 弯曲方向(1=左, -1=右)", + PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 100), + Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("direction", kPropertyTypeInteger, 1, -1, 1)}), + [this](const PropertyList& properties) -> ReturnValue { + int steps = properties["steps"].value(); + int speed = properties["speed"].value(); + int direction = properties["direction"].value(); + QueueAction(ACTION_BEND, steps, speed, direction, 0); + return true; + }); + + mcp_server.AddTool("self.otto.shake_leg", + "摇腿。steps: 摇腿次数(1-100); speed: 摇腿速度(500-1500,数值越小越快); " + "direction: 腿部选择(1=左腿, -1=右腿)", + PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 100), + Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("direction", kPropertyTypeInteger, 1, -1, 1)}), + [this](const PropertyList& properties) -> ReturnValue { + int steps = properties["steps"].value(); + int speed = properties["speed"].value(); + int direction = properties["direction"].value(); + QueueAction(ACTION_SHAKE_LEG, steps, speed, direction, 0); + return true; + }); + + mcp_server.AddTool("self.otto.updown", + "上下运动。steps: 上下运动次数(1-100); speed: " + "运动速度(500-1500,数值越小越快); amount: 运动幅度(0-170度)", + PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100), + Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("amount", kPropertyTypeInteger, 20, 0, 170)}), + [this](const PropertyList& properties) -> ReturnValue { + int steps = properties["steps"].value(); + int speed = properties["speed"].value(); + int amount = properties["amount"].value(); + QueueAction(ACTION_UPDOWN, steps, speed, 0, amount); + return true; + }); + + // 手部动作(仅在有手部舵机时可用) + if (has_hands_) { + mcp_server.AddTool( + "self.otto.hands_up", + "举手。speed: 举手速度(500-1500,数值越小越快); direction: 手部选择(1=左手, " + "-1=右手, 0=双手)", + PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("direction", kPropertyTypeInteger, 1, -1, 1)}), + [this](const PropertyList& properties) -> ReturnValue { + int speed = properties["speed"].value(); + int direction = properties["direction"].value(); + QueueAction(ACTION_HANDS_UP, 1, speed, direction, 0); + return true; + }); + + mcp_server.AddTool( + "self.otto.hands_down", + "放手。speed: 放手速度(500-1500,数值越小越快); direction: 手部选择(1=左手, " + "-1=右手, 0=双手)", + PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("direction", kPropertyTypeInteger, 1, -1, 1)}), + [this](const PropertyList& properties) -> ReturnValue { + int speed = properties["speed"].value(); + int direction = properties["direction"].value(); + QueueAction(ACTION_HANDS_DOWN, 1, speed, direction, 0); + return true; + }); + + mcp_server.AddTool( + "self.otto.hand_wave", + "挥手。speed: 挥手速度(500-1500,数值越小越快); direction: 手部选择(1=左手, " + "-1=右手, 0=双手)", + PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 1500), + Property("direction", kPropertyTypeInteger, 1, -1, 1)}), + [this](const PropertyList& properties) -> ReturnValue { + int speed = properties["speed"].value(); + int direction = properties["direction"].value(); + QueueAction(ACTION_HAND_WAVE, 1, speed, direction, 0); + return true; + }); + } + + // 系统工具 + mcp_server.AddTool("self.otto.stop", "立即停止", PropertyList(), + [this](const PropertyList& properties) -> ReturnValue { + if (action_task_handle_ != nullptr) { + vTaskDelete(action_task_handle_); + action_task_handle_ = nullptr; + } + is_action_in_progress_ = false; + xQueueReset(action_queue_); + + QueueAction(ACTION_HOME, 1, 1000, 1, 0); + return true; + }); + + mcp_server.AddTool( + "self.otto.set_trim", + "校准单个舵机位置。设置指定舵机的微调参数以调整Otto的初始站立姿态,设置将永久保存。" + "servo_type: 舵机类型(left_leg/right_leg/left_foot/right_foot/left_hand/right_hand); " + "trim_value: 微调值(-50到50度)", + PropertyList({Property("servo_type", kPropertyTypeString, "left_leg"), + Property("trim_value", kPropertyTypeInteger, 0, -50, 50)}), + [this](const PropertyList& properties) -> ReturnValue { + std::string servo_type = properties["servo_type"].value(); + int trim_value = properties["trim_value"].value(); + + ESP_LOGI(TAG, "设置舵机微调: %s = %d度", servo_type.c_str(), trim_value); + + // 获取当前所有微调值 + Settings settings("otto_trims", true); + int left_leg = settings.GetInt("left_leg", 0); + int right_leg = settings.GetInt("right_leg", 0); + int left_foot = settings.GetInt("left_foot", 0); + int right_foot = settings.GetInt("right_foot", 0); + int left_hand = settings.GetInt("left_hand", 0); + int right_hand = settings.GetInt("right_hand", 0); + + // 更新指定舵机的微调值 + if (servo_type == "left_leg") { + left_leg = trim_value; + settings.SetInt("left_leg", left_leg); + } else if (servo_type == "right_leg") { + right_leg = trim_value; + settings.SetInt("right_leg", right_leg); + } else if (servo_type == "left_foot") { + left_foot = trim_value; + settings.SetInt("left_foot", left_foot); + } else if (servo_type == "right_foot") { + right_foot = trim_value; + settings.SetInt("right_foot", right_foot); + } else if (servo_type == "left_hand") { + if (!has_hands_) { + return "错误:机器人没有配置手部舵机"; + } + left_hand = trim_value; + settings.SetInt("left_hand", left_hand); + } else if (servo_type == "right_hand") { + if (!has_hands_) { + return "错误:机器人没有配置手部舵机"; + } + right_hand = trim_value; + settings.SetInt("right_hand", right_hand); + } else { + return "错误:无效的舵机类型,请使用: left_leg, right_leg, left_foot, " + "right_foot, left_hand, right_hand"; + } + + otto_.SetTrims(left_leg, right_leg, left_foot, right_foot, left_hand, right_hand); + + QueueAction(ACTION_JUMP, 1, 500, 0, 0); + + return "舵机 " + servo_type + " 微调设置为 " + std::to_string(trim_value) + + " 度,已永久保存"; + }); + + mcp_server.AddTool("self.otto.get_trims", "获取当前的舵机微调设置", PropertyList(), + [this](const PropertyList& properties) -> ReturnValue { + Settings settings("otto_trims", false); + + int left_leg = settings.GetInt("left_leg", 0); + int right_leg = settings.GetInt("right_leg", 0); + int left_foot = settings.GetInt("left_foot", 0); + int right_foot = settings.GetInt("right_foot", 0); + int left_hand = settings.GetInt("left_hand", 0); + int right_hand = settings.GetInt("right_hand", 0); + + std::string result = + "{\"left_leg\":" + std::to_string(left_leg) + + ",\"right_leg\":" + std::to_string(right_leg) + + ",\"left_foot\":" + std::to_string(left_foot) + + ",\"right_foot\":" + std::to_string(right_foot) + + ",\"left_hand\":" + std::to_string(left_hand) + + ",\"right_hand\":" + std::to_string(right_hand) + "}"; + + ESP_LOGI(TAG, "获取微调设置: %s", result.c_str()); + return result; + }); + + mcp_server.AddTool("self.otto.get_status", "获取机器人状态,返回 moving 或 idle", + PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + return is_action_in_progress_ ? "moving" : "idle"; + }); + + mcp_server.AddTool("self.battery.get_level", "获取机器人电池电量和充电状态", PropertyList(), + [](const PropertyList& properties) -> ReturnValue { + auto& board = Board::GetInstance(); + int level = 0; + bool charging = false; + bool discharging = false; + board.GetBatteryLevel(level, charging, discharging); + + std::string status = + "{\"level\":" + std::to_string(level) + + ",\"charging\":" + (charging ? "true" : "false") + "}"; + return status; + }); + + ESP_LOGI(TAG, "MCP工具注册完成"); + } + + ~OttoController() { + if (action_task_handle_ != nullptr) { + vTaskDelete(action_task_handle_); + action_task_handle_ = nullptr; + } + vQueueDelete(action_queue_); + } +}; + +static OttoController* g_otto_controller = nullptr; + +void InitializeOttoController() { + if (g_otto_controller == nullptr) { + g_otto_controller = new OttoController(); + ESP_LOGI(TAG, "Otto控制器已初始化并注册MCP工具"); + } +} diff --git a/main/boards/otto-robot/otto_emoji_display.cc b/main/boards/otto-robot/otto_emoji_display.cc index 32158ff..45c0b7f 100644 --- a/main/boards/otto-robot/otto_emoji_display.cc +++ b/main/boards/otto-robot/otto_emoji_display.cc @@ -1,152 +1,152 @@ -#include "otto_emoji_display.h" -#include "lvgl_theme.h" - -#include -#include - -#include -#include -#include - -#include "display/lcd_display.h" - -#define TAG "OttoEmojiDisplay" - -// 表情映射表 - 将原版21种表情映射到现有6个GIF -const OttoEmojiDisplay::EmotionMap OttoEmojiDisplay::emotion_maps_[] = { - // 中性/平静类表情 -> staticstate - {"neutral", &staticstate}, - {"relaxed", &staticstate}, - {"sleepy", &staticstate}, - - // 积极/开心类表情 -> happy - {"happy", &happy}, - {"laughing", &happy}, - {"funny", &happy}, - {"loving", &happy}, - {"confident", &happy}, - {"winking", &happy}, - {"cool", &happy}, - {"delicious", &happy}, - {"kissy", &happy}, - {"silly", &happy}, - - // 悲伤类表情 -> sad - {"sad", &sad}, - {"crying", &sad}, - - // 愤怒类表情 -> anger - {"angry", &anger}, - - // 惊讶类表情 -> scare - {"surprised", &scare}, - {"shocked", &scare}, - - // 思考/困惑类表情 -> buxue - {"thinking", &buxue}, - {"confused", &buxue}, - {"embarrassed", &buxue}, - - {nullptr, nullptr} // 结束标记 -}; - -OttoEmojiDisplay::OttoEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, - bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy), - emotion_gif_(nullptr) { - SetupGifContainer(); -}; - -void OttoEmojiDisplay::SetupGifContainer() { - DisplayLockGuard lock(this); - - if (emoji_label_) { - lv_obj_del(emoji_label_); - } - - if (chat_message_label_) { - lv_obj_del(chat_message_label_); - } - if (content_) { - lv_obj_del(content_); - } - - content_ = lv_obj_create(container_); - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_size(content_, LV_HOR_RES, LV_HOR_RES); - lv_obj_set_style_bg_opa(content_, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_flex_grow(content_, 1); - lv_obj_center(content_); - - emoji_label_ = lv_label_create(content_); - lv_label_set_text(emoji_label_, ""); - lv_obj_set_width(emoji_label_, 0); - lv_obj_set_style_border_width(emoji_label_, 0, 0); - lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); - - emotion_gif_ = lv_gif_create(content_); - int gif_size = LV_HOR_RES; - lv_obj_set_size(emotion_gif_, gif_size, gif_size); - lv_obj_set_style_border_width(emotion_gif_, 0, 0); - lv_obj_set_style_bg_opa(emotion_gif_, LV_OPA_TRANSP, 0); - lv_obj_center(emotion_gif_); - lv_gif_set_src(emotion_gif_, &staticstate); - - chat_message_label_ = lv_label_create(content_); - lv_label_set_text(chat_message_label_, ""); - lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); - lv_obj_set_style_border_width(chat_message_label_, 0, 0); - - lv_obj_set_style_bg_opa(chat_message_label_, LV_OPA_70, 0); - lv_obj_set_style_bg_color(chat_message_label_, lv_color_black(), 0); - lv_obj_set_style_pad_ver(chat_message_label_, 5, 0); - - lv_obj_align(chat_message_label_, LV_ALIGN_BOTTOM_MID, 0, 0); - - auto& theme_manager = LvglThemeManager::GetInstance(); - auto theme = theme_manager.GetTheme("dark"); - if (theme != nullptr) { - LcdDisplay::SetTheme(theme); - } -} - -void OttoEmojiDisplay::SetEmotion(const char* emotion) { - if (!emotion || !emotion_gif_) { - return; - } - - DisplayLockGuard lock(this); - - for (const auto& map : emotion_maps_) { - if (map.name && strcmp(map.name, emotion) == 0) { - lv_gif_set_src(emotion_gif_, map.gif); - ESP_LOGI(TAG, "设置表情: %s", emotion); - return; - } - } - - lv_gif_set_src(emotion_gif_, &staticstate); - ESP_LOGI(TAG, "未知表情'%s',使用默认", emotion); -} - -void OttoEmojiDisplay::SetChatMessage(const char* role, const char* content) { - DisplayLockGuard lock(this); - if (chat_message_label_ == nullptr) { - return; - } - - if (content == nullptr || strlen(content) == 0) { - lv_obj_add_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); - return; - } - - lv_label_set_text(chat_message_label_, content); - lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); - - ESP_LOGI(TAG, "设置聊天消息 [%s]: %s", role, content); -} +#include "otto_emoji_display.h" +#include "lvgl_theme.h" + +#include +#include + +#include +#include +#include + +#include "display/lcd_display.h" + +#define TAG "OttoEmojiDisplay" + +// 表情映射表 - 将原版21种表情映射到现有6个GIF +const OttoEmojiDisplay::EmotionMap OttoEmojiDisplay::emotion_maps_[] = { + // 中性/平静类表情 -> staticstate + {"neutral", &staticstate}, + {"relaxed", &staticstate}, + {"sleepy", &staticstate}, + + // 积极/开心类表情 -> happy + {"happy", &happy}, + {"laughing", &happy}, + {"funny", &happy}, + {"loving", &happy}, + {"confident", &happy}, + {"winking", &happy}, + {"cool", &happy}, + {"delicious", &happy}, + {"kissy", &happy}, + {"silly", &happy}, + + // 悲伤类表情 -> sad + {"sad", &sad}, + {"crying", &sad}, + + // 愤怒类表情 -> anger + {"angry", &anger}, + + // 惊讶类表情 -> scare + {"surprised", &scare}, + {"shocked", &scare}, + + // 思考/困惑类表情 -> buxue + {"thinking", &buxue}, + {"confused", &buxue}, + {"embarrassed", &buxue}, + + {nullptr, nullptr} // 结束标记 +}; + +OttoEmojiDisplay::OttoEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, bool mirror_x, + bool mirror_y, bool swap_xy) + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy), + emotion_gif_(nullptr) { + SetupGifContainer(); +}; + +void OttoEmojiDisplay::SetupGifContainer() { + DisplayLockGuard lock(this); + + if (emoji_label_) { + lv_obj_del(emoji_label_); + } + + if (chat_message_label_) { + lv_obj_del(chat_message_label_); + } + if (content_) { + lv_obj_del(content_); + } + + content_ = lv_obj_create(container_); + lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_size(content_, LV_HOR_RES, LV_HOR_RES); + lv_obj_set_style_bg_opa(content_, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(content_, 0, 0); + lv_obj_set_flex_grow(content_, 1); + lv_obj_center(content_); + + emoji_label_ = lv_label_create(content_); + lv_label_set_text(emoji_label_, ""); + lv_obj_set_width(emoji_label_, 0); + lv_obj_set_style_border_width(emoji_label_, 0, 0); + lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); + + emotion_gif_ = lv_gif_create(content_); + int gif_size = LV_HOR_RES; + lv_obj_set_size(emotion_gif_, gif_size, gif_size); + lv_obj_set_style_border_width(emotion_gif_, 0, 0); + lv_obj_set_style_bg_opa(emotion_gif_, LV_OPA_TRANSP, 0); + lv_obj_center(emotion_gif_); + lv_gif_set_src(emotion_gif_, &staticstate); + + chat_message_label_ = lv_label_create(content_); + lv_label_set_text(chat_message_label_, ""); + lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); + lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0); + lv_obj_set_style_border_width(chat_message_label_, 0, 0); + + lv_obj_set_style_bg_opa(chat_message_label_, LV_OPA_70, 0); + lv_obj_set_style_bg_color(chat_message_label_, lv_color_black(), 0); + lv_obj_set_style_pad_ver(chat_message_label_, 5, 0); + + lv_obj_align(chat_message_label_, LV_ALIGN_BOTTOM_MID, 0, 0); + + auto& theme_manager = LvglThemeManager::GetInstance(); + auto theme = theme_manager.GetTheme("dark"); + if (theme != nullptr) { + LcdDisplay::SetTheme(theme); + } +} + +void OttoEmojiDisplay::SetEmotion(const char* emotion) { + if (!emotion || !emotion_gif_) { + return; + } + + DisplayLockGuard lock(this); + + for (const auto& map : emotion_maps_) { + if (map.name && strcmp(map.name, emotion) == 0) { + lv_gif_set_src(emotion_gif_, map.gif); + ESP_LOGI(TAG, "设置表情: %s", emotion); + return; + } + } + + lv_gif_set_src(emotion_gif_, &staticstate); + ESP_LOGI(TAG, "未知表情'%s',使用默认", emotion); +} + +void OttoEmojiDisplay::SetChatMessage(const char* role, const char* content) { + DisplayLockGuard lock(this); + if (chat_message_label_ == nullptr) { + return; + } + + if (content == nullptr || strlen(content) == 0) { + lv_obj_add_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); + return; + } + + lv_label_set_text(chat_message_label_, content); + lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); + + ESP_LOGI(TAG, "设置聊天消息 [%s]: %s", role, content); +} diff --git a/main/boards/otto-robot/otto_emoji_display.h b/main/boards/otto-robot/otto_emoji_display.h index b51926e..20c8fa7 100644 --- a/main/boards/otto-robot/otto_emoji_display.h +++ b/main/boards/otto-robot/otto_emoji_display.h @@ -1,41 +1,41 @@ -#pragma once - -#include - -#include "display/lcd_display.h" -#include "otto_emoji_gif.h" - -/** - * @brief Otto机器人GIF表情显示类 - * 继承LcdDisplay,添加GIF表情支持 - */ -class OttoEmojiDisplay : public SpiLcdDisplay { -public: - /** - * @brief 构造函数,参数与SpiLcdDisplay相同 - */ - OttoEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, - int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, - bool swap_xy); - - virtual ~OttoEmojiDisplay() = default; - - // 重写表情设置方法 - virtual void SetEmotion(const char* emotion) override; - - // 重写聊天消息设置方法 - virtual void SetChatMessage(const char* role, const char* content) override; - -private: - void SetupGifContainer(); - - lv_obj_t* emotion_gif_; ///< GIF表情组件 - - // 表情映射 - struct EmotionMap { - const char* name; - const lv_img_dsc_t* gif; - }; - - static const EmotionMap emotion_maps_[]; +#pragma once + +#include + +#include "display/lcd_display.h" +#include "otto_emoji_gif.h" + +/** + * @brief Otto机器人GIF表情显示类 + * 继承LcdDisplay,添加GIF表情支持 + */ +class OttoEmojiDisplay : public SpiLcdDisplay { +public: + /** + * @brief 构造函数,参数与SpiLcdDisplay相同 + */ + OttoEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, + int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, + bool swap_xy); + + virtual ~OttoEmojiDisplay() = default; + + // 重写表情设置方法 + virtual void SetEmotion(const char* emotion) override; + + // 重写聊天消息设置方法 + virtual void SetChatMessage(const char* role, const char* content) override; + +private: + void SetupGifContainer(); + + lv_obj_t* emotion_gif_; ///< GIF表情组件 + + // 表情映射 + struct EmotionMap { + const char* name; + const lv_img_dsc_t* gif; + }; + + static const EmotionMap emotion_maps_[]; }; \ No newline at end of file diff --git a/main/boards/otto-robot/otto_movements.cc b/main/boards/otto-robot/otto_movements.cc index 5f3315d..3901915 100644 --- a/main/boards/otto-robot/otto_movements.cc +++ b/main/boards/otto-robot/otto_movements.cc @@ -1,763 +1,763 @@ -#include "otto_movements.h" - -#include - -#include "oscillator.h" - -static const char* TAG = "OttoMovements"; - -#define HAND_HOME_POSITION 45 - -Otto::Otto() { - is_otto_resting_ = false; - has_hands_ = false; - // 初始化所有舵机管脚为-1(未连接) - for (int i = 0; i < SERVO_COUNT; i++) { - servo_pins_[i] = -1; - servo_trim_[i] = 0; - } -} - -Otto::~Otto() { - DetachServos(); -} - -unsigned long IRAM_ATTR millis() { - return (unsigned long)(esp_timer_get_time() / 1000ULL); -} - -void Otto::Init(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand, - int right_hand) { - servo_pins_[LEFT_LEG] = left_leg; - servo_pins_[RIGHT_LEG] = right_leg; - servo_pins_[LEFT_FOOT] = left_foot; - servo_pins_[RIGHT_FOOT] = right_foot; - servo_pins_[LEFT_HAND] = left_hand; - servo_pins_[RIGHT_HAND] = right_hand; - - // 检查是否有手部舵机 - has_hands_ = (left_hand != -1 && right_hand != -1); - - AttachServos(); - is_otto_resting_ = false; -} - -/////////////////////////////////////////////////////////////////// -//-- ATTACH & DETACH FUNCTIONS ----------------------------------// -/////////////////////////////////////////////////////////////////// -void Otto::AttachServos() { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].Attach(servo_pins_[i]); - } - } -} - -void Otto::DetachServos() { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].Detach(); - } - } -} - -/////////////////////////////////////////////////////////////////// -//-- OSCILLATORS TRIMS ------------------------------------------// -/////////////////////////////////////////////////////////////////// -void Otto::SetTrims(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand, - int right_hand) { - servo_trim_[LEFT_LEG] = left_leg; - servo_trim_[RIGHT_LEG] = right_leg; - servo_trim_[LEFT_FOOT] = left_foot; - servo_trim_[RIGHT_FOOT] = right_foot; - - if (has_hands_) { - servo_trim_[LEFT_HAND] = left_hand; - servo_trim_[RIGHT_HAND] = right_hand; - } - - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].SetTrim(servo_trim_[i]); - } - } -} - -/////////////////////////////////////////////////////////////////// -//-- BASIC MOTION FUNCTIONS -------------------------------------// -/////////////////////////////////////////////////////////////////// -void Otto::MoveServos(int time, int servo_target[]) { - if (GetRestState() == true) { - SetRestState(false); - } - - final_time_ = millis() + time; - if (time > 10) { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - increment_[i] = (servo_target[i] - servo_[i].GetPosition()) / (time / 10.0); - } - } - - for (int iteration = 1; millis() < final_time_; iteration++) { - partial_time_ = millis() + 10; - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].SetPosition(servo_[i].GetPosition() + increment_[i]); - } - } - vTaskDelay(pdMS_TO_TICKS(10)); - } - } else { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].SetPosition(servo_target[i]); - } - } - vTaskDelay(pdMS_TO_TICKS(time)); - } - - // final adjustment to the target. - bool f = true; - int adjustment_count = 0; - while (f && adjustment_count < 10) { - f = false; - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1 && servo_target[i] != servo_[i].GetPosition()) { - f = true; - break; - } - } - if (f) { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].SetPosition(servo_target[i]); - } - } - vTaskDelay(pdMS_TO_TICKS(10)); - adjustment_count++; - } - }; -} - -void Otto::MoveSingle(int position, int servo_number) { - if (position > 180) - position = 90; - if (position < 0) - position = 90; - - if (GetRestState() == true) { - SetRestState(false); - } - - if (servo_number >= 0 && servo_number < SERVO_COUNT && servo_pins_[servo_number] != -1) { - servo_[servo_number].SetPosition(position); - } -} - -void Otto::OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, - double phase_diff[SERVO_COUNT], float cycle = 1) { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].SetO(offset[i]); - servo_[i].SetA(amplitude[i]); - servo_[i].SetT(period); - servo_[i].SetPh(phase_diff[i]); - } - } - - double ref = millis(); - double end_time = period * cycle + ref; - - while (millis() < end_time) { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].Refresh(); - } - } - vTaskDelay(5); - } - vTaskDelay(pdMS_TO_TICKS(10)); -} - -void Otto::Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, - double phase_diff[SERVO_COUNT], float steps = 1.0) { - if (GetRestState() == true) { - SetRestState(false); - } - - int cycles = (int)steps; - - //-- Execute complete cycles - if (cycles >= 1) - for (int i = 0; i < cycles; i++) - OscillateServos(amplitude, offset, period, phase_diff); - - //-- Execute the final not complete cycle - OscillateServos(amplitude, offset, period, phase_diff, (float)steps - cycles); - vTaskDelay(pdMS_TO_TICKS(10)); -} - -/////////////////////////////////////////////////////////////////// -//-- HOME = Otto at rest position -------------------------------// -/////////////////////////////////////////////////////////////////// -void Otto::Home(bool hands_down) { - if (is_otto_resting_ == false) { // Go to rest position only if necessary - // 为所有舵机准备初始位置值 - int homes[SERVO_COUNT]; - for (int i = 0; i < SERVO_COUNT; i++) { - if (i == LEFT_HAND || i == RIGHT_HAND) { - if (hands_down) { - // 如果需要复位手部,设置为默认值 - if (i == LEFT_HAND) { - homes[i] = HAND_HOME_POSITION; - } else { // RIGHT_HAND - homes[i] = 180 - HAND_HOME_POSITION; // 右手镜像位置 - } - } else { - // 如果不需要复位手部,保持当前位置 - homes[i] = servo_[i].GetPosition(); - } - } else { - // 腿部和脚部舵机始终复位 - homes[i] = 90; - } - } - - MoveServos(500, homes); - is_otto_resting_ = true; - } - - vTaskDelay(pdMS_TO_TICKS(200)); -} - -bool Otto::GetRestState() { - return is_otto_resting_; -} - -void Otto::SetRestState(bool state) { - is_otto_resting_ = state; -} - -/////////////////////////////////////////////////////////////////// -//-- PREDETERMINED MOTION SEQUENCES -----------------------------// -/////////////////////////////////////////////////////////////////// -//-- Otto movement: Jump -//-- Parameters: -//-- steps: Number of steps -//-- T: Period -//--------------------------------------------------------- -void Otto::Jump(float steps, int period) { - int up[SERVO_COUNT] = {90, 90, 150, 30, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - MoveServos(period, up); - int down[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - MoveServos(period, down); -} - -//--------------------------------------------------------- -//-- Otto gait: Walking (forward or backward) -//-- Parameters: -//-- * steps: Number of steps -//-- * T : Period -//-- * Dir: Direction: FORWARD / BACKWARD -//-- * amount: 手部摆动幅度, 0表示不摆动 -//--------------------------------------------------------- -void Otto::Walk(float steps, int period, int dir, int amount) { - //-- Oscillator parameters for walking - //-- Hip sevos are in phase - //-- Feet servos are in phase - //-- Hip and feet are 90 degrees out of phase - //-- -90 : Walk forward - //-- 90 : Walk backward - //-- Feet servos also have the same offset (for tiptoe a little bit) - int A[SERVO_COUNT] = {30, 30, 30, 30, 0, 0}; - int O[SERVO_COUNT] = {0, 0, 5, -5, HAND_HOME_POSITION - 90, HAND_HOME_POSITION}; - double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(dir * -90), DEG2RAD(dir * -90), 0, 0}; - - // 如果amount>0且有手部舵机,设置手部振幅和相位 - if (amount > 0 && has_hands_) { - // 手臂振幅使用传入的amount参数 - A[LEFT_HAND] = amount; - A[RIGHT_HAND] = amount; - - // 左手与右腿同相,右手与左腿同相,使得机器人走路时手臂自然摆动 - phase_diff[LEFT_HAND] = phase_diff[RIGHT_LEG]; // 左手与右腿同相 - phase_diff[RIGHT_HAND] = phase_diff[LEFT_LEG]; // 右手与左腿同相 - } else { - A[LEFT_HAND] = 0; - A[RIGHT_HAND] = 0; - } - - //-- Let's oscillate the servos! - Execute(A, O, period, phase_diff, steps); -} - -//--------------------------------------------------------- -//-- Otto gait: Turning (left or right) -//-- Parameters: -//-- * Steps: Number of steps -//-- * T: Period -//-- * Dir: Direction: LEFT / RIGHT -//-- * amount: 手部摆动幅度, 0表示不摆动 -//--------------------------------------------------------- -void Otto::Turn(float steps, int period, int dir, int amount) { - //-- Same coordination than for walking (see Otto::walk) - //-- The Amplitudes of the hip's oscillators are not igual - //-- When the right hip servo amplitude is higher, the steps taken by - //-- the right leg are bigger than the left. So, the robot describes an - //-- left arc - int A[SERVO_COUNT] = {30, 30, 30, 30, 0, 0}; - int O[SERVO_COUNT] = {0, 0, 5, -5, HAND_HOME_POSITION - 90, HAND_HOME_POSITION}; - double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(-90), DEG2RAD(-90), 0, 0}; - - if (dir == LEFT) { - A[0] = 30; //-- Left hip servo - A[1] = 0; //-- Right hip servo - } else { - A[0] = 0; - A[1] = 30; - } - - // 如果amount>0且有手部舵机,设置手部振幅和相位 - if (amount > 0 && has_hands_) { - // 手臂振幅使用传入的amount参数 - A[LEFT_HAND] = amount; - A[RIGHT_HAND] = amount; - - // 转向时手臂摆动相位:左手与左腿同相,右手与右腿同相,增强转向效果 - phase_diff[LEFT_HAND] = phase_diff[LEFT_LEG]; // 左手与左腿同相 - phase_diff[RIGHT_HAND] = phase_diff[RIGHT_LEG]; // 右手与右腿同相 - } else { - A[LEFT_HAND] = 0; - A[RIGHT_HAND] = 0; - } - - //-- Let's oscillate the servos! - Execute(A, O, period, phase_diff, steps); -} - -//--------------------------------------------------------- -//-- Otto gait: Lateral bend -//-- Parameters: -//-- steps: Number of bends -//-- T: Period of one bend -//-- dir: RIGHT=Right bend LEFT=Left bend -//--------------------------------------------------------- -void Otto::Bend(int steps, int period, int dir) { - // Parameters of all the movements. Default: Left bend - int bend1[SERVO_COUNT] = {90, 90, 62, 35, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - int bend2[SERVO_COUNT] = {90, 90, 62, 105, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - int homes[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - - // Time of one bend, constrained in order to avoid movements too fast. - // T=max(T, 600); - // Changes in the parameters if right direction is chosen - if (dir == -1) { - bend1[2] = 180 - 35; - bend1[3] = 180 - 60; // Not 65. Otto is unbalanced - bend2[2] = 180 - 105; - bend2[3] = 180 - 60; - } - - // Time of the bend movement. Fixed parameter to avoid falls - int T2 = 800; - - // Bend movement - for (int i = 0; i < steps; i++) { - MoveServos(T2 / 2, bend1); - MoveServos(T2 / 2, bend2); - vTaskDelay(pdMS_TO_TICKS(period * 0.8)); - MoveServos(500, homes); - } -} - -//--------------------------------------------------------- -//-- Otto gait: Shake a leg -//-- Parameters: -//-- steps: Number of shakes -//-- T: Period of one shake -//-- dir: RIGHT=Right leg LEFT=Left leg -//--------------------------------------------------------- -void Otto::ShakeLeg(int steps, int period, int dir) { - // This variable change the amount of shakes - int numberLegMoves = 2; - - // Parameters of all the movements. Default: Right leg - int shake_leg1[SERVO_COUNT] = {90, 90, 58, 35, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - int shake_leg2[SERVO_COUNT] = {90, 90, 58, 120, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - int shake_leg3[SERVO_COUNT] = {90, 90, 58, 60, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - int homes[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - - // Changes in the parameters if left leg is chosen - if (dir == -1) { - shake_leg1[2] = 180 - 35; - shake_leg1[3] = 180 - 58; - shake_leg2[2] = 180 - 120; - shake_leg2[3] = 180 - 58; - shake_leg3[2] = 180 - 60; - shake_leg3[3] = 180 - 58; - } - - // Time of the bend movement. Fixed parameter to avoid falls - int T2 = 1000; - // Time of one shake, constrained in order to avoid movements too fast. - period = period - T2; - period = std::max(period, 200 * numberLegMoves); - - for (int j = 0; j < steps; j++) { - // Bend movement - MoveServos(T2 / 2, shake_leg1); - MoveServos(T2 / 2, shake_leg2); - - // Shake movement - for (int i = 0; i < numberLegMoves; i++) { - MoveServos(period / (2 * numberLegMoves), shake_leg3); - MoveServos(period / (2 * numberLegMoves), shake_leg2); - } - MoveServos(500, homes); // Return to home position - } - - vTaskDelay(pdMS_TO_TICKS(period)); -} - -//--------------------------------------------------------- -//-- Otto movement: up & down -//-- Parameters: -//-- * steps: Number of jumps -//-- * T: Period -//-- * h: Jump height: SMALL / MEDIUM / BIG -//-- (or a number in degrees 0 - 90) -//--------------------------------------------------------- -void Otto::UpDown(float steps, int period, int height) { - //-- Both feet are 180 degrees out of phase - //-- Feet amplitude and offset are the same - //-- Initial phase for the right foot is -90, so that it starts - //-- in one extreme position (not in the middle) - int A[SERVO_COUNT] = {0, 0, height, height, 0, 0}; - int O[SERVO_COUNT] = {0, 0, height, -height, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(-90), DEG2RAD(90), 0, 0}; - - //-- Let's oscillate the servos! - Execute(A, O, period, phase_diff, steps); -} - -//--------------------------------------------------------- -//-- Otto movement: swinging side to side -//-- Parameters: -//-- steps: Number of steps -//-- T : Period -//-- h : Amount of swing (from 0 to 50 aprox) -//--------------------------------------------------------- -void Otto::Swing(float steps, int period, int height) { - //-- Both feets are in phase. The offset is half the amplitude - //-- It causes the robot to swing from side to side - int A[SERVO_COUNT] = {0, 0, height, height, 0, 0}; - int O[SERVO_COUNT] = { - 0, 0, height / 2, -height / 2, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(0), DEG2RAD(0), 0, 0}; - - //-- Let's oscillate the servos! - Execute(A, O, period, phase_diff, steps); -} - -//--------------------------------------------------------- -//-- Otto movement: swinging side to side without touching the floor with the heel -//-- Parameters: -//-- steps: Number of steps -//-- T : Period -//-- h : Amount of swing (from 0 to 50 aprox) -//--------------------------------------------------------- -void Otto::TiptoeSwing(float steps, int period, int height) { - //-- Both feets are in phase. The offset is not half the amplitude in order to tiptoe - //-- It causes the robot to swing from side to side - int A[SERVO_COUNT] = {0, 0, height, height, 0, 0}; - int O[SERVO_COUNT] = {0, 0, height, -height, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, 0}; - - //-- Let's oscillate the servos! - Execute(A, O, period, phase_diff, steps); -} - -//--------------------------------------------------------- -//-- Otto gait: Jitter -//-- Parameters: -//-- steps: Number of jitters -//-- T: Period of one jitter -//-- h: height (Values between 5 - 25) -//--------------------------------------------------------- -void Otto::Jitter(float steps, int period, int height) { - //-- Both feet are 180 degrees out of phase - //-- Feet amplitude and offset are the same - //-- Initial phase for the right foot is -90, so that it starts - //-- in one extreme position (not in the middle) - //-- h is constrained to avoid hit the feets - height = std::min(25, height); - int A[SERVO_COUNT] = {height, height, 0, 0, 0, 0}; - int O[SERVO_COUNT] = {0, 0, 0, 0, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - double phase_diff[SERVO_COUNT] = {DEG2RAD(-90), DEG2RAD(90), 0, 0, 0, 0}; - - //-- Let's oscillate the servos! - Execute(A, O, period, phase_diff, steps); -} - -//--------------------------------------------------------- -//-- Otto gait: Ascending & turn (Jitter while up&down) -//-- Parameters: -//-- steps: Number of bends -//-- T: Period of one bend -//-- h: height (Values between 5 - 15) -//--------------------------------------------------------- -void Otto::AscendingTurn(float steps, int period, int height) { - //-- Both feet and legs are 180 degrees out of phase - //-- Initial phase for the right foot is -90, so that it starts - //-- in one extreme position (not in the middle) - //-- h is constrained to avoid hit the feets - height = std::min(13, height); - int A[SERVO_COUNT] = {height, height, height, height, 0, 0}; - int O[SERVO_COUNT] = { - 0, 0, height + 4, -height + 4, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - double phase_diff[SERVO_COUNT] = {DEG2RAD(-90), DEG2RAD(90), DEG2RAD(-90), DEG2RAD(90), 0, 0}; - - //-- Let's oscillate the servos! - Execute(A, O, period, phase_diff, steps); -} - -//--------------------------------------------------------- -//-- Otto gait: Moonwalker. Otto moves like Michael Jackson -//-- Parameters: -//-- Steps: Number of steps -//-- T: Period -//-- h: Height. Typical valures between 15 and 40 -//-- dir: Direction: LEFT / RIGHT -//--------------------------------------------------------- -void Otto::Moonwalker(float steps, int period, int height, int dir) { - //-- This motion is similar to that of the caterpillar robots: A travelling - //-- wave moving from one side to another - //-- The two Otto's feet are equivalent to a minimal configuration. It is known - //-- that 2 servos can move like a worm if they are 120 degrees out of phase - //-- In the example of Otto, the two feet are mirrored so that we have: - //-- 180 - 120 = 60 degrees. The actual phase difference given to the oscillators - //-- is 60 degrees. - //-- Both amplitudes are equal. The offset is half the amplitud plus a little bit of - //- offset so that the robot tiptoe lightly - - int A[SERVO_COUNT] = {0, 0, height, height, 0, 0}; - int O[SERVO_COUNT] = { - 0, 0, height / 2 + 2, -height / 2 - 2, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - int phi = -dir * 90; - double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(phi), DEG2RAD(-60 * dir + phi), 0, 0}; - - //-- Let's oscillate the servos! - Execute(A, O, period, phase_diff, steps); -} - -//---------------------------------------------------------- -//-- Otto gait: Crusaito. A mixture between moonwalker and walk -//-- Parameters: -//-- steps: Number of steps -//-- T: Period -//-- h: height (Values between 20 - 50) -//-- dir: Direction: LEFT / RIGHT -//----------------------------------------------------------- -void Otto::Crusaito(float steps, int period, int height, int dir) { - int A[SERVO_COUNT] = {25, 25, height, height, 0, 0}; - int O[SERVO_COUNT] = { - 0, 0, height / 2 + 4, -height / 2 - 4, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - double phase_diff[SERVO_COUNT] = {90, 90, DEG2RAD(0), DEG2RAD(-60 * dir), 0, 0}; - - //-- Let's oscillate the servos! - Execute(A, O, period, phase_diff, steps); -} - -//--------------------------------------------------------- -//-- Otto gait: Flapping -//-- Parameters: -//-- steps: Number of steps -//-- T: Period -//-- h: height (Values between 10 - 30) -//-- dir: direction: FOREWARD, BACKWARD -//--------------------------------------------------------- -void Otto::Flapping(float steps, int period, int height, int dir) { - int A[SERVO_COUNT] = {12, 12, height, height, 0, 0}; - int O[SERVO_COUNT] = { - 0, 0, height - 10, -height + 10, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - double phase_diff[SERVO_COUNT] = { - DEG2RAD(0), DEG2RAD(180), DEG2RAD(-90 * dir), DEG2RAD(90 * dir), 0, 0}; - - //-- Let's oscillate the servos! - Execute(A, O, period, phase_diff, steps); -} - -//--------------------------------------------------------- -//-- 手部动作: 举手 -//-- Parameters: -//-- period: 动作时间 -//-- dir: 方向 1=左手, -1=右手, 0=双手 -//--------------------------------------------------------- -void Otto::HandsUp(int period, int dir) { - if (!has_hands_) { - return; - } - - int initial[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - int target[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - - if (dir == 0) { - target[LEFT_HAND] = 170; - target[RIGHT_HAND] = 10; - } else if (dir == 1) { - target[LEFT_HAND] = 170; - target[RIGHT_HAND] = servo_[RIGHT_HAND].GetPosition(); - } else if (dir == -1) { - target[RIGHT_HAND] = 10; - target[LEFT_HAND] = servo_[LEFT_HAND].GetPosition(); - } - - MoveServos(period, target); -} - -//--------------------------------------------------------- -//-- 手部动作: 双手放下 -//-- Parameters: -//-- period: 动作时间 -//-- dir: 方向 1=左手, -1=右手, 0=双手 -//--------------------------------------------------------- -void Otto::HandsDown(int period, int dir) { - if (!has_hands_) { - return; - } - - int target[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; - - if (dir == 1) { - target[RIGHT_HAND] = servo_[RIGHT_HAND].GetPosition(); - } else if (dir == -1) { - target[LEFT_HAND] = servo_[LEFT_HAND].GetPosition(); - } - - MoveServos(period, target); -} - -//--------------------------------------------------------- -//-- 手部动作: 挥手 -//-- Parameters: -//-- period: 动作周期 -//-- dir: 方向 LEFT/RIGHT/BOTH -//--------------------------------------------------------- -void Otto::HandWave(int period, int dir) { - if (!has_hands_) { - return; - } - - if (dir == BOTH) { - HandWaveBoth(period); - return; - } - - int servo_index = (dir == LEFT) ? LEFT_HAND : RIGHT_HAND; - - int current_positions[SERVO_COUNT]; - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - current_positions[i] = servo_[i].GetPosition(); - } else { - current_positions[i] = 90; - } - } - - int position; - if (servo_index == LEFT_HAND) { - position = 170; - } else { - position = 10; - } - - current_positions[servo_index] = position; - MoveServos(300, current_positions); - vTaskDelay(pdMS_TO_TICKS(300)); - - // 左右摆动5次 - for (int i = 0; i < 5; i++) { - if (servo_index == LEFT_HAND) { - current_positions[servo_index] = position - 30; - MoveServos(period / 10, current_positions); - vTaskDelay(pdMS_TO_TICKS(period / 10)); - current_positions[servo_index] = position + 30; - MoveServos(period / 10, current_positions); - } else { - current_positions[servo_index] = position + 30; - MoveServos(period / 10, current_positions); - vTaskDelay(pdMS_TO_TICKS(period / 10)); - current_positions[servo_index] = position - 30; - MoveServos(period / 10, current_positions); - } - vTaskDelay(pdMS_TO_TICKS(period / 10)); - } - - if (servo_index == LEFT_HAND) { - current_positions[servo_index] = HAND_HOME_POSITION; - } else { - current_positions[servo_index] = 180 - HAND_HOME_POSITION; - } - MoveServos(300, current_positions); -} - -//--------------------------------------------------------- -//-- 手部动作: 双手同时挥手 -//-- Parameters: -//-- period: 动作周期 -//--------------------------------------------------------- -void Otto::HandWaveBoth(int period) { - if (!has_hands_) { - return; - } - - int current_positions[SERVO_COUNT]; - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - current_positions[i] = servo_[i].GetPosition(); - } else { - current_positions[i] = 90; - } - } - - int left_position = 170; - int right_position = 10; - - current_positions[LEFT_HAND] = left_position; - current_positions[RIGHT_HAND] = right_position; - MoveServos(300, current_positions); - - // 左右摆动5次 - for (int i = 0; i < 5; i++) { - // 波浪向左 - current_positions[LEFT_HAND] = left_position - 30; - current_positions[RIGHT_HAND] = right_position + 30; - MoveServos(period / 10, current_positions); - - // 波浪向右 - current_positions[LEFT_HAND] = left_position + 30; - current_positions[RIGHT_HAND] = right_position - 30; - MoveServos(period / 10, current_positions); - } - - current_positions[LEFT_HAND] = HAND_HOME_POSITION; - current_positions[RIGHT_HAND] = 180 - HAND_HOME_POSITION; - MoveServos(300, current_positions); -} - -void Otto::EnableServoLimit(int diff_limit) { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].SetLimiter(diff_limit); - } - } -} - -void Otto::DisableServoLimit() { - for (int i = 0; i < SERVO_COUNT; i++) { - if (servo_pins_[i] != -1) { - servo_[i].DisableLimiter(); - } - } -} +#include "otto_movements.h" + +#include + +#include "oscillator.h" + +static const char* TAG = "OttoMovements"; + +#define HAND_HOME_POSITION 45 + +Otto::Otto() { + is_otto_resting_ = false; + has_hands_ = false; + // 初始化所有舵机管脚为-1(未连接) + for (int i = 0; i < SERVO_COUNT; i++) { + servo_pins_[i] = -1; + servo_trim_[i] = 0; + } +} + +Otto::~Otto() { + DetachServos(); +} + +unsigned long IRAM_ATTR millis() { + return (unsigned long)(esp_timer_get_time() / 1000ULL); +} + +void Otto::Init(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand, + int right_hand) { + servo_pins_[LEFT_LEG] = left_leg; + servo_pins_[RIGHT_LEG] = right_leg; + servo_pins_[LEFT_FOOT] = left_foot; + servo_pins_[RIGHT_FOOT] = right_foot; + servo_pins_[LEFT_HAND] = left_hand; + servo_pins_[RIGHT_HAND] = right_hand; + + // 检查是否有手部舵机 + has_hands_ = (left_hand != -1 && right_hand != -1); + + AttachServos(); + is_otto_resting_ = false; +} + +/////////////////////////////////////////////////////////////////// +//-- ATTACH & DETACH FUNCTIONS ----------------------------------// +/////////////////////////////////////////////////////////////////// +void Otto::AttachServos() { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].Attach(servo_pins_[i]); + } + } +} + +void Otto::DetachServos() { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].Detach(); + } + } +} + +/////////////////////////////////////////////////////////////////// +//-- OSCILLATORS TRIMS ------------------------------------------// +/////////////////////////////////////////////////////////////////// +void Otto::SetTrims(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand, + int right_hand) { + servo_trim_[LEFT_LEG] = left_leg; + servo_trim_[RIGHT_LEG] = right_leg; + servo_trim_[LEFT_FOOT] = left_foot; + servo_trim_[RIGHT_FOOT] = right_foot; + + if (has_hands_) { + servo_trim_[LEFT_HAND] = left_hand; + servo_trim_[RIGHT_HAND] = right_hand; + } + + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].SetTrim(servo_trim_[i]); + } + } +} + +/////////////////////////////////////////////////////////////////// +//-- BASIC MOTION FUNCTIONS -------------------------------------// +/////////////////////////////////////////////////////////////////// +void Otto::MoveServos(int time, int servo_target[]) { + if (GetRestState() == true) { + SetRestState(false); + } + + final_time_ = millis() + time; + if (time > 10) { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + increment_[i] = (servo_target[i] - servo_[i].GetPosition()) / (time / 10.0); + } + } + + for (int iteration = 1; millis() < final_time_; iteration++) { + partial_time_ = millis() + 10; + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].SetPosition(servo_[i].GetPosition() + increment_[i]); + } + } + vTaskDelay(pdMS_TO_TICKS(10)); + } + } else { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].SetPosition(servo_target[i]); + } + } + vTaskDelay(pdMS_TO_TICKS(time)); + } + + // final adjustment to the target. + bool f = true; + int adjustment_count = 0; + while (f && adjustment_count < 10) { + f = false; + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1 && servo_target[i] != servo_[i].GetPosition()) { + f = true; + break; + } + } + if (f) { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].SetPosition(servo_target[i]); + } + } + vTaskDelay(pdMS_TO_TICKS(10)); + adjustment_count++; + } + }; +} + +void Otto::MoveSingle(int position, int servo_number) { + if (position > 180) + position = 90; + if (position < 0) + position = 90; + + if (GetRestState() == true) { + SetRestState(false); + } + + if (servo_number >= 0 && servo_number < SERVO_COUNT && servo_pins_[servo_number] != -1) { + servo_[servo_number].SetPosition(position); + } +} + +void Otto::OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, + double phase_diff[SERVO_COUNT], float cycle = 1) { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].SetO(offset[i]); + servo_[i].SetA(amplitude[i]); + servo_[i].SetT(period); + servo_[i].SetPh(phase_diff[i]); + } + } + + double ref = millis(); + double end_time = period * cycle + ref; + + while (millis() < end_time) { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].Refresh(); + } + } + vTaskDelay(5); + } + vTaskDelay(pdMS_TO_TICKS(10)); +} + +void Otto::Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, + double phase_diff[SERVO_COUNT], float steps = 1.0) { + if (GetRestState() == true) { + SetRestState(false); + } + + int cycles = (int)steps; + + //-- Execute complete cycles + if (cycles >= 1) + for (int i = 0; i < cycles; i++) + OscillateServos(amplitude, offset, period, phase_diff); + + //-- Execute the final not complete cycle + OscillateServos(amplitude, offset, period, phase_diff, (float)steps - cycles); + vTaskDelay(pdMS_TO_TICKS(10)); +} + +/////////////////////////////////////////////////////////////////// +//-- HOME = Otto at rest position -------------------------------// +/////////////////////////////////////////////////////////////////// +void Otto::Home(bool hands_down) { + if (is_otto_resting_ == false) { // Go to rest position only if necessary + // 为所有舵机准备初始位置值 + int homes[SERVO_COUNT]; + for (int i = 0; i < SERVO_COUNT; i++) { + if (i == LEFT_HAND || i == RIGHT_HAND) { + if (hands_down) { + // 如果需要复位手部,设置为默认值 + if (i == LEFT_HAND) { + homes[i] = HAND_HOME_POSITION; + } else { // RIGHT_HAND + homes[i] = 180 - HAND_HOME_POSITION; // 右手镜像位置 + } + } else { + // 如果不需要复位手部,保持当前位置 + homes[i] = servo_[i].GetPosition(); + } + } else { + // 腿部和脚部舵机始终复位 + homes[i] = 90; + } + } + + MoveServos(500, homes); + is_otto_resting_ = true; + } + + vTaskDelay(pdMS_TO_TICKS(200)); +} + +bool Otto::GetRestState() { + return is_otto_resting_; +} + +void Otto::SetRestState(bool state) { + is_otto_resting_ = state; +} + +/////////////////////////////////////////////////////////////////// +//-- PREDETERMINED MOTION SEQUENCES -----------------------------// +/////////////////////////////////////////////////////////////////// +//-- Otto movement: Jump +//-- Parameters: +//-- steps: Number of steps +//-- T: Period +//--------------------------------------------------------- +void Otto::Jump(float steps, int period) { + int up[SERVO_COUNT] = {90, 90, 150, 30, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + MoveServos(period, up); + int down[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + MoveServos(period, down); +} + +//--------------------------------------------------------- +//-- Otto gait: Walking (forward or backward) +//-- Parameters: +//-- * steps: Number of steps +//-- * T : Period +//-- * Dir: Direction: FORWARD / BACKWARD +//-- * amount: 手部摆动幅度, 0表示不摆动 +//--------------------------------------------------------- +void Otto::Walk(float steps, int period, int dir, int amount) { + //-- Oscillator parameters for walking + //-- Hip sevos are in phase + //-- Feet servos are in phase + //-- Hip and feet are 90 degrees out of phase + //-- -90 : Walk forward + //-- 90 : Walk backward + //-- Feet servos also have the same offset (for tiptoe a little bit) + int A[SERVO_COUNT] = {30, 30, 30, 30, 0, 0}; + int O[SERVO_COUNT] = {0, 0, 5, -5, HAND_HOME_POSITION - 90, HAND_HOME_POSITION}; + double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(dir * -90), DEG2RAD(dir * -90), 0, 0}; + + // 如果amount>0且有手部舵机,设置手部振幅和相位 + if (amount > 0 && has_hands_) { + // 手臂振幅使用传入的amount参数 + A[LEFT_HAND] = amount; + A[RIGHT_HAND] = amount; + + // 左手与右腿同相,右手与左腿同相,使得机器人走路时手臂自然摆动 + phase_diff[LEFT_HAND] = phase_diff[RIGHT_LEG]; // 左手与右腿同相 + phase_diff[RIGHT_HAND] = phase_diff[LEFT_LEG]; // 右手与左腿同相 + } else { + A[LEFT_HAND] = 0; + A[RIGHT_HAND] = 0; + } + + //-- Let's oscillate the servos! + Execute(A, O, period, phase_diff, steps); +} + +//--------------------------------------------------------- +//-- Otto gait: Turning (left or right) +//-- Parameters: +//-- * Steps: Number of steps +//-- * T: Period +//-- * Dir: Direction: LEFT / RIGHT +//-- * amount: 手部摆动幅度, 0表示不摆动 +//--------------------------------------------------------- +void Otto::Turn(float steps, int period, int dir, int amount) { + //-- Same coordination than for walking (see Otto::walk) + //-- The Amplitudes of the hip's oscillators are not igual + //-- When the right hip servo amplitude is higher, the steps taken by + //-- the right leg are bigger than the left. So, the robot describes an + //-- left arc + int A[SERVO_COUNT] = {30, 30, 30, 30, 0, 0}; + int O[SERVO_COUNT] = {0, 0, 5, -5, HAND_HOME_POSITION - 90, HAND_HOME_POSITION}; + double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(-90), DEG2RAD(-90), 0, 0}; + + if (dir == LEFT) { + A[0] = 30; //-- Left hip servo + A[1] = 0; //-- Right hip servo + } else { + A[0] = 0; + A[1] = 30; + } + + // 如果amount>0且有手部舵机,设置手部振幅和相位 + if (amount > 0 && has_hands_) { + // 手臂振幅使用传入的amount参数 + A[LEFT_HAND] = amount; + A[RIGHT_HAND] = amount; + + // 转向时手臂摆动相位:左手与左腿同相,右手与右腿同相,增强转向效果 + phase_diff[LEFT_HAND] = phase_diff[LEFT_LEG]; // 左手与左腿同相 + phase_diff[RIGHT_HAND] = phase_diff[RIGHT_LEG]; // 右手与右腿同相 + } else { + A[LEFT_HAND] = 0; + A[RIGHT_HAND] = 0; + } + + //-- Let's oscillate the servos! + Execute(A, O, period, phase_diff, steps); +} + +//--------------------------------------------------------- +//-- Otto gait: Lateral bend +//-- Parameters: +//-- steps: Number of bends +//-- T: Period of one bend +//-- dir: RIGHT=Right bend LEFT=Left bend +//--------------------------------------------------------- +void Otto::Bend(int steps, int period, int dir) { + // Parameters of all the movements. Default: Left bend + int bend1[SERVO_COUNT] = {90, 90, 62, 35, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + int bend2[SERVO_COUNT] = {90, 90, 62, 105, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + int homes[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + + // Time of one bend, constrained in order to avoid movements too fast. + // T=max(T, 600); + // Changes in the parameters if right direction is chosen + if (dir == -1) { + bend1[2] = 180 - 35; + bend1[3] = 180 - 60; // Not 65. Otto is unbalanced + bend2[2] = 180 - 105; + bend2[3] = 180 - 60; + } + + // Time of the bend movement. Fixed parameter to avoid falls + int T2 = 800; + + // Bend movement + for (int i = 0; i < steps; i++) { + MoveServos(T2 / 2, bend1); + MoveServos(T2 / 2, bend2); + vTaskDelay(pdMS_TO_TICKS(period * 0.8)); + MoveServos(500, homes); + } +} + +//--------------------------------------------------------- +//-- Otto gait: Shake a leg +//-- Parameters: +//-- steps: Number of shakes +//-- T: Period of one shake +//-- dir: RIGHT=Right leg LEFT=Left leg +//--------------------------------------------------------- +void Otto::ShakeLeg(int steps, int period, int dir) { + // This variable change the amount of shakes + int numberLegMoves = 2; + + // Parameters of all the movements. Default: Right leg + int shake_leg1[SERVO_COUNT] = {90, 90, 58, 35, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + int shake_leg2[SERVO_COUNT] = {90, 90, 58, 120, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + int shake_leg3[SERVO_COUNT] = {90, 90, 58, 60, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + int homes[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + + // Changes in the parameters if left leg is chosen + if (dir == -1) { + shake_leg1[2] = 180 - 35; + shake_leg1[3] = 180 - 58; + shake_leg2[2] = 180 - 120; + shake_leg2[3] = 180 - 58; + shake_leg3[2] = 180 - 60; + shake_leg3[3] = 180 - 58; + } + + // Time of the bend movement. Fixed parameter to avoid falls + int T2 = 1000; + // Time of one shake, constrained in order to avoid movements too fast. + period = period - T2; + period = std::max(period, 200 * numberLegMoves); + + for (int j = 0; j < steps; j++) { + // Bend movement + MoveServos(T2 / 2, shake_leg1); + MoveServos(T2 / 2, shake_leg2); + + // Shake movement + for (int i = 0; i < numberLegMoves; i++) { + MoveServos(period / (2 * numberLegMoves), shake_leg3); + MoveServos(period / (2 * numberLegMoves), shake_leg2); + } + MoveServos(500, homes); // Return to home position + } + + vTaskDelay(pdMS_TO_TICKS(period)); +} + +//--------------------------------------------------------- +//-- Otto movement: up & down +//-- Parameters: +//-- * steps: Number of jumps +//-- * T: Period +//-- * h: Jump height: SMALL / MEDIUM / BIG +//-- (or a number in degrees 0 - 90) +//--------------------------------------------------------- +void Otto::UpDown(float steps, int period, int height) { + //-- Both feet are 180 degrees out of phase + //-- Feet amplitude and offset are the same + //-- Initial phase for the right foot is -90, so that it starts + //-- in one extreme position (not in the middle) + int A[SERVO_COUNT] = {0, 0, height, height, 0, 0}; + int O[SERVO_COUNT] = {0, 0, height, -height, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(-90), DEG2RAD(90), 0, 0}; + + //-- Let's oscillate the servos! + Execute(A, O, period, phase_diff, steps); +} + +//--------------------------------------------------------- +//-- Otto movement: swinging side to side +//-- Parameters: +//-- steps: Number of steps +//-- T : Period +//-- h : Amount of swing (from 0 to 50 aprox) +//--------------------------------------------------------- +void Otto::Swing(float steps, int period, int height) { + //-- Both feets are in phase. The offset is half the amplitude + //-- It causes the robot to swing from side to side + int A[SERVO_COUNT] = {0, 0, height, height, 0, 0}; + int O[SERVO_COUNT] = { + 0, 0, height / 2, -height / 2, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(0), DEG2RAD(0), 0, 0}; + + //-- Let's oscillate the servos! + Execute(A, O, period, phase_diff, steps); +} + +//--------------------------------------------------------- +//-- Otto movement: swinging side to side without touching the floor with the heel +//-- Parameters: +//-- steps: Number of steps +//-- T : Period +//-- h : Amount of swing (from 0 to 50 aprox) +//--------------------------------------------------------- +void Otto::TiptoeSwing(float steps, int period, int height) { + //-- Both feets are in phase. The offset is not half the amplitude in order to tiptoe + //-- It causes the robot to swing from side to side + int A[SERVO_COUNT] = {0, 0, height, height, 0, 0}; + int O[SERVO_COUNT] = {0, 0, height, -height, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, 0}; + + //-- Let's oscillate the servos! + Execute(A, O, period, phase_diff, steps); +} + +//--------------------------------------------------------- +//-- Otto gait: Jitter +//-- Parameters: +//-- steps: Number of jitters +//-- T: Period of one jitter +//-- h: height (Values between 5 - 25) +//--------------------------------------------------------- +void Otto::Jitter(float steps, int period, int height) { + //-- Both feet are 180 degrees out of phase + //-- Feet amplitude and offset are the same + //-- Initial phase for the right foot is -90, so that it starts + //-- in one extreme position (not in the middle) + //-- h is constrained to avoid hit the feets + height = std::min(25, height); + int A[SERVO_COUNT] = {height, height, 0, 0, 0, 0}; + int O[SERVO_COUNT] = {0, 0, 0, 0, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + double phase_diff[SERVO_COUNT] = {DEG2RAD(-90), DEG2RAD(90), 0, 0, 0, 0}; + + //-- Let's oscillate the servos! + Execute(A, O, period, phase_diff, steps); +} + +//--------------------------------------------------------- +//-- Otto gait: Ascending & turn (Jitter while up&down) +//-- Parameters: +//-- steps: Number of bends +//-- T: Period of one bend +//-- h: height (Values between 5 - 15) +//--------------------------------------------------------- +void Otto::AscendingTurn(float steps, int period, int height) { + //-- Both feet and legs are 180 degrees out of phase + //-- Initial phase for the right foot is -90, so that it starts + //-- in one extreme position (not in the middle) + //-- h is constrained to avoid hit the feets + height = std::min(13, height); + int A[SERVO_COUNT] = {height, height, height, height, 0, 0}; + int O[SERVO_COUNT] = { + 0, 0, height + 4, -height + 4, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + double phase_diff[SERVO_COUNT] = {DEG2RAD(-90), DEG2RAD(90), DEG2RAD(-90), DEG2RAD(90), 0, 0}; + + //-- Let's oscillate the servos! + Execute(A, O, period, phase_diff, steps); +} + +//--------------------------------------------------------- +//-- Otto gait: Moonwalker. Otto moves like Michael Jackson +//-- Parameters: +//-- Steps: Number of steps +//-- T: Period +//-- h: Height. Typical valures between 15 and 40 +//-- dir: Direction: LEFT / RIGHT +//--------------------------------------------------------- +void Otto::Moonwalker(float steps, int period, int height, int dir) { + //-- This motion is similar to that of the caterpillar robots: A travelling + //-- wave moving from one side to another + //-- The two Otto's feet are equivalent to a minimal configuration. It is known + //-- that 2 servos can move like a worm if they are 120 degrees out of phase + //-- In the example of Otto, the two feet are mirrored so that we have: + //-- 180 - 120 = 60 degrees. The actual phase difference given to the oscillators + //-- is 60 degrees. + //-- Both amplitudes are equal. The offset is half the amplitud plus a little bit of + //- offset so that the robot tiptoe lightly + + int A[SERVO_COUNT] = {0, 0, height, height, 0, 0}; + int O[SERVO_COUNT] = { + 0, 0, height / 2 + 2, -height / 2 - 2, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + int phi = -dir * 90; + double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(phi), DEG2RAD(-60 * dir + phi), 0, 0}; + + //-- Let's oscillate the servos! + Execute(A, O, period, phase_diff, steps); +} + +//---------------------------------------------------------- +//-- Otto gait: Crusaito. A mixture between moonwalker and walk +//-- Parameters: +//-- steps: Number of steps +//-- T: Period +//-- h: height (Values between 20 - 50) +//-- dir: Direction: LEFT / RIGHT +//----------------------------------------------------------- +void Otto::Crusaito(float steps, int period, int height, int dir) { + int A[SERVO_COUNT] = {25, 25, height, height, 0, 0}; + int O[SERVO_COUNT] = { + 0, 0, height / 2 + 4, -height / 2 - 4, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + double phase_diff[SERVO_COUNT] = {90, 90, DEG2RAD(0), DEG2RAD(-60 * dir), 0, 0}; + + //-- Let's oscillate the servos! + Execute(A, O, period, phase_diff, steps); +} + +//--------------------------------------------------------- +//-- Otto gait: Flapping +//-- Parameters: +//-- steps: Number of steps +//-- T: Period +//-- h: height (Values between 10 - 30) +//-- dir: direction: FOREWARD, BACKWARD +//--------------------------------------------------------- +void Otto::Flapping(float steps, int period, int height, int dir) { + int A[SERVO_COUNT] = {12, 12, height, height, 0, 0}; + int O[SERVO_COUNT] = { + 0, 0, height - 10, -height + 10, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + double phase_diff[SERVO_COUNT] = { + DEG2RAD(0), DEG2RAD(180), DEG2RAD(-90 * dir), DEG2RAD(90 * dir), 0, 0}; + + //-- Let's oscillate the servos! + Execute(A, O, period, phase_diff, steps); +} + +//--------------------------------------------------------- +//-- 手部动作: 举手 +//-- Parameters: +//-- period: 动作时间 +//-- dir: 方向 1=左手, -1=右手, 0=双手 +//--------------------------------------------------------- +void Otto::HandsUp(int period, int dir) { + if (!has_hands_) { + return; + } + + int initial[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + int target[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + + if (dir == 0) { + target[LEFT_HAND] = 170; + target[RIGHT_HAND] = 10; + } else if (dir == 1) { + target[LEFT_HAND] = 170; + target[RIGHT_HAND] = servo_[RIGHT_HAND].GetPosition(); + } else if (dir == -1) { + target[RIGHT_HAND] = 10; + target[LEFT_HAND] = servo_[LEFT_HAND].GetPosition(); + } + + MoveServos(period, target); +} + +//--------------------------------------------------------- +//-- 手部动作: 双手放下 +//-- Parameters: +//-- period: 动作时间 +//-- dir: 方向 1=左手, -1=右手, 0=双手 +//--------------------------------------------------------- +void Otto::HandsDown(int period, int dir) { + if (!has_hands_) { + return; + } + + int target[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION}; + + if (dir == 1) { + target[RIGHT_HAND] = servo_[RIGHT_HAND].GetPosition(); + } else if (dir == -1) { + target[LEFT_HAND] = servo_[LEFT_HAND].GetPosition(); + } + + MoveServos(period, target); +} + +//--------------------------------------------------------- +//-- 手部动作: 挥手 +//-- Parameters: +//-- period: 动作周期 +//-- dir: 方向 LEFT/RIGHT/BOTH +//--------------------------------------------------------- +void Otto::HandWave(int period, int dir) { + if (!has_hands_) { + return; + } + + if (dir == BOTH) { + HandWaveBoth(period); + return; + } + + int servo_index = (dir == LEFT) ? LEFT_HAND : RIGHT_HAND; + + int current_positions[SERVO_COUNT]; + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + current_positions[i] = servo_[i].GetPosition(); + } else { + current_positions[i] = 90; + } + } + + int position; + if (servo_index == LEFT_HAND) { + position = 170; + } else { + position = 10; + } + + current_positions[servo_index] = position; + MoveServos(300, current_positions); + vTaskDelay(pdMS_TO_TICKS(300)); + + // 左右摆动5次 + for (int i = 0; i < 5; i++) { + if (servo_index == LEFT_HAND) { + current_positions[servo_index] = position - 30; + MoveServos(period / 10, current_positions); + vTaskDelay(pdMS_TO_TICKS(period / 10)); + current_positions[servo_index] = position + 30; + MoveServos(period / 10, current_positions); + } else { + current_positions[servo_index] = position + 30; + MoveServos(period / 10, current_positions); + vTaskDelay(pdMS_TO_TICKS(period / 10)); + current_positions[servo_index] = position - 30; + MoveServos(period / 10, current_positions); + } + vTaskDelay(pdMS_TO_TICKS(period / 10)); + } + + if (servo_index == LEFT_HAND) { + current_positions[servo_index] = HAND_HOME_POSITION; + } else { + current_positions[servo_index] = 180 - HAND_HOME_POSITION; + } + MoveServos(300, current_positions); +} + +//--------------------------------------------------------- +//-- 手部动作: 双手同时挥手 +//-- Parameters: +//-- period: 动作周期 +//--------------------------------------------------------- +void Otto::HandWaveBoth(int period) { + if (!has_hands_) { + return; + } + + int current_positions[SERVO_COUNT]; + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + current_positions[i] = servo_[i].GetPosition(); + } else { + current_positions[i] = 90; + } + } + + int left_position = 170; + int right_position = 10; + + current_positions[LEFT_HAND] = left_position; + current_positions[RIGHT_HAND] = right_position; + MoveServos(300, current_positions); + + // 左右摆动5次 + for (int i = 0; i < 5; i++) { + // 波浪向左 + current_positions[LEFT_HAND] = left_position - 30; + current_positions[RIGHT_HAND] = right_position + 30; + MoveServos(period / 10, current_positions); + + // 波浪向右 + current_positions[LEFT_HAND] = left_position + 30; + current_positions[RIGHT_HAND] = right_position - 30; + MoveServos(period / 10, current_positions); + } + + current_positions[LEFT_HAND] = HAND_HOME_POSITION; + current_positions[RIGHT_HAND] = 180 - HAND_HOME_POSITION; + MoveServos(300, current_positions); +} + +void Otto::EnableServoLimit(int diff_limit) { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].SetLimiter(diff_limit); + } + } +} + +void Otto::DisableServoLimit() { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servo_pins_[i] != -1) { + servo_[i].DisableLimiter(); + } + } +} diff --git a/main/boards/otto-robot/otto_movements.h b/main/boards/otto-robot/otto_movements.h index cab13a0..83f1871 100644 --- a/main/boards/otto-robot/otto_movements.h +++ b/main/boards/otto-robot/otto_movements.h @@ -1,105 +1,105 @@ -#ifndef __OTTO_MOVEMENTS_H__ -#define __OTTO_MOVEMENTS_H__ - -#include "driver/gpio.h" -#include "esp_log.h" -#include "esp_timer.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "oscillator.h" - -//-- Constants -#define FORWARD 1 -#define BACKWARD -1 -#define LEFT 1 -#define RIGHT -1 -#define BOTH 0 -#define SMALL 5 -#define MEDIUM 15 -#define BIG 30 - -// -- Servo delta limit default. degree / sec -#define SERVO_LIMIT_DEFAULT 240 - -// -- Servo indexes for easy access -#define LEFT_LEG 0 -#define RIGHT_LEG 1 -#define LEFT_FOOT 2 -#define RIGHT_FOOT 3 -#define LEFT_HAND 4 -#define RIGHT_HAND 5 -#define SERVO_COUNT 6 - -class Otto { -public: - Otto(); - ~Otto(); - - //-- Otto initialization - void Init(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand = -1, - int right_hand = -1); - //-- Attach & detach functions - void AttachServos(); - void DetachServos(); - - //-- Oscillator Trims - void SetTrims(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand = 0, - int right_hand = 0); - - //-- Predetermined Motion Functions - void MoveServos(int time, int servo_target[]); - void MoveSingle(int position, int servo_number); - void OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, - double phase_diff[SERVO_COUNT], float cycle); - - //-- HOME = Otto at rest position - void Home(bool hands_down = true); - bool GetRestState(); - void SetRestState(bool state); - - //-- Predetermined Motion Functions - void Jump(float steps = 1, int period = 2000); - - void Walk(float steps = 4, int period = 1000, int dir = FORWARD, int amount = 0); - void Turn(float steps = 4, int period = 2000, int dir = LEFT, int amount = 0); - void Bend(int steps = 1, int period = 1400, int dir = LEFT); - void ShakeLeg(int steps = 1, int period = 2000, int dir = RIGHT); - - void UpDown(float steps = 1, int period = 1000, int height = 20); - void Swing(float steps = 1, int period = 1000, int height = 20); - void TiptoeSwing(float steps = 1, int period = 900, int height = 20); - void Jitter(float steps = 1, int period = 500, int height = 20); - void AscendingTurn(float steps = 1, int period = 900, int height = 20); - - void Moonwalker(float steps = 1, int period = 900, int height = 20, int dir = LEFT); - void Crusaito(float steps = 1, int period = 900, int height = 20, int dir = FORWARD); - void Flapping(float steps = 1, int period = 1000, int height = 20, int dir = FORWARD); - - // -- 手部动作 - void HandsUp(int period = 1000, int dir = 0); // 双手举起 - void HandsDown(int period = 1000, int dir = 0); // 双手放下 - void HandWave(int period = 1000, int dir = LEFT); // 挥手 - void HandWaveBoth(int period = 1000); // 双手同时挥手 - - // -- Servo limiter - void EnableServoLimit(int speed_limit_degree_per_sec = SERVO_LIMIT_DEFAULT); - void DisableServoLimit(); - -private: - Oscillator servo_[SERVO_COUNT]; - - int servo_pins_[SERVO_COUNT]; - int servo_trim_[SERVO_COUNT]; - - unsigned long final_time_; - unsigned long partial_time_; - float increment_[SERVO_COUNT]; - - bool is_otto_resting_; - bool has_hands_; // 是否有手部舵机 - - void Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, - double phase_diff[SERVO_COUNT], float steps); -}; - +#ifndef __OTTO_MOVEMENTS_H__ +#define __OTTO_MOVEMENTS_H__ + +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "oscillator.h" + +//-- Constants +#define FORWARD 1 +#define BACKWARD -1 +#define LEFT 1 +#define RIGHT -1 +#define BOTH 0 +#define SMALL 5 +#define MEDIUM 15 +#define BIG 30 + +// -- Servo delta limit default. degree / sec +#define SERVO_LIMIT_DEFAULT 240 + +// -- Servo indexes for easy access +#define LEFT_LEG 0 +#define RIGHT_LEG 1 +#define LEFT_FOOT 2 +#define RIGHT_FOOT 3 +#define LEFT_HAND 4 +#define RIGHT_HAND 5 +#define SERVO_COUNT 6 + +class Otto { +public: + Otto(); + ~Otto(); + + //-- Otto initialization + void Init(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand = -1, + int right_hand = -1); + //-- Attach & detach functions + void AttachServos(); + void DetachServos(); + + //-- Oscillator Trims + void SetTrims(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand = 0, + int right_hand = 0); + + //-- Predetermined Motion Functions + void MoveServos(int time, int servo_target[]); + void MoveSingle(int position, int servo_number); + void OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, + double phase_diff[SERVO_COUNT], float cycle); + + //-- HOME = Otto at rest position + void Home(bool hands_down = true); + bool GetRestState(); + void SetRestState(bool state); + + //-- Predetermined Motion Functions + void Jump(float steps = 1, int period = 2000); + + void Walk(float steps = 4, int period = 1000, int dir = FORWARD, int amount = 0); + void Turn(float steps = 4, int period = 2000, int dir = LEFT, int amount = 0); + void Bend(int steps = 1, int period = 1400, int dir = LEFT); + void ShakeLeg(int steps = 1, int period = 2000, int dir = RIGHT); + + void UpDown(float steps = 1, int period = 1000, int height = 20); + void Swing(float steps = 1, int period = 1000, int height = 20); + void TiptoeSwing(float steps = 1, int period = 900, int height = 20); + void Jitter(float steps = 1, int period = 500, int height = 20); + void AscendingTurn(float steps = 1, int period = 900, int height = 20); + + void Moonwalker(float steps = 1, int period = 900, int height = 20, int dir = LEFT); + void Crusaito(float steps = 1, int period = 900, int height = 20, int dir = FORWARD); + void Flapping(float steps = 1, int period = 1000, int height = 20, int dir = FORWARD); + + // -- 手部动作 + void HandsUp(int period = 1000, int dir = 0); // 双手举起 + void HandsDown(int period = 1000, int dir = 0); // 双手放下 + void HandWave(int period = 1000, int dir = LEFT); // 挥手 + void HandWaveBoth(int period = 1000); // 双手同时挥手 + + // -- Servo limiter + void EnableServoLimit(int speed_limit_degree_per_sec = SERVO_LIMIT_DEFAULT); + void DisableServoLimit(); + +private: + Oscillator servo_[SERVO_COUNT]; + + int servo_pins_[SERVO_COUNT]; + int servo_trim_[SERVO_COUNT]; + + unsigned long final_time_; + unsigned long partial_time_; + float increment_[SERVO_COUNT]; + + bool is_otto_resting_; + bool has_hands_; // 是否有手部舵机 + + void Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period, + double phase_diff[SERVO_COUNT], float steps); +}; + #endif // __OTTO_MOVEMENTS_H__ \ No newline at end of file diff --git a/main/boards/otto-robot/otto_robot.cc b/main/boards/otto-robot/otto_robot.cc index d008787..049d97a 100644 --- a/main/boards/otto-robot/otto_robot.cc +++ b/main/boards/otto-robot/otto_robot.cc @@ -1,129 +1,129 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "application.h" -#include "codecs/no_audio_codec.h" -#include "button.h" -#include "config.h" -#include "display/lcd_display.h" -#include "lamp_controller.h" -#include "led/single_led.h" -#include "mcp_server.h" -#include "otto_emoji_display.h" -#include "power_manager.h" -#include "system_reset.h" -#include "wifi_board.h" - -#define TAG "OttoRobot" - -extern void InitializeOttoController(); - -class OttoRobot : public WifiBoard { -private: - LcdDisplay* display_; - PowerManager* power_manager_; - Button boot_button_; - void InitializePowerManager() { - power_manager_ = - new PowerManager(POWER_CHARGE_DETECT_PIN, POWER_ADC_UNIT, POWER_ADC_CHANNEL); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_CLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeLcdDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = DISPLAY_SPI_MODE; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; - - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new OttoEmojiDisplay( - panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, - DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeOttoController() { - ESP_LOGI(TAG, "初始化Otto机器人MCP控制器"); - ::InitializeOttoController(); - } - -public: - OttoRobot() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeSpi(); - InitializeLcdDisplay(); - InitializeButtons(); - InitializePowerManager(); - InitializeOttoController(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, - AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, - AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display* GetDisplay() override { return display_; } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - charging = power_manager_->IsCharging(); - discharging = !charging; - level = power_manager_->GetBatteryLevel(); - return true; - } -}; - -DECLARE_BOARD(OttoRobot); +#include +#include +#include +#include +#include +#include +#include + +#include "application.h" +#include "codecs/no_audio_codec.h" +#include "button.h" +#include "config.h" +#include "display/lcd_display.h" +#include "lamp_controller.h" +#include "led/single_led.h" +#include "mcp_server.h" +#include "otto_emoji_display.h" +#include "power_manager.h" +#include "system_reset.h" +#include "wifi_board.h" + +#define TAG "OttoRobot" + +extern void InitializeOttoController(); + +class OttoRobot : public WifiBoard { +private: + LcdDisplay* display_; + PowerManager* power_manager_; + Button boot_button_; + void InitializePowerManager() { + power_manager_ = + new PowerManager(POWER_CHARGE_DETECT_PIN, POWER_ADC_UNIT, POWER_ADC_CHANNEL); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = DISPLAY_SPI_MODE; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; + + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new OttoEmojiDisplay( + panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, + DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && + !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeOttoController() { + ESP_LOGI(TAG, "初始化Otto机器人MCP控制器"); + ::InitializeOttoController(); + } + +public: + OttoRobot() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeSpi(); + InitializeLcdDisplay(); + InitializeButtons(); + InitializePowerManager(); + InitializeOttoController(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, + AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, + AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + charging = power_manager_->IsCharging(); + discharging = !charging; + level = power_manager_->GetBatteryLevel(); + return true; + } +}; + +DECLARE_BOARD(OttoRobot); diff --git a/main/boards/otto-robot/power_manager.h b/main/boards/otto-robot/power_manager.h index 13d8ff3..ad92047 100644 --- a/main/boards/otto-robot/power_manager.h +++ b/main/boards/otto-robot/power_manager.h @@ -1,128 +1,128 @@ -#ifndef __POWER_MANAGER_H__ -#define __POWER_MANAGER_H__ - -#include -#include -#include -#include - -class PowerManager { -private: - // 电池电量区间-分压电阻为2个100k - static constexpr struct { - uint16_t adc; - uint8_t level; - } BATTERY_LEVELS[] = {{2150, 0}, {2450, 100}}; - static constexpr size_t BATTERY_LEVELS_COUNT = 2; - static constexpr size_t ADC_VALUES_COUNT = 10; - - esp_timer_handle_t timer_handle_ = nullptr; - gpio_num_t charging_pin_; - adc_unit_t adc_unit_; - adc_channel_t adc_channel_; - uint16_t adc_values_[ADC_VALUES_COUNT]; - size_t adc_values_index_ = 0; - size_t adc_values_count_ = 0; - uint8_t battery_level_ = 100; - bool is_charging_ = false; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - is_charging_ = gpio_get_level(charging_pin_) == 0; - ReadBatteryAdcData(); - } - - void ReadBatteryAdcData() { - int adc_value; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_value)); - - adc_values_[adc_values_index_] = adc_value; - adc_values_index_ = (adc_values_index_ + 1) % ADC_VALUES_COUNT; - if (adc_values_count_ < ADC_VALUES_COUNT) { - adc_values_count_++; - } - - uint32_t average_adc = 0; - for (size_t i = 0; i < adc_values_count_; i++) { - average_adc += adc_values_[i]; - } - average_adc /= adc_values_count_; - - CalculateBatteryLevel(average_adc); - - // ESP_LOGI("PowerManager", "ADC值: %d 平均值: %ld 电量: %u%%", adc_value, average_adc, - // battery_level_); - } - - void CalculateBatteryLevel(uint32_t average_adc) { - if (average_adc <= BATTERY_LEVELS[0].adc) { - battery_level_ = 0; - } else if (average_adc >= BATTERY_LEVELS[BATTERY_LEVELS_COUNT - 1].adc) { - battery_level_ = 100; - } else { - float ratio = static_cast(average_adc - BATTERY_LEVELS[0].adc) / - (BATTERY_LEVELS[1].adc - BATTERY_LEVELS[0].adc); - battery_level_ = ratio * 100; - } - } - -public: - PowerManager(gpio_num_t charging_pin, adc_unit_t adc_unit = ADC_UNIT_2, - adc_channel_t adc_channel = ADC_CHANNEL_3) - : charging_pin_(charging_pin), adc_unit_(adc_unit), adc_channel_(adc_channel) { - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&io_conf); - - esp_timer_create_args_t timer_args = { - .callback = - [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); // 1秒 - - InitializeAdc(); - } - - void InitializeAdc() { - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = adc_unit_, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &chan_config)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { return is_charging_; } - - uint8_t GetBatteryLevel() { return battery_level_; } -}; +#ifndef __POWER_MANAGER_H__ +#define __POWER_MANAGER_H__ + +#include +#include +#include +#include + +class PowerManager { +private: + // 电池电量区间-分压电阻为2个100k + static constexpr struct { + uint16_t adc; + uint8_t level; + } BATTERY_LEVELS[] = {{2150, 0}, {2450, 100}}; + static constexpr size_t BATTERY_LEVELS_COUNT = 2; + static constexpr size_t ADC_VALUES_COUNT = 10; + + esp_timer_handle_t timer_handle_ = nullptr; + gpio_num_t charging_pin_; + adc_unit_t adc_unit_; + adc_channel_t adc_channel_; + uint16_t adc_values_[ADC_VALUES_COUNT]; + size_t adc_values_index_ = 0; + size_t adc_values_count_ = 0; + uint8_t battery_level_ = 100; + bool is_charging_ = false; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + is_charging_ = gpio_get_level(charging_pin_) == 0; + ReadBatteryAdcData(); + } + + void ReadBatteryAdcData() { + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_value)); + + adc_values_[adc_values_index_] = adc_value; + adc_values_index_ = (adc_values_index_ + 1) % ADC_VALUES_COUNT; + if (adc_values_count_ < ADC_VALUES_COUNT) { + adc_values_count_++; + } + + uint32_t average_adc = 0; + for (size_t i = 0; i < adc_values_count_; i++) { + average_adc += adc_values_[i]; + } + average_adc /= adc_values_count_; + + CalculateBatteryLevel(average_adc); + + // ESP_LOGI("PowerManager", "ADC值: %d 平均值: %ld 电量: %u%%", adc_value, average_adc, + // battery_level_); + } + + void CalculateBatteryLevel(uint32_t average_adc) { + if (average_adc <= BATTERY_LEVELS[0].adc) { + battery_level_ = 0; + } else if (average_adc >= BATTERY_LEVELS[BATTERY_LEVELS_COUNT - 1].adc) { + battery_level_ = 100; + } else { + float ratio = static_cast(average_adc - BATTERY_LEVELS[0].adc) / + (BATTERY_LEVELS[1].adc - BATTERY_LEVELS[0].adc); + battery_level_ = ratio * 100; + } + } + +public: + PowerManager(gpio_num_t charging_pin, adc_unit_t adc_unit = ADC_UNIT_2, + adc_channel_t adc_channel = ADC_CHANNEL_3) + : charging_pin_(charging_pin), adc_unit_(adc_unit), adc_channel_(adc_channel) { + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + + esp_timer_create_args_t timer_args = { + .callback = + [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); // 1秒 + + InitializeAdc(); + } + + void InitializeAdc() { + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = adc_unit_, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { return is_charging_; } + + uint8_t GetBatteryLevel() { return battery_level_; } +}; #endif // __POWER_MANAGER_H__ \ No newline at end of file diff --git a/main/boards/sensecap-watcher/README.md b/main/boards/sensecap-watcher/README.md index b606399..5f7f0b2 100644 --- a/main/boards/sensecap-watcher/README.md +++ b/main/boards/sensecap-watcher/README.md @@ -1,52 +1,52 @@ -# 编译命令 - -## 一键编译 - -```bash -python scripts/release.py sensecap-watcher -``` - -## 手动配置编译 - -```bash -idf.py set-target esp32s3 -``` - -**配置** - -```bash -idf.py menuconfig -``` - -选择板子 - -``` -Xiaozhi Assistant -> Board Type -> SenseCAP Watcher -``` - -watcher 中一些额外的配置项如下,需要在menuconfig 中选择. - -``` -CONFIG_BOARD_TYPE_SENSECAP_WATCHER=y -CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/32m.csv" -CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y -CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n -CONFIG_IDF_EXPERIMENTAL_FEATURES=y -``` - -## 编译烧入 - -```bash -idf.py -DBOARD_NAME=sensecap-watcher build flash -``` - -注意: 如果当前设备出货之前是SenseCAP 固件(非小智版本),请特别小心处理闪存固件分区地址,以避免错误擦除 SenseCAP Watcher 的自身设备信息(EUI 等),否则设备即使恢复成SenseCAP固件也无法正确连接到 SenseCraft 服务器!所以在刷写固件之前,请务必记录设备的相关必要信息,以确保有恢复的方法! - -您可以使用以下命令备份生产信息 - -```bash -# firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server -esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 204800 nvsfactory.bin - +# 编译命令 + +## 一键编译 + +```bash +python scripts/release.py sensecap-watcher +``` + +## 手动配置编译 + +```bash +idf.py set-target esp32s3 +``` + +**配置** + +```bash +idf.py menuconfig +``` + +选择板子 + +``` +Xiaozhi Assistant -> Board Type -> SenseCAP Watcher +``` + +watcher 中一些额外的配置项如下,需要在menuconfig 中选择. + +``` +CONFIG_BOARD_TYPE_SENSECAP_WATCHER=y +CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/32m.csv" +CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y +CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +``` + +## 编译烧入 + +```bash +idf.py -DBOARD_NAME=sensecap-watcher build flash +``` + +注意: 如果当前设备出货之前是SenseCAP 固件(非小智版本),请特别小心处理闪存固件分区地址,以避免错误擦除 SenseCAP Watcher 的自身设备信息(EUI 等),否则设备即使恢复成SenseCAP固件也无法正确连接到 SenseCraft 服务器!所以在刷写固件之前,请务必记录设备的相关必要信息,以确保有恢复的方法! + +您可以使用以下命令备份生产信息 + +```bash +# firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server +esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 204800 nvsfactory.bin + ``` \ No newline at end of file diff --git a/main/boards/sensecap-watcher/README_en.md b/main/boards/sensecap-watcher/README_en.md index 8e95f55..e3dca06 100644 --- a/main/boards/sensecap-watcher/README_en.md +++ b/main/boards/sensecap-watcher/README_en.md @@ -1,53 +1,53 @@ -# Build Instructions - -## One-click Build - -```bash -python scripts/release.py sensecap-watcher -c config_en.json -``` - -## Manual Configuration and Build - -```bash -idf.py set-target esp32s3 -``` - -**Configuration** - -```bash -idf.py menuconfig -``` - -Select the board: - -``` -Xiaozhi Assistant -> Board Type -> SenseCAP Watcher -``` - -There are some additional configuration options for the watcher. Please select them in menuconfig: - -``` -CONFIG_BOARD_TYPE_SENSECAP_WATCHER=y -CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/32m.csv" -CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y -CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n -CONFIG_IDF_EXPERIMENTAL_FEATURES=y -CONFIG_LANGUAGE_EN_US=y -CONFIG_SR_WN_WN9_JARVIS_TTS=y -``` - -## Build and Flash - -```bash -idf.py -DBOARD_NAME=sensecap-watcher-en build flash -``` - -Note: If your device was previously shipped with the SenseCAP firmware (not the Xiaozhi version), please be very careful with the flash partition addresses to avoid accidentally erasing the device information (such as EUI) of the SenseCAP Watcher. Otherwise, even if you restore the SenseCAP firmware, the device may not be able to connect to the SenseCraft server correctly! Therefore, before flashing the firmware, be sure to record the necessary device information to ensure you have a way to recover it! - -You can use the following command to back up the factory information: - -```bash -# Firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server -esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 204800 nvsfactory.bin -``` +# Build Instructions + +## One-click Build + +```bash +python scripts/release.py sensecap-watcher -c config_en.json +``` + +## Manual Configuration and Build + +```bash +idf.py set-target esp32s3 +``` + +**Configuration** + +```bash +idf.py menuconfig +``` + +Select the board: + +``` +Xiaozhi Assistant -> Board Type -> SenseCAP Watcher +``` + +There are some additional configuration options for the watcher. Please select them in menuconfig: + +``` +CONFIG_BOARD_TYPE_SENSECAP_WATCHER=y +CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/32m.csv" +CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y +CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_LANGUAGE_EN_US=y +CONFIG_SR_WN_WN9_JARVIS_TTS=y +``` + +## Build and Flash + +```bash +idf.py -DBOARD_NAME=sensecap-watcher-en build flash +``` + +Note: If your device was previously shipped with the SenseCAP firmware (not the Xiaozhi version), please be very careful with the flash partition addresses to avoid accidentally erasing the device information (such as EUI) of the SenseCAP Watcher. Otherwise, even if you restore the SenseCAP firmware, the device may not be able to connect to the SenseCraft server correctly! Therefore, before flashing the firmware, be sure to record the necessary device information to ensure you have a way to recover it! + +You can use the following command to back up the factory information: + +```bash +# Firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server +esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 204800 nvsfactory.bin +``` diff --git a/main/boards/sensecap-watcher/config.h b/main/boards/sensecap-watcher/config.h index 8d1f5dc..66e540a 100644 --- a/main/boards/sensecap-watcher/config.h +++ b/main/boards/sensecap-watcher/config.h @@ -1,152 +1,152 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include "driver/spi_common.h" -#include "esp_io_expander.h" - -// SSCMA Client Configuration -#define CONFIG_SSCMA_EVENT_QUEUE_SIZE 1 -#define CONFIG_SSCMA_TX_BUFFER_SIZE 8192 -#define CONFIG_SSCMA_RX_BUFFER_SIZE 98304 - -// SSCMA Client Process Task -#define CONFIG_SSCMA_PROCESS_TASK_STACK_SIZE 10240 -#define CONFIG_SSCMA_PROCESS_TASK_PRIORITY 5 -#define CONFIG_SSCMA_PROCESS_TASK_AFFINITY_CPU1 1 -#define CONFIG_SSCMA_PROCESS_TASK_AFFINITY 1 -#define CONFIG_SSCMA_PROCESS_TASK_STACK_ALLOC_EXTERNAL 1 - -// SSCMA Client Monitor Task -#define CONFIG_SSCMA_MONITOR_TASK_STACK_SIZE 10240 -#define CONFIG_SSCMA_MONITOR_TASK_PRIORITY 4 -#define CONFIG_SSCMA_MONITOR_TASK_AFFINITY_CPU1 1 -#define CONFIG_SSCMA_MONITOR_TASK_AFFINITY 1 -#define CONFIG_SSCMA_MONITOR_TASK_STACK_ALLOC_EXTERNAL 1 -#define CONFIG_SSCMA_ALLOC_SMALL_SHORTTERM_MEM_EXTERNALLY 1 - -/* General I2C */ -#define BSP_GENERAL_I2C_NUM (I2C_NUM_0) -#define BSP_GENERAL_I2C_SDA (GPIO_NUM_47) -#define BSP_GENERAL_I2C_SCL (GPIO_NUM_48) - -/* Audio */ -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE false - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_11 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16 - - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7243E_ADDR (0x14) - - - -#define BUILTIN_LED_GPIO GPIO_NUM_40 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -/* Expander */ -#define BSP_IO_EXPANDER_INT (GPIO_NUM_2) -#define DRV_IO_EXP_INPUT_MASK (0x20ff) // P0.0 ~ P0.7 | P1.3 -#define DRV_IO_EXP_OUTPUT_MASK (0xDf00) // P1.0 ~ P1.7 & ~P1.3 - -/* Expander IO PIN */ -#define BSP_PWR_CHRG_DET (IO_EXPANDER_PIN_NUM_0) -#define BSP_PWR_STDBY_DET (IO_EXPANDER_PIN_NUM_1) -#define BSP_PWR_VBUS_IN_DET (IO_EXPANDER_PIN_NUM_2) -#define BSP_PWR_SDCARD (IO_EXPANDER_PIN_NUM_8) -#define BSP_PWR_LCD (IO_EXPANDER_PIN_NUM_9) -#define BSP_PWR_SYSTEM (IO_EXPANDER_PIN_NUM_10) -#define BSP_PWR_AI_CHIP (IO_EXPANDER_PIN_NUM_11) -#define BSP_PWR_CODEC_PA (IO_EXPANDER_PIN_NUM_12) -#define BSP_PWR_BAT_DET (IO_EXPANDER_PIN_NUM_13) -#define BSP_PWR_GROVE (IO_EXPANDER_PIN_NUM_14) -#define BSP_PWR_BAT_ADC (IO_EXPANDER_PIN_NUM_15) - -#define BSP_PWR_START_UP (BSP_PWR_SDCARD | BSP_PWR_LCD | BSP_PWR_SYSTEM | BSP_PWR_AI_CHIP | BSP_PWR_CODEC_PA | BSP_PWR_GROVE | BSP_PWR_BAT_ADC) - -#define BSP_KNOB_BTN (IO_EXPANDER_PIN_NUM_3) -#define BSP_KNOB_A_PIN GPIO_NUM_41 -#define BSP_KNOB_B_PIN GPIO_NUM_42 - -/* SPI */ -#define BSP_SPI2_HOST_SCLK (GPIO_NUM_4) -#define BSP_SPI2_HOST_MOSI (GPIO_NUM_5) -#define BSP_SPI2_HOST_MISO (GPIO_NUM_6) - -/* SD Card */ -#define BSP_SD_SPI_NUM (SPI2_HOST) -#define BSP_SD_SPI_CS (GPIO_NUM_46) -#define BSP_SD_GPIO_DET (IO_EXPANDER_PIN_NUM_4) - -/* QSPI */ -#define BSP_SPI3_HOST_PCLK (GPIO_NUM_7) -#define BSP_SPI3_HOST_DATA0 (GPIO_NUM_9) -#define BSP_SPI3_HOST_DATA1 (GPIO_NUM_1) -#define BSP_SPI3_HOST_DATA2 (GPIO_NUM_14) -#define BSP_SPI3_HOST_DATA3 (GPIO_NUM_13) - -/* LCD */ -#define BSP_LCD_SPI_NUM (SPI3_HOST) -#define BSP_LCD_SPI_CS (GPIO_NUM_45) -#define BSP_LCD_GPIO_RST (GPIO_NUM_NC) -#define BSP_LCD_GPIO_DC (GPIO_NUM_1) - -#define DISPLAY_WIDTH 412 -#define DISPLAY_HEIGHT 412 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_8 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -/* Touch */ -#define BSP_TOUCH_I2C_NUM (1) -#define BSP_TOUCH_GPIO_INT (IO_EXPANDER_PIN_NUM_5) -#define BSP_TOUCH_I2C_SDA (GPIO_NUM_39) -#define BSP_TOUCH_I2C_SCL (GPIO_NUM_38) -#define BSP_TOUCH_I2C_CLK (400000) - -/* Settings */ -#define DRV_LCD_PIXEL_CLK_HZ (40 * 1000 * 1000) -#define DRV_LCD_CMD_BITS (32) -#define DRV_LCD_PARAM_BITS (8) -#define DRV_LCD_RGB_ELEMENT_ORDER (LCD_RGB_ELEMENT_ORDER_RGB) -#define DRV_LCD_BITS_PER_PIXEL (16) - -#define CONFIG_BSP_LCD_SPI_DMA_SIZE_DIV 16 - -/* ADC */ -#define BSP_BAT_ADC_CHAN (ADC_CHANNEL_2) // GPIO3 -#define BSP_BAT_ADC_ATTEN (ADC_ATTEN_DB_2_5) // 0 ~ 1100 mV -#define BSP_BAT_VOL_RATIO ((62 + 20) / 20) - -/* Himax */ -#define BSP_SSCMA_CLIENT_RST (IO_EXPANDER_PIN_NUM_7) -#define BSP_SSCMA_CLIENT_RST_USE_EXPANDER (true) - -#define BSP_SSCMA_CLIENT_SPI_NUM (SPI2_HOST) -#define BSP_SSCMA_CLIENT_SPI_CS (GPIO_NUM_21) -#define BSP_SSCMA_CLIENT_SPI_SYNC (IO_EXPANDER_PIN_NUM_6) -#define BSP_SSCMA_CLIENT_SPI_SYNC_USE_EXPANDER (true) -#define BSP_SSCMA_CLIENT_SPI_CLK (12 * 1000 * 1000) - -#define BSP_SSCMA_FLASHER_UART_NUM (UART_NUM_1) -#define BSP_SSCMA_FLASHER_UART_TX (GPIO_NUM_17) -#define BSP_SSCMA_FLASHER_UART_RX (GPIO_NUM_18) -#define BSP_SSCMA_FLASHER_UART_BAUD_RATE (921600) - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include "driver/spi_common.h" +#include "esp_io_expander.h" + +// SSCMA Client Configuration +#define CONFIG_SSCMA_EVENT_QUEUE_SIZE 1 +#define CONFIG_SSCMA_TX_BUFFER_SIZE 8192 +#define CONFIG_SSCMA_RX_BUFFER_SIZE 98304 + +// SSCMA Client Process Task +#define CONFIG_SSCMA_PROCESS_TASK_STACK_SIZE 10240 +#define CONFIG_SSCMA_PROCESS_TASK_PRIORITY 5 +#define CONFIG_SSCMA_PROCESS_TASK_AFFINITY_CPU1 1 +#define CONFIG_SSCMA_PROCESS_TASK_AFFINITY 1 +#define CONFIG_SSCMA_PROCESS_TASK_STACK_ALLOC_EXTERNAL 1 + +// SSCMA Client Monitor Task +#define CONFIG_SSCMA_MONITOR_TASK_STACK_SIZE 10240 +#define CONFIG_SSCMA_MONITOR_TASK_PRIORITY 4 +#define CONFIG_SSCMA_MONITOR_TASK_AFFINITY_CPU1 1 +#define CONFIG_SSCMA_MONITOR_TASK_AFFINITY 1 +#define CONFIG_SSCMA_MONITOR_TASK_STACK_ALLOC_EXTERNAL 1 +#define CONFIG_SSCMA_ALLOC_SMALL_SHORTTERM_MEM_EXTERNALLY 1 + +/* General I2C */ +#define BSP_GENERAL_I2C_NUM (I2C_NUM_0) +#define BSP_GENERAL_I2C_SDA (GPIO_NUM_47) +#define BSP_GENERAL_I2C_SCL (GPIO_NUM_48) + +/* Audio */ +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE false + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_11 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16 + + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7243E_ADDR (0x14) + + + +#define BUILTIN_LED_GPIO GPIO_NUM_40 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +/* Expander */ +#define BSP_IO_EXPANDER_INT (GPIO_NUM_2) +#define DRV_IO_EXP_INPUT_MASK (0x20ff) // P0.0 ~ P0.7 | P1.3 +#define DRV_IO_EXP_OUTPUT_MASK (0xDf00) // P1.0 ~ P1.7 & ~P1.3 + +/* Expander IO PIN */ +#define BSP_PWR_CHRG_DET (IO_EXPANDER_PIN_NUM_0) +#define BSP_PWR_STDBY_DET (IO_EXPANDER_PIN_NUM_1) +#define BSP_PWR_VBUS_IN_DET (IO_EXPANDER_PIN_NUM_2) +#define BSP_PWR_SDCARD (IO_EXPANDER_PIN_NUM_8) +#define BSP_PWR_LCD (IO_EXPANDER_PIN_NUM_9) +#define BSP_PWR_SYSTEM (IO_EXPANDER_PIN_NUM_10) +#define BSP_PWR_AI_CHIP (IO_EXPANDER_PIN_NUM_11) +#define BSP_PWR_CODEC_PA (IO_EXPANDER_PIN_NUM_12) +#define BSP_PWR_BAT_DET (IO_EXPANDER_PIN_NUM_13) +#define BSP_PWR_GROVE (IO_EXPANDER_PIN_NUM_14) +#define BSP_PWR_BAT_ADC (IO_EXPANDER_PIN_NUM_15) + +#define BSP_PWR_START_UP (BSP_PWR_SDCARD | BSP_PWR_LCD | BSP_PWR_SYSTEM | BSP_PWR_AI_CHIP | BSP_PWR_CODEC_PA | BSP_PWR_GROVE | BSP_PWR_BAT_ADC) + +#define BSP_KNOB_BTN (IO_EXPANDER_PIN_NUM_3) +#define BSP_KNOB_A_PIN GPIO_NUM_41 +#define BSP_KNOB_B_PIN GPIO_NUM_42 + +/* SPI */ +#define BSP_SPI2_HOST_SCLK (GPIO_NUM_4) +#define BSP_SPI2_HOST_MOSI (GPIO_NUM_5) +#define BSP_SPI2_HOST_MISO (GPIO_NUM_6) + +/* SD Card */ +#define BSP_SD_SPI_NUM (SPI2_HOST) +#define BSP_SD_SPI_CS (GPIO_NUM_46) +#define BSP_SD_GPIO_DET (IO_EXPANDER_PIN_NUM_4) + +/* QSPI */ +#define BSP_SPI3_HOST_PCLK (GPIO_NUM_7) +#define BSP_SPI3_HOST_DATA0 (GPIO_NUM_9) +#define BSP_SPI3_HOST_DATA1 (GPIO_NUM_1) +#define BSP_SPI3_HOST_DATA2 (GPIO_NUM_14) +#define BSP_SPI3_HOST_DATA3 (GPIO_NUM_13) + +/* LCD */ +#define BSP_LCD_SPI_NUM (SPI3_HOST) +#define BSP_LCD_SPI_CS (GPIO_NUM_45) +#define BSP_LCD_GPIO_RST (GPIO_NUM_NC) +#define BSP_LCD_GPIO_DC (GPIO_NUM_1) + +#define DISPLAY_WIDTH 412 +#define DISPLAY_HEIGHT 412 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_8 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +/* Touch */ +#define BSP_TOUCH_I2C_NUM (1) +#define BSP_TOUCH_GPIO_INT (IO_EXPANDER_PIN_NUM_5) +#define BSP_TOUCH_I2C_SDA (GPIO_NUM_39) +#define BSP_TOUCH_I2C_SCL (GPIO_NUM_38) +#define BSP_TOUCH_I2C_CLK (400000) + +/* Settings */ +#define DRV_LCD_PIXEL_CLK_HZ (40 * 1000 * 1000) +#define DRV_LCD_CMD_BITS (32) +#define DRV_LCD_PARAM_BITS (8) +#define DRV_LCD_RGB_ELEMENT_ORDER (LCD_RGB_ELEMENT_ORDER_RGB) +#define DRV_LCD_BITS_PER_PIXEL (16) + +#define CONFIG_BSP_LCD_SPI_DMA_SIZE_DIV 16 + +/* ADC */ +#define BSP_BAT_ADC_CHAN (ADC_CHANNEL_2) // GPIO3 +#define BSP_BAT_ADC_ATTEN (ADC_ATTEN_DB_2_5) // 0 ~ 1100 mV +#define BSP_BAT_VOL_RATIO ((62 + 20) / 20) + +/* Himax */ +#define BSP_SSCMA_CLIENT_RST (IO_EXPANDER_PIN_NUM_7) +#define BSP_SSCMA_CLIENT_RST_USE_EXPANDER (true) + +#define BSP_SSCMA_CLIENT_SPI_NUM (SPI2_HOST) +#define BSP_SSCMA_CLIENT_SPI_CS (GPIO_NUM_21) +#define BSP_SSCMA_CLIENT_SPI_SYNC (IO_EXPANDER_PIN_NUM_6) +#define BSP_SSCMA_CLIENT_SPI_SYNC_USE_EXPANDER (true) +#define BSP_SSCMA_CLIENT_SPI_CLK (12 * 1000 * 1000) + +#define BSP_SSCMA_FLASHER_UART_NUM (UART_NUM_1) +#define BSP_SSCMA_FLASHER_UART_TX (GPIO_NUM_17) +#define BSP_SSCMA_FLASHER_UART_RX (GPIO_NUM_18) +#define BSP_SSCMA_FLASHER_UART_BAUD_RATE (921600) + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/sensecap-watcher/config.json b/main/boards/sensecap-watcher/config.json index 177fad7..8b348b4 100644 --- a/main/boards/sensecap-watcher/config.json +++ b/main/boards/sensecap-watcher/config.json @@ -1,16 +1,16 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "sensecap-watcher", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/32m.csv\"", - "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y", - "CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n", - "CONFIG_IDF_EXPERIMENTAL_FEATURES=y", - "CONFIG_FREERTOS_HZ=1000" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "sensecap-watcher", + "sdkconfig_append": [ + "CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/32m.csv\"", + "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y", + "CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n", + "CONFIG_IDF_EXPERIMENTAL_FEATURES=y", + "CONFIG_FREERTOS_HZ=1000" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/sensecap-watcher/config_en.json b/main/boards/sensecap-watcher/config_en.json index 0435fa3..7a71f16 100644 --- a/main/boards/sensecap-watcher/config_en.json +++ b/main/boards/sensecap-watcher/config_en.json @@ -1,20 +1,20 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "sensecap-watcher-en", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/32m.csv\"", - "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y", - "CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n", - "CONFIG_IDF_EXPERIMENTAL_FEATURES=y", - "CONFIG_FREERTOS_HZ=1000", - "CONFIG_LANGUAGE_EN_US=y", - "CONFIG_SR_WN_WN9_JARVIS_TTS=y", - "CONFIG_SR_WN_WN9_SOPHIA_TTS=y", - "CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=n" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "sensecap-watcher-en", + "sdkconfig_append": [ + "CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/32m.csv\"", + "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y", + "CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n", + "CONFIG_IDF_EXPERIMENTAL_FEATURES=y", + "CONFIG_FREERTOS_HZ=1000", + "CONFIG_LANGUAGE_EN_US=y", + "CONFIG_SR_WN_WN9_JARVIS_TTS=y", + "CONFIG_SR_WN_WN9_SOPHIA_TTS=y", + "CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=n" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/sensecap-watcher/sensecap_audio_codec.cc b/main/boards/sensecap-watcher/sensecap_audio_codec.cc index ac474d7..feafe8a 100644 --- a/main/boards/sensecap-watcher/sensecap_audio_codec.cc +++ b/main/boards/sensecap-watcher/sensecap_audio_codec.cc @@ -1,214 +1,214 @@ -#include "sensecap_audio_codec.h" - -#include -#include -#include - -static const char TAG[] = "SensecapAudioCodec"; - -SensecapAudioCodec::SensecapAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7243e_addr, bool input_reference) { - duplex_ = true; // 是否双工 - input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 - input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - CreateDuplexChannels(mclk, bclk, ws, dout, din); - - // Do initialize of related interface: data_if, ctrl_if and gpio_if - audio_codec_i2s_cfg_t i2s_cfg = { - .port = I2S_NUM_0, - .rx_handle = rx_handle_, - .tx_handle = tx_handle_, - }; - data_if_ = audio_codec_new_i2s_data(&i2s_cfg); - assert(data_if_ != NULL); - - // Output - audio_codec_i2c_cfg_t i2c_cfg = { - .port = (i2c_port_t)0, - .addr = es8311_addr, - .bus_handle = i2c_master_handle, - }; - out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(out_ctrl_if_ != NULL); - - gpio_if_ = audio_codec_new_gpio(); - assert(gpio_if_ != NULL); - - es8311_codec_cfg_t es8311_cfg = {}; - es8311_cfg.ctrl_if = out_ctrl_if_; - es8311_cfg.gpio_if = gpio_if_; - es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC; - es8311_cfg.pa_pin = pa_pin; - es8311_cfg.use_mclk = true; - es8311_cfg.hw_gain.pa_voltage = 5.0; - es8311_cfg.hw_gain.codec_dac_voltage = 3.3; - out_codec_if_ = es8311_codec_new(&es8311_cfg); - assert(out_codec_if_ != NULL); - - esp_codec_dev_cfg_t dev_cfg = { - .dev_type = ESP_CODEC_DEV_TYPE_OUT, - .codec_if = out_codec_if_, - .data_if = data_if_, - }; - output_dev_ = esp_codec_dev_new(&dev_cfg); - assert(output_dev_ != NULL); - - // Input - i2c_cfg.addr = es7243e_addr << 1; - in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); - assert(in_ctrl_if_ != NULL); - - es7243e_codec_cfg_t es7243e_cfg = {}; - es7243e_cfg.ctrl_if = in_ctrl_if_; - in_codec_if_ = es7243e_codec_new(&es7243e_cfg); - assert(in_codec_if_ != NULL); - - dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; - dev_cfg.codec_if = in_codec_if_; - input_dev_ = esp_codec_dev_new(&dev_cfg); - assert(input_dev_ != NULL); - - esp_codec_set_disable_when_closed(output_dev_, false); - esp_codec_set_disable_when_closed(input_dev_, false); - - ESP_LOGI(TAG, "SensecapAudioDevice initialized"); -} - -SensecapAudioCodec::~SensecapAudioCodec() { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - esp_codec_dev_delete(output_dev_); - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - esp_codec_dev_delete(input_dev_); - - audio_codec_delete_codec_if(in_codec_if_); - audio_codec_delete_ctrl_if(in_ctrl_if_); - audio_codec_delete_codec_if(out_codec_if_); - audio_codec_delete_ctrl_if(out_ctrl_if_); - audio_codec_delete_gpio_if(gpio_if_); - audio_codec_delete_data_if(data_if_); -} - -void SensecapAudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { - assert(input_sample_rate_ == output_sample_rate_); - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, - .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256 - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_MONO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_16BIT, - .ws_pol = false, - .bit_shift = true, - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - }, - .gpio_cfg = { - .mclk = mclk, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - - std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT; - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); -} - -void SensecapAudioCodec::SetOutputVolume(int volume) { - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); - AudioCodec::SetOutputVolume(volume); -} - -void SensecapAudioCodec::EnableInput(bool enable) { - if (enable == input_enabled_) { - return; - } - if (enable) { - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 2, - .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1), - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 27.0)); - } else { - ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); - } - AudioCodec::EnableInput(enable); -} - -void SensecapAudioCodec::EnableOutput(bool enable) { - if (enable == output_enabled_) { - return; - } - if (enable) { - // Play 16bit 1 channel - esp_codec_dev_sample_info_t fs = { - .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, - .sample_rate = (uint32_t)output_sample_rate_, - .mclk_multiple = 0, - }; - ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); - if (pa_pin_ != GPIO_NUM_NC) { - gpio_set_level(pa_pin_, 1); - } - } - else { - ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); - if (pa_pin_ != GPIO_NUM_NC) { - gpio_set_level(pa_pin_, 0); - } - } - AudioCodec::EnableOutput(enable); -} - -int SensecapAudioCodec::Read(int16_t* dest, int samples) { - if (input_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); - } - return samples; -} - -int SensecapAudioCodec::Write(const int16_t* data, int samples) { - if (output_enabled_) { - ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); - } - return samples; +#include "sensecap_audio_codec.h" + +#include +#include +#include + +static const char TAG[] = "SensecapAudioCodec"; + +SensecapAudioCodec::SensecapAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7243e_addr, bool input_reference) { + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + CreateDuplexChannels(mclk, bclk, ws, dout, din); + + // Do initialize of related interface: data_if, ctrl_if and gpio_if + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = rx_handle_, + .tx_handle = tx_handle_, + }; + data_if_ = audio_codec_new_i2s_data(&i2s_cfg); + assert(data_if_ != NULL); + + // Output + audio_codec_i2c_cfg_t i2c_cfg = { + .port = (i2c_port_t)0, + .addr = es8311_addr, + .bus_handle = i2c_master_handle, + }; + out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(out_ctrl_if_ != NULL); + + gpio_if_ = audio_codec_new_gpio(); + assert(gpio_if_ != NULL); + + es8311_codec_cfg_t es8311_cfg = {}; + es8311_cfg.ctrl_if = out_ctrl_if_; + es8311_cfg.gpio_if = gpio_if_; + es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC; + es8311_cfg.pa_pin = pa_pin; + es8311_cfg.use_mclk = true; + es8311_cfg.hw_gain.pa_voltage = 5.0; + es8311_cfg.hw_gain.codec_dac_voltage = 3.3; + out_codec_if_ = es8311_codec_new(&es8311_cfg); + assert(out_codec_if_ != NULL); + + esp_codec_dev_cfg_t dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_OUT, + .codec_if = out_codec_if_, + .data_if = data_if_, + }; + output_dev_ = esp_codec_dev_new(&dev_cfg); + assert(output_dev_ != NULL); + + // Input + i2c_cfg.addr = es7243e_addr << 1; + in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(in_ctrl_if_ != NULL); + + es7243e_codec_cfg_t es7243e_cfg = {}; + es7243e_cfg.ctrl_if = in_ctrl_if_; + in_codec_if_ = es7243e_codec_new(&es7243e_cfg); + assert(in_codec_if_ != NULL); + + dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; + dev_cfg.codec_if = in_codec_if_; + input_dev_ = esp_codec_dev_new(&dev_cfg); + assert(input_dev_ != NULL); + + esp_codec_set_disable_when_closed(output_dev_, false); + esp_codec_set_disable_when_closed(input_dev_, false); + + ESP_LOGI(TAG, "SensecapAudioDevice initialized"); +} + +SensecapAudioCodec::~SensecapAudioCodec() { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + esp_codec_dev_delete(output_dev_); + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + esp_codec_dev_delete(input_dev_); + + audio_codec_delete_codec_if(in_codec_if_); + audio_codec_delete_ctrl_if(in_ctrl_if_); + audio_codec_delete_codec_if(out_codec_if_); + audio_codec_delete_ctrl_if(out_ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void SensecapAudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + assert(input_sample_rate_ == output_sample_rate_); + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, + .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256 + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_MONO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + + std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + +void SensecapAudioCodec::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void SensecapAudioCodec::EnableInput(bool enable) { + if (enable == input_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 2, + .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1), + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 27.0)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void SensecapAudioCodec::EnableOutput(bool enable) { + if (enable == output_enabled_) { + return; + } + if (enable) { + // Play 16bit 1 channel + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); + if (pa_pin_ != GPIO_NUM_NC) { + gpio_set_level(pa_pin_, 1); + } + } + else { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + if (pa_pin_ != GPIO_NUM_NC) { + gpio_set_level(pa_pin_, 0); + } + } + AudioCodec::EnableOutput(enable); +} + +int SensecapAudioCodec::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} + +int SensecapAudioCodec::Write(const int16_t* data, int samples) { + if (output_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); + } + return samples; } \ No newline at end of file diff --git a/main/boards/sensecap-watcher/sensecap_audio_codec.h b/main/boards/sensecap-watcher/sensecap_audio_codec.h index 794a4d7..28f8308 100644 --- a/main/boards/sensecap-watcher/sensecap_audio_codec.h +++ b/main/boards/sensecap-watcher/sensecap_audio_codec.h @@ -1,38 +1,38 @@ -#ifndef _SENSECAP_AUDIO_CODEC_H -#define _SENSECAP_AUDIO_CODEC_H - -#include "audio_codec.h" - -#include -#include - -class SensecapAudioCodec : public AudioCodec { -private: - const audio_codec_data_if_t* data_if_ = nullptr; - const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; - const audio_codec_if_t* out_codec_if_ = nullptr; - const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; - const audio_codec_if_t* in_codec_if_ = nullptr; - const audio_codec_gpio_if_t* gpio_if_ = nullptr; - - esp_codec_dev_handle_t output_dev_ = nullptr; - esp_codec_dev_handle_t input_dev_ = nullptr; - gpio_num_t pa_pin_ = GPIO_NUM_NC; - - void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); - - virtual int Read(int16_t* dest, int samples) override; - virtual int Write(const int16_t* data, int samples) override; - -public: - SensecapAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, - gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference); - virtual ~SensecapAudioCodec(); - - virtual void SetOutputVolume(int volume) override; - virtual void EnableInput(bool enable) override; - virtual void EnableOutput(bool enable) override; -}; - -#endif // _SENSECAP_AUDIO_CODEC_H +#ifndef _SENSECAP_AUDIO_CODEC_H +#define _SENSECAP_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include + +class SensecapAudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t* data_if_ = nullptr; + const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; + const audio_codec_if_t* out_codec_if_ = nullptr; + const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; + const audio_codec_if_t* in_codec_if_ = nullptr; + const audio_codec_gpio_if_t* gpio_if_ = nullptr; + + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + gpio_num_t pa_pin_ = GPIO_NUM_NC; + + void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + SensecapAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference); + virtual ~SensecapAudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _SENSECAP_AUDIO_CODEC_H diff --git a/main/boards/sensecap-watcher/sensecap_watcher.cc b/main/boards/sensecap-watcher/sensecap_watcher.cc index bdc7351..92abc33 100644 --- a/main/boards/sensecap-watcher/sensecap_watcher.cc +++ b/main/boards/sensecap-watcher/sensecap_watcher.cc @@ -1,602 +1,602 @@ -#include "display/lv_display.h" -#include "misc/lv_event.h" -#include "wifi_board.h" -#include "sensecap_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "knob.h" -#include "config.h" -#include "led/single_led.h" -#include "power_save_timer.h" -#include "sscma_camera.h" -#include "lvgl_theme.h" - -#include -#include "esp_check.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "assets/lang_config.h" - -#define TAG "sensecap_watcher" - -class CustomLcdDisplay : public SpiLcdDisplay { - public: - CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, - esp_lcd_panel_handle_t panel_handle, - int width, - int height, - int offset_x, - int offset_y, - bool mirror_x, - bool mirror_y, - bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - - DisplayLockGuard lock(this); - auto lvgl_theme = static_cast(current_theme_); - auto text_font = lvgl_theme->text_font()->font(); - auto icon_font = lvgl_theme->icon_font()->font(); - - lv_obj_set_size(status_bar_, LV_HOR_RES, text_font->line_height * 2 + 10); - lv_obj_set_style_layout(status_bar_, LV_LAYOUT_NONE, 0); - lv_obj_set_style_pad_top(status_bar_, 10, 0); - lv_obj_set_style_pad_bottom(status_bar_, 1, 0); - - // 针对圆形屏幕调整位置 - // network battery mute // - // status // - lv_obj_align(battery_label_, LV_ALIGN_TOP_MID, -2.5 * icon_font->line_height, 0); - lv_obj_align(network_label_, LV_ALIGN_TOP_MID, -0.5 * icon_font->line_height, 0); - lv_obj_align(mute_label_, LV_ALIGN_TOP_MID, 1.5 * icon_font->line_height, 0); - - lv_obj_align(status_label_, LV_ALIGN_BOTTOM_MID, 0, 0); - lv_obj_set_flex_grow(status_label_, 0); - lv_obj_set_width(status_label_, LV_HOR_RES * 0.75); - lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - - lv_obj_align(notification_label_, LV_ALIGN_BOTTOM_MID, 0, 0); - lv_obj_set_width(notification_label_, LV_HOR_RES * 0.75); - lv_label_set_long_mode(notification_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - - lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, -20); - lv_obj_set_style_bg_color(low_battery_popup_, lv_color_hex(0xFF0000), 0); - lv_obj_set_width(low_battery_label_, LV_HOR_RES * 0.75); - lv_label_set_long_mode(low_battery_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - } -}; - -class SensecapWatcher : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - LcdDisplay* display_; - std::unique_ptr knob_; - esp_io_expander_handle_t io_exp_handle; - button_handle_t btns; - PowerSaveTimer* power_save_timer_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - uint32_t long_press_cnt_; - button_driver_t* btn_driver_ = nullptr; - static SensecapWatcher* instance_; - SscmaCamera* camera_ = nullptr; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(10); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - bool is_charging = (IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0); - if (is_charging) { - ESP_LOGI(TAG, "charging"); - GetBacklight()->SetBrightness(0); - } else { - IoExpanderSetLevel(BSP_PWR_SYSTEM, 0); - } - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = BSP_GENERAL_I2C_SDA, - .scl_io_num = BSP_GENERAL_I2C_SCL, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - - // pulldown for lcd i2c - const gpio_config_t io_config = { - .pin_bit_mask = (1ULL << BSP_TOUCH_I2C_SDA) | (1ULL << BSP_TOUCH_I2C_SCL) | (1ULL << BSP_SPI3_HOST_PCLK) | (1ULL << BSP_SPI3_HOST_DATA0) | (1ULL << BSP_SPI3_HOST_DATA1) - | (1ULL << BSP_SPI3_HOST_DATA2) | (1ULL << BSP_SPI3_HOST_DATA3) | (1ULL << BSP_LCD_SPI_CS) | (1UL << DISPLAY_BACKLIGHT_PIN), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - gpio_config(&io_config); - - gpio_set_level(BSP_TOUCH_I2C_SDA, 0); - gpio_set_level(BSP_TOUCH_I2C_SCL, 0); - - gpio_set_level(BSP_LCD_SPI_CS, 0); - gpio_set_level(DISPLAY_BACKLIGHT_PIN, 0); - gpio_set_level(BSP_SPI3_HOST_PCLK, 0); - gpio_set_level(BSP_SPI3_HOST_DATA0, 0); - gpio_set_level(BSP_SPI3_HOST_DATA1, 0); - gpio_set_level(BSP_SPI3_HOST_DATA2, 0); - gpio_set_level(BSP_SPI3_HOST_DATA3, 0); - - } - - esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) { - return esp_io_expander_set_level(io_exp_handle, pin_mask, level); - } - - uint8_t IoExpanderGetLevel(uint16_t pin_mask) { - uint32_t pin_val = 0; - esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val); - pin_mask &= DRV_IO_EXP_INPUT_MASK; - return (uint8_t)((pin_val & pin_mask) ? 1 : 0); - } - - void InitializeExpander() { - esp_err_t ret = ESP_OK; - esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_001, &io_exp_handle); - - ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT); - ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT); - ret |= esp_io_expander_set_level(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, 0); - ret |= esp_io_expander_set_level(io_exp_handle, BSP_PWR_SYSTEM, 1); - vTaskDelay(100 / portTICK_PERIOD_MS); - ret |= esp_io_expander_set_level(io_exp_handle, BSP_PWR_START_UP, 1); - vTaskDelay(50 / portTICK_PERIOD_MS); - - uint32_t pin_val = 0; - ret |= esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val); - ESP_LOGI(TAG, "IO expander initialized: %x", DRV_IO_EXP_OUTPUT_MASK | (uint16_t)pin_val); - - assert(ret == ESP_OK); - } - - void OnKnobRotate(bool clockwise) { - auto codec = GetAudioCodec(); - int current_volume = codec->output_volume(); - int new_volume = current_volume + (clockwise ? -5 : 5); - - // 确保音量在有效范围内 - if (new_volume > 100) { - new_volume = 100; - ESP_LOGW(TAG, "Volume reached maximum limit: %d", new_volume); - } else if (new_volume < 0) { - new_volume = 0; - ESP_LOGW(TAG, "Volume reached minimum limit: %d", new_volume); - } - - codec->SetOutputVolume(new_volume); - ESP_LOGI(TAG, "Volume changed from %d to %d", current_volume, new_volume); - - // 显示通知前检查实际变化 - if (new_volume != codec->output_volume()) { - ESP_LOGE(TAG, "Failed to set volume! Expected:%d Actual:%d", - new_volume, codec->output_volume()); - } - GetDisplay()->ShowNotification(std::string(Lang::Strings::VOLUME) + ": "+std::to_string(codec->output_volume())); - power_save_timer_->WakeUp(); - } - - void InitializeKnob() { - knob_ = std::make_unique(BSP_KNOB_A_PIN, BSP_KNOB_B_PIN); - knob_->OnRotate([this](bool clockwise) { - ESP_LOGD(TAG, "Knob rotation detected. Clockwise:%s", clockwise ? "true" : "false"); - OnKnobRotate(clockwise); - }); - ESP_LOGI(TAG, "Knob initialized with pins A:%d B:%d", BSP_KNOB_A_PIN, BSP_KNOB_B_PIN); - } - - void InitializeButton() { - // 设置静态实例指针 - instance_ = this; - - // watcher 是通过长按滚轮进行开机的, 需要等待滚轮释放, 否则用户开机松手时可能会误触成单击 - ESP_LOGI(TAG, "waiting for knob button release"); - while(IoExpanderGetLevel(BSP_KNOB_BTN) == 0) { - vTaskDelay(pdMS_TO_TICKS(50)); - } - - button_config_t btn_config = { - .long_press_time = 2000, - .short_press_time = 0 - }; - btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); - btn_driver_->enable_power_save = false; - btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { - return !instance_->IoExpanderGetLevel(BSP_KNOB_BTN); - }; - - ESP_ERROR_CHECK(iot_button_create(&btn_config, btn_driver_, &btns)); - - iot_button_register_cb(btns, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); - } - self->power_save_timer_->WakeUp(); - app.ToggleChatState(); - }, this); - - iot_button_register_cb(btns, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - bool is_charging = (self->IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0); - self->long_press_cnt_ = 0; - if (is_charging) { - ESP_LOGI(TAG, "charging"); - } else { - self->IoExpanderSetLevel(BSP_PWR_LCD, 0); - self->IoExpanderSetLevel(BSP_PWR_SYSTEM, 0); - } - }, this); - - iot_button_register_cb(btns, BUTTON_LONG_PRESS_HOLD, nullptr, [](void* button_handle, void* usr_data) { - auto self = static_cast(usr_data); - self->long_press_cnt_++; // 每隔20ms加一 - // 长按10s 恢复出厂设置: 2+0.02*400 = 10 - if (self->long_press_cnt_ > 400) { - ESP_LOGI(TAG, "Factory reset"); - nvs_flash_erase(); - esp_restart(); - } - }, this); - } - - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize SSCMA SPI bus"); - spi_bus_config_t spi_cfg = {0}; - - spi_cfg.mosi_io_num = BSP_SPI2_HOST_MOSI; - spi_cfg.miso_io_num = BSP_SPI2_HOST_MISO; - spi_cfg.sclk_io_num = BSP_SPI2_HOST_SCLK; - spi_cfg.quadwp_io_num = -1; - spi_cfg.quadhd_io_num = -1; - spi_cfg.isr_cpu_id = ESP_INTR_CPU_AFFINITY_1; - spi_cfg.max_transfer_sz = 4095; - - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &spi_cfg, SPI_DMA_CH_AUTO)); - - ESP_LOGI(TAG, "Initialize QSPI bus"); - - spi_bus_config_t qspi_cfg = {0}; - qspi_cfg.sclk_io_num = BSP_SPI3_HOST_PCLK; - qspi_cfg.data0_io_num = BSP_SPI3_HOST_DATA0; - qspi_cfg.data1_io_num = BSP_SPI3_HOST_DATA1; - qspi_cfg.data2_io_num = BSP_SPI3_HOST_DATA2; - qspi_cfg.data3_io_num = BSP_SPI3_HOST_DATA3; - qspi_cfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * DRV_LCD_BITS_PER_PIXEL / 8 / CONFIG_BSP_LCD_SPI_DMA_SIZE_DIV; - - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &qspi_cfg, SPI_DMA_CH_AUTO)); - } - - void Initializespd2010Display() { - ESP_LOGI(TAG, "Install panel IO"); - const esp_lcd_panel_io_spi_config_t io_config = { - .cs_gpio_num = BSP_LCD_SPI_CS, - .dc_gpio_num = -1, - .spi_mode = 3, - .pclk_hz = DRV_LCD_PIXEL_CLK_HZ, - .trans_queue_depth = 2, - .lcd_cmd_bits = DRV_LCD_CMD_BITS, - .lcd_param_bits = DRV_LCD_PARAM_BITS, - .flags = { - .quad_mode = true, - }, - }; - spd2010_vendor_config_t vendor_config = { - .flags = { - .use_qspi_interface = 1, - }, - }; - esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)BSP_LCD_SPI_NUM, &io_config, &panel_io_); - - ESP_LOGD(TAG, "Install LCD driver"); - const esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = BSP_LCD_GPIO_RST, // Shared with Touch reset - .rgb_ele_order = DRV_LCD_RGB_ELEMENT_ORDER, - .bits_per_pixel = DRV_LCD_BITS_PER_PIXEL, - .vendor_config = &vendor_config, - }; - esp_lcd_new_panel_spd2010(panel_io_, &panel_config, &panel_); - - esp_lcd_panel_reset(panel_); - esp_lcd_panel_init(panel_); - esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - esp_lcd_panel_disp_on_off(panel_, true); - - display_ = new CustomLcdDisplay(panel_io_, panel_, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - - // 使每次刷新的起始列数索引是4的倍数且列数总数是4的倍数,以满足SPD2010的要求 - lv_display_add_event_cb(lv_display_get_default(), [](lv_event_t *e) { - lv_area_t *area = (lv_area_t *)lv_event_get_param(e); - uint16_t x1 = area->x1; - uint16_t x2 = area->x2; - // round the start of area down to the nearest 4N number - area->x1 = (x1 >> 2) << 2; - // round the end of area up to the nearest 4M+3 number - area->x2 = ((x2 >> 2) << 2) + 3; - }, LV_EVENT_INVALIDATE_AREA, NULL); - - } - - uint16_t BatterygetVoltage(void) { - static bool initialized = false; - static adc_oneshot_unit_handle_t adc_handle; - static adc_cali_handle_t cali_handle = NULL; - if (!initialized) { - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - }; - adc_oneshot_new_unit(&init_config, &adc_handle); - - adc_oneshot_chan_cfg_t ch_config = { - .atten = BSP_BAT_ADC_ATTEN, - .bitwidth = ADC_BITWIDTH_DEFAULT, - }; - adc_oneshot_config_channel(adc_handle, BSP_BAT_ADC_CHAN, &ch_config); - - adc_cali_curve_fitting_config_t cali_config = { - .unit_id = ADC_UNIT_1, - .chan = BSP_BAT_ADC_CHAN, - .atten = BSP_BAT_ADC_ATTEN, - .bitwidth = ADC_BITWIDTH_DEFAULT, - }; - if (adc_cali_create_scheme_curve_fitting(&cali_config, &cali_handle) == ESP_OK) { - initialized = true; - } - } - if (initialized) { - int raw_value = 0; - int voltage = 0; // mV - adc_oneshot_read(adc_handle, BSP_BAT_ADC_CHAN, &raw_value); - adc_cali_raw_to_voltage(cali_handle, raw_value, &voltage); - voltage = voltage * 82 / 20; - // ESP_LOGI(TAG, "voltage: %dmV", voltage); - return (uint16_t)voltage; - } - return 0; - } - - uint8_t BatterygetPercent(bool print = false) { - int voltage = 0; - for (uint8_t i = 0; i < 10; i++) { - voltage += BatterygetVoltage(); - } - voltage /= 10; - int percent = (-1 * voltage * voltage + 9016 * voltage - 19189000) / 10000; - percent = (percent > 100) ? 100 : (percent < 0) ? 0 : percent; - if (print) { - printf("voltage: %dmV, percentage: %d%%\r\n", voltage, percent); - } - return (uint8_t)percent; - } - - void InitializeCmd() { - esp_console_repl_t *repl = NULL; - esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); - repl_config.max_cmdline_length = 1024; - repl_config.prompt = "SenseCAP>"; - - const esp_console_cmd_t cmd1 = { - .command = "reboot", - .help = "reboot the device", - .hint = nullptr, - .func = [](int argc, char** argv) -> int { - esp_restart(); - return 0; - }, - .argtable = nullptr - }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd1)); - - const esp_console_cmd_t cmd2 = { - .command = "shutdown", - .help = "shutdown the device", - .hint = nullptr, - .func = NULL, - .argtable = NULL, - .func_w_context = [](void *context,int argc, char** argv) -> int { - auto self = static_cast(context); - self->GetBacklight()->SetBrightness(0); - self->IoExpanderSetLevel(BSP_PWR_SYSTEM, 0); - return 0; - }, - .context =this - }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd2)); - - const esp_console_cmd_t cmd3 = { - .command = "battery", - .help = "get battery percent", - .hint = NULL, - .func = NULL, - .argtable = NULL, - .func_w_context = [](void *context,int argc, char** argv) -> int { - auto self = static_cast(context); - self->BatterygetPercent(true); - return 0; - }, - .context =this - }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd3)); - - const esp_console_cmd_t cmd4 = { - .command = "factory_reset", - .help = "factory reset and reboot the device", - .hint = NULL, - .func = NULL, - .argtable = NULL, - .func_w_context = [](void *context,int argc, char** argv) -> int { - nvs_flash_erase(); - esp_restart(); - return 0; - }, - .context =this - }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd4)); - - const esp_console_cmd_t cmd5 = { - .command = "read_mac", - .help = "Read mac address", - .hint = NULL, - .func = NULL, - .argtable = NULL, - .func_w_context = [](void *context,int argc, char** argv) -> int { - uint8_t mac[6]; - esp_read_mac(mac, ESP_MAC_WIFI_STA); - printf("wifi_sta_mac: " MACSTR "\n", MAC2STR(mac)); - esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP); - printf("wifi_softap_mac: " MACSTR "\n", MAC2STR(mac)); - esp_read_mac(mac, ESP_MAC_BT); - printf("bt_mac: " MACSTR "\n", MAC2STR(mac)); - return 0; - }, - .context =this - }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd5)); - - esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl)); - ESP_ERROR_CHECK(esp_console_start_repl(repl)); - } - - void InitializeCamera() { - - ESP_LOGI(TAG, "Initialize Camera"); - - // !!!NOTE: SD Card use same SPI bus as sscma client, so we need to disable SD card CS pin first - const gpio_config_t io_config = { - .pin_bit_mask = (1ULL << BSP_SD_SPI_CS), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_ENABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - esp_err_t ret = gpio_config(&io_config); - if (ret != ESP_OK) - return; - - gpio_set_level(BSP_SD_SPI_CS, 1); - - camera_ = new SscmaCamera(io_exp_handle); - } - -public: - SensecapWatcher() { - ESP_LOGI(TAG, "Initialize Sensecap Watcher"); - InitializePowerSaveTimer(); - InitializeI2c(); - InitializeSpi(); - InitializeExpander(); - InitializeCmd(); //工厂生产测试使用 - InitializeButton(); - InitializeKnob(); - Initializespd2010Display(); - GetBacklight()->RestoreBrightness(); // 对于不带摄像头的版本,InitializeCamera需要3s, 所以先恢复背光亮度 - InitializeCamera(); - } - - virtual AudioCodec* GetAudioCodec() override { - static SensecapAudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7243E_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - // 根据 https://github.com/Seeed-Studio/OSHW-SenseCAP-Watcher/blob/main/Hardware/SenseCAP_Watcher_v1.0_SCH.pdf - // RGB LED型号为 ws2813 mini, 连接在GPIO 40,供电电压 3.3v, 没有连接 BIN 双信号线 - // 可以直接兼容SingleLED采用的ws2812 - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } - - virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = (IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0); - discharging = !charging; - level = (int)BatterygetPercent(false); - - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - if (level <= 1 && discharging) { - ESP_LOGI(TAG, "Battery level is low, shutting down"); - IoExpanderSetLevel(BSP_PWR_SYSTEM, 0); - } - return true; - } - - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(SensecapWatcher); - -// 定义静态成员变量 -SensecapWatcher* SensecapWatcher::instance_ = nullptr; +#include "display/lv_display.h" +#include "misc/lv_event.h" +#include "wifi_board.h" +#include "sensecap_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "knob.h" +#include "config.h" +#include "led/single_led.h" +#include "power_save_timer.h" +#include "sscma_camera.h" +#include "lvgl_theme.h" + +#include +#include "esp_check.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "assets/lang_config.h" + +#define TAG "sensecap_watcher" + +class CustomLcdDisplay : public SpiLcdDisplay { + public: + CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_handle_t panel_handle, + int width, + int height, + int offset_x, + int offset_y, + bool mirror_x, + bool mirror_y, + bool swap_xy) + : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + + DisplayLockGuard lock(this); + auto lvgl_theme = static_cast(current_theme_); + auto text_font = lvgl_theme->text_font()->font(); + auto icon_font = lvgl_theme->icon_font()->font(); + + lv_obj_set_size(status_bar_, LV_HOR_RES, text_font->line_height * 2 + 10); + lv_obj_set_style_layout(status_bar_, LV_LAYOUT_NONE, 0); + lv_obj_set_style_pad_top(status_bar_, 10, 0); + lv_obj_set_style_pad_bottom(status_bar_, 1, 0); + + // 针对圆形屏幕调整位置 + // network battery mute // + // status // + lv_obj_align(battery_label_, LV_ALIGN_TOP_MID, -2.5 * icon_font->line_height, 0); + lv_obj_align(network_label_, LV_ALIGN_TOP_MID, -0.5 * icon_font->line_height, 0); + lv_obj_align(mute_label_, LV_ALIGN_TOP_MID, 1.5 * icon_font->line_height, 0); + + lv_obj_align(status_label_, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_set_flex_grow(status_label_, 0); + lv_obj_set_width(status_label_, LV_HOR_RES * 0.75); + lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + + lv_obj_align(notification_label_, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_set_width(notification_label_, LV_HOR_RES * 0.75); + lv_label_set_long_mode(notification_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + + lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, -20); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_hex(0xFF0000), 0); + lv_obj_set_width(low_battery_label_, LV_HOR_RES * 0.75); + lv_label_set_long_mode(low_battery_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + } +}; + +class SensecapWatcher : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + LcdDisplay* display_; + std::unique_ptr knob_; + esp_io_expander_handle_t io_exp_handle; + button_handle_t btns; + PowerSaveTimer* power_save_timer_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + uint32_t long_press_cnt_; + button_driver_t* btn_driver_ = nullptr; + static SensecapWatcher* instance_; + SscmaCamera* camera_ = nullptr; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(10); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + bool is_charging = (IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0); + if (is_charging) { + ESP_LOGI(TAG, "charging"); + GetBacklight()->SetBrightness(0); + } else { + IoExpanderSetLevel(BSP_PWR_SYSTEM, 0); + } + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = BSP_GENERAL_I2C_SDA, + .scl_io_num = BSP_GENERAL_I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + + // pulldown for lcd i2c + const gpio_config_t io_config = { + .pin_bit_mask = (1ULL << BSP_TOUCH_I2C_SDA) | (1ULL << BSP_TOUCH_I2C_SCL) | (1ULL << BSP_SPI3_HOST_PCLK) | (1ULL << BSP_SPI3_HOST_DATA0) | (1ULL << BSP_SPI3_HOST_DATA1) + | (1ULL << BSP_SPI3_HOST_DATA2) | (1ULL << BSP_SPI3_HOST_DATA3) | (1ULL << BSP_LCD_SPI_CS) | (1UL << DISPLAY_BACKLIGHT_PIN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config(&io_config); + + gpio_set_level(BSP_TOUCH_I2C_SDA, 0); + gpio_set_level(BSP_TOUCH_I2C_SCL, 0); + + gpio_set_level(BSP_LCD_SPI_CS, 0); + gpio_set_level(DISPLAY_BACKLIGHT_PIN, 0); + gpio_set_level(BSP_SPI3_HOST_PCLK, 0); + gpio_set_level(BSP_SPI3_HOST_DATA0, 0); + gpio_set_level(BSP_SPI3_HOST_DATA1, 0); + gpio_set_level(BSP_SPI3_HOST_DATA2, 0); + gpio_set_level(BSP_SPI3_HOST_DATA3, 0); + + } + + esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) { + return esp_io_expander_set_level(io_exp_handle, pin_mask, level); + } + + uint8_t IoExpanderGetLevel(uint16_t pin_mask) { + uint32_t pin_val = 0; + esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val); + pin_mask &= DRV_IO_EXP_INPUT_MASK; + return (uint8_t)((pin_val & pin_mask) ? 1 : 0); + } + + void InitializeExpander() { + esp_err_t ret = ESP_OK; + esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_001, &io_exp_handle); + + ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT); + ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT); + ret |= esp_io_expander_set_level(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, 0); + ret |= esp_io_expander_set_level(io_exp_handle, BSP_PWR_SYSTEM, 1); + vTaskDelay(100 / portTICK_PERIOD_MS); + ret |= esp_io_expander_set_level(io_exp_handle, BSP_PWR_START_UP, 1); + vTaskDelay(50 / portTICK_PERIOD_MS); + + uint32_t pin_val = 0; + ret |= esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val); + ESP_LOGI(TAG, "IO expander initialized: %x", DRV_IO_EXP_OUTPUT_MASK | (uint16_t)pin_val); + + assert(ret == ESP_OK); + } + + void OnKnobRotate(bool clockwise) { + auto codec = GetAudioCodec(); + int current_volume = codec->output_volume(); + int new_volume = current_volume + (clockwise ? -5 : 5); + + // 确保音量在有效范围内 + if (new_volume > 100) { + new_volume = 100; + ESP_LOGW(TAG, "Volume reached maximum limit: %d", new_volume); + } else if (new_volume < 0) { + new_volume = 0; + ESP_LOGW(TAG, "Volume reached minimum limit: %d", new_volume); + } + + codec->SetOutputVolume(new_volume); + ESP_LOGI(TAG, "Volume changed from %d to %d", current_volume, new_volume); + + // 显示通知前检查实际变化 + if (new_volume != codec->output_volume()) { + ESP_LOGE(TAG, "Failed to set volume! Expected:%d Actual:%d", + new_volume, codec->output_volume()); + } + GetDisplay()->ShowNotification(std::string(Lang::Strings::VOLUME) + ": "+std::to_string(codec->output_volume())); + power_save_timer_->WakeUp(); + } + + void InitializeKnob() { + knob_ = std::make_unique(BSP_KNOB_A_PIN, BSP_KNOB_B_PIN); + knob_->OnRotate([this](bool clockwise) { + ESP_LOGD(TAG, "Knob rotation detected. Clockwise:%s", clockwise ? "true" : "false"); + OnKnobRotate(clockwise); + }); + ESP_LOGI(TAG, "Knob initialized with pins A:%d B:%d", BSP_KNOB_A_PIN, BSP_KNOB_B_PIN); + } + + void InitializeButton() { + // 设置静态实例指针 + instance_ = this; + + // watcher 是通过长按滚轮进行开机的, 需要等待滚轮释放, 否则用户开机松手时可能会误触成单击 + ESP_LOGI(TAG, "waiting for knob button release"); + while(IoExpanderGetLevel(BSP_KNOB_BTN) == 0) { + vTaskDelay(pdMS_TO_TICKS(50)); + } + + button_config_t btn_config = { + .long_press_time = 2000, + .short_press_time = 0 + }; + btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + btn_driver_->enable_power_save = false; + btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return !instance_->IoExpanderGetLevel(BSP_KNOB_BTN); + }; + + ESP_ERROR_CHECK(iot_button_create(&btn_config, btn_driver_, &btns)); + + iot_button_register_cb(btns, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + self->ResetWifiConfiguration(); + } + self->power_save_timer_->WakeUp(); + app.ToggleChatState(); + }, this); + + iot_button_register_cb(btns, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + bool is_charging = (self->IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0); + self->long_press_cnt_ = 0; + if (is_charging) { + ESP_LOGI(TAG, "charging"); + } else { + self->IoExpanderSetLevel(BSP_PWR_LCD, 0); + self->IoExpanderSetLevel(BSP_PWR_SYSTEM, 0); + } + }, this); + + iot_button_register_cb(btns, BUTTON_LONG_PRESS_HOLD, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->long_press_cnt_++; // 每隔20ms加一 + // 长按10s 恢复出厂设置: 2+0.02*400 = 10 + if (self->long_press_cnt_ > 400) { + ESP_LOGI(TAG, "Factory reset"); + nvs_flash_erase(); + esp_restart(); + } + }, this); + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize SSCMA SPI bus"); + spi_bus_config_t spi_cfg = {0}; + + spi_cfg.mosi_io_num = BSP_SPI2_HOST_MOSI; + spi_cfg.miso_io_num = BSP_SPI2_HOST_MISO; + spi_cfg.sclk_io_num = BSP_SPI2_HOST_SCLK; + spi_cfg.quadwp_io_num = -1; + spi_cfg.quadhd_io_num = -1; + spi_cfg.isr_cpu_id = ESP_INTR_CPU_AFFINITY_1; + spi_cfg.max_transfer_sz = 4095; + + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &spi_cfg, SPI_DMA_CH_AUTO)); + + ESP_LOGI(TAG, "Initialize QSPI bus"); + + spi_bus_config_t qspi_cfg = {0}; + qspi_cfg.sclk_io_num = BSP_SPI3_HOST_PCLK; + qspi_cfg.data0_io_num = BSP_SPI3_HOST_DATA0; + qspi_cfg.data1_io_num = BSP_SPI3_HOST_DATA1; + qspi_cfg.data2_io_num = BSP_SPI3_HOST_DATA2; + qspi_cfg.data3_io_num = BSP_SPI3_HOST_DATA3; + qspi_cfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * DRV_LCD_BITS_PER_PIXEL / 8 / CONFIG_BSP_LCD_SPI_DMA_SIZE_DIV; + + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &qspi_cfg, SPI_DMA_CH_AUTO)); + } + + void Initializespd2010Display() { + ESP_LOGI(TAG, "Install panel IO"); + const esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = BSP_LCD_SPI_CS, + .dc_gpio_num = -1, + .spi_mode = 3, + .pclk_hz = DRV_LCD_PIXEL_CLK_HZ, + .trans_queue_depth = 2, + .lcd_cmd_bits = DRV_LCD_CMD_BITS, + .lcd_param_bits = DRV_LCD_PARAM_BITS, + .flags = { + .quad_mode = true, + }, + }; + spd2010_vendor_config_t vendor_config = { + .flags = { + .use_qspi_interface = 1, + }, + }; + esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)BSP_LCD_SPI_NUM, &io_config, &panel_io_); + + ESP_LOGD(TAG, "Install LCD driver"); + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = BSP_LCD_GPIO_RST, // Shared with Touch reset + .rgb_ele_order = DRV_LCD_RGB_ELEMENT_ORDER, + .bits_per_pixel = DRV_LCD_BITS_PER_PIXEL, + .vendor_config = &vendor_config, + }; + esp_lcd_new_panel_spd2010(panel_io_, &panel_config, &panel_); + + esp_lcd_panel_reset(panel_); + esp_lcd_panel_init(panel_); + esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_disp_on_off(panel_, true); + + display_ = new CustomLcdDisplay(panel_io_, panel_, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + + // 使每次刷新的起始列数索引是4的倍数且列数总数是4的倍数,以满足SPD2010的要求 + lv_display_add_event_cb(lv_display_get_default(), [](lv_event_t *e) { + lv_area_t *area = (lv_area_t *)lv_event_get_param(e); + uint16_t x1 = area->x1; + uint16_t x2 = area->x2; + // round the start of area down to the nearest 4N number + area->x1 = (x1 >> 2) << 2; + // round the end of area up to the nearest 4M+3 number + area->x2 = ((x2 >> 2) << 2) + 3; + }, LV_EVENT_INVALIDATE_AREA, NULL); + + } + + uint16_t BatterygetVoltage(void) { + static bool initialized = false; + static adc_oneshot_unit_handle_t adc_handle; + static adc_cali_handle_t cali_handle = NULL; + if (!initialized) { + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + }; + adc_oneshot_new_unit(&init_config, &adc_handle); + + adc_oneshot_chan_cfg_t ch_config = { + .atten = BSP_BAT_ADC_ATTEN, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + adc_oneshot_config_channel(adc_handle, BSP_BAT_ADC_CHAN, &ch_config); + + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = ADC_UNIT_1, + .chan = BSP_BAT_ADC_CHAN, + .atten = BSP_BAT_ADC_ATTEN, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + if (adc_cali_create_scheme_curve_fitting(&cali_config, &cali_handle) == ESP_OK) { + initialized = true; + } + } + if (initialized) { + int raw_value = 0; + int voltage = 0; // mV + adc_oneshot_read(adc_handle, BSP_BAT_ADC_CHAN, &raw_value); + adc_cali_raw_to_voltage(cali_handle, raw_value, &voltage); + voltage = voltage * 82 / 20; + // ESP_LOGI(TAG, "voltage: %dmV", voltage); + return (uint16_t)voltage; + } + return 0; + } + + uint8_t BatterygetPercent(bool print = false) { + int voltage = 0; + for (uint8_t i = 0; i < 10; i++) { + voltage += BatterygetVoltage(); + } + voltage /= 10; + int percent = (-1 * voltage * voltage + 9016 * voltage - 19189000) / 10000; + percent = (percent > 100) ? 100 : (percent < 0) ? 0 : percent; + if (print) { + printf("voltage: %dmV, percentage: %d%%\r\n", voltage, percent); + } + return (uint8_t)percent; + } + + void InitializeCmd() { + esp_console_repl_t *repl = NULL; + esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); + repl_config.max_cmdline_length = 1024; + repl_config.prompt = "SenseCAP>"; + + const esp_console_cmd_t cmd1 = { + .command = "reboot", + .help = "reboot the device", + .hint = nullptr, + .func = [](int argc, char** argv) -> int { + esp_restart(); + return 0; + }, + .argtable = nullptr + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd1)); + + const esp_console_cmd_t cmd2 = { + .command = "shutdown", + .help = "shutdown the device", + .hint = nullptr, + .func = NULL, + .argtable = NULL, + .func_w_context = [](void *context,int argc, char** argv) -> int { + auto self = static_cast(context); + self->GetBacklight()->SetBrightness(0); + self->IoExpanderSetLevel(BSP_PWR_SYSTEM, 0); + return 0; + }, + .context =this + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd2)); + + const esp_console_cmd_t cmd3 = { + .command = "battery", + .help = "get battery percent", + .hint = NULL, + .func = NULL, + .argtable = NULL, + .func_w_context = [](void *context,int argc, char** argv) -> int { + auto self = static_cast(context); + self->BatterygetPercent(true); + return 0; + }, + .context =this + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd3)); + + const esp_console_cmd_t cmd4 = { + .command = "factory_reset", + .help = "factory reset and reboot the device", + .hint = NULL, + .func = NULL, + .argtable = NULL, + .func_w_context = [](void *context,int argc, char** argv) -> int { + nvs_flash_erase(); + esp_restart(); + return 0; + }, + .context =this + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd4)); + + const esp_console_cmd_t cmd5 = { + .command = "read_mac", + .help = "Read mac address", + .hint = NULL, + .func = NULL, + .argtable = NULL, + .func_w_context = [](void *context,int argc, char** argv) -> int { + uint8_t mac[6]; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + printf("wifi_sta_mac: " MACSTR "\n", MAC2STR(mac)); + esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP); + printf("wifi_softap_mac: " MACSTR "\n", MAC2STR(mac)); + esp_read_mac(mac, ESP_MAC_BT); + printf("bt_mac: " MACSTR "\n", MAC2STR(mac)); + return 0; + }, + .context =this + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd5)); + + esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl)); + ESP_ERROR_CHECK(esp_console_start_repl(repl)); + } + + void InitializeCamera() { + + ESP_LOGI(TAG, "Initialize Camera"); + + // !!!NOTE: SD Card use same SPI bus as sscma client, so we need to disable SD card CS pin first + const gpio_config_t io_config = { + .pin_bit_mask = (1ULL << BSP_SD_SPI_CS), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + esp_err_t ret = gpio_config(&io_config); + if (ret != ESP_OK) + return; + + gpio_set_level(BSP_SD_SPI_CS, 1); + + camera_ = new SscmaCamera(io_exp_handle); + } + +public: + SensecapWatcher() { + ESP_LOGI(TAG, "Initialize Sensecap Watcher"); + InitializePowerSaveTimer(); + InitializeI2c(); + InitializeSpi(); + InitializeExpander(); + InitializeCmd(); //工厂生产测试使用 + InitializeButton(); + InitializeKnob(); + Initializespd2010Display(); + GetBacklight()->RestoreBrightness(); // 对于不带摄像头的版本,InitializeCamera需要3s, 所以先恢复背光亮度 + InitializeCamera(); + } + + virtual AudioCodec* GetAudioCodec() override { + static SensecapAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7243E_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + // 根据 https://github.com/Seeed-Studio/OSHW-SenseCAP-Watcher/blob/main/Hardware/SenseCAP_Watcher_v1.0_SCH.pdf + // RGB LED型号为 ws2813 mini, 连接在GPIO 40,供电电压 3.3v, 没有连接 BIN 双信号线 + // 可以直接兼容SingleLED采用的ws2812 + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } + + virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = (IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0); + discharging = !charging; + level = (int)BatterygetPercent(false); + + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + if (level <= 1 && discharging) { + ESP_LOGI(TAG, "Battery level is low, shutting down"); + IoExpanderSetLevel(BSP_PWR_SYSTEM, 0); + } + return true; + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(SensecapWatcher); + +// 定义静态成员变量 +SensecapWatcher* SensecapWatcher::instance_ = nullptr; diff --git a/main/boards/sensecap-watcher/sscma_camera.cc b/main/boards/sensecap-watcher/sscma_camera.cc index 69342c8..049e3c8 100644 --- a/main/boards/sensecap-watcher/sscma_camera.cc +++ b/main/boards/sensecap-watcher/sscma_camera.cc @@ -1,345 +1,344 @@ -#include "sscma_camera.h" -#include "mcp_server.h" -#include "lvgl_display.h" -#include "lvgl_image.h" -#include "board.h" -#include "system_info.h" -#include "config.h" - -#include -#include -#include -#include - -#define TAG "SscmaCamera" - -#define IMG_JPEG_BUF_SIZE 48 * 1024 - -SscmaCamera::SscmaCamera(esp_io_expander_handle_t io_exp_handle) { - sscma_client_io_spi_config_t spi_io_config = {0}; - spi_io_config.sync_gpio_num = BSP_SSCMA_CLIENT_SPI_SYNC; - spi_io_config.cs_gpio_num = BSP_SSCMA_CLIENT_SPI_CS; - spi_io_config.pclk_hz = BSP_SSCMA_CLIENT_SPI_CLK; - spi_io_config.spi_mode = 0; - spi_io_config.wait_delay = 10; //两个transfer之间至少延时4ms,但当前 FREERTOS_HZ=100, 延时精度只能达到10ms, - spi_io_config.user_ctx = NULL; - spi_io_config.io_expander = io_exp_handle; - spi_io_config.flags.sync_use_expander = BSP_SSCMA_CLIENT_RST_USE_EXPANDER; - - sscma_client_new_io_spi_bus((sscma_client_spi_bus_handle_t)BSP_SSCMA_CLIENT_SPI_NUM, &spi_io_config, &sscma_client_io_handle_); - - sscma_client_config_t sscma_client_config = SSCMA_CLIENT_CONFIG_DEFAULT(); - sscma_client_config.event_queue_size = CONFIG_SSCMA_EVENT_QUEUE_SIZE; - sscma_client_config.tx_buffer_size = CONFIG_SSCMA_TX_BUFFER_SIZE; - sscma_client_config.rx_buffer_size = CONFIG_SSCMA_RX_BUFFER_SIZE; - sscma_client_config.process_task_stack = CONFIG_SSCMA_PROCESS_TASK_STACK_SIZE; - sscma_client_config.process_task_affinity = CONFIG_SSCMA_PROCESS_TASK_AFFINITY; - sscma_client_config.process_task_priority = CONFIG_SSCMA_PROCESS_TASK_PRIORITY; - sscma_client_config.monitor_task_stack = CONFIG_SSCMA_MONITOR_TASK_STACK_SIZE; - sscma_client_config.monitor_task_affinity = CONFIG_SSCMA_MONITOR_TASK_AFFINITY; - sscma_client_config.monitor_task_priority = CONFIG_SSCMA_MONITOR_TASK_PRIORITY; - sscma_client_config.reset_gpio_num = BSP_SSCMA_CLIENT_RST; - sscma_client_config.io_expander = io_exp_handle; - sscma_client_config.flags.reset_use_expander = BSP_SSCMA_CLIENT_RST_USE_EXPANDER; - - sscma_client_new(sscma_client_io_handle_, &sscma_client_config, &sscma_client_handle_); - - sscma_data_queue_ = xQueueCreate(1, sizeof(SscmaData)); - - sscma_client_callback_t callback = {0}; - - callback.on_event = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) { - SscmaCamera* self = static_cast(user_ctx); - if (!self) return; - char *img = NULL; - int img_size = 0; - if (sscma_utils_fetch_image_from_reply(reply, &img, &img_size) == ESP_OK) - { - ESP_LOGI(TAG, "image_size: %d\n", img_size); - // 将数据通过队列发送出去 - SscmaData data; - data.img = (uint8_t*)img; - data.len = img_size; - - // 清空队列,保证只保存最新的数据 - SscmaData dummy; - while (xQueueReceive(self->sscma_data_queue_, &dummy, 0) == pdPASS) { - if (dummy.img) { - heap_caps_free(dummy.img); - } - } - xQueueSend(self->sscma_data_queue_, &data, 0); - // 注意:img 的释放由接收方负责 - } - }; - callback.on_connect = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) { - ESP_LOGI(TAG, "SSCMA client connected"); - }; - - callback.on_log = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) { - ESP_LOGI(TAG, "log: %s\n", reply->data); - }; - - sscma_client_register_callback(sscma_client_handle_, &callback, this); - - sscma_client_init(sscma_client_handle_); - - ESP_LOGI(TAG, "SSCMA client initialized"); - // 设置分辨率 - // 3 = 640x480 - if (sscma_client_set_sensor(sscma_client_handle_, 1, 3, true)) { - ESP_LOGE(TAG, "Failed to set sensor"); - sscma_client_del(sscma_client_handle_); - sscma_client_handle_ = NULL; - return; - } - - // 获取设备信息 - sscma_client_info_t *info; - if (sscma_client_get_info(sscma_client_handle_, &info, true) == ESP_OK) { - ESP_LOGI(TAG, "Device Info - ID: %s, Name: %s", - info->id ? info->id : "NULL", - info->name ? info->name : "NULL"); - } - // 初始化JPEG数据的内存 - jpeg_data_.len = 0; - jpeg_data_.buf = (uint8_t*)heap_caps_malloc(IMG_JPEG_BUF_SIZE, MALLOC_CAP_SPIRAM);; - if ( jpeg_data_.buf == nullptr ) { - ESP_LOGE(TAG, "Failed to allocate memory for JPEG buffer"); - return; - } - - //初始化JPEG解码 - jpeg_error_t err; - jpeg_dec_config_t config = { .output_type = JPEG_PIXEL_FORMAT_RGB565_LE, .rotate = JPEG_ROTATE_0D }; - err = jpeg_dec_open(&config, &jpeg_dec_); - if ( err != JPEG_ERR_OK ) { - ESP_LOGE(TAG, "Failed to open JPEG decoder"); - return; - } - jpeg_io_ = (jpeg_dec_io_t*)heap_caps_malloc(sizeof(jpeg_dec_io_t), MALLOC_CAP_SPIRAM); - if (!jpeg_io_) { - ESP_LOGE(TAG, "Failed to allocate memory for JPEG IO"); - jpeg_dec_close(jpeg_dec_); - return; - } - memset(jpeg_io_, 0, sizeof(jpeg_dec_io_t)); - - jpeg_out_ = (jpeg_dec_header_info_t*)heap_caps_aligned_alloc(16, sizeof(jpeg_dec_header_info_t), MALLOC_CAP_SPIRAM); - if (!jpeg_out_) { - ESP_LOGE(TAG, "Failed to allocate memory for JPEG output header"); - heap_caps_free(jpeg_io_); - jpeg_dec_close(jpeg_dec_); - return; - } - memset(jpeg_out_, 0, sizeof(jpeg_dec_header_info_t)); - - // 初始化预览图片的内存 - memset(&preview_image_, 0, sizeof(preview_image_)); - preview_image_.header.magic = LV_IMAGE_HEADER_MAGIC; - preview_image_.header.cf = LV_COLOR_FORMAT_RGB565; - preview_image_.header.flags = LV_IMAGE_FLAGS_ALLOCATED | LV_IMAGE_FLAGS_MODIFIABLE; - preview_image_.header.w = 640; - preview_image_.header.h = 480; - - preview_image_.header.stride = preview_image_.header.w * 2; - preview_image_.data_size = preview_image_.header.w * preview_image_.header.h * 2; - preview_image_.data = (uint8_t*)heap_caps_malloc(preview_image_.data_size, MALLOC_CAP_SPIRAM); - if (preview_image_.data == nullptr) { - ESP_LOGE(TAG, "Failed to allocate memory for preview image"); - return; - } -} - -SscmaCamera::~SscmaCamera() { - if (preview_image_.data) { - heap_caps_free((void*)preview_image_.data); - preview_image_.data = nullptr; - } - if (sscma_client_handle_) { - sscma_client_del(sscma_client_handle_); - } - if (sscma_data_queue_) { - vQueueDelete(sscma_data_queue_); - } - if (jpeg_data_.buf) { - heap_caps_free(jpeg_data_.buf); - jpeg_data_.buf = nullptr; - } - if (jpeg_dec_) { - jpeg_dec_close(jpeg_dec_); - jpeg_dec_ = nullptr; - } - if (jpeg_io_) { - heap_caps_free(jpeg_io_); - jpeg_io_ = nullptr; - } - if (jpeg_out_) { - heap_caps_free(jpeg_out_); - jpeg_out_ = nullptr; - } -} - -void SscmaCamera::SetExplainUrl(const std::string& url, const std::string& token) { - explain_url_ = url; - explain_token_ = token; -} - -bool SscmaCamera::Capture() { - - SscmaData data; - int ret = 0; - - if (sscma_client_handle_ == nullptr) { - ESP_LOGE(TAG, "SSCMA client handle is not initialized"); - return false; - } - - ESP_LOGI(TAG, "Capturing image..."); - - // himax 有缓存数据,需要拍两张照片, 只获取最新的照片即可. - if (sscma_client_sample(sscma_client_handle_, 2) ) { - ESP_LOGE(TAG, "Failed to capture image from SSCMA client"); - return false; - } - vTaskDelay(pdMS_TO_TICKS(500)); // 等待SSCMA客户端处理数据 - if (xQueueReceive(sscma_data_queue_, &data, pdMS_TO_TICKS(1000)) != pdPASS) { - ESP_LOGE(TAG, "Failed to receive JPEG data from SSCMA client"); - return false; - } - - if (jpeg_data_.buf == nullptr) { - heap_caps_free(data.img); - return false; - } - - ret = mbedtls_base64_decode(jpeg_data_.buf, IMG_JPEG_BUF_SIZE, &jpeg_data_.len, data.img, data.len); - if (ret != 0 || jpeg_data_.len == 0) { - ESP_LOGE(TAG, "Failed to decode base64 image data, ret: %d, output_len: %zu", ret, jpeg_data_.len); - heap_caps_free(data.img); - return false; - } - heap_caps_free(data.img); - - //DECODE JPEG - if (!jpeg_dec_ || !jpeg_io_ || !jpeg_out_ || !preview_image_.data) { - return true; - } - jpeg_io_->inbuf = jpeg_data_.buf; - jpeg_io_->inbuf_len = jpeg_data_.len; - ret = jpeg_dec_parse_header(jpeg_dec_, jpeg_io_, jpeg_out_); - if (ret < 0) { - ESP_LOGE(TAG, "Failed to parse JPEG header, ret: %d", ret); - return true; - } - jpeg_io_->outbuf = (unsigned char*)preview_image_.data; - int inbuf_consumed = jpeg_io_->inbuf_len - jpeg_io_->inbuf_remain; - jpeg_io_->inbuf = jpeg_data_.buf + inbuf_consumed; - jpeg_io_->inbuf_len = jpeg_io_->inbuf_remain; - - ret = jpeg_dec_process(jpeg_dec_, jpeg_io_); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to decode JPEG image, ret: %d", ret); - return true; - } - - // 显示预览图片 - auto display = dynamic_cast(Board::GetInstance().GetDisplay()); - if (display != nullptr) { - auto image = std::make_unique(&preview_image_); - display->SetPreviewImage(std::move(image)); - } - return true; -} -bool SscmaCamera::SetHMirror(bool enabled) { - return false; -} - -bool SscmaCamera::SetVFlip(bool enabled) { - return false; -} - -/** - * @brief 将摄像头捕获的图像发送到远程服务器进行AI分析和解释 - * - * 该函数将当前摄像头缓冲区中的图像编码为JPEG格式,并通过HTTP POST请求 - * 以multipart/form-data的形式发送到指定的解释服务器。服务器将根据提供的 - * 问题对图像进行AI分析并返回结果。 - * - * @param question 要向AI提出的关于图像的问题,将作为表单字段发送 - * @return std::string 服务器返回的JSON格式响应字符串 - * 成功时包含AI分析结果,失败时包含错误信息 - * 格式示例:{"success": true, "result": "分析结果"} - * {"success": false, "message": "错误信息"} - * - * @note 调用此函数前必须先调用SetExplainUrl()设置服务器URL - * @note 函数会等待之前的编码线程完成后再开始新的处理 - * @warning 如果摄像头缓冲区为空或网络连接失败,将返回错误信息 - */ -std::string SscmaCamera::Explain(const std::string& question) { - if (explain_url_.empty()) { - return "{\"success\": false, \"message\": \"Image explain URL or token is not set\"}"; - } - - auto network = Board::GetInstance().GetNetwork(); - auto http = network->CreateHttp(3); - // 构造multipart/form-data请求体 - std::string boundary = "----ESP32_CAMERA_BOUNDARY"; - - // 构造question字段 - std::string question_field; - question_field += "--" + boundary + "\r\n"; - question_field += "Content-Disposition: form-data; name=\"question\"\r\n"; - question_field += "\r\n"; - question_field += question + "\r\n"; - - // 构造文件字段头部 - std::string file_header; - file_header += "--" + boundary + "\r\n"; - file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n"; - file_header += "Content-Type: image/jpeg\r\n"; - file_header += "\r\n"; - - // 构造尾部 - std::string multipart_footer; - multipart_footer += "\r\n--" + boundary + "--\r\n"; - - // 配置HTTP客户端,使用分块传输编码 - http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); - http->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str()); - if (!explain_token_.empty()) { - http->SetHeader("Authorization", "Bearer " + explain_token_); - } - http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary); - http->SetHeader("Transfer-Encoding", "chunked"); - if (!http->Open("POST", explain_url_)) { - ESP_LOGE(TAG, "Failed to connect to explain URL"); - return "{\"success\": false, \"message\": \"Failed to connect to explain URL\"}"; - } - - // 第一块:question字段 - http->Write(question_field.c_str(), question_field.size()); - - // 第二块:文件字段头部 - http->Write(file_header.c_str(), file_header.size()); - - // 第三块:JPEG数据 - http->Write((const char*)jpeg_data_.buf, jpeg_data_.len); - - // 第四块:multipart尾部 - http->Write(multipart_footer.c_str(), multipart_footer.size()); - - // 结束块 - http->Write("", 0); - - if (http->GetStatusCode() != 200) { - ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode()); - return "{\"success\": false, \"message\": \"Failed to upload photo\"}"; - } - - std::string result = http->ReadAll(); - http->Close(); - - ESP_LOGI(TAG, "Explain image size=%d, question=%s\n%s", jpeg_data_.len, question.c_str(), result.c_str()); - return result; -} +#include "sscma_camera.h" +#include "mcp_server.h" +#include "lvgl_display.h" +#include "lvgl_image.h" +#include "board.h" +#include "system_info.h" +#include "config.h" + +#include +#include +#include + +#define TAG "SscmaCamera" + +#define IMG_JPEG_BUF_SIZE 48 * 1024 + +SscmaCamera::SscmaCamera(esp_io_expander_handle_t io_exp_handle) { + sscma_client_io_spi_config_t spi_io_config = {0}; + spi_io_config.sync_gpio_num = BSP_SSCMA_CLIENT_SPI_SYNC; + spi_io_config.cs_gpio_num = BSP_SSCMA_CLIENT_SPI_CS; + spi_io_config.pclk_hz = BSP_SSCMA_CLIENT_SPI_CLK; + spi_io_config.spi_mode = 0; + spi_io_config.wait_delay = 10; //两个transfer之间至少延时4ms,但当前 FREERTOS_HZ=100, 延时精度只能达到10ms, + spi_io_config.user_ctx = NULL; + spi_io_config.io_expander = io_exp_handle; + spi_io_config.flags.sync_use_expander = BSP_SSCMA_CLIENT_RST_USE_EXPANDER; + + sscma_client_new_io_spi_bus((sscma_client_spi_bus_handle_t)BSP_SSCMA_CLIENT_SPI_NUM, &spi_io_config, &sscma_client_io_handle_); + + sscma_client_config_t sscma_client_config = SSCMA_CLIENT_CONFIG_DEFAULT(); + sscma_client_config.event_queue_size = CONFIG_SSCMA_EVENT_QUEUE_SIZE; + sscma_client_config.tx_buffer_size = CONFIG_SSCMA_TX_BUFFER_SIZE; + sscma_client_config.rx_buffer_size = CONFIG_SSCMA_RX_BUFFER_SIZE; + sscma_client_config.process_task_stack = CONFIG_SSCMA_PROCESS_TASK_STACK_SIZE; + sscma_client_config.process_task_affinity = CONFIG_SSCMA_PROCESS_TASK_AFFINITY; + sscma_client_config.process_task_priority = CONFIG_SSCMA_PROCESS_TASK_PRIORITY; + sscma_client_config.monitor_task_stack = CONFIG_SSCMA_MONITOR_TASK_STACK_SIZE; + sscma_client_config.monitor_task_affinity = CONFIG_SSCMA_MONITOR_TASK_AFFINITY; + sscma_client_config.monitor_task_priority = CONFIG_SSCMA_MONITOR_TASK_PRIORITY; + sscma_client_config.reset_gpio_num = BSP_SSCMA_CLIENT_RST; + sscma_client_config.io_expander = io_exp_handle; + sscma_client_config.flags.reset_use_expander = BSP_SSCMA_CLIENT_RST_USE_EXPANDER; + + sscma_client_new(sscma_client_io_handle_, &sscma_client_config, &sscma_client_handle_); + + sscma_data_queue_ = xQueueCreate(1, sizeof(SscmaData)); + + sscma_client_callback_t callback = {0}; + + callback.on_event = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) { + SscmaCamera* self = static_cast(user_ctx); + if (!self) return; + char *img = NULL; + int img_size = 0; + if (sscma_utils_fetch_image_from_reply(reply, &img, &img_size) == ESP_OK) + { + ESP_LOGI(TAG, "image_size: %d\n", img_size); + // 将数据通过队列发送出去 + SscmaData data; + data.img = (uint8_t*)img; + data.len = img_size; + + // 清空队列,保证只保存最新的数据 + SscmaData dummy; + while (xQueueReceive(self->sscma_data_queue_, &dummy, 0) == pdPASS) { + if (dummy.img) { + heap_caps_free(dummy.img); + } + } + xQueueSend(self->sscma_data_queue_, &data, 0); + // 注意:img 的释放由接收方负责 + } + }; + callback.on_connect = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) { + ESP_LOGI(TAG, "SSCMA client connected"); + }; + + callback.on_log = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) { + ESP_LOGI(TAG, "log: %s\n", reply->data); + }; + + sscma_client_register_callback(sscma_client_handle_, &callback, this); + + sscma_client_init(sscma_client_handle_); + + ESP_LOGI(TAG, "SSCMA client initialized"); + // 设置分辨率 + // 3 = 640x480 + if (sscma_client_set_sensor(sscma_client_handle_, 1, 3, true)) { + ESP_LOGE(TAG, "Failed to set sensor"); + sscma_client_del(sscma_client_handle_); + sscma_client_handle_ = NULL; + return; + } + + // 获取设备信息 + sscma_client_info_t *info; + if (sscma_client_get_info(sscma_client_handle_, &info, true) == ESP_OK) { + ESP_LOGI(TAG, "Device Info - ID: %s, Name: %s", + info->id ? info->id : "NULL", + info->name ? info->name : "NULL"); + } + // 初始化JPEG数据的内存 + jpeg_data_.len = 0; + jpeg_data_.buf = (uint8_t*)heap_caps_malloc(IMG_JPEG_BUF_SIZE, MALLOC_CAP_SPIRAM);; + if ( jpeg_data_.buf == nullptr ) { + ESP_LOGE(TAG, "Failed to allocate memory for JPEG buffer"); + return; + } + + //初始化JPEG解码 + jpeg_error_t err; + jpeg_dec_config_t config = { .output_type = JPEG_PIXEL_FORMAT_RGB565_LE, .rotate = JPEG_ROTATE_0D }; + err = jpeg_dec_open(&config, &jpeg_dec_); + if ( err != JPEG_ERR_OK ) { + ESP_LOGE(TAG, "Failed to open JPEG decoder"); + return; + } + jpeg_io_ = (jpeg_dec_io_t*)heap_caps_malloc(sizeof(jpeg_dec_io_t), MALLOC_CAP_SPIRAM); + if (!jpeg_io_) { + ESP_LOGE(TAG, "Failed to allocate memory for JPEG IO"); + jpeg_dec_close(jpeg_dec_); + return; + } + memset(jpeg_io_, 0, sizeof(jpeg_dec_io_t)); + + jpeg_out_ = (jpeg_dec_header_info_t*)heap_caps_aligned_alloc(16, sizeof(jpeg_dec_header_info_t), MALLOC_CAP_SPIRAM); + if (!jpeg_out_) { + ESP_LOGE(TAG, "Failed to allocate memory for JPEG output header"); + heap_caps_free(jpeg_io_); + jpeg_dec_close(jpeg_dec_); + return; + } + memset(jpeg_out_, 0, sizeof(jpeg_dec_header_info_t)); + + // 初始化预览图片的内存 + memset(&preview_image_, 0, sizeof(preview_image_)); + preview_image_.header.magic = LV_IMAGE_HEADER_MAGIC; + preview_image_.header.cf = LV_COLOR_FORMAT_RGB565; + preview_image_.header.flags = LV_IMAGE_FLAGS_ALLOCATED | LV_IMAGE_FLAGS_MODIFIABLE; + preview_image_.header.w = 640; + preview_image_.header.h = 480; + + preview_image_.header.stride = preview_image_.header.w * 2; + preview_image_.data_size = preview_image_.header.w * preview_image_.header.h * 2; + preview_image_.data = (uint8_t*)heap_caps_malloc(preview_image_.data_size, MALLOC_CAP_SPIRAM); + if (preview_image_.data == nullptr) { + ESP_LOGE(TAG, "Failed to allocate memory for preview image"); + return; + } +} + +SscmaCamera::~SscmaCamera() { + if (preview_image_.data) { + heap_caps_free((void*)preview_image_.data); + preview_image_.data = nullptr; + } + if (sscma_client_handle_) { + sscma_client_del(sscma_client_handle_); + } + if (sscma_data_queue_) { + vQueueDelete(sscma_data_queue_); + } + if (jpeg_data_.buf) { + heap_caps_free(jpeg_data_.buf); + jpeg_data_.buf = nullptr; + } + if (jpeg_dec_) { + jpeg_dec_close(jpeg_dec_); + jpeg_dec_ = nullptr; + } + if (jpeg_io_) { + heap_caps_free(jpeg_io_); + jpeg_io_ = nullptr; + } + if (jpeg_out_) { + heap_caps_free(jpeg_out_); + jpeg_out_ = nullptr; + } +} + +void SscmaCamera::SetExplainUrl(const std::string& url, const std::string& token) { + explain_url_ = url; + explain_token_ = token; +} + +bool SscmaCamera::Capture() { + + SscmaData data; + int ret = 0; + + if (sscma_client_handle_ == nullptr) { + ESP_LOGE(TAG, "SSCMA client handle is not initialized"); + return false; + } + + ESP_LOGI(TAG, "Capturing image..."); + + // himax 有缓存数据,需要拍两张照片, 只获取最新的照片即可. + if (sscma_client_sample(sscma_client_handle_, 2) ) { + ESP_LOGE(TAG, "Failed to capture image from SSCMA client"); + return false; + } + vTaskDelay(pdMS_TO_TICKS(500)); // 等待SSCMA客户端处理数据 + if (xQueueReceive(sscma_data_queue_, &data, pdMS_TO_TICKS(1000)) != pdPASS) { + ESP_LOGE(TAG, "Failed to receive JPEG data from SSCMA client"); + return false; + } + + if (jpeg_data_.buf == nullptr) { + heap_caps_free(data.img); + return false; + } + + ret = mbedtls_base64_decode(jpeg_data_.buf, IMG_JPEG_BUF_SIZE, &jpeg_data_.len, data.img, data.len); + if (ret != 0 || jpeg_data_.len == 0) { + ESP_LOGE(TAG, "Failed to decode base64 image data, ret: %d, output_len: %zu", ret, jpeg_data_.len); + heap_caps_free(data.img); + return false; + } + heap_caps_free(data.img); + + //DECODE JPEG + if (!jpeg_dec_ || !jpeg_io_ || !jpeg_out_ || !preview_image_.data) { + return true; + } + jpeg_io_->inbuf = jpeg_data_.buf; + jpeg_io_->inbuf_len = jpeg_data_.len; + ret = jpeg_dec_parse_header(jpeg_dec_, jpeg_io_, jpeg_out_); + if (ret < 0) { + ESP_LOGE(TAG, "Failed to parse JPEG header, ret: %d", ret); + return true; + } + jpeg_io_->outbuf = (unsigned char*)preview_image_.data; + int inbuf_consumed = jpeg_io_->inbuf_len - jpeg_io_->inbuf_remain; + jpeg_io_->inbuf = jpeg_data_.buf + inbuf_consumed; + jpeg_io_->inbuf_len = jpeg_io_->inbuf_remain; + + ret = jpeg_dec_process(jpeg_dec_, jpeg_io_); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to decode JPEG image, ret: %d", ret); + return true; + } + + // 显示预览图片 + auto display = dynamic_cast(Board::GetInstance().GetDisplay()); + if (display != nullptr) { + auto image = std::make_unique(&preview_image_); + display->SetPreviewImage(std::move(image)); + } + return true; +} +bool SscmaCamera::SetHMirror(bool enabled) { + return false; +} + +bool SscmaCamera::SetVFlip(bool enabled) { + return false; +} + +/** + * @brief 将摄像头捕获的图像发送到远程服务器进行AI分析和解释 + * + * 该函数将当前摄像头缓冲区中的图像编码为JPEG格式,并通过HTTP POST请求 + * 以multipart/form-data的形式发送到指定的解释服务器。服务器将根据提供的 + * 问题对图像进行AI分析并返回结果。 + * + * @param question 要向AI提出的关于图像的问题,将作为表单字段发送 + * @return std::string 服务器返回的JSON格式响应字符串 + * 成功时包含AI分析结果,失败时包含错误信息 + * 格式示例:{"success": true, "result": "分析结果"} + * {"success": false, "message": "错误信息"} + * + * @note 调用此函数前必须先调用SetExplainUrl()设置服务器URL + * @note 函数会等待之前的编码线程完成后再开始新的处理 + * @warning 如果摄像头缓冲区为空或网络连接失败,将返回错误信息 + */ +std::string SscmaCamera::Explain(const std::string& question) { + if (explain_url_.empty()) { + return "{\"success\": false, \"message\": \"Image explain URL or token is not set\"}"; + } + + auto network = Board::GetInstance().GetNetwork(); + auto http = network->CreateHttp(3); + // 构造multipart/form-data请求体 + std::string boundary = "----ESP32_CAMERA_BOUNDARY"; + + // 构造question字段 + std::string question_field; + question_field += "--" + boundary + "\r\n"; + question_field += "Content-Disposition: form-data; name=\"question\"\r\n"; + question_field += "\r\n"; + question_field += question + "\r\n"; + + // 构造文件字段头部 + std::string file_header; + file_header += "--" + boundary + "\r\n"; + file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n"; + file_header += "Content-Type: image/jpeg\r\n"; + file_header += "\r\n"; + + // 构造尾部 + std::string multipart_footer; + multipart_footer += "\r\n--" + boundary + "--\r\n"; + + // 配置HTTP客户端,使用分块传输编码 + http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); + http->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str()); + if (!explain_token_.empty()) { + http->SetHeader("Authorization", "Bearer " + explain_token_); + } + http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary); + http->SetHeader("Transfer-Encoding", "chunked"); + if (!http->Open("POST", explain_url_)) { + ESP_LOGE(TAG, "Failed to connect to explain URL"); + return "{\"success\": false, \"message\": \"Failed to connect to explain URL\"}"; + } + + // 第一块:question字段 + http->Write(question_field.c_str(), question_field.size()); + + // 第二块:文件字段头部 + http->Write(file_header.c_str(), file_header.size()); + + // 第三块:JPEG数据 + http->Write((const char*)jpeg_data_.buf, jpeg_data_.len); + + // 第四块:multipart尾部 + http->Write(multipart_footer.c_str(), multipart_footer.size()); + + // 结束块 + http->Write("", 0); + + if (http->GetStatusCode() != 200) { + ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode()); + return "{\"success\": false, \"message\": \"Failed to upload photo\"}"; + } + + std::string result = http->ReadAll(); + http->Close(); + + ESP_LOGI(TAG, "Explain image size=%d, question=%s\n%s", jpeg_data_.len, question.c_str(), result.c_str()); + return result; +} diff --git a/main/boards/sensecap-watcher/sscma_camera.h b/main/boards/sensecap-watcher/sscma_camera.h index 731b8da..b599c23 100644 --- a/main/boards/sensecap-watcher/sscma_camera.h +++ b/main/boards/sensecap-watcher/sscma_camera.h @@ -1,50 +1,50 @@ -#ifndef SSCMA_CAMERA_H -#define SSCMA_CAMERA_H - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "sscma_client.h" -#include "camera.h" - -struct SscmaData { - uint8_t* img; - size_t len; -}; -struct JpegData { - uint8_t* buf; - size_t len; -}; - -class SscmaCamera : public Camera { -private: - lv_img_dsc_t preview_image_; - std::string explain_url_; - std::string explain_token_; - sscma_client_io_handle_t sscma_client_io_handle_; - sscma_client_handle_t sscma_client_handle_; - QueueHandle_t sscma_data_queue_; - JpegData jpeg_data_; - jpeg_dec_handle_t jpeg_dec_; - jpeg_dec_io_t *jpeg_io_; - jpeg_dec_header_info_t *jpeg_out_; -public: - SscmaCamera(esp_io_expander_handle_t io_exp_handle); - ~SscmaCamera(); - - virtual void SetExplainUrl(const std::string& url, const std::string& token); - virtual bool Capture(); - // 翻转控制函数 - virtual bool SetHMirror(bool enabled) override; - virtual bool SetVFlip(bool enabled) override; - virtual std::string Explain(const std::string& question); -}; - -#endif // ESP32_CAMERA_H +#ifndef SSCMA_CAMERA_H +#define SSCMA_CAMERA_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "sscma_client.h" +#include "camera.h" + +struct SscmaData { + uint8_t* img; + size_t len; +}; +struct JpegData { + uint8_t* buf; + size_t len; +}; + +class SscmaCamera : public Camera { +private: + lv_img_dsc_t preview_image_; + std::string explain_url_; + std::string explain_token_; + sscma_client_io_handle_t sscma_client_io_handle_; + sscma_client_handle_t sscma_client_handle_; + QueueHandle_t sscma_data_queue_; + JpegData jpeg_data_; + jpeg_dec_handle_t jpeg_dec_; + jpeg_dec_io_t *jpeg_io_; + jpeg_dec_header_info_t *jpeg_out_; +public: + SscmaCamera(esp_io_expander_handle_t io_exp_handle); + ~SscmaCamera(); + + virtual void SetExplainUrl(const std::string& url, const std::string& token); + virtual bool Capture(); + // 翻转控制函数 + virtual bool SetHMirror(bool enabled) override; + virtual bool SetVFlip(bool enabled) override; + virtual std::string Explain(const std::string& question); +}; + +#endif // ESP32_CAMERA_H diff --git a/main/boards/sp-esp32-s3-1.28-box/README.md b/main/boards/sp-esp32-s3-1.28-box/README.md index 84b854b..da7a645 100644 --- a/main/boards/sp-esp32-s3-1.28-box/README.md +++ b/main/boards/sp-esp32-s3-1.28-box/README.md @@ -1,31 +1,31 @@ -【产品简介】 -】支持触摸 -】支持充电 -】独特外形设计 -产品链接1:https://spotpear.cn/shop/ESP32-S3-N16R8-AI-DeepSeek-XiaoZhi-XiaGe-Qwen-DouBao-1.28-inch-LCD.html -产品链接2:https://spotpear.cn/shop/ESP32-S3-N16R8-AI-DeepSeek-XiaoZhi-XiaGe-Qwen-DouBao-1.28-inch-Round-LCD-BOX-TouchScreen.html -# 编译配置命令 - -**配置编译目标为 ESP32S3:** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> Spotpear ESP32-S3-1.28-BOX -``` - -**编译:** - -```bash -idf.py build -``` +【产品简介】 +】支持触摸 +】支持充电 +】独特外形设计 +产品链接1:https://spotpear.cn/shop/ESP32-S3-N16R8-AI-DeepSeek-XiaoZhi-XiaGe-Qwen-DouBao-1.28-inch-LCD.html +产品链接2:https://spotpear.cn/shop/ESP32-S3-N16R8-AI-DeepSeek-XiaoZhi-XiaGe-Qwen-DouBao-1.28-inch-Round-LCD-BOX-TouchScreen.html +# 编译配置命令 + +**配置编译目标为 ESP32S3:** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> Spotpear ESP32-S3-1.28-BOX +``` + +**编译:** + +```bash +idf.py build +``` diff --git a/main/boards/sp-esp32-s3-1.28-box/config.h b/main/boards/sp-esp32-s3-1.28-box/config.h index 3dbea9a..67185d1 100644 --- a/main/boards/sp-esp32-s3-1.28-box/config.h +++ b/main/boards/sp-esp32-s3-1.28-box/config.h @@ -1,52 +1,52 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -// Movecall Moji configuration - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 //MCLK -#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 //LRCK -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 //SCLK -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 //DOUT -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 //DIN - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_48 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - -#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_4 -#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_2 -#define DISPLAY_SPI_CS_PIN GPIO_NUM_5 -#define DISPLAY_SPI_DC_PIN GPIO_NUM_47 -#define DISPLAY_SPI_RESET_PIN GPIO_NUM_38 - -#define TP_PIN_NUM_TP_SDA (GPIO_NUM_11) -#define TP_PIN_NUM_TP_SCL (GPIO_NUM_7) -#define TP_PIN_NUM_TP_RST (GPIO_NUM_6) -#define TP_PIN_NUM_TP_INT (GPIO_NUM_12) - -#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000) - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// Movecall Moji configuration + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 //MCLK +#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 //LRCK +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 //SCLK +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 //DOUT +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 //DIN + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_4 +#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_2 +#define DISPLAY_SPI_CS_PIN GPIO_NUM_5 +#define DISPLAY_SPI_DC_PIN GPIO_NUM_47 +#define DISPLAY_SPI_RESET_PIN GPIO_NUM_38 + +#define TP_PIN_NUM_TP_SDA (GPIO_NUM_11) +#define TP_PIN_NUM_TP_SCL (GPIO_NUM_7) +#define TP_PIN_NUM_TP_RST (GPIO_NUM_6) +#define TP_PIN_NUM_TP_INT (GPIO_NUM_12) + +#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000) + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/sp-esp32-s3-1.28-box/config.json b/main/boards/sp-esp32-s3-1.28-box/config.json index db34c1c..d1f4033 100644 --- a/main/boards/sp-esp32-s3-1.28-box/config.json +++ b/main/boards/sp-esp32-s3-1.28-box/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "sp-esp32-s3-1.28-box", - "sdkconfig_append": [] - } - ] -} +{ + "target": "esp32s3", + "builds": [ + { + "name": "sp-esp32-s3-1.28-box", + "sdkconfig_append": [] + } + ] +} diff --git a/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc b/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc index a885feb..4f9f221 100644 --- a/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc +++ b/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc @@ -1,308 +1,308 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include -#include -#include -#include - -#include -#include -#include -#include "system_reset.h" -#include "driver/gpio.h" -#include "driver/spi_master.h" -#include -#include "i2c_device.h" -#include -#include -#include "power_save_timer.h" -#include -#include - -#define TAG "Spotpear_ESP32_S3_1_28_BOX" - -class Cst816d : public I2cDevice { -public: - struct TouchPoint_t { - int num = 0; - int x = -1; - int y = -1; - }; - Cst816d(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - uint8_t chip_id = ReadReg(0xA3); - ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); - read_buffer_ = new uint8_t[6]; - } - - ~Cst816d() { - delete[] read_buffer_; - } - - void UpdateTouchPoint() { - ReadRegs(0x02, read_buffer_, 6); - if (read_buffer_[0] == 0xFF) - { - read_buffer_[0] = 0x00; - } - tp_.num = read_buffer_[0] & 0x01; - tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; - tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; - } - - const TouchPoint_t& GetTouchPoint() { - return tp_; - } - -private: - uint8_t* read_buffer_ = nullptr; - TouchPoint_t tp_; -}; - -class CustomLcdDisplay : public SpiLcdDisplay { -public: - CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, - esp_lcd_panel_handle_t panel_handle, - int width, - int height, - int offset_x, - int offset_y, - bool mirror_x, - bool mirror_y, - bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - - DisplayLockGuard lock(this); - // 由于屏幕是圆的,所以状态栏需要增加左右内边距 - lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.33, 0); - lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.33, 0); - } -}; - -class Spotpear_ESP32_S3_1_28_BOX : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - Display* display_; - esp_timer_handle_t touchpad_timer_; - Cst816d* cst816d_; - PowerSaveTimer* power_save_timer_; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializePowerSaveTimer() { - rtc_gpio_init(GPIO_NUM_3); - rtc_gpio_set_direction(GPIO_NUM_3, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_3, 1); - - power_save_timer_ = new PowerSaveTimer(-1, 60, 290); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - rtc_gpio_set_level(GPIO_NUM_3, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_3); - esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - esp_deep_sleep_start(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeCodecI2c_Touch() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_1, - .sda_io_num = TP_PIN_NUM_TP_SDA, - .scl_io_num = TP_PIN_NUM_TP_SCL, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - static void touchpad_timer_callback(void* arg) { - auto& board = (Spotpear_ESP32_S3_1_28_BOX&)Board::GetInstance(); - auto touchpad = board.GetTouchpad(); - static bool was_touched = false; - static int64_t touch_start_time = 0; - const int64_t TOUCH_THRESHOLD_MS = 500; // 触摸时长阈值,超过500ms视为长按 - - touchpad->UpdateTouchPoint(); - auto touch_point = touchpad->GetTouchPoint(); - - // 检测触摸开始 - if (touch_point.num > 0 && !was_touched) { - was_touched = true; - touch_start_time = esp_timer_get_time() / 1000; // 转换为毫秒 - } - // 检测触摸释放 - else if (touch_point.num == 0 && was_touched) { - was_touched = false; - int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time; - - // 只有短触才触发 - if (touch_duration < TOUCH_THRESHOLD_MS) { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - board.ResetWifiConfiguration(); - } - app.ToggleChatState(); - } - } - } - - void InitializeCst816DTouchPad() { - ESP_LOGI(TAG, "Init Cst816D"); - cst816d_ = new Cst816d(i2c_bus_, 0x15); - - // 创建定时器,10ms 间隔 - esp_timer_create_args_t timer_args = { - .callback = touchpad_timer_callback, - .arg = NULL, - .dispatch_method = ESP_TIMER_TASK, - .name = "touchpad_timer", - .skip_unhandled_events = true, - }; - - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 10 * 1000)); // 10ms = 10000us - } - - // SPI初始化 - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize SPI bus"); - spi_bus_config_t buscfg = GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN, - DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t)); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - // GC9A01初始化 - void InitializeGc9a01Display() { - ESP_LOGI(TAG, "Init GC9A01 display"); - ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_spi_config_t io_config = GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, 0, NULL); - io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); - - ESP_LOGI(TAG, "Install GC9A01 panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use - panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; //LCD_RGB_ENDIAN_RGB; - panel_config.bits_per_pixel = 16; - - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); - - uint8_t data_0x62[] = { 0x18, 0x0D, 0x71, 0xED, 0x70, 0x70, 0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70 }; - esp_lcd_panel_io_tx_param(io_handle, 0x62, data_0x62, sizeof(data_0x62)); - - uint8_t data_0x63[] = { 0x18, 0x11, 0x71, 0xF1, 0x70, 0x70, 0x18, 0x13, 0x71, 0xF3, 0x70, 0x70 }; - esp_lcd_panel_io_tx_param(io_handle, 0x63, data_0x63, sizeof(data_0x63)); - - display_ = new CustomLcdDisplay(io_handle, panel_handle, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - -public: - Spotpear_ESP32_S3_1_28_BOX() : boot_button_(BOOT_BUTTON_GPIO) { - - gpio_set_direction(TP_PIN_NUM_TP_INT, GPIO_MODE_INPUT); - int level = gpio_get_level(TP_PIN_NUM_TP_INT); - if (level == 1) { - InitializeCodecI2c_Touch(); - InitializeCst816DTouchPad(); - } - InitializePowerSaveTimer(); - InitializeCodecI2c(); - InitializeSpi(); - InitializeGc9a01Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - Cst816d* GetTouchpad() { - return cst816d_; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(Spotpear_ESP32_S3_1_28_BOX); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include +#include +#include +#include + +#include +#include +#include +#include "system_reset.h" +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include +#include "i2c_device.h" +#include +#include +#include "power_save_timer.h" +#include +#include + +#define TAG "Spotpear_ESP32_S3_1_28_BOX" + +class Cst816d : public I2cDevice { +public: + struct TouchPoint_t { + int num = 0; + int x = -1; + int y = -1; + }; + Cst816d(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + uint8_t chip_id = ReadReg(0xA3); + ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); + read_buffer_ = new uint8_t[6]; + } + + ~Cst816d() { + delete[] read_buffer_; + } + + void UpdateTouchPoint() { + ReadRegs(0x02, read_buffer_, 6); + if (read_buffer_[0] == 0xFF) + { + read_buffer_[0] = 0x00; + } + tp_.num = read_buffer_[0] & 0x01; + tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; + tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; + } + + const TouchPoint_t& GetTouchPoint() { + return tp_; + } + +private: + uint8_t* read_buffer_ = nullptr; + TouchPoint_t tp_; +}; + +class CustomLcdDisplay : public SpiLcdDisplay { +public: + CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_handle_t panel_handle, + int width, + int height, + int offset_x, + int offset_y, + bool mirror_x, + bool mirror_y, + bool swap_xy) + : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + + DisplayLockGuard lock(this); + // 由于屏幕是圆的,所以状态栏需要增加左右内边距 + lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.33, 0); + lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.33, 0); + } +}; + +class Spotpear_ESP32_S3_1_28_BOX : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + Display* display_; + esp_timer_handle_t touchpad_timer_; + Cst816d* cst816d_; + PowerSaveTimer* power_save_timer_; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_3); + rtc_gpio_set_direction(GPIO_NUM_3, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_3, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 290); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_3, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_3); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeCodecI2c_Touch() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_1, + .sda_io_num = TP_PIN_NUM_TP_SDA, + .scl_io_num = TP_PIN_NUM_TP_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + static void touchpad_timer_callback(void* arg) { + auto& board = (Spotpear_ESP32_S3_1_28_BOX&)Board::GetInstance(); + auto touchpad = board.GetTouchpad(); + static bool was_touched = false; + static int64_t touch_start_time = 0; + const int64_t TOUCH_THRESHOLD_MS = 500; // 触摸时长阈值,超过500ms视为长按 + + touchpad->UpdateTouchPoint(); + auto touch_point = touchpad->GetTouchPoint(); + + // 检测触摸开始 + if (touch_point.num > 0 && !was_touched) { + was_touched = true; + touch_start_time = esp_timer_get_time() / 1000; // 转换为毫秒 + } + // 检测触摸释放 + else if (touch_point.num == 0 && was_touched) { + was_touched = false; + int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time; + + // 只有短触才触发 + if (touch_duration < TOUCH_THRESHOLD_MS) { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && + !WifiStation::GetInstance().IsConnected()) { + board.ResetWifiConfiguration(); + } + app.ToggleChatState(); + } + } + } + + void InitializeCst816DTouchPad() { + ESP_LOGI(TAG, "Init Cst816D"); + cst816d_ = new Cst816d(i2c_bus_, 0x15); + + // 创建定时器,10ms 间隔 + esp_timer_create_args_t timer_args = { + .callback = touchpad_timer_callback, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "touchpad_timer", + .skip_unhandled_events = true, + }; + + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 10 * 1000)); // 10ms = 10000us + } + + // SPI初始化 + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize SPI bus"); + spi_bus_config_t buscfg = GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN, + DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t)); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + // GC9A01初始化 + void InitializeGc9a01Display() { + ESP_LOGI(TAG, "Init GC9A01 display"); + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_spi_config_t io_config = GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, 0, NULL); + io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); + + ESP_LOGI(TAG, "Install GC9A01 panel driver"); + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use + panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; //LCD_RGB_ENDIAN_RGB; + panel_config.bits_per_pixel = 16; + + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); + + uint8_t data_0x62[] = { 0x18, 0x0D, 0x71, 0xED, 0x70, 0x70, 0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70 }; + esp_lcd_panel_io_tx_param(io_handle, 0x62, data_0x62, sizeof(data_0x62)); + + uint8_t data_0x63[] = { 0x18, 0x11, 0x71, 0xF1, 0x70, 0x70, 0x18, 0x13, 0x71, 0xF3, 0x70, 0x70 }; + esp_lcd_panel_io_tx_param(io_handle, 0x63, data_0x63, sizeof(data_0x63)); + + display_ = new CustomLcdDisplay(io_handle, panel_handle, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + +public: + Spotpear_ESP32_S3_1_28_BOX() : boot_button_(BOOT_BUTTON_GPIO) { + + gpio_set_direction(TP_PIN_NUM_TP_INT, GPIO_MODE_INPUT); + int level = gpio_get_level(TP_PIN_NUM_TP_INT); + if (level == 1) { + InitializeCodecI2c_Touch(); + InitializeCst816DTouchPad(); + } + InitializePowerSaveTimer(); + InitializeCodecI2c(); + InitializeSpi(); + InitializeGc9a01Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + Cst816d* GetTouchpad() { + return cst816d_; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(Spotpear_ESP32_S3_1_28_BOX); diff --git a/main/boards/sp-esp32-s3-1.54-muma/README.md b/main/boards/sp-esp32-s3-1.54-muma/README.md index 94a00bb..10d488c 100644 --- a/main/boards/sp-esp32-s3-1.54-muma/README.md +++ b/main/boards/sp-esp32-s3-1.54-muma/README.md @@ -1,34 +1,34 @@ -【产品简介】 -[] ESP32 S3小木马开发板1.54寸LCD小智muma虾哥AI DeepSeek人工智能语音聊天机器人N16R8 -【功能】 -[] 可爱小木马,支持天气时钟, SD视频播放, AI智能对话所有固件源码开源,适合小孩编程学习,可开发更多功能。 -AI小智支持语音唤醒。触摸版本额外支持触摸唤醒和打断 -显示屏:1.54寸ST7789 240x240分辨率 -产品链接: -https://spotpear.cn/shop/ESP32-S3-AI-1.54-inch-LCD-Display-TouchScreen-N16R8-muma-DeepSeek/sp-esp32-s3-1.54-muma-W-Bat.html - -# 编译配置命令 - -**配置编译目标为 ESP32S3:** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> Spotpear ESP32-S3-LCD-1.54-MUMA -``` - -**编译:** - -```bash -idf.py build -``` +【产品简介】 +[] ESP32 S3小木马开发板1.54寸LCD小智muma虾哥AI DeepSeek人工智能语音聊天机器人N16R8 +【功能】 +[] 可爱小木马,支持天气时钟, SD视频播放, AI智能对话所有固件源码开源,适合小孩编程学习,可开发更多功能。 +AI小智支持语音唤醒。触摸版本额外支持触摸唤醒和打断 +显示屏:1.54寸ST7789 240x240分辨率 +产品链接: +https://spotpear.cn/shop/ESP32-S3-AI-1.54-inch-LCD-Display-TouchScreen-N16R8-muma-DeepSeek/sp-esp32-s3-1.54-muma-W-Bat.html + +# 编译配置命令 + +**配置编译目标为 ESP32S3:** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> Spotpear ESP32-S3-LCD-1.54-MUMA +``` + +**编译:** + +```bash +idf.py build +``` diff --git a/main/boards/sp-esp32-s3-1.54-muma/config.h b/main/boards/sp-esp32-s3-1.54-muma/config.h index a13ce75..68ea0af 100644 --- a/main/boards/sp-esp32-s3-1.54-muma/config.h +++ b/main/boards/sp-esp32-s3-1.54-muma/config.h @@ -1,58 +1,58 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -// Movecall Moji configuration - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 //MCLK -#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 //LRCK -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 //SCLK -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 //DOUT -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 //DIN - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_48 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - -#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_4 -#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_2 -#define DISPLAY_SPI_CS_PIN GPIO_NUM_5 -#define DISPLAY_SPI_DC_PIN GPIO_NUM_47 -#define DISPLAY_SPI_RESET_PIN GPIO_NUM_38 - - -#define TP_PIN_NUM_TP_SDA (GPIO_NUM_11) -#define TP_PIN_NUM_TP_SCL (GPIO_NUM_7) -#define TP_PIN_NUM_TP_RST (GPIO_NUM_6) -#define TP_PIN_NUM_TP_INT (GPIO_NUM_12) - -#define POWER_CHARGE_LED_PIN GPIO_NUM_3 -#define POWER_CHARGE_DETECT_PIN GPIO_NUM_41 -#define POWER_ADC_UNIT ADC_UNIT_1 -#define POWER_ADC_CHANNEL ADC_CHANNEL_0 - -#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000) - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// Movecall Moji configuration + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 //MCLK +#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 //LRCK +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 //SCLK +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 //DOUT +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 //DIN + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_4 +#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_2 +#define DISPLAY_SPI_CS_PIN GPIO_NUM_5 +#define DISPLAY_SPI_DC_PIN GPIO_NUM_47 +#define DISPLAY_SPI_RESET_PIN GPIO_NUM_38 + + +#define TP_PIN_NUM_TP_SDA (GPIO_NUM_11) +#define TP_PIN_NUM_TP_SCL (GPIO_NUM_7) +#define TP_PIN_NUM_TP_RST (GPIO_NUM_6) +#define TP_PIN_NUM_TP_INT (GPIO_NUM_12) + +#define POWER_CHARGE_LED_PIN GPIO_NUM_3 +#define POWER_CHARGE_DETECT_PIN GPIO_NUM_41 +#define POWER_ADC_UNIT ADC_UNIT_1 +#define POWER_ADC_CHANNEL ADC_CHANNEL_0 + +#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000) + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/sp-esp32-s3-1.54-muma/config.json b/main/boards/sp-esp32-s3-1.54-muma/config.json index 2bc4d1b..8a3468c 100644 --- a/main/boards/sp-esp32-s3-1.54-muma/config.json +++ b/main/boards/sp-esp32-s3-1.54-muma/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "sp-esp32-s3-1.54-muma", - "sdkconfig_append": [] - } - ] -} +{ + "target": "esp32s3", + "builds": [ + { + "name": "sp-esp32-s3-1.54-muma", + "sdkconfig_append": [] + } + ] +} diff --git a/main/boards/sp-esp32-s3-1.54-muma/power_manager.h b/main/boards/sp-esp32-s3-1.54-muma/power_manager.h index 2df53a1..aa8755e 100644 --- a/main/boards/sp-esp32-s3-1.54-muma/power_manager.h +++ b/main/boards/sp-esp32-s3-1.54-muma/power_manager.h @@ -1,186 +1,186 @@ -#pragma once -#include -#include - -#include -#include -#include - - -class PowerManager { -private: - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - - gpio_num_t charging_pin_ = GPIO_NUM_41; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 1; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); - - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += value; - } - average_adc /= adc_values_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {1980, 0}, - {2081, 20}, - {2163, 40}, - {2250, 60}, - {2340, 80}, - {2480, 100} - }; - - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&io_conf); - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 100000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include + +#include +#include +#include + + +class PowerManager { +private: + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + + gpio_num_t charging_pin_ = GPIO_NUM_41; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 1; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {1980, 0}, + {2081, 20}, + {2163, 40}, + {2250, 60}, + {2340, 80}, + {2480, 100} + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 100000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc b/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc index 436cc34..f1e660f 100644 --- a/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc +++ b/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc @@ -1,325 +1,325 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include -#include -#include -#include "system_reset.h" - -#include -#include - -#include "driver/gpio.h" -#include "driver/spi_master.h" -#include -#include -#include -#include "i2c_device.h" -#include -#include "power_manager.h" -#include "power_save_timer.h" -#include -#include - -#define TAG "Spotpear_esp32_s3_lcd_1_54" - -class Cst816d : public I2cDevice { -public: - struct TouchPoint_t { - int num = 0; - int x = -1; - int y = -1; - }; - Cst816d(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - uint8_t chip_id = ReadReg(0xA3); - ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); - read_buffer_ = new uint8_t[6]; - } - - ~Cst816d() { - delete[] read_buffer_; - } - - void UpdateTouchPoint() { - ReadRegs(0x02, read_buffer_, 6); - tp_.num = read_buffer_[0] & 0x0F; - tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; - tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; - } - - const TouchPoint_t& GetTouchPoint() { - return tp_; - } - -private: - uint8_t* read_buffer_ = nullptr; - TouchPoint_t tp_; -}; - -class Spotpear_esp32_s3_lcd_1_54 : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - Display* display_; - esp_timer_handle_t touchpad_timer_; - Cst816d* cst816d_; - esp_io_expander_handle_t io_expander_ = NULL; - esp_lcd_panel_handle_t panel_ = nullptr; - - PowerManager* power_manager_; - PowerSaveTimer* power_save_timer_; - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_41); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - rtc_gpio_init(GPIO_NUM_3); - rtc_gpio_set_direction(GPIO_NUM_3, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_3, 1); - - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - rtc_gpio_set_level(GPIO_NUM_3, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_3); - esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - esp_deep_sleep_start(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeCodecI2c_Touch() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_1, - .sda_io_num = TP_PIN_NUM_TP_SDA, - .scl_io_num = TP_PIN_NUM_TP_SCL, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - static void touchpad_timer_callback(void* arg) { - auto& board = (Spotpear_esp32_s3_lcd_1_54&)Board::GetInstance(); - auto touchpad = board.GetTouchpad(); - static bool was_touched = false; - static int64_t touch_start_time = 0; - const int64_t TOUCH_THRESHOLD_MS = 500; // 触摸时长阈值,超过500ms视为长按 - - touchpad->UpdateTouchPoint(); - auto touch_point = touchpad->GetTouchPoint(); - // 检测触摸开始 - if (touch_point.num > 0 && !was_touched) { - was_touched = true; - touch_start_time = esp_timer_get_time() / 1000; // 转换为毫秒 - } - // 检测触摸释放 - else if (touch_point.num == 0 && was_touched) { - was_touched = false; - int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time; - - // 只有短触才触发 - if (touch_duration < TOUCH_THRESHOLD_MS) { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - board.ResetWifiConfiguration(); - } - app.ToggleChatState(); - } - } - } - - void InitializeCst816DTouchPad() { - ESP_LOGI(TAG, "Init Cst816D"); - cst816d_ = new Cst816d(i2c_bus_, 0x15); - - // 创建定时器,10ms 间隔 - esp_timer_create_args_t timer_args = { - .callback = touchpad_timer_callback, - .arg = NULL, - .dispatch_method = ESP_TIMER_TASK, - .name = "touchpad_timer", - .skip_unhandled_events = true, - }; - - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 10 * 1000)); // 10ms = 10000us - } - - void EnableLcdCs() { - if(io_expander_ != NULL) { - esp_io_expander_set_level(io_expander_, DISPLAY_SPI_CS_PIN, 0);// 置低 LCD CS - } - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SPI_SCLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; - io_config.dc_gpio_num = DISPLAY_SPI_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 60 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - EnableLcdCs(); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - - // uint8_t data_0xBB[] = { 0x3F }; - // esp_lcd_panel_io_tx_param(panel_io, 0xBB, data_0xBB, sizeof(data_0xBB)); - - uint8_t data_0xBB[] = { 0x38 }; - esp_lcd_panel_io_tx_param(panel_io, 0xBB, data_0xBB, sizeof(data_0xBB)); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - -public: - - Spotpear_esp32_s3_lcd_1_54() :boot_button_(BOOT_BUTTON_GPIO){ - gpio_set_direction(TP_PIN_NUM_TP_INT, GPIO_MODE_INPUT); - int level = gpio_get_level(TP_PIN_NUM_TP_INT); - if (level == 1) { - InitializeCodecI2c_Touch(); - InitializeCst816DTouchPad(); - } - InitializePowerSaveTimer(); - InitializeCodecI2c(); - InitializeSpi(); - InitializePowerManager(); - InitializeSt7789Display(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - - } - - virtual Led* GetLed() override { - static SingleLed led_strip(BUILTIN_LED_GPIO); - return &led_strip; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - Cst816d* GetTouchpad() { - return cst816d_; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(Spotpear_esp32_s3_lcd_1_54); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include +#include +#include +#include "system_reset.h" + +#include +#include + +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include +#include +#include +#include "i2c_device.h" +#include +#include "power_manager.h" +#include "power_save_timer.h" +#include +#include + +#define TAG "Spotpear_esp32_s3_lcd_1_54" + +class Cst816d : public I2cDevice { +public: + struct TouchPoint_t { + int num = 0; + int x = -1; + int y = -1; + }; + Cst816d(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + uint8_t chip_id = ReadReg(0xA3); + ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); + read_buffer_ = new uint8_t[6]; + } + + ~Cst816d() { + delete[] read_buffer_; + } + + void UpdateTouchPoint() { + ReadRegs(0x02, read_buffer_, 6); + tp_.num = read_buffer_[0] & 0x0F; + tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; + tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; + } + + const TouchPoint_t& GetTouchPoint() { + return tp_; + } + +private: + uint8_t* read_buffer_ = nullptr; + TouchPoint_t tp_; +}; + +class Spotpear_esp32_s3_lcd_1_54 : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + Display* display_; + esp_timer_handle_t touchpad_timer_; + Cst816d* cst816d_; + esp_io_expander_handle_t io_expander_ = NULL; + esp_lcd_panel_handle_t panel_ = nullptr; + + PowerManager* power_manager_; + PowerSaveTimer* power_save_timer_; + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_41); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_3); + rtc_gpio_set_direction(GPIO_NUM_3, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_3, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_3, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_3); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeCodecI2c_Touch() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_1, + .sda_io_num = TP_PIN_NUM_TP_SDA, + .scl_io_num = TP_PIN_NUM_TP_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + static void touchpad_timer_callback(void* arg) { + auto& board = (Spotpear_esp32_s3_lcd_1_54&)Board::GetInstance(); + auto touchpad = board.GetTouchpad(); + static bool was_touched = false; + static int64_t touch_start_time = 0; + const int64_t TOUCH_THRESHOLD_MS = 500; // 触摸时长阈值,超过500ms视为长按 + + touchpad->UpdateTouchPoint(); + auto touch_point = touchpad->GetTouchPoint(); + // 检测触摸开始 + if (touch_point.num > 0 && !was_touched) { + was_touched = true; + touch_start_time = esp_timer_get_time() / 1000; // 转换为毫秒 + } + // 检测触摸释放 + else if (touch_point.num == 0 && was_touched) { + was_touched = false; + int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time; + + // 只有短触才触发 + if (touch_duration < TOUCH_THRESHOLD_MS) { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && + !WifiStation::GetInstance().IsConnected()) { + board.ResetWifiConfiguration(); + } + app.ToggleChatState(); + } + } + } + + void InitializeCst816DTouchPad() { + ESP_LOGI(TAG, "Init Cst816D"); + cst816d_ = new Cst816d(i2c_bus_, 0x15); + + // 创建定时器,10ms 间隔 + esp_timer_create_args_t timer_args = { + .callback = touchpad_timer_callback, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "touchpad_timer", + .skip_unhandled_events = true, + }; + + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 10 * 1000)); // 10ms = 10000us + } + + void EnableLcdCs() { + if(io_expander_ != NULL) { + esp_io_expander_set_level(io_expander_, DISPLAY_SPI_CS_PIN, 0);// 置低 LCD CS + } + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SPI_SCLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; + io_config.dc_gpio_num = DISPLAY_SPI_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 60 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + EnableLcdCs(); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); + + // uint8_t data_0xBB[] = { 0x3F }; + // esp_lcd_panel_io_tx_param(panel_io, 0xBB, data_0xBB, sizeof(data_0xBB)); + + uint8_t data_0xBB[] = { 0x38 }; + esp_lcd_panel_io_tx_param(panel_io, 0xBB, data_0xBB, sizeof(data_0xBB)); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + +public: + + Spotpear_esp32_s3_lcd_1_54() :boot_button_(BOOT_BUTTON_GPIO){ + gpio_set_direction(TP_PIN_NUM_TP_INT, GPIO_MODE_INPUT); + int level = gpio_get_level(TP_PIN_NUM_TP_INT); + if (level == 1) { + InitializeCodecI2c_Touch(); + InitializeCst816DTouchPad(); + } + InitializePowerSaveTimer(); + InitializeCodecI2c(); + InitializeSpi(); + InitializePowerManager(); + InitializeSt7789Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + + } + + virtual Led* GetLed() override { + static SingleLed led_strip(BUILTIN_LED_GPIO); + return &led_strip; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + Cst816d* GetTouchpad() { + return cst816d_; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(Spotpear_esp32_s3_lcd_1_54); diff --git a/main/boards/surfer-c3-1.14tft/README.md b/main/boards/surfer-c3-1.14tft/README.md index 9a6d211..31c249d 100644 --- a/main/boards/surfer-c3-1.14tft/README.md +++ b/main/boards/surfer-c3-1.14tft/README.md @@ -1,5 +1,5 @@ -## Surfer-ESP32-C3 开发板 - -1、参考立创·实战派C3-ESP32C3开发板,修改了TFT屏幕背光引脚,增加ADC电池电量检测功能; -2、该开发板 flash 大小为 16MB,编译时注意选择默认的分区表。 - +## Surfer-ESP32-C3 开发板 + +1、参考立创·实战派C3-ESP32C3开发板,修改了TFT屏幕背光引脚,增加ADC电池电量检测功能; +2、该开发板 flash 大小为 16MB,编译时注意选择默认的分区表。 + diff --git a/main/boards/surfer-c3-1.14tft/config.h b/main/boards/surfer-c3-1.14tft/config.h index 7970330..f9e3b39 100644 --- a/main/boards/surfer-c3-1.14tft/config.h +++ b/main/boards/surfer-c3-1.14tft/config.h @@ -1,45 +1,45 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 - -// #define AUDIO_CODEC_USE_PCA9557 -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -// #define AUDIO_CODEC_ES7210_ADDR 0x82 - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_9 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SPI_SCK_PIN GPIO_NUM_3 -#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5 -#define DISPLAY_DC_PIN GPIO_NUM_6 -#define DISPLAY_SPI_CS_PIN GPIO_NUM_4 - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 135 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY true - -#define DISPLAY_OFFSET_X 40 -#define DISPLAY_OFFSET_Y 53 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 + +// #define AUDIO_CODEC_USE_PCA9557 +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +// #define AUDIO_CODEC_ES7210_ADDR 0x82 + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_9 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SPI_SCK_PIN GPIO_NUM_3 +#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5 +#define DISPLAY_DC_PIN GPIO_NUM_6 +#define DISPLAY_SPI_CS_PIN GPIO_NUM_4 + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 135 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 40 +#define DISPLAY_OFFSET_Y 53 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/surfer-c3-1.14tft/config.json b/main/boards/surfer-c3-1.14tft/config.json index a8f30ad..dd5245b 100644 --- a/main/boards/surfer-c3-1.14tft/config.json +++ b/main/boards/surfer-c3-1.14tft/config.json @@ -1,14 +1,14 @@ -{ - "target": "esp32c3", - "builds": [ - { - "name": "surfer-c3-1.14tft", - "sdkconfig_append": [ - "CONFIG_PM_ENABLE=y", - "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", - "CONFIG_USE_ESP_WAKE_WORD=y", - "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y" - ] - } - ] +{ + "target": "esp32c3", + "builds": [ + { + "name": "surfer-c3-1.14tft", + "sdkconfig_append": [ + "CONFIG_PM_ENABLE=y", + "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", + "CONFIG_USE_ESP_WAKE_WORD=y", + "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/surfer-c3-1.14tft/power_manager.h b/main/boards/surfer-c3-1.14tft/power_manager.h index 22643b8..83447cd 100644 --- a/main/boards/surfer-c3-1.14tft/power_manager.h +++ b/main/boards/surfer-c3-1.14tft/power_manager.h @@ -1,201 +1,201 @@ -#pragma once -#include -#include - -#include -#include -#include - - -class PowerManager { -private: - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 1; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_2, &adc_value)); - - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += value; - } - average_adc /= adc_values_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {3060, 0}, - {3200, 20}, - {3340, 40}, - {3480, 60}, - {3620, 80}, - {3760, 100} - }; - - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - - - if (charging_pin_ != GPIO_NUM_NC) { - // 不初始化 ADC,不检测 - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - } - - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_2_5, - .bitwidth = ADC_BITWIDTH_DEFAULT, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_2, &chan_config)); - ESP_LOGI("PowerManager ADC INIT------------------", - "ADC atten: ADC_ATTEN_DB_2_5, bitwidth: ADC_BITWIDTH_DEFAULT ADC chan: ADC_CHANNEL_2"); - - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - if (charging_pin_ == GPIO_NUM_NC) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - if (charging_pin_ == GPIO_NUM_NC) { - return false; - } - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include + +#include +#include +#include + + +class PowerManager { +private: + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 1; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_2, &adc_value)); + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {3060, 0}, + {3200, 20}, + {3340, 40}, + {3480, 60}, + {3620, 80}, + {3760, 100} + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + + + if (charging_pin_ != GPIO_NUM_NC) { + // 不初始化 ADC,不检测 + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + } + + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_2_5, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_2, &chan_config)); + ESP_LOGI("PowerManager ADC INIT------------------", + "ADC atten: ADC_ATTEN_DB_2_5, bitwidth: ADC_BITWIDTH_DEFAULT ADC chan: ADC_CHANNEL_2"); + + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + if (charging_pin_ == GPIO_NUM_NC) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + if (charging_pin_ == GPIO_NUM_NC) { + return false; + } + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc b/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc index cad05c6..e20c179 100644 --- a/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc +++ b/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc @@ -1,210 +1,210 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "i2c_device.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "power_save_timer.h" -#include "power_manager.h" -// #include - -#define TAG "SURFERC3114TFT" - -class SurferC3114TFT : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - LcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - - // Print I2C bus info - if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { - while (true) { - ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - } - } - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_NC); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - - } - - void InitializePowerSaveTimer() { - //定时器,调整设备为modem-sleep模式和屏幕亮度 - power_save_timer_ = new PowerSaveTimer(-1, 60, -1); - power_save_timer_->OnEnterSleepMode([this]() { - ESP_LOGI(TAG, "Enabling modem-sleep mode"); - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - esp_wifi_set_ps(WIFI_PS_MIN_MODEM); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - esp_wifi_set_ps(WIFI_PS_NONE); // 关闭Wi-Fi省电,恢复正常 - // esp_lcd_panel_disp_on_off(panel_, true); // 重新打开显示 - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down display"); - GetBacklight()->SetBrightness(1); - // esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - // power_save_timer_->SetEnabled(false); // 禁用定时器,防止重复 - - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeSt7789Display() { - - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io_)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); - - esp_lcd_panel_reset(panel_); - - esp_lcd_panel_init(panel_); - esp_lcd_panel_invert_color(panel_, true); - esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new SpiLcdDisplay(panel_io_, panel_, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - SurferC3114TFT() : boot_button_(BOOT_BUTTON_GPIO) { - InitializePowerManager(); - InitializePowerSaveTimer(); - - InitializeI2c(); - InitializeSpi(); - InitializeSt7789Display(); - InitializeButtons(); - - GetBacklight()->RestoreBrightness(); - - // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 - esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec( - codec_i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } - -}; - -DECLARE_BOARD(SurferC3114TFT); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "power_save_timer.h" +#include "power_manager.h" +// #include + +#define TAG "SURFERC3114TFT" + +class SurferC3114TFT : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + LcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + // Print I2C bus info + if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { + while (true) { + ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } + } + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_NC); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + + } + + void InitializePowerSaveTimer() { + //定时器,调整设备为modem-sleep模式和屏幕亮度 + power_save_timer_ = new PowerSaveTimer(-1, 60, -1); + power_save_timer_->OnEnterSleepMode([this]() { + ESP_LOGI(TAG, "Enabling modem-sleep mode"); + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + esp_wifi_set_ps(WIFI_PS_MIN_MODEM); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + esp_wifi_set_ps(WIFI_PS_NONE); // 关闭Wi-Fi省电,恢复正常 + // esp_lcd_panel_disp_on_off(panel_, true); // 重新打开显示 + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down display"); + GetBacklight()->SetBrightness(1); + // esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + // power_save_timer_->SetEnabled(false); // 禁用定时器,防止重复 + + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeSt7789Display() { + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io_)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); + + esp_lcd_panel_reset(panel_); + + esp_lcd_panel_init(panel_); + esp_lcd_panel_invert_color(panel_, true); + esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io_, panel_, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + SurferC3114TFT() : boot_button_(BOOT_BUTTON_GPIO) { + InitializePowerManager(); + InitializePowerSaveTimer(); + + InitializeI2c(); + InitializeSpi(); + InitializeSt7789Display(); + InitializeButtons(); + + GetBacklight()->RestoreBrightness(); + + // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 + esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec( + codec_i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } + +}; + +DECLARE_BOARD(SurferC3114TFT); diff --git a/main/boards/taiji-pi-s3/README.md b/main/boards/taiji-pi-s3/README.md index 8f02d6d..4a26540 100644 --- a/main/boards/taiji-pi-s3/README.md +++ b/main/boards/taiji-pi-s3/README.md @@ -1,41 +1,45 @@ -# 由于原来的麦克风型号停产,2025年7月之后的太极派(JC3636W518)更换了麦克风,并且更换了屏幕玻璃,所以在产品标签上批次号大于2528的用户请选择I2S Type PDM, -# 新增双声道配置 -# 编译配置命令 - -**配置编译目标为 ESP32S3:** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> 太极小派esp32s3 - -Xiaozhi Assistant -> TAIJIPAI_S3_CONFIG -> taiji-pi-S3 I2S Type -> I2S Type PDM -``` - -**如果需要选择双声道:** -``` -Xiaozhi Assistant -> TAIJIPAI_S3_CONFIG -> Enabel use 2 slot -``` - -**修改PSRAM配置:** - -``` -component config -> ESP PSRAM -> SPI RAM config -> Try to allocate memories of WiFi and LWIP in SPIRAM firstly. If failed, allocate internal memory - -``` - -**编译:** - -```bash -idf.py build -``` +# 由于原来的麦克风型号停产,2025年7月之后的太极派(JC3636W518)更换了麦克风,并且更换了屏幕玻璃,所以在产品标签上批次号大于2528的用户请选择I2S Type PDM, + +# 新增双声道配置 + +# 编译配置命令 + +**配置编译目标为 ESP32S3:** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> 太极小派esp32s3 + +Xiaozhi Assistant -> TAIJIPAI_S3_CONFIG -> taiji-pi-S3 I2S Type -> I2S Type PDM +``` + +**如果需要选择双声道:** +``` + +Xiaozhi Assistant -> TAIJIPAI_S3_CONFIG -> Enabel use 2 slot +``` + + +**修改PSRAM配置:** + +``` +component config -> ESP PSRAM -> SPI RAM config -> Try to allocate memories of WiFi and LWIP in SPIRAM firstly. If failed, allocate internal memory + +``` + +**编译:** + +```bash +idf.py build +``` diff --git a/main/boards/taiji-pi-s3/config.h b/main/boards/taiji-pi-s3/config.h index 6b9f54a..728f581 100644 --- a/main/boards/taiji-pi-s3/config.h +++ b/main/boards/taiji-pi-s3/config.h @@ -1,66 +1,66 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -// Taiji Pi S3 Board configuration - -#include -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 -#define AUDIO_DEFAULT_OUTPUT_VOLUME 80 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_21 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_16 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_18 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_NC -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_17 -#define AUDIO_MUTE_PIN GPIO_NUM_48 // 低电平静音 - -#define AUDIO_MIC_WS_PIN GPIO_NUM_45 -#define AUDIO_MIC_SD_PIN GPIO_NUM_46 -#define AUDIO_MIC_SCK_PIN GPIO_NUM_42 - -#define DISPLAY_WIDTH 360 -#define DISPLAY_HEIGHT 360 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define QSPI_LCD_H_RES (360) -#define QSPI_LCD_V_RES (360) -#define QSPI_LCD_BIT_PER_PIXEL (16) - -#define QSPI_LCD_HOST SPI2_HOST -#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_9 -#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_10 -#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_11 -#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_12 -#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_13 -#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_14 -#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_47 -#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_15 - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define TP_PORT (I2C_NUM_1) -#define TP_PIN_NUM_TP_SDA (GPIO_NUM_7) -#define TP_PIN_NUM_TP_SCL (GPIO_NUM_8) -#define TP_PIN_NUM_TP_RST (GPIO_NUM_40) -#define TP_PIN_NUM_TP_INT (GPIO_NUM_41) - -#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \ - { \ - .data0_io_num = d0, \ - .data1_io_num = d1, \ - .sclk_io_num = sclk, \ - .data2_io_num = d2, \ - .data3_io_num = d3, \ - .max_transfer_sz = max_trans_sz, \ - } - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// Taiji Pi S3 Board configuration + +#include +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_DEFAULT_OUTPUT_VOLUME 80 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_21 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_16 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_18 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_NC +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_17 +#define AUDIO_MUTE_PIN GPIO_NUM_48 // 低电平静音 + +#define AUDIO_MIC_WS_PIN GPIO_NUM_45 +#define AUDIO_MIC_SD_PIN GPIO_NUM_46 +#define AUDIO_MIC_SCK_PIN GPIO_NUM_42 + +#define DISPLAY_WIDTH 360 +#define DISPLAY_HEIGHT 360 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false + +#define QSPI_LCD_H_RES (360) +#define QSPI_LCD_V_RES (360) +#define QSPI_LCD_BIT_PER_PIXEL (16) + +#define QSPI_LCD_HOST SPI2_HOST +#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_9 +#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_10 +#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_11 +#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_12 +#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_13 +#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_14 +#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_47 +#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_15 + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define TP_PORT (I2C_NUM_1) +#define TP_PIN_NUM_TP_SDA (GPIO_NUM_7) +#define TP_PIN_NUM_TP_SCL (GPIO_NUM_8) +#define TP_PIN_NUM_TP_RST (GPIO_NUM_40) +#define TP_PIN_NUM_TP_INT (GPIO_NUM_41) + +#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \ + { \ + .data0_io_num = d0, \ + .data1_io_num = d1, \ + .sclk_io_num = sclk, \ + .data2_io_num = d2, \ + .data3_io_num = d3, \ + .max_transfer_sz = max_trans_sz, \ + } + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/taiji-pi-s3/config.json b/main/boards/taiji-pi-s3/config.json index 4328536..c4dfcf3 100644 --- a/main/boards/taiji-pi-s3/config.json +++ b/main/boards/taiji-pi-s3/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "taiji-pi-s3", - "sdkconfig_append": [ - "CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "taiji-pi-s3", + "sdkconfig_append": [ + "CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/taiji-pi-s3/taiji_pi_s3.cc b/main/boards/taiji-pi-s3/taiji_pi_s3.cc index 9d9fac7..44783e3 100644 --- a/main/boards/taiji-pi-s3/taiji_pi_s3.cc +++ b/main/boards/taiji-pi-s3/taiji_pi_s3.cc @@ -1,664 +1,664 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "i2c_device.h" -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#define TAG "TaijiPiS3Board" - -static const st77916_lcd_init_cmd_t lcd_init_cmds[] = { -#ifdef CONFIG_TAIJIPAI_I2S_TYPE_STD - {0xF0, (uint8_t[]){0x08}, 1, 0}, - {0xF2, (uint8_t[]){0x08}, 1, 0}, - {0x9B, (uint8_t[]){0x51}, 1, 0}, - {0x86, (uint8_t[]){0x53}, 1, 0}, - {0xF2, (uint8_t[]){0x80}, 1, 0}, - {0xF0, (uint8_t[]){0x00}, 1, 0}, - {0xF0, (uint8_t[]){0x01}, 1, 0}, - {0xF1, (uint8_t[]){0x01}, 1, 0}, - {0xB0, (uint8_t[]){0x54}, 1, 0}, - {0xB1, (uint8_t[]){0x3F}, 1, 0}, - {0xB2, (uint8_t[]){0x2A}, 1, 0}, - {0xB4, (uint8_t[]){0x46}, 1, 0}, - {0xB5, (uint8_t[]){0x34}, 1, 0}, - {0xB6, (uint8_t[]){0xD5}, 1, 0}, - {0xB7, (uint8_t[]){0x30}, 1, 0}, - {0xBA, (uint8_t[]){0x00}, 1, 0}, - {0xBB, (uint8_t[]){0x08}, 1, 0}, - {0xBC, (uint8_t[]){0x08}, 1, 0}, - {0xBD, (uint8_t[]){0x00}, 1, 0}, - {0xC0, (uint8_t[]){0x80}, 1, 0}, - {0xC1, (uint8_t[]){0x10}, 1, 0}, - {0xC2, (uint8_t[]){0x37}, 1, 0}, - {0xC3, (uint8_t[]){0x80}, 1, 0}, - {0xC4, (uint8_t[]){0x10}, 1, 0}, - {0xC5, (uint8_t[]){0x37}, 1, 0}, - {0xC6, (uint8_t[]){0xA9}, 1, 0}, - {0xC7, (uint8_t[]){0x41}, 1, 0}, - {0xC8, (uint8_t[]){0x51}, 1, 0}, - {0xC9, (uint8_t[]){0xA9}, 1, 0}, - {0xCA, (uint8_t[]){0x41}, 1, 0}, - {0xCB, (uint8_t[]){0x51}, 1, 0}, - {0xD0, (uint8_t[]){0x91}, 1, 0}, - {0xD1, (uint8_t[]){0x68}, 1, 0}, - {0xD2, (uint8_t[]){0x69}, 1, 0}, - {0xF5, (uint8_t[]){0x00, 0xA5}, 2, 0}, - {0xDD, (uint8_t[]){0x3F}, 1, 0}, - {0xDE, (uint8_t[]){0x3F}, 1, 0}, - {0xF1, (uint8_t[]){0x10}, 1, 0}, - {0xF0, (uint8_t[]){0x00}, 1, 0}, - {0xF0, (uint8_t[]){0x02}, 1, 0}, - {0xE0, (uint8_t[]){0x70, 0x09, 0x12, 0x0C, 0x0B, 0x27, 0x38, 0x54, 0x4E, 0x19, 0x15, 0x15, 0x2C, 0x2F}, 14, 0}, - {0xE1, (uint8_t[]){0x70, 0x08, 0x11, 0x0C, 0x0B, 0x27, 0x38, 0x43, 0x4C, 0x18, 0x14, 0x14, 0x2B, 0x2D}, 14, 0}, - {0xF0, (uint8_t[]){0x10}, 1, 0}, - {0xF3, (uint8_t[]){0x10}, 1, 0}, - {0xE0, (uint8_t[]){0x08}, 1, 0}, - {0xE1, (uint8_t[]){0x00}, 1, 0}, - {0xE2, (uint8_t[]){0x00}, 1, 0}, - {0xE3, (uint8_t[]){0x00}, 1, 0}, - {0xE4, (uint8_t[]){0xE0}, 1, 0}, - {0xE5, (uint8_t[]){0x06}, 1, 0}, - {0xE6, (uint8_t[]){0x21}, 1, 0}, - {0xE7, (uint8_t[]){0x00}, 1, 0}, - {0xE8, (uint8_t[]){0x05}, 1, 0}, - {0xE9, (uint8_t[]){0x82}, 1, 0}, - {0xEA, (uint8_t[]){0xDF}, 1, 0}, - {0xEB, (uint8_t[]){0x89}, 1, 0}, - {0xEC, (uint8_t[]){0x20}, 1, 0}, - {0xED, (uint8_t[]){0x14}, 1, 0}, - {0xEE, (uint8_t[]){0xFF}, 1, 0}, - {0xEF, (uint8_t[]){0x00}, 1, 0}, - {0xF8, (uint8_t[]){0xFF}, 1, 0}, - {0xF9, (uint8_t[]){0x00}, 1, 0}, - {0xFA, (uint8_t[]){0x00}, 1, 0}, - {0xFB, (uint8_t[]){0x30}, 1, 0}, - {0xFC, (uint8_t[]){0x00}, 1, 0}, - {0xFD, (uint8_t[]){0x00}, 1, 0}, - {0xFE, (uint8_t[]){0x00}, 1, 0}, - {0xFF, (uint8_t[]){0x00}, 1, 0}, - {0x60, (uint8_t[]){0x42}, 1, 0}, - {0x61, (uint8_t[]){0xE0}, 1, 0}, - {0x62, (uint8_t[]){0x40}, 1, 0}, - {0x63, (uint8_t[]){0x40}, 1, 0}, - {0x64, (uint8_t[]){0x02}, 1, 0}, - {0x65, (uint8_t[]){0x00}, 1, 0}, - {0x66, (uint8_t[]){0x40}, 1, 0}, - {0x67, (uint8_t[]){0x03}, 1, 0}, - {0x68, (uint8_t[]){0x00}, 1, 0}, - {0x69, (uint8_t[]){0x00}, 1, 0}, - {0x6A, (uint8_t[]){0x00}, 1, 0}, - {0x6B, (uint8_t[]){0x00}, 1, 0}, - {0x70, (uint8_t[]){0x42}, 1, 0}, - {0x71, (uint8_t[]){0xE0}, 1, 0}, - {0x72, (uint8_t[]){0x40}, 1, 0}, - {0x73, (uint8_t[]){0x40}, 1, 0}, - {0x74, (uint8_t[]){0x02}, 1, 0}, - {0x75, (uint8_t[]){0x00}, 1, 0}, - {0x76, (uint8_t[]){0x40}, 1, 0}, - {0x77, (uint8_t[]){0x03}, 1, 0}, - {0x78, (uint8_t[]){0x00}, 1, 0}, - {0x79, (uint8_t[]){0x00}, 1, 0}, - {0x7A, (uint8_t[]){0x00}, 1, 0}, - {0x7B, (uint8_t[]){0x00}, 1, 0}, - {0x80, (uint8_t[]){0x48}, 1, 0}, - {0x81, (uint8_t[]){0x00}, 1, 0}, - {0x82, (uint8_t[]){0x05}, 1, 0}, - {0x83, (uint8_t[]){0x02}, 1, 0}, - {0x84, (uint8_t[]){0xDD}, 1, 0}, - {0x85, (uint8_t[]){0x00}, 1, 0}, - {0x86, (uint8_t[]){0x00}, 1, 0}, - {0x87, (uint8_t[]){0x00}, 1, 0}, - {0x88, (uint8_t[]){0x48}, 1, 0}, - {0x89, (uint8_t[]){0x00}, 1, 0}, - {0x8A, (uint8_t[]){0x07}, 1, 0}, - {0x8B, (uint8_t[]){0x02}, 1, 0}, - {0x8C, (uint8_t[]){0xDF}, 1, 0}, - {0x8D, (uint8_t[]){0x00}, 1, 0}, - {0x8E, (uint8_t[]){0x00}, 1, 0}, - {0x8F, (uint8_t[]){0x00}, 1, 0}, - {0x90, (uint8_t[]){0x48}, 1, 0}, - {0x91, (uint8_t[]){0x00}, 1, 0}, - {0x92, (uint8_t[]){0x09}, 1, 0}, - {0x93, (uint8_t[]){0x02}, 1, 0}, - {0x94, (uint8_t[]){0xE1}, 1, 0}, - {0x95, (uint8_t[]){0x00}, 1, 0}, - {0x96, (uint8_t[]){0x00}, 1, 0}, - {0x97, (uint8_t[]){0x00}, 1, 0}, - {0x98, (uint8_t[]){0x48}, 1, 0}, - {0x99, (uint8_t[]){0x00}, 1, 0}, - {0x9A, (uint8_t[]){0x0B}, 1, 0}, - {0x9B, (uint8_t[]){0x02}, 1, 0}, - {0x9C, (uint8_t[]){0xE3}, 1, 0}, - {0x9D, (uint8_t[]){0x00}, 1, 0}, - {0x9E, (uint8_t[]){0x00}, 1, 0}, - {0x9F, (uint8_t[]){0x00}, 1, 0}, - {0xA0, (uint8_t[]){0x48}, 1, 0}, - {0xA1, (uint8_t[]){0x00}, 1, 0}, - {0xA2, (uint8_t[]){0x04}, 1, 0}, - {0xA3, (uint8_t[]){0x02}, 1, 0}, - {0xA4, (uint8_t[]){0xDC}, 1, 0}, - {0xA5, (uint8_t[]){0x00}, 1, 0}, - {0xA6, (uint8_t[]){0x00}, 1, 0}, - {0xA7, (uint8_t[]){0x00}, 1, 0}, - {0xA8, (uint8_t[]){0x48}, 1, 0}, - {0xA9, (uint8_t[]){0x00}, 1, 0}, - {0xAA, (uint8_t[]){0x06}, 1, 0}, - {0xAB, (uint8_t[]){0x02}, 1, 0}, - {0xAC, (uint8_t[]){0xDE}, 1, 0}, - {0xAD, (uint8_t[]){0x00}, 1, 0}, - {0xAE, (uint8_t[]){0x00}, 1, 0}, - {0xAF, (uint8_t[]){0x00}, 1, 0}, - {0xB0, (uint8_t[]){0x48}, 1, 0}, - {0xB1, (uint8_t[]){0x00}, 1, 0}, - {0xB2, (uint8_t[]){0x08}, 1, 0}, - {0xB3, (uint8_t[]){0x02}, 1, 0}, - {0xB4, (uint8_t[]){0xE0}, 1, 0}, - {0xB5, (uint8_t[]){0x00}, 1, 0}, - {0xB6, (uint8_t[]){0x00}, 1, 0}, - {0xB7, (uint8_t[]){0x00}, 1, 0}, - {0xB8, (uint8_t[]){0x48}, 1, 0}, - {0xB9, (uint8_t[]){0x00}, 1, 0}, - {0xBA, (uint8_t[]){0x0A}, 1, 0}, - {0xBB, (uint8_t[]){0x02}, 1, 0}, - {0xBC, (uint8_t[]){0xE2}, 1, 0}, - {0xBD, (uint8_t[]){0x00}, 1, 0}, - {0xBE, (uint8_t[]){0x00}, 1, 0}, - {0xBF, (uint8_t[]){0x00}, 1, 0}, - {0xC0, (uint8_t[]){0x12}, 1, 0}, - {0xC1, (uint8_t[]){0xAA}, 1, 0}, - {0xC2, (uint8_t[]){0x65}, 1, 0}, - {0xC3, (uint8_t[]){0x74}, 1, 0}, - {0xC4, (uint8_t[]){0x47}, 1, 0}, - {0xC5, (uint8_t[]){0x56}, 1, 0}, - {0xC6, (uint8_t[]){0x00}, 1, 0}, - {0xC7, (uint8_t[]){0x88}, 1, 0}, - {0xC8, (uint8_t[]){0x99}, 1, 0}, - {0xC9, (uint8_t[]){0x33}, 1, 0}, - {0xD0, (uint8_t[]){0x21}, 1, 0}, - {0xD1, (uint8_t[]){0xAA}, 1, 0}, - {0xD2, (uint8_t[]){0x65}, 1, 0}, - {0xD3, (uint8_t[]){0x74}, 1, 0}, - {0xD4, (uint8_t[]){0x47}, 1, 0}, - {0xD5, (uint8_t[]){0x56}, 1, 0}, - {0xD6, (uint8_t[]){0x00}, 1, 0}, - {0xD7, (uint8_t[]){0x88}, 1, 0}, - {0xD8, (uint8_t[]){0x99}, 1, 0}, - {0xD9, (uint8_t[]){0x33}, 1, 0}, - {0xF3, (uint8_t[]){0x01}, 1, 0}, - {0xF0, (uint8_t[]){0x00}, 1, 0}, - {0xF0, (uint8_t[]){0x01}, 1, 0}, - {0xF1, (uint8_t[]){0x01}, 1, 0}, - {0xA0, (uint8_t[]){0x0B}, 1, 0}, - {0xA3, (uint8_t[]){0x2A}, 1, 0}, - {0xA5, (uint8_t[]){0xC3}, 1, 1}, - {0xA3, (uint8_t[]){0x2B}, 1, 0}, - {0xA5, (uint8_t[]){0xC3}, 1, 1}, - {0xA3, (uint8_t[]){0x2C}, 1, 0}, - {0xA5, (uint8_t[]){0xC3}, 1, 1}, - {0xA3, (uint8_t[]){0x2D}, 1, 0}, - {0xA5, (uint8_t[]){0xC3}, 1, 1}, - {0xA3, (uint8_t[]){0x2E}, 1, 0}, - {0xA5, (uint8_t[]){0xC3}, 1, 1}, - {0xA3, (uint8_t[]){0x2F}, 1, 0}, - {0xA5, (uint8_t[]){0xC3}, 1, 1}, - {0xA3, (uint8_t[]){0x30}, 1, 0}, - {0xA5, (uint8_t[]){0xC3}, 1, 1}, - {0xA3, (uint8_t[]){0x31}, 1, 0}, - {0xA5, (uint8_t[]){0xC3}, 1, 1}, - {0xA3, (uint8_t[]){0x32}, 1, 0}, - {0xA5, (uint8_t[]){0xC3}, 1, 1}, - {0xA3, (uint8_t[]){0x33}, 1, 0}, - {0xA5, (uint8_t[]){0xC3}, 1, 1}, - {0xA0, (uint8_t[]){0x09}, 1, 0}, - {0xF1, (uint8_t[]){0x10}, 1, 0}, - {0xF0, (uint8_t[]){0x00}, 1, 0}, - {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0}, - {0x2B, (uint8_t[]){0x01, 0x68, 0x01, 0x68}, 4, 0}, - {0x4D, (uint8_t[]){0x00}, 1, 0}, - {0x4E, (uint8_t[]){0x00}, 1, 0}, - {0x4F, (uint8_t[]){0x00}, 1, 0}, - {0x4C, (uint8_t[]){0x01}, 1, 10}, - {0x4C, (uint8_t[]){0x00}, 1, 0}, - {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0}, - {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0}, - {0x21, (uint8_t[]){0x00}, 1, 0}, - //{0x3A, (uint8_t[]){0x55}, 1, 0}, // color=16 - {0x11, (uint8_t[]){0x00}, 1, 120}, - {0x29, (uint8_t[]){0x00}, 1, 0}, -#else - {0xF0, (uint8_t[]){0x28}, 1, 0}, - {0xF2, (uint8_t[]){0x28}, 1, 0}, - {0x73, (uint8_t[]){0xF0}, 1, 0}, - {0x7C, (uint8_t[]){0xD1}, 1, 0}, - {0x83, (uint8_t[]){0xE0}, 1, 0}, - {0x84, (uint8_t[]){0x61}, 1, 0}, - {0xF2, (uint8_t[]){0x82}, 1, 0}, - {0xF0, (uint8_t[]){0x00}, 1, 0}, - {0xF0, (uint8_t[]){0x01}, 1, 0}, - {0xF1, (uint8_t[]){0x01}, 1, 0}, - {0xB0, (uint8_t[]){0x56}, 1, 0}, - {0xB1, (uint8_t[]){0x4D}, 1, 0}, - {0xB2, (uint8_t[]){0x24}, 1, 0}, - {0xB4, (uint8_t[]){0x87}, 1, 0}, - {0xB5, (uint8_t[]){0x44}, 1, 0}, - {0xB6, (uint8_t[]){0x8B}, 1, 0}, - {0xB7, (uint8_t[]){0x40}, 1, 0}, - {0xB8, (uint8_t[]){0x86}, 1, 0}, - {0xBA, (uint8_t[]){0x00}, 1, 0}, - {0xBB, (uint8_t[]){0x08}, 1, 0}, - {0xBC, (uint8_t[]){0x08}, 1, 0}, - {0xBD, (uint8_t[]){0x00}, 1, 0}, - {0xC0, (uint8_t[]){0x80}, 1, 0}, - {0xC1, (uint8_t[]){0x10}, 1, 0}, - {0xC2, (uint8_t[]){0x37}, 1, 0}, - {0xC3, (uint8_t[]){0x80}, 1, 0}, - {0xC4, (uint8_t[]){0x10}, 1, 0}, - {0xC5, (uint8_t[]){0x37}, 1, 0}, - {0xC6, (uint8_t[]){0xA9}, 1, 0}, - {0xC7, (uint8_t[]){0x41}, 1, 0}, - {0xC8, (uint8_t[]){0x01}, 1, 0}, - {0xC9, (uint8_t[]){0xA9}, 1, 0}, - {0xCA, (uint8_t[]){0x41}, 1, 0}, - {0xCB, (uint8_t[]){0x01}, 1, 0}, - {0xD0, (uint8_t[]){0x91}, 1, 0}, - {0xD1, (uint8_t[]){0x68}, 1, 0}, - {0xD2, (uint8_t[]){0x68}, 1, 0}, - {0xF5, (uint8_t[]){0x00, 0xA5}, 2, 0}, - {0xDD, (uint8_t[]){0x4F}, 1, 0}, - {0xDE, (uint8_t[]){0x4F}, 1, 0}, - {0xF1, (uint8_t[]){0x10}, 1, 0}, - {0xF0, (uint8_t[]){0x00}, 1, 0}, - {0xF0, (uint8_t[]){0x02}, 1, 0}, - {0xE0, (uint8_t[]){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0}, - {0xE1, (uint8_t[]){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0}, - {0xF0, (uint8_t[]){0x10}, 1, 0}, - {0xF3, (uint8_t[]){0x10}, 1, 0}, - {0xE0, (uint8_t[]){0x07}, 1, 0}, - {0xE1, (uint8_t[]){0x00}, 1, 0}, - {0xE2, (uint8_t[]){0x00}, 1, 0}, - {0xE3, (uint8_t[]){0x00}, 1, 0}, - {0xE4, (uint8_t[]){0xE0}, 1, 0}, - {0xE5, (uint8_t[]){0x06}, 1, 0}, - {0xE6, (uint8_t[]){0x21}, 1, 0}, - {0xE7, (uint8_t[]){0x01}, 1, 0}, - {0xE8, (uint8_t[]){0x05}, 1, 0}, - {0xE9, (uint8_t[]){0x02}, 1, 0}, - {0xEA, (uint8_t[]){0xDA}, 1, 0}, - {0xEB, (uint8_t[]){0x00}, 1, 0}, - {0xEC, (uint8_t[]){0x00}, 1, 0}, - {0xED, (uint8_t[]){0x0F}, 1, 0}, - {0xEE, (uint8_t[]){0x00}, 1, 0}, - {0xEF, (uint8_t[]){0x00}, 1, 0}, - {0xF8, (uint8_t[]){0x00}, 1, 0}, - {0xF9, (uint8_t[]){0x00}, 1, 0}, - {0xFA, (uint8_t[]){0x00}, 1, 0}, - {0xFB, (uint8_t[]){0x00}, 1, 0}, - {0xFC, (uint8_t[]){0x00}, 1, 0}, - {0xFD, (uint8_t[]){0x00}, 1, 0}, - {0xFE, (uint8_t[]){0x00}, 1, 0}, - {0xFF, (uint8_t[]){0x00}, 1, 0}, - {0x60, (uint8_t[]){0x40}, 1, 0}, - {0x61, (uint8_t[]){0x04}, 1, 0}, - {0x62, (uint8_t[]){0x00}, 1, 0}, - {0x63, (uint8_t[]){0x42}, 1, 0}, - {0x64, (uint8_t[]){0xD9}, 1, 0}, - {0x65, (uint8_t[]){0x00}, 1, 0}, - {0x66, (uint8_t[]){0x00}, 1, 0}, - {0x67, (uint8_t[]){0x00}, 1, 0}, - {0x68, (uint8_t[]){0x00}, 1, 0}, - {0x69, (uint8_t[]){0x00}, 1, 0}, - {0x6A, (uint8_t[]){0x00}, 1, 0}, - {0x6B, (uint8_t[]){0x00}, 1, 0}, - {0x70, (uint8_t[]){0x40}, 1, 0}, - {0x71, (uint8_t[]){0x03}, 1, 0}, - {0x72, (uint8_t[]){0x00}, 1, 0}, - {0x73, (uint8_t[]){0x42}, 1, 0}, - {0x74, (uint8_t[]){0xD8}, 1, 0}, - {0x75, (uint8_t[]){0x00}, 1, 0}, - {0x76, (uint8_t[]){0x00}, 1, 0}, - {0x77, (uint8_t[]){0x00}, 1, 0}, - {0x78, (uint8_t[]){0x00}, 1, 0}, - {0x79, (uint8_t[]){0x00}, 1, 0}, - {0x7A, (uint8_t[]){0x00}, 1, 0}, - {0x7B, (uint8_t[]){0x00}, 1, 0}, - {0x80, (uint8_t[]){0x48}, 1, 0}, - {0x81, (uint8_t[]){0x00}, 1, 0}, - {0x82, (uint8_t[]){0x06}, 1, 0}, - {0x83, (uint8_t[]){0x02}, 1, 0}, - {0x84, (uint8_t[]){0xD6}, 1, 0}, - {0x85, (uint8_t[]){0x04}, 1, 0}, - {0x86, (uint8_t[]){0x00}, 1, 0}, - {0x87, (uint8_t[]){0x00}, 1, 0}, - {0x88, (uint8_t[]){0x48}, 1, 0}, - {0x89, (uint8_t[]){0x00}, 1, 0}, - {0x8A, (uint8_t[]){0x08}, 1, 0}, - {0x8B, (uint8_t[]){0x02}, 1, 0}, - {0x8C, (uint8_t[]){0xD8}, 1, 0}, - {0x8D, (uint8_t[]){0x04}, 1, 0}, - {0x8E, (uint8_t[]){0x00}, 1, 0}, - {0x8F, (uint8_t[]){0x00}, 1, 0}, - {0x90, (uint8_t[]){0x48}, 1, 0}, - {0x91, (uint8_t[]){0x00}, 1, 0}, - {0x92, (uint8_t[]){0x0A}, 1, 0}, - {0x93, (uint8_t[]){0x02}, 1, 0}, - {0x94, (uint8_t[]){0xDA}, 1, 0}, - {0x95, (uint8_t[]){0x04}, 1, 0}, - {0x96, (uint8_t[]){0x00}, 1, 0}, - {0x97, (uint8_t[]){0x00}, 1, 0}, - {0x98, (uint8_t[]){0x48}, 1, 0}, - {0x99, (uint8_t[]){0x00}, 1, 0}, - {0x9A, (uint8_t[]){0x0C}, 1, 0}, - {0x9B, (uint8_t[]){0x02}, 1, 0}, - {0x9C, (uint8_t[]){0xDC}, 1, 0}, - {0x9D, (uint8_t[]){0x04}, 1, 0}, - {0x9E, (uint8_t[]){0x00}, 1, 0}, - {0x9F, (uint8_t[]){0x00}, 1, 0}, - {0xA0, (uint8_t[]){0x48}, 1, 0}, - {0xA1, (uint8_t[]){0x00}, 1, 0}, - {0xA2, (uint8_t[]){0x05}, 1, 0}, - {0xA3, (uint8_t[]){0x02}, 1, 0}, - {0xA4, (uint8_t[]){0xD5}, 1, 0}, - {0xA5, (uint8_t[]){0x04}, 1, 0}, - {0xA6, (uint8_t[]){0x00}, 1, 0}, - {0xA7, (uint8_t[]){0x00}, 1, 0}, - {0xA8, (uint8_t[]){0x48}, 1, 0}, - {0xA9, (uint8_t[]){0x00}, 1, 0}, - {0xAA, (uint8_t[]){0x07}, 1, 0}, - {0xAB, (uint8_t[]){0x02}, 1, 0}, - {0xAC, (uint8_t[]){0xD7}, 1, 0}, - {0xAD, (uint8_t[]){0x04}, 1, 0}, - {0xAE, (uint8_t[]){0x00}, 1, 0}, - {0xAF, (uint8_t[]){0x00}, 1, 0}, - {0xB0, (uint8_t[]){0x48}, 1, 0}, - {0xB1, (uint8_t[]){0x00}, 1, 0}, - {0xB2, (uint8_t[]){0x09}, 1, 0}, - {0xB3, (uint8_t[]){0x02}, 1, 0}, - {0xB4, (uint8_t[]){0xD9}, 1, 0}, - {0xB5, (uint8_t[]){0x04}, 1, 0}, - {0xB6, (uint8_t[]){0x00}, 1, 0}, - {0xB7, (uint8_t[]){0x00}, 1, 0}, - {0xB8, (uint8_t[]){0x48}, 1, 0}, - {0xB9, (uint8_t[]){0x00}, 1, 0}, - {0xBA, (uint8_t[]){0x0B}, 1, 0}, - {0xBB, (uint8_t[]){0x02}, 1, 0}, - {0xBC, (uint8_t[]){0xDB}, 1, 0}, - {0xBD, (uint8_t[]){0x04}, 1, 0}, - {0xBE, (uint8_t[]){0x00}, 1, 0}, - {0xBF, (uint8_t[]){0x00}, 1, 0}, - {0xC0, (uint8_t[]){0x10}, 1, 0}, - {0xC1, (uint8_t[]){0x47}, 1, 0}, - {0xC2, (uint8_t[]){0x56}, 1, 0}, - {0xC3, (uint8_t[]){0x65}, 1, 0}, - {0xC4, (uint8_t[]){0x74}, 1, 0}, - {0xC5, (uint8_t[]){0x88}, 1, 0}, - {0xC6, (uint8_t[]){0x99}, 1, 0}, - {0xC7, (uint8_t[]){0x01}, 1, 0}, - {0xC8, (uint8_t[]){0xBB}, 1, 0}, - {0xC9, (uint8_t[]){0xAA}, 1, 0}, - {0xD0, (uint8_t[]){0x10}, 1, 0}, - {0xD1, (uint8_t[]){0x47}, 1, 0}, - {0xD2, (uint8_t[]){0x56}, 1, 0}, - {0xD3, (uint8_t[]){0x65}, 1, 0}, - {0xD4, (uint8_t[]){0x74}, 1, 0}, - {0xD5, (uint8_t[]){0x88}, 1, 0}, - {0xD6, (uint8_t[]){0x99}, 1, 0}, - {0xD7, (uint8_t[]){0x01}, 1, 0}, - {0xD8, (uint8_t[]){0xBB}, 1, 0}, - {0xD9, (uint8_t[]){0xAA}, 1, 0}, - {0xF3, (uint8_t[]){0x01}, 1, 0}, - {0xF0, (uint8_t[]){0x00}, 1, 0}, - {0x21, (uint8_t[]){0x00}, 1, 0}, - {0x11, (uint8_t[]){0x00}, 1, 120}, - {0x29, (uint8_t[]){0x00}, 1, 0}, -#endif -}; - -class Cst816s : public I2cDevice { -public: - struct TouchPoint_t { - int num = 0; - int x = -1; - int y = -1; - }; - Cst816s(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - uint8_t chip_id = ReadReg(0xA3); - ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); - read_buffer_ = new uint8_t[6]; - } - - ~Cst816s() { - delete[] read_buffer_; - } - - void UpdateTouchPoint() { - ReadRegs(0x02, read_buffer_, 6); - tp_.num = read_buffer_[0] & 0x0F; - tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; - tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; - } - - const TouchPoint_t& GetTouchPoint() { - return tp_; - } - -private: - uint8_t* read_buffer_ = nullptr; - TouchPoint_t tp_; -}; - -class TaijiPiS3Board : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Cst816s* cst816s_; - LcdDisplay* display_; - esp_timer_handle_t touchpad_timer_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = TP_PIN_NUM_TP_SDA, - .scl_io_num = TP_PIN_NUM_TP_SCL, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - static void touchpad_timer_callback(void* arg) { - auto& board = (TaijiPiS3Board&)Board::GetInstance(); - auto touchpad = board.GetTouchpad(); - static bool was_touched = false; - static int64_t touch_start_time = 0; - const int64_t TOUCH_THRESHOLD_MS = 500; // 触摸时长阈值,超过500ms视为长按 - - touchpad->UpdateTouchPoint(); - auto touch_point = touchpad->GetTouchPoint(); - - // 检测触摸开始 - if (touch_point.num > 0 && !was_touched) { - was_touched = true; - touch_start_time = esp_timer_get_time() / 1000; // 转换为毫秒 - } - // 检测触摸释放 - else if (touch_point.num == 0 && was_touched) { - was_touched = false; - int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time; - - // 只有短触才触发 - if (touch_duration < TOUCH_THRESHOLD_MS) { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - board.ResetWifiConfiguration(); - } - app.ToggleChatState(); - } - } - } - - void InitializeCst816sTouchPad() { - ESP_LOGI(TAG, "Init Cst816s"); - cst816s_ = new Cst816s(i2c_bus_, 0x15); - - // 创建定时器,10ms 间隔 - esp_timer_create_args_t timer_args = { - .callback = touchpad_timer_callback, - .arg = NULL, - .dispatch_method = ESP_TIMER_TASK, - .name = "touchpad_timer", - .skip_unhandled_events = true, - }; - - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 10 * 1000)); // 10ms = 10000us - } - - void BspLcdBlSet(int brightness_percent) - { - if (brightness_percent > 100) { - brightness_percent = 100; - } - if (brightness_percent < 0) { - brightness_percent = 0; - } - - ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness_percent); - uint32_t duty_cycle = (1023 * brightness_percent) / 100; // LEDC resolution set to 10bits, thus: 100% = 1023 - ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty_cycle); - ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0); - } - - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize QSPI bus"); - - const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK, - QSPI_PIN_NUM_LCD_DATA0, - QSPI_PIN_NUM_LCD_DATA1, - QSPI_PIN_NUM_LCD_DATA2, - QSPI_PIN_NUM_LCD_DATA3, - QSPI_LCD_H_RES * 80 * sizeof(uint16_t)); - ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO)); - } - - void Initializest77916Display() { - - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - ESP_LOGI(TAG, "Install panel IO"); - - const esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(QSPI_PIN_NUM_LCD_CS, NULL, NULL); - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io)); - - ESP_LOGI(TAG, "Install ST77916 panel driver"); - - st77916_vendor_config_t vendor_config = { - .init_cmds = lcd_init_cmds, // 如果使用自定义初始化命令,请取消注释这些行 - .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st77916_lcd_init_cmd_t), - .flags = { - .use_qspi_interface = 1, - }, - }; - const esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = QSPI_PIN_NUM_LCD_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h` - .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18) - .vendor_config = &vendor_config, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_disp_on_off(panel, true); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeMute() { - gpio_reset_pin(AUDIO_MUTE_PIN); - /* Set the GPIO as a push/pull output */ - gpio_set_direction(AUDIO_MUTE_PIN, GPIO_MODE_OUTPUT); - gpio_set_level(AUDIO_MUTE_PIN, 1); - } - -public: - TaijiPiS3Board() { - InitializeI2c(); - InitializeCst816sTouchPad(); - InitializeSpi(); - Initializest77916Display(); - InitializeMute(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { -#ifdef CONFIG_TAIJIPAI_I2S_TYPE_STD - static NoAudioCodecSimplex audio_codec( - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - #ifdef CONFIG_I2S_USE_2SLOT - I2S_STD_SLOT_BOTH, - #endif - AUDIO_MIC_SCK_PIN, - AUDIO_MIC_WS_PIN, - #ifdef CONFIG_I2S_USE_2SLOT - AUDIO_MIC_SD_PIN, - I2S_STD_SLOT_LEFT - #else - AUDIO_MIC_SD_PIN - #endif - ); -#else - static NoAudioCodecSimplexPdm audio_codec( - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - #ifdef CONFIG_I2S_USE_2SLOT - I2S_STD_SLOT_BOTH, - #endif - AUDIO_MIC_WS_PIN, - AUDIO_MIC_SD_PIN - ); -#endif - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - Cst816s* GetTouchpad() { - return cst816s_; - } -}; - -DECLARE_BOARD(TaijiPiS3Board); \ No newline at end of file +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "i2c_device.h" +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "TaijiPiS3Board" + +static const st77916_lcd_init_cmd_t lcd_init_cmds[] = { +#ifdef CONFIG_TAIJIPAI_I2S_TYPE_STD + {0xF0, (uint8_t[]){0x08}, 1, 0}, + {0xF2, (uint8_t[]){0x08}, 1, 0}, + {0x9B, (uint8_t[]){0x51}, 1, 0}, + {0x86, (uint8_t[]){0x53}, 1, 0}, + {0xF2, (uint8_t[]){0x80}, 1, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0xF0, (uint8_t[]){0x01}, 1, 0}, + {0xF1, (uint8_t[]){0x01}, 1, 0}, + {0xB0, (uint8_t[]){0x54}, 1, 0}, + {0xB1, (uint8_t[]){0x3F}, 1, 0}, + {0xB2, (uint8_t[]){0x2A}, 1, 0}, + {0xB4, (uint8_t[]){0x46}, 1, 0}, + {0xB5, (uint8_t[]){0x34}, 1, 0}, + {0xB6, (uint8_t[]){0xD5}, 1, 0}, + {0xB7, (uint8_t[]){0x30}, 1, 0}, + {0xBA, (uint8_t[]){0x00}, 1, 0}, + {0xBB, (uint8_t[]){0x08}, 1, 0}, + {0xBC, (uint8_t[]){0x08}, 1, 0}, + {0xBD, (uint8_t[]){0x00}, 1, 0}, + {0xC0, (uint8_t[]){0x80}, 1, 0}, + {0xC1, (uint8_t[]){0x10}, 1, 0}, + {0xC2, (uint8_t[]){0x37}, 1, 0}, + {0xC3, (uint8_t[]){0x80}, 1, 0}, + {0xC4, (uint8_t[]){0x10}, 1, 0}, + {0xC5, (uint8_t[]){0x37}, 1, 0}, + {0xC6, (uint8_t[]){0xA9}, 1, 0}, + {0xC7, (uint8_t[]){0x41}, 1, 0}, + {0xC8, (uint8_t[]){0x51}, 1, 0}, + {0xC9, (uint8_t[]){0xA9}, 1, 0}, + {0xCA, (uint8_t[]){0x41}, 1, 0}, + {0xCB, (uint8_t[]){0x51}, 1, 0}, + {0xD0, (uint8_t[]){0x91}, 1, 0}, + {0xD1, (uint8_t[]){0x68}, 1, 0}, + {0xD2, (uint8_t[]){0x69}, 1, 0}, + {0xF5, (uint8_t[]){0x00, 0xA5}, 2, 0}, + {0xDD, (uint8_t[]){0x3F}, 1, 0}, + {0xDE, (uint8_t[]){0x3F}, 1, 0}, + {0xF1, (uint8_t[]){0x10}, 1, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0xF0, (uint8_t[]){0x02}, 1, 0}, + {0xE0, (uint8_t[]){0x70, 0x09, 0x12, 0x0C, 0x0B, 0x27, 0x38, 0x54, 0x4E, 0x19, 0x15, 0x15, 0x2C, 0x2F}, 14, 0}, + {0xE1, (uint8_t[]){0x70, 0x08, 0x11, 0x0C, 0x0B, 0x27, 0x38, 0x43, 0x4C, 0x18, 0x14, 0x14, 0x2B, 0x2D}, 14, 0}, + {0xF0, (uint8_t[]){0x10}, 1, 0}, + {0xF3, (uint8_t[]){0x10}, 1, 0}, + {0xE0, (uint8_t[]){0x08}, 1, 0}, + {0xE1, (uint8_t[]){0x00}, 1, 0}, + {0xE2, (uint8_t[]){0x00}, 1, 0}, + {0xE3, (uint8_t[]){0x00}, 1, 0}, + {0xE4, (uint8_t[]){0xE0}, 1, 0}, + {0xE5, (uint8_t[]){0x06}, 1, 0}, + {0xE6, (uint8_t[]){0x21}, 1, 0}, + {0xE7, (uint8_t[]){0x00}, 1, 0}, + {0xE8, (uint8_t[]){0x05}, 1, 0}, + {0xE9, (uint8_t[]){0x82}, 1, 0}, + {0xEA, (uint8_t[]){0xDF}, 1, 0}, + {0xEB, (uint8_t[]){0x89}, 1, 0}, + {0xEC, (uint8_t[]){0x20}, 1, 0}, + {0xED, (uint8_t[]){0x14}, 1, 0}, + {0xEE, (uint8_t[]){0xFF}, 1, 0}, + {0xEF, (uint8_t[]){0x00}, 1, 0}, + {0xF8, (uint8_t[]){0xFF}, 1, 0}, + {0xF9, (uint8_t[]){0x00}, 1, 0}, + {0xFA, (uint8_t[]){0x00}, 1, 0}, + {0xFB, (uint8_t[]){0x30}, 1, 0}, + {0xFC, (uint8_t[]){0x00}, 1, 0}, + {0xFD, (uint8_t[]){0x00}, 1, 0}, + {0xFE, (uint8_t[]){0x00}, 1, 0}, + {0xFF, (uint8_t[]){0x00}, 1, 0}, + {0x60, (uint8_t[]){0x42}, 1, 0}, + {0x61, (uint8_t[]){0xE0}, 1, 0}, + {0x62, (uint8_t[]){0x40}, 1, 0}, + {0x63, (uint8_t[]){0x40}, 1, 0}, + {0x64, (uint8_t[]){0x02}, 1, 0}, + {0x65, (uint8_t[]){0x00}, 1, 0}, + {0x66, (uint8_t[]){0x40}, 1, 0}, + {0x67, (uint8_t[]){0x03}, 1, 0}, + {0x68, (uint8_t[]){0x00}, 1, 0}, + {0x69, (uint8_t[]){0x00}, 1, 0}, + {0x6A, (uint8_t[]){0x00}, 1, 0}, + {0x6B, (uint8_t[]){0x00}, 1, 0}, + {0x70, (uint8_t[]){0x42}, 1, 0}, + {0x71, (uint8_t[]){0xE0}, 1, 0}, + {0x72, (uint8_t[]){0x40}, 1, 0}, + {0x73, (uint8_t[]){0x40}, 1, 0}, + {0x74, (uint8_t[]){0x02}, 1, 0}, + {0x75, (uint8_t[]){0x00}, 1, 0}, + {0x76, (uint8_t[]){0x40}, 1, 0}, + {0x77, (uint8_t[]){0x03}, 1, 0}, + {0x78, (uint8_t[]){0x00}, 1, 0}, + {0x79, (uint8_t[]){0x00}, 1, 0}, + {0x7A, (uint8_t[]){0x00}, 1, 0}, + {0x7B, (uint8_t[]){0x00}, 1, 0}, + {0x80, (uint8_t[]){0x48}, 1, 0}, + {0x81, (uint8_t[]){0x00}, 1, 0}, + {0x82, (uint8_t[]){0x05}, 1, 0}, + {0x83, (uint8_t[]){0x02}, 1, 0}, + {0x84, (uint8_t[]){0xDD}, 1, 0}, + {0x85, (uint8_t[]){0x00}, 1, 0}, + {0x86, (uint8_t[]){0x00}, 1, 0}, + {0x87, (uint8_t[]){0x00}, 1, 0}, + {0x88, (uint8_t[]){0x48}, 1, 0}, + {0x89, (uint8_t[]){0x00}, 1, 0}, + {0x8A, (uint8_t[]){0x07}, 1, 0}, + {0x8B, (uint8_t[]){0x02}, 1, 0}, + {0x8C, (uint8_t[]){0xDF}, 1, 0}, + {0x8D, (uint8_t[]){0x00}, 1, 0}, + {0x8E, (uint8_t[]){0x00}, 1, 0}, + {0x8F, (uint8_t[]){0x00}, 1, 0}, + {0x90, (uint8_t[]){0x48}, 1, 0}, + {0x91, (uint8_t[]){0x00}, 1, 0}, + {0x92, (uint8_t[]){0x09}, 1, 0}, + {0x93, (uint8_t[]){0x02}, 1, 0}, + {0x94, (uint8_t[]){0xE1}, 1, 0}, + {0x95, (uint8_t[]){0x00}, 1, 0}, + {0x96, (uint8_t[]){0x00}, 1, 0}, + {0x97, (uint8_t[]){0x00}, 1, 0}, + {0x98, (uint8_t[]){0x48}, 1, 0}, + {0x99, (uint8_t[]){0x00}, 1, 0}, + {0x9A, (uint8_t[]){0x0B}, 1, 0}, + {0x9B, (uint8_t[]){0x02}, 1, 0}, + {0x9C, (uint8_t[]){0xE3}, 1, 0}, + {0x9D, (uint8_t[]){0x00}, 1, 0}, + {0x9E, (uint8_t[]){0x00}, 1, 0}, + {0x9F, (uint8_t[]){0x00}, 1, 0}, + {0xA0, (uint8_t[]){0x48}, 1, 0}, + {0xA1, (uint8_t[]){0x00}, 1, 0}, + {0xA2, (uint8_t[]){0x04}, 1, 0}, + {0xA3, (uint8_t[]){0x02}, 1, 0}, + {0xA4, (uint8_t[]){0xDC}, 1, 0}, + {0xA5, (uint8_t[]){0x00}, 1, 0}, + {0xA6, (uint8_t[]){0x00}, 1, 0}, + {0xA7, (uint8_t[]){0x00}, 1, 0}, + {0xA8, (uint8_t[]){0x48}, 1, 0}, + {0xA9, (uint8_t[]){0x00}, 1, 0}, + {0xAA, (uint8_t[]){0x06}, 1, 0}, + {0xAB, (uint8_t[]){0x02}, 1, 0}, + {0xAC, (uint8_t[]){0xDE}, 1, 0}, + {0xAD, (uint8_t[]){0x00}, 1, 0}, + {0xAE, (uint8_t[]){0x00}, 1, 0}, + {0xAF, (uint8_t[]){0x00}, 1, 0}, + {0xB0, (uint8_t[]){0x48}, 1, 0}, + {0xB1, (uint8_t[]){0x00}, 1, 0}, + {0xB2, (uint8_t[]){0x08}, 1, 0}, + {0xB3, (uint8_t[]){0x02}, 1, 0}, + {0xB4, (uint8_t[]){0xE0}, 1, 0}, + {0xB5, (uint8_t[]){0x00}, 1, 0}, + {0xB6, (uint8_t[]){0x00}, 1, 0}, + {0xB7, (uint8_t[]){0x00}, 1, 0}, + {0xB8, (uint8_t[]){0x48}, 1, 0}, + {0xB9, (uint8_t[]){0x00}, 1, 0}, + {0xBA, (uint8_t[]){0x0A}, 1, 0}, + {0xBB, (uint8_t[]){0x02}, 1, 0}, + {0xBC, (uint8_t[]){0xE2}, 1, 0}, + {0xBD, (uint8_t[]){0x00}, 1, 0}, + {0xBE, (uint8_t[]){0x00}, 1, 0}, + {0xBF, (uint8_t[]){0x00}, 1, 0}, + {0xC0, (uint8_t[]){0x12}, 1, 0}, + {0xC1, (uint8_t[]){0xAA}, 1, 0}, + {0xC2, (uint8_t[]){0x65}, 1, 0}, + {0xC3, (uint8_t[]){0x74}, 1, 0}, + {0xC4, (uint8_t[]){0x47}, 1, 0}, + {0xC5, (uint8_t[]){0x56}, 1, 0}, + {0xC6, (uint8_t[]){0x00}, 1, 0}, + {0xC7, (uint8_t[]){0x88}, 1, 0}, + {0xC8, (uint8_t[]){0x99}, 1, 0}, + {0xC9, (uint8_t[]){0x33}, 1, 0}, + {0xD0, (uint8_t[]){0x21}, 1, 0}, + {0xD1, (uint8_t[]){0xAA}, 1, 0}, + {0xD2, (uint8_t[]){0x65}, 1, 0}, + {0xD3, (uint8_t[]){0x74}, 1, 0}, + {0xD4, (uint8_t[]){0x47}, 1, 0}, + {0xD5, (uint8_t[]){0x56}, 1, 0}, + {0xD6, (uint8_t[]){0x00}, 1, 0}, + {0xD7, (uint8_t[]){0x88}, 1, 0}, + {0xD8, (uint8_t[]){0x99}, 1, 0}, + {0xD9, (uint8_t[]){0x33}, 1, 0}, + {0xF3, (uint8_t[]){0x01}, 1, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0xF0, (uint8_t[]){0x01}, 1, 0}, + {0xF1, (uint8_t[]){0x01}, 1, 0}, + {0xA0, (uint8_t[]){0x0B}, 1, 0}, + {0xA3, (uint8_t[]){0x2A}, 1, 0}, + {0xA5, (uint8_t[]){0xC3}, 1, 1}, + {0xA3, (uint8_t[]){0x2B}, 1, 0}, + {0xA5, (uint8_t[]){0xC3}, 1, 1}, + {0xA3, (uint8_t[]){0x2C}, 1, 0}, + {0xA5, (uint8_t[]){0xC3}, 1, 1}, + {0xA3, (uint8_t[]){0x2D}, 1, 0}, + {0xA5, (uint8_t[]){0xC3}, 1, 1}, + {0xA3, (uint8_t[]){0x2E}, 1, 0}, + {0xA5, (uint8_t[]){0xC3}, 1, 1}, + {0xA3, (uint8_t[]){0x2F}, 1, 0}, + {0xA5, (uint8_t[]){0xC3}, 1, 1}, + {0xA3, (uint8_t[]){0x30}, 1, 0}, + {0xA5, (uint8_t[]){0xC3}, 1, 1}, + {0xA3, (uint8_t[]){0x31}, 1, 0}, + {0xA5, (uint8_t[]){0xC3}, 1, 1}, + {0xA3, (uint8_t[]){0x32}, 1, 0}, + {0xA5, (uint8_t[]){0xC3}, 1, 1}, + {0xA3, (uint8_t[]){0x33}, 1, 0}, + {0xA5, (uint8_t[]){0xC3}, 1, 1}, + {0xA0, (uint8_t[]){0x09}, 1, 0}, + {0xF1, (uint8_t[]){0x10}, 1, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0}, + {0x2B, (uint8_t[]){0x01, 0x68, 0x01, 0x68}, 4, 0}, + {0x4D, (uint8_t[]){0x00}, 1, 0}, + {0x4E, (uint8_t[]){0x00}, 1, 0}, + {0x4F, (uint8_t[]){0x00}, 1, 0}, + {0x4C, (uint8_t[]){0x01}, 1, 10}, + {0x4C, (uint8_t[]){0x00}, 1, 0}, + {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0}, + {0x21, (uint8_t[]){0x00}, 1, 0}, + //{0x3A, (uint8_t[]){0x55}, 1, 0}, // color=16 + {0x11, (uint8_t[]){0x00}, 1, 120}, + {0x29, (uint8_t[]){0x00}, 1, 0}, +#else + {0xF0, (uint8_t[]){0x28}, 1, 0}, + {0xF2, (uint8_t[]){0x28}, 1, 0}, + {0x73, (uint8_t[]){0xF0}, 1, 0}, + {0x7C, (uint8_t[]){0xD1}, 1, 0}, + {0x83, (uint8_t[]){0xE0}, 1, 0}, + {0x84, (uint8_t[]){0x61}, 1, 0}, + {0xF2, (uint8_t[]){0x82}, 1, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0xF0, (uint8_t[]){0x01}, 1, 0}, + {0xF1, (uint8_t[]){0x01}, 1, 0}, + {0xB0, (uint8_t[]){0x56}, 1, 0}, + {0xB1, (uint8_t[]){0x4D}, 1, 0}, + {0xB2, (uint8_t[]){0x24}, 1, 0}, + {0xB4, (uint8_t[]){0x87}, 1, 0}, + {0xB5, (uint8_t[]){0x44}, 1, 0}, + {0xB6, (uint8_t[]){0x8B}, 1, 0}, + {0xB7, (uint8_t[]){0x40}, 1, 0}, + {0xB8, (uint8_t[]){0x86}, 1, 0}, + {0xBA, (uint8_t[]){0x00}, 1, 0}, + {0xBB, (uint8_t[]){0x08}, 1, 0}, + {0xBC, (uint8_t[]){0x08}, 1, 0}, + {0xBD, (uint8_t[]){0x00}, 1, 0}, + {0xC0, (uint8_t[]){0x80}, 1, 0}, + {0xC1, (uint8_t[]){0x10}, 1, 0}, + {0xC2, (uint8_t[]){0x37}, 1, 0}, + {0xC3, (uint8_t[]){0x80}, 1, 0}, + {0xC4, (uint8_t[]){0x10}, 1, 0}, + {0xC5, (uint8_t[]){0x37}, 1, 0}, + {0xC6, (uint8_t[]){0xA9}, 1, 0}, + {0xC7, (uint8_t[]){0x41}, 1, 0}, + {0xC8, (uint8_t[]){0x01}, 1, 0}, + {0xC9, (uint8_t[]){0xA9}, 1, 0}, + {0xCA, (uint8_t[]){0x41}, 1, 0}, + {0xCB, (uint8_t[]){0x01}, 1, 0}, + {0xD0, (uint8_t[]){0x91}, 1, 0}, + {0xD1, (uint8_t[]){0x68}, 1, 0}, + {0xD2, (uint8_t[]){0x68}, 1, 0}, + {0xF5, (uint8_t[]){0x00, 0xA5}, 2, 0}, + {0xDD, (uint8_t[]){0x4F}, 1, 0}, + {0xDE, (uint8_t[]){0x4F}, 1, 0}, + {0xF1, (uint8_t[]){0x10}, 1, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0xF0, (uint8_t[]){0x02}, 1, 0}, + {0xE0, (uint8_t[]){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0}, + {0xE1, (uint8_t[]){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0}, + {0xF0, (uint8_t[]){0x10}, 1, 0}, + {0xF3, (uint8_t[]){0x10}, 1, 0}, + {0xE0, (uint8_t[]){0x07}, 1, 0}, + {0xE1, (uint8_t[]){0x00}, 1, 0}, + {0xE2, (uint8_t[]){0x00}, 1, 0}, + {0xE3, (uint8_t[]){0x00}, 1, 0}, + {0xE4, (uint8_t[]){0xE0}, 1, 0}, + {0xE5, (uint8_t[]){0x06}, 1, 0}, + {0xE6, (uint8_t[]){0x21}, 1, 0}, + {0xE7, (uint8_t[]){0x01}, 1, 0}, + {0xE8, (uint8_t[]){0x05}, 1, 0}, + {0xE9, (uint8_t[]){0x02}, 1, 0}, + {0xEA, (uint8_t[]){0xDA}, 1, 0}, + {0xEB, (uint8_t[]){0x00}, 1, 0}, + {0xEC, (uint8_t[]){0x00}, 1, 0}, + {0xED, (uint8_t[]){0x0F}, 1, 0}, + {0xEE, (uint8_t[]){0x00}, 1, 0}, + {0xEF, (uint8_t[]){0x00}, 1, 0}, + {0xF8, (uint8_t[]){0x00}, 1, 0}, + {0xF9, (uint8_t[]){0x00}, 1, 0}, + {0xFA, (uint8_t[]){0x00}, 1, 0}, + {0xFB, (uint8_t[]){0x00}, 1, 0}, + {0xFC, (uint8_t[]){0x00}, 1, 0}, + {0xFD, (uint8_t[]){0x00}, 1, 0}, + {0xFE, (uint8_t[]){0x00}, 1, 0}, + {0xFF, (uint8_t[]){0x00}, 1, 0}, + {0x60, (uint8_t[]){0x40}, 1, 0}, + {0x61, (uint8_t[]){0x04}, 1, 0}, + {0x62, (uint8_t[]){0x00}, 1, 0}, + {0x63, (uint8_t[]){0x42}, 1, 0}, + {0x64, (uint8_t[]){0xD9}, 1, 0}, + {0x65, (uint8_t[]){0x00}, 1, 0}, + {0x66, (uint8_t[]){0x00}, 1, 0}, + {0x67, (uint8_t[]){0x00}, 1, 0}, + {0x68, (uint8_t[]){0x00}, 1, 0}, + {0x69, (uint8_t[]){0x00}, 1, 0}, + {0x6A, (uint8_t[]){0x00}, 1, 0}, + {0x6B, (uint8_t[]){0x00}, 1, 0}, + {0x70, (uint8_t[]){0x40}, 1, 0}, + {0x71, (uint8_t[]){0x03}, 1, 0}, + {0x72, (uint8_t[]){0x00}, 1, 0}, + {0x73, (uint8_t[]){0x42}, 1, 0}, + {0x74, (uint8_t[]){0xD8}, 1, 0}, + {0x75, (uint8_t[]){0x00}, 1, 0}, + {0x76, (uint8_t[]){0x00}, 1, 0}, + {0x77, (uint8_t[]){0x00}, 1, 0}, + {0x78, (uint8_t[]){0x00}, 1, 0}, + {0x79, (uint8_t[]){0x00}, 1, 0}, + {0x7A, (uint8_t[]){0x00}, 1, 0}, + {0x7B, (uint8_t[]){0x00}, 1, 0}, + {0x80, (uint8_t[]){0x48}, 1, 0}, + {0x81, (uint8_t[]){0x00}, 1, 0}, + {0x82, (uint8_t[]){0x06}, 1, 0}, + {0x83, (uint8_t[]){0x02}, 1, 0}, + {0x84, (uint8_t[]){0xD6}, 1, 0}, + {0x85, (uint8_t[]){0x04}, 1, 0}, + {0x86, (uint8_t[]){0x00}, 1, 0}, + {0x87, (uint8_t[]){0x00}, 1, 0}, + {0x88, (uint8_t[]){0x48}, 1, 0}, + {0x89, (uint8_t[]){0x00}, 1, 0}, + {0x8A, (uint8_t[]){0x08}, 1, 0}, + {0x8B, (uint8_t[]){0x02}, 1, 0}, + {0x8C, (uint8_t[]){0xD8}, 1, 0}, + {0x8D, (uint8_t[]){0x04}, 1, 0}, + {0x8E, (uint8_t[]){0x00}, 1, 0}, + {0x8F, (uint8_t[]){0x00}, 1, 0}, + {0x90, (uint8_t[]){0x48}, 1, 0}, + {0x91, (uint8_t[]){0x00}, 1, 0}, + {0x92, (uint8_t[]){0x0A}, 1, 0}, + {0x93, (uint8_t[]){0x02}, 1, 0}, + {0x94, (uint8_t[]){0xDA}, 1, 0}, + {0x95, (uint8_t[]){0x04}, 1, 0}, + {0x96, (uint8_t[]){0x00}, 1, 0}, + {0x97, (uint8_t[]){0x00}, 1, 0}, + {0x98, (uint8_t[]){0x48}, 1, 0}, + {0x99, (uint8_t[]){0x00}, 1, 0}, + {0x9A, (uint8_t[]){0x0C}, 1, 0}, + {0x9B, (uint8_t[]){0x02}, 1, 0}, + {0x9C, (uint8_t[]){0xDC}, 1, 0}, + {0x9D, (uint8_t[]){0x04}, 1, 0}, + {0x9E, (uint8_t[]){0x00}, 1, 0}, + {0x9F, (uint8_t[]){0x00}, 1, 0}, + {0xA0, (uint8_t[]){0x48}, 1, 0}, + {0xA1, (uint8_t[]){0x00}, 1, 0}, + {0xA2, (uint8_t[]){0x05}, 1, 0}, + {0xA3, (uint8_t[]){0x02}, 1, 0}, + {0xA4, (uint8_t[]){0xD5}, 1, 0}, + {0xA5, (uint8_t[]){0x04}, 1, 0}, + {0xA6, (uint8_t[]){0x00}, 1, 0}, + {0xA7, (uint8_t[]){0x00}, 1, 0}, + {0xA8, (uint8_t[]){0x48}, 1, 0}, + {0xA9, (uint8_t[]){0x00}, 1, 0}, + {0xAA, (uint8_t[]){0x07}, 1, 0}, + {0xAB, (uint8_t[]){0x02}, 1, 0}, + {0xAC, (uint8_t[]){0xD7}, 1, 0}, + {0xAD, (uint8_t[]){0x04}, 1, 0}, + {0xAE, (uint8_t[]){0x00}, 1, 0}, + {0xAF, (uint8_t[]){0x00}, 1, 0}, + {0xB0, (uint8_t[]){0x48}, 1, 0}, + {0xB1, (uint8_t[]){0x00}, 1, 0}, + {0xB2, (uint8_t[]){0x09}, 1, 0}, + {0xB3, (uint8_t[]){0x02}, 1, 0}, + {0xB4, (uint8_t[]){0xD9}, 1, 0}, + {0xB5, (uint8_t[]){0x04}, 1, 0}, + {0xB6, (uint8_t[]){0x00}, 1, 0}, + {0xB7, (uint8_t[]){0x00}, 1, 0}, + {0xB8, (uint8_t[]){0x48}, 1, 0}, + {0xB9, (uint8_t[]){0x00}, 1, 0}, + {0xBA, (uint8_t[]){0x0B}, 1, 0}, + {0xBB, (uint8_t[]){0x02}, 1, 0}, + {0xBC, (uint8_t[]){0xDB}, 1, 0}, + {0xBD, (uint8_t[]){0x04}, 1, 0}, + {0xBE, (uint8_t[]){0x00}, 1, 0}, + {0xBF, (uint8_t[]){0x00}, 1, 0}, + {0xC0, (uint8_t[]){0x10}, 1, 0}, + {0xC1, (uint8_t[]){0x47}, 1, 0}, + {0xC2, (uint8_t[]){0x56}, 1, 0}, + {0xC3, (uint8_t[]){0x65}, 1, 0}, + {0xC4, (uint8_t[]){0x74}, 1, 0}, + {0xC5, (uint8_t[]){0x88}, 1, 0}, + {0xC6, (uint8_t[]){0x99}, 1, 0}, + {0xC7, (uint8_t[]){0x01}, 1, 0}, + {0xC8, (uint8_t[]){0xBB}, 1, 0}, + {0xC9, (uint8_t[]){0xAA}, 1, 0}, + {0xD0, (uint8_t[]){0x10}, 1, 0}, + {0xD1, (uint8_t[]){0x47}, 1, 0}, + {0xD2, (uint8_t[]){0x56}, 1, 0}, + {0xD3, (uint8_t[]){0x65}, 1, 0}, + {0xD4, (uint8_t[]){0x74}, 1, 0}, + {0xD5, (uint8_t[]){0x88}, 1, 0}, + {0xD6, (uint8_t[]){0x99}, 1, 0}, + {0xD7, (uint8_t[]){0x01}, 1, 0}, + {0xD8, (uint8_t[]){0xBB}, 1, 0}, + {0xD9, (uint8_t[]){0xAA}, 1, 0}, + {0xF3, (uint8_t[]){0x01}, 1, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0x21, (uint8_t[]){0x00}, 1, 0}, + {0x11, (uint8_t[]){0x00}, 1, 120}, + {0x29, (uint8_t[]){0x00}, 1, 0}, +#endif +}; + +class Cst816s : public I2cDevice { +public: + struct TouchPoint_t { + int num = 0; + int x = -1; + int y = -1; + }; + Cst816s(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + uint8_t chip_id = ReadReg(0xA3); + ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); + read_buffer_ = new uint8_t[6]; + } + + ~Cst816s() { + delete[] read_buffer_; + } + + void UpdateTouchPoint() { + ReadRegs(0x02, read_buffer_, 6); + tp_.num = read_buffer_[0] & 0x0F; + tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; + tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; + } + + const TouchPoint_t& GetTouchPoint() { + return tp_; + } + +private: + uint8_t* read_buffer_ = nullptr; + TouchPoint_t tp_; +}; + +class TaijiPiS3Board : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Cst816s* cst816s_; + LcdDisplay* display_; + esp_timer_handle_t touchpad_timer_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = TP_PIN_NUM_TP_SDA, + .scl_io_num = TP_PIN_NUM_TP_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + static void touchpad_timer_callback(void* arg) { + auto& board = (TaijiPiS3Board&)Board::GetInstance(); + auto touchpad = board.GetTouchpad(); + static bool was_touched = false; + static int64_t touch_start_time = 0; + const int64_t TOUCH_THRESHOLD_MS = 500; // 触摸时长阈值,超过500ms视为长按 + + touchpad->UpdateTouchPoint(); + auto touch_point = touchpad->GetTouchPoint(); + + // 检测触摸开始 + if (touch_point.num > 0 && !was_touched) { + was_touched = true; + touch_start_time = esp_timer_get_time() / 1000; // 转换为毫秒 + } + // 检测触摸释放 + else if (touch_point.num == 0 && was_touched) { + was_touched = false; + int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time; + + // 只有短触才触发 + if (touch_duration < TOUCH_THRESHOLD_MS) { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && + !WifiStation::GetInstance().IsConnected()) { + board.ResetWifiConfiguration(); + } + app.ToggleChatState(); + } + } + } + + void InitializeCst816sTouchPad() { + ESP_LOGI(TAG, "Init Cst816s"); + cst816s_ = new Cst816s(i2c_bus_, 0x15); + + // 创建定时器,10ms 间隔 + esp_timer_create_args_t timer_args = { + .callback = touchpad_timer_callback, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "touchpad_timer", + .skip_unhandled_events = true, + }; + + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 10 * 1000)); // 10ms = 10000us + } + + void BspLcdBlSet(int brightness_percent) + { + if (brightness_percent > 100) { + brightness_percent = 100; + } + if (brightness_percent < 0) { + brightness_percent = 0; + } + + ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness_percent); + uint32_t duty_cycle = (1023 * brightness_percent) / 100; // LEDC resolution set to 10bits, thus: 100% = 1023 + ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty_cycle); + ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0); + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize QSPI bus"); + + const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK, + QSPI_PIN_NUM_LCD_DATA0, + QSPI_PIN_NUM_LCD_DATA1, + QSPI_PIN_NUM_LCD_DATA2, + QSPI_PIN_NUM_LCD_DATA3, + QSPI_LCD_H_RES * 80 * sizeof(uint16_t)); + ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO)); + } + + void Initializest77916Display() { + + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + ESP_LOGI(TAG, "Install panel IO"); + + const esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(QSPI_PIN_NUM_LCD_CS, NULL, NULL); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io)); + + ESP_LOGI(TAG, "Install ST77916 panel driver"); + + st77916_vendor_config_t vendor_config = { + .init_cmds = lcd_init_cmds, // 如果使用自定义初始化命令,请取消注释这些行 + .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st77916_lcd_init_cmd_t), + .flags = { + .use_qspi_interface = 1, + }, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = QSPI_PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h` + .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18) + .vendor_config = &vendor_config, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_disp_on_off(panel, true); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeMute() { + gpio_reset_pin(AUDIO_MUTE_PIN); + /* Set the GPIO as a push/pull output */ + gpio_set_direction(AUDIO_MUTE_PIN, GPIO_MODE_OUTPUT); + gpio_set_level(AUDIO_MUTE_PIN, 1); + } + +public: + TaijiPiS3Board() { + InitializeI2c(); + InitializeCst816sTouchPad(); + InitializeSpi(); + Initializest77916Display(); + InitializeMute(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { +#ifdef CONFIG_TAIJIPAI_I2S_TYPE_STD + static NoAudioCodecSimplex audio_codec( + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + #ifdef CONFIG_I2S_USE_2SLOT + I2S_STD_SLOT_BOTH, + #endif + AUDIO_MIC_SCK_PIN, + AUDIO_MIC_WS_PIN, + #ifdef CONFIG_I2S_USE_2SLOT + AUDIO_MIC_SD_PIN, + I2S_STD_SLOT_LEFT + #else + AUDIO_MIC_SD_PIN + #endif + ); +#else + static NoAudioCodecSimplexPdm audio_codec( + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + #ifdef CONFIG_I2S_USE_2SLOT + I2S_STD_SLOT_BOTH, + #endif + AUDIO_MIC_WS_PIN, + AUDIO_MIC_SD_PIN + ); +#endif + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + Cst816s* GetTouchpad() { + return cst816s_; + } +}; + +DECLARE_BOARD(TaijiPiS3Board); diff --git a/main/boards/tudouzi/config.h b/main/boards/tudouzi/config.h index a272900..fc21a29 100644 --- a/main/boards/tudouzi/config.h +++ b/main/boards/tudouzi/config.h @@ -1,41 +1,41 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_40 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_38 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_9 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_42 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_41 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_3 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_1 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_2 - -#define DISPLAY_SDA_PIN GPIO_NUM_7 -#define DISPLAY_SCL_PIN GPIO_NUM_8 -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 64 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false - -#define ML307_RX_PIN GPIO_NUM_5 -#define ML307_TX_PIN GPIO_NUM_6 - -#define AXP2101_I2C_ADDR 0x34 - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_40 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_38 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_9 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_42 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_41 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_3 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_1 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_2 + +#define DISPLAY_SDA_PIN GPIO_NUM_7 +#define DISPLAY_SCL_PIN GPIO_NUM_8 +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 64 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false + +#define ML307_RX_PIN GPIO_NUM_5 +#define ML307_TX_PIN GPIO_NUM_6 + +#define AXP2101_I2C_ADDR 0x34 + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/tudouzi/config.json b/main/boards/tudouzi/config.json index d9eee68..f12bbf2 100644 --- a/main/boards/tudouzi/config.json +++ b/main/boards/tudouzi/config.json @@ -1,13 +1,13 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "tudouzi", - "sdkconfig_append": [ - "CONFIG_USE_WAKE_WORD_DETECT=n", - "CONFIG_PM_ENABLE=y", - "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "tudouzi", + "sdkconfig_append": [ + "CONFIG_USE_WAKE_WORD_DETECT=n", + "CONFIG_PM_ENABLE=y", + "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/tudouzi/kevin_box_board.cc b/main/boards/tudouzi/kevin_box_board.cc index d6cb5b2..32129b9 100644 --- a/main/boards/tudouzi/kevin_box_board.cc +++ b/main/boards/tudouzi/kevin_box_board.cc @@ -1,255 +1,255 @@ -#include "ml307_board.h" -#include "codecs/box_audio_codec.h" -#include "display/oled_display.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "config.h" -#include "power_save_timer.h" -#include "axp2101.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include - -#define TAG "KevinBoxBoard" - -class Pmic : public Axp2101 { -public: - Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { - // ** EFUSE defaults ** - WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable - WriteReg(0x27, 0x10); // hold 4s to power off - - WriteReg(0x93, 0x1C); // 配置 aldo2 输出为 3.3V - - uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0 - value = value | 0x02; // set bit 1 (ALDO2) - WriteReg(0x90, value); // and power channels now enabled - - WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V - - WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA - WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) - WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA - - WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables - WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables - WriteReg(0x16, 0x05); // set input current limit to 2000mA - - WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery) - WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature) - } -}; - -class KevinBoxBoard : public Ml307Board { -private: - i2c_master_bus_handle_t display_i2c_bus_; - i2c_master_bus_handle_t codec_i2c_bus_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - Display* display_ = nullptr; - Pmic* pmic_ = nullptr; - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - PowerSaveTimer* power_save_timer_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(240, 60, -1); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - }); - power_save_timer_->SetEnabled(true); - } - - void Enable4GModule() { - // Make GPIO HIGH to enable the 4G module - gpio_config_t ml307_enable_config = { - .pin_bit_mask = (1ULL << 4), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - gpio_config(&ml307_enable_config); - gpio_set_level(GPIO_NUM_4, 1); - } - - void InitializeDisplayI2c() { - i2c_master_bus_config_t bus_config = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = DISPLAY_SDA_PIN, - .scl_io_num = DISPLAY_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeButtons() { - boot_button_.OnPressDown([this]() { - power_save_timer_->WakeUp(); - Application::GetInstance().StartListening(); - }); - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - - volume_up_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - -public: - KevinBoxBoard() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN), - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializeDisplayI2c(); - InitializeSsd1306Display(); - InitializeCodecI2c(); - pmic_ = new Pmic(codec_i2c_bus_, AXP2101_I2C_ADDR); - - Enable4GModule(); - - InitializeButtons(); - InitializePowerSaveTimer(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec(codec_i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = pmic_->IsCharging(); - discharging = pmic_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - - level = pmic_->GetBatteryLevel(); - return true; - } -}; - +#include "ml307_board.h" +#include "codecs/box_audio_codec.h" +#include "display/oled_display.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "config.h" +#include "power_save_timer.h" +#include "axp2101.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include + +#define TAG "KevinBoxBoard" + +class Pmic : public Axp2101 { +public: + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + // ** EFUSE defaults ** + WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable + WriteReg(0x27, 0x10); // hold 4s to power off + + WriteReg(0x93, 0x1C); // 配置 aldo2 输出为 3.3V + + uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0 + value = value | 0x02; // set bit 1 (ALDO2) + WriteReg(0x90, value); // and power channels now enabled + + WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V + + WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA + WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) + WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA + + WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables + WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables + WriteReg(0x16, 0x05); // set input current limit to 2000mA + + WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery) + WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature) + } +}; + +class KevinBoxBoard : public Ml307Board { +private: + i2c_master_bus_handle_t display_i2c_bus_; + i2c_master_bus_handle_t codec_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + Display* display_ = nullptr; + Pmic* pmic_ = nullptr; + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(240, 60, -1); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + }); + power_save_timer_->SetEnabled(true); + } + + void Enable4GModule() { + // Make GPIO HIGH to enable the 4G module + gpio_config_t ml307_enable_config = { + .pin_bit_mask = (1ULL << 4), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config(&ml307_enable_config); + gpio_set_level(GPIO_NUM_4, 1); + } + + void InitializeDisplayI2c() { + i2c_master_bus_config_t bus_config = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = DISPLAY_SDA_PIN, + .scl_io_num = DISPLAY_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); + } + + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeButtons() { + boot_button_.OnPressDown([this]() { + power_save_timer_->WakeUp(); + Application::GetInstance().StartListening(); + }); + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + + volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + +public: + KevinBoxBoard() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN), + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializeDisplayI2c(); + InitializeSsd1306Display(); + InitializeCodecI2c(); + pmic_ = new Pmic(codec_i2c_bus_, AXP2101_I2C_ADDR); + + Enable4GModule(); + + InitializeButtons(); + InitializePowerSaveTimer(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec(codec_i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = pmic_->IsCharging(); + discharging = pmic_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + + level = pmic_->GetBatteryLevel(); + return true; + } +}; + DECLARE_BOARD(KevinBoxBoard); \ No newline at end of file diff --git a/main/boards/waveshare-c6-lcd-1.69/README.md b/main/boards/waveshare-c6-lcd-1.69/README.md index 5a44c06..c1c8a95 100644 --- a/main/boards/waveshare-c6-lcd-1.69/README.md +++ b/main/boards/waveshare-c6-lcd-1.69/README.md @@ -1,56 +1,56 @@ -# 产品链接 - -[微雪电子 ESP32-C6-Touch-LCD-1.69](https://www.waveshare.net/shop/ESP32-C6-Touch-LCD-1.69.htm) -[微雪电子 ESP32-C6-LCD-1.69](https://www.waveshare.net/shop/ESP32-C6-LCD-1.69.htm) - -# 编译配置命令 - -**克隆工程** - -```bash -git clone https://github.com/78/xiaozhi-esp32.git -``` - -**进入工程** - -```bash -cd xiaozhi-esp32 -``` - -**配置编译目标为 ESP32C6** - -```bash -idf.py set-target esp32c6 -``` - -**打开 menuconfig** - -```bash -idf.py menuconfig -``` - -**选择板子** - -```bash -Xiaozhi Assistant -> Board Type -> Waveshare ESP32-C6-LCD-1.69 -``` - -**编译** - -```ba -idf.py build -``` - -**下载并打开串口终端** - -```bash -idf.py build flash monitor -``` -# 按键操作 -## BOOT 按键 -**未连接服务器前单击: 进入配网模式** -**连接服务器后单击: 唤醒、打断** - -## PWR 按键 -**双击:息屏、亮屏** +# 产品链接 + +[微雪电子 ESP32-C6-Touch-LCD-1.69](https://www.waveshare.net/shop/ESP32-C6-Touch-LCD-1.69.htm) +[微雪电子 ESP32-C6-LCD-1.69](https://www.waveshare.net/shop/ESP32-C6-LCD-1.69.htm) + +# 编译配置命令 + +**克隆工程** + +```bash +git clone https://github.com/78/xiaozhi-esp32.git +``` + +**进入工程** + +```bash +cd xiaozhi-esp32 +``` + +**配置编译目标为 ESP32C6** + +```bash +idf.py set-target esp32c6 +``` + +**打开 menuconfig** + +```bash +idf.py menuconfig +``` + +**选择板子** + +```bash +Xiaozhi Assistant -> Board Type -> Waveshare ESP32-C6-LCD-1.69 +``` + +**编译** + +```ba +idf.py build +``` + +**下载并打开串口终端** + +```bash +idf.py build flash monitor +``` +# 按键操作 +## BOOT 按键 +**未连接服务器前单击: 进入配网模式** +**连接服务器后单击: 唤醒、打断** + +## PWR 按键 +**双击:息屏、亮屏** **长按:开关机** \ No newline at end of file diff --git a/main/boards/waveshare-c6-lcd-1.69/config.h b/main/boards/waveshare-c6-lcd-1.69/config.h index 0904631..5a15fef 100644 --- a/main/boards/waveshare-c6-lcd-1.69/config.h +++ b/main/boards/waveshare-c6-lcd-1.69/config.h @@ -1,54 +1,54 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_19 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_22 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_20 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_21 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_9 -#define PWR_BUTTON_GPIO GPIO_NUM_18 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SPI_MODE 3 -#define DISPLAY_CS_PIN GPIO_NUM_5 -#define DISPLAY_MOSI_PIN GPIO_NUM_2 -#define DISPLAY_MISO_PIN GPIO_NUM_NC -#define DISPLAY_CLK_PIN GPIO_NUM_1 -#define DISPLAY_DC_PIN GPIO_NUM_3 -#define DISPLAY_RST_PIN GPIO_NUM_4 - -#define BATTERY_EN_PIN GPIO_NUM_15 -#define BATTERY_ADC_PIN GPIO_NUM_0 -#define BATTERY_CHARGING_PIN GPIO_NUM_NC - - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 280 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_INVERT_COLOR true - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 20 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_6 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_19 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_22 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_20 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_21 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_9 +#define PWR_BUTTON_GPIO GPIO_NUM_18 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SPI_MODE 3 +#define DISPLAY_CS_PIN GPIO_NUM_5 +#define DISPLAY_MOSI_PIN GPIO_NUM_2 +#define DISPLAY_MISO_PIN GPIO_NUM_NC +#define DISPLAY_CLK_PIN GPIO_NUM_1 +#define DISPLAY_DC_PIN GPIO_NUM_3 +#define DISPLAY_RST_PIN GPIO_NUM_4 + +#define BATTERY_EN_PIN GPIO_NUM_15 +#define BATTERY_ADC_PIN GPIO_NUM_0 +#define BATTERY_CHARGING_PIN GPIO_NUM_NC + + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 280 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_INVERT_COLOR true + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 20 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_6 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/waveshare-c6-lcd-1.69/config.json b/main/boards/waveshare-c6-lcd-1.69/config.json index 81dc2b1..91bf908 100644 --- a/main/boards/waveshare-c6-lcd-1.69/config.json +++ b/main/boards/waveshare-c6-lcd-1.69/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32c6", - "builds": [ - { - "name": "waveshare-c6-lcd-1.69", - "sdkconfig_append": [ - "CONFIG_USE_ESP_WAKE_WORD=y" - ] - } - ] +{ + "target": "esp32c6", + "builds": [ + { + "name": "waveshare-c6-lcd-1.69", + "sdkconfig_append": [ + "CONFIG_USE_ESP_WAKE_WORD=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc b/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc index bebe974..25ebbf9 100644 --- a/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc +++ b/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc @@ -1,254 +1,254 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include -#include "i2c_device.h" -#include -#include -#include -#include -#include -#include -#include -#include "iot_button.h" -#include "power_manager.h" -#include "power_save_timer.h" - -#define TAG "waveshare_lcd_1_69" - -class CustomLcdDisplay : public SpiLcdDisplay { -public: - CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, - esp_lcd_panel_handle_t panel_handle, - int width, - int height, - int offset_x, - int offset_y, - bool mirror_x, - bool mirror_y, - bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, - width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - DisplayLockGuard lock(this); - lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.1, 0); - lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.1, 0); - } -}; - -class CustomButton: public Button { -public: - void OnPressDownDel(void) { - if (button_handle_ == nullptr) { - return; - } - on_press_down_ = NULL; - iot_button_unregister_cb(button_handle_, BUTTON_PRESS_DOWN, nullptr); - } - void OnPressUpDel(void) { - if (button_handle_ == nullptr) { - return; - } - on_press_up_ = NULL; - iot_button_unregister_cb(button_handle_, BUTTON_PRESS_UP, nullptr); - } -}; - -class CustomBoard : public WifiBoard { -private: - CustomButton boot_button_; - CustomButton pwr_button_; - i2c_master_bus_handle_t i2c_bus_; - LcdDisplay* display_; - PowerManager* power_manager_ = nullptr; - PowerSaveTimer* power_save_timer_ = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(BATTERY_CHARGING_PIN, BATTERY_ADC_PIN, BATTERY_EN_PIN); - power_manager_->PowerON(); - } - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - }); - power_save_timer_->OnShutdownRequest([this]() { - power_manager_->PowerOff(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize QSPI bus"); - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = DISPLAY_MISO_PIN; - buscfg.sclk_io_num = DISPLAY_CLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeLcdDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - // 液晶屏控制IO初始化 - ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = DISPLAY_SPI_MODE; - io_config.pclk_hz = 40 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGI(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RST_PIN; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new CustomLcdDisplay( - panel_io, - panel, - DISPLAY_WIDTH, - DISPLAY_HEIGHT, - DISPLAY_OFFSET_X, - DISPLAY_OFFSET_Y, - DISPLAY_MIRROR_X, - DISPLAY_MIRROR_Y, - DISPLAY_SWAP_XY - ); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - - pwr_button_.OnPressUp([this]() { - pwr_button_.OnDoubleClick([this]() { - static uint8_t brightness_last = 0; - auto backlight = Board::GetInstance().GetBacklight(); - if (backlight->brightness() == 0) { - brightness_last = 0; - if (brightness_last == 0) { - backlight->SetBrightness(50, true); - } else { - backlight->SetBrightness(brightness_last, true); - } - } else { - brightness_last = backlight->brightness(); - backlight->SetBrightness(0); - } - }); - - pwr_button_.OnLongPress([this]() { - // printf("Power button long press\n"); - if (power_manager_ != nullptr){ - power_manager_->PowerOff(); - } - }); - - pwr_button_.OnPressUpDel(); - }); - } - -public: - CustomBoard() : - boot_button_(BOOT_BUTTON_GPIO), pwr_button_(PWR_BUTTON_GPIO) { - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeI2c(); - InitializeSpi(); - InitializeLcdDisplay(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec( - i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR - ); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(CustomBoard); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include +#include "i2c_device.h" +#include +#include +#include +#include +#include +#include +#include +#include "iot_button.h" +#include "power_manager.h" +#include "power_save_timer.h" + +#define TAG "waveshare_lcd_1_69" + +class CustomLcdDisplay : public SpiLcdDisplay { +public: + CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_handle_t panel_handle, + int width, + int height, + int offset_x, + int offset_y, + bool mirror_x, + bool mirror_y, + bool swap_xy) + : SpiLcdDisplay(io_handle, panel_handle, + width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + DisplayLockGuard lock(this); + lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.1, 0); + lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.1, 0); + } +}; + +class CustomButton: public Button { +public: + void OnPressDownDel(void) { + if (button_handle_ == nullptr) { + return; + } + on_press_down_ = NULL; + iot_button_unregister_cb(button_handle_, BUTTON_PRESS_DOWN, nullptr); + } + void OnPressUpDel(void) { + if (button_handle_ == nullptr) { + return; + } + on_press_up_ = NULL; + iot_button_unregister_cb(button_handle_, BUTTON_PRESS_UP, nullptr); + } +}; + +class CustomBoard : public WifiBoard { +private: + CustomButton boot_button_; + CustomButton pwr_button_; + i2c_master_bus_handle_t i2c_bus_; + LcdDisplay* display_; + PowerManager* power_manager_ = nullptr; + PowerSaveTimer* power_save_timer_ = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(BATTERY_CHARGING_PIN, BATTERY_ADC_PIN, BATTERY_EN_PIN); + power_manager_->PowerON(); + } + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + }); + power_save_timer_->OnShutdownRequest([this]() { + power_manager_->PowerOff(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize QSPI bus"); + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = DISPLAY_MISO_PIN; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = DISPLAY_SPI_MODE; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGI(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new CustomLcdDisplay( + panel_io, + panel, + DISPLAY_WIDTH, + DISPLAY_HEIGHT, + DISPLAY_OFFSET_X, + DISPLAY_OFFSET_Y, + DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, + DISPLAY_SWAP_XY + ); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + + pwr_button_.OnPressUp([this]() { + pwr_button_.OnDoubleClick([this]() { + static uint8_t brightness_last = 0; + auto backlight = Board::GetInstance().GetBacklight(); + if (backlight->brightness() == 0) { + brightness_last = 0; + if (brightness_last == 0) { + backlight->SetBrightness(50, true); + } else { + backlight->SetBrightness(brightness_last, true); + } + } else { + brightness_last = backlight->brightness(); + backlight->SetBrightness(0); + } + }); + + pwr_button_.OnLongPress([this]() { + // printf("Power button long press\n"); + if (power_manager_ != nullptr){ + power_manager_->PowerOff(); + } + }); + + pwr_button_.OnPressUpDel(); + }); + } + +public: + CustomBoard() : + boot_button_(BOOT_BUTTON_GPIO), pwr_button_(PWR_BUTTON_GPIO) { + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeI2c(); + InitializeSpi(); + InitializeLcdDisplay(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec( + i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR + ); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(CustomBoard); diff --git a/main/boards/waveshare-c6-lcd-1.69/power_manager.h b/main/boards/waveshare-c6-lcd-1.69/power_manager.h index adb7bee..f4c58bb 100644 --- a/main/boards/waveshare-c6-lcd-1.69/power_manager.h +++ b/main/boards/waveshare-c6-lcd-1.69/power_manager.h @@ -1,174 +1,174 @@ -#pragma once -#include -#include -#include -#include -#include -#include "esp_adc/adc_oneshot.h" -#include "esp_adc/adc_cali.h" -#include "esp_adc/adc_cali_scheme.h" -#include - - -class PowerManager { -private: - gpio_num_t charging_pin_ = GPIO_NUM_NC; - gpio_num_t bat_adc_pin_ = GPIO_NUM_NC; - gpio_num_t bat_power_pin_ = GPIO_NUM_NC; - adc_oneshot_unit_handle_t adc_handle_ = NULL; - adc_cali_handle_t adc_cali_handle_ = NULL; - adc_channel_t adc_channel_; - bool do_calibration = false; - - bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle) { - adc_cali_handle_t handle = NULL; - esp_err_t ret = ESP_FAIL; - bool calibrated = false; - - if (!calibrated) { - ESP_LOGI("PowerManager", "calibration scheme version is %s", "Curve Fitting"); - adc_cali_curve_fitting_config_t cali_config = { - .unit_id = unit, - .chan = channel, - .atten = atten, - .bitwidth = ADC_BITWIDTH_DEFAULT, - }; - ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); - if (ret == ESP_OK) { - calibrated = true; - } - } - - *out_handle = handle; - if (ret == ESP_OK) { - ESP_LOGI("PowerManager", "Calibration Success"); - } - else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) { - ESP_LOGW("PowerManager", "eFuse not burnt, skip software calibration"); - } - else { - ESP_LOGE("PowerManager", "Invalid arg or no memory"); - } - return calibrated; - } - -public: - PowerManager(gpio_num_t charging_pin, gpio_num_t bat_adc_pin, gpio_num_t bat_power_pin) - : charging_pin_(charging_pin), bat_adc_pin_(bat_adc_pin), bat_power_pin_(bat_power_pin) { - // 初始化充电引脚 - if (charging_pin_ != GPIO_NUM_NC) { - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = 1ULL << charging_pin_; - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_ENABLE; - gpio_config(&io_conf); - } - - // 初始化电池使能引脚 - if (bat_power_pin_ != GPIO_NUM_NC) { - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = 1ULL << bat_power_pin_; - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - } - - // 初始化adc - if (bat_adc_pin_ != GPIO_NUM_NC) { - adc_oneshot_unit_init_cfg_t init_config = {}; - init_config.ulp_mode = ADC_ULP_MODE_DISABLE; - init_config.unit_id = ADC_UNIT_1; - - if (bat_adc_pin_ >= GPIO_NUM_0 && bat_adc_pin_ <= GPIO_NUM_6) - adc_channel_ = (adc_channel_t)((int)bat_adc_pin_); - else - return; - - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - adc_oneshot_chan_cfg_t config = {}; - config.bitwidth = ADC_BITWIDTH_DEFAULT; - config.atten = ADC_ATTEN_DB_12; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &config)); - do_calibration = adc_calibration_init(init_config.unit_id, adc_channel_, config.atten, &adc_cali_handle_); - } - } - - ~PowerManager() { - if (adc_handle_) { - ESP_ERROR_CHECK(adc_oneshot_del_unit(adc_handle_)); - } - } - - int GetBatteryLevel(void) { - int adc_raw = 0; - int voltage_int = 0; - const float voltage_float_threshold = 0.1f; - float voltage_float = 0.0f; - static float last_voltage_float = 0.0f; - static int last_battery_level = 0; - - if (adc_handle_ != nullptr) { - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_raw)); - if (do_calibration) { - ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_raw, &voltage_int)); - voltage_float = (voltage_int / 1000.0f) * 3.0; - - if (fabs(voltage_float - last_voltage_float) >= voltage_float_threshold) { - last_voltage_float = voltage_float; - if (voltage_float < 3.52) { - last_battery_level = 1; - } else if (voltage_float < 3.64) { - last_battery_level = 20; - } else if (voltage_float < 3.76) { - last_battery_level = 40; - } else if (voltage_float < 3.88) { - last_battery_level = 60; - } else if (voltage_float < 4.0) { - last_battery_level = 80; - } else { - last_battery_level = 100; - } - } - return last_battery_level; - } - } - return 100; - } - - bool IsCharging(void) { - if (charging_pin_ != GPIO_NUM_NC) { - return gpio_get_level(charging_pin_) == 0 ? true : false; - } - return false; - } - - bool IsDischarging(void) { - if (charging_pin_ != GPIO_NUM_NC) { - return gpio_get_level(charging_pin_) == 1; - } - return true; - } - - bool IsChargingDone(void) { - if (GetBatteryLevel() == 100) { - return true; - } - return false; - } - - void PowerOff(void) { - if (bat_power_pin_ != GPIO_NUM_NC) { - gpio_set_level(bat_power_pin_, 0); - } - } - - void PowerON(void) { - if (bat_power_pin_ != GPIO_NUM_NC) { - gpio_set_level(bat_power_pin_, 1); - } - } -}; +#pragma once +#include +#include +#include +#include +#include +#include "esp_adc/adc_oneshot.h" +#include "esp_adc/adc_cali.h" +#include "esp_adc/adc_cali_scheme.h" +#include + + +class PowerManager { +private: + gpio_num_t charging_pin_ = GPIO_NUM_NC; + gpio_num_t bat_adc_pin_ = GPIO_NUM_NC; + gpio_num_t bat_power_pin_ = GPIO_NUM_NC; + adc_oneshot_unit_handle_t adc_handle_ = NULL; + adc_cali_handle_t adc_cali_handle_ = NULL; + adc_channel_t adc_channel_; + bool do_calibration = false; + + bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle) { + adc_cali_handle_t handle = NULL; + esp_err_t ret = ESP_FAIL; + bool calibrated = false; + + if (!calibrated) { + ESP_LOGI("PowerManager", "calibration scheme version is %s", "Curve Fitting"); + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = unit, + .chan = channel, + .atten = atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); + if (ret == ESP_OK) { + calibrated = true; + } + } + + *out_handle = handle; + if (ret == ESP_OK) { + ESP_LOGI("PowerManager", "Calibration Success"); + } + else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) { + ESP_LOGW("PowerManager", "eFuse not burnt, skip software calibration"); + } + else { + ESP_LOGE("PowerManager", "Invalid arg or no memory"); + } + return calibrated; + } + +public: + PowerManager(gpio_num_t charging_pin, gpio_num_t bat_adc_pin, gpio_num_t bat_power_pin) + : charging_pin_(charging_pin), bat_adc_pin_(bat_adc_pin), bat_power_pin_(bat_power_pin) { + // 初始化充电引脚 + if (charging_pin_ != GPIO_NUM_NC) { + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = 1ULL << charging_pin_; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + } + + // 初始化电池使能引脚 + if (bat_power_pin_ != GPIO_NUM_NC) { + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1ULL << bat_power_pin_; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + } + + // 初始化adc + if (bat_adc_pin_ != GPIO_NUM_NC) { + adc_oneshot_unit_init_cfg_t init_config = {}; + init_config.ulp_mode = ADC_ULP_MODE_DISABLE; + init_config.unit_id = ADC_UNIT_1; + + if (bat_adc_pin_ >= GPIO_NUM_0 && bat_adc_pin_ <= GPIO_NUM_6) + adc_channel_ = (adc_channel_t)((int)bat_adc_pin_); + else + return; + + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + adc_oneshot_chan_cfg_t config = {}; + config.bitwidth = ADC_BITWIDTH_DEFAULT; + config.atten = ADC_ATTEN_DB_12; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &config)); + do_calibration = adc_calibration_init(init_config.unit_id, adc_channel_, config.atten, &adc_cali_handle_); + } + } + + ~PowerManager() { + if (adc_handle_) { + ESP_ERROR_CHECK(adc_oneshot_del_unit(adc_handle_)); + } + } + + int GetBatteryLevel(void) { + int adc_raw = 0; + int voltage_int = 0; + const float voltage_float_threshold = 0.1f; + float voltage_float = 0.0f; + static float last_voltage_float = 0.0f; + static int last_battery_level = 0; + + if (adc_handle_ != nullptr) { + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_raw)); + if (do_calibration) { + ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_raw, &voltage_int)); + voltage_float = (voltage_int / 1000.0f) * 3.0; + + if (fabs(voltage_float - last_voltage_float) >= voltage_float_threshold) { + last_voltage_float = voltage_float; + if (voltage_float < 3.52) { + last_battery_level = 1; + } else if (voltage_float < 3.64) { + last_battery_level = 20; + } else if (voltage_float < 3.76) { + last_battery_level = 40; + } else if (voltage_float < 3.88) { + last_battery_level = 60; + } else if (voltage_float < 4.0) { + last_battery_level = 80; + } else { + last_battery_level = 100; + } + } + return last_battery_level; + } + } + return 100; + } + + bool IsCharging(void) { + if (charging_pin_ != GPIO_NUM_NC) { + return gpio_get_level(charging_pin_) == 0 ? true : false; + } + return false; + } + + bool IsDischarging(void) { + if (charging_pin_ != GPIO_NUM_NC) { + return gpio_get_level(charging_pin_) == 1; + } + return true; + } + + bool IsChargingDone(void) { + if (GetBatteryLevel() == 100) { + return true; + } + return false; + } + + void PowerOff(void) { + if (bat_power_pin_ != GPIO_NUM_NC) { + gpio_set_level(bat_power_pin_, 0); + } + } + + void PowerON(void) { + if (bat_power_pin_ != GPIO_NUM_NC) { + gpio_set_level(bat_power_pin_, 1); + } + } +}; diff --git a/main/boards/waveshare-c6-touch-amoled-1.43/README.md b/main/boards/waveshare-c6-touch-amoled-1.43/README.md index 7703850..60ebd94 100644 --- a/main/boards/waveshare-c6-touch-amoled-1.43/README.md +++ b/main/boards/waveshare-c6-touch-amoled-1.43/README.md @@ -1,49 +1,49 @@ -# 产品链接 - -[微雪电子 ESP32-C6-Touch-AMOLED-1.43](https://www.waveshare.net/shop/ESP32-C6-Touch-AMOLED-1.43.htm) -[微雪电子 ESP32-C6-Touch-AMOLED-1.43-B](https://www.waveshare.net/shop/ESP32-C6-Touch-AMOLED-1.43-B.htm) - -# 编译配置命令 - -**克隆工程** - -```bash -git clone https://github.com/78/xiaozhi-esp32.git -``` - -**进入工程** - -```bash -cd xiaozhi-esp32 -``` - -**配置编译目标为 ESP32C6** - -```bash -idf.py set-target esp32c6 -``` - -**打开 menuconfig** - -```bash -idf.py menuconfig -``` - -**选择板子** - -```bash -Xiaozhi Assistant -> Board Type -> Waveshare ESP32-C6-Touch-AMOLED-1.43 -``` - -**编译** - -```ba -idf.py build -``` - -**下载并打开串口终端** - -```bash -idf.py build flash monitor -``` - +# 产品链接 + +[微雪电子 ESP32-C6-Touch-AMOLED-1.43](https://www.waveshare.net/shop/ESP32-C6-Touch-AMOLED-1.43.htm) +[微雪电子 ESP32-C6-Touch-AMOLED-1.43-B](https://www.waveshare.net/shop/ESP32-C6-Touch-AMOLED-1.43-B.htm) + +# 编译配置命令 + +**克隆工程** + +```bash +git clone https://github.com/78/xiaozhi-esp32.git +``` + +**进入工程** + +```bash +cd xiaozhi-esp32 +``` + +**配置编译目标为 ESP32C6** + +```bash +idf.py set-target esp32c6 +``` + +**打开 menuconfig** + +```bash +idf.py menuconfig +``` + +**选择板子** + +```bash +Xiaozhi Assistant -> Board Type -> Waveshare ESP32-C6-Touch-AMOLED-1.43 +``` + +**编译** + +```ba +idf.py build +``` + +**下载并打开串口终端** + +```bash +idf.py build flash monitor +``` + diff --git a/main/boards/waveshare-c6-touch-amoled-1.43/config.h b/main/boards/waveshare-c6-touch-amoled-1.43/config.h index 0d39b3f..3da35fe 100644 --- a/main/boards/waveshare-c6-touch-amoled-1.43/config.h +++ b/main/boards/waveshare-c6-touch-amoled-1.43/config.h @@ -1,49 +1,49 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE false - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_19 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_22 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_21 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_20 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_18 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define I2C_Touch_ADDRESS 0x38 -#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 -#define BOOT_BUTTON_GPIO GPIO_NUM_9 -#define PWR_BUTTON_GPIO GPIO_NUM_2 - -#define LCD_CS GPIO_NUM_10 -#define LCD_PCLK GPIO_NUM_11 -#define LCD_D0 GPIO_NUM_4 -#define LCD_D1 GPIO_NUM_5 -#define LCD_D2 GPIO_NUM_6 -#define LCD_D3 GPIO_NUM_7 -#define LCD_RST GPIO_NUM_3 -#define LCD_LIGHT (-1) - -#define EXAMPLE_LCD_H_RES 466 -#define EXAMPLE_LCD_V_RES 466 - -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE false + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_19 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_22 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_21 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_20 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_18 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define I2C_Touch_ADDRESS 0x38 +#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 +#define BOOT_BUTTON_GPIO GPIO_NUM_9 +#define PWR_BUTTON_GPIO GPIO_NUM_2 + +#define LCD_CS GPIO_NUM_10 +#define LCD_PCLK GPIO_NUM_11 +#define LCD_D0 GPIO_NUM_4 +#define LCD_D1 GPIO_NUM_5 +#define LCD_D2 GPIO_NUM_6 +#define LCD_D3 GPIO_NUM_7 +#define LCD_RST GPIO_NUM_3 +#define LCD_LIGHT (-1) + +#define EXAMPLE_LCD_H_RES 466 +#define EXAMPLE_LCD_V_RES 466 + +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/waveshare-c6-touch-amoled-1.43/config.json b/main/boards/waveshare-c6-touch-amoled-1.43/config.json index f1a4551..23d7fe2 100644 --- a/main/boards/waveshare-c6-touch-amoled-1.43/config.json +++ b/main/boards/waveshare-c6-touch-amoled-1.43/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32c6", - "builds": [ - { - "name": "waveshare-c6-touch-amoled-1.43", - "sdkconfig_append": [ - "CONFIG_USE_ESP_WAKE_WORD=y" - ] - } - ] +{ + "target": "esp32c6", + "builds": [ + { + "name": "waveshare-c6-touch-amoled-1.43", + "sdkconfig_append": [ + "CONFIG_USE_ESP_WAKE_WORD=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc b/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc index 42cd4b9..b39ec57 100644 --- a/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc +++ b/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc @@ -1,284 +1,284 @@ -#include "wifi_board.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "codecs/box_audio_codec.h" - -#include -#include -#include -#include -#include -#include "esp_lcd_sh8601.h" -#include "display/lcd_display.h" -#include "esp_io_expander_tca9554.h" -#include "mcp_server.h" -#include "lvgl.h" - -#define TAG "waveshare_c6_amoled_1_43" - -static const sh8601_lcd_init_cmd_t lcd_init_cmds[] = -{ - {0x11, (uint8_t []){0x00}, 0, 80}, - {0xC4, (uint8_t []){0x80}, 1, 0}, - {0x53, (uint8_t []){0x20}, 1, 1}, - {0x63, (uint8_t []){0xFF}, 1, 1}, - {0x51, (uint8_t []){0x00}, 1, 1}, - {0x29, (uint8_t []){0x00}, 0, 10}, - {0x51, (uint8_t []){0xFF}, 1, 0}, -}; - -class CustomLcdDisplay : public SpiLcdDisplay { -public: - static void MyDrawEventCb(lv_event_t *e) { - lv_area_t *area = (lv_area_t *)lv_event_get_param(e); - uint16_t x1 = area->x1; - uint16_t x2 = area->x2; - uint16_t y1 = area->y1; - uint16_t y2 = area->y2; - // round the start of coordinate down to the nearest 2M number - area->x1 = (x1 >> 1) << 1; - area->y1 = (y1 >> 1) << 1; - // round the end of coordinate up to the nearest 2N+1 number - area->x2 = ((x2 >> 1) << 1) + 1; - area->y2 = ((y2 >> 1) << 1) + 1; - } - - CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, - esp_lcd_panel_handle_t panel_handle, - int width, - int height, - int offset_x, - int offset_y, - bool mirror_x, - bool mirror_y, - bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, - width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - DisplayLockGuard lock(this); - lv_display_add_event_cb(display_, MyDrawEventCb, LV_EVENT_INVALIDATE_AREA, NULL); - } -}; - -class CustomBoard : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - Button pwr_button_; - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_io_expander_handle_t io_expander = NULL; - CustomLcdDisplay* display_; - i2c_master_dev_handle_t disp_touch_dev_handle = NULL; - lv_indev_t *touch_indev = NULL; //touch - uint8_t pwr_flag = 0; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeTca9554(void) { - esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); - if(ret != ESP_OK) - ESP_LOGE(TAG, "TCA9554 create returned error"); - ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_7 | IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_OUTPUT); - ESP_ERROR_CHECK(ret); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_7 | IO_EXPANDER_PIN_NUM_6, 1); - ESP_ERROR_CHECK(ret); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = { - .data0_io_num = LCD_D0, - .data1_io_num = LCD_D1, - .sclk_io_num = LCD_PCLK, - .data2_io_num = LCD_D2, - .data3_io_num = LCD_D3, - .max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t), - }; - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeLcdDisplay() { - const esp_lcd_panel_io_spi_config_t io_config = { - .cs_gpio_num = LCD_CS, - .dc_gpio_num = -1, - .spi_mode = 0, - .pclk_hz = 40 * 1000 * 1000, - .trans_queue_depth = 4, - .on_color_trans_done = NULL, - .user_ctx = NULL, - .lcd_cmd_bits = 32, - .lcd_param_bits = 8, - .flags = { - .quad_mode = true, - }, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &io_handle)); - sh8601_vendor_config_t vendor_config = { - .init_cmds = lcd_init_cmds, // Uncomment these line if use custom initialization commands - .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), // sizeof(axs15231b_lcd_init_cmd_t), - .flags = - { - .use_qspi_interface = 1, - }, - }; - const esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = LCD_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h` - .bits_per_pixel = 16, // Implemented by LCD command `3Ah` (16/18) - .vendor_config = &vendor_config, - }; - ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(io_handle, &panel_config, &panel_handle)); - esp_lcd_panel_set_gap(panel_handle,0x06,0x00); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); - display_ = new CustomLcdDisplay(io_handle, panel_handle, - EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { //接入锂电池时,可长按PWR开机/关机 - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - }); - - boot_button_.OnPressDown([this]() { - Application::GetInstance().StartListening(); - }); - - boot_button_.OnPressUp([this]() { - Application::GetInstance().StopListening(); - }); - - pwr_button_.OnLongPress([this]() { - if(pwr_flag == 1) - { - pwr_flag = 0; - esp_err_t ret; - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, 0); - ESP_ERROR_CHECK(ret); - } - }); - - pwr_button_.OnPressUp([this]() { - if(pwr_flag == 0) - { - pwr_flag = 1; - } - }); - } - - void InitializeTouch() { - i2c_device_config_t dev_cfg = - { - .dev_addr_length = I2C_ADDR_BIT_LEN_7, - .device_address = I2C_Touch_ADDRESS, - .scl_speed_hz = 300000, - }; - ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &disp_touch_dev_handle)); - - touch_indev = lv_indev_create(); - lv_indev_set_type(touch_indev, LV_INDEV_TYPE_POINTER); - lv_indev_set_read_cb(touch_indev, TouchInputReadCallback); - lv_indev_set_user_data(touch_indev, disp_touch_dev_handle); - } - - static void TouchInputReadCallback(lv_indev_t * indev, lv_indev_data_t *indevData) - { - i2c_master_dev_handle_t i2c_dev = (i2c_master_dev_handle_t)lv_indev_get_user_data(indev); - uint8_t cmd = 0x02; - uint8_t buf[5] = {0}; - uint16_t tp_x,tp_y; - i2c_master_transmit_receive(i2c_dev,&cmd,1,buf,5,1000); - if(buf[0]) - { - tp_x = (((uint16_t)buf[1] & 0x0f)<<8) | (uint16_t)buf[2]; - tp_y = (((uint16_t)buf[3] & 0x0f)<<8) | (uint16_t)buf[4]; - if(tp_x > EXAMPLE_LCD_H_RES) - {tp_x = EXAMPLE_LCD_H_RES;} - if(tp_y > EXAMPLE_LCD_V_RES) - {tp_y = EXAMPLE_LCD_V_RES;} - indevData->point.x = tp_x; - indevData->point.y = tp_y; - //ESP_LOGI("tp","(%ld,%ld)",indevData->point.x,indevData->point.y); - indevData->state = LV_INDEV_STATE_PRESSED; - } - else - { - indevData->state = LV_INDEV_STATE_RELEASED; - } - } - - void InitializeTools() - { - auto& mcp_server = McpServer::GetInstance(); - mcp_server.AddTool("self.disp.setbacklight", "设置屏幕亮度", PropertyList({ - Property("level", kPropertyTypeInteger, 0, 255) - }), [this](const PropertyList& properties) -> ReturnValue { - int level = properties["level"].value(); - ESP_LOGI("setbacklight","%d",level); - SetDispbacklight(level); - return true; - }); - } - - void SetDispbacklight(uint8_t backlight) { - uint32_t lcd_cmd = 0x51; - lcd_cmd &= 0xff; - lcd_cmd <<= 8; - lcd_cmd |= 0x02 << 24; - uint8_t param = backlight; - esp_lcd_panel_io_tx_param(io_handle, lcd_cmd, ¶m,1); - } - -public: - CustomBoard() : - boot_button_(BOOT_BUTTON_GPIO),pwr_button_(PWR_BUTTON_GPIO) { - InitializeI2c(); - InitializeTca9554(); - InitializeSpi(); - InitializeLcdDisplay(); - InitializeButtons(); - InitializeTools(); - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - -}; - +#include "wifi_board.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "codecs/box_audio_codec.h" + +#include +#include +#include +#include +#include +#include "esp_lcd_sh8601.h" +#include "display/lcd_display.h" +#include "esp_io_expander_tca9554.h" +#include "mcp_server.h" +#include "lvgl.h" + +#define TAG "waveshare_c6_amoled_1_43" + +static const sh8601_lcd_init_cmd_t lcd_init_cmds[] = +{ + {0x11, (uint8_t []){0x00}, 0, 80}, + {0xC4, (uint8_t []){0x80}, 1, 0}, + {0x53, (uint8_t []){0x20}, 1, 1}, + {0x63, (uint8_t []){0xFF}, 1, 1}, + {0x51, (uint8_t []){0x00}, 1, 1}, + {0x29, (uint8_t []){0x00}, 0, 10}, + {0x51, (uint8_t []){0xFF}, 1, 0}, +}; + +class CustomLcdDisplay : public SpiLcdDisplay { +public: + static void MyDrawEventCb(lv_event_t *e) { + lv_area_t *area = (lv_area_t *)lv_event_get_param(e); + uint16_t x1 = area->x1; + uint16_t x2 = area->x2; + uint16_t y1 = area->y1; + uint16_t y2 = area->y2; + // round the start of coordinate down to the nearest 2M number + area->x1 = (x1 >> 1) << 1; + area->y1 = (y1 >> 1) << 1; + // round the end of coordinate up to the nearest 2N+1 number + area->x2 = ((x2 >> 1) << 1) + 1; + area->y2 = ((y2 >> 1) << 1) + 1; + } + + CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_handle_t panel_handle, + int width, + int height, + int offset_x, + int offset_y, + bool mirror_x, + bool mirror_y, + bool swap_xy) + : SpiLcdDisplay(io_handle, panel_handle, + width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + DisplayLockGuard lock(this); + lv_display_add_event_cb(display_, MyDrawEventCb, LV_EVENT_INVALIDATE_AREA, NULL); + } +}; + +class CustomBoard : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + Button pwr_button_; + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_io_expander_handle_t io_expander = NULL; + CustomLcdDisplay* display_; + i2c_master_dev_handle_t disp_touch_dev_handle = NULL; + lv_indev_t *touch_indev = NULL; //touch + uint8_t pwr_flag = 0; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeTca9554(void) { + esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); + if(ret != ESP_OK) + ESP_LOGE(TAG, "TCA9554 create returned error"); + ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_7 | IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_OUTPUT); + ESP_ERROR_CHECK(ret); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_7 | IO_EXPANDER_PIN_NUM_6, 1); + ESP_ERROR_CHECK(ret); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = { + .data0_io_num = LCD_D0, + .data1_io_num = LCD_D1, + .sclk_io_num = LCD_PCLK, + .data2_io_num = LCD_D2, + .data3_io_num = LCD_D3, + .max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t), + }; + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() { + const esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = LCD_CS, + .dc_gpio_num = -1, + .spi_mode = 0, + .pclk_hz = 40 * 1000 * 1000, + .trans_queue_depth = 4, + .on_color_trans_done = NULL, + .user_ctx = NULL, + .lcd_cmd_bits = 32, + .lcd_param_bits = 8, + .flags = { + .quad_mode = true, + }, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &io_handle)); + sh8601_vendor_config_t vendor_config = { + .init_cmds = lcd_init_cmds, // Uncomment these line if use custom initialization commands + .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), // sizeof(axs15231b_lcd_init_cmd_t), + .flags = + { + .use_qspi_interface = 1, + }, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h` + .bits_per_pixel = 16, // Implemented by LCD command `3Ah` (16/18) + .vendor_config = &vendor_config, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(io_handle, &panel_config, &panel_handle)); + esp_lcd_panel_set_gap(panel_handle,0x06,0x00); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + display_ = new CustomLcdDisplay(io_handle, panel_handle, + EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { //接入锂电池时,可长按PWR开机/关机 + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + }); + + boot_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + + boot_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + + pwr_button_.OnLongPress([this]() { + if(pwr_flag == 1) + { + pwr_flag = 0; + esp_err_t ret; + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, 0); + ESP_ERROR_CHECK(ret); + } + }); + + pwr_button_.OnPressUp([this]() { + if(pwr_flag == 0) + { + pwr_flag = 1; + } + }); + } + + void InitializeTouch() { + i2c_device_config_t dev_cfg = + { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = I2C_Touch_ADDRESS, + .scl_speed_hz = 300000, + }; + ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &disp_touch_dev_handle)); + + touch_indev = lv_indev_create(); + lv_indev_set_type(touch_indev, LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb(touch_indev, TouchInputReadCallback); + lv_indev_set_user_data(touch_indev, disp_touch_dev_handle); + } + + static void TouchInputReadCallback(lv_indev_t * indev, lv_indev_data_t *indevData) + { + i2c_master_dev_handle_t i2c_dev = (i2c_master_dev_handle_t)lv_indev_get_user_data(indev); + uint8_t cmd = 0x02; + uint8_t buf[5] = {0}; + uint16_t tp_x,tp_y; + i2c_master_transmit_receive(i2c_dev,&cmd,1,buf,5,1000); + if(buf[0]) + { + tp_x = (((uint16_t)buf[1] & 0x0f)<<8) | (uint16_t)buf[2]; + tp_y = (((uint16_t)buf[3] & 0x0f)<<8) | (uint16_t)buf[4]; + if(tp_x > EXAMPLE_LCD_H_RES) + {tp_x = EXAMPLE_LCD_H_RES;} + if(tp_y > EXAMPLE_LCD_V_RES) + {tp_y = EXAMPLE_LCD_V_RES;} + indevData->point.x = tp_x; + indevData->point.y = tp_y; + //ESP_LOGI("tp","(%ld,%ld)",indevData->point.x,indevData->point.y); + indevData->state = LV_INDEV_STATE_PRESSED; + } + else + { + indevData->state = LV_INDEV_STATE_RELEASED; + } + } + + void InitializeTools() + { + auto& mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.disp.setbacklight", "设置屏幕亮度", PropertyList({ + Property("level", kPropertyTypeInteger, 0, 255) + }), [this](const PropertyList& properties) -> ReturnValue { + int level = properties["level"].value(); + ESP_LOGI("setbacklight","%d",level); + SetDispbacklight(level); + return true; + }); + } + + void SetDispbacklight(uint8_t backlight) { + uint32_t lcd_cmd = 0x51; + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= 0x02 << 24; + uint8_t param = backlight; + esp_lcd_panel_io_tx_param(io_handle, lcd_cmd, ¶m,1); + } + +public: + CustomBoard() : + boot_button_(BOOT_BUTTON_GPIO),pwr_button_(PWR_BUTTON_GPIO) { + InitializeI2c(); + InitializeTca9554(); + InitializeSpi(); + InitializeLcdDisplay(); + InitializeButtons(); + InitializeTools(); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + +}; + DECLARE_BOARD(CustomBoard); \ No newline at end of file diff --git a/main/boards/waveshare-p4-nano/README.md b/main/boards/waveshare-p4-nano/README.md index 580785c..e4025e5 100644 --- a/main/boards/waveshare-p4-nano/README.md +++ b/main/boards/waveshare-p4-nano/README.md @@ -1,43 +1,43 @@ -# Waveshare ESP32-P4-NANO - - -[ESP32-P4-NANO](https://www.waveshare.com/esp32-p4-nano.htm) is a small size and highly integrated development board designed by waveshare electronics based on ESP32-P4 chip - - - -## Display Page - - -### Recommended display screen - -| Product ID | Dependency | tested | -|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|--------| -| [10.1-DSI-TOUCH-A](https://www.waveshare.com/10.1-dsi-touch-a.htm)
| [waveshare/esp_lcd_jd9365_10_1](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365_10_1) | ✅ | -| [101M-8001280-IPS-CT-K](https://www.waveshare.com/101m-8001280-ips-ct-k.htm)
| [waveshare/esp_lcd_jd9365_10_1](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365_10_1) | ✅ | - -### Common Raspberry adapter screen - -**These displays are supported on [ESP32-P4-NANO BSP](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/bsp/esp32_p4_nano), but not on xiaozhi-esp32** - -
-View full display - -| Product ID | Dependency | tested | -|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|--------| -| [2.8inch DSI LCD](https://www.waveshare.com/2.8inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [3.4inch DSI LCD (C)](https://www.waveshare.com/3.4inch-dsi-lcd-c.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [4inch DSI LCD (C)](https://www.waveshare.com/4inch-dsi-lcd-c.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [4inch DSI LCD](https://www.waveshare.com/4inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [5inch DSI LCD (D)](https://www.waveshare.com/5inch-dsi-lcd-d.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [6.25inch DSI LCD](https://www.waveshare.com/6.25inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [5inch DSI LCD (C)](https://www.waveshare.com/5inch-dsi-lcd-c.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [7inch DSI LCD (C)](https://www.waveshare.com/7inch-dsi-lcd-c-with-case-a.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [7.9inch DSI LCD](https://www.waveshare.com/7.9inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [7inch DSI LCD (E)](https://www.waveshare.com/7inch-dsi-lcd-e.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [8inch DSI LCD (C)](https://www.waveshare.com/8inch-dsi-lcd-c.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [10.1inch DSI LCD (C)](https://www.waveshare.com/10.1inch-dsi-lcd-c.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [8.8inch DSI LCD](https://www.waveshare.com/8.8inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [11.9inch DSI LCD](https://www.waveshare.com/11.9inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | -| [7-DSI-TOUCH-A](https://www.waveshare.com/7-dsi-touch-a.htm)
| [waveshare/esp_lcd_ili9881c](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_ili9881c) | 🕒 | - +# Waveshare ESP32-P4-NANO + + +[ESP32-P4-NANO](https://www.waveshare.com/esp32-p4-nano.htm) is a small size and highly integrated development board designed by waveshare electronics based on ESP32-P4 chip + + + +## Display Page + + +### Recommended display screen + +| Product ID | Dependency | tested | +|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|--------| +| [10.1-DSI-TOUCH-A](https://www.waveshare.com/10.1-dsi-touch-a.htm)
| [waveshare/esp_lcd_jd9365_10_1](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365_10_1) | ✅ | +| [101M-8001280-IPS-CT-K](https://www.waveshare.com/101m-8001280-ips-ct-k.htm)
| [waveshare/esp_lcd_jd9365_10_1](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365_10_1) | ✅ | + +### Common Raspberry adapter screen + +**These displays are supported on [ESP32-P4-NANO BSP](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/bsp/esp32_p4_nano), but not on xiaozhi-esp32** + +
+View full display + +| Product ID | Dependency | tested | +|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|--------| +| [2.8inch DSI LCD](https://www.waveshare.com/2.8inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [3.4inch DSI LCD (C)](https://www.waveshare.com/3.4inch-dsi-lcd-c.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [4inch DSI LCD (C)](https://www.waveshare.com/4inch-dsi-lcd-c.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [4inch DSI LCD](https://www.waveshare.com/4inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [5inch DSI LCD (D)](https://www.waveshare.com/5inch-dsi-lcd-d.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [6.25inch DSI LCD](https://www.waveshare.com/6.25inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [5inch DSI LCD (C)](https://www.waveshare.com/5inch-dsi-lcd-c.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [7inch DSI LCD (C)](https://www.waveshare.com/7inch-dsi-lcd-c-with-case-a.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [7.9inch DSI LCD](https://www.waveshare.com/7.9inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [7inch DSI LCD (E)](https://www.waveshare.com/7inch-dsi-lcd-e.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [8inch DSI LCD (C)](https://www.waveshare.com/8inch-dsi-lcd-c.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [10.1inch DSI LCD (C)](https://www.waveshare.com/10.1inch-dsi-lcd-c.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [8.8inch DSI LCD](https://www.waveshare.com/8.8inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [11.9inch DSI LCD](https://www.waveshare.com/11.9inch-dsi-lcd.htm)
| [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 🕒 | +| [7-DSI-TOUCH-A](https://www.waveshare.com/7-dsi-touch-a.htm)
| [waveshare/esp_lcd_ili9881c](https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_ili9881c) | 🕒 | +
\ No newline at end of file diff --git a/main/boards/waveshare-p4-nano/config.h b/main/boards/waveshare-p4-nano/config.h index 8fb9ccc..1d6af45 100644 --- a/main/boards/waveshare-p4-nano/config.h +++ b/main/boards/waveshare-p4-nano/config.h @@ -1,44 +1,44 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_10 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_12 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_53 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BOOT_BUTTON_GPIO GPIO_NUM_35 - -#define DISPLAY_WIDTH 800 -#define DISPLAY_HEIGHT 1280 - -#define LCD_BIT_PER_PIXEL (16) -#define PIN_NUM_LCD_RST GPIO_NUM_NC - -#define DELAY_TIME_MS (3000) -#define LCD_MIPI_DSI_LANE_NUM (2) // 2 data lanes - -#define MIPI_DSI_PHY_PWR_LDO_CHAN (3) -#define MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500) - -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_10 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_12 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_53 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_GPIO GPIO_NUM_35 + +#define DISPLAY_WIDTH 800 +#define DISPLAY_HEIGHT 1280 + +#define LCD_BIT_PER_PIXEL (16) +#define PIN_NUM_LCD_RST GPIO_NUM_NC + +#define DELAY_TIME_MS (3000) +#define LCD_MIPI_DSI_LANE_NUM (2) // 2 data lanes + +#define MIPI_DSI_PHY_PWR_LDO_CHAN (3) +#define MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500) + +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/waveshare-p4-nano/esp32-p4-nano.cc b/main/boards/waveshare-p4-nano/esp32-p4-nano.cc index a7dc2dc..e5e79c3 100644 --- a/main/boards/waveshare-p4-nano/esp32-p4-nano.cc +++ b/main/boards/waveshare-p4-nano/esp32-p4-nano.cc @@ -1,230 +1,230 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "application.h" -#include "display/lcd_display.h" -// #include "display/no_display.h" -#include "button.h" -#include "config.h" - -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_mipi_dsi.h" -#include "esp_ldo_regulator.h" - -#include "esp_lcd_mipi_dsi.h" -#include "esp_lcd_jd9365_10_1.h" - -#include -#include -#include -#include -#include "esp_lcd_touch_gt911.h" -#define TAG "WaveshareEsp32p4nano" - -class CustomBacklight : public Backlight { -public: - CustomBacklight(i2c_master_bus_handle_t i2c_handle) - : Backlight(), i2c_handle_(i2c_handle) {} - -protected: - i2c_master_bus_handle_t i2c_handle_; - - virtual void SetBrightnessImpl(uint8_t brightness) override { - uint8_t i2c_address = 0x45; // 7-bit address -#if CONFIG_LCD_TYPE_800_1280_10_1_INCH - uint8_t reg = 0x86; -#elif CONFIG_LCD_TYPE_800_1280_10_1_INCH_A - uint8_t reg = 0x96; -#endif - uint8_t data[2] = {reg, brightness}; - - i2c_master_dev_handle_t dev_handle; - i2c_device_config_t dev_cfg = { - .dev_addr_length = I2C_ADDR_BIT_LEN_7, - .device_address = i2c_address, - .scl_speed_hz = 100000, - }; - - esp_err_t err = i2c_master_bus_add_device(i2c_handle_, &dev_cfg, &dev_handle); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to add I2C device: %s", esp_err_to_name(err)); - return; - } - - err = i2c_master_transmit(dev_handle, data, sizeof(data), -1); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to transmit brightness: %s", esp_err_to_name(err)); - } else { - ESP_LOGI(TAG, "Backlight brightness set to %u", brightness); - } - - // i2c_master_bus_rm_device(dev_handle); - } -}; - -class WaveshareEsp32p4nano : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - LcdDisplay *display__; - CustomBacklight *backlight_; - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - static esp_err_t bsp_enable_dsi_phy_power(void) { -#if MIPI_DSI_PHY_PWR_LDO_CHAN > 0 - // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state - static esp_ldo_channel_handle_t phy_pwr_chan = NULL; - esp_ldo_channel_config_t ldo_cfg = { - .chan_id = MIPI_DSI_PHY_PWR_LDO_CHAN, - .voltage_mv = MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, - }; - esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan); - ESP_LOGI(TAG, "MIPI DSI PHY Powered on"); -#endif // BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0 - - return ESP_OK; - } - - void InitializeLCD() { - bsp_enable_dsi_phy_power(); - esp_lcd_panel_io_handle_t io = NULL; - esp_lcd_panel_handle_t disp_panel = NULL; - - esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; - esp_lcd_dsi_bus_config_t bus_config = JD9365_PANEL_BUS_DSI_2CH_CONFIG(); - esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus); - - ESP_LOGI(TAG, "Install MIPI DSI LCD control panel"); - // we use DBI interface to send LCD commands and parameters - esp_lcd_dbi_io_config_t dbi_config = JD9365_PANEL_IO_DBI_CONFIG(); - esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io); - - esp_lcd_dpi_panel_config_t dpi_config = { - .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, - .dpi_clock_freq_mhz = 80, - .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, - .num_fbs = 1, - .video_timing = { - .h_size = 800, - .v_size = 1280, - .hsync_pulse_width = 20, - .hsync_back_porch = 20, - .hsync_front_porch = 40, - .vsync_pulse_width = 10, - .vsync_back_porch = 4, - .vsync_front_porch = 30, - }, - .flags = { - .use_dma2d = true, - }, - }; - - jd9365_vendor_config_t vendor_config = { - - .mipi_config = { - .dsi_bus = mipi_dsi_bus, - .dpi_config = &dpi_config, - .lane_num = 2, - }, - .flags = { - .use_mipi_interface = 1, - }, - }; - - const esp_lcd_panel_dev_config_t lcd_dev_config = { - .reset_gpio_num = PIN_NUM_LCD_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - .vendor_config = &vendor_config, - }; - esp_lcd_new_panel_jd9365(io, &lcd_dev_config, &disp_panel); - esp_lcd_panel_reset(disp_panel); - esp_lcd_panel_init(disp_panel); - - display__ = new MipiLcdDisplay(io, disp_panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - backlight_ = new CustomBacklight(codec_i2c_bus_); - backlight_->RestoreBrightness(); - } - void InitializeTouch() - { - esp_lcd_touch_handle_t tp; - esp_lcd_touch_config_t tp_cfg = { - .x_max = DISPLAY_WIDTH, - .y_max = DISPLAY_HEIGHT, - .rst_gpio_num = GPIO_NUM_NC, - .int_gpio_num = GPIO_NUM_NC, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = 0, - .mirror_x = 0, - .mirror_y = 0, - }, - }; - esp_lcd_panel_io_handle_t tp_io_handle = NULL; - esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); - tp_io_config.scl_speed_hz = 100 * 1000; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(codec_i2c_bus_, &tp_io_config, &tp_io_handle)); - ESP_LOGI(TAG, "Initialize touch controller"); - ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp)); - const lvgl_port_touch_cfg_t touch_cfg = { - .disp = lv_display_get_default(), - .handle = tp, - }; - lvgl_port_add_touch(&touch_cfg); - ESP_LOGI(TAG, "Touch panel initialized successfully"); - } - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); }); - } - -public: - WaveshareEsp32p4nano() : - boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeLCD(); - InitializeTouch(); - InitializeButtons(); - } - - virtual AudioCodec *GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_1, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display *GetDisplay() override { - return display__; - } - - virtual Backlight *GetBacklight() override { - return backlight_; - } - -}; - -DECLARE_BOARD(WaveshareEsp32p4nano); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "application.h" +#include "display/lcd_display.h" +// #include "display/no_display.h" +#include "button.h" +#include "config.h" + +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_ldo_regulator.h" + +#include "esp_lcd_mipi_dsi.h" +#include "esp_lcd_jd9365_10_1.h" + +#include +#include +#include +#include +#include "esp_lcd_touch_gt911.h" +#define TAG "WaveshareEsp32p4nano" + +class CustomBacklight : public Backlight { +public: + CustomBacklight(i2c_master_bus_handle_t i2c_handle) + : Backlight(), i2c_handle_(i2c_handle) {} + +protected: + i2c_master_bus_handle_t i2c_handle_; + + virtual void SetBrightnessImpl(uint8_t brightness) override { + uint8_t i2c_address = 0x45; // 7-bit address +#if CONFIG_LCD_TYPE_800_1280_10_1_INCH + uint8_t reg = 0x86; +#elif CONFIG_LCD_TYPE_800_1280_10_1_INCH_A + uint8_t reg = 0x96; +#endif + uint8_t data[2] = {reg, brightness}; + + i2c_master_dev_handle_t dev_handle; + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = i2c_address, + .scl_speed_hz = 100000, + }; + + esp_err_t err = i2c_master_bus_add_device(i2c_handle_, &dev_cfg, &dev_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to add I2C device: %s", esp_err_to_name(err)); + return; + } + + err = i2c_master_transmit(dev_handle, data, sizeof(data), -1); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to transmit brightness: %s", esp_err_to_name(err)); + } else { + ESP_LOGI(TAG, "Backlight brightness set to %u", brightness); + } + + // i2c_master_bus_rm_device(dev_handle); + } +}; + +class WaveshareEsp32p4nano : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + LcdDisplay *display__; + CustomBacklight *backlight_; + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + static esp_err_t bsp_enable_dsi_phy_power(void) { +#if MIPI_DSI_PHY_PWR_LDO_CHAN > 0 + // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state + static esp_ldo_channel_handle_t phy_pwr_chan = NULL; + esp_ldo_channel_config_t ldo_cfg = { + .chan_id = MIPI_DSI_PHY_PWR_LDO_CHAN, + .voltage_mv = MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, + }; + esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan); + ESP_LOGI(TAG, "MIPI DSI PHY Powered on"); +#endif // BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0 + + return ESP_OK; + } + + void InitializeLCD() { + bsp_enable_dsi_phy_power(); + esp_lcd_panel_io_handle_t io = NULL; + esp_lcd_panel_handle_t disp_panel = NULL; + + esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; + esp_lcd_dsi_bus_config_t bus_config = JD9365_PANEL_BUS_DSI_2CH_CONFIG(); + esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus); + + ESP_LOGI(TAG, "Install MIPI DSI LCD control panel"); + // we use DBI interface to send LCD commands and parameters + esp_lcd_dbi_io_config_t dbi_config = JD9365_PANEL_IO_DBI_CONFIG(); + esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io); + + esp_lcd_dpi_panel_config_t dpi_config = { + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = 80, + .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, + .num_fbs = 1, + .video_timing = { + .h_size = 800, + .v_size = 1280, + .hsync_pulse_width = 20, + .hsync_back_porch = 20, + .hsync_front_porch = 40, + .vsync_pulse_width = 10, + .vsync_back_porch = 4, + .vsync_front_porch = 30, + }, + .flags = { + .use_dma2d = true, + }, + }; + + jd9365_vendor_config_t vendor_config = { + + .mipi_config = { + .dsi_bus = mipi_dsi_bus, + .dpi_config = &dpi_config, + .lane_num = 2, + }, + .flags = { + .use_mipi_interface = 1, + }, + }; + + const esp_lcd_panel_dev_config_t lcd_dev_config = { + .reset_gpio_num = PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + .vendor_config = &vendor_config, + }; + esp_lcd_new_panel_jd9365(io, &lcd_dev_config, &disp_panel); + esp_lcd_panel_reset(disp_panel); + esp_lcd_panel_init(disp_panel); + + display__ = new MipiLcdDisplay(io, disp_panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + backlight_ = new CustomBacklight(codec_i2c_bus_); + backlight_->RestoreBrightness(); + } + void InitializeTouch() + { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH, + .y_max = DISPLAY_HEIGHT, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); + tp_io_config.scl_speed_hz = 100 * 1000; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(codec_i2c_bus_, &tp_io_config, &tp_io_handle)); + ESP_LOGI(TAG, "Initialize touch controller"); + ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp)); + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + lvgl_port_add_touch(&touch_cfg); + ESP_LOGI(TAG, "Touch panel initialized successfully"); + } + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); }); + } + +public: + WaveshareEsp32p4nano() : + boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeLCD(); + InitializeTouch(); + InitializeButtons(); + } + + virtual AudioCodec *GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_1, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display *GetDisplay() override { + return display__; + } + + virtual Backlight *GetBacklight() override { + return backlight_; + } + +}; + +DECLARE_BOARD(WaveshareEsp32p4nano); diff --git a/main/boards/waveshare-p4-wifi6-touch-lcd-4b/README.md b/main/boards/waveshare-p4-wifi6-touch-lcd-4b/README.md index eb94e64..6aedac2 100644 --- a/main/boards/waveshare-p4-wifi6-touch-lcd-4b/README.md +++ b/main/boards/waveshare-p4-wifi6-touch-lcd-4b/README.md @@ -1,12 +1,12 @@ -# Waveshare ESP32-P4-WIFI6-Touch-LCD-4B - - -[ESP32-P4-WIFI6-Touch-LCD-4B](https://www.waveshare.com/esp32-p4-wifi6-touch-lcd-4b.htm) is waveshare electronics designed an intelligent 86 box based on ESP32-P4 module equipped with a 720*720 IPS capacitive touch screen - - -## Configuration - -Configuration in `menuconfig`. - -Selection Board Type `Xiaozhi Assistant --> Board Type` +# Waveshare ESP32-P4-WIFI6-Touch-LCD-4B + + +[ESP32-P4-WIFI6-Touch-LCD-4B](https://www.waveshare.com/esp32-p4-wifi6-touch-lcd-4b.htm) is waveshare electronics designed an intelligent 86 box based on ESP32-P4 module equipped with a 720*720 IPS capacitive touch screen + + +## Configuration + +Configuration in `menuconfig`. + +Selection Board Type `Xiaozhi Assistant --> Board Type` - Waveshare ESP32-P4-WIFI6-Touch-LCD-4B \ No newline at end of file diff --git a/main/boards/waveshare-p4-wifi6-touch-lcd-4b/config.h b/main/boards/waveshare-p4-wifi6-touch-lcd-4b/config.h index 027c215..5077e03 100644 --- a/main/boards/waveshare-p4-wifi6-touch-lcd-4b/config.h +++ b/main/boards/waveshare-p4-wifi6-touch-lcd-4b/config.h @@ -1,47 +1,47 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_10 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_12 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_53 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BOOT_BUTTON_GPIO GPIO_NUM_35 - -#define DISPLAY_WIDTH 720 -#define DISPLAY_HEIGHT 720 - -#define LCD_BIT_PER_PIXEL (16) -#define PIN_NUM_LCD_RST GPIO_NUM_27 - -#define DELAY_TIME_MS (3000) -#define LCD_MIPI_DSI_LANE_NUM (2) // 2 data lanes - -#define MIPI_DSI_PHY_PWR_LDO_CHAN (3) -#define MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500) - -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_26 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_10 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_12 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_53 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_GPIO GPIO_NUM_35 + +#define DISPLAY_WIDTH 720 +#define DISPLAY_HEIGHT 720 + +#define LCD_BIT_PER_PIXEL (16) +#define PIN_NUM_LCD_RST GPIO_NUM_27 + +#define DELAY_TIME_MS (3000) +#define LCD_MIPI_DSI_LANE_NUM (2) // 2 data lanes + +#define MIPI_DSI_PHY_PWR_LDO_CHAN (3) +#define MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500) + +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_26 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc b/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc index 20ec9f4..653c5f7 100644 --- a/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc +++ b/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc @@ -1,195 +1,195 @@ -#include "wifi_board.h" -#include "codecs/box_audio_codec.h" -#include "application.h" -#include "display/lcd_display.h" -// #include "display/no_display.h" -#include "button.h" -#include "config.h" - -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_mipi_dsi.h" -#include "esp_ldo_regulator.h" - -#include "esp_lcd_st7703.h" - -#include -#include -#include -#include -#include "esp_lcd_touch_gt911.h" -#define TAG "WaveshareEsp32p44b" - -class WaveshareEsp32p44b : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - LcdDisplay *display_; - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - static esp_err_t bsp_enable_dsi_phy_power(void) { -#if MIPI_DSI_PHY_PWR_LDO_CHAN > 0 - // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state - static esp_ldo_channel_handle_t phy_pwr_chan = NULL; - esp_ldo_channel_config_t ldo_cfg = { - .chan_id = MIPI_DSI_PHY_PWR_LDO_CHAN, - .voltage_mv = MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, - }; - esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan); - ESP_LOGI(TAG, "MIPI DSI PHY Powered on"); -#endif // BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0 - - return ESP_OK; - } - - void InitializeLCD() { - bsp_enable_dsi_phy_power(); - esp_lcd_panel_io_handle_t io = NULL; - esp_lcd_panel_handle_t disp_panel = NULL; - - esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; - esp_lcd_dsi_bus_config_t bus_config = ST7703_PANEL_BUS_DSI_2CH_CONFIG(); - esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus); - - ESP_LOGI(TAG, "Install MIPI DSI LCD control panel"); - // we use DBI interface to send LCD commands and parameters - esp_lcd_dbi_io_config_t dbi_config = ST7703_PANEL_IO_DBI_CONFIG(); - esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io); - - esp_lcd_dpi_panel_config_t dpi_config = { - .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, - .dpi_clock_freq_mhz = 46, - .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, - .num_fbs = 1, - .video_timing = { - .h_size = 720, - .v_size = 720, - .hsync_pulse_width = 20, - .hsync_back_porch = 80, - .hsync_front_porch = 80, - .vsync_pulse_width = 4, - .vsync_back_porch = 12, - .vsync_front_porch = 30, - }, - .flags = { - .use_dma2d = true, - }, - }; - st7703_vendor_config_t vendor_config = { - - .mipi_config = { - .dsi_bus = mipi_dsi_bus, - .dpi_config = &dpi_config, - }, - .flags = { - .use_mipi_interface = 1, - }, - }; - - const esp_lcd_panel_dev_config_t lcd_dev_config = { - .reset_gpio_num = PIN_NUM_LCD_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - .vendor_config = &vendor_config, - }; - esp_lcd_new_panel_st7703(io, &lcd_dev_config, &disp_panel); - esp_lcd_panel_reset(disp_panel); - esp_lcd_panel_init(disp_panel); - - display_ = new MipiLcdDisplay(io, disp_panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - void InitializeTouch() - { - esp_lcd_touch_handle_t tp; - esp_lcd_touch_config_t tp_cfg = { - .x_max = DISPLAY_WIDTH, - .y_max = DISPLAY_HEIGHT, - .rst_gpio_num = GPIO_NUM_23, - .int_gpio_num = GPIO_NUM_NC, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = 0, - .mirror_x = 0, - .mirror_y = 0, - }, - }; - esp_lcd_panel_io_handle_t tp_io_handle = NULL; - esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); - tp_io_config.scl_speed_hz = 400 * 1000; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); - ESP_LOGI(TAG, "Initialize touch controller"); - ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp)); - const lvgl_port_touch_cfg_t touch_cfg = { - .disp = lv_display_get_default(), - .handle = tp, - }; - lvgl_port_add_touch(&touch_cfg); - ESP_LOGI(TAG, "Touch panel initialized successfully"); - } - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); }); - } - -public: - WaveshareEsp32p44b() : - boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeLCD(); - InitializeTouch(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display *GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - -}; - -DECLARE_BOARD(WaveshareEsp32p44b); +#include "wifi_board.h" +#include "codecs/box_audio_codec.h" +#include "application.h" +#include "display/lcd_display.h" +// #include "display/no_display.h" +#include "button.h" +#include "config.h" + +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_ldo_regulator.h" + +#include "esp_lcd_st7703.h" + +#include +#include +#include +#include +#include "esp_lcd_touch_gt911.h" +#define TAG "WaveshareEsp32p44b" + +class WaveshareEsp32p44b : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + LcdDisplay *display_; + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + static esp_err_t bsp_enable_dsi_phy_power(void) { +#if MIPI_DSI_PHY_PWR_LDO_CHAN > 0 + // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state + static esp_ldo_channel_handle_t phy_pwr_chan = NULL; + esp_ldo_channel_config_t ldo_cfg = { + .chan_id = MIPI_DSI_PHY_PWR_LDO_CHAN, + .voltage_mv = MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, + }; + esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan); + ESP_LOGI(TAG, "MIPI DSI PHY Powered on"); +#endif // BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0 + + return ESP_OK; + } + + void InitializeLCD() { + bsp_enable_dsi_phy_power(); + esp_lcd_panel_io_handle_t io = NULL; + esp_lcd_panel_handle_t disp_panel = NULL; + + esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; + esp_lcd_dsi_bus_config_t bus_config = ST7703_PANEL_BUS_DSI_2CH_CONFIG(); + esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus); + + ESP_LOGI(TAG, "Install MIPI DSI LCD control panel"); + // we use DBI interface to send LCD commands and parameters + esp_lcd_dbi_io_config_t dbi_config = ST7703_PANEL_IO_DBI_CONFIG(); + esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io); + + esp_lcd_dpi_panel_config_t dpi_config = { + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = 46, + .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, + .num_fbs = 1, + .video_timing = { + .h_size = 720, + .v_size = 720, + .hsync_pulse_width = 20, + .hsync_back_porch = 80, + .hsync_front_porch = 80, + .vsync_pulse_width = 4, + .vsync_back_porch = 12, + .vsync_front_porch = 30, + }, + .flags = { + .use_dma2d = true, + }, + }; + st7703_vendor_config_t vendor_config = { + + .mipi_config = { + .dsi_bus = mipi_dsi_bus, + .dpi_config = &dpi_config, + }, + .flags = { + .use_mipi_interface = 1, + }, + }; + + const esp_lcd_panel_dev_config_t lcd_dev_config = { + .reset_gpio_num = PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + .vendor_config = &vendor_config, + }; + esp_lcd_new_panel_st7703(io, &lcd_dev_config, &disp_panel); + esp_lcd_panel_reset(disp_panel); + esp_lcd_panel_init(disp_panel); + + display_ = new MipiLcdDisplay(io, disp_panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + void InitializeTouch() + { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH, + .y_max = DISPLAY_HEIGHT, + .rst_gpio_num = GPIO_NUM_23, + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); + tp_io_config.scl_speed_hz = 400 * 1000; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); + ESP_LOGI(TAG, "Initialize touch controller"); + ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp)); + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + lvgl_port_add_touch(&touch_cfg); + ESP_LOGI(TAG, "Touch panel initialized successfully"); + } + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); }); + } + +public: + WaveshareEsp32p44b() : + boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeLCD(); + InitializeTouch(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display *GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + +}; + +DECLARE_BOARD(WaveshareEsp32p44b); diff --git a/main/boards/waveshare-p4-wifi6-touch-lcd-xc/README.md b/main/boards/waveshare-p4-wifi6-touch-lcd-xc/README.md index 09271dd..ee93ca3 100644 --- a/main/boards/waveshare-p4-wifi6-touch-lcd-xc/README.md +++ b/main/boards/waveshare-p4-wifi6-touch-lcd-xc/README.md @@ -1,22 +1,22 @@ -# Waveshare ESP32-P4-WIFI6-Touch-LCD-XC - - -[ESP32-P4-WIFI6-Touch-LCD-XC](https://www.waveshare.com/esp32-p4-wifi6-touch-lcd-3.4c.htm) is waveshare electronics designed a 3.4-inch, 4-inch circular screen, highly integrated development board - - -## Configuration - -Configuration in `menuconfig`. - -Selection Board Type `Xiaozhi Assistant --> Board Type` -- Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C or ESP32-P4-WIFI6-Touch-LCD-4C - -Selection Display LCD Type `Xiaozhi Assistant --> LCD Type` -- Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C with 800*800 3.4inch round display -- Waveshare ESP32-P4-WIFI6-Touch-LCD-4C with 720*720 4inch round display - - - -| [ESP32-P4-WIFI6-Touch-LCD-3.4C](https://www.waveshare.com/esp32-p4-wifi6-touch-lcd-3.4c.htm) | [ESP32-P4-WIFI6-Touch-LCD-4C](https://www.waveshare.com/esp32-p4-wifi6-touch-lcd-4c.htm) | -|----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| -| | | +# Waveshare ESP32-P4-WIFI6-Touch-LCD-XC + + +[ESP32-P4-WIFI6-Touch-LCD-XC](https://www.waveshare.com/esp32-p4-wifi6-touch-lcd-3.4c.htm) is waveshare electronics designed a 3.4-inch, 4-inch circular screen, highly integrated development board + + +## Configuration + +Configuration in `menuconfig`. + +Selection Board Type `Xiaozhi Assistant --> Board Type` +- Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C or ESP32-P4-WIFI6-Touch-LCD-4C + +Selection Display LCD Type `Xiaozhi Assistant --> LCD Type` +- Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C with 800*800 3.4inch round display +- Waveshare ESP32-P4-WIFI6-Touch-LCD-4C with 720*720 4inch round display + + + +| [ESP32-P4-WIFI6-Touch-LCD-3.4C](https://www.waveshare.com/esp32-p4-wifi6-touch-lcd-3.4c.htm) | [ESP32-P4-WIFI6-Touch-LCD-4C](https://www.waveshare.com/esp32-p4-wifi6-touch-lcd-4c.htm) | +|----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| +| | | diff --git a/main/boards/waveshare-p4-wifi6-touch-lcd-xc/config.h b/main/boards/waveshare-p4-wifi6-touch-lcd-xc/config.h index 30e73ae..2684b52 100644 --- a/main/boards/waveshare-p4-wifi6-touch-lcd-xc/config.h +++ b/main/boards/waveshare-p4-wifi6-touch-lcd-xc/config.h @@ -1,490 +1,490 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_10 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_12 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_53 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BOOT_BUTTON_GPIO GPIO_NUM_35 - -#define LCD_BIT_PER_PIXEL (16) -#define PIN_NUM_LCD_RST GPIO_NUM_27 - -#define DELAY_TIME_MS (3000) -#define LCD_MIPI_DSI_LANE_NUM (2) // 2 data lanes - -#define MIPI_DSI_PHY_PWR_LDO_CHAN (3) -#define MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500) - -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_26 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true - -#if CONFIG_LCD_TYPE_800_800_3_4_INCH -#define DISPLAY_WIDTH 800 -#define DISPLAY_HEIGHT 800 - -static const jd9365_lcd_init_cmd_t lcd_init_cmds[] = { - {0xE0, (uint8_t[]){0x00}, 1, 0}, - - {0xE1, (uint8_t[]){0x93}, 1, 0}, - {0xE2, (uint8_t[]){0x65}, 1, 0}, - {0xE3, (uint8_t[]){0xF8}, 1, 0}, - {0x80, (uint8_t[]){0x01}, 1, 0}, - - {0xE0, (uint8_t[]){0x01}, 1, 0}, - - {0x00, (uint8_t[]){0x00}, 1, 0}, - {0x01, (uint8_t[]){0x41}, 1, 0}, - {0x03, (uint8_t[]){0x10}, 1, 0}, - {0x04, (uint8_t[]){0x44}, 1, 0}, - - {0x17, (uint8_t[]){0x00}, 1, 0}, - {0x18, (uint8_t[]){0xD0}, 1, 0}, - {0x19, (uint8_t[]){0x00}, 1, 0}, - {0x1A, (uint8_t[]){0x00}, 1, 0}, - {0x1B, (uint8_t[]){0xD0}, 1, 0}, - {0x1C, (uint8_t[]){0x00}, 1, 0}, - - {0x24, (uint8_t[]){0xFE}, 1, 0}, - {0x35, (uint8_t[]){0x26}, 1, 0}, - - {0x37, (uint8_t[]){0x09}, 1, 0}, - - {0x38, (uint8_t[]){0x04}, 1, 0}, - {0x39, (uint8_t[]){0x08}, 1, 0}, - {0x3A, (uint8_t[]){0x0A}, 1, 0}, - {0x3C, (uint8_t[]){0x78}, 1, 0}, - {0x3D, (uint8_t[]){0xFF}, 1, 0}, - {0x3E, (uint8_t[]){0xFF}, 1, 0}, - {0x3F, (uint8_t[]){0xFF}, 1, 0}, - - {0x40, (uint8_t[]){0x00}, 1, 0}, - {0x41, (uint8_t[]){0x64}, 1, 0}, - {0x42, (uint8_t[]){0xC7}, 1, 0}, - {0x43, (uint8_t[]){0x18}, 1, 0}, - {0x44, (uint8_t[]){0x0B}, 1, 0}, - {0x45, (uint8_t[]){0x14}, 1, 0}, - - {0x55, (uint8_t[]){0x02}, 1, 0}, - {0x57, (uint8_t[]){0x49}, 1, 0}, - {0x59, (uint8_t[]){0x0A}, 1, 0}, - {0x5A, (uint8_t[]){0x1B}, 1, 0}, - {0x5B, (uint8_t[]){0x19}, 1, 0}, - - {0x5D, (uint8_t[]){0x7F}, 1, 0}, - {0x5E, (uint8_t[]){0x56}, 1, 0}, - {0x5F, (uint8_t[]){0x43}, 1, 0}, - {0x60, (uint8_t[]){0x37}, 1, 0}, - {0x61, (uint8_t[]){0x33}, 1, 0}, - {0x62, (uint8_t[]){0x25}, 1, 0}, - {0x63, (uint8_t[]){0x2A}, 1, 0}, - {0x64, (uint8_t[]){0x16}, 1, 0}, - {0x65, (uint8_t[]){0x30}, 1, 0}, - {0x66, (uint8_t[]){0x2F}, 1, 0}, - {0x67, (uint8_t[]){0x32}, 1, 0}, - {0x68, (uint8_t[]){0x53}, 1, 0}, - {0x69, (uint8_t[]){0x43}, 1, 0}, - {0x6A, (uint8_t[]){0x4C}, 1, 0}, - {0x6B, (uint8_t[]){0x40}, 1, 0}, - {0x6C, (uint8_t[]){0x3D}, 1, 0}, - {0x6D, (uint8_t[]){0x31}, 1, 0}, - {0x6E, (uint8_t[]){0x20}, 1, 0}, - {0x6F, (uint8_t[]){0x0F}, 1, 0}, - - {0x70, (uint8_t[]){0x7F}, 1, 0}, - {0x71, (uint8_t[]){0x56}, 1, 0}, - {0x72, (uint8_t[]){0x43}, 1, 0}, - {0x73, (uint8_t[]){0x37}, 1, 0}, - {0x74, (uint8_t[]){0x33}, 1, 0}, - {0x75, (uint8_t[]){0x25}, 1, 0}, - {0x76, (uint8_t[]){0x2A}, 1, 0}, - {0x77, (uint8_t[]){0x16}, 1, 0}, - {0x78, (uint8_t[]){0x30}, 1, 0}, - {0x79, (uint8_t[]){0x2F}, 1, 0}, - {0x7A, (uint8_t[]){0x32}, 1, 0}, - {0x7B, (uint8_t[]){0x53}, 1, 0}, - {0x7C, (uint8_t[]){0x43}, 1, 0}, - {0x7D, (uint8_t[]){0x4C}, 1, 0}, - {0x7E, (uint8_t[]){0x40}, 1, 0}, - {0x7F, (uint8_t[]){0x3D}, 1, 0}, - {0x80, (uint8_t[]){0x31}, 1, 0}, - {0x81, (uint8_t[]){0x20}, 1, 0}, - {0x82, (uint8_t[]){0x0F}, 1, 0}, - - {0xE0, (uint8_t[]){0x02}, 1, 0}, - {0x00, (uint8_t[]){0x5F}, 1, 0}, - {0x01, (uint8_t[]){0x5F}, 1, 0}, - {0x02, (uint8_t[]){0x5E}, 1, 0}, - {0x03, (uint8_t[]){0x5E}, 1, 0}, - {0x04, (uint8_t[]){0x50}, 1, 0}, - {0x05, (uint8_t[]){0x48}, 1, 0}, - {0x06, (uint8_t[]){0x48}, 1, 0}, - {0x07, (uint8_t[]){0x4A}, 1, 0}, - {0x08, (uint8_t[]){0x4A}, 1, 0}, - {0x09, (uint8_t[]){0x44}, 1, 0}, - {0x0A, (uint8_t[]){0x44}, 1, 0}, - {0x0B, (uint8_t[]){0x46}, 1, 0}, - {0x0C, (uint8_t[]){0x46}, 1, 0}, - {0x0D, (uint8_t[]){0x5F}, 1, 0}, - {0x0E, (uint8_t[]){0x5F}, 1, 0}, - {0x0F, (uint8_t[]){0x57}, 1, 0}, - {0x10, (uint8_t[]){0x57}, 1, 0}, - {0x11, (uint8_t[]){0x77}, 1, 0}, - {0x12, (uint8_t[]){0x77}, 1, 0}, - {0x13, (uint8_t[]){0x40}, 1, 0}, - {0x14, (uint8_t[]){0x42}, 1, 0}, - {0x15, (uint8_t[]){0x5F}, 1, 0}, - - {0x16, (uint8_t[]){0x5F}, 1, 0}, - {0x17, (uint8_t[]){0x5F}, 1, 0}, - {0x18, (uint8_t[]){0x5E}, 1, 0}, - {0x19, (uint8_t[]){0x5E}, 1, 0}, - {0x1A, (uint8_t[]){0x50}, 1, 0}, - {0x1B, (uint8_t[]){0x49}, 1, 0}, - {0x1C, (uint8_t[]){0x49}, 1, 0}, - {0x1D, (uint8_t[]){0x4B}, 1, 0}, - {0x1E, (uint8_t[]){0x4B}, 1, 0}, - {0x1F, (uint8_t[]){0x45}, 1, 0}, - {0x20, (uint8_t[]){0x45}, 1, 0}, - {0x21, (uint8_t[]){0x47}, 1, 0}, - {0x22, (uint8_t[]){0x47}, 1, 0}, - {0x23, (uint8_t[]){0x5F}, 1, 0}, - {0x24, (uint8_t[]){0x5F}, 1, 0}, - {0x25, (uint8_t[]){0x57}, 1, 0}, - {0x26, (uint8_t[]){0x57}, 1, 0}, - {0x27, (uint8_t[]){0x77}, 1, 0}, - {0x28, (uint8_t[]){0x77}, 1, 0}, - {0x29, (uint8_t[]){0x41}, 1, 0}, - {0x2A, (uint8_t[]){0x43}, 1, 0}, - {0x2B, (uint8_t[]){0x5F}, 1, 0}, - - {0x2C, (uint8_t[]){0x1E}, 1, 0}, - {0x2D, (uint8_t[]){0x1E}, 1, 0}, - {0x2E, (uint8_t[]){0x1F}, 1, 0}, - {0x2F, (uint8_t[]){0x1F}, 1, 0}, - {0x30, (uint8_t[]){0x10}, 1, 0}, - {0x31, (uint8_t[]){0x07}, 1, 0}, - {0x32, (uint8_t[]){0x07}, 1, 0}, - {0x33, (uint8_t[]){0x05}, 1, 0}, - {0x34, (uint8_t[]){0x05}, 1, 0}, - {0x35, (uint8_t[]){0x0B}, 1, 0}, - {0x36, (uint8_t[]){0x0B}, 1, 0}, - {0x37, (uint8_t[]){0x09}, 1, 0}, - {0x38, (uint8_t[]){0x09}, 1, 0}, - {0x39, (uint8_t[]){0x1F}, 1, 0}, - {0x3A, (uint8_t[]){0x1F}, 1, 0}, - {0x3B, (uint8_t[]){0x17}, 1, 0}, - {0x3C, (uint8_t[]){0x17}, 1, 0}, - {0x3D, (uint8_t[]){0x17}, 1, 0}, - {0x3E, (uint8_t[]){0x17}, 1, 0}, - {0x3F, (uint8_t[]){0x03}, 1, 0}, - {0x40, (uint8_t[]){0x01}, 1, 0}, - {0x41, (uint8_t[]){0x1F}, 1, 0}, - - {0x42, (uint8_t[]){0x1E}, 1, 0}, - {0x43, (uint8_t[]){0x1E}, 1, 0}, - {0x44, (uint8_t[]){0x1F}, 1, 0}, - {0x45, (uint8_t[]){0x1F}, 1, 0}, - {0x46, (uint8_t[]){0x10}, 1, 0}, - {0x47, (uint8_t[]){0x06}, 1, 0}, - {0x48, (uint8_t[]){0x06}, 1, 0}, - {0x49, (uint8_t[]){0x04}, 1, 0}, - {0x4A, (uint8_t[]){0x04}, 1, 0}, - {0x4B, (uint8_t[]){0x0A}, 1, 0}, - {0x4C, (uint8_t[]){0x0A}, 1, 0}, - {0x4D, (uint8_t[]){0x08}, 1, 0}, - {0x4E, (uint8_t[]){0x08}, 1, 0}, - {0x4F, (uint8_t[]){0x1F}, 1, 0}, - {0x50, (uint8_t[]){0x1F}, 1, 0}, - {0x51, (uint8_t[]){0x17}, 1, 0}, - {0x52, (uint8_t[]){0x17}, 1, 0}, - {0x53, (uint8_t[]){0x17}, 1, 0}, - {0x54, (uint8_t[]){0x17}, 1, 0}, - {0x55, (uint8_t[]){0x02}, 1, 0}, - {0x56, (uint8_t[]){0x00}, 1, 0}, - {0x57, (uint8_t[]){0x1F}, 1, 0}, - - {0xE0, (uint8_t[]){0x02}, 1, 0}, - {0x58, (uint8_t[]){0x40}, 1, 0}, - {0x59, (uint8_t[]){0x00}, 1, 0}, - {0x5A, (uint8_t[]){0x00}, 1, 0}, - {0x5B, (uint8_t[]){0x30}, 1, 0}, - {0x5C, (uint8_t[]){0x01}, 1, 0}, - {0x5D, (uint8_t[]){0x30}, 1, 0}, - {0x5E, (uint8_t[]){0x01}, 1, 0}, - {0x5F, (uint8_t[]){0x02}, 1, 0}, - {0x60, (uint8_t[]){0x30}, 1, 0}, - {0x61, (uint8_t[]){0x03}, 1, 0}, - {0x62, (uint8_t[]){0x04}, 1, 0}, - {0x63, (uint8_t[]){0x04}, 1, 0}, - {0x64, (uint8_t[]){0xA6}, 1, 0}, - {0x65, (uint8_t[]){0x43}, 1, 0}, - {0x66, (uint8_t[]){0x30}, 1, 0}, - {0x67, (uint8_t[]){0x73}, 1, 0}, - {0x68, (uint8_t[]){0x05}, 1, 0}, - {0x69, (uint8_t[]){0x04}, 1, 0}, - {0x6A, (uint8_t[]){0x7F}, 1, 0}, - {0x6B, (uint8_t[]){0x08}, 1, 0}, - {0x6C, (uint8_t[]){0x00}, 1, 0}, - {0x6D, (uint8_t[]){0x04}, 1, 0}, - {0x6E, (uint8_t[]){0x04}, 1, 0}, - {0x6F, (uint8_t[]){0x88}, 1, 0}, - - {0x75, (uint8_t[]){0xD9}, 1, 0}, - {0x76, (uint8_t[]){0x00}, 1, 0}, - {0x77, (uint8_t[]){0x33}, 1, 0}, - {0x78, (uint8_t[]){0x43}, 1, 0}, - - {0xE0, (uint8_t[]){0x00}, 1, 0}, - - {0x11, (uint8_t[]){0x00}, 1, 120}, - - {0x29, (uint8_t[]){0x00}, 1, 20}, - {0x35, (uint8_t[]){0x00}, 1, 0}, -}; -#else -#define DISPLAY_WIDTH 720 -#define DISPLAY_HEIGHT 720 -static const jd9365_lcd_init_cmd_t lcd_init_cmds[] = { - {0xE0, (uint8_t[]){0x00}, 1, 0}, - - {0xE1, (uint8_t[]){0x93}, 1, 0}, - {0xE2, (uint8_t[]){0x65}, 1, 0}, - {0xE3, (uint8_t[]){0xF8}, 1, 0}, - {0x80, (uint8_t[]){0x01}, 1, 0}, - - {0xE0, (uint8_t[]){0x01}, 1, 0}, - - {0x00, (uint8_t[]){0x00}, 1, 0}, - {0x01, (uint8_t[]){0x41}, 1, 0}, - {0x03, (uint8_t[]){0x10}, 1, 0}, - {0x04, (uint8_t[]){0x44}, 1, 0}, - - {0x17, (uint8_t[]){0x00}, 1, 0}, - {0x18, (uint8_t[]){0xD0}, 1, 0}, - {0x19, (uint8_t[]){0x00}, 1, 0}, - {0x1A, (uint8_t[]){0x00}, 1, 0}, - {0x1B, (uint8_t[]){0xD0}, 1, 0}, - {0x1C, (uint8_t[]){0x00}, 1, 0}, - - {0x24, (uint8_t[]){0xFE}, 1, 0}, - {0x35, (uint8_t[]){0x26}, 1, 0}, - - {0x37, (uint8_t[]){0x09}, 1, 0}, - - {0x38, (uint8_t[]){0x04}, 1, 0}, - {0x39, (uint8_t[]){0x08}, 1, 0}, - {0x3A, (uint8_t[]){0x0A}, 1, 0}, - {0x3C, (uint8_t[]){0x78}, 1, 0}, - {0x3D, (uint8_t[]){0xFF}, 1, 0}, - {0x3E, (uint8_t[]){0xFF}, 1, 0}, - {0x3F, (uint8_t[]){0xFF}, 1, 0}, - - {0x40, (uint8_t[]){0x04}, 1, 0}, - {0x41, (uint8_t[]){0x64}, 1, 0}, - {0x42, (uint8_t[]){0xC7}, 1, 0}, - {0x43, (uint8_t[]){0x18}, 1, 0}, - {0x44, (uint8_t[]){0x0B}, 1, 0}, - {0x45, (uint8_t[]){0x14}, 1, 0}, - - {0x55, (uint8_t[]){0x02}, 1, 0}, - {0x57, (uint8_t[]){0x49}, 1, 0}, - {0x59, (uint8_t[]){0x0A}, 1, 0}, - {0x5A, (uint8_t[]){0x1B}, 1, 0}, - {0x5B, (uint8_t[]){0x19}, 1, 0}, - - {0x5D, (uint8_t[]){0x7F}, 1, 0}, - {0x5E, (uint8_t[]){0x56}, 1, 0}, - {0x5F, (uint8_t[]){0x43}, 1, 0}, - {0x60, (uint8_t[]){0x37}, 1, 0}, - {0x61, (uint8_t[]){0x33}, 1, 0}, - {0x62, (uint8_t[]){0x25}, 1, 0}, - {0x63, (uint8_t[]){0x2A}, 1, 0}, - {0x64, (uint8_t[]){0x16}, 1, 0}, - {0x65, (uint8_t[]){0x30}, 1, 0}, - {0x66, (uint8_t[]){0x2F}, 1, 0}, - {0x67, (uint8_t[]){0x32}, 1, 0}, - {0x68, (uint8_t[]){0x53}, 1, 0}, - {0x69, (uint8_t[]){0x43}, 1, 0}, - {0x6A, (uint8_t[]){0x4C}, 1, 0}, - {0x6B, (uint8_t[]){0x40}, 1, 0}, - {0x6C, (uint8_t[]){0x3D}, 1, 0}, - {0x6D, (uint8_t[]){0x31}, 1, 0}, - {0x6E, (uint8_t[]){0x20}, 1, 0}, - {0x6F, (uint8_t[]){0x0F}, 1, 0}, - - {0x70, (uint8_t[]){0x7F}, 1, 0}, - {0x71, (uint8_t[]){0x56}, 1, 0}, - {0x72, (uint8_t[]){0x43}, 1, 0}, - {0x73, (uint8_t[]){0x37}, 1, 0}, - {0x74, (uint8_t[]){0x33}, 1, 0}, - {0x75, (uint8_t[]){0x25}, 1, 0}, - {0x76, (uint8_t[]){0x2A}, 1, 0}, - {0x77, (uint8_t[]){0x16}, 1, 0}, - {0x78, (uint8_t[]){0x30}, 1, 0}, - {0x79, (uint8_t[]){0x2F}, 1, 0}, - {0x7A, (uint8_t[]){0x32}, 1, 0}, - {0x7B, (uint8_t[]){0x53}, 1, 0}, - {0x7C, (uint8_t[]){0x43}, 1, 0}, - {0x7D, (uint8_t[]){0x4C}, 1, 0}, - {0x7E, (uint8_t[]){0x40}, 1, 0}, - {0x7F, (uint8_t[]){0x3D}, 1, 0}, - {0x80, (uint8_t[]){0x31}, 1, 0}, - {0x81, (uint8_t[]){0x20}, 1, 0}, - {0x82, (uint8_t[]){0x0F}, 1, 0}, - - {0xE0, (uint8_t[]){0x02}, 1, 0}, - {0x00, (uint8_t[]){0x5F}, 1, 0}, - {0x01, (uint8_t[]){0x5F}, 1, 0}, - {0x02, (uint8_t[]){0x5E}, 1, 0}, - {0x03, (uint8_t[]){0x5E}, 1, 0}, - {0x04, (uint8_t[]){0x50}, 1, 0}, - {0x05, (uint8_t[]){0x48}, 1, 0}, - {0x06, (uint8_t[]){0x48}, 1, 0}, - {0x07, (uint8_t[]){0x4A}, 1, 0}, - {0x08, (uint8_t[]){0x4A}, 1, 0}, - {0x09, (uint8_t[]){0x44}, 1, 0}, - {0x0A, (uint8_t[]){0x44}, 1, 0}, - {0x0B, (uint8_t[]){0x46}, 1, 0}, - {0x0C, (uint8_t[]){0x46}, 1, 0}, - {0x0D, (uint8_t[]){0x5F}, 1, 0}, - {0x0E, (uint8_t[]){0x5F}, 1, 0}, - {0x0F, (uint8_t[]){0x57}, 1, 0}, - {0x10, (uint8_t[]){0x57}, 1, 0}, - {0x11, (uint8_t[]){0x77}, 1, 0}, - {0x12, (uint8_t[]){0x77}, 1, 0}, - {0x13, (uint8_t[]){0x40}, 1, 0}, - {0x14, (uint8_t[]){0x42}, 1, 0}, - {0x15, (uint8_t[]){0x5F}, 1, 0}, - - {0x16, (uint8_t[]){0x5F}, 1, 0}, - {0x17, (uint8_t[]){0x5F}, 1, 0}, - {0x18, (uint8_t[]){0x5E}, 1, 0}, - {0x19, (uint8_t[]){0x5E}, 1, 0}, - {0x1A, (uint8_t[]){0x50}, 1, 0}, - {0x1B, (uint8_t[]){0x49}, 1, 0}, - {0x1C, (uint8_t[]){0x49}, 1, 0}, - {0x1D, (uint8_t[]){0x4B}, 1, 0}, - {0x1E, (uint8_t[]){0x4B}, 1, 0}, - {0x1F, (uint8_t[]){0x45}, 1, 0}, - {0x20, (uint8_t[]){0x45}, 1, 0}, - {0x21, (uint8_t[]){0x47}, 1, 0}, - {0x22, (uint8_t[]){0x47}, 1, 0}, - {0x23, (uint8_t[]){0x5F}, 1, 0}, - {0x24, (uint8_t[]){0x5F}, 1, 0}, - {0x25, (uint8_t[]){0x57}, 1, 0}, - {0x26, (uint8_t[]){0x57}, 1, 0}, - {0x27, (uint8_t[]){0x77}, 1, 0}, - {0x28, (uint8_t[]){0x77}, 1, 0}, - {0x29, (uint8_t[]){0x41}, 1, 0}, - {0x2A, (uint8_t[]){0x43}, 1, 0}, - {0x2B, (uint8_t[]){0x5F}, 1, 0}, - - {0x2C, (uint8_t[]){0x1E}, 1, 0}, - {0x2D, (uint8_t[]){0x1E}, 1, 0}, - {0x2E, (uint8_t[]){0x1F}, 1, 0}, - {0x2F, (uint8_t[]){0x1F}, 1, 0}, - {0x30, (uint8_t[]){0x10}, 1, 0}, - {0x31, (uint8_t[]){0x07}, 1, 0}, - {0x32, (uint8_t[]){0x07}, 1, 0}, - {0x33, (uint8_t[]){0x05}, 1, 0}, - {0x34, (uint8_t[]){0x05}, 1, 0}, - {0x35, (uint8_t[]){0x0B}, 1, 0}, - {0x36, (uint8_t[]){0x0B}, 1, 0}, - {0x37, (uint8_t[]){0x09}, 1, 0}, - {0x38, (uint8_t[]){0x09}, 1, 0}, - {0x39, (uint8_t[]){0x1F}, 1, 0}, - {0x3A, (uint8_t[]){0x1F}, 1, 0}, - {0x3B, (uint8_t[]){0x17}, 1, 0}, - {0x3C, (uint8_t[]){0x17}, 1, 0}, - {0x3D, (uint8_t[]){0x17}, 1, 0}, - {0x3E, (uint8_t[]){0x17}, 1, 0}, - {0x3F, (uint8_t[]){0x03}, 1, 0}, - {0x40, (uint8_t[]){0x01}, 1, 0}, - {0x41, (uint8_t[]){0x1F}, 1, 0}, - - {0x42, (uint8_t[]){0x1E}, 1, 0}, - {0x43, (uint8_t[]){0x1E}, 1, 0}, - {0x44, (uint8_t[]){0x1F}, 1, 0}, - {0x45, (uint8_t[]){0x1F}, 1, 0}, - {0x46, (uint8_t[]){0x10}, 1, 0}, - {0x47, (uint8_t[]){0x06}, 1, 0}, - {0x48, (uint8_t[]){0x06}, 1, 0}, - {0x49, (uint8_t[]){0x04}, 1, 0}, - {0x4A, (uint8_t[]){0x04}, 1, 0}, - {0x4B, (uint8_t[]){0x0A}, 1, 0}, - {0x4C, (uint8_t[]){0x0A}, 1, 0}, - {0x4D, (uint8_t[]){0x08}, 1, 0}, - {0x4E, (uint8_t[]){0x08}, 1, 0}, - {0x4F, (uint8_t[]){0x1F}, 1, 0}, - {0x50, (uint8_t[]){0x1F}, 1, 0}, - {0x51, (uint8_t[]){0x17}, 1, 0}, - {0x52, (uint8_t[]){0x17}, 1, 0}, - {0x53, (uint8_t[]){0x17}, 1, 0}, - {0x54, (uint8_t[]){0x17}, 1, 0}, - {0x55, (uint8_t[]){0x02}, 1, 0}, - {0x56, (uint8_t[]){0x00}, 1, 0}, - {0x57, (uint8_t[]){0x1F}, 1, 0}, - - {0xE0, (uint8_t[]){0x02}, 1, 0}, - {0x58, (uint8_t[]){0x40}, 1, 0}, - {0x59, (uint8_t[]){0x00}, 1, 0}, - {0x5A, (uint8_t[]){0x00}, 1, 0}, - {0x5B, (uint8_t[]){0x30}, 1, 0}, - {0x5C, (uint8_t[]){0x01}, 1, 0}, - {0x5D, (uint8_t[]){0x30}, 1, 0}, - {0x5E, (uint8_t[]){0x01}, 1, 0}, - {0x5F, (uint8_t[]){0x02}, 1, 0}, - {0x60, (uint8_t[]){0x30}, 1, 0}, - {0x61, (uint8_t[]){0x03}, 1, 0}, - {0x62, (uint8_t[]){0x04}, 1, 0}, - {0x63, (uint8_t[]){0x04}, 1, 0}, - {0x64, (uint8_t[]){0xA6}, 1, 0}, - {0x65, (uint8_t[]){0x43}, 1, 0}, - {0x66, (uint8_t[]){0x30}, 1, 0}, - {0x67, (uint8_t[]){0x73}, 1, 0}, - {0x68, (uint8_t[]){0x05}, 1, 0}, - {0x69, (uint8_t[]){0x04}, 1, 0}, - {0x6A, (uint8_t[]){0x7F}, 1, 0}, - {0x6B, (uint8_t[]){0x08}, 1, 0}, - {0x6C, (uint8_t[]){0x00}, 1, 0}, - {0x6D, (uint8_t[]){0x04}, 1, 0}, - {0x6E, (uint8_t[]){0x04}, 1, 0}, - {0x6F, (uint8_t[]){0x88}, 1, 0}, - - {0x75, (uint8_t[]){0xD9}, 1, 0}, - {0x76, (uint8_t[]){0x00}, 1, 0}, - {0x77, (uint8_t[]){0x33}, 1, 0}, - {0x78, (uint8_t[]){0x43}, 1, 0}, - - {0xE0, (uint8_t[]){0x00}, 1, 0}, - {0x11, (uint8_t[]){0x00}, 1, 120}, - - {0x29, (uint8_t[]){0x00}, 1, 20}, - {0x35, (uint8_t[]){0x00}, 1, 0}, -}; -#endif - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_10 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_12 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_53 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_GPIO GPIO_NUM_35 + +#define LCD_BIT_PER_PIXEL (16) +#define PIN_NUM_LCD_RST GPIO_NUM_27 + +#define DELAY_TIME_MS (3000) +#define LCD_MIPI_DSI_LANE_NUM (2) // 2 data lanes + +#define MIPI_DSI_PHY_PWR_LDO_CHAN (3) +#define MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500) + +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_26 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +#if CONFIG_LCD_TYPE_800_800_3_4_INCH +#define DISPLAY_WIDTH 800 +#define DISPLAY_HEIGHT 800 + +static const jd9365_lcd_init_cmd_t lcd_init_cmds[] = { + {0xE0, (uint8_t[]){0x00}, 1, 0}, + + {0xE1, (uint8_t[]){0x93}, 1, 0}, + {0xE2, (uint8_t[]){0x65}, 1, 0}, + {0xE3, (uint8_t[]){0xF8}, 1, 0}, + {0x80, (uint8_t[]){0x01}, 1, 0}, + + {0xE0, (uint8_t[]){0x01}, 1, 0}, + + {0x00, (uint8_t[]){0x00}, 1, 0}, + {0x01, (uint8_t[]){0x41}, 1, 0}, + {0x03, (uint8_t[]){0x10}, 1, 0}, + {0x04, (uint8_t[]){0x44}, 1, 0}, + + {0x17, (uint8_t[]){0x00}, 1, 0}, + {0x18, (uint8_t[]){0xD0}, 1, 0}, + {0x19, (uint8_t[]){0x00}, 1, 0}, + {0x1A, (uint8_t[]){0x00}, 1, 0}, + {0x1B, (uint8_t[]){0xD0}, 1, 0}, + {0x1C, (uint8_t[]){0x00}, 1, 0}, + + {0x24, (uint8_t[]){0xFE}, 1, 0}, + {0x35, (uint8_t[]){0x26}, 1, 0}, + + {0x37, (uint8_t[]){0x09}, 1, 0}, + + {0x38, (uint8_t[]){0x04}, 1, 0}, + {0x39, (uint8_t[]){0x08}, 1, 0}, + {0x3A, (uint8_t[]){0x0A}, 1, 0}, + {0x3C, (uint8_t[]){0x78}, 1, 0}, + {0x3D, (uint8_t[]){0xFF}, 1, 0}, + {0x3E, (uint8_t[]){0xFF}, 1, 0}, + {0x3F, (uint8_t[]){0xFF}, 1, 0}, + + {0x40, (uint8_t[]){0x00}, 1, 0}, + {0x41, (uint8_t[]){0x64}, 1, 0}, + {0x42, (uint8_t[]){0xC7}, 1, 0}, + {0x43, (uint8_t[]){0x18}, 1, 0}, + {0x44, (uint8_t[]){0x0B}, 1, 0}, + {0x45, (uint8_t[]){0x14}, 1, 0}, + + {0x55, (uint8_t[]){0x02}, 1, 0}, + {0x57, (uint8_t[]){0x49}, 1, 0}, + {0x59, (uint8_t[]){0x0A}, 1, 0}, + {0x5A, (uint8_t[]){0x1B}, 1, 0}, + {0x5B, (uint8_t[]){0x19}, 1, 0}, + + {0x5D, (uint8_t[]){0x7F}, 1, 0}, + {0x5E, (uint8_t[]){0x56}, 1, 0}, + {0x5F, (uint8_t[]){0x43}, 1, 0}, + {0x60, (uint8_t[]){0x37}, 1, 0}, + {0x61, (uint8_t[]){0x33}, 1, 0}, + {0x62, (uint8_t[]){0x25}, 1, 0}, + {0x63, (uint8_t[]){0x2A}, 1, 0}, + {0x64, (uint8_t[]){0x16}, 1, 0}, + {0x65, (uint8_t[]){0x30}, 1, 0}, + {0x66, (uint8_t[]){0x2F}, 1, 0}, + {0x67, (uint8_t[]){0x32}, 1, 0}, + {0x68, (uint8_t[]){0x53}, 1, 0}, + {0x69, (uint8_t[]){0x43}, 1, 0}, + {0x6A, (uint8_t[]){0x4C}, 1, 0}, + {0x6B, (uint8_t[]){0x40}, 1, 0}, + {0x6C, (uint8_t[]){0x3D}, 1, 0}, + {0x6D, (uint8_t[]){0x31}, 1, 0}, + {0x6E, (uint8_t[]){0x20}, 1, 0}, + {0x6F, (uint8_t[]){0x0F}, 1, 0}, + + {0x70, (uint8_t[]){0x7F}, 1, 0}, + {0x71, (uint8_t[]){0x56}, 1, 0}, + {0x72, (uint8_t[]){0x43}, 1, 0}, + {0x73, (uint8_t[]){0x37}, 1, 0}, + {0x74, (uint8_t[]){0x33}, 1, 0}, + {0x75, (uint8_t[]){0x25}, 1, 0}, + {0x76, (uint8_t[]){0x2A}, 1, 0}, + {0x77, (uint8_t[]){0x16}, 1, 0}, + {0x78, (uint8_t[]){0x30}, 1, 0}, + {0x79, (uint8_t[]){0x2F}, 1, 0}, + {0x7A, (uint8_t[]){0x32}, 1, 0}, + {0x7B, (uint8_t[]){0x53}, 1, 0}, + {0x7C, (uint8_t[]){0x43}, 1, 0}, + {0x7D, (uint8_t[]){0x4C}, 1, 0}, + {0x7E, (uint8_t[]){0x40}, 1, 0}, + {0x7F, (uint8_t[]){0x3D}, 1, 0}, + {0x80, (uint8_t[]){0x31}, 1, 0}, + {0x81, (uint8_t[]){0x20}, 1, 0}, + {0x82, (uint8_t[]){0x0F}, 1, 0}, + + {0xE0, (uint8_t[]){0x02}, 1, 0}, + {0x00, (uint8_t[]){0x5F}, 1, 0}, + {0x01, (uint8_t[]){0x5F}, 1, 0}, + {0x02, (uint8_t[]){0x5E}, 1, 0}, + {0x03, (uint8_t[]){0x5E}, 1, 0}, + {0x04, (uint8_t[]){0x50}, 1, 0}, + {0x05, (uint8_t[]){0x48}, 1, 0}, + {0x06, (uint8_t[]){0x48}, 1, 0}, + {0x07, (uint8_t[]){0x4A}, 1, 0}, + {0x08, (uint8_t[]){0x4A}, 1, 0}, + {0x09, (uint8_t[]){0x44}, 1, 0}, + {0x0A, (uint8_t[]){0x44}, 1, 0}, + {0x0B, (uint8_t[]){0x46}, 1, 0}, + {0x0C, (uint8_t[]){0x46}, 1, 0}, + {0x0D, (uint8_t[]){0x5F}, 1, 0}, + {0x0E, (uint8_t[]){0x5F}, 1, 0}, + {0x0F, (uint8_t[]){0x57}, 1, 0}, + {0x10, (uint8_t[]){0x57}, 1, 0}, + {0x11, (uint8_t[]){0x77}, 1, 0}, + {0x12, (uint8_t[]){0x77}, 1, 0}, + {0x13, (uint8_t[]){0x40}, 1, 0}, + {0x14, (uint8_t[]){0x42}, 1, 0}, + {0x15, (uint8_t[]){0x5F}, 1, 0}, + + {0x16, (uint8_t[]){0x5F}, 1, 0}, + {0x17, (uint8_t[]){0x5F}, 1, 0}, + {0x18, (uint8_t[]){0x5E}, 1, 0}, + {0x19, (uint8_t[]){0x5E}, 1, 0}, + {0x1A, (uint8_t[]){0x50}, 1, 0}, + {0x1B, (uint8_t[]){0x49}, 1, 0}, + {0x1C, (uint8_t[]){0x49}, 1, 0}, + {0x1D, (uint8_t[]){0x4B}, 1, 0}, + {0x1E, (uint8_t[]){0x4B}, 1, 0}, + {0x1F, (uint8_t[]){0x45}, 1, 0}, + {0x20, (uint8_t[]){0x45}, 1, 0}, + {0x21, (uint8_t[]){0x47}, 1, 0}, + {0x22, (uint8_t[]){0x47}, 1, 0}, + {0x23, (uint8_t[]){0x5F}, 1, 0}, + {0x24, (uint8_t[]){0x5F}, 1, 0}, + {0x25, (uint8_t[]){0x57}, 1, 0}, + {0x26, (uint8_t[]){0x57}, 1, 0}, + {0x27, (uint8_t[]){0x77}, 1, 0}, + {0x28, (uint8_t[]){0x77}, 1, 0}, + {0x29, (uint8_t[]){0x41}, 1, 0}, + {0x2A, (uint8_t[]){0x43}, 1, 0}, + {0x2B, (uint8_t[]){0x5F}, 1, 0}, + + {0x2C, (uint8_t[]){0x1E}, 1, 0}, + {0x2D, (uint8_t[]){0x1E}, 1, 0}, + {0x2E, (uint8_t[]){0x1F}, 1, 0}, + {0x2F, (uint8_t[]){0x1F}, 1, 0}, + {0x30, (uint8_t[]){0x10}, 1, 0}, + {0x31, (uint8_t[]){0x07}, 1, 0}, + {0x32, (uint8_t[]){0x07}, 1, 0}, + {0x33, (uint8_t[]){0x05}, 1, 0}, + {0x34, (uint8_t[]){0x05}, 1, 0}, + {0x35, (uint8_t[]){0x0B}, 1, 0}, + {0x36, (uint8_t[]){0x0B}, 1, 0}, + {0x37, (uint8_t[]){0x09}, 1, 0}, + {0x38, (uint8_t[]){0x09}, 1, 0}, + {0x39, (uint8_t[]){0x1F}, 1, 0}, + {0x3A, (uint8_t[]){0x1F}, 1, 0}, + {0x3B, (uint8_t[]){0x17}, 1, 0}, + {0x3C, (uint8_t[]){0x17}, 1, 0}, + {0x3D, (uint8_t[]){0x17}, 1, 0}, + {0x3E, (uint8_t[]){0x17}, 1, 0}, + {0x3F, (uint8_t[]){0x03}, 1, 0}, + {0x40, (uint8_t[]){0x01}, 1, 0}, + {0x41, (uint8_t[]){0x1F}, 1, 0}, + + {0x42, (uint8_t[]){0x1E}, 1, 0}, + {0x43, (uint8_t[]){0x1E}, 1, 0}, + {0x44, (uint8_t[]){0x1F}, 1, 0}, + {0x45, (uint8_t[]){0x1F}, 1, 0}, + {0x46, (uint8_t[]){0x10}, 1, 0}, + {0x47, (uint8_t[]){0x06}, 1, 0}, + {0x48, (uint8_t[]){0x06}, 1, 0}, + {0x49, (uint8_t[]){0x04}, 1, 0}, + {0x4A, (uint8_t[]){0x04}, 1, 0}, + {0x4B, (uint8_t[]){0x0A}, 1, 0}, + {0x4C, (uint8_t[]){0x0A}, 1, 0}, + {0x4D, (uint8_t[]){0x08}, 1, 0}, + {0x4E, (uint8_t[]){0x08}, 1, 0}, + {0x4F, (uint8_t[]){0x1F}, 1, 0}, + {0x50, (uint8_t[]){0x1F}, 1, 0}, + {0x51, (uint8_t[]){0x17}, 1, 0}, + {0x52, (uint8_t[]){0x17}, 1, 0}, + {0x53, (uint8_t[]){0x17}, 1, 0}, + {0x54, (uint8_t[]){0x17}, 1, 0}, + {0x55, (uint8_t[]){0x02}, 1, 0}, + {0x56, (uint8_t[]){0x00}, 1, 0}, + {0x57, (uint8_t[]){0x1F}, 1, 0}, + + {0xE0, (uint8_t[]){0x02}, 1, 0}, + {0x58, (uint8_t[]){0x40}, 1, 0}, + {0x59, (uint8_t[]){0x00}, 1, 0}, + {0x5A, (uint8_t[]){0x00}, 1, 0}, + {0x5B, (uint8_t[]){0x30}, 1, 0}, + {0x5C, (uint8_t[]){0x01}, 1, 0}, + {0x5D, (uint8_t[]){0x30}, 1, 0}, + {0x5E, (uint8_t[]){0x01}, 1, 0}, + {0x5F, (uint8_t[]){0x02}, 1, 0}, + {0x60, (uint8_t[]){0x30}, 1, 0}, + {0x61, (uint8_t[]){0x03}, 1, 0}, + {0x62, (uint8_t[]){0x04}, 1, 0}, + {0x63, (uint8_t[]){0x04}, 1, 0}, + {0x64, (uint8_t[]){0xA6}, 1, 0}, + {0x65, (uint8_t[]){0x43}, 1, 0}, + {0x66, (uint8_t[]){0x30}, 1, 0}, + {0x67, (uint8_t[]){0x73}, 1, 0}, + {0x68, (uint8_t[]){0x05}, 1, 0}, + {0x69, (uint8_t[]){0x04}, 1, 0}, + {0x6A, (uint8_t[]){0x7F}, 1, 0}, + {0x6B, (uint8_t[]){0x08}, 1, 0}, + {0x6C, (uint8_t[]){0x00}, 1, 0}, + {0x6D, (uint8_t[]){0x04}, 1, 0}, + {0x6E, (uint8_t[]){0x04}, 1, 0}, + {0x6F, (uint8_t[]){0x88}, 1, 0}, + + {0x75, (uint8_t[]){0xD9}, 1, 0}, + {0x76, (uint8_t[]){0x00}, 1, 0}, + {0x77, (uint8_t[]){0x33}, 1, 0}, + {0x78, (uint8_t[]){0x43}, 1, 0}, + + {0xE0, (uint8_t[]){0x00}, 1, 0}, + + {0x11, (uint8_t[]){0x00}, 1, 120}, + + {0x29, (uint8_t[]){0x00}, 1, 20}, + {0x35, (uint8_t[]){0x00}, 1, 0}, +}; +#else +#define DISPLAY_WIDTH 720 +#define DISPLAY_HEIGHT 720 +static const jd9365_lcd_init_cmd_t lcd_init_cmds[] = { + {0xE0, (uint8_t[]){0x00}, 1, 0}, + + {0xE1, (uint8_t[]){0x93}, 1, 0}, + {0xE2, (uint8_t[]){0x65}, 1, 0}, + {0xE3, (uint8_t[]){0xF8}, 1, 0}, + {0x80, (uint8_t[]){0x01}, 1, 0}, + + {0xE0, (uint8_t[]){0x01}, 1, 0}, + + {0x00, (uint8_t[]){0x00}, 1, 0}, + {0x01, (uint8_t[]){0x41}, 1, 0}, + {0x03, (uint8_t[]){0x10}, 1, 0}, + {0x04, (uint8_t[]){0x44}, 1, 0}, + + {0x17, (uint8_t[]){0x00}, 1, 0}, + {0x18, (uint8_t[]){0xD0}, 1, 0}, + {0x19, (uint8_t[]){0x00}, 1, 0}, + {0x1A, (uint8_t[]){0x00}, 1, 0}, + {0x1B, (uint8_t[]){0xD0}, 1, 0}, + {0x1C, (uint8_t[]){0x00}, 1, 0}, + + {0x24, (uint8_t[]){0xFE}, 1, 0}, + {0x35, (uint8_t[]){0x26}, 1, 0}, + + {0x37, (uint8_t[]){0x09}, 1, 0}, + + {0x38, (uint8_t[]){0x04}, 1, 0}, + {0x39, (uint8_t[]){0x08}, 1, 0}, + {0x3A, (uint8_t[]){0x0A}, 1, 0}, + {0x3C, (uint8_t[]){0x78}, 1, 0}, + {0x3D, (uint8_t[]){0xFF}, 1, 0}, + {0x3E, (uint8_t[]){0xFF}, 1, 0}, + {0x3F, (uint8_t[]){0xFF}, 1, 0}, + + {0x40, (uint8_t[]){0x04}, 1, 0}, + {0x41, (uint8_t[]){0x64}, 1, 0}, + {0x42, (uint8_t[]){0xC7}, 1, 0}, + {0x43, (uint8_t[]){0x18}, 1, 0}, + {0x44, (uint8_t[]){0x0B}, 1, 0}, + {0x45, (uint8_t[]){0x14}, 1, 0}, + + {0x55, (uint8_t[]){0x02}, 1, 0}, + {0x57, (uint8_t[]){0x49}, 1, 0}, + {0x59, (uint8_t[]){0x0A}, 1, 0}, + {0x5A, (uint8_t[]){0x1B}, 1, 0}, + {0x5B, (uint8_t[]){0x19}, 1, 0}, + + {0x5D, (uint8_t[]){0x7F}, 1, 0}, + {0x5E, (uint8_t[]){0x56}, 1, 0}, + {0x5F, (uint8_t[]){0x43}, 1, 0}, + {0x60, (uint8_t[]){0x37}, 1, 0}, + {0x61, (uint8_t[]){0x33}, 1, 0}, + {0x62, (uint8_t[]){0x25}, 1, 0}, + {0x63, (uint8_t[]){0x2A}, 1, 0}, + {0x64, (uint8_t[]){0x16}, 1, 0}, + {0x65, (uint8_t[]){0x30}, 1, 0}, + {0x66, (uint8_t[]){0x2F}, 1, 0}, + {0x67, (uint8_t[]){0x32}, 1, 0}, + {0x68, (uint8_t[]){0x53}, 1, 0}, + {0x69, (uint8_t[]){0x43}, 1, 0}, + {0x6A, (uint8_t[]){0x4C}, 1, 0}, + {0x6B, (uint8_t[]){0x40}, 1, 0}, + {0x6C, (uint8_t[]){0x3D}, 1, 0}, + {0x6D, (uint8_t[]){0x31}, 1, 0}, + {0x6E, (uint8_t[]){0x20}, 1, 0}, + {0x6F, (uint8_t[]){0x0F}, 1, 0}, + + {0x70, (uint8_t[]){0x7F}, 1, 0}, + {0x71, (uint8_t[]){0x56}, 1, 0}, + {0x72, (uint8_t[]){0x43}, 1, 0}, + {0x73, (uint8_t[]){0x37}, 1, 0}, + {0x74, (uint8_t[]){0x33}, 1, 0}, + {0x75, (uint8_t[]){0x25}, 1, 0}, + {0x76, (uint8_t[]){0x2A}, 1, 0}, + {0x77, (uint8_t[]){0x16}, 1, 0}, + {0x78, (uint8_t[]){0x30}, 1, 0}, + {0x79, (uint8_t[]){0x2F}, 1, 0}, + {0x7A, (uint8_t[]){0x32}, 1, 0}, + {0x7B, (uint8_t[]){0x53}, 1, 0}, + {0x7C, (uint8_t[]){0x43}, 1, 0}, + {0x7D, (uint8_t[]){0x4C}, 1, 0}, + {0x7E, (uint8_t[]){0x40}, 1, 0}, + {0x7F, (uint8_t[]){0x3D}, 1, 0}, + {0x80, (uint8_t[]){0x31}, 1, 0}, + {0x81, (uint8_t[]){0x20}, 1, 0}, + {0x82, (uint8_t[]){0x0F}, 1, 0}, + + {0xE0, (uint8_t[]){0x02}, 1, 0}, + {0x00, (uint8_t[]){0x5F}, 1, 0}, + {0x01, (uint8_t[]){0x5F}, 1, 0}, + {0x02, (uint8_t[]){0x5E}, 1, 0}, + {0x03, (uint8_t[]){0x5E}, 1, 0}, + {0x04, (uint8_t[]){0x50}, 1, 0}, + {0x05, (uint8_t[]){0x48}, 1, 0}, + {0x06, (uint8_t[]){0x48}, 1, 0}, + {0x07, (uint8_t[]){0x4A}, 1, 0}, + {0x08, (uint8_t[]){0x4A}, 1, 0}, + {0x09, (uint8_t[]){0x44}, 1, 0}, + {0x0A, (uint8_t[]){0x44}, 1, 0}, + {0x0B, (uint8_t[]){0x46}, 1, 0}, + {0x0C, (uint8_t[]){0x46}, 1, 0}, + {0x0D, (uint8_t[]){0x5F}, 1, 0}, + {0x0E, (uint8_t[]){0x5F}, 1, 0}, + {0x0F, (uint8_t[]){0x57}, 1, 0}, + {0x10, (uint8_t[]){0x57}, 1, 0}, + {0x11, (uint8_t[]){0x77}, 1, 0}, + {0x12, (uint8_t[]){0x77}, 1, 0}, + {0x13, (uint8_t[]){0x40}, 1, 0}, + {0x14, (uint8_t[]){0x42}, 1, 0}, + {0x15, (uint8_t[]){0x5F}, 1, 0}, + + {0x16, (uint8_t[]){0x5F}, 1, 0}, + {0x17, (uint8_t[]){0x5F}, 1, 0}, + {0x18, (uint8_t[]){0x5E}, 1, 0}, + {0x19, (uint8_t[]){0x5E}, 1, 0}, + {0x1A, (uint8_t[]){0x50}, 1, 0}, + {0x1B, (uint8_t[]){0x49}, 1, 0}, + {0x1C, (uint8_t[]){0x49}, 1, 0}, + {0x1D, (uint8_t[]){0x4B}, 1, 0}, + {0x1E, (uint8_t[]){0x4B}, 1, 0}, + {0x1F, (uint8_t[]){0x45}, 1, 0}, + {0x20, (uint8_t[]){0x45}, 1, 0}, + {0x21, (uint8_t[]){0x47}, 1, 0}, + {0x22, (uint8_t[]){0x47}, 1, 0}, + {0x23, (uint8_t[]){0x5F}, 1, 0}, + {0x24, (uint8_t[]){0x5F}, 1, 0}, + {0x25, (uint8_t[]){0x57}, 1, 0}, + {0x26, (uint8_t[]){0x57}, 1, 0}, + {0x27, (uint8_t[]){0x77}, 1, 0}, + {0x28, (uint8_t[]){0x77}, 1, 0}, + {0x29, (uint8_t[]){0x41}, 1, 0}, + {0x2A, (uint8_t[]){0x43}, 1, 0}, + {0x2B, (uint8_t[]){0x5F}, 1, 0}, + + {0x2C, (uint8_t[]){0x1E}, 1, 0}, + {0x2D, (uint8_t[]){0x1E}, 1, 0}, + {0x2E, (uint8_t[]){0x1F}, 1, 0}, + {0x2F, (uint8_t[]){0x1F}, 1, 0}, + {0x30, (uint8_t[]){0x10}, 1, 0}, + {0x31, (uint8_t[]){0x07}, 1, 0}, + {0x32, (uint8_t[]){0x07}, 1, 0}, + {0x33, (uint8_t[]){0x05}, 1, 0}, + {0x34, (uint8_t[]){0x05}, 1, 0}, + {0x35, (uint8_t[]){0x0B}, 1, 0}, + {0x36, (uint8_t[]){0x0B}, 1, 0}, + {0x37, (uint8_t[]){0x09}, 1, 0}, + {0x38, (uint8_t[]){0x09}, 1, 0}, + {0x39, (uint8_t[]){0x1F}, 1, 0}, + {0x3A, (uint8_t[]){0x1F}, 1, 0}, + {0x3B, (uint8_t[]){0x17}, 1, 0}, + {0x3C, (uint8_t[]){0x17}, 1, 0}, + {0x3D, (uint8_t[]){0x17}, 1, 0}, + {0x3E, (uint8_t[]){0x17}, 1, 0}, + {0x3F, (uint8_t[]){0x03}, 1, 0}, + {0x40, (uint8_t[]){0x01}, 1, 0}, + {0x41, (uint8_t[]){0x1F}, 1, 0}, + + {0x42, (uint8_t[]){0x1E}, 1, 0}, + {0x43, (uint8_t[]){0x1E}, 1, 0}, + {0x44, (uint8_t[]){0x1F}, 1, 0}, + {0x45, (uint8_t[]){0x1F}, 1, 0}, + {0x46, (uint8_t[]){0x10}, 1, 0}, + {0x47, (uint8_t[]){0x06}, 1, 0}, + {0x48, (uint8_t[]){0x06}, 1, 0}, + {0x49, (uint8_t[]){0x04}, 1, 0}, + {0x4A, (uint8_t[]){0x04}, 1, 0}, + {0x4B, (uint8_t[]){0x0A}, 1, 0}, + {0x4C, (uint8_t[]){0x0A}, 1, 0}, + {0x4D, (uint8_t[]){0x08}, 1, 0}, + {0x4E, (uint8_t[]){0x08}, 1, 0}, + {0x4F, (uint8_t[]){0x1F}, 1, 0}, + {0x50, (uint8_t[]){0x1F}, 1, 0}, + {0x51, (uint8_t[]){0x17}, 1, 0}, + {0x52, (uint8_t[]){0x17}, 1, 0}, + {0x53, (uint8_t[]){0x17}, 1, 0}, + {0x54, (uint8_t[]){0x17}, 1, 0}, + {0x55, (uint8_t[]){0x02}, 1, 0}, + {0x56, (uint8_t[]){0x00}, 1, 0}, + {0x57, (uint8_t[]){0x1F}, 1, 0}, + + {0xE0, (uint8_t[]){0x02}, 1, 0}, + {0x58, (uint8_t[]){0x40}, 1, 0}, + {0x59, (uint8_t[]){0x00}, 1, 0}, + {0x5A, (uint8_t[]){0x00}, 1, 0}, + {0x5B, (uint8_t[]){0x30}, 1, 0}, + {0x5C, (uint8_t[]){0x01}, 1, 0}, + {0x5D, (uint8_t[]){0x30}, 1, 0}, + {0x5E, (uint8_t[]){0x01}, 1, 0}, + {0x5F, (uint8_t[]){0x02}, 1, 0}, + {0x60, (uint8_t[]){0x30}, 1, 0}, + {0x61, (uint8_t[]){0x03}, 1, 0}, + {0x62, (uint8_t[]){0x04}, 1, 0}, + {0x63, (uint8_t[]){0x04}, 1, 0}, + {0x64, (uint8_t[]){0xA6}, 1, 0}, + {0x65, (uint8_t[]){0x43}, 1, 0}, + {0x66, (uint8_t[]){0x30}, 1, 0}, + {0x67, (uint8_t[]){0x73}, 1, 0}, + {0x68, (uint8_t[]){0x05}, 1, 0}, + {0x69, (uint8_t[]){0x04}, 1, 0}, + {0x6A, (uint8_t[]){0x7F}, 1, 0}, + {0x6B, (uint8_t[]){0x08}, 1, 0}, + {0x6C, (uint8_t[]){0x00}, 1, 0}, + {0x6D, (uint8_t[]){0x04}, 1, 0}, + {0x6E, (uint8_t[]){0x04}, 1, 0}, + {0x6F, (uint8_t[]){0x88}, 1, 0}, + + {0x75, (uint8_t[]){0xD9}, 1, 0}, + {0x76, (uint8_t[]){0x00}, 1, 0}, + {0x77, (uint8_t[]){0x33}, 1, 0}, + {0x78, (uint8_t[]){0x43}, 1, 0}, + + {0xE0, (uint8_t[]){0x00}, 1, 0}, + {0x11, (uint8_t[]){0x00}, 1, 120}, + + {0x29, (uint8_t[]){0x00}, 1, 20}, + {0x35, (uint8_t[]){0x00}, 1, 0}, +}; +#endif + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc b/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc index db8370b..7844f2a 100644 --- a/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc +++ b/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc @@ -1,196 +1,196 @@ -#include "wifi_board.h" -#include "codecs/box_audio_codec.h" -#include "application.h" -#include "display/lcd_display.h" -// #include "display/no_display.h" -#include "button.h" - -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_mipi_dsi.h" -#include "esp_ldo_regulator.h" - -#include "esp_lcd_jd9365_10_1.h" -#include "config.h" - -#include -#include -#include -#include -#include "esp_lcd_touch_gt911.h" -#define TAG "WaveshareEsp32p4xc" - -class WaveshareEsp32p4xc : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Button boot_button_; - LcdDisplay *display_; - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_1, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - static esp_err_t bsp_enable_dsi_phy_power(void) { -#if MIPI_DSI_PHY_PWR_LDO_CHAN > 0 - // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state - static esp_ldo_channel_handle_t phy_pwr_chan = NULL; - esp_ldo_channel_config_t ldo_cfg = { - .chan_id = MIPI_DSI_PHY_PWR_LDO_CHAN, - .voltage_mv = MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, - }; - esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan); - ESP_LOGI(TAG, "MIPI DSI PHY Powered on"); -#endif // BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0 - - return ESP_OK; - } - - void InitializeLCD() { - bsp_enable_dsi_phy_power(); - esp_lcd_panel_io_handle_t io = NULL; - esp_lcd_panel_handle_t disp_panel = NULL; - - esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; - esp_lcd_dsi_bus_config_t bus_config = JD9365_PANEL_BUS_DSI_2CH_CONFIG(); - esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus); - - ESP_LOGI(TAG, "Install MIPI DSI LCD control panel"); - // we use DBI interface to send LCD commands and parameters - esp_lcd_dbi_io_config_t dbi_config = JD9365_PANEL_IO_DBI_CONFIG(); - esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io); - - esp_lcd_dpi_panel_config_t dpi_config = { - .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, - .dpi_clock_freq_mhz = 46, - .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, - .num_fbs = 1, - .video_timing = { - .h_size = DISPLAY_WIDTH, - .v_size = DISPLAY_HEIGHT, - .hsync_pulse_width = 20, - .hsync_back_porch = 20, - .hsync_front_porch = 40, - .vsync_pulse_width = 4, - .vsync_back_porch = 12, - .vsync_front_porch = 24, - }, - .flags = { - .use_dma2d = true, - }, - }; - jd9365_vendor_config_t vendor_config = { - .init_cmds = lcd_init_cmds, - .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), - .mipi_config = { - .dsi_bus = mipi_dsi_bus, - .dpi_config = &dpi_config, - .lane_num = 2, - }, - .flags = { - .use_mipi_interface = 1, - }, - }; - - const esp_lcd_panel_dev_config_t lcd_dev_config = { - .reset_gpio_num = PIN_NUM_LCD_RST, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - .vendor_config = &vendor_config, - }; - esp_lcd_new_panel_jd9365(io, &lcd_dev_config, &disp_panel); - esp_lcd_panel_reset(disp_panel); - esp_lcd_panel_init(disp_panel); - - display_ = new MipiLcdDisplay(io, disp_panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - void InitializeTouch() - { - esp_lcd_touch_handle_t tp; - esp_lcd_touch_config_t tp_cfg = { - .x_max = DISPLAY_WIDTH, - .y_max = DISPLAY_HEIGHT, - .rst_gpio_num = GPIO_NUM_23, - .int_gpio_num = GPIO_NUM_NC, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = 0, - .mirror_x = 0, - .mirror_y = 0, - }, - }; - esp_lcd_panel_io_handle_t tp_io_handle = NULL; - esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); - tp_io_config.scl_speed_hz = 400 * 1000; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); - ESP_LOGI(TAG, "Initialize touch controller"); - ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp)); - const lvgl_port_touch_cfg_t touch_cfg = { - .disp = lv_display_get_default(), - .handle = tp, - }; - lvgl_port_add_touch(&touch_cfg); - ESP_LOGI(TAG, "Touch panel initialized successfully"); - } - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); }); - } - -public: - WaveshareEsp32p4xc() : - boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeLCD(); - InitializeTouch(); - InitializeButtons(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display *GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } -}; - -DECLARE_BOARD(WaveshareEsp32p4xc); +#include "wifi_board.h" +#include "codecs/box_audio_codec.h" +#include "application.h" +#include "display/lcd_display.h" +// #include "display/no_display.h" +#include "button.h" + +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_ldo_regulator.h" + +#include "esp_lcd_jd9365_10_1.h" +#include "config.h" + +#include +#include +#include +#include +#include "esp_lcd_touch_gt911.h" +#define TAG "WaveshareEsp32p4xc" + +class WaveshareEsp32p4xc : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + LcdDisplay *display_; + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + static esp_err_t bsp_enable_dsi_phy_power(void) { +#if MIPI_DSI_PHY_PWR_LDO_CHAN > 0 + // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state + static esp_ldo_channel_handle_t phy_pwr_chan = NULL; + esp_ldo_channel_config_t ldo_cfg = { + .chan_id = MIPI_DSI_PHY_PWR_LDO_CHAN, + .voltage_mv = MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, + }; + esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan); + ESP_LOGI(TAG, "MIPI DSI PHY Powered on"); +#endif // BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0 + + return ESP_OK; + } + + void InitializeLCD() { + bsp_enable_dsi_phy_power(); + esp_lcd_panel_io_handle_t io = NULL; + esp_lcd_panel_handle_t disp_panel = NULL; + + esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; + esp_lcd_dsi_bus_config_t bus_config = JD9365_PANEL_BUS_DSI_2CH_CONFIG(); + esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus); + + ESP_LOGI(TAG, "Install MIPI DSI LCD control panel"); + // we use DBI interface to send LCD commands and parameters + esp_lcd_dbi_io_config_t dbi_config = JD9365_PANEL_IO_DBI_CONFIG(); + esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io); + + esp_lcd_dpi_panel_config_t dpi_config = { + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = 46, + .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, + .num_fbs = 1, + .video_timing = { + .h_size = DISPLAY_WIDTH, + .v_size = DISPLAY_HEIGHT, + .hsync_pulse_width = 20, + .hsync_back_porch = 20, + .hsync_front_porch = 40, + .vsync_pulse_width = 4, + .vsync_back_porch = 12, + .vsync_front_porch = 24, + }, + .flags = { + .use_dma2d = true, + }, + }; + jd9365_vendor_config_t vendor_config = { + .init_cmds = lcd_init_cmds, + .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), + .mipi_config = { + .dsi_bus = mipi_dsi_bus, + .dpi_config = &dpi_config, + .lane_num = 2, + }, + .flags = { + .use_mipi_interface = 1, + }, + }; + + const esp_lcd_panel_dev_config_t lcd_dev_config = { + .reset_gpio_num = PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + .vendor_config = &vendor_config, + }; + esp_lcd_new_panel_jd9365(io, &lcd_dev_config, &disp_panel); + esp_lcd_panel_reset(disp_panel); + esp_lcd_panel_init(disp_panel); + + display_ = new MipiLcdDisplay(io, disp_panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + void InitializeTouch() + { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH, + .y_max = DISPLAY_HEIGHT, + .rst_gpio_num = GPIO_NUM_23, + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); + tp_io_config.scl_speed_hz = 400 * 1000; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); + ESP_LOGI(TAG, "Initialize touch controller"); + ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp)); + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + lvgl_port_add_touch(&touch_cfg); + ESP_LOGI(TAG, "Touch panel initialized successfully"); + } + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); }); + } + +public: + WaveshareEsp32p4xc() : + boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeLCD(); + InitializeTouch(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display *GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(WaveshareEsp32p4xc); diff --git a/main/boards/waveshare-s3-audio-board/README.md b/main/boards/waveshare-s3-audio-board/README.md index dff4acc..48155d8 100644 --- a/main/boards/waveshare-s3-audio-board/README.md +++ b/main/boards/waveshare-s3-audio-board/README.md @@ -1,3 +1,3 @@ -新增 微雪 开发板: ESP32-S3-AUDIO-Board -产品链接: +新增 微雪 开发板: ESP32-S3-AUDIO-Board +产品链接: https://www.waveshare.net/shop/ESP32-S3-AUDIO-Board.htm \ No newline at end of file diff --git a/main/boards/waveshare-s3-audio-board/config.h b/main/boards/waveshare-s3-audio-board/config.h index 1ffa617..7012cd9 100644 --- a/main/boards/waveshare-s3-audio-board/config.h +++ b/main/boards/waveshare-s3-audio-board/config.h @@ -1,95 +1,95 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define BUILTIN_LED_GPIO GPIO_NUM_38 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_12 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_14 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16 -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC -#define AUDIO_INPUT_REFERENCE true -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define I2C_SCL_IO GPIO_NUM_10 -#define I2C_SDA_IO GPIO_NUM_11 - -#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000 - -#define DISPLAY_SDA_PIN I2C_SDA_IO -#define DISPLAY_SCL_PIN I2C_SCL_IO - -#define DISPLAY_MISO_PIN GPIO_NUM_8 -#define DISPLAY_MOSI_PIN GPIO_NUM_9 -#define DISPLAY_SCLK_PIN GPIO_NUM_4 -#define DISPLAY_CS_PIN GPIO_NUM_3 -#define DISPLAY_DC_PIN GPIO_NUM_7 -#define DISPLAY_RESET_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_5 - -#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000) - -/* Camera pins */ -#define CAMERA_PIN_PWDN -1 -#define CAMERA_PIN_RESET -1 -#define CAMERA_PIN_XCLK 43 -#define CAMERA_PIN_SIOD -1 -#define CAMERA_PIN_SIOC -1 - -#define CAMERA_PIN_D7 48 -#define CAMERA_PIN_D6 47 -#define CAMERA_PIN_D5 46 -#define CAMERA_PIN_D4 45 -#define CAMERA_PIN_D3 39 -#define CAMERA_PIN_D2 18 -#define CAMERA_PIN_D1 17 -#define CAMERA_PIN_D0 2 -#define CAMERA_PIN_VSYNC 21 -#define CAMERA_PIN_HREF 1 -#define CAMERA_PIN_PCLK 44 - -#define XCLK_FREQ_HZ 20000000 - - - - -#ifdef CONFIG_AUDIO_BOARD_LCD_JD9853 -#define LCD_TYPE_JD9853_SERIAL -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 172 - -#define DISPLAY_SWAP_XY true -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define DISPLAY_INVERT_COLOR true -#define BACKLIGHT_INVERT false -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#endif - -#ifdef CONFIG_AUDIO_BOARD_LCD_ST7789 -#define LCD_TYPE_ST7789_SERIAL -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 320 - -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_INVERT_COLOR true -#define BACKLIGHT_INVERT false -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#endif - - - +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define BUILTIN_LED_GPIO GPIO_NUM_38 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_12 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_14 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16 +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define I2C_SCL_IO GPIO_NUM_10 +#define I2C_SDA_IO GPIO_NUM_11 + +#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000 + +#define DISPLAY_SDA_PIN I2C_SDA_IO +#define DISPLAY_SCL_PIN I2C_SCL_IO + +#define DISPLAY_MISO_PIN GPIO_NUM_8 +#define DISPLAY_MOSI_PIN GPIO_NUM_9 +#define DISPLAY_SCLK_PIN GPIO_NUM_4 +#define DISPLAY_CS_PIN GPIO_NUM_3 +#define DISPLAY_DC_PIN GPIO_NUM_7 +#define DISPLAY_RESET_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_5 + +#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000) + +/* Camera pins */ +#define CAMERA_PIN_PWDN -1 +#define CAMERA_PIN_RESET -1 +#define CAMERA_PIN_XCLK 43 +#define CAMERA_PIN_SIOD -1 +#define CAMERA_PIN_SIOC -1 + +#define CAMERA_PIN_D7 48 +#define CAMERA_PIN_D6 47 +#define CAMERA_PIN_D5 46 +#define CAMERA_PIN_D4 45 +#define CAMERA_PIN_D3 39 +#define CAMERA_PIN_D2 18 +#define CAMERA_PIN_D1 17 +#define CAMERA_PIN_D0 2 +#define CAMERA_PIN_VSYNC 21 +#define CAMERA_PIN_HREF 1 +#define CAMERA_PIN_PCLK 44 + +#define XCLK_FREQ_HZ 20000000 + + + + +#ifdef CONFIG_AUDIO_BOARD_LCD_JD9853 +#define LCD_TYPE_JD9853_SERIAL +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 172 + +#define DISPLAY_SWAP_XY true +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_INVERT_COLOR true +#define BACKLIGHT_INVERT false +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#endif + +#ifdef CONFIG_AUDIO_BOARD_LCD_ST7789 +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 + +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_INVERT_COLOR true +#define BACKLIGHT_INVERT false +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#endif + + + #endif // _BOARD_CONFIG_H_ \ No newline at end of file diff --git a/main/boards/waveshare-s3-audio-board/config.json b/main/boards/waveshare-s3-audio-board/config.json index 2f21e32..f074023 100644 --- a/main/boards/waveshare-s3-audio-board/config.json +++ b/main/boards/waveshare-s3-audio-board/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "waveshare-s3-audio-board", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "waveshare-s3-audio-board", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/waveshare-s3-audio-board/esp32-s3-audio_board.cc b/main/boards/waveshare-s3-audio-board/esp32-s3-audio_board.cc index e1a4067..fe71cc8 100644 --- a/main/boards/waveshare-s3-audio-board/esp32-s3-audio_board.cc +++ b/main/boards/waveshare-s3-audio-board/esp32-s3-audio_board.cc @@ -1,231 +1,231 @@ -#include "wifi_board.h" -#include "codecs/box_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" - -#include -#include "i2c_device.h" -#include -#include -#include -#include -#include -#include -#include -#include "esp_io_expander_tca95xx_16bit.h" -#include "esp32_camera.h" -#include "led/circular_strip.h" -#include "esp_lcd_jd9853.h" - -#define TAG "waveshare_lcd_1_85c" - -#define LCD_OPCODE_WRITE_CMD (0x02ULL) -#define LCD_OPCODE_READ_CMD (0x0BULL) -#define LCD_OPCODE_WRITE_COLOR (0x32ULL) - -class CustomBoard : public WifiBoard { -private: - Button boot_button_; - i2c_master_bus_handle_t i2c_bus_; - esp_io_expander_handle_t io_expander = NULL; - LcdDisplay* display_; - Esp32Camera* camera_; - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = I2C_SDA_IO, - .scl_io_num = I2C_SCL_IO, - .clk_source = I2C_CLK_SRC_DEFAULT, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeTca9555(void) - { - esp_err_t ret = esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, I2C_ADDRESS, &io_expander); - if(ret != ESP_OK) - ESP_LOGE(TAG, "TCA9554 create returned error"); // 打印引脚状态 - - ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_8|IO_EXPANDER_PIN_NUM_5|IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输出 - ESP_ERROR_CHECK(ret); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(10)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(10)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad - ESP_ERROR_CHECK(ret); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_8, 1); // 启用喇叭功放 - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_5, false); // 复位摄像头 - vTaskDelay(pdMS_TO_TICKS(5)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, true); - vTaskDelay(pdMS_TO_TICKS(5)); - ESP_ERROR_CHECK(ret); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_MOSI_PIN; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCLK_PIN; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR)); - - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeJd9853Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS_PIN; - io_config.dc_gpio_num = DISPLAY_DC_PIN; - io_config.spi_mode = 0; - io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片JD9853 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_NC; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - //ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_new_panel_jd9853(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 0, 34)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, true, false)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, true)); - display_ = new SpiLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeCamera() { - camera_config_t config = {}; - config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 - config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 - config.pin_d0 = CAMERA_PIN_D0; - config.pin_d1 = CAMERA_PIN_D1; - config.pin_d2 = CAMERA_PIN_D2; - config.pin_d3 = CAMERA_PIN_D3; - config.pin_d4 = CAMERA_PIN_D4; - config.pin_d5 = CAMERA_PIN_D5; - config.pin_d6 = CAMERA_PIN_D6; - config.pin_d7 = CAMERA_PIN_D7; - config.pin_xclk = CAMERA_PIN_XCLK; - config.pin_pclk = CAMERA_PIN_PCLK; - config.pin_vsync = CAMERA_PIN_VSYNC; - config.pin_href = CAMERA_PIN_HREF; - config.pin_sccb_sda = CAMERA_PIN_SIOD; // 这里如果写-1 表示使用已经初始化的I2C接口 - config.pin_sccb_scl = CAMERA_PIN_SIOC; - config.sccb_i2c_port = 0; // 这里如果写1 默认使用I2C1 - config.pin_pwdn = CAMERA_PIN_PWDN; - config.pin_reset = CAMERA_PIN_RESET; - config.xclk_freq_hz = XCLK_FREQ_HZ; - config.pixel_format = PIXFORMAT_RGB565; - config.frame_size = FRAMESIZE_QVGA; - config.jpeg_quality = 12; - config.fb_count = 1; - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - camera_ = new Esp32Camera(config); - camera_->SetVFlip(1); - } -public: - CustomBoard() : - boot_button_(BOOT_BUTTON_GPIO) { - InitializeI2c(); - InitializeTca9555(); - InitializeSpi(); - InitializeButtons(); - #ifdef LCD_TYPE_JD9853_SERIAL - InitializeJd9853Display(); - #else - InitializeSt7789Display(); - #endif - InitializeCamera(); - GetBacklight()->RestoreBrightness(); - } - - virtual Led* GetLed() override { - static CircularStrip led(BUILTIN_LED_GPIO, 6); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec(i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, BACKLIGHT_INVERT); - return &backlight; - } - - virtual Camera* GetCamera() override { - return camera_; - } -}; - -DECLARE_BOARD(CustomBoard); +#include "wifi_board.h" +#include "codecs/box_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" + +#include +#include "i2c_device.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_io_expander_tca95xx_16bit.h" +#include "esp32_camera.h" +#include "led/circular_strip.h" +#include "esp_lcd_jd9853.h" + +#define TAG "waveshare_lcd_1_85c" + +#define LCD_OPCODE_WRITE_CMD (0x02ULL) +#define LCD_OPCODE_READ_CMD (0x0BULL) +#define LCD_OPCODE_WRITE_COLOR (0x32ULL) + +class CustomBoard : public WifiBoard { +private: + Button boot_button_; + i2c_master_bus_handle_t i2c_bus_; + esp_io_expander_handle_t io_expander = NULL; + LcdDisplay* display_; + Esp32Camera* camera_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = I2C_SDA_IO, + .scl_io_num = I2C_SCL_IO, + .clk_source = I2C_CLK_SRC_DEFAULT, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeTca9555(void) + { + esp_err_t ret = esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, I2C_ADDRESS, &io_expander); + if(ret != ESP_OK) + ESP_LOGE(TAG, "TCA9554 create returned error"); // 打印引脚状态 + + ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_8|IO_EXPANDER_PIN_NUM_5|IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输出 + ESP_ERROR_CHECK(ret); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(10)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(10)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad + ESP_ERROR_CHECK(ret); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_8, 1); // 启用喇叭功放 + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_5, false); // 复位摄像头 + vTaskDelay(pdMS_TO_TICKS(5)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, true); + vTaskDelay(pdMS_TO_TICKS(5)); + ESP_ERROR_CHECK(ret); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR)); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeJd9853Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片JD9853 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + //ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_new_panel_jd9853(panel_io, &panel_config, &panel)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); + ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 0, 34)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, true, false)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, true)); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeCamera() { + camera_config_t config = {}; + config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 + config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用 + config.pin_d0 = CAMERA_PIN_D0; + config.pin_d1 = CAMERA_PIN_D1; + config.pin_d2 = CAMERA_PIN_D2; + config.pin_d3 = CAMERA_PIN_D3; + config.pin_d4 = CAMERA_PIN_D4; + config.pin_d5 = CAMERA_PIN_D5; + config.pin_d6 = CAMERA_PIN_D6; + config.pin_d7 = CAMERA_PIN_D7; + config.pin_xclk = CAMERA_PIN_XCLK; + config.pin_pclk = CAMERA_PIN_PCLK; + config.pin_vsync = CAMERA_PIN_VSYNC; + config.pin_href = CAMERA_PIN_HREF; + config.pin_sccb_sda = CAMERA_PIN_SIOD; // 这里如果写-1 表示使用已经初始化的I2C接口 + config.pin_sccb_scl = CAMERA_PIN_SIOC; + config.sccb_i2c_port = 0; // 这里如果写1 默认使用I2C1 + config.pin_pwdn = CAMERA_PIN_PWDN; + config.pin_reset = CAMERA_PIN_RESET; + config.xclk_freq_hz = XCLK_FREQ_HZ; + config.pixel_format = PIXFORMAT_RGB565; + config.frame_size = FRAMESIZE_QVGA; + config.jpeg_quality = 12; + config.fb_count = 1; + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + camera_ = new Esp32Camera(config); + camera_->SetVFlip(1); + } +public: + CustomBoard() : + boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeTca9555(); + InitializeSpi(); + InitializeButtons(); + #ifdef LCD_TYPE_JD9853_SERIAL + InitializeJd9853Display(); + #else + InitializeSt7789Display(); + #endif + InitializeCamera(); + GetBacklight()->RestoreBrightness(); + } + + virtual Led* GetLed() override { + static CircularStrip led(BUILTIN_LED_GPIO, 6); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec(i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, BACKLIGHT_INVERT); + return &backlight; + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(CustomBoard); diff --git a/main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.c b/main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.c index dea68f2..2d34d02 100644 --- a/main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.c +++ b/main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.c @@ -1,460 +1,460 @@ -#include -#include "esp_lcd_jd9853.h" -/* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_lcd_panel_interface.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_commands.h" -#include "driver/gpio.h" -#include "esp_log.h" -#include "esp_check.h" - -static const char *TAG = "JD9853"; - -static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel); -static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel); -static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel); -static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); -static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); -static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); -static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); -static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool off); - -typedef struct -{ - esp_lcd_panel_t base; - esp_lcd_panel_io_handle_t io; - int reset_gpio_num; - bool reset_level; - int x_gap; - int y_gap; - uint8_t fb_bits_per_pixel; - uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register - uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register - const jd9853_lcd_init_cmd_t *init_cmds; - uint16_t init_cmds_size; -} jd9853_panel_t; - -esp_err_t esp_lcd_new_panel_jd9853(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) -{ - esp_err_t ret = ESP_OK; - jd9853_panel_t *jd9853 = NULL; - gpio_config_t io_conf = {0}; - - ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - jd9853 = (jd9853_panel_t *)calloc(1, sizeof(jd9853_panel_t)); - ESP_GOTO_ON_FALSE(jd9853, ESP_ERR_NO_MEM, err, TAG, "no mem for jd9853 panel"); - - if (panel_dev_config->reset_gpio_num >= 0) - { - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; - ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); - } - -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - switch (panel_dev_config->color_space) - { - case ESP_LCD_COLOR_SPACE_RGB: - jd9853->madctl_val = 0; - break; - case ESP_LCD_COLOR_SPACE_BGR: - jd9853->madctl_val |= LCD_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); - break; - } -#else - switch (panel_dev_config->rgb_endian) - { - case LCD_RGB_ENDIAN_RGB: - jd9853->madctl_val = 0; - break; - case LCD_RGB_ENDIAN_BGR: - jd9853->madctl_val |= LCD_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian"); - break; - } -#endif - - switch (panel_dev_config->bits_per_pixel) - { - case 16: // RGB565 - jd9853->colmod_val = 0x55; - jd9853->fb_bits_per_pixel = 16; - break; - case 18: // RGB666 - jd9853->colmod_val = 0x66; - // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel - jd9853->fb_bits_per_pixel = 24; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); - break; - } - - jd9853->io = io; - jd9853->reset_gpio_num = panel_dev_config->reset_gpio_num; - jd9853->reset_level = panel_dev_config->flags.reset_active_high; - if (panel_dev_config->vendor_config) - { - jd9853->init_cmds = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; - jd9853->init_cmds_size = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; - } - jd9853->base.del = panel_jd9853_del; - jd9853->base.reset = panel_jd9853_reset; - jd9853->base.init = panel_jd9853_init; - jd9853->base.draw_bitmap = panel_jd9853_draw_bitmap; - jd9853->base.invert_color = panel_jd9853_invert_color; - jd9853->base.set_gap = panel_jd9853_set_gap; - jd9853->base.mirror = panel_jd9853_mirror; - jd9853->base.swap_xy = panel_jd9853_swap_xy; -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - jd9853->base.disp_off = panel_jd9853_disp_on_off; -#else - jd9853->base.disp_on_off = panel_jd9853_disp_on_off; -#endif - *ret_panel = &(jd9853->base); - ESP_LOGD(TAG, "new jd9853 panel @%p", jd9853); - - // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_jd9853_VER_MAJOR, ESP_LCD_jd9853_VER_MINOR, - // ESP_LCD_jd9853_VER_PATCH); - - return ESP_OK; - -err: - if (jd9853) - { - if (panel_dev_config->reset_gpio_num >= 0) - { - gpio_reset_pin(panel_dev_config->reset_gpio_num); - } - free(jd9853); - } - return ret; -} - -static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel) -{ - jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); - - if (jd9853->reset_gpio_num >= 0) - { - gpio_reset_pin(jd9853->reset_gpio_num); - } - ESP_LOGD(TAG, "del jd9853 panel @%p", jd9853); - free(jd9853); - return ESP_OK; -} - -static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel) -{ - jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); - esp_lcd_panel_io_handle_t io = jd9853->io; - - // perform hardware reset - if (jd9853->reset_gpio_num >= 0) - { - gpio_set_level(jd9853->reset_gpio_num, jd9853->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(jd9853->reset_gpio_num, !jd9853->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - } - else - { // perform software reset - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command - } - - return ESP_OK; -} - -typedef struct -{ - uint8_t cmd; - uint8_t data[16]; - uint8_t data_bytes; // Length of data in above data array; 0xFF = end of cmds. -} lcd_init_cmd_t; - -// static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = { -// // {cmd, { data }, data_size, delay_ms} -// /* Power contorl B, power control = 0, DC_ENA = 1 */ -// {0xCF, (uint8_t []){0x00, 0xAA, 0XE0}, 3, 0}, -// /* Power on sequence control, -// * cp1 keeps 1 frame, 1st frame enable -// * vcl = 0, ddvdh=3, vgh=1, vgl=2 -// * DDVDH_ENH=1 -// */ -// {0xED, (uint8_t []){0x67, 0x03, 0X12, 0X81}, 4, 0}, -// /* Driver timing control A, -// * non-overlap=default +1 -// * EQ=default - 1, CR=default -// * pre-charge=default - 1 -// */ -// {0xE8, (uint8_t []){0x8A, 0x01, 0x78}, 3, 0}, -// /* Power control A, Vcore=1.6V, DDVDH=5.6V */ -// {0xCB, (uint8_t []){0x39, 0x2C, 0x00, 0x34, 0x02}, 5, 0}, -// /* Pump ratio control, DDVDH=2xVCl */ -// {0xF7, (uint8_t []){0x20}, 1, 0}, - -// {0xF7, (uint8_t []){0x20}, 1, 0}, -// /* Driver timing control, all=0 unit */ -// {0xEA, (uint8_t []){0x00, 0x00}, 2, 0}, -// /* Power control 1, GVDD=4.75V */ -// {0xC0, (uint8_t []){0x23}, 1, 0}, -// /* Power control 2, DDVDH=VCl*2, VGH=VCl*7, VGL=-VCl*3 */ -// {0xC1, (uint8_t []){0x11}, 1, 0}, -// /* VCOM control 1, VCOMH=4.025V, VCOML=-0.950V */ -// {0xC5, (uint8_t []){0x43, 0x4C}, 2, 0}, -// /* VCOM control 2, VCOMH=VMH-2, VCOML=VML-2 */ -// {0xC7, (uint8_t []){0xA0}, 1, 0}, -// /* Frame rate control, f=fosc, 70Hz fps */ -// {0xB1, (uint8_t []){0x00, 0x1B}, 2, 0}, -// /* Enable 3G, disabled */ -// {0xF2, (uint8_t []){0x00}, 1, 0}, -// /* Gamma set, curve 1 */ -// {0x26, (uint8_t []){0x01}, 1, 0}, -// /* Positive gamma correction */ -// {0xE0, (uint8_t []){0x1F, 0x36, 0x36, 0x3A, 0x0C, 0x05, 0x4F, 0X87, 0x3C, 0x08, 0x11, 0x35, 0x19, 0x13, 0x00}, 15, 0}, -// /* Negative gamma correction */ -// {0xE1, (uint8_t []){0x00, 0x09, 0x09, 0x05, 0x13, 0x0A, 0x30, 0x78, 0x43, 0x07, 0x0E, 0x0A, 0x26, 0x2C, 0x1F}, 15, 0}, -// /* Entry mode set, Low vol detect disabled, normal display */ -// {0xB7, (uint8_t []){0x07}, 1, 0}, -// /* Display function control */ -// {0xB6, (uint8_t []){0x08, 0x82, 0x27}, 3, 0}, -// }; - -static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = { - {0x11, (uint8_t []){ 0x00 }, 0, 120}, - {0xDF, (uint8_t[]){0x98, 0x53}, 2, 0}, - {0xDF, (uint8_t[]){0x98, 0x53}, 2, 0}, - {0xB2, (uint8_t[]){0x23}, 1, 0}, - {0xB7, (uint8_t[]){0x00, 0x47, 0x00, 0x6F}, 4, 0}, - {0xBB, (uint8_t[]){0x1C, 0x1A, 0x55, 0x73, 0x63, 0xF0}, 6, 0}, - {0xC0, (uint8_t[]){0x44, 0xA4}, 2, 0}, - {0xC1, (uint8_t[]){0x16}, 1, 0}, - {0xC3, (uint8_t[]){0x7D, 0x07, 0x14, 0x06, 0xCF, 0x71, 0x72, 0x77}, 8, 0}, - {0xC4, (uint8_t[]){0x00, 0x00, 0xA0, 0x79, 0x0B, 0x0A, 0x16, 0x79, 0x0B, 0x0A, 0x16, 0x82}, 12, 0}, // 00=60Hz 06=57Hz 08=51Hz, LN=320 Line - {0xC8, (uint8_t[]){0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00, 0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00}, 32, 0}, // SET_R_GAMMA - {0xD0, (uint8_t[]){0x04, 0x06, 0x6B, 0x0F, 0x00}, 5, 0}, - {0xD7, (uint8_t[]){0x00, 0x30}, 2, 0}, - {0xE6, (uint8_t[]){0x14}, 1, 0}, - {0xDE, (uint8_t[]){0x01}, 1, 0}, - {0xB7, (uint8_t[]){0x03, 0x13, 0xEF, 0x35, 0x35}, 5, 0}, - {0xC1, (uint8_t[]){0x14, 0x15, 0xC0}, 3, 0}, - {0xC2, (uint8_t[]){0x06, 0x3A}, 2, 0}, - {0xC4, (uint8_t[]){0x72, 0x12}, 2, 0}, - {0xBE, (uint8_t[]){0x00}, 1, 0}, - {0xDE, (uint8_t[]){0x02}, 1, 0}, - {0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0}, - {0xE5, (uint8_t[]){0x01, 0x02, 0x00}, 3, 0}, - {0xDE, (uint8_t[]){0x00}, 1, 0}, - {0x35, (uint8_t[]){0x00}, 1, 0}, - {0x3A, (uint8_t[]){0x05}, 1, 0}, // 06=RGB666;05=RGB565 - {0x2A, (uint8_t[]){0x00, 0x22, 0x00, 0xCD}, 4, 0}, // Start_X=34, End_X=205 - {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x3F}, 4, 0}, // Start_Y=0, End_Y=319 - {0xDE, (uint8_t[]){0x02}, 1, 0}, - {0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0}, - {0xDE, (uint8_t[]){0x00}, 1, 0}, - {0x29, (uint8_t []){ 0x00 }, 0, 0}, -}; - -static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel) -{ - jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); - esp_lcd_panel_io_handle_t io = jd9853->io; - - // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(100)); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){ - jd9853->madctl_val, - }, - 1), - TAG, "send command failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){ - jd9853->colmod_val, - }, - 1), - TAG, "send command failed"); - - const jd9853_lcd_init_cmd_t *init_cmds = NULL; - uint16_t init_cmds_size = 0; - if (jd9853->init_cmds) - { - init_cmds = jd9853->init_cmds; - init_cmds_size = jd9853->init_cmds_size; - } - else - { - init_cmds = vendor_specific_init_default; - init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(jd9853_lcd_init_cmd_t); - } - - bool is_cmd_overwritten = false; - for (int i = 0; i < init_cmds_size; i++) - { - // Check if the command has been used or conflicts with the internal - switch (init_cmds[i].cmd) - { - case LCD_CMD_MADCTL: - is_cmd_overwritten = true; - jd9853->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - case LCD_CMD_COLMOD: - is_cmd_overwritten = true; - jd9853->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - default: - is_cmd_overwritten = false; - break; - } - - if (is_cmd_overwritten) - { - ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); - } - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); - } - ESP_LOGD(TAG, "send init commands success"); - - return ESP_OK; -} - -static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) -{ - jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); - assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); - esp_lcd_panel_io_handle_t io = jd9853->io; - - x_start += jd9853->x_gap; - x_end += jd9853->x_gap; - y_start += jd9853->y_gap; - y_end += jd9853->y_gap; - - // define an area of frame memory where MCU can access - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]){ - (x_start >> 8) & 0xFF, - x_start & 0xFF, - ((x_end - 1) >> 8) & 0xFF, - (x_end - 1) & 0xFF, - }, - 4), - TAG, "send command failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]){ - (y_start >> 8) & 0xFF, - y_start & 0xFF, - ((y_end - 1) >> 8) & 0xFF, - (y_end - 1) & 0xFF, - }, - 4), - TAG, "send command failed"); - // transfer frame buffer - size_t len = (x_end - x_start) * (y_end - y_start) * jd9853->fb_bits_per_pixel / 8; - esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len); - - return ESP_OK; -} - -static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) -{ - jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); - esp_lcd_panel_io_handle_t io = jd9853->io; - int command = 0; - if (invert_color_data) - { - command = LCD_CMD_INVON; - } - else - { - command = LCD_CMD_INVOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); - return ESP_OK; -} - -static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) -{ - jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); - esp_lcd_panel_io_handle_t io = jd9853->io; - if (mirror_x) - { - jd9853->madctl_val |= LCD_CMD_MX_BIT; - } - else - { - jd9853->madctl_val &= ~LCD_CMD_MX_BIT; - } - if (mirror_y) - { - jd9853->madctl_val |= LCD_CMD_MY_BIT; - } - else - { - jd9853->madctl_val &= ~LCD_CMD_MY_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed"); - return ESP_OK; -} - -static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) -{ - jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); - esp_lcd_panel_io_handle_t io = jd9853->io; - if (swap_axes) - { - jd9853->madctl_val |= LCD_CMD_MV_BIT; - } - else - { - jd9853->madctl_val &= ~LCD_CMD_MV_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed"); - return ESP_OK; -} - -static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) -{ - jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); - jd9853->x_gap = x_gap; - jd9853->y_gap = y_gap; - return ESP_OK; -} - -static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool on_off) -{ - jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); - esp_lcd_panel_io_handle_t io = jd9853->io; - int command = 0; - -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - on_off = !on_off; -#endif - - if (on_off) - { - command = LCD_CMD_DISPON; - } - else - { - command = LCD_CMD_DISPOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); - return ESP_OK; -} +#include +#include "esp_lcd_jd9853.h" +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_commands.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_check.h" + +static const char *TAG = "JD9853"; + +static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel); +static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel); +static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); +static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool off); + +typedef struct +{ + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + bool reset_level; + int x_gap; + int y_gap; + uint8_t fb_bits_per_pixel; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register + const jd9853_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; +} jd9853_panel_t; + +esp_err_t esp_lcd_new_panel_jd9853(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) +{ + esp_err_t ret = ESP_OK; + jd9853_panel_t *jd9853 = NULL; + gpio_config_t io_conf = {0}; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + jd9853 = (jd9853_panel_t *)calloc(1, sizeof(jd9853_panel_t)); + ESP_GOTO_ON_FALSE(jd9853, ESP_ERR_NO_MEM, err, TAG, "no mem for jd9853 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) + { + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + switch (panel_dev_config->color_space) + { + case ESP_LCD_COLOR_SPACE_RGB: + jd9853->madctl_val = 0; + break; + case ESP_LCD_COLOR_SPACE_BGR: + jd9853->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); + break; + } +#else + switch (panel_dev_config->rgb_endian) + { + case LCD_RGB_ENDIAN_RGB: + jd9853->madctl_val = 0; + break; + case LCD_RGB_ENDIAN_BGR: + jd9853->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian"); + break; + } +#endif + + switch (panel_dev_config->bits_per_pixel) + { + case 16: // RGB565 + jd9853->colmod_val = 0x55; + jd9853->fb_bits_per_pixel = 16; + break; + case 18: // RGB666 + jd9853->colmod_val = 0x66; + // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel + jd9853->fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + jd9853->io = io; + jd9853->reset_gpio_num = panel_dev_config->reset_gpio_num; + jd9853->reset_level = panel_dev_config->flags.reset_active_high; + if (panel_dev_config->vendor_config) + { + jd9853->init_cmds = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; + jd9853->init_cmds_size = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; + } + jd9853->base.del = panel_jd9853_del; + jd9853->base.reset = panel_jd9853_reset; + jd9853->base.init = panel_jd9853_init; + jd9853->base.draw_bitmap = panel_jd9853_draw_bitmap; + jd9853->base.invert_color = panel_jd9853_invert_color; + jd9853->base.set_gap = panel_jd9853_set_gap; + jd9853->base.mirror = panel_jd9853_mirror; + jd9853->base.swap_xy = panel_jd9853_swap_xy; +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + jd9853->base.disp_off = panel_jd9853_disp_on_off; +#else + jd9853->base.disp_on_off = panel_jd9853_disp_on_off; +#endif + *ret_panel = &(jd9853->base); + ESP_LOGD(TAG, "new jd9853 panel @%p", jd9853); + + // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_jd9853_VER_MAJOR, ESP_LCD_jd9853_VER_MINOR, + // ESP_LCD_jd9853_VER_PATCH); + + return ESP_OK; + +err: + if (jd9853) + { + if (panel_dev_config->reset_gpio_num >= 0) + { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(jd9853); + } + return ret; +} + +static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + + if (jd9853->reset_gpio_num >= 0) + { + gpio_reset_pin(jd9853->reset_gpio_num); + } + ESP_LOGD(TAG, "del jd9853 panel @%p", jd9853); + free(jd9853); + return ESP_OK; +} + +static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + + // perform hardware reset + if (jd9853->reset_gpio_num >= 0) + { + gpio_set_level(jd9853->reset_gpio_num, jd9853->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(jd9853->reset_gpio_num, !jd9853->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + } + else + { // perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command + } + + return ESP_OK; +} + +typedef struct +{ + uint8_t cmd; + uint8_t data[16]; + uint8_t data_bytes; // Length of data in above data array; 0xFF = end of cmds. +} lcd_init_cmd_t; + +// static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = { +// // {cmd, { data }, data_size, delay_ms} +// /* Power contorl B, power control = 0, DC_ENA = 1 */ +// {0xCF, (uint8_t []){0x00, 0xAA, 0XE0}, 3, 0}, +// /* Power on sequence control, +// * cp1 keeps 1 frame, 1st frame enable +// * vcl = 0, ddvdh=3, vgh=1, vgl=2 +// * DDVDH_ENH=1 +// */ +// {0xED, (uint8_t []){0x67, 0x03, 0X12, 0X81}, 4, 0}, +// /* Driver timing control A, +// * non-overlap=default +1 +// * EQ=default - 1, CR=default +// * pre-charge=default - 1 +// */ +// {0xE8, (uint8_t []){0x8A, 0x01, 0x78}, 3, 0}, +// /* Power control A, Vcore=1.6V, DDVDH=5.6V */ +// {0xCB, (uint8_t []){0x39, 0x2C, 0x00, 0x34, 0x02}, 5, 0}, +// /* Pump ratio control, DDVDH=2xVCl */ +// {0xF7, (uint8_t []){0x20}, 1, 0}, + +// {0xF7, (uint8_t []){0x20}, 1, 0}, +// /* Driver timing control, all=0 unit */ +// {0xEA, (uint8_t []){0x00, 0x00}, 2, 0}, +// /* Power control 1, GVDD=4.75V */ +// {0xC0, (uint8_t []){0x23}, 1, 0}, +// /* Power control 2, DDVDH=VCl*2, VGH=VCl*7, VGL=-VCl*3 */ +// {0xC1, (uint8_t []){0x11}, 1, 0}, +// /* VCOM control 1, VCOMH=4.025V, VCOML=-0.950V */ +// {0xC5, (uint8_t []){0x43, 0x4C}, 2, 0}, +// /* VCOM control 2, VCOMH=VMH-2, VCOML=VML-2 */ +// {0xC7, (uint8_t []){0xA0}, 1, 0}, +// /* Frame rate control, f=fosc, 70Hz fps */ +// {0xB1, (uint8_t []){0x00, 0x1B}, 2, 0}, +// /* Enable 3G, disabled */ +// {0xF2, (uint8_t []){0x00}, 1, 0}, +// /* Gamma set, curve 1 */ +// {0x26, (uint8_t []){0x01}, 1, 0}, +// /* Positive gamma correction */ +// {0xE0, (uint8_t []){0x1F, 0x36, 0x36, 0x3A, 0x0C, 0x05, 0x4F, 0X87, 0x3C, 0x08, 0x11, 0x35, 0x19, 0x13, 0x00}, 15, 0}, +// /* Negative gamma correction */ +// {0xE1, (uint8_t []){0x00, 0x09, 0x09, 0x05, 0x13, 0x0A, 0x30, 0x78, 0x43, 0x07, 0x0E, 0x0A, 0x26, 0x2C, 0x1F}, 15, 0}, +// /* Entry mode set, Low vol detect disabled, normal display */ +// {0xB7, (uint8_t []){0x07}, 1, 0}, +// /* Display function control */ +// {0xB6, (uint8_t []){0x08, 0x82, 0x27}, 3, 0}, +// }; + +static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = { + {0x11, (uint8_t []){ 0x00 }, 0, 120}, + {0xDF, (uint8_t[]){0x98, 0x53}, 2, 0}, + {0xDF, (uint8_t[]){0x98, 0x53}, 2, 0}, + {0xB2, (uint8_t[]){0x23}, 1, 0}, + {0xB7, (uint8_t[]){0x00, 0x47, 0x00, 0x6F}, 4, 0}, + {0xBB, (uint8_t[]){0x1C, 0x1A, 0x55, 0x73, 0x63, 0xF0}, 6, 0}, + {0xC0, (uint8_t[]){0x44, 0xA4}, 2, 0}, + {0xC1, (uint8_t[]){0x16}, 1, 0}, + {0xC3, (uint8_t[]){0x7D, 0x07, 0x14, 0x06, 0xCF, 0x71, 0x72, 0x77}, 8, 0}, + {0xC4, (uint8_t[]){0x00, 0x00, 0xA0, 0x79, 0x0B, 0x0A, 0x16, 0x79, 0x0B, 0x0A, 0x16, 0x82}, 12, 0}, // 00=60Hz 06=57Hz 08=51Hz, LN=320 Line + {0xC8, (uint8_t[]){0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00, 0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00}, 32, 0}, // SET_R_GAMMA + {0xD0, (uint8_t[]){0x04, 0x06, 0x6B, 0x0F, 0x00}, 5, 0}, + {0xD7, (uint8_t[]){0x00, 0x30}, 2, 0}, + {0xE6, (uint8_t[]){0x14}, 1, 0}, + {0xDE, (uint8_t[]){0x01}, 1, 0}, + {0xB7, (uint8_t[]){0x03, 0x13, 0xEF, 0x35, 0x35}, 5, 0}, + {0xC1, (uint8_t[]){0x14, 0x15, 0xC0}, 3, 0}, + {0xC2, (uint8_t[]){0x06, 0x3A}, 2, 0}, + {0xC4, (uint8_t[]){0x72, 0x12}, 2, 0}, + {0xBE, (uint8_t[]){0x00}, 1, 0}, + {0xDE, (uint8_t[]){0x02}, 1, 0}, + {0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0}, + {0xE5, (uint8_t[]){0x01, 0x02, 0x00}, 3, 0}, + {0xDE, (uint8_t[]){0x00}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x3A, (uint8_t[]){0x05}, 1, 0}, // 06=RGB666;05=RGB565 + {0x2A, (uint8_t[]){0x00, 0x22, 0x00, 0xCD}, 4, 0}, // Start_X=34, End_X=205 + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x3F}, 4, 0}, // Start_Y=0, End_Y=319 + {0xDE, (uint8_t[]){0x02}, 1, 0}, + {0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0}, + {0xDE, (uint8_t[]){0x00}, 1, 0}, + {0x29, (uint8_t []){ 0x00 }, 0, 0}, +}; + +static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + + // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(100)); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){ + jd9853->madctl_val, + }, + 1), + TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){ + jd9853->colmod_val, + }, + 1), + TAG, "send command failed"); + + const jd9853_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + if (jd9853->init_cmds) + { + init_cmds = jd9853->init_cmds; + init_cmds_size = jd9853->init_cmds_size; + } + else + { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(jd9853_lcd_init_cmd_t); + } + + bool is_cmd_overwritten = false; + for (int i = 0; i < init_cmds_size; i++) + { + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd) + { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + jd9853->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + jd9853->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) + { + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); + esp_lcd_panel_io_handle_t io = jd9853->io; + + x_start += jd9853->x_gap; + x_end += jd9853->x_gap; + y_start += jd9853->y_gap; + y_end += jd9853->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]){ + (x_start >> 8) & 0xFF, + x_start & 0xFF, + ((x_end - 1) >> 8) & 0xFF, + (x_end - 1) & 0xFF, + }, + 4), + TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]){ + (y_start >> 8) & 0xFF, + y_start & 0xFF, + ((y_end - 1) >> 8) & 0xFF, + (y_end - 1) & 0xFF, + }, + 4), + TAG, "send command failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * jd9853->fb_bits_per_pixel / 8; + esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len); + + return ESP_OK; +} + +static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + int command = 0; + if (invert_color_data) + { + command = LCD_CMD_INVON; + } + else + { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + if (mirror_x) + { + jd9853->madctl_val |= LCD_CMD_MX_BIT; + } + else + { + jd9853->madctl_val &= ~LCD_CMD_MX_BIT; + } + if (mirror_y) + { + jd9853->madctl_val |= LCD_CMD_MY_BIT; + } + else + { + jd9853->madctl_val &= ~LCD_CMD_MY_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + if (swap_axes) + { + jd9853->madctl_val |= LCD_CMD_MV_BIT; + } + else + { + jd9853->madctl_val &= ~LCD_CMD_MV_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + jd9853->x_gap = x_gap; + jd9853->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base); + esp_lcd_panel_io_handle_t io = jd9853->io; + int command = 0; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + on_off = !on_off; +#endif + + if (on_off) + { + command = LCD_CMD_DISPON; + } + else + { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} diff --git a/main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.h b/main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.h index 487e749..0a1b5a7 100644 --- a/main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.h +++ b/main/boards/waveshare-s3-audio-board/esp_lcd_jd9853.h @@ -1,102 +1,102 @@ -/* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -/** - * @file - * @brief ESP LCD: jd9853 - */ - -#pragma once - -#include "hal/spi_ll.h" -#include "esp_lcd_panel_vendor.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief LCD panel initialization commands. - * - */ -typedef struct { - int cmd; /* | -| | -| | - +# Waveshare ESP32-S3-Touch-AMOLED-1.75 + + +[ESP32-S3-Touch-AMOLED-1.75](https://www.waveshare.com/esp32-s3-touch-amoled-1.75.htm) ESP32-S3-Touch-AMOLED-1.75 is a high performance, highly integrated microcontroller development board designed by Waveshare.\ +In the smaller form, the 1.75-inch capacitive HD AMOLED screen, highly integrated power management chip, six-axis sensor (three-axis accelerometer and three-axis gyroscope), RTC, low-power audio codec chip and echo cancellation circuit are mounted on the board, which is convenient for development and embedding into the product. + + +## images + +| [ESP32-S3-Touch-AMOLED-1.75](https://www.waveshare.com/esp32-s3-touch-amoled-1.75.htm) | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| | +| | +| | + diff --git a/main/boards/waveshare-s3-touch-amoled-1.75/config.h b/main/boards/waveshare-s3-touch-amoled-1.75/config.h index 40ac5df..4d6917f 100644 --- a/main/boards/waveshare-s3-touch-amoled-1.75/config.h +++ b/main/boards/waveshare-s3-touch-amoled-1.75/config.h @@ -1,44 +1,44 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_12 -#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_38 -#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_4 -#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_5 -#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_6 -#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_7 -#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_39 -#define DISPLAY_WIDTH 466 -#define DISPLAY_HEIGHT 466 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_12 +#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_38 +#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_4 +#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_5 +#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_6 +#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_7 +#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_39 +#define DISPLAY_WIDTH 466 +#define DISPLAY_HEIGHT 466 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/waveshare-s3-touch-amoled-1.75/config.json b/main/boards/waveshare-s3-touch-amoled-1.75/config.json index 6e4e8ac..41258d1 100644 --- a/main/boards/waveshare-s3-touch-amoled-1.75/config.json +++ b/main/boards/waveshare-s3-touch-amoled-1.75/config.json @@ -1,12 +1,12 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "waveshare-s3-touch-amoled-1.75", - "sdkconfig_append": [ - "CONFIG_USE_WECHAT_MESSAGE_STYLE=n", - "CONFIG_USE_DEVICE_AEC=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "waveshare-s3-touch-amoled-1.75", + "sdkconfig_append": [ + "CONFIG_USE_WECHAT_MESSAGE_STYLE=n", + "CONFIG_USE_DEVICE_AEC=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc b/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc index 9053154..e940fb4 100644 --- a/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc +++ b/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc @@ -1,361 +1,361 @@ -#include "wifi_board.h" -#include "display/lcd_display.h" -#include "esp_lcd_sh8601.h" - -#include "codecs/box_audio_codec.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "mcp_server.h" -#include "config.h" -#include "power_save_timer.h" -#include "axp2101.h" -#include "i2c_device.h" -#include - -#include -#include -#include -#include -#include "esp_io_expander_tca9554.h" -#include "settings.h" - -#include -#include -#include - -#define TAG "WaveshareEsp32s3TouchAMOLED1inch75" - -class Pmic : public Axp2101 { -public: - Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { - WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable - WriteReg(0x27, 0x10); // hold 4s to power off - - // Disable All DCs but DC1 - WriteReg(0x80, 0x01); - // Disable All LDOs - WriteReg(0x90, 0x00); - WriteReg(0x91, 0x00); - - // Set DC1 to 3.3V - WriteReg(0x82, (3300 - 1500) / 100); - - // Set ALDO1 to 3.3V - WriteReg(0x92, (3300 - 500) / 100); - - // Enable ALDO1(MIC) - WriteReg(0x90, 0x01); - - WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V - - WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA - WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) - WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA - } -}; - -#define LCD_OPCODE_WRITE_CMD (0x02ULL) -#define LCD_OPCODE_READ_CMD (0x03ULL) -#define LCD_OPCODE_WRITE_COLOR (0x32ULL) - -static const sh8601_lcd_init_cmd_t vendor_specific_init[] = { - // set display to qspi mode - {0xFE, (uint8_t[]){0x20}, 1, 0}, - {0x19, (uint8_t[]){0x10}, 1, 0}, - {0x1C, (uint8_t[]){0xA0}, 1, 0}, - - {0xFE, (uint8_t[]){0x00}, 1, 0}, - {0xC4, (uint8_t[]){0x80}, 1, 0}, - {0x3A, (uint8_t[]){0x55}, 1, 0}, - {0x35, (uint8_t[]){0x00}, 1, 0}, - {0x53, (uint8_t[]){0x20}, 1, 0}, - {0x51, (uint8_t[]){0xFF}, 1, 0}, - {0x63, (uint8_t[]){0xFF}, 1, 0}, - {0x2A, (uint8_t[]){0x00, 0x06, 0x01, 0xD7}, 4, 0}, - {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xD1}, 4, 600}, - {0x11, NULL, 0, 600}, - {0x29, NULL, 0, 0}, -}; - -// 在waveshare_amoled_1_75类之前添加新的显示类 -class CustomLcdDisplay : public SpiLcdDisplay { -public: - static void rounder_event_cb(lv_event_t* e) { - lv_area_t* area = (lv_area_t* )lv_event_get_param(e); - uint16_t x1 = area->x1; - uint16_t x2 = area->x2; - - uint16_t y1 = area->y1; - uint16_t y2 = area->y2; - - // round the start of coordinate down to the nearest 2M number - area->x1 = (x1 >> 1) << 1; - area->y1 = (y1 >> 1) << 1; - // round the end of coordinate up to the nearest 2N+1 number - area->x2 = ((x2 >> 1) << 1) + 1; - area->y2 = ((y2 >> 1) << 1) + 1; - } - - CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, - esp_lcd_panel_handle_t panel_handle, - int width, - int height, - int offset_x, - int offset_y, - bool mirror_x, - bool mirror_y, - bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, - width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - DisplayLockGuard lock(this); - lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES* 0.1, 0); - lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES* 0.1, 0); - lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL); - } -}; - -class CustomBacklight : public Backlight { -public: - CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {} - -protected: - esp_lcd_panel_io_handle_t panel_io_; - - virtual void SetBrightnessImpl(uint8_t brightness) override { - auto display = Board::GetInstance().GetDisplay(); - DisplayLockGuard lock(display); - uint8_t data[1] = {((uint8_t)((255* brightness) / 100))}; - int lcd_cmd = 0x51; - lcd_cmd &= 0xff; - lcd_cmd <<= 8; - lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24; - esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data)); - } -}; - -class WaveshareEsp32s3TouchAMOLED1inch75 : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Pmic* pmic_ = nullptr; - Button boot_button_; - CustomLcdDisplay* display_; - CustomBacklight* backlight_; - esp_io_expander_handle_t io_expander = NULL; - PowerSaveTimer* power_save_timer_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(20); }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); }); - power_save_timer_->OnShutdownRequest([this](){ - pmic_->PowerOff(); }); - power_save_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeTca9554(void) { - esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); - if (ret != ESP_OK) - ESP_LOGE(TAG, "TCA9554 create returned error"); - ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_4, IO_EXPANDER_INPUT); - ESP_ERROR_CHECK(ret); - } - - void InitializeAxp2101() { - ESP_LOGI(TAG, "Init AXP2101"); - pmic_ = new Pmic(i2c_bus_, 0x34); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.sclk_io_num = EXAMPLE_PIN_NUM_LCD_PCLK; - buscfg.data0_io_num = EXAMPLE_PIN_NUM_LCD_DATA0; - buscfg.data1_io_num = EXAMPLE_PIN_NUM_LCD_DATA1; - buscfg.data2_io_num = EXAMPLE_PIN_NUM_LCD_DATA2; - buscfg.data3_io_num = EXAMPLE_PIN_NUM_LCD_DATA3; - buscfg.max_transfer_sz = DISPLAY_WIDTH* DISPLAY_HEIGHT* sizeof(uint16_t); - buscfg.flags = SPICOMMON_BUSFLAG_QUAD; - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - -#if CONFIG_USE_DEVICE_AEC - boot_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateIdle) { - app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); - } - }); -#endif - } - - void InitializeSH8601Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = SH8601_PANEL_IO_QSPI_CONFIG( - EXAMPLE_PIN_NUM_LCD_CS, - nullptr, - nullptr); - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - const sh8601_vendor_config_t vendor_config = { - .init_cmds = &vendor_specific_init[0], - .init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t), - .flags = { - .use_qspi_interface = 1, - }}; - - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = (void* )&vendor_config; - ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel)); - esp_lcd_panel_set_gap(panel, 0x06, 0); - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, false); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - esp_lcd_panel_disp_on_off(panel, true); - display_ = new CustomLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - backlight_ = new CustomBacklight(panel_io); - backlight_->RestoreBrightness(); - } - - void InitializeTouch() { - esp_lcd_touch_handle_t tp; - esp_lcd_touch_config_t tp_cfg = { - .x_max = DISPLAY_WIDTH - 1, - .y_max = DISPLAY_HEIGHT - 1, - .rst_gpio_num = GPIO_NUM_40, - .int_gpio_num = GPIO_NUM_NC, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = 0, - .mirror_x = 1, - .mirror_y = 1, - }, - }; - esp_lcd_panel_io_handle_t tp_io_handle = NULL; - esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_CST9217_CONFIG(); - tp_io_config.scl_speed_hz = 400* 1000; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); - ESP_LOGI(TAG, "Initialize touch controller"); - ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_cst9217(tp_io_handle, &tp_cfg, &tp)); - const lvgl_port_touch_cfg_t touch_cfg = { - .disp = lv_display_get_default(), - .handle = tp, - }; - lvgl_port_add_touch(&touch_cfg); - ESP_LOGI(TAG, "Touch panel initialized successfully"); - } - - // 初始化工具 - void InitializeTools() { - auto &mcp_server = McpServer::GetInstance(); - mcp_server.AddTool("self.system.reconfigure_wifi", - "Reboot the device and enter WiFi configuration mode.\n" - "**CAUTION** You must ask the user to confirm this action.", - PropertyList(), [this](const PropertyList& properties) { - ResetWifiConfiguration(); - return true; - }); - } - -public: - WaveshareEsp32s3TouchAMOLED1inch75() : boot_button_(BOOT_BUTTON_GPIO) { - InitializePowerSaveTimer(); - InitializeCodecI2c(); - InitializeTca9554(); - InitializeAxp2101(); - InitializeSpi(); - InitializeSH8601Display(); - InitializeTouch(); - InitializeButtons(); - InitializeTools(); - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - return backlight_; - } - - virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override { - static bool last_discharging = false; - charging = pmic_->IsCharging(); - discharging = pmic_->IsDischarging(); - if (discharging != last_discharging) - { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - - level = pmic_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) - { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(WaveshareEsp32s3TouchAMOLED1inch75); +#include "wifi_board.h" +#include "display/lcd_display.h" +#include "esp_lcd_sh8601.h" + +#include "codecs/box_audio_codec.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "mcp_server.h" +#include "config.h" +#include "power_save_timer.h" +#include "axp2101.h" +#include "i2c_device.h" +#include + +#include +#include +#include +#include +#include "esp_io_expander_tca9554.h" +#include "settings.h" + +#include +#include +#include + +#define TAG "WaveshareEsp32s3TouchAMOLED1inch75" + +class Pmic : public Axp2101 { +public: + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable + WriteReg(0x27, 0x10); // hold 4s to power off + + // Disable All DCs but DC1 + WriteReg(0x80, 0x01); + // Disable All LDOs + WriteReg(0x90, 0x00); + WriteReg(0x91, 0x00); + + // Set DC1 to 3.3V + WriteReg(0x82, (3300 - 1500) / 100); + + // Set ALDO1 to 3.3V + WriteReg(0x92, (3300 - 500) / 100); + + // Enable ALDO1(MIC) + WriteReg(0x90, 0x01); + + WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V + + WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA + WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) + WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA + } +}; + +#define LCD_OPCODE_WRITE_CMD (0x02ULL) +#define LCD_OPCODE_READ_CMD (0x03ULL) +#define LCD_OPCODE_WRITE_COLOR (0x32ULL) + +static const sh8601_lcd_init_cmd_t vendor_specific_init[] = { + // set display to qspi mode + {0xFE, (uint8_t[]){0x20}, 1, 0}, + {0x19, (uint8_t[]){0x10}, 1, 0}, + {0x1C, (uint8_t[]){0xA0}, 1, 0}, + + {0xFE, (uint8_t[]){0x00}, 1, 0}, + {0xC4, (uint8_t[]){0x80}, 1, 0}, + {0x3A, (uint8_t[]){0x55}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x53, (uint8_t[]){0x20}, 1, 0}, + {0x51, (uint8_t[]){0xFF}, 1, 0}, + {0x63, (uint8_t[]){0xFF}, 1, 0}, + {0x2A, (uint8_t[]){0x00, 0x06, 0x01, 0xD7}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xD1}, 4, 600}, + {0x11, NULL, 0, 600}, + {0x29, NULL, 0, 0}, +}; + +// 在waveshare_amoled_1_75类之前添加新的显示类 +class CustomLcdDisplay : public SpiLcdDisplay { +public: + static void rounder_event_cb(lv_event_t* e) { + lv_area_t* area = (lv_area_t* )lv_event_get_param(e); + uint16_t x1 = area->x1; + uint16_t x2 = area->x2; + + uint16_t y1 = area->y1; + uint16_t y2 = area->y2; + + // round the start of coordinate down to the nearest 2M number + area->x1 = (x1 >> 1) << 1; + area->y1 = (y1 >> 1) << 1; + // round the end of coordinate up to the nearest 2N+1 number + area->x2 = ((x2 >> 1) << 1) + 1; + area->y2 = ((y2 >> 1) << 1) + 1; + } + + CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_handle_t panel_handle, + int width, + int height, + int offset_x, + int offset_y, + bool mirror_x, + bool mirror_y, + bool swap_xy) + : SpiLcdDisplay(io_handle, panel_handle, + width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + DisplayLockGuard lock(this); + lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES* 0.1, 0); + lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES* 0.1, 0); + lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL); + } +}; + +class CustomBacklight : public Backlight { +public: + CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {} + +protected: + esp_lcd_panel_io_handle_t panel_io_; + + virtual void SetBrightnessImpl(uint8_t brightness) override { + auto display = Board::GetInstance().GetDisplay(); + DisplayLockGuard lock(display); + uint8_t data[1] = {((uint8_t)((255* brightness) / 100))}; + int lcd_cmd = 0x51; + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24; + esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data)); + } +}; + +class WaveshareEsp32s3TouchAMOLED1inch75 : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Pmic* pmic_ = nullptr; + Button boot_button_; + CustomLcdDisplay* display_; + CustomBacklight* backlight_; + esp_io_expander_handle_t io_expander = NULL; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(20); }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); }); + power_save_timer_->OnShutdownRequest([this](){ + pmic_->PowerOff(); }); + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeTca9554(void) { + esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); + if (ret != ESP_OK) + ESP_LOGE(TAG, "TCA9554 create returned error"); + ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_4, IO_EXPANDER_INPUT); + ESP_ERROR_CHECK(ret); + } + + void InitializeAxp2101() { + ESP_LOGI(TAG, "Init AXP2101"); + pmic_ = new Pmic(i2c_bus_, 0x34); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.sclk_io_num = EXAMPLE_PIN_NUM_LCD_PCLK; + buscfg.data0_io_num = EXAMPLE_PIN_NUM_LCD_DATA0; + buscfg.data1_io_num = EXAMPLE_PIN_NUM_LCD_DATA1; + buscfg.data2_io_num = EXAMPLE_PIN_NUM_LCD_DATA2; + buscfg.data3_io_num = EXAMPLE_PIN_NUM_LCD_DATA3; + buscfg.max_transfer_sz = DISPLAY_WIDTH* DISPLAY_HEIGHT* sizeof(uint16_t); + buscfg.flags = SPICOMMON_BUSFLAG_QUAD; + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif + } + + void InitializeSH8601Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = SH8601_PANEL_IO_QSPI_CONFIG( + EXAMPLE_PIN_NUM_LCD_CS, + nullptr, + nullptr); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + const sh8601_vendor_config_t vendor_config = { + .init_cmds = &vendor_specific_init[0], + .init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t), + .flags = { + .use_qspi_interface = 1, + }}; + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = (void* )&vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel)); + esp_lcd_panel_set_gap(panel, 0x06, 0); + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_disp_on_off(panel, true); + display_ = new CustomLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + backlight_ = new CustomBacklight(panel_io); + backlight_->RestoreBrightness(); + } + + void InitializeTouch() { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH - 1, + .y_max = DISPLAY_HEIGHT - 1, + .rst_gpio_num = GPIO_NUM_40, + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 1, + .mirror_y = 1, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_CST9217_CONFIG(); + tp_io_config.scl_speed_hz = 400* 1000; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); + ESP_LOGI(TAG, "Initialize touch controller"); + ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_cst9217(tp_io_handle, &tp_cfg, &tp)); + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + lvgl_port_add_touch(&touch_cfg); + ESP_LOGI(TAG, "Touch panel initialized successfully"); + } + + // 初始化工具 + void InitializeTools() { + auto &mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.system.reconfigure_wifi", + "Reboot the device and enter WiFi configuration mode.\n" + "**CAUTION** You must ask the user to confirm this action.", + PropertyList(), [this](const PropertyList& properties) { + ResetWifiConfiguration(); + return true; + }); + } + +public: + WaveshareEsp32s3TouchAMOLED1inch75() : boot_button_(BOOT_BUTTON_GPIO) { + InitializePowerSaveTimer(); + InitializeCodecI2c(); + InitializeTca9554(); + InitializeAxp2101(); + InitializeSpi(); + InitializeSH8601Display(); + InitializeTouch(); + InitializeButtons(); + InitializeTools(); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + return backlight_; + } + + virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override { + static bool last_discharging = false; + charging = pmic_->IsCharging(); + discharging = pmic_->IsDischarging(); + if (discharging != last_discharging) + { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + + level = pmic_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) + { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(WaveshareEsp32s3TouchAMOLED1inch75); diff --git a/main/boards/waveshare-s3-touch-amoled-2.06/README.md b/main/boards/waveshare-s3-touch-amoled-2.06/README.md index aa8bbe8..4c67ff2 100644 --- a/main/boards/waveshare-s3-touch-amoled-2.06/README.md +++ b/main/boards/waveshare-s3-touch-amoled-2.06/README.md @@ -1,11 +1,11 @@ -# Waveshare ESP32-S3-Touch-AMOLED-2.06 - - -[ESP32-S3-Touch-AMOLED-2.06](https://www.waveshare.com/esp32-s3-touch-amoled-2.06.htm) is a high-performance, wearable watch-style development board designed by Waveshare. Based on the ESP32-S3R8 microcontroller, it integrates a 2.06inch AMOLED capacitive touch display, 6-axis IMU, RTC chip, audio codec chip, power management IC, and so on. Comes with a custom-designed case with a smartwatch-like appearance, making it ideal for prototyping and functional verification of wearable applications. - -## images - -| [ESP32-S3-Touch-AMOLED-2.06](https://www.waveshare.com/esp32-s3-touch-amoled-2.06.htm) | -|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| | - +# Waveshare ESP32-S3-Touch-AMOLED-2.06 + + +[ESP32-S3-Touch-AMOLED-2.06](https://www.waveshare.com/esp32-s3-touch-amoled-2.06.htm) is a high-performance, wearable watch-style development board designed by Waveshare. Based on the ESP32-S3R8 microcontroller, it integrates a 2.06inch AMOLED capacitive touch display, 6-axis IMU, RTC chip, audio codec chip, power management IC, and so on. Comes with a custom-designed case with a smartwatch-like appearance, making it ideal for prototyping and functional verification of wearable applications. + +## images + +| [ESP32-S3-Touch-AMOLED-2.06](https://www.waveshare.com/esp32-s3-touch-amoled-2.06.htm) | +|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| | + diff --git a/main/boards/waveshare-s3-touch-amoled-2.06/config.h b/main/boards/waveshare-s3-touch-amoled-2.06/config.h index 6ad75a7..e266c1b 100644 --- a/main/boards/waveshare-s3-touch-amoled-2.06/config.h +++ b/main/boards/waveshare-s3-touch-amoled-2.06/config.h @@ -1,43 +1,43 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_42 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_40 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR -#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 - -#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_12 -#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_11 -#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_4 -#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_5 -#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_6 -#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_7 -#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_8 -#define DISPLAY_WIDTH 410 -#define DISPLAY_HEIGHT 502 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_42 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_40 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_12 +#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_11 +#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_4 +#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_5 +#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_6 +#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_7 +#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_8 +#define DISPLAY_WIDTH 410 +#define DISPLAY_HEIGHT 502 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/waveshare-s3-touch-amoled-2.06/config.json b/main/boards/waveshare-s3-touch-amoled-2.06/config.json index 3a95f74..83269ce 100644 --- a/main/boards/waveshare-s3-touch-amoled-2.06/config.json +++ b/main/boards/waveshare-s3-touch-amoled-2.06/config.json @@ -1,12 +1,12 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "waveshare-s3-touch-amoled-2.06", - "sdkconfig_append": [ - "CONFIG_USE_WECHAT_MESSAGE_STYLE=n", - "CONFIG_USE_DEVICE_AEC=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "waveshare-s3-touch-amoled-2.06", + "sdkconfig_append": [ + "CONFIG_USE_WECHAT_MESSAGE_STYLE=n", + "CONFIG_USE_DEVICE_AEC=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/waveshare-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc b/main/boards/waveshare-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc index 15facfc..9a89566 100644 --- a/main/boards/waveshare-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc +++ b/main/boards/waveshare-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc @@ -1,347 +1,347 @@ -#include "wifi_board.h" -#include "display/lcd_display.h" -#include "esp_lcd_sh8601.h" - -#include "codecs/box_audio_codec.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "mcp_server.h" -#include "config.h" -#include "power_save_timer.h" -#include "axp2101.h" -#include "i2c_device.h" -#include - -#include -#include -#include -#include -#include "settings.h" - -#include -#include -#include - -#define TAG "WaveshareEsp32s3TouchAMOLED2inch06" - -class Pmic : public Axp2101 { -public: - Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { - WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable - WriteReg(0x27, 0x10); // hold 4s to power off - - // Disable All DCs but DC1 - WriteReg(0x80, 0x01); - // Disable All LDOs - WriteReg(0x90, 0x00); - WriteReg(0x91, 0x00); - - // Set DC1 to 3.3V - WriteReg(0x82, (3300 - 1500) / 100); - - // Set ALDO1 to 3.3V - WriteReg(0x92, (3300 - 500) / 100); - WriteReg(0x93, (3300 - 500) / 100); - - // Enable ALDO1(MIC) - WriteReg(0x90, 0x03); - - WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V - - WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA - WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) - WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA - } -}; - -#define LCD_OPCODE_WRITE_CMD (0x02ULL) -#define LCD_OPCODE_READ_CMD (0x03ULL) -#define LCD_OPCODE_WRITE_COLOR (0x32ULL) - -static const sh8601_lcd_init_cmd_t vendor_specific_init[] = { - // set display to qspi mode - {0x11, (uint8_t []){0x00}, 0, 120}, - {0xC4, (uint8_t []){0x80}, 1, 0}, - {0x44, (uint8_t []){0x01, 0xD1}, 2, 0}, - {0x35, (uint8_t []){0x00}, 1, 0}, - {0x53, (uint8_t []){0x20}, 1, 10}, - {0x63, (uint8_t []){0xFF}, 1, 10}, - {0x51, (uint8_t []){0x00}, 1, 10}, - {0x2A, (uint8_t []){0x00,0x16,0x01,0xAF}, 4, 0}, - {0x2B, (uint8_t []){0x00,0x00,0x01,0xF5}, 4, 0}, - {0x29, (uint8_t []){0x00}, 0, 10}, - {0x51, (uint8_t []){0xFF}, 1, 0}, -}; - -// 在waveshare_amoled_2_06类之前添加新的显示类 -class CustomLcdDisplay : public SpiLcdDisplay { -public: - static void rounder_event_cb(lv_event_t* e) { - lv_area_t* area = (lv_area_t* )lv_event_get_param(e); - uint16_t x1 = area->x1; - uint16_t x2 = area->x2; - - uint16_t y1 = area->y1; - uint16_t y2 = area->y2; - - // round the start of coordinate down to the nearest 2M number - area->x1 = (x1 >> 1) << 1; - area->y1 = (y1 >> 1) << 1; - // round the end of coordinate up to the nearest 2N+1 number - area->x2 = ((x2 >> 1) << 1) + 1; - area->y2 = ((y2 >> 1) << 1) + 1; - } - - CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, - esp_lcd_panel_handle_t panel_handle, - int width, - int height, - int offset_x, - int offset_y, - bool mirror_x, - bool mirror_y, - bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, - width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { - DisplayLockGuard lock(this); - lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES* 0.1, 0); - lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES* 0.1, 0); - lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL); - } -}; - -class CustomBacklight : public Backlight { -public: - CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {} - -protected: - esp_lcd_panel_io_handle_t panel_io_; - - virtual void SetBrightnessImpl(uint8_t brightness) override { - auto display = Board::GetInstance().GetDisplay(); - DisplayLockGuard lock(display); - uint8_t data[1] = {((uint8_t)((255* brightness) / 100))}; - int lcd_cmd = 0x51; - lcd_cmd &= 0xff; - lcd_cmd <<= 8; - lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24; - esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data)); - } -}; - -class WaveshareEsp32s3TouchAMOLED2inch06 : public WifiBoard { -private: - i2c_master_bus_handle_t i2c_bus_; - Pmic* pmic_ = nullptr; - Button boot_button_; - CustomLcdDisplay* display_; - CustomBacklight* backlight_; - PowerSaveTimer* power_save_timer_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(20); }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); }); - power_save_timer_->OnShutdownRequest([this](){ - pmic_->PowerOff(); }); - power_save_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeAxp2101() { - ESP_LOGI(TAG, "Init AXP2101"); - pmic_ = new Pmic(i2c_bus_, 0x34); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.sclk_io_num = EXAMPLE_PIN_NUM_LCD_PCLK; - buscfg.data0_io_num = EXAMPLE_PIN_NUM_LCD_DATA0; - buscfg.data1_io_num = EXAMPLE_PIN_NUM_LCD_DATA1; - buscfg.data2_io_num = EXAMPLE_PIN_NUM_LCD_DATA2; - buscfg.data3_io_num = EXAMPLE_PIN_NUM_LCD_DATA3; - buscfg.max_transfer_sz = DISPLAY_WIDTH* DISPLAY_HEIGHT* sizeof(uint16_t); - buscfg.flags = SPICOMMON_BUSFLAG_QUAD; - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - -#if CONFIG_USE_DEVICE_AEC - boot_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateIdle) { - app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); - } - }); -#endif - } - - void InitializeSH8601Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = SH8601_PANEL_IO_QSPI_CONFIG( - EXAMPLE_PIN_NUM_LCD_CS, - nullptr, - nullptr); - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGD(TAG, "Install LCD driver"); - const sh8601_vendor_config_t vendor_config = { - .init_cmds = &vendor_specific_init[0], - .init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t), - .flags = { - .use_qspi_interface = 1, - }}; - - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = (void* )&vendor_config; - ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel)); - esp_lcd_panel_set_gap(panel, 0x16, 0); - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, false); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - esp_lcd_panel_disp_on_off(panel, true); - display_ = new CustomLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - backlight_ = new CustomBacklight(panel_io); - backlight_->RestoreBrightness(); - } - - void InitializeTouch() { - esp_lcd_touch_handle_t tp; - esp_lcd_touch_config_t tp_cfg = { - .x_max = DISPLAY_WIDTH - 1, - .y_max = DISPLAY_HEIGHT - 1, - .rst_gpio_num = GPIO_NUM_9, - .int_gpio_num = GPIO_NUM_38, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = 0, - .mirror_x = 0, - .mirror_y = 0, - }, - }; - esp_lcd_panel_io_handle_t tp_io_handle = NULL; - esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG(); - tp_io_config.scl_speed_hz = 400* 1000; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); - ESP_LOGI(TAG, "Initialize touch controller"); - ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp)); - const lvgl_port_touch_cfg_t touch_cfg = { - .disp = lv_display_get_default(), - .handle = tp, - }; - lvgl_port_add_touch(&touch_cfg); - ESP_LOGI(TAG, "Touch panel initialized successfully"); - } - - // 初始化工具 - void InitializeTools() { - auto &mcp_server = McpServer::GetInstance(); - mcp_server.AddTool("self.system.reconfigure_wifi", - "Reboot the device and enter WiFi configuration mode.\n" - "**CAUTION** You must ask the user to confirm this action.", - PropertyList(), [this](const PropertyList& properties) { - ResetWifiConfiguration(); - return true; - }); - } - -public: - WaveshareEsp32s3TouchAMOLED2inch06() : boot_button_(BOOT_BUTTON_GPIO) { - InitializePowerSaveTimer(); - InitializeCodecI2c(); - InitializeAxp2101(); - InitializeSpi(); - InitializeSH8601Display(); - InitializeTouch(); - InitializeButtons(); - InitializeTools(); - } - - virtual AudioCodec* GetAudioCodec() override { - static BoxAudioCodec audio_codec( - i2c_bus_, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8311_ADDR, - AUDIO_CODEC_ES7210_ADDR, - AUDIO_INPUT_REFERENCE); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - return backlight_; - } - - virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override { - static bool last_discharging = false; - charging = pmic_->IsCharging(); - discharging = pmic_->IsDischarging(); - if (discharging != last_discharging) - { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - - level = pmic_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) - { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(WaveshareEsp32s3TouchAMOLED2inch06); +#include "wifi_board.h" +#include "display/lcd_display.h" +#include "esp_lcd_sh8601.h" + +#include "codecs/box_audio_codec.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "mcp_server.h" +#include "config.h" +#include "power_save_timer.h" +#include "axp2101.h" +#include "i2c_device.h" +#include + +#include +#include +#include +#include +#include "settings.h" + +#include +#include +#include + +#define TAG "WaveshareEsp32s3TouchAMOLED2inch06" + +class Pmic : public Axp2101 { +public: + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable + WriteReg(0x27, 0x10); // hold 4s to power off + + // Disable All DCs but DC1 + WriteReg(0x80, 0x01); + // Disable All LDOs + WriteReg(0x90, 0x00); + WriteReg(0x91, 0x00); + + // Set DC1 to 3.3V + WriteReg(0x82, (3300 - 1500) / 100); + + // Set ALDO1 to 3.3V + WriteReg(0x92, (3300 - 500) / 100); + WriteReg(0x93, (3300 - 500) / 100); + + // Enable ALDO1(MIC) + WriteReg(0x90, 0x03); + + WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V + + WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA + WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) + WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA + } +}; + +#define LCD_OPCODE_WRITE_CMD (0x02ULL) +#define LCD_OPCODE_READ_CMD (0x03ULL) +#define LCD_OPCODE_WRITE_COLOR (0x32ULL) + +static const sh8601_lcd_init_cmd_t vendor_specific_init[] = { + // set display to qspi mode + {0x11, (uint8_t []){0x00}, 0, 120}, + {0xC4, (uint8_t []){0x80}, 1, 0}, + {0x44, (uint8_t []){0x01, 0xD1}, 2, 0}, + {0x35, (uint8_t []){0x00}, 1, 0}, + {0x53, (uint8_t []){0x20}, 1, 10}, + {0x63, (uint8_t []){0xFF}, 1, 10}, + {0x51, (uint8_t []){0x00}, 1, 10}, + {0x2A, (uint8_t []){0x00,0x16,0x01,0xAF}, 4, 0}, + {0x2B, (uint8_t []){0x00,0x00,0x01,0xF5}, 4, 0}, + {0x29, (uint8_t []){0x00}, 0, 10}, + {0x51, (uint8_t []){0xFF}, 1, 0}, +}; + +// 在waveshare_amoled_2_06类之前添加新的显示类 +class CustomLcdDisplay : public SpiLcdDisplay { +public: + static void rounder_event_cb(lv_event_t* e) { + lv_area_t* area = (lv_area_t* )lv_event_get_param(e); + uint16_t x1 = area->x1; + uint16_t x2 = area->x2; + + uint16_t y1 = area->y1; + uint16_t y2 = area->y2; + + // round the start of coordinate down to the nearest 2M number + area->x1 = (x1 >> 1) << 1; + area->y1 = (y1 >> 1) << 1; + // round the end of coordinate up to the nearest 2N+1 number + area->x2 = ((x2 >> 1) << 1) + 1; + area->y2 = ((y2 >> 1) << 1) + 1; + } + + CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_handle_t panel_handle, + int width, + int height, + int offset_x, + int offset_y, + bool mirror_x, + bool mirror_y, + bool swap_xy) + : SpiLcdDisplay(io_handle, panel_handle, + width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { + DisplayLockGuard lock(this); + lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES* 0.1, 0); + lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES* 0.1, 0); + lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL); + } +}; + +class CustomBacklight : public Backlight { +public: + CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {} + +protected: + esp_lcd_panel_io_handle_t panel_io_; + + virtual void SetBrightnessImpl(uint8_t brightness) override { + auto display = Board::GetInstance().GetDisplay(); + DisplayLockGuard lock(display); + uint8_t data[1] = {((uint8_t)((255* brightness) / 100))}; + int lcd_cmd = 0x51; + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24; + esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data)); + } +}; + +class WaveshareEsp32s3TouchAMOLED2inch06 : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Pmic* pmic_ = nullptr; + Button boot_button_; + CustomLcdDisplay* display_; + CustomBacklight* backlight_; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(20); }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); }); + power_save_timer_->OnShutdownRequest([this](){ + pmic_->PowerOff(); }); + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeAxp2101() { + ESP_LOGI(TAG, "Init AXP2101"); + pmic_ = new Pmic(i2c_bus_, 0x34); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.sclk_io_num = EXAMPLE_PIN_NUM_LCD_PCLK; + buscfg.data0_io_num = EXAMPLE_PIN_NUM_LCD_DATA0; + buscfg.data1_io_num = EXAMPLE_PIN_NUM_LCD_DATA1; + buscfg.data2_io_num = EXAMPLE_PIN_NUM_LCD_DATA2; + buscfg.data3_io_num = EXAMPLE_PIN_NUM_LCD_DATA3; + buscfg.max_transfer_sz = DISPLAY_WIDTH* DISPLAY_HEIGHT* sizeof(uint16_t); + buscfg.flags = SPICOMMON_BUSFLAG_QUAD; + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif + } + + void InitializeSH8601Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = SH8601_PANEL_IO_QSPI_CONFIG( + EXAMPLE_PIN_NUM_LCD_CS, + nullptr, + nullptr); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + const sh8601_vendor_config_t vendor_config = { + .init_cmds = &vendor_specific_init[0], + .init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t), + .flags = { + .use_qspi_interface = 1, + }}; + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = (void* )&vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel)); + esp_lcd_panel_set_gap(panel, 0x16, 0); + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_disp_on_off(panel, true); + display_ = new CustomLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + backlight_ = new CustomBacklight(panel_io); + backlight_->RestoreBrightness(); + } + + void InitializeTouch() { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH - 1, + .y_max = DISPLAY_HEIGHT - 1, + .rst_gpio_num = GPIO_NUM_9, + .int_gpio_num = GPIO_NUM_38, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG(); + tp_io_config.scl_speed_hz = 400* 1000; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); + ESP_LOGI(TAG, "Initialize touch controller"); + ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp)); + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + lvgl_port_add_touch(&touch_cfg); + ESP_LOGI(TAG, "Touch panel initialized successfully"); + } + + // 初始化工具 + void InitializeTools() { + auto &mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.system.reconfigure_wifi", + "Reboot the device and enter WiFi configuration mode.\n" + "**CAUTION** You must ask the user to confirm this action.", + PropertyList(), [this](const PropertyList& properties) { + ResetWifiConfiguration(); + return true; + }); + } + +public: + WaveshareEsp32s3TouchAMOLED2inch06() : boot_button_(BOOT_BUTTON_GPIO) { + InitializePowerSaveTimer(); + InitializeCodecI2c(); + InitializeAxp2101(); + InitializeSpi(); + InitializeSH8601Display(); + InitializeTouch(); + InitializeButtons(); + InitializeTools(); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + return backlight_; + } + + virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override { + static bool last_discharging = false; + charging = pmic_->IsCharging(); + discharging = pmic_->IsDischarging(); + if (discharging != last_discharging) + { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + + level = pmic_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) + { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(WaveshareEsp32s3TouchAMOLED2inch06); diff --git a/main/boards/waveshare-s3-touch-lcd-3.49/README.md b/main/boards/waveshare-s3-touch-lcd-3.49/README.md new file mode 100644 index 0000000..c5c41e4 --- /dev/null +++ b/main/boards/waveshare-s3-touch-lcd-3.49/README.md @@ -0,0 +1,3 @@ +新增 微雪 开发板: ESP32-S3-Touch-LCD-3.49 +产品链接: +https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-3.49.htm \ No newline at end of file diff --git a/main/boards/waveshare-s3-touch-lcd-3.49/config.h b/main/boards/waveshare-s3-touch-lcd-3.49/config.h new file mode 100644 index 0000000..500894d --- /dev/null +++ b/main/boards/waveshare-s3-touch-lcd-3.49/config.h @@ -0,0 +1,62 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include +#include "lvgl.h" + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_7 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_46 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48 +#define Dev_Touch_I2C_SDA_PIN GPIO_NUM_17 +#define Dev_Touch_I2C_SCL_PIN GPIO_NUM_18 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define I2C_Touch_ADDRESS 0x3b +#define I2C_Touch_SDA_PIN GPIO_NUM_17 +#define I2C_Touch_SCL_PIN GPIO_NUM_18 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define PWR_BUTTON_GPIO GPIO_NUM_16 + +#define LCD_CS GPIO_NUM_9 +#define LCD_PCLK GPIO_NUM_10 +#define LCD_D0 GPIO_NUM_11 +#define LCD_D1 GPIO_NUM_12 +#define LCD_D2 GPIO_NUM_13 +#define LCD_D3 GPIO_NUM_14 +#define LCD_RST GPIO_NUM_21 +#define LCD_LIGHT (-1) + +#define DISPLAY_WIDTH 172 +#define DISPLAY_HEIGHT 640 +#define LVGL_DMA_BUFF_LEN (DISPLAY_WIDTH * 64 * 2) +#define LVGL_SPIRAM_BUFF_LEN (DISPLAY_WIDTH * DISPLAY_HEIGHT * 2) + +#define DISPLAY_ROTATION_90 false + +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_8 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/waveshare-s3-touch-lcd-3.49/config.json b/main/boards/waveshare-s3-touch-lcd-3.49/config.json new file mode 100644 index 0000000..0d25bb1 --- /dev/null +++ b/main/boards/waveshare-s3-touch-lcd-3.49/config.json @@ -0,0 +1,12 @@ +{ + "target": "esp32s3", + "builds": [ + { + "name": "waveshare-s3-touch-lcd-3.49", + "sdkconfig_append": [ + "CONFIG_USE_WECHAT_MESSAGE_STYLE=n", + "CONFIG_USE_DEVICE_AEC=y" + ] + } + ] +} \ No newline at end of file diff --git a/main/boards/waveshare-s3-touch-lcd-3.49/custom_lcd_display.cc b/main/boards/waveshare-s3-touch-lcd-3.49/custom_lcd_display.cc new file mode 100644 index 0000000..0bbc721 --- /dev/null +++ b/main/boards/waveshare-s3-touch-lcd-3.49/custom_lcd_display.cc @@ -0,0 +1,145 @@ +#include "custom_lcd_display.h" + +#include "lcd_display.h" + +#include +#include +#include +#include +#include "assets/lang_config.h" +#include +#include "settings.h" + +#include "esp_lcd_panel_io.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include "config.h" + +#include "board.h" + +#define TAG "CustomLcdDisplay" + +static SemaphoreHandle_t trans_done_sem = NULL; +static uint16_t *trans_buf_1; + +#if (DISPLAY_ROTATION_90 == true) +static uint16_t *dest_map; +#endif + + +bool CustomLcdDisplay::lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) { + BaseType_t taskAwake = pdFALSE; + lv_display_t *disp_drv = (lv_display_t *)user_ctx; + assert(disp_drv != NULL); + if (trans_done_sem) { + xSemaphoreGiveFromISR(trans_done_sem, &taskAwake); + } + return false; +} + +void CustomLcdDisplay::lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map) { + assert(drv != NULL); + esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(drv); + assert(panel_handle != NULL); + + lv_draw_sw_rgb565_swap(color_map, lv_area_get_width(area) * lv_area_get_height(area)); + +#if (DISPLAY_ROTATION_90 == true) + lv_display_rotation_t rotation = lv_display_get_rotation(drv); + lv_area_t rotated_area; + if(rotation != LV_DISPLAY_ROTATION_0) { + lv_color_format_t cf = lv_display_get_color_format(drv); + /*Calculate the position of the rotated area*/ + rotated_area = *area; + lv_display_rotate_area(drv, &rotated_area); + /*Calculate the source stride (bytes in a line) from the width of the area*/ + uint32_t src_stride = lv_draw_buf_width_to_stride(lv_area_get_width(area), cf); + /*Calculate the stride of the destination (rotated) area too*/ + uint32_t dest_stride = lv_draw_buf_width_to_stride(lv_area_get_width(&rotated_area), cf); + /*Have a buffer to store the rotated area and perform the rotation*/ + + int32_t src_w = lv_area_get_width(area); + int32_t src_h = lv_area_get_height(area); + lv_draw_sw_rotate(color_map, dest_map, src_w, src_h, src_stride, dest_stride, rotation, cf); + /*Use the rotated area and rotated buffer from now on*/ + area = &rotated_area; + } +#endif + const int flush_coun = (LVGL_SPIRAM_BUFF_LEN / LVGL_DMA_BUFF_LEN); + const int offgap = (DISPLAY_HEIGHT / flush_coun); + const int dmalen = (LVGL_DMA_BUFF_LEN / 2); + int offsetx1 = 0; + int offsety1 = 0; + int offsetx2 = DISPLAY_WIDTH; + int offsety2 = offgap; +#if (DISPLAY_ROTATION_90 == true) + uint16_t *map = (uint16_t*)dest_map; +#else + uint16_t *map = (uint16_t*)color_map; +#endif + xSemaphoreGive(trans_done_sem); + + for(int i = 0; i +#include "i2c_device.h" +#include +#include +#include +#include +#include +#include + +#include +#include "esp_io_expander_tca9554.h" + +#include "esp_lcd_axs15231b.h" + +#include "custom_lcd_display.h" + +#include + +#define TAG "waveshare_lcd_3_39" + + +static const axs15231b_lcd_init_cmd_t lcd_init_cmds[] = { + {0x11, (uint8_t []){0x00}, 0, 100}, + {0x29, (uint8_t []){0x00}, 0, 100}, +}; + +class CustomBoard : public WifiBoard { +private: + Button boot_button_; + Button pwr_button_; + i2c_master_bus_handle_t i2c_bus_; + esp_io_expander_handle_t io_expander = NULL; + LcdDisplay* display_; + i2c_master_dev_handle_t disp_touch_dev_handle = NULL; + lv_indev_t *touch_indev = NULL; //touch + bool is_PwrControlEn = false; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeTca9554(void) { + esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander); + if(ret != ESP_OK) + ESP_LOGE(TAG, "TCA9554 create returned error"); + ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_7 | IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_OUTPUT); + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(100)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_7 | IO_EXPANDER_PIN_NUM_6, 1); + ESP_ERROR_CHECK(ret); + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize QSPI bus"); + spi_bus_config_t buscfg = {}; + buscfg.data0_io_num = LCD_D0; + buscfg.data1_io_num = LCD_D1; + buscfg.data2_io_num = LCD_D2; + buscfg.data3_io_num = LCD_D3; + buscfg.sclk_io_num = LCD_PCLK; + buscfg.max_transfer_sz = LVGL_DMA_BUFF_LEN; + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // RESET PIN INIT + gpio_config_t gpio_conf = {}; + gpio_conf.intr_type = GPIO_INTR_DISABLE; + gpio_conf.mode = GPIO_MODE_OUTPUT; + gpio_conf.pin_bit_mask = ((uint64_t)0x01<0 && buff[1]<5) { + indevData->state = LV_INDEV_STATE_PRESSED; + if(pointX > DISPLAY_WIDTH) pointX = DISPLAY_WIDTH; + if(pointY > DISPLAY_HEIGHT) pointY = DISPLAY_HEIGHT; + indevData->point.x = pointY; + indevData->point.y = (DISPLAY_HEIGHT-pointX); + ESP_LOGE("Touch","(%ld,%ld)",indevData->point.x,indevData->point.y); + } else { + indevData->state = LV_INDEV_STATE_RELEASED; + } + } + + void GetPwrCurrentState() { + if(gpio_get_level(PWR_BUTTON_GPIO)) { + is_PwrControlEn = true; + } + } + +public: + CustomBoard() : + boot_button_(BOOT_BUTTON_GPIO), + pwr_button_(PWR_BUTTON_GPIO) { + InitializeI2c(); + InitializeTca9554(); + InitializeSpi(); + InitializeLcdDisplay(); + InitializeButtons(); + InitializeTouch(); + GetPwrCurrentState(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +}; + +DECLARE_BOARD(CustomBoard); \ No newline at end of file diff --git a/main/boards/waveshare-s3-touch-lcd-3.5b/README.md b/main/boards/waveshare-s3-touch-lcd-3.5b/README.md index aaadeae..fe74b57 100644 --- a/main/boards/waveshare-s3-touch-lcd-3.5b/README.md +++ b/main/boards/waveshare-s3-touch-lcd-3.5b/README.md @@ -1,3 +1,3 @@ -新增 微雪 开发板: ESP32-S3-Touch-LCD-3.5B -产品链接: +新增 微雪 开发板: ESP32-S3-Touch-LCD-3.5B +产品链接: https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-3.5B.htm \ No newline at end of file diff --git a/main/boards/waveshare-s3-touch-lcd-3.5b/config.h b/main/boards/waveshare-s3-touch-lcd-3.5b/config.h index c44d734..7ad8f87 100644 --- a/main/boards/waveshare-s3-touch-lcd-3.5b/config.h +++ b/main/boards/waveshare-s3-touch-lcd-3.5b/config.h @@ -1,78 +1,78 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include -#include -#include "lvgl.h" - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_44 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_15 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_NC -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SPI_MODE 0 -#define DISPLAY_CS_PIN GPIO_NUM_12 -#define DISPLAY_CLK_PIN GPIO_NUM_5 -#define DISPLAY_DATA0_PIN GPIO_NUM_1 -#define DISPLAY_DATA1_PIN GPIO_NUM_2 -#define DISPLAY_DATA2_PIN GPIO_NUM_3 -#define DISPLAY_DATA3_PIN GPIO_NUM_4 - -#define DISPLAY_RST_PIN GPIO_NUM_NC - - - -#define DISPLAY_WIDTH 480 -#define DISPLAY_HEIGHT 320 -#define DISPLAY_TRANS_SIZE (DISPLAY_WIDTH * 10) - -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false -#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#define DISPLAY_INVERT_COLOR false - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define LV_DISPLAY_ROTATION LV_DISPLAY_ROTATION_90 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_6 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define PMIC_ENABLE 0 -#define TOUCH_ENABLE 1 - -#define CAM_PIN_PWDN GPIO_NUM_NC -#define CAM_PIN_RESET GPIO_NUM_NC -#define CAM_PIN_VSYNC GPIO_NUM_17 -#define CAM_PIN_HREF GPIO_NUM_18 -#define CAM_PIN_PCLK GPIO_NUM_41 -#define CAM_PIN_XCLK GPIO_NUM_38 -#define CAM_PIN_SIOD GPIO_NUM_NC -#define CAM_PIN_SIOC GPIO_NUM_NC -#define CAM_PIN_D0 GPIO_NUM_45 -#define CAM_PIN_D1 GPIO_NUM_47 -#define CAM_PIN_D2 GPIO_NUM_48 -#define CAM_PIN_D3 GPIO_NUM_46 -#define CAM_PIN_D4 GPIO_NUM_42 -#define CAM_PIN_D5 GPIO_NUM_40 -#define CAM_PIN_D6 GPIO_NUM_39 -#define CAM_PIN_D7 GPIO_NUM_21 - - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include +#include +#include "lvgl.h" + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_44 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_15 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SPI_MODE 0 +#define DISPLAY_CS_PIN GPIO_NUM_12 +#define DISPLAY_CLK_PIN GPIO_NUM_5 +#define DISPLAY_DATA0_PIN GPIO_NUM_1 +#define DISPLAY_DATA1_PIN GPIO_NUM_2 +#define DISPLAY_DATA2_PIN GPIO_NUM_3 +#define DISPLAY_DATA3_PIN GPIO_NUM_4 + +#define DISPLAY_RST_PIN GPIO_NUM_NC + + + +#define DISPLAY_WIDTH 480 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_TRANS_SIZE (DISPLAY_WIDTH * 10) + +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_INVERT_COLOR false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define LV_DISPLAY_ROTATION LV_DISPLAY_ROTATION_90 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_6 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define PMIC_ENABLE 0 +#define TOUCH_ENABLE 1 + +#define CAM_PIN_PWDN GPIO_NUM_NC +#define CAM_PIN_RESET GPIO_NUM_NC +#define CAM_PIN_VSYNC GPIO_NUM_17 +#define CAM_PIN_HREF GPIO_NUM_18 +#define CAM_PIN_PCLK GPIO_NUM_41 +#define CAM_PIN_XCLK GPIO_NUM_38 +#define CAM_PIN_SIOD GPIO_NUM_NC +#define CAM_PIN_SIOC GPIO_NUM_NC +#define CAM_PIN_D0 GPIO_NUM_45 +#define CAM_PIN_D1 GPIO_NUM_47 +#define CAM_PIN_D2 GPIO_NUM_48 +#define CAM_PIN_D3 GPIO_NUM_46 +#define CAM_PIN_D4 GPIO_NUM_42 +#define CAM_PIN_D5 GPIO_NUM_40 +#define CAM_PIN_D6 GPIO_NUM_39 +#define CAM_PIN_D7 GPIO_NUM_21 + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/waveshare-s3-touch-lcd-3.5b/config.json b/main/boards/waveshare-s3-touch-lcd-3.5b/config.json index aaa7c6e..8782e8c 100644 --- a/main/boards/waveshare-s3-touch-lcd-3.5b/config.json +++ b/main/boards/waveshare-s3-touch-lcd-3.5b/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "waveshare-s3-touch-lcd-3.5b", - "sdkconfig_append": [ - "CONFIG_USE_WECHAT_MESSAGE_STYLE=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "waveshare-s3-touch-lcd-3.5b", + "sdkconfig_append": [ + "CONFIG_USE_WECHAT_MESSAGE_STYLE=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/waveshare-s3-touch-lcd-3.5b/custom_lcd_display.cc b/main/boards/waveshare-s3-touch-lcd-3.5b/custom_lcd_display.cc index 11d492f..3642b34 100644 --- a/main/boards/waveshare-s3-touch-lcd-3.5b/custom_lcd_display.cc +++ b/main/boards/waveshare-s3-touch-lcd-3.5b/custom_lcd_display.cc @@ -1,286 +1,286 @@ - -#include "config.h" -#include "custom_lcd_display.h" -#include "lcd_display.h" -#include "assets/lang_config.h" -#include "settings.h" -#include "board.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#define TAG "CustomLcdDisplay" - - -static SemaphoreHandle_t trans_done_sem = NULL; -static uint16_t *trans_act; -static uint16_t *trans_buf_1; -static uint16_t *trans_buf_2; - -bool CustomLcdDisplay::lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) -{ - BaseType_t taskAwake = pdFALSE; - lv_display_t *disp_drv = (lv_display_t *)user_ctx; - assert(disp_drv != NULL); - if (trans_done_sem) { - xSemaphoreGiveFromISR(trans_done_sem, &taskAwake); - } - return false; -} - -void CustomLcdDisplay::lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map) -{ - assert(drv != NULL); - esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_driver_data(drv); - assert(panel_handle != NULL); - - size_t len = lv_area_get_size(area); - lv_draw_sw_rgb565_swap(color_map, len); - - const int x_start = area->x1; - const int x_end = area->x2; - const int y_start = area->y1; - const int y_end = area->y2; - const int width = x_end - x_start + 1; - const int height = y_end - y_start + 1; - - int32_t hor_res = lv_display_get_horizontal_resolution(drv); - int32_t ver_res = lv_display_get_vertical_resolution(drv); - - // printf("hor_res: %ld, ver_res: %ld\r\n", hor_res, ver_res); - // printf("x_start: %d, x_end: %d, y_start: %d, y_end: %d, width: %d, height: %d\r\n", x_start, x_end, y_start, y_end, width, height); - uint16_t *from = (uint16_t *)color_map; - uint16_t *to = NULL; - - if (DISPLAY_TRANS_SIZE > 0) { - assert(trans_buf_1 != NULL); - - int x_draw_start = 0; - int x_draw_end = 0; - int y_draw_start = 0; - int y_draw_end = 0; - int trans_count = 0; - - trans_act = trans_buf_1; - lv_display_rotation_t rotate = LV_DISPLAY_ROTATION; - - int x_start_tmp = 0; - int x_end_tmp = 0; - int max_width = 0; - int trans_width = 0; - - int y_start_tmp = 0; - int y_end_tmp = 0; - int max_height = 0; - int trans_height = 0; - - if (LV_DISPLAY_ROTATION_270 == rotate || LV_DISPLAY_ROTATION_90 == rotate) { - max_width = ((DISPLAY_TRANS_SIZE / height) > width) ? (width) : (DISPLAY_TRANS_SIZE / height); - trans_count = width / max_width + (width % max_width ? (1) : (0)); - - x_start_tmp = x_start; - x_end_tmp = x_end; - } else { - max_height = ((DISPLAY_TRANS_SIZE / width) > height) ? (height) : (DISPLAY_TRANS_SIZE / width); - trans_count = height / max_height + (height % max_height ? (1) : (0)); - - y_start_tmp = y_start; - y_end_tmp = y_end; - } - - for (int i = 0; i < trans_count; i++) { - - if (LV_DISPLAY_ROTATION_90 == rotate) { - trans_width = (x_end - x_start_tmp + 1) > max_width ? max_width : (x_end - x_start_tmp + 1); - x_end_tmp = (x_end - x_start_tmp + 1) > max_width ? (x_start_tmp + max_width - 1) : x_end; - } else if (LV_DISPLAY_ROTATION_270 == rotate) { - trans_width = (x_end_tmp - x_start + 1) > max_width ? max_width : (x_end_tmp - x_start + 1); - x_start_tmp = (x_end_tmp - x_start + 1) > max_width ? (x_end_tmp - trans_width + 1) : x_start; - } else if (LV_DISPLAY_ROTATION_0 == rotate) { - trans_height = (y_end - y_start_tmp + 1) > max_height ? max_height : (y_end - y_start_tmp + 1); - y_end_tmp = (y_end - y_start_tmp + 1) > max_height ? (y_start_tmp + max_height - 1) : y_end; - } else { - trans_height = (y_end_tmp - y_start + 1) > max_height ? max_height : (y_end_tmp - y_start + 1); - y_start_tmp = (y_end_tmp - y_start + 1) > max_height ? (y_end_tmp - max_height + 1) : y_start; - } - - trans_act = (trans_act == trans_buf_1) ? (trans_buf_2) : (trans_buf_1); - to = trans_act; - - switch (rotate) { - case LV_DISPLAY_ROTATION_90: - for (int y = 0; y < height; y++) { - for (int x = 0; x < trans_width; x++) { - *(to + x * height + (height - y - 1)) = *(from + y * width + x_start_tmp + x); - } - } - x_draw_start = ver_res - y_end - 1; - x_draw_end = ver_res - y_start - 1; - y_draw_start = x_start_tmp; - y_draw_end = x_end_tmp; - break; - case LV_DISPLAY_ROTATION_270: - for (int y = 0; y < height; y++) { - for (int x = 0; x < trans_width; x++) { - *(to + (trans_width - x - 1) * height + y) = *(from + y * width + x_start_tmp + x); - } - } - x_draw_start = y_start; - x_draw_end = y_end; - y_draw_start = hor_res - x_end_tmp - 1; - y_draw_end = hor_res - x_start_tmp - 1; - break; - case LV_DISPLAY_ROTATION_180: - for (int y = 0; y < trans_height; y++) { - for (int x = 0; x < width; x++) { - *(to + (trans_height - y - 1)*width + (width - x - 1)) = *(from + y_start_tmp * width + y * (width) + x); - } - } - x_draw_start = hor_res - x_end - 1; - x_draw_end = hor_res - x_start - 1; - y_draw_start = ver_res - y_end_tmp - 1; - y_draw_end = ver_res - y_start_tmp - 1; - break; - case LV_DISPLAY_ROTATION_0: - for (int y = 0; y < trans_height; y++) { - for (int x = 0; x < width; x++) { - *(to + y * (width) + x) = *(from + y_start_tmp * width + y * (width) + x); - } - } - x_draw_start = x_start; - x_draw_end = x_end; - y_draw_start = y_start_tmp; - y_draw_end = y_end_tmp; - break; - default: - break; - } - - if (0 == i) { - // if (disp_ctx->draw_wait_cb) { - // disp_ctx->draw_wait_cb(disp_ctx->panel_handle->user_data); - // } - xSemaphoreGive(trans_done_sem); - } - - xSemaphoreTake(trans_done_sem, portMAX_DELAY); - // printf("i: %d, x_draw_start: %d, x_draw_end: %d, y_draw_start: %d, y_draw_end: %d\r\n", i, x_draw_start, x_draw_end, y_draw_start, y_draw_end); - esp_lcd_panel_draw_bitmap(panel_handle, x_draw_start, y_draw_start, x_draw_end + 1, y_draw_end + 1, to); - - if (LV_DISPLAY_ROTATION_90 == rotate) { - x_start_tmp += max_width; - } else if (LV_DISPLAY_ROTATION_270 == rotate) { - x_end_tmp -= max_width; - } if (LV_DISPLAY_ROTATION_0 == rotate) { - y_start_tmp += max_height; - } else { - y_end_tmp -= max_height; - } - } - } else { - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map); - } - lv_disp_flush_ready(drv); -} - -CustomLcdDisplay::CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : LcdDisplay(panel_io, panel, width, height) { - // width_ = width; - // height_ = height; - - // draw white - std::vector buffer(width_, 0xFFFF); - for (int y = 0; y < height_; y++) { - esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data()); - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - ESP_LOGI(TAG, "Initialize LVGL library"); - lv_init(); - - ESP_LOGI(TAG, "Initialize LVGL port"); - lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); - port_cfg.task_priority = 1; - port_cfg.timer_period_ms = 50; - lvgl_port_init(&port_cfg); - trans_done_sem = xSemaphoreCreateCounting(1, 0); - trans_buf_1 = (uint16_t *)heap_caps_malloc(DISPLAY_TRANS_SIZE * sizeof(uint16_t), MALLOC_CAP_DMA); - trans_buf_2 = (uint16_t *)heap_caps_malloc(DISPLAY_TRANS_SIZE * sizeof(uint16_t), MALLOC_CAP_DMA); -#if 0 - ESP_LOGI(TAG, "Adding LCD screen"); - const lvgl_port_display_cfg_t display_cfg = { - .io_handle = panel_io_, - .panel_handle = panel_, - .control_handle = nullptr, - .buffer_size = static_cast(width_ * height_), - .double_buffer = false, - .trans_size = 0, - .hres = static_cast(width_), - .vres = static_cast(height_), - .monochrome = false, - .rotation = { - .swap_xy = swap_xy, - .mirror_x = mirror_x, - .mirror_y = mirror_y, - }, - .color_format = LV_COLOR_FORMAT_RGB565, - .flags = { - .buff_dma = 0, - .buff_spiram = 1, - .sw_rotate = 0, - .swap_bytes = 1, - .full_refresh = 1, - .direct_mode = 0, - }, - }; - - display_ = lvgl_port_add_disp(&display_cfg); - lv_display_set_flush_cb(display_, lvgl_port_flush_callback); -#else - - uint32_t buffer_size = 0; - lv_color_t *buf1 = NULL; - lvgl_port_lock(0); - uint8_t color_bytes = lv_color_format_get_size(LV_COLOR_FORMAT_RGB565); - display_ = lv_display_create(width_, height_); - lv_display_set_flush_cb(display_, lvgl_port_flush_callback); - buffer_size = width_ * height_; - buf1 = (lv_color_t *)heap_caps_aligned_alloc(1, buffer_size * color_bytes, MALLOC_CAP_SPIRAM); - lv_display_set_buffers(display_, buf1, NULL, buffer_size * color_bytes, LV_DISPLAY_RENDER_MODE_FULL); - lv_display_set_driver_data(display_, panel_); - lvgl_port_unlock(); - -#endif - - esp_lcd_panel_io_callbacks_t cbs = { - .on_color_trans_done = lvgl_port_flush_io_ready_callback, - }; - /* Register done callback */ - esp_lcd_panel_io_register_event_callbacks(panel_io_, &cbs, display_); - - esp_lcd_panel_disp_on_off(panel_, false); - - if (display_ == nullptr) { - ESP_LOGE(TAG, "Failed to add display"); - return; - } - - if (offset_x != 0 || offset_y != 0) { - lv_display_set_offset(display_, offset_x, offset_y); - } - - - SetupUI(); + +#include "config.h" +#include "custom_lcd_display.h" +#include "lcd_display.h" +#include "assets/lang_config.h" +#include "settings.h" +#include "board.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define TAG "CustomLcdDisplay" + + +static SemaphoreHandle_t trans_done_sem = NULL; +static uint16_t *trans_act; +static uint16_t *trans_buf_1; +static uint16_t *trans_buf_2; + +bool CustomLcdDisplay::lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) +{ + BaseType_t taskAwake = pdFALSE; + lv_display_t *disp_drv = (lv_display_t *)user_ctx; + assert(disp_drv != NULL); + if (trans_done_sem) { + xSemaphoreGiveFromISR(trans_done_sem, &taskAwake); + } + return false; +} + +void CustomLcdDisplay::lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map) +{ + assert(drv != NULL); + esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_driver_data(drv); + assert(panel_handle != NULL); + + size_t len = lv_area_get_size(area); + lv_draw_sw_rgb565_swap(color_map, len); + + const int x_start = area->x1; + const int x_end = area->x2; + const int y_start = area->y1; + const int y_end = area->y2; + const int width = x_end - x_start + 1; + const int height = y_end - y_start + 1; + + int32_t hor_res = lv_display_get_horizontal_resolution(drv); + int32_t ver_res = lv_display_get_vertical_resolution(drv); + + // printf("hor_res: %ld, ver_res: %ld\r\n", hor_res, ver_res); + // printf("x_start: %d, x_end: %d, y_start: %d, y_end: %d, width: %d, height: %d\r\n", x_start, x_end, y_start, y_end, width, height); + uint16_t *from = (uint16_t *)color_map; + uint16_t *to = NULL; + + if (DISPLAY_TRANS_SIZE > 0) { + assert(trans_buf_1 != NULL); + + int x_draw_start = 0; + int x_draw_end = 0; + int y_draw_start = 0; + int y_draw_end = 0; + int trans_count = 0; + + trans_act = trans_buf_1; + lv_display_rotation_t rotate = LV_DISPLAY_ROTATION; + + int x_start_tmp = 0; + int x_end_tmp = 0; + int max_width = 0; + int trans_width = 0; + + int y_start_tmp = 0; + int y_end_tmp = 0; + int max_height = 0; + int trans_height = 0; + + if (LV_DISPLAY_ROTATION_270 == rotate || LV_DISPLAY_ROTATION_90 == rotate) { + max_width = ((DISPLAY_TRANS_SIZE / height) > width) ? (width) : (DISPLAY_TRANS_SIZE / height); + trans_count = width / max_width + (width % max_width ? (1) : (0)); + + x_start_tmp = x_start; + x_end_tmp = x_end; + } else { + max_height = ((DISPLAY_TRANS_SIZE / width) > height) ? (height) : (DISPLAY_TRANS_SIZE / width); + trans_count = height / max_height + (height % max_height ? (1) : (0)); + + y_start_tmp = y_start; + y_end_tmp = y_end; + } + + for (int i = 0; i < trans_count; i++) { + + if (LV_DISPLAY_ROTATION_90 == rotate) { + trans_width = (x_end - x_start_tmp + 1) > max_width ? max_width : (x_end - x_start_tmp + 1); + x_end_tmp = (x_end - x_start_tmp + 1) > max_width ? (x_start_tmp + max_width - 1) : x_end; + } else if (LV_DISPLAY_ROTATION_270 == rotate) { + trans_width = (x_end_tmp - x_start + 1) > max_width ? max_width : (x_end_tmp - x_start + 1); + x_start_tmp = (x_end_tmp - x_start + 1) > max_width ? (x_end_tmp - trans_width + 1) : x_start; + } else if (LV_DISPLAY_ROTATION_0 == rotate) { + trans_height = (y_end - y_start_tmp + 1) > max_height ? max_height : (y_end - y_start_tmp + 1); + y_end_tmp = (y_end - y_start_tmp + 1) > max_height ? (y_start_tmp + max_height - 1) : y_end; + } else { + trans_height = (y_end_tmp - y_start + 1) > max_height ? max_height : (y_end_tmp - y_start + 1); + y_start_tmp = (y_end_tmp - y_start + 1) > max_height ? (y_end_tmp - max_height + 1) : y_start; + } + + trans_act = (trans_act == trans_buf_1) ? (trans_buf_2) : (trans_buf_1); + to = trans_act; + + switch (rotate) { + case LV_DISPLAY_ROTATION_90: + for (int y = 0; y < height; y++) { + for (int x = 0; x < trans_width; x++) { + *(to + x * height + (height - y - 1)) = *(from + y * width + x_start_tmp + x); + } + } + x_draw_start = ver_res - y_end - 1; + x_draw_end = ver_res - y_start - 1; + y_draw_start = x_start_tmp; + y_draw_end = x_end_tmp; + break; + case LV_DISPLAY_ROTATION_270: + for (int y = 0; y < height; y++) { + for (int x = 0; x < trans_width; x++) { + *(to + (trans_width - x - 1) * height + y) = *(from + y * width + x_start_tmp + x); + } + } + x_draw_start = y_start; + x_draw_end = y_end; + y_draw_start = hor_res - x_end_tmp - 1; + y_draw_end = hor_res - x_start_tmp - 1; + break; + case LV_DISPLAY_ROTATION_180: + for (int y = 0; y < trans_height; y++) { + for (int x = 0; x < width; x++) { + *(to + (trans_height - y - 1)*width + (width - x - 1)) = *(from + y_start_tmp * width + y * (width) + x); + } + } + x_draw_start = hor_res - x_end - 1; + x_draw_end = hor_res - x_start - 1; + y_draw_start = ver_res - y_end_tmp - 1; + y_draw_end = ver_res - y_start_tmp - 1; + break; + case LV_DISPLAY_ROTATION_0: + for (int y = 0; y < trans_height; y++) { + for (int x = 0; x < width; x++) { + *(to + y * (width) + x) = *(from + y_start_tmp * width + y * (width) + x); + } + } + x_draw_start = x_start; + x_draw_end = x_end; + y_draw_start = y_start_tmp; + y_draw_end = y_end_tmp; + break; + default: + break; + } + + if (0 == i) { + // if (disp_ctx->draw_wait_cb) { + // disp_ctx->draw_wait_cb(disp_ctx->panel_handle->user_data); + // } + xSemaphoreGive(trans_done_sem); + } + + xSemaphoreTake(trans_done_sem, portMAX_DELAY); + // printf("i: %d, x_draw_start: %d, x_draw_end: %d, y_draw_start: %d, y_draw_end: %d\r\n", i, x_draw_start, x_draw_end, y_draw_start, y_draw_end); + esp_lcd_panel_draw_bitmap(panel_handle, x_draw_start, y_draw_start, x_draw_end + 1, y_draw_end + 1, to); + + if (LV_DISPLAY_ROTATION_90 == rotate) { + x_start_tmp += max_width; + } else if (LV_DISPLAY_ROTATION_270 == rotate) { + x_end_tmp -= max_width; + } if (LV_DISPLAY_ROTATION_0 == rotate) { + y_start_tmp += max_height; + } else { + y_end_tmp -= max_height; + } + } + } else { + esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map); + } + lv_disp_flush_ready(drv); +} + +CustomLcdDisplay::CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) + : LcdDisplay(panel_io, panel, width, height) { + // width_ = width; + // height_ = height; + + // draw white + std::vector buffer(width_, 0xFFFF); + for (int y = 0; y < height_; y++) { + esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data()); + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + ESP_LOGI(TAG, "Initialize LVGL library"); + lv_init(); + + ESP_LOGI(TAG, "Initialize LVGL port"); + lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + port_cfg.task_priority = 1; + port_cfg.timer_period_ms = 50; + lvgl_port_init(&port_cfg); + trans_done_sem = xSemaphoreCreateCounting(1, 0); + trans_buf_1 = (uint16_t *)heap_caps_malloc(DISPLAY_TRANS_SIZE * sizeof(uint16_t), MALLOC_CAP_DMA); + trans_buf_2 = (uint16_t *)heap_caps_malloc(DISPLAY_TRANS_SIZE * sizeof(uint16_t), MALLOC_CAP_DMA); +#if 0 + ESP_LOGI(TAG, "Adding LCD screen"); + const lvgl_port_display_cfg_t display_cfg = { + .io_handle = panel_io_, + .panel_handle = panel_, + .control_handle = nullptr, + .buffer_size = static_cast(width_ * height_), + .double_buffer = false, + .trans_size = 0, + .hres = static_cast(width_), + .vres = static_cast(height_), + .monochrome = false, + .rotation = { + .swap_xy = swap_xy, + .mirror_x = mirror_x, + .mirror_y = mirror_y, + }, + .color_format = LV_COLOR_FORMAT_RGB565, + .flags = { + .buff_dma = 0, + .buff_spiram = 1, + .sw_rotate = 0, + .swap_bytes = 1, + .full_refresh = 1, + .direct_mode = 0, + }, + }; + + display_ = lvgl_port_add_disp(&display_cfg); + lv_display_set_flush_cb(display_, lvgl_port_flush_callback); +#else + + uint32_t buffer_size = 0; + lv_color_t *buf1 = NULL; + lvgl_port_lock(0); + uint8_t color_bytes = lv_color_format_get_size(LV_COLOR_FORMAT_RGB565); + display_ = lv_display_create(width_, height_); + lv_display_set_flush_cb(display_, lvgl_port_flush_callback); + buffer_size = width_ * height_; + buf1 = (lv_color_t *)heap_caps_aligned_alloc(1, buffer_size * color_bytes, MALLOC_CAP_SPIRAM); + lv_display_set_buffers(display_, buf1, NULL, buffer_size * color_bytes, LV_DISPLAY_RENDER_MODE_FULL); + lv_display_set_driver_data(display_, panel_); + lvgl_port_unlock(); + +#endif + + esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = lvgl_port_flush_io_ready_callback, + }; + /* Register done callback */ + esp_lcd_panel_io_register_event_callbacks(panel_io_, &cbs, display_); + + esp_lcd_panel_disp_on_off(panel_, false); + + if (display_ == nullptr) { + ESP_LOGE(TAG, "Failed to add display"); + return; + } + + if (offset_x != 0 || offset_y != 0) { + lv_display_set_offset(display_, offset_x, offset_y); + } + + + SetupUI(); } \ No newline at end of file diff --git a/main/boards/waveshare-s3-touch-lcd-3.5b/custom_lcd_display.h b/main/boards/waveshare-s3-touch-lcd-3.5b/custom_lcd_display.h index c3a9900..bd62787 100644 --- a/main/boards/waveshare-s3-touch-lcd-3.5b/custom_lcd_display.h +++ b/main/boards/waveshare-s3-touch-lcd-3.5b/custom_lcd_display.h @@ -1,19 +1,19 @@ -#ifndef __CUSTOM_LCD_DISPLAY_H__ -#define __CUSTOM_LCD_DISPLAY_H__ - -#include "lcd_display.h" - -// // SPI LCD显示器 -class CustomLcdDisplay : public LcdDisplay { -public: - CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, - bool mirror_x, bool mirror_y, bool swap_xy); -private: - static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); - static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map); -}; - - - +#ifndef __CUSTOM_LCD_DISPLAY_H__ +#define __CUSTOM_LCD_DISPLAY_H__ + +#include "lcd_display.h" + +// // SPI LCD显示器 +class CustomLcdDisplay : public LcdDisplay { +public: + CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, + bool mirror_x, bool mirror_y, bool swap_xy); +private: + static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); + static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map); +}; + + + #endif // __CUSTOM_LCD_DISPLAY_H__ \ No newline at end of file diff --git a/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc b/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc index ac15d55..62c18ef 100644 --- a/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc +++ b/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc @@ -1,373 +1,373 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" - -#include -#include "i2c_device.h" -#include -#include -#include -#include -#include -#include - -#include -#include "esp_io_expander_tca9554.h" - -#include "axp2101.h" -#include "power_save_timer.h" - -#include "esp_lcd_axs15231b.h" - -#include "custom_lcd_display.h" - -#include - -#include -#include -#include "esp32_camera.h" - -#define TAG "waveshare_lcd_3_5b" - -static const axs15231b_lcd_init_cmd_t lcd_init_cmds[] = { - {0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5}, 8, 0}, - {0xA0, (uint8_t[]){0xC0, 0x10, 0x00, 0x02, 0x00, 0x00, 0x04, 0x3F, 0x20, 0x05, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00}, 17, 0}, - {0xA2, (uint8_t[]){0x30, 0x3C, 0x24, 0x14, 0xD0, 0x20, 0xFF, 0xE0, 0x40, 0x19, 0x80, 0x80, 0x80, 0x20, 0xf9, 0x10, 0x02, 0xff, 0xff, 0xF0, 0x90, 0x01, 0x32, 0xA0, 0x91, 0xE0, 0x20, 0x7F, 0xFF, 0x00, 0x5A}, 31, 0}, - {0xD0, (uint8_t[]){0xE0, 0x40, 0x51, 0x24, 0x08, 0x05, 0x10, 0x01, 0x20, 0x15, 0x42, 0xC2, 0x22, 0x22, 0xAA, 0x03, 0x10, 0x12, 0x60, 0x14, 0x1E, 0x51, 0x15, 0x00, 0x8A, 0x20, 0x00, 0x03, 0x3A, 0x12}, 30, 0}, - {0xA3, (uint8_t[]){0xA0, 0x06, 0xAa, 0x00, 0x08, 0x02, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x55, 0x55}, 22, 0}, - {0xC1, (uint8_t[]){0x31, 0x04, 0x02, 0x02, 0x71, 0x05, 0x24, 0x55, 0x02, 0x00, 0x41, 0x00, 0x53, 0xFF, 0xFF, 0xFF, 0x4F, 0x52, 0x00, 0x4F, 0x52, 0x00, 0x45, 0x3B, 0x0B, 0x02, 0x0d, 0x00, 0xFF, 0x40}, 30, 0}, - {0xC3, (uint8_t[]){0x00, 0x00, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, 0x01, 0x80, 0x01}, 11, 0}, - {0xC4, (uint8_t[]){0x00, 0x24, 0x33, 0x80, 0x00, 0xea, 0x64, 0x32, 0xC8, 0x64, 0xC8, 0x32, 0x90, 0x90, 0x11, 0x06, 0xDC, 0xFA, 0x00, 0x00, 0x80, 0xFE, 0x10, 0x10, 0x00, 0x0A, 0x0A, 0x44, 0x50}, 29, 0}, - {0xC5, (uint8_t[]){0x18, 0x00, 0x00, 0x03, 0xFE, 0x3A, 0x4A, 0x20, 0x30, 0x10, 0x88, 0xDE, 0x0D, 0x08, 0x0F, 0x0F, 0x01, 0x3A, 0x4A, 0x20, 0x10, 0x10, 0x00}, 23, 0}, - {0xC6, (uint8_t[]){0x05, 0x0A, 0x05, 0x0A, 0x00, 0xE0, 0x2E, 0x0B, 0x12, 0x22, 0x12, 0x22, 0x01, 0x03, 0x00, 0x3F, 0x6A, 0x18, 0xC8, 0x22}, 20, 0}, - {0xC7, (uint8_t[]){0x50, 0x32, 0x28, 0x00, 0xa2, 0x80, 0x8f, 0x00, 0x80, 0xff, 0x07, 0x11, 0x9c, 0x67, 0xff, 0x24, 0x0c, 0x0d, 0x0e, 0x0f}, 20, 0}, - {0xC9, (uint8_t[]){0x33, 0x44, 0x44, 0x01}, 4, 0}, - {0xCF, (uint8_t[]){0x2C, 0x1E, 0x88, 0x58, 0x13, 0x18, 0x56, 0x18, 0x1E, 0x68, 0x88, 0x00, 0x65, 0x09, 0x22, 0xC4, 0x0C, 0x77, 0x22, 0x44, 0xAA, 0x55, 0x08, 0x08, 0x12, 0xA0, 0x08}, 27, 0}, - {0xD5, (uint8_t[]){0x40, 0x8E, 0x8D, 0x01, 0x35, 0x04, 0x92, 0x74, 0x04, 0x92, 0x74, 0x04, 0x08, 0x6A, 0x04, 0x46, 0x03, 0x03, 0x03, 0x03, 0x82, 0x01, 0x03, 0x00, 0xE0, 0x51, 0xA1, 0x00, 0x00, 0x00}, 30, 0}, - {0xD6, (uint8_t[]){0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE, 0x93, 0x00, 0x01, 0x83, 0x07, 0x07, 0x00, 0x07, 0x07, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x84, 0x00, 0x20, 0x01, 0x00}, 30, 0}, - {0xD7, (uint8_t[]){0x03, 0x01, 0x0b, 0x09, 0x0f, 0x0d, 0x1E, 0x1F, 0x18, 0x1d, 0x1f, 0x19, 0x40, 0x8E, 0x04, 0x00, 0x20, 0xA0, 0x1F}, 19, 0}, - {0xD8, (uint8_t[]){0x02, 0x00, 0x0a, 0x08, 0x0e, 0x0c, 0x1E, 0x1F, 0x18, 0x1d, 0x1f, 0x19}, 12, 0}, - {0xD9, (uint8_t[]){0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}, 12, 0}, - {0xDD, (uint8_t[]){0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}, 12, 0}, - {0xDF, (uint8_t[]){0x44, 0x73, 0x4B, 0x69, 0x00, 0x0A, 0x02, 0x90}, 8, 0}, - {0xE0, (uint8_t[]){0x3B, 0x28, 0x10, 0x16, 0x0c, 0x06, 0x11, 0x28, 0x5c, 0x21, 0x0D, 0x35, 0x13, 0x2C, 0x33, 0x28, 0x0D}, 17, 0}, - {0xE1, (uint8_t[]){0x37, 0x28, 0x10, 0x16, 0x0b, 0x06, 0x11, 0x28, 0x5C, 0x21, 0x0D, 0x35, 0x14, 0x2C, 0x33, 0x28, 0x0F}, 17, 0}, - {0xE2, (uint8_t[]){0x3B, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x35, 0x44, 0x32, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0D}, 17, 0}, - {0xE3, (uint8_t[]){0x37, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x35, 0x44, 0x32, 0x0C, 0x14, 0x14, 0x36, 0x32, 0x2F, 0x0F}, 17, 0}, - {0xE4, (uint8_t[]){0x3B, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x39, 0x44, 0x2E, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0D}, 17, 0}, - {0xE5, (uint8_t[]){0x37, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x39, 0x44, 0x2E, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0F}, 17, 0}, - {0xA4, (uint8_t[]){0x85, 0x85, 0x95, 0x82, 0xAF, 0xAA, 0xAA, 0x80, 0x10, 0x30, 0x40, 0x40, 0x20, 0xFF, 0x60, 0x30}, 16, 0}, - {0xA4, (uint8_t[]){0x85, 0x85, 0x95, 0x85}, 4, 0}, - {0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 8, 0}, - {0x13, (uint8_t[]){0x00}, 0, 0}, - {0x11, (uint8_t[]){0x00}, 0, 120}, - {0x2C, (uint8_t[]){0x00, 0x00, 0x00, 0x00}, 4, 0}, - {0x2a, (uint8_t[]){0x00, 0x00, 0x01, 0x3f}, 4, 0}, - {0x2b, (uint8_t[]){0x00, 0x00, 0x01, 0xdf}, 4, 0}}; - -class Pmic : public Axp2101 { - public: - Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { - WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable - WriteReg(0x27, 0x10); // hold 4s to power off - - // Disable All DCs but DC1 - WriteReg(0x80, 0x01); - // Disable All LDOs - WriteReg(0x90, 0x00); - WriteReg(0x91, 0x00); - - // Set DC1 to 3.3V - WriteReg(0x82, (3300 - 1500) / 100); - - // Set ALDO1 to 3.3V - WriteReg(0x92, (3300 - 500) / 100); - - WriteReg(0x96, (1500 - 500) / 100); - WriteReg(0x97, (2800 - 500) / 100); - - // Enable ALDO1 BLDO1 BLDO2 - WriteReg(0x90, 0x31); - - WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V - - WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA - WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) - WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA - } - }; - -class CustomBoard : public WifiBoard { -private: - Button boot_button_; - Pmic* pmic_ = nullptr; - i2c_master_bus_handle_t i2c_bus_; - esp_io_expander_handle_t io_expander = NULL; - LcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - Esp32Camera* camera_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(20); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - pmic_->PowerOff(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = (i2c_port_t)I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); - } - - void InitializeTca9554(void) - { - esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander); - if(ret != ESP_OK) - ESP_LOGE(TAG, "TCA9554 create returned error"); - ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(100)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); - ESP_ERROR_CHECK(ret); - vTaskDelay(pdMS_TO_TICKS(100)); - ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_1, 1); - ESP_ERROR_CHECK(ret); - } - - void InitializeAxp2101() { - ESP_LOGI(TAG, "Init AXP2101"); - pmic_ = new Pmic(i2c_bus_, 0x34); - } - - void InitializeSpi() { - ESP_LOGI(TAG, "Initialize QSPI bus"); - spi_bus_config_t buscfg = {}; - buscfg.data0_io_num = DISPLAY_DATA0_PIN; - buscfg.data1_io_num = DISPLAY_DATA1_PIN; - buscfg.data2_io_num = DISPLAY_DATA2_PIN; - buscfg.data3_io_num = DISPLAY_DATA3_PIN; - buscfg.sclk_io_num = DISPLAY_CLK_PIN; - buscfg.max_transfer_sz = DISPLAY_TRANS_SIZE * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeCamera() { - camera_config_t config = {}; - - config.pin_pwdn = CAM_PIN_PWDN; - config.pin_reset = CAM_PIN_RESET; - config.pin_xclk = CAM_PIN_XCLK; - config.pin_sccb_sda = CAM_PIN_SIOD; - config.pin_sccb_scl = CAM_PIN_SIOC; - config.sccb_i2c_port = I2C_NUM_0; - - config.pin_d7 = CAM_PIN_D7; - config.pin_d6 = CAM_PIN_D6; - config.pin_d5 = CAM_PIN_D5; - config.pin_d4 = CAM_PIN_D4; - config.pin_d3 = CAM_PIN_D3; - config.pin_d2 = CAM_PIN_D2; - config.pin_d1 = CAM_PIN_D1; - config.pin_d0 = CAM_PIN_D0; - config.pin_vsync = CAM_PIN_VSYNC; - config.pin_href = CAM_PIN_HREF; - config.pin_pclk = CAM_PIN_PCLK; - - /* XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) */ - config.xclk_freq_hz = 10000000; - config.ledc_timer = LEDC_TIMER_1; - config.ledc_channel = LEDC_CHANNEL_0; - - config.pixel_format = PIXFORMAT_RGB565; /* YUV422,GRAYSCALE,RGB565,JPEG */ - config.frame_size = FRAMESIZE_240X240; /* QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates */ - - config.jpeg_quality = 12; /* 0-63, for OV series camera sensors, lower number means higher quality */ - config.fb_count = 2; /* When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode */ - config.fb_location = CAMERA_FB_IN_PSRAM; - config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; - - esp_err_t err = esp_camera_init(&config); // 测试相机是否存在 - if (err != ESP_OK) { - ESP_LOGE(TAG, "Camera is not plugged in or not supported, error: %s", esp_err_to_name(err)); - // 如果摄像头初始化失败,设置 camera_ 为 nullptr - camera_ = nullptr; - return; - }else - { - esp_camera_deinit();// 释放之前的摄像头资源,为正确初始化做准备 - camera_ = new Esp32Camera(config); - } - - } - - void InitializeLcdDisplay() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = AXS15231B_PANEL_IO_QSPI_CONFIG( - DISPLAY_CS_PIN, - NULL, - NULL); - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片 - ESP_LOGI(TAG, "Install LCD driver"); - const axs15231b_vendor_config_t vendor_config = { - .init_cmds = lcd_init_cmds, // Uncomment these line if use custom initialization commands - .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), - .flags = { - .use_qspi_interface = 1, - }, - }; - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = DISPLAY_RST_PIN, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - .vendor_config = (void *)&vendor_config, - }; - esp_lcd_new_panel_axs15231b(panel_io, &panel_config, &panel); - - esp_lcd_panel_reset(panel); - - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - // esp_lcd_panel_disp_on_off(panel, false); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - - display_ = new CustomLcdDisplay(panel_io, panel, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeTouch() - { - esp_lcd_touch_handle_t tp; - esp_lcd_touch_config_t tp_cfg = { - .x_max = DISPLAY_WIDTH, - .y_max = DISPLAY_HEIGHT, - .rst_gpio_num = GPIO_NUM_NC, - .int_gpio_num = GPIO_NUM_NC, - .levels = { - .reset = 0, - .interrupt = 0, - }, - .flags = { - .swap_xy = 1, - .mirror_x = 1, - .mirror_y = 1, - }, - }; - esp_lcd_panel_io_handle_t tp_io_handle = NULL; - esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_AXS15231B_CONFIG(); - tp_io_config.scl_speed_hz = 400 * 1000; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); - ESP_LOGI(TAG, "Initialize touch controller"); - ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_axs15231b(tp_io_handle, &tp_cfg, &tp)); - const lvgl_port_touch_cfg_t touch_cfg = { - .disp = lv_display_get_default(), - .handle = tp, - }; - lvgl_port_add_touch(&touch_cfg); - ESP_LOGI(TAG, "Touch panel initialized successfully"); - } - -public: - CustomBoard() : - boot_button_(BOOT_BUTTON_GPIO) { - - InitializeI2c(); - InitializeTca9554(); - - InitializeAxp2101(); -#if PMIC_ENABLE - InitializePowerSaveTimer(); -#endif - InitializeSpi(); - InitializeLcdDisplay(); -#if TOUCH_ENABLE - InitializeTouch(); -#endif - InitializeButtons(); - InitializeCamera(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - virtual Camera* GetCamera() override { - return camera_; - } - -#if PMIC_ENABLE - virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = pmic_->IsCharging(); - discharging = pmic_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - - level = pmic_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -#endif -}; - -DECLARE_BOARD(CustomBoard); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" + +#include +#include "i2c_device.h" +#include +#include +#include +#include +#include +#include + +#include +#include "esp_io_expander_tca9554.h" + +#include "axp2101.h" +#include "power_save_timer.h" + +#include "esp_lcd_axs15231b.h" + +#include "custom_lcd_display.h" + +#include + +#include +#include +#include "esp32_camera.h" + +#define TAG "waveshare_lcd_3_5b" + +static const axs15231b_lcd_init_cmd_t lcd_init_cmds[] = { + {0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5}, 8, 0}, + {0xA0, (uint8_t[]){0xC0, 0x10, 0x00, 0x02, 0x00, 0x00, 0x04, 0x3F, 0x20, 0x05, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00}, 17, 0}, + {0xA2, (uint8_t[]){0x30, 0x3C, 0x24, 0x14, 0xD0, 0x20, 0xFF, 0xE0, 0x40, 0x19, 0x80, 0x80, 0x80, 0x20, 0xf9, 0x10, 0x02, 0xff, 0xff, 0xF0, 0x90, 0x01, 0x32, 0xA0, 0x91, 0xE0, 0x20, 0x7F, 0xFF, 0x00, 0x5A}, 31, 0}, + {0xD0, (uint8_t[]){0xE0, 0x40, 0x51, 0x24, 0x08, 0x05, 0x10, 0x01, 0x20, 0x15, 0x42, 0xC2, 0x22, 0x22, 0xAA, 0x03, 0x10, 0x12, 0x60, 0x14, 0x1E, 0x51, 0x15, 0x00, 0x8A, 0x20, 0x00, 0x03, 0x3A, 0x12}, 30, 0}, + {0xA3, (uint8_t[]){0xA0, 0x06, 0xAa, 0x00, 0x08, 0x02, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x55, 0x55}, 22, 0}, + {0xC1, (uint8_t[]){0x31, 0x04, 0x02, 0x02, 0x71, 0x05, 0x24, 0x55, 0x02, 0x00, 0x41, 0x00, 0x53, 0xFF, 0xFF, 0xFF, 0x4F, 0x52, 0x00, 0x4F, 0x52, 0x00, 0x45, 0x3B, 0x0B, 0x02, 0x0d, 0x00, 0xFF, 0x40}, 30, 0}, + {0xC3, (uint8_t[]){0x00, 0x00, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, 0x01, 0x80, 0x01}, 11, 0}, + {0xC4, (uint8_t[]){0x00, 0x24, 0x33, 0x80, 0x00, 0xea, 0x64, 0x32, 0xC8, 0x64, 0xC8, 0x32, 0x90, 0x90, 0x11, 0x06, 0xDC, 0xFA, 0x00, 0x00, 0x80, 0xFE, 0x10, 0x10, 0x00, 0x0A, 0x0A, 0x44, 0x50}, 29, 0}, + {0xC5, (uint8_t[]){0x18, 0x00, 0x00, 0x03, 0xFE, 0x3A, 0x4A, 0x20, 0x30, 0x10, 0x88, 0xDE, 0x0D, 0x08, 0x0F, 0x0F, 0x01, 0x3A, 0x4A, 0x20, 0x10, 0x10, 0x00}, 23, 0}, + {0xC6, (uint8_t[]){0x05, 0x0A, 0x05, 0x0A, 0x00, 0xE0, 0x2E, 0x0B, 0x12, 0x22, 0x12, 0x22, 0x01, 0x03, 0x00, 0x3F, 0x6A, 0x18, 0xC8, 0x22}, 20, 0}, + {0xC7, (uint8_t[]){0x50, 0x32, 0x28, 0x00, 0xa2, 0x80, 0x8f, 0x00, 0x80, 0xff, 0x07, 0x11, 0x9c, 0x67, 0xff, 0x24, 0x0c, 0x0d, 0x0e, 0x0f}, 20, 0}, + {0xC9, (uint8_t[]){0x33, 0x44, 0x44, 0x01}, 4, 0}, + {0xCF, (uint8_t[]){0x2C, 0x1E, 0x88, 0x58, 0x13, 0x18, 0x56, 0x18, 0x1E, 0x68, 0x88, 0x00, 0x65, 0x09, 0x22, 0xC4, 0x0C, 0x77, 0x22, 0x44, 0xAA, 0x55, 0x08, 0x08, 0x12, 0xA0, 0x08}, 27, 0}, + {0xD5, (uint8_t[]){0x40, 0x8E, 0x8D, 0x01, 0x35, 0x04, 0x92, 0x74, 0x04, 0x92, 0x74, 0x04, 0x08, 0x6A, 0x04, 0x46, 0x03, 0x03, 0x03, 0x03, 0x82, 0x01, 0x03, 0x00, 0xE0, 0x51, 0xA1, 0x00, 0x00, 0x00}, 30, 0}, + {0xD6, (uint8_t[]){0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE, 0x93, 0x00, 0x01, 0x83, 0x07, 0x07, 0x00, 0x07, 0x07, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x84, 0x00, 0x20, 0x01, 0x00}, 30, 0}, + {0xD7, (uint8_t[]){0x03, 0x01, 0x0b, 0x09, 0x0f, 0x0d, 0x1E, 0x1F, 0x18, 0x1d, 0x1f, 0x19, 0x40, 0x8E, 0x04, 0x00, 0x20, 0xA0, 0x1F}, 19, 0}, + {0xD8, (uint8_t[]){0x02, 0x00, 0x0a, 0x08, 0x0e, 0x0c, 0x1E, 0x1F, 0x18, 0x1d, 0x1f, 0x19}, 12, 0}, + {0xD9, (uint8_t[]){0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}, 12, 0}, + {0xDD, (uint8_t[]){0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}, 12, 0}, + {0xDF, (uint8_t[]){0x44, 0x73, 0x4B, 0x69, 0x00, 0x0A, 0x02, 0x90}, 8, 0}, + {0xE0, (uint8_t[]){0x3B, 0x28, 0x10, 0x16, 0x0c, 0x06, 0x11, 0x28, 0x5c, 0x21, 0x0D, 0x35, 0x13, 0x2C, 0x33, 0x28, 0x0D}, 17, 0}, + {0xE1, (uint8_t[]){0x37, 0x28, 0x10, 0x16, 0x0b, 0x06, 0x11, 0x28, 0x5C, 0x21, 0x0D, 0x35, 0x14, 0x2C, 0x33, 0x28, 0x0F}, 17, 0}, + {0xE2, (uint8_t[]){0x3B, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x35, 0x44, 0x32, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0D}, 17, 0}, + {0xE3, (uint8_t[]){0x37, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x35, 0x44, 0x32, 0x0C, 0x14, 0x14, 0x36, 0x32, 0x2F, 0x0F}, 17, 0}, + {0xE4, (uint8_t[]){0x3B, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x39, 0x44, 0x2E, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0D}, 17, 0}, + {0xE5, (uint8_t[]){0x37, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x39, 0x44, 0x2E, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0F}, 17, 0}, + {0xA4, (uint8_t[]){0x85, 0x85, 0x95, 0x82, 0xAF, 0xAA, 0xAA, 0x80, 0x10, 0x30, 0x40, 0x40, 0x20, 0xFF, 0x60, 0x30}, 16, 0}, + {0xA4, (uint8_t[]){0x85, 0x85, 0x95, 0x85}, 4, 0}, + {0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 8, 0}, + {0x13, (uint8_t[]){0x00}, 0, 0}, + {0x11, (uint8_t[]){0x00}, 0, 120}, + {0x2C, (uint8_t[]){0x00, 0x00, 0x00, 0x00}, 4, 0}, + {0x2a, (uint8_t[]){0x00, 0x00, 0x01, 0x3f}, 4, 0}, + {0x2b, (uint8_t[]){0x00, 0x00, 0x01, 0xdf}, 4, 0}}; + +class Pmic : public Axp2101 { + public: + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable + WriteReg(0x27, 0x10); // hold 4s to power off + + // Disable All DCs but DC1 + WriteReg(0x80, 0x01); + // Disable All LDOs + WriteReg(0x90, 0x00); + WriteReg(0x91, 0x00); + + // Set DC1 to 3.3V + WriteReg(0x82, (3300 - 1500) / 100); + + // Set ALDO1 to 3.3V + WriteReg(0x92, (3300 - 500) / 100); + + WriteReg(0x96, (1500 - 500) / 100); + WriteReg(0x97, (2800 - 500) / 100); + + // Enable ALDO1 BLDO1 BLDO2 + WriteReg(0x90, 0x31); + + WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V + + WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA + WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) + WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA + } + }; + +class CustomBoard : public WifiBoard { +private: + Button boot_button_; + Pmic* pmic_ = nullptr; + i2c_master_bus_handle_t i2c_bus_; + esp_io_expander_handle_t io_expander = NULL; + LcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + Esp32Camera* camera_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(20); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + pmic_->PowerOff(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeTca9554(void) + { + esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander); + if(ret != ESP_OK) + ESP_LOGE(TAG, "TCA9554 create returned error"); + ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(100)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); + ESP_ERROR_CHECK(ret); + vTaskDelay(pdMS_TO_TICKS(100)); + ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_1, 1); + ESP_ERROR_CHECK(ret); + } + + void InitializeAxp2101() { + ESP_LOGI(TAG, "Init AXP2101"); + pmic_ = new Pmic(i2c_bus_, 0x34); + } + + void InitializeSpi() { + ESP_LOGI(TAG, "Initialize QSPI bus"); + spi_bus_config_t buscfg = {}; + buscfg.data0_io_num = DISPLAY_DATA0_PIN; + buscfg.data1_io_num = DISPLAY_DATA1_PIN; + buscfg.data2_io_num = DISPLAY_DATA2_PIN; + buscfg.data3_io_num = DISPLAY_DATA3_PIN; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.max_transfer_sz = DISPLAY_TRANS_SIZE * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeCamera() { + camera_config_t config = {}; + + config.pin_pwdn = CAM_PIN_PWDN; + config.pin_reset = CAM_PIN_RESET; + config.pin_xclk = CAM_PIN_XCLK; + config.pin_sccb_sda = CAM_PIN_SIOD; + config.pin_sccb_scl = CAM_PIN_SIOC; + config.sccb_i2c_port = I2C_NUM_0; + + config.pin_d7 = CAM_PIN_D7; + config.pin_d6 = CAM_PIN_D6; + config.pin_d5 = CAM_PIN_D5; + config.pin_d4 = CAM_PIN_D4; + config.pin_d3 = CAM_PIN_D3; + config.pin_d2 = CAM_PIN_D2; + config.pin_d1 = CAM_PIN_D1; + config.pin_d0 = CAM_PIN_D0; + config.pin_vsync = CAM_PIN_VSYNC; + config.pin_href = CAM_PIN_HREF; + config.pin_pclk = CAM_PIN_PCLK; + + /* XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) */ + config.xclk_freq_hz = 10000000; + config.ledc_timer = LEDC_TIMER_1; + config.ledc_channel = LEDC_CHANNEL_0; + + config.pixel_format = PIXFORMAT_RGB565; /* YUV422,GRAYSCALE,RGB565,JPEG */ + config.frame_size = FRAMESIZE_240X240; /* QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates */ + + config.jpeg_quality = 12; /* 0-63, for OV series camera sensors, lower number means higher quality */ + config.fb_count = 2; /* When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode */ + config.fb_location = CAMERA_FB_IN_PSRAM; + config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + esp_err_t err = esp_camera_init(&config); // 测试相机是否存在 + if (err != ESP_OK) { + ESP_LOGE(TAG, "Camera is not plugged in or not supported, error: %s", esp_err_to_name(err)); + // 如果摄像头初始化失败,设置 camera_ 为 nullptr + camera_ = nullptr; + return; + }else + { + esp_camera_deinit();// 释放之前的摄像头资源,为正确初始化做准备 + camera_ = new Esp32Camera(config); + } + + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = AXS15231B_PANEL_IO_QSPI_CONFIG( + DISPLAY_CS_PIN, + NULL, + NULL); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGI(TAG, "Install LCD driver"); + const axs15231b_vendor_config_t vendor_config = { + .init_cmds = lcd_init_cmds, // Uncomment these line if use custom initialization commands + .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), + .flags = { + .use_qspi_interface = 1, + }, + }; + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = DISPLAY_RST_PIN, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + .vendor_config = (void *)&vendor_config, + }; + esp_lcd_new_panel_axs15231b(panel_io, &panel_config, &panel); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + // esp_lcd_panel_disp_on_off(panel, false); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new CustomLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeTouch() + { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH, + .y_max = DISPLAY_HEIGHT, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 1, + .mirror_x = 1, + .mirror_y = 1, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_AXS15231B_CONFIG(); + tp_io_config.scl_speed_hz = 400 * 1000; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); + ESP_LOGI(TAG, "Initialize touch controller"); + ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_axs15231b(tp_io_handle, &tp_cfg, &tp)); + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + lvgl_port_add_touch(&touch_cfg); + ESP_LOGI(TAG, "Touch panel initialized successfully"); + } + +public: + CustomBoard() : + boot_button_(BOOT_BUTTON_GPIO) { + + InitializeI2c(); + InitializeTca9554(); + + InitializeAxp2101(); +#if PMIC_ENABLE + InitializePowerSaveTimer(); +#endif + InitializeSpi(); + InitializeLcdDisplay(); +#if TOUCH_ENABLE + InitializeTouch(); +#endif + InitializeButtons(); + InitializeCamera(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + virtual Camera* GetCamera() override { + return camera_; + } + +#if PMIC_ENABLE + virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = pmic_->IsCharging(); + discharging = pmic_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + + level = pmic_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +#endif +}; + +DECLARE_BOARD(CustomBoard); diff --git a/main/boards/waveshare-s3-touch-lcd-4b/README.md b/main/boards/waveshare-s3-touch-lcd-4b/README.md new file mode 100644 index 0000000..37c51f8 --- /dev/null +++ b/main/boards/waveshare-s3-touch-lcd-4b/README.md @@ -0,0 +1,12 @@ +# Waveshare ESP32-S3-Touch-LCD-4B + + +[ESP32-S3-Touch-LCD-4B](https://www.waveshare.com/esp32-s3-touch-lcd-4b.htm) is waveshare electronics designed an intelligent 86 box based on ESP32-S3 module equipped with a 480*480 IPS capacitive touch screen + + +## Configuration + +Configuration in `menuconfig`. + +Selection Board Type `Xiaozhi Assistant --> Board Type` +- Waveshare ESP32-S3-Touch-LCD-4B \ No newline at end of file diff --git a/main/boards/waveshare-s3-touch-lcd-4b/config.h b/main/boards/waveshare-s3-touch-lcd-4b/config.h new file mode 100644 index 0000000..9a4bc37 --- /dev/null +++ b/main/boards/waveshare-s3-touch-lcd-4b/config.h @@ -0,0 +1,65 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_7 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_6 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define BSP_LCD_VSYNC (GPIO_NUM_3) +#define BSP_LCD_HSYNC (GPIO_NUM_46) +#define BSP_LCD_DE (GPIO_NUM_17) +#define BSP_LCD_PCLK (GPIO_NUM_9) +#define BSP_LCD_DISP (GPIO_NUM_NC) +#define BSP_LCD_DATA0 (GPIO_NUM_40) +#define BSP_LCD_DATA1 (GPIO_NUM_41) +#define BSP_LCD_DATA2 (GPIO_NUM_42) +#define BSP_LCD_DATA3 (GPIO_NUM_2) +#define BSP_LCD_DATA4 (GPIO_NUM_1) +#define BSP_LCD_DATA5 (GPIO_NUM_21) +#define BSP_LCD_DATA6 (GPIO_NUM_8) +#define BSP_LCD_DATA7 (GPIO_NUM_18) +#define BSP_LCD_DATA8 (GPIO_NUM_45) +#define BSP_LCD_DATA9 (GPIO_NUM_38) +#define BSP_LCD_DATA10 (GPIO_NUM_39) +#define BSP_LCD_DATA11 (GPIO_NUM_10) +#define BSP_LCD_DATA12 (GPIO_NUM_11) +#define BSP_LCD_DATA13 (GPIO_NUM_12) +#define BSP_LCD_DATA14 (GPIO_NUM_13) +#define BSP_LCD_DATA15 (GPIO_NUM_14) + +#define BSP_LCD_IO_SPI_CS (IO_EXPANDER_PIN_NUM_0) +#define BSP_LCD_IO_SPI_SCL (IO_EXPANDER_PIN_NUM_2) +#define BSP_LCD_IO_SPI_SDA (IO_EXPANDER_PIN_NUM_1) + +#define DISPLAY_WIDTH 480 +#define DISPLAY_HEIGHT 480 + +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/waveshare-s3-touch-lcd-4b/config.json b/main/boards/waveshare-s3-touch-lcd-4b/config.json new file mode 100644 index 0000000..14eb54f --- /dev/null +++ b/main/boards/waveshare-s3-touch-lcd-4b/config.json @@ -0,0 +1,12 @@ +{ + "target": "esp32s3", + "builds": [ + { + "name": "waveshare-s3-touch-lcd-4b", + "sdkconfig_append": [ + "CONFIG_USE_WECHAT_MESSAGE_STYLE=n", + "CONFIG_USE_DEVICE_AEC=y" + ] + } + ] +} \ No newline at end of file diff --git a/main/boards/waveshare-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc b/main/boards/waveshare-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc new file mode 100644 index 0000000..f368ad7 --- /dev/null +++ b/main/boards/waveshare-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc @@ -0,0 +1,432 @@ +#include "wifi_board.h" +#include "display/lcd_display.h" +#include "esp_lcd_st7701.h" + +#include "codecs/box_audio_codec.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "mcp_server.h" +#include "config.h" +#include "power_save_timer.h" +#include "axp2101.h" +#include "i2c_device.h" +#include + +#include +#include +#include +#include +#include "esp_io_expander_tca9554.h" +#include "settings.h" + +#include +#include +#include + +#include +#include +#include +#include + +#define TAG "WaveshareEsp32s3TouchLCD4b" + +class Pmic : public Axp2101 { +public: + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable + WriteReg(0x27, 0x10); // hold 4s to power off + + // Disable All DCs but DC1 + WriteReg(0x80, 0x01); + // Disable All LDOs + WriteReg(0x90, 0x00); + WriteReg(0x91, 0x00); + + // Set DC1 to 3.3V + WriteReg(0x82, (3300 - 1500) / 100); + + // Set ALDO1 to 3.3V + WriteReg(0x92, (3300 - 500) / 100); + + // Enable ALDO1(MIC) + WriteReg(0x90, 0x01); + + WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V + + WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA + WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) + WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA + } +}; + +#define LCD_OPCODE_WRITE_CMD (0x02ULL) +#define LCD_OPCODE_READ_CMD (0x03ULL) +#define LCD_OPCODE_WRITE_COLOR (0x32ULL) + +static const st7701_lcd_init_cmd_t lcd_init_cmds[] = { +// {cmd, { data }, data_size, delay_ms} + {0x11, (uint8_t[]){0x00}, 0, 120}, + {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, + {0xC0, (uint8_t[]){0x3B, 0x00}, 2, 0}, + {0xC1, (uint8_t[]){0x0D, 0x02}, 2, 0}, + {0xC2, (uint8_t[]){0x21, 0x08}, 2, 0}, + {0xCD, (uint8_t[]){0x08}, 1, 0}, + {0xB0, (uint8_t[]){0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18}, 16, 0}, + {0xB1, (uint8_t[]){0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08, 0x08, 0x22, 0x04, 0x11, 0x11, 0xA9, 0x32, 0x18}, 16, 0}, + {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0}, + {0xB0, (uint8_t[]){0x60}, 1, 0}, + {0xB1, (uint8_t[]){0x30}, 1, 0}, + {0xB2, (uint8_t[]){0x87}, 1, 0}, + {0xB3, (uint8_t[]){0x80}, 1, 0}, + {0xB5, (uint8_t[]){0x49}, 1, 0}, + {0xB7, (uint8_t[]){0x85}, 1, 0}, + {0xB8, (uint8_t[]){0x21}, 1, 0}, + {0xC1, (uint8_t[]){0x78}, 1, 0}, + {0xC2, (uint8_t[]){0x78}, 1, 20}, + {0xE0, (uint8_t[]){0x00, 0x1B, 0x02}, 3, 0}, + {0xE1, (uint8_t[]){0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44}, 11, 0}, + {0xE2, (uint8_t[]){0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00}, 12, 0}, + {0xE3, (uint8_t[]){0x00, 0x00, 0x11, 0x11}, 4, 0}, + {0xE4, (uint8_t[]){0x44, 0x44}, 2, 0}, + {0xE5, (uint8_t[]){0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0, 0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0}, 16, 0}, + {0xE6, (uint8_t[]){0x00, 0x00, 0x11, 0x11}, 4, 0}, + {0xE7, (uint8_t[]){0x44, 0x44}, 2, 0}, + {0xE8, (uint8_t[]){0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, 0x0F, 0xEE, 0xD8, 0xA0}, 16, 0}, + {0xEB, (uint8_t[]){0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40}, 7, 0}, + {0xEC, (uint8_t[]){0x3C, 0x00}, 2, 0}, + {0xED, (uint8_t[]){0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA}, 16, 0}, + {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0}, + {0x36, (uint8_t[]){0x00}, 1, 0}, + {0x3A, (uint8_t[]){0x66}, 1, 0}, + {0x21, (uint8_t[]){0x00}, 0, 120}, + {0x29, (uint8_t[]){0x00}, 0, 0}, +}; + +class WaveshareEsp32s3TouchLCD4b : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Pmic* pmic_ = nullptr; + Button boot_button_; + LcdDisplay* display_; + esp_io_expander_handle_t io_expander = NULL; + PowerSaveTimer* power_save_timer_; + + uint32_t key_press_start; + bool key_pressed; + bool key_handled; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(70); }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); }); + power_save_timer_->OnShutdownRequest([this](){ + pmic_->PowerOff(); }); + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + void InitializeTca9554(void) { + esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); + esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_3|IO_EXPANDER_PIN_NUM_5 | IO_EXPANDER_PIN_NUM_6 , IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_3, 1); + esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, 0); + vTaskDelay(pdMS_TO_TICKS(200)); + esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_5, 0); + vTaskDelay(pdMS_TO_TICKS(200)); + esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_5, 1); + vTaskDelay(pdMS_TO_TICKS(200)); + esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_4|IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_INPUT); + } + void InitializeAxp2101() { + ESP_LOGI(TAG, "Init AXP2101"); + pmic_ = new Pmic(i2c_bus_, 0x34); + } + + void InitializeRGB() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + + spi_line_config_t line_config = { + .cs_io_type = IO_TYPE_EXPANDER, + .cs_expander_pin = BSP_LCD_IO_SPI_CS, + .scl_io_type = IO_TYPE_EXPANDER, + .scl_expander_pin = BSP_LCD_IO_SPI_SCL, + .sda_io_type = IO_TYPE_EXPANDER, + .sda_expander_pin = BSP_LCD_IO_SPI_SDA, + .io_expander = io_expander, + }; + esp_lcd_panel_io_3wire_spi_config_t io_config = ST7701_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io)); + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_rgb_panel_config_t rgb_config = { + .clk_src = LCD_CLK_SRC_DEFAULT, + .timings = { + .pclk_hz = 16 * 1000 * 1000, + .h_res = DISPLAY_WIDTH, + .v_res = DISPLAY_HEIGHT, + .hsync_pulse_width = 10, + .hsync_back_porch = 10, + .hsync_front_porch = 20, + .vsync_pulse_width = 10, + .vsync_back_porch = 10, + .vsync_front_porch = 10, + .flags = { + .pclk_active_neg = false + } + }, + .data_width = 16, + .bits_per_pixel = 16, + .num_fbs = 2, + .bounce_buffer_size_px = 480 * 20, + .psram_trans_align = 64, + .hsync_gpio_num = BSP_LCD_HSYNC, + .vsync_gpio_num = BSP_LCD_VSYNC, + .de_gpio_num = BSP_LCD_DE, + .pclk_gpio_num = BSP_LCD_PCLK, + .disp_gpio_num = BSP_LCD_DISP, + .data_gpio_nums = { + BSP_LCD_DATA0, BSP_LCD_DATA1, BSP_LCD_DATA2, BSP_LCD_DATA3, + BSP_LCD_DATA4, BSP_LCD_DATA5, BSP_LCD_DATA6, BSP_LCD_DATA7, + BSP_LCD_DATA8, BSP_LCD_DATA9, BSP_LCD_DATA10, BSP_LCD_DATA11, + BSP_LCD_DATA12, BSP_LCD_DATA13, BSP_LCD_DATA14, BSP_LCD_DATA15 + }, + .flags = { + .fb_in_psram = 1, + }, + }; + + rgb_config.timings.h_res = DISPLAY_WIDTH; + rgb_config.timings.v_res = DISPLAY_HEIGHT; + st7701_vendor_config_t vendor_config = { + .init_cmds = lcd_init_cmds, + .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), + .rgb_config = &rgb_config, + .flags = { + .mirror_by_cmd = 0, + .auto_del_panel_io = 1, + } + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = GPIO_NUM_NC, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 18, + .vendor_config = &vendor_config, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7701(panel_io, &panel_config, &panel_handle)); + esp_lcd_panel_init(panel_handle); + + display_ = new RgbLcdDisplay(panel_io, panel_handle, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif + } + + void InitializeTouch() { + esp_lcd_touch_handle_t tp; + esp_lcd_touch_config_t tp_cfg = { + .x_max = DISPLAY_WIDTH - 1, + .y_max = DISPLAY_HEIGHT - 1, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); + tp_io_config.scl_speed_hz = 400* 1000; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); + ESP_LOGI(TAG, "Initialize touch controller"); + ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp)); + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lv_display_get_default(), + .handle = tp, + }; + lvgl_port_add_touch(&touch_cfg); + ESP_LOGI(TAG, "Touch panel initialized successfully"); + } + + void InitializeTools() { + auto &mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.system.reconfigure_wifi", + "Reboot the device and enter WiFi configuration mode.\n" + "**CAUTION** You must ask the user to confirm this action.", + PropertyList(), [this](const PropertyList& properties) { + ResetWifiConfiguration(); + return true; + }); + } + void CheckKeyState() { + if (!io_expander) return; + + uint32_t current_level; + esp_err_t ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_4, ¤t_level); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to read IO_EXPANDER_PIN_NUM_4 level"); + return; + } + + static uint32_t last_level = 0; + static uint64_t press_start_time_ms = 0; + + if (current_level != last_level) { + last_level = current_level; + + if (current_level > 0) { + press_start_time_ms = esp_timer_get_time() / 1000; + ESP_LOGD(TAG, "Button pressed, start time recorded"); + } else { + uint64_t press_duration = (esp_timer_get_time() / 1000) - press_start_time_ms; + ESP_LOGI(TAG, "Button released after %llums", press_duration); + + if (press_duration < 1000) { + ESP_LOGI(TAG, "Short press detected, switching to factory partition"); + + const esp_partition_t* factory_partition = esp_partition_find_first( + ESP_PARTITION_TYPE_APP, + ESP_PARTITION_SUBTYPE_APP_FACTORY, + nullptr + ); + if (factory_partition) { + ESP_LOGI(TAG, "Found factory partition: %s", factory_partition->label); + ESP_ERROR_CHECK(esp_ota_set_boot_partition(factory_partition)); + esp_restart(); + } else { + ESP_LOGE(TAG, "Factory partition not found"); + } + } else { + ESP_LOGI(TAG, "Long press detected (>1000ms), no action"); + } + } + } + } + + void InitializeKeyMonitor() { + key_press_start = 0; + key_pressed = false; + key_handled = false; + + xTaskCreatePinnedToCore( + [](void* arg) { + auto* board = static_cast(arg); + while (true) { + board->CheckKeyState(); + vTaskDelay(pdMS_TO_TICKS(20)); + } + }, + "key_monitor_task", + 4096, + this, + 5, + nullptr, + 0 + ); + } + +public: + WaveshareEsp32s3TouchLCD4b() : boot_button_(BOOT_BUTTON_GPIO) { + InitializePowerSaveTimer(); + InitializeCodecI2c(); + InitializeTca9554(); + InitializeAxp2101(); + InitializeRGB(); + InitializeTouch(); + InitializeButtons(); + InitializeTools(); + InitializeKeyMonitor(); // 启动按键监听 + GetBacklight()->SetBrightness(100); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override { + static bool last_discharging = false; + charging = pmic_->IsCharging(); + discharging = pmic_->IsDischarging(); + if (discharging != last_discharging) + { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + + level = pmic_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) + { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(WaveshareEsp32s3TouchLCD4b); diff --git a/main/boards/xingzhi-cube-0.85tft-ml307/config.h b/main/boards/xingzhi-cube-0.85tft-ml307/config.h index 7388d39..ac7b65a 100644 --- a/main/boards/xingzhi-cube-0.85tft-ml307/config.h +++ b/main/boards/xingzhi-cube-0.85tft-ml307/config.h @@ -1,40 +1,40 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SDA GPIO_NUM_10 -#define DISPLAY_SCL GPIO_NUM_9 -#define DISPLAY_DC GPIO_NUM_8 -#define DISPLAY_CS GPIO_NUM_14 -#define DISPLAY_RES GPIO_NUM_18 -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define BACKLIGHT_INVERT false -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define ML307_RX_PIN GPIO_NUM_11 -#define ML307_TX_PIN GPIO_NUM_12 - -#endif // _BOARD_CONFIG_H_ + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SDA GPIO_NUM_10 +#define DISPLAY_SCL GPIO_NUM_9 +#define DISPLAY_DC GPIO_NUM_8 +#define DISPLAY_CS GPIO_NUM_14 +#define DISPLAY_RES GPIO_NUM_18 +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define BACKLIGHT_INVERT false +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define ML307_RX_PIN GPIO_NUM_11 +#define ML307_TX_PIN GPIO_NUM_12 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/xingzhi-cube-0.85tft-ml307/config.json b/main/boards/xingzhi-cube-0.85tft-ml307/config.json index 0305c46..2220935 100644 --- a/main/boards/xingzhi-cube-0.85tft-ml307/config.json +++ b/main/boards/xingzhi-cube-0.85tft-ml307/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "xingzhi-cube-0.85tft-ml307", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "xingzhi-cube-0.85tft-ml307", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc b/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc index 2d2f36e..536fd0a 100644 --- a/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc +++ b/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc @@ -1,240 +1,240 @@ -#include "ml307_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" - -#include -#include -#include - -#include -#include - -#include -#include "settings.h" - -#define TAG "XINGZHI_CUBE_0_85TFT_ML307" - -static const nv3023_lcd_init_cmd_t lcd_init_cmds[] = { - {0xff, (uint8_t[]){0xa5}, 1, 0}, - {0x3E, (uint8_t[]){0x09}, 1, 0}, - {0x3A, (uint8_t[]){0x65}, 1, 0}, - {0x82, (uint8_t[]){0x00}, 1, 0}, - {0x98, (uint8_t[]){0x00}, 1, 0}, - {0x63, (uint8_t[]){0x0f}, 1, 0}, - {0x64, (uint8_t[]){0x0f}, 1, 0}, - {0xB4, (uint8_t[]){0x34}, 1, 0}, - {0xB5, (uint8_t[]){0x30}, 1, 0}, - {0x83, (uint8_t[]){0x03}, 1, 0}, - {0x86, (uint8_t[]){0x04}, 1, 0}, - {0x87, (uint8_t[]){0x16}, 1, 0}, - {0x88, (uint8_t[]){0x0A}, 1, 0}, - {0x89, (uint8_t[]){0x27}, 1, 0}, - {0x93, (uint8_t[]){0x63}, 1, 0}, - {0x96, (uint8_t[]){0x81}, 1, 0}, - {0xC3, (uint8_t[]){0x10}, 1, 0}, - {0xE6, (uint8_t[]){0x00}, 1, 0}, - {0x99, (uint8_t[]){0x01}, 1, 0}, - {0x70, (uint8_t[]){0x09}, 1, 0}, - {0x71, (uint8_t[]){0x1D}, 1, 0}, - {0x72, (uint8_t[]){0x14}, 1, 0}, - {0x73, (uint8_t[]){0x0a}, 1, 0}, - {0x74, (uint8_t[]){0x11}, 1, 0}, - {0x75, (uint8_t[]){0x16}, 1, 0}, - {0x76, (uint8_t[]){0x38}, 1, 0}, - {0x77, (uint8_t[]){0x0B}, 1, 0}, - {0x78, (uint8_t[]){0x08}, 1, 0}, - {0x79, (uint8_t[]){0x3E}, 1, 0}, - {0x7a, (uint8_t[]){0x07}, 1, 0}, - {0x7b, (uint8_t[]){0x0D}, 1, 0}, - {0x7c, (uint8_t[]){0x16}, 1, 0}, - {0x7d, (uint8_t[]){0x0F}, 1, 0}, - {0x7e, (uint8_t[]){0x14}, 1, 0}, - {0x7f, (uint8_t[]){0x05}, 1, 0}, - {0xa0, (uint8_t[]){0x04}, 1, 0}, - {0xa1, (uint8_t[]){0x28}, 1, 0}, - {0xa2, (uint8_t[]){0x0c}, 1, 0}, - {0xa3, (uint8_t[]){0x11}, 1, 0}, - {0xa4, (uint8_t[]){0x0b}, 1, 0}, - {0xa5, (uint8_t[]){0x23}, 1, 0}, - {0xa6, (uint8_t[]){0x45}, 1, 0}, - {0xa7, (uint8_t[]){0x07}, 1, 0}, - {0xa8, (uint8_t[]){0x0a}, 1, 0}, - {0xa9, (uint8_t[]){0x3b}, 1, 0}, - {0xaa, (uint8_t[]){0x0d}, 1, 0}, - {0xab, (uint8_t[]){0x18}, 1, 0}, - {0xac, (uint8_t[]){0x14}, 1, 0}, - {0xad, (uint8_t[]){0x0F}, 1, 0}, - {0xae, (uint8_t[]){0x19}, 1, 0}, - {0xaf, (uint8_t[]){0x08}, 1, 0}, - {0xff, (uint8_t[]){0x00}, 1, 0}, - {0x11, (uint8_t[]){0x00}, 0, 120}, - {0x29, (uint8_t[]){0x00}, 0, 10} -}; - -class XINGZHI_CUBE_0_85TFT_ML307 : public Ml307Board { -private: - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - SpiLcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_38); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - rtc_gpio_set_level(GPIO_NUM_21, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_21); - esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - esp_deep_sleep_start(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SDA; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCL; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_HEIGHT * 80 *sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - app.ToggleChatState(); - }); - } - - void InitializeNv3023Display() { - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = NV3023_PANEL_IO_SPI_CONFIG(DISPLAY_CS, DISPLAY_DC, NULL, NULL); - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI3_HOST, &io_config, &panel_io_)); - - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - nv3023_vendor_config_t vendor_config = { // Uncomment these lines if use custom initialization commands - .init_cmds = lcd_init_cmds, - .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(nv3023_lcd_init_cmd_t), - }; - panel_config.reset_gpio_num = DISPLAY_RES; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = &vendor_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io_, &panel_config, &panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new SpiLcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, - DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void Initializegpio21_45() { - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - //gpio_num_t sp_45 = GPIO_NUM_45; - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = (1ULL << GPIO_NUM_45); - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; - gpio_config(&io_conf); - gpio_set_level(GPIO_NUM_45, 0); - } - -public: - XINGZHI_CUBE_0_85TFT_ML307(): Ml307Board(ML307_TX_PIN, ML307_RX_PIN), - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - Initializegpio21_45(); // 初始时,拉高21引脚,保证4g模块正常工作 - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeSpi(); - InitializeButtons(); - InitializeNv3023Display(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - Ml307Board::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(XINGZHI_CUBE_0_85TFT_ML307); +#include "ml307_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" + +#include +#include +#include + +#include +#include + +#include +#include "settings.h" + +#define TAG "XINGZHI_CUBE_0_85TFT_ML307" + +static const nv3023_lcd_init_cmd_t lcd_init_cmds[] = { + {0xff, (uint8_t[]){0xa5}, 1, 0}, + {0x3E, (uint8_t[]){0x09}, 1, 0}, + {0x3A, (uint8_t[]){0x65}, 1, 0}, + {0x82, (uint8_t[]){0x00}, 1, 0}, + {0x98, (uint8_t[]){0x00}, 1, 0}, + {0x63, (uint8_t[]){0x0f}, 1, 0}, + {0x64, (uint8_t[]){0x0f}, 1, 0}, + {0xB4, (uint8_t[]){0x34}, 1, 0}, + {0xB5, (uint8_t[]){0x30}, 1, 0}, + {0x83, (uint8_t[]){0x03}, 1, 0}, + {0x86, (uint8_t[]){0x04}, 1, 0}, + {0x87, (uint8_t[]){0x16}, 1, 0}, + {0x88, (uint8_t[]){0x0A}, 1, 0}, + {0x89, (uint8_t[]){0x27}, 1, 0}, + {0x93, (uint8_t[]){0x63}, 1, 0}, + {0x96, (uint8_t[]){0x81}, 1, 0}, + {0xC3, (uint8_t[]){0x10}, 1, 0}, + {0xE6, (uint8_t[]){0x00}, 1, 0}, + {0x99, (uint8_t[]){0x01}, 1, 0}, + {0x70, (uint8_t[]){0x09}, 1, 0}, + {0x71, (uint8_t[]){0x1D}, 1, 0}, + {0x72, (uint8_t[]){0x14}, 1, 0}, + {0x73, (uint8_t[]){0x0a}, 1, 0}, + {0x74, (uint8_t[]){0x11}, 1, 0}, + {0x75, (uint8_t[]){0x16}, 1, 0}, + {0x76, (uint8_t[]){0x38}, 1, 0}, + {0x77, (uint8_t[]){0x0B}, 1, 0}, + {0x78, (uint8_t[]){0x08}, 1, 0}, + {0x79, (uint8_t[]){0x3E}, 1, 0}, + {0x7a, (uint8_t[]){0x07}, 1, 0}, + {0x7b, (uint8_t[]){0x0D}, 1, 0}, + {0x7c, (uint8_t[]){0x16}, 1, 0}, + {0x7d, (uint8_t[]){0x0F}, 1, 0}, + {0x7e, (uint8_t[]){0x14}, 1, 0}, + {0x7f, (uint8_t[]){0x05}, 1, 0}, + {0xa0, (uint8_t[]){0x04}, 1, 0}, + {0xa1, (uint8_t[]){0x28}, 1, 0}, + {0xa2, (uint8_t[]){0x0c}, 1, 0}, + {0xa3, (uint8_t[]){0x11}, 1, 0}, + {0xa4, (uint8_t[]){0x0b}, 1, 0}, + {0xa5, (uint8_t[]){0x23}, 1, 0}, + {0xa6, (uint8_t[]){0x45}, 1, 0}, + {0xa7, (uint8_t[]){0x07}, 1, 0}, + {0xa8, (uint8_t[]){0x0a}, 1, 0}, + {0xa9, (uint8_t[]){0x3b}, 1, 0}, + {0xaa, (uint8_t[]){0x0d}, 1, 0}, + {0xab, (uint8_t[]){0x18}, 1, 0}, + {0xac, (uint8_t[]){0x14}, 1, 0}, + {0xad, (uint8_t[]){0x0F}, 1, 0}, + {0xae, (uint8_t[]){0x19}, 1, 0}, + {0xaf, (uint8_t[]){0x08}, 1, 0}, + {0xff, (uint8_t[]){0x00}, 1, 0}, + {0x11, (uint8_t[]){0x00}, 0, 120}, + {0x29, (uint8_t[]){0x00}, 0, 10} +}; + +class XINGZHI_CUBE_0_85TFT_ML307 : public Ml307Board { +private: + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + SpiLcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_38); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SDA; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCL; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_HEIGHT * 80 *sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + app.ToggleChatState(); + }); + } + + void InitializeNv3023Display() { + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = NV3023_PANEL_IO_SPI_CONFIG(DISPLAY_CS, DISPLAY_DC, NULL, NULL); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI3_HOST, &io_config, &panel_io_)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + nv3023_vendor_config_t vendor_config = { // Uncomment these lines if use custom initialization commands + .init_cmds = lcd_init_cmds, + .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(nv3023_lcd_init_cmd_t), + }; + panel_config.reset_gpio_num = DISPLAY_RES; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = &vendor_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io_, &panel_config, &panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new SpiLcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, + DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void Initializegpio21_45() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + //gpio_num_t sp_45 = GPIO_NUM_45; + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << GPIO_NUM_45); + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; + gpio_config(&io_conf); + gpio_set_level(GPIO_NUM_45, 0); + } + +public: + XINGZHI_CUBE_0_85TFT_ML307(): Ml307Board(ML307_TX_PIN, ML307_RX_PIN), + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + Initializegpio21_45(); // 初始时,拉高21引脚,保证4g模块正常工作 + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeButtons(); + InitializeNv3023Display(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + Ml307Board::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(XINGZHI_CUBE_0_85TFT_ML307); diff --git a/main/boards/xingzhi-cube-0.85tft-wifi/config.h b/main/boards/xingzhi-cube-0.85tft-wifi/config.h index 8b2bf9d..6ba56a7 100644 --- a/main/boards/xingzhi-cube-0.85tft-wifi/config.h +++ b/main/boards/xingzhi-cube-0.85tft-wifi/config.h @@ -1,37 +1,37 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC - -#define DISPLAY_SDA GPIO_NUM_10 -#define DISPLAY_SCL GPIO_NUM_9 -#define DISPLAY_DC GPIO_NUM_8 -#define DISPLAY_CS GPIO_NUM_14 -#define DISPLAY_RES GPIO_NUM_18 -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define BACKLIGHT_INVERT false -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_SDA GPIO_NUM_10 +#define DISPLAY_SCL GPIO_NUM_9 +#define DISPLAY_DC GPIO_NUM_8 +#define DISPLAY_CS GPIO_NUM_14 +#define DISPLAY_RES GPIO_NUM_18 +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define BACKLIGHT_INVERT false +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/xingzhi-cube-0.85tft-wifi/config.json b/main/boards/xingzhi-cube-0.85tft-wifi/config.json index 867160f..903c854 100644 --- a/main/boards/xingzhi-cube-0.85tft-wifi/config.json +++ b/main/boards/xingzhi-cube-0.85tft-wifi/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "xingzhi-cube-0.85tft-wifi", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "xingzhi-cube-0.85tft-wifi", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc b/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc index 37d56e4..af87e65 100644 --- a/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc +++ b/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc @@ -1,244 +1,244 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" - -#include -#include -#include - -#include -#include - -#include -#include "settings.h" - -#define TAG "XINGZHI_CUBE_0_85TFT_WIFI" - -static const nv3023_lcd_init_cmd_t lcd_init_cmds[] = { - {0xff, (uint8_t[]){0xa5}, 1, 0}, - {0x3E, (uint8_t[]){0x09}, 1, 0}, - {0x3A, (uint8_t[]){0x65}, 1, 0}, - {0x82, (uint8_t[]){0x00}, 1, 0}, - {0x98, (uint8_t[]){0x00}, 1, 0}, - {0x63, (uint8_t[]){0x0f}, 1, 0}, - {0x64, (uint8_t[]){0x0f}, 1, 0}, - {0xB4, (uint8_t[]){0x34}, 1, 0}, - {0xB5, (uint8_t[]){0x30}, 1, 0}, - {0x83, (uint8_t[]){0x03}, 1, 0}, - {0x86, (uint8_t[]){0x04}, 1, 0}, - {0x87, (uint8_t[]){0x16}, 1, 0}, - {0x88, (uint8_t[]){0x0A}, 1, 0}, - {0x89, (uint8_t[]){0x27}, 1, 0}, - {0x93, (uint8_t[]){0x63}, 1, 0}, - {0x96, (uint8_t[]){0x81}, 1, 0}, - {0xC3, (uint8_t[]){0x10}, 1, 0}, - {0xE6, (uint8_t[]){0x00}, 1, 0}, - {0x99, (uint8_t[]){0x01}, 1, 0}, - {0x70, (uint8_t[]){0x09}, 1, 0}, - {0x71, (uint8_t[]){0x1D}, 1, 0}, - {0x72, (uint8_t[]){0x14}, 1, 0}, - {0x73, (uint8_t[]){0x0a}, 1, 0}, - {0x74, (uint8_t[]){0x11}, 1, 0}, - {0x75, (uint8_t[]){0x16}, 1, 0}, - {0x76, (uint8_t[]){0x38}, 1, 0}, - {0x77, (uint8_t[]){0x0B}, 1, 0}, - {0x78, (uint8_t[]){0x08}, 1, 0}, - {0x79, (uint8_t[]){0x3E}, 1, 0}, - {0x7a, (uint8_t[]){0x07}, 1, 0}, - {0x7b, (uint8_t[]){0x0D}, 1, 0}, - {0x7c, (uint8_t[]){0x16}, 1, 0}, - {0x7d, (uint8_t[]){0x0F}, 1, 0}, - {0x7e, (uint8_t[]){0x14}, 1, 0}, - {0x7f, (uint8_t[]){0x05}, 1, 0}, - {0xa0, (uint8_t[]){0x04}, 1, 0}, - {0xa1, (uint8_t[]){0x28}, 1, 0}, - {0xa2, (uint8_t[]){0x0c}, 1, 0}, - {0xa3, (uint8_t[]){0x11}, 1, 0}, - {0xa4, (uint8_t[]){0x0b}, 1, 0}, - {0xa5, (uint8_t[]){0x23}, 1, 0}, - {0xa6, (uint8_t[]){0x45}, 1, 0}, - {0xa7, (uint8_t[]){0x07}, 1, 0}, - {0xa8, (uint8_t[]){0x0a}, 1, 0}, - {0xa9, (uint8_t[]){0x3b}, 1, 0}, - {0xaa, (uint8_t[]){0x0d}, 1, 0}, - {0xab, (uint8_t[]){0x18}, 1, 0}, - {0xac, (uint8_t[]){0x14}, 1, 0}, - {0xad, (uint8_t[]){0x0F}, 1, 0}, - {0xae, (uint8_t[]){0x19}, 1, 0}, - {0xaf, (uint8_t[]){0x08}, 1, 0}, - {0xff, (uint8_t[]){0x00}, 1, 0}, - {0x11, (uint8_t[]){0x00}, 0, 120}, - {0x29, (uint8_t[]){0x00}, 0, 10} -}; - -class XINGZHI_CUBE_0_85TFT_WIFI : public WifiBoard { -private: - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - SpiLcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_38); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - rtc_gpio_set_level(GPIO_NUM_21, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_21); - esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - esp_deep_sleep_start(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SDA; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCL; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_HEIGHT * 80 *sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - } - - void InitializeNv3023Display() { - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = NV3023_PANEL_IO_SPI_CONFIG(DISPLAY_CS, DISPLAY_DC, NULL, NULL); - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI3_HOST, &io_config, &panel_io_)); - - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - nv3023_vendor_config_t vendor_config = { // Uncomment these lines if use custom initialization commands - .init_cmds = lcd_init_cmds, - .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(nv3023_lcd_init_cmd_t), - }; - panel_config.reset_gpio_num = DISPLAY_RES; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; - panel_config.bits_per_pixel = 16; - panel_config.vendor_config = &vendor_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io_, &panel_config, &panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new SpiLcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, - DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - - void Initializegpio21_45() { - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - //gpio_num_t sp_45 = GPIO_NUM_45; - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.pin_bit_mask = (1ULL << GPIO_NUM_45); - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; - gpio_config(&io_conf); - gpio_set_level(GPIO_NUM_45, 0); - } - -public: - XINGZHI_CUBE_0_85TFT_WIFI(): - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - Initializegpio21_45(); // 初始时,拉高21引脚,保证4g模块正常工作 - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeSpi(); - InitializeButtons(); - InitializeNv3023Display(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(XINGZHI_CUBE_0_85TFT_WIFI); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" + +#include +#include +#include + +#include +#include + +#include +#include "settings.h" + +#define TAG "XINGZHI_CUBE_0_85TFT_WIFI" + +static const nv3023_lcd_init_cmd_t lcd_init_cmds[] = { + {0xff, (uint8_t[]){0xa5}, 1, 0}, + {0x3E, (uint8_t[]){0x09}, 1, 0}, + {0x3A, (uint8_t[]){0x65}, 1, 0}, + {0x82, (uint8_t[]){0x00}, 1, 0}, + {0x98, (uint8_t[]){0x00}, 1, 0}, + {0x63, (uint8_t[]){0x0f}, 1, 0}, + {0x64, (uint8_t[]){0x0f}, 1, 0}, + {0xB4, (uint8_t[]){0x34}, 1, 0}, + {0xB5, (uint8_t[]){0x30}, 1, 0}, + {0x83, (uint8_t[]){0x03}, 1, 0}, + {0x86, (uint8_t[]){0x04}, 1, 0}, + {0x87, (uint8_t[]){0x16}, 1, 0}, + {0x88, (uint8_t[]){0x0A}, 1, 0}, + {0x89, (uint8_t[]){0x27}, 1, 0}, + {0x93, (uint8_t[]){0x63}, 1, 0}, + {0x96, (uint8_t[]){0x81}, 1, 0}, + {0xC3, (uint8_t[]){0x10}, 1, 0}, + {0xE6, (uint8_t[]){0x00}, 1, 0}, + {0x99, (uint8_t[]){0x01}, 1, 0}, + {0x70, (uint8_t[]){0x09}, 1, 0}, + {0x71, (uint8_t[]){0x1D}, 1, 0}, + {0x72, (uint8_t[]){0x14}, 1, 0}, + {0x73, (uint8_t[]){0x0a}, 1, 0}, + {0x74, (uint8_t[]){0x11}, 1, 0}, + {0x75, (uint8_t[]){0x16}, 1, 0}, + {0x76, (uint8_t[]){0x38}, 1, 0}, + {0x77, (uint8_t[]){0x0B}, 1, 0}, + {0x78, (uint8_t[]){0x08}, 1, 0}, + {0x79, (uint8_t[]){0x3E}, 1, 0}, + {0x7a, (uint8_t[]){0x07}, 1, 0}, + {0x7b, (uint8_t[]){0x0D}, 1, 0}, + {0x7c, (uint8_t[]){0x16}, 1, 0}, + {0x7d, (uint8_t[]){0x0F}, 1, 0}, + {0x7e, (uint8_t[]){0x14}, 1, 0}, + {0x7f, (uint8_t[]){0x05}, 1, 0}, + {0xa0, (uint8_t[]){0x04}, 1, 0}, + {0xa1, (uint8_t[]){0x28}, 1, 0}, + {0xa2, (uint8_t[]){0x0c}, 1, 0}, + {0xa3, (uint8_t[]){0x11}, 1, 0}, + {0xa4, (uint8_t[]){0x0b}, 1, 0}, + {0xa5, (uint8_t[]){0x23}, 1, 0}, + {0xa6, (uint8_t[]){0x45}, 1, 0}, + {0xa7, (uint8_t[]){0x07}, 1, 0}, + {0xa8, (uint8_t[]){0x0a}, 1, 0}, + {0xa9, (uint8_t[]){0x3b}, 1, 0}, + {0xaa, (uint8_t[]){0x0d}, 1, 0}, + {0xab, (uint8_t[]){0x18}, 1, 0}, + {0xac, (uint8_t[]){0x14}, 1, 0}, + {0xad, (uint8_t[]){0x0F}, 1, 0}, + {0xae, (uint8_t[]){0x19}, 1, 0}, + {0xaf, (uint8_t[]){0x08}, 1, 0}, + {0xff, (uint8_t[]){0x00}, 1, 0}, + {0x11, (uint8_t[]){0x00}, 0, 120}, + {0x29, (uint8_t[]){0x00}, 0, 10} +}; + +class XINGZHI_CUBE_0_85TFT_WIFI : public WifiBoard { +private: + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + SpiLcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_38); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SDA; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCL; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_HEIGHT * 80 *sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeNv3023Display() { + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = NV3023_PANEL_IO_SPI_CONFIG(DISPLAY_CS, DISPLAY_DC, NULL, NULL); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI3_HOST, &io_config, &panel_io_)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + nv3023_vendor_config_t vendor_config = { // Uncomment these lines if use custom initialization commands + .init_cmds = lcd_init_cmds, + .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(nv3023_lcd_init_cmd_t), + }; + panel_config.reset_gpio_num = DISPLAY_RES; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = &vendor_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io_, &panel_config, &panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new SpiLcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, + DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void Initializegpio21_45() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + //gpio_num_t sp_45 = GPIO_NUM_45; + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << GPIO_NUM_45); + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; + gpio_config(&io_conf); + gpio_set_level(GPIO_NUM_45, 0); + } + +public: + XINGZHI_CUBE_0_85TFT_WIFI(): + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + Initializegpio21_45(); // 初始时,拉高21引脚,保证4g模块正常工作 + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeButtons(); + InitializeNv3023Display(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(XINGZHI_CUBE_0_85TFT_WIFI); diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/config.h b/main/boards/xingzhi-cube-0.96oled-ml307/config.h index 0f3f8ce..b5c1189 100644 --- a/main/boards/xingzhi-cube-0.96oled-ml307/config.h +++ b/main/boards/xingzhi-cube-0.96oled-ml307/config.h @@ -1,30 +1,30 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#define BUILTIN_LED_GPIO GPIO_NUM_48 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 - -#define DISPLAY_SDA_PIN GPIO_NUM_41 -#define DISPLAY_SCL_PIN GPIO_NUM_42 -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 64 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true - -#define ML307_RX_PIN GPIO_NUM_11 -#define ML307_TX_PIN GPIO_NUM_12 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 + +#define DISPLAY_SDA_PIN GPIO_NUM_41 +#define DISPLAY_SCL_PIN GPIO_NUM_42 +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 64 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true + +#define ML307_RX_PIN GPIO_NUM_11 +#define ML307_TX_PIN GPIO_NUM_12 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/config.json b/main/boards/xingzhi-cube-0.96oled-ml307/config.json index be5919c..e99c903 100644 --- a/main/boards/xingzhi-cube-0.96oled-ml307/config.json +++ b/main/boards/xingzhi-cube-0.96oled-ml307/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "xingzhi-cube-0.96oled-ml307", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "xingzhi-cube-0.96oled-ml307", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc index 18f7a01..bcc7d32 100644 --- a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc +++ b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc @@ -1,234 +1,234 @@ -#include "dual_network_board.h" -#include "codecs/no_audio_codec.h" -#include "display/oled_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" - -#include -#include -#include -#include -#include -#include -#include - -#define TAG "XINGZHI_CUBE_0_96OLED_ML307" - -class XINGZHI_CUBE_0_96OLED_ML307 : public DualNetworkBoard { -private: - i2c_master_bus_handle_t display_i2c_bus_; - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - Display* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_38); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - rtc_gpio_set_level(GPIO_NUM_21, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_21); - esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - esp_deep_sleep_start(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeDisplayI2c() { - i2c_master_bus_config_t bus_config = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = DISPLAY_SDA_PIN, - .scl_io_num = DISPLAY_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - // cast to WifiBoard - auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); - } - } - app.ToggleChatState(); - }); - boot_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { - SwitchNetworkType(); - } - }); - - volume_up_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - -public: - XINGZHI_CUBE_0_96OLED_ML307() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeDisplayI2c(); - InitializeSsd1306Display(); - InitializeButtons(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - DualNetworkBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(XINGZHI_CUBE_0_96OLED_ML307); +#include "dual_network_board.h" +#include "codecs/no_audio_codec.h" +#include "display/oled_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" + +#include +#include +#include +#include +#include +#include +#include + +#define TAG "XINGZHI_CUBE_0_96OLED_ML307" + +class XINGZHI_CUBE_0_96OLED_ML307 : public DualNetworkBoard { +private: + i2c_master_bus_handle_t display_i2c_bus_; + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + Display* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_38); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeDisplayI2c() { + i2c_master_bus_config_t bus_config = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = DISPLAY_SDA_PIN, + .scl_io_num = DISPLAY_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); + } + + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (GetNetworkType() == NetworkType::WIFI) { + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + // cast to WifiBoard + auto& wifi_board = static_cast(GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + } + } + app.ToggleChatState(); + }); + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + SwitchNetworkType(); + } + }); + + volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + +public: + XINGZHI_CUBE_0_96OLED_ML307() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeDisplayI2c(); + InitializeSsd1306Display(); + InitializeButtons(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + DualNetworkBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(XINGZHI_CUBE_0_96OLED_ML307); diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/config.h b/main/boards/xingzhi-cube-0.96oled-wifi/config.h index b353890..3e97d5f 100644 --- a/main/boards/xingzhi-cube-0.96oled-wifi/config.h +++ b/main/boards/xingzhi-cube-0.96oled-wifi/config.h @@ -1,27 +1,27 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#define BUILTIN_LED_GPIO GPIO_NUM_48 -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 - -#define DISPLAY_SDA_PIN GPIO_NUM_41 -#define DISPLAY_SCL_PIN GPIO_NUM_42 -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 64 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 + +#define DISPLAY_SDA_PIN GPIO_NUM_41 +#define DISPLAY_SCL_PIN GPIO_NUM_42 +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 64 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/config.json b/main/boards/xingzhi-cube-0.96oled-wifi/config.json index 2cba4c6..4e51536 100644 --- a/main/boards/xingzhi-cube-0.96oled-wifi/config.json +++ b/main/boards/xingzhi-cube-0.96oled-wifi/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "xingzhi-cube-0.96oled-wifi", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "xingzhi-cube-0.96oled-wifi", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc index d2f0685..ca22609 100644 --- a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc +++ b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc @@ -1,225 +1,225 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/oled_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "power_save_timer.h" -#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" - -#include - -#include -#include -#include -#include -#include -#include - -#define TAG "XINGZHI_CUBE_0_96OLED_WIFI" - -class XINGZHI_CUBE_0_96OLED_WIFI : public WifiBoard { -private: - i2c_master_bus_handle_t display_i2c_bus_; - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - Display* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_38); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - rtc_gpio_set_level(GPIO_NUM_21, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_21); - esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - esp_deep_sleep_start(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeDisplayI2c() { - i2c_master_bus_config_t bus_config = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = DISPLAY_SDA_PIN, - .scl_io_num = DISPLAY_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - - volume_up_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - -public: - XINGZHI_CUBE_0_96OLED_WIFI() : - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeDisplayI2c(); - InitializeSsd1306Display(); - InitializeButtons(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(XINGZHI_CUBE_0_96OLED_WIFI); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/oled_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "power_save_timer.h" +#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" + +#include + +#include +#include +#include +#include +#include +#include + +#define TAG "XINGZHI_CUBE_0_96OLED_WIFI" + +class XINGZHI_CUBE_0_96OLED_WIFI : public WifiBoard { +private: + i2c_master_bus_handle_t display_i2c_bus_; + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + Display* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_38); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeDisplayI2c() { + i2c_master_bus_config_t bus_config = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = DISPLAY_SDA_PIN, + .scl_io_num = DISPLAY_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); + } + + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + + volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + +public: + XINGZHI_CUBE_0_96OLED_WIFI() : + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeDisplayI2c(); + InitializeSsd1306Display(); + InitializeButtons(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(XINGZHI_CUBE_0_96OLED_WIFI); diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/config.h b/main/boards/xingzhi-cube-1.54tft-ml307/config.h index 9167578..bf77357 100644 --- a/main/boards/xingzhi-cube-1.54tft-ml307/config.h +++ b/main/boards/xingzhi-cube-1.54tft-ml307/config.h @@ -1,40 +1,40 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 - -#define DISPLAY_SDA GPIO_NUM_10 -#define DISPLAY_SCL GPIO_NUM_9 -#define DISPLAY_DC GPIO_NUM_8 -#define DISPLAY_CS GPIO_NUM_14 -#define DISPLAY_RES GPIO_NUM_18 -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define BACKLIGHT_INVERT false -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define ML307_RX_PIN GPIO_NUM_11 -#define ML307_TX_PIN GPIO_NUM_12 - -#endif // _BOARD_CONFIG_H_ + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 + +#define DISPLAY_SDA GPIO_NUM_10 +#define DISPLAY_SCL GPIO_NUM_9 +#define DISPLAY_DC GPIO_NUM_8 +#define DISPLAY_CS GPIO_NUM_14 +#define DISPLAY_RES GPIO_NUM_18 +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define BACKLIGHT_INVERT false +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define ML307_RX_PIN GPIO_NUM_11 +#define ML307_TX_PIN GPIO_NUM_12 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/config.json b/main/boards/xingzhi-cube-1.54tft-ml307/config.json index e3d5f50..9e9e4cd 100644 --- a/main/boards/xingzhi-cube-1.54tft-ml307/config.json +++ b/main/boards/xingzhi-cube-1.54tft-ml307/config.json @@ -1,15 +1,15 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "xingzhi-cube-1.54tft-ml307", - "sdkconfig_append": [] - }, - { - "name": "xingzhi-cube-1.54tft-ml307-wechatui", - "sdkconfig_append": [ - "CONFIG_USE_WECHAT_MESSAGE_STYLE=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "xingzhi-cube-1.54tft-ml307", + "sdkconfig_append": [] + }, + { + "name": "xingzhi-cube-1.54tft-ml307-wechatui", + "sdkconfig_append": [ + "CONFIG_USE_WECHAT_MESSAGE_STYLE=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc index 72aff95..5bddb4b 100644 --- a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc +++ b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc @@ -1,212 +1,212 @@ -#include "dual_network_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" - -#include -#include -#include - -#include -#include - -#define TAG "XINGZHI_CUBE_1_54TFT_ML307" - -class XINGZHI_CUBE_1_54TFT_ML307 : public DualNetworkBoard { -private: - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - SpiLcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_38); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - rtc_gpio_set_level(GPIO_NUM_21, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_21); - esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - esp_deep_sleep_start(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SDA; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCL; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - // cast to WifiBoard - auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); - } - } - app.ToggleChatState(); - }); - boot_button_.OnDoubleClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { - SwitchNetworkType(); - } - }); - - volume_up_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - - void InitializeSt7789Display() { - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS; - io_config.dc_gpio_num = DISPLAY_DC; - io_config.spi_mode = 3; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_)); - - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RES; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); - - display_ = new SpiLcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, - DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - XINGZHI_CUBE_1_54TFT_ML307() : - DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeSpi(); - InitializeButtons(); - InitializeSt7789Display(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - DualNetworkBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(XINGZHI_CUBE_1_54TFT_ML307); +#include "dual_network_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" + +#include +#include +#include + +#include +#include + +#define TAG "XINGZHI_CUBE_1_54TFT_ML307" + +class XINGZHI_CUBE_1_54TFT_ML307 : public DualNetworkBoard { +private: + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + SpiLcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_38); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SDA; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCL; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (GetNetworkType() == NetworkType::WIFI) { + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + // cast to WifiBoard + auto& wifi_board = static_cast(GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + } + } + app.ToggleChatState(); + }); + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + SwitchNetworkType(); + } + }); + + volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + + void InitializeSt7789Display() { + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS; + io_config.dc_gpio_num = DISPLAY_DC; + io_config.spi_mode = 3; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RES; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); + + display_ = new SpiLcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, + DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + XINGZHI_CUBE_1_54TFT_ML307() : + DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeButtons(); + InitializeSt7789Display(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + DualNetworkBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(XINGZHI_CUBE_1_54TFT_ML307); diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/config.h b/main/boards/xingzhi-cube-1.54tft-wifi/config.h index c1a998a..8cb0723 100644 --- a/main/boards/xingzhi-cube-1.54tft-wifi/config.h +++ b/main/boards/xingzhi-cube-1.54tft-wifi/config.h @@ -1,36 +1,36 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 - -#define DISPLAY_SDA GPIO_NUM_10 -#define DISPLAY_SCL GPIO_NUM_9 -#define DISPLAY_DC GPIO_NUM_8 -#define DISPLAY_CS GPIO_NUM_14 -#define DISPLAY_RES GPIO_NUM_18 -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define BACKLIGHT_INVERT false -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#endif // _BOARD_CONFIG_H_ + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 + +#define DISPLAY_SDA GPIO_NUM_10 +#define DISPLAY_SCL GPIO_NUM_9 +#define DISPLAY_DC GPIO_NUM_8 +#define DISPLAY_CS GPIO_NUM_14 +#define DISPLAY_RES GPIO_NUM_18 +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define BACKLIGHT_INVERT false +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/config.json b/main/boards/xingzhi-cube-1.54tft-wifi/config.json index 6cfa0d3..1f5532a 100644 --- a/main/boards/xingzhi-cube-1.54tft-wifi/config.json +++ b/main/boards/xingzhi-cube-1.54tft-wifi/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "xingzhi-cube-1.54tft-wifi", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "xingzhi-cube-1.54tft-wifi", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h b/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h index 8d238f2..2d078d7 100644 --- a/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h +++ b/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h @@ -1,186 +1,186 @@ -#pragma once -#include -#include - -#include -#include -#include - - -class PowerManager { -private: - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 1; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_6, &adc_value)); - - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += value; - } - average_adc /= adc_values_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {1970, 0}, - {2062, 20}, - {2154, 40}, - {2246, 60}, - {2338, 80}, - {2430, 100} - }; - - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_2, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_6, &chan_config)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include + +#include +#include +#include + + +class PowerManager { +private: + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 1; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_6, &adc_value)); + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {1970, 0}, + {2062, 20}, + {2154, 40}, + {2246, 60}, + {2338, 80}, + {2430, 100} + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_2, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_6, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc index 8827b37..171da7a 100644 --- a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc +++ b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc @@ -1,201 +1,201 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "display/lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "power_manager.h" - -#include -#include -#include - -#include -#include - -#define TAG "XINGZHI_CUBE_1_54TFT_WIFI" - -class XINGZHI_CUBE_1_54TFT_WIFI : public WifiBoard { -private: - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - SpiLcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_38); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - rtc_gpio_set_level(GPIO_NUM_21, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_21); - esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - esp_deep_sleep_start(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SDA; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCL; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - - volume_up_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_up_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); - }); - - volume_down_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - - void InitializeSt7789Display() { - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS; - io_config.dc_gpio_num = DISPLAY_DC; - io_config.spi_mode = 3; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_)); - - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RES; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); - - display_ = new SpiLcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, - DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - } - -public: - XINGZHI_CUBE_1_54TFT_WIFI() : - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeSpi(); - InitializeButtons(); - InitializeSt7789Display(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(XINGZHI_CUBE_1_54TFT_WIFI); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "power_manager.h" + +#include +#include +#include + +#include +#include + +#define TAG "XINGZHI_CUBE_1_54TFT_WIFI" + +class XINGZHI_CUBE_1_54TFT_WIFI : public WifiBoard { +private: + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + SpiLcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_38); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SDA; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCL; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + + volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + + void InitializeSt7789Display() { + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS; + io_config.dc_gpio_num = DISPLAY_DC; + io_config.spi_mode = 3; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RES; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); + + display_ = new SpiLcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, + DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + +public: + XINGZHI_CUBE_1_54TFT_WIFI() : + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeButtons(); + InitializeSt7789Display(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(XINGZHI_CUBE_1_54TFT_WIFI); diff --git a/main/boards/xmini-c3-4g/README.md b/main/boards/xmini-c3-4g/README.md index 9697085..20c959c 100644 --- a/main/boards/xmini-c3-4g/README.md +++ b/main/boards/xmini-c3-4g/README.md @@ -1,4 +1,4 @@ -# 开源地址 - -https://oshwhub.com/tenclass01/xmini_c3_4g - +# 开源地址 + +https://oshwhub.com/tenclass01/xmini_c3_4g + diff --git a/main/boards/xmini-c3-4g/config.h b/main/boards/xmini-c3-4g/config.h index 8a38e0f..d10b924 100644 --- a/main/boards/xmini-c3-4g/config.h +++ b/main/boards/xmini-c3-4g/config.h @@ -1,32 +1,32 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_7 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_13 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_21 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_20 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_3 -#define BOOT_BUTTON_GPIO GPIO_NUM_9 - -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 64 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true - -#define ML307_TX_PIN GPIO_NUM_2 -#define ML307_RX_PIN GPIO_NUM_0 -#define ML307_DTR_PIN GPIO_NUM_1 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_7 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_13 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_21 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_20 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_3 +#define BOOT_BUTTON_GPIO GPIO_NUM_9 + +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 64 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true + +#define ML307_TX_PIN GPIO_NUM_2 +#define ML307_RX_PIN GPIO_NUM_0 +#define ML307_DTR_PIN GPIO_NUM_1 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/xmini-c3-4g/config.json b/main/boards/xmini-c3-4g/config.json index b17fde6..eb2ef3d 100644 --- a/main/boards/xmini-c3-4g/config.json +++ b/main/boards/xmini-c3-4g/config.json @@ -1,15 +1,15 @@ -{ - "target": "esp32c3", - "builds": [ - { - "name": "xmini-c3-4g", - "sdkconfig_append": [ - "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", - "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"", - "CONFIG_PM_ENABLE=y", - "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", - "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y" - ] - } - ] +{ + "target": "esp32c3", + "builds": [ + { + "name": "xmini-c3-4g", + "sdkconfig_append": [ + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"", + "CONFIG_PM_ENABLE=y", + "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", + "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc b/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc index eca1918..ce3c169 100644 --- a/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc +++ b/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc @@ -1,207 +1,204 @@ -#include "ml307_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/oled_display.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "mcp_server.h" -#include "settings.h" -#include "config.h" -#include "sleep_timer.h" -#include "adc_battery_monitor.h" -#include "press_to_talk_mcp_tool.h" - -#include -#include -#include -#include -#include -#include - -#define TAG "XminiC3Board" - -class XminiC3Board : public Ml307Board { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - Display* display_ = nullptr; - Button boot_button_; - SleepTimer* sleep_timer_ = nullptr; - AdcBatteryMonitor* adc_battery_monitor_ = nullptr; - PressToTalkMcpTool* press_to_talk_tool_ = nullptr; - - void InitializeBatteryMonitor() { - adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_4, 100000, 100000, GPIO_NUM_12); - adc_battery_monitor_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - sleep_timer_->SetEnabled(false); - } else { - sleep_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { -#if CONFIG_USE_ESP_WAKE_WORD - sleep_timer_ = new SleepTimer(300); -#else - sleep_timer_ = new SleepTimer(30); -#endif - sleep_timer_->OnEnterLightSleepMode([this]() { - ESP_LOGI(TAG, "Enabling sleep mode"); - // Show the standby screen - GetDisplay()->SetPowerSaveMode(true); - // Enable sleep mode, and sleep in 1 second after DTR is set to high - modem_->SetSleepMode(true, 1); - // Set the DTR pin to high to make the modem enter sleep mode - modem_->GetAtUart()->SetDtrPin(true); - }); - sleep_timer_->OnExitLightSleepMode([this]() { - // Set the DTR pin to low to make the modem wake up - modem_->GetAtUart()->SetDtrPin(false); - // Hide the standby screen - GetDisplay()->SetPowerSaveMode(false); - }); - sleep_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - - if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { - while (true) { - ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - } - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(codec_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) { - app.ToggleChatState(); - } - }); - boot_button_.OnPressDown([this]() { - if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { - Application::GetInstance().StartListening(); - } - }); - boot_button_.OnPressUp([this]() { - if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { - Application::GetInstance().StopListening(); - } - }); - } - - void InitializeTools() { - press_to_talk_tool_ = new PressToTalkMcpTool(); - press_to_talk_tool_->Initialize(); - } - -public: - XminiC3Board() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, ML307_DTR_PIN), - boot_button_(BOOT_BUTTON_GPIO, false, 0, 0, true) { - - InitializeBatteryMonitor(); - InitializePowerSaveTimer(); - InitializeCodecI2c(); - InitializeSsd1306Display(); - InitializeButtons(); - InitializeTools(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - charging = adc_battery_monitor_->IsCharging(); - discharging = adc_battery_monitor_->IsDischarging(); - level = adc_battery_monitor_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - sleep_timer_->WakeUp(); - } - Ml307Board::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(XminiC3Board); +#include "ml307_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/oled_display.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "mcp_server.h" +#include "settings.h" +#include "config.h" +#include "sleep_timer.h" +#include "adc_battery_monitor.h" +#include "press_to_talk_mcp_tool.h" + +#include +#include +#include +#include +#include +#include + +#define TAG "XminiC3Board" + +class XminiC3Board : public Ml307Board { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + Display* display_ = nullptr; + Button boot_button_; + SleepTimer* sleep_timer_ = nullptr; + AdcBatteryMonitor* adc_battery_monitor_ = nullptr; + PressToTalkMcpTool* press_to_talk_tool_ = nullptr; + + void InitializeBatteryMonitor() { + adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_4, 100000, 100000, GPIO_NUM_12); + adc_battery_monitor_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + sleep_timer_->SetEnabled(false); + } else { + sleep_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + // Wake word detection will be disabled in light sleep mode + sleep_timer_ = new SleepTimer(30); + sleep_timer_->OnEnterLightSleepMode([this]() { + ESP_LOGI(TAG, "Enabling sleep mode"); + // Show the standby screen + GetDisplay()->SetPowerSaveMode(true); + // Enable sleep mode, and sleep in 1 second after DTR is set to high + modem_->SetSleepMode(true, 1); + // Set the DTR pin to high to make the modem enter sleep mode + modem_->GetAtUart()->SetDtrPin(true); + }); + sleep_timer_->OnExitLightSleepMode([this]() { + // Set the DTR pin to low to make the modem wake up + modem_->GetAtUart()->SetDtrPin(false); + // Hide the standby screen + GetDisplay()->SetPowerSaveMode(false); + }); + sleep_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { + while (true) { + ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } + } + + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(codec_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) { + app.ToggleChatState(); + } + }); + boot_button_.OnPressDown([this]() { + if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { + Application::GetInstance().StartListening(); + } + }); + boot_button_.OnPressUp([this]() { + if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { + Application::GetInstance().StopListening(); + } + }); + } + + void InitializeTools() { + press_to_talk_tool_ = new PressToTalkMcpTool(); + press_to_talk_tool_->Initialize(); + } + +public: + XminiC3Board() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, ML307_DTR_PIN), + boot_button_(BOOT_BUTTON_GPIO, false, 0, 0, true) { + + InitializeBatteryMonitor(); + InitializePowerSaveTimer(); + InitializeCodecI2c(); + InitializeSsd1306Display(); + InitializeButtons(); + InitializeTools(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + charging = adc_battery_monitor_->IsCharging(); + discharging = adc_battery_monitor_->IsDischarging(); + level = adc_battery_monitor_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + sleep_timer_->WakeUp(); + } + Ml307Board::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(XminiC3Board); diff --git a/main/boards/xmini-c3-v3/README.md b/main/boards/xmini-c3-v3/README.md index 87dfd43..243b54c 100644 --- a/main/boards/xmini-c3-v3/README.md +++ b/main/boards/xmini-c3-v3/README.md @@ -1,4 +1,4 @@ -# 开源地址 - -https://oshwhub.com/tenclass01/xmini_c3 - +# 开源地址 + +https://oshwhub.com/tenclass01/xmini_c3 + diff --git a/main/boards/xmini-c3-v3/config.h b/main/boards/xmini-c3-v3/config.h index 1f75de6..35c843e 100644 --- a/main/boards/xmini-c3-v3/config.h +++ b/main/boards/xmini-c3-v3/config.h @@ -1,28 +1,28 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_5 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_4 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_10 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_2 -#define BOOT_BUTTON_GPIO GPIO_NUM_9 - -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 64 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_5 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_4 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_10 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_2 +#define BOOT_BUTTON_GPIO GPIO_NUM_9 + +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 64 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/xmini-c3-v3/config.json b/main/boards/xmini-c3-v3/config.json index e830af0..c779c8e 100644 --- a/main/boards/xmini-c3-v3/config.json +++ b/main/boards/xmini-c3-v3/config.json @@ -1,14 +1,14 @@ -{ - "target": "esp32c3", - "builds": [ - { - "name": "xmini-c3-v3", - "sdkconfig_append": [ - "CONFIG_PM_ENABLE=y", - "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", - "CONFIG_USE_ESP_WAKE_WORD=y", - "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y" - ] - } - ] +{ + "target": "esp32c3", + "builds": [ + { + "name": "xmini-c3-v3", + "sdkconfig_append": [ + "CONFIG_PM_ENABLE=y", + "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", + "CONFIG_USE_ESP_WAKE_WORD=y", + "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/xmini-c3-v3/xmini_c3_board.cc b/main/boards/xmini-c3-v3/xmini_c3_board.cc index c1f7cb7..41535cd 100644 --- a/main/boards/xmini-c3-v3/xmini_c3_board.cc +++ b/main/boards/xmini-c3-v3/xmini_c3_board.cc @@ -1,203 +1,199 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/oled_display.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "mcp_server.h" -#include "settings.h" -#include "config.h" -#include "power_save_timer.h" -#include "adc_battery_monitor.h" -#include "press_to_talk_mcp_tool.h" - -#include -#include -#include -#include -#include -#include - -#define TAG "XminiC3Board" - -class XminiC3Board : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - Display* display_ = nullptr; - Button boot_button_; - PowerSaveTimer* power_save_timer_ = nullptr; - AdcBatteryMonitor* adc_battery_monitor_ = nullptr; - PressToTalkMcpTool* press_to_talk_tool_ = nullptr; - - void InitializePowerManager() { - adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_3, 100000, 100000, GPIO_NUM_12); - adc_battery_monitor_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { -#if CONFIG_USE_ESP_WAKE_WORD - power_save_timer_ = new PowerSaveTimer(160, 300); -#else - power_save_timer_ = new PowerSaveTimer(160, 60); -#endif - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - - // Print I2C bus info - if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { - while (true) { - ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - } - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(codec_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) { - app.ToggleChatState(); - } - }); - boot_button_.OnPressDown([this]() { - if (power_save_timer_) { - power_save_timer_->WakeUp(); - } - if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { - Application::GetInstance().StartListening(); - } - }); - boot_button_.OnPressUp([this]() { - if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { - Application::GetInstance().StopListening(); - } - }); - } - - void InitializeTools() { - press_to_talk_tool_ = new PressToTalkMcpTool(); - press_to_talk_tool_->Initialize(); - } - -public: - XminiC3Board() : boot_button_(BOOT_BUTTON_GPIO, false, 0, 0, true) { - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeCodecI2c(); - InitializeSsd1306Display(); - InitializeButtons(); - InitializeTools(); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - charging = adc_battery_monitor_->IsCharging(); - discharging = adc_battery_monitor_->IsDischarging(); - level = adc_battery_monitor_->GetBatteryLevel(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(XminiC3Board); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/oled_display.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "mcp_server.h" +#include "settings.h" +#include "config.h" +#include "power_save_timer.h" +#include "adc_battery_monitor.h" +#include "press_to_talk_mcp_tool.h" + +#include +#include +#include +#include +#include +#include + +#define TAG "XminiC3Board" + +class XminiC3Board : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + Display* display_ = nullptr; + Button boot_button_; + PowerSaveTimer* power_save_timer_ = nullptr; + AdcBatteryMonitor* adc_battery_monitor_ = nullptr; + PressToTalkMcpTool* press_to_talk_tool_ = nullptr; + + void InitializePowerManager() { + adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_3, 100000, 100000, GPIO_NUM_12); + adc_battery_monitor_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(160, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + // Print I2C bus info + if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { + while (true) { + ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } + } + + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(codec_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) { + app.ToggleChatState(); + } + }); + boot_button_.OnPressDown([this]() { + if (power_save_timer_) { + power_save_timer_->WakeUp(); + } + if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { + Application::GetInstance().StartListening(); + } + }); + boot_button_.OnPressUp([this]() { + if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { + Application::GetInstance().StopListening(); + } + }); + } + + void InitializeTools() { + press_to_talk_tool_ = new PressToTalkMcpTool(); + press_to_talk_tool_->Initialize(); + } + +public: + XminiC3Board() : boot_button_(BOOT_BUTTON_GPIO, false, 0, 0, true) { + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeCodecI2c(); + InitializeSsd1306Display(); + InitializeButtons(); + InitializeTools(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + charging = adc_battery_monitor_->IsCharging(); + discharging = adc_battery_monitor_->IsDischarging(); + level = adc_battery_monitor_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(XminiC3Board); diff --git a/main/boards/xmini-c3/config.h b/main/boards/xmini-c3/config.h index f37a035..d367d59 100644 --- a/main/boards/xmini-c3/config.h +++ b/main/boards/xmini-c3/config.h @@ -1,28 +1,28 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_6 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_11 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4 -#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR - -#define BUILTIN_LED_GPIO GPIO_NUM_2 -#define BOOT_BUTTON_GPIO GPIO_NUM_9 - -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 64 -#define DISPLAY_MIRROR_X true -#define DISPLAY_MIRROR_Y true - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_6 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_11 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_2 +#define BOOT_BUTTON_GPIO GPIO_NUM_9 + +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 64 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/xmini-c3/config.json b/main/boards/xmini-c3/config.json index 0073568..716bd38 100644 --- a/main/boards/xmini-c3/config.json +++ b/main/boards/xmini-c3/config.json @@ -1,14 +1,14 @@ -{ - "target": "esp32c3", - "builds": [ - { - "name": "xmini-c3", - "sdkconfig_append": [ - "CONFIG_PM_ENABLE=y", - "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", - "CONFIG_USE_ESP_WAKE_WORD=y", - "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y" - ] - } - ] +{ + "target": "esp32c3", + "builds": [ + { + "name": "xmini-c3", + "sdkconfig_append": [ + "CONFIG_PM_ENABLE=y", + "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y", + "CONFIG_USE_ESP_WAKE_WORD=y", + "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/xmini-c3/xmini_c3_board.cc b/main/boards/xmini-c3/xmini_c3_board.cc index 91bd050..f7e4741 100644 --- a/main/boards/xmini-c3/xmini_c3_board.cc +++ b/main/boards/xmini-c3/xmini_c3_board.cc @@ -1,186 +1,182 @@ -#include "wifi_board.h" -#include "codecs/es8311_audio_codec.h" -#include "display/oled_display.h" -#include "application.h" -#include "button.h" -#include "led/single_led.h" -#include "mcp_server.h" -#include "settings.h" -#include "config.h" -#include "power_save_timer.h" -#include "press_to_talk_mcp_tool.h" - -#include -#include -#include -#include -#include -#include - -#define TAG "XminiC3Board" - -class XminiC3Board : public WifiBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - Display* display_ = nullptr; - Button boot_button_; - PowerSaveTimer* power_save_timer_ = nullptr; - PressToTalkMcpTool* press_to_talk_tool_ = nullptr; - - void InitializePowerSaveTimer() { -#if CONFIG_USE_ESP_WAKE_WORD - power_save_timer_ = new PowerSaveTimer(160, 300); -#else - power_save_timer_ = new PowerSaveTimer(160, 60); -#endif - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeCodecI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - - // Print I2C bus info - if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { - while (true) { - ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - } - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(codec_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) { - app.ToggleChatState(); - } - }); - boot_button_.OnPressDown([this]() { - if (power_save_timer_) { - power_save_timer_->WakeUp(); - } - if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { - Application::GetInstance().StartListening(); - } - }); - boot_button_.OnPressUp([this]() { - if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { - Application::GetInstance().StopListening(); - } - }); - } - - void InitializeTools() { - press_to_talk_tool_ = new PressToTalkMcpTool(); - press_to_talk_tool_->Initialize(); - } - -public: - XminiC3Board() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); - InitializeSsd1306Display(); - InitializeButtons(); - InitializePowerSaveTimer(); - InitializeTools(); - - // 避免使用错误的固件,把 EFUSE 操作放在最后 - // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 - esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); - } - - virtual Led* GetLed() override { - static SingleLed led(BUILTIN_LED_GPIO); - return &led; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); - return &audio_codec; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(XminiC3Board); +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/oled_display.h" +#include "application.h" +#include "button.h" +#include "led/single_led.h" +#include "mcp_server.h" +#include "settings.h" +#include "config.h" +#include "power_save_timer.h" +#include "press_to_talk_mcp_tool.h" + +#include +#include +#include +#include +#include +#include + +#define TAG "XminiC3Board" + +class XminiC3Board : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + Display* display_ = nullptr; + Button boot_button_; + PowerSaveTimer* power_save_timer_ = nullptr; + PressToTalkMcpTool* press_to_talk_tool_ = nullptr; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(160, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeCodecI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + // Print I2C bus info + if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { + while (true) { + ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } + } + + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(codec_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) { + app.ToggleChatState(); + } + }); + boot_button_.OnPressDown([this]() { + if (power_save_timer_) { + power_save_timer_->WakeUp(); + } + if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { + Application::GetInstance().StartListening(); + } + }); + boot_button_.OnPressUp([this]() { + if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) { + Application::GetInstance().StopListening(); + } + }); + } + + void InitializeTools() { + press_to_talk_tool_ = new PressToTalkMcpTool(); + press_to_talk_tool_->Initialize(); + } + +public: + XminiC3Board() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeCodecI2c(); + InitializeSsd1306Display(); + InitializeButtons(); + InitializePowerSaveTimer(); + InitializeTools(); + + // 避免使用错误的固件,把 EFUSE 操作放在最后 + // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 + esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(XminiC3Board); diff --git a/main/boards/yunliao-s3/README.md b/main/boards/yunliao-s3/README.md index 011cc54..90c88a1 100644 --- a/main/boards/yunliao-s3/README.md +++ b/main/boards/yunliao-s3/README.md @@ -1,88 +1,88 @@ -# 小智云聊S3 - -## 简介 -小智云聊S3是小智AI的魔改项目,是首个2.8寸护眼大屏+大字体+2000mah大电池的量产成品,做了大量创新和优化。 - -## 合并版 -合并版代码在小智AI主项目中维护,跟随主项目的一起版本更新,便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G自由切换等功能。 - ->### 按键操作 ->- **开机**: 关机状态,长按1秒后释放按键,自动开机 ->- **关机**: 开机状态,长按1秒后释放按键,标题栏会显示'请稍候',再等2秒自动关机 ->- **唤醒/打断**: 正常通话环境下,单击按键 ->- **切换4G/Wifi**: 启动过程或者配网界面,1秒钟内双击按键(需安装4G模块) ->- **重新配网**: 开机状态,1秒钟内三击按键,会自动重启并进入配网界面 - -## 魔改版 -魔改版由于底层改动太大,代码单独维护,定期合并主项目代码。 - ->### 为什么是魔改 ->- 首个实现微信二维码配网。 ->- 首个支持单手机配网。 ->- 首个支持扫二维码访问控制台。 ->- 首发支持繁体、日文、英文版界面 ->- 首个全语音操控模式 ->- 独家提供一键刷机脚本等多种刷机方式 - -## 版本区别 ->| 特性 | 合并版 | 魔改版 | ->| --- | --- | --- | ->| 语音打断 | ✓ | ✓ | ->| 4G功能 | ✓ | ✓ | ->| 自动更新固件 | ✓ | X | ->| 第三方固件支持 | ✓ | X | ->| 天气待机界面 | X | ✓ | ->| 闹钟提醒 | X | ✓ | ->| 网络音乐播放 | X | ✓ | ->| 微信扫码配网 | X | ✓ | ->| 单手机配网 | X | ✓ | ->| 扫码访问控制台 | X | ✓ | ->| 繁日英文界面 | X | ✓ | ->| 多语言支持 | X | ✓ | ->| 外接蓝牙音箱 | X | ✓ | - - -# 编译配置命令 - -**克隆工程** - -```bash -git clone https://github.com/78/xiaozhi-esp32.git -``` - -**进入工程** - -```bash -cd xiaozhi-esp32 -``` - -**配置编译目标为 ESP32S3** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig** - -```bash -idf.py menuconfig -``` - -**选择板子** - -```bash -- `Xiaozhi Assistant` → `Board Type` → 选择 `小智云聊-S3` → 选择 `Enable Device-Side AEC` -``` - -**编译** - -```ba -idf.py build -``` - -**下载并打开串口终端** - -```bash -idf.py build flash monitor -``` - +# 小智云聊S3 + +## 简介 +小智云聊S3是小智AI的魔改项目,是首个2.8寸护眼大屏+大字体+2000mah大电池的量产成品,做了大量创新和优化。 + +## 合并版 +合并版代码在小智AI主项目中维护,跟随主项目的一起版本更新,便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G自由切换等功能。 + +>### 按键操作 +>- **开机**: 关机状态,长按1秒后释放按键,自动开机 +>- **关机**: 开机状态,长按1秒后释放按键,标题栏会显示'请稍候',再等2秒自动关机 +>- **唤醒/打断**: 正常通话环境下,单击按键 +>- **切换4G/Wifi**: 启动过程或者配网界面,1秒钟内双击按键(需安装4G模块) +>- **重新配网**: 开机状态,1秒钟内三击按键,会自动重启并进入配网界面 + +## 魔改版 +魔改版由于底层改动太大,代码单独维护,定期合并主项目代码。 + +>### 为什么是魔改 +>- 首个实现微信二维码配网。 +>- 首个支持单手机配网。 +>- 首个支持扫二维码访问控制台。 +>- 首发支持繁体、日文、英文版界面 +>- 首个全语音操控模式 +>- 独家提供一键刷机脚本等多种刷机方式 + +## 版本区别 +>| 特性 | 合并版 | 魔改版 | +>| --- | --- | --- | +>| 语音打断 | ✓ | ✓ | +>| 4G功能 | ✓ | ✓ | +>| 自动更新固件 | ✓ | X | +>| 第三方固件支持 | ✓ | X | +>| 天气待机界面 | X | ✓ | +>| 闹钟提醒 | X | ✓ | +>| 网络音乐播放 | X | ✓ | +>| 微信扫码配网 | X | ✓ | +>| 单手机配网 | X | ✓ | +>| 扫码访问控制台 | X | ✓ | +>| 繁日英文界面 | X | ✓ | +>| 多语言支持 | X | ✓ | +>| 外接蓝牙音箱 | X | ✓ | + + +# 编译配置命令 + +**克隆工程** + +```bash +git clone https://github.com/78/xiaozhi-esp32.git +``` + +**进入工程** + +```bash +cd xiaozhi-esp32 +``` + +**配置编译目标为 ESP32S3** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig** + +```bash +idf.py menuconfig +``` + +**选择板子** + +```bash +- `Xiaozhi Assistant` → `Board Type` → 选择 `小智云聊-S3` → 选择 `Enable Device-Side AEC` +``` + +**编译** + +```ba +idf.py build +``` + +**下载并打开串口终端** + +```bash +idf.py build flash monitor +``` + diff --git a/main/boards/yunliao-s3/config.h b/main/boards/yunliao-s3/config.h index be74002..99847dc 100644 --- a/main/boards/yunliao-s3/config.h +++ b/main/boards/yunliao-s3/config.h @@ -1,59 +1,59 @@ -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_REFERENCE true - -#define AUDIO_INPUT_SAMPLE_RATE 24000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 -#define AUDIO_DEFAULT_OUTPUT_VOLUME 70 - -#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14 -#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13 -#define AUDIO_I2S_GPIO_WS GPIO_NUM_11 -#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12 -#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 - -#define AUDIO_CODEC_PA_PIN GPIO_NUM_17 -#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21 -#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_18 -#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR - -#define BOOT_BUTTON_PIN GPIO_NUM_2 -#define BOOT_5V_PIN GPIO_NUM_3 //5V升压输出 -#define BOOT_4G_PIN GPIO_NUM_5 //4G模块使能 -#define MON_BATT_PIN GPIO_NUM_43 //检测PMU电池指示 -#define MON_BATT_CNT 70 //检测PMU电池秒数 -#define MON_USB_PIN GPIO_NUM_47 //检测USB插入 - - -#define ML307_RX_PIN GPIO_NUM_16 -#define ML307_TX_PIN GPIO_NUM_15 - -#define DISPLAY_SPI_LCD_HOST SPI2_HOST -#define DISPLAY_SPI_CLOCK_HZ (40 * 1000 * 1000) -#define DISPLAY_SPI_PIN_SCLK 42 -#define DISPLAY_SPI_PIN_MOSI 40 -#define DISPLAY_SPI_PIN_MISO -1 -#define DISPLAY_SPI_PIN_LCD_DC 41 -#define DISPLAY_SPI_PIN_LCD_RST 45 -#define DISPLAY_SPI_PIN_LCD_CS -1 -#define DISPLAY_PIN_TOUCH_CS -1 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_SWAP_XY true -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y true -#define DISPLAY_INVERT_COLOR false -#define DISPLAY_RGB_ORDER_COLOR LCD_RGB_ELEMENT_ORDER_RGB - -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 -#define KEY_EXPIRE_MS 800 - -#endif // _BOARD_CONFIG_H_ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_DEFAULT_OUTPUT_VOLUME 70 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_11 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_17 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_18 +#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_PIN GPIO_NUM_2 +#define BOOT_5V_PIN GPIO_NUM_3 //5V升压输出 +#define BOOT_4G_PIN GPIO_NUM_5 //4G模块使能 +#define MON_BATT_PIN GPIO_NUM_43 //检测PMU电池指示 +#define MON_BATT_CNT 70 //检测PMU电池秒数 +#define MON_USB_PIN GPIO_NUM_47 //检测USB插入 + + +#define ML307_RX_PIN GPIO_NUM_16 +#define ML307_TX_PIN GPIO_NUM_15 + +#define DISPLAY_SPI_LCD_HOST SPI2_HOST +#define DISPLAY_SPI_CLOCK_HZ (40 * 1000 * 1000) +#define DISPLAY_SPI_PIN_SCLK 42 +#define DISPLAY_SPI_PIN_MOSI 40 +#define DISPLAY_SPI_PIN_MISO -1 +#define DISPLAY_SPI_PIN_LCD_DC 41 +#define DISPLAY_SPI_PIN_LCD_RST 45 +#define DISPLAY_SPI_PIN_LCD_CS -1 +#define DISPLAY_PIN_TOUCH_CS -1 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_SWAP_XY true +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER_COLOR LCD_RGB_ELEMENT_ORDER_RGB + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define KEY_EXPIRE_MS 800 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/yunliao-s3/config.json b/main/boards/yunliao-s3/config.json index 1261a68..b4e284e 100644 --- a/main/boards/yunliao-s3/config.json +++ b/main/boards/yunliao-s3/config.json @@ -1,11 +1,11 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "yunliao-s3", - "sdkconfig_append": [ - "CONFIG_USE_DEVICE_AEC=y" - ] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "yunliao-s3", + "sdkconfig_append": [ + "CONFIG_USE_DEVICE_AEC=y" + ] + } + ] } \ No newline at end of file diff --git a/main/boards/yunliao-s3/power_manager.cc b/main/boards/yunliao-s3/power_manager.cc index 19999e6..6f1bc96 100644 --- a/main/boards/yunliao-s3/power_manager.cc +++ b/main/boards/yunliao-s3/power_manager.cc @@ -1,203 +1,203 @@ -#include "power_manager.h" -#include "esp_sleep.h" -#include "driver/rtc_io.h" -#include "esp_log.h" -#include "config.h" -#include -#include "esp_log.h" -#include "settings.h" - -#define TAG "PowerManager" - -static QueueHandle_t gpio_evt_queue = NULL; -uint16_t battCnt;//闪灯次数 -int battLife = -1; //电量 - -// 中断服务程序 -static void IRAM_ATTR batt_mon_isr_handler(void* arg) { - uint32_t gpio_num = (uint32_t) arg; - xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); -} - -// 添加任务处理函数 -static void batt_mon_task(void* arg) { - uint32_t io_num; - while(1) { - if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) { - battCnt++; - } - } -} - -static void calBattLife() { - // 计算电量 - battLife = battCnt; - - if (battLife > 100){ - battLife = 100; - } - // ESP_LOGI(TAG, "Battery life:%d", (int)battLife); - // 重置计数器 - battCnt = 0; -} - -PowerManager::PowerManager(){ -} - -void PowerManager::Initialize(){ - // 初始化5V控制引脚 - gpio_config_t io_conf_5v = { - .pin_bit_mask = 1<(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); -} - -void PowerManager::CheckBatteryStatus(){ - call_count_++; - if(call_count_ >= MON_BATT_CNT) { - calBattLife(); - call_count_ = 0; - } - - bool new_charging_status = IsCharging(); - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (charging_callback_) { - charging_callback_(is_charging_); - } - } - - bool new_discharging_status = IsDischarging(); - if (new_discharging_status != is_discharging_) { - is_discharging_ = new_discharging_status; - if (discharging_callback_) { - discharging_callback_(is_discharging_); - } - } -} - -bool PowerManager::IsCharging() { - return gpio_get_level(MON_USB_PIN) == 1 && !IsChargingDone(); -} - -bool PowerManager::IsDischarging() { - return gpio_get_level(MON_USB_PIN) == 0; -} - -bool PowerManager::IsChargingDone() { - return battLife >= 95; -} - -int PowerManager::GetBatteryLevel() { - return battLife; -} - -void PowerManager::OnChargingStatusChanged(std::function callback) { - charging_callback_ = callback; -} - -void PowerManager::OnChargingStatusDisChanged(std::function callback) { - discharging_callback_ = callback; -} - -void PowerManager::CheckStartup() { - Settings settings1("board", true); - if(settings1.GetInt("sleep_flag", 0) > 0){ - vTaskDelay(pdMS_TO_TICKS(1000)); - if( gpio_get_level(BOOT_BUTTON_PIN) == 1) { - Sleep(); //进入休眠模式 - }else{ - settings1.SetInt("sleep_flag", 0); - } - } -} - -void PowerManager::Start5V() { - gpio_set_level(BOOT_5V_PIN, 1); -} - -void PowerManager::Shutdown5V() { - gpio_set_level(BOOT_5V_PIN, 0); -} - -void PowerManager::Start4G() { - gpio_set_level(BOOT_4G_PIN, 1); -} - -void PowerManager::Shutdown4G() { - gpio_set_level(BOOT_4G_PIN, 0); - gpio_set_level(ML307_RX_PIN,1); - gpio_set_level(ML307_TX_PIN,1); -} - -void PowerManager::Sleep() { - ESP_LOGI(TAG, "Entering deep sleep"); - Settings settings("board", true); - settings.SetInt("sleep_flag", 1); - Shutdown4G(); - Shutdown5V(); - - if(gpio_evt_queue) { - vQueueDelete(gpio_evt_queue); - gpio_evt_queue = NULL; - } - ESP_ERROR_CHECK(gpio_isr_handler_remove(BOOT_BUTTON_PIN)); - ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(BOOT_BUTTON_PIN, 0)); - ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(BOOT_BUTTON_PIN)); - ESP_ERROR_CHECK(rtc_gpio_pullup_en(BOOT_BUTTON_PIN)); - esp_deep_sleep_start(); +#include "power_manager.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" +#include "esp_log.h" +#include "config.h" +#include +#include "esp_log.h" +#include "settings.h" + +#define TAG "PowerManager" + +static QueueHandle_t gpio_evt_queue = NULL; +uint16_t battCnt;//闪灯次数 +int battLife = 70; //电量 + +// 中断服务程序 +static void IRAM_ATTR batt_mon_isr_handler(void* arg) { + uint32_t gpio_num = (uint32_t) arg; + xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); +} + +// 添加任务处理函数 +static void batt_mon_task(void* arg) { + uint32_t io_num; + while(1) { + if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) { + battCnt++; + } + } +} + +static void calBattLife() { + // 计算电量 + battLife = battCnt; + + if (battLife > 100){ + battLife = 100; + } + // ESP_LOGI(TAG, "Battery life:%d", (int)battLife); + // 重置计数器 + battCnt = 0; +} + +PowerManager::PowerManager(){ +} + +void PowerManager::Initialize(){ + // 初始化5V控制引脚 + gpio_config_t io_conf_5v = { + .pin_bit_mask = 1<(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); +} + +void PowerManager::CheckBatteryStatus(){ + call_count_++; + if(call_count_ >= MON_BATT_CNT) { + calBattLife(); + call_count_ = 0; + } + + bool new_charging_status = IsCharging(); + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (charging_callback_) { + charging_callback_(is_charging_); + } + } + + bool new_discharging_status = IsDischarging(); + if (new_discharging_status != is_discharging_) { + is_discharging_ = new_discharging_status; + if (discharging_callback_) { + discharging_callback_(is_discharging_); + } + } +} + +bool PowerManager::IsCharging() { + return gpio_get_level(MON_USB_PIN) == 1 && !IsChargingDone(); +} + +bool PowerManager::IsDischarging() { + return gpio_get_level(MON_USB_PIN) == 0; +} + +bool PowerManager::IsChargingDone() { + return battLife >= 95; +} + +int PowerManager::GetBatteryLevel() { + return battLife; +} + +void PowerManager::OnChargingStatusChanged(std::function callback) { + charging_callback_ = callback; +} + +void PowerManager::OnChargingStatusDisChanged(std::function callback) { + discharging_callback_ = callback; +} + +void PowerManager::CheckStartup() { + Settings settings1("board", true); + if(settings1.GetInt("sleep_flag", 0) > 0){ + vTaskDelay(pdMS_TO_TICKS(1000)); + if( gpio_get_level(BOOT_BUTTON_PIN) == 1) { + Sleep(); //进入休眠模式 + }else{ + settings1.SetInt("sleep_flag", 0); + } + } +} + +void PowerManager::Start5V() { + gpio_set_level(BOOT_5V_PIN, 1); +} + +void PowerManager::Shutdown5V() { + gpio_set_level(BOOT_5V_PIN, 0); +} + +void PowerManager::Start4G() { + gpio_set_level(BOOT_4G_PIN, 1); +} + +void PowerManager::Shutdown4G() { + gpio_set_level(BOOT_4G_PIN, 0); + gpio_set_level(ML307_RX_PIN,1); + gpio_set_level(ML307_TX_PIN,1); +} + +void PowerManager::Sleep() { + ESP_LOGI(TAG, "Entering deep sleep"); + Settings settings("board", true); + settings.SetInt("sleep_flag", 1); + Shutdown4G(); + Shutdown5V(); + + if(gpio_evt_queue) { + vQueueDelete(gpio_evt_queue); + gpio_evt_queue = NULL; + } + ESP_ERROR_CHECK(gpio_isr_handler_remove(BOOT_BUTTON_PIN)); + ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(BOOT_BUTTON_PIN, 0)); + ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(BOOT_BUTTON_PIN)); + ESP_ERROR_CHECK(rtc_gpio_pullup_en(BOOT_BUTTON_PIN)); + esp_deep_sleep_start(); } \ No newline at end of file diff --git a/main/boards/yunliao-s3/power_manager.h b/main/boards/yunliao-s3/power_manager.h index 921cf47..9979d1d 100644 --- a/main/boards/yunliao-s3/power_manager.h +++ b/main/boards/yunliao-s3/power_manager.h @@ -1,37 +1,37 @@ -#ifndef __POWERMANAGER_H__ -#define __POWERMANAGER_H__ - -#include -#include "driver/gpio.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/queue.h" -#include "freertos/timers.h" - -class PowerManager{ -public: - PowerManager(); - void Initialize(); - bool IsCharging(); - bool IsDischarging(); - bool IsChargingDone(); - int GetBatteryLevel(); - void CheckStartup(); - void Start5V(); - void Shutdown5V(); - void Start4G(); - void Shutdown4G(); - void Sleep(); - void CheckBatteryStatus(); - void OnChargingStatusChanged(std::function callback); - void OnChargingStatusDisChanged(std::function callback); -private: - esp_timer_handle_t timer_handle_; - std::function charging_callback_; - std::function discharging_callback_; - int is_charging_ = -1; - int is_discharging_ = -1; - int call_count_ = 0; -}; - +#ifndef __POWERMANAGER_H__ +#define __POWERMANAGER_H__ + +#include +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/timers.h" + +class PowerManager{ +public: + PowerManager(); + void Initialize(); + bool IsCharging(); + bool IsDischarging(); + bool IsChargingDone(); + int GetBatteryLevel(); + void CheckStartup(); + void Start5V(); + void Shutdown5V(); + void Start4G(); + void Shutdown4G(); + void Sleep(); + void CheckBatteryStatus(); + void OnChargingStatusChanged(std::function callback); + void OnChargingStatusDisChanged(std::function callback); +private: + esp_timer_handle_t timer_handle_; + std::function charging_callback_; + std::function discharging_callback_; + int is_charging_ = -1; + int is_discharging_ = -1; + int call_count_ = 0; +}; + #endif \ No newline at end of file diff --git a/main/boards/yunliao-s3/yunliao_s3.cc b/main/boards/yunliao-s3/yunliao_s3.cc index f8e43e8..a9e059d 100644 --- a/main/boards/yunliao-s3/yunliao_s3.cc +++ b/main/boards/yunliao-s3/yunliao_s3.cc @@ -1,207 +1,212 @@ -#include "lvgl_theme.h" -#include "dual_network_board.h" -#include "codecs/es8388_audio_codec.h" -#include "display/lcd_display.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "power_manager.h" -#include "assets/lang_config.h" -#include -#include -#include - - -#define TAG "YunliaoS3" - -class YunliaoS3 : public DualNetworkBoard { -private: - i2c_master_bus_handle_t codec_i2c_bus_; - Button boot_button_; - SpiLcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - - void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, 600); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(10); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->OnShutdownRequest([this]() { - ESP_LOGI(TAG, "Shutting down"); - power_manager_->Sleep(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeI2c() { - // Initialize I2C peripheral - i2c_master_bus_config_t i2c_bus_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, - .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SPI_PIN_MOSI; - buscfg.miso_io_num = DISPLAY_SPI_PIN_MISO; - buscfg.sclk_io_num = DISPLAY_SPI_PIN_SCLK; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - app.ToggleChatState(); - }); - boot_button_.OnDoubleClick([this]() { - ESP_LOGI(TAG, "Button OnDoubleClick"); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { - SwitchNetworkType(); - } - }); - boot_button_.OnMultipleClick([this]() { - ESP_LOGI(TAG, "Button OnThreeClick"); - if (GetNetworkType() == NetworkType::WIFI) { - auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); - } - },3); - boot_button_.OnLongPress([this]() { - ESP_LOGI(TAG, "Button LongPress to Sleep"); - display_->SetStatus(Lang::Strings::PLEASE_WAIT); - vTaskDelay(pdMS_TO_TICKS(2000)); - power_manager_->Sleep(); - }); - } - void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - // 液晶屏控制IO初始化 - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_SPI_PIN_LCD_CS; - io_config.dc_gpio_num = DISPLAY_SPI_PIN_LCD_DC; - io_config.spi_mode = 3; - io_config.pclk_hz = DISPLAY_SPI_CLOCK_HZ; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(DISPLAY_SPI_LCD_HOST, &io_config, &panel_io)); - - // 初始化液晶屏驱动芯片ST7789 - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_SPI_PIN_LCD_RST; - panel_config.rgb_ele_order = DISPLAY_RGB_ORDER_COLOR; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - - esp_lcd_panel_reset(panel); - esp_lcd_panel_init(panel); - esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); - esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); - esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_WIDTH, - DISPLAY_HEIGHT, DISPLAY_OFFSET_X, - DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, - DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - auto& theme_manager = LvglThemeManager::GetInstance(); - auto theme = theme_manager.GetTheme("dark"); - if (theme != nullptr) { - display_->SetTheme(theme); - } - } - -public: - YunliaoS3() : - DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0), - boot_button_(BOOT_BUTTON_PIN), - power_manager_(new PowerManager()){ - power_manager_->Start5V(); - power_manager_->Initialize(); - InitializeI2c(); - power_manager_->CheckStartup(); - InitializePowerSaveTimer(); - InitializeSpi(); - InitializeButtons(); - InitializeSt7789Display(); - power_manager_->OnChargingStatusDisChanged([this](bool is_discharging) { - if(power_save_timer_){ - if (is_discharging) { - power_save_timer_->SetEnabled(true); - } else { - power_save_timer_->SetEnabled(false); - } - } - }); - if(GetNetworkType() == NetworkType::WIFI){ - power_manager_->Shutdown4G(); - } - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static Es8388AudioCodec audio_codec( - codec_i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_GPIO_MCLK, - AUDIO_I2S_GPIO_BCLK, - AUDIO_I2S_GPIO_WS, - AUDIO_I2S_GPIO_DOUT, - AUDIO_I2S_GPIO_DIN, - AUDIO_CODEC_PA_PIN, - AUDIO_CODEC_ES8388_ADDR, - AUDIO_INPUT_REFERENCE - ); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - level = power_manager_->GetBatteryLevel(); - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - DualNetworkBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(YunliaoS3); +#include "lvgl_theme.h" +#include "dual_network_board.h" +#include "codecs/es8388_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "power_manager.h" +#include "assets/lang_config.h" +#include +#include +#include + + +#define TAG "YunliaoS3" + +class YunliaoS3 : public DualNetworkBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + SpiLcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 600); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(10); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + power_manager_->Sleep(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SPI_PIN_MOSI; + buscfg.miso_io_num = DISPLAY_SPI_PIN_MISO; + buscfg.sclk_io_num = DISPLAY_SPI_PIN_SCLK; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + app.ToggleChatState(); + }); + boot_button_.OnDoubleClick([this]() { + ESP_LOGI(TAG, "Button OnDoubleClick"); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + SwitchNetworkType(); + } + }); + boot_button_.OnMultipleClick([this]() { + ESP_LOGI(TAG, "Button OnThreeClick"); + if (GetNetworkType() == NetworkType::WIFI) { + auto& wifi_board = static_cast(GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + } + },3); + boot_button_.OnLongPress([this]() { + ESP_LOGI(TAG, "Button LongPress to Sleep"); + display_->SetStatus(Lang::Strings::PLEASE_WAIT); + vTaskDelay(pdMS_TO_TICKS(2000)); + power_manager_->Sleep(); + }); + } + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_SPI_PIN_LCD_CS; + io_config.dc_gpio_num = DISPLAY_SPI_PIN_LCD_DC; + io_config.spi_mode = 3; + io_config.pclk_hz = DISPLAY_SPI_CLOCK_HZ; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(DISPLAY_SPI_LCD_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_SPI_PIN_LCD_RST; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER_COLOR; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_WIDTH, + DISPLAY_HEIGHT, DISPLAY_OFFSET_X, + DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + auto& theme_manager = LvglThemeManager::GetInstance(); + auto theme = theme_manager.GetTheme("dark"); + if (theme != nullptr) { + display_->SetTheme(theme); + } + } + +public: + YunliaoS3() : + DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0), + boot_button_(BOOT_BUTTON_PIN), + power_manager_(new PowerManager()){ + power_manager_->Start5V(); + power_manager_->Initialize(); + InitializeI2c(); + power_manager_->CheckStartup(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeSt7789Display(); + power_manager_->OnChargingStatusDisChanged([this](bool is_discharging) { + if(power_save_timer_){ + if (is_discharging) { + power_save_timer_->SetEnabled(true); + } else { + power_save_timer_->SetEnabled(false); + } + } + }); + if(GetNetworkType() == NetworkType::WIFI){ + power_manager_->Shutdown4G(); + }else{ + power_manager_->Start4G(); + } + GetBacklight()->RestoreBrightness(); + while(gpio_get_level(BOOT_BUTTON_PIN) == 0){ + vTaskDelay(pdMS_TO_TICKS(10)); + } + InitializeButtons(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8388AudioCodec audio_codec( + codec_i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8388_ADDR, + AUDIO_INPUT_REFERENCE + ); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + level = power_manager_->GetBatteryLevel(); + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + DualNetworkBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(YunliaoS3); diff --git a/main/boards/zhengchen-1.54tft-ml307/README.md b/main/boards/zhengchen-1.54tft-ml307/README.md index a7f289b..0ab5a94 100644 --- a/main/boards/zhengchen-1.54tft-ml307/README.md +++ b/main/boards/zhengchen-1.54tft-ml307/README.md @@ -1,45 +1,45 @@ -# 产品相关介绍网址 - -```http -https://e.tb.cn/h.6Gl2LC7rsrswQZp?tk=qFuaV9hzh0k CZ356 -``` - -# 编译配置命令 - -**配置编译目标为 ESP32S3:** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> zhengchen-1.54tft-ml307 -``` - -``` - -**编译:** - -bash -idf.py build -``` - -**下载:** -idf.py build flash monitor - -进行下载和显示日志 - - -**固件生成:** - -```bash -idf.py merge-bin -``` +# 产品相关介绍网址 + +```http +https://e.tb.cn/h.6Gl2LC7rsrswQZp?tk=qFuaV9hzh0k CZ356 +``` + +# 编译配置命令 + +**配置编译目标为 ESP32S3:** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> zhengchen-1.54tft-ml307 +``` + +``` + +**编译:** + +bash +idf.py build +``` + +**下载:** +idf.py build flash monitor + +进行下载和显示日志 + + +**固件生成:** + +```bash +idf.py merge-bin +``` diff --git a/main/boards/zhengchen-1.54tft-ml307/config.h b/main/boards/zhengchen-1.54tft-ml307/config.h index 888a052..6da2524 100644 --- a/main/boards/zhengchen-1.54tft-ml307/config.h +++ b/main/boards/zhengchen-1.54tft-ml307/config.h @@ -1,42 +1,42 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 - -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_10 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 - -#define DISPLAY_SDA GPIO_NUM_41 -#define DISPLAY_SCL GPIO_NUM_42 -#define DISPLAY_RES GPIO_NUM_45 -#define DISPLAY_DC GPIO_NUM_40 -#define DISPLAY_CS GPIO_NUM_21 - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define BACKLIGHT_INVERT false -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_20 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - -#define ML307_RX_PIN GPIO_NUM_11 -#define ML307_TX_PIN GPIO_NUM_12 - -#endif // _BOARD_CONFIG_H_ + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_10 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 + +#define DISPLAY_SDA GPIO_NUM_41 +#define DISPLAY_SCL GPIO_NUM_42 +#define DISPLAY_RES GPIO_NUM_45 +#define DISPLAY_DC GPIO_NUM_40 +#define DISPLAY_CS GPIO_NUM_21 + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define BACKLIGHT_INVERT false +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_20 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define ML307_RX_PIN GPIO_NUM_11 +#define ML307_TX_PIN GPIO_NUM_12 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/zhengchen-1.54tft-ml307/config.json b/main/boards/zhengchen-1.54tft-ml307/config.json index 6e17be7..cd42065 100644 --- a/main/boards/zhengchen-1.54tft-ml307/config.json +++ b/main/boards/zhengchen-1.54tft-ml307/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "zhengchen-1.54tft-ml307", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "zhengchen-1.54tft-ml307", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc b/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc index 48845a1..2a2751c 100644 --- a/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc +++ b/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc @@ -1,211 +1,211 @@ -#include "dual_network_board.h" -#include "codecs/no_audio_codec.h" -#include "../zhengchen-1.54tft-wifi/zhengchen_lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "../zhengchen-1.54tft-wifi/power_manager.h" - -#include -#include -#include - -#include -#include - -#define TAG "ZHENGCHEN_1_54TFT_ML307" - -class ZHENGCHEN_1_54TFT_ML307 : public DualNetworkBoard { -private: - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - ZHENGCHEN_LcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_9); - power_manager_->OnTemperatureChanged([this](float chip_temp) { - display_->UpdateHighTempWarning(chip_temp); - }); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - } else { - power_save_timer_->SetEnabled(true); - } - }); - } - - void InitializePowerSaveTimer() { - rtc_gpio_init(GPIO_NUM_2); - rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_2, 1); - - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SDA; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCL; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - boot_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - // cast to WifiBoard - auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); - } - } - app.ToggleChatState(); - }); - - boot_button_.OnLongPress([this]() { - SwitchNetworkType(); - }); - - volume_up_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10)); - }); - - volume_up_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10)); - }); - - volume_down_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - - void InitializeSt7789Display() { - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS; - io_config.dc_gpio_num = DISPLAY_DC; - io_config.spi_mode = 3; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_)); - - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RES; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); - - display_ = new ZHENGCHEN_LcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, - DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - display_->SetupHighTempWarningPopup(); - } - -public: - ZHENGCHEN_1_54TFT_ML307() : - DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeSpi(); - InitializeButtons(); - InitializeSt7789Display(); - GetBacklight()->RestoreBrightness(); - } - - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = std::max(power_manager_->GetBatteryLevel(), 20); - return true; - } - - virtual bool GetTemperature(float& esp32temp) override { - esp32temp = power_manager_->GetTemperature(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - DualNetworkBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(ZHENGCHEN_1_54TFT_ML307); +#include "dual_network_board.h" +#include "codecs/no_audio_codec.h" +#include "../zhengchen-1.54tft-wifi/zhengchen_lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "../zhengchen-1.54tft-wifi/power_manager.h" + +#include +#include +#include + +#include +#include + +#define TAG "ZHENGCHEN_1_54TFT_ML307" + +class ZHENGCHEN_1_54TFT_ML307 : public DualNetworkBoard { +private: + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + ZHENGCHEN_LcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_9); + power_manager_->OnTemperatureChanged([this](float chip_temp) { + display_->UpdateHighTempWarning(chip_temp); + }); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_2); + rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_2, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SDA; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCL; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (GetNetworkType() == NetworkType::WIFI) { + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + // cast to WifiBoard + auto& wifi_board = static_cast(GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + } + } + app.ToggleChatState(); + }); + + boot_button_.OnLongPress([this]() { + SwitchNetworkType(); + }); + + volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10)); + }); + + volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10)); + }); + + volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + + void InitializeSt7789Display() { + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS; + io_config.dc_gpio_num = DISPLAY_DC; + io_config.spi_mode = 3; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RES; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); + + display_ = new ZHENGCHEN_LcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, + DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + display_->SetupHighTempWarningPopup(); + } + +public: + ZHENGCHEN_1_54TFT_ML307() : + DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeButtons(); + InitializeSt7789Display(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = std::max(power_manager_->GetBatteryLevel(), 20); + return true; + } + + virtual bool GetTemperature(float& esp32temp) override { + esp32temp = power_manager_->GetTemperature(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + DualNetworkBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(ZHENGCHEN_1_54TFT_ML307); diff --git a/main/boards/zhengchen-1.54tft-wifi/README.md b/main/boards/zhengchen-1.54tft-wifi/README.md index 20bd9a6..39c1fcc 100644 --- a/main/boards/zhengchen-1.54tft-wifi/README.md +++ b/main/boards/zhengchen-1.54tft-wifi/README.md @@ -1,45 +1,45 @@ -# 产品相关介绍网址 - -```http -https://e.tb.cn/h.6Gl2LC7rsrswQZp?tk=qFuaV9hzh0k CZ356 -``` - -# 编译配置命令 - -**配置编译目标为 ESP32S3:** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> zhengchen-1.54tft-wifi -``` - -``` - -**编译:** - -bash -idf.py build -``` - -**下载:** -idf.py build flash monitor - -进行下载和显示日志 - - -**固件生成:** - -```bash -idf.py merge-bin -``` +# 产品相关介绍网址 + +```http +https://e.tb.cn/h.6Gl2LC7rsrswQZp?tk=qFuaV9hzh0k CZ356 +``` + +# 编译配置命令 + +**配置编译目标为 ESP32S3:** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> zhengchen-1.54tft-wifi +``` + +``` + +**编译:** + +bash +idf.py build +``` + +**下载:** +idf.py build flash monitor + +进行下载和显示日志 + + +**固件生成:** + +```bash +idf.py merge-bin +``` diff --git a/main/boards/zhengchen-1.54tft-wifi/config.h b/main/boards/zhengchen-1.54tft-wifi/config.h index f7d971a..f54c396 100644 --- a/main/boards/zhengchen-1.54tft-wifi/config.h +++ b/main/boards/zhengchen-1.54tft-wifi/config.h @@ -1,39 +1,39 @@ - -#ifndef _BOARD_CONFIG_H_ -#define _BOARD_CONFIG_H_ - -#include - -#define AUDIO_INPUT_SAMPLE_RATE 16000 -#define AUDIO_OUTPUT_SAMPLE_RATE 24000 -#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 -#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 -#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 -#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 -#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 -#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 - -#define BOOT_BUTTON_GPIO GPIO_NUM_0 -#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_10 -#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 - -#define DISPLAY_SDA GPIO_NUM_41 -#define DISPLAY_SCL GPIO_NUM_42 -#define DISPLAY_RES GPIO_NUM_45 -#define DISPLAY_DC GPIO_NUM_40 -#define DISPLAY_CS GPIO_NUM_21 - -#define DISPLAY_WIDTH 240 -#define DISPLAY_HEIGHT 240 -#define DISPLAY_SWAP_XY false -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define BACKLIGHT_INVERT false -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 0 - -#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_20 -#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false - - -#endif // _BOARD_CONFIG_H_ + +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 + +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_10 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 + +#define DISPLAY_SDA GPIO_NUM_41 +#define DISPLAY_SCL GPIO_NUM_42 +#define DISPLAY_RES GPIO_NUM_45 +#define DISPLAY_DC GPIO_NUM_40 +#define DISPLAY_CS GPIO_NUM_21 + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_SWAP_XY false +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define BACKLIGHT_INVERT false +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_20 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/zhengchen-1.54tft-wifi/config.json b/main/boards/zhengchen-1.54tft-wifi/config.json index 3c17d11..83ab41e 100644 --- a/main/boards/zhengchen-1.54tft-wifi/config.json +++ b/main/boards/zhengchen-1.54tft-wifi/config.json @@ -1,9 +1,9 @@ -{ - "target": "esp32s3", - "builds": [ - { - "name": "zhengchen-1.54tft-wifi", - "sdkconfig_append": [] - } - ] +{ + "target": "esp32s3", + "builds": [ + { + "name": "zhengchen-1.54tft-wifi", + "sdkconfig_append": [] + } + ] } \ No newline at end of file diff --git a/main/boards/zhengchen-1.54tft-wifi/power_manager.h b/main/boards/zhengchen-1.54tft-wifi/power_manager.h index de6e82d..bef21bf 100644 --- a/main/boards/zhengchen-1.54tft-wifi/power_manager.h +++ b/main/boards/zhengchen-1.54tft-wifi/power_manager.h @@ -1,238 +1,238 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include "application.h" -#include "zhengchen_lcd_display.h" - -class PowerManager { -private: - // 定时器句柄 - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - std::function on_temperature_changed_; - - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector adc_values_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - float current_temperature_ = 0.0f; - int ticks_ = 0; - const int kBatteryAdcInterval = 60; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - const int kTemperatureReadInterval = 10; // 每 10 秒读取一次温度 - - adc_oneshot_unit_handle_t adc_handle_; - temperature_sensor_handle_t temp_sensor_ = NULL; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 1; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (adc_values_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - - // 新增:周期性读取温度 - if (ticks_ % kTemperatureReadInterval == 0) { - ReadTemperature(); - } - } - - void ReadBatteryAdcData() { - // 读取 ADC 值 - int adc_value; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_7, &adc_value)); - - - // 将 ADC 值添加到队列中 - adc_values_.push_back(adc_value); - if (adc_values_.size() > kBatteryAdcDataCount) { - adc_values_.erase(adc_values_.begin()); - } - uint32_t average_adc = 0; - for (auto value : adc_values_) { - average_adc += (value + 80); - } - average_adc /= adc_values_.size(); - - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {2030, 0}, - {2134, 20}, - {2252, 40}, - {2370, 60}, - {2488, 80}, - {2606, 100} - }; - // 低于最低值时 - if (average_adc < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_adc >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { - float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - // 检查是否达到低电量阈值 - if (adc_values_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - - ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); - } - - void ReadTemperature() { - float temperature = 0.0f; - ESP_ERROR_CHECK(temperature_sensor_get_celsius(temp_sensor_, &temperature)); - - if (abs(temperature - current_temperature_) >= 3.5f) { // 温度变化超过3.5°C才触发回调 - current_temperature_ = temperature; - if (on_temperature_changed_) { - on_temperature_changed_(current_temperature_); - } - ESP_LOGI("PowerManager", "Temperature updated: %.1f°C", current_temperature_); - } - } - - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_7, &chan_config)); - - // 初始化温度传感器 - temperature_sensor_config_t temp_config = { - .range_min = 10, - .range_max = 80, - .clk_src = TEMPERATURE_SENSOR_CLK_SRC_DEFAULT - }; - ESP_ERROR_CHECK(temperature_sensor_install(&temp_config, &temp_sensor_)); - ESP_ERROR_CHECK(temperature_sensor_enable(temp_sensor_)); - ESP_LOGI("PowerManager", "Temperature sensor initialized (new driver)"); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - - if (temp_sensor_) { - temperature_sensor_disable(temp_sensor_); - temperature_sensor_uninstall(temp_sensor_); - } - - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - // 获取电池电量 - uint8_t GetBatteryLevel() { - // 返回电池电量 - return battery_level_; - } - - float GetTemperature() const { return current_temperature_; } // 获取当前温度 - - void OnTemperatureChanged(std::function callback) { - on_temperature_changed_ = callback; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include "application.h" +#include "zhengchen_lcd_display.h" + +class PowerManager { +private: + // 定时器句柄 + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + std::function on_temperature_changed_; + + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + float current_temperature_ = 0.0f; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + const int kTemperatureReadInterval = 10; // 每 10 秒读取一次温度 + + adc_oneshot_unit_handle_t adc_handle_; + temperature_sensor_handle_t temp_sensor_ = NULL; + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 1; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + + // 新增:周期性读取温度 + if (ticks_ % kTemperatureReadInterval == 0) { + ReadTemperature(); + } + } + + void ReadBatteryAdcData() { + // 读取 ADC 值 + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_7, &adc_value)); + + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += (value + 80); + } + average_adc /= adc_values_.size(); + + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {2030, 0}, + {2134, 20}, + {2252, 40}, + {2370, 60}, + {2488, 80}, + {2606, 100} + }; + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + // 检查是否达到低电量阈值 + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + + void ReadTemperature() { + float temperature = 0.0f; + ESP_ERROR_CHECK(temperature_sensor_get_celsius(temp_sensor_, &temperature)); + + if (abs(temperature - current_temperature_) >= 3.5f) { // 温度变化超过3.5°C才触发回调 + current_temperature_ = temperature; + if (on_temperature_changed_) { + on_temperature_changed_(current_temperature_); + } + ESP_LOGI("PowerManager", "Temperature updated: %.1f°C", current_temperature_); + } + } + + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_1, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_7, &chan_config)); + + // 初始化温度传感器 + temperature_sensor_config_t temp_config = { + .range_min = 10, + .range_max = 80, + .clk_src = TEMPERATURE_SENSOR_CLK_SRC_DEFAULT + }; + ESP_ERROR_CHECK(temperature_sensor_install(&temp_config, &temp_sensor_)); + ESP_ERROR_CHECK(temperature_sensor_enable(temp_sensor_)); + ESP_LOGI("PowerManager", "Temperature sensor initialized (new driver)"); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } + + if (temp_sensor_) { + temperature_sensor_disable(temp_sensor_); + temperature_sensor_uninstall(temp_sensor_); + } + + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + // 获取电池电量 + uint8_t GetBatteryLevel() { + // 返回电池电量 + return battery_level_; + } + + float GetTemperature() const { return current_temperature_; } // 获取当前温度 + + void OnTemperatureChanged(std::function callback) { + on_temperature_changed_ = callback; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; diff --git a/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc b/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc index 282a628..15c4539 100644 --- a/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc +++ b/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc @@ -1,228 +1,228 @@ -#include "wifi_board.h" -#include "codecs/no_audio_codec.h" -#include "zhengchen_lcd_display.h" -#include "system_reset.h" -#include "application.h" -#include "button.h" -#include "config.h" -#include "power_save_timer.h" -#include "led/single_led.h" -#include "assets/lang_config.h" -#include "power_manager.h" - -#include -#include -#include - -#include -#include - -#define TAG "ZHENGCHEN_1_54TFT_WIFI" - -class ZHENGCHEN_1_54TFT_WIFI : public WifiBoard { -private: - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - ZHENGCHEN_LcdDisplay* display_; - PowerSaveTimer* power_save_timer_; - PowerManager* power_manager_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_9); - power_manager_->OnTemperatureChanged([this](float chip_temp) { - display_->UpdateHighTempWarning(chip_temp); - }); - - power_manager_->OnChargingStatusChanged([this](bool is_charging) { - if (is_charging) { - power_save_timer_->SetEnabled(false); - ESP_LOGI("PowerManager", "Charging started"); - } else { - power_save_timer_->SetEnabled(true); - ESP_LOGI("PowerManager", "Charging stopped"); - } - }); - - } - - void InitializePowerSaveTimer() { - rtc_gpio_init(GPIO_NUM_2); - rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_2, 1); - - power_save_timer_ = new PowerSaveTimer(-1, 60, 300); - power_save_timer_->OnEnterSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(true); - GetBacklight()->SetBrightness(1); - }); - power_save_timer_->OnExitSleepMode([this]() { - GetDisplay()->SetPowerSaveMode(false); - GetBacklight()->RestoreBrightness(); - }); - power_save_timer_->SetEnabled(true); - } - - void InitializeSpi() { - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = DISPLAY_SDA; - buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = DISPLAY_SCL; - buscfg.quadwp_io_num = GPIO_NUM_NC; - buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); - } - - void InitializeButtons() { - - boot_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); - - // 设置开机按钮的长按事件(直接进入配网模式) - boot_button_.OnLongPress([this]() { - // 唤醒电源保存定时器 - power_save_timer_->WakeUp(); - // 获取应用程序实例 - auto& app = Application::GetInstance(); - - // 进入配网模式 - app.SetDeviceState(kDeviceStateWifiConfiguring); - - // 重置WiFi配置以确保进入配网模式 - ResetWifiConfiguration(); - }); - - volume_up_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10)); - }); - - volume_up_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(100); - GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); - }); - - volume_down_button_.OnClick([this]() { - power_save_timer_->WakeUp(); - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() - 10; - if (volume < 0) { - volume = 0; - } - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10)); - }); - - volume_down_button_.OnLongPress([this]() { - power_save_timer_->WakeUp(); - GetAudioCodec()->SetOutputVolume(0); - GetDisplay()->ShowNotification(Lang::Strings::MUTED); - }); - } - - void InitializeSt7789Display() { - ESP_LOGD(TAG, "Install panel IO"); - esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = DISPLAY_CS; - io_config.dc_gpio_num = DISPLAY_DC; - io_config.spi_mode = 3; - io_config.pclk_hz = 80 * 1000 * 1000; - io_config.trans_queue_depth = 10; - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_)); - - ESP_LOGD(TAG, "Install LCD driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = DISPLAY_RES; - panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; - panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); - - display_ = new ZHENGCHEN_LcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, - DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); - display_->SetupHighTempWarningPopup(); - } - - void InitializeTools() { - } - -public: - ZHENGCHEN_1_54TFT_WIFI() : - boot_button_(BOOT_BUTTON_GPIO), - volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { - InitializePowerManager(); - InitializePowerSaveTimer(); - InitializeSpi(); - InitializeButtons(); - InitializeSt7789Display(); - InitializeTools(); - GetBacklight()->RestoreBrightness(); - } - - // 获取音频编解码器 - virtual AudioCodec* GetAudioCodec() override { - // 静态实例化NoAudioCodecSimplex类 - static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); - // 返回音频编解码器 - return &audio_codec; - } - - virtual Display* GetDisplay() override { - return display_; - } - - virtual Backlight* GetBacklight() override { - static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); - return &backlight; - } - - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = std::max(power_manager_->GetBatteryLevel(), 20); - return true; - } - - virtual bool GetTemperature(float& esp32temp) override { - esp32temp = power_manager_->GetTemperature(); - return true; - } - - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { - power_save_timer_->WakeUp(); - } - WifiBoard::SetPowerSaveMode(enabled); - } -}; - -DECLARE_BOARD(ZHENGCHEN_1_54TFT_WIFI); +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "zhengchen_lcd_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "power_manager.h" + +#include +#include +#include + +#include +#include + +#define TAG "ZHENGCHEN_1_54TFT_WIFI" + +class ZHENGCHEN_1_54TFT_WIFI : public WifiBoard { +private: + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + ZHENGCHEN_LcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_9); + power_manager_->OnTemperatureChanged([this](float chip_temp) { + display_->UpdateHighTempWarning(chip_temp); + }); + + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + ESP_LOGI("PowerManager", "Charging started"); + } else { + power_save_timer_->SetEnabled(true); + ESP_LOGI("PowerManager", "Charging stopped"); + } + }); + + } + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_2); + rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_2, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SDA; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCL; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + + // 设置开机按钮的长按事件(直接进入配网模式) + boot_button_.OnLongPress([this]() { + // 唤醒电源保存定时器 + power_save_timer_->WakeUp(); + // 获取应用程序实例 + auto& app = Application::GetInstance(); + + // 进入配网模式 + app.SetDeviceState(kDeviceStateWifiConfiguring); + + // 重置WiFi配置以确保进入配网模式 + ResetWifiConfiguration(); + }); + + volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10)); + }); + + volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10)); + }); + + volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + + void InitializeSt7789Display() { + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS; + io_config.dc_gpio_num = DISPLAY_DC; + io_config.spi_mode = 3; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RES; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); + + display_ = new ZHENGCHEN_LcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, + DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + display_->SetupHighTempWarningPopup(); + } + + void InitializeTools() { + } + +public: + ZHENGCHEN_1_54TFT_WIFI() : + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeButtons(); + InitializeSt7789Display(); + InitializeTools(); + GetBacklight()->RestoreBrightness(); + } + + // 获取音频编解码器 + virtual AudioCodec* GetAudioCodec() override { + // 静态实例化NoAudioCodecSimplex类 + static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); + // 返回音频编解码器 + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = std::max(power_manager_->GetBatteryLevel(), 20); + return true; + } + + virtual bool GetTemperature(float& esp32temp) override { + esp32temp = power_manager_->GetTemperature(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(ZHENGCHEN_1_54TFT_WIFI); diff --git a/main/boards/zhengchen-1.54tft-wifi/zhengchen_lcd_display.h b/main/boards/zhengchen-1.54tft-wifi/zhengchen_lcd_display.h index fa6222a..aa406c1 100644 --- a/main/boards/zhengchen-1.54tft-wifi/zhengchen_lcd_display.h +++ b/main/boards/zhengchen-1.54tft-wifi/zhengchen_lcd_display.h @@ -1,66 +1,66 @@ -#ifndef ZHENGCHEN_LCD_DISPLAY_H -#define ZHENGCHEN_LCD_DISPLAY_H - -#include "display/lcd_display.h" -#include "lvgl_theme.h" -#include - -class ZHENGCHEN_LcdDisplay : public SpiLcdDisplay { -protected: - lv_obj_t* high_temp_popup_ = nullptr; // 高温警告弹窗 - lv_obj_t* high_temp_label_ = nullptr; // 高温警告标签 - -public: - // 继承构造函数 - using SpiLcdDisplay::SpiLcdDisplay; - - void SetupHighTempWarningPopup() { - auto lvgl_theme = static_cast(current_theme_); - auto text_font = lvgl_theme->text_font()->font(); - // 创建高温警告弹窗 - high_temp_popup_ = lv_obj_create(lv_screen_active()); // 使用当前屏幕 - lv_obj_set_scrollbar_mode(high_temp_popup_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_size(high_temp_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2); - lv_obj_align(high_temp_popup_, LV_ALIGN_BOTTOM_MID, 0, 0); - lv_obj_set_style_bg_color(high_temp_popup_, lv_palette_main(LV_PALETTE_RED), 0); - lv_obj_set_style_radius(high_temp_popup_, 10, 0); - - // 创建警告标签 - high_temp_label_ = lv_label_create(high_temp_popup_); - lv_label_set_text(high_temp_label_, "警告:温度过高"); - lv_obj_set_style_text_color(high_temp_label_, lv_color_white(), 0); - lv_obj_center(high_temp_label_); - - // 默认隐藏 - lv_obj_add_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN); - } - - void UpdateHighTempWarning(float chip_temp, float threshold = 75.0f) { - if (high_temp_popup_ == nullptr) { - ESP_LOGW("ZHENGCHEN_LcdDisplay", "High temp popup not initialized!"); - return; - } - - if (chip_temp >= threshold) { - ShowHighTempWarning(); - } else { - HideHighTempWarning(); - } - } - - void ShowHighTempWarning() { - if (high_temp_popup_ && lv_obj_has_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN)) { - lv_obj_remove_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN); - } - } - - void HideHighTempWarning() { - if (high_temp_popup_ && !lv_obj_has_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN)) { - lv_obj_add_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN); - } - } -}; - - - +#ifndef ZHENGCHEN_LCD_DISPLAY_H +#define ZHENGCHEN_LCD_DISPLAY_H + +#include "display/lcd_display.h" +#include "lvgl_theme.h" +#include + +class ZHENGCHEN_LcdDisplay : public SpiLcdDisplay { +protected: + lv_obj_t* high_temp_popup_ = nullptr; // 高温警告弹窗 + lv_obj_t* high_temp_label_ = nullptr; // 高温警告标签 + +public: + // 继承构造函数 + using SpiLcdDisplay::SpiLcdDisplay; + + void SetupHighTempWarningPopup() { + auto lvgl_theme = static_cast(current_theme_); + auto text_font = lvgl_theme->text_font()->font(); + // 创建高温警告弹窗 + high_temp_popup_ = lv_obj_create(lv_screen_active()); // 使用当前屏幕 + lv_obj_set_scrollbar_mode(high_temp_popup_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_size(high_temp_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2); + lv_obj_align(high_temp_popup_, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_set_style_bg_color(high_temp_popup_, lv_palette_main(LV_PALETTE_RED), 0); + lv_obj_set_style_radius(high_temp_popup_, 10, 0); + + // 创建警告标签 + high_temp_label_ = lv_label_create(high_temp_popup_); + lv_label_set_text(high_temp_label_, "警告:温度过高"); + lv_obj_set_style_text_color(high_temp_label_, lv_color_white(), 0); + lv_obj_center(high_temp_label_); + + // 默认隐藏 + lv_obj_add_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN); + } + + void UpdateHighTempWarning(float chip_temp, float threshold = 75.0f) { + if (high_temp_popup_ == nullptr) { + ESP_LOGW("ZHENGCHEN_LcdDisplay", "High temp popup not initialized!"); + return; + } + + if (chip_temp >= threshold) { + ShowHighTempWarning(); + } else { + HideHighTempWarning(); + } + } + + void ShowHighTempWarning() { + if (high_temp_popup_ && lv_obj_has_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN)) { + lv_obj_remove_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN); + } + } + + void HideHighTempWarning() { + if (high_temp_popup_ && !lv_obj_has_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN)) { + lv_obj_add_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN); + } + } +}; + + + #endif // ZHENGCHEN_LCD_DISPLAY_H \ No newline at end of file diff --git a/main/device_manager.cc b/main/device_manager.cc new file mode 100644 index 0000000..55b44a5 --- /dev/null +++ b/main/device_manager.cc @@ -0,0 +1,451 @@ +#include "device_manager.h" +#include +#include +#include +#include +#include +#include "board.h" +#include "server_config.h" + +#define TAG "DeviceManager" + +DeviceManager& DeviceManager::GetInstance() { + static DeviceManager instance; + return instance; +} + +DeviceManager::DeviceManager() : is_bound_(false) { + // 初始化NVS(如果尚未初始化) + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + // 获取MAC地址 + uint8_t mac[6]; + esp_err_t err = esp_efuse_mac_get_default(mac); + if (err == ESP_OK) { + char mac_str[18]; + snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + mac_address_ = std::string(mac_str); + ESP_LOGI(TAG, "Device MAC Address: %s", mac_address_.c_str()); + } else { + ESP_LOGE(TAG, "Failed to get MAC address"); + } + + // 从NVS加载配置 + LoadFromNVS(); + + // 如果没有 token,尝试从服务器自动获取(可能已在网页端绑定) + if (device_token_.empty() && !mac_address_.empty()) { + ESP_LOGI(TAG, "No token found, trying to fetch from server..."); + TryFetchTokenFromServer(); + } +} + +DeviceManager::~DeviceManager() { +} + +std::string DeviceManager::GetMACAddress() { + return mac_address_; +} + +void DeviceManager::LoadFromNVS() { + nvs_handle_t nvs_handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle); + + if (err == ESP_OK) { + // 读取Token + size_t token_len = 0; + err = nvs_get_str(nvs_handle, NVS_KEY_TOKEN, nullptr, &token_len); + if (err == ESP_OK && token_len > 0) { + char* token_buf = new char[token_len]; + err = nvs_get_str(nvs_handle, NVS_KEY_TOKEN, token_buf, &token_len); + if (err == ESP_OK) { + device_token_ = std::string(token_buf); + is_bound_ = true; + ESP_LOGI(TAG, "Loaded device token from NVS (length: %d)", token_len - 1); + } + delete[] token_buf; + } + + // 读取用户名 + size_t username_len = 0; + err = nvs_get_str(nvs_handle, NVS_KEY_USERNAME, nullptr, &username_len); + if (err == ESP_OK && username_len > 0) { + char* username_buf = new char[username_len]; + err = nvs_get_str(nvs_handle, NVS_KEY_USERNAME, username_buf, &username_len); + if (err == ESP_OK) { + bound_username_ = std::string(username_buf); + ESP_LOGI(TAG, "Device bound to user: %s", bound_username_.c_str()); + } + delete[] username_buf; + } + + nvs_close(nvs_handle); + } else if (err == ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGI(TAG, "No device binding found in NVS"); + } else { + ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err)); + } +} + +bool DeviceManager::SaveDeviceToken(const std::string& token) { + nvs_handle_t nvs_handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to open NVS for writing: %s", esp_err_to_name(err)); + return false; + } + + err = nvs_set_str(nvs_handle, NVS_KEY_TOKEN, token.c_str()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to write token to NVS: %s", esp_err_to_name(err)); + nvs_close(nvs_handle); + return false; + } + + err = nvs_commit(nvs_handle); + nvs_close(nvs_handle); + + if (err == ESP_OK) { + device_token_ = token; + is_bound_ = true; + ESP_LOGI(TAG, "Device token saved to NVS"); + return true; + } else { + ESP_LOGE(TAG, "Failed to commit NVS: %s", esp_err_to_name(err)); + return false; + } +} + +std::string DeviceManager::GetDeviceToken() { + return device_token_; +} + +bool DeviceManager::ClearDeviceToken() { + nvs_handle_t nvs_handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to open NVS for erasing: %s", esp_err_to_name(err)); + return false; + } + + nvs_erase_key(nvs_handle, NVS_KEY_TOKEN); + nvs_erase_key(nvs_handle, NVS_KEY_USERNAME); + err = nvs_commit(nvs_handle); + nvs_close(nvs_handle); + + if (err == ESP_OK) { + device_token_.clear(); + bound_username_.clear(); + is_bound_ = false; + ESP_LOGI(TAG, "Device token cleared"); + return true; + } else { + ESP_LOGE(TAG, "Failed to clear device token: %s", esp_err_to_name(err)); + return false; + } +} + +bool DeviceManager::BindDevice(const std::string& binding_code, const std::string& device_name) { + ESP_LOGI(TAG, "Starting device binding with code: %s", binding_code.c_str()); + + if (mac_address_.empty()) { + ESP_LOGE(TAG, "MAC address not available"); + return false; + } + + // 构建请求JSON + cJSON* request = cJSON_CreateObject(); + cJSON_AddStringToObject(request, "mac", mac_address_.c_str()); + cJSON_AddStringToObject(request, "binding_code", binding_code.c_str()); + if (!device_name.empty()) { + cJSON_AddStringToObject(request, "device_name", device_name.c_str()); + } else { + cJSON_AddStringToObject(request, "device_name", "ESP32音乐播放器"); + } + + char* request_str = cJSON_PrintUnformatted(request); + std::string request_body(request_str); + cJSON_Delete(request); + cJSON_free(request_str); + + // 发送HTTP请求到服务器 + auto& board = Board::GetInstance(); + auto network = board.GetNetwork(); + auto http = network->CreateHttp(0); + + std::string bind_url = DEVICE_BIND_API_URL; + + http->SetHeader("Content-Type", "application/json"); + http->SetHeader("User-Agent", "ESP32-Music-Player/1.0"); + + ESP_LOGI(TAG, "Sending bind request to: %s", bind_url.c_str()); + ESP_LOGD(TAG, "Request body: %s", request_body.c_str()); + + if (!http->Open("POST", bind_url)) { + ESP_LOGE(TAG, "Failed to connect to bind API"); + return false; + } + + // 发送请求体 + http->Write(request_body.c_str(), request_body.length()); + + int status_code = http->GetStatusCode(); + ESP_LOGI(TAG, "Bind request status code: %d", status_code); + + if (status_code != 200) { + ESP_LOGE(TAG, "Bind request failed with status: %d", status_code); + http->Close(); + return false; + } + + // 读取响应 + std::string response = http->ReadAll(); + http->Close(); + + ESP_LOGD(TAG, "Bind response: %s", response.c_str()); + + // 解析响应JSON + cJSON* response_json = cJSON_Parse(response.c_str()); + if (!response_json) { + ESP_LOGE(TAG, "Failed to parse bind response"); + return false; + } + + cJSON* success = cJSON_GetObjectItem(response_json, "success"); + if (!success || !cJSON_IsBool(success) || !cJSON_IsTrue(success)) { + ESP_LOGE(TAG, "Bind request was not successful"); + cJSON_Delete(response_json); + return false; + } + + cJSON* token = cJSON_GetObjectItem(response_json, "token"); + cJSON* username = cJSON_GetObjectItem(response_json, "username"); + + if (!token || !cJSON_IsString(token)) { + ESP_LOGE(TAG, "No token in bind response"); + cJSON_Delete(response_json); + return false; + } + + std::string token_str = token->valuestring; + std::string username_str = username && cJSON_IsString(username) ? username->valuestring : ""; + + cJSON_Delete(response_json); + + // 保存Token到NVS + if (!SaveDeviceToken(token_str)) { + ESP_LOGE(TAG, "Failed to save token"); + return false; + } + + // 保存用户名到NVS + if (!username_str.empty()) { + nvs_handle_t nvs_handle; + if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle) == ESP_OK) { + nvs_set_str(nvs_handle, NVS_KEY_USERNAME, username_str.c_str()); + nvs_commit(nvs_handle); + nvs_close(nvs_handle); + bound_username_ = username_str; + } + } + + ESP_LOGI(TAG, "Device successfully bound to user: %s", username_str.c_str()); + + return true; +} + +bool DeviceManager::VerifyDevice() { + if (device_token_.empty()) { + ESP_LOGW(TAG, "No device token available for verification"); + return false; + } + + auto& board = Board::GetInstance(); + auto network = board.GetNetwork(); + auto http = network->CreateHttp(0); + + std::string verify_url = DEVICE_VERIFY_API_URL; + + http->SetHeader("X-Device-Token", device_token_.c_str()); + http->SetHeader("User-Agent", "ESP32-Music-Player/1.0"); + + if (!http->Open("GET", verify_url)) { + ESP_LOGE(TAG, "Failed to connect to verify API"); + return false; + } + + int status_code = http->GetStatusCode(); + http->Close(); + + if (status_code == 200) { + ESP_LOGI(TAG, "Device verification successful"); + return true; + } else { + ESP_LOGW(TAG, "Device verification failed with status: %d", status_code); + return false; + } +} + +std::string DeviceManager::GetFavorites() { + if (device_token_.empty()) { + return ""; + } + + auto& board = Board::GetInstance(); + auto network = board.GetNetwork(); + auto http = network->CreateHttp(0); + + std::string url = FAVORITE_LIST_API_URL; + + http->SetHeader("X-Device-Token", device_token_.c_str()); + http->SetHeader("User-Agent", "ESP32-Music-Player/1.0"); + + if (!http->Open("GET", url)) { + ESP_LOGE(TAG, "Failed to connect to favorites API"); + return ""; + } + + int status_code = http->GetStatusCode(); + std::string response = http->ReadAll(); + http->Close(); + + if (status_code == 200) { + return response; + } else { + ESP_LOGW(TAG, "GetFavorites failed with status: %d", status_code); + return ""; + } +} + +std::string DeviceManager::GetUserPlaylists() { + if (device_token_.empty()) { + return ""; + } + + auto& board = Board::GetInstance(); + auto network = board.GetNetwork(); + auto http = network->CreateHttp(0); + + std::string url = PLAYLIST_LIST_API_URL; + + http->SetHeader("X-Device-Token", device_token_.c_str()); + http->SetHeader("User-Agent", "ESP32-Music-Player/1.0"); + + if (!http->Open("GET", url)) { + ESP_LOGE(TAG, "Failed to connect to playlist API"); + return ""; + } + + int status_code = http->GetStatusCode(); + std::string response = http->ReadAll(); + http->Close(); + + if (status_code == 200) { + return response; + } else { + ESP_LOGW(TAG, "GetUserPlaylists failed with status: %d", status_code); + return ""; + } +} + +bool DeviceManager::IsDeviceBound() { + return is_bound_ && !device_token_.empty(); +} + +std::string DeviceManager::GetBoundUsername() { + return bound_username_; +} + +bool DeviceManager::TryFetchTokenFromServer() { + ESP_LOGI(TAG, "Attempting to fetch token from server using MAC: %s", mac_address_.c_str()); + + auto& board = Board::GetInstance(); + auto network = board.GetNetwork(); + auto http = network->CreateHttp(0); + + // 构建请求 JSON + cJSON* request = cJSON_CreateObject(); + cJSON_AddStringToObject(request, "mac", mac_address_.c_str()); + char* request_str = cJSON_PrintUnformatted(request); + std::string request_body(request_str); + cJSON_Delete(request); + cJSON_free(request_str); + + std::string sync_url = MUSIC_SERVER_URL; + sync_url += "/api/esp32/sync"; + + http->SetHeader("Content-Type", "application/json"); + http->SetHeader("User-Agent", "ESP32-Music-Player/1.0"); + + if (!http->Open("POST", sync_url)) { + ESP_LOGW(TAG, "Failed to connect to sync API"); + return false; + } + + http->Write(request_body.c_str(), request_body.length()); + + int status_code = http->GetStatusCode(); + std::string response = http->ReadAll(); + http->Close(); + + if (status_code == 404) { + ESP_LOGI(TAG, "Device not bound on server yet"); + return false; + } + + if (status_code != 200) { + ESP_LOGW(TAG, "Token sync failed with status: %d", status_code); + return false; + } + + // 解析响应 + cJSON* response_json = cJSON_Parse(response.c_str()); + if (!response_json) { + ESP_LOGE(TAG, "Failed to parse sync response"); + return false; + } + + cJSON* token = cJSON_GetObjectItem(response_json, "token"); + cJSON* username = cJSON_GetObjectItem(response_json, "username"); + + if (!token || !cJSON_IsString(token)) { + ESP_LOGE(TAG, "Invalid sync response: missing token"); + cJSON_Delete(response_json); + return false; + } + + std::string token_str = token->valuestring; + std::string username_str = username && cJSON_IsString(username) ? username->valuestring : ""; + + cJSON_Delete(response_json); + + // 保存 Token 到 NVS + if (!SaveDeviceToken(token_str)) { + ESP_LOGE(TAG, "Failed to save synced token"); + return false; + } + + // 保存用户名到 NVS + if (!username_str.empty()) { + nvs_handle_t nvs_handle; + if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle) == ESP_OK) { + nvs_set_str(nvs_handle, NVS_KEY_USERNAME, username_str.c_str()); + nvs_commit(nvs_handle); + nvs_close(nvs_handle); + bound_username_ = username_str; + is_bound_ = true; + } + } + + ESP_LOGI(TAG, "✅ Token synced successfully for user: %s", username_str.c_str()); + + return true; +} diff --git a/main/device_manager.h b/main/device_manager.h new file mode 100644 index 0000000..4dd0dbd --- /dev/null +++ b/main/device_manager.h @@ -0,0 +1,63 @@ +#ifndef DEVICE_MANAGER_H +#define DEVICE_MANAGER_H + +#include + +class DeviceManager { +public: + static DeviceManager& GetInstance(); + + // 获取设备MAC地址 + std::string GetMACAddress(); + + // 保存设备Token到NVS + bool SaveDeviceToken(const std::string& token); + + // 从NVS读取设备Token + std::string GetDeviceToken(); + + // 清除设备Token(解绑) + bool ClearDeviceToken(); + + // 设备绑定流程 + bool BindDevice(const std::string& binding_code, const std::string& device_name = ""); + + // 验证设备状态 + bool VerifyDevice(); + + // 歌单相关 + std::string GetFavorites(); + std::string GetUserPlaylists(); + + // 检查设备是否已绑定 + bool IsDeviceBound(); + + // 获取绑定的用户名 + std::string GetBoundUsername(); + +private: + DeviceManager(); + ~DeviceManager(); + + // 禁止拷贝 + DeviceManager(const DeviceManager&) = delete; + DeviceManager& operator=(const DeviceManager&) = delete; + + std::string mac_address_; + std::string device_token_; + std::string bound_username_; + bool is_bound_; + + // NVS命名空间 + static constexpr const char* NVS_NAMESPACE = "device"; + static constexpr const char* NVS_KEY_TOKEN = "token"; + static constexpr const char* NVS_KEY_USERNAME = "username"; + + // 从NVS加载配置 + void LoadFromNVS(); + + // 尝试从服务器获取 token(用于网页端绑定后自动同步) + bool TryFetchTokenFromServer(); +}; + +#endif // DEVICE_MANAGER_H diff --git a/main/device_state.h b/main/device_state.h index 4ffafae..df8a543 100644 --- a/main/device_state.h +++ b/main/device_state.h @@ -1,18 +1,18 @@ -#ifndef _DEVICE_STATE_H_ -#define _DEVICE_STATE_H_ - -enum DeviceState { - kDeviceStateUnknown, - kDeviceStateStarting, - kDeviceStateWifiConfiguring, - kDeviceStateIdle, - kDeviceStateConnecting, - kDeviceStateListening, - kDeviceStateSpeaking, - kDeviceStateUpgrading, - kDeviceStateActivating, - kDeviceStateAudioTesting, - kDeviceStateFatalError -}; - +#ifndef _DEVICE_STATE_H_ +#define _DEVICE_STATE_H_ + +enum DeviceState { + kDeviceStateUnknown, + kDeviceStateStarting, + kDeviceStateWifiConfiguring, + kDeviceStateIdle, + kDeviceStateConnecting, + kDeviceStateListening, + kDeviceStateSpeaking, + kDeviceStateUpgrading, + kDeviceStateActivating, + kDeviceStateAudioTesting, + kDeviceStateFatalError +}; + #endif // _DEVICE_STATE_H_ \ No newline at end of file diff --git a/main/device_state_event.cc b/main/device_state_event.cc index 81e2be2..eec0990 100644 --- a/main/device_state_event.cc +++ b/main/device_state_event.cc @@ -1,46 +1,46 @@ -#include "device_state_event.h" - -ESP_EVENT_DEFINE_BASE(XIAOZHI_STATE_EVENTS); - -DeviceStateEventManager& DeviceStateEventManager::GetInstance() { - static DeviceStateEventManager instance; - return instance; -} - -void DeviceStateEventManager::RegisterStateChangeCallback(std::function callback) { - std::lock_guard lock(mutex_); - callbacks_.push_back(callback); -} - -void DeviceStateEventManager::PostStateChangeEvent(DeviceState previous_state, DeviceState current_state) { - device_state_event_data_t event_data = { - .previous_state = previous_state, - .current_state = current_state - }; - esp_event_post(XIAOZHI_STATE_EVENTS, XIAOZHI_STATE_CHANGED_EVENT, &event_data, sizeof(event_data), portMAX_DELAY); -} - -std::vector> DeviceStateEventManager::GetCallbacks() { - std::lock_guard lock(mutex_); - return callbacks_; -} - -DeviceStateEventManager::DeviceStateEventManager() { - esp_err_t err = esp_event_loop_create_default(); - if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { - ESP_ERROR_CHECK(err); - } - - ESP_ERROR_CHECK(esp_event_handler_register(XIAOZHI_STATE_EVENTS, XIAOZHI_STATE_CHANGED_EVENT, - [](void* handler_args, esp_event_base_t base, int32_t id, void* event_data) { - auto* data = static_cast(event_data); - auto& manager = DeviceStateEventManager::GetInstance(); - for (const auto& callback : manager.GetCallbacks()) { - callback(data->previous_state, data->current_state); - } - }, nullptr)); -} - -DeviceStateEventManager::~DeviceStateEventManager() { - esp_event_handler_unregister(XIAOZHI_STATE_EVENTS, XIAOZHI_STATE_CHANGED_EVENT, nullptr); +#include "device_state_event.h" + +ESP_EVENT_DEFINE_BASE(XIAOZHI_STATE_EVENTS); + +DeviceStateEventManager& DeviceStateEventManager::GetInstance() { + static DeviceStateEventManager instance; + return instance; +} + +void DeviceStateEventManager::RegisterStateChangeCallback(std::function callback) { + std::lock_guard lock(mutex_); + callbacks_.push_back(callback); +} + +void DeviceStateEventManager::PostStateChangeEvent(DeviceState previous_state, DeviceState current_state) { + device_state_event_data_t event_data = { + .previous_state = previous_state, + .current_state = current_state + }; + esp_event_post(XIAOZHI_STATE_EVENTS, XIAOZHI_STATE_CHANGED_EVENT, &event_data, sizeof(event_data), portMAX_DELAY); +} + +std::vector> DeviceStateEventManager::GetCallbacks() { + std::lock_guard lock(mutex_); + return callbacks_; +} + +DeviceStateEventManager::DeviceStateEventManager() { + esp_err_t err = esp_event_loop_create_default(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_ERROR_CHECK(err); + } + + ESP_ERROR_CHECK(esp_event_handler_register(XIAOZHI_STATE_EVENTS, XIAOZHI_STATE_CHANGED_EVENT, + [](void* handler_args, esp_event_base_t base, int32_t id, void* event_data) { + auto* data = static_cast(event_data); + auto& manager = DeviceStateEventManager::GetInstance(); + for (const auto& callback : manager.GetCallbacks()) { + callback(data->previous_state, data->current_state); + } + }, nullptr)); +} + +DeviceStateEventManager::~DeviceStateEventManager() { + esp_event_handler_unregister(XIAOZHI_STATE_EVENTS, XIAOZHI_STATE_CHANGED_EVENT, nullptr); } \ No newline at end of file diff --git a/main/device_state_event.h b/main/device_state_event.h index 03e2713..eb6e9d2 100644 --- a/main/device_state_event.h +++ b/main/device_state_event.h @@ -1,39 +1,39 @@ -#ifndef _DEVICE_STATE_EVENT_H_ -#define _DEVICE_STATE_EVENT_H_ - -#include -#include -#include -#include -#include "device_state.h" - -ESP_EVENT_DECLARE_BASE(XIAOZHI_STATE_EVENTS); - -enum { - XIAOZHI_STATE_CHANGED_EVENT, -}; - -struct device_state_event_data_t { - DeviceState previous_state; - DeviceState current_state; -}; - -class DeviceStateEventManager { -public: - static DeviceStateEventManager& GetInstance(); - DeviceStateEventManager(const DeviceStateEventManager&) = delete; - DeviceStateEventManager& operator=(const DeviceStateEventManager&) = delete; - - void RegisterStateChangeCallback(std::function callback); - void PostStateChangeEvent(DeviceState previous_state, DeviceState current_state); - std::vector> GetCallbacks(); - -private: - DeviceStateEventManager(); - ~DeviceStateEventManager(); - - std::vector> callbacks_; - std::mutex mutex_; -}; - +#ifndef _DEVICE_STATE_EVENT_H_ +#define _DEVICE_STATE_EVENT_H_ + +#include +#include +#include +#include +#include "device_state.h" + +ESP_EVENT_DECLARE_BASE(XIAOZHI_STATE_EVENTS); + +enum { + XIAOZHI_STATE_CHANGED_EVENT, +}; + +struct device_state_event_data_t { + DeviceState previous_state; + DeviceState current_state; +}; + +class DeviceStateEventManager { +public: + static DeviceStateEventManager& GetInstance(); + DeviceStateEventManager(const DeviceStateEventManager&) = delete; + DeviceStateEventManager& operator=(const DeviceStateEventManager&) = delete; + + void RegisterStateChangeCallback(std::function callback); + void PostStateChangeEvent(DeviceState previous_state, DeviceState current_state); + std::vector> GetCallbacks(); + +private: + DeviceStateEventManager(); + ~DeviceStateEventManager(); + + std::vector> callbacks_; + std::mutex mutex_; +}; + #endif // _DEVICE_STATE_EVENT_H_ \ No newline at end of file diff --git a/main/display/display.cc b/main/display/display.cc index 9090ba4..818248c 100644 --- a/main/display/display.cc +++ b/main/display/display.cc @@ -1,58 +1,100 @@ -#include -#include -#include -#include -#include -#include - -#include "display.h" -#include "board.h" -#include "application.h" -#include "audio_codec.h" -#include "settings.h" -#include "assets/lang_config.h" - -#define TAG "Display" - -Display::Display() { -} - -Display::~Display() { -} - -void Display::SetStatus(const char* status) { - ESP_LOGW(TAG, "SetStatus: %s", status); -} - -void Display::ShowNotification(const std::string ¬ification, int duration_ms) { - ShowNotification(notification.c_str(), duration_ms); -} - -void Display::ShowNotification(const char* notification, int duration_ms) { - ESP_LOGW(TAG, "ShowNotification: %s", notification); -} - -void Display::UpdateStatusBar(bool update_all) { -} - - -void Display::SetEmotion(const char* emotion) { - ESP_LOGW(TAG, "SetEmotion: %s", emotion); -} - -void Display::SetChatMessage(const char* role, const char* content) { - ESP_LOGW(TAG, "Role:%s", role); - ESP_LOGW(TAG, " %s", content); -} - -void Display::SetTheme(Theme* theme) { - current_theme_ = theme; - if (theme != nullptr) { - Settings settings("display", true); - settings.SetString("theme", theme->name()); - } -} - -void Display::SetPowerSaveMode(bool on) { - ESP_LOGW(TAG, "SetPowerSaveMode: %d", on); -} +#include +#include +#include +#include +#include +#include + +#include "display.h" +#include "board.h" +#include "application.h" +#include "audio_codec.h" +#include "settings.h" +#include "assets/lang_config.h" + +#define TAG "Display" + +Display::Display() { +} + +Display::~Display() { +} + +void Display::SetStatus(const char* status) { + ESP_LOGW(TAG, "SetStatus: %s", status); +} + +void Display::ShowNotification(const std::string ¬ification, int duration_ms) { + ShowNotification(notification.c_str(), duration_ms); +} + +void Display::ShowNotification(const char* notification, int duration_ms) { + ESP_LOGW(TAG, "ShowNotification: %s", notification); +} + +void Display::UpdateStatusBar(bool update_all) { +} + + +void Display::SetEmotion(const char* emotion) { + ESP_LOGW(TAG, "SetEmotion: %s", emotion); +} + +void Display::SetChatMessage(const char* role, const char* content) { + ESP_LOGW(TAG, "Role:%s", role); + ESP_LOGW(TAG, " %s", content); +} + +void Display::SetTheme(Theme* theme) { + current_theme_ = theme; + Settings settings("display", true); + settings.SetString("theme", theme->name()); +} + +void Display::SetPowerSaveMode(bool on) { + ESP_LOGW(TAG, "SetPowerSaveMode: %d", on); +} + +void Display::SetMusicInfo(const char* song_name) { + // 默认实现:对于非微信模式,将歌名显示在聊天消息标签中 + DisplayLockGuard lock(this); + if (chat_message_label_ == nullptr) { + return; + } + if (song_name != nullptr && strlen(song_name) > 0) { + std::string music_text = ""; + music_text += song_name; + lv_label_set_text(chat_message_label_, music_text.c_str()); + SetEmotion(FONT_AWESOME_MUSIC); + } else { + lv_label_set_text(chat_message_label_, ""); + SetEmotion(FONT_AWESOME_NEUTRAL); + } +} + +void Display::SetMusicProgress(const char* song_name, int current_seconds, int total_seconds, float progress_percent) { + // 默认实现:简化显示,只显示歌名和时间 + char time_info[64]; + int current_min = current_seconds / 60; + int current_sec = current_seconds % 60; + int total_min = total_seconds / 60; + int total_sec = total_seconds % 60; + + snprintf(time_info, sizeof(time_info), "%s %02d:%02d / %02d:%02d", + song_name ? song_name : "Unknown", + current_min, current_sec, total_min, total_sec); + + DisplayLockGuard lock(this); + if (chat_message_label_ != nullptr) { + lv_label_set_text(chat_message_label_, time_info); + SetEmotion(FONT_AWESOME_MUSIC); + } +} + +void Display::ClearMusicInfo() { + DisplayLockGuard lock(this); + if (chat_message_label_ != nullptr) { + lv_label_set_text(chat_message_label_, ""); + SetEmotion(FONT_AWESOME_NEUTRAL); + } +} \ No newline at end of file diff --git a/main/display/display.h b/main/display/display.h index 45de9c5..8e5f4d6 100644 --- a/main/display/display.h +++ b/main/display/display.h @@ -1,86 +1,98 @@ -#ifndef DISPLAY_H -#define DISPLAY_H - -#include "emoji_collection.h" - -#ifdef LVGL_VERSION_MAJOR -#define HAVE_LVGL 1 -#include -#endif - -#include -#include -#include - -#include -#include - -class Theme { -public: - Theme(const std::string& name) : name_(name) {} - virtual ~Theme() = default; - - inline std::string name() const { return name_; } -private: - std::string name_; -}; - -class Display { -public: - Display(); - virtual ~Display(); - - virtual void SetStatus(const char* status); - virtual void ShowNotification(const char* notification, int duration_ms = 3000); - virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000); - virtual void SetEmotion(const char* emotion); - virtual void SetChatMessage(const char* role, const char* content); - virtual void SetTheme(Theme* theme); - virtual Theme* GetTheme() { return current_theme_; } - virtual void UpdateStatusBar(bool update_all = false); - virtual void SetPowerSaveMode(bool on); - - // 音乐播放相关方法 - virtual void SetMusicInfo(const char* info) {} - virtual void start() {} - virtual void stopFft() {} - - inline int width() const { return width_; } - inline int height() const { return height_; } - -protected: - int width_ = 0; - int height_ = 0; - - Theme* current_theme_ = nullptr; - - friend class DisplayLockGuard; - virtual bool Lock(int timeout_ms = 0) = 0; - virtual void Unlock() = 0; -}; - - -class DisplayLockGuard { -public: - DisplayLockGuard(Display *display) : display_(display) { - if (!display_->Lock(30000)) { - ESP_LOGE("Display", "Failed to lock display"); - } - } - ~DisplayLockGuard() { - display_->Unlock(); - } - -private: - Display *display_; -}; - -class NoDisplay : public Display { -private: - virtual bool Lock(int timeout_ms = 0) override { - return true; - } - virtual void Unlock() override {} -}; - -#endif +#ifndef DISPLAY_H +#define DISPLAY_H + +#include "emoji_collection.h" + +#ifndef CONFIG_USE_EMOTE_MESSAGE_STYLE +#define HAVE_LVGL 1 +#include +#endif + +#include +#include +#include + +#include +#include + +class Theme { +public: + Theme(const std::string& name) : name_(name) {} + virtual ~Theme() = default; + + inline std::string name() const { return name_; } +private: + std::string name_; +}; + +class Display { +public: + Display(); + virtual ~Display(); + + virtual void SetStatus(const char* status); + virtual void ShowNotification(const char* notification, int duration_ms = 3000); + virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000); + virtual void SetEmotion(const char* emotion); + virtual void SetChatMessage(const char* role, const char* content); + virtual void SetTheme(Theme* theme); + virtual Theme* GetTheme() { return current_theme_; } + virtual void UpdateStatusBar(bool update_all = false); + virtual void SetPowerSaveMode(bool on); + virtual void SetMusicInfo(const char* song_name); + virtual void SetMusicProgress(const char* song_name, int current_seconds, int total_seconds, float progress_percent); + virtual void ClearMusicInfo(); + + // Alarm display on idle screen (for boards with idle screen support) + virtual void ShowAlarmOnIdleScreen(const char* alarm_message) {} // Default: do nothing + virtual void HideAlarmOnIdleScreen() {} // Default: do nothing + + // State change and clock timer callbacks (for boards with idle screen support) + virtual void OnStateChanged() {} // Default: do nothing + virtual void OnClockTimer() {} // Default: do nothing, called every second + + virtual void start() {} + virtual void clearScreen() {} // 清除FFT显示,默认为空实现 + virtual void stopFft() {} // 停止FFT显示,默认为空实现 + + inline int width() const { return width_; } + inline int height() const { return height_; } + +protected: + int width_ = 0; + int height_ = 0; + + Theme* current_theme_ = nullptr; + + friend class DisplayLockGuard; + virtual bool Lock(int timeout_ms = 0) = 0; + virtual void Unlock() = 0; + lv_obj_t* chat_message_label_ = nullptr; + lv_obj_t *emotion_label_ = nullptr; +}; + + +class DisplayLockGuard { +public: + DisplayLockGuard(Display *display) : display_(display) { + if (!display_->Lock(30000)) { + ESP_LOGE("Display", "Failed to lock display"); + } + } + ~DisplayLockGuard() { + display_->Unlock(); + } + +private: + Display *display_; +}; + +class NoDisplay : public Display { +private: + virtual bool Lock(int timeout_ms = 0) override { + return true; + } + virtual void Unlock() override {} +}; + +#endif diff --git a/main/display/emote_display.cc b/main/display/emote_display.cc new file mode 100644 index 0000000..dca34d1 --- /dev/null +++ b/main/display/emote_display.cc @@ -0,0 +1,655 @@ +#include "emote_display.h" + +// Standard C++ headers +#include +#include +#include +#include + +// Standard C headers +#include +#include + +// ESP-IDF headers +#include +#include +#include + +// FreeRTOS headers +#include +#include + +// Project headers +#include "assets.h" +#include "assets/lang_config.h" +#include "board.h" +#include "gfx.h" + +LV_FONT_DECLARE(BUILTIN_TEXT_FONT); + +namespace emote { + +// ============================================================================ +// Constants and Type Definitions +// ============================================================================ + +static const char* TAG = "EmoteDisplay"; + +// UI Element Names - Centralized Management +#define UI_ELEMENT_EYE_ANIM "eye_anim" +#define UI_ELEMENT_TOAST_LABEL "toast_label" +#define UI_ELEMENT_CLOCK_LABEL "clock_label" +#define UI_ELEMENT_LISTEN_ANIM "listen_anim" +#define UI_ELEMENT_STATUS_ICON "status_icon" + +// Icon Names - Centralized Management +#define ICON_MIC "icon_mic" +#define ICON_BATTERY "icon_Battery" +#define ICON_SPEAKER_ZZZ "icon_speaker_zzz" +#define ICON_WIFI_FAILED "icon_WiFi_failed" +#define ICON_WIFI_OK "icon_wifi" +#define ICON_LISTEN "listen" + +using FlushIoReadyCallback = std::function; +using FlushCallback = std::function; + +// ============================================================================ +// Global Variables +// ============================================================================ + +// UI element management +static gfx_obj_t* g_obj_label_toast = nullptr; +static gfx_obj_t* g_obj_label_clock = nullptr; +static gfx_obj_t* g_obj_anim_eye = nullptr; +static gfx_obj_t* g_obj_anim_listen = nullptr; +static gfx_obj_t* g_obj_img_status = nullptr; + +// Track current icon to determine when to show time +static std::string g_current_icon_type = ICON_WIFI_FAILED; +static gfx_image_dsc_t g_icon_img_dsc; + + +// ============================================================================ +// Forward Declarations +// ============================================================================ + +class EmoteDisplay; +class EmoteEngine; + +enum class UIDisplayMode : uint8_t { + SHOW_LISTENING = 1, // Show g_obj_anim_listen + SHOW_TIME = 2, // Show g_obj_label_clock + SHOW_TIPS = 3 // Show g_obj_label_toast +}; + +// ============================================================================ +// Helper Functions +// ============================================================================ + +// Function to convert align string to GFX_ALIGN enum value +char StringToGfxAlign(const std::string &align_str) +{ + static const std::unordered_map align_map = { + {"GFX_ALIGN_DEFAULT", GFX_ALIGN_DEFAULT}, + {"GFX_ALIGN_TOP_LEFT", GFX_ALIGN_TOP_LEFT}, + {"GFX_ALIGN_TOP_MID", GFX_ALIGN_TOP_MID}, + {"GFX_ALIGN_TOP_RIGHT", GFX_ALIGN_TOP_RIGHT}, + {"GFX_ALIGN_LEFT_MID", GFX_ALIGN_LEFT_MID}, + {"GFX_ALIGN_CENTER", GFX_ALIGN_CENTER}, + {"GFX_ALIGN_RIGHT_MID", GFX_ALIGN_RIGHT_MID}, + {"GFX_ALIGN_BOTTOM_LEFT", GFX_ALIGN_BOTTOM_LEFT}, + {"GFX_ALIGN_BOTTOM_MID", GFX_ALIGN_BOTTOM_MID}, + {"GFX_ALIGN_BOTTOM_RIGHT", GFX_ALIGN_BOTTOM_RIGHT}, + {"GFX_ALIGN_OUT_TOP_LEFT", GFX_ALIGN_OUT_TOP_LEFT}, + {"GFX_ALIGN_OUT_TOP_MID", GFX_ALIGN_OUT_TOP_MID}, + {"GFX_ALIGN_OUT_TOP_RIGHT", GFX_ALIGN_OUT_TOP_RIGHT}, + {"GFX_ALIGN_OUT_LEFT_TOP", GFX_ALIGN_OUT_LEFT_TOP}, + {"GFX_ALIGN_OUT_LEFT_MID", GFX_ALIGN_OUT_LEFT_MID}, + {"GFX_ALIGN_OUT_LEFT_BOTTOM", GFX_ALIGN_OUT_LEFT_BOTTOM}, + {"GFX_ALIGN_OUT_RIGHT_TOP", GFX_ALIGN_OUT_RIGHT_TOP}, + {"GFX_ALIGN_OUT_RIGHT_MID", GFX_ALIGN_OUT_RIGHT_MID}, + {"GFX_ALIGN_OUT_RIGHT_BOTTOM", GFX_ALIGN_OUT_RIGHT_BOTTOM}, + {"GFX_ALIGN_OUT_BOTTOM_LEFT", GFX_ALIGN_OUT_BOTTOM_LEFT}, + {"GFX_ALIGN_OUT_BOTTOM_MID", GFX_ALIGN_OUT_BOTTOM_MID}, + {"GFX_ALIGN_OUT_BOTTOM_RIGHT", GFX_ALIGN_OUT_BOTTOM_RIGHT} + }; + + const auto it = align_map.find(align_str); + if (it != align_map.cend()) { + return it->second; + } + + ESP_LOGW(TAG, "Unknown align string: %s, using GFX_ALIGN_DEFAULT", align_str.c_str()); + return GFX_ALIGN_DEFAULT; +} + +// ============================================================================ +// EmoteEngine Class Declaration +// ============================================================================ + +class EmoteEngine { +public: + EmoteEngine(const esp_lcd_panel_handle_t panel, const esp_lcd_panel_io_handle_t panel_io, + const int width, const int height, EmoteDisplay* const display); + ~EmoteEngine(); + + void SetEyes(const std::string &emoji_name, const bool repeat, const int fps, EmoteDisplay* const display); + void SetIcon(const std::string &icon_name, EmoteDisplay* const display); + + void* GetEngineHandle() const + { + return engine_handle_; + } + + // Callback functions (public to be accessible from static helper functions) + static bool OnFlushIoReady(const esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t* const edata, void* const user_ctx); + static void OnFlush(const gfx_handle_t handle, const int x_start, const int y_start, const int x_end, const int y_end, const void* const color_data); + +private: + gfx_handle_t engine_handle_; +}; + +// ============================================================================ +// UI Management Functions +// ============================================================================ + +static void SetUIDisplayMode(const UIDisplayMode mode, EmoteDisplay* const display) +{ + if (!display) { + ESP_LOGE(TAG, "SetUIDisplayMode: display is nullptr"); + return; + } + + gfx_obj_set_visible(g_obj_anim_listen, false); + gfx_obj_set_visible(g_obj_label_clock, false); + gfx_obj_set_visible(g_obj_label_toast, false); + + // Show the selected control + switch (mode) { + case UIDisplayMode::SHOW_LISTENING: { + gfx_obj_set_visible(g_obj_anim_listen, true); + const AssetData emoji_data = display->GetIconData(ICON_LISTEN); + if (emoji_data.data) { + gfx_anim_set_src(g_obj_anim_listen, emoji_data.data, emoji_data.size); + gfx_anim_set_segment(g_obj_anim_listen, 0, 0xFFFF, 20, true); + gfx_anim_start(g_obj_anim_listen); + } + break; + } + case UIDisplayMode::SHOW_TIME: + gfx_obj_set_visible(g_obj_label_clock, true); + break; + case UIDisplayMode::SHOW_TIPS: + gfx_obj_set_visible(g_obj_label_toast, true); + break; + } +} + +// ============================================================================ +// Graphics Initialization Functions +// ============================================================================ + +static void InitializeGraphics(const esp_lcd_panel_handle_t panel, gfx_handle_t* const engine_handle, + const int width, const int height) +{ + if (!panel || !engine_handle) { + ESP_LOGE(TAG, "InitializeGraphics: Invalid parameters"); + return; + } + + gfx_core_config_t gfx_cfg = { + .flush_cb = EmoteEngine::OnFlush, + .user_data = panel, + .flags = { + .swap = true, + .double_buffer = true, + .buff_dma = true, + }, + .h_res = static_cast(width), + .v_res = static_cast(height), + .fps = 30, + .buffers = { + .buf1 = nullptr, + .buf2 = nullptr, + .buf_pixels = static_cast(width * 16), + }, + .task = GFX_EMOTE_INIT_CONFIG() + }; + + gfx_cfg.task.task_stack_caps = MALLOC_CAP_DEFAULT; + gfx_cfg.task.task_affinity = 0; + gfx_cfg.task.task_priority = 5; + gfx_cfg.task.task_stack = 8 * 1024; + + *engine_handle = gfx_emote_init(&gfx_cfg); +} + +static void SetupUI(const gfx_handle_t engine_handle, EmoteDisplay* const display) +{ + if (!display) { + ESP_LOGE(TAG, "SetupUI: display is nullptr"); + return; + } + + gfx_emote_set_bg_color(engine_handle, GFX_COLOR_HEX(0x000000)); + + g_obj_anim_eye = gfx_anim_create(engine_handle); + gfx_obj_align(g_obj_anim_eye, GFX_ALIGN_LEFT_MID, 10, 30); + gfx_anim_set_auto_mirror(g_obj_anim_eye, true); + gfx_obj_set_visible(g_obj_anim_eye, false); + + g_obj_label_toast = gfx_label_create(engine_handle); + gfx_obj_align(g_obj_label_toast, GFX_ALIGN_TOP_MID, 0, 20); + gfx_obj_set_size(g_obj_label_toast, 200, 40); + gfx_label_set_text(g_obj_label_toast, Lang::Strings::INITIALIZING); + gfx_label_set_color(g_obj_label_toast, GFX_COLOR_HEX(0xFFFFFF)); + gfx_label_set_text_align(g_obj_label_toast, GFX_TEXT_ALIGN_CENTER); + gfx_label_set_long_mode(g_obj_label_toast, GFX_LABEL_LONG_SCROLL); + gfx_label_set_scroll_speed(g_obj_label_toast, 20); + gfx_label_set_scroll_loop(g_obj_label_toast, true); + gfx_label_set_font(g_obj_label_toast, (gfx_font_t)&BUILTIN_TEXT_FONT); + + g_obj_label_clock = gfx_label_create(engine_handle); + gfx_obj_align(g_obj_label_clock, GFX_ALIGN_TOP_MID, 0, 15); + gfx_obj_set_size(g_obj_label_clock, 200, 50); + gfx_label_set_text(g_obj_label_clock, "--:--"); + gfx_label_set_color(g_obj_label_clock, GFX_COLOR_HEX(0xFFFFFF)); + gfx_label_set_text_align(g_obj_label_clock, GFX_TEXT_ALIGN_CENTER); + gfx_label_set_font(g_obj_label_clock, (gfx_font_t)&BUILTIN_TEXT_FONT); + + g_obj_anim_listen = gfx_anim_create(engine_handle); + gfx_obj_align(g_obj_anim_listen, GFX_ALIGN_TOP_MID, 0, 5); + gfx_anim_start(g_obj_anim_listen); + gfx_obj_set_visible(g_obj_anim_listen, false); + + g_obj_img_status = gfx_img_create(engine_handle); + gfx_obj_align(g_obj_img_status, GFX_ALIGN_TOP_MID, -120, 18); + + SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, display); +} + +static void RegisterCallbacks(const esp_lcd_panel_io_handle_t panel_io, const gfx_handle_t engine_handle) +{ + if (!panel_io) { + ESP_LOGE(TAG, "RegisterCallbacks: panel_io is nullptr"); + return; + } + + const esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = EmoteEngine::OnFlushIoReady, + }; + esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, engine_handle); +} + +// ============================================================================ +// EmoteEngine Class Implementation +// ============================================================================ + +EmoteEngine::EmoteEngine(const esp_lcd_panel_handle_t panel, const esp_lcd_panel_io_handle_t panel_io, + const int width, const int height, EmoteDisplay* const display) +{ + InitializeGraphics(panel, &engine_handle_, width, height); + + if (display) { + gfx_emote_lock(engine_handle_); + SetupUI(engine_handle_, display); + gfx_emote_unlock(engine_handle_); + } + + RegisterCallbacks(panel_io, engine_handle_); +} + +EmoteEngine::~EmoteEngine() +{ + if (engine_handle_) { + gfx_emote_deinit(engine_handle_); + engine_handle_ = nullptr; + } +} + +void EmoteEngine::SetEyes(const std::string &emoji_name, const bool repeat, const int fps, EmoteDisplay* const display) +{ + if (!engine_handle_) { + ESP_LOGE(TAG, "SetEyes: engine_handle_ is nullptr"); + return; + } + + if (!display) { + ESP_LOGE(TAG, "SetEyes: display is nullptr"); + return; + } + + const AssetData emoji_data = display->GetEmojiData(emoji_name); + if (emoji_data.data) { + DisplayLockGuard lock(display); + gfx_anim_set_src(g_obj_anim_eye, emoji_data.data, emoji_data.size); + gfx_anim_set_segment(g_obj_anim_eye, 0, 0xFFFF, fps, repeat); + gfx_obj_set_visible(g_obj_anim_eye, true); + gfx_anim_start(g_obj_anim_eye); + } else { + ESP_LOGW(TAG, "SetEyes: No emoji data found for %s", emoji_name.c_str()); + } +} + +void EmoteEngine::SetIcon(const std::string &icon_name, EmoteDisplay* const display) +{ + if (!engine_handle_) { + ESP_LOGE(TAG, "SetIcon: engine_handle_ is nullptr"); + return; + } + + if (!display) { + ESP_LOGE(TAG, "SetIcon: display is nullptr"); + return; + } + + const AssetData icon_data = display->GetIconData(icon_name); + if (icon_data.data) { + DisplayLockGuard lock(display); + + std::memcpy(&g_icon_img_dsc.header, icon_data.data, sizeof(gfx_image_header_t)); + g_icon_img_dsc.data = static_cast(icon_data.data) + sizeof(gfx_image_header_t); + g_icon_img_dsc.data_size = icon_data.size - sizeof(gfx_image_header_t); + + gfx_img_set_src(g_obj_img_status, &g_icon_img_dsc); + } else { + ESP_LOGW(TAG, "SetIcon: No icon data found for %s", icon_name.c_str()); + } + g_current_icon_type = icon_name; +} + +bool EmoteEngine::OnFlushIoReady(const esp_lcd_panel_io_handle_t panel_io, + esp_lcd_panel_io_event_data_t* const edata, + void* const user_ctx) +{ + return true; +} + +void EmoteEngine::OnFlush(const gfx_handle_t handle, const int x_start, const int y_start, + const int x_end, const int y_end, const void* const color_data) +{ + auto* const panel = static_cast(gfx_emote_get_user_data(handle)); + if (panel) { + esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data); + } + gfx_emote_flush_ready(handle, true); +} + +// ============================================================================ +// EmoteDisplay Class Implementation +// ============================================================================ + +EmoteDisplay::EmoteDisplay(const esp_lcd_panel_handle_t panel, const esp_lcd_panel_io_handle_t panel_io, + const int width, const int height) +{ + InitializeEngine(panel, panel_io, width, height); +} + +EmoteDisplay::~EmoteDisplay() = default; + +void EmoteDisplay::SetEmotion(const char* const emotion) +{ + if (!emotion) { + ESP_LOGE(TAG, "SetEmotion: emotion is nullptr"); + return; + } + + ESP_LOGI(TAG, "SetEmotion: %s", emotion); + if (!engine_) { + return; + } + + const AssetData emoji_data = GetEmojiData(emotion); + bool repeat = emoji_data.loop; + int fps = emoji_data.fps > 0 ? emoji_data.fps : 20; + + if (std::strcmp(emotion, "idle") == 0 || std::strcmp(emotion, "neutral") == 0) { + repeat = false; + } + + DisplayLockGuard lock(this); + engine_->SetEyes(emotion, repeat, fps, this); +} + +void EmoteDisplay::SetChatMessage(const char* const role, const char* const content) +{ + if (!engine_) { + return; + } + + DisplayLockGuard lock(this); + if (content && strlen(content) > 0) { + gfx_label_set_text(g_obj_label_toast, content); + SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, this); + } +} + +void EmoteDisplay::SetStatus(const char* const status) +{ + if (!status) { + ESP_LOGE(TAG, "SetStatus: status is nullptr"); + return; + } + + if (!engine_) { + return; + } + + DisplayLockGuard lock(this); + + if (std::strcmp(status, Lang::Strings::LISTENING) == 0) { + SetUIDisplayMode(UIDisplayMode::SHOW_LISTENING, this); + engine_->SetEyes("happy", true, 20, this); + engine_->SetIcon(ICON_MIC, this); + } else if (std::strcmp(status, Lang::Strings::STANDBY) == 0) { + SetUIDisplayMode(UIDisplayMode::SHOW_TIME, this); + engine_->SetIcon(ICON_BATTERY, this); + } else if (std::strcmp(status, Lang::Strings::SPEAKING) == 0) { + SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, this); + engine_->SetIcon(ICON_SPEAKER_ZZZ, this); + } else if (std::strcmp(status, Lang::Strings::ERROR) == 0) { + SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, this); + engine_->SetIcon(ICON_WIFI_FAILED, this); + } + + if (std::strcmp(status, Lang::Strings::CONNECTING) != 0) { + gfx_label_set_text(g_obj_label_toast, status); + } +} + +void EmoteDisplay::ShowNotification(const char* notification, int duration_ms) +{ + if (!notification || !engine_) { + return; + } + ESP_LOGI(TAG, "ShowNotification: %s", notification); + + DisplayLockGuard lock(this); + gfx_label_set_text(g_obj_label_toast, notification); + SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, this); +} + +void EmoteDisplay::UpdateStatusBar(bool update_all) +{ + if (!engine_) { + return; + } + + // Only display time when battery icon is shown + DisplayLockGuard lock(this); + if (g_current_icon_type == ICON_BATTERY) { + time_t now; + struct tm timeinfo; + time(&now); + + setenv("TZ", "GMT+0", 1); + tzset(); + localtime_r(&now, &timeinfo); + + char time_str[6]; + snprintf(time_str, sizeof(time_str), "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min); + + DisplayLockGuard lock(this); + gfx_label_set_text(g_obj_label_clock, time_str); + SetUIDisplayMode(UIDisplayMode::SHOW_TIME, this); + } +} + +void EmoteDisplay::SetPowerSaveMode(bool on) +{ + if (!engine_) { + return; + } + + DisplayLockGuard lock(this); + ESP_LOGI(TAG, "SetPowerSaveMode: %s", on ? "ON" : "OFF"); + if (on) { + gfx_anim_stop(g_obj_anim_eye); + } else { + gfx_anim_start(g_obj_anim_eye); + } +} + +void EmoteDisplay::SetPreviewImage(const void* image) +{ + if (image) { + ESP_LOGI(TAG, "SetPreviewImage: Preview image not supported, using default icon"); + if (engine_) { + } + } +} + +void EmoteDisplay::SetTheme(Theme* const theme) +{ + ESP_LOGI(TAG, "SetTheme: %p", theme); + +} +void EmoteDisplay::AddEmojiData(const std::string &name, const void* const data, const size_t size, + uint8_t fps, bool loop, bool lack) +{ + emoji_data_map_[name] = AssetData(data, size, fps, loop, lack); + ESP_LOGD(TAG, "Added emoji data: %s, size: %d, fps: %d, loop: %s, lack: %s", + name.c_str(), size, fps, loop ? "true" : "false", lack ? "true" : "false"); + + DisplayLockGuard lock(this); + if (name == "happy") { + engine_->SetEyes("happy", loop, fps > 0 ? fps : 20, this); + } +} + +void EmoteDisplay::AddIconData(const std::string &name, const void* const data, const size_t size) +{ + icon_data_map_[name] = AssetData(data, size); + ESP_LOGD(TAG, "Added icon data: %s, size: %d", name.c_str(), size); + + DisplayLockGuard lock(this); + if (name == ICON_WIFI_FAILED) { + SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, this); + engine_->SetIcon(ICON_WIFI_FAILED, this); + } +} + +void EmoteDisplay::AddLayoutData(const std::string &name, const std::string &align_str, + const int x, const int y, const int width, const int height) +{ + const char align_enum = StringToGfxAlign(align_str); + ESP_LOGI(TAG, "layout: %-12s | %-20s(%d) | %4d, %4d | %4dx%-4d", + name.c_str(), align_str.c_str(), align_enum, x, y, width, height); + + struct UIElement { + gfx_obj_t* obj; + const char* name; + }; + + const UIElement elements[] = { + {g_obj_anim_eye, UI_ELEMENT_EYE_ANIM}, + {g_obj_label_toast, UI_ELEMENT_TOAST_LABEL}, + {g_obj_label_clock, UI_ELEMENT_CLOCK_LABEL}, + {g_obj_anim_listen, UI_ELEMENT_LISTEN_ANIM}, + {g_obj_img_status, UI_ELEMENT_STATUS_ICON} + }; + + DisplayLockGuard lock(this); + for (const auto &element : elements) { + if (name == element.name && element.obj) { + gfx_obj_align(element.obj, align_enum, x, y); + if (width > 0 && height > 0) { + gfx_obj_set_size(element.obj, width, height); + } + return; + } + } + + ESP_LOGW(TAG, "AddLayoutData: UI element '%s' not found", name.c_str()); +} + +void EmoteDisplay::AddTextFont(std::shared_ptr text_font) +{ + if (!text_font) { + ESP_LOGW(TAG, "AddTextFont: text_font is nullptr"); + return; + } + + text_font_ = text_font; + ESP_LOGD(TAG, "AddTextFont: Text font added successfully"); + + DisplayLockGuard lock(this); + if (g_obj_label_toast && text_font_) { + gfx_label_set_font(g_obj_label_toast, const_cast(static_cast(text_font_->font()))); + } + if (g_obj_label_clock && text_font_) { + gfx_label_set_font(g_obj_label_clock, const_cast(static_cast(text_font_->font()))); + } +} + +AssetData EmoteDisplay::GetEmojiData(const std::string &name) const +{ + const auto it = emoji_data_map_.find(name); + if (it != emoji_data_map_.cend()) { + return it->second; + } + return AssetData(); +} + +AssetData EmoteDisplay::GetIconData(const std::string &name) const +{ + const auto it = icon_data_map_.find(name); + if (it != icon_data_map_.cend()) { + return it->second; + } + return AssetData(); +} + +EmoteEngine* EmoteDisplay::GetEngine() const +{ + return engine_.get(); +} + +void* EmoteDisplay::GetEngineHandle() const +{ + return engine_ ? engine_->GetEngineHandle() : nullptr; +} + +void EmoteDisplay::InitializeEngine(const esp_lcd_panel_handle_t panel, const esp_lcd_panel_io_handle_t panel_io, + const int width, const int height) +{ + engine_ = std::make_unique(panel, panel_io, width, height, this); +} + +bool EmoteDisplay::Lock(const int timeout_ms) +{ + if (engine_ && engine_->GetEngineHandle()) { + gfx_emote_lock(engine_->GetEngineHandle()); + return true; + } + return false; +} + +void EmoteDisplay::Unlock() +{ + if (engine_ && engine_->GetEngineHandle()) { + gfx_emote_unlock(engine_->GetEngineHandle()); + } +} + +} // namespace emote \ No newline at end of file diff --git a/main/display/emote_display.h b/main/display/emote_display.h new file mode 100644 index 0000000..e78123e --- /dev/null +++ b/main/display/emote_display.h @@ -0,0 +1,102 @@ +#pragma once + +#include "display.h" +#include "lvgl_font.h" +#include +#include +#include +#include +#include +#include + +namespace emote { + +// Simple data structure for storing asset data without LVGL dependency +struct AssetData { + const void* data; + size_t size; + union { + uint8_t flags; // 1 byte for all animation flags + struct { + uint8_t fps : 6; // FPS (0-63) - 6 bits + uint8_t loop : 1; // Loop animation - 1 bit + uint8_t lack : 1; // Lack animation - 1 bit + }; + }; + + AssetData() : data(nullptr), size(0), flags(0) {} + AssetData(const void* d, size_t s) : data(d), size(s), flags(0) {} + AssetData(const void* d, size_t s, uint8_t f, bool l, bool k) + : data(d), size(s) + { + fps = f > 63 ? 63 : f; // 限制 FPS 到 6 位范围 + loop = l; + lack = k; + } +}; + +// Layout element data structure +struct LayoutData { + char align; // Store as char instead of string + int x; + int y; + int width; + int height; + bool has_size; + + LayoutData() : align(0), x(0), y(0), width(0), height(0), has_size(false) {} + LayoutData(char a, int x_pos, int y_pos, int w = 0, int h = 0) + : align(a), x(x_pos), y(y_pos), width(w), height(h), has_size(w > 0 && h > 0) {} +}; + +// Function to convert align string to GFX_ALIGN enum value +char StringToGfxAlign(const std::string &align_str); + +class EmoteEngine; + +class EmoteDisplay : public Display { +public: + EmoteDisplay(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io, int width, int height); + virtual ~EmoteDisplay(); + + virtual void SetEmotion(const char* emotion) override; + virtual void SetStatus(const char* status) override; + virtual void SetChatMessage(const char* role, const char* content) override; + virtual void SetTheme(Theme* theme) override; + virtual void ShowNotification(const char* notification, int duration_ms = 3000) override; + virtual void UpdateStatusBar(bool update_all = false) override; + virtual void SetPowerSaveMode(bool on) override; + virtual void SetPreviewImage(const void* image); + + void AddEmojiData(const std::string &name, const void* data, size_t size, uint8_t fps = 0, bool loop = false, bool lack = false); + void AddIconData(const std::string &name, const void* data, size_t size); + void AddLayoutData(const std::string &name, const std::string &align_str, int x, int y, int width = 0, int height = 0); + void AddTextFont(std::shared_ptr text_font); + AssetData GetEmojiData(const std::string &name) const; + AssetData GetIconData(const std::string &name) const; + + EmoteEngine* GetEngine() const; + void* GetEngineHandle() const; + + inline std::shared_ptr text_font() const + { + return text_font_; + } + +private: + void InitializeEngine(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io, int width, int height); + virtual bool Lock(int timeout_ms = 0) override; + virtual void Unlock() override; + + std::unique_ptr engine_; + + // Font management + std::shared_ptr text_font_ = nullptr; + + // Non-LVGL asset data storage + std::map emoji_data_map_; + std::map icon_data_map_; + +}; + +} // namespace emote diff --git a/main/display/esplog_display.cc b/main/display/esplog_display.cc deleted file mode 100644 index 01049a5..0000000 --- a/main/display/esplog_display.cc +++ /dev/null @@ -1,57 +0,0 @@ -#include "esplog_display.h" - -#include "esp_log.h" - -#define TAG "Display2Log" - - -EspLogDisplay::EspLogDisplay() -{} - -EspLogDisplay::~EspLogDisplay() -{} - -void EspLogDisplay::SetStatus(const char* status) -{ - ESP_LOGW(TAG, "SetStatus: %s", status); -} - -void EspLogDisplay::ShowNotification(const char* notification, int duration_ms) -{ - ESP_LOGW(TAG, "ShowNotification: %s", notification); -} -void EspLogDisplay::ShowNotification(const std::string ¬ification, int duration_ms) -{ - ShowNotification(notification.c_str(), duration_ms); -} - - -void EspLogDisplay::SetEmotion(const char* emotion) -{ - ESP_LOGW(TAG, "SetEmotion: %s", emotion); -} - -void EspLogDisplay::SetChatMessage(const char* role, const char* content) -{ - ESP_LOGW(TAG, "Role:%s", role); - ESP_LOGW(TAG, " %s", content); -} - -// 音乐播放相关(用日志模拟UI行为) -// 显示当前播放的歌曲信息 -void EspLogDisplay::SetMusicInfo(const char* info) -{ - ESP_LOGW(TAG, "MusicInfo: %s", info ? info : ""); -} - -// 启动频谱显示(此处仅打印日志) -void EspLogDisplay::start() -{ - ESP_LOGW(TAG, "Spectrum start"); -} - -// 停止频谱显示(此处仅打印日志) -void EspLogDisplay::stopFft() -{ - ESP_LOGW(TAG, "Spectrum stop"); -} \ No newline at end of file diff --git a/main/display/esplog_display.h b/main/display/esplog_display.h deleted file mode 100644 index 417a257..0000000 --- a/main/display/esplog_display.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ESPLOG_DISPLAY_H_ -#define ESPLOG_DISPLAY_H_ - -#include "display.h" - -#include - -class EspLogDisplay : public Display { -public: - EspLogDisplay(); - ~EspLogDisplay(); - - virtual void SetStatus(const char* status); - virtual void ShowNotification(const char* notification, int duration_ms = 3000); - virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000); - virtual void SetEmotion(const char* emotion) override; - virtual void SetChatMessage(const char* role, const char* content) override; - - // 音乐播放相关(无屏版本用日志模拟) - virtual void SetMusicInfo(const char* info) override; - virtual void start() override; - virtual void stopFft() override; - - virtual inline void SetPreviewImage(const lv_img_dsc_t* image) {} - virtual inline void SetTheme(const std::string& theme_name) {} - virtual inline void UpdateStatusBar(bool update_all = false) override {} - -protected: - virtual inline bool Lock(int timeout_ms = 0) override { return true; } - virtual inline void Unlock() override {} -}; - -#endif \ No newline at end of file diff --git a/main/display/lcd_display.cc b/main/display/lcd_display.cc index 4035975..2b1df2f 100644 --- a/main/display/lcd_display.cc +++ b/main/display/lcd_display.cc @@ -1,1111 +1,1425 @@ -#include "lcd_display.h" -#include "gif/lvgl_gif.h" -#include "settings.h" -#include "lvgl_theme.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "board.h" - -#define TAG "LcdDisplay" - -LV_FONT_DECLARE(LVGL_TEXT_FONT); -LV_FONT_DECLARE(LVGL_ICON_FONT); -LV_FONT_DECLARE(font_awesome_30_4); - -void LcdDisplay::InitializeLcdThemes() { - auto text_font = std::make_shared(&LVGL_TEXT_FONT); - auto icon_font = std::make_shared(&LVGL_ICON_FONT); - auto large_icon_font = std::make_shared(&font_awesome_30_4); - - // light theme - auto light_theme = new LvglTheme("light"); - light_theme->set_background_color(lv_color_hex(0xFFFFFF)); //rgb(255, 255, 255) - light_theme->set_text_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) - light_theme->set_chat_background_color(lv_color_hex(0xE0E0E0)); //rgb(224, 224, 224) - light_theme->set_user_bubble_color(lv_color_hex(0x00FF00)); //rgb(0, 128, 0) - light_theme->set_assistant_bubble_color(lv_color_hex(0xDDDDDD)); //rgb(221, 221, 221) - light_theme->set_system_bubble_color(lv_color_hex(0xFFFFFF)); //rgb(255, 255, 255) - light_theme->set_system_text_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) - light_theme->set_border_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) - light_theme->set_low_battery_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) - light_theme->set_text_font(text_font); - light_theme->set_icon_font(icon_font); - light_theme->set_large_icon_font(large_icon_font); - - // dark theme - auto dark_theme = new LvglTheme("dark"); - dark_theme->set_background_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) - dark_theme->set_text_color(lv_color_hex(0xFFFFFF)); //rgb(255, 255, 255) - dark_theme->set_chat_background_color(lv_color_hex(0x1F1F1F)); //rgb(31, 31, 31) - dark_theme->set_user_bubble_color(lv_color_hex(0x00FF00)); //rgb(0, 128, 0) - dark_theme->set_assistant_bubble_color(lv_color_hex(0x222222)); //rgb(34, 34, 34) - dark_theme->set_system_bubble_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) - dark_theme->set_system_text_color(lv_color_hex(0xFFFFFF)); //rgb(255, 255, 255) - dark_theme->set_border_color(lv_color_hex(0xFFFFFF)); //rgb(255, 255, 255) - dark_theme->set_low_battery_color(lv_color_hex(0xFF0000)); //rgb(255, 0, 0) - dark_theme->set_text_font(text_font); - dark_theme->set_icon_font(icon_font); - dark_theme->set_large_icon_font(large_icon_font); - - auto& theme_manager = LvglThemeManager::GetInstance(); - theme_manager.RegisterTheme("light", light_theme); - theme_manager.RegisterTheme("dark", dark_theme); -} - -LcdDisplay::LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height) - : panel_io_(panel_io), panel_(panel) { - width_ = width; - height_ = height; - - // Initialize LCD themes - InitializeLcdThemes(); - - // Load theme from settings - Settings settings("display", false); - std::string theme_name = settings.GetString("theme", "light"); - current_theme_ = LvglThemeManager::GetInstance().GetTheme(theme_name); - - // Create a timer to hide the preview image - esp_timer_create_args_t preview_timer_args = { - .callback = [](void* arg) { - LcdDisplay* display = static_cast(arg); - display->SetPreviewImage(nullptr); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "preview_timer", - .skip_unhandled_events = false, - }; - esp_timer_create(&preview_timer_args, &preview_timer_); -} - -SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : LcdDisplay(panel_io, panel, width, height) { - - // draw white - std::vector buffer(width_, 0xFFFF); - for (int y = 0; y < height_; y++) { - esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data()); - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - ESP_LOGI(TAG, "Initialize LVGL library"); - lv_init(); - -#if CONFIG_SPIRAM - // lv image cache, currently only PNG is supported - size_t psram_size_mb = esp_psram_get_size() / 1024 / 1024; - if (psram_size_mb >= 8) { - lv_image_cache_resize(2 * 1024 * 1024, true); - ESP_LOGI(TAG, "Use 2MB of PSRAM for image cache"); - } else if (psram_size_mb >= 2) { - lv_image_cache_resize(512 * 1024, true); - ESP_LOGI(TAG, "Use 512KB of PSRAM for image cache"); - } -#endif - - ESP_LOGI(TAG, "Initialize LVGL port"); - lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); - port_cfg.task_priority = 1; -#if CONFIG_SOC_CPU_CORES_NUM > 1 - port_cfg.task_affinity = 1; -#endif - lvgl_port_init(&port_cfg); - - ESP_LOGI(TAG, "Adding LCD display"); - const lvgl_port_display_cfg_t display_cfg = { - .io_handle = panel_io_, - .panel_handle = panel_, - .control_handle = nullptr, - .buffer_size = static_cast(width_ * 20), - .double_buffer = false, - .trans_size = 0, - .hres = static_cast(width_), - .vres = static_cast(height_), - .monochrome = false, - .rotation = { - .swap_xy = swap_xy, - .mirror_x = mirror_x, - .mirror_y = mirror_y, - }, - .color_format = LV_COLOR_FORMAT_RGB565, - .flags = { - .buff_dma = 1, - .buff_spiram = 0, - .sw_rotate = 0, - .swap_bytes = 1, - .full_refresh = 0, - .direct_mode = 0, - }, - }; - - display_ = lvgl_port_add_disp(&display_cfg); - if (display_ == nullptr) { - ESP_LOGE(TAG, "Failed to add display"); - return; - } - - if (offset_x != 0 || offset_y != 0) { - lv_display_set_offset(display_, offset_x, offset_y); - } - - SetupUI(); -} - -// RGB LCD实现 -RgbLcdDisplay::RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, - bool mirror_x, bool mirror_y, bool swap_xy) - : LcdDisplay(panel_io, panel, width, height) { - - // draw white - std::vector buffer(width_, 0xFFFF); - for (int y = 0; y < height_; y++) { - esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data()); - } - - ESP_LOGI(TAG, "Initialize LVGL library"); - lv_init(); - - ESP_LOGI(TAG, "Initialize LVGL port"); - lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); - port_cfg.task_priority = 1; - port_cfg.timer_period_ms = 50; - lvgl_port_init(&port_cfg); - - ESP_LOGI(TAG, "Adding LCD display"); - const lvgl_port_display_cfg_t display_cfg = { - .io_handle = panel_io_, - .panel_handle = panel_, - .buffer_size = static_cast(width_ * 20), - .double_buffer = true, - .hres = static_cast(width_), - .vres = static_cast(height_), - .rotation = { - .swap_xy = swap_xy, - .mirror_x = mirror_x, - .mirror_y = mirror_y, - }, - .flags = { - .buff_dma = 1, - .swap_bytes = 0, - .full_refresh = 1, - .direct_mode = 1, - }, - }; - - const lvgl_port_display_rgb_cfg_t rgb_cfg = { - .flags = { - .bb_mode = true, - .avoid_tearing = true, - } - }; - - display_ = lvgl_port_add_disp_rgb(&display_cfg, &rgb_cfg); - if (display_ == nullptr) { - ESP_LOGE(TAG, "Failed to add RGB display"); - return; - } - - if (offset_x != 0 || offset_y != 0) { - lv_display_set_offset(display_, offset_x, offset_y); - } - - SetupUI(); -} - -MipiLcdDisplay::MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, - bool mirror_x, bool mirror_y, bool swap_xy) - : LcdDisplay(panel_io, panel, width, height) { - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - ESP_LOGI(TAG, "Initialize LVGL library"); - lv_init(); - - ESP_LOGI(TAG, "Initialize LVGL port"); - lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); - lvgl_port_init(&port_cfg); - - ESP_LOGI(TAG, "Adding LCD display"); - const lvgl_port_display_cfg_t disp_cfg = { - .io_handle = panel_io, - .panel_handle = panel, - .control_handle = nullptr, - .buffer_size = static_cast(width_ * 50), - .double_buffer = false, - .hres = static_cast(width_), - .vres = static_cast(height_), - .monochrome = false, - /* Rotation values must be same as used in esp_lcd for initial settings of the screen */ - .rotation = { - .swap_xy = swap_xy, - .mirror_x = mirror_x, - .mirror_y = mirror_y, - }, - .flags = { - .buff_dma = true, - .buff_spiram =false, - .sw_rotate = false, - }, - }; - - const lvgl_port_display_dsi_cfg_t dpi_cfg = { - .flags = { - .avoid_tearing = false, - } - }; - display_ = lvgl_port_add_disp_dsi(&disp_cfg, &dpi_cfg); - if (display_ == nullptr) { - ESP_LOGE(TAG, "Failed to add display"); - return; - } - - if (offset_x != 0 || offset_y != 0) { - lv_display_set_offset(display_, offset_x, offset_y); - } - - SetupUI(); -} - -LcdDisplay::~LcdDisplay() { - SetPreviewImage(nullptr); - - // Clean up GIF controller - if (gif_controller_) { - gif_controller_->Stop(); - gif_controller_.reset(); - } - - if (preview_timer_ != nullptr) { - esp_timer_stop(preview_timer_); - esp_timer_delete(preview_timer_); - } - - if (preview_image_ != nullptr) { - lv_obj_del(preview_image_); - } - if (chat_message_label_ != nullptr) { - lv_obj_del(chat_message_label_); - } - if (emoji_label_ != nullptr) { - lv_obj_del(emoji_label_); - } - if (emoji_image_ != nullptr) { - lv_obj_del(emoji_image_); - } - if (emoji_box_ != nullptr) { - lv_obj_del(emoji_box_); - } - if (content_ != nullptr) { - lv_obj_del(content_); - } - if (status_bar_ != nullptr) { - lv_obj_del(status_bar_); - } - if (side_bar_ != nullptr) { - lv_obj_del(side_bar_); - } - if (container_ != nullptr) { - lv_obj_del(container_); - } - if (display_ != nullptr) { - lv_display_delete(display_); - } - - if (panel_ != nullptr) { - esp_lcd_panel_del(panel_); - } - if (panel_io_ != nullptr) { - esp_lcd_panel_io_del(panel_io_); - } -} - -bool LcdDisplay::Lock(int timeout_ms) { - return lvgl_port_lock(timeout_ms); -} - -void LcdDisplay::Unlock() { - lvgl_port_unlock(); -} - -#if CONFIG_USE_WECHAT_MESSAGE_STYLE -void LcdDisplay::SetupUI() { - DisplayLockGuard lock(this); - - auto lvgl_theme = static_cast(current_theme_); - auto text_font = lvgl_theme->text_font()->font(); - auto icon_font = lvgl_theme->icon_font()->font(); - auto large_icon_font = lvgl_theme->large_icon_font()->font(); - - auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font, 0); - lv_obj_set_style_text_color(screen, lvgl_theme->text_color(), 0); - lv_obj_set_style_bg_color(screen, lvgl_theme->background_color(), 0); - - /* Container */ - container_ = lv_obj_create(screen); - lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); - lv_obj_set_style_radius(container_, 0, 0); - lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_all(container_, 0, 0); - lv_obj_set_style_border_width(container_, 0, 0); - lv_obj_set_style_pad_row(container_, 0, 0); - lv_obj_set_style_bg_color(container_, lvgl_theme->background_color(), 0); - lv_obj_set_style_border_color(container_, lvgl_theme->border_color(), 0); - - /* Status bar */ - status_bar_ = lv_obj_create(container_); - lv_obj_set_size(status_bar_, LV_HOR_RES, LV_SIZE_CONTENT); - lv_obj_set_style_radius(status_bar_, 0, 0); - lv_obj_set_style_bg_color(status_bar_, lvgl_theme->background_color(), 0); - lv_obj_set_style_text_color(status_bar_, lvgl_theme->text_color(), 0); - - /* Content - Chat area */ - content_ = lv_obj_create(container_); - lv_obj_set_style_radius(content_, 0, 0); - lv_obj_set_width(content_, LV_HOR_RES); - lv_obj_set_flex_grow(content_, 1); - lv_obj_set_style_pad_all(content_, lvgl_theme->spacing(4), 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_style_bg_color(content_, lvgl_theme->chat_background_color(), 0); // Background for chat area - - // Enable scrolling for chat content - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_scroll_dir(content_, LV_DIR_VER); - - // Create a flex container for chat messages - lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); - lv_obj_set_style_pad_row(content_, lvgl_theme->spacing(4), 0); // Space between messages - - // We'll create chat messages dynamically in SetChatMessage - chat_message_label_ = nullptr; - - /* Status bar */ - lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_column(status_bar_, 0, 0); - lv_obj_set_style_pad_top(status_bar_, lvgl_theme->spacing(2), 0); - lv_obj_set_style_pad_bottom(status_bar_, lvgl_theme->spacing(2), 0); - lv_obj_set_style_pad_left(status_bar_, lvgl_theme->spacing(4), 0); - lv_obj_set_style_pad_right(status_bar_, lvgl_theme->spacing(4), 0); - lv_obj_set_scrollbar_mode(status_bar_, LV_SCROLLBAR_MODE_OFF); - // 设置状态栏的内容垂直居中 - lv_obj_set_flex_align(status_bar_, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - - network_label_ = lv_label_create(status_bar_); - lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, icon_font, 0); - lv_obj_set_style_text_color(network_label_, lvgl_theme->text_color(), 0); - - notification_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(notification_label_, 1); - lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_color(notification_label_, lvgl_theme->text_color(), 0); - lv_label_set_text(notification_label_, ""); - lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - - status_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(status_label_, 1); - lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_color(status_label_, lvgl_theme->text_color(), 0); - lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); - - mute_label_ = lv_label_create(status_bar_); - lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font, 0); - lv_obj_set_style_text_color(mute_label_, lvgl_theme->text_color(), 0); - - battery_label_ = lv_label_create(status_bar_); - lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, icon_font, 0); - lv_obj_set_style_text_color(battery_label_, lvgl_theme->text_color(), 0); - lv_obj_set_style_margin_left(battery_label_, lvgl_theme->spacing(2), 0); // 添加左边距,与前面的元素分隔 - - low_battery_popup_ = lv_obj_create(screen); - lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2); - lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, -lvgl_theme->spacing(4)); - lv_obj_set_style_bg_color(low_battery_popup_, lvgl_theme->low_battery_color(), 0); - lv_obj_set_style_radius(low_battery_popup_, lvgl_theme->spacing(4), 0); - low_battery_label_ = lv_label_create(low_battery_popup_); - lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE); - lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0); - lv_obj_center(low_battery_label_); - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - - emoji_image_ = lv_img_create(screen); - lv_obj_align(emoji_image_, LV_ALIGN_TOP_MID, 0, text_font->line_height + lvgl_theme->spacing(8)); - - // Display AI logo while booting - emoji_label_ = lv_label_create(screen); - lv_obj_center(emoji_label_); - lv_obj_set_style_text_font(emoji_label_, large_icon_font, 0); - lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0); - lv_label_set_text(emoji_label_, FONT_AWESOME_MICROCHIP_AI); -} -#if CONFIG_IDF_TARGET_ESP32P4 -#define MAX_MESSAGES 40 -#else -#define MAX_MESSAGES 20 -#endif -void LcdDisplay::SetChatMessage(const char* role, const char* content) { - DisplayLockGuard lock(this); - if (content_ == nullptr) { - return; - } - - // 检查消息数量是否超过限制 - uint32_t child_count = lv_obj_get_child_cnt(content_); - if (child_count >= MAX_MESSAGES) { - // 删除最早的消息(第一个子对象) - lv_obj_t* first_child = lv_obj_get_child(content_, 0); - lv_obj_t* last_child = lv_obj_get_child(content_, child_count - 1); - if (first_child != nullptr) { - lv_obj_del(first_child); - } - // Scroll to the last message immediately - if (last_child != nullptr) { - lv_obj_scroll_to_view_recursive(last_child, LV_ANIM_OFF); - } - } - - // 折叠系统消息(如果是系统消息,检查最后一个消息是否也是系统消息) - if (strcmp(role, "system") == 0) { - if (child_count > 0) { - // 获取最后一个消息容器 - lv_obj_t* last_container = lv_obj_get_child(content_, child_count - 1); - if (last_container != nullptr && lv_obj_get_child_cnt(last_container) > 0) { - // 获取容器内的气泡 - lv_obj_t* last_bubble = lv_obj_get_child(last_container, 0); - if (last_bubble != nullptr) { - // 检查气泡类型是否为系统消息 - void* bubble_type_ptr = lv_obj_get_user_data(last_bubble); - if (bubble_type_ptr != nullptr && strcmp((const char*)bubble_type_ptr, "system") == 0) { - // 如果最后一个消息也是系统消息,则删除它 - lv_obj_del(last_container); - } - } - } - } - } else { - // 隐藏居中显示的 AI logo - lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); - } - - //避免出现空的消息框 - if(strlen(content) == 0) { - return; - } - - auto lvgl_theme = static_cast(current_theme_); - auto text_font = lvgl_theme->text_font()->font(); - - // Create a message bubble - lv_obj_t* msg_bubble = lv_obj_create(content_); - lv_obj_set_style_radius(msg_bubble, 8, 0); - lv_obj_set_scrollbar_mode(msg_bubble, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_style_border_width(msg_bubble, 0, 0); - lv_obj_set_style_pad_all(msg_bubble, lvgl_theme->spacing(4), 0); - - // Create the message text - lv_obj_t* msg_text = lv_label_create(msg_bubble); - lv_label_set_text(msg_text, content); - - // 计算文本实际宽度 - lv_coord_t text_width = lv_txt_get_width(content, strlen(content), text_font, 0); - - // 计算气泡宽度 - lv_coord_t max_width = LV_HOR_RES * 85 / 100 - 16; // 屏幕宽度的85% - lv_coord_t min_width = 20; - lv_coord_t bubble_width; - - // 确保文本宽度不小于最小宽度 - if (text_width < min_width) { - text_width = min_width; - } - - // 如果文本宽度小于最大宽度,使用文本宽度 - if (text_width < max_width) { - bubble_width = text_width; - } else { - bubble_width = max_width; - } - - // 设置消息文本的宽度 - lv_obj_set_width(msg_text, bubble_width); // 减去padding - lv_label_set_long_mode(msg_text, LV_LABEL_LONG_WRAP); - - // 设置气泡宽度 - lv_obj_set_width(msg_bubble, bubble_width); - lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT); - - // Set alignment and style based on message role - if (strcmp(role, "user") == 0) { - // User messages are right-aligned with green background - lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->user_bubble_color(), 0); - lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0); - // Set text color for contrast - lv_obj_set_style_text_color(msg_text, lvgl_theme->text_color(), 0); - - // 设置自定义属性标记气泡类型 - lv_obj_set_user_data(msg_bubble, (void*)"user"); - - // Set appropriate width for content - lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT); - lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT); - - // Don't grow - lv_obj_set_style_flex_grow(msg_bubble, 0, 0); - } else if (strcmp(role, "assistant") == 0) { - // Assistant messages are left-aligned with white background - lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->assistant_bubble_color(), 0); - lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0); - // Set text color for contrast - lv_obj_set_style_text_color(msg_text, lvgl_theme->text_color(), 0); - - // 设置自定义属性标记气泡类型 - lv_obj_set_user_data(msg_bubble, (void*)"assistant"); - - // Set appropriate width for content - lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT); - lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT); - - // Don't grow - lv_obj_set_style_flex_grow(msg_bubble, 0, 0); - } else if (strcmp(role, "system") == 0) { - // System messages are center-aligned with light gray background - lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->system_bubble_color(), 0); - lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0); - // Set text color for contrast - lv_obj_set_style_text_color(msg_text, lvgl_theme->system_text_color(), 0); - - // 设置自定义属性标记气泡类型 - lv_obj_set_user_data(msg_bubble, (void*)"system"); - - // Set appropriate width for content - lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT); - lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT); - - // Don't grow - lv_obj_set_style_flex_grow(msg_bubble, 0, 0); - } - - // Create a full-width container for user messages to ensure right alignment - if (strcmp(role, "user") == 0) { - // Create a full-width container - lv_obj_t* container = lv_obj_create(content_); - lv_obj_set_width(container, LV_HOR_RES); - lv_obj_set_height(container, LV_SIZE_CONTENT); - - // Make container transparent and borderless - lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(container, 0, 0); - lv_obj_set_style_pad_all(container, 0, 0); - - // Move the message bubble into this container - lv_obj_set_parent(msg_bubble, container); - - // Right align the bubble in the container - lv_obj_align(msg_bubble, LV_ALIGN_RIGHT_MID, -25, 0); - - // Auto-scroll to this container - lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON); - } else if (strcmp(role, "system") == 0) { - // 为系统消息创建全宽容器以确保居中对齐 - lv_obj_t* container = lv_obj_create(content_); - lv_obj_set_width(container, LV_HOR_RES); - lv_obj_set_height(container, LV_SIZE_CONTENT); - - // 使容器透明且无边框 - lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(container, 0, 0); - lv_obj_set_style_pad_all(container, 0, 0); - - // 将消息气泡移入此容器 - lv_obj_set_parent(msg_bubble, container); - - // 将气泡居中对齐在容器中 - lv_obj_align(msg_bubble, LV_ALIGN_CENTER, 0, 0); - - // 自动滚动底部 - lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON); - } else { - // For assistant messages - // Left align assistant messages - lv_obj_align(msg_bubble, LV_ALIGN_LEFT_MID, 0, 0); - - // Auto-scroll to the message bubble - lv_obj_scroll_to_view_recursive(msg_bubble, LV_ANIM_ON); - } - - // Store reference to the latest message label - chat_message_label_ = msg_text; -} - -void LcdDisplay::SetPreviewImage(std::unique_ptr image) { - DisplayLockGuard lock(this); - if (content_ == nullptr) { - return; - } - - if (image == nullptr) { - return; - } - - auto lvgl_theme = static_cast(current_theme_); - // Create a message bubble for image preview - lv_obj_t* img_bubble = lv_obj_create(content_); - lv_obj_set_style_radius(img_bubble, 8, 0); - lv_obj_set_scrollbar_mode(img_bubble, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_style_border_width(img_bubble, 0, 0); - lv_obj_set_style_pad_all(img_bubble, lvgl_theme->spacing(4), 0); - - // Set image bubble background color (similar to system message) - lv_obj_set_style_bg_color(img_bubble, lvgl_theme->assistant_bubble_color(), 0); - lv_obj_set_style_bg_opa(img_bubble, LV_OPA_70, 0); - - // 设置自定义属性标记气泡类型 - lv_obj_set_user_data(img_bubble, (void*)"image"); - - // Create the image object inside the bubble - lv_obj_t* preview_image = lv_image_create(img_bubble); - - // Calculate appropriate size for the image - lv_coord_t max_width = LV_HOR_RES * 70 / 100; // 70% of screen width - lv_coord_t max_height = LV_VER_RES * 50 / 100; // 50% of screen height - - // Calculate zoom factor to fit within maximum dimensions - auto img_dsc = image->image_dsc(); - lv_coord_t img_width = img_dsc->header.w; - lv_coord_t img_height = img_dsc->header.h; - if (img_width == 0 || img_height == 0) { - img_width = max_width; - img_height = max_height; - ESP_LOGW(TAG, "Invalid image dimensions: %ld x %ld, using default dimensions: %ld x %ld", img_width, img_height, max_width, max_height); - } - - lv_coord_t zoom_w = (max_width * 256) / img_width; - lv_coord_t zoom_h = (max_height * 256) / img_height; - lv_coord_t zoom = (zoom_w < zoom_h) ? zoom_w : zoom_h; - - // Ensure zoom doesn't exceed 256 (100%) - if (zoom > 256) zoom = 256; - - // Set image properties - lv_image_set_src(preview_image, img_dsc); - lv_image_set_scale(preview_image, zoom); - - // Add event handler to clean up LvglImage when image is deleted - // We need to transfer ownership of the unique_ptr to the event callback - LvglImage* raw_image = image.release(); // 释放智能指针的所有权 - lv_obj_add_event_cb(preview_image, [](lv_event_t* e) { - LvglImage* img = (LvglImage*)lv_event_get_user_data(e); - if (img != nullptr) { - delete img; // 通过删除 LvglImage 对象来正确释放内存 - } - }, LV_EVENT_DELETE, (void*)raw_image); - - // Calculate actual scaled image dimensions - lv_coord_t scaled_width = (img_width * zoom) / 256; - lv_coord_t scaled_height = (img_height * zoom) / 256; - - // Set bubble size to be 16 pixels larger than the image (8 pixels on each side) - lv_obj_set_width(img_bubble, scaled_width + 16); - lv_obj_set_height(img_bubble, scaled_height + 16); - - // Don't grow in flex layout - lv_obj_set_style_flex_grow(img_bubble, 0, 0); - - // Center the image within the bubble - lv_obj_center(preview_image); - - // Left align the image bubble like assistant messages - lv_obj_align(img_bubble, LV_ALIGN_LEFT_MID, 0, 0); - - // Auto-scroll to the image bubble - lv_obj_scroll_to_view_recursive(img_bubble, LV_ANIM_ON); -} -#else -void LcdDisplay::SetupUI() { - DisplayLockGuard lock(this); - LvglTheme* lvgl_theme = static_cast(current_theme_); - auto text_font = lvgl_theme->text_font()->font(); - auto icon_font = lvgl_theme->icon_font()->font(); - auto large_icon_font = lvgl_theme->large_icon_font()->font(); - - auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font, 0); - lv_obj_set_style_text_color(screen, lvgl_theme->text_color(), 0); - lv_obj_set_style_bg_color(screen, lvgl_theme->background_color(), 0); - - /* Container */ - container_ = lv_obj_create(screen); - lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); - lv_obj_set_style_radius(container_, 0, 0); - lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_all(container_, 0, 0); - lv_obj_set_style_border_width(container_, 0, 0); - lv_obj_set_style_pad_row(container_, 0, 0); - lv_obj_set_style_bg_color(container_, lvgl_theme->background_color(), 0); - lv_obj_set_style_border_color(container_, lvgl_theme->border_color(), 0); - - /* Status bar */ - status_bar_ = lv_obj_create(container_); - lv_obj_set_size(status_bar_, LV_HOR_RES, LV_SIZE_CONTENT); - lv_obj_set_style_radius(status_bar_, 0, 0); - lv_obj_set_style_bg_color(status_bar_, lvgl_theme->background_color(), 0); - lv_obj_set_style_text_color(status_bar_, lvgl_theme->text_color(), 0); - lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_top(status_bar_, lvgl_theme->spacing(2), 0); - lv_obj_set_style_pad_bottom(status_bar_, lvgl_theme->spacing(2), 0); - lv_obj_set_style_pad_left(status_bar_, lvgl_theme->spacing(4), 0); - lv_obj_set_style_pad_right(status_bar_, lvgl_theme->spacing(4), 0); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_column(status_bar_, 0, 0); - - /* Content */ - content_ = lv_obj_create(container_); - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_style_radius(content_, 0, 0); - lv_obj_set_width(content_, LV_HOR_RES); - lv_obj_set_flex_grow(content_, 1); - lv_obj_set_style_pad_all(content_, 0, 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_style_bg_color(content_, lvgl_theme->chat_background_color(), 0); - - lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); // 垂直布局(从上到下) - lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY); // 子对象居中对齐,等距分布 - - emoji_box_ = lv_obj_create(content_); - lv_obj_set_size(emoji_box_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_set_style_bg_opa(emoji_box_, LV_OPA_TRANSP, 0); - lv_obj_set_style_pad_all(emoji_box_, 0, 0); - lv_obj_set_style_border_width(emoji_box_, 0, 0); - - emoji_label_ = lv_label_create(emoji_box_); - lv_obj_set_style_text_font(emoji_label_, large_icon_font, 0); - lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0); - lv_label_set_text(emoji_label_, FONT_AWESOME_MICROCHIP_AI); - - emoji_image_ = lv_img_create(emoji_box_); - lv_obj_center(emoji_image_); - lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN); - - preview_image_ = lv_image_create(content_); - lv_obj_set_size(preview_image_, width_ / 2, height_ / 2); - lv_obj_align(preview_image_, LV_ALIGN_CENTER, 0, 0); - lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN); - - chat_message_label_ = lv_label_create(content_); - lv_label_set_text(chat_message_label_, ""); - lv_obj_set_width(chat_message_label_, width_ * 0.9); // 限制宽度为屏幕宽度的 90% - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式 - lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐 - lv_obj_set_style_text_color(chat_message_label_, lvgl_theme->text_color(), 0); - - /* Status bar */ - network_label_ = lv_label_create(status_bar_); - lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, icon_font, 0); - lv_obj_set_style_text_color(network_label_, lvgl_theme->text_color(), 0); - - notification_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(notification_label_, 1); - lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_color(notification_label_, lvgl_theme->text_color(), 0); - lv_label_set_text(notification_label_, ""); - lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - - status_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(status_label_, 1); - lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_color(status_label_, lvgl_theme->text_color(), 0); - lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); - - mute_label_ = lv_label_create(status_bar_); - lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font, 0); - lv_obj_set_style_text_color(mute_label_, lvgl_theme->text_color(), 0); - - battery_label_ = lv_label_create(status_bar_); - lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, icon_font, 0); - lv_obj_set_style_text_color(battery_label_, lvgl_theme->text_color(), 0); - - low_battery_popup_ = lv_obj_create(screen); - lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2); - lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, -lvgl_theme->spacing(4)); - lv_obj_set_style_bg_color(low_battery_popup_, lvgl_theme->low_battery_color(), 0); - lv_obj_set_style_radius(low_battery_popup_, lvgl_theme->spacing(4), 0); - - low_battery_label_ = lv_label_create(low_battery_popup_); - lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE); - lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0); - lv_obj_center(low_battery_label_); - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); -} - -void LcdDisplay::SetPreviewImage(std::unique_ptr image) { - DisplayLockGuard lock(this); - if (preview_image_ == nullptr) { - ESP_LOGE(TAG, "Preview image is not initialized"); - return; - } - - if (image == nullptr) { - esp_timer_stop(preview_timer_); - lv_obj_remove_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN); - preview_image_cached_.reset(); - return; - } - - preview_image_cached_ = std::move(image); - auto img_dsc = preview_image_cached_->image_dsc(); - // 设置图片源并显示预览图片 - lv_image_set_src(preview_image_, img_dsc); - if (img_dsc->header.w > 0 && img_dsc->header.h > 0) { - // zoom factor 0.5 - lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w); - } - - // Hide emoji_box_ - lv_obj_add_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(preview_image_, LV_OBJ_FLAG_HIDDEN); - esp_timer_stop(preview_timer_); - ESP_ERROR_CHECK(esp_timer_start_once(preview_timer_, PREVIEW_IMAGE_DURATION_MS * 1000)); -} - -void LcdDisplay::SetChatMessage(const char* role, const char* content) { - DisplayLockGuard lock(this); - if (chat_message_label_ == nullptr) { - return; - } - lv_label_set_text(chat_message_label_, content); -} -#endif - -void LcdDisplay::SetEmotion(const char* emotion) { - // Stop any running GIF animation - if (gif_controller_) { - DisplayLockGuard lock(this); - gif_controller_->Stop(); - gif_controller_.reset(); - } - - if (emoji_image_ == nullptr) { - return; - } - - auto emoji_collection = static_cast(current_theme_)->emoji_collection(); - auto image = emoji_collection != nullptr ? emoji_collection->GetEmojiImage(emotion) : nullptr; - if (image == nullptr) { - const char* utf8 = font_awesome_get_utf8(emotion); - if (utf8 != nullptr && emoji_label_ != nullptr) { - DisplayLockGuard lock(this); - lv_label_set_text(emoji_label_, utf8); - lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); - } - return; - } - - DisplayLockGuard lock(this); - if (image->IsGif()) { - // Create new GIF controller - gif_controller_ = std::make_unique(image->image_dsc()); - - if (gif_controller_->IsLoaded()) { - // Set up frame update callback - gif_controller_->SetFrameCallback([this]() { - lv_image_set_src(emoji_image_, gif_controller_->image_dsc()); - }); - - // Set initial frame and start animation - lv_image_set_src(emoji_image_, gif_controller_->image_dsc()); - gif_controller_->Start(); - - // Show GIF, hide others - lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN); - } else { - ESP_LOGE(TAG, "Failed to load GIF for emotion: %s", emotion); - gif_controller_.reset(); - } - } else { - lv_image_set_src(emoji_image_, image->image_dsc()); - lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN); - } - -#if CONFIG_USE_WECHAT_MESSAGE_STYLE - // Wechat message style中,如果emotion是neutral,则不显示 - uint32_t child_count = lv_obj_get_child_cnt(content_); - if (strcmp(emotion, "neutral") == 0 && child_count > 0) { - // Stop GIF animation if running - if (gif_controller_) { - gif_controller_->Stop(); - gif_controller_.reset(); - } - - lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); - } -#endif -} - -void LcdDisplay::SetTheme(Theme* theme) { - DisplayLockGuard lock(this); - - auto lvgl_theme = static_cast(theme); - - // Get the active screen - lv_obj_t* screen = lv_screen_active(); - - // Set font - auto text_font = lvgl_theme->text_font()->font(); - auto icon_font = lvgl_theme->icon_font()->font(); - auto large_icon_font = lvgl_theme->large_icon_font()->font(); - - if (text_font->line_height >= 40) { - lv_obj_set_style_text_font(mute_label_, large_icon_font, 0); - lv_obj_set_style_text_font(battery_label_, large_icon_font, 0); - lv_obj_set_style_text_font(network_label_, large_icon_font, 0); - } else { - lv_obj_set_style_text_font(mute_label_, icon_font, 0); - lv_obj_set_style_text_font(battery_label_, icon_font, 0); - lv_obj_set_style_text_font(network_label_, icon_font, 0); - } - - // Set parent text color - lv_obj_set_style_text_font(screen, text_font, 0); - lv_obj_set_style_text_color(screen, lvgl_theme->text_color(), 0); - - // Set background image - if (lvgl_theme->background_image() != nullptr) { - lv_obj_set_style_bg_image_src(container_, lvgl_theme->background_image()->image_dsc(), 0); - } else { - lv_obj_set_style_bg_image_src(container_, nullptr, 0); - lv_obj_set_style_bg_color(container_, lvgl_theme->background_color(), 0); - } - - // Update status bar background color with 50% opacity - lv_obj_set_style_bg_opa(status_bar_, LV_OPA_50, 0); - lv_obj_set_style_bg_color(status_bar_, lvgl_theme->background_color(), 0); - - // Update status bar elements - lv_obj_set_style_text_color(network_label_, lvgl_theme->text_color(), 0); - lv_obj_set_style_text_color(status_label_, lvgl_theme->text_color(), 0); - lv_obj_set_style_text_color(notification_label_, lvgl_theme->text_color(), 0); - lv_obj_set_style_text_color(mute_label_, lvgl_theme->text_color(), 0); - lv_obj_set_style_text_color(battery_label_, lvgl_theme->text_color(), 0); - lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0); - - // Set content background opacity - lv_obj_set_style_bg_opa(content_, LV_OPA_TRANSP, 0); - - // If we have the chat message style, update all message bubbles -#if CONFIG_USE_WECHAT_MESSAGE_STYLE - // Iterate through all children of content (message containers or bubbles) - uint32_t child_count = lv_obj_get_child_cnt(content_); - for (uint32_t i = 0; i < child_count; i++) { - lv_obj_t* obj = lv_obj_get_child(content_, i); - if (obj == nullptr) continue; - - lv_obj_t* bubble = nullptr; - - // 检查这个对象是容器还是气泡 - // 如果是容器(用户或系统消息),则获取其子对象作为气泡 - // 如果是气泡(助手消息),则直接使用 - if (lv_obj_get_child_cnt(obj) > 0) { - // 可能是容器,检查它是否为用户或系统消息容器 - // 用户和系统消息容器是透明的 - lv_opa_t bg_opa = lv_obj_get_style_bg_opa(obj, 0); - if (bg_opa == LV_OPA_TRANSP) { - // 这是用户或系统消息的容器 - bubble = lv_obj_get_child(obj, 0); - } else { - // 这可能是助手消息的气泡自身 - bubble = obj; - } - } else { - // 没有子元素,可能是其他UI元素,跳过 - continue; - } - - if (bubble == nullptr) continue; - - // 使用保存的用户数据来识别气泡类型 - void* bubble_type_ptr = lv_obj_get_user_data(bubble); - if (bubble_type_ptr != nullptr) { - const char* bubble_type = static_cast(bubble_type_ptr); - - // 根据气泡类型应用正确的颜色 - if (strcmp(bubble_type, "user") == 0) { - lv_obj_set_style_bg_color(bubble, lvgl_theme->user_bubble_color(), 0); - } else if (strcmp(bubble_type, "assistant") == 0) { - lv_obj_set_style_bg_color(bubble, lvgl_theme->assistant_bubble_color(), 0); - } else if (strcmp(bubble_type, "system") == 0) { - lv_obj_set_style_bg_color(bubble, lvgl_theme->system_bubble_color(), 0); - } else if (strcmp(bubble_type, "image") == 0) { - lv_obj_set_style_bg_color(bubble, lvgl_theme->system_bubble_color(), 0); - } - - // Update border color - lv_obj_set_style_border_color(bubble, lvgl_theme->border_color(), 0); - - // Update text color for the message - if (lv_obj_get_child_cnt(bubble) > 0) { - lv_obj_t* text = lv_obj_get_child(bubble, 0); - if (text != nullptr) { - // 根据气泡类型设置文本颜色 - if (strcmp(bubble_type, "system") == 0) { - lv_obj_set_style_text_color(text, lvgl_theme->system_text_color(), 0); - } else { - lv_obj_set_style_text_color(text, lvgl_theme->text_color(), 0); - } - } - } - } else { - ESP_LOGW(TAG, "child[%lu] Bubble type is not found", i); - } - } -#else - // Simple UI mode - just update the main chat message - if (chat_message_label_ != nullptr) { - lv_obj_set_style_text_color(chat_message_label_, lvgl_theme->text_color(), 0); - } - - if (emoji_label_ != nullptr) { - lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0); - } -#endif - - // Update low battery popup - lv_obj_set_style_bg_color(low_battery_popup_, lvgl_theme->low_battery_color(), 0); - - // No errors occurred. Save theme to settings - Display::SetTheme(lvgl_theme); -} +#include "lcd_display.h" +#include "gif/lvgl_gif.h" +#include "settings.h" +#include "lvgl_theme.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "board.h" + +#define TAG "LcdDisplay" + +LV_FONT_DECLARE(BUILTIN_TEXT_FONT); +LV_FONT_DECLARE(BUILTIN_ICON_FONT); +LV_FONT_DECLARE(font_awesome_30_4); + +void LcdDisplay::InitializeLcdThemes() { + auto text_font = std::make_shared(&BUILTIN_TEXT_FONT); + auto icon_font = std::make_shared(&BUILTIN_ICON_FONT); + auto large_icon_font = std::make_shared(&font_awesome_30_4); + + // light theme + auto light_theme = new LvglTheme("light"); + light_theme->set_background_color(lv_color_hex(0xFFFFFF)); //rgb(255, 255, 255) + light_theme->set_text_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) + light_theme->set_chat_background_color(lv_color_hex(0xE0E0E0)); //rgb(224, 224, 224) + light_theme->set_user_bubble_color(lv_color_hex(0x00FF00)); //rgb(0, 128, 0) + light_theme->set_assistant_bubble_color(lv_color_hex(0xDDDDDD)); //rgb(221, 221, 221) + light_theme->set_system_bubble_color(lv_color_hex(0xFFFFFF)); //rgb(255, 255, 255) + light_theme->set_system_text_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) + light_theme->set_border_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) + light_theme->set_low_battery_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) + light_theme->set_text_font(text_font); + light_theme->set_icon_font(icon_font); + light_theme->set_large_icon_font(large_icon_font); + + // dark theme + auto dark_theme = new LvglTheme("dark"); + dark_theme->set_background_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) + dark_theme->set_text_color(lv_color_hex(0xFFFFFF)); //rgb(255, 255, 255) + dark_theme->set_chat_background_color(lv_color_hex(0x1F1F1F)); //rgb(31, 31, 31) + dark_theme->set_user_bubble_color(lv_color_hex(0x00FF00)); //rgb(0, 128, 0) + dark_theme->set_assistant_bubble_color(lv_color_hex(0x222222)); //rgb(34, 34, 34) + dark_theme->set_system_bubble_color(lv_color_hex(0x000000)); //rgb(0, 0, 0) + dark_theme->set_system_text_color(lv_color_hex(0xFFFFFF)); //rgb(255, 255, 255) + dark_theme->set_border_color(lv_color_hex(0xFFFFFF)); //rgb(255, 255, 255) + dark_theme->set_low_battery_color(lv_color_hex(0xFF0000)); //rgb(255, 0, 0) + dark_theme->set_text_font(text_font); + dark_theme->set_icon_font(icon_font); + dark_theme->set_large_icon_font(large_icon_font); + + auto& theme_manager = LvglThemeManager::GetInstance(); + theme_manager.RegisterTheme("light", light_theme); + theme_manager.RegisterTheme("dark", dark_theme); +} + +LcdDisplay::LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height) + : panel_io_(panel_io), panel_(panel) { + width_ = width; + height_ = height; + + // Initialize LCD themes + InitializeLcdThemes(); + + // Load theme from settings + Settings settings("display", false); + std::string theme_name = settings.GetString("theme", "light"); + current_theme_ = LvglThemeManager::GetInstance().GetTheme(theme_name); + + // Create a timer to hide the preview image + esp_timer_create_args_t preview_timer_args = { + .callback = [](void* arg) { + LcdDisplay* display = static_cast(arg); + display->SetPreviewImage(nullptr); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "preview_timer", + .skip_unhandled_events = false, + }; + esp_timer_create(&preview_timer_args, &preview_timer_); +} + +SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) + : LcdDisplay(panel_io, panel, width, height) { + + // draw white + std::vector buffer(width_, 0xFFFF); + for (int y = 0; y < height_; y++) { + esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data()); + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + ESP_LOGI(TAG, "Initialize LVGL library"); + lv_init(); + +#if CONFIG_SPIRAM + // lv image cache, currently only PNG is supported + size_t psram_size_mb = esp_psram_get_size() / 1024 / 1024; + if (psram_size_mb >= 8) { + lv_image_cache_resize(2 * 1024 * 1024, true); + ESP_LOGI(TAG, "Use 2MB of PSRAM for image cache"); + } else if (psram_size_mb >= 2) { + lv_image_cache_resize(512 * 1024, true); + ESP_LOGI(TAG, "Use 512KB of PSRAM for image cache"); + } +#endif + + ESP_LOGI(TAG, "Initialize LVGL port"); + lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + port_cfg.task_priority = 1; +#if CONFIG_SOC_CPU_CORES_NUM > 1 + port_cfg.task_affinity = 1; +#endif + lvgl_port_init(&port_cfg); + + ESP_LOGI(TAG, "Adding LCD display"); + const lvgl_port_display_cfg_t display_cfg = { + .io_handle = panel_io_, + .panel_handle = panel_, + .control_handle = nullptr, + .buffer_size = static_cast(width_ * 20), + .double_buffer = false, + .trans_size = 0, + .hres = static_cast(width_), + .vres = static_cast(height_), + .monochrome = false, + .rotation = { + .swap_xy = swap_xy, + .mirror_x = mirror_x, + .mirror_y = mirror_y, + }, + .color_format = LV_COLOR_FORMAT_RGB565, + .flags = { + .buff_dma = 1, + .buff_spiram = 0, + .sw_rotate = 0, + .swap_bytes = 1, + .full_refresh = 0, + .direct_mode = 0, + }, + }; + + display_ = lvgl_port_add_disp(&display_cfg); + if (display_ == nullptr) { + ESP_LOGE(TAG, "Failed to add display"); + return; + } + + if (offset_x != 0 || offset_y != 0) { + lv_display_set_offset(display_, offset_x, offset_y); + } + + SetupUI(); +} + +// RGB LCD实现 +RgbLcdDisplay::RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, + bool mirror_x, bool mirror_y, bool swap_xy) + : LcdDisplay(panel_io, panel, width, height) { + + // draw white + std::vector buffer(width_, 0xFFFF); + for (int y = 0; y < height_; y++) { + esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data()); + } + + ESP_LOGI(TAG, "Initialize LVGL library"); + lv_init(); + + ESP_LOGI(TAG, "Initialize LVGL port"); + lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + port_cfg.task_priority = 1; + port_cfg.timer_period_ms = 50; + lvgl_port_init(&port_cfg); + + ESP_LOGI(TAG, "Adding LCD display"); + const lvgl_port_display_cfg_t display_cfg = { + .io_handle = panel_io_, + .panel_handle = panel_, + .buffer_size = static_cast(width_ * 20), + .double_buffer = true, + .hres = static_cast(width_), + .vres = static_cast(height_), + .rotation = { + .swap_xy = swap_xy, + .mirror_x = mirror_x, + .mirror_y = mirror_y, + }, + .flags = { + .buff_dma = 1, + .swap_bytes = 0, + .full_refresh = 1, + .direct_mode = 1, + }, + }; + + const lvgl_port_display_rgb_cfg_t rgb_cfg = { + .flags = { + .bb_mode = true, + .avoid_tearing = true, + } + }; + + display_ = lvgl_port_add_disp_rgb(&display_cfg, &rgb_cfg); + if (display_ == nullptr) { + ESP_LOGE(TAG, "Failed to add RGB display"); + return; + } + + if (offset_x != 0 || offset_y != 0) { + lv_display_set_offset(display_, offset_x, offset_y); + } + + SetupUI(); +} + +MipiLcdDisplay::MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, + bool mirror_x, bool mirror_y, bool swap_xy) + : LcdDisplay(panel_io, panel, width, height) { + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + ESP_LOGI(TAG, "Initialize LVGL library"); + lv_init(); + + ESP_LOGI(TAG, "Initialize LVGL port"); + lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + lvgl_port_init(&port_cfg); + + ESP_LOGI(TAG, "Adding LCD display"); + const lvgl_port_display_cfg_t disp_cfg = { + .io_handle = panel_io, + .panel_handle = panel, + .control_handle = nullptr, + .buffer_size = static_cast(width_ * 50), + .double_buffer = false, + .hres = static_cast(width_), + .vres = static_cast(height_), + .monochrome = false, + /* Rotation values must be same as used in esp_lcd for initial settings of the screen */ + .rotation = { + .swap_xy = swap_xy, + .mirror_x = mirror_x, + .mirror_y = mirror_y, + }, + .flags = { + .buff_dma = true, + .buff_spiram =false, + .sw_rotate = false, + }, + }; + + const lvgl_port_display_dsi_cfg_t dpi_cfg = { + .flags = { + .avoid_tearing = false, + } + }; + display_ = lvgl_port_add_disp_dsi(&disp_cfg, &dpi_cfg); + if (display_ == nullptr) { + ESP_LOGE(TAG, "Failed to add display"); + return; + } + + if (offset_x != 0 || offset_y != 0) { + lv_display_set_offset(display_, offset_x, offset_y); + } + + SetupUI(); +} + +LcdDisplay::~LcdDisplay() { + SetPreviewImage(nullptr); + + // Clean up GIF controller + if (gif_controller_) { + gif_controller_->Stop(); + gif_controller_.reset(); + } + + if (preview_timer_ != nullptr) { + esp_timer_stop(preview_timer_); + esp_timer_delete(preview_timer_); + } + + // Clean up vinyl rotation animation + if (vinyl_rotation_anim_) { + if (music_vinyl_record_) { + // 删除该对象的所有动画 + lv_anim_del(music_vinyl_record_, nullptr); + } + delete vinyl_rotation_anim_; + vinyl_rotation_anim_ = nullptr; + } + + if (preview_image_ != nullptr) { + lv_obj_del(preview_image_); + } + if (chat_message_label_ != nullptr) { + lv_obj_del(chat_message_label_); + } + if (emoji_label_ != nullptr) { + lv_obj_del(emoji_label_); + } + if (emoji_image_ != nullptr) { + lv_obj_del(emoji_image_); + } + if (emoji_box_ != nullptr) { + lv_obj_del(emoji_box_); + } + if (content_ != nullptr) { + lv_obj_del(content_); + } + if (status_bar_ != nullptr) { + lv_obj_del(status_bar_); + } + if (side_bar_ != nullptr) { + lv_obj_del(side_bar_); + } + if (container_ != nullptr) { + lv_obj_del(container_); + } + if (display_ != nullptr) { + lv_display_delete(display_); + } + + if (panel_ != nullptr) { + esp_lcd_panel_del(panel_); + } + if (panel_io_ != nullptr) { + esp_lcd_panel_io_del(panel_io_); + } +} + +bool LcdDisplay::Lock(int timeout_ms) { + return lvgl_port_lock(timeout_ms); +} + +void LcdDisplay::Unlock() { + lvgl_port_unlock(); +} + +#if CONFIG_USE_WECHAT_MESSAGE_STYLE +void LcdDisplay::SetupUI() { + DisplayLockGuard lock(this); + + auto lvgl_theme = static_cast(current_theme_); + auto text_font = lvgl_theme->text_font()->font(); + auto icon_font = lvgl_theme->icon_font()->font(); + auto large_icon_font = lvgl_theme->large_icon_font()->font(); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, text_font, 0); + lv_obj_set_style_text_color(screen, lvgl_theme->text_color(), 0); + lv_obj_set_style_bg_color(screen, lvgl_theme->background_color(), 0); + + /* Container */ + container_ = lv_obj_create(screen); + lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); + lv_obj_set_style_radius(container_, 0, 0); + lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(container_, 0, 0); + lv_obj_set_style_border_width(container_, 0, 0); + lv_obj_set_style_pad_row(container_, 0, 0); + lv_obj_set_style_bg_color(container_, lvgl_theme->background_color(), 0); + lv_obj_set_style_border_color(container_, lvgl_theme->border_color(), 0); + + /* Status bar */ + status_bar_ = lv_obj_create(container_); + lv_obj_set_size(status_bar_, LV_HOR_RES, LV_SIZE_CONTENT); + lv_obj_set_style_radius(status_bar_, 0, 0); + lv_obj_set_style_bg_color(status_bar_, lvgl_theme->background_color(), 0); + lv_obj_set_style_text_color(status_bar_, lvgl_theme->text_color(), 0); + + /* Content - Chat area */ + content_ = lv_obj_create(container_); + lv_obj_set_style_radius(content_, 0, 0); + lv_obj_set_width(content_, LV_HOR_RES); + lv_obj_set_flex_grow(content_, 1); + lv_obj_set_style_pad_all(content_, lvgl_theme->spacing(4), 0); + lv_obj_set_style_border_width(content_, 0, 0); + lv_obj_set_style_bg_color(content_, lvgl_theme->chat_background_color(), 0); // Background for chat area + + // Enable scrolling for chat content + lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_scroll_dir(content_, LV_DIR_VER); + + // Create a flex container for chat messages + lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_set_style_pad_row(content_, lvgl_theme->spacing(4), 0); // Space between messages + + // We'll create chat messages dynamically in SetChatMessage + chat_message_label_ = nullptr; + + /* Status bar */ + lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_column(status_bar_, 0, 0); + lv_obj_set_style_pad_top(status_bar_, lvgl_theme->spacing(2), 0); + lv_obj_set_style_pad_bottom(status_bar_, lvgl_theme->spacing(2), 0); + lv_obj_set_style_pad_left(status_bar_, lvgl_theme->spacing(4), 0); + lv_obj_set_style_pad_right(status_bar_, lvgl_theme->spacing(4), 0); + lv_obj_set_scrollbar_mode(status_bar_, LV_SCROLLBAR_MODE_OFF); + // 设置状态栏的内容垂直居中 + lv_obj_set_flex_align(status_bar_, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + network_label_ = lv_label_create(status_bar_); + lv_label_set_text(network_label_, ""); + lv_obj_set_style_text_font(network_label_, icon_font, 0); + lv_obj_set_style_text_color(network_label_, lvgl_theme->text_color(), 0); + + notification_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(notification_label_, 1); + lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_color(notification_label_, lvgl_theme->text_color(), 0); + lv_label_set_text(notification_label_, ""); + lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + status_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(status_label_, 1); + lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_color(status_label_, lvgl_theme->text_color(), 0); + lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); + + mute_label_ = lv_label_create(status_bar_); + lv_label_set_text(mute_label_, ""); + lv_obj_set_style_text_font(mute_label_, icon_font, 0); + lv_obj_set_style_text_color(mute_label_, lvgl_theme->text_color(), 0); + + battery_label_ = lv_label_create(status_bar_); + lv_label_set_text(battery_label_, ""); + lv_obj_set_style_text_font(battery_label_, icon_font, 0); + lv_obj_set_style_text_color(battery_label_, lvgl_theme->text_color(), 0); + lv_obj_set_style_margin_left(battery_label_, lvgl_theme->spacing(2), 0); // 添加左边距,与前面的元素分隔 + + low_battery_popup_ = lv_obj_create(screen); + lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2); + lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, -lvgl_theme->spacing(4)); + lv_obj_set_style_bg_color(low_battery_popup_, lvgl_theme->low_battery_color(), 0); + lv_obj_set_style_radius(low_battery_popup_, lvgl_theme->spacing(4), 0); + low_battery_label_ = lv_label_create(low_battery_popup_); + lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE); + lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0); + lv_obj_center(low_battery_label_); + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + + emoji_image_ = lv_img_create(screen); + lv_obj_align(emoji_image_, LV_ALIGN_TOP_MID, 0, text_font->line_height + lvgl_theme->spacing(8)); + + // Display AI logo while booting + emoji_label_ = lv_label_create(screen); + lv_obj_center(emoji_label_); + lv_obj_set_style_text_font(emoji_label_, large_icon_font, 0); + lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0); + lv_label_set_text(emoji_label_, FONT_AWESOME_MICROCHIP_AI); +} +#if CONFIG_IDF_TARGET_ESP32P4 +#define MAX_MESSAGES 40 +#else +#define MAX_MESSAGES 20 +#endif +void LcdDisplay::SetChatMessage(const char* role, const char* content) { + DisplayLockGuard lock(this); + if (content_ == nullptr) { + return; + } + + // 检查消息数量是否超过限制 + uint32_t child_count = lv_obj_get_child_cnt(content_); + if (child_count >= MAX_MESSAGES) { + // 删除最早的消息(第一个子对象) + lv_obj_t* first_child = lv_obj_get_child(content_, 0); + lv_obj_t* last_child = lv_obj_get_child(content_, child_count - 1); + if (first_child != nullptr) { + lv_obj_del(first_child); + } + // Scroll to the last message immediately + if (last_child != nullptr) { + lv_obj_scroll_to_view_recursive(last_child, LV_ANIM_OFF); + } + } + + // 折叠系统消息(如果是系统消息,检查最后一个消息是否也是系统消息) + if (strcmp(role, "system") == 0) { + if (child_count > 0) { + // 获取最后一个消息容器 + lv_obj_t* last_container = lv_obj_get_child(content_, child_count - 1); + if (last_container != nullptr && lv_obj_get_child_cnt(last_container) > 0) { + // 获取容器内的气泡 + lv_obj_t* last_bubble = lv_obj_get_child(last_container, 0); + if (last_bubble != nullptr) { + // 检查气泡类型是否为系统消息 + void* bubble_type_ptr = lv_obj_get_user_data(last_bubble); + if (bubble_type_ptr != nullptr && strcmp((const char*)bubble_type_ptr, "system") == 0) { + // 如果最后一个消息也是系统消息,则删除它 + lv_obj_del(last_container); + } + } + } + } + } else { + // 隐藏居中显示的 AI logo + lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); + } + + //避免出现空的消息框 + if(strlen(content) == 0) { + return; + } + + auto lvgl_theme = static_cast(current_theme_); + auto text_font = lvgl_theme->text_font()->font(); + + // Create a message bubble + lv_obj_t* msg_bubble = lv_obj_create(content_); + lv_obj_set_style_radius(msg_bubble, 8, 0); + lv_obj_set_scrollbar_mode(msg_bubble, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_border_width(msg_bubble, 0, 0); + lv_obj_set_style_pad_all(msg_bubble, lvgl_theme->spacing(4), 0); + + // Create the message text + lv_obj_t* msg_text = lv_label_create(msg_bubble); + lv_label_set_text(msg_text, content); + + // 计算文本实际宽度 + lv_coord_t text_width = lv_txt_get_width(content, strlen(content), text_font, 0); + + // 计算气泡宽度 + lv_coord_t max_width = LV_HOR_RES * 85 / 100 - 16; // 屏幕宽度的85% + lv_coord_t min_width = 20; + lv_coord_t bubble_width; + + // 确保文本宽度不小于最小宽度 + if (text_width < min_width) { + text_width = min_width; + } + + // 如果文本宽度小于最大宽度,使用文本宽度 + if (text_width < max_width) { + bubble_width = text_width; + } else { + bubble_width = max_width; + } + + // 设置消息文本的宽度 + lv_obj_set_width(msg_text, bubble_width); // 减去padding + lv_label_set_long_mode(msg_text, LV_LABEL_LONG_WRAP); + + // 设置气泡宽度 + lv_obj_set_width(msg_bubble, bubble_width); + lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT); + + // Set alignment and style based on message role + if (strcmp(role, "user") == 0) { + // User messages are right-aligned with green background + lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->user_bubble_color(), 0); + lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0); + // Set text color for contrast + lv_obj_set_style_text_color(msg_text, lvgl_theme->text_color(), 0); + + // 设置自定义属性标记气泡类型 + lv_obj_set_user_data(msg_bubble, (void*)"user"); + + // Set appropriate width for content + lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT); + lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT); + + // Don't grow + lv_obj_set_style_flex_grow(msg_bubble, 0, 0); + } else if (strcmp(role, "assistant") == 0) { + // Assistant messages are left-aligned with white background + lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->assistant_bubble_color(), 0); + lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0); + // Set text color for contrast + lv_obj_set_style_text_color(msg_text, lvgl_theme->text_color(), 0); + + // 设置自定义属性标记气泡类型 + lv_obj_set_user_data(msg_bubble, (void*)"assistant"); + + // Set appropriate width for content + lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT); + lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT); + + // Don't grow + lv_obj_set_style_flex_grow(msg_bubble, 0, 0); + } else if (strcmp(role, "system") == 0) { + // System messages are center-aligned with light gray background + lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->system_bubble_color(), 0); + lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0); + // Set text color for contrast + lv_obj_set_style_text_color(msg_text, lvgl_theme->system_text_color(), 0); + + // 设置自定义属性标记气泡类型 + lv_obj_set_user_data(msg_bubble, (void*)"system"); + + // Set appropriate width for content + lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT); + lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT); + + // Don't grow + lv_obj_set_style_flex_grow(msg_bubble, 0, 0); + } + + // Create a full-width container for user messages to ensure right alignment + if (strcmp(role, "user") == 0) { + // Create a full-width container + lv_obj_t* container = lv_obj_create(content_); + lv_obj_set_width(container, LV_HOR_RES); + lv_obj_set_height(container, LV_SIZE_CONTENT); + + // Make container transparent and borderless + lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(container, 0, 0); + lv_obj_set_style_pad_all(container, 0, 0); + + // Move the message bubble into this container + lv_obj_set_parent(msg_bubble, container); + + // Right align the bubble in the container + lv_obj_align(msg_bubble, LV_ALIGN_RIGHT_MID, -25, 0); + + // Auto-scroll to this container + lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON); + } else if (strcmp(role, "system") == 0) { + // 为系统消息创建全宽容器以确保居中对齐 + lv_obj_t* container = lv_obj_create(content_); + lv_obj_set_width(container, LV_HOR_RES); + lv_obj_set_height(container, LV_SIZE_CONTENT); + + // 使容器透明且无边框 + lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(container, 0, 0); + lv_obj_set_style_pad_all(container, 0, 0); + + // 将消息气泡移入此容器 + lv_obj_set_parent(msg_bubble, container); + + // 将气泡居中对齐在容器中 + lv_obj_align(msg_bubble, LV_ALIGN_CENTER, 0, 0); + + // 自动滚动底部 + lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON); + } else { + // For assistant messages + // Left align assistant messages + lv_obj_align(msg_bubble, LV_ALIGN_LEFT_MID, 0, 0); + + // Auto-scroll to the message bubble + lv_obj_scroll_to_view_recursive(msg_bubble, LV_ANIM_ON); + } + + // Store reference to the latest message label + chat_message_label_ = msg_text; +} + +void LcdDisplay::SetPreviewImage(std::unique_ptr image) { + DisplayLockGuard lock(this); + if (content_ == nullptr) { + return; + } + + if (image == nullptr) { + return; + } + + auto lvgl_theme = static_cast(current_theme_); + // Create a message bubble for image preview + lv_obj_t* img_bubble = lv_obj_create(content_); + lv_obj_set_style_radius(img_bubble, 8, 0); + lv_obj_set_scrollbar_mode(img_bubble, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_border_width(img_bubble, 0, 0); + lv_obj_set_style_pad_all(img_bubble, lvgl_theme->spacing(4), 0); + + // Set image bubble background color (similar to system message) + lv_obj_set_style_bg_color(img_bubble, lvgl_theme->assistant_bubble_color(), 0); + lv_obj_set_style_bg_opa(img_bubble, LV_OPA_70, 0); + + // 设置自定义属性标记气泡类型 + lv_obj_set_user_data(img_bubble, (void*)"image"); + + // Create the image object inside the bubble + lv_obj_t* preview_image = lv_image_create(img_bubble); + + // Calculate appropriate size for the image + lv_coord_t max_width = LV_HOR_RES * 70 / 100; // 70% of screen width + lv_coord_t max_height = LV_VER_RES * 50 / 100; // 50% of screen height + + // Calculate zoom factor to fit within maximum dimensions + auto img_dsc = image->image_dsc(); + lv_coord_t img_width = img_dsc->header.w; + lv_coord_t img_height = img_dsc->header.h; + if (img_width == 0 || img_height == 0) { + img_width = max_width; + img_height = max_height; + ESP_LOGW(TAG, "Invalid image dimensions: %ld x %ld, using default dimensions: %ld x %ld", img_width, img_height, max_width, max_height); + } + + lv_coord_t zoom_w = (max_width * 256) / img_width; + lv_coord_t zoom_h = (max_height * 256) / img_height; + lv_coord_t zoom = (zoom_w < zoom_h) ? zoom_w : zoom_h; + + // Ensure zoom doesn't exceed 256 (100%) + if (zoom > 256) zoom = 256; + + // Set image properties + lv_image_set_src(preview_image, img_dsc); + lv_image_set_scale(preview_image, zoom); + + // Add event handler to clean up LvglImage when image is deleted + // We need to transfer ownership of the unique_ptr to the event callback + LvglImage* raw_image = image.release(); // 释放智能指针的所有权 + lv_obj_add_event_cb(preview_image, [](lv_event_t* e) { + LvglImage* img = (LvglImage*)lv_event_get_user_data(e); + if (img != nullptr) { + delete img; // 通过删除 LvglImage 对象来正确释放内存 + } + }, LV_EVENT_DELETE, (void*)raw_image); + + // Calculate actual scaled image dimensions + lv_coord_t scaled_width = (img_width * zoom) / 256; + lv_coord_t scaled_height = (img_height * zoom) / 256; + + // Set bubble size to be 16 pixels larger than the image (8 pixels on each side) + lv_obj_set_width(img_bubble, scaled_width + 16); + lv_obj_set_height(img_bubble, scaled_height + 16); + + // Don't grow in flex layout + lv_obj_set_style_flex_grow(img_bubble, 0, 0); + + // Center the image within the bubble + lv_obj_center(preview_image); + + // Left align the image bubble like assistant messages + lv_obj_align(img_bubble, LV_ALIGN_LEFT_MID, 0, 0); + + // Auto-scroll to the image bubble + lv_obj_scroll_to_view_recursive(img_bubble, LV_ANIM_ON); +} +#else +void LcdDisplay::SetupUI() { + DisplayLockGuard lock(this); + LvglTheme* lvgl_theme = static_cast(current_theme_); + auto text_font = lvgl_theme->text_font()->font(); + auto icon_font = lvgl_theme->icon_font()->font(); + auto large_icon_font = lvgl_theme->large_icon_font()->font(); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, text_font, 0); + lv_obj_set_style_text_color(screen, lvgl_theme->text_color(), 0); + lv_obj_set_style_bg_color(screen, lvgl_theme->background_color(), 0); + + /* Container */ + container_ = lv_obj_create(screen); + lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); + lv_obj_set_style_radius(container_, 0, 0); + lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(container_, 0, 0); + lv_obj_set_style_border_width(container_, 0, 0); + lv_obj_set_style_pad_row(container_, 0, 0); + lv_obj_set_style_bg_color(container_, lvgl_theme->background_color(), 0); + lv_obj_set_style_border_color(container_, lvgl_theme->border_color(), 0); + + /* Status bar */ + status_bar_ = lv_obj_create(container_); + lv_obj_set_size(status_bar_, LV_HOR_RES, LV_SIZE_CONTENT); + lv_obj_set_style_radius(status_bar_, 0, 0); + lv_obj_set_style_bg_color(status_bar_, lvgl_theme->background_color(), 0); + lv_obj_set_style_text_color(status_bar_, lvgl_theme->text_color(), 0); + lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_top(status_bar_, lvgl_theme->spacing(2), 0); + lv_obj_set_style_pad_bottom(status_bar_, lvgl_theme->spacing(2), 0); + lv_obj_set_style_pad_left(status_bar_, lvgl_theme->spacing(4), 0); + lv_obj_set_style_pad_right(status_bar_, lvgl_theme->spacing(4), 0); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_column(status_bar_, 0, 0); + + /* Content */ + content_ = lv_obj_create(container_); + lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_radius(content_, 0, 0); + lv_obj_set_width(content_, LV_HOR_RES); + lv_obj_set_flex_grow(content_, 1); + lv_obj_set_style_pad_all(content_, 0, 0); + lv_obj_set_style_border_width(content_, 0, 0); + lv_obj_set_style_bg_color(content_, lvgl_theme->chat_background_color(), 0); + + lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); // 垂直布局(从上到下) + lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY); // 子对象居中对齐,等距分布 + + emoji_box_ = lv_obj_create(content_); + lv_obj_set_size(emoji_box_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_bg_opa(emoji_box_, LV_OPA_TRANSP, 0); + lv_obj_set_style_pad_all(emoji_box_, 0, 0); + lv_obj_set_style_border_width(emoji_box_, 0, 0); + + emoji_label_ = lv_label_create(emoji_box_); + lv_obj_set_style_text_font(emoji_label_, large_icon_font, 0); + lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0); + lv_label_set_text(emoji_label_, FONT_AWESOME_MICROCHIP_AI); + + emoji_image_ = lv_img_create(emoji_box_); + lv_obj_center(emoji_image_); + lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN); + + preview_image_ = lv_image_create(content_); + lv_obj_set_size(preview_image_, width_ / 2, height_ / 2); + lv_obj_align(preview_image_, LV_ALIGN_CENTER, 0, 0); + lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN); + + chat_message_label_ = lv_label_create(content_); + lv_label_set_text(chat_message_label_, ""); + lv_obj_set_width(chat_message_label_, width_ * 0.9); // 限制宽度为屏幕宽度的 90% + lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式 + lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐 + lv_obj_set_style_text_color(chat_message_label_, lvgl_theme->text_color(), 0); + + /* Status bar */ + network_label_ = lv_label_create(status_bar_); + lv_label_set_text(network_label_, ""); + lv_obj_set_style_text_font(network_label_, icon_font, 0); + lv_obj_set_style_text_color(network_label_, lvgl_theme->text_color(), 0); + + notification_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(notification_label_, 1); + lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_color(notification_label_, lvgl_theme->text_color(), 0); + lv_label_set_text(notification_label_, ""); + lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + status_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(status_label_, 1); + lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_color(status_label_, lvgl_theme->text_color(), 0); + lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); + + mute_label_ = lv_label_create(status_bar_); + lv_label_set_text(mute_label_, ""); + lv_obj_set_style_text_font(mute_label_, icon_font, 0); + lv_obj_set_style_text_color(mute_label_, lvgl_theme->text_color(), 0); + + battery_label_ = lv_label_create(status_bar_); + lv_label_set_text(battery_label_, ""); + lv_obj_set_style_text_font(battery_label_, icon_font, 0); + lv_obj_set_style_text_color(battery_label_, lvgl_theme->text_color(), 0); + + low_battery_popup_ = lv_obj_create(screen); + lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2); + lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, -lvgl_theme->spacing(4)); + lv_obj_set_style_bg_color(low_battery_popup_, lvgl_theme->low_battery_color(), 0); + lv_obj_set_style_radius(low_battery_popup_, lvgl_theme->spacing(4), 0); + + low_battery_label_ = lv_label_create(low_battery_popup_); + lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE); + lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0); + lv_obj_center(low_battery_label_); + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); +} + +void LcdDisplay::SetPreviewImage(std::unique_ptr image) { + DisplayLockGuard lock(this); + if (preview_image_ == nullptr) { + ESP_LOGE(TAG, "Preview image is not initialized"); + return; + } + + if (image == nullptr) { + esp_timer_stop(preview_timer_); + lv_obj_remove_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN); + preview_image_cached_.reset(); + if (gif_controller_) { + gif_controller_->Start(); + } + return; + } + + preview_image_cached_ = std::move(image); + auto img_dsc = preview_image_cached_->image_dsc(); + // 设置图片源并显示预览图片 + lv_image_set_src(preview_image_, img_dsc); + if (img_dsc->header.w > 0 && img_dsc->header.h > 0) { + // zoom factor 0.5 + lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w); + } + + // Hide emoji_box_ + if (gif_controller_) { + gif_controller_->Stop(); + } + lv_obj_add_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(preview_image_, LV_OBJ_FLAG_HIDDEN); + esp_timer_stop(preview_timer_); + ESP_ERROR_CHECK(esp_timer_start_once(preview_timer_, PREVIEW_IMAGE_DURATION_MS * 1000)); +} + +void LcdDisplay::SetChatMessage(const char* role, const char* content) { + DisplayLockGuard lock(this); + if (chat_message_label_ == nullptr) { + return; + } + lv_label_set_text(chat_message_label_, content); +} +#endif + +void LcdDisplay::SetEmotion(const char* emotion) { + // Stop any running GIF animation + if (gif_controller_) { + DisplayLockGuard lock(this); + gif_controller_->Stop(); + gif_controller_.reset(); + } + + if (emoji_image_ == nullptr) { + return; + } + + auto emoji_collection = static_cast(current_theme_)->emoji_collection(); + auto image = emoji_collection != nullptr ? emoji_collection->GetEmojiImage(emotion) : nullptr; + if (image == nullptr) { + const char* utf8 = font_awesome_get_utf8(emotion); + if (utf8 != nullptr && emoji_label_ != nullptr) { + DisplayLockGuard lock(this); + lv_label_set_text(emoji_label_, utf8); + lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); + } + return; + } + + DisplayLockGuard lock(this); + if (image->IsGif()) { + // Create new GIF controller + gif_controller_ = std::make_unique(image->image_dsc()); + + if (gif_controller_->IsLoaded()) { + // Set up frame update callback + gif_controller_->SetFrameCallback([this]() { + lv_image_set_src(emoji_image_, gif_controller_->image_dsc()); + }); + + // Set initial frame and start animation + lv_image_set_src(emoji_image_, gif_controller_->image_dsc()); + gif_controller_->Start(); + + // Show GIF, hide others + lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN); + } else { + ESP_LOGE(TAG, "Failed to load GIF for emotion: %s", emotion); + gif_controller_.reset(); + } + } else { + lv_image_set_src(emoji_image_, image->image_dsc()); + lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN); + } + +#if CONFIG_USE_WECHAT_MESSAGE_STYLE + // Wechat message style中,如果emotion是neutral,则不显示 + uint32_t child_count = lv_obj_get_child_cnt(content_); + if (strcmp(emotion, "neutral") == 0 && child_count > 0) { + // Stop GIF animation if running + if (gif_controller_) { + gif_controller_->Stop(); + gif_controller_.reset(); + } + + lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN); + } +#endif +} + +void LcdDisplay::SetTheme(Theme* theme) { + DisplayLockGuard lock(this); + + auto lvgl_theme = static_cast(theme); + + // Get the active screen + lv_obj_t* screen = lv_screen_active(); + + // Set font + auto text_font = lvgl_theme->text_font()->font(); + auto icon_font = lvgl_theme->icon_font()->font(); + auto large_icon_font = lvgl_theme->large_icon_font()->font(); + + if (text_font->line_height >= 40) { + lv_obj_set_style_text_font(mute_label_, large_icon_font, 0); + lv_obj_set_style_text_font(battery_label_, large_icon_font, 0); + lv_obj_set_style_text_font(network_label_, large_icon_font, 0); + } else { + lv_obj_set_style_text_font(mute_label_, icon_font, 0); + lv_obj_set_style_text_font(battery_label_, icon_font, 0); + lv_obj_set_style_text_font(network_label_, icon_font, 0); + } + + // Set parent text color + lv_obj_set_style_text_font(screen, text_font, 0); + lv_obj_set_style_text_color(screen, lvgl_theme->text_color(), 0); + + // Set background image + if (lvgl_theme->background_image() != nullptr) { + lv_obj_set_style_bg_image_src(container_, lvgl_theme->background_image()->image_dsc(), 0); + } else { + lv_obj_set_style_bg_image_src(container_, nullptr, 0); + lv_obj_set_style_bg_color(container_, lvgl_theme->background_color(), 0); + } + + // Update status bar background color with 50% opacity + lv_obj_set_style_bg_opa(status_bar_, LV_OPA_50, 0); + lv_obj_set_style_bg_color(status_bar_, lvgl_theme->background_color(), 0); + + // Update status bar elements + lv_obj_set_style_text_color(network_label_, lvgl_theme->text_color(), 0); + lv_obj_set_style_text_color(status_label_, lvgl_theme->text_color(), 0); + lv_obj_set_style_text_color(notification_label_, lvgl_theme->text_color(), 0); + lv_obj_set_style_text_color(mute_label_, lvgl_theme->text_color(), 0); + lv_obj_set_style_text_color(battery_label_, lvgl_theme->text_color(), 0); + lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0); + + // Set content background opacity + lv_obj_set_style_bg_opa(content_, LV_OPA_TRANSP, 0); + + // If we have the chat message style, update all message bubbles +#if CONFIG_USE_WECHAT_MESSAGE_STYLE + // Iterate through all children of content (message containers or bubbles) + uint32_t child_count = lv_obj_get_child_cnt(content_); + for (uint32_t i = 0; i < child_count; i++) { + lv_obj_t* obj = lv_obj_get_child(content_, i); + if (obj == nullptr) continue; + + lv_obj_t* bubble = nullptr; + + // 检查这个对象是容器还是气泡 + // 如果是容器(用户或系统消息),则获取其子对象作为气泡 + // 如果是气泡(助手消息),则直接使用 + if (lv_obj_get_child_cnt(obj) > 0) { + // 可能是容器,检查它是否为用户或系统消息容器 + // 用户和系统消息容器是透明的 + lv_opa_t bg_opa = lv_obj_get_style_bg_opa(obj, 0); + if (bg_opa == LV_OPA_TRANSP) { + // 这是用户或系统消息的容器 + bubble = lv_obj_get_child(obj, 0); + } else { + // 这可能是助手消息的气泡自身 + bubble = obj; + } + } else { + // 没有子元素,可能是其他UI元素,跳过 + continue; + } + + if (bubble == nullptr) continue; + + // 使用保存的用户数据来识别气泡类型 + void* bubble_type_ptr = lv_obj_get_user_data(bubble); + if (bubble_type_ptr != nullptr) { + const char* bubble_type = static_cast(bubble_type_ptr); + + // 根据气泡类型应用正确的颜色 + if (strcmp(bubble_type, "user") == 0) { + lv_obj_set_style_bg_color(bubble, lvgl_theme->user_bubble_color(), 0); + } else if (strcmp(bubble_type, "assistant") == 0) { + lv_obj_set_style_bg_color(bubble, lvgl_theme->assistant_bubble_color(), 0); + } else if (strcmp(bubble_type, "system") == 0) { + lv_obj_set_style_bg_color(bubble, lvgl_theme->system_bubble_color(), 0); + } else if (strcmp(bubble_type, "image") == 0) { + lv_obj_set_style_bg_color(bubble, lvgl_theme->system_bubble_color(), 0); + } + + // Update border color + lv_obj_set_style_border_color(bubble, lvgl_theme->border_color(), 0); + + // Update text color for the message + if (lv_obj_get_child_cnt(bubble) > 0) { + lv_obj_t* text = lv_obj_get_child(bubble, 0); + if (text != nullptr) { + // 根据气泡类型设置文本颜色 + if (strcmp(bubble_type, "system") == 0) { + lv_obj_set_style_text_color(text, lvgl_theme->system_text_color(), 0); + } else { + lv_obj_set_style_text_color(text, lvgl_theme->text_color(), 0); + } + } + } + } else { + ESP_LOGW(TAG, "child[%lu] Bubble type is not found", i); + } + } +#else + // Simple UI mode - just update the main chat message + if (chat_message_label_ != nullptr) { + lv_obj_set_style_text_color(chat_message_label_, lvgl_theme->text_color(), 0); + } + + if (emoji_label_ != nullptr) { + lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0); + } +#endif + + // Update low battery popup + lv_obj_set_style_bg_color(low_battery_popup_, lvgl_theme->low_battery_color(), 0); + + // No errors occurred. Save theme to settings + Display::SetTheme(lvgl_theme); +} + +void LcdDisplay::SetMusicInfo(const char* song_name) { +#if CONFIG_USE_WECHAT_MESSAGE_STYLE + // 微信模式下不显示歌名,保持原有聊天功能 + return; +#else + // 非微信模式:在表情下方显示歌名 + DisplayLockGuard lock(this); + if (chat_message_label_ == nullptr) { + return; + } + + if (song_name != nullptr && strlen(song_name) > 0) { + std::string music_text = ""; + music_text += song_name; + lv_label_set_text(chat_message_label_, music_text.c_str()); + + // 确保显示 emotion_label_ 和 chat_message_label_,隐藏 preview_image_ + if (emotion_label_ != nullptr) { + lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN); + } + if (preview_image_ != nullptr) { + lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN); + } + } else { + // 清空歌名显示 + lv_label_set_text(chat_message_label_, ""); + } +#endif +} + +// 唱片旋转动画回调函数 +static void vinyl_rotation_cb(void* obj, int32_t v) { + lv_obj_set_style_transform_angle((lv_obj_t*)obj, v, 0); +} + +void LcdDisplay::SetupMusicPanel() { + if (music_panel_ != nullptr) { + return; // 已经初始化过了 + } + + // 创建音乐播放面板 + music_panel_ = lv_obj_create(content_); + lv_obj_set_size(music_panel_, width_ - 20, 160); // 增加高度容纳唱片 + lv_obj_align(music_panel_, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_bg_color(music_panel_, lv_color_hex(0x0a0a0a), 0); + lv_obj_set_style_bg_opa(music_panel_, LV_OPA_90, 0); + lv_obj_set_style_border_color(music_panel_, lv_color_hex(0x00ff88), 0); + lv_obj_set_style_border_width(music_panel_, 2, 0); + lv_obj_set_style_radius(music_panel_, 20, 0); + lv_obj_add_flag(music_panel_, LV_OBJ_FLAG_HIDDEN); // 默认隐藏 + + // === 创建旋转唱片 === + int vinyl_size = 80; // 唱片大小 + music_vinyl_record_ = lv_obj_create(music_panel_); + lv_obj_set_size(music_vinyl_record_, vinyl_size, vinyl_size); + lv_obj_align(music_vinyl_record_, LV_ALIGN_LEFT_MID, 15, -10); + lv_obj_set_style_bg_color(music_vinyl_record_, lv_color_hex(0x1a1a1a), 0); + lv_obj_set_style_bg_opa(music_vinyl_record_, LV_OPA_COVER, 0); + lv_obj_set_style_border_color(music_vinyl_record_, lv_color_hex(0x333333), 0); + lv_obj_set_style_border_width(music_vinyl_record_, 2, 0); + lv_obj_set_style_radius(music_vinyl_record_, LV_RADIUS_CIRCLE, 0); + + // 设置旋转中心点到唱片的中心,让唱片原地自转 + lv_obj_set_style_transform_pivot_x(music_vinyl_record_, vinyl_size / 2, 0); + lv_obj_set_style_transform_pivot_y(music_vinyl_record_, vinyl_size / 2, 0); + + // 添加唱片纹理环 + for (int i = 0; i < 3; i++) { + lv_obj_t* ring = lv_obj_create(music_vinyl_record_); + int ring_size = vinyl_size - 20 - (i * 12); + lv_obj_set_size(ring, ring_size, ring_size); + lv_obj_center(ring); + lv_obj_set_style_bg_opa(ring, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_color(ring, lv_color_hex(0x444444), 0); + lv_obj_set_style_border_width(ring, 1, 0); + lv_obj_set_style_radius(ring, LV_RADIUS_CIRCLE, 0); + } + + // 创建唱片中心圆点 + music_vinyl_center_ = lv_obj_create(music_vinyl_record_); + lv_obj_set_size(music_vinyl_center_, 12, 12); + lv_obj_center(music_vinyl_center_); + lv_obj_set_style_bg_color(music_vinyl_center_, lv_color_hex(0x00ff88), 0); + lv_obj_set_style_bg_opa(music_vinyl_center_, LV_OPA_COVER, 0); + lv_obj_set_style_border_width(music_vinyl_center_, 0, 0); + lv_obj_set_style_radius(music_vinyl_center_, LV_RADIUS_CIRCLE, 0); + + // 创建唱片上的小标签 + lv_obj_t* vinyl_label = lv_label_create(music_vinyl_record_); + lv_label_set_text(vinyl_label, "♪"); + lv_obj_set_style_text_color(vinyl_label, lv_color_hex(0x888888), 0); + lv_obj_align(vinyl_label, LV_ALIGN_CENTER, 0, -15); + + // === 创建唱片臂 === + music_vinyl_arm_ = lv_obj_create(music_panel_); + lv_obj_set_size(music_vinyl_arm_, 4, 35); + lv_obj_align(music_vinyl_arm_, LV_ALIGN_LEFT_MID, 75, -35); + lv_obj_set_style_bg_color(music_vinyl_arm_, lv_color_hex(0x666666), 0); + lv_obj_set_style_bg_opa(music_vinyl_arm_, LV_OPA_COVER, 0); + lv_obj_set_style_border_width(music_vinyl_arm_, 0, 0); + lv_obj_set_style_radius(music_vinyl_arm_, 2, 0); + lv_obj_set_style_transform_pivot_x(music_vinyl_arm_, 2, 0); + lv_obj_set_style_transform_pivot_y(music_vinyl_arm_, 2, 0); + lv_obj_set_style_transform_angle(music_vinyl_arm_, 200, 0); // 初始角度 + + // 唱片臂头部 + lv_obj_t* arm_head = lv_obj_create(music_vinyl_arm_); + lv_obj_set_size(arm_head, 8, 8); + lv_obj_align(arm_head, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_set_style_bg_color(arm_head, lv_color_hex(0x888888), 0); + lv_obj_set_style_bg_opa(arm_head, LV_OPA_COVER, 0); + lv_obj_set_style_border_width(arm_head, 0, 0); + lv_obj_set_style_radius(arm_head, LV_RADIUS_CIRCLE, 0); + + // === 创建右侧信息区域 === + // 创建歌曲名称标签 + music_title_label_ = lv_label_create(music_panel_); + lv_obj_set_width(music_title_label_, width_ - 140); // 为左侧唱片留出空间 + lv_obj_align(music_title_label_, LV_ALIGN_TOP_RIGHT, -15, 20); + lv_label_set_text(music_title_label_, "未知歌曲"); + lv_obj_set_style_text_color(music_title_label_, lv_color_hex(0xffffff), 0); + lv_obj_set_style_text_align(music_title_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_label_set_long_mode(music_title_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + + // 创建时间显示标签 + music_time_label_ = lv_label_create(music_panel_); + lv_obj_align(music_time_label_, LV_ALIGN_TOP_RIGHT, -15, 50); + lv_label_set_text(music_time_label_, "00:00 / 00:00"); + lv_obj_set_style_text_color(music_time_label_, lv_color_hex(0xcccccc), 0); + lv_obj_set_style_text_align(music_time_label_, LV_TEXT_ALIGN_CENTER, 0); + + // 创建进度条背景 + music_progress_bg_ = lv_obj_create(music_panel_); + lv_obj_set_size(music_progress_bg_, width_ - 140, 8); // 调整宽度 + lv_obj_align(music_progress_bg_, LV_ALIGN_TOP_RIGHT, -15, 80); + lv_obj_set_style_bg_color(music_progress_bg_, lv_color_hex(0x333333), 0); + lv_obj_set_style_bg_opa(music_progress_bg_, LV_OPA_COVER, 0); + lv_obj_set_style_border_width(music_progress_bg_, 0, 0); + lv_obj_set_style_radius(music_progress_bg_, 4, 0); + + // 创建进度条 + music_progress_bar_ = lv_obj_create(music_progress_bg_); + lv_obj_set_size(music_progress_bar_, 0, 8); + lv_obj_align(music_progress_bar_, LV_ALIGN_LEFT_MID, 0, 0); + lv_obj_set_style_bg_color(music_progress_bar_, lv_color_hex(0x00ff88), 0); + lv_obj_set_style_bg_opa(music_progress_bar_, LV_OPA_COVER, 0); + lv_obj_set_style_border_width(music_progress_bar_, 0, 0); + lv_obj_set_style_radius(music_progress_bar_, 4, 0); + + // 添加音乐波形装饰 + lv_obj_t* wave1 = lv_obj_create(music_panel_); + lv_obj_set_size(wave1, 3, 20); + lv_obj_align(wave1, LV_ALIGN_BOTTOM_RIGHT, -80, -15); + lv_obj_set_style_bg_color(wave1, lv_color_hex(0x00ff88), 0); + lv_obj_set_style_bg_opa(wave1, LV_OPA_60, 0); + lv_obj_set_style_border_width(wave1, 0, 0); + lv_obj_set_style_radius(wave1, 2, 0); + + lv_obj_t* wave2 = lv_obj_create(music_panel_); + lv_obj_set_size(wave2, 3, 30); + lv_obj_align(wave2, LV_ALIGN_BOTTOM_RIGHT, -70, -15); + lv_obj_set_style_bg_color(wave2, lv_color_hex(0x00ff88), 0); + lv_obj_set_style_bg_opa(wave2, LV_OPA_80, 0); + lv_obj_set_style_border_width(wave2, 0, 0); + lv_obj_set_style_radius(wave2, 2, 0); + + lv_obj_t* wave3 = lv_obj_create(music_panel_); + lv_obj_set_size(wave3, 3, 15); + lv_obj_align(wave3, LV_ALIGN_BOTTOM_RIGHT, -60, -15); + lv_obj_set_style_bg_color(wave3, lv_color_hex(0x00ff88), 0); + lv_obj_set_style_bg_opa(wave3, LV_OPA_40, 0); + lv_obj_set_style_border_width(wave3, 0, 0); + lv_obj_set_style_radius(wave3, 2, 0); + + // 初始化旋转动画 + vinyl_rotation_anim_ = new lv_anim_t; + lv_anim_init(vinyl_rotation_anim_); + lv_anim_set_var(vinyl_rotation_anim_, music_vinyl_record_); + lv_anim_set_exec_cb(vinyl_rotation_anim_, vinyl_rotation_cb); + lv_anim_set_values(vinyl_rotation_anim_, 0, 3600); // 0到360度(3600是LVGL中的360度) + lv_anim_set_time(vinyl_rotation_anim_, 3000); // 3秒一圈 + lv_anim_set_repeat_count(vinyl_rotation_anim_, LV_ANIM_REPEAT_INFINITE); +} + +void LcdDisplay::SetMusicProgress(const char* song_name, int current_seconds, int total_seconds, float progress_percent) { +#if CONFIG_USE_WECHAT_MESSAGE_STYLE + // 微信模式下使用简化显示 + Display::SetMusicProgress(song_name, current_seconds, total_seconds, progress_percent); + return; +#else + DisplayLockGuard lock(this); + + // 确保音乐面板已创建 + if (music_panel_ == nullptr) { + SetupMusicPanel(); + } + + // 显示音乐面板 + if (!music_panel_visible_) { + lv_obj_clear_flag(music_panel_, LV_OBJ_FLAG_HIDDEN); + music_panel_visible_ = true; + + // 隐藏其他元素 + if (chat_message_label_) { + lv_obj_add_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); + } + if (emoji_box_) { + lv_obj_add_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN); + } + + // 启动唱片旋转动画 + if (vinyl_rotation_anim_ && music_vinyl_record_) { + lv_anim_start(vinyl_rotation_anim_); + ESP_LOGI("LcdDisplay", "Started vinyl rotation animation"); + } + + // 唱片臂放下动画 + if (music_vinyl_arm_) { + lv_anim_t arm_anim; + lv_anim_init(&arm_anim); + lv_anim_set_var(&arm_anim, music_vinyl_arm_); + lv_anim_set_exec_cb(&arm_anim, vinyl_rotation_cb); + lv_anim_set_values(&arm_anim, 200, 320); // 从200度转到320度 + lv_anim_set_time(&arm_anim, 500); // 0.5秒动画 + lv_anim_start(&arm_anim); + } + } + + // 更新歌曲名称 + if (music_title_label_ && song_name) { + lv_label_set_text(music_title_label_, song_name); + } + + // 更新时间显示 + if (music_time_label_) { + char time_str[32]; + int current_min = current_seconds / 60; + int current_sec = current_seconds % 60; + int total_min = total_seconds / 60; + int total_sec = total_seconds % 60; + + snprintf(time_str, sizeof(time_str), "%02d:%02d / %02d:%02d", + current_min, current_sec, total_min, total_sec); + lv_label_set_text(music_time_label_, time_str); + } + + // 更新进度条 + if (music_progress_bar_ && music_progress_bg_) { + int bg_width = lv_obj_get_width(music_progress_bg_); + int progress_width = (int)(bg_width * progress_percent / 100.0f); + progress_width = progress_width < 0 ? 0 : (progress_width > bg_width ? bg_width : progress_width); + lv_obj_set_width(music_progress_bar_, progress_width); + } + + // 设置音乐相关的表情图标 + SetEmotion(FONT_AWESOME_MUSIC); +#endif +} + +void LcdDisplay::ClearMusicInfo() { + DisplayLockGuard lock(this); + + // 隐藏音乐面板 + if (music_panel_ && music_panel_visible_) { + // 停止唱片旋转动画 + if (vinyl_rotation_anim_ && music_vinyl_record_) { + lv_anim_del(music_vinyl_record_, nullptr); // 删除该对象的所有动画 + ESP_LOGI("LcdDisplay", "Stopped vinyl rotation animation"); + } + + // 唱片臂收起动画 + if (music_vinyl_arm_) { + lv_anim_t arm_anim; + lv_anim_init(&arm_anim); + lv_anim_set_var(&arm_anim, music_vinyl_arm_); + lv_anim_set_exec_cb(&arm_anim, vinyl_rotation_cb); + lv_anim_set_values(&arm_anim, 320, 200); // 从320度转回200度 + lv_anim_set_time(&arm_anim, 500); // 0.5秒动画 + lv_anim_start(&arm_anim); + } + + lv_obj_add_flag(music_panel_, LV_OBJ_FLAG_HIDDEN); + music_panel_visible_ = false; + + // 重新显示聊天消息 + if (chat_message_label_) { + lv_obj_clear_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN); + lv_label_set_text(chat_message_label_, ""); + } + if (emoji_box_) { + lv_obj_clear_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN); + } + } + + // 重置表情图标 + SetEmotion(FONT_AWESOME_NEUTRAL); +} \ No newline at end of file diff --git a/main/display/lcd_display.h b/main/display/lcd_display.h index 42ecc29..ffec7d2 100644 --- a/main/display/lcd_display.h +++ b/main/display/lcd_display.h @@ -1,79 +1,97 @@ -#ifndef LCD_DISPLAY_H -#define LCD_DISPLAY_H - -#include "lvgl_display.h" -#include "gif/lvgl_gif.h" - -#include -#include -#include - -#include -#include - -#define PREVIEW_IMAGE_DURATION_MS 5000 - - -class LcdDisplay : public LvglDisplay { -protected: - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - lv_draw_buf_t draw_buf_; - lv_obj_t* status_bar_ = nullptr; - lv_obj_t* content_ = nullptr; - lv_obj_t* container_ = nullptr; - lv_obj_t* side_bar_ = nullptr; - lv_obj_t* preview_image_ = nullptr; - lv_obj_t* emoji_label_ = nullptr; - lv_obj_t* emoji_image_ = nullptr; - std::unique_ptr gif_controller_ = nullptr; - lv_obj_t* emoji_box_ = nullptr; - lv_obj_t* chat_message_label_ = nullptr; - esp_timer_handle_t preview_timer_ = nullptr; - std::unique_ptr preview_image_cached_ = nullptr; - - void InitializeLcdThemes(); - void SetupUI(); - virtual bool Lock(int timeout_ms = 0) override; - virtual void Unlock() override; - -protected: - // 添加protected构造函数 - LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height); - -public: - ~LcdDisplay(); - virtual void SetEmotion(const char* emotion) override; - virtual void SetChatMessage(const char* role, const char* content) override; - virtual void SetPreviewImage(std::unique_ptr image) override; - - // Add theme switching function - virtual void SetTheme(Theme* theme) override; -}; - -// SPI LCD显示器 -class SpiLcdDisplay : public LcdDisplay { -public: - SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, - bool mirror_x, bool mirror_y, bool swap_xy); -}; - -// RGB LCD显示器 -class RgbLcdDisplay : public LcdDisplay { -public: - RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, - bool mirror_x, bool mirror_y, bool swap_xy); -}; - -// MIPI LCD显示器 -class MipiLcdDisplay : public LcdDisplay { -public: - MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, - bool mirror_x, bool mirror_y, bool swap_xy); -}; - -#endif // LCD_DISPLAY_H +#ifndef LCD_DISPLAY_H +#define LCD_DISPLAY_H + +#include "lvgl_display.h" +#include "gif/lvgl_gif.h" + +#include +#include +#include + +#include +#include + +#define PREVIEW_IMAGE_DURATION_MS 5000 + + +class LcdDisplay : public LvglDisplay { +protected: + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + lv_draw_buf_t draw_buf_; + lv_obj_t* status_bar_ = nullptr; + lv_obj_t* content_ = nullptr; + lv_obj_t* container_ = nullptr; + lv_obj_t* side_bar_ = nullptr; + lv_obj_t* preview_image_ = nullptr; + lv_obj_t* emoji_label_ = nullptr; + lv_obj_t* emoji_image_ = nullptr; + std::unique_ptr gif_controller_ = nullptr; + lv_obj_t* emoji_box_ = nullptr; + lv_obj_t* chat_message_label_ = nullptr; + esp_timer_handle_t preview_timer_ = nullptr; + std::unique_ptr preview_image_cached_ = nullptr; + + // 音乐播放界面UI元素 + lv_obj_t* music_panel_ = nullptr; // 音乐播放面板 + lv_obj_t* music_title_label_ = nullptr; // 歌曲名称标签 + lv_obj_t* music_time_label_ = nullptr; // 时间显示标签 + lv_obj_t* music_progress_bar_ = nullptr; // 进度条 + lv_obj_t* music_progress_bg_ = nullptr; // 进度条背景 + lv_obj_t* music_vinyl_record_ = nullptr; // 旋转唱片 + lv_obj_t* music_vinyl_center_ = nullptr; // 唱片中心圆点 + lv_obj_t* music_vinyl_arm_ = nullptr; // 唱片臂(可选) + lv_anim_t* vinyl_rotation_anim_ = nullptr; // 旋转动画 + bool music_panel_visible_ = false; // 音乐面板是否可见 + + void InitializeLcdThemes(); + void SetupUI(); + void SetupMusicPanel(); // 初始化音乐播放面板 + virtual bool Lock(int timeout_ms = 0) override; + virtual void Unlock() override; + +protected: + // 添加protected构造函数 + LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height); + +public: + ~LcdDisplay(); + virtual void SetEmotion(const char* emotion) override; + virtual void SetChatMessage(const char* role, const char* content) override; + virtual void SetPreviewImage(std::unique_ptr image) override; + + // Add theme switching function + virtual void SetTheme(Theme* theme) override; + + // Add music display functions + virtual void SetMusicInfo(const char* song_name) override; + virtual void SetMusicProgress(const char* song_name, int current_seconds, int total_seconds, float progress_percent) override; + virtual void ClearMusicInfo() override; +}; + +// SPI LCD显示器 +class SpiLcdDisplay : public LcdDisplay { +public: + SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, + bool mirror_x, bool mirror_y, bool swap_xy); +}; + +// RGB LCD显示器 +class RgbLcdDisplay : public LcdDisplay { +public: + RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, + bool mirror_x, bool mirror_y, bool swap_xy); +}; + +// MIPI LCD显示器 +class MipiLcdDisplay : public LcdDisplay { +public: + MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, + bool mirror_x, bool mirror_y, bool swap_xy); +}; + +#endif // LCD_DISPLAY_H diff --git a/main/display/lvgl_display/emoji_collection.cc b/main/display/lvgl_display/emoji_collection.cc index c9a1a60..c10df5c 100644 --- a/main/display/lvgl_display/emoji_collection.cc +++ b/main/display/lvgl_display/emoji_collection.cc @@ -1,123 +1,123 @@ -#include "emoji_collection.h" - -#include -#include -#include - -#define TAG "EmojiCollection" - -void EmojiCollection::AddEmoji(const std::string& name, LvglImage* image) { - emoji_collection_[name] = image; -} - -const LvglImage* EmojiCollection::GetEmojiImage(const char* name) { - auto it = emoji_collection_.find(name); - if (it != emoji_collection_.end()) { - return it->second; - } - - ESP_LOGW(TAG, "Emoji not found: %s", name); - return nullptr; -} - -EmojiCollection::~EmojiCollection() { - for (auto it = emoji_collection_.begin(); it != emoji_collection_.end(); ++it) { - delete it->second; - } - emoji_collection_.clear(); -} - -// These are declared in xiaozhi-fonts/src/font_emoji_32.c -extern const lv_image_dsc_t emoji_1f636_32; // neutral -extern const lv_image_dsc_t emoji_1f642_32; // happy -extern const lv_image_dsc_t emoji_1f606_32; // laughing -extern const lv_image_dsc_t emoji_1f602_32; // funny -extern const lv_image_dsc_t emoji_1f614_32; // sad -extern const lv_image_dsc_t emoji_1f620_32; // angry -extern const lv_image_dsc_t emoji_1f62d_32; // crying -extern const lv_image_dsc_t emoji_1f60d_32; // loving -extern const lv_image_dsc_t emoji_1f633_32; // embarrassed -extern const lv_image_dsc_t emoji_1f62f_32; // surprised -extern const lv_image_dsc_t emoji_1f631_32; // shocked -extern const lv_image_dsc_t emoji_1f914_32; // thinking -extern const lv_image_dsc_t emoji_1f609_32; // winking -extern const lv_image_dsc_t emoji_1f60e_32; // cool -extern const lv_image_dsc_t emoji_1f60c_32; // relaxed -extern const lv_image_dsc_t emoji_1f924_32; // delicious -extern const lv_image_dsc_t emoji_1f618_32; // kissy -extern const lv_image_dsc_t emoji_1f60f_32; // confident -extern const lv_image_dsc_t emoji_1f634_32; // sleepy -extern const lv_image_dsc_t emoji_1f61c_32; // silly -extern const lv_image_dsc_t emoji_1f644_32; // confused - -Twemoji32::Twemoji32() { - AddEmoji("neutral", new LvglSourceImage(&emoji_1f636_32)); - AddEmoji("happy", new LvglSourceImage(&emoji_1f642_32)); - AddEmoji("laughing", new LvglSourceImage(&emoji_1f606_32)); - AddEmoji("funny", new LvglSourceImage(&emoji_1f602_32)); - AddEmoji("sad", new LvglSourceImage(&emoji_1f614_32)); - AddEmoji("angry", new LvglSourceImage(&emoji_1f620_32)); - AddEmoji("crying", new LvglSourceImage(&emoji_1f62d_32)); - AddEmoji("loving", new LvglSourceImage(&emoji_1f60d_32)); - AddEmoji("embarrassed", new LvglSourceImage(&emoji_1f633_32)); - AddEmoji("surprised", new LvglSourceImage(&emoji_1f62f_32)); - AddEmoji("shocked", new LvglSourceImage(&emoji_1f631_32)); - AddEmoji("thinking", new LvglSourceImage(&emoji_1f914_32)); - AddEmoji("winking", new LvglSourceImage(&emoji_1f609_32)); - AddEmoji("cool", new LvglSourceImage(&emoji_1f60e_32)); - AddEmoji("relaxed", new LvglSourceImage(&emoji_1f60c_32)); - AddEmoji("delicious", new LvglSourceImage(&emoji_1f924_32)); - AddEmoji("kissy", new LvglSourceImage(&emoji_1f618_32)); - AddEmoji("confident", new LvglSourceImage(&emoji_1f60f_32)); - AddEmoji("sleepy", new LvglSourceImage(&emoji_1f634_32)); - AddEmoji("silly", new LvglSourceImage(&emoji_1f61c_32)); - AddEmoji("confused", new LvglSourceImage(&emoji_1f644_32)); -} - - -// These are declared in xiaozhi-fonts/src/font_emoji_64.c -extern const lv_image_dsc_t emoji_1f636_64; // neutral -extern const lv_image_dsc_t emoji_1f642_64; // happy -extern const lv_image_dsc_t emoji_1f606_64; // laughing -extern const lv_image_dsc_t emoji_1f602_64; // funny -extern const lv_image_dsc_t emoji_1f614_64; // sad -extern const lv_image_dsc_t emoji_1f620_64; // angry -extern const lv_image_dsc_t emoji_1f62d_64; // crying -extern const lv_image_dsc_t emoji_1f60d_64; // loving -extern const lv_image_dsc_t emoji_1f633_64; // embarrassed -extern const lv_image_dsc_t emoji_1f62f_64; // surprised -extern const lv_image_dsc_t emoji_1f631_64; // shocked -extern const lv_image_dsc_t emoji_1f914_64; // thinking -extern const lv_image_dsc_t emoji_1f609_64; // winking -extern const lv_image_dsc_t emoji_1f60e_64; // cool -extern const lv_image_dsc_t emoji_1f60c_64; // relaxed -extern const lv_image_dsc_t emoji_1f924_64; // delicious -extern const lv_image_dsc_t emoji_1f618_64; // kissy -extern const lv_image_dsc_t emoji_1f60f_64; // confident -extern const lv_image_dsc_t emoji_1f634_64; // sleepy -extern const lv_image_dsc_t emoji_1f61c_64; // silly -extern const lv_image_dsc_t emoji_1f644_64; // confused - -Twemoji64::Twemoji64() { - AddEmoji("neutral", new LvglSourceImage(&emoji_1f636_64)); - AddEmoji("happy", new LvglSourceImage(&emoji_1f642_64)); - AddEmoji("laughing", new LvglSourceImage(&emoji_1f606_64)); - AddEmoji("funny", new LvglSourceImage(&emoji_1f602_64)); - AddEmoji("sad", new LvglSourceImage(&emoji_1f614_64)); - AddEmoji("angry", new LvglSourceImage(&emoji_1f620_64)); - AddEmoji("crying", new LvglSourceImage(&emoji_1f62d_64)); - AddEmoji("loving", new LvglSourceImage(&emoji_1f60d_64)); - AddEmoji("embarrassed", new LvglSourceImage(&emoji_1f633_64)); - AddEmoji("surprised", new LvglSourceImage(&emoji_1f62f_64)); - AddEmoji("shocked", new LvglSourceImage(&emoji_1f631_64)); - AddEmoji("thinking", new LvglSourceImage(&emoji_1f914_64)); - AddEmoji("winking", new LvglSourceImage(&emoji_1f609_64)); - AddEmoji("cool", new LvglSourceImage(&emoji_1f60e_64)); - AddEmoji("relaxed", new LvglSourceImage(&emoji_1f60c_64)); - AddEmoji("delicious", new LvglSourceImage(&emoji_1f924_64)); - AddEmoji("kissy", new LvglSourceImage(&emoji_1f618_64)); - AddEmoji("confident", new LvglSourceImage(&emoji_1f60f_64)); - AddEmoji("sleepy", new LvglSourceImage(&emoji_1f634_64)); - AddEmoji("silly", new LvglSourceImage(&emoji_1f61c_64)); - AddEmoji("confused", new LvglSourceImage(&emoji_1f644_64)); -} +#include "emoji_collection.h" + +#include +#include +#include + +#define TAG "EmojiCollection" + +void EmojiCollection::AddEmoji(const std::string& name, LvglImage* image) { + emoji_collection_[name] = image; +} + +const LvglImage* EmojiCollection::GetEmojiImage(const char* name) { + auto it = emoji_collection_.find(name); + if (it != emoji_collection_.end()) { + return it->second; + } + + ESP_LOGW(TAG, "Emoji not found: %s", name); + return nullptr; +} + +EmojiCollection::~EmojiCollection() { + for (auto it = emoji_collection_.begin(); it != emoji_collection_.end(); ++it) { + delete it->second; + } + emoji_collection_.clear(); +} + +// These are declared in xiaozhi-fonts/src/font_emoji_32.c +extern const lv_image_dsc_t emoji_1f636_32; // neutral +extern const lv_image_dsc_t emoji_1f642_32; // happy +extern const lv_image_dsc_t emoji_1f606_32; // laughing +extern const lv_image_dsc_t emoji_1f602_32; // funny +extern const lv_image_dsc_t emoji_1f614_32; // sad +extern const lv_image_dsc_t emoji_1f620_32; // angry +extern const lv_image_dsc_t emoji_1f62d_32; // crying +extern const lv_image_dsc_t emoji_1f60d_32; // loving +extern const lv_image_dsc_t emoji_1f633_32; // embarrassed +extern const lv_image_dsc_t emoji_1f62f_32; // surprised +extern const lv_image_dsc_t emoji_1f631_32; // shocked +extern const lv_image_dsc_t emoji_1f914_32; // thinking +extern const lv_image_dsc_t emoji_1f609_32; // winking +extern const lv_image_dsc_t emoji_1f60e_32; // cool +extern const lv_image_dsc_t emoji_1f60c_32; // relaxed +extern const lv_image_dsc_t emoji_1f924_32; // delicious +extern const lv_image_dsc_t emoji_1f618_32; // kissy +extern const lv_image_dsc_t emoji_1f60f_32; // confident +extern const lv_image_dsc_t emoji_1f634_32; // sleepy +extern const lv_image_dsc_t emoji_1f61c_32; // silly +extern const lv_image_dsc_t emoji_1f644_32; // confused + +Twemoji32::Twemoji32() { + AddEmoji("neutral", new LvglSourceImage(&emoji_1f636_32)); + AddEmoji("happy", new LvglSourceImage(&emoji_1f642_32)); + AddEmoji("laughing", new LvglSourceImage(&emoji_1f606_32)); + AddEmoji("funny", new LvglSourceImage(&emoji_1f602_32)); + AddEmoji("sad", new LvglSourceImage(&emoji_1f614_32)); + AddEmoji("angry", new LvglSourceImage(&emoji_1f620_32)); + AddEmoji("crying", new LvglSourceImage(&emoji_1f62d_32)); + AddEmoji("loving", new LvglSourceImage(&emoji_1f60d_32)); + AddEmoji("embarrassed", new LvglSourceImage(&emoji_1f633_32)); + AddEmoji("surprised", new LvglSourceImage(&emoji_1f62f_32)); + AddEmoji("shocked", new LvglSourceImage(&emoji_1f631_32)); + AddEmoji("thinking", new LvglSourceImage(&emoji_1f914_32)); + AddEmoji("winking", new LvglSourceImage(&emoji_1f609_32)); + AddEmoji("cool", new LvglSourceImage(&emoji_1f60e_32)); + AddEmoji("relaxed", new LvglSourceImage(&emoji_1f60c_32)); + AddEmoji("delicious", new LvglSourceImage(&emoji_1f924_32)); + AddEmoji("kissy", new LvglSourceImage(&emoji_1f618_32)); + AddEmoji("confident", new LvglSourceImage(&emoji_1f60f_32)); + AddEmoji("sleepy", new LvglSourceImage(&emoji_1f634_32)); + AddEmoji("silly", new LvglSourceImage(&emoji_1f61c_32)); + AddEmoji("confused", new LvglSourceImage(&emoji_1f644_32)); +} + + +// These are declared in xiaozhi-fonts/src/font_emoji_64.c +extern const lv_image_dsc_t emoji_1f636_64; // neutral +extern const lv_image_dsc_t emoji_1f642_64; // happy +extern const lv_image_dsc_t emoji_1f606_64; // laughing +extern const lv_image_dsc_t emoji_1f602_64; // funny +extern const lv_image_dsc_t emoji_1f614_64; // sad +extern const lv_image_dsc_t emoji_1f620_64; // angry +extern const lv_image_dsc_t emoji_1f62d_64; // crying +extern const lv_image_dsc_t emoji_1f60d_64; // loving +extern const lv_image_dsc_t emoji_1f633_64; // embarrassed +extern const lv_image_dsc_t emoji_1f62f_64; // surprised +extern const lv_image_dsc_t emoji_1f631_64; // shocked +extern const lv_image_dsc_t emoji_1f914_64; // thinking +extern const lv_image_dsc_t emoji_1f609_64; // winking +extern const lv_image_dsc_t emoji_1f60e_64; // cool +extern const lv_image_dsc_t emoji_1f60c_64; // relaxed +extern const lv_image_dsc_t emoji_1f924_64; // delicious +extern const lv_image_dsc_t emoji_1f618_64; // kissy +extern const lv_image_dsc_t emoji_1f60f_64; // confident +extern const lv_image_dsc_t emoji_1f634_64; // sleepy +extern const lv_image_dsc_t emoji_1f61c_64; // silly +extern const lv_image_dsc_t emoji_1f644_64; // confused + +Twemoji64::Twemoji64() { + AddEmoji("neutral", new LvglSourceImage(&emoji_1f636_64)); + AddEmoji("happy", new LvglSourceImage(&emoji_1f642_64)); + AddEmoji("laughing", new LvglSourceImage(&emoji_1f606_64)); + AddEmoji("funny", new LvglSourceImage(&emoji_1f602_64)); + AddEmoji("sad", new LvglSourceImage(&emoji_1f614_64)); + AddEmoji("angry", new LvglSourceImage(&emoji_1f620_64)); + AddEmoji("crying", new LvglSourceImage(&emoji_1f62d_64)); + AddEmoji("loving", new LvglSourceImage(&emoji_1f60d_64)); + AddEmoji("embarrassed", new LvglSourceImage(&emoji_1f633_64)); + AddEmoji("surprised", new LvglSourceImage(&emoji_1f62f_64)); + AddEmoji("shocked", new LvglSourceImage(&emoji_1f631_64)); + AddEmoji("thinking", new LvglSourceImage(&emoji_1f914_64)); + AddEmoji("winking", new LvglSourceImage(&emoji_1f609_64)); + AddEmoji("cool", new LvglSourceImage(&emoji_1f60e_64)); + AddEmoji("relaxed", new LvglSourceImage(&emoji_1f60c_64)); + AddEmoji("delicious", new LvglSourceImage(&emoji_1f924_64)); + AddEmoji("kissy", new LvglSourceImage(&emoji_1f618_64)); + AddEmoji("confident", new LvglSourceImage(&emoji_1f60f_64)); + AddEmoji("sleepy", new LvglSourceImage(&emoji_1f634_64)); + AddEmoji("silly", new LvglSourceImage(&emoji_1f61c_64)); + AddEmoji("confused", new LvglSourceImage(&emoji_1f644_64)); +} diff --git a/main/display/lvgl_display/emoji_collection.h b/main/display/lvgl_display/emoji_collection.h index c9eac05..d64a9cc 100644 --- a/main/display/lvgl_display/emoji_collection.h +++ b/main/display/lvgl_display/emoji_collection.h @@ -1,34 +1,34 @@ -#ifndef EMOJI_COLLECTION_H -#define EMOJI_COLLECTION_H - -#include "lvgl_image.h" - -#include - -#include -#include -#include - - -// Define interface for emoji collection -class EmojiCollection { -public: - virtual void AddEmoji(const std::string& name, LvglImage* image); - virtual const LvglImage* GetEmojiImage(const char* name); - virtual ~EmojiCollection(); - -private: - std::map emoji_collection_; -}; - -class Twemoji32 : public EmojiCollection { -public: - Twemoji32(); -}; - -class Twemoji64 : public EmojiCollection { -public: - Twemoji64(); -}; - -#endif +#ifndef EMOJI_COLLECTION_H +#define EMOJI_COLLECTION_H + +#include "lvgl_image.h" + +#include + +#include +#include +#include + + +// Define interface for emoji collection +class EmojiCollection { +public: + virtual void AddEmoji(const std::string& name, LvglImage* image); + virtual const LvglImage* GetEmojiImage(const char* name); + virtual ~EmojiCollection(); + +private: + std::map emoji_collection_; +}; + +class Twemoji32 : public EmojiCollection { +public: + Twemoji32(); +}; + +class Twemoji64 : public EmojiCollection { +public: + Twemoji64(); +}; + +#endif diff --git a/main/display/lvgl_display/gif/LICENSE.txt b/main/display/lvgl_display/gif/LICENSE.txt index c53d519..491c071 100644 --- a/main/display/lvgl_display/gif/LICENSE.txt +++ b/main/display/lvgl_display/gif/LICENSE.txt @@ -1,2 +1,2 @@ -All of the source code and documentation for gifdec is released into the -public domain and provided without warranty of any kind. +All of the source code and documentation for gifdec is released into the +public domain and provided without warranty of any kind. diff --git a/main/display/lvgl_display/gif/README.md b/main/display/lvgl_display/gif/README.md new file mode 100644 index 0000000..d84fa84 --- /dev/null +++ b/main/display/lvgl_display/gif/README.md @@ -0,0 +1,17 @@ +# 说明 / Description + +## 中文 + +本目录代码移植自 LVGL 的 GIF 程序。 + +主要修复和改进: +- 修复了透明背景问题 +- 兼容了 87a 版本的 GIF 格式 + +## English + +The code in this directory is ported from LVGL's GIF program. + +Main fixes and improvements: +- Fixed transparent background issues +- Added compatibility for GIF 87a version format diff --git a/main/display/lvgl_display/gif/gifdec.c b/main/display/lvgl_display/gif/gifdec.c index 1e74aab..4deced4 100644 --- a/main/display/lvgl_display/gif/gifdec.c +++ b/main/display/lvgl_display/gif/gifdec.c @@ -1,821 +1,821 @@ -#include "gifdec.h" - -#include -#include -#include -#include - -#define TAG "GIF" - -#define MIN(A, B) ((A) < (B) ? (A) : (B)) -#define MAX(A, B) ((A) > (B) ? (A) : (B)) - -typedef struct Entry { - uint16_t length; - uint16_t prefix; - uint8_t suffix; -} Entry; - -typedef struct Table { - int bulk; - int nentries; - Entry * entries; -} Table; - -#if LV_GIF_CACHE_DECODE_DATA -#define LZW_MAXBITS 12 -#define LZW_TABLE_SIZE (1 << LZW_MAXBITS) -#define LZW_CACHE_SIZE (LZW_TABLE_SIZE * 4) -#endif - -static gd_GIF * gif_open(gd_GIF * gif); -static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file); -static void f_gif_read(gd_GIF * gif, void * buf, size_t len); -static int f_gif_seek(gd_GIF * gif, size_t pos, int k); -static void f_gif_close(gd_GIF * gif); - -#if LV_USE_DRAW_SW_ASM == LV_DRAW_SW_ASM_HELIUM - #include "gifdec_mve.h" -#endif - -static uint16_t -read_num(gd_GIF * gif) -{ - uint8_t bytes[2]; - - f_gif_read(gif, bytes, 2); - return bytes[0] + (((uint16_t) bytes[1]) << 8); -} - -gd_GIF * -gd_open_gif_file(const char * fname) -{ - gd_GIF gif_base; - memset(&gif_base, 0, sizeof(gif_base)); - - bool res = f_gif_open(&gif_base, fname, true); - if(!res) return NULL; - - return gif_open(&gif_base); -} - -gd_GIF * -gd_open_gif_data(const void * data) -{ - gd_GIF gif_base; - memset(&gif_base, 0, sizeof(gif_base)); - - bool res = f_gif_open(&gif_base, data, false); - if(!res) return NULL; - - return gif_open(&gif_base); -} - -static gd_GIF * gif_open(gd_GIF * gif_base) -{ - uint8_t sigver[3]; - uint16_t width, height, depth; - uint8_t fdsz, bgidx, aspect; - uint8_t * bgcolor; - int gct_sz; - gd_GIF * gif = NULL; - - /* Header */ - f_gif_read(gif_base, sigver, 3); - if(memcmp(sigver, "GIF", 3) != 0) { - ESP_LOGW(TAG, "invalid signature"); - goto fail; - } - /* Version */ - f_gif_read(gif_base, sigver, 3); - if(memcmp(sigver, "89a", 3) != 0 && memcmp(sigver, "87a", 3) != 0) { - ESP_LOGW(TAG, "invalid version"); - goto fail; - } - /* Width x Height */ - width = read_num(gif_base); - height = read_num(gif_base); - /* FDSZ */ - f_gif_read(gif_base, &fdsz, 1); - /* Presence of GCT */ - if(!(fdsz & 0x80)) { - ESP_LOGW(TAG, "no global color table"); - goto fail; - } - /* Color Space's Depth */ - depth = ((fdsz >> 4) & 7) + 1; - /* Ignore Sort Flag. */ - /* GCT Size */ - gct_sz = 1 << ((fdsz & 0x07) + 1); - /* Background Color Index */ - f_gif_read(gif_base, &bgidx, 1); - /* Aspect Ratio */ - f_gif_read(gif_base, &aspect, 1); - /* Create gd_GIF Structure. */ - if(0 == width || 0 == height){ - ESP_LOGW(TAG, "Zero size image"); - goto fail; - } -#if LV_GIF_CACHE_DECODE_DATA - if(0 == (INT_MAX - sizeof(gd_GIF) - LZW_CACHE_SIZE) / width / height / 5){ - ESP_LOGW(TAG, "Image dimensions are too large"); - goto fail; - } - gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height + LZW_CACHE_SIZE); -#else - if(0 == (INT_MAX - sizeof(gd_GIF)) / width / height / 5){ - ESP_LOGW(TAG, "Image dimensions are too large"); - goto fail; - } - gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height); -#endif - if(!gif) goto fail; - memcpy(gif, gif_base, sizeof(gd_GIF)); - gif->width = width; - gif->height = height; - gif->depth = depth; - /* Read GCT */ - gif->gct.size = gct_sz; - f_gif_read(gif, gif->gct.colors, 3 * gif->gct.size); - gif->palette = &gif->gct; - gif->bgindex = bgidx; - gif->canvas = (uint8_t *) &gif[1]; - gif->frame = &gif->canvas[4 * width * height]; - if(gif->bgindex) { - memset(gif->frame, gif->bgindex, gif->width * gif->height); - } - bgcolor = &gif->palette->colors[gif->bgindex * 3]; - #if LV_GIF_CACHE_DECODE_DATA - gif->lzw_cache = gif->frame + width * height; - #endif - -#ifdef GIFDEC_FILL_BG - GIFDEC_FILL_BG(gif->canvas, gif->width * gif->height, 1, gif->width * gif->height, bgcolor, 0x00); -#else - for(int i = 0; i < gif->width * gif->height; i++) { - gif->canvas[i * 4 + 0] = *(bgcolor + 2); - gif->canvas[i * 4 + 1] = *(bgcolor + 1); - gif->canvas[i * 4 + 2] = *(bgcolor + 0); - gif->canvas[i * 4 + 3] = 0x00; // 初始化为透明,让第一帧根据自己的透明度设置来渲染 - } -#endif - gif->anim_start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - gif->loop_count = -1; - goto ok; -fail: - f_gif_close(gif_base); -ok: - return gif; -} - -static void -discard_sub_blocks(gd_GIF * gif) -{ - uint8_t size; - - do { - f_gif_read(gif, &size, 1); - f_gif_seek(gif, size, LV_FS_SEEK_CUR); - } while(size); -} - -static void -read_plain_text_ext(gd_GIF * gif) -{ - if(gif->plain_text) { - uint16_t tx, ty, tw, th; - uint8_t cw, ch, fg, bg; - size_t sub_block; - f_gif_seek(gif, 1, LV_FS_SEEK_CUR); /* block size = 12 */ - tx = read_num(gif); - ty = read_num(gif); - tw = read_num(gif); - th = read_num(gif); - f_gif_read(gif, &cw, 1); - f_gif_read(gif, &ch, 1); - f_gif_read(gif, &fg, 1); - f_gif_read(gif, &bg, 1); - sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg); - f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); - } - else { - /* Discard plain text metadata. */ - f_gif_seek(gif, 13, LV_FS_SEEK_CUR); - } - /* Discard plain text sub-blocks. */ - discard_sub_blocks(gif); -} - -static void -read_graphic_control_ext(gd_GIF * gif) -{ - uint8_t rdit; - - /* Discard block size (always 0x04). */ - f_gif_seek(gif, 1, LV_FS_SEEK_CUR); - f_gif_read(gif, &rdit, 1); - gif->gce.disposal = (rdit >> 2) & 3; - gif->gce.input = rdit & 2; - gif->gce.transparency = rdit & 1; - gif->gce.delay = read_num(gif); - f_gif_read(gif, &gif->gce.tindex, 1); - /* Skip block terminator. */ - f_gif_seek(gif, 1, LV_FS_SEEK_CUR); -} - -static void -read_comment_ext(gd_GIF * gif) -{ - if(gif->comment) { - size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - gif->comment(gif); - f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); - } - /* Discard comment sub-blocks. */ - discard_sub_blocks(gif); -} - -static void -read_application_ext(gd_GIF * gif) -{ - char app_id[8]; - char app_auth_code[3]; - uint16_t loop_count; - - /* Discard block size (always 0x0B). */ - f_gif_seek(gif, 1, LV_FS_SEEK_CUR); - /* Application Identifier. */ - f_gif_read(gif, app_id, 8); - /* Application Authentication Code. */ - f_gif_read(gif, app_auth_code, 3); - if(!strncmp(app_id, "NETSCAPE", sizeof(app_id))) { - /* Discard block size (0x03) and constant byte (0x01). */ - f_gif_seek(gif, 2, LV_FS_SEEK_CUR); - loop_count = read_num(gif); - if(gif->loop_count < 0) { - if(loop_count == 0) { - gif->loop_count = 0; - } - else { - gif->loop_count = loop_count + 1; - } - } - /* Skip block terminator. */ - f_gif_seek(gif, 1, LV_FS_SEEK_CUR); - } - else if(gif->application) { - size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - gif->application(gif, app_id, app_auth_code); - f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); - discard_sub_blocks(gif); - } - else { - discard_sub_blocks(gif); - } -} - -static void -read_ext(gd_GIF * gif) -{ - uint8_t label; - - f_gif_read(gif, &label, 1); - switch(label) { - case 0x01: - read_plain_text_ext(gif); - break; - case 0xF9: - read_graphic_control_ext(gif); - break; - case 0xFE: - read_comment_ext(gif); - break; - case 0xFF: - read_application_ext(gif); - break; - default: - ESP_LOGW(TAG, "unknown extension: %02X\n", label); - } -} - -static uint16_t -get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte) -{ - int bits_read; - int rpad; - int frag_size; - uint16_t key; - - key = 0; - for (bits_read = 0; bits_read < key_size; bits_read += frag_size) { - rpad = (*shift + bits_read) % 8; - if (rpad == 0) { - /* Update byte. */ - if (*sub_len == 0) { - f_gif_read(gif, sub_len, 1); /* Must be nonzero! */ - if (*sub_len == 0) return 0x1000; - } - f_gif_read(gif, byte, 1); - (*sub_len)--; - } - frag_size = MIN(key_size - bits_read, 8 - rpad); - key |= ((uint16_t) ((*byte) >> rpad)) << bits_read; - } - /* Clear extra bits to the left. */ - key &= (1 << key_size) - 1; - *shift = (*shift + key_size) % 8; - return key; -} - -#if LV_GIF_CACHE_DECODE_DATA -/* Decompress image pixels. - * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ -static int -read_image_data(gd_GIF *gif, int interlace) -{ - uint8_t sub_len, shift, byte; - int ret = 0; - int key_size; - int y, pass, linesize; - uint8_t *ptr = NULL; - uint8_t *ptr_row_start = NULL; - uint8_t *ptr_base = NULL; - size_t start, end; - uint16_t key, clear_code, stop_code, curr_code; - int frm_off, frm_size,curr_size,top_slot,new_codes,slot; - /* The first value of the value sequence corresponding to key */ - int first_value; - int last_key; - uint8_t *sp = NULL; - uint8_t *p_stack = NULL; - uint8_t *p_suffix = NULL; - uint16_t *p_prefix = NULL; - - /* get initial key size and clear code, stop code */ - f_gif_read(gif, &byte, 1); - key_size = (int) byte; - clear_code = 1 << key_size; - stop_code = clear_code + 1; - key = 0; - - start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - discard_sub_blocks(gif); - end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - f_gif_seek(gif, start, LV_FS_SEEK_SET); - - linesize = gif->width; - ptr_base = &gif->frame[gif->fy * linesize + gif->fx]; - ptr_row_start = ptr_base; - ptr = ptr_row_start; - sub_len = shift = 0; - /* decoder */ - pass = 0; - y = 0; - p_stack = gif->lzw_cache; - p_suffix = gif->lzw_cache + LZW_TABLE_SIZE; - p_prefix = (uint16_t*)(gif->lzw_cache + LZW_TABLE_SIZE * 2); - frm_off = 0; - frm_size = gif->fw * gif->fh; - curr_size = key_size + 1; - top_slot = 1 << curr_size; - new_codes = clear_code + 2; - slot = new_codes; - first_value = -1; - last_key = -1; - sp = p_stack; - - while (frm_off < frm_size) { - /* copy data to frame buffer */ - while (sp > p_stack) { - if(frm_off >= frm_size){ - ESP_LOGW(TAG, "LZW table token overflows the frame buffer"); - return -1; - } - *ptr++ = *(--sp); - frm_off += 1; - /* read one line */ - if ((ptr - ptr_row_start) == gif->fw) { - if (interlace) { - switch(pass) { - case 0: - case 1: - y += 8; - ptr_row_start += linesize * 8; - break; - case 2: - y += 4; - ptr_row_start += linesize * 4; - break; - case 3: - y += 2; - ptr_row_start += linesize * 2; - break; - default: - break; - } - while (y >= gif->fh) { - y = 4 >> pass; - ptr_row_start = ptr_base + linesize * y; - pass++; - } - } else { - ptr_row_start += linesize; - } - ptr = ptr_row_start; - } - } - - key = get_key(gif, curr_size, &sub_len, &shift, &byte); - - if (key == stop_code || key >= LZW_TABLE_SIZE) - break; - - if (key == clear_code) { - curr_size = key_size + 1; - slot = new_codes; - top_slot = 1 << curr_size; - first_value = last_key = -1; - sp = p_stack; - continue; - } - - curr_code = key; - /* - * If the current code is a code that will be added to the decoding - * dictionary, it is composed of the data list corresponding to the - * previous key and its first data. - * */ - if (curr_code == slot && first_value >= 0) { - *sp++ = first_value; - curr_code = last_key; - }else if(curr_code >= slot) - break; - - while (curr_code >= new_codes) { - *sp++ = p_suffix[curr_code]; - curr_code = p_prefix[curr_code]; - } - *sp++ = curr_code; - - /* Add code to decoding dictionary */ - if (slot < top_slot && last_key >= 0) { - p_suffix[slot] = curr_code; - p_prefix[slot++] = last_key; - } - first_value = curr_code; - last_key = key; - if (slot >= top_slot) { - if (curr_size < LZW_MAXBITS) { - top_slot <<= 1; - curr_size += 1; - } - } - } - - if (key == stop_code) f_gif_read(gif, &sub_len, 1); /* Must be zero! */ - f_gif_seek(gif, end, LV_FS_SEEK_SET); - return ret; -} -#else -static Table * -new_table(int key_size) -{ - int key; - int init_bulk = MAX(1 << (key_size + 1), 0x100); - Table * table = lv_malloc(sizeof(*table) + sizeof(Entry) * init_bulk); - if(table) { - table->bulk = init_bulk; - table->nentries = (1 << key_size) + 2; - table->entries = (Entry *) &table[1]; - for(key = 0; key < (1 << key_size); key++) - table->entries[key] = (Entry) { - 1, 0xFFF, key - }; - } - return table; -} - -/* Add table entry. Return value: - * 0 on success - * +1 if key size must be incremented after this addition - * -1 if could not realloc table */ -static int -add_entry(Table ** tablep, uint16_t length, uint16_t prefix, uint8_t suffix) -{ - Table * table = *tablep; - if(table->nentries == table->bulk) { - table->bulk *= 2; - table = lv_realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk); - if(!table) return -1; - table->entries = (Entry *) &table[1]; - *tablep = table; - } - table->entries[table->nentries] = (Entry) { - length, prefix, suffix - }; - table->nentries++; - if((table->nentries & (table->nentries - 1)) == 0) - return 1; - return 0; -} - -/* Compute output index of y-th input line, in frame of height h. */ -static int -interlaced_line_index(int h, int y) -{ - int p; /* number of lines in current pass */ - - p = (h - 1) / 8 + 1; - if(y < p) /* pass 1 */ - return y * 8; - y -= p; - p = (h - 5) / 8 + 1; - if(y < p) /* pass 2 */ - return y * 8 + 4; - y -= p; - p = (h - 3) / 4 + 1; - if(y < p) /* pass 3 */ - return y * 4 + 2; - y -= p; - /* pass 4 */ - return y * 2 + 1; -} - -/* Decompress image pixels. - * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ -static int -read_image_data(gd_GIF * gif, int interlace) -{ - uint8_t sub_len, shift, byte; - int init_key_size, key_size, table_is_full = 0; - int frm_off, frm_size, str_len = 0, i, p, x, y; - uint16_t key, clear, stop; - int ret; - Table * table; - Entry entry = {0}; - size_t start, end; - - f_gif_read(gif, &byte, 1); - key_size = (int) byte; - start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - discard_sub_blocks(gif); - end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - f_gif_seek(gif, start, LV_FS_SEEK_SET); - clear = 1 << key_size; - stop = clear + 1; - table = new_table(key_size); - key_size++; - init_key_size = key_size; - sub_len = shift = 0; - key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */ - frm_off = 0; - ret = 0; - frm_size = gif->fw * gif->fh; - while(frm_off < frm_size) { - if(key == clear) { - key_size = init_key_size; - table->nentries = (1 << (key_size - 1)) + 2; - table_is_full = 0; - } - else if(!table_is_full) { - ret = add_entry(&table, str_len + 1, key, entry.suffix); - if(ret == -1) { - lv_free(table); - return -1; - } - if(table->nentries == 0x1000) { - ret = 0; - table_is_full = 1; - } - } - key = get_key(gif, key_size, &sub_len, &shift, &byte); - if(key == clear) continue; - if(key == stop || key == 0x1000) break; - if(ret == 1) key_size++; - entry = table->entries[key]; - str_len = entry.length; - if(frm_off + str_len > frm_size){ - ESP_LOGW(TAG, "LZW table token overflows the frame buffer"); - lv_free(table); - return -1; - } - for(i = 0; i < str_len; i++) { - p = frm_off + entry.length - 1; - x = p % gif->fw; - y = p / gif->fw; - if(interlace) - y = interlaced_line_index((int) gif->fh, y); - gif->frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix; - if(entry.prefix == 0xFFF) - break; - else - entry = table->entries[entry.prefix]; - } - frm_off += str_len; - if(key < table->nentries - 1 && !table_is_full) - table->entries[table->nentries - 1].suffix = entry.suffix; - } - lv_free(table); - if(key == stop) f_gif_read(gif, &sub_len, 1); /* Must be zero! */ - f_gif_seek(gif, end, LV_FS_SEEK_SET); - return 0; -} - -#endif - -/* Read image. - * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ -static int -read_image(gd_GIF * gif) -{ - uint8_t fisrz; - int interlace; - - /* Image Descriptor. */ - gif->fx = read_num(gif); - gif->fy = read_num(gif); - gif->fw = read_num(gif); - gif->fh = read_num(gif); - if(gif->fx + (uint32_t)gif->fw > gif->width || gif->fy + (uint32_t)gif->fh > gif->height){ - ESP_LOGW(TAG, "Frame coordinates out of image bounds"); - return -1; - } - f_gif_read(gif, &fisrz, 1); - interlace = fisrz & 0x40; - /* Ignore Sort Flag. */ - /* Local Color Table? */ - if(fisrz & 0x80) { - /* Read LCT */ - gif->lct.size = 1 << ((fisrz & 0x07) + 1); - f_gif_read(gif, gif->lct.colors, 3 * gif->lct.size); - gif->palette = &gif->lct; - } - else - gif->palette = &gif->gct; - /* Image Data. */ - return read_image_data(gif, interlace); -} - -static void -render_frame_rect(gd_GIF * gif, uint8_t * buffer) -{ - int i = gif->fy * gif->width + gif->fx; -#ifdef GIFDEC_RENDER_FRAME - GIFDEC_RENDER_FRAME(&buffer[i * 4], gif->fw, gif->fh, gif->width, - &gif->frame[i], gif->palette->colors, - gif->gce.transparency ? gif->gce.tindex : 0x100); -#else - int j, k; - uint8_t index, * color; - - for(j = 0; j < gif->fh; j++) { - for(k = 0; k < gif->fw; k++) { - index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k]; - color = &gif->palette->colors[index * 3]; - if(!gif->gce.transparency || index != gif->gce.tindex) { - buffer[(i + k) * 4 + 0] = *(color + 2); - buffer[(i + k) * 4 + 1] = *(color + 1); - buffer[(i + k) * 4 + 2] = *(color + 0); - buffer[(i + k) * 4 + 3] = 0xFF; - } - } - i += gif->width; - } -#endif -} - -static void -dispose(gd_GIF * gif) -{ - int i; - uint8_t * bgcolor; - switch(gif->gce.disposal) { - case 2: /* Restore to background color. */ - bgcolor = &gif->palette->colors[gif->bgindex * 3]; - - uint8_t opa = 0xff; - if(gif->gce.transparency) opa = 0x00; - - i = gif->fy * gif->width + gif->fx; -#ifdef GIFDEC_FILL_BG - GIFDEC_FILL_BG(&(gif->canvas[i * 4]), gif->fw, gif->fh, gif->width, bgcolor, opa); -#else - int j, k; - for(j = 0; j < gif->fh; j++) { - for(k = 0; k < gif->fw; k++) { - gif->canvas[(i + k) * 4 + 0] = *(bgcolor + 2); - gif->canvas[(i + k) * 4 + 1] = *(bgcolor + 1); - gif->canvas[(i + k) * 4 + 2] = *(bgcolor + 0); - gif->canvas[(i + k) * 4 + 3] = opa; - } - i += gif->width; - } -#endif - break; - case 3: /* Restore to previous, i.e., don't update canvas.*/ - break; - default: - /* Add frame non-transparent pixels to canvas. */ - render_frame_rect(gif, gif->canvas); - } -} - -/* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */ -int -gd_get_frame(gd_GIF * gif) -{ - char sep; - - dispose(gif); - f_gif_read(gif, &sep, 1); - while(sep != ',') { - if(sep == ';') { - f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET); - if(gif->loop_count == 1 || gif->loop_count < 0) { - return 0; - } - else if(gif->loop_count > 1) { - gif->loop_count--; - } - } - else if(sep == '!') - read_ext(gif); - else return -1; - f_gif_read(gif, &sep, 1); - } - if(read_image(gif) == -1) - return -1; - return 1; -} - -void -gd_render_frame(gd_GIF * gif, uint8_t * buffer) -{ - render_frame_rect(gif, buffer); -} - -void -gd_rewind(gd_GIF * gif) -{ - gif->loop_count = -1; - f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET); -} - -void -gd_close_gif(gd_GIF * gif) -{ - f_gif_close(gif); - lv_free(gif); -} - -static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file) -{ - gif->f_rw_p = 0; - gif->data = NULL; - gif->is_file = is_file; - - if(is_file) { - lv_fs_res_t res = lv_fs_open(&gif->fd, path, LV_FS_MODE_RD); - if(res != LV_FS_RES_OK) return false; - else return true; - } - else { - gif->data = path; - return true; - } -} - -static void f_gif_read(gd_GIF * gif, void * buf, size_t len) -{ - if(gif->is_file) { - lv_fs_read(&gif->fd, buf, len, NULL); - } - else { - memcpy(buf, &gif->data[gif->f_rw_p], len); - gif->f_rw_p += len; - } -} - -static int f_gif_seek(gd_GIF * gif, size_t pos, int k) -{ - if(gif->is_file) { - lv_fs_seek(&gif->fd, pos, k); - uint32_t x; - lv_fs_tell(&gif->fd, &x); - return x; - } - else { - if(k == LV_FS_SEEK_CUR) gif->f_rw_p += pos; - else if(k == LV_FS_SEEK_SET) gif->f_rw_p = pos; - return gif->f_rw_p; - } -} - -static void f_gif_close(gd_GIF * gif) -{ - if(gif->is_file) { - lv_fs_close(&gif->fd); - } -} - +#include "gifdec.h" + +#include +#include +#include +#include + +#define TAG "GIF" + +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MAX(A, B) ((A) > (B) ? (A) : (B)) + +typedef struct Entry { + uint16_t length; + uint16_t prefix; + uint8_t suffix; +} Entry; + +typedef struct Table { + int bulk; + int nentries; + Entry * entries; +} Table; + +#if LV_GIF_CACHE_DECODE_DATA +#define LZW_MAXBITS 12 +#define LZW_TABLE_SIZE (1 << LZW_MAXBITS) +#define LZW_CACHE_SIZE (LZW_TABLE_SIZE * 4) +#endif + +static gd_GIF * gif_open(gd_GIF * gif); +static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file); +static void f_gif_read(gd_GIF * gif, void * buf, size_t len); +static int f_gif_seek(gd_GIF * gif, size_t pos, int k); +static void f_gif_close(gd_GIF * gif); + +#if LV_USE_DRAW_SW_ASM == LV_DRAW_SW_ASM_HELIUM + #include "gifdec_mve.h" +#endif + +static uint16_t +read_num(gd_GIF * gif) +{ + uint8_t bytes[2]; + + f_gif_read(gif, bytes, 2); + return bytes[0] + (((uint16_t) bytes[1]) << 8); +} + +gd_GIF * +gd_open_gif_file(const char * fname) +{ + gd_GIF gif_base; + memset(&gif_base, 0, sizeof(gif_base)); + + bool res = f_gif_open(&gif_base, fname, true); + if(!res) return NULL; + + return gif_open(&gif_base); +} + +gd_GIF * +gd_open_gif_data(const void * data) +{ + gd_GIF gif_base; + memset(&gif_base, 0, sizeof(gif_base)); + + bool res = f_gif_open(&gif_base, data, false); + if(!res) return NULL; + + return gif_open(&gif_base); +} + +static gd_GIF * gif_open(gd_GIF * gif_base) +{ + uint8_t sigver[3]; + uint16_t width, height, depth; + uint8_t fdsz, bgidx, aspect; + uint8_t * bgcolor; + int gct_sz; + gd_GIF * gif = NULL; + + /* Header */ + f_gif_read(gif_base, sigver, 3); + if(memcmp(sigver, "GIF", 3) != 0) { + ESP_LOGW(TAG, "invalid signature"); + goto fail; + } + /* Version */ + f_gif_read(gif_base, sigver, 3); + if(memcmp(sigver, "89a", 3) != 0 && memcmp(sigver, "87a", 3) != 0) { + ESP_LOGW(TAG, "invalid version"); + goto fail; + } + /* Width x Height */ + width = read_num(gif_base); + height = read_num(gif_base); + /* FDSZ */ + f_gif_read(gif_base, &fdsz, 1); + /* Presence of GCT */ + if(!(fdsz & 0x80)) { + ESP_LOGW(TAG, "no global color table"); + goto fail; + } + /* Color Space's Depth */ + depth = ((fdsz >> 4) & 7) + 1; + /* Ignore Sort Flag. */ + /* GCT Size */ + gct_sz = 1 << ((fdsz & 0x07) + 1); + /* Background Color Index */ + f_gif_read(gif_base, &bgidx, 1); + /* Aspect Ratio */ + f_gif_read(gif_base, &aspect, 1); + /* Create gd_GIF Structure. */ + if(0 == width || 0 == height){ + ESP_LOGW(TAG, "Zero size image"); + goto fail; + } +#if LV_GIF_CACHE_DECODE_DATA + if(0 == (INT_MAX - sizeof(gd_GIF) - LZW_CACHE_SIZE) / width / height / 5){ + ESP_LOGW(TAG, "Image dimensions are too large"); + goto fail; + } + gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height + LZW_CACHE_SIZE); +#else + if(0 == (INT_MAX - sizeof(gd_GIF)) / width / height / 5){ + ESP_LOGW(TAG, "Image dimensions are too large"); + goto fail; + } + gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height); +#endif + if(!gif) goto fail; + memcpy(gif, gif_base, sizeof(gd_GIF)); + gif->width = width; + gif->height = height; + gif->depth = depth; + /* Read GCT */ + gif->gct.size = gct_sz; + f_gif_read(gif, gif->gct.colors, 3 * gif->gct.size); + gif->palette = &gif->gct; + gif->bgindex = bgidx; + gif->canvas = (uint8_t *) &gif[1]; + gif->frame = &gif->canvas[4 * width * height]; + if(gif->bgindex) { + memset(gif->frame, gif->bgindex, gif->width * gif->height); + } + bgcolor = &gif->palette->colors[gif->bgindex * 3]; + #if LV_GIF_CACHE_DECODE_DATA + gif->lzw_cache = gif->frame + width * height; + #endif + +#ifdef GIFDEC_FILL_BG + GIFDEC_FILL_BG(gif->canvas, gif->width * gif->height, 1, gif->width * gif->height, bgcolor, 0x00); +#else + for(int i = 0; i < gif->width * gif->height; i++) { + gif->canvas[i * 4 + 0] = *(bgcolor + 2); + gif->canvas[i * 4 + 1] = *(bgcolor + 1); + gif->canvas[i * 4 + 2] = *(bgcolor + 0); + gif->canvas[i * 4 + 3] = 0x00; // 初始化为透明,让第一帧根据自己的透明度设置来渲染 + } +#endif + gif->anim_start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + gif->loop_count = -1; + goto ok; +fail: + f_gif_close(gif_base); +ok: + return gif; +} + +static void +discard_sub_blocks(gd_GIF * gif) +{ + uint8_t size; + + do { + f_gif_read(gif, &size, 1); + f_gif_seek(gif, size, LV_FS_SEEK_CUR); + } while(size); +} + +static void +read_plain_text_ext(gd_GIF * gif) +{ + if(gif->plain_text) { + uint16_t tx, ty, tw, th; + uint8_t cw, ch, fg, bg; + size_t sub_block; + f_gif_seek(gif, 1, LV_FS_SEEK_CUR); /* block size = 12 */ + tx = read_num(gif); + ty = read_num(gif); + tw = read_num(gif); + th = read_num(gif); + f_gif_read(gif, &cw, 1); + f_gif_read(gif, &ch, 1); + f_gif_read(gif, &fg, 1); + f_gif_read(gif, &bg, 1); + sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg); + f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); + } + else { + /* Discard plain text metadata. */ + f_gif_seek(gif, 13, LV_FS_SEEK_CUR); + } + /* Discard plain text sub-blocks. */ + discard_sub_blocks(gif); +} + +static void +read_graphic_control_ext(gd_GIF * gif) +{ + uint8_t rdit; + + /* Discard block size (always 0x04). */ + f_gif_seek(gif, 1, LV_FS_SEEK_CUR); + f_gif_read(gif, &rdit, 1); + gif->gce.disposal = (rdit >> 2) & 3; + gif->gce.input = rdit & 2; + gif->gce.transparency = rdit & 1; + gif->gce.delay = read_num(gif); + f_gif_read(gif, &gif->gce.tindex, 1); + /* Skip block terminator. */ + f_gif_seek(gif, 1, LV_FS_SEEK_CUR); +} + +static void +read_comment_ext(gd_GIF * gif) +{ + if(gif->comment) { + size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + gif->comment(gif); + f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); + } + /* Discard comment sub-blocks. */ + discard_sub_blocks(gif); +} + +static void +read_application_ext(gd_GIF * gif) +{ + char app_id[8]; + char app_auth_code[3]; + uint16_t loop_count; + + /* Discard block size (always 0x0B). */ + f_gif_seek(gif, 1, LV_FS_SEEK_CUR); + /* Application Identifier. */ + f_gif_read(gif, app_id, 8); + /* Application Authentication Code. */ + f_gif_read(gif, app_auth_code, 3); + if(!strncmp(app_id, "NETSCAPE", sizeof(app_id))) { + /* Discard block size (0x03) and constant byte (0x01). */ + f_gif_seek(gif, 2, LV_FS_SEEK_CUR); + loop_count = read_num(gif); + if(gif->loop_count < 0) { + if(loop_count == 0) { + gif->loop_count = 0; + } + else { + gif->loop_count = loop_count + 1; + } + } + /* Skip block terminator. */ + f_gif_seek(gif, 1, LV_FS_SEEK_CUR); + } + else if(gif->application) { + size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + gif->application(gif, app_id, app_auth_code); + f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); + discard_sub_blocks(gif); + } + else { + discard_sub_blocks(gif); + } +} + +static void +read_ext(gd_GIF * gif) +{ + uint8_t label; + + f_gif_read(gif, &label, 1); + switch(label) { + case 0x01: + read_plain_text_ext(gif); + break; + case 0xF9: + read_graphic_control_ext(gif); + break; + case 0xFE: + read_comment_ext(gif); + break; + case 0xFF: + read_application_ext(gif); + break; + default: + ESP_LOGW(TAG, "unknown extension: %02X\n", label); + } +} + +static uint16_t +get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte) +{ + int bits_read; + int rpad; + int frag_size; + uint16_t key; + + key = 0; + for (bits_read = 0; bits_read < key_size; bits_read += frag_size) { + rpad = (*shift + bits_read) % 8; + if (rpad == 0) { + /* Update byte. */ + if (*sub_len == 0) { + f_gif_read(gif, sub_len, 1); /* Must be nonzero! */ + if (*sub_len == 0) return 0x1000; + } + f_gif_read(gif, byte, 1); + (*sub_len)--; + } + frag_size = MIN(key_size - bits_read, 8 - rpad); + key |= ((uint16_t) ((*byte) >> rpad)) << bits_read; + } + /* Clear extra bits to the left. */ + key &= (1 << key_size) - 1; + *shift = (*shift + key_size) % 8; + return key; +} + +#if LV_GIF_CACHE_DECODE_DATA +/* Decompress image pixels. + * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ +static int +read_image_data(gd_GIF *gif, int interlace) +{ + uint8_t sub_len, shift, byte; + int ret = 0; + int key_size; + int y, pass, linesize; + uint8_t *ptr = NULL; + uint8_t *ptr_row_start = NULL; + uint8_t *ptr_base = NULL; + size_t start, end; + uint16_t key, clear_code, stop_code, curr_code; + int frm_off, frm_size,curr_size,top_slot,new_codes,slot; + /* The first value of the value sequence corresponding to key */ + int first_value; + int last_key; + uint8_t *sp = NULL; + uint8_t *p_stack = NULL; + uint8_t *p_suffix = NULL; + uint16_t *p_prefix = NULL; + + /* get initial key size and clear code, stop code */ + f_gif_read(gif, &byte, 1); + key_size = (int) byte; + clear_code = 1 << key_size; + stop_code = clear_code + 1; + key = 0; + + start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + discard_sub_blocks(gif); + end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + f_gif_seek(gif, start, LV_FS_SEEK_SET); + + linesize = gif->width; + ptr_base = &gif->frame[gif->fy * linesize + gif->fx]; + ptr_row_start = ptr_base; + ptr = ptr_row_start; + sub_len = shift = 0; + /* decoder */ + pass = 0; + y = 0; + p_stack = gif->lzw_cache; + p_suffix = gif->lzw_cache + LZW_TABLE_SIZE; + p_prefix = (uint16_t*)(gif->lzw_cache + LZW_TABLE_SIZE * 2); + frm_off = 0; + frm_size = gif->fw * gif->fh; + curr_size = key_size + 1; + top_slot = 1 << curr_size; + new_codes = clear_code + 2; + slot = new_codes; + first_value = -1; + last_key = -1; + sp = p_stack; + + while (frm_off < frm_size) { + /* copy data to frame buffer */ + while (sp > p_stack) { + if(frm_off >= frm_size){ + ESP_LOGW(TAG, "LZW table token overflows the frame buffer"); + return -1; + } + *ptr++ = *(--sp); + frm_off += 1; + /* read one line */ + if ((ptr - ptr_row_start) == gif->fw) { + if (interlace) { + switch(pass) { + case 0: + case 1: + y += 8; + ptr_row_start += linesize * 8; + break; + case 2: + y += 4; + ptr_row_start += linesize * 4; + break; + case 3: + y += 2; + ptr_row_start += linesize * 2; + break; + default: + break; + } + while (y >= gif->fh) { + y = 4 >> pass; + ptr_row_start = ptr_base + linesize * y; + pass++; + } + } else { + ptr_row_start += linesize; + } + ptr = ptr_row_start; + } + } + + key = get_key(gif, curr_size, &sub_len, &shift, &byte); + + if (key == stop_code || key >= LZW_TABLE_SIZE) + break; + + if (key == clear_code) { + curr_size = key_size + 1; + slot = new_codes; + top_slot = 1 << curr_size; + first_value = last_key = -1; + sp = p_stack; + continue; + } + + curr_code = key; + /* + * If the current code is a code that will be added to the decoding + * dictionary, it is composed of the data list corresponding to the + * previous key and its first data. + * */ + if (curr_code == slot && first_value >= 0) { + *sp++ = first_value; + curr_code = last_key; + }else if(curr_code >= slot) + break; + + while (curr_code >= new_codes) { + *sp++ = p_suffix[curr_code]; + curr_code = p_prefix[curr_code]; + } + *sp++ = curr_code; + + /* Add code to decoding dictionary */ + if (slot < top_slot && last_key >= 0) { + p_suffix[slot] = curr_code; + p_prefix[slot++] = last_key; + } + first_value = curr_code; + last_key = key; + if (slot >= top_slot) { + if (curr_size < LZW_MAXBITS) { + top_slot <<= 1; + curr_size += 1; + } + } + } + + if (key == stop_code) f_gif_read(gif, &sub_len, 1); /* Must be zero! */ + f_gif_seek(gif, end, LV_FS_SEEK_SET); + return ret; +} +#else +static Table * +new_table(int key_size) +{ + int key; + int init_bulk = MAX(1 << (key_size + 1), 0x100); + Table * table = lv_malloc(sizeof(*table) + sizeof(Entry) * init_bulk); + if(table) { + table->bulk = init_bulk; + table->nentries = (1 << key_size) + 2; + table->entries = (Entry *) &table[1]; + for(key = 0; key < (1 << key_size); key++) + table->entries[key] = (Entry) { + 1, 0xFFF, key + }; + } + return table; +} + +/* Add table entry. Return value: + * 0 on success + * +1 if key size must be incremented after this addition + * -1 if could not realloc table */ +static int +add_entry(Table ** tablep, uint16_t length, uint16_t prefix, uint8_t suffix) +{ + Table * table = *tablep; + if(table->nentries == table->bulk) { + table->bulk *= 2; + table = lv_realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk); + if(!table) return -1; + table->entries = (Entry *) &table[1]; + *tablep = table; + } + table->entries[table->nentries] = (Entry) { + length, prefix, suffix + }; + table->nentries++; + if((table->nentries & (table->nentries - 1)) == 0) + return 1; + return 0; +} + +/* Compute output index of y-th input line, in frame of height h. */ +static int +interlaced_line_index(int h, int y) +{ + int p; /* number of lines in current pass */ + + p = (h - 1) / 8 + 1; + if(y < p) /* pass 1 */ + return y * 8; + y -= p; + p = (h - 5) / 8 + 1; + if(y < p) /* pass 2 */ + return y * 8 + 4; + y -= p; + p = (h - 3) / 4 + 1; + if(y < p) /* pass 3 */ + return y * 4 + 2; + y -= p; + /* pass 4 */ + return y * 2 + 1; +} + +/* Decompress image pixels. + * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ +static int +read_image_data(gd_GIF * gif, int interlace) +{ + uint8_t sub_len, shift, byte; + int init_key_size, key_size, table_is_full = 0; + int frm_off, frm_size, str_len = 0, i, p, x, y; + uint16_t key, clear, stop; + int ret; + Table * table; + Entry entry = {0}; + size_t start, end; + + f_gif_read(gif, &byte, 1); + key_size = (int) byte; + start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + discard_sub_blocks(gif); + end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + f_gif_seek(gif, start, LV_FS_SEEK_SET); + clear = 1 << key_size; + stop = clear + 1; + table = new_table(key_size); + key_size++; + init_key_size = key_size; + sub_len = shift = 0; + key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */ + frm_off = 0; + ret = 0; + frm_size = gif->fw * gif->fh; + while(frm_off < frm_size) { + if(key == clear) { + key_size = init_key_size; + table->nentries = (1 << (key_size - 1)) + 2; + table_is_full = 0; + } + else if(!table_is_full) { + ret = add_entry(&table, str_len + 1, key, entry.suffix); + if(ret == -1) { + lv_free(table); + return -1; + } + if(table->nentries == 0x1000) { + ret = 0; + table_is_full = 1; + } + } + key = get_key(gif, key_size, &sub_len, &shift, &byte); + if(key == clear) continue; + if(key == stop || key == 0x1000) break; + if(ret == 1) key_size++; + entry = table->entries[key]; + str_len = entry.length; + if(frm_off + str_len > frm_size){ + ESP_LOGW(TAG, "LZW table token overflows the frame buffer"); + lv_free(table); + return -1; + } + for(i = 0; i < str_len; i++) { + p = frm_off + entry.length - 1; + x = p % gif->fw; + y = p / gif->fw; + if(interlace) + y = interlaced_line_index((int) gif->fh, y); + gif->frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix; + if(entry.prefix == 0xFFF) + break; + else + entry = table->entries[entry.prefix]; + } + frm_off += str_len; + if(key < table->nentries - 1 && !table_is_full) + table->entries[table->nentries - 1].suffix = entry.suffix; + } + lv_free(table); + if(key == stop) f_gif_read(gif, &sub_len, 1); /* Must be zero! */ + f_gif_seek(gif, end, LV_FS_SEEK_SET); + return 0; +} + +#endif + +/* Read image. + * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ +static int +read_image(gd_GIF * gif) +{ + uint8_t fisrz; + int interlace; + + /* Image Descriptor. */ + gif->fx = read_num(gif); + gif->fy = read_num(gif); + gif->fw = read_num(gif); + gif->fh = read_num(gif); + if(gif->fx + (uint32_t)gif->fw > gif->width || gif->fy + (uint32_t)gif->fh > gif->height){ + ESP_LOGW(TAG, "Frame coordinates out of image bounds"); + return -1; + } + f_gif_read(gif, &fisrz, 1); + interlace = fisrz & 0x40; + /* Ignore Sort Flag. */ + /* Local Color Table? */ + if(fisrz & 0x80) { + /* Read LCT */ + gif->lct.size = 1 << ((fisrz & 0x07) + 1); + f_gif_read(gif, gif->lct.colors, 3 * gif->lct.size); + gif->palette = &gif->lct; + } + else + gif->palette = &gif->gct; + /* Image Data. */ + return read_image_data(gif, interlace); +} + +static void +render_frame_rect(gd_GIF * gif, uint8_t * buffer) +{ + int i = gif->fy * gif->width + gif->fx; +#ifdef GIFDEC_RENDER_FRAME + GIFDEC_RENDER_FRAME(&buffer[i * 4], gif->fw, gif->fh, gif->width, + &gif->frame[i], gif->palette->colors, + gif->gce.transparency ? gif->gce.tindex : 0x100); +#else + int j, k; + uint8_t index, * color; + + for(j = 0; j < gif->fh; j++) { + for(k = 0; k < gif->fw; k++) { + index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k]; + color = &gif->palette->colors[index * 3]; + if(!gif->gce.transparency || index != gif->gce.tindex) { + buffer[(i + k) * 4 + 0] = *(color + 2); + buffer[(i + k) * 4 + 1] = *(color + 1); + buffer[(i + k) * 4 + 2] = *(color + 0); + buffer[(i + k) * 4 + 3] = 0xFF; + } + } + i += gif->width; + } +#endif +} + +static void +dispose(gd_GIF * gif) +{ + int i; + uint8_t * bgcolor; + switch(gif->gce.disposal) { + case 2: /* Restore to background color. */ + bgcolor = &gif->palette->colors[gif->bgindex * 3]; + + uint8_t opa = 0xff; + if(gif->gce.transparency) opa = 0x00; + + i = gif->fy * gif->width + gif->fx; +#ifdef GIFDEC_FILL_BG + GIFDEC_FILL_BG(&(gif->canvas[i * 4]), gif->fw, gif->fh, gif->width, bgcolor, opa); +#else + int j, k; + for(j = 0; j < gif->fh; j++) { + for(k = 0; k < gif->fw; k++) { + gif->canvas[(i + k) * 4 + 0] = *(bgcolor + 2); + gif->canvas[(i + k) * 4 + 1] = *(bgcolor + 1); + gif->canvas[(i + k) * 4 + 2] = *(bgcolor + 0); + gif->canvas[(i + k) * 4 + 3] = opa; + } + i += gif->width; + } +#endif + break; + case 3: /* Restore to previous, i.e., don't update canvas.*/ + break; + default: + /* Add frame non-transparent pixels to canvas. */ + render_frame_rect(gif, gif->canvas); + } +} + +/* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */ +int +gd_get_frame(gd_GIF * gif) +{ + char sep; + + dispose(gif); + f_gif_read(gif, &sep, 1); + while(sep != ',') { + if(sep == ';') { + f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET); + if(gif->loop_count == 1 || gif->loop_count < 0) { + return 0; + } + else if(gif->loop_count > 1) { + gif->loop_count--; + } + } + else if(sep == '!') + read_ext(gif); + else return -1; + f_gif_read(gif, &sep, 1); + } + if(read_image(gif) == -1) + return -1; + return 1; +} + +void +gd_render_frame(gd_GIF * gif, uint8_t * buffer) +{ + render_frame_rect(gif, buffer); +} + +void +gd_rewind(gd_GIF * gif) +{ + gif->loop_count = -1; + f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET); +} + +void +gd_close_gif(gd_GIF * gif) +{ + f_gif_close(gif); + lv_free(gif); +} + +static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file) +{ + gif->f_rw_p = 0; + gif->data = NULL; + gif->is_file = is_file; + + if(is_file) { + lv_fs_res_t res = lv_fs_open(&gif->fd, path, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) return false; + else return true; + } + else { + gif->data = path; + return true; + } +} + +static void f_gif_read(gd_GIF * gif, void * buf, size_t len) +{ + if(gif->is_file) { + lv_fs_read(&gif->fd, buf, len, NULL); + } + else { + memcpy(buf, &gif->data[gif->f_rw_p], len); + gif->f_rw_p += len; + } +} + +static int f_gif_seek(gd_GIF * gif, size_t pos, int k) +{ + if(gif->is_file) { + lv_fs_seek(&gif->fd, pos, k); + uint32_t x; + lv_fs_tell(&gif->fd, &x); + return x; + } + else { + if(k == LV_FS_SEEK_CUR) gif->f_rw_p += pos; + else if(k == LV_FS_SEEK_SET) gif->f_rw_p = pos; + return gif->f_rw_p; + } +} + +static void f_gif_close(gd_GIF * gif) +{ + if(gif->is_file) { + lv_fs_close(&gif->fd); + } +} + diff --git a/main/display/lvgl_display/gif/gifdec.h b/main/display/lvgl_display/gif/gifdec.h index 12c171e..95da703 100644 --- a/main/display/lvgl_display/gif/gifdec.h +++ b/main/display/lvgl_display/gif/gifdec.h @@ -1,68 +1,68 @@ -#ifndef GIFDEC_H -#define GIFDEC_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include - -typedef struct _gd_Palette { - int size; - uint8_t colors[0x100 * 3]; -} gd_Palette; - -typedef struct _gd_GCE { - uint16_t delay; - uint8_t tindex; - uint8_t disposal; - int input; - int transparency; -} gd_GCE; - - - -typedef struct _gd_GIF { - lv_fs_file_t fd; - const char * data; - uint8_t is_file; - uint32_t f_rw_p; - int32_t anim_start; - uint16_t width, height; - uint16_t depth; - int32_t loop_count; - gd_GCE gce; - gd_Palette * palette; - gd_Palette lct, gct; - void (*plain_text)( - struct _gd_GIF * gif, uint16_t tx, uint16_t ty, - uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch, - uint8_t fg, uint8_t bg - ); - void (*comment)(struct _gd_GIF * gif); - void (*application)(struct _gd_GIF * gif, char id[8], char auth[3]); - uint16_t fx, fy, fw, fh; - uint8_t bgindex; - uint8_t * canvas, * frame; -#if LV_GIF_CACHE_DECODE_DATA - uint8_t *lzw_cache; -#endif -} gd_GIF; - -gd_GIF * gd_open_gif_file(const char * fname); - -gd_GIF * gd_open_gif_data(const void * data); - -void gd_render_frame(gd_GIF * gif, uint8_t * buffer); - -int gd_get_frame(gd_GIF * gif); -void gd_rewind(gd_GIF * gif); -void gd_close_gif(gd_GIF * gif); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* GIFDEC_H */ +#ifndef GIFDEC_H +#define GIFDEC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include + +typedef struct _gd_Palette { + int size; + uint8_t colors[0x100 * 3]; +} gd_Palette; + +typedef struct _gd_GCE { + uint16_t delay; + uint8_t tindex; + uint8_t disposal; + int input; + int transparency; +} gd_GCE; + + + +typedef struct _gd_GIF { + lv_fs_file_t fd; + const char * data; + uint8_t is_file; + uint32_t f_rw_p; + int32_t anim_start; + uint16_t width, height; + uint16_t depth; + int32_t loop_count; + gd_GCE gce; + gd_Palette * palette; + gd_Palette lct, gct; + void (*plain_text)( + struct _gd_GIF * gif, uint16_t tx, uint16_t ty, + uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch, + uint8_t fg, uint8_t bg + ); + void (*comment)(struct _gd_GIF * gif); + void (*application)(struct _gd_GIF * gif, char id[8], char auth[3]); + uint16_t fx, fy, fw, fh; + uint8_t bgindex; + uint8_t * canvas, * frame; +#if LV_GIF_CACHE_DECODE_DATA + uint8_t *lzw_cache; +#endif +} gd_GIF; + +gd_GIF * gd_open_gif_file(const char * fname); + +gd_GIF * gd_open_gif_data(const void * data); + +void gd_render_frame(gd_GIF * gif, uint8_t * buffer); + +int gd_get_frame(gd_GIF * gif); +void gd_rewind(gd_GIF * gif); +void gd_close_gif(gd_GIF * gif); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* GIFDEC_H */ diff --git a/main/display/lvgl_display/gif/gifdec_mve.h b/main/display/lvgl_display/gif/gifdec_mve.h index 6d83393..a680fae 100644 --- a/main/display/lvgl_display/gif/gifdec_mve.h +++ b/main/display/lvgl_display/gif/gifdec_mve.h @@ -1,140 +1,140 @@ -/** - * @file gifdec_mve.h - * - */ - -#ifndef GIFDEC_MVE_H -#define GIFDEC_MVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -/********************* - * INCLUDES - *********************/ -#include -#include "../../misc/lv_color.h" - -/********************* - * DEFINES - *********************/ - -#define GIFDEC_FILL_BG(dst, w, h, stride, color, opa) \ - _gifdec_fill_bg_mve(dst, w, h, stride, color, opa) - -#define GIFDEC_RENDER_FRAME(dst, w, h, stride, frame, pattern, tindex) \ - _gifdec_render_frame_mve(dst, w, h, stride, frame, pattern, tindex) - -/********************** - * MACROS - **********************/ - -/********************** - * TYPEDEFS - **********************/ - -/********************** - * GLOBAL PROTOTYPES - **********************/ - -static inline void _gifdec_fill_bg_mve(uint8_t * dst, uint16_t w, uint16_t h, uint16_t stride, uint8_t * color, - uint8_t opa) -{ - lv_color32_t c = lv_color32_make(*(color + 0), *(color + 1), *(color + 2), opa); - uint32_t color_32 = *(uint32_t *)&c; - - __asm volatile( - ".p2align 2 \n" - "vdup.32 q0, %[src] \n" - "3: \n" - "mov r0, %[dst] \n" - - "wlstp.32 lr, %[w], 1f \n" - "2: \n" - - "vstrw.32 q0, [r0], #16 \n" - "letp lr, 2b \n" - "1: \n" - "add %[dst], %[iTargetStride] \n" - "subs %[h], #1 \n" - "bne 3b \n" - : [dst] "+r"(dst), - [h] "+r"(h) - : [src] "r"(color_32), - [w] "r"(w), - [iTargetStride] "r"(stride * sizeof(uint32_t)) - : "r0", "q0", "memory", "r14", "cc"); -} - -static inline void _gifdec_render_frame_mve(uint8_t * dst, uint16_t w, uint16_t h, uint16_t stride, uint8_t * frame, - uint8_t * pattern, uint16_t tindex) -{ - if(w == 0 || h == 0) { - return; - } - - __asm volatile( - "vmov.u16 q3, #255 \n" - "vshl.u16 q3, q3, #8 \n" /* left shift 8 for a*/ - - "mov r0, #2 \n" - "vidup.u16 q6, r0, #4 \n" /* [2, 6, 10, 14, 18, 22, 26, 30] */ - "mov r0, #0 \n" - "vidup.u16 q7, r0, #4 \n" /* [0, 4, 8, 12, 16, 20, 24, 28] */ - - "3: \n" - "mov r1, %[dst] \n" - "mov r2, %[frame] \n" - - "wlstp.16 lr, %[w], 1f \n" - "2: \n" - - "mov r0, #3 \n" - "vldrb.u16 q4, [r2], #8 \n" - "vmul.u16 q5, q4, r0 \n" - - "mov r0, #1 \n" - "vldrb.u16 q2, [%[pattern], q5] \n" /* load 8 pixel r*/ - - "vadd.u16 q5, q5, r0 \n" - "vldrb.u16 q1, [%[pattern], q5] \n" /* load 8 pixel g*/ - - "vadd.u16 q5, q5, r0 \n" - "vldrb.u16 q0, [%[pattern], q5] \n" /* load 8 pixel b*/ - - "vshl.u16 q1, q1, #8 \n" /* left shift 8 for g*/ - - "vorr.u16 q0, q0, q1 \n" /* make 8 pixel gb*/ - "vorr.u16 q1, q2, q3 \n" /* make 8 pixel ar*/ - - "vcmp.i16 ne, q4, %[tindex] \n" - "vpstt \n" - "vstrht.16 q0, [r1, q7] \n" - "vstrht.16 q1, [r1, q6] \n" - "add r1, r1, #32 \n" - - "letp lr, 2b \n" - - "1: \n" - "mov r0, %[stride], LSL #2 \n" - "add %[dst], r0 \n" - "add %[frame], %[stride] \n" - "subs %[h], #1 \n" - "bne 3b \n" - - : [dst] "+r"(dst), - [frame] "+r"(frame), - [h] "+r"(h) - : [pattern] "r"(pattern), - [w] "r"(w), - [stride] "r"(stride), - [tindex] "r"(tindex) - : "r0", "r1", "r2", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "memory", "r14", "cc"); -} - -#ifdef __cplusplus -} /*extern "C"*/ -#endif - -#endif /*GIFDEC_MVE_H*/ +/** + * @file gifdec_mve.h + * + */ + +#ifndef GIFDEC_MVE_H +#define GIFDEC_MVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include +#include "../../misc/lv_color.h" + +/********************* + * DEFINES + *********************/ + +#define GIFDEC_FILL_BG(dst, w, h, stride, color, opa) \ + _gifdec_fill_bg_mve(dst, w, h, stride, color, opa) + +#define GIFDEC_RENDER_FRAME(dst, w, h, stride, frame, pattern, tindex) \ + _gifdec_render_frame_mve(dst, w, h, stride, frame, pattern, tindex) + +/********************** + * MACROS + **********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +static inline void _gifdec_fill_bg_mve(uint8_t * dst, uint16_t w, uint16_t h, uint16_t stride, uint8_t * color, + uint8_t opa) +{ + lv_color32_t c = lv_color32_make(*(color + 0), *(color + 1), *(color + 2), opa); + uint32_t color_32 = *(uint32_t *)&c; + + __asm volatile( + ".p2align 2 \n" + "vdup.32 q0, %[src] \n" + "3: \n" + "mov r0, %[dst] \n" + + "wlstp.32 lr, %[w], 1f \n" + "2: \n" + + "vstrw.32 q0, [r0], #16 \n" + "letp lr, 2b \n" + "1: \n" + "add %[dst], %[iTargetStride] \n" + "subs %[h], #1 \n" + "bne 3b \n" + : [dst] "+r"(dst), + [h] "+r"(h) + : [src] "r"(color_32), + [w] "r"(w), + [iTargetStride] "r"(stride * sizeof(uint32_t)) + : "r0", "q0", "memory", "r14", "cc"); +} + +static inline void _gifdec_render_frame_mve(uint8_t * dst, uint16_t w, uint16_t h, uint16_t stride, uint8_t * frame, + uint8_t * pattern, uint16_t tindex) +{ + if(w == 0 || h == 0) { + return; + } + + __asm volatile( + "vmov.u16 q3, #255 \n" + "vshl.u16 q3, q3, #8 \n" /* left shift 8 for a*/ + + "mov r0, #2 \n" + "vidup.u16 q6, r0, #4 \n" /* [2, 6, 10, 14, 18, 22, 26, 30] */ + "mov r0, #0 \n" + "vidup.u16 q7, r0, #4 \n" /* [0, 4, 8, 12, 16, 20, 24, 28] */ + + "3: \n" + "mov r1, %[dst] \n" + "mov r2, %[frame] \n" + + "wlstp.16 lr, %[w], 1f \n" + "2: \n" + + "mov r0, #3 \n" + "vldrb.u16 q4, [r2], #8 \n" + "vmul.u16 q5, q4, r0 \n" + + "mov r0, #1 \n" + "vldrb.u16 q2, [%[pattern], q5] \n" /* load 8 pixel r*/ + + "vadd.u16 q5, q5, r0 \n" + "vldrb.u16 q1, [%[pattern], q5] \n" /* load 8 pixel g*/ + + "vadd.u16 q5, q5, r0 \n" + "vldrb.u16 q0, [%[pattern], q5] \n" /* load 8 pixel b*/ + + "vshl.u16 q1, q1, #8 \n" /* left shift 8 for g*/ + + "vorr.u16 q0, q0, q1 \n" /* make 8 pixel gb*/ + "vorr.u16 q1, q2, q3 \n" /* make 8 pixel ar*/ + + "vcmp.i16 ne, q4, %[tindex] \n" + "vpstt \n" + "vstrht.16 q0, [r1, q7] \n" + "vstrht.16 q1, [r1, q6] \n" + "add r1, r1, #32 \n" + + "letp lr, 2b \n" + + "1: \n" + "mov r0, %[stride], LSL #2 \n" + "add %[dst], r0 \n" + "add %[frame], %[stride] \n" + "subs %[h], #1 \n" + "bne 3b \n" + + : [dst] "+r"(dst), + [frame] "+r"(frame), + [h] "+r"(h) + : [pattern] "r"(pattern), + [w] "r"(w), + [stride] "r"(stride), + [tindex] "r"(tindex) + : "r0", "r1", "r2", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "memory", "r14", "cc"); +} + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*GIFDEC_MVE_H*/ diff --git a/main/display/lvgl_display/gif/lvgl_gif.cc b/main/display/lvgl_display/gif/lvgl_gif.cc index 172d5ba..ebb2764 100644 --- a/main/display/lvgl_display/gif/lvgl_gif.cc +++ b/main/display/lvgl_display/gif/lvgl_gif.cc @@ -1,208 +1,208 @@ -#include "lvgl_gif.h" -#include -#include - -#define TAG "LvglGif" - -LvglGif::LvglGif(const lv_img_dsc_t* img_dsc) - : gif_(nullptr), timer_(nullptr), last_call_(0), playing_(false), loaded_(false) { - if (!img_dsc || !img_dsc->data) { - ESP_LOGE(TAG, "Invalid image descriptor"); - return; - } - - gif_ = gd_open_gif_data(img_dsc->data); - if (!gif_) { - ESP_LOGE(TAG, "Failed to open GIF from image descriptor"); - return; - } - - // Setup LVGL image descriptor - memset(&img_dsc_, 0, sizeof(img_dsc_)); - img_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC; - img_dsc_.header.flags = LV_IMAGE_FLAGS_MODIFIABLE; - img_dsc_.header.cf = LV_COLOR_FORMAT_ARGB8888; - img_dsc_.header.w = gif_->width; - img_dsc_.header.h = gif_->height; - img_dsc_.header.stride = gif_->width * 4; - img_dsc_.data = gif_->canvas; - img_dsc_.data_size = gif_->width * gif_->height * 4; - - // Render first frame - if (gif_->canvas) { - gd_render_frame(gif_, gif_->canvas); - } - - loaded_ = true; - ESP_LOGD(TAG, "GIF loaded from image descriptor: %dx%d", gif_->width, gif_->height); -} - -// Destructor -LvglGif::~LvglGif() { - Cleanup(); -} - -// LvglImage interface implementation -const lv_img_dsc_t* LvglGif::image_dsc() const { - if (!loaded_) { - return nullptr; - } - return &img_dsc_; -} - -// Animation control methods -void LvglGif::Start() { - if (!loaded_ || !gif_) { - ESP_LOGW(TAG, "GIF not loaded, cannot start"); - return; - } - - if (!timer_) { - timer_ = lv_timer_create([](lv_timer_t* timer) { - LvglGif* gif_obj = static_cast(lv_timer_get_user_data(timer)); - gif_obj->NextFrame(); - }, 10, this); - } - - if (timer_) { - playing_ = true; - last_call_ = lv_tick_get(); - lv_timer_resume(timer_); - lv_timer_reset(timer_); - - // Render first frame - NextFrame(); - - ESP_LOGD(TAG, "GIF animation started"); - } -} - -void LvglGif::Pause() { - if (timer_) { - playing_ = false; - lv_timer_pause(timer_); - ESP_LOGD(TAG, "GIF animation paused"); - } -} - -void LvglGif::Resume() { - if (!loaded_ || !gif_) { - ESP_LOGW(TAG, "GIF not loaded, cannot resume"); - return; - } - - if (timer_) { - playing_ = true; - lv_timer_resume(timer_); - ESP_LOGD(TAG, "GIF animation resumed"); - } -} - -void LvglGif::Stop() { - if (timer_) { - playing_ = false; - lv_timer_pause(timer_); - } - - if (gif_) { - gd_rewind(gif_); - NextFrame(); - ESP_LOGD(TAG, "GIF animation stopped and rewound"); - } -} - -bool LvglGif::IsPlaying() const { - return playing_; -} - -bool LvglGif::IsLoaded() const { - return loaded_; -} - -int32_t LvglGif::GetLoopCount() const { - if (!loaded_ || !gif_) { - return -1; - } - return gif_->loop_count; -} - -void LvglGif::SetLoopCount(int32_t count) { - if (!loaded_ || !gif_) { - ESP_LOGW(TAG, "GIF not loaded, cannot set loop count"); - return; - } - gif_->loop_count = count; -} - -uint16_t LvglGif::width() const { - if (!loaded_ || !gif_) { - return 0; - } - return gif_->width; -} - -uint16_t LvglGif::height() const { - if (!loaded_ || !gif_) { - return 0; - } - return gif_->height; -} - -void LvglGif::SetFrameCallback(std::function callback) { - frame_callback_ = callback; -} - -void LvglGif::NextFrame() { - if (!loaded_ || !gif_ || !playing_) { - return; - } - - // Check if enough time has passed for the next frame - uint32_t elapsed = lv_tick_elaps(last_call_); - if (elapsed < gif_->gce.delay * 10) { - return; - } - - last_call_ = lv_tick_get(); - - // Get next frame - int has_next = gd_get_frame(gif_); - if (has_next == 0) { - // Animation finished, pause timer - playing_ = false; - if (timer_) { - lv_timer_pause(timer_); - } - ESP_LOGD(TAG, "GIF animation completed"); - } - - // Render current frame - if (gif_->canvas) { - gd_render_frame(gif_, gif_->canvas); - - // Call frame callback if set - if (frame_callback_) { - frame_callback_(); - } - } -} - -void LvglGif::Cleanup() { - // Stop and delete timer - if (timer_) { - lv_timer_delete(timer_); - timer_ = nullptr; - } - - // Close GIF decoder - if (gif_) { - gd_close_gif(gif_); - gif_ = nullptr; - } - - playing_ = false; - loaded_ = false; - - // Clear image descriptor - memset(&img_dsc_, 0, sizeof(img_dsc_)); -} +#include "lvgl_gif.h" +#include +#include + +#define TAG "LvglGif" + +LvglGif::LvglGif(const lv_img_dsc_t* img_dsc) + : gif_(nullptr), timer_(nullptr), last_call_(0), playing_(false), loaded_(false) { + if (!img_dsc || !img_dsc->data) { + ESP_LOGE(TAG, "Invalid image descriptor"); + return; + } + + gif_ = gd_open_gif_data(img_dsc->data); + if (!gif_) { + ESP_LOGE(TAG, "Failed to open GIF from image descriptor"); + return; + } + + // Setup LVGL image descriptor + memset(&img_dsc_, 0, sizeof(img_dsc_)); + img_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC; + img_dsc_.header.flags = LV_IMAGE_FLAGS_MODIFIABLE; + img_dsc_.header.cf = LV_COLOR_FORMAT_ARGB8888; + img_dsc_.header.w = gif_->width; + img_dsc_.header.h = gif_->height; + img_dsc_.header.stride = gif_->width * 4; + img_dsc_.data = gif_->canvas; + img_dsc_.data_size = gif_->width * gif_->height * 4; + + // Render first frame + if (gif_->canvas) { + gd_render_frame(gif_, gif_->canvas); + } + + loaded_ = true; + ESP_LOGD(TAG, "GIF loaded from image descriptor: %dx%d", gif_->width, gif_->height); +} + +// Destructor +LvglGif::~LvglGif() { + Cleanup(); +} + +// LvglImage interface implementation +const lv_img_dsc_t* LvglGif::image_dsc() const { + if (!loaded_) { + return nullptr; + } + return &img_dsc_; +} + +// Animation control methods +void LvglGif::Start() { + if (!loaded_ || !gif_) { + ESP_LOGW(TAG, "GIF not loaded, cannot start"); + return; + } + + if (!timer_) { + timer_ = lv_timer_create([](lv_timer_t* timer) { + LvglGif* gif_obj = static_cast(lv_timer_get_user_data(timer)); + gif_obj->NextFrame(); + }, 10, this); + } + + if (timer_) { + playing_ = true; + last_call_ = lv_tick_get(); + lv_timer_resume(timer_); + lv_timer_reset(timer_); + + // Render first frame + NextFrame(); + + ESP_LOGD(TAG, "GIF animation started"); + } +} + +void LvglGif::Pause() { + if (timer_) { + playing_ = false; + lv_timer_pause(timer_); + ESP_LOGD(TAG, "GIF animation paused"); + } +} + +void LvglGif::Resume() { + if (!loaded_ || !gif_) { + ESP_LOGW(TAG, "GIF not loaded, cannot resume"); + return; + } + + if (timer_) { + playing_ = true; + lv_timer_resume(timer_); + ESP_LOGD(TAG, "GIF animation resumed"); + } +} + +void LvglGif::Stop() { + if (timer_) { + playing_ = false; + lv_timer_pause(timer_); + } + + if (gif_) { + gd_rewind(gif_); + NextFrame(); + ESP_LOGD(TAG, "GIF animation stopped and rewound"); + } +} + +bool LvglGif::IsPlaying() const { + return playing_; +} + +bool LvglGif::IsLoaded() const { + return loaded_; +} + +int32_t LvglGif::GetLoopCount() const { + if (!loaded_ || !gif_) { + return -1; + } + return gif_->loop_count; +} + +void LvglGif::SetLoopCount(int32_t count) { + if (!loaded_ || !gif_) { + ESP_LOGW(TAG, "GIF not loaded, cannot set loop count"); + return; + } + gif_->loop_count = count; +} + +uint16_t LvglGif::width() const { + if (!loaded_ || !gif_) { + return 0; + } + return gif_->width; +} + +uint16_t LvglGif::height() const { + if (!loaded_ || !gif_) { + return 0; + } + return gif_->height; +} + +void LvglGif::SetFrameCallback(std::function callback) { + frame_callback_ = callback; +} + +void LvglGif::NextFrame() { + if (!loaded_ || !gif_ || !playing_) { + return; + } + + // Check if enough time has passed for the next frame + uint32_t elapsed = lv_tick_elaps(last_call_); + if (elapsed < gif_->gce.delay * 10) { + return; + } + + last_call_ = lv_tick_get(); + + // Get next frame + int has_next = gd_get_frame(gif_); + if (has_next == 0) { + // Animation finished, pause timer + playing_ = false; + if (timer_) { + lv_timer_pause(timer_); + } + ESP_LOGD(TAG, "GIF animation completed"); + } + + // Render current frame + if (gif_->canvas) { + gd_render_frame(gif_, gif_->canvas); + + // Call frame callback if set + if (frame_callback_) { + frame_callback_(); + } + } +} + +void LvglGif::Cleanup() { + // Stop and delete timer + if (timer_) { + lv_timer_delete(timer_); + timer_ = nullptr; + } + + // Close GIF decoder + if (gif_) { + gd_close_gif(gif_); + gif_ = nullptr; + } + + playing_ = false; + loaded_ = false; + + // Clear image descriptor + memset(&img_dsc_, 0, sizeof(img_dsc_)); +} diff --git a/main/display/lvgl_display/gif/lvgl_gif.h b/main/display/lvgl_display/gif/lvgl_gif.h index afa2959..fbe7319 100644 --- a/main/display/lvgl_display/gif/lvgl_gif.h +++ b/main/display/lvgl_display/gif/lvgl_gif.h @@ -1,101 +1,101 @@ -#pragma once - -#include "../lvgl_image.h" -#include "gifdec.h" -#include -#include -#include - -/** - * C++ implementation of LVGL GIF widget - * Provides GIF animation functionality using gifdec library - */ -class LvglGif { -public: - explicit LvglGif(const lv_img_dsc_t* img_dsc); - virtual ~LvglGif(); - - // LvglImage interface implementation - virtual const lv_img_dsc_t* image_dsc() const; - - /** - * Start/restart GIF animation - */ - void Start(); - - /** - * Pause GIF animation - */ - void Pause(); - - /** - * Resume GIF animation - */ - void Resume(); - - /** - * Stop GIF animation and rewind to first frame - */ - void Stop(); - - /** - * Check if GIF is currently playing - */ - bool IsPlaying() const; - - /** - * Check if GIF was loaded successfully - */ - bool IsLoaded() const; - - /** - * Get loop count - */ - int32_t GetLoopCount() const; - - /** - * Set loop count - */ - void SetLoopCount(int32_t count); - - /** - * Get GIF dimensions - */ - uint16_t width() const; - uint16_t height() const; - - /** - * Set frame update callback - */ - void SetFrameCallback(std::function callback); - -private: - // GIF decoder instance - gd_GIF* gif_; - - // LVGL image descriptor - lv_img_dsc_t img_dsc_; - - // Animation timer - lv_timer_t* timer_; - - // Last frame update time - uint32_t last_call_; - - // Animation state - bool playing_; - bool loaded_; - - // Frame update callback - std::function frame_callback_; - - /** - * Update to next frame - */ - void NextFrame(); - - /** - * Cleanup resources - */ - void Cleanup(); -}; +#pragma once + +#include "../lvgl_image.h" +#include "gifdec.h" +#include +#include +#include + +/** + * C++ implementation of LVGL GIF widget + * Provides GIF animation functionality using gifdec library + */ +class LvglGif { +public: + explicit LvglGif(const lv_img_dsc_t* img_dsc); + virtual ~LvglGif(); + + // LvglImage interface implementation + virtual const lv_img_dsc_t* image_dsc() const; + + /** + * Start/restart GIF animation + */ + void Start(); + + /** + * Pause GIF animation + */ + void Pause(); + + /** + * Resume GIF animation + */ + void Resume(); + + /** + * Stop GIF animation and rewind to first frame + */ + void Stop(); + + /** + * Check if GIF is currently playing + */ + bool IsPlaying() const; + + /** + * Check if GIF was loaded successfully + */ + bool IsLoaded() const; + + /** + * Get loop count + */ + int32_t GetLoopCount() const; + + /** + * Set loop count + */ + void SetLoopCount(int32_t count); + + /** + * Get GIF dimensions + */ + uint16_t width() const; + uint16_t height() const; + + /** + * Set frame update callback + */ + void SetFrameCallback(std::function callback); + +private: + // GIF decoder instance + gd_GIF* gif_; + + // LVGL image descriptor + lv_img_dsc_t img_dsc_; + + // Animation timer + lv_timer_t* timer_; + + // Last frame update time + uint32_t last_call_; + + // Animation state + bool playing_; + bool loaded_; + + // Frame update callback + std::function frame_callback_; + + /** + * Update to next frame + */ + void NextFrame(); + + /** + * Cleanup resources + */ + void Cleanup(); +}; diff --git a/main/display/lvgl_display/jpg/README.md b/main/display/lvgl_display/jpg/README.md new file mode 100644 index 0000000..8e97093 --- /dev/null +++ b/main/display/lvgl_display/jpg/README.md @@ -0,0 +1,17 @@ +# 说明 / Description + +## 中文 + +本目录代码移植自 https://github.com/espressif/esp32-camera/blob/master/conversions/jpge.cpp + +由于原版本使用了 8KB 静态全局变量,会导致程序加载后长期占用 SRAM。 + +本版本改为类成员变量,仅在使用时从堆内存申请,代码由 Cursor 重新生成。 + +## English + +The code in this directory is ported from https://github.com/espressif/esp32-camera/blob/master/conversions/jpge.cpp + +The original version used 8KB static global variables, which would cause long-term SRAM occupation after program loading. + +This version has been changed to class member variables, which are only allocated from heap memory when in use. The code has been regenerated by Cursor. \ No newline at end of file diff --git a/main/display/lvgl_display/jpg/image_to_jpeg.cpp b/main/display/lvgl_display/jpg/image_to_jpeg.cpp new file mode 100644 index 0000000..b737ea1 --- /dev/null +++ b/main/display/lvgl_display/jpg/image_to_jpeg.cpp @@ -0,0 +1,228 @@ +// 基于原版to_jpg.cpp,替换为使用jpeg_encoder以节省SRAM +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD + +#include +#include +#include +#include +#include +#include + +#include "jpeg_encoder.h" // 使用新的JPEG编码器 +#include "image_to_jpeg.h" + + +#define TAG "image_to_jpeg" + +static void *_malloc(size_t size) +{ + void * res = malloc(size); + if(res) { + return res; + } + + // check if SPIRAM is enabled and is allocatable +#if (CONFIG_SPIRAM_SUPPORT && (CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC)) + return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +#endif + return NULL; +} + +static IRAM_ATTR void convert_line_format(uint8_t * src, pixformat_t format, uint8_t * dst, size_t width, size_t in_channels, size_t line) +{ + int i=0, o=0, l=0; + if(format == PIXFORMAT_GRAYSCALE) { + memcpy(dst, src + line * width, width); + } else if(format == PIXFORMAT_RGB888) { + l = width * 3; + src += l * line; + for(i=0; i> 3; + dst[o++] = (src[i+1] & 0x1F) << 3; + } + } else if(format == PIXFORMAT_YUV422) { + // YUV422转RGB的简化实现 + l = width * 2; + src += l * line; + for(i=0; i> 8; + int g = (298 * c - 100 * d - 208 * e + 128) >> 8; + int b = (298 * c + 516 * d + 128) >> 8; + + dst[o++] = (r < 0) ? 0 : ((r > 255) ? 255 : r); + dst[o++] = (g < 0) ? 0 : ((g > 255) ? 255 : g); + dst[o++] = (b < 0) ? 0 : ((b > 255) ? 255 : b); + + // Y1像素 + c = y1 - 16; + r = (298 * c + 409 * e + 128) >> 8; + g = (298 * c - 100 * d - 208 * e + 128) >> 8; + b = (298 * c + 516 * d + 128) >> 8; + + dst[o++] = (r < 0) ? 0 : ((r > 255) ? 255 : r); + dst[o++] = (g < 0) ? 0 : ((g > 255) ? 255 : g); + dst[o++] = (b < 0) ? 0 : ((b > 255) ? 255 : b); + } + } +} + +// 回调流实现 - 用于回调版本的JPEG编码 +class callback_stream : public jpge2_simple::output_stream { +protected: + jpg_out_cb ocb; + void * oarg; + size_t index; + +public: + callback_stream(jpg_out_cb cb, void * arg) : ocb(cb), oarg(arg), index(0) { } + virtual ~callback_stream() { } + virtual bool put_buf(const void* data, int len) + { + index += ocb(oarg, index, data, len); + return true; + } + virtual jpge2_simple::uint get_size() const + { + return static_cast(index); + } +}; + +// 内存流实现 - 用于直接内存输出 +class memory_stream : public jpge2_simple::output_stream { +protected: + uint8_t *out_buf; + size_t max_len, index; + +public: + memory_stream(void *pBuf, uint buf_size) : out_buf(static_cast(pBuf)), max_len(buf_size), index(0) { } + + virtual ~memory_stream() { } + + virtual bool put_buf(const void* pBuf, int len) + { + if (!pBuf) { + //end of image + return true; + } + if ((size_t)len > (max_len - index)) { + //ESP_LOGW(TAG, "JPG output overflow: %d bytes (%d,%d,%d)", len - (max_len - index), len, index, max_len); + len = max_len - index; + } + if (len) { + memcpy(out_buf + index, pBuf, len); + index += len; + } + return true; + } + + virtual jpge2_simple::uint get_size() const + { + return static_cast(index); + } +}; + +// 使用优化的JPEG编码器进行图像转换,必须在堆上创建编码器 +static bool convert_image(uint8_t *src, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpge2_simple::output_stream *dst_stream) +{ + int num_channels = 3; + jpge2_simple::subsampling_t subsampling = jpge2_simple::H2V2; + + if(format == PIXFORMAT_GRAYSCALE) { + num_channels = 1; + subsampling = jpge2_simple::Y_ONLY; + } + + if(!quality) { + quality = 1; + } else if(quality > 100) { + quality = 100; + } + + jpge2_simple::params comp_params = jpge2_simple::params(); + comp_params.m_subsampling = subsampling; + comp_params.m_quality = quality; + + // ⚠️ 关键:必须在堆上创建编码器!约8KB内存从堆分配 + auto dst_image = std::make_unique(); + + if (!dst_image->init(dst_stream, width, height, num_channels, comp_params)) { + ESP_LOGE(TAG, "JPG encoder init failed"); + return false; + } + + uint8_t* line = (uint8_t*)_malloc(width * num_channels); + if(!line) { + ESP_LOGE(TAG, "Scan line malloc failed"); + return false; + } + + for (int i = 0; i < height; i++) { + convert_line_format(src, format, line, width, num_channels, i); + if (!dst_image->process_scanline(line)) { + ESP_LOGE(TAG, "JPG process line %u failed", i); + free(line); + return false; + } + } + free(line); + + if (!dst_image->process_scanline(NULL)) { + ESP_LOGE(TAG, "JPG image finish failed"); + return false; + } + + // dst_image会在unique_ptr销毁时自动释放内存 + return true; +} + +// 🚀 主要函数:高效的图像到JPEG转换实现,节省8KB SRAM +bool image_to_jpeg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len) +{ + ESP_LOGI(TAG, "Using optimized JPEG encoder (saves ~8KB SRAM)"); + + // 分配JPEG输出缓冲区,这个大小对于大多数图像应该足够 + int jpg_buf_len = 128*1024; + + uint8_t * jpg_buf = (uint8_t *)_malloc(jpg_buf_len); + if(jpg_buf == NULL) { + ESP_LOGE(TAG, "JPG buffer malloc failed"); + return false; + } + memory_stream dst_stream(jpg_buf, jpg_buf_len); + + if(!convert_image(src, width, height, format, quality, &dst_stream)) { + free(jpg_buf); + return false; + } + + *out = jpg_buf; + *out_len = dst_stream.get_size(); + return true; +} + +// 🚀 回调版本:使用回调函数处理JPEG数据流,适合流式传输 +bool image_to_jpeg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void *arg) +{ + callback_stream dst_stream(cb, arg); + return convert_image(src, width, height, format, quality, &dst_stream); +} + diff --git a/main/display/lvgl_display/jpg/image_to_jpeg.h b/main/display/lvgl_display/jpg/image_to_jpeg.h new file mode 100644 index 0000000..694b22b --- /dev/null +++ b/main/display/lvgl_display/jpg/image_to_jpeg.h @@ -0,0 +1,68 @@ +// image_to_jpeg.h - 图像到JPEG转换的高效编码接口 +// 节省约8KB SRAM的JPEG编码实现 + +#ifndef IMAGE_TO_JPEG_H +#define IMAGE_TO_JPEG_H + +#include +#include +#include // 包含ESP32相机驱动的定义,避免重复定义pixformat_t和camera_fb_t + +#ifdef __cplusplus +extern "C" { +#endif + +// JPEG输出回调函数类型 +// arg: 用户自定义参数, index: 当前数据索引, data: JPEG数据块, len: 数据块长度 +// 返回: 实际处理的字节数 +typedef size_t (*jpg_out_cb)(void *arg, size_t index, const void *data, size_t len); + +/** + * @brief 将图像格式高效转换为JPEG + * + * 这个函数使用优化的JPEG编码器进行编码,主要特点: + * - 节省约8KB的SRAM使用(静态变量改为堆分配) + * - 支持多种图像格式输入 + * - 高质量JPEG输出 + * + * @param src 源图像数据 + * @param src_len 源图像数据长度 + * @param width 图像宽度 + * @param height 图像高度 + * @param format 图像格式 (PIXFORMAT_RGB565, PIXFORMAT_RGB888, 等) + * @param quality JPEG质量 (1-100) + * @param out 输出JPEG数据指针 (需要调用者释放) + * @param out_len 输出JPEG数据长度 + * + * @return true 成功, false 失败 + */ +bool image_to_jpeg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, + pixformat_t format, uint8_t quality, uint8_t **out, size_t *out_len); + +/** + * @brief 将图像格式转换为JPEG(回调版本) + * + * 使用回调函数处理JPEG输出数据,适合流式传输或分块处理: + * - 节省约8KB的SRAM使用(静态变量改为堆分配) + * - 支持流式输出,无需预分配大缓冲区 + * - 通过回调函数逐块处理JPEG数据 + * + * @param src 源图像数据 + * @param src_len 源图像数据长度 + * @param width 图像宽度 + * @param height 图像高度 + * @param format 图像格式 + * @param quality JPEG质量 (1-100) + * @param cb 输出回调函数 + * @param arg 传递给回调函数的用户参数 + * + * @return true 成功, false 失败 + */ +bool image_to_jpeg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, + pixformat_t format, uint8_t quality, jpg_out_cb cb, void *arg); + +#ifdef __cplusplus +} +#endif + +#endif /* IMAGE_TO_JPEG_H */ diff --git a/main/display/lvgl_display/jpg/jpeg_encoder.cpp b/main/display/lvgl_display/jpg/jpeg_encoder.cpp new file mode 100644 index 0000000..faed1cb --- /dev/null +++ b/main/display/lvgl_display/jpg/jpeg_encoder.cpp @@ -0,0 +1,722 @@ +// jpeg_encoder.cpp - C++ class for JPEG compression with class member arrays. +// 简单版本:直接使用类成员变量,必须在堆上创建实例 +// Modified from jpge.cpp to use class member variables instead of static variables +// Public domain, Rich Geldreich + +#include "jpeg_encoder.h" + +#include +#include +#include +#include +#include +#include +#include +#include "esp_heap_caps.h" + +#define JPGE_MAX(a,b) (((a)>(b))?(a):(b)) +#define JPGE_MIN(a,b) (((a)<(b))?(a):(b)) + +namespace jpge2_simple { + + static inline void *jpge_malloc(size_t nSize) { + void * b = malloc(nSize); + if(b){ + return b; + } + // check if SPIRAM is enabled and allocate on SPIRAM if allocatable +#if (CONFIG_SPIRAM_SUPPORT && (CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC)) + return heap_caps_malloc(nSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +#else + return NULL; +#endif + } + static inline void jpge_free(void *p) { free(p); } + + // Various JPEG enums and tables. + enum { M_SOF0 = 0xC0, M_DHT = 0xC4, M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_APP0 = 0xE0 }; + enum { DC_LUM_CODES = 12, AC_LUM_CODES = 256, DC_CHROMA_CODES = 12, AC_CHROMA_CODES = 256, MAX_HUFF_SYMBOLS = 257, MAX_HUFF_CODESIZE = 32 }; + + static const uint8 s_zag[64] = { 0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63 }; + static const int16 s_std_lum_quant[64] = { 16,11,12,14,12,10,16,14,13,14,18,17,16,19,24,40,26,24,22,22,24,49,35,37,29,40,58,51,61,60,57,51,56,55,64,72,92,78,64,68,87,69,55,56,80,109,81,87,95,98,103,104,103,62,77,113,121,112,100,120,92,101,103,99 }; + static const int16 s_std_croma_quant[64] = { 17,18,18,24,21,24,47,26,26,47,99,66,56,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 }; + static const uint8 s_dc_lum_bits[17] = { 0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 }; + static const uint8 s_dc_lum_val[DC_LUM_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static const uint8 s_ac_lum_bits[17] = { 0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d }; + static const uint8 s_ac_lum_val[AC_LUM_CODES] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + }; + static const uint8 s_dc_chroma_bits[17] = { 0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; + static const uint8 s_dc_chroma_val[DC_CHROMA_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static const uint8 s_ac_chroma_bits[17] = { 0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77 }; + static const uint8 s_ac_chroma_val[AC_CHROMA_CODES] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + }; + + const int YR = 19595, YG = 38470, YB = 7471, CB_R = -11059, CB_G = -21709, CB_B = 32768, CR_R = 32768, CR_G = -27439, CR_B = -5329; + + static inline uint8 clamp(int i) { + if (i < 0) { + i = 0; + } else if (i > 255){ + i = 255; + } + return static_cast(i); + } + + static void RGB_to_YCC(uint8* pDst, const uint8 *pSrc, int num_pixels) { + for ( ; num_pixels; pDst += 3, pSrc += 3, num_pixels--) { + const int r = pSrc[0], g = pSrc[1], b = pSrc[2]; + pDst[0] = static_cast((r * YR + g * YG + b * YB + 32768) >> 16); + pDst[1] = clamp(128 + ((r * CB_R + g * CB_G + b * CB_B + 32768) >> 16)); + pDst[2] = clamp(128 + ((r * CR_R + g * CR_G + b * CR_B + 32768) >> 16)); + } + } + + static void RGB_to_Y(uint8* pDst, const uint8 *pSrc, int num_pixels) { + for ( ; num_pixels; pDst++, pSrc += 3, num_pixels--) { + pDst[0] = static_cast((pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) >> 16); + } + } + + static void Y_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) { + for( ; num_pixels; pDst += 3, pSrc++, num_pixels--) { + pDst[0] = pSrc[0]; + pDst[1] = 128; + pDst[2] = 128; + } + } + + // Forward DCT - DCT derived from jfdctint. + enum { CONST_BITS = 13, ROW_BITS = 2 }; +#define DCT_DESCALE(x, n) (((x) + (((int32)1) << ((n) - 1))) >> (n)) +#define DCT_MUL(var, c) (static_cast(var) * static_cast(c)) +#define DCT1D(s0, s1, s2, s3, s4, s5, s6, s7) \ + int32 t0 = s0 + s7, t7 = s0 - s7, t1 = s1 + s6, t6 = s1 - s6, t2 = s2 + s5, t5 = s2 - s5, t3 = s3 + s4, t4 = s3 - s4; \ + int32 t10 = t0 + t3, t13 = t0 - t3, t11 = t1 + t2, t12 = t1 - t2; \ + int32 u1 = DCT_MUL(t12 + t13, 4433); \ + s2 = u1 + DCT_MUL(t13, 6270); \ + s6 = u1 + DCT_MUL(t12, -15137); \ + u1 = t4 + t7; \ + int32 u2 = t5 + t6, u3 = t4 + t6, u4 = t5 + t7; \ + int32 z5 = DCT_MUL(u3 + u4, 9633); \ + t4 = DCT_MUL(t4, 2446); t5 = DCT_MUL(t5, 16819); \ + t6 = DCT_MUL(t6, 25172); t7 = DCT_MUL(t7, 12299); \ + u1 = DCT_MUL(u1, -7373); u2 = DCT_MUL(u2, -20995); \ + u3 = DCT_MUL(u3, -16069); u4 = DCT_MUL(u4, -3196); \ + u3 += z5; u4 += z5; \ + s0 = t10 + t11; s1 = t7 + u1 + u4; s3 = t6 + u2 + u3; s4 = t10 - t11; s5 = t5 + u2 + u4; s7 = t4 + u1 + u3; + + static void DCT2D(int32 *p) { + int32 c, *q = p; + for (c = 7; c >= 0; c--, q += 8) { + int32 s0 = q[0], s1 = q[1], s2 = q[2], s3 = q[3], s4 = q[4], s5 = q[5], s6 = q[6], s7 = q[7]; + DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); + q[0] = s0 << ROW_BITS; q[1] = DCT_DESCALE(s1, CONST_BITS-ROW_BITS); q[2] = DCT_DESCALE(s2, CONST_BITS-ROW_BITS); q[3] = DCT_DESCALE(s3, CONST_BITS-ROW_BITS); + q[4] = s4 << ROW_BITS; q[5] = DCT_DESCALE(s5, CONST_BITS-ROW_BITS); q[6] = DCT_DESCALE(s6, CONST_BITS-ROW_BITS); q[7] = DCT_DESCALE(s7, CONST_BITS-ROW_BITS); + } + for (q = p, c = 7; c >= 0; c--, q++) { + int32 s0 = q[0*8], s1 = q[1*8], s2 = q[2*8], s3 = q[3*8], s4 = q[4*8], s5 = q[5*8], s6 = q[6*8], s7 = q[7*8]; + DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); + q[0*8] = DCT_DESCALE(s0, ROW_BITS+3); q[1*8] = DCT_DESCALE(s1, CONST_BITS+ROW_BITS+3); q[2*8] = DCT_DESCALE(s2, CONST_BITS+ROW_BITS+3); q[3*8] = DCT_DESCALE(s3, CONST_BITS+ROW_BITS+3); + q[4*8] = DCT_DESCALE(s4, ROW_BITS+3); q[5*8] = DCT_DESCALE(s5, CONST_BITS+ROW_BITS+3); q[6*8] = DCT_DESCALE(s6, CONST_BITS+ROW_BITS+3); q[7*8] = DCT_DESCALE(s7, CONST_BITS+ROW_BITS+3); + } + } + + // Compute the actual canonical Huffman codes/code sizes given the JPEG huff bits and val arrays. + // 简化版本:直接使用成员变量,不需要动态分配 + void jpeg_encoder::compute_huffman_table(uint *codes, uint8 *code_sizes, uint8 *bits, uint8 *val) + { + int i, l, last_p, si; + uint8 *huff_size = m_huff_size_temp; // 直接使用成员变量 + uint *huff_code = m_huff_code_temp; // 直接使用成员变量 + uint code; + + int p = 0; + for (l = 1; l <= 16; l++) { + for (i = 1; i <= bits[l]; i++) { + huff_size[p++] = (char)l; + } + } + + huff_size[p] = 0; + last_p = p; // write sentinel + + code = 0; si = huff_size[0]; p = 0; + + while (huff_size[p]) { + while (huff_size[p] == si) { + huff_code[p++] = code++; + } + code <<= 1; + si++; + } + + memset(codes, 0, sizeof(codes[0])*256); + memset(code_sizes, 0, sizeof(code_sizes[0])*256); + for (p = 0; p < last_p; p++) { + codes[val[p]] = huff_code[p]; + code_sizes[val[p]] = huff_size[p]; + } + } + + void jpeg_encoder::flush_output_buffer() + { + if (m_out_buf_left != JPGE_OUT_BUF_SIZE) { + m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(m_out_buf, JPGE_OUT_BUF_SIZE - m_out_buf_left); + } + m_pOut_buf = m_out_buf; + m_out_buf_left = JPGE_OUT_BUF_SIZE; + } + + void jpeg_encoder::emit_byte(uint8 i) + { + *m_pOut_buf++ = i; + if (--m_out_buf_left == 0) { + flush_output_buffer(); + } + } + + void jpeg_encoder::put_bits(uint bits, uint len) + { + uint8 c = 0; + m_bit_buffer |= ((uint32)bits << (24 - (m_bits_in += len))); + while (m_bits_in >= 8) { + c = (uint8)((m_bit_buffer >> 16) & 0xFF); + emit_byte(c); + if (c == 0xFF) { + emit_byte(0); + } + m_bit_buffer <<= 8; + m_bits_in -= 8; + } + } + + void jpeg_encoder::emit_word(uint i) + { + emit_byte(uint8(i >> 8)); emit_byte(uint8(i & 0xFF)); + } + + // JPEG marker generation. + void jpeg_encoder::emit_marker(int marker) + { + emit_byte(uint8(0xFF)); emit_byte(uint8(marker)); + } + + // Emit JFIF marker + void jpeg_encoder::emit_jfif_app0() + { + emit_marker(M_APP0); + emit_word(2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); + emit_byte(0x4A); emit_byte(0x46); emit_byte(0x49); emit_byte(0x46); /* Identifier: ASCII "JFIF" */ + emit_byte(0); + emit_byte(1); /* Major version */ + emit_byte(1); /* Minor version */ + emit_byte(0); /* Density unit */ + emit_word(1); + emit_word(1); + emit_byte(0); /* No thumbnail image */ + emit_byte(0); + } + + // Emit quantization tables + void jpeg_encoder::emit_dqt() + { + for (int i = 0; i < ((m_num_components == 3) ? 2 : 1); i++) + { + emit_marker(M_DQT); + emit_word(64 + 1 + 2); + emit_byte(static_cast(i)); + for (int j = 0; j < 64; j++) + emit_byte(static_cast(m_quantization_tables[i][j])); + } + } + + // Emit start of frame marker + void jpeg_encoder::emit_sof() + { + emit_marker(M_SOF0); /* baseline */ + emit_word(3 * m_num_components + 2 + 5 + 1); + emit_byte(8); /* precision */ + emit_word(m_image_y); + emit_word(m_image_x); + emit_byte(m_num_components); + for (int i = 0; i < m_num_components; i++) + { + emit_byte(static_cast(i + 1)); /* component ID */ + emit_byte((m_comp_h_samp[i] << 4) + m_comp_v_samp[i]); /* h and v sampling */ + emit_byte(i > 0); /* quant. table num */ + } + } + + // Emit Huffman table. + void jpeg_encoder::emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag) + { + emit_marker(M_DHT); + + int length = 0; + for (int i = 1; i <= 16; i++) + length += bits[i]; + + emit_word(length + 2 + 1 + 16); + emit_byte(static_cast(index + (ac_flag << 4))); + + for (int i = 1; i <= 16; i++) + emit_byte(bits[i]); + + for (int i = 0; i < length; i++) + emit_byte(val[i]); + } + + // Emit all Huffman tables. + void jpeg_encoder::emit_dhts() + { + emit_dht(m_huff_bits[0+0], m_huff_val[0+0], 0, false); + emit_dht(m_huff_bits[2+0], m_huff_val[2+0], 0, true); + if (m_num_components == 3) { + emit_dht(m_huff_bits[0+1], m_huff_val[0+1], 1, false); + emit_dht(m_huff_bits[2+1], m_huff_val[2+1], 1, true); + } + } + + // emit start of scan + void jpeg_encoder::emit_sos() + { + emit_marker(M_SOS); + emit_word(2 * m_num_components + 2 + 1 + 3); + emit_byte(m_num_components); + for (int i = 0; i < m_num_components; i++) + { + emit_byte(static_cast(i + 1)); + if (i == 0) + emit_byte((0 << 4) + 0); + else + emit_byte((1 << 4) + 1); + } + emit_byte(0); /* spectral selection */ + emit_byte(63); + emit_byte(0); + } + + void jpeg_encoder::load_block_8_8_grey(int x) + { + uint8 *pSrc; + sample_array_t *pDst = m_sample_array; + x <<= 3; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc = m_mcu_lines[i] + x; + pDst[0] = pSrc[0] - 128; pDst[1] = pSrc[1] - 128; pDst[2] = pSrc[2] - 128; pDst[3] = pSrc[3] - 128; + pDst[4] = pSrc[4] - 128; pDst[5] = pSrc[5] - 128; pDst[6] = pSrc[6] - 128; pDst[7] = pSrc[7] - 128; + } + } + + void jpeg_encoder::load_block_8_8(int x, int y, int c) + { + uint8 *pSrc; + sample_array_t *pDst = m_sample_array; + x = (x * (8 * 3)) + c; + y <<= 3; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc = m_mcu_lines[y + i] + x; + pDst[0] = pSrc[0 * 3] - 128; pDst[1] = pSrc[1 * 3] - 128; pDst[2] = pSrc[2 * 3] - 128; pDst[3] = pSrc[3 * 3] - 128; + pDst[4] = pSrc[4 * 3] - 128; pDst[5] = pSrc[5 * 3] - 128; pDst[6] = pSrc[6 * 3] - 128; pDst[7] = pSrc[7 * 3] - 128; + } + } + + void jpeg_encoder::load_block_16_8(int x, int c) + { + uint8 *pSrc1, *pSrc2; + sample_array_t *pDst = m_sample_array; + x = (x * (16 * 3)) + c; + int a = 0, b = 2; + for (int i = 0; i < 16; i += 2, pDst += 8) + { + pSrc1 = m_mcu_lines[i + 0] + x; + pSrc2 = m_mcu_lines[i + 1] + x; + pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3] + pSrc2[ 0 * 3] + pSrc2[ 1 * 3] + a) >> 2) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3] + pSrc2[ 2 * 3] + pSrc2[ 3 * 3] + b) >> 2) - 128; + pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3] + pSrc2[ 4 * 3] + pSrc2[ 5 * 3] + a) >> 2) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3] + pSrc2[ 6 * 3] + pSrc2[ 7 * 3] + b) >> 2) - 128; + pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3] + pSrc2[ 8 * 3] + pSrc2[ 9 * 3] + a) >> 2) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3] + pSrc2[10 * 3] + pSrc2[11 * 3] + b) >> 2) - 128; + pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3] + pSrc2[12 * 3] + pSrc2[13 * 3] + a) >> 2) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3] + pSrc2[14 * 3] + pSrc2[15 * 3] + b) >> 2) - 128; + int temp = a; a = b; b = temp; + } + } + + void jpeg_encoder::load_block_16_8_8(int x, int c) + { + uint8 *pSrc1; + sample_array_t *pDst = m_sample_array; + x = (x * (16 * 3)) + c; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc1 = m_mcu_lines[i + 0] + x; + pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3]) >> 1) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3]) >> 1) - 128; + pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3]) >> 1) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3]) >> 1) - 128; + pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3]) >> 1) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3]) >> 1) - 128; + pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3]) >> 1) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3]) >> 1) - 128; + } + } + + void jpeg_encoder::load_quantized_coefficients(int component_num) + { + int32 *q = m_quantization_tables[component_num > 0]; + int16 *pDst = m_coefficient_array; + for (int i = 0; i < 64; i++) + { + sample_array_t j = m_sample_array[s_zag[i]]; + if (j < 0) + { + if ((j = -j + (*q >> 1)) < *q) + *pDst++ = 0; + else + *pDst++ = static_cast(-(j / *q)); + } + else + { + if ((j = j + (*q >> 1)) < *q) + *pDst++ = 0; + else + *pDst++ = static_cast((j / *q)); + } + q++; + } + } + + void jpeg_encoder::code_coefficients_pass_two(int component_num) + { + int i, j, run_len, nbits, temp1, temp2; + int16 *pSrc = m_coefficient_array; + uint *codes[2]; + uint8 *code_sizes[2]; + + if (component_num == 0) + { + codes[0] = m_huff_codes[0 + 0]; codes[1] = m_huff_codes[2 + 0]; + code_sizes[0] = m_huff_code_sizes[0 + 0]; code_sizes[1] = m_huff_code_sizes[2 + 0]; + } + else + { + codes[0] = m_huff_codes[0 + 1]; codes[1] = m_huff_codes[2 + 1]; + code_sizes[0] = m_huff_code_sizes[0 + 1]; code_sizes[1] = m_huff_code_sizes[2 + 1]; + } + + temp1 = temp2 = pSrc[0] - m_last_dc_val[component_num]; + m_last_dc_val[component_num] = pSrc[0]; + + if (temp1 < 0) + { + temp1 = -temp1; temp2--; + } + + nbits = 0; + while (temp1) + { + nbits++; temp1 >>= 1; + } + + put_bits(codes[0][nbits], code_sizes[0][nbits]); + if (nbits) put_bits(temp2 & ((1 << nbits) - 1), nbits); + + for (run_len = 0, i = 1; i < 64; i++) + { + if ((temp1 = m_coefficient_array[i]) == 0) + run_len++; + else + { + while (run_len >= 16) + { + put_bits(codes[1][0xF0], code_sizes[1][0xF0]); + run_len -= 16; + } + if ((temp2 = temp1) < 0) + { + temp1 = -temp1; + temp2--; + } + nbits = 1; + while (temp1 >>= 1) + nbits++; + j = (run_len << 4) + nbits; + put_bits(codes[1][j], code_sizes[1][j]); + put_bits(temp2 & ((1 << nbits) - 1), nbits); + run_len = 0; + } + } + if (run_len) + put_bits(codes[1][0], code_sizes[1][0]); + } + + void jpeg_encoder::code_block(int component_num) + { + DCT2D(m_sample_array); + load_quantized_coefficients(component_num); + code_coefficients_pass_two(component_num); + } + + void jpeg_encoder::process_mcu_row() + { + if (m_num_components == 1) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8_grey(i); code_block(0); + } + } + else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i, 0, 0); code_block(0); load_block_8_8(i, 0, 1); code_block(1); load_block_8_8(i, 0, 2); code_block(2); + } + } + else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); + load_block_16_8_8(i, 1); code_block(1); load_block_16_8_8(i, 2); code_block(2); + } + } + else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); + load_block_8_8(i * 2 + 0, 1, 0); code_block(0); load_block_8_8(i * 2 + 1, 1, 0); code_block(0); + load_block_16_8(i, 1); code_block(1); load_block_16_8(i, 2); code_block(2); + } + } + } + + void jpeg_encoder::load_mcu(const void *pSrc) + { + const uint8* Psrc = reinterpret_cast(pSrc); + + uint8* pDst = m_mcu_lines[m_mcu_y_ofs]; // OK to write up to m_image_bpl_xlt bytes to pDst + + if (m_num_components == 1) { + if (m_image_bpp == 3) + RGB_to_Y(pDst, Psrc, m_image_x); + else + memcpy(pDst, Psrc, m_image_x); + } else { + if (m_image_bpp == 3) + RGB_to_YCC(pDst, Psrc, m_image_x); + else + Y_to_YCC(pDst, Psrc, m_image_x); + } + + // Possibly duplicate pixels at end of scanline if not a multiple of 8 or 16 + if (m_num_components == 1) + memset(m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt, pDst[m_image_bpl_xlt - 1], m_image_x_mcu - m_image_x); + else + { + const uint8 y = pDst[m_image_bpl_xlt - 3 + 0], cb = pDst[m_image_bpl_xlt - 3 + 1], cr = pDst[m_image_bpl_xlt - 3 + 2]; + uint8 *q = m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt; + for (int i = m_image_x; i < m_image_x_mcu; i++) + { + *q++ = y; *q++ = cb; *q++ = cr; + } + } + + if (++m_mcu_y_ofs == m_mcu_y) + { + process_mcu_row(); + m_mcu_y_ofs = 0; + } + } + + // Quantization table generation. + void jpeg_encoder::compute_quant_table(int32 *pDst, const int16 *pSrc) + { + int32 q; + if (m_params.m_quality < 50) + q = 5000 / m_params.m_quality; + else + q = 200 - m_params.m_quality * 2; + for (int i = 0; i < 64; i++) + { + int32 j = *pSrc++; j = (j * q + 50L) / 100L; + *pDst++ = JPGE_MIN(JPGE_MAX(j, 1), 255); + } + } + + // Higher-level methods. + bool jpeg_encoder::jpg_open(int p_x_res, int p_y_res, int src_channels) + { + m_num_components = 3; + switch (m_params.m_subsampling) + { + case Y_ONLY: + { + m_num_components = 1; + m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; + m_mcu_x = 8; m_mcu_y = 8; + break; + } + case H1V1: + { + m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 8; m_mcu_y = 8; + break; + } + case H2V1: + { + m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 1; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 16; m_mcu_y = 8; + break; + } + case H2V2: + { + m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 2; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 16; m_mcu_y = 16; + } + } + + m_image_x = p_x_res; m_image_y = p_y_res; + m_image_bpp = src_channels; + m_image_bpl = m_image_x * src_channels; + m_image_x_mcu = (m_image_x + m_mcu_x - 1) & (~(m_mcu_x - 1)); + m_image_y_mcu = (m_image_y + m_mcu_y - 1) & (~(m_mcu_y - 1)); + m_image_bpl_xlt = m_image_x * m_num_components; + m_image_bpl_mcu = m_image_x_mcu * m_num_components; + m_mcus_per_row = m_image_x_mcu / m_mcu_x; + + if ((m_mcu_lines[0] = static_cast(jpge_malloc(m_image_bpl_mcu * m_mcu_y))) == NULL) { + return false; + } + for (int i = 1; i < m_mcu_y; i++) + m_mcu_lines[i] = m_mcu_lines[i-1] + m_image_bpl_mcu; + + if(m_last_quality != m_params.m_quality){ + m_last_quality = m_params.m_quality; + compute_quant_table(m_quantization_tables[0], s_std_lum_quant); + compute_quant_table(m_quantization_tables[1], s_std_croma_quant); + } + + if(!m_huff_initialized){ + m_huff_initialized = true; + + memcpy(m_huff_bits[0+0], s_dc_lum_bits, 17); memcpy(m_huff_val[0+0], s_dc_lum_val, DC_LUM_CODES); + memcpy(m_huff_bits[2+0], s_ac_lum_bits, 17); memcpy(m_huff_val[2+0], s_ac_lum_val, AC_LUM_CODES); + memcpy(m_huff_bits[0+1], s_dc_chroma_bits, 17); memcpy(m_huff_val[0+1], s_dc_chroma_val, DC_CHROMA_CODES); + memcpy(m_huff_bits[2+1], s_ac_chroma_bits, 17); memcpy(m_huff_val[2+1], s_ac_chroma_val, AC_CHROMA_CODES); + + compute_huffman_table(m_huff_codes[0+0], m_huff_code_sizes[0+0], m_huff_bits[0+0], m_huff_val[0+0]); + compute_huffman_table(m_huff_codes[2+0], m_huff_code_sizes[2+0], m_huff_bits[2+0], m_huff_val[2+0]); + compute_huffman_table(m_huff_codes[0+1], m_huff_code_sizes[0+1], m_huff_bits[0+1], m_huff_val[0+1]); + compute_huffman_table(m_huff_codes[2+1], m_huff_code_sizes[2+1], m_huff_bits[2+1], m_huff_val[2+1]); + } + + m_out_buf_left = JPGE_OUT_BUF_SIZE; + m_pOut_buf = m_out_buf; + m_bit_buffer = 0; + m_bits_in = 0; + m_mcu_y_ofs = 0; + m_pass_num = 2; + memset(m_last_dc_val, 0, 3 * sizeof(m_last_dc_val[0])); + + // Emit all markers at beginning of image file. + emit_marker(M_SOI); + emit_jfif_app0(); + emit_dqt(); + emit_sof(); + emit_dhts(); + emit_sos(); + + return m_all_stream_writes_succeeded; + } + + bool jpeg_encoder::process_end_of_image() + { + if (m_mcu_y_ofs) { + if (m_mcu_y_ofs < 16) { // check here just to shut up static analysis + for (int i = m_mcu_y_ofs; i < m_mcu_y; i++) { + memcpy(m_mcu_lines[i], m_mcu_lines[m_mcu_y_ofs - 1], m_image_bpl_mcu); + } + } + process_mcu_row(); + } + + put_bits(0x7F, 7); + emit_marker(M_EOI); + flush_output_buffer(); + m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(NULL, 0); + m_pass_num++; // purposely bump up m_pass_num, for debugging + return true; + } + + void jpeg_encoder::clear() + { + m_mcu_lines[0] = NULL; + m_pass_num = 0; + m_all_stream_writes_succeeded = true; + + // 简单版本:成员变量自动初始化,不需要额外处理 + m_last_quality = 0; + m_huff_initialized = false; + } + + jpeg_encoder::jpeg_encoder() + { + clear(); + } + + jpeg_encoder::~jpeg_encoder() + { + deinit(); + } + + bool jpeg_encoder::init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params) + { + deinit(); + if (((!pStream) || (width < 1) || (height < 1)) || ((src_channels != 1) && (src_channels != 3) && (src_channels != 4)) || (!comp_params.check())) return false; + + // 简单版本:不需要动态分配内存,成员变量已经存在 + m_pStream = pStream; + m_params = comp_params; + return jpg_open(width, height, src_channels); + } + + void jpeg_encoder::deinit() + { + jpge_free(m_mcu_lines[0]); + clear(); + // 简单版本:不需要释放成员变量内存 + } + + bool jpeg_encoder::process_scanline(const void* pScanline) + { + if ((m_pass_num < 1) || (m_pass_num > 2)) { + return false; + } + if (m_all_stream_writes_succeeded) { + if (!pScanline) { + if (!process_end_of_image()) { + return false; + } + } else { + load_mcu(pScanline); + } + } + return m_all_stream_writes_succeeded; + } + +} // namespace jpge2_simple diff --git a/main/display/lvgl_display/jpg/jpeg_encoder.h b/main/display/lvgl_display/jpg/jpeg_encoder.h new file mode 100644 index 0000000..b58078f --- /dev/null +++ b/main/display/lvgl_display/jpg/jpeg_encoder.h @@ -0,0 +1,119 @@ +// jpeg_encoder.h - 使用类成员变量的简单版本 +// 这个版本直接在类中声明数组,要求必须在堆上创建实例 + +#ifndef JPEG_ENCODER_H +#define JPEG_ENCODER_H + +namespace jpge2_simple +{ + typedef unsigned char uint8; + typedef signed short int16; + typedef signed int int32; + typedef unsigned short uint16; + typedef unsigned int uint32; + typedef unsigned int uint; + + enum subsampling_t { Y_ONLY = 0, H1V1 = 1, H2V1 = 2, H2V2 = 3 }; + + struct params { + inline params() : m_quality(85), m_subsampling(H2V2) { } + inline bool check() const { + if ((m_quality < 1) || (m_quality > 100)) return false; + if ((uint)m_subsampling > (uint)H2V2) return false; + return true; + } + int m_quality; + subsampling_t m_subsampling; + }; + + class output_stream { + public: + virtual ~output_stream() { }; + virtual bool put_buf(const void* Pbuf, int len) = 0; + virtual uint get_size() const = 0; + }; + + // 简单版本:直接在类中声明数组 + // 警告:必须在堆上创建实例!(使用 new) + class jpeg_encoder { + public: + jpeg_encoder(); + ~jpeg_encoder(); + + bool init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params = params()); + bool process_scanline(const void* pScanline); + void deinit(); + + private: + jpeg_encoder(const jpeg_encoder &); + jpeg_encoder &operator =(const jpeg_encoder &); + + typedef int32 sample_array_t; + enum { JPGE_OUT_BUF_SIZE = 512 }; + + output_stream *m_pStream; + params m_params; + uint8 m_num_components; + uint8 m_comp_h_samp[3], m_comp_v_samp[3]; + int m_image_x, m_image_y, m_image_bpp, m_image_bpl; + int m_image_x_mcu, m_image_y_mcu; + int m_image_bpl_xlt, m_image_bpl_mcu; + int m_mcus_per_row; + int m_mcu_x, m_mcu_y; + uint8 *m_mcu_lines[16]; + uint8 m_mcu_y_ofs; + sample_array_t m_sample_array[64]; + int16 m_coefficient_array[64]; + + int m_last_dc_val[3]; + uint8 m_out_buf[JPGE_OUT_BUF_SIZE]; + uint8 *m_pOut_buf; + uint m_out_buf_left; + uint32 m_bit_buffer; + uint m_bits_in; + uint8 m_pass_num; + bool m_all_stream_writes_succeeded; + + // 直接声明为类成员变量(约8KB) + int32 m_last_quality; + int32 m_quantization_tables[2][64]; // 512 bytes + bool m_huff_initialized; + uint m_huff_codes[4][256]; // 4096 bytes + uint8 m_huff_code_sizes[4][256]; // 1024 bytes + uint8 m_huff_bits[4][17]; // 68 bytes + uint8 m_huff_val[4][256]; // 1024 bytes + + // compute_huffman_table的临时缓冲区也作为成员变量 + uint8 m_huff_size_temp[257]; // 257 bytes + uint m_huff_code_temp[257]; // 1028 bytes + + bool jpg_open(int p_x_res, int p_y_res, int src_channels); + void flush_output_buffer(); + void put_bits(uint bits, uint len); + void emit_byte(uint8 i); + void emit_word(uint i); + void emit_marker(int marker); + void emit_jfif_app0(); + void emit_dqt(); + void emit_sof(); + void emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag); + void emit_dhts(); + void emit_sos(); + void compute_quant_table(int32 *dst, const int16 *src); + void load_quantized_coefficients(int component_num); + void load_block_8_8_grey(int x); + void load_block_8_8(int x, int y, int c); + void load_block_16_8(int x, int c); + void load_block_16_8_8(int x, int c); + void code_coefficients_pass_two(int component_num); + void code_block(int component_num); + void process_mcu_row(); + bool process_end_of_image(); + void load_mcu(const void* src); + void clear(); + void compute_huffman_table(uint *codes, uint8 *code_sizes, uint8 *bits, uint8 *val); + }; + +} // namespace jpge2_simple + +#endif // JPEG_ENCODER_H diff --git a/main/display/lvgl_display/lvgl_display.cc b/main/display/lvgl_display/lvgl_display.cc index 2ec00a6..29c66c3 100644 --- a/main/display/lvgl_display/lvgl_display.cc +++ b/main/display/lvgl_display/lvgl_display.cc @@ -1,242 +1,258 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "lvgl_display.h" -#include "board.h" -#include "application.h" -#include "audio_codec.h" -#include "settings.h" -#include "assets/lang_config.h" - -#define TAG "Display" - -LvglDisplay::LvglDisplay() { - // Notification timer - esp_timer_create_args_t notification_timer_args = { - .callback = [](void *arg) { - LvglDisplay *display = static_cast(arg); - DisplayLockGuard lock(display); - lv_obj_add_flag(display->notification_label_, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "notification_timer", - .skip_unhandled_events = false, - }; - ESP_ERROR_CHECK(esp_timer_create(¬ification_timer_args, ¬ification_timer_)); - - // Create a power management lock - auto ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "display_update", &pm_lock_); - if (ret == ESP_ERR_NOT_SUPPORTED) { - ESP_LOGI(TAG, "Power management not supported"); - } else { - ESP_ERROR_CHECK(ret); - } -} - -LvglDisplay::~LvglDisplay() { - if (notification_timer_ != nullptr) { - esp_timer_stop(notification_timer_); - esp_timer_delete(notification_timer_); - } - - if (network_label_ != nullptr) { - lv_obj_del(network_label_); - } - if (notification_label_ != nullptr) { - lv_obj_del(notification_label_); - } - if (status_label_ != nullptr) { - lv_obj_del(status_label_); - } - if (mute_label_ != nullptr) { - lv_obj_del(mute_label_); - } - if (battery_label_ != nullptr) { - lv_obj_del(battery_label_); - } - if( low_battery_popup_ != nullptr ) { - lv_obj_del(low_battery_popup_); - } - if (pm_lock_ != nullptr) { - esp_pm_lock_delete(pm_lock_); - } -} - -void LvglDisplay::SetStatus(const char* status) { - DisplayLockGuard lock(this); - if (status_label_ == nullptr) { - return; - } - lv_label_set_text(status_label_, status); - lv_obj_remove_flag(status_label_, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - - last_status_update_time_ = std::chrono::system_clock::now(); -} - -void LvglDisplay::ShowNotification(const std::string ¬ification, int duration_ms) { - ShowNotification(notification.c_str(), duration_ms); -} - -void LvglDisplay::ShowNotification(const char* notification, int duration_ms) { - DisplayLockGuard lock(this); - if (notification_label_ == nullptr) { - return; - } - lv_label_set_text(notification_label_, notification); - lv_obj_remove_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(status_label_, LV_OBJ_FLAG_HIDDEN); - - esp_timer_stop(notification_timer_); - ESP_ERROR_CHECK(esp_timer_start_once(notification_timer_, duration_ms * 1000)); -} - -void LvglDisplay::UpdateStatusBar(bool update_all) { - auto& app = Application::GetInstance(); - auto& board = Board::GetInstance(); - auto codec = board.GetAudioCodec(); - - // Update mute icon - { - DisplayLockGuard lock(this); - if (mute_label_ == nullptr) { - return; - } - - // 如果静音状态改变,则更新图标 - if (codec->output_volume() == 0 && !muted_) { - muted_ = true; - lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_XMARK); - } else if (codec->output_volume() > 0 && muted_) { - muted_ = false; - lv_label_set_text(mute_label_, ""); - } - } - - // Update time - if (app.GetDeviceState() == kDeviceStateIdle) { - if (last_status_update_time_ + std::chrono::seconds(10) < std::chrono::system_clock::now()) { - // Set status to clock "HH:MM" - time_t now = time(NULL); - struct tm* tm = localtime(&now); - // Check if the we have already set the time - if (tm->tm_year >= 2025 - 1900) { - char time_str[16]; - strftime(time_str, sizeof(time_str), "%H:%M ", tm); - SetStatus(time_str); - } else { - ESP_LOGW(TAG, "System time is not set, tm_year: %d", tm->tm_year); - } - } - } - - esp_pm_lock_acquire(pm_lock_); - // 更新电池图标 - int battery_level; - bool charging, discharging; - const char* icon = nullptr; - if (board.GetBatteryLevel(battery_level, charging, discharging)) { - if (charging) { - icon = FONT_AWESOME_BATTERY_BOLT; - } else { - const char* levels[] = { - FONT_AWESOME_BATTERY_EMPTY, // 0-19% - FONT_AWESOME_BATTERY_QUARTER, // 20-39% - FONT_AWESOME_BATTERY_HALF, // 40-59% - FONT_AWESOME_BATTERY_THREE_QUARTERS, // 60-79% - FONT_AWESOME_BATTERY_FULL, // 80-99% - FONT_AWESOME_BATTERY_FULL, // 100% - }; - icon = levels[battery_level / 20]; - } - DisplayLockGuard lock(this); - if (battery_label_ != nullptr && battery_icon_ != icon) { - battery_icon_ = icon; - lv_label_set_text(battery_label_, battery_icon_); - } - - if (low_battery_popup_ != nullptr) { - if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0 && discharging) { - if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框隐藏,则显示 - lv_obj_remove_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - app.PlaySound(Lang::Sounds::OGG_LOW_BATTERY); - } - } else { - // Hide the low battery popup when the battery is not empty - if (!lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框显示,则隐藏 - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } - } - } - - // 每 10 秒更新一次网络图标 - static int seconds_counter = 0; - if (update_all || seconds_counter++ % 10 == 0) { - // 升级固件时,不读取 4G 网络状态,避免占用 UART 资源 - auto device_state = Application::GetInstance().GetDeviceState(); - static const std::vector allowed_states = { - kDeviceStateIdle, - kDeviceStateStarting, - kDeviceStateWifiConfiguring, - kDeviceStateListening, - kDeviceStateActivating, - }; - if (std::find(allowed_states.begin(), allowed_states.end(), device_state) != allowed_states.end()) { - icon = board.GetNetworkStateIcon(); - if (network_label_ != nullptr && icon != nullptr && network_icon_ != icon) { - DisplayLockGuard lock(this); - network_icon_ = icon; - lv_label_set_text(network_label_, network_icon_); - } - } - } - - esp_pm_lock_release(pm_lock_); -} - -void LvglDisplay::SetPreviewImage(std::unique_ptr image) { -} - -void LvglDisplay::SetPowerSaveMode(bool on) { - if (on) { - SetChatMessage("system", ""); - SetEmotion("sleepy"); - } else { - SetChatMessage("system", ""); - SetEmotion("neutral"); - } -} - -bool LvglDisplay::SnapshotToJpeg(uint8_t*& jpeg_output_data, size_t& jpeg_output_data_size, int quality) { - DisplayLockGuard lock(this); - - lv_obj_t* screen = lv_screen_active(); - lv_draw_buf_t* draw_buffer = lv_snapshot_take(screen, LV_COLOR_FORMAT_RGB565); - if (draw_buffer == nullptr) { - return false; - } - - // swap bytes - uint16_t* data = (uint16_t*)draw_buffer->data; - size_t pixel_count = draw_buffer->data_size / 2; - for (size_t i = 0; i < pixel_count; i++) { - data[i] = __builtin_bswap16(data[i]); - } - - if (!fmt2jpg(draw_buffer->data, draw_buffer->data_size, draw_buffer->header.w, draw_buffer->header.h, - PIXFORMAT_RGB565, quality, &jpeg_output_data, &jpeg_output_data_size)) { - lv_draw_buf_destroy(draw_buffer); - return false; - } - - lv_draw_buf_destroy(draw_buffer); - return true; -} +#include +#include +#include +#include +#include +#include + +#include "lvgl_display.h" +#include "board.h" +#include "application.h" +#include "audio_codec.h" +#include "settings.h" +#include "assets/lang_config.h" +#include "jpg/image_to_jpeg.h" + +#define TAG "Display" + +LvglDisplay::LvglDisplay() { + // Notification timer + esp_timer_create_args_t notification_timer_args = { + .callback = [](void *arg) { + LvglDisplay *display = static_cast(arg); + DisplayLockGuard lock(display); + lv_obj_add_flag(display->notification_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "notification_timer", + .skip_unhandled_events = false, + }; + ESP_ERROR_CHECK(esp_timer_create(¬ification_timer_args, ¬ification_timer_)); + + // Create a power management lock + auto ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "display_update", &pm_lock_); + if (ret == ESP_ERR_NOT_SUPPORTED) { + ESP_LOGI(TAG, "Power management not supported"); + } else { + ESP_ERROR_CHECK(ret); + } +} + +LvglDisplay::~LvglDisplay() { + if (notification_timer_ != nullptr) { + esp_timer_stop(notification_timer_); + esp_timer_delete(notification_timer_); + } + + if (network_label_ != nullptr) { + lv_obj_del(network_label_); + } + if (notification_label_ != nullptr) { + lv_obj_del(notification_label_); + } + if (status_label_ != nullptr) { + lv_obj_del(status_label_); + } + if (mute_label_ != nullptr) { + lv_obj_del(mute_label_); + } + if (battery_label_ != nullptr) { + lv_obj_del(battery_label_); + } + if( low_battery_popup_ != nullptr ) { + lv_obj_del(low_battery_popup_); + } + if (pm_lock_ != nullptr) { + esp_pm_lock_delete(pm_lock_); + } +} + +void LvglDisplay::SetStatus(const char* status) { + DisplayLockGuard lock(this); + if (status_label_ == nullptr) { + return; + } + lv_label_set_text(status_label_, status); + lv_obj_remove_flag(status_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + last_status_update_time_ = std::chrono::system_clock::now(); +} + +void LvglDisplay::ShowNotification(const std::string ¬ification, int duration_ms) { + ShowNotification(notification.c_str(), duration_ms); +} + +void LvglDisplay::ShowNotification(const char* notification, int duration_ms) { + DisplayLockGuard lock(this); + if (notification_label_ == nullptr) { + return; + } + lv_label_set_text(notification_label_, notification); + lv_obj_remove_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(status_label_, LV_OBJ_FLAG_HIDDEN); + + esp_timer_stop(notification_timer_); + ESP_ERROR_CHECK(esp_timer_start_once(notification_timer_, duration_ms * 1000)); +} + +void LvglDisplay::UpdateStatusBar(bool update_all) { + auto& app = Application::GetInstance(); + auto& board = Board::GetInstance(); + auto codec = board.GetAudioCodec(); + + // Update mute icon + { + DisplayLockGuard lock(this); + if (mute_label_ == nullptr) { + return; + } + + // 如果静音状态改变,则更新图标 + if (codec->output_volume() == 0 && !muted_) { + muted_ = true; + lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_XMARK); + } else if (codec->output_volume() > 0 && muted_) { + muted_ = false; + lv_label_set_text(mute_label_, ""); + } + } + + // Update time + if (app.GetDeviceState() == kDeviceStateIdle) { + if (last_status_update_time_ + std::chrono::seconds(10) < std::chrono::system_clock::now()) { + // Set status to clock "HH:MM" + time_t now = time(NULL); + struct tm* tm = localtime(&now); + // Check if the we have already set the time + if (tm->tm_year >= 2025 - 1900) { + char time_str[16]; + strftime(time_str, sizeof(time_str), "%H:%M ", tm); + SetStatus(time_str); + } else { + ESP_LOGW(TAG, "System time is not set, tm_year: %d", tm->tm_year); + } + } + } + + esp_pm_lock_acquire(pm_lock_); + // 更新电池图标 + int battery_level; + bool charging, discharging; + const char* icon = nullptr; + if (board.GetBatteryLevel(battery_level, charging, discharging)) { + if (charging) { + icon = FONT_AWESOME_BATTERY_BOLT; + } else { + const char* levels[] = { + FONT_AWESOME_BATTERY_EMPTY, // 0-19% + FONT_AWESOME_BATTERY_QUARTER, // 20-39% + FONT_AWESOME_BATTERY_HALF, // 40-59% + FONT_AWESOME_BATTERY_THREE_QUARTERS, // 60-79% + FONT_AWESOME_BATTERY_FULL, // 80-99% + FONT_AWESOME_BATTERY_FULL, // 100% + }; + icon = levels[battery_level / 20]; + } + DisplayLockGuard lock(this); + if (battery_label_ != nullptr && battery_icon_ != icon) { + battery_icon_ = icon; + lv_label_set_text(battery_label_, battery_icon_); + } + + if (low_battery_popup_ != nullptr) { + if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0 && discharging) { + if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框隐藏,则显示 + lv_obj_remove_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + app.PlaySound(Lang::Sounds::OGG_LOW_BATTERY); + } + } else { + // Hide the low battery popup when the battery is not empty + if (!lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框显示,则隐藏 + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } + } + } + + // 每 10 秒更新一次网络图标 + static int seconds_counter = 0; + if (update_all || seconds_counter++ % 10 == 0) { + // 升级固件时,不读取 4G 网络状态,避免占用 UART 资源 + auto device_state = Application::GetInstance().GetDeviceState(); + static const std::vector allowed_states = { + kDeviceStateIdle, + kDeviceStateStarting, + kDeviceStateWifiConfiguring, + kDeviceStateListening, + kDeviceStateActivating, + }; + if (std::find(allowed_states.begin(), allowed_states.end(), device_state) != allowed_states.end()) { + icon = board.GetNetworkStateIcon(); + if (network_label_ != nullptr && icon != nullptr && network_icon_ != icon) { + DisplayLockGuard lock(this); + network_icon_ = icon; + lv_label_set_text(network_label_, network_icon_); + } + } + } + + esp_pm_lock_release(pm_lock_); +} + +void LvglDisplay::SetPreviewImage(std::unique_ptr image) { +} + +void LvglDisplay::SetPowerSaveMode(bool on) { + if (on) { + SetChatMessage("system", ""); + SetEmotion("sleepy"); + } else { + SetChatMessage("system", ""); + SetEmotion("neutral"); + } +} + +bool LvglDisplay::SnapshotToJpeg(std::string& jpeg_data, int quality) { +#if CONFIG_LV_USE_SNAPSHOT + DisplayLockGuard lock(this); + + lv_obj_t* screen = lv_screen_active(); + lv_draw_buf_t* draw_buffer = lv_snapshot_take(screen, LV_COLOR_FORMAT_RGB565); + if (draw_buffer == nullptr) { + ESP_LOGE(TAG, "Failed to take snapshot, draw_buffer is nullptr"); + return false; + } + + // swap bytes + uint16_t* data = (uint16_t*)draw_buffer->data; + size_t pixel_count = draw_buffer->data_size / 2; + for (size_t i = 0; i < pixel_count; i++) { + data[i] = __builtin_bswap16(data[i]); + } + + // 清空输出字符串并使用回调版本,避免预分配大内存块 + jpeg_data.clear(); + + // 🚀 使用回调版本的JPEG编码器,进一步节省内存 + bool ret = image_to_jpeg_cb(draw_buffer->data, draw_buffer->data_size, draw_buffer->header.w, draw_buffer->header.h, PIXFORMAT_RGB565, quality, + [](void *arg, size_t index, const void *data, size_t len) -> size_t { + std::string* output = static_cast(arg); + if (data && len > 0) { + output->append(static_cast(data), len); + } + return len; + }, &jpeg_data); + if (!ret) { + ESP_LOGE(TAG, "Failed to convert image to JPEG"); + } + + lv_draw_buf_destroy(draw_buffer); + return ret; +#else + ESP_LOGE(TAG, "LV_USE_SNAPSHOT is not enabled"); + return false; +#endif +} diff --git a/main/display/lvgl_display/lvgl_display.h b/main/display/lvgl_display/lvgl_display.h index c3a5627..66dd766 100644 --- a/main/display/lvgl_display/lvgl_display.h +++ b/main/display/lvgl_display/lvgl_display.h @@ -1,53 +1,53 @@ -#ifndef LVGL_DISPLAY_H -#define LVGL_DISPLAY_H - -#include "display.h" -#include "lvgl_image.h" - -#include -#include -#include -#include - -#include -#include - -class LvglDisplay : public Display { -public: - LvglDisplay(); - virtual ~LvglDisplay(); - - virtual void SetStatus(const char* status); - virtual void ShowNotification(const char* notification, int duration_ms = 3000); - virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000); - virtual void SetPreviewImage(std::unique_ptr image); - virtual void UpdateStatusBar(bool update_all = false); - virtual void SetPowerSaveMode(bool on); - virtual bool SnapshotToJpeg(uint8_t*& jpeg_output_data, size_t& jpeg_output_size, int quality = 80); - -protected: - esp_pm_lock_handle_t pm_lock_ = nullptr; - lv_display_t *display_ = nullptr; - - lv_obj_t *network_label_ = nullptr; - lv_obj_t *status_label_ = nullptr; - lv_obj_t *notification_label_ = nullptr; - lv_obj_t *mute_label_ = nullptr; - lv_obj_t *battery_label_ = nullptr; - lv_obj_t* low_battery_popup_ = nullptr; - lv_obj_t* low_battery_label_ = nullptr; - - const char* battery_icon_ = nullptr; - const char* network_icon_ = nullptr; - bool muted_ = false; - - std::chrono::system_clock::time_point last_status_update_time_; - esp_timer_handle_t notification_timer_ = nullptr; - - friend class DisplayLockGuard; - virtual bool Lock(int timeout_ms = 0) = 0; - virtual void Unlock() = 0; -}; - - -#endif +#ifndef LVGL_DISPLAY_H +#define LVGL_DISPLAY_H + +#include "display.h" +#include "lvgl_image.h" + +#include +#include +#include +#include + +#include +#include + +class LvglDisplay : public Display { +public: + LvglDisplay(); + virtual ~LvglDisplay(); + + virtual void SetStatus(const char* status); + virtual void ShowNotification(const char* notification, int duration_ms = 3000); + virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000); + virtual void SetPreviewImage(std::unique_ptr image); + virtual void UpdateStatusBar(bool update_all = false); + virtual void SetPowerSaveMode(bool on); + virtual bool SnapshotToJpeg(std::string& jpeg_data, int quality = 80); + +protected: + esp_pm_lock_handle_t pm_lock_ = nullptr; + lv_display_t *display_ = nullptr; + + lv_obj_t *network_label_ = nullptr; + lv_obj_t *status_label_ = nullptr; + lv_obj_t *notification_label_ = nullptr; + lv_obj_t *mute_label_ = nullptr; + lv_obj_t *battery_label_ = nullptr; + lv_obj_t* low_battery_popup_ = nullptr; + lv_obj_t* low_battery_label_ = nullptr; + + const char* battery_icon_ = nullptr; + const char* network_icon_ = nullptr; + bool muted_ = false; + + std::chrono::system_clock::time_point last_status_update_time_; + esp_timer_handle_t notification_timer_ = nullptr; + + friend class DisplayLockGuard; + virtual bool Lock(int timeout_ms = 0) = 0; + virtual void Unlock() = 0; +}; + + +#endif diff --git a/main/display/lvgl_display/lvgl_font.cc b/main/display/lvgl_display/lvgl_font.cc index b0a45c7..24f39a2 100644 --- a/main/display/lvgl_display/lvgl_font.cc +++ b/main/display/lvgl_display/lvgl_font.cc @@ -1,13 +1,13 @@ -#include "lvgl_font.h" -#include - - -LvglCBinFont::LvglCBinFont(void* data) { - font_ = cbin_font_create(static_cast(data)); -} - -LvglCBinFont::~LvglCBinFont() { - if (font_ != nullptr) { - cbin_font_delete(font_); - } +#include "lvgl_font.h" +#include + + +LvglCBinFont::LvglCBinFont(void* data) { + font_ = cbin_font_create(static_cast(data)); +} + +LvglCBinFont::~LvglCBinFont() { + if (font_ != nullptr) { + cbin_font_delete(font_); + } } \ No newline at end of file diff --git a/main/display/lvgl_display/lvgl_font.h b/main/display/lvgl_display/lvgl_font.h index d539dc0..ce1f9f2 100644 --- a/main/display/lvgl_display/lvgl_font.h +++ b/main/display/lvgl_display/lvgl_font.h @@ -1,31 +1,31 @@ -#pragma once - -#include - - -class LvglFont { -public: - virtual const lv_font_t* font() const = 0; - virtual ~LvglFont() = default; -}; - -// Built-in font -class LvglBuiltInFont : public LvglFont { -public: - LvglBuiltInFont(const lv_font_t* font) : font_(font) {} - virtual const lv_font_t* font() const override { return font_; } - -private: - const lv_font_t* font_; -}; - - -class LvglCBinFont : public LvglFont { -public: - LvglCBinFont(void* data); - virtual ~LvglCBinFont(); - virtual const lv_font_t* font() const override { return font_; } - -private: - lv_font_t* font_; -}; +#pragma once + +#include + + +class LvglFont { +public: + virtual const lv_font_t* font() const = 0; + virtual ~LvglFont() = default; +}; + +// Built-in font +class LvglBuiltInFont : public LvglFont { +public: + LvglBuiltInFont(const lv_font_t* font) : font_(font) {} + virtual const lv_font_t* font() const override { return font_; } + +private: + const lv_font_t* font_; +}; + + +class LvglCBinFont : public LvglFont { +public: + LvglCBinFont(void* data); + virtual ~LvglCBinFont(); + virtual const lv_font_t* font() const override { return font_; } + +private: + lv_font_t* font_; +}; diff --git a/main/display/lvgl_display/lvgl_image.cc b/main/display/lvgl_display/lvgl_image.cc index eefc031..3c1bedd 100644 --- a/main/display/lvgl_display/lvgl_image.cc +++ b/main/display/lvgl_display/lvgl_image.cc @@ -1,64 +1,64 @@ -#include "lvgl_image.h" -#include - -#include -#include -#include -#include - -#define TAG "LvglImage" - - -LvglRawImage::LvglRawImage(void* data, size_t size) { - bzero(&image_dsc_, sizeof(image_dsc_)); - image_dsc_.data_size = size; - image_dsc_.data = static_cast(data); - image_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC; - image_dsc_.header.cf = LV_COLOR_FORMAT_RAW_ALPHA; - image_dsc_.header.w = 0; - image_dsc_.header.h = 0; -} - -bool LvglRawImage::IsGif() const { - auto ptr = (const uint8_t*)image_dsc_.data; - return ptr[0] == 'G' && ptr[1] == 'I' && ptr[2] == 'F'; -} - -LvglCBinImage::LvglCBinImage(void* data) { - image_dsc_ = cbin_img_dsc_create(static_cast(data)); -} - -LvglCBinImage::~LvglCBinImage() { - if (image_dsc_ != nullptr) { - cbin_img_dsc_delete(image_dsc_); - } -} - -LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size) { - bzero(&image_dsc_, sizeof(image_dsc_)); - image_dsc_.data_size = size; - image_dsc_.data = static_cast(data); - - if (lv_image_decoder_get_info(&image_dsc_, &image_dsc_.header) != LV_RESULT_OK) { - ESP_LOGE(TAG, "Failed to get image info, data: %p size: %u", data, size); - throw std::runtime_error("Failed to get image info"); - } -} - -LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format) { - bzero(&image_dsc_, sizeof(image_dsc_)); - image_dsc_.data_size = size; - image_dsc_.data = static_cast(data); - image_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC; - image_dsc_.header.cf = color_format; - image_dsc_.header.w = width; - image_dsc_.header.h = height; - image_dsc_.header.stride = stride; -} - -LvglAllocatedImage::~LvglAllocatedImage() { - if (image_dsc_.data) { - heap_caps_free((void*)image_dsc_.data); - image_dsc_.data = nullptr; - } +#include "lvgl_image.h" +#include + +#include +#include +#include +#include + +#define TAG "LvglImage" + + +LvglRawImage::LvglRawImage(void* data, size_t size) { + bzero(&image_dsc_, sizeof(image_dsc_)); + image_dsc_.data_size = size; + image_dsc_.data = static_cast(data); + image_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC; + image_dsc_.header.cf = LV_COLOR_FORMAT_RAW_ALPHA; + image_dsc_.header.w = 0; + image_dsc_.header.h = 0; +} + +bool LvglRawImage::IsGif() const { + auto ptr = (const uint8_t*)image_dsc_.data; + return ptr[0] == 'G' && ptr[1] == 'I' && ptr[2] == 'F'; +} + +LvglCBinImage::LvglCBinImage(void* data) { + image_dsc_ = cbin_img_dsc_create(static_cast(data)); +} + +LvglCBinImage::~LvglCBinImage() { + if (image_dsc_ != nullptr) { + cbin_img_dsc_delete(image_dsc_); + } +} + +LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size) { + bzero(&image_dsc_, sizeof(image_dsc_)); + image_dsc_.data_size = size; + image_dsc_.data = static_cast(data); + + if (lv_image_decoder_get_info(&image_dsc_, &image_dsc_.header) != LV_RESULT_OK) { + ESP_LOGE(TAG, "Failed to get image info, data: %p size: %u", data, size); + throw std::runtime_error("Failed to get image info"); + } +} + +LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format) { + bzero(&image_dsc_, sizeof(image_dsc_)); + image_dsc_.data_size = size; + image_dsc_.data = static_cast(data); + image_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC; + image_dsc_.header.cf = color_format; + image_dsc_.header.w = width; + image_dsc_.header.h = height; + image_dsc_.header.stride = stride; +} + +LvglAllocatedImage::~LvglAllocatedImage() { + if (image_dsc_.data) { + heap_caps_free((void*)image_dsc_.data); + image_dsc_.data = nullptr; + } } \ No newline at end of file diff --git a/main/display/lvgl_display/lvgl_image.h b/main/display/lvgl_display/lvgl_image.h index 0bbf39b..5eabf66 100644 --- a/main/display/lvgl_display/lvgl_image.h +++ b/main/display/lvgl_display/lvgl_image.h @@ -1,53 +1,53 @@ -#pragma once - -#include - - -// Wrap around lv_img_dsc_t -class LvglImage { -public: - virtual const lv_img_dsc_t* image_dsc() const = 0; - virtual bool IsGif() const { return false; } - virtual ~LvglImage() = default; -}; - - -class LvglRawImage : public LvglImage { -public: - LvglRawImage(void* data, size_t size); - virtual const lv_img_dsc_t* image_dsc() const override { return &image_dsc_; } - virtual bool IsGif() const; - -private: - lv_img_dsc_t image_dsc_; -}; - -class LvglCBinImage : public LvglImage { -public: - LvglCBinImage(void* data); - virtual ~LvglCBinImage(); - virtual const lv_img_dsc_t* image_dsc() const override { return image_dsc_; } - -private: - lv_img_dsc_t* image_dsc_ = nullptr; -}; - -class LvglSourceImage : public LvglImage { -public: - LvglSourceImage(const lv_img_dsc_t* image_dsc) : image_dsc_(image_dsc) {} - virtual const lv_img_dsc_t* image_dsc() const override { return image_dsc_; } - -private: - const lv_img_dsc_t* image_dsc_; -}; - -class LvglAllocatedImage : public LvglImage { -public: - LvglAllocatedImage(void* data, size_t size); - LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format); - virtual ~LvglAllocatedImage(); - virtual const lv_img_dsc_t* image_dsc() const override { return &image_dsc_; } - -private: - lv_img_dsc_t image_dsc_; +#pragma once + +#include + + +// Wrap around lv_img_dsc_t +class LvglImage { +public: + virtual const lv_img_dsc_t* image_dsc() const = 0; + virtual bool IsGif() const { return false; } + virtual ~LvglImage() = default; +}; + + +class LvglRawImage : public LvglImage { +public: + LvglRawImage(void* data, size_t size); + virtual const lv_img_dsc_t* image_dsc() const override { return &image_dsc_; } + virtual bool IsGif() const; + +private: + lv_img_dsc_t image_dsc_; +}; + +class LvglCBinImage : public LvglImage { +public: + LvglCBinImage(void* data); + virtual ~LvglCBinImage(); + virtual const lv_img_dsc_t* image_dsc() const override { return image_dsc_; } + +private: + lv_img_dsc_t* image_dsc_ = nullptr; +}; + +class LvglSourceImage : public LvglImage { +public: + LvglSourceImage(const lv_img_dsc_t* image_dsc) : image_dsc_(image_dsc) {} + virtual const lv_img_dsc_t* image_dsc() const override { return image_dsc_; } + +private: + const lv_img_dsc_t* image_dsc_; +}; + +class LvglAllocatedImage : public LvglImage { +public: + LvglAllocatedImage(void* data, size_t size); + LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format); + virtual ~LvglAllocatedImage(); + virtual const lv_img_dsc_t* image_dsc() const override { return &image_dsc_; } + +private: + lv_img_dsc_t image_dsc_; }; \ No newline at end of file diff --git a/main/display/lvgl_display/lvgl_theme.cc b/main/display/lvgl_display/lvgl_theme.cc index f1bdffe..bbf8320 100644 --- a/main/display/lvgl_display/lvgl_theme.cc +++ b/main/display/lvgl_display/lvgl_theme.cc @@ -1,30 +1,30 @@ -#include "lvgl_theme.h" - -LvglTheme::LvglTheme(const std::string& name) : Theme(name) { -} - -lv_color_t LvglTheme::ParseColor(const std::string& color) { - if (color.find("#") == 0) { - // Convert #112233 to lv_color_t - uint8_t r = strtol(color.substr(1, 2).c_str(), nullptr, 16); - uint8_t g = strtol(color.substr(3, 2).c_str(), nullptr, 16); - uint8_t b = strtol(color.substr(5, 2).c_str(), nullptr, 16); - return lv_color_make(r, g, b); - } - return lv_color_black(); -} - -LvglThemeManager::LvglThemeManager() { -} - -LvglTheme* LvglThemeManager::GetTheme(const std::string& theme_name) { - auto it = themes_.find(theme_name); - if (it != themes_.end()) { - return it->second; - } - return nullptr; -} - -void LvglThemeManager::RegisterTheme(const std::string& theme_name, LvglTheme* theme) { - themes_[theme_name] = theme; -} +#include "lvgl_theme.h" + +LvglTheme::LvglTheme(const std::string& name) : Theme(name) { +} + +lv_color_t LvglTheme::ParseColor(const std::string& color) { + if (color.find("#") == 0) { + // Convert #112233 to lv_color_t + uint8_t r = strtol(color.substr(1, 2).c_str(), nullptr, 16); + uint8_t g = strtol(color.substr(3, 2).c_str(), nullptr, 16); + uint8_t b = strtol(color.substr(5, 2).c_str(), nullptr, 16); + return lv_color_make(r, g, b); + } + return lv_color_black(); +} + +LvglThemeManager::LvglThemeManager() { +} + +LvglTheme* LvglThemeManager::GetTheme(const std::string& theme_name) { + auto it = themes_.find(theme_name); + if (it != themes_.end()) { + return it->second; + } + return nullptr; +} + +void LvglThemeManager::RegisterTheme(const std::string& theme_name, LvglTheme* theme) { + themes_[theme_name] = theme; +} diff --git a/main/display/lvgl_display/lvgl_theme.h b/main/display/lvgl_display/lvgl_theme.h index 85527a9..321498b 100644 --- a/main/display/lvgl_display/lvgl_theme.h +++ b/main/display/lvgl_display/lvgl_theme.h @@ -1,94 +1,94 @@ -#pragma once - -#include "display.h" -#include "lvgl_image.h" -#include "lvgl_font.h" -#include "emoji_collection.h" - -#include -#include -#include -#include - - -class LvglTheme : public Theme { -public: - static lv_color_t ParseColor(const std::string& color); - - LvglTheme(const std::string& name); - - // Properties - inline lv_color_t background_color() const { return background_color_; } - inline lv_color_t text_color() const { return text_color_; } - inline lv_color_t chat_background_color() const { return chat_background_color_; } - inline lv_color_t user_bubble_color() const { return user_bubble_color_; } - inline lv_color_t assistant_bubble_color() const { return assistant_bubble_color_; } - inline lv_color_t system_bubble_color() const { return system_bubble_color_; } - inline lv_color_t system_text_color() const { return system_text_color_; } - inline lv_color_t border_color() const { return border_color_; } - inline lv_color_t low_battery_color() const { return low_battery_color_; } - inline std::shared_ptr background_image() const { return background_image_; } - inline std::shared_ptr emoji_collection() const { return emoji_collection_; } - inline std::shared_ptr text_font() const { return text_font_; } - inline std::shared_ptr icon_font() const { return icon_font_; } - inline std::shared_ptr large_icon_font() const { return large_icon_font_; } - inline int spacing(int scale) const { return spacing_ * scale; } - - inline void set_background_color(lv_color_t background) { background_color_ = background; } - inline void set_text_color(lv_color_t text) { text_color_ = text; } - inline void set_chat_background_color(lv_color_t chat_background) { chat_background_color_ = chat_background; } - inline void set_user_bubble_color(lv_color_t user_bubble) { user_bubble_color_ = user_bubble; } - inline void set_assistant_bubble_color(lv_color_t assistant_bubble) { assistant_bubble_color_ = assistant_bubble; } - inline void set_system_bubble_color(lv_color_t system_bubble) { system_bubble_color_ = system_bubble; } - inline void set_system_text_color(lv_color_t system_text) { system_text_color_ = system_text; } - inline void set_border_color(lv_color_t border) { border_color_ = border; } - inline void set_low_battery_color(lv_color_t low_battery) { low_battery_color_ = low_battery; } - inline void set_background_image(std::shared_ptr background_image) { background_image_ = background_image; } - inline void set_emoji_collection(std::shared_ptr emoji_collection) { emoji_collection_ = emoji_collection; } - inline void set_text_font(std::shared_ptr text_font) { text_font_ = text_font; } - inline void set_icon_font(std::shared_ptr icon_font) { icon_font_ = icon_font; } - inline void set_large_icon_font(std::shared_ptr large_icon_font) { large_icon_font_ = large_icon_font; } - -private: - int spacing_ = 2; - - // Colors - lv_color_t background_color_; - lv_color_t text_color_; - lv_color_t chat_background_color_; - lv_color_t user_bubble_color_; - lv_color_t assistant_bubble_color_; - lv_color_t system_bubble_color_; - lv_color_t system_text_color_; - lv_color_t border_color_; - lv_color_t low_battery_color_; - - // Background image - std::shared_ptr background_image_ = nullptr; - - // fonts - std::shared_ptr text_font_ = nullptr; - std::shared_ptr icon_font_ = nullptr; - std::shared_ptr large_icon_font_ = nullptr; - - // Emoji collection - std::shared_ptr emoji_collection_ = nullptr; -}; - - -class LvglThemeManager { -public: - static LvglThemeManager& GetInstance() { - static LvglThemeManager instance; - return instance; - } - - void RegisterTheme(const std::string& theme_name, LvglTheme* theme); - LvglTheme* GetTheme(const std::string& theme_name); - -private: - LvglThemeManager(); - void InitializeDefaultThemes(); - - std::map themes_; -}; +#pragma once + +#include "display.h" +#include "lvgl_image.h" +#include "lvgl_font.h" +#include "emoji_collection.h" + +#include +#include +#include +#include + + +class LvglTheme : public Theme { +public: + static lv_color_t ParseColor(const std::string& color); + + LvglTheme(const std::string& name); + + // Properties + inline lv_color_t background_color() const { return background_color_; } + inline lv_color_t text_color() const { return text_color_; } + inline lv_color_t chat_background_color() const { return chat_background_color_; } + inline lv_color_t user_bubble_color() const { return user_bubble_color_; } + inline lv_color_t assistant_bubble_color() const { return assistant_bubble_color_; } + inline lv_color_t system_bubble_color() const { return system_bubble_color_; } + inline lv_color_t system_text_color() const { return system_text_color_; } + inline lv_color_t border_color() const { return border_color_; } + inline lv_color_t low_battery_color() const { return low_battery_color_; } + inline std::shared_ptr background_image() const { return background_image_; } + inline std::shared_ptr emoji_collection() const { return emoji_collection_; } + inline std::shared_ptr text_font() const { return text_font_; } + inline std::shared_ptr icon_font() const { return icon_font_; } + inline std::shared_ptr large_icon_font() const { return large_icon_font_; } + inline int spacing(int scale) const { return spacing_ * scale; } + + inline void set_background_color(lv_color_t background) { background_color_ = background; } + inline void set_text_color(lv_color_t text) { text_color_ = text; } + inline void set_chat_background_color(lv_color_t chat_background) { chat_background_color_ = chat_background; } + inline void set_user_bubble_color(lv_color_t user_bubble) { user_bubble_color_ = user_bubble; } + inline void set_assistant_bubble_color(lv_color_t assistant_bubble) { assistant_bubble_color_ = assistant_bubble; } + inline void set_system_bubble_color(lv_color_t system_bubble) { system_bubble_color_ = system_bubble; } + inline void set_system_text_color(lv_color_t system_text) { system_text_color_ = system_text; } + inline void set_border_color(lv_color_t border) { border_color_ = border; } + inline void set_low_battery_color(lv_color_t low_battery) { low_battery_color_ = low_battery; } + inline void set_background_image(std::shared_ptr background_image) { background_image_ = background_image; } + inline void set_emoji_collection(std::shared_ptr emoji_collection) { emoji_collection_ = emoji_collection; } + inline void set_text_font(std::shared_ptr text_font) { text_font_ = text_font; } + inline void set_icon_font(std::shared_ptr icon_font) { icon_font_ = icon_font; } + inline void set_large_icon_font(std::shared_ptr large_icon_font) { large_icon_font_ = large_icon_font; } + +private: + int spacing_ = 2; + + // Colors + lv_color_t background_color_; + lv_color_t text_color_; + lv_color_t chat_background_color_; + lv_color_t user_bubble_color_; + lv_color_t assistant_bubble_color_; + lv_color_t system_bubble_color_; + lv_color_t system_text_color_; + lv_color_t border_color_; + lv_color_t low_battery_color_; + + // Background image + std::shared_ptr background_image_ = nullptr; + + // fonts + std::shared_ptr text_font_ = nullptr; + std::shared_ptr icon_font_ = nullptr; + std::shared_ptr large_icon_font_ = nullptr; + + // Emoji collection + std::shared_ptr emoji_collection_ = nullptr; +}; + + +class LvglThemeManager { +public: + static LvglThemeManager& GetInstance() { + static LvglThemeManager instance; + return instance; + } + + void RegisterTheme(const std::string& theme_name, LvglTheme* theme); + LvglTheme* GetTheme(const std::string& theme_name); + +private: + LvglThemeManager(); + void InitializeDefaultThemes(); + + std::map themes_; +}; diff --git a/main/display/oled_display.cc b/main/display/oled_display.cc index 66dee40..a1bb5ee 100644 --- a/main/display/oled_display.cc +++ b/main/display/oled_display.cc @@ -1,361 +1,361 @@ -#include "oled_display.h" -#include "assets/lang_config.h" -#include "lvgl_theme.h" -#include "lvgl_font.h" - -#include -#include - -#include -#include -#include -#include - -#define TAG "OledDisplay" - -LV_FONT_DECLARE(LVGL_TEXT_FONT); -LV_FONT_DECLARE(LVGL_ICON_FONT); -LV_FONT_DECLARE(font_awesome_30_1); - -OledDisplay::OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, bool mirror_x, bool mirror_y) - : panel_io_(panel_io), panel_(panel) { - width_ = width; - height_ = height; - - auto text_font = std::make_shared(&LVGL_TEXT_FONT); - auto icon_font = std::make_shared(&LVGL_ICON_FONT); - auto large_icon_font = std::make_shared(&font_awesome_30_1); - - auto dark_theme = new LvglTheme("dark"); - dark_theme->set_text_font(text_font); - dark_theme->set_icon_font(icon_font); - dark_theme->set_large_icon_font(large_icon_font); - - auto& theme_manager = LvglThemeManager::GetInstance(); - theme_manager.RegisterTheme("dark", dark_theme); - current_theme_ = dark_theme; - - ESP_LOGI(TAG, "Initialize LVGL"); - lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); - port_cfg.task_priority = 1; - port_cfg.task_stack = 6144; -#if CONFIG_SOC_CPU_CORES_NUM > 1 - port_cfg.task_affinity = 1; -#endif - lvgl_port_init(&port_cfg); - - ESP_LOGI(TAG, "Adding OLED display"); - const lvgl_port_display_cfg_t display_cfg = { - .io_handle = panel_io_, - .panel_handle = panel_, - .control_handle = nullptr, - .buffer_size = static_cast(width_ * height_), - .double_buffer = false, - .trans_size = 0, - .hres = static_cast(width_), - .vres = static_cast(height_), - .monochrome = true, - .rotation = { - .swap_xy = false, - .mirror_x = mirror_x, - .mirror_y = mirror_y, - }, - .flags = { - .buff_dma = 1, - .buff_spiram = 0, - .sw_rotate = 0, - .full_refresh = 0, - .direct_mode = 0, - }, - }; - - display_ = lvgl_port_add_disp(&display_cfg); - if (display_ == nullptr) { - ESP_LOGE(TAG, "Failed to add display"); - return; - } - - if (height_ == 64) { - SetupUI_128x64(); - } else { - SetupUI_128x32(); - } -} - -OledDisplay::~OledDisplay() { - if (content_ != nullptr) { - lv_obj_del(content_); - } - if (status_bar_ != nullptr) { - lv_obj_del(status_bar_); - } - if (side_bar_ != nullptr) { - lv_obj_del(side_bar_); - } - if (container_ != nullptr) { - lv_obj_del(container_); - } - - if (panel_ != nullptr) { - esp_lcd_panel_del(panel_); - } - if (panel_io_ != nullptr) { - esp_lcd_panel_io_del(panel_io_); - } - lvgl_port_deinit(); -} - -bool OledDisplay::Lock(int timeout_ms) { - return lvgl_port_lock(timeout_ms); -} - -void OledDisplay::Unlock() { - lvgl_port_unlock(); -} - -void OledDisplay::SetChatMessage(const char* role, const char* content) { - DisplayLockGuard lock(this); - if (chat_message_label_ == nullptr) { - return; - } - - // Replace all newlines with spaces - std::string content_str = content; - std::replace(content_str.begin(), content_str.end(), '\n', ' '); - - if (content_right_ == nullptr) { - lv_label_set_text(chat_message_label_, content_str.c_str()); - } else { - if (content == nullptr || content[0] == '\0') { - lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); - } else { - lv_label_set_text(chat_message_label_, content_str.c_str()); - lv_obj_remove_flag(content_right_, LV_OBJ_FLAG_HIDDEN); - } - } -} - -void OledDisplay::SetupUI_128x64() { - DisplayLockGuard lock(this); - - auto lvgl_theme = static_cast(current_theme_); - auto text_font = lvgl_theme->text_font()->font(); - auto icon_font = lvgl_theme->icon_font()->font(); - auto large_icon_font = lvgl_theme->large_icon_font()->font(); - - auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font, 0); - lv_obj_set_style_text_color(screen, lv_color_black(), 0); - - /* Container */ - container_ = lv_obj_create(screen); - lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); - lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_all(container_, 0, 0); - lv_obj_set_style_border_width(container_, 0, 0); - lv_obj_set_style_pad_row(container_, 0, 0); - - /* Status bar */ - status_bar_ = lv_obj_create(container_); - lv_obj_set_size(status_bar_, LV_HOR_RES, 16); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_radius(status_bar_, 0, 0); - - /* Content */ - content_ = lv_obj_create(container_); - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_style_radius(content_, 0, 0); - lv_obj_set_style_pad_all(content_, 0, 0); - lv_obj_set_width(content_, LV_HOR_RES); - lv_obj_set_flex_grow(content_, 1); - lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_flex_main_place(content_, LV_FLEX_ALIGN_CENTER, 0); - - // 创建左侧固定宽度的容器 - content_left_ = lv_obj_create(content_); - lv_obj_set_size(content_left_, 32, LV_SIZE_CONTENT); // 固定宽度32像素 - lv_obj_set_style_pad_all(content_left_, 0, 0); - lv_obj_set_style_border_width(content_left_, 0, 0); - - emotion_label_ = lv_label_create(content_left_); - lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0); - lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI); - lv_obj_center(emotion_label_); - lv_obj_set_style_pad_top(emotion_label_, 8, 0); - - // 创建右侧可扩展的容器 - content_right_ = lv_obj_create(content_); - lv_obj_set_size(content_right_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(content_right_, 0, 0); - lv_obj_set_style_border_width(content_right_, 0, 0); - lv_obj_set_flex_grow(content_right_, 1); - lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); - - chat_message_label_ = lv_label_create(content_right_); - lv_label_set_text(chat_message_label_, ""); - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_LEFT, 0); - lv_obj_set_width(chat_message_label_, width_ - 32); - lv_obj_set_style_pad_top(chat_message_label_, 14, 0); - - // 延迟一定的时间后开始滚动字幕 - static lv_anim_t a; - lv_anim_init(&a); - lv_anim_set_delay(&a, 1000); - lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); - lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN); - lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN); - - /* Status bar */ - lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_column(status_bar_, 0, 0); - - network_label_ = lv_label_create(status_bar_); - lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, icon_font, 0); - - notification_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(notification_label_, 1); - lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_label_set_text(notification_label_, ""); - lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - - status_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(status_label_, 1); - lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); - lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); - - mute_label_ = lv_label_create(status_bar_); - lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font, 0); - - battery_label_ = lv_label_create(status_bar_); - lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, icon_font, 0); - - low_battery_popup_ = lv_obj_create(screen); - lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2); - lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0); - lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); - lv_obj_set_style_radius(low_battery_popup_, 10, 0); - low_battery_label_ = lv_label_create(low_battery_popup_); - lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE); - lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0); - lv_obj_center(low_battery_label_); - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); -} - -void OledDisplay::SetupUI_128x32() { - DisplayLockGuard lock(this); - - auto lvgl_theme = static_cast(current_theme_); - auto text_font = lvgl_theme->text_font()->font(); - auto icon_font = lvgl_theme->icon_font()->font(); - auto large_icon_font = lvgl_theme->large_icon_font()->font(); - - auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font, 0); - - /* Container */ - container_ = lv_obj_create(screen); - lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); - lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(container_, 0, 0); - lv_obj_set_style_border_width(container_, 0, 0); - lv_obj_set_style_pad_column(container_, 0, 0); - - /* Emotion label on the left side */ - content_ = lv_obj_create(container_); - lv_obj_set_size(content_, 32, 32); - lv_obj_set_style_pad_all(content_, 0, 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_style_radius(content_, 0, 0); - - emotion_label_ = lv_label_create(content_); - lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0); - lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI); - lv_obj_center(emotion_label_); - - /* Right side */ - side_bar_ = lv_obj_create(container_); - lv_obj_set_size(side_bar_, width_ - 32, 32); - lv_obj_set_flex_flow(side_bar_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_all(side_bar_, 0, 0); - lv_obj_set_style_border_width(side_bar_, 0, 0); - lv_obj_set_style_radius(side_bar_, 0, 0); - lv_obj_set_style_pad_row(side_bar_, 0, 0); - - /* Status bar */ - status_bar_ = lv_obj_create(side_bar_); - lv_obj_set_size(status_bar_, width_ - 32, 16); - lv_obj_set_style_radius(status_bar_, 0, 0); - lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_column(status_bar_, 0, 0); - - status_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(status_label_, 1); - lv_obj_set_style_pad_left(status_label_, 2, 0); - lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); - - notification_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(notification_label_, 1); - lv_obj_set_style_pad_left(notification_label_, 2, 0); - lv_label_set_text(notification_label_, ""); - lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - - mute_label_ = lv_label_create(status_bar_); - lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font, 0); - - network_label_ = lv_label_create(status_bar_); - lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, icon_font, 0); - - battery_label_ = lv_label_create(status_bar_); - lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, icon_font, 0); - - chat_message_label_ = lv_label_create(side_bar_); - lv_obj_set_size(chat_message_label_, width_ - 32, LV_SIZE_CONTENT); - lv_obj_set_style_pad_left(chat_message_label_, 2, 0); - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_label_set_text(chat_message_label_, ""); - - // 延迟一定的时间后开始滚动字幕 - static lv_anim_t a; - lv_anim_init(&a); - lv_anim_set_delay(&a, 1000); - lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); - lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN); - lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN); -} - -void OledDisplay::SetEmotion(const char* emotion) { - const char* utf8 = font_awesome_get_utf8(emotion); - DisplayLockGuard lock(this); - if (emotion_label_ == nullptr) { - return; - } - if (utf8 != nullptr) { - lv_label_set_text(emotion_label_, utf8); - } else { - lv_label_set_text(emotion_label_, FONT_AWESOME_NEUTRAL); - } -} - -void OledDisplay::SetTheme(Theme* theme) { - DisplayLockGuard lock(this); - - auto lvgl_theme = static_cast(theme); - auto text_font = lvgl_theme->text_font()->font(); - - auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font, 0); -} +#include "oled_display.h" +#include "assets/lang_config.h" +#include "lvgl_theme.h" +#include "lvgl_font.h" + +#include +#include + +#include +#include +#include +#include + +#define TAG "OledDisplay" + +LV_FONT_DECLARE(BUILTIN_TEXT_FONT); +LV_FONT_DECLARE(BUILTIN_ICON_FONT); +LV_FONT_DECLARE(font_awesome_30_1); + +OledDisplay::OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, bool mirror_x, bool mirror_y) + : panel_io_(panel_io), panel_(panel) { + width_ = width; + height_ = height; + + auto text_font = std::make_shared(&BUILTIN_TEXT_FONT); + auto icon_font = std::make_shared(&BUILTIN_ICON_FONT); + auto large_icon_font = std::make_shared(&font_awesome_30_1); + + auto dark_theme = new LvglTheme("dark"); + dark_theme->set_text_font(text_font); + dark_theme->set_icon_font(icon_font); + dark_theme->set_large_icon_font(large_icon_font); + + auto& theme_manager = LvglThemeManager::GetInstance(); + theme_manager.RegisterTheme("dark", dark_theme); + current_theme_ = dark_theme; + + ESP_LOGI(TAG, "Initialize LVGL"); + lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + port_cfg.task_priority = 1; + port_cfg.task_stack = 6144; +#if CONFIG_SOC_CPU_CORES_NUM > 1 + port_cfg.task_affinity = 1; +#endif + lvgl_port_init(&port_cfg); + + ESP_LOGI(TAG, "Adding OLED display"); + const lvgl_port_display_cfg_t display_cfg = { + .io_handle = panel_io_, + .panel_handle = panel_, + .control_handle = nullptr, + .buffer_size = static_cast(width_ * height_), + .double_buffer = false, + .trans_size = 0, + .hres = static_cast(width_), + .vres = static_cast(height_), + .monochrome = true, + .rotation = { + .swap_xy = false, + .mirror_x = mirror_x, + .mirror_y = mirror_y, + }, + .flags = { + .buff_dma = 1, + .buff_spiram = 0, + .sw_rotate = 0, + .full_refresh = 0, + .direct_mode = 0, + }, + }; + + display_ = lvgl_port_add_disp(&display_cfg); + if (display_ == nullptr) { + ESP_LOGE(TAG, "Failed to add display"); + return; + } + + if (height_ == 64) { + SetupUI_128x64(); + } else { + SetupUI_128x32(); + } +} + +OledDisplay::~OledDisplay() { + if (content_ != nullptr) { + lv_obj_del(content_); + } + if (status_bar_ != nullptr) { + lv_obj_del(status_bar_); + } + if (side_bar_ != nullptr) { + lv_obj_del(side_bar_); + } + if (container_ != nullptr) { + lv_obj_del(container_); + } + + if (panel_ != nullptr) { + esp_lcd_panel_del(panel_); + } + if (panel_io_ != nullptr) { + esp_lcd_panel_io_del(panel_io_); + } + lvgl_port_deinit(); +} + +bool OledDisplay::Lock(int timeout_ms) { + return lvgl_port_lock(timeout_ms); +} + +void OledDisplay::Unlock() { + lvgl_port_unlock(); +} + +void OledDisplay::SetChatMessage(const char* role, const char* content) { + DisplayLockGuard lock(this); + if (chat_message_label_ == nullptr) { + return; + } + + // Replace all newlines with spaces + std::string content_str = content; + std::replace(content_str.begin(), content_str.end(), '\n', ' '); + + if (content_right_ == nullptr) { + lv_label_set_text(chat_message_label_, content_str.c_str()); + } else { + if (content == nullptr || content[0] == '\0') { + lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); + } else { + lv_label_set_text(chat_message_label_, content_str.c_str()); + lv_obj_remove_flag(content_right_, LV_OBJ_FLAG_HIDDEN); + } + } +} + +void OledDisplay::SetupUI_128x64() { + DisplayLockGuard lock(this); + + auto lvgl_theme = static_cast(current_theme_); + auto text_font = lvgl_theme->text_font()->font(); + auto icon_font = lvgl_theme->icon_font()->font(); + auto large_icon_font = lvgl_theme->large_icon_font()->font(); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, text_font, 0); + lv_obj_set_style_text_color(screen, lv_color_black(), 0); + + /* Container */ + container_ = lv_obj_create(screen); + lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); + lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(container_, 0, 0); + lv_obj_set_style_border_width(container_, 0, 0); + lv_obj_set_style_pad_row(container_, 0, 0); + + /* Status bar */ + status_bar_ = lv_obj_create(container_); + lv_obj_set_size(status_bar_, LV_HOR_RES, 16); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_radius(status_bar_, 0, 0); + + /* Content */ + content_ = lv_obj_create(container_); + lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_radius(content_, 0, 0); + lv_obj_set_style_pad_all(content_, 0, 0); + lv_obj_set_width(content_, LV_HOR_RES); + lv_obj_set_flex_grow(content_, 1); + lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_flex_main_place(content_, LV_FLEX_ALIGN_CENTER, 0); + + // 创建左侧固定宽度的容器 + content_left_ = lv_obj_create(content_); + lv_obj_set_size(content_left_, 32, LV_SIZE_CONTENT); // 固定宽度32像素 + lv_obj_set_style_pad_all(content_left_, 0, 0); + lv_obj_set_style_border_width(content_left_, 0, 0); + + emotion_label_ = lv_label_create(content_left_); + lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0); + lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI); + lv_obj_center(emotion_label_); + lv_obj_set_style_pad_top(emotion_label_, 8, 0); + + // 创建右侧可扩展的容器 + content_right_ = lv_obj_create(content_); + lv_obj_set_size(content_right_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(content_right_, 0, 0); + lv_obj_set_style_border_width(content_right_, 0, 0); + lv_obj_set_flex_grow(content_right_, 1); + lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); + + chat_message_label_ = lv_label_create(content_right_); + lv_label_set_text(chat_message_label_, ""); + lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_LEFT, 0); + lv_obj_set_width(chat_message_label_, width_ - 32); + lv_obj_set_style_pad_top(chat_message_label_, 14, 0); + + // 延迟一定的时间后开始滚动字幕 + static lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_delay(&a, 1000); + lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); + lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN); + lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN); + + /* Status bar */ + lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_column(status_bar_, 0, 0); + + network_label_ = lv_label_create(status_bar_); + lv_label_set_text(network_label_, ""); + lv_obj_set_style_text_font(network_label_, icon_font, 0); + + notification_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(notification_label_, 1); + lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_label_set_text(notification_label_, ""); + lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + status_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(status_label_, 1); + lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); + lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); + + mute_label_ = lv_label_create(status_bar_); + lv_label_set_text(mute_label_, ""); + lv_obj_set_style_text_font(mute_label_, icon_font, 0); + + battery_label_ = lv_label_create(status_bar_); + lv_label_set_text(battery_label_, ""); + lv_obj_set_style_text_font(battery_label_, icon_font, 0); + + low_battery_popup_ = lv_obj_create(screen); + lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2); + lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); + lv_obj_set_style_radius(low_battery_popup_, 10, 0); + low_battery_label_ = lv_label_create(low_battery_popup_); + lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE); + lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0); + lv_obj_center(low_battery_label_); + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); +} + +void OledDisplay::SetupUI_128x32() { + DisplayLockGuard lock(this); + + auto lvgl_theme = static_cast(current_theme_); + auto text_font = lvgl_theme->text_font()->font(); + auto icon_font = lvgl_theme->icon_font()->font(); + auto large_icon_font = lvgl_theme->large_icon_font()->font(); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, text_font, 0); + + /* Container */ + container_ = lv_obj_create(screen); + lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); + lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(container_, 0, 0); + lv_obj_set_style_border_width(container_, 0, 0); + lv_obj_set_style_pad_column(container_, 0, 0); + + /* Emotion label on the left side */ + content_ = lv_obj_create(container_); + lv_obj_set_size(content_, 32, 32); + lv_obj_set_style_pad_all(content_, 0, 0); + lv_obj_set_style_border_width(content_, 0, 0); + lv_obj_set_style_radius(content_, 0, 0); + + emotion_label_ = lv_label_create(content_); + lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0); + lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI); + lv_obj_center(emotion_label_); + + /* Right side */ + side_bar_ = lv_obj_create(container_); + lv_obj_set_size(side_bar_, width_ - 32, 32); + lv_obj_set_flex_flow(side_bar_, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(side_bar_, 0, 0); + lv_obj_set_style_border_width(side_bar_, 0, 0); + lv_obj_set_style_radius(side_bar_, 0, 0); + lv_obj_set_style_pad_row(side_bar_, 0, 0); + + /* Status bar */ + status_bar_ = lv_obj_create(side_bar_); + lv_obj_set_size(status_bar_, width_ - 32, 16); + lv_obj_set_style_radius(status_bar_, 0, 0); + lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_all(status_bar_, 0, 0); + lv_obj_set_style_border_width(status_bar_, 0, 0); + lv_obj_set_style_pad_column(status_bar_, 0, 0); + + status_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(status_label_, 1); + lv_obj_set_style_pad_left(status_label_, 2, 0); + lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); + + notification_label_ = lv_label_create(status_bar_); + lv_obj_set_flex_grow(notification_label_, 1); + lv_obj_set_style_pad_left(notification_label_, 2, 0); + lv_label_set_text(notification_label_, ""); + lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + mute_label_ = lv_label_create(status_bar_); + lv_label_set_text(mute_label_, ""); + lv_obj_set_style_text_font(mute_label_, icon_font, 0); + + network_label_ = lv_label_create(status_bar_); + lv_label_set_text(network_label_, ""); + lv_obj_set_style_text_font(network_label_, icon_font, 0); + + battery_label_ = lv_label_create(status_bar_); + lv_label_set_text(battery_label_, ""); + lv_obj_set_style_text_font(battery_label_, icon_font, 0); + + chat_message_label_ = lv_label_create(side_bar_); + lv_obj_set_size(chat_message_label_, width_ - 32, LV_SIZE_CONTENT); + lv_obj_set_style_pad_left(chat_message_label_, 2, 0); + lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_label_set_text(chat_message_label_, ""); + + // 延迟一定的时间后开始滚动字幕 + static lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_delay(&a, 1000); + lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); + lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN); + lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN); +} + +void OledDisplay::SetEmotion(const char* emotion) { + const char* utf8 = font_awesome_get_utf8(emotion); + DisplayLockGuard lock(this); + if (emotion_label_ == nullptr) { + return; + } + if (utf8 != nullptr) { + lv_label_set_text(emotion_label_, utf8); + } else { + lv_label_set_text(emotion_label_, FONT_AWESOME_NEUTRAL); + } +} + +void OledDisplay::SetTheme(Theme* theme) { + DisplayLockGuard lock(this); + + auto lvgl_theme = static_cast(theme); + auto text_font = lvgl_theme->text_font()->font(); + + auto screen = lv_screen_active(); + lv_obj_set_style_text_font(screen, text_font, 0); +} diff --git a/main/display/oled_display.h b/main/display/oled_display.h index 321f5da..1473c39 100644 --- a/main/display/oled_display.h +++ b/main/display/oled_display.h @@ -1,39 +1,39 @@ -#ifndef OLED_DISPLAY_H -#define OLED_DISPLAY_H - -#include "lvgl_display.h" - -#include -#include - - -class OledDisplay : public LvglDisplay { -private: - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - lv_obj_t* status_bar_ = nullptr; - lv_obj_t* content_ = nullptr; - lv_obj_t* content_left_ = nullptr; - lv_obj_t* content_right_ = nullptr; - lv_obj_t* container_ = nullptr; - lv_obj_t* side_bar_ = nullptr; - lv_obj_t *emotion_label_ = nullptr; - lv_obj_t* chat_message_label_ = nullptr; - - virtual bool Lock(int timeout_ms = 0) override; - virtual void Unlock() override; - - void SetupUI_128x64(); - void SetupUI_128x32(); - -public: - OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, bool mirror_x, bool mirror_y); - ~OledDisplay(); - - virtual void SetChatMessage(const char* role, const char* content) override; - virtual void SetEmotion(const char* emotion) override; - virtual void SetTheme(Theme* theme) override; -}; - -#endif // OLED_DISPLAY_H +#ifndef OLED_DISPLAY_H +#define OLED_DISPLAY_H + +#include "lvgl_display.h" + +#include +#include + + +class OledDisplay : public LvglDisplay { +private: + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + lv_obj_t* status_bar_ = nullptr; + lv_obj_t* content_ = nullptr; + lv_obj_t* content_left_ = nullptr; + lv_obj_t* content_right_ = nullptr; + lv_obj_t* container_ = nullptr; + lv_obj_t* side_bar_ = nullptr; + lv_obj_t *emotion_label_ = nullptr; + lv_obj_t* chat_message_label_ = nullptr; + + virtual bool Lock(int timeout_ms = 0) override; + virtual void Unlock() override; + + void SetupUI_128x64(); + void SetupUI_128x32(); + +public: + OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, bool mirror_x, bool mirror_y); + ~OledDisplay(); + + virtual void SetChatMessage(const char* role, const char* content) override; + virtual void SetEmotion(const char* emotion) override; + virtual void SetTheme(Theme* theme) override; +}; + +#endif // OLED_DISPLAY_H diff --git a/main/idf_component.yml b/main/idf_component.yml index 8fdb456..794ef20 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -1,80 +1,84 @@ -## IDF Component Manager Manifest File -dependencies: - waveshare/esp_lcd_sh8601: 1.0.2 - espressif/esp_lcd_ili9341: ==1.2.0 - espressif/esp_lcd_gc9a01: ==2.0.1 - espressif/esp_lcd_st77916: ^1.0.1 - espressif/esp_lcd_axs15231b: ^1.0.0 - espressif/esp_lcd_st7796: - version: 1.3.4 - rules: - - if: target not in [esp32c3] - espressif/esp_lcd_spd2010: ==1.0.2 - espressif/esp_io_expander_tca9554: ==2.0.0 - espressif/esp_lcd_panel_io_additions: ^1.0.1 - 78/esp_lcd_nv3023: ~1.0.0 - 78/esp-wifi-connect: ~2.5.2 - 78/esp-opus-encoder: ~2.4.1 - 78/esp-ml307: ~3.3.5 - 78/xiaozhi-fonts: ~1.5.2 - espressif/led_strip: ~3.0.1 - espressif/esp_codec_dev: ~1.4.0 - espressif/esp-sr: ~2.1.5 - espressif/button: ~4.1.3 - espressif/knob: ^1.0.0 - espressif/esp32-camera: ~2.1.2 - espressif/esp_lcd_touch_ft5x06: ~1.0.7 - espressif/esp_lcd_touch_gt911: ^1 - espressif/esp_lcd_touch_gt1151: ^1 - waveshare/esp_lcd_touch_cst9217: ^1.0.3 - espressif/esp_lcd_touch_cst816s: ^1.0.6 - lvgl/lvgl: ~9.3.0 - esp_lvgl_port: ~2.6.0 - espressif/esp_io_expander_tca95xx_16bit: ^2.0.0 - espressif2022/image_player: ==1.1.0~1 - espressif2022/esp_emote_gfx: ==1.0.0~2 - espressif/adc_mic: ^0.2.1 - espressif/esp_mmap_assets: '>=1.2' - txp666/otto-emoji-gif-component: ~1.0.2 - espressif/adc_battery_estimation: ^0.2.0 - - # SenseCAP Watcher Board - wvirgil123/sscma_client: - version: 1.0.2 - rules: - - if: target in [esp32s3] - - tny-robotics/sh1106-esp-idf: - version: ^1.0.0 - rules: - - if: idf_version >= "5.4.0" - - waveshare/esp_lcd_jd9365_10_1: - version: '*' - rules: - - if: target in [esp32p4] - waveshare/esp_lcd_st7703: - version: '*' - rules: - - if: target in [esp32p4] - espressif/esp_lcd_ili9881c: - version: ^1.0.1 - rules: - - if: target in [esp32p4] - espressif/esp_hosted: - version: 2.0.17 - rules: - - if: target in [esp32h2, esp32p4] - espressif/esp_wifi_remote: - version: '*' - rules: - - if: target in [esp32p4] - espfriends/servo_dog_ctrl: - version: ^0.1.8 - rules: - - if: target in [esp32c3] - chmorgan/esp-libhelix-mp3: - version: "*" - ## Required IDF version - idf: - version: '>=5.4.0' +## IDF Component Manager Manifest File +dependencies: + waveshare/esp_lcd_sh8601: 1.0.2 + espressif/esp_lcd_ili9341: ==1.2.0 + espressif/esp_lcd_gc9a01: ==2.0.1 + espressif/esp_lcd_st77916: ^1.0.1 + espressif/esp_lcd_axs15231b: ^1.0.0 + espressif/esp_lcd_st7701: + version: ^1.1.4 + rules: + - if: target in [esp32s3, esp32p4] + espressif/esp_lcd_st7796: + version: 1.3.5 + rules: + - if: target not in [esp32c3, esp32c6] + espressif/esp_lcd_spd2010: ==1.0.2 + espressif/esp_io_expander_tca9554: ==2.0.0 + espressif/esp_lcd_panel_io_additions: ^1.0.1 + 78/esp_lcd_nv3023: ~1.0.0 + 78/esp-wifi-connect: ~2.5.2 + 78/esp-opus-encoder: ~2.4.1 + 78/esp-ml307: ~3.3.5 + 78/xiaozhi-fonts: ~1.5.3 + espressif/led_strip: ~3.0.1 + espressif/esp_codec_dev: ~1.4.0 + espressif/esp-sr: ~2.1.5 + espressif/button: ~4.1.3 + espressif/knob: ^1.0.0 + espressif/esp32-camera: ~2.1.2 + espressif/esp_lcd_touch_ft5x06: ~1.0.7 + espressif/esp_lcd_touch_gt911: ^1 + espressif/esp_lcd_touch_gt1151: ^1 + waveshare/esp_lcd_touch_cst9217: ^1.0.3 + espressif/esp_lcd_touch_cst816s: ^1.0.6 + lvgl/lvgl: ~9.3.0 + esp_lvgl_port: ~2.6.0 + espressif/esp_io_expander_tca95xx_16bit: ^2.0.0 + espressif2022/image_player: "*" + espressif2022/esp_emote_gfx: ^1.1.0 + espressif/adc_mic: ^0.2.1 + espressif/esp_mmap_assets: '>=1.2' + txp666/otto-emoji-gif-component: "*" + espressif/adc_battery_estimation: ^0.2.0 + + # SenseCAP Watcher Board + wvirgil123/sscma_client: + version: 1.0.2 + rules: + - if: target in [esp32s3] + + tny-robotics/sh1106-esp-idf: + version: ^1.0.0 + rules: + - if: idf_version >= "5.4.0" + + waveshare/esp_lcd_jd9365_10_1: + version: '*' + rules: + - if: target in [esp32p4] + waveshare/esp_lcd_st7703: + version: '*' + rules: + - if: target in [esp32p4] + espressif/esp_lcd_ili9881c: + version: ^1.0.1 + rules: + - if: target in [esp32p4] + espressif/esp_hosted: + version: 2.0.17 + rules: + - if: target in [esp32h2, esp32p4] + espressif/esp_wifi_remote: + version: '*' + rules: + - if: target in [esp32p4] + espfriends/servo_dog_ctrl: + version: ^0.1.8 + rules: + - if: target in [esp32c3] + chmorgan/esp-libhelix-mp3: + version: "*" + ## Required IDF version + idf: + version: '>=5.4.0' diff --git a/main/led/circular_strip.cc b/main/led/circular_strip.cc index cb428ad..1629684 100644 --- a/main/led/circular_strip.cc +++ b/main/led/circular_strip.cc @@ -1,233 +1,233 @@ -#include "circular_strip.h" -#include "application.h" -#include - -#define TAG "CircularStrip" - -#define BLINK_INFINITE -1 - -CircularStrip::CircularStrip(gpio_num_t gpio, uint8_t max_leds) : max_leds_(max_leds) { - // If the gpio is not connected, you should use NoLed class - assert(gpio != GPIO_NUM_NC); - - colors_.resize(max_leds_); - - led_strip_config_t strip_config = {}; - strip_config.strip_gpio_num = gpio; - strip_config.max_leds = max_leds_; - strip_config.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB; - strip_config.led_model = LED_MODEL_WS2812; - - led_strip_rmt_config_t rmt_config = {}; - rmt_config.resolution_hz = 10 * 1000 * 1000; // 10MHz - - ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_)); - led_strip_clear(led_strip_); - - esp_timer_create_args_t strip_timer_args = { - .callback = [](void *arg) { - auto strip = static_cast(arg); - std::lock_guard lock(strip->mutex_); - if (strip->strip_callback_ != nullptr) { - strip->strip_callback_(); - } - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "strip_timer", - .skip_unhandled_events = false, - }; - ESP_ERROR_CHECK(esp_timer_create(&strip_timer_args, &strip_timer_)); -} - -CircularStrip::~CircularStrip() { - esp_timer_stop(strip_timer_); - if (led_strip_ != nullptr) { - led_strip_del(led_strip_); - } -} - - -void CircularStrip::SetAllColor(StripColor color) { - std::lock_guard lock(mutex_); - esp_timer_stop(strip_timer_); - for (int i = 0; i < max_leds_; i++) { - colors_[i] = color; - led_strip_set_pixel(led_strip_, i, color.red, color.green, color.blue); - } - led_strip_refresh(led_strip_); -} - -void CircularStrip::SetSingleColor(uint8_t index, StripColor color) { - std::lock_guard lock(mutex_); - esp_timer_stop(strip_timer_); - colors_[index] = color; - led_strip_set_pixel(led_strip_, index, color.red, color.green, color.blue); - led_strip_refresh(led_strip_); -} - -void CircularStrip::Blink(StripColor color, int interval_ms) { - for (int i = 0; i < max_leds_; i++) { - colors_[i] = color; - } - StartStripTask(interval_ms, [this]() { - static bool on = true; - if (on) { - for (int i = 0; i < max_leds_; i++) { - led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue); - } - led_strip_refresh(led_strip_); - } else { - led_strip_clear(led_strip_); - } - on = !on; - }); -} - -void CircularStrip::FadeOut(int interval_ms) { - StartStripTask(interval_ms, [this]() { - bool all_off = true; - for (int i = 0; i < max_leds_; i++) { - colors_[i].red /= 2; - colors_[i].green /= 2; - colors_[i].blue /= 2; - if (colors_[i].red != 0 || colors_[i].green != 0 || colors_[i].blue != 0) { - all_off = false; - } - led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue); - } - if (all_off) { - led_strip_clear(led_strip_); - esp_timer_stop(strip_timer_); - } else { - led_strip_refresh(led_strip_); - } - }); -} - -void CircularStrip::Breathe(StripColor low, StripColor high, int interval_ms) { - StartStripTask(interval_ms, [this, low, high]() { - static bool increase = true; - static StripColor color = low; - if (increase) { - if (color.red < high.red) { - color.red++; - } - if (color.green < high.green) { - color.green++; - } - if (color.blue < high.blue) { - color.blue++; - } - if (color.red == high.red && color.green == high.green && color.blue == high.blue) { - increase = false; - } - } else { - if (color.red > low.red) { - color.red--; - } - if (color.green > low.green) { - color.green--; - } - if (color.blue > low.blue) { - color.blue--; - } - if (color.red == low.red && color.green == low.green && color.blue == low.blue) { - increase = true; - } - } - for (int i = 0; i < max_leds_; i++) { - led_strip_set_pixel(led_strip_, i, color.red, color.green, color.blue); - } - led_strip_refresh(led_strip_); - }); -} - -void CircularStrip::Scroll(StripColor low, StripColor high, int length, int interval_ms) { - for (int i = 0; i < max_leds_; i++) { - colors_[i] = low; - } - StartStripTask(interval_ms, [this, low, high, length]() { - static int offset = 0; - for (int i = 0; i < max_leds_; i++) { - colors_[i] = low; - } - for (int j = 0; j < length; j++) { - int i = (offset + j) % max_leds_; - colors_[i] = high; - } - for (int i = 0; i < max_leds_; i++) { - led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue); - } - led_strip_refresh(led_strip_); - offset = (offset + 1) % max_leds_; - }); -} - -void CircularStrip::StartStripTask(int interval_ms, std::function cb) { - if (led_strip_ == nullptr) { - return; - } - - std::lock_guard lock(mutex_); - esp_timer_stop(strip_timer_); - - strip_callback_ = cb; - esp_timer_start_periodic(strip_timer_, interval_ms * 1000); -} - -void CircularStrip::SetBrightness(uint8_t default_brightness, uint8_t low_brightness) { - default_brightness_ = default_brightness; - low_brightness_ = low_brightness; - OnStateChanged(); -} - -void CircularStrip::OnStateChanged() { - auto& app = Application::GetInstance(); - auto device_state = app.GetDeviceState(); - switch (device_state) { - case kDeviceStateStarting: { - StripColor low = { 0, 0, 0 }; - StripColor high = { low_brightness_, low_brightness_, default_brightness_ }; - Scroll(low, high, 3, 100); - break; - } - case kDeviceStateWifiConfiguring: { - StripColor color = { low_brightness_, low_brightness_, default_brightness_ }; - Blink(color, 500); - break; - } - case kDeviceStateIdle: - FadeOut(50); - break; - case kDeviceStateConnecting: { - StripColor color = { low_brightness_, low_brightness_, default_brightness_ }; - SetAllColor(color); - break; - } - case kDeviceStateListening: - case kDeviceStateAudioTesting: { - StripColor color = { default_brightness_, low_brightness_, low_brightness_ }; - SetAllColor(color); - break; - } - case kDeviceStateSpeaking: { - StripColor color = { low_brightness_, default_brightness_, low_brightness_ }; - SetAllColor(color); - break; - } - case kDeviceStateUpgrading: { - StripColor color = { low_brightness_, default_brightness_, low_brightness_ }; - Blink(color, 100); - break; - } - case kDeviceStateActivating: { - StripColor color = { low_brightness_, default_brightness_, low_brightness_ }; - Blink(color, 500); - break; - } - default: - ESP_LOGW(TAG, "Unknown led strip event: %d", device_state); - return; - } -} +#include "circular_strip.h" +#include "application.h" +#include + +#define TAG "CircularStrip" + +#define BLINK_INFINITE -1 + +CircularStrip::CircularStrip(gpio_num_t gpio, uint8_t max_leds) : max_leds_(max_leds) { + // If the gpio is not connected, you should use NoLed class + assert(gpio != GPIO_NUM_NC); + + colors_.resize(max_leds_); + + led_strip_config_t strip_config = {}; + strip_config.strip_gpio_num = gpio; + strip_config.max_leds = max_leds_; + strip_config.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB; + strip_config.led_model = LED_MODEL_WS2812; + + led_strip_rmt_config_t rmt_config = {}; + rmt_config.resolution_hz = 10 * 1000 * 1000; // 10MHz + + ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_)); + led_strip_clear(led_strip_); + + esp_timer_create_args_t strip_timer_args = { + .callback = [](void *arg) { + auto strip = static_cast(arg); + std::lock_guard lock(strip->mutex_); + if (strip->strip_callback_ != nullptr) { + strip->strip_callback_(); + } + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "strip_timer", + .skip_unhandled_events = false, + }; + ESP_ERROR_CHECK(esp_timer_create(&strip_timer_args, &strip_timer_)); +} + +CircularStrip::~CircularStrip() { + esp_timer_stop(strip_timer_); + if (led_strip_ != nullptr) { + led_strip_del(led_strip_); + } +} + + +void CircularStrip::SetAllColor(StripColor color) { + std::lock_guard lock(mutex_); + esp_timer_stop(strip_timer_); + for (int i = 0; i < max_leds_; i++) { + colors_[i] = color; + led_strip_set_pixel(led_strip_, i, color.red, color.green, color.blue); + } + led_strip_refresh(led_strip_); +} + +void CircularStrip::SetSingleColor(uint8_t index, StripColor color) { + std::lock_guard lock(mutex_); + esp_timer_stop(strip_timer_); + colors_[index] = color; + led_strip_set_pixel(led_strip_, index, color.red, color.green, color.blue); + led_strip_refresh(led_strip_); +} + +void CircularStrip::Blink(StripColor color, int interval_ms) { + for (int i = 0; i < max_leds_; i++) { + colors_[i] = color; + } + StartStripTask(interval_ms, [this]() { + static bool on = true; + if (on) { + for (int i = 0; i < max_leds_; i++) { + led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue); + } + led_strip_refresh(led_strip_); + } else { + led_strip_clear(led_strip_); + } + on = !on; + }); +} + +void CircularStrip::FadeOut(int interval_ms) { + StartStripTask(interval_ms, [this]() { + bool all_off = true; + for (int i = 0; i < max_leds_; i++) { + colors_[i].red /= 2; + colors_[i].green /= 2; + colors_[i].blue /= 2; + if (colors_[i].red != 0 || colors_[i].green != 0 || colors_[i].blue != 0) { + all_off = false; + } + led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue); + } + if (all_off) { + led_strip_clear(led_strip_); + esp_timer_stop(strip_timer_); + } else { + led_strip_refresh(led_strip_); + } + }); +} + +void CircularStrip::Breathe(StripColor low, StripColor high, int interval_ms) { + StartStripTask(interval_ms, [this, low, high]() { + static bool increase = true; + static StripColor color = low; + if (increase) { + if (color.red < high.red) { + color.red++; + } + if (color.green < high.green) { + color.green++; + } + if (color.blue < high.blue) { + color.blue++; + } + if (color.red == high.red && color.green == high.green && color.blue == high.blue) { + increase = false; + } + } else { + if (color.red > low.red) { + color.red--; + } + if (color.green > low.green) { + color.green--; + } + if (color.blue > low.blue) { + color.blue--; + } + if (color.red == low.red && color.green == low.green && color.blue == low.blue) { + increase = true; + } + } + for (int i = 0; i < max_leds_; i++) { + led_strip_set_pixel(led_strip_, i, color.red, color.green, color.blue); + } + led_strip_refresh(led_strip_); + }); +} + +void CircularStrip::Scroll(StripColor low, StripColor high, int length, int interval_ms) { + for (int i = 0; i < max_leds_; i++) { + colors_[i] = low; + } + StartStripTask(interval_ms, [this, low, high, length]() { + static int offset = 0; + for (int i = 0; i < max_leds_; i++) { + colors_[i] = low; + } + for (int j = 0; j < length; j++) { + int i = (offset + j) % max_leds_; + colors_[i] = high; + } + for (int i = 0; i < max_leds_; i++) { + led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue); + } + led_strip_refresh(led_strip_); + offset = (offset + 1) % max_leds_; + }); +} + +void CircularStrip::StartStripTask(int interval_ms, std::function cb) { + if (led_strip_ == nullptr) { + return; + } + + std::lock_guard lock(mutex_); + esp_timer_stop(strip_timer_); + + strip_callback_ = cb; + esp_timer_start_periodic(strip_timer_, interval_ms * 1000); +} + +void CircularStrip::SetBrightness(uint8_t default_brightness, uint8_t low_brightness) { + default_brightness_ = default_brightness; + low_brightness_ = low_brightness; + OnStateChanged(); +} + +void CircularStrip::OnStateChanged() { + auto& app = Application::GetInstance(); + auto device_state = app.GetDeviceState(); + switch (device_state) { + case kDeviceStateStarting: { + StripColor low = { 0, 0, 0 }; + StripColor high = { low_brightness_, low_brightness_, default_brightness_ }; + Scroll(low, high, 3, 100); + break; + } + case kDeviceStateWifiConfiguring: { + StripColor color = { low_brightness_, low_brightness_, default_brightness_ }; + Blink(color, 500); + break; + } + case kDeviceStateIdle: + FadeOut(50); + break; + case kDeviceStateConnecting: { + StripColor color = { low_brightness_, low_brightness_, default_brightness_ }; + SetAllColor(color); + break; + } + case kDeviceStateListening: + case kDeviceStateAudioTesting: { + StripColor color = { default_brightness_, low_brightness_, low_brightness_ }; + SetAllColor(color); + break; + } + case kDeviceStateSpeaking: { + StripColor color = { low_brightness_, default_brightness_, low_brightness_ }; + SetAllColor(color); + break; + } + case kDeviceStateUpgrading: { + StripColor color = { low_brightness_, default_brightness_, low_brightness_ }; + Blink(color, 100); + break; + } + case kDeviceStateActivating: { + StripColor color = { low_brightness_, default_brightness_, low_brightness_ }; + Blink(color, 500); + break; + } + default: + ESP_LOGW(TAG, "Unknown led strip event: %d", device_state); + return; + } +} diff --git a/main/led/circular_strip.h b/main/led/circular_strip.h index d5d6c22..f00bce5 100644 --- a/main/led/circular_strip.h +++ b/main/led/circular_strip.h @@ -1,51 +1,51 @@ -#ifndef _CIRCULAR_STRIP_H_ -#define _CIRCULAR_STRIP_H_ - -#include "led.h" -#include -#include -#include -#include -#include -#include - -#define DEFAULT_BRIGHTNESS 32 -#define LOW_BRIGHTNESS 4 - -struct StripColor { - uint8_t red = 0, green = 0, blue = 0; -}; - -class CircularStrip : public Led { -public: - CircularStrip(gpio_num_t gpio, uint8_t max_leds); - virtual ~CircularStrip(); - - void OnStateChanged() override; - void SetBrightness(uint8_t default_brightness, uint8_t low_brightness); - void SetAllColor(StripColor color); - void SetSingleColor(uint8_t index, StripColor color); - void Blink(StripColor color, int interval_ms); - void Breathe(StripColor low, StripColor high, int interval_ms); - void Scroll(StripColor low, StripColor high, int length, int interval_ms); - -private: - std::mutex mutex_; - TaskHandle_t blink_task_ = nullptr; - led_strip_handle_t led_strip_ = nullptr; - int max_leds_ = 0; - std::vector colors_; - int blink_counter_ = 0; - int blink_interval_ms_ = 0; - esp_timer_handle_t strip_timer_ = nullptr; - std::function strip_callback_ = nullptr; - - uint8_t default_brightness_ = DEFAULT_BRIGHTNESS; - uint8_t low_brightness_ = LOW_BRIGHTNESS; - - void StartStripTask(int interval_ms, std::function cb); - void Rainbow(StripColor low, StripColor high, int interval_ms); - void FadeOut(int interval_ms); -}; - -#endif // _CIRCULAR_STRIP_H_ +#ifndef _CIRCULAR_STRIP_H_ +#define _CIRCULAR_STRIP_H_ + +#include "led.h" +#include +#include +#include +#include +#include +#include + +#define DEFAULT_BRIGHTNESS 32 +#define LOW_BRIGHTNESS 4 + +struct StripColor { + uint8_t red = 0, green = 0, blue = 0; +}; + +class CircularStrip : public Led { +public: + CircularStrip(gpio_num_t gpio, uint8_t max_leds); + virtual ~CircularStrip(); + + void OnStateChanged() override; + void SetBrightness(uint8_t default_brightness, uint8_t low_brightness); + void SetAllColor(StripColor color); + void SetSingleColor(uint8_t index, StripColor color); + void Blink(StripColor color, int interval_ms); + void Breathe(StripColor low, StripColor high, int interval_ms); + void Scroll(StripColor low, StripColor high, int length, int interval_ms); + +private: + std::mutex mutex_; + TaskHandle_t blink_task_ = nullptr; + led_strip_handle_t led_strip_ = nullptr; + int max_leds_ = 0; + std::vector colors_; + int blink_counter_ = 0; + int blink_interval_ms_ = 0; + esp_timer_handle_t strip_timer_ = nullptr; + std::function strip_callback_ = nullptr; + + uint8_t default_brightness_ = DEFAULT_BRIGHTNESS; + uint8_t low_brightness_ = LOW_BRIGHTNESS; + + void StartStripTask(int interval_ms, std::function cb); + void Rainbow(StripColor low, StripColor high, int interval_ms); + void FadeOut(int interval_ms); +}; + +#endif // _CIRCULAR_STRIP_H_ diff --git a/main/led/gpio_led.cc b/main/led/gpio_led.cc index c3c3266..7993569 100644 --- a/main/led/gpio_led.cc +++ b/main/led/gpio_led.cc @@ -1,249 +1,249 @@ -#include "gpio_led.h" -#include "application.h" -#include "device_state.h" -#include - -#define TAG "GpioLed" - -#define DEFAULT_BRIGHTNESS 50 -#define HIGH_BRIGHTNESS 100 -#define LOW_BRIGHTNESS 10 - -#define IDLE_BRIGHTNESS 5 -#define SPEAKING_BRIGHTNESS 75 -#define UPGRADING_BRIGHTNESS 25 -#define ACTIVATING_BRIGHTNESS 35 - -#define BLINK_INFINITE -1 - -// GPIO_LED -#define LEDC_LS_TIMER LEDC_TIMER_1 -#define LEDC_LS_MODE LEDC_LOW_SPEED_MODE -#define LEDC_LS_CH0_CHANNEL LEDC_CHANNEL_0 - -#define LEDC_DUTY (8191) -#define LEDC_FADE_TIME (1000) -// GPIO_LED - -GpioLed::GpioLed(gpio_num_t gpio) - : GpioLed(gpio, 0, LEDC_LS_TIMER, LEDC_LS_CH0_CHANNEL) { -} - -GpioLed::GpioLed(gpio_num_t gpio, int output_invert) - : GpioLed(gpio, output_invert, LEDC_LS_TIMER, LEDC_LS_CH0_CHANNEL) { -} - -GpioLed::GpioLed(gpio_num_t gpio, int output_invert, ledc_timer_t timer_num, ledc_channel_t channel) { - // If the gpio is not connected, you should use NoLed class - assert(gpio != GPIO_NUM_NC); - - /* - * Prepare and set configuration of timers - * that will be used by LED Controller - */ - ledc_timer_config_t ledc_timer = {}; - ledc_timer.duty_resolution = LEDC_TIMER_13_BIT; // resolution of PWM duty - ledc_timer.freq_hz = 4000; // frequency of PWM signal - ledc_timer.speed_mode = LEDC_LS_MODE; // timer mode - ledc_timer.timer_num = timer_num; // timer index - ledc_timer.clk_cfg = LEDC_AUTO_CLK; // Auto select the source clock - - ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); - - ledc_channel_.channel = channel, - ledc_channel_.duty = 0, - ledc_channel_.gpio_num = gpio, - ledc_channel_.speed_mode = LEDC_LS_MODE, - ledc_channel_.hpoint = 0, - ledc_channel_.timer_sel = timer_num, - ledc_channel_.flags.output_invert = output_invert & 0x01, - - // Set LED Controller with previously prepared configuration - ledc_channel_config(&ledc_channel_); - - // Initialize fade service. - ledc_fade_func_install(0); - - // When the callback registered by ledc_cb_degister is called, run led ->OnFadeEnd() - ledc_cbs_t ledc_callbacks = { - .fade_cb = FadeCallback - }; - ledc_cb_register(ledc_channel_.speed_mode, ledc_channel_.channel, &ledc_callbacks, this); - - esp_timer_create_args_t blink_timer_args = { - .callback = [](void *arg) { - auto led = static_cast(arg); - led->OnBlinkTimer(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "Blink Timer", - .skip_unhandled_events = false, - }; - ESP_ERROR_CHECK(esp_timer_create(&blink_timer_args, &blink_timer_)); - - ledc_initialized_ = true; -} - -GpioLed::~GpioLed() { - esp_timer_stop(blink_timer_); - if (ledc_initialized_) { - ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel); - ledc_fade_func_uninstall(); - } -} - - -void GpioLed::SetBrightness(uint8_t brightness) { - if (brightness == 100) { - duty_ = LEDC_DUTY; - } else { - duty_ = brightness * LEDC_DUTY / 100; - } -} - -void GpioLed::TurnOn() { - if (!ledc_initialized_) { - return; - } - - std::lock_guard lock(mutex_); - esp_timer_stop(blink_timer_); - ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel); - ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, duty_); - ledc_update_duty(ledc_channel_.speed_mode, ledc_channel_.channel); -} - -void GpioLed::TurnOff() { - if (!ledc_initialized_) { - return; - } - - std::lock_guard lock(mutex_); - esp_timer_stop(blink_timer_); - ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel); - ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, 0); - ledc_update_duty(ledc_channel_.speed_mode, ledc_channel_.channel); -} - -void GpioLed::BlinkOnce() { - Blink(1, 100); -} - -void GpioLed::Blink(int times, int interval_ms) { - StartBlinkTask(times, interval_ms); -} - -void GpioLed::StartContinuousBlink(int interval_ms) { - StartBlinkTask(BLINK_INFINITE, interval_ms); -} - -void GpioLed::StartBlinkTask(int times, int interval_ms) { - if (!ledc_initialized_) { - return; - } - - std::lock_guard lock(mutex_); - esp_timer_stop(blink_timer_); - ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel); - - blink_counter_ = times * 2; - blink_interval_ms_ = interval_ms; - esp_timer_start_periodic(blink_timer_, interval_ms * 1000); -} - -void GpioLed::OnBlinkTimer() { - std::lock_guard lock(mutex_); - blink_counter_--; - if (blink_counter_ & 1) { - ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, duty_); - } else { - ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, 0); - - if (blink_counter_ == 0) { - esp_timer_stop(blink_timer_); - } - } - ledc_update_duty(ledc_channel_.speed_mode, ledc_channel_.channel); -} - -void GpioLed::StartFadeTask() { - if (!ledc_initialized_) { - return; - } - - std::lock_guard lock(mutex_); - esp_timer_stop(blink_timer_); - ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel); - fade_up_ = true; - ledc_set_fade_with_time(ledc_channel_.speed_mode, - ledc_channel_.channel, LEDC_DUTY, LEDC_FADE_TIME); - ledc_fade_start(ledc_channel_.speed_mode, - ledc_channel_.channel, LEDC_FADE_NO_WAIT); -} - -void GpioLed::OnFadeEnd() { - std::lock_guard lock(mutex_); - fade_up_ = !fade_up_; - ledc_set_fade_with_time(ledc_channel_.speed_mode, - ledc_channel_.channel, fade_up_ ? LEDC_DUTY : 0, LEDC_FADE_TIME); - ledc_fade_start(ledc_channel_.speed_mode, - ledc_channel_.channel, LEDC_FADE_NO_WAIT); -} - -bool IRAM_ATTR GpioLed::FadeCallback(const ledc_cb_param_t *param, void *user_arg) { - if (param->event == LEDC_FADE_END_EVT) { - auto led = static_cast(user_arg); - led->OnFadeEnd(); - } - return true; -} - -void GpioLed::OnStateChanged() { - auto& app = Application::GetInstance(); - auto device_state = app.GetDeviceState(); - switch (device_state) { - case kDeviceStateStarting: - SetBrightness(DEFAULT_BRIGHTNESS); - StartContinuousBlink(100); - break; - case kDeviceStateWifiConfiguring: - SetBrightness(DEFAULT_BRIGHTNESS); - StartContinuousBlink(500); - break; - case kDeviceStateIdle: - SetBrightness(IDLE_BRIGHTNESS); - TurnOn(); - // TurnOff(); - break; - case kDeviceStateConnecting: - SetBrightness(DEFAULT_BRIGHTNESS); - TurnOn(); - break; - case kDeviceStateListening: - case kDeviceStateAudioTesting: - if (app.IsVoiceDetected()) { - SetBrightness(HIGH_BRIGHTNESS); - } else { - SetBrightness(LOW_BRIGHTNESS); - } - // TurnOn(); - StartFadeTask(); - break; - case kDeviceStateSpeaking: - SetBrightness(SPEAKING_BRIGHTNESS); - TurnOn(); - break; - case kDeviceStateUpgrading: - SetBrightness(UPGRADING_BRIGHTNESS); - StartContinuousBlink(100); - break; - case kDeviceStateActivating: - SetBrightness(ACTIVATING_BRIGHTNESS); - StartContinuousBlink(500); - break; - default: - ESP_LOGE(TAG, "Unknown gpio led event: %d", device_state); - return; - } -} +#include "gpio_led.h" +#include "application.h" +#include "device_state.h" +#include + +#define TAG "GpioLed" + +#define DEFAULT_BRIGHTNESS 50 +#define HIGH_BRIGHTNESS 100 +#define LOW_BRIGHTNESS 10 + +#define IDLE_BRIGHTNESS 5 +#define SPEAKING_BRIGHTNESS 75 +#define UPGRADING_BRIGHTNESS 25 +#define ACTIVATING_BRIGHTNESS 35 + +#define BLINK_INFINITE -1 + +// GPIO_LED +#define LEDC_LS_TIMER LEDC_TIMER_1 +#define LEDC_LS_MODE LEDC_LOW_SPEED_MODE +#define LEDC_LS_CH0_CHANNEL LEDC_CHANNEL_0 + +#define LEDC_DUTY (8191) +#define LEDC_FADE_TIME (1000) +// GPIO_LED + +GpioLed::GpioLed(gpio_num_t gpio) + : GpioLed(gpio, 0, LEDC_LS_TIMER, LEDC_LS_CH0_CHANNEL) { +} + +GpioLed::GpioLed(gpio_num_t gpio, int output_invert) + : GpioLed(gpio, output_invert, LEDC_LS_TIMER, LEDC_LS_CH0_CHANNEL) { +} + +GpioLed::GpioLed(gpio_num_t gpio, int output_invert, ledc_timer_t timer_num, ledc_channel_t channel) { + // If the gpio is not connected, you should use NoLed class + assert(gpio != GPIO_NUM_NC); + + /* + * Prepare and set configuration of timers + * that will be used by LED Controller + */ + ledc_timer_config_t ledc_timer = {}; + ledc_timer.duty_resolution = LEDC_TIMER_13_BIT; // resolution of PWM duty + ledc_timer.freq_hz = 4000; // frequency of PWM signal + ledc_timer.speed_mode = LEDC_LS_MODE; // timer mode + ledc_timer.timer_num = timer_num; // timer index + ledc_timer.clk_cfg = LEDC_AUTO_CLK; // Auto select the source clock + + ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); + + ledc_channel_.channel = channel, + ledc_channel_.duty = 0, + ledc_channel_.gpio_num = gpio, + ledc_channel_.speed_mode = LEDC_LS_MODE, + ledc_channel_.hpoint = 0, + ledc_channel_.timer_sel = timer_num, + ledc_channel_.flags.output_invert = output_invert & 0x01, + + // Set LED Controller with previously prepared configuration + ledc_channel_config(&ledc_channel_); + + // Initialize fade service. + ledc_fade_func_install(0); + + // When the callback registered by ledc_cb_degister is called, run led ->OnFadeEnd() + ledc_cbs_t ledc_callbacks = { + .fade_cb = FadeCallback + }; + ledc_cb_register(ledc_channel_.speed_mode, ledc_channel_.channel, &ledc_callbacks, this); + + esp_timer_create_args_t blink_timer_args = { + .callback = [](void *arg) { + auto led = static_cast(arg); + led->OnBlinkTimer(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "Blink Timer", + .skip_unhandled_events = false, + }; + ESP_ERROR_CHECK(esp_timer_create(&blink_timer_args, &blink_timer_)); + + ledc_initialized_ = true; +} + +GpioLed::~GpioLed() { + esp_timer_stop(blink_timer_); + if (ledc_initialized_) { + ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel); + ledc_fade_func_uninstall(); + } +} + + +void GpioLed::SetBrightness(uint8_t brightness) { + if (brightness == 100) { + duty_ = LEDC_DUTY; + } else { + duty_ = brightness * LEDC_DUTY / 100; + } +} + +void GpioLed::TurnOn() { + if (!ledc_initialized_) { + return; + } + + std::lock_guard lock(mutex_); + esp_timer_stop(blink_timer_); + ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel); + ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, duty_); + ledc_update_duty(ledc_channel_.speed_mode, ledc_channel_.channel); +} + +void GpioLed::TurnOff() { + if (!ledc_initialized_) { + return; + } + + std::lock_guard lock(mutex_); + esp_timer_stop(blink_timer_); + ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel); + ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, 0); + ledc_update_duty(ledc_channel_.speed_mode, ledc_channel_.channel); +} + +void GpioLed::BlinkOnce() { + Blink(1, 100); +} + +void GpioLed::Blink(int times, int interval_ms) { + StartBlinkTask(times, interval_ms); +} + +void GpioLed::StartContinuousBlink(int interval_ms) { + StartBlinkTask(BLINK_INFINITE, interval_ms); +} + +void GpioLed::StartBlinkTask(int times, int interval_ms) { + if (!ledc_initialized_) { + return; + } + + std::lock_guard lock(mutex_); + esp_timer_stop(blink_timer_); + ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel); + + blink_counter_ = times * 2; + blink_interval_ms_ = interval_ms; + esp_timer_start_periodic(blink_timer_, interval_ms * 1000); +} + +void GpioLed::OnBlinkTimer() { + std::lock_guard lock(mutex_); + blink_counter_--; + if (blink_counter_ & 1) { + ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, duty_); + } else { + ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, 0); + + if (blink_counter_ == 0) { + esp_timer_stop(blink_timer_); + } + } + ledc_update_duty(ledc_channel_.speed_mode, ledc_channel_.channel); +} + +void GpioLed::StartFadeTask() { + if (!ledc_initialized_) { + return; + } + + std::lock_guard lock(mutex_); + esp_timer_stop(blink_timer_); + ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel); + fade_up_ = true; + ledc_set_fade_with_time(ledc_channel_.speed_mode, + ledc_channel_.channel, LEDC_DUTY, LEDC_FADE_TIME); + ledc_fade_start(ledc_channel_.speed_mode, + ledc_channel_.channel, LEDC_FADE_NO_WAIT); +} + +void GpioLed::OnFadeEnd() { + std::lock_guard lock(mutex_); + fade_up_ = !fade_up_; + ledc_set_fade_with_time(ledc_channel_.speed_mode, + ledc_channel_.channel, fade_up_ ? LEDC_DUTY : 0, LEDC_FADE_TIME); + ledc_fade_start(ledc_channel_.speed_mode, + ledc_channel_.channel, LEDC_FADE_NO_WAIT); +} + +bool IRAM_ATTR GpioLed::FadeCallback(const ledc_cb_param_t *param, void *user_arg) { + if (param->event == LEDC_FADE_END_EVT) { + auto led = static_cast(user_arg); + led->OnFadeEnd(); + } + return true; +} + +void GpioLed::OnStateChanged() { + auto& app = Application::GetInstance(); + auto device_state = app.GetDeviceState(); + switch (device_state) { + case kDeviceStateStarting: + SetBrightness(DEFAULT_BRIGHTNESS); + StartContinuousBlink(100); + break; + case kDeviceStateWifiConfiguring: + SetBrightness(DEFAULT_BRIGHTNESS); + StartContinuousBlink(500); + break; + case kDeviceStateIdle: + SetBrightness(IDLE_BRIGHTNESS); + TurnOn(); + // TurnOff(); + break; + case kDeviceStateConnecting: + SetBrightness(DEFAULT_BRIGHTNESS); + TurnOn(); + break; + case kDeviceStateListening: + case kDeviceStateAudioTesting: + if (app.IsVoiceDetected()) { + SetBrightness(HIGH_BRIGHTNESS); + } else { + SetBrightness(LOW_BRIGHTNESS); + } + // TurnOn(); + StartFadeTask(); + break; + case kDeviceStateSpeaking: + SetBrightness(SPEAKING_BRIGHTNESS); + TurnOn(); + break; + case kDeviceStateUpgrading: + SetBrightness(UPGRADING_BRIGHTNESS); + StartContinuousBlink(100); + break; + case kDeviceStateActivating: + SetBrightness(ACTIVATING_BRIGHTNESS); + StartContinuousBlink(500); + break; + default: + ESP_LOGE(TAG, "Unknown gpio led event: %d", device_state); + return; + } +} diff --git a/main/led/gpio_led.h b/main/led/gpio_led.h index 4d1f5db..058b8a6 100644 --- a/main/led/gpio_led.h +++ b/main/led/gpio_led.h @@ -1,47 +1,47 @@ -#ifndef _GPIO_LED_H_ -#define _GPIO_LED_H_ - -#include -#include -#include "led.h" -#include -#include -#include -#include -#include - -class GpioLed : public Led { - public: - GpioLed(gpio_num_t gpio); - GpioLed(gpio_num_t gpio, int output_invert); - GpioLed(gpio_num_t gpio, int output_invert, ledc_timer_t timer_num, ledc_channel_t channel); - virtual ~GpioLed(); - - void OnStateChanged() override; - void TurnOn(); - void TurnOff(); - void SetBrightness(uint8_t brightness); - - private: - std::mutex mutex_; - TaskHandle_t blink_task_ = nullptr; - ledc_channel_config_t ledc_channel_ = {0}; - bool ledc_initialized_ = false; - uint32_t duty_ = 0; - int blink_counter_ = 0; - int blink_interval_ms_ = 0; - esp_timer_handle_t blink_timer_ = nullptr; - bool fade_up_ = true; - - void StartBlinkTask(int times, int interval_ms); - void OnBlinkTimer(); - - void BlinkOnce(); - void Blink(int times, int interval_ms); - void StartContinuousBlink(int interval_ms); - void StartFadeTask(); - void OnFadeEnd(); - static bool IRAM_ATTR FadeCallback(const ledc_cb_param_t *param, void *user_arg); -}; - -#endif // _GPIO_LED_H_ +#ifndef _GPIO_LED_H_ +#define _GPIO_LED_H_ + +#include +#include +#include "led.h" +#include +#include +#include +#include +#include + +class GpioLed : public Led { + public: + GpioLed(gpio_num_t gpio); + GpioLed(gpio_num_t gpio, int output_invert); + GpioLed(gpio_num_t gpio, int output_invert, ledc_timer_t timer_num, ledc_channel_t channel); + virtual ~GpioLed(); + + void OnStateChanged() override; + void TurnOn(); + void TurnOff(); + void SetBrightness(uint8_t brightness); + + private: + std::mutex mutex_; + TaskHandle_t blink_task_ = nullptr; + ledc_channel_config_t ledc_channel_ = {0}; + bool ledc_initialized_ = false; + uint32_t duty_ = 0; + int blink_counter_ = 0; + int blink_interval_ms_ = 0; + esp_timer_handle_t blink_timer_ = nullptr; + bool fade_up_ = true; + + void StartBlinkTask(int times, int interval_ms); + void OnBlinkTimer(); + + void BlinkOnce(); + void Blink(int times, int interval_ms); + void StartContinuousBlink(int interval_ms); + void StartFadeTask(); + void OnFadeEnd(); + static bool IRAM_ATTR FadeCallback(const ledc_cb_param_t *param, void *user_arg); +}; + +#endif // _GPIO_LED_H_ diff --git a/main/led/led.h b/main/led/led.h index 251fd6a..0a6a3cc 100644 --- a/main/led/led.h +++ b/main/led/led.h @@ -1,17 +1,17 @@ -#ifndef _LED_H_ -#define _LED_H_ - -class Led { -public: - virtual ~Led() = default; - // Set the led state based on the device state - virtual void OnStateChanged() = 0; -}; - - -class NoLed : public Led { -public: - virtual void OnStateChanged() override {} -}; - -#endif // _LED_H_ +#ifndef _LED_H_ +#define _LED_H_ + +class Led { +public: + virtual ~Led() = default; + // Set the led state based on the device state + virtual void OnStateChanged() = 0; +}; + + +class NoLed : public Led { +public: + virtual void OnStateChanged() override {} +}; + +#endif // _LED_H_ diff --git a/main/led/single_led.cc b/main/led/single_led.cc index 338af0a..671cb52 100644 --- a/main/led/single_led.cc +++ b/main/led/single_led.cc @@ -1,163 +1,163 @@ -#include "single_led.h" -#include "application.h" -#include - -#define TAG "SingleLed" - -#define DEFAULT_BRIGHTNESS 4 -#define HIGH_BRIGHTNESS 16 -#define LOW_BRIGHTNESS 2 - -#define BLINK_INFINITE -1 - - -SingleLed::SingleLed(gpio_num_t gpio) { - // If the gpio is not connected, you should use NoLed class - assert(gpio != GPIO_NUM_NC); - - led_strip_config_t strip_config = {}; - strip_config.strip_gpio_num = gpio; - strip_config.max_leds = 1; - strip_config.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB; - strip_config.led_model = LED_MODEL_WS2812; - - led_strip_rmt_config_t rmt_config = {}; - rmt_config.resolution_hz = 10 * 1000 * 1000; // 10MHz - - ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_)); - led_strip_clear(led_strip_); - - esp_timer_create_args_t blink_timer_args = { - .callback = [](void *arg) { - auto led = static_cast(arg); - led->OnBlinkTimer(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "blink_timer", - .skip_unhandled_events = false, - }; - ESP_ERROR_CHECK(esp_timer_create(&blink_timer_args, &blink_timer_)); -} - -SingleLed::~SingleLed() { - esp_timer_stop(blink_timer_); - if (led_strip_ != nullptr) { - led_strip_del(led_strip_); - } -} - - -void SingleLed::SetColor(uint8_t r, uint8_t g, uint8_t b) { - r_ = r; - g_ = g; - b_ = b; -} - -void SingleLed::TurnOn() { - if (led_strip_ == nullptr) { - return; - } - - std::lock_guard lock(mutex_); - esp_timer_stop(blink_timer_); - led_strip_set_pixel(led_strip_, 0, r_, g_, b_); - led_strip_refresh(led_strip_); -} - -void SingleLed::TurnOff() { - if (led_strip_ == nullptr) { - return; - } - - std::lock_guard lock(mutex_); - esp_timer_stop(blink_timer_); - led_strip_clear(led_strip_); -} - -void SingleLed::BlinkOnce() { - Blink(1, 100); -} - -void SingleLed::Blink(int times, int interval_ms) { - StartBlinkTask(times, interval_ms); -} - -void SingleLed::StartContinuousBlink(int interval_ms) { - StartBlinkTask(BLINK_INFINITE, interval_ms); -} - -void SingleLed::StartBlinkTask(int times, int interval_ms) { - if (led_strip_ == nullptr) { - return; - } - - std::lock_guard lock(mutex_); - esp_timer_stop(blink_timer_); - - blink_counter_ = times * 2; - blink_interval_ms_ = interval_ms; - esp_timer_start_periodic(blink_timer_, interval_ms * 1000); -} - -void SingleLed::OnBlinkTimer() { - std::lock_guard lock(mutex_); - blink_counter_--; - if (blink_counter_ & 1) { - led_strip_set_pixel(led_strip_, 0, r_, g_, b_); - led_strip_refresh(led_strip_); - } else { - led_strip_clear(led_strip_); - - if (blink_counter_ == 0) { - esp_timer_stop(blink_timer_); - } - } -} - - -void SingleLed::OnStateChanged() { - auto& app = Application::GetInstance(); - auto device_state = app.GetDeviceState(); - switch (device_state) { - case kDeviceStateStarting: - SetColor(0, 0, DEFAULT_BRIGHTNESS); - StartContinuousBlink(100); - break; - case kDeviceStateWifiConfiguring: - SetColor(0, 0, DEFAULT_BRIGHTNESS); - StartContinuousBlink(500); - break; - case kDeviceStateIdle: - TurnOff(); - break; - case kDeviceStateConnecting: - SetColor(0, 0, DEFAULT_BRIGHTNESS); - TurnOn(); - break; - case kDeviceStateListening: - case kDeviceStateAudioTesting: - if (app.IsVoiceDetected()) { - SetColor(HIGH_BRIGHTNESS, 0, 0); - } else { - SetColor(LOW_BRIGHTNESS, 0, 0); - } - TurnOn(); - break; - case kDeviceStateSpeaking: - SetColor(0, DEFAULT_BRIGHTNESS, 0); - TurnOn(); - break; - case kDeviceStateUpgrading: - SetColor(0, DEFAULT_BRIGHTNESS, 0); - StartContinuousBlink(100); - break; - case kDeviceStateActivating: - SetColor(0, DEFAULT_BRIGHTNESS, 0); - StartContinuousBlink(500); - break; - default: - ESP_LOGW(TAG, "Unknown led strip event: %d", device_state); - return; - } -} +#include "single_led.h" +#include "application.h" +#include + +#define TAG "SingleLed" + +#define DEFAULT_BRIGHTNESS 4 +#define HIGH_BRIGHTNESS 16 +#define LOW_BRIGHTNESS 2 + +#define BLINK_INFINITE -1 + + +SingleLed::SingleLed(gpio_num_t gpio) { + // If the gpio is not connected, you should use NoLed class + assert(gpio != GPIO_NUM_NC); + + led_strip_config_t strip_config = {}; + strip_config.strip_gpio_num = gpio; + strip_config.max_leds = 1; + strip_config.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB; + strip_config.led_model = LED_MODEL_WS2812; + + led_strip_rmt_config_t rmt_config = {}; + rmt_config.resolution_hz = 10 * 1000 * 1000; // 10MHz + + ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_)); + led_strip_clear(led_strip_); + + esp_timer_create_args_t blink_timer_args = { + .callback = [](void *arg) { + auto led = static_cast(arg); + led->OnBlinkTimer(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "blink_timer", + .skip_unhandled_events = false, + }; + ESP_ERROR_CHECK(esp_timer_create(&blink_timer_args, &blink_timer_)); +} + +SingleLed::~SingleLed() { + esp_timer_stop(blink_timer_); + if (led_strip_ != nullptr) { + led_strip_del(led_strip_); + } +} + + +void SingleLed::SetColor(uint8_t r, uint8_t g, uint8_t b) { + r_ = r; + g_ = g; + b_ = b; +} + +void SingleLed::TurnOn() { + if (led_strip_ == nullptr) { + return; + } + + std::lock_guard lock(mutex_); + esp_timer_stop(blink_timer_); + led_strip_set_pixel(led_strip_, 0, r_, g_, b_); + led_strip_refresh(led_strip_); +} + +void SingleLed::TurnOff() { + if (led_strip_ == nullptr) { + return; + } + + std::lock_guard lock(mutex_); + esp_timer_stop(blink_timer_); + led_strip_clear(led_strip_); +} + +void SingleLed::BlinkOnce() { + Blink(1, 100); +} + +void SingleLed::Blink(int times, int interval_ms) { + StartBlinkTask(times, interval_ms); +} + +void SingleLed::StartContinuousBlink(int interval_ms) { + StartBlinkTask(BLINK_INFINITE, interval_ms); +} + +void SingleLed::StartBlinkTask(int times, int interval_ms) { + if (led_strip_ == nullptr) { + return; + } + + std::lock_guard lock(mutex_); + esp_timer_stop(blink_timer_); + + blink_counter_ = times * 2; + blink_interval_ms_ = interval_ms; + esp_timer_start_periodic(blink_timer_, interval_ms * 1000); +} + +void SingleLed::OnBlinkTimer() { + std::lock_guard lock(mutex_); + blink_counter_--; + if (blink_counter_ & 1) { + led_strip_set_pixel(led_strip_, 0, r_, g_, b_); + led_strip_refresh(led_strip_); + } else { + led_strip_clear(led_strip_); + + if (blink_counter_ == 0) { + esp_timer_stop(blink_timer_); + } + } +} + + +void SingleLed::OnStateChanged() { + auto& app = Application::GetInstance(); + auto device_state = app.GetDeviceState(); + switch (device_state) { + case kDeviceStateStarting: + SetColor(0, 0, DEFAULT_BRIGHTNESS); + StartContinuousBlink(100); + break; + case kDeviceStateWifiConfiguring: + SetColor(0, 0, DEFAULT_BRIGHTNESS); + StartContinuousBlink(500); + break; + case kDeviceStateIdle: + TurnOff(); + break; + case kDeviceStateConnecting: + SetColor(0, 0, DEFAULT_BRIGHTNESS); + TurnOn(); + break; + case kDeviceStateListening: + case kDeviceStateAudioTesting: + if (app.IsVoiceDetected()) { + SetColor(HIGH_BRIGHTNESS, 0, 0); + } else { + SetColor(LOW_BRIGHTNESS, 0, 0); + } + TurnOn(); + break; + case kDeviceStateSpeaking: + SetColor(0, DEFAULT_BRIGHTNESS, 0); + TurnOn(); + break; + case kDeviceStateUpgrading: + SetColor(0, DEFAULT_BRIGHTNESS, 0); + StartContinuousBlink(100); + break; + case kDeviceStateActivating: + SetColor(0, DEFAULT_BRIGHTNESS, 0); + StartContinuousBlink(500); + break; + default: + ESP_LOGW(TAG, "Unknown led strip event: %d", device_state); + return; + } +} diff --git a/main/led/single_led.h b/main/led/single_led.h index b949f74..22b21fe 100644 --- a/main/led/single_led.h +++ b/main/led/single_led.h @@ -1,38 +1,38 @@ -#ifndef _SINGLE_LED_H_ -#define _SINGLE_LED_H_ - -#include "led.h" -#include -#include -#include -#include -#include - -class SingleLed : public Led { -public: - SingleLed(gpio_num_t gpio); - virtual ~SingleLed(); - - void OnStateChanged() override; - -private: - std::mutex mutex_; - TaskHandle_t blink_task_ = nullptr; - led_strip_handle_t led_strip_ = nullptr; - uint8_t r_ = 0, g_ = 0, b_ = 0; - int blink_counter_ = 0; - int blink_interval_ms_ = 0; - esp_timer_handle_t blink_timer_ = nullptr; - - void StartBlinkTask(int times, int interval_ms); - void OnBlinkTimer(); - - void BlinkOnce(); - void Blink(int times, int interval_ms); - void StartContinuousBlink(int interval_ms); - void TurnOn(); - void TurnOff(); - void SetColor(uint8_t r, uint8_t g, uint8_t b); -}; - -#endif // _SINGLE_LED_H_ +#ifndef _SINGLE_LED_H_ +#define _SINGLE_LED_H_ + +#include "led.h" +#include +#include +#include +#include +#include + +class SingleLed : public Led { +public: + SingleLed(gpio_num_t gpio); + virtual ~SingleLed(); + + void OnStateChanged() override; + +private: + std::mutex mutex_; + TaskHandle_t blink_task_ = nullptr; + led_strip_handle_t led_strip_ = nullptr; + uint8_t r_ = 0, g_ = 0, b_ = 0; + int blink_counter_ = 0; + int blink_interval_ms_ = 0; + esp_timer_handle_t blink_timer_ = nullptr; + + void StartBlinkTask(int times, int interval_ms); + void OnBlinkTimer(); + + void BlinkOnce(); + void Blink(int times, int interval_ms); + void StartContinuousBlink(int interval_ms); + void TurnOn(); + void TurnOff(); + void SetColor(uint8_t r, uint8_t g, uint8_t b); +}; + +#endif // _SINGLE_LED_H_ diff --git a/main/main.cc b/main/main.cc old mode 100755 new mode 100644 index 7ceda91..ac350e7 --- a/main/main.cc +++ b/main/main.cc @@ -1,32 +1,32 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "application.h" -#include "system_info.h" - -#define TAG "main" - -extern "C" void app_main(void) -{ - // Initialize the default event loop - ESP_ERROR_CHECK(esp_event_loop_create_default()); - - // Initialize NVS flash for WiFi configuration - esp_err_t ret = nvs_flash_init(); - if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { - ESP_LOGW(TAG, "Erasing NVS flash to fix corruption"); - ESP_ERROR_CHECK(nvs_flash_erase()); - ret = nvs_flash_init(); - } - ESP_ERROR_CHECK(ret); - - // Launch the application - auto& app = Application::GetInstance(); - app.Start(); -} +#include +#include +#include +#include +#include +#include +#include +#include + +#include "application.h" +#include "system_info.h" + +#define TAG "main" + +extern "C" void app_main(void) +{ + // Initialize the default event loop + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + // Initialize NVS flash for WiFi configuration + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_LOGW(TAG, "Erasing NVS flash to fix corruption"); + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + // Launch the application + auto& app = Application::GetInstance(); + app.Start(); +} diff --git a/main/mcp_server.cc b/main/mcp_server.cc index 8db21a2..92c3b8f 100644 --- a/main/mcp_server.cc +++ b/main/mcp_server.cc @@ -1,865 +1,1131 @@ -/* - * MCP Server Implementation - * Reference: https://modelcontextprotocol.io/specification/2024-11-05 - */ - -#include "mcp_server.h" -#include -#include -#include -#include -#include - -#include "application.h" -#include "display.h" -#include "oled_display.h" -#include "board.h" -#include "boards/common/esp32_music.h" -#include "settings.h" -#include "lvgl_theme.h" -#include "lvgl_display.h" - -#include "schedule_manager.h" -#include "timer_manager.h" - -#define TAG "MCP" - -McpServer::McpServer() { -} - -McpServer::~McpServer() { - for (auto tool : tools_) { - delete tool; - } - tools_.clear(); -} - -void McpServer::AddCommonTools() { - // *Important* To speed up the response time, we add the common tools to the beginning of - // the tools list to utilize the prompt cache. - // **重要** 为了提升响应速度,我们把常用的工具放在前面,利用 prompt cache 的特性。 - - // Backup the original tools list and restore it after adding the common tools. - auto original_tools = std::move(tools_); - auto& board = Board::GetInstance(); - - // Do not add custom tools here. - // Custom tools must be added in the board's InitializeTools function. - - AddTool("self.get_device_status", - "Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\n" - "Use this tool for: \n" - "1. Answering questions about current condition (e.g. what is the current volume of the audio speaker?)\n" - "2. As the first step to control the device (e.g. turn up / down the volume of the audio speaker, etc.)", - PropertyList(), - [&board](const PropertyList& properties) -> ReturnValue { - return board.GetDeviceStatusJson(); - }); - - AddTool("self.audio_speaker.set_volume", - "Set the volume of the audio speaker. If the current volume is unknown, you must call `self.get_device_status` tool first and then call this tool.", - PropertyList({ - Property("volume", kPropertyTypeInteger, 0, 100) - }), - [&board](const PropertyList& properties) -> ReturnValue { - auto codec = board.GetAudioCodec(); - codec->SetOutputVolume(properties["volume"].value()); - return true; - }); - - auto backlight = board.GetBacklight(); - if (backlight) { - AddTool("self.screen.set_brightness", - "Set the brightness of the screen.", - PropertyList({ - Property("brightness", kPropertyTypeInteger, 0, 100) - }), - [backlight](const PropertyList& properties) -> ReturnValue { - uint8_t brightness = static_cast(properties["brightness"].value()); - backlight->SetBrightness(brightness, true); - return true; - }); - } - -#ifdef HAVE_LVGL - auto display = board.GetDisplay(); - if (display && display->GetTheme() != nullptr) { - AddTool("self.screen.set_theme", - "Set the theme of the screen. The theme can be `light` or `dark`.", - PropertyList({ - Property("theme", kPropertyTypeString) - }), - [display](const PropertyList& properties) -> ReturnValue { - auto theme_name = properties["theme"].value(); - auto& theme_manager = LvglThemeManager::GetInstance(); - auto theme = theme_manager.GetTheme(theme_name); - if (theme != nullptr) { - display->SetTheme(theme); - return true; - } - return false; - }); - } - - auto camera = board.GetCamera(); - if (camera) { - AddTool("self.camera.take_photo", - "Take a photo and explain it. Use this tool after the user asks you to see something.\n" - "Args:\n" - " `question`: The question that you want to ask about the photo.\n" - "Return:\n" - " A JSON object that provides the photo information.", - PropertyList({ - Property("question", kPropertyTypeString) - }), - [camera](const PropertyList& properties) -> ReturnValue { - // Lower the priority to do the camera capture - TaskPriorityReset priority_reset(1); - - if (!camera->Capture()) { - throw std::runtime_error("Failed to capture photo"); - } - auto question = properties["question"].value(); - return camera->Explain(question); - }); - } -#endif -auto music = board.GetMusic(); - if (music) { - AddTool("self.music.play_song", - "Play the specified song. When users request to play music, this tool will automatically retrieve song details and start streaming.\n" - "parameter:\n" - " `song_name`: The name of the song to be played.\n" - "return:\n" - " Play status information without confirmation, immediately play the song.", - PropertyList({Property("song_name", kPropertyTypeString)}), - [music](const PropertyList &properties) -> ReturnValue { - auto song_name = properties["song_name"].value(); - if (!music->Download(song_name)) { - return "{\"success\": false, \"message\": \"Failed to obtain music resources\"}"; - } - auto download_result = music->GetDownloadResult(); - ESP_LOGD(TAG, "Music details result: %s", download_result.c_str()); - return true; - }); - - AddTool("self.music.set_volume", - "Set music volume (0-100).", - PropertyList({Property("volume", kPropertyTypeInteger, 0, 100)}), - [music](const PropertyList &properties) -> ReturnValue { - int vol = properties["volume"].value(); - bool ok = music->SetVolume(vol); - return ok; - }); - - AddTool("self.music.play", - "Play current music.", - PropertyList(), - [music](const PropertyList &properties) -> ReturnValue { - bool ok = music->PlaySong(); - return ok; - }); - - // 兼容更明确的命名:stop_song / pause_song / resume_song - AddTool("self.music.stop_song", - "Stop current song.", - PropertyList(), - [music](const PropertyList &properties) -> ReturnValue { - bool ok = music->StopSong(); - return ok; - }); - - AddTool("self.music.pause_song", - "Pause current song.", - PropertyList(), - [music](const PropertyList &properties) -> ReturnValue { - bool ok = music->PauseSong(); - return ok; - }); - - AddTool("self.music.resume_song", - "Resume current song.", - PropertyList(), - [music](const PropertyList &properties) -> ReturnValue { - bool ok = music->ResumeSong(); - return ok; - }); - - AddTool("self.music.set_display_mode", - "Set the display mode for music playback. You can choose to display the spectrum or lyrics, for example, if the user says' open spectrum 'or' display spectrum ', the corresponding display mode will be set for' open lyrics' or 'display lyrics'.\n" - "parameter:\n" - " `mode`: Display mode, with optional values of 'spectrum' or 'lyrics'.\n" - "return:\n" - " Set result information.", - PropertyList({ - Property("mode", kPropertyTypeString) // Display mode: "spectrum" or "lyrics" - }), - [music](const PropertyList &properties) -> ReturnValue { - auto mode_str = properties["mode"].value(); - - // Convert to lowercase for comparison - std::transform(mode_str.begin(), mode_str.end(), mode_str.begin(), ::tolower); - - if (mode_str == "spectrum" || mode_str == "频谱") { - // Set to spectrum display mode - auto esp32_music = static_cast(music); - esp32_music->SetDisplayMode(Esp32Music::DISPLAY_MODE_SPECTRUM); - return "{\"success\": true, \"message\": \"Switched to spectrum display mode\"}"; - } else if (mode_str == "lyrics" || mode_str == "歌词") { - // Set to lyrics display mode - auto esp32_music = static_cast(music); - esp32_music->SetDisplayMode(Esp32Music::DISPLAY_MODE_LYRICS); - return "{\"success\": true, \"message\": \"Switched to lyrics display mode\"}"; - } else { - return "{\"success\": false, \"message\": \"Invalid display mode, please use 'spectrum' or 'lyrics'\"}"; - } - - return "{\"success\": false, \"message\": \"Failed to set display mode\"}"; - }); - } - - // Restore the original tools list to the end of the tools list - tools_.insert(tools_.end(), original_tools.begin(), original_tools.end()); -} - -void McpServer::AddUserOnlyTools() { - // System tools - AddUserOnlyTool("self.get_system_info", - "Get the system information", - PropertyList(), - [this](const PropertyList& properties) -> ReturnValue { - auto& board = Board::GetInstance(); - return board.GetSystemInfoJson(); - }); - - AddUserOnlyTool("self.reboot", "Reboot the system", - PropertyList(), - [this](const PropertyList& properties) -> ReturnValue { - auto& app = Application::GetInstance(); - app.Schedule([&app]() { - ESP_LOGW(TAG, "User requested reboot"); - vTaskDelay(pdMS_TO_TICKS(1000)); - - app.Reboot(); - }); - return true; - }); - - // Firmware upgrade - AddUserOnlyTool("self.upgrade_firmware", "Upgrade firmware from a specific URL. This will download and install the firmware, then reboot the device.", - PropertyList({ - Property("url", kPropertyTypeString, "The URL of the firmware binary file to download and install") - }), - [this](const PropertyList& properties) -> ReturnValue { - auto url = properties["url"].value(); - ESP_LOGI(TAG, "User requested firmware upgrade from URL: %s", url.c_str()); - - auto& app = Application::GetInstance(); - app.Schedule([url, &app]() { - auto ota = std::make_unique(); - - bool success = app.UpgradeFirmware(*ota, url); - if (!success) { - ESP_LOGE(TAG, "Firmware upgrade failed"); - } - }); - - return true; - }); - - // Display control -#ifdef HAVE_LVGL - auto display = dynamic_cast(Board::GetInstance().GetDisplay()); - if (display) { - AddUserOnlyTool("self.screen.get_info", "Information about the screen, including width, height, etc.", - PropertyList(), - [display](const PropertyList& properties) -> ReturnValue { - cJSON *json = cJSON_CreateObject(); - cJSON_AddNumberToObject(json, "width", display->width()); - cJSON_AddNumberToObject(json, "height", display->height()); - if (dynamic_cast(display)) { - cJSON_AddBoolToObject(json, "monochrome", true); - } else { - cJSON_AddBoolToObject(json, "monochrome", false); - } - return json; - }); - - AddUserOnlyTool("self.screen.snapshot", "Snapshot the screen and upload it to a specific URL", - PropertyList({ - Property("url", kPropertyTypeString), - Property("quality", kPropertyTypeInteger, 80, 1, 100) - }), - [display](const PropertyList& properties) -> ReturnValue { - auto url = properties["url"].value(); - auto quality = properties["quality"].value(); - - uint8_t* jpeg_output_data = nullptr; - size_t jpeg_output_size = 0; - if (!display->SnapshotToJpeg(jpeg_output_data, jpeg_output_size, quality)) { - throw std::runtime_error("Failed to snapshot screen"); - } - - ESP_LOGI(TAG, "Upload snapshot %u bytes to %s", jpeg_output_size, url.c_str()); - - // 构造multipart/form-data请求体 - std::string boundary = "----ESP32_SCREEN_SNAPSHOT_BOUNDARY"; - - auto http = Board::GetInstance().GetNetwork()->CreateHttp(3); - http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary); - if (!http->Open("POST", url)) { - free(jpeg_output_data); - throw std::runtime_error("Failed to open URL: " + url); - } - { - // 文件字段头部 - std::string file_header; - file_header += "--" + boundary + "\r\n"; - file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"screenshot.jpg\"\r\n"; - file_header += "Content-Type: image/jpeg\r\n"; - file_header += "\r\n"; - http->Write(file_header.c_str(), file_header.size()); - } - - // JPEG数据 - http->Write((const char*)jpeg_output_data, jpeg_output_size); - free(jpeg_output_data); - - { - // multipart尾部 - std::string multipart_footer; - multipart_footer += "\r\n--" + boundary + "--\r\n"; - http->Write(multipart_footer.c_str(), multipart_footer.size()); - } - http->Write("", 0); - - if (http->GetStatusCode() != 200) { - throw std::runtime_error("Unexpected status code: " + std::to_string(http->GetStatusCode())); - } - std::string result = http->ReadAll(); - http->Close(); - ESP_LOGI(TAG, "Snapshot screen result: %s", result.c_str()); - return true; - }); - - AddUserOnlyTool("self.screen.preview_image", "Preview an image on the screen", - PropertyList({ - Property("url", kPropertyTypeString) - }), - [display](const PropertyList& properties) -> ReturnValue { - auto url = properties["url"].value(); - auto http = Board::GetInstance().GetNetwork()->CreateHttp(3); - - if (!http->Open("GET", url)) { - throw std::runtime_error("Failed to open URL: " + url); - } - int status_code = http->GetStatusCode(); - if (status_code != 200) { - throw std::runtime_error("Unexpected status code: " + std::to_string(status_code)); - } - - size_t content_length = http->GetBodyLength(); - char* data = (char*)heap_caps_malloc(content_length, MALLOC_CAP_8BIT); - if (data == nullptr) { - throw std::runtime_error("Failed to allocate memory for image: " + url); - } - size_t total_read = 0; - while (total_read < content_length) { - int ret = http->Read(data + total_read, content_length - total_read); - if (ret < 0) { - heap_caps_free(data); - throw std::runtime_error("Failed to download image: " + url); - } - if (ret == 0) { - break; - } - total_read += ret; - } - http->Close(); - - auto image = std::make_unique(data, content_length); - display->SetPreviewImage(std::move(image)); - return true; - }); - } -#endif - - // Assets download url - auto assets = Board::GetInstance().GetAssets(); - if (assets) { - if (assets->partition_valid()) { - AddUserOnlyTool("self.assets.set_download_url", "Set the download url for the assets", - PropertyList({ - Property("url", kPropertyTypeString) - }), - [assets](const PropertyList& properties) -> ReturnValue { - auto url = properties["url"].value(); - Settings settings("assets", true); - settings.SetString("download_url", url); - return true; - }); - } - } - - // 日程管理工具 - auto &schedule_manager = ScheduleManager::GetInstance(); - - AddTool("self.schedule.create_event", - "Create a new schedule event. Support intelligent classification and reminder functions.\n" - "parameter:\n" - " `title`: Event title (required)\n" - " `description`: Event description (optional)\n" - " `start_time`: Start timestamp (required)\n" - " `end_time`: End timestamp (optional, 0 means no end time)\n" - " `category`: Event category (optional, if not provided, it will be automatically classified)\n" - " `is_all_day`: Whether it is an all-day event (optional, default false)\n" - " `reminder_minutes`: Reminder time (optional, default 15 minutes)\n" - "Return:\n" - " Event ID string, used for subsequent operations", - PropertyList({Property("title", kPropertyTypeString), - Property("description", kPropertyTypeString, ""), - Property("start_time", kPropertyTypeInteger), - Property("end_time", kPropertyTypeInteger, 0), - Property("category", kPropertyTypeString, ""), - Property("is_all_day", kPropertyTypeBoolean, false), - Property("reminder_minutes", kPropertyTypeInteger, 15, 0, 1440)}), - [&schedule_manager](const PropertyList &properties) -> ReturnValue { - auto title = properties["title"].value(); - auto description = properties["description"].value(); - time_t start_time = properties["start_time"].value(); - time_t end_time = properties["end_time"].value(); - auto category = properties["category"].value(); - bool is_all_day = properties["is_all_day"].value(); - int reminder_minutes = properties["reminder_minutes"].value(); - - std::string event_id = schedule_manager.CreateEvent(title, description, start_time, end_time,category, is_all_day, reminder_minutes); - - if (event_id.empty()) - { - return "{\"success\": false, \"message\": \"Event creation failed\"}"; - } - - return "{\"success\": true, \"event_id\": \"" + event_id + "\", \"message\": \"Event created successfully\"}"; - }); - - AddTool("self.schedule.get_events", - "Get all schedule events.\n" - "Return:\n" - " JSON array of event objects", - PropertyList(), - [&schedule_manager](const PropertyList &properties) -> ReturnValue { - std::string json_str = schedule_manager.ExportToJson(); - return json_str; - }); - - AddTool("self.schedule.delete_event", - "Delete a schedule event.\n" - "Parameter:\n" - " `event_id`: ID of the event to delete (required)\n" - "Return:\n" - " Operation result", - PropertyList({Property("event_id", kPropertyTypeString)}), - [&schedule_manager](const PropertyList &properties) -> ReturnValue { - auto event_id = properties["event_id"].value(); - - bool success = schedule_manager.DeleteEvent(event_id); - - if (success) { - return "{\"success\": true, \"message\": \"Event deleted successfully\"}"; - } else { - return "{\"success\": false, \"message\": \"Event deletion failed\"}"; - } - }); - - AddTool("self.schedule.get_statistics", - "Obtain schedule statistics information.\n" - "Return:\n" - " JSON object of statistics information", - PropertyList(), - [&schedule_manager](const PropertyList &properties) -> ReturnValue { - int total_events = schedule_manager.GetEventCount(); - - cJSON *json = cJSON_CreateObject(); - cJSON_AddNumberToObject(json, "total_events", total_events); - cJSON_AddBoolToObject(json, "success", true); - - return json; - }); - - // 定时任务工具 - auto &timer_manager = TimerManager::GetInstance(); - - AddTool("self.timer.create_countdown", - "Create a countdown timer.\n" - "Return:\n" - " `name`: Timer name (required)\n" - " `duration_ms`: Duration in milliseconds (required)\n" - " `description`: Description (optional)\n" - "Return:\n" - " Timer ID", - PropertyList({Property("name", kPropertyTypeString), - Property("duration_ms", kPropertyTypeInteger, 1000, 100, 3600000), - Property("description", kPropertyTypeString, "")}), - [&timer_manager](const PropertyList &properties) -> ReturnValue { - auto name = properties["name"].value(); - uint32_t duration_ms = properties["duration_ms"].value(); - auto description = properties["description"].value(); - - std::string timer_id = timer_manager.CreateCountdownTimer(name, duration_ms, description); - - return "{\"success\": true, \"timer_id\": \"" + timer_id + "\", \"message\": \"Countdown timer successfully created\"}"; - }); - - AddTool("self.timer.create_delayed_task", - "Create a task for delaying the execution of MCP tools.\n" - "Parameter:\n" - " `name`: Task name (required)\n" - " `delay_ms`: Delay time in milliseconds (required)\n" - " `mcp_tool_name`: MCP tool name (required)\n" - " `mcp_tool_args`: MCP tool arguments (optional)\n" - " `description`: Description (optional)\n" - "Return:\n" - " Task ID", - PropertyList({Property("name", kPropertyTypeString), - Property("delay_ms", kPropertyTypeInteger, 1000, 100, 3600000), - Property("mcp_tool_name", kPropertyTypeString), - Property("mcp_tool_args", kPropertyTypeString, ""), - Property("description", kPropertyTypeString, "")}), - [&timer_manager](const PropertyList &properties) -> ReturnValue - { - auto name = properties["name"].value(); - uint32_t delay_ms = properties["delay_ms"].value(); - auto mcp_tool_name = properties["mcp_tool_name"].value(); - auto mcp_tool_args = properties["mcp_tool_args"].value(); - auto description = properties["description"].value(); - - std::string task_id = timer_manager.CreateDelayedMcpTask( - name, delay_ms, mcp_tool_name, mcp_tool_args, description); - - return "{\"success\": true, \"task_id\": \"" + task_id + "\", \"message\": \"Delay task created successfully\"}"; - }); - - AddTool("self.timer.start_task", - "Start scheduled tasks.\n" - "Parameter:\n" - " `task_id`: Task ID (required)\n" - "Return:\n" - " Operation result", - PropertyList({Property("task_id", kPropertyTypeString)}), - [&timer_manager](const PropertyList &properties) -> ReturnValue { - auto task_id = properties["task_id"].value(); - - bool success = timer_manager.StartTask(task_id); - - if (success) { - return "{\"success\": true, \"message\": \"Task started successfully\"}"; - } else { - return "{\"success\": false, \"message\": \"Task start failed\"}"; - } - }); - - AddTool("self.timer.stop_task", - "Stop scheduled tasks.\n" - "Parameter:\n" - " `task_id`: Task ID (Required)\n" - "Return:\n" - " Operation result", - PropertyList({Property("task_id", kPropertyTypeString)}), - [&timer_manager](const PropertyList &properties) -> ReturnValue { - auto task_id = properties["task_id"].value(); - - bool success = timer_manager.StopTask(task_id); - - if (success) { - return "{\"success\": true, \"message\": \"Task stopped successfully\"}"; - } else { - return "{\"success\": false, \"message\": \"Task stop failed\"}"; - } - }); - AddTool("self.timer.get_tasks", - "Get all scheduled tasks.\n" - "Return:\n" - " JSON array of task objects", - PropertyList(), - [&timer_manager](const PropertyList &properties) -> ReturnValue { - std::string json_str = timer_manager.ExportToJson(); - return json_str; - }); - - AddTool("self.timer.get_statistics", - "Obtain timer statistics information.\n" - "Return:\n" - " JSON object of statistics information", - PropertyList(), - [&timer_manager](const PropertyList &properties) -> ReturnValue { - int total_tasks = timer_manager.GetTaskCount(); - - cJSON *json = cJSON_CreateObject(); - cJSON_AddNumberToObject(json, "total_tasks", total_tasks); - cJSON_AddBoolToObject(json, "success", true); - - return json; - }); -} - -void McpServer::AddTool(McpTool* tool) { - // Prevent adding duplicate tools - if (std::find_if(tools_.begin(), tools_.end(), [tool](const McpTool* t) { return t->name() == tool->name(); }) != tools_.end()) { - ESP_LOGW(TAG, "Tool %s already added", tool->name().c_str()); - return; - } - - ESP_LOGI(TAG, "Add tool: %s%s", tool->name().c_str(), tool->user_only() ? " [user]" : ""); - tools_.push_back(tool); -} - -void McpServer::AddTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function callback) { - AddTool(new McpTool(name, description, properties, callback)); -} - -void McpServer::AddUserOnlyTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function callback) { - auto tool = new McpTool(name, description, properties, callback); - tool->set_user_only(true); - AddTool(tool); -} - -void McpServer::ParseMessage(const std::string& message) { - cJSON* json = cJSON_Parse(message.c_str()); - if (json == nullptr) { - ESP_LOGE(TAG, "Failed to parse MCP message: %s", message.c_str()); - return; - } - ParseMessage(json); - cJSON_Delete(json); -} - -void McpServer::ParseCapabilities(const cJSON* capabilities) { - auto vision = cJSON_GetObjectItem(capabilities, "vision"); - if (cJSON_IsObject(vision)) { - auto url = cJSON_GetObjectItem(vision, "url"); - auto token = cJSON_GetObjectItem(vision, "token"); - if (cJSON_IsString(url)) { - auto camera = Board::GetInstance().GetCamera(); - if (camera) { - std::string url_str = std::string(url->valuestring); - std::string token_str; - if (cJSON_IsString(token)) { - token_str = std::string(token->valuestring); - } - camera->SetExplainUrl(url_str, token_str); - } - } - } -} - -void McpServer::ParseMessage(const cJSON* json) { - // Check JSONRPC version - auto version = cJSON_GetObjectItem(json, "jsonrpc"); - if (version == nullptr || !cJSON_IsString(version) || strcmp(version->valuestring, "2.0") != 0) { - ESP_LOGE(TAG, "Invalid JSONRPC version: %s", version ? version->valuestring : "null"); - return; - } - - // Check method - auto method = cJSON_GetObjectItem(json, "method"); - if (method == nullptr || !cJSON_IsString(method)) { - ESP_LOGE(TAG, "Missing method"); - return; - } - - auto method_str = std::string(method->valuestring); - if (method_str.find("notifications") == 0) { - return; - } - - // Check params - auto params = cJSON_GetObjectItem(json, "params"); - if (params != nullptr && !cJSON_IsObject(params)) { - ESP_LOGE(TAG, "Invalid params for method: %s", method_str.c_str()); - return; - } - - auto id = cJSON_GetObjectItem(json, "id"); - if (id == nullptr || !cJSON_IsNumber(id)) { - ESP_LOGE(TAG, "Invalid id for method: %s", method_str.c_str()); - return; - } - auto id_int = id->valueint; - - if (method_str == "initialize") { - if (cJSON_IsObject(params)) { - auto capabilities = cJSON_GetObjectItem(params, "capabilities"); - if (cJSON_IsObject(capabilities)) { - ParseCapabilities(capabilities); - } - } - auto app_desc = esp_app_get_description(); - std::string message = "{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{}},\"serverInfo\":{\"name\":\"" BOARD_NAME "\",\"version\":\""; - message += app_desc->version; - message += "\"}}"; - ReplyResult(id_int, message); - } else if (method_str == "tools/list") { - std::string cursor_str = ""; - bool list_user_only_tools = false; - if (params != nullptr) { - auto cursor = cJSON_GetObjectItem(params, "cursor"); - if (cJSON_IsString(cursor)) { - cursor_str = std::string(cursor->valuestring); - } - auto with_user_tools = cJSON_GetObjectItem(params, "withUserTools"); - if (cJSON_IsBool(with_user_tools)) { - list_user_only_tools = with_user_tools->valueint == 1; - } - } - GetToolsList(id_int, cursor_str, list_user_only_tools); - } else if (method_str == "tools/call") { - if (!cJSON_IsObject(params)) { - ESP_LOGE(TAG, "tools/call: Missing params"); - ReplyError(id_int, "Missing params"); - return; - } - auto tool_name = cJSON_GetObjectItem(params, "name"); - if (!cJSON_IsString(tool_name)) { - ESP_LOGE(TAG, "tools/call: Missing name"); - ReplyError(id_int, "Missing name"); - return; - } - auto tool_arguments = cJSON_GetObjectItem(params, "arguments"); - if (tool_arguments != nullptr && !cJSON_IsObject(tool_arguments)) { - ESP_LOGE(TAG, "tools/call: Invalid arguments"); - ReplyError(id_int, "Invalid arguments"); - return; - } - DoToolCall(id_int, std::string(tool_name->valuestring), tool_arguments); - } else { - ESP_LOGE(TAG, "Method not implemented: %s", method_str.c_str()); - ReplyError(id_int, "Method not implemented: " + method_str); - } -} - -void McpServer::ReplyResult(int id, const std::string& result) { - std::string payload = "{\"jsonrpc\":\"2.0\",\"id\":"; - payload += std::to_string(id) + ",\"result\":"; - payload += result; - payload += "}"; - Application::GetInstance().SendMcpMessage(payload); -} - -void McpServer::ReplyError(int id, const std::string& message) { - std::string payload = "{\"jsonrpc\":\"2.0\",\"id\":"; - payload += std::to_string(id); - payload += ",\"error\":{\"message\":\""; - payload += message; - payload += "\"}}"; - Application::GetInstance().SendMcpMessage(payload); -} - -void McpServer::GetToolsList(int id, const std::string& cursor, bool list_user_only_tools) { - const int max_payload_size = 8000; - std::string json = "{\"tools\":["; - - bool found_cursor = cursor.empty(); - auto it = tools_.begin(); - std::string next_cursor = ""; - - while (it != tools_.end()) { - // 如果我们还没有找到起始位置,继续搜索 - if (!found_cursor) { - if ((*it)->name() == cursor) { - found_cursor = true; - } else { - ++it; - continue; - } - } - - if (!list_user_only_tools && (*it)->user_only()) { - ++it; - continue; - } - - // 添加tool前检查大小 - std::string tool_json = (*it)->to_json() + ","; - if (json.length() + tool_json.length() + 30 > max_payload_size) { - // 如果添加这个tool会超出大小限制,设置next_cursor并退出循环 - next_cursor = (*it)->name(); - break; - } - - json += tool_json; - ++it; - } - - if (json.back() == ',') { - json.pop_back(); - } - - if (json.back() == '[' && !tools_.empty()) { - // 如果没有添加任何tool,返回错误 - ESP_LOGE(TAG, "tools/list: Failed to add tool %s because of payload size limit", next_cursor.c_str()); - ReplyError(id, "Failed to add tool " + next_cursor + " because of payload size limit"); - return; - } - - if (next_cursor.empty()) { - json += "]}"; - } else { - json += "],\"nextCursor\":\"" + next_cursor + "\"}"; - } - - ReplyResult(id, json); -} - -void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments) { - auto tool_iter = std::find_if(tools_.begin(), tools_.end(), - [&tool_name](const McpTool* tool) { - return tool->name() == tool_name; - }); - - if (tool_iter == tools_.end()) { - ESP_LOGE(TAG, "tools/call: Unknown tool: %s", tool_name.c_str()); - ReplyError(id, "Unknown tool: " + tool_name); - return; - } - - PropertyList arguments = (*tool_iter)->properties(); - try { - for (auto& argument : arguments) { - bool found = false; - if (cJSON_IsObject(tool_arguments)) { - auto value = cJSON_GetObjectItem(tool_arguments, argument.name().c_str()); - if (argument.type() == kPropertyTypeBoolean && cJSON_IsBool(value)) { - argument.set_value(value->valueint == 1); - found = true; - } else if (argument.type() == kPropertyTypeInteger && cJSON_IsNumber(value)) { - argument.set_value(value->valueint); - found = true; - } else if (argument.type() == kPropertyTypeString && cJSON_IsString(value)) { - argument.set_value(value->valuestring); - found = true; - } - } - - if (!argument.has_default_value() && !found) { - ESP_LOGE(TAG, "tools/call: Missing valid argument: %s", argument.name().c_str()); - ReplyError(id, "Missing valid argument: " + argument.name()); - return; - } - } - } catch (const std::exception& e) { - ESP_LOGE(TAG, "tools/call: %s", e.what()); - ReplyError(id, e.what()); - return; - } - - // Use main thread to call the tool - auto& app = Application::GetInstance(); - app.Schedule([this, id, tool_iter, arguments = std::move(arguments)]() { - try { - ReplyResult(id, (*tool_iter)->Call(arguments)); - } catch (const std::exception& e) { - ESP_LOGE(TAG, "tools/call: %s", e.what()); - ReplyError(id, e.what()); - } - }); -} \ No newline at end of file +/* + * MCP Server Implementation + * Reference: https://modelcontextprotocol.io/specification/2024-11-05 + */ + +#include "mcp_server.h" +#include +#include +#include +#include +#include + +#include "application.h" +#include "display.h" +#include "oled_display.h" +#include "board.h" +#include "settings.h" +#include "lvgl_theme.h" +#include "lvgl_display.h" +#include "boards/common/esp32_music.h" +#include "device_manager.h" +#define TAG "MCP" + +McpServer::McpServer() { +} + +McpServer::~McpServer() { + for (auto tool : tools_) { + delete tool; + } + tools_.clear(); +} + +void McpServer::AddCommonTools() { + // *Important* To speed up the response time, we add the common tools to the beginning of + // the tools list to utilize the prompt cache. + // **重要** 为了提升响应速度,我们把常用的工具放在前面,利用 prompt cache 的特性。 + + // Backup the original tools list and restore it after adding the common tools. + auto original_tools = std::move(tools_); + auto& board = Board::GetInstance(); + + // Do not add custom tools here. + // Custom tools must be added in the board's InitializeTools function. + + AddTool("self.get_device_status", + "Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\n" + "Use this tool for: \n" + "1. Answering questions about current condition (e.g. what is the current volume of the audio speaker?)\n" + "2. As the first step to control the device (e.g. turn up / down the volume of the audio speaker, etc.)", + PropertyList(), + [&board](const PropertyList& properties) -> ReturnValue { + return board.GetDeviceStatusJson(); + }); + + AddTool("self.audio_speaker.set_volume", + "Set the volume of the audio speaker. If the current volume is unknown, you must call `self.get_device_status` tool first and then call this tool.", + PropertyList({ + Property("volume", kPropertyTypeInteger, 0, 100) + }), + [&board](const PropertyList& properties) -> ReturnValue { + auto codec = board.GetAudioCodec(); + codec->SetOutputVolume(properties["volume"].value()); + return true; + }); + + auto backlight = board.GetBacklight(); + if (backlight) { + AddTool("self.screen.set_brightness", + "Set the brightness of the screen.", + PropertyList({ + Property("brightness", kPropertyTypeInteger, 0, 100) + }), + [backlight](const PropertyList& properties) -> ReturnValue { + uint8_t brightness = static_cast(properties["brightness"].value()); + backlight->SetBrightness(brightness, true); + return true; + }); + } + +#ifdef HAVE_LVGL + auto display = board.GetDisplay(); + if (display && display->GetTheme() != nullptr) { + AddTool("self.screen.set_theme", + "Set the theme of the screen. The theme can be `light` or `dark`.", + PropertyList({ + Property("theme", kPropertyTypeString) + }), + [display](const PropertyList& properties) -> ReturnValue { + auto theme_name = properties["theme"].value(); + auto& theme_manager = LvglThemeManager::GetInstance(); + auto theme = theme_manager.GetTheme(theme_name); + if (theme != nullptr) { + display->SetTheme(theme); + return true; + } + return false; + }); + } + + auto camera = board.GetCamera(); + if (camera) { + AddTool("self.camera.take_photo", + "Take a photo and explain it. Use this tool after the user asks you to see something.\n" + "Args:\n" + " `question`: The question that you want to ask about the photo.\n" + "Return:\n" + " A JSON object that provides the photo information.", + PropertyList({ + Property("question", kPropertyTypeString) + }), + [camera](const PropertyList& properties) -> ReturnValue { + // Lower the priority to do the camera capture + TaskPriorityReset priority_reset(1); + + if (!camera->Capture()) { + throw std::runtime_error("Failed to capture photo"); + } + auto question = properties["question"].value(); + return camera->Explain(question); + }); + } + auto music = board.GetMusic(); + if (music) + { + AddTool("self.music.play_song", + "Play the specified song. When users request to play music, this tool will automatically retrieve song details and start streaming.\n" + "parameters:\n" + " `song_name`: The name of the song to be played.\n" + " `artist`: (Optional) The artist name. Highly recommended when playing from playlists to ensure correct song match.\n" + "return:\n" + " Play status information without confirmation, immediately play the song.", + PropertyList({ + Property("song_name", kPropertyTypeString), + Property("artist", kPropertyTypeString, "") + }), + [music](const PropertyList &properties) -> ReturnValue + { + auto song_name = properties["song_name"].value(); + auto artist = properties["artist"].value(); + + // 分别传递歌名和艺术家,不拼接 + if (!music->Download(song_name, artist)) + { + return "{\"success\": false, \"message\": \"Failed to obtain music resources\"}"; + } + auto download_result = music->GetDownloadResult(); + ESP_LOGD(TAG, "Music details result: %s", download_result.c_str()); + return true; + }); + + } + + // Device binding tools + AddTool("self.device.bind", + "Bind this ESP32 device to a user account using a 6-digit binding code.\n" + "Users need to:\n" + "1. Login to the web console (http://47.118.17.234:2233)\n" + "2. Generate a binding code (valid for 5 minutes)\n" + "3. Tell the device: '绑定设备,绑定码123456'\n" + "Parameters:\n" + " `binding_code`: 6-digit binding code from web console\n" + " `device_name`: Optional custom device name (default: ESP32音乐播放器)\n" + "Returns:\n" + " Success message with bound username, or error message.", + PropertyList({ + Property("binding_code", kPropertyTypeString), + Property("device_name", kPropertyTypeString, "") + }), + [](const PropertyList& properties) -> ReturnValue { + auto& device_manager = DeviceManager::GetInstance(); + + std::string binding_code = properties["binding_code"].value(); + std::string device_name = properties["device_name"].value(); + + if (binding_code.empty()) { + return "错误:绑定码不能为空"; + } + + if (binding_code.length() != 6) { + return "错误:绑定码必须是6位数字"; + } + + // Check if device is already bound + if (device_manager.IsDeviceBound()) { + std::string username = device_manager.GetBoundUsername(); + return "设备已绑定到用户: " + username + "\n如需重新绑定,请先解绑。"; + } + + // Attempt to bind + bool success = device_manager.BindDevice(binding_code, device_name); + + if (success) { + std::string username = device_manager.GetBoundUsername(); + return "✅ 设备绑定成功!\n已绑定到用户: " + username; + } else { + return "❌ 绑定失败!请检查:\n" + "1. 绑定码是否正确\n" + "2. 绑定码是否已过期(有效期5分钟)\n" + "3. 网络连接是否正常"; + } + }); + + AddTool("self.device.unbind", + "Unbind this device from the current user account.\n" + "This will remove the device binding and require re-binding to use personalized features.\n" + "Returns:\n" + " Success or error message.", + PropertyList(), + [](const PropertyList& properties) -> ReturnValue { + auto& device_manager = DeviceManager::GetInstance(); + + if (!device_manager.IsDeviceBound()) { + return "设备未绑定,无需解绑"; + } + + std::string username = device_manager.GetBoundUsername(); + bool success = device_manager.ClearDeviceToken(); + + if (success) { + return "✅ 设备已解绑\n之前绑定的用户: " + username; + } else { + return "❌ 解绑失败,请稍后重试"; + } + }); + + AddTool("self.device.status", + "Get the current device binding status and information.\n" + "Returns:\n" + " Device binding status, MAC address, bound username, etc.", + PropertyList(), + [](const PropertyList& properties) -> ReturnValue { + auto& device_manager = DeviceManager::GetInstance(); + + std::string result = "📱 设备信息:\n\n"; + result += "MAC地址: " + device_manager.GetMACAddress() + "\n"; + + if (device_manager.IsDeviceBound()) { + result += "绑定状态: ✅ 已绑定\n"; + result += "绑定用户: " + device_manager.GetBoundUsername() + "\n"; + + // Try to verify with server + bool verified = device_manager.VerifyDevice(); + result += "服务器验证: " + std::string(verified ? "✅ 通过" : "❌ 失败") + "\n"; + } else { + result += "绑定状态: ❌ 未绑定\n"; + result += "\n💡 提示: 使用 '绑定设备' 功能来绑定账号"; + } + + return result; + }); + + // 歌单相关工具 + AddTool("self.music.favorite_list", + "获取我的'我喜欢'歌单中的歌曲列表。\n" + "Returns:\n" + " 歌曲列表JSON数组,每首歌包含:\n" + " - title: 歌曲名\n" + " - artist: 艺术家名\n" + " - duration: 时长\n" + " **播放选项**:\n" + " 1. 播放单首歌:使用 play_song 工具,同时传递 song_name 和 artist 参数\n" + " 2. 播放整个歌单:使用 play_playlist 工具,传递完整的歌曲JSON数组", + PropertyList(), + [](const PropertyList& properties) -> ReturnValue { + auto& device_manager = DeviceManager::GetInstance(); + if (!device_manager.IsDeviceBound()) { + return "错误:设备未绑定,请先绑定账号"; + } + + std::string result = device_manager.GetFavorites(); + if (result.empty()) { + return "获取歌单失败或歌单为空"; + } + return result; + }); + + AddTool("self.music.my_playlists", + "获取我创建的歌单列表。\n" + "Returns:\n" + " 歌单列表JSON数组,每个歌单包含 songs 数组,每首歌包含:\n" + " - title: 歌曲名\n" + " - artist: 艺术家名\n" + " - duration: 时长\n" + " **重要**: 播放歌单中的歌曲时,请同时传递 song_name 和 artist 参数给 play_song 工具。", + PropertyList(), + [](const PropertyList& properties) -> ReturnValue { + auto& device_manager = DeviceManager::GetInstance(); + if (!device_manager.IsDeviceBound()) { + return "错误:设备未绑定,请先绑定账号"; + } + + std::string result = device_manager.GetUserPlaylists(); + if (result.empty()) { + return "获取歌单失败或没有歌单"; + } + return result; + }); + + // 播放整个歌单工具 + AddTool("self.music.play_playlist", + "播放整个歌单,连续播放歌单中的所有歌曲。\n" + "parameters:\n" + " `songs`: JSON格式的歌曲数组,每首歌必须包含 title 和 artist 字段\n" + "return:\n" + " 开始播放歌单的状态信息", + PropertyList({ + Property("songs", kPropertyTypeString) + }), + [music](const PropertyList &properties) -> ReturnValue + { + auto songs_json = properties["songs"].value(); + + // 解析歌曲JSON数组 + cJSON* json = cJSON_Parse(songs_json.c_str()); + if (!json || !cJSON_IsArray(json)) { + if (json) cJSON_Delete(json); + return "{\"success\": false, \"message\": \"Invalid songs JSON format\"}"; + } + + std::vector playlist; + int array_size = cJSON_GetArraySize(json); + + for (int i = 0; i < array_size; i++) { + cJSON* song_item = cJSON_GetArrayItem(json, i); + if (!song_item) continue; + + cJSON* title = cJSON_GetObjectItem(song_item, "title"); + cJSON* artist = cJSON_GetObjectItem(song_item, "artist"); + + if (cJSON_IsString(title) && cJSON_IsString(artist)) { + playlist.emplace_back(title->valuestring, artist->valuestring); + } + } + + cJSON_Delete(json); + + if (playlist.empty()) { + return "{\"success\": false, \"message\": \"No valid songs found in playlist\"}"; + } + + // 开始播放歌单 + if (music->PlayPlaylist(playlist)) { + return "{\"success\": true, \"message\": \"Started playing playlist with " + + std::to_string(playlist.size()) + " songs\"}"; + } else { + return "{\"success\": false, \"message\": \"Failed to start playlist\"}"; + } + }); + + // 播放队列控制工具 + AddTool("self.music.next_song", + "播放下一首歌曲(仅在播放歌单时有效)。\n" + "return:\n" + " 切换到下一首歌的状态信息", + PropertyList(), + [music](const PropertyList &properties) -> ReturnValue + { + if (!music->IsPlaylistMode()) { + return "{\"success\": false, \"message\": \"Not in playlist mode\"}"; + } + + if (music->NextSong()) { + return "{\"success\": true, \"message\": \"Switched to next song\"}"; + } else { + return "{\"success\": false, \"message\": \"Already at last song or playlist ended\"}"; + } + }); + + AddTool("self.music.previous_song", + "播放上一首歌曲(仅在播放歌单时有效)。\n" + "return:\n" + " 切换到上一首歌的状态信息", + PropertyList(), + [music](const PropertyList &properties) -> ReturnValue + { + if (!music->IsPlaylistMode()) { + return "{\"success\": false, \"message\": \"Not in playlist mode\"}"; + } + + if (music->PreviousSong()) { + return "{\"success\": true, \"message\": \"Switched to previous song\"}"; + } else { + return "{\"success\": false, \"message\": \"Already at first song\"}"; + } + }); + + AddTool("self.music.stop_playlist", + "停止播放歌单。\n" + "return:\n" + " 停止播放歌单的状态信息", + PropertyList(), + [music](const PropertyList &properties) -> ReturnValue + { + music->StopPlaylist(); + return "{\"success\": true, \"message\": \"Playlist stopped\"}"; + }); + + // 闹钟功能工具 + AddTool("self.alarm.add", + "Set a new alarm with music playback. When users request to set an alarm, this tool will create the alarm with specified parameters.\n" + "🎵 Music Feature: If no specific music is provided, the system will randomly select from 40+ popular songs including Chinese pop, classics, and international hits.\n" + "Parameters:\n" + " `hour`: Hour of the alarm (0-23)\n" + " `minute`: Minute of the alarm (0-59)\n" + " `repeat_mode`: Repeat mode (0=once, 1=daily, 2=weekdays, 3=weekends)\n" + " `label`: Optional label/description for the alarm\n" + " `music_name`: Optional specific music to play (leave empty for random selection)\n" + "Returns:\n" + " Alarm ID if successful, error message if failed.", + PropertyList({ + Property("hour", kPropertyTypeInteger, 0, 23), + Property("minute", kPropertyTypeInteger, 0, 59), + Property("repeat_mode", kPropertyTypeInteger, 0, 0, 3), + Property("label", kPropertyTypeString, ""), + Property("music_name", kPropertyTypeString, "") + }), + [this](const PropertyList& properties) -> ReturnValue { + auto& alarm_manager = AlarmManager::GetInstance(); + + int hour = properties["hour"].value(); + int minute = properties["minute"].value(); + AlarmRepeatMode repeat_mode = (AlarmRepeatMode)properties["repeat_mode"].value(); + std::string label = properties["label"].value(); + std::string music_name = properties["music_name"].value(); + + int alarm_id = alarm_manager.AddAlarm(hour, minute, repeat_mode, label, music_name); + + if (alarm_id > 0) { + std::string result = "已设置闹钟: " + AlarmManager::FormatTime(hour, minute); + if (!label.empty()) { + result += " - " + label; + } + if (!music_name.empty()) { + result += " (音乐: " + music_name + ")"; + } + + // 显示重复模式 + switch (repeat_mode) { + case kAlarmOnce: result += " (一次性)"; break; + case kAlarmDaily: result += " (每日)"; break; + case kAlarmWeekdays: result += " (工作日)"; break; + case kAlarmWeekends: result += " (周末)"; break; + case kAlarmCustom: result += " (自定义)"; break; + } + + return result; + } else { + return "设置闹钟失败,请检查时间格式"; + } + }); + + AddTool("self.alarm.list", + "List all alarms and show their status.\n" + "Returns:\n" + " List of all alarms with their details.", + PropertyList(), + [this](const PropertyList& properties) -> ReturnValue { + auto& alarm_manager = AlarmManager::GetInstance(); + auto alarms = alarm_manager.GetAllAlarms(); + + if (alarms.empty()) { + return "没有设置任何闹钟"; + } + + std::string result = "闹钟列表:\n"; + for (const auto& alarm : alarms) { + result += "ID " + std::to_string(alarm.id) + ": "; + result += AlarmManager::FormatAlarmTime(alarm); + + if (!alarm.label.empty()) { + result += " - " + alarm.label; + } + + switch (alarm.status) { + case kAlarmEnabled: result += " [启用]"; break; + case kAlarmDisabled: result += " [禁用]"; break; + case kAlarmTriggered: result += " [正在响铃]"; break; + case kAlarmSnoozed: result += " [贪睡中]"; break; + } + + if (!alarm.music_name.empty()) { + result += " (音乐: " + alarm.music_name + ")"; + } + result += "\n"; + } + + result += "\n" + alarm_manager.GetNextAlarmInfo(); + return result; + }); + + AddTool("self.alarm.remove", + "Remove/delete an alarm by ID.\n" + "Parameters:\n" + " `alarm_id`: ID of the alarm to remove\n" + "Returns:\n" + " Success or error message.", + PropertyList({ + Property("alarm_id", kPropertyTypeInteger) + }), + [this](const PropertyList& properties) -> ReturnValue { + auto& alarm_manager = AlarmManager::GetInstance(); + int alarm_id = properties["alarm_id"].value(); + + if (alarm_manager.RemoveAlarm(alarm_id)) { + return "已删除闹钟 ID " + std::to_string(alarm_id); + } else { + return "未找到闹钟 ID " + std::to_string(alarm_id); + } + }); + + AddTool("self.alarm.toggle", + "Enable or disable an alarm by ID.\n" + "Parameters:\n" + " `alarm_id`: ID of the alarm to toggle\n" + " `enabled`: True to enable, false to disable\n" + "Returns:\n" + " Success or error message.", + PropertyList({ + Property("alarm_id", kPropertyTypeInteger), + Property("enabled", kPropertyTypeBoolean, true) + }), + [this](const PropertyList& properties) -> ReturnValue { + auto& alarm_manager = AlarmManager::GetInstance(); + int alarm_id = properties["alarm_id"].value(); + bool enabled = properties["enabled"].value(); + + if (alarm_manager.EnableAlarm(alarm_id, enabled)) { + return "闹钟 ID " + std::to_string(alarm_id) + (enabled ? " 已启用" : " 已禁用"); + } else { + return "未找到闹钟 ID " + std::to_string(alarm_id); + } + }); + + AddTool("self.alarm.snooze", + "Snooze the currently active alarm.\n" + "Parameters:\n" + " `alarm_id`: ID of the alarm to snooze (optional, will snooze first active alarm if not specified)\n" + "Returns:\n" + " Success or error message.", + PropertyList({ + Property("alarm_id", kPropertyTypeInteger, -1) + }), + [this](const PropertyList& properties) -> ReturnValue { + auto& alarm_manager = AlarmManager::GetInstance(); + int alarm_id = properties["alarm_id"].value(); + + if (alarm_id == -1) { + // 贪睡第一个活动的闹钟 + auto active_alarms = alarm_manager.GetActiveAlarms(); + if (!active_alarms.empty()) { + alarm_id = active_alarms[0].id; + } else { + return "没有正在响铃的闹钟"; + } + } + + if (alarm_manager.SnoozeAlarm(alarm_id)) { + return "闹钟已贪睡5分钟"; + } else { + return "无法贪睡闹钟,可能已达到最大贪睡次数"; + } + }); + + AddTool("self.alarm.stop", + "Stop the currently active alarm.\n" + "Parameters:\n" + " `alarm_id`: ID of the alarm to stop (optional, will stop first active alarm if not specified)\n" + "Returns:\n" + " Success or error message.", + PropertyList({ + Property("alarm_id", kPropertyTypeInteger, -1) + }), + [this](const PropertyList& properties) -> ReturnValue { + auto& alarm_manager = AlarmManager::GetInstance(); + int alarm_id = properties["alarm_id"].value(); + + if (alarm_id == -1) { + // 停止第一个活动的闹钟 + auto active_alarms = alarm_manager.GetActiveAlarms(); + if (!active_alarms.empty()) { + alarm_id = active_alarms[0].id; + } else { + return "没有正在响铃的闹钟"; + } + } + + if (alarm_manager.StopAlarm(alarm_id)) { + return "闹钟已关闭"; + } else { + return "未找到活动的闹钟"; + } + }); + + AddTool("self.alarm.music_list", + "Show the list of default alarm music. Users can reference this list when setting custom alarm music.\n" + "Returns:\n" + " List of available alarm music songs.", + PropertyList(), + [this](const PropertyList& properties) -> ReturnValue { + auto& app = Application::GetInstance(); + auto music_list = app.GetDefaultAlarmMusicList(); + + if (music_list.empty()) { + return "暂无可用的闹钟音乐"; + } + + std::string result = "🎵 可用的闹钟音乐列表:\n\n"; + result += "📝 使用说明: 设置闹钟时可以指定以下任意一首歌曲作为闹钟铃声\n"; + result += "🎲 如果不指定音乐,系统会随机播放其中一首\n\n"; + + // 分类显示音乐 + result += "🇨🇳 中文流行:\n"; + std::vector chinese_songs = { + "晴天", "七里香", "青花瓷", "稻香", "彩虹", "告白气球", "说好不哭", + "夜曲", "花海", "简单爱", "听妈妈的话", "东风破", "菊花台", + "起风了", "红豆", "好久不见", "匆匆那年", "老男孩", "那些年", + "小幸运", "成都", "南山南", "演员", "体面", "盗将行", "大鱼" + }; + + for (size_t i = 0; i < chinese_songs.size() && i < 15; i++) { + result += " • " + chinese_songs[i] + "\n"; + } + + result += "\n🎼 经典怀旧:\n"; + std::vector classic_songs = { + "新不了情", "月亮代表我的心", "甜蜜蜜", "我只在乎你", + "友谊之光", "童年", "海阔天空", "光辉岁月", "真的爱你", "喜欢你" + }; + + for (const auto& song : classic_songs) { + result += " • " + song + "\n"; + } + + result += "\n🌍 国际流行:\n"; + std::vector international_songs = { + "closer", "sugar", "shape of you", "despacito", + "perfect", "happier", "someone like you" + }; + + for (const auto& song : international_songs) { + result += " • " + song + "\n"; + } + + result += "\n💡 示例: \"明天早上7点播放青花瓷叫我起床\""; + return result; + }); + + AddTool("self.alarm.test_music_ui", + "Test the new vinyl record music UI interface. This tool will simulate a music playback to showcase the new rotating vinyl record interface.\n" + "Parameters:\n" + " `song_name`: Name of the song to display (optional)\n" + " `duration`: Test duration in seconds (default 10 seconds)\n" + "Returns:\n" + " Status message about the UI test.", + PropertyList({ + Property("song_name", kPropertyTypeString, "晴天"), + Property("duration", kPropertyTypeInteger, 10, 5, 60) + }), + [this](const PropertyList& properties) -> ReturnValue { + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + + if (!display) { + return "显示器不可用,无法测试音乐界面"; + } + + std::string song_name = properties["song_name"].value(); + int duration = properties["duration"].value(); + + if (song_name.empty()) { + song_name = "UI测试 - 旋转唱片界面"; + } + + // 显示音乐界面 + display->SetMusicProgress(song_name.c_str(), 0, duration, 0.0f); + + return "🎵 已启动音乐界面测试!\n" + "✨ 特色功能展示:\n" + " 🎵 旋转唱片 - 黑胶唱片持续旋转\n" + " 📡 唱片臂 - 自动放下/收起动画\n" + " 📊 进度条 - 实时显示播放进度\n" + " ⏰ 时间显示 - 当前时间/总时长\n" + " 🌊 音波装饰 - 动态音乐波形\n" + "测试时长: " + std::to_string(duration) + " 秒\n" + "歌曲: " + song_name; + }); +#endif + + // Restore the original tools list to the end of the tools list + tools_.insert(tools_.end(), original_tools.begin(), original_tools.end()); +} + +void McpServer::AddUserOnlyTools() { + // System tools + AddUserOnlyTool("self.get_system_info", + "Get the system information", + PropertyList(), + [this](const PropertyList& properties) -> ReturnValue { + auto& board = Board::GetInstance(); + return board.GetSystemInfoJson(); + }); + + AddUserOnlyTool("self.reboot", "Reboot the system", + PropertyList(), + [this](const PropertyList& properties) -> ReturnValue { + auto& app = Application::GetInstance(); + app.Schedule([&app]() { + ESP_LOGW(TAG, "User requested reboot"); + vTaskDelay(pdMS_TO_TICKS(1000)); + + app.Reboot(); + }); + return true; + }); + + // Firmware upgrade + AddUserOnlyTool("self.upgrade_firmware", "Upgrade firmware from a specific URL. This will download and install the firmware, then reboot the device.", + PropertyList({ + Property("url", kPropertyTypeString, "The URL of the firmware binary file to download and install") + }), + [this](const PropertyList& properties) -> ReturnValue { + auto url = properties["url"].value(); + ESP_LOGI(TAG, "User requested firmware upgrade from URL: %s", url.c_str()); + + auto& app = Application::GetInstance(); + app.Schedule([url, &app]() { + auto ota = std::make_unique(); + + bool success = app.UpgradeFirmware(*ota, url); + if (!success) { + ESP_LOGE(TAG, "Firmware upgrade failed"); + } + }); + + return true; + }); + + // Display control +#ifdef HAVE_LVGL + auto display = dynamic_cast(Board::GetInstance().GetDisplay()); + if (display) { + AddUserOnlyTool("self.screen.get_info", "Information about the screen, including width, height, etc.", + PropertyList(), + [display](const PropertyList& properties) -> ReturnValue { + cJSON *json = cJSON_CreateObject(); + cJSON_AddNumberToObject(json, "width", display->width()); + cJSON_AddNumberToObject(json, "height", display->height()); + if (dynamic_cast(display)) { + cJSON_AddBoolToObject(json, "monochrome", true); + } else { + cJSON_AddBoolToObject(json, "monochrome", false); + } + return json; + }); + +#if CONFIG_LV_USE_SNAPSHOT + AddUserOnlyTool("self.screen.snapshot", "Snapshot the screen and upload it to a specific URL", + PropertyList({ + Property("url", kPropertyTypeString), + Property("quality", kPropertyTypeInteger, 80, 1, 100) + }), + [display](const PropertyList& properties) -> ReturnValue { + auto url = properties["url"].value(); + auto quality = properties["quality"].value(); + + std::string jpeg_data; + if (!display->SnapshotToJpeg(jpeg_data, quality)) { + throw std::runtime_error("Failed to snapshot screen"); + } + + ESP_LOGI(TAG, "Upload snapshot %u bytes to %s", jpeg_data.size(), url.c_str()); + + // 构造multipart/form-data请求体 + std::string boundary = "----ESP32_SCREEN_SNAPSHOT_BOUNDARY"; + + auto http = Board::GetInstance().GetNetwork()->CreateHttp(3); + http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary); + if (!http->Open("POST", url)) { + throw std::runtime_error("Failed to open URL: " + url); + } + { + // 文件字段头部 + std::string file_header; + file_header += "--" + boundary + "\r\n"; + file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"screenshot.jpg\"\r\n"; + file_header += "Content-Type: image/jpeg\r\n"; + file_header += "\r\n"; + http->Write(file_header.c_str(), file_header.size()); + } + + // JPEG数据 + http->Write((const char*)jpeg_data.data(), jpeg_data.size()); + + { + // multipart尾部 + std::string multipart_footer; + multipart_footer += "\r\n--" + boundary + "--\r\n"; + http->Write(multipart_footer.c_str(), multipart_footer.size()); + } + http->Write("", 0); + + if (http->GetStatusCode() != 200) { + throw std::runtime_error("Unexpected status code: " + std::to_string(http->GetStatusCode())); + } + std::string result = http->ReadAll(); + http->Close(); + ESP_LOGI(TAG, "Snapshot screen result: %s", result.c_str()); + return true; + }); + + AddUserOnlyTool("self.screen.preview_image", "Preview an image on the screen", + PropertyList({ + Property("url", kPropertyTypeString) + }), + [display](const PropertyList& properties) -> ReturnValue { + auto url = properties["url"].value(); + auto http = Board::GetInstance().GetNetwork()->CreateHttp(3); + + if (!http->Open("GET", url)) { + throw std::runtime_error("Failed to open URL: " + url); + } + int status_code = http->GetStatusCode(); + if (status_code != 200) { + throw std::runtime_error("Unexpected status code: " + std::to_string(status_code)); + } + + size_t content_length = http->GetBodyLength(); + char* data = (char*)heap_caps_malloc(content_length, MALLOC_CAP_8BIT); + if (data == nullptr) { + throw std::runtime_error("Failed to allocate memory for image: " + url); + } + size_t total_read = 0; + while (total_read < content_length) { + int ret = http->Read(data + total_read, content_length - total_read); + if (ret < 0) { + heap_caps_free(data); + throw std::runtime_error("Failed to download image: " + url); + } + if (ret == 0) { + break; + } + total_read += ret; + } + http->Close(); + + auto image = std::make_unique(data, content_length); + display->SetPreviewImage(std::move(image)); + return true; + }); +#endif // CONFIG_LV_USE_SNAPSHOT + } +#endif // HAVE_LVGL + + // Assets download url + auto& assets = Assets::GetInstance(); + if (assets.partition_valid()) { + AddUserOnlyTool("self.assets.set_download_url", "Set the download url for the assets", + PropertyList({ + Property("url", kPropertyTypeString) + }), + [](const PropertyList& properties) -> ReturnValue { + auto url = properties["url"].value(); + Settings settings("assets", true); + settings.SetString("download_url", url); + return true; + }); + } +} + +void McpServer::AddTool(McpTool* tool) { + // Prevent adding duplicate tools + if (std::find_if(tools_.begin(), tools_.end(), [tool](const McpTool* t) { return t->name() == tool->name(); }) != tools_.end()) { + ESP_LOGW(TAG, "Tool %s already added", tool->name().c_str()); + return; + } + + ESP_LOGI(TAG, "Add tool: %s%s", tool->name().c_str(), tool->user_only() ? " [user]" : ""); + tools_.push_back(tool); +} + +void McpServer::AddTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function callback) { + AddTool(new McpTool(name, description, properties, callback)); +} + +void McpServer::AddUserOnlyTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function callback) { + auto tool = new McpTool(name, description, properties, callback); + tool->set_user_only(true); + AddTool(tool); +} + +void McpServer::ParseMessage(const std::string& message) { + cJSON* json = cJSON_Parse(message.c_str()); + if (json == nullptr) { + ESP_LOGE(TAG, "Failed to parse MCP message: %s", message.c_str()); + return; + } + ParseMessage(json); + cJSON_Delete(json); +} + +void McpServer::ParseCapabilities(const cJSON* capabilities) { + auto vision = cJSON_GetObjectItem(capabilities, "vision"); + if (cJSON_IsObject(vision)) { + auto url = cJSON_GetObjectItem(vision, "url"); + auto token = cJSON_GetObjectItem(vision, "token"); + if (cJSON_IsString(url)) { + auto camera = Board::GetInstance().GetCamera(); + if (camera) { + std::string url_str = std::string(url->valuestring); + std::string token_str; + if (cJSON_IsString(token)) { + token_str = std::string(token->valuestring); + } + camera->SetExplainUrl(url_str, token_str); + } + } + } +} + +void McpServer::ParseMessage(const cJSON* json) { + // Check JSONRPC version + auto version = cJSON_GetObjectItem(json, "jsonrpc"); + if (version == nullptr || !cJSON_IsString(version) || strcmp(version->valuestring, "2.0") != 0) { + ESP_LOGE(TAG, "Invalid JSONRPC version: %s", version ? version->valuestring : "null"); + return; + } + + // Check method + auto method = cJSON_GetObjectItem(json, "method"); + if (method == nullptr || !cJSON_IsString(method)) { + ESP_LOGE(TAG, "Missing method"); + return; + } + + auto method_str = std::string(method->valuestring); + if (method_str.find("notifications") == 0) { + return; + } + + // Check params + auto params = cJSON_GetObjectItem(json, "params"); + if (params != nullptr && !cJSON_IsObject(params)) { + ESP_LOGE(TAG, "Invalid params for method: %s", method_str.c_str()); + return; + } + + auto id = cJSON_GetObjectItem(json, "id"); + if (id == nullptr || !cJSON_IsNumber(id)) { + ESP_LOGE(TAG, "Invalid id for method: %s", method_str.c_str()); + return; + } + auto id_int = id->valueint; + + if (method_str == "initialize") { + if (cJSON_IsObject(params)) { + auto capabilities = cJSON_GetObjectItem(params, "capabilities"); + if (cJSON_IsObject(capabilities)) { + ParseCapabilities(capabilities); + } + } + auto app_desc = esp_app_get_description(); + std::string message = "{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{}},\"serverInfo\":{\"name\":\"" BOARD_NAME "\",\"version\":\""; + message += app_desc->version; + message += "\"}}"; + ReplyResult(id_int, message); + } else if (method_str == "tools/list") { + std::string cursor_str = ""; + bool list_user_only_tools = false; + if (params != nullptr) { + auto cursor = cJSON_GetObjectItem(params, "cursor"); + if (cJSON_IsString(cursor)) { + cursor_str = std::string(cursor->valuestring); + } + auto with_user_tools = cJSON_GetObjectItem(params, "withUserTools"); + if (cJSON_IsBool(with_user_tools)) { + list_user_only_tools = with_user_tools->valueint == 1; + } + } + GetToolsList(id_int, cursor_str, list_user_only_tools); + } else if (method_str == "tools/call") { + if (!cJSON_IsObject(params)) { + ESP_LOGE(TAG, "tools/call: Missing params"); + ReplyError(id_int, "Missing params"); + return; + } + auto tool_name = cJSON_GetObjectItem(params, "name"); + if (!cJSON_IsString(tool_name)) { + ESP_LOGE(TAG, "tools/call: Missing name"); + ReplyError(id_int, "Missing name"); + return; + } + auto tool_arguments = cJSON_GetObjectItem(params, "arguments"); + if (tool_arguments != nullptr && !cJSON_IsObject(tool_arguments)) { + ESP_LOGE(TAG, "tools/call: Invalid arguments"); + ReplyError(id_int, "Invalid arguments"); + return; + } + DoToolCall(id_int, std::string(tool_name->valuestring), tool_arguments); + } else { + ESP_LOGE(TAG, "Method not implemented: %s", method_str.c_str()); + ReplyError(id_int, "Method not implemented: " + method_str); + } +} + +void McpServer::ReplyResult(int id, const std::string& result) { + std::string payload = "{\"jsonrpc\":\"2.0\",\"id\":"; + payload += std::to_string(id) + ",\"result\":"; + payload += result; + payload += "}"; + Application::GetInstance().SendMcpMessage(payload); +} + +void McpServer::ReplyError(int id, const std::string& message) { + std::string payload = "{\"jsonrpc\":\"2.0\",\"id\":"; + payload += std::to_string(id); + payload += ",\"error\":{\"message\":\""; + payload += message; + payload += "\"}}"; + Application::GetInstance().SendMcpMessage(payload); +} + +void McpServer::GetToolsList(int id, const std::string& cursor, bool list_user_only_tools) { + const int max_payload_size = 8000; + std::string json = "{\"tools\":["; + + bool found_cursor = cursor.empty(); + auto it = tools_.begin(); + std::string next_cursor = ""; + + while (it != tools_.end()) { + // 如果我们还没有找到起始位置,继续搜索 + if (!found_cursor) { + if ((*it)->name() == cursor) { + found_cursor = true; + } else { + ++it; + continue; + } + } + + if (!list_user_only_tools && (*it)->user_only()) { + ++it; + continue; + } + + // 添加tool前检查大小 + std::string tool_json = (*it)->to_json() + ","; + if (json.length() + tool_json.length() + 30 > max_payload_size) { + // 如果添加这个tool会超出大小限制,设置next_cursor并退出循环 + next_cursor = (*it)->name(); + break; + } + + json += tool_json; + ++it; + } + + if (json.back() == ',') { + json.pop_back(); + } + + if (json.back() == '[' && !tools_.empty()) { + // 如果没有添加任何tool,返回错误 + ESP_LOGE(TAG, "tools/list: Failed to add tool %s because of payload size limit", next_cursor.c_str()); + ReplyError(id, "Failed to add tool " + next_cursor + " because of payload size limit"); + return; + } + + if (next_cursor.empty()) { + json += "]}"; + } else { + json += "],\"nextCursor\":\"" + next_cursor + "\"}"; + } + + ReplyResult(id, json); +} + +void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments) { + auto tool_iter = std::find_if(tools_.begin(), tools_.end(), + [&tool_name](const McpTool* tool) { + return tool->name() == tool_name; + }); + + if (tool_iter == tools_.end()) { + ESP_LOGE(TAG, "tools/call: Unknown tool: %s", tool_name.c_str()); + ReplyError(id, "Unknown tool: " + tool_name); + return; + } + + PropertyList arguments = (*tool_iter)->properties(); + try { + for (auto& argument : arguments) { + bool found = false; + if (cJSON_IsObject(tool_arguments)) { + auto value = cJSON_GetObjectItem(tool_arguments, argument.name().c_str()); + if (argument.type() == kPropertyTypeBoolean && cJSON_IsBool(value)) { + argument.set_value(value->valueint == 1); + found = true; + } else if (argument.type() == kPropertyTypeInteger && cJSON_IsNumber(value)) { + argument.set_value(value->valueint); + found = true; + } else if (argument.type() == kPropertyTypeString && cJSON_IsString(value)) { + argument.set_value(value->valuestring); + found = true; + } + } + + if (!argument.has_default_value() && !found) { + ESP_LOGE(TAG, "tools/call: Missing valid argument: %s", argument.name().c_str()); + ReplyError(id, "Missing valid argument: " + argument.name()); + return; + } + } + } catch (const std::exception& e) { + ESP_LOGE(TAG, "tools/call: %s", e.what()); + ReplyError(id, e.what()); + return; + } + + // Use main thread to call the tool + auto& app = Application::GetInstance(); + app.Schedule([this, id, tool_iter, arguments = std::move(arguments)]() { + try { + ReplyResult(id, (*tool_iter)->Call(arguments)); + } catch (const std::exception& e) { + ESP_LOGE(TAG, "tools/call: %s", e.what()); + ReplyError(id, e.what()); + } + }); +} diff --git a/main/mcp_server.h b/main/mcp_server.h index dacdd55..9668548 100644 --- a/main/mcp_server.h +++ b/main/mcp_server.h @@ -1,344 +1,344 @@ -#ifndef MCP_SERVER_H -#define MCP_SERVER_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -class ImageContent { -private: - std::string encoded_data_; - std::string mime_type_; - - static std::string Base64Encode(const std::string& data) { - size_t dlen = 0, olen = 0; - mbedtls_base64_encode((unsigned char*)nullptr, 0, &dlen, (const unsigned char*)data.data(), data.size()); - std::string result(dlen, 0); - mbedtls_base64_encode((unsigned char*)result.data(), result.size(), &olen, (const unsigned char*)data.data(), data.size()); - return result; - } - -public: - ImageContent(const std::string& mime_type, const std::string& data) { - mime_type_ = mime_type; - // base64 encode data - encoded_data_ = Base64Encode(data); - } - - std::string to_json() const { - cJSON *json = cJSON_CreateObject(); - cJSON_AddStringToObject(json, "type", "image"); - cJSON_AddStringToObject(json, "mimeType", mime_type_.c_str()); - cJSON_AddStringToObject(json, "data", encoded_data_.c_str()); - char* json_str = cJSON_PrintUnformatted(json); - std::string result(json_str); - cJSON_free(json_str); - cJSON_Delete(json); - return result; - } -}; - -// 添加类型别名 -using ReturnValue = std::variant; - -enum PropertyType { - kPropertyTypeBoolean, - kPropertyTypeInteger, - kPropertyTypeString -}; - -class Property { -private: - std::string name_; - PropertyType type_; - std::variant value_; - bool has_default_value_; - std::optional min_value_; // 新增:整数最小值 - std::optional max_value_; // 新增:整数最大值 - -public: - // Required field constructor - Property(const std::string& name, PropertyType type) - : name_(name), type_(type), has_default_value_(false) {} - - // Optional field constructor with default value - template - Property(const std::string& name, PropertyType type, const T& default_value) - : name_(name), type_(type), has_default_value_(true) { - value_ = default_value; - } - - Property(const std::string& name, PropertyType type, int min_value, int max_value) - : name_(name), type_(type), has_default_value_(false), min_value_(min_value), max_value_(max_value) { - if (type != kPropertyTypeInteger) { - throw std::invalid_argument("Range limits only apply to integer properties"); - } - } - - Property(const std::string& name, PropertyType type, int default_value, int min_value, int max_value) - : name_(name), type_(type), has_default_value_(true), min_value_(min_value), max_value_(max_value) { - if (type != kPropertyTypeInteger) { - throw std::invalid_argument("Range limits only apply to integer properties"); - } - if (default_value < min_value || default_value > max_value) { - throw std::invalid_argument("Default value must be within the specified range"); - } - value_ = default_value; - } - - inline const std::string& name() const { return name_; } - inline PropertyType type() const { return type_; } - inline bool has_default_value() const { return has_default_value_; } - inline bool has_range() const { return min_value_.has_value() && max_value_.has_value(); } - inline int min_value() const { return min_value_.value_or(0); } - inline int max_value() const { return max_value_.value_or(0); } - - template - inline T value() const { - return std::get(value_); - } - - template - inline void set_value(const T& value) { - // 添加对设置的整数值进行范围检查 - if constexpr (std::is_same_v) { - if (min_value_.has_value() && value < min_value_.value()) { - throw std::invalid_argument("Value is below minimum allowed: " + std::to_string(min_value_.value())); - } - if (max_value_.has_value() && value > max_value_.value()) { - throw std::invalid_argument("Value exceeds maximum allowed: " + std::to_string(max_value_.value())); - } - } - value_ = value; - } - - std::string to_json() const { - cJSON *json = cJSON_CreateObject(); - - if (type_ == kPropertyTypeBoolean) { - cJSON_AddStringToObject(json, "type", "boolean"); - if (has_default_value_) { - cJSON_AddBoolToObject(json, "default", value()); - } - } else if (type_ == kPropertyTypeInteger) { - cJSON_AddStringToObject(json, "type", "integer"); - if (has_default_value_) { - cJSON_AddNumberToObject(json, "default", value()); - } - if (min_value_.has_value()) { - cJSON_AddNumberToObject(json, "minimum", min_value_.value()); - } - if (max_value_.has_value()) { - cJSON_AddNumberToObject(json, "maximum", max_value_.value()); - } - } else if (type_ == kPropertyTypeString) { - cJSON_AddStringToObject(json, "type", "string"); - if (has_default_value_) { - cJSON_AddStringToObject(json, "default", value().c_str()); - } - } - - char *json_str = cJSON_PrintUnformatted(json); - std::string result(json_str); - cJSON_free(json_str); - cJSON_Delete(json); - - return result; - } -}; - -class PropertyList { -private: - std::vector properties_; - -public: - PropertyList() = default; - PropertyList(const std::vector& properties) : properties_(properties) {} - void AddProperty(const Property& property) { - properties_.push_back(property); - } - - const Property& operator[](const std::string& name) const { - for (const auto& property : properties_) { - if (property.name() == name) { - return property; - } - } - throw std::runtime_error("Property not found: " + name); - } - - auto begin() { return properties_.begin(); } - auto end() { return properties_.end(); } - - std::vector GetRequired() const { - std::vector required; - for (auto& property : properties_) { - if (!property.has_default_value()) { - required.push_back(property.name()); - } - } - return required; - } - - std::string to_json() const { - cJSON *json = cJSON_CreateObject(); - - for (const auto& property : properties_) { - cJSON *prop_json = cJSON_Parse(property.to_json().c_str()); - cJSON_AddItemToObject(json, property.name().c_str(), prop_json); - } - - char *json_str = cJSON_PrintUnformatted(json); - std::string result(json_str); - cJSON_free(json_str); - cJSON_Delete(json); - - return result; - } -}; - -class McpTool { -private: - std::string name_; - std::string description_; - PropertyList properties_; - std::function callback_; - bool user_only_ = false; - -public: - McpTool(const std::string& name, - const std::string& description, - const PropertyList& properties, - std::function callback) - : name_(name), - description_(description), - properties_(properties), - callback_(callback) {} - - void set_user_only(bool user_only) { user_only_ = user_only; } - inline const std::string& name() const { return name_; } - inline const std::string& description() const { return description_; } - inline const PropertyList& properties() const { return properties_; } - inline bool user_only() const { return user_only_; } - - std::string to_json() const { - std::vector required = properties_.GetRequired(); - - cJSON *json = cJSON_CreateObject(); - cJSON_AddStringToObject(json, "name", name_.c_str()); - cJSON_AddStringToObject(json, "description", description_.c_str()); - - cJSON *input_schema = cJSON_CreateObject(); - cJSON_AddStringToObject(input_schema, "type", "object"); - - cJSON *properties = cJSON_Parse(properties_.to_json().c_str()); - cJSON_AddItemToObject(input_schema, "properties", properties); - - if (!required.empty()) { - cJSON *required_array = cJSON_CreateArray(); - for (const auto& property : required) { - cJSON_AddItemToArray(required_array, cJSON_CreateString(property.c_str())); - } - cJSON_AddItemToObject(input_schema, "required", required_array); - } - - cJSON_AddItemToObject(json, "inputSchema", input_schema); - - // Add audience annotation if the tool is user only (invisible to AI) - if (user_only_) { - cJSON *annotations = cJSON_CreateObject(); - cJSON *audience = cJSON_CreateArray(); - cJSON_AddItemToArray(audience, cJSON_CreateString("user")); - cJSON_AddItemToObject(annotations, "audience", audience); - cJSON_AddItemToObject(json, "annotations", annotations); - } - - char *json_str = cJSON_PrintUnformatted(json); - std::string result(json_str); - cJSON_free(json_str); - cJSON_Delete(json); - - return result; - } - - std::string Call(const PropertyList& properties) { - ReturnValue return_value = callback_(properties); - // 返回结果 - cJSON* result = cJSON_CreateObject(); - cJSON* content = cJSON_CreateArray(); - - if (std::holds_alternative(return_value)) { - auto image_content = std::get(return_value); - cJSON* image = cJSON_CreateObject(); - cJSON_AddStringToObject(image, "type", "image"); - cJSON_AddStringToObject(image, "image", image_content->to_json().c_str()); - cJSON_AddItemToArray(content, image); - delete image_content; - } else { - cJSON* text = cJSON_CreateObject(); - cJSON_AddStringToObject(text, "type", "text"); - if (std::holds_alternative(return_value)) { - cJSON_AddStringToObject(text, "text", std::get(return_value).c_str()); - } else if (std::holds_alternative(return_value)) { - cJSON_AddStringToObject(text, "text", std::get(return_value) ? "true" : "false"); - } else if (std::holds_alternative(return_value)) { - cJSON_AddStringToObject(text, "text", std::to_string(std::get(return_value)).c_str()); - } else if (std::holds_alternative(return_value)) { - cJSON* json = std::get(return_value); - char* json_str = cJSON_PrintUnformatted(json); - cJSON_AddStringToObject(text, "text", json_str); - cJSON_free(json_str); - cJSON_Delete(json); - } - cJSON_AddItemToArray(content, text); - } - cJSON_AddItemToObject(result, "content", content); - cJSON_AddBoolToObject(result, "isError", false); - - auto json_str = cJSON_PrintUnformatted(result); - std::string result_str(json_str); - cJSON_free(json_str); - cJSON_Delete(result); - return result_str; - } -}; - -class McpServer { -public: - static McpServer& GetInstance() { - static McpServer instance; - return instance; - } - - void AddCommonTools(); - void AddUserOnlyTools(); - void AddTool(McpTool* tool); - void AddTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function callback); - void AddUserOnlyTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function callback); - void ParseMessage(const cJSON* json); - void ParseMessage(const std::string& message); - -private: - McpServer(); - ~McpServer(); - - void ParseCapabilities(const cJSON* capabilities); - - void ReplyResult(int id, const std::string& result); - void ReplyError(int id, const std::string& message); - - void GetToolsList(int id, const std::string& cursor, bool list_user_only_tools); - void DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments); - - std::vector tools_; -}; - -#endif // MCP_SERVER_H +#ifndef MCP_SERVER_H +#define MCP_SERVER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class ImageContent { +private: + std::string encoded_data_; + std::string mime_type_; + + static std::string Base64Encode(const std::string& data) { + size_t dlen = 0, olen = 0; + mbedtls_base64_encode((unsigned char*)nullptr, 0, &dlen, (const unsigned char*)data.data(), data.size()); + std::string result(dlen, 0); + mbedtls_base64_encode((unsigned char*)result.data(), result.size(), &olen, (const unsigned char*)data.data(), data.size()); + return result; + } + +public: + ImageContent(const std::string& mime_type, const std::string& data) { + mime_type_ = mime_type; + // base64 encode data + encoded_data_ = Base64Encode(data); + } + + std::string to_json() const { + cJSON *json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "type", "image"); + cJSON_AddStringToObject(json, "mimeType", mime_type_.c_str()); + cJSON_AddStringToObject(json, "data", encoded_data_.c_str()); + char* json_str = cJSON_PrintUnformatted(json); + std::string result(json_str); + cJSON_free(json_str); + cJSON_Delete(json); + return result; + } +}; + +// 添加类型别名 +using ReturnValue = std::variant; + +enum PropertyType { + kPropertyTypeBoolean, + kPropertyTypeInteger, + kPropertyTypeString +}; + +class Property { +private: + std::string name_; + PropertyType type_; + std::variant value_; + bool has_default_value_; + std::optional min_value_; // 新增:整数最小值 + std::optional max_value_; // 新增:整数最大值 + +public: + // Required field constructor + Property(const std::string& name, PropertyType type) + : name_(name), type_(type), has_default_value_(false) {} + + // Optional field constructor with default value + template + Property(const std::string& name, PropertyType type, const T& default_value) + : name_(name), type_(type), has_default_value_(true) { + value_ = default_value; + } + + Property(const std::string& name, PropertyType type, int min_value, int max_value) + : name_(name), type_(type), has_default_value_(false), min_value_(min_value), max_value_(max_value) { + if (type != kPropertyTypeInteger) { + throw std::invalid_argument("Range limits only apply to integer properties"); + } + } + + Property(const std::string& name, PropertyType type, int default_value, int min_value, int max_value) + : name_(name), type_(type), has_default_value_(true), min_value_(min_value), max_value_(max_value) { + if (type != kPropertyTypeInteger) { + throw std::invalid_argument("Range limits only apply to integer properties"); + } + if (default_value < min_value || default_value > max_value) { + throw std::invalid_argument("Default value must be within the specified range"); + } + value_ = default_value; + } + + inline const std::string& name() const { return name_; } + inline PropertyType type() const { return type_; } + inline bool has_default_value() const { return has_default_value_; } + inline bool has_range() const { return min_value_.has_value() && max_value_.has_value(); } + inline int min_value() const { return min_value_.value_or(0); } + inline int max_value() const { return max_value_.value_or(0); } + + template + inline T value() const { + return std::get(value_); + } + + template + inline void set_value(const T& value) { + // 添加对设置的整数值进行范围检查 + if constexpr (std::is_same_v) { + if (min_value_.has_value() && value < min_value_.value()) { + throw std::invalid_argument("Value is below minimum allowed: " + std::to_string(min_value_.value())); + } + if (max_value_.has_value() && value > max_value_.value()) { + throw std::invalid_argument("Value exceeds maximum allowed: " + std::to_string(max_value_.value())); + } + } + value_ = value; + } + + std::string to_json() const { + cJSON *json = cJSON_CreateObject(); + + if (type_ == kPropertyTypeBoolean) { + cJSON_AddStringToObject(json, "type", "boolean"); + if (has_default_value_) { + cJSON_AddBoolToObject(json, "default", value()); + } + } else if (type_ == kPropertyTypeInteger) { + cJSON_AddStringToObject(json, "type", "integer"); + if (has_default_value_) { + cJSON_AddNumberToObject(json, "default", value()); + } + if (min_value_.has_value()) { + cJSON_AddNumberToObject(json, "minimum", min_value_.value()); + } + if (max_value_.has_value()) { + cJSON_AddNumberToObject(json, "maximum", max_value_.value()); + } + } else if (type_ == kPropertyTypeString) { + cJSON_AddStringToObject(json, "type", "string"); + if (has_default_value_) { + cJSON_AddStringToObject(json, "default", value().c_str()); + } + } + + char *json_str = cJSON_PrintUnformatted(json); + std::string result(json_str); + cJSON_free(json_str); + cJSON_Delete(json); + + return result; + } +}; + +class PropertyList { +private: + std::vector properties_; + +public: + PropertyList() = default; + PropertyList(const std::vector& properties) : properties_(properties) {} + void AddProperty(const Property& property) { + properties_.push_back(property); + } + + const Property& operator[](const std::string& name) const { + for (const auto& property : properties_) { + if (property.name() == name) { + return property; + } + } + throw std::runtime_error("Property not found: " + name); + } + + auto begin() { return properties_.begin(); } + auto end() { return properties_.end(); } + + std::vector GetRequired() const { + std::vector required; + for (auto& property : properties_) { + if (!property.has_default_value()) { + required.push_back(property.name()); + } + } + return required; + } + + std::string to_json() const { + cJSON *json = cJSON_CreateObject(); + + for (const auto& property : properties_) { + cJSON *prop_json = cJSON_Parse(property.to_json().c_str()); + cJSON_AddItemToObject(json, property.name().c_str(), prop_json); + } + + char *json_str = cJSON_PrintUnformatted(json); + std::string result(json_str); + cJSON_free(json_str); + cJSON_Delete(json); + + return result; + } +}; + +class McpTool { +private: + std::string name_; + std::string description_; + PropertyList properties_; + std::function callback_; + bool user_only_ = false; + +public: + McpTool(const std::string& name, + const std::string& description, + const PropertyList& properties, + std::function callback) + : name_(name), + description_(description), + properties_(properties), + callback_(callback) {} + + void set_user_only(bool user_only) { user_only_ = user_only; } + inline const std::string& name() const { return name_; } + inline const std::string& description() const { return description_; } + inline const PropertyList& properties() const { return properties_; } + inline bool user_only() const { return user_only_; } + + std::string to_json() const { + std::vector required = properties_.GetRequired(); + + cJSON *json = cJSON_CreateObject(); + cJSON_AddStringToObject(json, "name", name_.c_str()); + cJSON_AddStringToObject(json, "description", description_.c_str()); + + cJSON *input_schema = cJSON_CreateObject(); + cJSON_AddStringToObject(input_schema, "type", "object"); + + cJSON *properties = cJSON_Parse(properties_.to_json().c_str()); + cJSON_AddItemToObject(input_schema, "properties", properties); + + if (!required.empty()) { + cJSON *required_array = cJSON_CreateArray(); + for (const auto& property : required) { + cJSON_AddItemToArray(required_array, cJSON_CreateString(property.c_str())); + } + cJSON_AddItemToObject(input_schema, "required", required_array); + } + + cJSON_AddItemToObject(json, "inputSchema", input_schema); + + // Add audience annotation if the tool is user only (invisible to AI) + if (user_only_) { + cJSON *annotations = cJSON_CreateObject(); + cJSON *audience = cJSON_CreateArray(); + cJSON_AddItemToArray(audience, cJSON_CreateString("user")); + cJSON_AddItemToObject(annotations, "audience", audience); + cJSON_AddItemToObject(json, "annotations", annotations); + } + + char *json_str = cJSON_PrintUnformatted(json); + std::string result(json_str); + cJSON_free(json_str); + cJSON_Delete(json); + + return result; + } + + std::string Call(const PropertyList& properties) { + ReturnValue return_value = callback_(properties); + // 返回结果 + cJSON* result = cJSON_CreateObject(); + cJSON* content = cJSON_CreateArray(); + + if (std::holds_alternative(return_value)) { + auto image_content = std::get(return_value); + cJSON* image = cJSON_CreateObject(); + cJSON_AddStringToObject(image, "type", "image"); + cJSON_AddStringToObject(image, "image", image_content->to_json().c_str()); + cJSON_AddItemToArray(content, image); + delete image_content; + } else { + cJSON* text = cJSON_CreateObject(); + cJSON_AddStringToObject(text, "type", "text"); + if (std::holds_alternative(return_value)) { + cJSON_AddStringToObject(text, "text", std::get(return_value).c_str()); + } else if (std::holds_alternative(return_value)) { + cJSON_AddStringToObject(text, "text", std::get(return_value) ? "true" : "false"); + } else if (std::holds_alternative(return_value)) { + cJSON_AddStringToObject(text, "text", std::to_string(std::get(return_value)).c_str()); + } else if (std::holds_alternative(return_value)) { + cJSON* json = std::get(return_value); + char* json_str = cJSON_PrintUnformatted(json); + cJSON_AddStringToObject(text, "text", json_str); + cJSON_free(json_str); + cJSON_Delete(json); + } + cJSON_AddItemToArray(content, text); + } + cJSON_AddItemToObject(result, "content", content); + cJSON_AddBoolToObject(result, "isError", false); + + auto json_str = cJSON_PrintUnformatted(result); + std::string result_str(json_str); + cJSON_free(json_str); + cJSON_Delete(result); + return result_str; + } +}; + +class McpServer { +public: + static McpServer& GetInstance() { + static McpServer instance; + return instance; + } + + void AddCommonTools(); + void AddUserOnlyTools(); + void AddTool(McpTool* tool); + void AddTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function callback); + void AddUserOnlyTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function callback); + void ParseMessage(const cJSON* json); + void ParseMessage(const std::string& message); + +private: + McpServer(); + ~McpServer(); + + void ParseCapabilities(const cJSON* capabilities); + + void ReplyResult(int id, const std::string& result); + void ReplyError(int id, const std::string& message); + + void GetToolsList(int id, const std::string& cursor, bool list_user_only_tools); + void DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments); + + std::vector tools_; +}; + +#endif // MCP_SERVER_H diff --git a/main/ota.cc b/main/ota.cc index b793273..8e3ab5a 100644 --- a/main/ota.cc +++ b/main/ota.cc @@ -1,479 +1,477 @@ -#include "ota.h" -#include "system_info.h" -#include "settings.h" -#include "assets/lang_config.h" - -#include -#include -#include -#include -#include -#include -#include -#ifdef SOC_HMAC_SUPPORTED -#include -#endif - -#include -#include -#include -#include - -#define TAG "Ota" - - -Ota::Ota() { -#ifdef ESP_EFUSE_BLOCK_USR_DATA - // Read Serial Number from efuse user_data - uint8_t serial_number[33] = {0}; - if (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA, serial_number, 32 * 8) == ESP_OK) { - if (serial_number[0] == 0) { - has_serial_number_ = false; - } else { - serial_number_ = std::string(reinterpret_cast(serial_number), 32); - has_serial_number_ = true; - } - } -#endif -} - -Ota::~Ota() { -} - -std::string Ota::GetCheckVersionUrl() { - Settings settings("wifi", false); - std::string url = settings.GetString("ota_url"); - if (url.empty()) { - url = CONFIG_OTA_URL; - } - return url; -} - -std::unique_ptr Ota::SetupHttp() { - auto& board = Board::GetInstance(); - auto app_desc = esp_app_get_description(); - - auto network = board.GetNetwork(); - auto http = network->CreateHttp(0); - auto user_agent = std::string(BOARD_NAME "/") + app_desc->version; - http->SetHeader("Activation-Version", has_serial_number_ ? "2" : "1"); - http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); - http->SetHeader("Client-Id", board.GetUuid()); - if (has_serial_number_) { - http->SetHeader("Serial-Number", serial_number_.c_str()); - ESP_LOGI(TAG, "Setup HTTP, User-Agent: %s, Serial-Number: %s", user_agent.c_str(), serial_number_.c_str()); - } - http->SetHeader("User-Agent", user_agent); - http->SetHeader("Accept-Language", Lang::CODE); - http->SetHeader("Content-Type", "application/json"); - - return http; -} - -/* - * Specification: https://ccnphfhqs21z.feishu.cn/wiki/FjW6wZmisimNBBkov6OcmfvknVd - */ -bool Ota::CheckVersion() { - auto& board = Board::GetInstance(); - auto app_desc = esp_app_get_description(); - - // Check if there is a new firmware version available - current_version_ = app_desc->version; - ESP_LOGI(TAG, "Current version: %s", current_version_.c_str()); - - std::string url = GetCheckVersionUrl(); - if (url.length() < 10) { - ESP_LOGE(TAG, "Check version URL is not properly set"); - return false; - } - - auto http = SetupHttp(); - - std::string data = board.GetSystemInfoJson(); - std::string method = data.length() > 0 ? "POST" : "GET"; - http->SetContent(std::move(data)); - - if (!http->Open(method, url)) { - ESP_LOGE(TAG, "Failed to open HTTP connection"); - return false; - } - - auto status_code = http->GetStatusCode(); - if (status_code != 200) { - ESP_LOGE(TAG, "Failed to check version, status code: %d", status_code); - return false; - } - - data = http->ReadAll(); - http->Close(); - - // Response: { "firmware": { "version": "1.0.0", "url": "http://" } } - // Parse the JSON response and check if the version is newer - // If it is, set has_new_version_ to true and store the new version and URL - - cJSON *root = cJSON_Parse(data.c_str()); - if (root == NULL) { - ESP_LOGE(TAG, "Failed to parse JSON response"); - return false; - } - - has_activation_code_ = false; - has_activation_challenge_ = false; - cJSON *activation = cJSON_GetObjectItem(root, "activation"); - if (cJSON_IsObject(activation)) { - cJSON* message = cJSON_GetObjectItem(activation, "message"); - if (cJSON_IsString(message)) { - activation_message_ = message->valuestring; - } - cJSON* code = cJSON_GetObjectItem(activation, "code"); - if (cJSON_IsString(code)) { - activation_code_ = code->valuestring; - has_activation_code_ = true; - } - cJSON* challenge = cJSON_GetObjectItem(activation, "challenge"); - if (cJSON_IsString(challenge)) { - activation_challenge_ = challenge->valuestring; - has_activation_challenge_ = true; - } - cJSON* timeout_ms = cJSON_GetObjectItem(activation, "timeout_ms"); - if (cJSON_IsNumber(timeout_ms)) { - activation_timeout_ms_ = timeout_ms->valueint; - } - } - - has_mqtt_config_ = false; - cJSON *mqtt = cJSON_GetObjectItem(root, "mqtt"); - if (cJSON_IsObject(mqtt)) { - Settings settings("mqtt", true); - cJSON *item = NULL; - cJSON_ArrayForEach(item, mqtt) { - if (cJSON_IsString(item)) { - if (settings.GetString(item->string) != item->valuestring) { - settings.SetString(item->string, item->valuestring); - } - } else if (cJSON_IsNumber(item)) { - if (settings.GetInt(item->string) != item->valueint) { - settings.SetInt(item->string, item->valueint); - } - } - } - has_mqtt_config_ = true; - } else { - ESP_LOGI(TAG, "No mqtt section found !"); - } - - has_websocket_config_ = false; - cJSON *websocket = cJSON_GetObjectItem(root, "websocket"); - if (cJSON_IsObject(websocket)) { - Settings settings("websocket", true); - cJSON *item = NULL; - cJSON_ArrayForEach(item, websocket) { - if (cJSON_IsString(item)) { - if (settings.GetString(item->string) != item->valuestring) { - settings.SetString(item->string, item->valuestring); - } - } else if (cJSON_IsNumber(item)) { - if (settings.GetInt(item->string) != item->valueint) { - settings.SetInt(item->string, item->valueint); - } - } - } - has_websocket_config_ = true; - } else { - ESP_LOGI(TAG, "No websocket section found!"); - } - - has_server_time_ = false; - cJSON *server_time = cJSON_GetObjectItem(root, "server_time"); - if (cJSON_IsObject(server_time)) { - cJSON *timestamp = cJSON_GetObjectItem(server_time, "timestamp"); - cJSON *timezone_offset = cJSON_GetObjectItem(server_time, "timezone_offset"); - - if (cJSON_IsNumber(timestamp)) { - // 设置系统时间 - struct timeval tv; - double ts = timestamp->valuedouble; - - // 如果有时区偏移,计算本地时间 - if (cJSON_IsNumber(timezone_offset)) { - ts += (timezone_offset->valueint * 60 * 1000); // 转换分钟为毫秒 - } - - tv.tv_sec = (time_t)(ts / 1000); // 转换毫秒为秒 - tv.tv_usec = (suseconds_t)((long long)ts % 1000) * 1000; // 剩余的毫秒转换为微秒 - settimeofday(&tv, NULL); - has_server_time_ = true; - } - } else { - ESP_LOGW(TAG, "No server_time section found!"); - } - - has_new_version_ = false; - cJSON *firmware = cJSON_GetObjectItem(root, "firmware"); - if (cJSON_IsObject(firmware)) { - cJSON *version = cJSON_GetObjectItem(firmware, "version"); - if (cJSON_IsString(version)) { - firmware_version_ = version->valuestring; - } - cJSON *url = cJSON_GetObjectItem(firmware, "url"); - if (cJSON_IsString(url)) { - firmware_url_ = url->valuestring; - } - - if (cJSON_IsString(version) && cJSON_IsString(url)) { - // Check if the version is newer, for example, 0.1.0 is newer than 0.0.1 - has_new_version_ = IsNewVersionAvailable(current_version_, firmware_version_); - if (has_new_version_) { - ESP_LOGI(TAG, "New version available: %s", firmware_version_.c_str()); - } else { - ESP_LOGI(TAG, "Current is the latest version"); - } - // If the force flag is set to 1, the given version is forced to be installed - cJSON *force = cJSON_GetObjectItem(firmware, "force"); - if (cJSON_IsNumber(force) && force->valueint == 1) { - has_new_version_ = true; - } - } - } else { - ESP_LOGW(TAG, "No firmware section found!"); - } - - cJSON_Delete(root); - return true; -} - -void Ota::MarkCurrentVersionValid() { - auto partition = esp_ota_get_running_partition(); - if (strcmp(partition->label, "factory") == 0) { - ESP_LOGI(TAG, "Running from factory partition, skipping"); - return; - } - - ESP_LOGI(TAG, "Running partition: %s", partition->label); - esp_ota_img_states_t state; - if (esp_ota_get_state_partition(partition, &state) != ESP_OK) { - ESP_LOGE(TAG, "Failed to get state of partition"); - return; - } - - if (state == ESP_OTA_IMG_PENDING_VERIFY) { - ESP_LOGI(TAG, "Marking firmware as valid"); - esp_ota_mark_app_valid_cancel_rollback(); - } -} - -bool Ota::Upgrade(const std::string& firmware_url) { - ESP_LOGI(TAG, "Upgrading firmware from %s", firmware_url.c_str()); - esp_ota_handle_t update_handle = 0; - auto update_partition = esp_ota_get_next_update_partition(NULL); - if (update_partition == NULL) { - ESP_LOGE(TAG, "Failed to get update partition"); - return false; - } - - ESP_LOGI(TAG, "Writing to partition %s at offset 0x%lx", update_partition->label, update_partition->address); - bool image_header_checked = false; - std::string image_header; - - auto network = Board::GetInstance().GetNetwork(); - auto http = network->CreateHttp(0); - if (!http->Open("GET", firmware_url)) { - ESP_LOGE(TAG, "Failed to open HTTP connection"); - return false; - } - - if (http->GetStatusCode() != 200) { - ESP_LOGE(TAG, "Failed to get firmware, status code: %d", http->GetStatusCode()); - return false; - } - - size_t content_length = http->GetBodyLength(); - if (content_length == 0) { - ESP_LOGE(TAG, "Failed to get content length"); - return false; - } - - char buffer[512]; - size_t total_read = 0, recent_read = 0; - auto last_calc_time = esp_timer_get_time(); - while (true) { - int ret = http->Read(buffer, sizeof(buffer)); - if (ret < 0) { - ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret)); - return false; - } - - // Calculate speed and progress every second - recent_read += ret; - total_read += ret; - if (esp_timer_get_time() - last_calc_time >= 1000000 || ret == 0) { - size_t progress = total_read * 100 / content_length; - ESP_LOGI(TAG, "Progress: %u%% (%u/%u), Speed: %uB/s", progress, total_read, content_length, recent_read); - if (upgrade_callback_) { - upgrade_callback_(progress, recent_read); - } - last_calc_time = esp_timer_get_time(); - recent_read = 0; - } - - if (ret == 0) { - break; - } - - if (!image_header_checked) { - image_header.append(buffer, ret); - if (image_header.size() >= sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) { - esp_app_desc_t new_app_info; - memcpy(&new_app_info, image_header.data() + sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t), sizeof(esp_app_desc_t)); - - auto current_version = esp_app_get_description()->version; - ESP_LOGI(TAG, "Current version: %s, New version: %s", current_version, new_app_info.version); - - if (esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle)) { - esp_ota_abort(update_handle); - ESP_LOGE(TAG, "Failed to begin OTA"); - return false; - } - - image_header_checked = true; - std::string().swap(image_header); - } - } - auto err = esp_ota_write(update_handle, buffer, ret); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err)); - esp_ota_abort(update_handle); - return false; - } - } - http->Close(); - - esp_err_t err = esp_ota_end(update_handle); - if (err != ESP_OK) { - if (err == ESP_ERR_OTA_VALIDATE_FAILED) { - ESP_LOGE(TAG, "Image validation failed, image is corrupted"); - } else { - ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err)); - } - return false; - } - - err = esp_ota_set_boot_partition(update_partition); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to set boot partition: %s", esp_err_to_name(err)); - return false; - } - - ESP_LOGI(TAG, "Firmware upgrade successful"); - return true; -} - -bool Ota::StartUpgrade(std::function callback) { - upgrade_callback_ = callback; - return Upgrade(firmware_url_); -} - -bool Ota::StartUpgradeFromUrl(const std::string& url, std::function callback) { - upgrade_callback_ = callback; - return Upgrade(url); -} - -std::vector Ota::ParseVersion(const std::string& version) { - std::vector versionNumbers; - std::stringstream ss(version); - std::string segment; - - while (std::getline(ss, segment, '.')) { - versionNumbers.push_back(std::stoi(segment)); - } - - return versionNumbers; -} - -bool Ota::IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion) { - std::vector current = ParseVersion(currentVersion); - std::vector newer = ParseVersion(newVersion); - - for (size_t i = 0; i < std::min(current.size(), newer.size()); ++i) { - if (newer[i] > current[i]) { - return true; - } else if (newer[i] < current[i]) { - return false; - } - } - - return newer.size() > current.size(); -} - -std::string Ota::GetActivationPayload() { - if (!has_serial_number_) { - return "{}"; - } - - std::string hmac_hex; -#ifdef SOC_HMAC_SUPPORTED - uint8_t hmac_result[32]; // SHA-256 输出为32字节 - - // 使用Key0计算HMAC - esp_err_t ret = esp_hmac_calculate(HMAC_KEY0, (uint8_t*)activation_challenge_.data(), activation_challenge_.size(), hmac_result); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "HMAC calculation failed: %s", esp_err_to_name(ret)); - return "{}"; - } - - for (size_t i = 0; i < sizeof(hmac_result); i++) { - char buffer[3]; - sprintf(buffer, "%02x", hmac_result[i]); - hmac_hex += buffer; - } -#endif - - cJSON *payload = cJSON_CreateObject(); - cJSON_AddStringToObject(payload, "algorithm", "hmac-sha256"); - cJSON_AddStringToObject(payload, "serial_number", serial_number_.c_str()); - cJSON_AddStringToObject(payload, "challenge", activation_challenge_.c_str()); - cJSON_AddStringToObject(payload, "hmac", hmac_hex.c_str()); - auto json_str = cJSON_PrintUnformatted(payload); - std::string json(json_str); - cJSON_free(json_str); - cJSON_Delete(payload); - - ESP_LOGI(TAG, "Activation payload: %s", json.c_str()); - return json; -} - -esp_err_t Ota::Activate() { - if (!has_activation_challenge_) { - ESP_LOGW(TAG, "No activation challenge found"); - return ESP_FAIL; - } - - std::string url = GetCheckVersionUrl(); - if (url.back() != '/') { - url += "/activate"; - } else { - url += "activate"; - } - - auto http = SetupHttp(); - - std::string data = GetActivationPayload(); - http->SetContent(std::move(data)); - - if (!http->Open("POST", url)) { - ESP_LOGE(TAG, "Failed to open HTTP connection"); - return ESP_FAIL; - } - - auto status_code = http->GetStatusCode(); - if (status_code == 202) { - return ESP_ERR_TIMEOUT; - } - if (status_code != 200) { - ESP_LOGE(TAG, "Failed to activate, code: %d, body: %s", status_code, http->ReadAll().c_str()); - return ESP_FAIL; - } - - ESP_LOGI(TAG, "Activation successful"); - return ESP_OK; -} +#include "ota.h" +#include "system_info.h" +#include "settings.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef SOC_HMAC_SUPPORTED +#include +#endif + +#include +#include +#include +#include + +#define TAG "Ota" + + +Ota::Ota() { +#ifdef ESP_EFUSE_BLOCK_USR_DATA + // Read Serial Number from efuse user_data + uint8_t serial_number[33] = {0}; + if (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA, serial_number, 32 * 8) == ESP_OK) { + if (serial_number[0] == 0) { + has_serial_number_ = false; + } else { + serial_number_ = std::string(reinterpret_cast(serial_number), 32); + has_serial_number_ = true; + } + } +#endif +} + +Ota::~Ota() { +} + +std::string Ota::GetCheckVersionUrl() { + Settings settings("wifi", false); + std::string url = settings.GetString("ota_url"); + if (url.empty()) { + url = CONFIG_OTA_URL; + } + return url; +} + +std::unique_ptr Ota::SetupHttp() { + auto& board = Board::GetInstance(); + auto network = board.GetNetwork(); + auto http = network->CreateHttp(0); + auto user_agent = SystemInfo::GetUserAgent(); + http->SetHeader("Activation-Version", has_serial_number_ ? "2" : "1"); + http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); + http->SetHeader("Client-Id", board.GetUuid()); + if (has_serial_number_) { + http->SetHeader("Serial-Number", serial_number_.c_str()); + ESP_LOGI(TAG, "Setup HTTP, User-Agent: %s, Serial-Number: %s", user_agent.c_str(), serial_number_.c_str()); + } + http->SetHeader("User-Agent", user_agent); + http->SetHeader("Accept-Language", Lang::CODE); + http->SetHeader("Content-Type", "application/json"); + + return http; +} + +/* + * Specification: https://ccnphfhqs21z.feishu.cn/wiki/FjW6wZmisimNBBkov6OcmfvknVd + */ +bool Ota::CheckVersion() { + auto& board = Board::GetInstance(); + auto app_desc = esp_app_get_description(); + + // Check if there is a new firmware version available + current_version_ = app_desc->version; + ESP_LOGI(TAG, "Current version: %s", current_version_.c_str()); + + std::string url = GetCheckVersionUrl(); + if (url.length() < 10) { + ESP_LOGE(TAG, "Check version URL is not properly set"); + return false; + } + + auto http = SetupHttp(); + + std::string data = board.GetSystemInfoJson(); + std::string method = data.length() > 0 ? "POST" : "GET"; + http->SetContent(std::move(data)); + + if (!http->Open(method, url)) { + ESP_LOGE(TAG, "Failed to open HTTP connection"); + return false; + } + + auto status_code = http->GetStatusCode(); + if (status_code != 200) { + ESP_LOGE(TAG, "Failed to check version, status code: %d", status_code); + return false; + } + + data = http->ReadAll(); + http->Close(); + + // Response: { "firmware": { "version": "1.0.0", "url": "http://" } } + // Parse the JSON response and check if the version is newer + // If it is, set has_new_version_ to true and store the new version and URL + + cJSON *root = cJSON_Parse(data.c_str()); + if (root == NULL) { + ESP_LOGE(TAG, "Failed to parse JSON response"); + return false; + } + + has_activation_code_ = false; + has_activation_challenge_ = false; + cJSON *activation = cJSON_GetObjectItem(root, "activation"); + if (cJSON_IsObject(activation)) { + cJSON* message = cJSON_GetObjectItem(activation, "message"); + if (cJSON_IsString(message)) { + activation_message_ = message->valuestring; + } + cJSON* code = cJSON_GetObjectItem(activation, "code"); + if (cJSON_IsString(code)) { + activation_code_ = code->valuestring; + has_activation_code_ = true; + } + cJSON* challenge = cJSON_GetObjectItem(activation, "challenge"); + if (cJSON_IsString(challenge)) { + activation_challenge_ = challenge->valuestring; + has_activation_challenge_ = true; + } + cJSON* timeout_ms = cJSON_GetObjectItem(activation, "timeout_ms"); + if (cJSON_IsNumber(timeout_ms)) { + activation_timeout_ms_ = timeout_ms->valueint; + } + } + + has_mqtt_config_ = false; + cJSON *mqtt = cJSON_GetObjectItem(root, "mqtt"); + if (cJSON_IsObject(mqtt)) { + Settings settings("mqtt", true); + cJSON *item = NULL; + cJSON_ArrayForEach(item, mqtt) { + if (cJSON_IsString(item)) { + if (settings.GetString(item->string) != item->valuestring) { + settings.SetString(item->string, item->valuestring); + } + } else if (cJSON_IsNumber(item)) { + if (settings.GetInt(item->string) != item->valueint) { + settings.SetInt(item->string, item->valueint); + } + } + } + has_mqtt_config_ = true; + } else { + ESP_LOGI(TAG, "No mqtt section found !"); + } + + has_websocket_config_ = false; + cJSON *websocket = cJSON_GetObjectItem(root, "websocket"); + if (cJSON_IsObject(websocket)) { + Settings settings("websocket", true); + cJSON *item = NULL; + cJSON_ArrayForEach(item, websocket) { + if (cJSON_IsString(item)) { + if (settings.GetString(item->string) != item->valuestring) { + settings.SetString(item->string, item->valuestring); + } + } else if (cJSON_IsNumber(item)) { + if (settings.GetInt(item->string) != item->valueint) { + settings.SetInt(item->string, item->valueint); + } + } + } + has_websocket_config_ = true; + } else { + ESP_LOGI(TAG, "No websocket section found!"); + } + + has_server_time_ = false; + cJSON *server_time = cJSON_GetObjectItem(root, "server_time"); + if (cJSON_IsObject(server_time)) { + cJSON *timestamp = cJSON_GetObjectItem(server_time, "timestamp"); + cJSON *timezone_offset = cJSON_GetObjectItem(server_time, "timezone_offset"); + + if (cJSON_IsNumber(timestamp)) { + // 设置系统时间 + struct timeval tv; + double ts = timestamp->valuedouble; + + // 如果有时区偏移,计算本地时间 + if (cJSON_IsNumber(timezone_offset)) { + ts += (timezone_offset->valueint * 60 * 1000); // 转换分钟为毫秒 + } + + tv.tv_sec = (time_t)(ts / 1000); // 转换毫秒为秒 + tv.tv_usec = (suseconds_t)((long long)ts % 1000) * 1000; // 剩余的毫秒转换为微秒 + settimeofday(&tv, NULL); + has_server_time_ = true; + } + } else { + ESP_LOGW(TAG, "No server_time section found!"); + } + + has_new_version_ = false; + cJSON *firmware = cJSON_GetObjectItem(root, "firmware"); + if (cJSON_IsObject(firmware)) { + cJSON *version = cJSON_GetObjectItem(firmware, "version"); + if (cJSON_IsString(version)) { + firmware_version_ = version->valuestring; + } + cJSON *url = cJSON_GetObjectItem(firmware, "url"); + if (cJSON_IsString(url)) { + firmware_url_ = url->valuestring; + } + + if (cJSON_IsString(version) && cJSON_IsString(url)) { + // Check if the version is newer, for example, 0.1.0 is newer than 0.0.1 + has_new_version_ = IsNewVersionAvailable(current_version_, firmware_version_); + if (has_new_version_) { + ESP_LOGI(TAG, "New version available: %s", firmware_version_.c_str()); + } else { + ESP_LOGI(TAG, "Current is the latest version"); + } + // If the force flag is set to 1, the given version is forced to be installed + cJSON *force = cJSON_GetObjectItem(firmware, "force"); + if (cJSON_IsNumber(force) && force->valueint == 1) { + has_new_version_ = true; + } + } + } else { + ESP_LOGW(TAG, "No firmware section found!"); + } + + cJSON_Delete(root); + return true; +} + +void Ota::MarkCurrentVersionValid() { + auto partition = esp_ota_get_running_partition(); + if (strcmp(partition->label, "factory") == 0) { + ESP_LOGI(TAG, "Running from factory partition, skipping"); + return; + } + + ESP_LOGI(TAG, "Running partition: %s", partition->label); + esp_ota_img_states_t state; + if (esp_ota_get_state_partition(partition, &state) != ESP_OK) { + ESP_LOGE(TAG, "Failed to get state of partition"); + return; + } + + if (state == ESP_OTA_IMG_PENDING_VERIFY) { + ESP_LOGI(TAG, "Marking firmware as valid"); + esp_ota_mark_app_valid_cancel_rollback(); + } +} + +bool Ota::Upgrade(const std::string& firmware_url) { + ESP_LOGI(TAG, "Upgrading firmware from %s", firmware_url.c_str()); + esp_ota_handle_t update_handle = 0; + auto update_partition = esp_ota_get_next_update_partition(NULL); + if (update_partition == NULL) { + ESP_LOGE(TAG, "Failed to get update partition"); + return false; + } + + ESP_LOGI(TAG, "Writing to partition %s at offset 0x%lx", update_partition->label, update_partition->address); + bool image_header_checked = false; + std::string image_header; + + auto network = Board::GetInstance().GetNetwork(); + auto http = network->CreateHttp(0); + if (!http->Open("GET", firmware_url)) { + ESP_LOGE(TAG, "Failed to open HTTP connection"); + return false; + } + + if (http->GetStatusCode() != 200) { + ESP_LOGE(TAG, "Failed to get firmware, status code: %d", http->GetStatusCode()); + return false; + } + + size_t content_length = http->GetBodyLength(); + if (content_length == 0) { + ESP_LOGE(TAG, "Failed to get content length"); + return false; + } + + char buffer[512]; + size_t total_read = 0, recent_read = 0; + auto last_calc_time = esp_timer_get_time(); + while (true) { + int ret = http->Read(buffer, sizeof(buffer)); + if (ret < 0) { + ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret)); + return false; + } + + // Calculate speed and progress every second + recent_read += ret; + total_read += ret; + if (esp_timer_get_time() - last_calc_time >= 1000000 || ret == 0) { + size_t progress = total_read * 100 / content_length; + ESP_LOGI(TAG, "Progress: %u%% (%u/%u), Speed: %uB/s", progress, total_read, content_length, recent_read); + if (upgrade_callback_) { + upgrade_callback_(progress, recent_read); + } + last_calc_time = esp_timer_get_time(); + recent_read = 0; + } + + if (ret == 0) { + break; + } + + if (!image_header_checked) { + image_header.append(buffer, ret); + if (image_header.size() >= sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) { + esp_app_desc_t new_app_info; + memcpy(&new_app_info, image_header.data() + sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t), sizeof(esp_app_desc_t)); + + auto current_version = esp_app_get_description()->version; + ESP_LOGI(TAG, "Current version: %s, New version: %s", current_version, new_app_info.version); + + if (esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle)) { + esp_ota_abort(update_handle); + ESP_LOGE(TAG, "Failed to begin OTA"); + return false; + } + + image_header_checked = true; + std::string().swap(image_header); + } + } + auto err = esp_ota_write(update_handle, buffer, ret); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err)); + esp_ota_abort(update_handle); + return false; + } + } + http->Close(); + + esp_err_t err = esp_ota_end(update_handle); + if (err != ESP_OK) { + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + ESP_LOGE(TAG, "Image validation failed, image is corrupted"); + } else { + ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err)); + } + return false; + } + + err = esp_ota_set_boot_partition(update_partition); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set boot partition: %s", esp_err_to_name(err)); + return false; + } + + ESP_LOGI(TAG, "Firmware upgrade successful"); + return true; +} + +bool Ota::StartUpgrade(std::function callback) { + upgrade_callback_ = callback; + return Upgrade(firmware_url_); +} + +bool Ota::StartUpgradeFromUrl(const std::string& url, std::function callback) { + upgrade_callback_ = callback; + return Upgrade(url); +} + +std::vector Ota::ParseVersion(const std::string& version) { + std::vector versionNumbers; + std::stringstream ss(version); + std::string segment; + + while (std::getline(ss, segment, '.')) { + versionNumbers.push_back(std::stoi(segment)); + } + + return versionNumbers; +} + +bool Ota::IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion) { + std::vector current = ParseVersion(currentVersion); + std::vector newer = ParseVersion(newVersion); + + for (size_t i = 0; i < std::min(current.size(), newer.size()); ++i) { + if (newer[i] > current[i]) { + return true; + } else if (newer[i] < current[i]) { + return false; + } + } + + return newer.size() > current.size(); +} + +std::string Ota::GetActivationPayload() { + if (!has_serial_number_) { + return "{}"; + } + + std::string hmac_hex; +#ifdef SOC_HMAC_SUPPORTED + uint8_t hmac_result[32]; // SHA-256 输出为32字节 + + // 使用Key0计算HMAC + esp_err_t ret = esp_hmac_calculate(HMAC_KEY0, (uint8_t*)activation_challenge_.data(), activation_challenge_.size(), hmac_result); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "HMAC calculation failed: %s", esp_err_to_name(ret)); + return "{}"; + } + + for (size_t i = 0; i < sizeof(hmac_result); i++) { + char buffer[3]; + sprintf(buffer, "%02x", hmac_result[i]); + hmac_hex += buffer; + } +#endif + + cJSON *payload = cJSON_CreateObject(); + cJSON_AddStringToObject(payload, "algorithm", "hmac-sha256"); + cJSON_AddStringToObject(payload, "serial_number", serial_number_.c_str()); + cJSON_AddStringToObject(payload, "challenge", activation_challenge_.c_str()); + cJSON_AddStringToObject(payload, "hmac", hmac_hex.c_str()); + auto json_str = cJSON_PrintUnformatted(payload); + std::string json(json_str); + cJSON_free(json_str); + cJSON_Delete(payload); + + ESP_LOGI(TAG, "Activation payload: %s", json.c_str()); + return json; +} + +esp_err_t Ota::Activate() { + if (!has_activation_challenge_) { + ESP_LOGW(TAG, "No activation challenge found"); + return ESP_FAIL; + } + + std::string url = GetCheckVersionUrl(); + if (url.back() != '/') { + url += "/activate"; + } else { + url += "activate"; + } + + auto http = SetupHttp(); + + std::string data = GetActivationPayload(); + http->SetContent(std::move(data)); + + if (!http->Open("POST", url)) { + ESP_LOGE(TAG, "Failed to open HTTP connection"); + return ESP_FAIL; + } + + auto status_code = http->GetStatusCode(); + if (status_code == 202) { + return ESP_ERR_TIMEOUT; + } + if (status_code != 200) { + ESP_LOGE(TAG, "Failed to activate, code: %d, body: %s", status_code, http->ReadAll().c_str()); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Activation successful"); + return ESP_OK; +} diff --git a/main/ota.h b/main/ota.h index 82e670e..fd59a22 100644 --- a/main/ota.h +++ b/main/ota.h @@ -1,59 +1,59 @@ -#ifndef _OTA_H -#define _OTA_H - -#include -#include - -#include -#include "board.h" - -class Ota { -public: - Ota(); - ~Ota(); - - bool CheckVersion(); - esp_err_t Activate(); - bool HasActivationChallenge() { return has_activation_challenge_; } - bool HasNewVersion() { return has_new_version_; } - bool HasMqttConfig() { return has_mqtt_config_; } - bool HasWebsocketConfig() { return has_websocket_config_; } - bool HasActivationCode() { return has_activation_code_; } - bool HasServerTime() { return has_server_time_; } - bool StartUpgrade(std::function callback); - bool StartUpgradeFromUrl(const std::string& url, std::function callback); - void MarkCurrentVersionValid(); - - const std::string& GetFirmwareVersion() const { return firmware_version_; } - const std::string& GetCurrentVersion() const { return current_version_; } - const std::string& GetFirmwareUrl() const { return firmware_url_; } - const std::string& GetActivationMessage() const { return activation_message_; } - const std::string& GetActivationCode() const { return activation_code_; } - std::string GetCheckVersionUrl(); - -private: - std::string activation_message_; - std::string activation_code_; - bool has_new_version_ = false; - bool has_mqtt_config_ = false; - bool has_websocket_config_ = false; - bool has_server_time_ = false; - bool has_activation_code_ = false; - bool has_serial_number_ = false; - bool has_activation_challenge_ = false; - std::string current_version_; - std::string firmware_version_; - std::string firmware_url_; - std::string activation_challenge_; - std::string serial_number_; - int activation_timeout_ms_ = 30000; - - bool Upgrade(const std::string& firmware_url); - std::function upgrade_callback_; - std::vector ParseVersion(const std::string& version); - bool IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion); - std::string GetActivationPayload(); - std::unique_ptr SetupHttp(); -}; - -#endif // _OTA_H +#ifndef _OTA_H +#define _OTA_H + +#include +#include + +#include +#include "board.h" + +class Ota { +public: + Ota(); + ~Ota(); + + bool CheckVersion(); + esp_err_t Activate(); + bool HasActivationChallenge() { return has_activation_challenge_; } + bool HasNewVersion() { return has_new_version_; } + bool HasMqttConfig() { return has_mqtt_config_; } + bool HasWebsocketConfig() { return has_websocket_config_; } + bool HasActivationCode() { return has_activation_code_; } + bool HasServerTime() { return has_server_time_; } + bool StartUpgrade(std::function callback); + bool StartUpgradeFromUrl(const std::string& url, std::function callback); + void MarkCurrentVersionValid(); + + const std::string& GetFirmwareVersion() const { return firmware_version_; } + const std::string& GetCurrentVersion() const { return current_version_; } + const std::string& GetFirmwareUrl() const { return firmware_url_; } + const std::string& GetActivationMessage() const { return activation_message_; } + const std::string& GetActivationCode() const { return activation_code_; } + std::string GetCheckVersionUrl(); + +private: + std::string activation_message_; + std::string activation_code_; + bool has_new_version_ = false; + bool has_mqtt_config_ = false; + bool has_websocket_config_ = false; + bool has_server_time_ = false; + bool has_activation_code_ = false; + bool has_serial_number_ = false; + bool has_activation_challenge_ = false; + std::string current_version_; + std::string firmware_version_; + std::string firmware_url_; + std::string activation_challenge_; + std::string serial_number_; + int activation_timeout_ms_ = 30000; + + bool Upgrade(const std::string& firmware_url); + std::function upgrade_callback_; + std::vector ParseVersion(const std::string& version); + bool IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion); + std::string GetActivationPayload(); + std::unique_ptr SetupHttp(); +}; + +#endif // _OTA_H diff --git a/main/protocols/mqtt_protocol.cc b/main/protocols/mqtt_protocol.cc index 7fbd77f..210ba73 100644 --- a/main/protocols/mqtt_protocol.cc +++ b/main/protocols/mqtt_protocol.cc @@ -1,372 +1,372 @@ -#include "mqtt_protocol.h" -#include "board.h" -#include "application.h" -#include "settings.h" - -#include -#include -#include -#include "assets/lang_config.h" - -#define TAG "MQTT" - -MqttProtocol::MqttProtocol() { - event_group_handle_ = xEventGroupCreate(); - - // Initialize reconnect timer - esp_timer_create_args_t reconnect_timer_args = { - .callback = [](void* arg) { - MqttProtocol* protocol = (MqttProtocol*)arg; - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateIdle) { - ESP_LOGI(TAG, "Reconnecting to MQTT server"); - app.Schedule([protocol]() { - protocol->StartMqttClient(false); - }); - } - }, - .arg = this, - }; - esp_timer_create(&reconnect_timer_args, &reconnect_timer_); -} - -MqttProtocol::~MqttProtocol() { - ESP_LOGI(TAG, "MqttProtocol deinit"); - if (reconnect_timer_ != nullptr) { - esp_timer_stop(reconnect_timer_); - esp_timer_delete(reconnect_timer_); - } - - udp_.reset(); - mqtt_.reset(); - - if (event_group_handle_ != nullptr) { - vEventGroupDelete(event_group_handle_); - } -} - -bool MqttProtocol::Start() { - return StartMqttClient(false); -} - -bool MqttProtocol::StartMqttClient(bool report_error) { - if (mqtt_ != nullptr) { - ESP_LOGW(TAG, "Mqtt client already started"); - mqtt_.reset(); - } - - Settings settings("mqtt", false); - auto endpoint = settings.GetString("endpoint"); - auto client_id = settings.GetString("client_id"); - auto username = settings.GetString("username"); - auto password = settings.GetString("password"); - int keepalive_interval = settings.GetInt("keepalive", 240); - publish_topic_ = settings.GetString("publish_topic"); - - if (endpoint.empty()) { - ESP_LOGW(TAG, "MQTT endpoint is not specified"); - if (report_error) { - SetError(Lang::Strings::SERVER_NOT_FOUND); - } - return false; - } - - auto network = Board::GetInstance().GetNetwork(); - mqtt_ = network->CreateMqtt(0); - mqtt_->SetKeepAlive(keepalive_interval); - - mqtt_->OnDisconnected([this]() { - if (on_disconnected_ != nullptr) { - on_disconnected_(); - } - ESP_LOGI(TAG, "MQTT disconnected, schedule reconnect in %d seconds", MQTT_RECONNECT_INTERVAL_MS / 1000); - esp_timer_start_once(reconnect_timer_, MQTT_RECONNECT_INTERVAL_MS * 1000); - }); - - mqtt_->OnConnected([this]() { - if (on_connected_ != nullptr) { - on_connected_(); - } - esp_timer_stop(reconnect_timer_); - }); - - mqtt_->OnMessage([this](const std::string& topic, const std::string& payload) { - cJSON* root = cJSON_Parse(payload.c_str()); - if (root == nullptr) { - ESP_LOGE(TAG, "Failed to parse json message %s", payload.c_str()); - return; - } - cJSON* type = cJSON_GetObjectItem(root, "type"); - if (!cJSON_IsString(type)) { - ESP_LOGE(TAG, "Message type is invalid"); - cJSON_Delete(root); - return; - } - - if (strcmp(type->valuestring, "hello") == 0) { - ParseServerHello(root); - } else if (strcmp(type->valuestring, "goodbye") == 0) { - auto session_id = cJSON_GetObjectItem(root, "session_id"); - ESP_LOGI(TAG, "Received goodbye message, session_id: %s", session_id ? session_id->valuestring : "null"); - if (session_id == nullptr || session_id_ == session_id->valuestring) { - Application::GetInstance().Schedule([this]() { - CloseAudioChannel(); - }); - } - } else if (on_incoming_json_ != nullptr) { - on_incoming_json_(root); - } - cJSON_Delete(root); - last_incoming_time_ = std::chrono::steady_clock::now(); - }); - - ESP_LOGI(TAG, "Connecting to endpoint %s", endpoint.c_str()); - std::string broker_address; - int broker_port = 8883; - size_t pos = endpoint.find(':'); - if (pos != std::string::npos) { - broker_address = endpoint.substr(0, pos); - broker_port = std::stoi(endpoint.substr(pos + 1)); - } else { - broker_address = endpoint; - } - if (!mqtt_->Connect(broker_address, broker_port, client_id, username, password)) { - ESP_LOGE(TAG, "Failed to connect to endpoint"); - SetError(Lang::Strings::SERVER_NOT_CONNECTED); - return false; - } - - ESP_LOGI(TAG, "Connected to endpoint"); - return true; -} - -bool MqttProtocol::SendText(const std::string& text) { - if (publish_topic_.empty()) { - return false; - } - if (!mqtt_->Publish(publish_topic_, text)) { - ESP_LOGE(TAG, "Failed to publish message: %s", text.c_str()); - SetError(Lang::Strings::SERVER_ERROR); - return false; - } - return true; -} - -bool MqttProtocol::SendAudio(std::unique_ptr packet) { - std::lock_guard lock(channel_mutex_); - if (udp_ == nullptr) { - return false; - } - - std::string nonce(aes_nonce_); - *(uint16_t*)&nonce[2] = htons(packet->payload.size()); - *(uint32_t*)&nonce[8] = htonl(packet->timestamp); - *(uint32_t*)&nonce[12] = htonl(++local_sequence_); - - std::string encrypted; - encrypted.resize(aes_nonce_.size() + packet->payload.size()); - memcpy(encrypted.data(), nonce.data(), nonce.size()); - - size_t nc_off = 0; - uint8_t stream_block[16] = {0}; - if (mbedtls_aes_crypt_ctr(&aes_ctx_, packet->payload.size(), &nc_off, (uint8_t*)nonce.c_str(), stream_block, - (uint8_t*)packet->payload.data(), (uint8_t*)&encrypted[nonce.size()]) != 0) { - ESP_LOGE(TAG, "Failed to encrypt audio data"); - return false; - } - - return udp_->Send(encrypted) > 0; -} - -void MqttProtocol::CloseAudioChannel() { - { - std::lock_guard lock(channel_mutex_); - udp_.reset(); - } - - std::string message = "{"; - message += "\"session_id\":\"" + session_id_ + "\","; - message += "\"type\":\"goodbye\""; - message += "}"; - SendText(message); - - if (on_audio_channel_closed_ != nullptr) { - on_audio_channel_closed_(); - } -} - -bool MqttProtocol::OpenAudioChannel() { - if (mqtt_ == nullptr || !mqtt_->IsConnected()) { - ESP_LOGI(TAG, "MQTT is not connected, try to connect now"); - if (!StartMqttClient(true)) { - return false; - } - } - - error_occurred_ = false; - session_id_ = ""; - xEventGroupClearBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT); - - auto message = GetHelloMessage(); - if (!SendText(message)) { - return false; - } - - // 等待服务器响应 - EventBits_t bits = xEventGroupWaitBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000)); - if (!(bits & MQTT_PROTOCOL_SERVER_HELLO_EVENT)) { - ESP_LOGE(TAG, "Failed to receive server hello"); - SetError(Lang::Strings::SERVER_TIMEOUT); - return false; - } - - std::lock_guard lock(channel_mutex_); - auto network = Board::GetInstance().GetNetwork(); - udp_ = network->CreateUdp(2); - udp_->OnMessage([this](const std::string& data) { - /* - * UDP Encrypted OPUS Packet Format: - * |type 1u|flags 1u|payload_len 2u|ssrc 4u|timestamp 4u|sequence 4u| - * |payload payload_len| - */ - if (data.size() < sizeof(aes_nonce_)) { - ESP_LOGE(TAG, "Invalid audio packet size: %u", data.size()); - return; - } - if (data[0] != 0x01) { - ESP_LOGE(TAG, "Invalid audio packet type: %x", data[0]); - return; - } - uint32_t timestamp = ntohl(*(uint32_t*)&data[8]); - uint32_t sequence = ntohl(*(uint32_t*)&data[12]); - if (sequence < remote_sequence_) { - ESP_LOGW(TAG, "Received audio packet with old sequence: %lu, expected: %lu", sequence, remote_sequence_); - return; - } - if (sequence != remote_sequence_ + 1) { - ESP_LOGW(TAG, "Received audio packet with wrong sequence: %lu, expected: %lu", sequence, remote_sequence_ + 1); - } - - size_t decrypted_size = data.size() - aes_nonce_.size(); - size_t nc_off = 0; - uint8_t stream_block[16] = {0}; - auto nonce = (uint8_t*)data.data(); - auto encrypted = (uint8_t*)data.data() + aes_nonce_.size(); - auto packet = std::make_unique(); - packet->sample_rate = server_sample_rate_; - packet->frame_duration = server_frame_duration_; - packet->timestamp = timestamp; - packet->payload.resize(decrypted_size); - int ret = mbedtls_aes_crypt_ctr(&aes_ctx_, decrypted_size, &nc_off, nonce, stream_block, encrypted, (uint8_t*)packet->payload.data()); - if (ret != 0) { - ESP_LOGE(TAG, "Failed to decrypt audio data, ret: %d", ret); - return; - } - if (on_incoming_audio_ != nullptr) { - on_incoming_audio_(std::move(packet)); - } - remote_sequence_ = sequence; - last_incoming_time_ = std::chrono::steady_clock::now(); - }); - - udp_->Connect(udp_server_, udp_port_); - - if (on_audio_channel_opened_ != nullptr) { - on_audio_channel_opened_(); - } - return true; -} - -std::string MqttProtocol::GetHelloMessage() { - // 发送 hello 消息申请 UDP 通道 - cJSON* root = cJSON_CreateObject(); - cJSON_AddStringToObject(root, "type", "hello"); - cJSON_AddNumberToObject(root, "version", 3); - cJSON_AddStringToObject(root, "transport", "udp"); - cJSON* features = cJSON_CreateObject(); -#if CONFIG_USE_SERVER_AEC - cJSON_AddBoolToObject(features, "aec", true); -#endif - cJSON_AddBoolToObject(features, "mcp", true); - cJSON_AddItemToObject(root, "features", features); - cJSON* audio_params = cJSON_CreateObject(); - cJSON_AddStringToObject(audio_params, "format", "opus"); - cJSON_AddNumberToObject(audio_params, "sample_rate", 16000); - cJSON_AddNumberToObject(audio_params, "channels", 1); - cJSON_AddNumberToObject(audio_params, "frame_duration", OPUS_FRAME_DURATION_MS); - cJSON_AddItemToObject(root, "audio_params", audio_params); - auto json_str = cJSON_PrintUnformatted(root); - std::string message(json_str); - cJSON_free(json_str); - cJSON_Delete(root); - return message; -} - -void MqttProtocol::ParseServerHello(const cJSON* root) { - auto transport = cJSON_GetObjectItem(root, "transport"); - if (transport == nullptr || strcmp(transport->valuestring, "udp") != 0) { - ESP_LOGE(TAG, "Unsupported transport: %s", transport->valuestring); - return; - } - - auto session_id = cJSON_GetObjectItem(root, "session_id"); - if (cJSON_IsString(session_id)) { - session_id_ = session_id->valuestring; - ESP_LOGI(TAG, "Session ID: %s", session_id_.c_str()); - } - - // Get sample rate from hello message - auto audio_params = cJSON_GetObjectItem(root, "audio_params"); - if (cJSON_IsObject(audio_params)) { - auto sample_rate = cJSON_GetObjectItem(audio_params, "sample_rate"); - if (cJSON_IsNumber(sample_rate)) { - server_sample_rate_ = sample_rate->valueint; - } - auto frame_duration = cJSON_GetObjectItem(audio_params, "frame_duration"); - if (cJSON_IsNumber(frame_duration)) { - server_frame_duration_ = frame_duration->valueint; - } - } - - auto udp = cJSON_GetObjectItem(root, "udp"); - if (!cJSON_IsObject(udp)) { - ESP_LOGE(TAG, "UDP is not specified"); - return; - } - udp_server_ = cJSON_GetObjectItem(udp, "server")->valuestring; - udp_port_ = cJSON_GetObjectItem(udp, "port")->valueint; - auto key = cJSON_GetObjectItem(udp, "key")->valuestring; - auto nonce = cJSON_GetObjectItem(udp, "nonce")->valuestring; - - // auto encryption = cJSON_GetObjectItem(udp, "encryption")->valuestring; - // ESP_LOGI(TAG, "UDP server: %s, port: %d, encryption: %s", udp_server_.c_str(), udp_port_, encryption); - aes_nonce_ = DecodeHexString(nonce); - mbedtls_aes_init(&aes_ctx_); - mbedtls_aes_setkey_enc(&aes_ctx_, (const unsigned char*)DecodeHexString(key).c_str(), 128); - local_sequence_ = 0; - remote_sequence_ = 0; - xEventGroupSetBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT); -} - -static const char hex_chars[] = "0123456789ABCDEF"; -// 辅助函数,将单个十六进制字符转换为对应的数值 -static inline uint8_t CharToHex(char c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - return 0; // 对于无效输入,返回0 -} - -std::string MqttProtocol::DecodeHexString(const std::string& hex_string) { - std::string decoded; - decoded.reserve(hex_string.size() / 2); - for (size_t i = 0; i < hex_string.size(); i += 2) { - char byte = (CharToHex(hex_string[i]) << 4) | CharToHex(hex_string[i + 1]); - decoded.push_back(byte); - } - return decoded; -} - -bool MqttProtocol::IsAudioChannelOpened() const { - return udp_ != nullptr && !error_occurred_ && !IsTimeout(); -} +#include "mqtt_protocol.h" +#include "board.h" +#include "application.h" +#include "settings.h" + +#include +#include +#include +#include "assets/lang_config.h" + +#define TAG "MQTT" + +MqttProtocol::MqttProtocol() { + event_group_handle_ = xEventGroupCreate(); + + // Initialize reconnect timer + esp_timer_create_args_t reconnect_timer_args = { + .callback = [](void* arg) { + MqttProtocol* protocol = (MqttProtocol*)arg; + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + ESP_LOGI(TAG, "Reconnecting to MQTT server"); + app.Schedule([protocol]() { + protocol->StartMqttClient(false); + }); + } + }, + .arg = this, + }; + esp_timer_create(&reconnect_timer_args, &reconnect_timer_); +} + +MqttProtocol::~MqttProtocol() { + ESP_LOGI(TAG, "MqttProtocol deinit"); + if (reconnect_timer_ != nullptr) { + esp_timer_stop(reconnect_timer_); + esp_timer_delete(reconnect_timer_); + } + + udp_.reset(); + mqtt_.reset(); + + if (event_group_handle_ != nullptr) { + vEventGroupDelete(event_group_handle_); + } +} + +bool MqttProtocol::Start() { + return StartMqttClient(false); +} + +bool MqttProtocol::StartMqttClient(bool report_error) { + if (mqtt_ != nullptr) { + ESP_LOGW(TAG, "Mqtt client already started"); + mqtt_.reset(); + } + + Settings settings("mqtt", false); + auto endpoint = settings.GetString("endpoint"); + auto client_id = settings.GetString("client_id"); + auto username = settings.GetString("username"); + auto password = settings.GetString("password"); + int keepalive_interval = settings.GetInt("keepalive", 240); + publish_topic_ = settings.GetString("publish_topic"); + + if (endpoint.empty()) { + ESP_LOGW(TAG, "MQTT endpoint is not specified"); + if (report_error) { + SetError(Lang::Strings::SERVER_NOT_FOUND); + } + return false; + } + + auto network = Board::GetInstance().GetNetwork(); + mqtt_ = network->CreateMqtt(0); + mqtt_->SetKeepAlive(keepalive_interval); + + mqtt_->OnDisconnected([this]() { + if (on_disconnected_ != nullptr) { + on_disconnected_(); + } + ESP_LOGI(TAG, "MQTT disconnected, schedule reconnect in %d seconds", MQTT_RECONNECT_INTERVAL_MS / 1000); + esp_timer_start_once(reconnect_timer_, MQTT_RECONNECT_INTERVAL_MS * 1000); + }); + + mqtt_->OnConnected([this]() { + if (on_connected_ != nullptr) { + on_connected_(); + } + esp_timer_stop(reconnect_timer_); + }); + + mqtt_->OnMessage([this](const std::string& topic, const std::string& payload) { + cJSON* root = cJSON_Parse(payload.c_str()); + if (root == nullptr) { + ESP_LOGE(TAG, "Failed to parse json message %s", payload.c_str()); + return; + } + cJSON* type = cJSON_GetObjectItem(root, "type"); + if (!cJSON_IsString(type)) { + ESP_LOGE(TAG, "Message type is invalid"); + cJSON_Delete(root); + return; + } + + if (strcmp(type->valuestring, "hello") == 0) { + ParseServerHello(root); + } else if (strcmp(type->valuestring, "goodbye") == 0) { + auto session_id = cJSON_GetObjectItem(root, "session_id"); + ESP_LOGI(TAG, "Received goodbye message, session_id: %s", session_id ? session_id->valuestring : "null"); + if (session_id == nullptr || session_id_ == session_id->valuestring) { + Application::GetInstance().Schedule([this]() { + CloseAudioChannel(); + }); + } + } else if (on_incoming_json_ != nullptr) { + on_incoming_json_(root); + } + cJSON_Delete(root); + last_incoming_time_ = std::chrono::steady_clock::now(); + }); + + ESP_LOGI(TAG, "Connecting to endpoint %s", endpoint.c_str()); + std::string broker_address; + int broker_port = 8883; + size_t pos = endpoint.find(':'); + if (pos != std::string::npos) { + broker_address = endpoint.substr(0, pos); + broker_port = std::stoi(endpoint.substr(pos + 1)); + } else { + broker_address = endpoint; + } + if (!mqtt_->Connect(broker_address, broker_port, client_id, username, password)) { + ESP_LOGE(TAG, "Failed to connect to endpoint"); + SetError(Lang::Strings::SERVER_NOT_CONNECTED); + return false; + } + + ESP_LOGI(TAG, "Connected to endpoint"); + return true; +} + +bool MqttProtocol::SendText(const std::string& text) { + if (publish_topic_.empty()) { + return false; + } + if (!mqtt_->Publish(publish_topic_, text)) { + ESP_LOGE(TAG, "Failed to publish message: %s", text.c_str()); + SetError(Lang::Strings::SERVER_ERROR); + return false; + } + return true; +} + +bool MqttProtocol::SendAudio(std::unique_ptr packet) { + std::lock_guard lock(channel_mutex_); + if (udp_ == nullptr) { + return false; + } + + std::string nonce(aes_nonce_); + *(uint16_t*)&nonce[2] = htons(packet->payload.size()); + *(uint32_t*)&nonce[8] = htonl(packet->timestamp); + *(uint32_t*)&nonce[12] = htonl(++local_sequence_); + + std::string encrypted; + encrypted.resize(aes_nonce_.size() + packet->payload.size()); + memcpy(encrypted.data(), nonce.data(), nonce.size()); + + size_t nc_off = 0; + uint8_t stream_block[16] = {0}; + if (mbedtls_aes_crypt_ctr(&aes_ctx_, packet->payload.size(), &nc_off, (uint8_t*)nonce.c_str(), stream_block, + (uint8_t*)packet->payload.data(), (uint8_t*)&encrypted[nonce.size()]) != 0) { + ESP_LOGE(TAG, "Failed to encrypt audio data"); + return false; + } + + return udp_->Send(encrypted) > 0; +} + +void MqttProtocol::CloseAudioChannel() { + { + std::lock_guard lock(channel_mutex_); + udp_.reset(); + } + + std::string message = "{"; + message += "\"session_id\":\"" + session_id_ + "\","; + message += "\"type\":\"goodbye\""; + message += "}"; + SendText(message); + + if (on_audio_channel_closed_ != nullptr) { + on_audio_channel_closed_(); + } +} + +bool MqttProtocol::OpenAudioChannel() { + if (mqtt_ == nullptr || !mqtt_->IsConnected()) { + ESP_LOGI(TAG, "MQTT is not connected, try to connect now"); + if (!StartMqttClient(true)) { + return false; + } + } + + error_occurred_ = false; + session_id_ = ""; + xEventGroupClearBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT); + + auto message = GetHelloMessage(); + if (!SendText(message)) { + return false; + } + + // 等待服务器响应 + EventBits_t bits = xEventGroupWaitBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000)); + if (!(bits & MQTT_PROTOCOL_SERVER_HELLO_EVENT)) { + ESP_LOGE(TAG, "Failed to receive server hello"); + SetError(Lang::Strings::SERVER_TIMEOUT); + return false; + } + + std::lock_guard lock(channel_mutex_); + auto network = Board::GetInstance().GetNetwork(); + udp_ = network->CreateUdp(2); + udp_->OnMessage([this](const std::string& data) { + /* + * UDP Encrypted OPUS Packet Format: + * |type 1u|flags 1u|payload_len 2u|ssrc 4u|timestamp 4u|sequence 4u| + * |payload payload_len| + */ + if (data.size() < sizeof(aes_nonce_)) { + ESP_LOGE(TAG, "Invalid audio packet size: %u", data.size()); + return; + } + if (data[0] != 0x01) { + ESP_LOGE(TAG, "Invalid audio packet type: %x", data[0]); + return; + } + uint32_t timestamp = ntohl(*(uint32_t*)&data[8]); + uint32_t sequence = ntohl(*(uint32_t*)&data[12]); + if (sequence < remote_sequence_) { + ESP_LOGW(TAG, "Received audio packet with old sequence: %lu, expected: %lu", sequence, remote_sequence_); + return; + } + if (sequence != remote_sequence_ + 1) { + ESP_LOGW(TAG, "Received audio packet with wrong sequence: %lu, expected: %lu", sequence, remote_sequence_ + 1); + } + + size_t decrypted_size = data.size() - aes_nonce_.size(); + size_t nc_off = 0; + uint8_t stream_block[16] = {0}; + auto nonce = (uint8_t*)data.data(); + auto encrypted = (uint8_t*)data.data() + aes_nonce_.size(); + auto packet = std::make_unique(); + packet->sample_rate = server_sample_rate_; + packet->frame_duration = server_frame_duration_; + packet->timestamp = timestamp; + packet->payload.resize(decrypted_size); + int ret = mbedtls_aes_crypt_ctr(&aes_ctx_, decrypted_size, &nc_off, nonce, stream_block, encrypted, (uint8_t*)packet->payload.data()); + if (ret != 0) { + ESP_LOGE(TAG, "Failed to decrypt audio data, ret: %d", ret); + return; + } + if (on_incoming_audio_ != nullptr) { + on_incoming_audio_(std::move(packet)); + } + remote_sequence_ = sequence; + last_incoming_time_ = std::chrono::steady_clock::now(); + }); + + udp_->Connect(udp_server_, udp_port_); + + if (on_audio_channel_opened_ != nullptr) { + on_audio_channel_opened_(); + } + return true; +} + +std::string MqttProtocol::GetHelloMessage() { + // 发送 hello 消息申请 UDP 通道 + cJSON* root = cJSON_CreateObject(); + cJSON_AddStringToObject(root, "type", "hello"); + cJSON_AddNumberToObject(root, "version", 3); + cJSON_AddStringToObject(root, "transport", "udp"); + cJSON* features = cJSON_CreateObject(); +#if CONFIG_USE_SERVER_AEC + cJSON_AddBoolToObject(features, "aec", true); +#endif + cJSON_AddBoolToObject(features, "mcp", true); + cJSON_AddItemToObject(root, "features", features); + cJSON* audio_params = cJSON_CreateObject(); + cJSON_AddStringToObject(audio_params, "format", "opus"); + cJSON_AddNumberToObject(audio_params, "sample_rate", 16000); + cJSON_AddNumberToObject(audio_params, "channels", 1); + cJSON_AddNumberToObject(audio_params, "frame_duration", OPUS_FRAME_DURATION_MS); + cJSON_AddItemToObject(root, "audio_params", audio_params); + auto json_str = cJSON_PrintUnformatted(root); + std::string message(json_str); + cJSON_free(json_str); + cJSON_Delete(root); + return message; +} + +void MqttProtocol::ParseServerHello(const cJSON* root) { + auto transport = cJSON_GetObjectItem(root, "transport"); + if (transport == nullptr || strcmp(transport->valuestring, "udp") != 0) { + ESP_LOGE(TAG, "Unsupported transport: %s", transport->valuestring); + return; + } + + auto session_id = cJSON_GetObjectItem(root, "session_id"); + if (cJSON_IsString(session_id)) { + session_id_ = session_id->valuestring; + ESP_LOGI(TAG, "Session ID: %s", session_id_.c_str()); + } + + // Get sample rate from hello message + auto audio_params = cJSON_GetObjectItem(root, "audio_params"); + if (cJSON_IsObject(audio_params)) { + auto sample_rate = cJSON_GetObjectItem(audio_params, "sample_rate"); + if (cJSON_IsNumber(sample_rate)) { + server_sample_rate_ = sample_rate->valueint; + } + auto frame_duration = cJSON_GetObjectItem(audio_params, "frame_duration"); + if (cJSON_IsNumber(frame_duration)) { + server_frame_duration_ = frame_duration->valueint; + } + } + + auto udp = cJSON_GetObjectItem(root, "udp"); + if (!cJSON_IsObject(udp)) { + ESP_LOGE(TAG, "UDP is not specified"); + return; + } + udp_server_ = cJSON_GetObjectItem(udp, "server")->valuestring; + udp_port_ = cJSON_GetObjectItem(udp, "port")->valueint; + auto key = cJSON_GetObjectItem(udp, "key")->valuestring; + auto nonce = cJSON_GetObjectItem(udp, "nonce")->valuestring; + + // auto encryption = cJSON_GetObjectItem(udp, "encryption")->valuestring; + // ESP_LOGI(TAG, "UDP server: %s, port: %d, encryption: %s", udp_server_.c_str(), udp_port_, encryption); + aes_nonce_ = DecodeHexString(nonce); + mbedtls_aes_init(&aes_ctx_); + mbedtls_aes_setkey_enc(&aes_ctx_, (const unsigned char*)DecodeHexString(key).c_str(), 128); + local_sequence_ = 0; + remote_sequence_ = 0; + xEventGroupSetBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT); +} + +static const char hex_chars[] = "0123456789ABCDEF"; +// 辅助函数,将单个十六进制字符转换为对应的数值 +static inline uint8_t CharToHex(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + return 0; // 对于无效输入,返回0 +} + +std::string MqttProtocol::DecodeHexString(const std::string& hex_string) { + std::string decoded; + decoded.reserve(hex_string.size() / 2); + for (size_t i = 0; i < hex_string.size(); i += 2) { + char byte = (CharToHex(hex_string[i]) << 4) | CharToHex(hex_string[i + 1]); + decoded.push_back(byte); + } + return decoded; +} + +bool MqttProtocol::IsAudioChannelOpened() const { + return udp_ != nullptr && !error_occurred_ && !IsTimeout(); +} diff --git a/main/protocols/mqtt_protocol.h b/main/protocols/mqtt_protocol.h index 0b7b20a..2ab2098 100644 --- a/main/protocols/mqtt_protocol.h +++ b/main/protocols/mqtt_protocol.h @@ -1,60 +1,60 @@ -#ifndef MQTT_PROTOCOL_H -#define MQTT_PROTOCOL_H - - -#include "protocol.h" -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#define MQTT_PING_INTERVAL_SECONDS 90 -#define MQTT_RECONNECT_INTERVAL_MS 60000 - -#define MQTT_PROTOCOL_SERVER_HELLO_EVENT (1 << 0) - -class MqttProtocol : public Protocol { -public: - MqttProtocol(); - ~MqttProtocol(); - - bool Start() override; - bool SendAudio(std::unique_ptr packet) override; - bool OpenAudioChannel() override; - void CloseAudioChannel() override; - bool IsAudioChannelOpened() const override; - -private: - EventGroupHandle_t event_group_handle_; - - std::string publish_topic_; - - std::mutex channel_mutex_; - std::unique_ptr mqtt_; - std::unique_ptr udp_; - mbedtls_aes_context aes_ctx_; - std::string aes_nonce_; - std::string udp_server_; - int udp_port_; - uint32_t local_sequence_; - uint32_t remote_sequence_; - esp_timer_handle_t reconnect_timer_; - - bool StartMqttClient(bool report_error=false); - void ParseServerHello(const cJSON* root); - std::string DecodeHexString(const std::string& hex_string); - - bool SendText(const std::string& text) override; - std::string GetHelloMessage(); -}; - - -#endif // MQTT_PROTOCOL_H +#ifndef MQTT_PROTOCOL_H +#define MQTT_PROTOCOL_H + + +#include "protocol.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MQTT_PING_INTERVAL_SECONDS 90 +#define MQTT_RECONNECT_INTERVAL_MS 60000 + +#define MQTT_PROTOCOL_SERVER_HELLO_EVENT (1 << 0) + +class MqttProtocol : public Protocol { +public: + MqttProtocol(); + ~MqttProtocol(); + + bool Start() override; + bool SendAudio(std::unique_ptr packet) override; + bool OpenAudioChannel() override; + void CloseAudioChannel() override; + bool IsAudioChannelOpened() const override; + +private: + EventGroupHandle_t event_group_handle_; + + std::string publish_topic_; + + std::mutex channel_mutex_; + std::unique_ptr mqtt_; + std::unique_ptr udp_; + mbedtls_aes_context aes_ctx_; + std::string aes_nonce_; + std::string udp_server_; + int udp_port_; + uint32_t local_sequence_; + uint32_t remote_sequence_; + esp_timer_handle_t reconnect_timer_; + + bool StartMqttClient(bool report_error=false); + void ParseServerHello(const cJSON* root); + std::string DecodeHexString(const std::string& hex_string); + + bool SendText(const std::string& text) override; + std::string GetHelloMessage(); +}; + + +#endif // MQTT_PROTOCOL_H diff --git a/main/protocols/protocol.cc b/main/protocols/protocol.cc index 470cc91..bb30d86 100644 --- a/main/protocols/protocol.cc +++ b/main/protocols/protocol.cc @@ -1,90 +1,90 @@ -#include "protocol.h" - -#include - -#define TAG "Protocol" - -void Protocol::OnIncomingJson(std::function callback) { - on_incoming_json_ = callback; -} - -void Protocol::OnIncomingAudio(std::function packet)> callback) { - on_incoming_audio_ = callback; -} - -void Protocol::OnAudioChannelOpened(std::function callback) { - on_audio_channel_opened_ = callback; -} - -void Protocol::OnAudioChannelClosed(std::function callback) { - on_audio_channel_closed_ = callback; -} - -void Protocol::OnNetworkError(std::function callback) { - on_network_error_ = callback; -} - -void Protocol::OnConnected(std::function callback) { - on_connected_ = callback; -} - -void Protocol::OnDisconnected(std::function callback) { - on_disconnected_ = callback; -} - -void Protocol::SetError(const std::string& message) { - error_occurred_ = true; - if (on_network_error_ != nullptr) { - on_network_error_(message); - } -} - -void Protocol::SendAbortSpeaking(AbortReason reason) { - std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"abort\""; - if (reason == kAbortReasonWakeWordDetected) { - message += ",\"reason\":\"wake_word_detected\""; - } - message += "}"; - SendText(message); -} - -void Protocol::SendWakeWordDetected(const std::string& wake_word) { - std::string json = "{\"session_id\":\"" + session_id_ + - "\",\"type\":\"listen\",\"state\":\"detect\",\"text\":\"" + wake_word + "\"}"; - SendText(json); -} - -void Protocol::SendStartListening(ListeningMode mode) { - std::string message = "{\"session_id\":\"" + session_id_ + "\""; - message += ",\"type\":\"listen\",\"state\":\"start\""; - if (mode == kListeningModeRealtime) { - message += ",\"mode\":\"realtime\""; - } else if (mode == kListeningModeAutoStop) { - message += ",\"mode\":\"auto\""; - } else { - message += ",\"mode\":\"manual\""; - } - message += "}"; - SendText(message); -} - -void Protocol::SendStopListening() { - std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"listen\",\"state\":\"stop\"}"; - SendText(message); -} - -void Protocol::SendMcpMessage(const std::string& payload) { - std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"mcp\",\"payload\":" + payload + "}"; - SendText(message); -} - -bool Protocol::IsTimeout() const { - const int kTimeoutSeconds = 120; - auto now = std::chrono::steady_clock::now(); - auto duration = std::chrono::duration_cast(now - last_incoming_time_); - bool timeout = duration.count() > kTimeoutSeconds; - if (timeout) { - ESP_LOGE(TAG, "Channel timeout %ld seconds", (long)duration.count()); - } - return timeout; -} +#include "protocol.h" + +#include + +#define TAG "Protocol" + +void Protocol::OnIncomingJson(std::function callback) { + on_incoming_json_ = callback; +} + +void Protocol::OnIncomingAudio(std::function packet)> callback) { + on_incoming_audio_ = callback; +} + +void Protocol::OnAudioChannelOpened(std::function callback) { + on_audio_channel_opened_ = callback; +} + +void Protocol::OnAudioChannelClosed(std::function callback) { + on_audio_channel_closed_ = callback; +} + +void Protocol::OnNetworkError(std::function callback) { + on_network_error_ = callback; +} + +void Protocol::OnConnected(std::function callback) { + on_connected_ = callback; +} + +void Protocol::OnDisconnected(std::function callback) { + on_disconnected_ = callback; +} + +void Protocol::SetError(const std::string& message) { + error_occurred_ = true; + if (on_network_error_ != nullptr) { + on_network_error_(message); + } +} + +void Protocol::SendAbortSpeaking(AbortReason reason) { + std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"abort\""; + if (reason == kAbortReasonWakeWordDetected) { + message += ",\"reason\":\"wake_word_detected\""; + } + message += "}"; + SendText(message); +} + +void Protocol::SendWakeWordDetected(const std::string& wake_word) { + std::string json = "{\"session_id\":\"" + session_id_ + + "\",\"type\":\"listen\",\"state\":\"detect\",\"text\":\"" + wake_word + "\"}"; + SendText(json); +} + +void Protocol::SendStartListening(ListeningMode mode) { + std::string message = "{\"session_id\":\"" + session_id_ + "\""; + message += ",\"type\":\"listen\",\"state\":\"start\""; + if (mode == kListeningModeRealtime) { + message += ",\"mode\":\"realtime\""; + } else if (mode == kListeningModeAutoStop) { + message += ",\"mode\":\"auto\""; + } else { + message += ",\"mode\":\"manual\""; + } + message += "}"; + SendText(message); +} + +void Protocol::SendStopListening() { + std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"listen\",\"state\":\"stop\"}"; + SendText(message); +} + +void Protocol::SendMcpMessage(const std::string& payload) { + std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"mcp\",\"payload\":" + payload + "}"; + SendText(message); +} + +bool Protocol::IsTimeout() const { + const int kTimeoutSeconds = 120; + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(now - last_incoming_time_); + bool timeout = duration.count() > kTimeoutSeconds; + if (timeout) { + ESP_LOGE(TAG, "Channel timeout %ld seconds", (long)duration.count()); + } + return timeout; +} diff --git a/main/protocols/protocol.h b/main/protocols/protocol.h index 17ab315..cfc3366 100644 --- a/main/protocols/protocol.h +++ b/main/protocols/protocol.h @@ -1,98 +1,98 @@ -#ifndef PROTOCOL_H -#define PROTOCOL_H - -#include -#include -#include -#include -#include - -struct AudioStreamPacket { - int sample_rate = 0; - int frame_duration = 0; - uint32_t timestamp = 0; - std::vector payload; -}; - -struct BinaryProtocol2 { - uint16_t version; - uint16_t type; // Message type (0: OPUS, 1: JSON) - uint32_t reserved; // Reserved for future use - uint32_t timestamp; // Timestamp in milliseconds (used for server-side AEC) - uint32_t payload_size; // Payload size in bytes - uint8_t payload[]; // Payload data -} __attribute__((packed)); - -struct BinaryProtocol3 { - uint8_t type; - uint8_t reserved; - uint16_t payload_size; - uint8_t payload[]; -} __attribute__((packed)); - -enum AbortReason { - kAbortReasonNone, - kAbortReasonWakeWordDetected -}; - -enum ListeningMode { - kListeningModeAutoStop, - kListeningModeManualStop, - kListeningModeRealtime // 需要 AEC 支持 -}; - -class Protocol { -public: - virtual ~Protocol() = default; - - inline int server_sample_rate() const { - return server_sample_rate_; - } - inline int server_frame_duration() const { - return server_frame_duration_; - } - inline const std::string& session_id() const { - return session_id_; - } - - void OnIncomingAudio(std::function packet)> callback); - void OnIncomingJson(std::function callback); - void OnAudioChannelOpened(std::function callback); - void OnAudioChannelClosed(std::function callback); - void OnNetworkError(std::function callback); - void OnConnected(std::function callback); - void OnDisconnected(std::function callback); - - virtual bool Start() = 0; - virtual bool OpenAudioChannel() = 0; - virtual void CloseAudioChannel() = 0; - virtual bool IsAudioChannelOpened() const = 0; - virtual bool SendAudio(std::unique_ptr packet) = 0; - virtual void SendWakeWordDetected(const std::string& wake_word); - virtual void SendStartListening(ListeningMode mode); - virtual void SendStopListening(); - virtual void SendAbortSpeaking(AbortReason reason); - virtual void SendMcpMessage(const std::string& message); - -protected: - std::function on_incoming_json_; - std::function packet)> on_incoming_audio_; - std::function on_audio_channel_opened_; - std::function on_audio_channel_closed_; - std::function on_network_error_; - std::function on_connected_; - std::function on_disconnected_; - - int server_sample_rate_ = 24000; - int server_frame_duration_ = 60; - bool error_occurred_ = false; - std::string session_id_; - std::chrono::time_point last_incoming_time_; - - virtual bool SendText(const std::string& text) = 0; - virtual void SetError(const std::string& message); - virtual bool IsTimeout() const; -}; - -#endif // PROTOCOL_H - +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include +#include +#include +#include +#include + +struct AudioStreamPacket { + int sample_rate = 0; + int frame_duration = 0; + uint32_t timestamp = 0; + std::vector payload; +}; + +struct BinaryProtocol2 { + uint16_t version; + uint16_t type; // Message type (0: OPUS, 1: JSON) + uint32_t reserved; // Reserved for future use + uint32_t timestamp; // Timestamp in milliseconds (used for server-side AEC) + uint32_t payload_size; // Payload size in bytes + uint8_t payload[]; // Payload data +} __attribute__((packed)); + +struct BinaryProtocol3 { + uint8_t type; + uint8_t reserved; + uint16_t payload_size; + uint8_t payload[]; +} __attribute__((packed)); + +enum AbortReason { + kAbortReasonNone, + kAbortReasonWakeWordDetected +}; + +enum ListeningMode { + kListeningModeAutoStop, + kListeningModeManualStop, + kListeningModeRealtime // 需要 AEC 支持 +}; + +class Protocol { +public: + virtual ~Protocol() = default; + + inline int server_sample_rate() const { + return server_sample_rate_; + } + inline int server_frame_duration() const { + return server_frame_duration_; + } + inline const std::string& session_id() const { + return session_id_; + } + + void OnIncomingAudio(std::function packet)> callback); + void OnIncomingJson(std::function callback); + void OnAudioChannelOpened(std::function callback); + void OnAudioChannelClosed(std::function callback); + void OnNetworkError(std::function callback); + void OnConnected(std::function callback); + void OnDisconnected(std::function callback); + + virtual bool Start() = 0; + virtual bool OpenAudioChannel() = 0; + virtual void CloseAudioChannel() = 0; + virtual bool IsAudioChannelOpened() const = 0; + virtual bool SendAudio(std::unique_ptr packet) = 0; + virtual void SendWakeWordDetected(const std::string& wake_word); + virtual void SendStartListening(ListeningMode mode); + virtual void SendStopListening(); + virtual void SendAbortSpeaking(AbortReason reason); + virtual void SendMcpMessage(const std::string& message); + +protected: + std::function on_incoming_json_; + std::function packet)> on_incoming_audio_; + std::function on_audio_channel_opened_; + std::function on_audio_channel_closed_; + std::function on_network_error_; + std::function on_connected_; + std::function on_disconnected_; + + int server_sample_rate_ = 24000; + int server_frame_duration_ = 60; + bool error_occurred_ = false; + std::string session_id_; + std::chrono::time_point last_incoming_time_; + + virtual bool SendText(const std::string& text) = 0; + virtual void SetError(const std::string& message); + virtual bool IsTimeout() const; +}; + +#endif // PROTOCOL_H + diff --git a/main/protocols/sleep_music_protocol.cc b/main/protocols/sleep_music_protocol.cc deleted file mode 100644 index 751b61d..0000000 --- a/main/protocols/sleep_music_protocol.cc +++ /dev/null @@ -1,105 +0,0 @@ -#include "sleep_music_protocol.h" -#include "board.h" -#include "application.h" -#include "protocol.h" - -#include -#include - -#define TAG "SleepMusic" - -SleepMusicProtocol& SleepMusicProtocol::GetInstance() { - static SleepMusicProtocol instance; - return instance; -} - -SleepMusicProtocol::SleepMusicProtocol() { - event_group_handle_ = xEventGroupCreate(); -} - -SleepMusicProtocol::~SleepMusicProtocol() { - vEventGroupDelete(event_group_handle_); -} - -bool SleepMusicProtocol::IsAudioChannelOpened() const { - return is_connected_ && websocket_ != nullptr && websocket_->IsConnected(); -} - -void SleepMusicProtocol::CloseAudioChannel() { - if (websocket_) { - ESP_LOGI(TAG, "Closing sleep music audio channel"); - - // 清理状态 - is_connected_ = false; - - // 关闭WebSocket连接 - websocket_.reset(); - - ESP_LOGI(TAG, "Sleep music audio channel closed"); - } -} - -bool SleepMusicProtocol::OpenAudioChannel() { - std::string url = "ws://180.76.190.230:8765"; - - ESP_LOGI(TAG, "Connecting to sleep music server: %s", url.c_str()); - - auto network = Board::GetInstance().GetNetwork(); - websocket_ = network->CreateWebSocket(2); // 使用不同的WebSocket实例ID - if (websocket_ == nullptr) { - ESP_LOGE(TAG, "Failed to create websocket for sleep music"); - return false; - } - - // 设置WebSocket数据接收回调 - websocket_->OnData([this](const char* data, size_t len, bool binary) { - if (binary) { - // 接收到的二进制数据是OPUS编码的音频帧 - OnAudioDataReceived(data, len); - } else { - ESP_LOGW(TAG, "Received non-binary data from sleep music server, ignoring"); - } - }); - - websocket_->OnDisconnected([this]() { - ESP_LOGI(TAG, "Sleep music websocket disconnected"); - }); - - // 连接到睡眠音乐服务器 - if (!websocket_->Connect(url.c_str())) { - ESP_LOGE(TAG, "Failed to connect to sleep music server"); - return false; - } - - // 设置连接成功事件 - xEventGroupSetBits(event_group_handle_, SLEEP_MUSIC_PROTOCOL_CONNECTED_EVENT); - - ESP_LOGI(TAG, "Successfully connected to sleep music server"); - is_connected_ = true; - return true; -} - -void SleepMusicProtocol::OnAudioDataReceived(const char* data, size_t len) { - if (len == 0) { - ESP_LOGW(TAG, "Received empty audio data"); - return; - } - - ESP_LOGD(TAG, "Received audio frame: %zu bytes", len); - - // 创建AudioStreamPacket - auto packet = std::make_unique(); - packet->sample_rate = SAMPLE_RATE; - packet->frame_duration = FRAME_DURATION_MS; - packet->timestamp = 0; // 睡眠音乐不需要时间戳同步 - packet->payload.resize(len); - std::memcpy(packet->payload.data(), data, len); - - // 将音频包推入解码队列 - auto& app = Application::GetInstance(); - auto& audio_service = app.GetAudioService(); - - if (!audio_service.PushPacketToDecodeQueue(std::move(packet), false)) { - ESP_LOGW(TAG, "Audio decode queue is full, dropping packet"); - } -} \ No newline at end of file diff --git a/main/protocols/sleep_music_protocol.h b/main/protocols/sleep_music_protocol.h deleted file mode 100644 index fc6fd6d..0000000 --- a/main/protocols/sleep_music_protocol.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef _SLEEP_MUSIC_PROTOCOL_H_ -#define _SLEEP_MUSIC_PROTOCOL_H_ - -#include -#include -#include -#include - -#define SLEEP_MUSIC_PROTOCOL_CONNECTED_EVENT (1 << 0) - -class SleepMusicProtocol { -public: - static SleepMusicProtocol& GetInstance(); - - bool OpenAudioChannel(); - void CloseAudioChannel(); - bool IsAudioChannelOpened() const; - -private: - SleepMusicProtocol(); - ~SleepMusicProtocol(); - - EventGroupHandle_t event_group_handle_; - std::unique_ptr websocket_; - bool is_connected_ = false; - - // 睡眠音乐服务器配置 - static constexpr int SAMPLE_RATE = 24000; // 24kHz - static constexpr int CHANNELS = 2; // 立体声 - static constexpr int FRAME_DURATION_MS = 60; // 60ms帧时长 - - void OnAudioDataReceived(const char* data, size_t len); -}; - -#endif \ No newline at end of file diff --git a/main/protocols/websocket_protocol.cc b/main/protocols/websocket_protocol.cc index d4f435c..0b32a4b 100644 --- a/main/protocols/websocket_protocol.cc +++ b/main/protocols/websocket_protocol.cc @@ -1,253 +1,253 @@ -#include "websocket_protocol.h" -#include "board.h" -#include "system_info.h" -#include "application.h" -#include "settings.h" - -#include -#include -#include -#include -#include "assets/lang_config.h" - -#define TAG "WS" - -WebsocketProtocol::WebsocketProtocol() { - event_group_handle_ = xEventGroupCreate(); -} - -WebsocketProtocol::~WebsocketProtocol() { - vEventGroupDelete(event_group_handle_); -} - -bool WebsocketProtocol::Start() { - // Only connect to server when audio channel is needed - return true; -} - -bool WebsocketProtocol::SendAudio(std::unique_ptr packet) { - if (websocket_ == nullptr || !websocket_->IsConnected()) { - return false; - } - - if (version_ == 2) { - std::string serialized; - serialized.resize(sizeof(BinaryProtocol2) + packet->payload.size()); - auto bp2 = (BinaryProtocol2*)serialized.data(); - bp2->version = htons(version_); - bp2->type = 0; - bp2->reserved = 0; - bp2->timestamp = htonl(packet->timestamp); - bp2->payload_size = htonl(packet->payload.size()); - memcpy(bp2->payload, packet->payload.data(), packet->payload.size()); - - return websocket_->Send(serialized.data(), serialized.size(), true); - } else if (version_ == 3) { - std::string serialized; - serialized.resize(sizeof(BinaryProtocol3) + packet->payload.size()); - auto bp3 = (BinaryProtocol3*)serialized.data(); - bp3->type = 0; - bp3->reserved = 0; - bp3->payload_size = htons(packet->payload.size()); - memcpy(bp3->payload, packet->payload.data(), packet->payload.size()); - - return websocket_->Send(serialized.data(), serialized.size(), true); - } else { - return websocket_->Send(packet->payload.data(), packet->payload.size(), true); - } -} - -bool WebsocketProtocol::SendText(const std::string& text) { - if (websocket_ == nullptr || !websocket_->IsConnected()) { - return false; - } - - if (!websocket_->Send(text)) { - ESP_LOGE(TAG, "Failed to send text: %s", text.c_str()); - SetError(Lang::Strings::SERVER_ERROR); - return false; - } - - return true; -} - -bool WebsocketProtocol::IsAudioChannelOpened() const { - return websocket_ != nullptr && websocket_->IsConnected() && !error_occurred_ && !IsTimeout(); -} - -void WebsocketProtocol::CloseAudioChannel() { - websocket_.reset(); -} - -bool WebsocketProtocol::OpenAudioChannel() { - Settings settings("websocket", false); - std::string url = settings.GetString("url"); - std::string token = settings.GetString("token"); - int version = settings.GetInt("version"); - if (version != 0) { - version_ = version; - } - - error_occurred_ = false; - - auto network = Board::GetInstance().GetNetwork(); - websocket_ = network->CreateWebSocket(1); - if (websocket_ == nullptr) { - ESP_LOGE(TAG, "Failed to create websocket"); - return false; - } - - if (!token.empty()) { - // If token not has a space, add "Bearer " prefix - if (token.find(" ") == std::string::npos) { - token = "Bearer " + token; - } - websocket_->SetHeader("Authorization", token.c_str()); - } - websocket_->SetHeader("Protocol-Version", std::to_string(version_).c_str()); - websocket_->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); - websocket_->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str()); - - websocket_->OnData([this](const char* data, size_t len, bool binary) { - if (binary) { - if (on_incoming_audio_ != nullptr) { - if (version_ == 2) { - BinaryProtocol2* bp2 = (BinaryProtocol2*)data; - bp2->version = ntohs(bp2->version); - bp2->type = ntohs(bp2->type); - bp2->timestamp = ntohl(bp2->timestamp); - bp2->payload_size = ntohl(bp2->payload_size); - auto payload = (uint8_t*)bp2->payload; - on_incoming_audio_(std::make_unique(AudioStreamPacket{ - .sample_rate = server_sample_rate_, - .frame_duration = server_frame_duration_, - .timestamp = bp2->timestamp, - .payload = std::vector(payload, payload + bp2->payload_size) - })); - } else if (version_ == 3) { - BinaryProtocol3* bp3 = (BinaryProtocol3*)data; - bp3->type = bp3->type; - bp3->payload_size = ntohs(bp3->payload_size); - auto payload = (uint8_t*)bp3->payload; - on_incoming_audio_(std::make_unique(AudioStreamPacket{ - .sample_rate = server_sample_rate_, - .frame_duration = server_frame_duration_, - .timestamp = 0, - .payload = std::vector(payload, payload + bp3->payload_size) - })); - } else { - on_incoming_audio_(std::make_unique(AudioStreamPacket{ - .sample_rate = server_sample_rate_, - .frame_duration = server_frame_duration_, - .timestamp = 0, - .payload = std::vector((uint8_t*)data, (uint8_t*)data + len) - })); - } - } - } else { - // Parse JSON data - auto root = cJSON_Parse(data); - auto type = cJSON_GetObjectItem(root, "type"); - if (cJSON_IsString(type)) { - if (strcmp(type->valuestring, "hello") == 0) { - ParseServerHello(root); - } else { - if (on_incoming_json_ != nullptr) { - on_incoming_json_(root); - } - } - } else { - ESP_LOGE(TAG, "Missing message type, data: %s", data); - } - cJSON_Delete(root); - } - last_incoming_time_ = std::chrono::steady_clock::now(); - }); - - websocket_->OnDisconnected([this]() { - ESP_LOGI(TAG, "Websocket disconnected"); - if (on_audio_channel_closed_ != nullptr) { - on_audio_channel_closed_(); - } - }); - - ESP_LOGI(TAG, "Connecting to websocket server: %s with version: %d", url.c_str(), version_); - if (!websocket_->Connect(url.c_str())) { - ESP_LOGE(TAG, "Failed to connect to websocket server"); - SetError(Lang::Strings::SERVER_NOT_CONNECTED); - return false; - } - - // Send hello message to describe the client - auto message = GetHelloMessage(); - if (!SendText(message)) { - return false; - } - - // Wait for server hello - EventBits_t bits = xEventGroupWaitBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000)); - if (!(bits & WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT)) { - ESP_LOGE(TAG, "Failed to receive server hello"); - SetError(Lang::Strings::SERVER_TIMEOUT); - return false; - } - - if (on_audio_channel_opened_ != nullptr) { - on_audio_channel_opened_(); - } - - return true; -} - -std::string WebsocketProtocol::GetHelloMessage() { - // keys: message type, version, audio_params (format, sample_rate, channels) - cJSON* root = cJSON_CreateObject(); - cJSON_AddStringToObject(root, "type", "hello"); - cJSON_AddNumberToObject(root, "version", version_); - cJSON* features = cJSON_CreateObject(); -#if CONFIG_USE_SERVER_AEC - cJSON_AddBoolToObject(features, "aec", true); -#endif - cJSON_AddBoolToObject(features, "mcp", true); - cJSON_AddItemToObject(root, "features", features); - cJSON_AddStringToObject(root, "transport", "websocket"); - cJSON* audio_params = cJSON_CreateObject(); - cJSON_AddStringToObject(audio_params, "format", "opus"); - cJSON_AddNumberToObject(audio_params, "sample_rate", 16000); - cJSON_AddNumberToObject(audio_params, "channels", 1); - cJSON_AddNumberToObject(audio_params, "frame_duration", OPUS_FRAME_DURATION_MS); - cJSON_AddItemToObject(root, "audio_params", audio_params); - auto json_str = cJSON_PrintUnformatted(root); - std::string message(json_str); - cJSON_free(json_str); - cJSON_Delete(root); - return message; -} - -void WebsocketProtocol::ParseServerHello(const cJSON* root) { - auto transport = cJSON_GetObjectItem(root, "transport"); - if (transport == nullptr || strcmp(transport->valuestring, "websocket") != 0) { - ESP_LOGE(TAG, "Unsupported transport: %s", transport->valuestring); - return; - } - - auto session_id = cJSON_GetObjectItem(root, "session_id"); - if (cJSON_IsString(session_id)) { - session_id_ = session_id->valuestring; - ESP_LOGI(TAG, "Session ID: %s", session_id_.c_str()); - } - - auto audio_params = cJSON_GetObjectItem(root, "audio_params"); - if (cJSON_IsObject(audio_params)) { - auto sample_rate = cJSON_GetObjectItem(audio_params, "sample_rate"); - if (cJSON_IsNumber(sample_rate)) { - server_sample_rate_ = sample_rate->valueint; - } - auto frame_duration = cJSON_GetObjectItem(audio_params, "frame_duration"); - if (cJSON_IsNumber(frame_duration)) { - server_frame_duration_ = frame_duration->valueint; - } - } - - xEventGroupSetBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT); -} +#include "websocket_protocol.h" +#include "board.h" +#include "system_info.h" +#include "application.h" +#include "settings.h" + +#include +#include +#include +#include +#include "assets/lang_config.h" + +#define TAG "WS" + +WebsocketProtocol::WebsocketProtocol() { + event_group_handle_ = xEventGroupCreate(); +} + +WebsocketProtocol::~WebsocketProtocol() { + vEventGroupDelete(event_group_handle_); +} + +bool WebsocketProtocol::Start() { + // Only connect to server when audio channel is needed + return true; +} + +bool WebsocketProtocol::SendAudio(std::unique_ptr packet) { + if (websocket_ == nullptr || !websocket_->IsConnected()) { + return false; + } + + if (version_ == 2) { + std::string serialized; + serialized.resize(sizeof(BinaryProtocol2) + packet->payload.size()); + auto bp2 = (BinaryProtocol2*)serialized.data(); + bp2->version = htons(version_); + bp2->type = 0; + bp2->reserved = 0; + bp2->timestamp = htonl(packet->timestamp); + bp2->payload_size = htonl(packet->payload.size()); + memcpy(bp2->payload, packet->payload.data(), packet->payload.size()); + + return websocket_->Send(serialized.data(), serialized.size(), true); + } else if (version_ == 3) { + std::string serialized; + serialized.resize(sizeof(BinaryProtocol3) + packet->payload.size()); + auto bp3 = (BinaryProtocol3*)serialized.data(); + bp3->type = 0; + bp3->reserved = 0; + bp3->payload_size = htons(packet->payload.size()); + memcpy(bp3->payload, packet->payload.data(), packet->payload.size()); + + return websocket_->Send(serialized.data(), serialized.size(), true); + } else { + return websocket_->Send(packet->payload.data(), packet->payload.size(), true); + } +} + +bool WebsocketProtocol::SendText(const std::string& text) { + if (websocket_ == nullptr || !websocket_->IsConnected()) { + return false; + } + + if (!websocket_->Send(text)) { + ESP_LOGE(TAG, "Failed to send text: %s", text.c_str()); + SetError(Lang::Strings::SERVER_ERROR); + return false; + } + + return true; +} + +bool WebsocketProtocol::IsAudioChannelOpened() const { + return websocket_ != nullptr && websocket_->IsConnected() && !error_occurred_ && !IsTimeout(); +} + +void WebsocketProtocol::CloseAudioChannel() { + websocket_.reset(); +} + +bool WebsocketProtocol::OpenAudioChannel() { + Settings settings("websocket", false); + std::string url = settings.GetString("url"); + std::string token = settings.GetString("token"); + int version = settings.GetInt("version"); + if (version != 0) { + version_ = version; + } + + error_occurred_ = false; + + auto network = Board::GetInstance().GetNetwork(); + websocket_ = network->CreateWebSocket(1); + if (websocket_ == nullptr) { + ESP_LOGE(TAG, "Failed to create websocket"); + return false; + } + + if (!token.empty()) { + // If token not has a space, add "Bearer " prefix + if (token.find(" ") == std::string::npos) { + token = "Bearer " + token; + } + websocket_->SetHeader("Authorization", token.c_str()); + } + websocket_->SetHeader("Protocol-Version", std::to_string(version_).c_str()); + websocket_->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); + websocket_->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str()); + + websocket_->OnData([this](const char* data, size_t len, bool binary) { + if (binary) { + if (on_incoming_audio_ != nullptr) { + if (version_ == 2) { + BinaryProtocol2* bp2 = (BinaryProtocol2*)data; + bp2->version = ntohs(bp2->version); + bp2->type = ntohs(bp2->type); + bp2->timestamp = ntohl(bp2->timestamp); + bp2->payload_size = ntohl(bp2->payload_size); + auto payload = (uint8_t*)bp2->payload; + on_incoming_audio_(std::make_unique(AudioStreamPacket{ + .sample_rate = server_sample_rate_, + .frame_duration = server_frame_duration_, + .timestamp = bp2->timestamp, + .payload = std::vector(payload, payload + bp2->payload_size) + })); + } else if (version_ == 3) { + BinaryProtocol3* bp3 = (BinaryProtocol3*)data; + bp3->type = bp3->type; + bp3->payload_size = ntohs(bp3->payload_size); + auto payload = (uint8_t*)bp3->payload; + on_incoming_audio_(std::make_unique(AudioStreamPacket{ + .sample_rate = server_sample_rate_, + .frame_duration = server_frame_duration_, + .timestamp = 0, + .payload = std::vector(payload, payload + bp3->payload_size) + })); + } else { + on_incoming_audio_(std::make_unique(AudioStreamPacket{ + .sample_rate = server_sample_rate_, + .frame_duration = server_frame_duration_, + .timestamp = 0, + .payload = std::vector((uint8_t*)data, (uint8_t*)data + len) + })); + } + } + } else { + // Parse JSON data + auto root = cJSON_Parse(data); + auto type = cJSON_GetObjectItem(root, "type"); + if (cJSON_IsString(type)) { + if (strcmp(type->valuestring, "hello") == 0) { + ParseServerHello(root); + } else { + if (on_incoming_json_ != nullptr) { + on_incoming_json_(root); + } + } + } else { + ESP_LOGE(TAG, "Missing message type, data: %s", data); + } + cJSON_Delete(root); + } + last_incoming_time_ = std::chrono::steady_clock::now(); + }); + + websocket_->OnDisconnected([this]() { + ESP_LOGI(TAG, "Websocket disconnected"); + if (on_audio_channel_closed_ != nullptr) { + on_audio_channel_closed_(); + } + }); + + ESP_LOGI(TAG, "Connecting to websocket server: %s with version: %d", url.c_str(), version_); + if (!websocket_->Connect(url.c_str())) { + ESP_LOGE(TAG, "Failed to connect to websocket server"); + SetError(Lang::Strings::SERVER_NOT_CONNECTED); + return false; + } + + // Send hello message to describe the client + auto message = GetHelloMessage(); + if (!SendText(message)) { + return false; + } + + // Wait for server hello + EventBits_t bits = xEventGroupWaitBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000)); + if (!(bits & WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT)) { + ESP_LOGE(TAG, "Failed to receive server hello"); + SetError(Lang::Strings::SERVER_TIMEOUT); + return false; + } + + if (on_audio_channel_opened_ != nullptr) { + on_audio_channel_opened_(); + } + + return true; +} + +std::string WebsocketProtocol::GetHelloMessage() { + // keys: message type, version, audio_params (format, sample_rate, channels) + cJSON* root = cJSON_CreateObject(); + cJSON_AddStringToObject(root, "type", "hello"); + cJSON_AddNumberToObject(root, "version", version_); + cJSON* features = cJSON_CreateObject(); +#if CONFIG_USE_SERVER_AEC + cJSON_AddBoolToObject(features, "aec", true); +#endif + cJSON_AddBoolToObject(features, "mcp", true); + cJSON_AddItemToObject(root, "features", features); + cJSON_AddStringToObject(root, "transport", "websocket"); + cJSON* audio_params = cJSON_CreateObject(); + cJSON_AddStringToObject(audio_params, "format", "opus"); + cJSON_AddNumberToObject(audio_params, "sample_rate", 16000); + cJSON_AddNumberToObject(audio_params, "channels", 1); + cJSON_AddNumberToObject(audio_params, "frame_duration", OPUS_FRAME_DURATION_MS); + cJSON_AddItemToObject(root, "audio_params", audio_params); + auto json_str = cJSON_PrintUnformatted(root); + std::string message(json_str); + cJSON_free(json_str); + cJSON_Delete(root); + return message; +} + +void WebsocketProtocol::ParseServerHello(const cJSON* root) { + auto transport = cJSON_GetObjectItem(root, "transport"); + if (transport == nullptr || strcmp(transport->valuestring, "websocket") != 0) { + ESP_LOGE(TAG, "Unsupported transport: %s", transport->valuestring); + return; + } + + auto session_id = cJSON_GetObjectItem(root, "session_id"); + if (cJSON_IsString(session_id)) { + session_id_ = session_id->valuestring; + ESP_LOGI(TAG, "Session ID: %s", session_id_.c_str()); + } + + auto audio_params = cJSON_GetObjectItem(root, "audio_params"); + if (cJSON_IsObject(audio_params)) { + auto sample_rate = cJSON_GetObjectItem(audio_params, "sample_rate"); + if (cJSON_IsNumber(sample_rate)) { + server_sample_rate_ = sample_rate->valueint; + } + auto frame_duration = cJSON_GetObjectItem(audio_params, "frame_duration"); + if (cJSON_IsNumber(frame_duration)) { + server_frame_duration_ = frame_duration->valueint; + } + } + + xEventGroupSetBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT); +} diff --git a/main/protocols/websocket_protocol.h b/main/protocols/websocket_protocol.h index 8c7dd65..bd579fb 100644 --- a/main/protocols/websocket_protocol.h +++ b/main/protocols/websocket_protocol.h @@ -1,34 +1,34 @@ -#ifndef _WEBSOCKET_PROTOCOL_H_ -#define _WEBSOCKET_PROTOCOL_H_ - - -#include "protocol.h" - -#include -#include -#include - -#define WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT (1 << 0) - -class WebsocketProtocol : public Protocol { -public: - WebsocketProtocol(); - ~WebsocketProtocol(); - - bool Start() override; - bool SendAudio(std::unique_ptr packet) override; - bool OpenAudioChannel() override; - void CloseAudioChannel() override; - bool IsAudioChannelOpened() const override; - -private: - EventGroupHandle_t event_group_handle_; - std::unique_ptr websocket_; - int version_ = 1; - - void ParseServerHello(const cJSON* root); - bool SendText(const std::string& text) override; - std::string GetHelloMessage(); -}; - -#endif +#ifndef _WEBSOCKET_PROTOCOL_H_ +#define _WEBSOCKET_PROTOCOL_H_ + + +#include "protocol.h" + +#include +#include +#include + +#define WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT (1 << 0) + +class WebsocketProtocol : public Protocol { +public: + WebsocketProtocol(); + ~WebsocketProtocol(); + + bool Start() override; + bool SendAudio(std::unique_ptr packet) override; + bool OpenAudioChannel() override; + void CloseAudioChannel() override; + bool IsAudioChannelOpened() const override; + +private: + EventGroupHandle_t event_group_handle_; + std::unique_ptr websocket_; + int version_ = 1; + + void ParseServerHello(const cJSON* root); + bool SendText(const std::string& text) override; + std::string GetHelloMessage(); +}; + +#endif diff --git a/main/schedule_manager.cc b/main/schedule_manager.cc deleted file mode 100644 index e51e16a..0000000 --- a/main/schedule_manager.cc +++ /dev/null @@ -1,409 +0,0 @@ -#include "schedule_manager.h" -#include -#include -#include -#include -#include - -const char* ScheduleManager::TAG = "ScheduleManager"; - -ScheduleManager::ScheduleManager() { - ESP_LOGI(TAG, "ScheduleManager initialized"); -} - -ScheduleManager::~ScheduleManager() { - ESP_LOGI(TAG, "ScheduleManager destroyed"); -} - -std::string ScheduleManager::CreateEvent(const std::string& title, - const std::string& description, - time_t start_time, - time_t end_time, - const std::string& category, - bool is_all_day, - int reminder_minutes) { - std::lock_guard lock(events_mutex_); - - if (title.empty()) { - ESP_LOGE(TAG, "Event title cannot be empty"); - return ""; - } - - if (!IsEventTimeValid(start_time, end_time)) { - ESP_LOGE(TAG, "Invalid event time"); - return ""; - } - - ScheduleEvent event; - event.id = GenerateEventId(); - event.title = title; - event.description = description; - event.start_time = start_time; - event.end_time = end_time; - event.is_all_day = is_all_day; - event.reminder_minutes = reminder_minutes; - event.created_time = time(nullptr); - event.updated_time = event.created_time; - - // 智能分类 - if (category.empty()) { - event.category = CategorizeEvent(title, description); - } else { - event.category = category; - } - - events_[event.id] = event; - - ESP_LOGI(TAG, "Created event: %s (ID: %s)", title.c_str(), event.id.c_str()); - return event.id; -} - -bool ScheduleManager::UpdateEvent(const std::string& event_id, - const std::string& title, - const std::string& description, - time_t start_time, - time_t end_time, - const std::string& category, - bool is_all_day, - int reminder_minutes) { - std::lock_guard lock(events_mutex_); - - auto it = events_.find(event_id); - if (it == events_.end()) { - ESP_LOGE(TAG, "Event not found: %s", event_id.c_str()); - return false; - } - - ScheduleEvent& event = it->second; - - if (!title.empty()) { - event.title = title; - } - if (!description.empty()) { - event.description = description; - } - if (start_time > 0) { - event.start_time = start_time; - } - if (end_time > 0) { - event.end_time = end_time; - } - if (!category.empty()) { - event.category = category; - } - if (reminder_minutes >= 0) { - event.reminder_minutes = reminder_minutes; - } - - event.is_all_day = is_all_day; - event.updated_time = time(nullptr); - - ESP_LOGI(TAG, "Updated event: %s", event_id.c_str()); - return true; -} - -bool ScheduleManager::DeleteEvent(const std::string& event_id) { - std::lock_guard lock(events_mutex_); - - auto it = events_.find(event_id); - if (it == events_.end()) { - ESP_LOGE(TAG, "Event not found: %s", event_id.c_str()); - return false; - } - - events_.erase(it); - ESP_LOGI(TAG, "Deleted event: %s", event_id.c_str()); - return true; -} - -ScheduleEvent* ScheduleManager::GetEvent(const std::string& event_id) { - std::lock_guard lock(events_mutex_); - - auto it = events_.find(event_id); - if (it == events_.end()) { - return nullptr; - } - - return &it->second; -} - -std::vector ScheduleManager::GetEventsByDate(time_t date) { - std::lock_guard lock(events_mutex_); - std::vector result; - - struct tm date_tm = *localtime(&date); - - for (const auto& pair : events_) { - const ScheduleEvent& event = pair.second; - struct tm event_tm = *localtime(&event.start_time); - - if (event_tm.tm_year == date_tm.tm_year && - event_tm.tm_mon == date_tm.tm_mon && - event_tm.tm_mday == date_tm.tm_mday) { - result.push_back(event); - } - } - - // 按开始时间排序 - std::sort(result.begin(), result.end(), - [](const ScheduleEvent& a, const ScheduleEvent& b) { - return a.start_time < b.start_time; - }); - - return result; -} - -std::vector ScheduleManager::GetEventsByCategory(const std::string& category) { - std::lock_guard lock(events_mutex_); - std::vector result; - - for (const auto& pair : events_) { - const ScheduleEvent& event = pair.second; - if (event.category == category) { - result.push_back(event); - } - } - - return result; -} - -std::vector ScheduleManager::GetUpcomingEvents(int days) { - std::lock_guard lock(events_mutex_); - std::vector result; - - time_t now = time(nullptr); - time_t future_time = now + (days * 24 * 60 * 60); - - for (const auto& pair : events_) { - const ScheduleEvent& event = pair.second; - if (event.start_time >= now && event.start_time <= future_time && !event.is_completed) { - result.push_back(event); - } - } - - // 按开始时间排序 - std::sort(result.begin(), result.end(), - [](const ScheduleEvent& a, const ScheduleEvent& b) { - return a.start_time < b.start_time; - }); - - return result; -} - -std::vector ScheduleManager::GetEventsByKeyword(const std::string& keyword) { - std::lock_guard lock(events_mutex_); - std::vector result; - - std::string lower_keyword = keyword; - std::transform(lower_keyword.begin(), lower_keyword.end(), lower_keyword.begin(), ::tolower); - - for (const auto& pair : events_) { - const ScheduleEvent& event = pair.second; - - std::string lower_title = event.title; - std::string lower_description = event.description; - std::transform(lower_title.begin(), lower_title.end(), lower_title.begin(), ::tolower); - std::transform(lower_description.begin(), lower_description.end(), lower_description.begin(), ::tolower); - - if (lower_title.find(lower_keyword) != std::string::npos || - lower_description.find(lower_keyword) != std::string::npos) { - result.push_back(event); - } - } - - return result; -} - -std::string ScheduleManager::CategorizeEvent(const std::string& title, const std::string& description) { - std::string text = title + " " + description; - std::transform(text.begin(), text.end(), text.begin(), ::tolower); - - // 工作相关关键词 - if (text.find("会议") != std::string::npos || text.find("工作") != std::string::npos || - text.find("项目") != std::string::npos || text.find("报告") != std::string::npos || - text.find("deadline") != std::string::npos || text.find("meeting") != std::string::npos) { - return CategoryToString(EventCategory::WORK); - } - - // 学习相关关键词 - if (text.find("学习") != std::string::npos || text.find("课程") != std::string::npos || - text.find("考试") != std::string::npos || text.find("作业") != std::string::npos || - text.find("study") != std::string::npos || text.find("exam") != std::string::npos) { - return CategoryToString(EventCategory::STUDY); - } - - // 健康相关关键词 - if (text.find("运动") != std::string::npos || text.find("健身") != std::string::npos || - text.find("医院") != std::string::npos || text.find("体检") != std::string::npos || - text.find("exercise") != std::string::npos || text.find("doctor") != std::string::npos) { - return CategoryToString(EventCategory::HEALTH); - } - - // 娱乐相关关键词 - if (text.find("电影") != std::string::npos || text.find("游戏") != std::string::npos || - text.find("聚会") != std::string::npos || text.find("娱乐") != std::string::npos || - text.find("movie") != std::string::npos || text.find("party") != std::string::npos) { - return CategoryToString(EventCategory::ENTERTAINMENT); - } - - // 旅行相关关键词 - if (text.find("旅行") != std::string::npos || text.find("旅游") != std::string::npos || - text.find("出差") != std::string::npos || text.find("travel") != std::string::npos || - text.find("trip") != std::string::npos) { - return CategoryToString(EventCategory::TRAVEL); - } - - // 家庭相关关键词 - if (text.find("家庭") != std::string::npos || text.find("家人") != std::string::npos || - text.find("孩子") != std::string::npos || text.find("family") != std::string::npos || - text.find("child") != std::string::npos) { - return CategoryToString(EventCategory::FAMILY); - } - - return CategoryToString(EventCategory::OTHER); -} - -void ScheduleManager::SetReminderCallback(ReminderCallback callback) { - reminder_callback_ = callback; -} - -void ScheduleManager::CheckReminders() { - std::lock_guard lock(events_mutex_); - - time_t now = time(nullptr); - - for (const auto& pair : events_) { - const ScheduleEvent& event = pair.second; - - if (event.is_completed || event.reminder_minutes <= 0) { - continue; - } - - time_t reminder_time = event.start_time - (event.reminder_minutes * 60); - - if (now >= reminder_time && now < event.start_time) { - ESP_LOGI(TAG, "Reminder triggered for event: %s", event.title.c_str()); - if (reminder_callback_) { - reminder_callback_(event); - } - } - } -} - -int ScheduleManager::GetEventCount() { - std::lock_guard lock(events_mutex_); - return events_.size(); -} - -int ScheduleManager::GetEventCountByCategory(const std::string& category) { - std::lock_guard lock(events_mutex_); - int count = 0; - - for (const auto& pair : events_) { - if (pair.second.category == category) { - count++; - } - } - - return count; -} - -std::map ScheduleManager::GetCategoryStatistics() { - std::lock_guard lock(events_mutex_); - std::map stats; - - for (const auto& pair : events_) { - stats[pair.second.category]++; - } - - return stats; -} - -bool ScheduleManager::SaveToStorage() { - // TODO: 实现数据持久化到NVS或SPIFFS - ESP_LOGW(TAG, "SaveToStorage not implemented yet"); - return true; -} - -bool ScheduleManager::LoadFromStorage() { - // TODO: 实现从NVS或SPIFFS加载数据 - ESP_LOGW(TAG, "LoadFromStorage not implemented yet"); - return true; -} - -std::string ScheduleManager::ExportToJson() { - std::lock_guard lock(events_mutex_); - - std::stringstream json; - json << "{\"events\":["; - - bool first = true; - for (const auto& pair : events_) { - if (!first) json << ","; - first = false; - - const ScheduleEvent& event = pair.second; - json << "{" - << "\"id\":\"" << event.id << "\"," - << "\"title\":\"" << event.title << "\"," - << "\"description\":\"" << event.description << "\"," - << "\"category\":\"" << event.category << "\"," - << "\"start_time\":" << event.start_time << "," - << "\"end_time\":" << event.end_time << "," - << "\"is_all_day\":" << (event.is_all_day ? "true" : "false") << "," - << "\"reminder_minutes\":" << event.reminder_minutes << "," - << "\"is_completed\":" << (event.is_completed ? "true" : "false") << "," - << "\"created_time\":" << event.created_time << "," - << "\"updated_time\":" << event.updated_time - << "}"; - } - - json << "]}"; - return json.str(); -} - -bool ScheduleManager::ImportFromJson(const std::string& json_data) { - // TODO: 实现JSON导入功能 - ESP_LOGW(TAG, "ImportFromJson not implemented yet"); - return false; -} - -std::string ScheduleManager::GenerateEventId() { - static int counter = 0; - return "event_" + std::to_string(++counter) + "_" + std::to_string(time(nullptr)); -} - -std::string ScheduleManager::CategoryToString(EventCategory category) { - switch (category) { - case EventCategory::WORK: return "工作"; - case EventCategory::LIFE: return "生活"; - case EventCategory::STUDY: return "学习"; - case EventCategory::HEALTH: return "健康"; - case EventCategory::ENTERTAINMENT: return "娱乐"; - case EventCategory::TRAVEL: return "旅行"; - case EventCategory::FAMILY: return "家庭"; - case EventCategory::OTHER: return "其他"; - default: return "其他"; - } -} - -EventCategory ScheduleManager::StringToCategory(const std::string& category_str) { - if (category_str == "工作") return EventCategory::WORK; - if (category_str == "生活") return EventCategory::LIFE; - if (category_str == "学习") return EventCategory::STUDY; - if (category_str == "健康") return EventCategory::HEALTH; - if (category_str == "娱乐") return EventCategory::ENTERTAINMENT; - if (category_str == "旅行") return EventCategory::TRAVEL; - if (category_str == "家庭") return EventCategory::FAMILY; - return EventCategory::OTHER; -} - -bool ScheduleManager::IsEventTimeValid(time_t start_time, time_t end_time) { - if (start_time <= 0) return false; - if (end_time > 0 && end_time <= start_time) return false; - return true; -} - -void ScheduleManager::UpdateEventTimestamp(ScheduleEvent& event) { - event.updated_time = time(nullptr); -} \ No newline at end of file diff --git a/main/schedule_manager.h b/main/schedule_manager.h deleted file mode 100644 index b97af81..0000000 --- a/main/schedule_manager.h +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef SCHEDULE_MANAGER_H -#define SCHEDULE_MANAGER_H - -#include -#include -#include -#include -#include -#include -#include - -// 日程事件结构 -struct ScheduleEvent { - std::string id; // 唯一标识符 - std::string title; // 事件标题 - std::string description; // 事件描述 - std::string category; // 事件分类(工作、生活、学习等) - time_t start_time; // 开始时间 - time_t end_time; // 结束时间 - bool is_all_day; // 是否全天事件 - bool is_recurring; // 是否重复事件 - std::string recurrence; // 重复规则(daily, weekly, monthly) - int reminder_minutes; // 提醒时间(分钟) - bool is_completed; // 是否已完成 - time_t created_time; // 创建时间 - time_t updated_time; // 更新时间 - - ScheduleEvent() : start_time(0), end_time(0), is_all_day(false), - is_recurring(false), reminder_minutes(0), - is_completed(false), created_time(0), updated_time(0) {} -}; - -// 智能分类枚举 -enum class EventCategory { - WORK, // 工作 - LIFE, // 生活 - STUDY, // 学习 - HEALTH, // 健康 - ENTERTAINMENT, // 娱乐 - TRAVEL, // 旅行 - FAMILY, // 家庭 - OTHER // 其他 -}; - -// 提醒回调函数类型 -using ReminderCallback = std::function; - -class ScheduleManager { -public: - static ScheduleManager& GetInstance() { - static ScheduleManager instance; - return instance; - } - - // 事件管理 - std::string CreateEvent(const std::string& title, - const std::string& description, - time_t start_time, - time_t end_time, - const std::string& category = "", - bool is_all_day = false, - int reminder_minutes = 15); - - bool UpdateEvent(const std::string& event_id, - const std::string& title = "", - const std::string& description = "", - time_t start_time = 0, - time_t end_time = 0, - const std::string& category = "", - bool is_all_day = false, - int reminder_minutes = -1); - - bool DeleteEvent(const std::string& event_id); - - ScheduleEvent* GetEvent(const std::string& event_id); - - // 查询功能 - std::vector GetEventsByDate(time_t date); - std::vector GetEventsByCategory(const std::string& category); - std::vector GetUpcomingEvents(int days = 7); - std::vector GetEventsByKeyword(const std::string& keyword); - - // 智能分类 - std::string CategorizeEvent(const std::string& title, const std::string& description); - - // 提醒功能 - void SetReminderCallback(ReminderCallback callback); - void CheckReminders(); - - // 统计功能 - int GetEventCount(); - int GetEventCountByCategory(const std::string& category); - std::map GetCategoryStatistics(); - - // 数据持久化 - bool SaveToStorage(); - bool LoadFromStorage(); - - // 导出功能 - std::string ExportToJson(); - bool ImportFromJson(const std::string& json_data); - -private: - ScheduleManager(); - ~ScheduleManager(); - - std::string GenerateEventId(); - std::string CategoryToString(EventCategory category); - EventCategory StringToCategory(const std::string& category_str); - bool IsEventTimeValid(time_t start_time, time_t end_time); - void UpdateEventTimestamp(ScheduleEvent& event); - - std::map events_; - std::mutex events_mutex_; - ReminderCallback reminder_callback_; - - static const char* TAG; -}; - -#endif // SCHEDULE_MANAGER_H \ No newline at end of file diff --git a/main/server_config.h b/main/server_config.h new file mode 100644 index 0000000..2de2087 --- /dev/null +++ b/main/server_config.h @@ -0,0 +1,33 @@ +#ifndef SERVER_CONFIG_H +#define SERVER_CONFIG_H + +// ⚠️ 请修改为您的服务器地址 +// 服务器配置说明: +// +// 1. 本地测试(服务器在同一台电脑): +// - 先查看电脑的局域网IP: +// Windows: 打开cmd,输入 ipconfig,查看"IPv4 地址" +// 示例:192.168.1.100 +// - 将下面的地址改为:http://192.168.1.100:2233 +// +// 2. 远程服务器(有公网IP或域名): +// - 使用公网IP:http://123.45.67.89:2233 +// - 使用域名:http://your-domain.com:2233 +// +// 3. 使用原作者的在线服务器: +// - http://http-embedded-music.miao-lab.top:2233 + +// 👇 在这里修改您的服务器地址 +#define MUSIC_SERVER_URL "http://110.42.59.54:2233" + +// 自动生成的完整API地址(不要修改) +#define MUSIC_API_URL MUSIC_SERVER_URL "/stream_pcm" +#define DEVICE_BIND_API_URL MUSIC_SERVER_URL "/api/esp32/bind" +#define DEVICE_VERIFY_API_URL MUSIC_SERVER_URL "/api/esp32/verify" + +// 歌单与收藏 API +#define FAVORITE_LIST_API_URL MUSIC_SERVER_URL "/api/favorite/list" +#define PLAYLIST_LIST_API_URL MUSIC_SERVER_URL "/api/user/playlists" +#define PLAYLIST_DETAIL_API_URL MUSIC_SERVER_URL "/api/user/playlists" // 详情可能需要带ID参数 + +#endif // SERVER_CONFIG_H diff --git a/main/settings.cc b/main/settings.cc index 3b40622..81b87ee 100644 --- a/main/settings.cc +++ b/main/settings.cc @@ -1,108 +1,108 @@ -#include "settings.h" - -#include -#include - -#define TAG "Settings" - -Settings::Settings(const std::string& ns, bool read_write) : ns_(ns), read_write_(read_write) { - nvs_open(ns.c_str(), read_write_ ? NVS_READWRITE : NVS_READONLY, &nvs_handle_); -} - -Settings::~Settings() { - if (nvs_handle_ != 0) { - if (read_write_ && dirty_) { - ESP_ERROR_CHECK(nvs_commit(nvs_handle_)); - } - nvs_close(nvs_handle_); - } -} - -std::string Settings::GetString(const std::string& key, const std::string& default_value) { - if (nvs_handle_ == 0) { - return default_value; - } - - size_t length = 0; - if (nvs_get_str(nvs_handle_, key.c_str(), nullptr, &length) != ESP_OK) { - return default_value; - } - - std::string value; - value.resize(length); - ESP_ERROR_CHECK(nvs_get_str(nvs_handle_, key.c_str(), value.data(), &length)); - while (!value.empty() && value.back() == '\0') { - value.pop_back(); - } - return value; -} - -void Settings::SetString(const std::string& key, const std::string& value) { - if (read_write_) { - ESP_ERROR_CHECK(nvs_set_str(nvs_handle_, key.c_str(), value.c_str())); - dirty_ = true; - } else { - ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); - } -} - -int32_t Settings::GetInt(const std::string& key, int32_t default_value) { - if (nvs_handle_ == 0) { - return default_value; - } - - int32_t value; - if (nvs_get_i32(nvs_handle_, key.c_str(), &value) != ESP_OK) { - return default_value; - } - return value; -} - -void Settings::SetInt(const std::string& key, int32_t value) { - if (read_write_) { - ESP_ERROR_CHECK(nvs_set_i32(nvs_handle_, key.c_str(), value)); - dirty_ = true; - } else { - ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); - } -} - -bool Settings::GetBool(const std::string& key, bool default_value) { - if (nvs_handle_ == 0) { - return default_value; - } - - uint8_t value; - if (nvs_get_u8(nvs_handle_, key.c_str(), &value) != ESP_OK) { - return default_value; - } - return value != 0; -} - -void Settings::SetBool(const std::string& key, bool value) { - if (read_write_) { - ESP_ERROR_CHECK(nvs_set_u8(nvs_handle_, key.c_str(), value ? 1 : 0)); - dirty_ = true; - } else { - ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); - } -} - -void Settings::EraseKey(const std::string& key) { - if (read_write_) { - auto ret = nvs_erase_key(nvs_handle_, key.c_str()); - if (ret != ESP_ERR_NVS_NOT_FOUND) { - ESP_ERROR_CHECK(ret); - } - } else { - ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); - } -} - -void Settings::EraseAll() { - if (read_write_) { - ESP_ERROR_CHECK(nvs_erase_all(nvs_handle_)); - } else { - ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); - } -} +#include "settings.h" + +#include +#include + +#define TAG "Settings" + +Settings::Settings(const std::string& ns, bool read_write) : ns_(ns), read_write_(read_write) { + nvs_open(ns.c_str(), read_write_ ? NVS_READWRITE : NVS_READONLY, &nvs_handle_); +} + +Settings::~Settings() { + if (nvs_handle_ != 0) { + if (read_write_ && dirty_) { + ESP_ERROR_CHECK(nvs_commit(nvs_handle_)); + } + nvs_close(nvs_handle_); + } +} + +std::string Settings::GetString(const std::string& key, const std::string& default_value) { + if (nvs_handle_ == 0) { + return default_value; + } + + size_t length = 0; + if (nvs_get_str(nvs_handle_, key.c_str(), nullptr, &length) != ESP_OK) { + return default_value; + } + + std::string value; + value.resize(length); + ESP_ERROR_CHECK(nvs_get_str(nvs_handle_, key.c_str(), value.data(), &length)); + while (!value.empty() && value.back() == '\0') { + value.pop_back(); + } + return value; +} + +void Settings::SetString(const std::string& key, const std::string& value) { + if (read_write_) { + ESP_ERROR_CHECK(nvs_set_str(nvs_handle_, key.c_str(), value.c_str())); + dirty_ = true; + } else { + ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); + } +} + +int32_t Settings::GetInt(const std::string& key, int32_t default_value) { + if (nvs_handle_ == 0) { + return default_value; + } + + int32_t value; + if (nvs_get_i32(nvs_handle_, key.c_str(), &value) != ESP_OK) { + return default_value; + } + return value; +} + +void Settings::SetInt(const std::string& key, int32_t value) { + if (read_write_) { + ESP_ERROR_CHECK(nvs_set_i32(nvs_handle_, key.c_str(), value)); + dirty_ = true; + } else { + ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); + } +} + +bool Settings::GetBool(const std::string& key, bool default_value) { + if (nvs_handle_ == 0) { + return default_value; + } + + uint8_t value; + if (nvs_get_u8(nvs_handle_, key.c_str(), &value) != ESP_OK) { + return default_value; + } + return value != 0; +} + +void Settings::SetBool(const std::string& key, bool value) { + if (read_write_) { + ESP_ERROR_CHECK(nvs_set_u8(nvs_handle_, key.c_str(), value ? 1 : 0)); + dirty_ = true; + } else { + ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); + } +} + +void Settings::EraseKey(const std::string& key) { + if (read_write_) { + auto ret = nvs_erase_key(nvs_handle_, key.c_str()); + if (ret != ESP_ERR_NVS_NOT_FOUND) { + ESP_ERROR_CHECK(ret); + } + } else { + ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); + } +} + +void Settings::EraseAll() { + if (read_write_) { + ESP_ERROR_CHECK(nvs_erase_all(nvs_handle_)); + } else { + ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); + } +} diff --git a/main/settings.h b/main/settings.h index 7eb596e..35f656c 100644 --- a/main/settings.h +++ b/main/settings.h @@ -1,28 +1,28 @@ -#ifndef SETTINGS_H -#define SETTINGS_H - -#include -#include - -class Settings { -public: - Settings(const std::string& ns, bool read_write = false); - ~Settings(); - - std::string GetString(const std::string& key, const std::string& default_value = ""); - void SetString(const std::string& key, const std::string& value); - int32_t GetInt(const std::string& key, int32_t default_value = 0); - void SetInt(const std::string& key, int32_t value); - bool GetBool(const std::string& key, bool default_value = false); - void SetBool(const std::string& key, bool value); - void EraseKey(const std::string& key); - void EraseAll(); - -private: - std::string ns_; - nvs_handle_t nvs_handle_ = 0; - bool read_write_ = false; - bool dirty_ = false; -}; - -#endif +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include + +class Settings { +public: + Settings(const std::string& ns, bool read_write = false); + ~Settings(); + + std::string GetString(const std::string& key, const std::string& default_value = ""); + void SetString(const std::string& key, const std::string& value); + int32_t GetInt(const std::string& key, int32_t default_value = 0); + void SetInt(const std::string& key, int32_t value); + bool GetBool(const std::string& key, bool default_value = false); + void SetBool(const std::string& key, bool value); + void EraseKey(const std::string& key); + void EraseAll(); + +private: + std::string ns_; + nvs_handle_t nvs_handle_ = 0; + bool read_write_ = false; + bool dirty_ = false; +}; + +#endif diff --git a/main/system_info.cc b/main/system_info.cc index 22ce809..fa55d5b 100644 --- a/main/system_info.cc +++ b/main/system_info.cc @@ -1,145 +1,151 @@ -#include "system_info.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#if CONFIG_IDF_TARGET_ESP32P4 -#include "esp_wifi_remote.h" -#endif - -#define TAG "SystemInfo" - -size_t SystemInfo::GetFlashSize() { - uint32_t flash_size; - if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) { - ESP_LOGE(TAG, "Failed to get flash size"); - return 0; - } - return (size_t)flash_size; -} - -size_t SystemInfo::GetMinimumFreeHeapSize() { - return esp_get_minimum_free_heap_size(); -} - -size_t SystemInfo::GetFreeHeapSize() { - return esp_get_free_heap_size(); -} - -std::string SystemInfo::GetMacAddress() { - uint8_t mac[6]; -#if CONFIG_IDF_TARGET_ESP32P4 - esp_wifi_get_mac(WIFI_IF_STA, mac); -#else - esp_read_mac(mac, ESP_MAC_WIFI_STA); -#endif - char mac_str[18]; - snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return std::string(mac_str); -} - -std::string SystemInfo::GetChipModelName() { - return std::string(CONFIG_IDF_TARGET); -} - -esp_err_t SystemInfo::PrintTaskCpuUsage(TickType_t xTicksToWait) { - #define ARRAY_SIZE_OFFSET 5 - TaskStatus_t *start_array = NULL, *end_array = NULL; - UBaseType_t start_array_size, end_array_size; - configRUN_TIME_COUNTER_TYPE start_run_time, end_run_time; - esp_err_t ret; - uint32_t total_elapsed_time; - - //Allocate array to store current task states - start_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET; - start_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * start_array_size); - if (start_array == NULL) { - ret = ESP_ERR_NO_MEM; - goto exit; - } - //Get current task states - start_array_size = uxTaskGetSystemState(start_array, start_array_size, &start_run_time); - if (start_array_size == 0) { - ret = ESP_ERR_INVALID_SIZE; - goto exit; - } - - vTaskDelay(xTicksToWait); - - //Allocate array to store tasks states post delay - end_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET; - end_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * end_array_size); - if (end_array == NULL) { - ret = ESP_ERR_NO_MEM; - goto exit; - } - //Get post delay task states - end_array_size = uxTaskGetSystemState(end_array, end_array_size, &end_run_time); - if (end_array_size == 0) { - ret = ESP_ERR_INVALID_SIZE; - goto exit; - } - - //Calculate total_elapsed_time in units of run time stats clock period. - total_elapsed_time = (end_run_time - start_run_time); - if (total_elapsed_time == 0) { - ret = ESP_ERR_INVALID_STATE; - goto exit; - } - - printf("| Task | Run Time | Percentage\n"); - //Match each task in start_array to those in the end_array - for (int i = 0; i < start_array_size; i++) { - int k = -1; - for (int j = 0; j < end_array_size; j++) { - if (start_array[i].xHandle == end_array[j].xHandle) { - k = j; - //Mark that task have been matched by overwriting their handles - start_array[i].xHandle = NULL; - end_array[j].xHandle = NULL; - break; - } - } - //Check if matching task found - if (k >= 0) { - uint32_t task_elapsed_time = end_array[k].ulRunTimeCounter - start_array[i].ulRunTimeCounter; - uint32_t percentage_time = (task_elapsed_time * 100UL) / (total_elapsed_time * CONFIG_FREERTOS_NUMBER_OF_CORES); - printf("| %-16s | %8lu | %4lu%%\n", start_array[i].pcTaskName, task_elapsed_time, percentage_time); - } - } - - //Print unmatched tasks - for (int i = 0; i < start_array_size; i++) { - if (start_array[i].xHandle != NULL) { - printf("| %s | Deleted\n", start_array[i].pcTaskName); - } - } - for (int i = 0; i < end_array_size; i++) { - if (end_array[i].xHandle != NULL) { - printf("| %s | Created\n", end_array[i].pcTaskName); - } - } - ret = ESP_OK; - -exit: //Common return path - free(start_array); - free(end_array); - return ret; -} - -void SystemInfo::PrintTaskList() { - char buffer[1000]; - vTaskList(buffer); - ESP_LOGI(TAG, "Task list: \n%s", buffer); -} - -void SystemInfo::PrintHeapStats() { - int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); - int min_free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL); - ESP_LOGI(TAG, "free sram: %u minimal sram: %u", free_sram, min_free_sram); -} +#include "system_info.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#if CONFIG_IDF_TARGET_ESP32P4 +#include "esp_wifi_remote.h" +#endif + +#define TAG "SystemInfo" + +size_t SystemInfo::GetFlashSize() { + uint32_t flash_size; + if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) { + ESP_LOGE(TAG, "Failed to get flash size"); + return 0; + } + return (size_t)flash_size; +} + +size_t SystemInfo::GetMinimumFreeHeapSize() { + return esp_get_minimum_free_heap_size(); +} + +size_t SystemInfo::GetFreeHeapSize() { + return esp_get_free_heap_size(); +} + +std::string SystemInfo::GetMacAddress() { + uint8_t mac[6]; +#if CONFIG_IDF_TARGET_ESP32P4 + esp_wifi_get_mac(WIFI_IF_STA, mac); +#else + esp_read_mac(mac, ESP_MAC_WIFI_STA); +#endif + char mac_str[18]; + snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(mac_str); +} + +std::string SystemInfo::GetChipModelName() { + return std::string(CONFIG_IDF_TARGET); +} + +std::string SystemInfo::GetUserAgent() { + auto app_desc = esp_app_get_description(); + auto user_agent = std::string(BOARD_NAME "/") + app_desc->version; + return user_agent; +} + +esp_err_t SystemInfo::PrintTaskCpuUsage(TickType_t xTicksToWait) { + #define ARRAY_SIZE_OFFSET 5 + TaskStatus_t *start_array = NULL, *end_array = NULL; + UBaseType_t start_array_size, end_array_size; + configRUN_TIME_COUNTER_TYPE start_run_time, end_run_time; + esp_err_t ret; + uint32_t total_elapsed_time; + + //Allocate array to store current task states + start_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET; + start_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * start_array_size); + if (start_array == NULL) { + ret = ESP_ERR_NO_MEM; + goto exit; + } + //Get current task states + start_array_size = uxTaskGetSystemState(start_array, start_array_size, &start_run_time); + if (start_array_size == 0) { + ret = ESP_ERR_INVALID_SIZE; + goto exit; + } + + vTaskDelay(xTicksToWait); + + //Allocate array to store tasks states post delay + end_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET; + end_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * end_array_size); + if (end_array == NULL) { + ret = ESP_ERR_NO_MEM; + goto exit; + } + //Get post delay task states + end_array_size = uxTaskGetSystemState(end_array, end_array_size, &end_run_time); + if (end_array_size == 0) { + ret = ESP_ERR_INVALID_SIZE; + goto exit; + } + + //Calculate total_elapsed_time in units of run time stats clock period. + total_elapsed_time = (end_run_time - start_run_time); + if (total_elapsed_time == 0) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + + printf("| Task | Run Time | Percentage\n"); + //Match each task in start_array to those in the end_array + for (int i = 0; i < start_array_size; i++) { + int k = -1; + for (int j = 0; j < end_array_size; j++) { + if (start_array[i].xHandle == end_array[j].xHandle) { + k = j; + //Mark that task have been matched by overwriting their handles + start_array[i].xHandle = NULL; + end_array[j].xHandle = NULL; + break; + } + } + //Check if matching task found + if (k >= 0) { + uint32_t task_elapsed_time = end_array[k].ulRunTimeCounter - start_array[i].ulRunTimeCounter; + uint32_t percentage_time = (task_elapsed_time * 100UL) / (total_elapsed_time * CONFIG_FREERTOS_NUMBER_OF_CORES); + printf("| %-16s | %8lu | %4lu%%\n", start_array[i].pcTaskName, task_elapsed_time, percentage_time); + } + } + + //Print unmatched tasks + for (int i = 0; i < start_array_size; i++) { + if (start_array[i].xHandle != NULL) { + printf("| %s | Deleted\n", start_array[i].pcTaskName); + } + } + for (int i = 0; i < end_array_size; i++) { + if (end_array[i].xHandle != NULL) { + printf("| %s | Created\n", end_array[i].pcTaskName); + } + } + ret = ESP_OK; + +exit: //Common return path + free(start_array); + free(end_array); + return ret; +} + +void SystemInfo::PrintTaskList() { + char buffer[1000]; + vTaskList(buffer); + ESP_LOGI(TAG, "Task list: \n%s", buffer); +} + +void SystemInfo::PrintHeapStats() { + int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); + int min_free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL); + ESP_LOGI(TAG, "free sram: %u minimal sram: %u", free_sram, min_free_sram); +} diff --git a/main/system_info.h b/main/system_info.h index b29c72f..b4b6229 100644 --- a/main/system_info.h +++ b/main/system_info.h @@ -1,21 +1,22 @@ -#ifndef _SYSTEM_INFO_H_ -#define _SYSTEM_INFO_H_ - -#include - -#include -#include - -class SystemInfo { -public: - static size_t GetFlashSize(); - static size_t GetMinimumFreeHeapSize(); - static size_t GetFreeHeapSize(); - static std::string GetMacAddress(); - static std::string GetChipModelName(); - static esp_err_t PrintTaskCpuUsage(TickType_t xTicksToWait); - static void PrintTaskList(); - static void PrintHeapStats(); -}; - -#endif // _SYSTEM_INFO_H_ +#ifndef _SYSTEM_INFO_H_ +#define _SYSTEM_INFO_H_ + +#include + +#include +#include + +class SystemInfo { +public: + static size_t GetFlashSize(); + static size_t GetMinimumFreeHeapSize(); + static size_t GetFreeHeapSize(); + static std::string GetMacAddress(); + static std::string GetChipModelName(); + static std::string GetUserAgent(); + static esp_err_t PrintTaskCpuUsage(TickType_t xTicksToWait); + static void PrintTaskList(); + static void PrintHeapStats(); +}; + +#endif // _SYSTEM_INFO_H_ diff --git a/main/timer_manager.cc b/main/timer_manager.cc deleted file mode 100644 index c83e422..0000000 --- a/main/timer_manager.cc +++ /dev/null @@ -1,559 +0,0 @@ -#include "timer_manager.h" -#include -#include -#include -#include - -const char* TimerManager::TAG = "TimerManager"; - -TimerManager::TimerManager() : is_running_(false) { - ESP_LOGI(TAG, "TimerManager initialized"); -} - -TimerManager::~TimerManager() { - StopManager(); - ESP_LOGI(TAG, "TimerManager destroyed"); -} - -std::string TimerManager::CreateCountdownTimer(const std::string& name, - uint32_t duration_ms, - const std::string& description) { - std::lock_guard lock(tasks_mutex_); - - TimerTask task; - task.id = GenerateTaskId(); - task.name = name; - task.type = TimerType::COUNTDOWN; - task.status = TimerStatus::PENDING; - task.duration_ms = duration_ms; - task.description = description; - task.created_time = time(nullptr); - - tasks_[task.id] = task; - - ESP_LOGI(TAG, "Created countdown timer: %s (ID: %s, Duration: %u ms)", - name.c_str(), task.id.c_str(), duration_ms); - - return task.id; -} - -std::string TimerManager::CreateDelayedMcpTask(const std::string& name, - uint32_t delay_ms, - const std::string& mcp_tool_name, - const std::string& mcp_tool_args, - const std::string& description) { - std::lock_guard lock(tasks_mutex_); - - TimerTask task; - task.id = GenerateTaskId(); - task.name = name; - task.type = TimerType::DELAYED_EXEC; - task.status = TimerStatus::PENDING; - task.duration_ms = delay_ms; - task.mcp_tool_name = mcp_tool_name; - task.mcp_tool_args = mcp_tool_args; - task.description = description; - task.created_time = time(nullptr); - - tasks_[task.id] = task; - - ESP_LOGI(TAG, "Created delayed MCP task: %s (ID: %s, Delay: %u ms, Tool: %s)", - name.c_str(), task.id.c_str(), delay_ms, mcp_tool_name.c_str()); - - return task.id; -} - -std::string TimerManager::CreatePeriodicTask(const std::string& name, - uint32_t interval_ms, - int repeat_count, - const std::string& mcp_tool_name, - const std::string& mcp_tool_args, - const std::string& description) { - std::lock_guard lock(tasks_mutex_); - - TimerTask task; - task.id = GenerateTaskId(); - task.name = name; - task.type = TimerType::PERIODIC; - task.status = TimerStatus::PENDING; - task.interval_ms = interval_ms; - task.repeat_count = repeat_count; - task.current_repeat = 0; - task.mcp_tool_name = mcp_tool_name; - task.mcp_tool_args = mcp_tool_args; - task.description = description; - task.created_time = time(nullptr); - - tasks_[task.id] = task; - - ESP_LOGI(TAG, "Created periodic task: %s (ID: %s, Interval: %u ms, Repeat: %d)", - name.c_str(), task.id.c_str(), interval_ms, repeat_count); - - return task.id; -} - -std::string TimerManager::CreateScheduledTask(const std::string& name, - time_t scheduled_time, - const std::string& mcp_tool_name, - const std::string& mcp_tool_args, - const std::string& description) { - std::lock_guard lock(tasks_mutex_); - - TimerTask task; - task.id = GenerateTaskId(); - task.name = name; - task.type = TimerType::SCHEDULED; - task.status = TimerStatus::PENDING; - task.scheduled_time = scheduled_time; - task.mcp_tool_name = mcp_tool_name; - task.mcp_tool_args = mcp_tool_args; - task.description = description; - task.created_time = time(nullptr); - - tasks_[task.id] = task; - - ESP_LOGI(TAG, "Created scheduled task: %s (ID: %s, Time: %ld)", - name.c_str(), task.id.c_str(), scheduled_time); - - return task.id; -} - -bool TimerManager::StartTask(const std::string& task_id) { - std::lock_guard lock(tasks_mutex_); - - auto it = tasks_.find(task_id); - if (it == tasks_.end()) { - ESP_LOGE(TAG, "Task not found: %s", task_id.c_str()); - return false; - } - - TimerTask& task = it->second; - - if (task.status != TimerStatus::PENDING) { - ESP_LOGW(TAG, "Task %s is not in pending status", task_id.c_str()); - return false; - } - - // 创建FreeRTOS定时器 - TimerHandle_t timer_handle = xTimerCreate( - task.name.c_str(), - pdMS_TO_TICKS(task.duration_ms), - (task.type == TimerType::PERIODIC) ? pdTRUE : pdFALSE, - (void*)task_id.c_str(), - TimerCallback - ); - - if (timer_handle == nullptr) { - ESP_LOGE(TAG, "Failed to create timer for task: %s", task_id.c_str()); - return false; - } - - timers_[task_id] = timer_handle; - task.status = TimerStatus::RUNNING; - task.start_time = time(nullptr); - - if (xTimerStart(timer_handle, 0) != pdPASS) { - ESP_LOGE(TAG, "Failed to start timer for task: %s", task_id.c_str()); - xTimerDelete(timer_handle, 0); - timers_.erase(task_id); - task.status = TimerStatus::FAILED; - return false; - } - - ESP_LOGI(TAG, "Started task: %s", task_id.c_str()); - return true; -} - -bool TimerManager::StopTask(const std::string& task_id) { - std::lock_guard lock(tasks_mutex_); - - auto it = tasks_.find(task_id); - if (it == tasks_.end()) { - ESP_LOGE(TAG, "Task not found: %s", task_id.c_str()); - return false; - } - - TimerTask& task = it->second; - - if (task.status != TimerStatus::RUNNING) { - ESP_LOGW(TAG, "Task %s is not running", task_id.c_str()); - return false; - } - - auto timer_it = timers_.find(task_id); - if (timer_it != timers_.end()) { - xTimerStop(timer_it->second, 0); - xTimerDelete(timer_it->second, 0); - timers_.erase(timer_it); - } - - task.status = TimerStatus::CANCELLED; - task.end_time = time(nullptr); - - ESP_LOGI(TAG, "Stopped task: %s", task_id.c_str()); - return true; -} - -bool TimerManager::CancelTask(const std::string& task_id) { - return StopTask(task_id); -} - -bool TimerManager::DeleteTask(const std::string& task_id) { - std::lock_guard lock(tasks_mutex_); - - // 先停止任务 - StopTask(task_id); - - auto it = tasks_.find(task_id); - if (it == tasks_.end()) { - ESP_LOGE(TAG, "Task not found: %s", task_id.c_str()); - return false; - } - - tasks_.erase(it); - ESP_LOGI(TAG, "Deleted task: %s", task_id.c_str()); - return true; -} - -TimerTask* TimerManager::GetTask(const std::string& task_id) { - std::lock_guard lock(tasks_mutex_); - - auto it = tasks_.find(task_id); - if (it == tasks_.end()) { - return nullptr; - } - - return &it->second; -} - -std::vector TimerManager::GetAllTasks() { - std::lock_guard lock(tasks_mutex_); - std::vector result; - - for (const auto& pair : tasks_) { - result.push_back(pair.second); - } - - return result; -} - -std::vector TimerManager::GetTasksByStatus(TimerStatus status) { - std::lock_guard lock(tasks_mutex_); - std::vector result; - - for (const auto& pair : tasks_) { - if (pair.second.status == status) { - result.push_back(pair.second); - } - } - - return result; -} - -std::vector TimerManager::GetRunningTasks() { - return GetTasksByStatus(TimerStatus::RUNNING); -} - -std::vector TimerManager::GetUpcomingTasks(int minutes) { - std::lock_guard lock(tasks_mutex_); - std::vector result; - - time_t now = time(nullptr); - time_t future_time = now + (minutes * 60); - - for (const auto& pair : tasks_) { - const TimerTask& task = pair.second; - if (task.status == TimerStatus::PENDING && - task.scheduled_time >= now && - task.scheduled_time <= future_time) { - result.push_back(task); - } - } - - return result; -} - -int TimerManager::GetTaskCount() { - std::lock_guard lock(tasks_mutex_); - return tasks_.size(); -} - -int TimerManager::GetTaskCountByStatus(TimerStatus status) { - std::lock_guard lock(tasks_mutex_); - int count = 0; - - for (const auto& pair : tasks_) { - if (pair.second.status == status) { - count++; - } - } - - return count; -} - -int TimerManager::GetTaskCountByType(TimerType type) { - std::lock_guard lock(tasks_mutex_); - int count = 0; - - for (const auto& pair : tasks_) { - if (pair.second.type == type) { - count++; - } - } - - return count; -} - -void TimerManager::StartManager() { - if (is_running_) { - ESP_LOGW(TAG, "TimerManager is already running"); - return; - } - - is_running_ = true; - worker_thread_ = std::thread(&TimerManager::TaskWorker, this); - - ESP_LOGI(TAG, "TimerManager started"); -} - -void TimerManager::StopManager() { - if (!is_running_) { - return; - } - - is_running_ = false; - - if (worker_thread_.joinable()) { - worker_thread_.join(); - } - - // 停止所有定时器 - std::lock_guard lock(tasks_mutex_); - for (auto& pair : timers_) { - xTimerStop(pair.second, 0); - xTimerDelete(pair.second, 0); - } - timers_.clear(); - - ESP_LOGI(TAG, "TimerManager stopped"); -} - -bool TimerManager::IsRunning() { - return is_running_; -} - -void TimerManager::SetTaskCompletedCallback(std::function callback) { - task_completed_callback_ = callback; -} - -void TimerManager::SetTaskFailedCallback(std::function callback) { - task_failed_callback_ = callback; -} - -bool TimerManager::SaveToStorage() { - // TODO: 实现数据持久化到NVS或SPIFFS - ESP_LOGW(TAG, "SaveToStorage not implemented yet"); - return true; -} - -bool TimerManager::LoadFromStorage() { - // TODO: 实现从NVS或SPIFFS加载数据 - ESP_LOGW(TAG, "LoadFromStorage not implemented yet"); - return true; -} - -std::string TimerManager::ExportToJson() { - std::lock_guard lock(tasks_mutex_); - - std::stringstream json; - json << "{\"tasks\":["; - - bool first = true; - for (const auto& pair : tasks_) { - if (!first) json << ","; - first = false; - - const TimerTask& task = pair.second; - json << "{" - << "\"id\":\"" << task.id << "\"," - << "\"name\":\"" << task.name << "\"," - << "\"description\":\"" << task.description << "\"," - << "\"duration_ms\":" << task.duration_ms << "," - << "\"interval_ms\":" << task.interval_ms << "," - << "\"repeat_count\":" << task.repeat_count << "," - << "\"current_repeat\":" << task.current_repeat << "," - << "\"created_time\":" << task.created_time << "," - << "\"start_time\":" << task.start_time << "," - << "\"end_time\":" << task.end_time << "," - << "\"scheduled_time\":" << task.scheduled_time << "," - << "\"mcp_tool_name\":\"" << task.mcp_tool_name << "\"," - << "\"mcp_tool_args\":\"" << task.mcp_tool_args << "\"," - << "\"user_data\":\"" << task.user_data << "\","; - - std::string status_str; - switch (task.status) { - case TimerStatus::PENDING: status_str = "pending"; break; - case TimerStatus::RUNNING: status_str = "running"; break; - case TimerStatus::COMPLETED: status_str = "completed"; break; - case TimerStatus::CANCELLED: status_str = "cancelled"; break; - case TimerStatus::FAILED: status_str = "failed"; break; - } - json << "\"status\":\"" << status_str << "\","; - - std::string type_str; - switch (task.type) { - case TimerType::COUNTDOWN: type_str = "countdown"; break; - case TimerType::DELAYED_EXEC: type_str = "delayed_exec"; break; - case TimerType::PERIODIC: type_str = "periodic"; break; - case TimerType::SCHEDULED: type_str = "scheduled"; break; - } - json << "\"type\":\"" << type_str << "\""; - - json << "}"; - } - - json << "]}"; - return json.str(); -} - -std::string TimerManager::GenerateTaskId() { - static int counter = 0; - return "task_" + std::to_string(++counter) + "_" + std::to_string(time(nullptr)); -} - -void TimerManager::TaskWorker() { - ESP_LOGI(TAG, "Task worker thread started"); - - while (is_running_) { - vTaskDelay(pdMS_TO_TICKS(1000)); - - // 检查定时任务 - std::lock_guard lock(tasks_mutex_); - time_t now = time(nullptr); - - for (auto& pair : tasks_) { - TimerTask& task = pair.second; - - if (task.status == TimerStatus::PENDING && - task.type == TimerType::SCHEDULED && - now >= task.scheduled_time) { - - ESP_LOGI(TAG, "Executing scheduled task: %s", task.id.c_str()); - ExecuteTask(task); - } - } - } - - ESP_LOGI(TAG, "Task worker thread stopped"); -} - -void TimerManager::ExecuteTask(TimerTask& task) { - task.status = TimerStatus::RUNNING; - task.start_time = time(nullptr); - - bool success = true; - std::string error_msg; - - try { - if (!task.mcp_tool_name.empty()) { - success = ExecuteMcpTool(task.mcp_tool_name, task.mcp_tool_args); - if (!success) { - error_msg = "MCP tool execution failed"; - } - } - } catch (const std::exception& e) { - success = false; - error_msg = e.what(); - } catch (...) { - success = false; - error_msg = "Unknown error occurred"; - } - - task.end_time = time(nullptr); - - if (success) { - if (task.type == TimerType::PERIODIC) { - task.current_repeat++; - if (task.repeat_count == -1 || task.current_repeat < task.repeat_count) { - // 继续下一次重复 - task.status = TimerStatus::PENDING; - ESP_LOGI(TAG, "Periodic task %s completed repeat %d/%d", - task.id.c_str(), task.current_repeat, task.repeat_count); - } else { - // 所有重复完成 - task.status = TimerStatus::COMPLETED; - ESP_LOGI(TAG, "Periodic task %s completed all repeats", task.id.c_str()); - NotifyTaskCompleted(task); - } - } else { - task.status = TimerStatus::COMPLETED; - ESP_LOGI(TAG, "Task %s completed successfully", task.id.c_str()); - NotifyTaskCompleted(task); - } - } else { - task.status = TimerStatus::FAILED; - ESP_LOGE(TAG, "Task %s failed: %s", task.id.c_str(), error_msg.c_str()); - NotifyTaskFailed(task, error_msg); - } -} - -bool TimerManager::ExecuteMcpTool(const std::string& tool_name, const std::string& args) { - // TODO: 实现MCP工具执行 - ESP_LOGW(TAG, "ExecuteMcpTool not implemented yet: %s with args: %s", - tool_name.c_str(), args.c_str()); - return true; -} - -void TimerManager::UpdateTaskStatus(TimerTask& task, TimerStatus status) { - task.status = status; - if (status == TimerStatus::RUNNING) { - task.start_time = time(nullptr); - } else if (status == TimerStatus::COMPLETED || status == TimerStatus::FAILED) { - task.end_time = time(nullptr); - } -} - -void TimerManager::NotifyTaskCompleted(const TimerTask& task) { - if (task_completed_callback_) { - task_completed_callback_(task); - } -} - -void TimerManager::NotifyTaskFailed(const TimerTask& task, const std::string& error) { - if (task_failed_callback_) { - task_failed_callback_(task, error); - } -} - -void TimerManager::TimerCallback(TimerHandle_t timer_handle) { - const char* task_id = (const char*)pvTimerGetTimerID(timer_handle); - - ESP_LOGI(TAG, "Timer callback triggered for task: %s", task_id); - - // 获取任务管理器实例 - TimerManager& manager = TimerManager::GetInstance(); - - std::lock_guard lock(manager.tasks_mutex_); - - auto it = manager.tasks_.find(task_id); - if (it != manager.tasks_.end()) { - TimerTask& task = it->second; - - if (task.type == TimerType::COUNTDOWN) { - // 倒计时完成 - task.status = TimerStatus::COMPLETED; - task.end_time = time(nullptr); - ESP_LOGI(TAG, "Countdown timer %s completed", task_id); - manager.NotifyTaskCompleted(task); - } else if (task.type == TimerType::DELAYED_EXEC) { - // 延时执行MCP工具 - manager.ExecuteTask(task); - } - } - - // 删除一次性定时器 - if (xTimerIsTimerActive(timer_handle) == pdFALSE) { - xTimerDelete(timer_handle, 0); - manager.timers_.erase(task_id); - } -} \ No newline at end of file diff --git a/main/timer_manager.h b/main/timer_manager.h deleted file mode 100644 index af63f67..0000000 --- a/main/timer_manager.h +++ /dev/null @@ -1,165 +0,0 @@ -#ifndef TIMER_MANAGER_H -#define TIMER_MANAGER_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// 定时任务类型 -enum class TimerType { - COUNTDOWN, // 倒计时 - DELAYED_EXEC, // 延时执行 - PERIODIC, // 周期性任务 - SCHEDULED // 定时执行 -}; - -// 定时任务状态 -enum class TimerStatus { - PENDING, // 等待中 - RUNNING, // 运行中 - COMPLETED, // 已完成 - CANCELLED, // 已取消 - FAILED // 失败 -}; - -// MCP工具回调函数类型 -using McpToolCallback = std::function; - -// 定时任务结构 -struct TimerTask { - std::string id; // 唯一标识符 - std::string name; // 任务名称 - TimerType type; // 任务类型 - TimerStatus status; // 任务状态 - uint32_t duration_ms; // 持续时间(毫秒) - time_t scheduled_time; // 预定执行时间 - time_t created_time; // 创建时间 - time_t start_time; // 开始时间 - time_t end_time; // 结束时间 - - // MCP工具相关 - std::string mcp_tool_name; // MCP工具名称 - std::string mcp_tool_args; // MCP工具参数 - McpToolCallback callback; // 回调函数 - - // 周期性任务相关 - uint32_t interval_ms; // 间隔时间(毫秒) - int repeat_count; // 重复次数(-1表示无限) - int current_repeat; // 当前重复次数 - - // 用户数据 - std::string user_data; // 用户自定义数据 - std::string description; // 任务描述 - - TimerTask() : type(TimerType::COUNTDOWN), status(TimerStatus::PENDING), - duration_ms(0), scheduled_time(0), created_time(0), - start_time(0), end_time(0), interval_ms(0), - repeat_count(0), current_repeat(0) {} -}; - -class TimerManager { -public: - static TimerManager& GetInstance() { - static TimerManager instance; - return instance; - } - - // 倒计时器功能 - std::string CreateCountdownTimer(const std::string& name, - uint32_t duration_ms, - const std::string& description = ""); - - // 延时执行MCP工具 - std::string CreateDelayedMcpTask(const std::string& name, - uint32_t delay_ms, - const std::string& mcp_tool_name, - const std::string& mcp_tool_args = "", - const std::string& description = ""); - - // 周期性任务 - std::string CreatePeriodicTask(const std::string& name, - uint32_t interval_ms, - int repeat_count = -1, // -1表示无限重复 - const std::string& mcp_tool_name = "", - const std::string& mcp_tool_args = "", - const std::string& description = ""); - - // 定时执行任务 - std::string CreateScheduledTask(const std::string& name, - time_t scheduled_time, - const std::string& mcp_tool_name, - const std::string& mcp_tool_args = "", - const std::string& description = ""); - - // 任务管理 - bool StartTask(const std::string& task_id); - bool StopTask(const std::string& task_id); - bool CancelTask(const std::string& task_id); - bool DeleteTask(const std::string& task_id); - - // 查询功能 - TimerTask* GetTask(const std::string& task_id); - std::vector GetAllTasks(); - std::vector GetTasksByStatus(TimerStatus status); - std::vector GetRunningTasks(); - std::vector GetUpcomingTasks(int minutes = 60); - - // 统计功能 - int GetTaskCount(); - int GetTaskCountByStatus(TimerStatus status); - int GetTaskCountByType(TimerType type); - - // 系统控制 - void StartManager(); - void StopManager(); - bool IsRunning(); - - // 回调设置 - void SetTaskCompletedCallback(std::function callback); - void SetTaskFailedCallback(std::function callback); - - // 数据持久化 - bool SaveToStorage(); - bool LoadFromStorage(); - - // 导出功能 - std::string ExportToJson(); - -private: - TimerManager(); - ~TimerManager(); - - // 内部方法 - std::string GenerateTaskId(); - void TaskWorker(); - void ExecuteTask(TimerTask& task); - bool ExecuteMcpTool(const std::string& tool_name, const std::string& args); - void UpdateTaskStatus(TimerTask& task, TimerStatus status); - void NotifyTaskCompleted(const TimerTask& task); - void NotifyTaskFailed(const TimerTask& task, const std::string& error); - - // FreeRTOS定时器回调 - static void TimerCallback(TimerHandle_t timer_handle); - - std::map tasks_; - std::map timers_; - std::mutex tasks_mutex_; - std::atomic is_running_; - std::thread worker_thread_; - - // 回调函数 - std::function task_completed_callback_; - std::function task_failed_callback_; - - static const char* TAG; -}; - -#endif // TIMER_MANAGER_H \ No newline at end of file diff --git a/partitions/v1/16m.csv b/partitions/v1/16m.csv index bc3a0e8..41d4854 100644 --- a/partitions/v1/16m.csv +++ b/partitions/v1/16m.csv @@ -1,8 +1,8 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x4000, -otadata, data, ota, 0xd000, 0x2000, -phy_init, data, phy, 0xf000, 0x1000, -model, data, spiffs, 0x10000, 0xF0000, -ota_0, app, ota_0, 0x100000, 6M, -ota_1, app, ota_1, 0x700000, 6M, +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +model, data, spiffs, 0x10000, 0xF0000, +ota_0, app, ota_0, 0x100000, 6M, +ota_1, app, ota_1, 0x700000, 6M, diff --git a/partitions/v1/16m_custom_wakeword.csv b/partitions/v1/16m_custom_wakeword.csv index 868294e..4995ec7 100644 --- a/partitions/v1/16m_custom_wakeword.csv +++ b/partitions/v1/16m_custom_wakeword.csv @@ -1,8 +1,8 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x4000, -otadata, data, ota, 0xd000, 0x2000, -phy_init, data, phy, 0xf000, 0x1000, -model, data, spiffs, 0x10000, 0x3f0000, -ota_0, app, ota_0, 0x400000, 6M, +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +model, data, spiffs, 0x10000, 0x3f0000, +ota_0, app, ota_0, 0x400000, 6M, ota_1, app, ota_1, 0xa00000, 6M, \ No newline at end of file diff --git a/partitions/v1/16m_echoear.csv b/partitions/v1/16m_echoear.csv index 543c92c..c2195ec 100644 --- a/partitions/v1/16m_echoear.csv +++ b/partitions/v1/16m_echoear.csv @@ -1,9 +1,9 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x4000, -otadata, data, ota, 0xd000, 0x2000, -phy_init, data, phy, 0xf000, 0x1000, -model, data, spiffs, 0x10000, 0xF0000, -ota_0, app, ota_0, 0x100000, 5M, -ota_1, app, ota_1, 0x700000, 5M, -assets_A, data, spiffs, , 4000K, +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +model, data, spiffs, 0x10000, 0xF0000, +ota_0, app, ota_0, 0x100000, 5M, +ota_1, app, ota_1, 0x700000, 5M, +assets_A, data, spiffs, , 4000K, diff --git a/partitions/v1/32m.csv b/partitions/v1/32m.csv index e95eb22..72a688b 100644 --- a/partitions/v1/32m.csv +++ b/partitions/v1/32m.csv @@ -1,10 +1,10 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvsfactory, data, nvs, , 200K, -nvs, data, nvs, , 840K, -otadata, data, ota, , 0x2000, -phy_init, data, phy, , 0x1000, -model, data, spiffs, , 0xF0000, -# According to scripts/versions.py, app partition must be aligned to 1MB -ota_0, app, ota_0, 0x200000, 12M, -ota_1, app, ota_1, , 12M, +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvsfactory, data, nvs, , 200K, +nvs, data, nvs, , 840K, +otadata, data, ota, , 0x2000, +phy_init, data, phy, , 0x1000, +model, data, spiffs, , 0xF0000, +# According to scripts/versions.py, app partition must be aligned to 1MB +ota_0, app, ota_0, 0x200000, 12M, +ota_1, app, ota_1, , 12M, diff --git a/partitions/v1/4m.csv b/partitions/v1/4m.csv index 101349f..8623a7f 100644 --- a/partitions/v1/4m.csv +++ b/partitions/v1/4m.csv @@ -1,7 +1,7 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x4000, -otadata, data, ota, 0xd000, 0x2000, -phy_init, data, phy, 0xf000, 0x1000, -model, data, spiffs, 0x10000, 0xF0000, -factory, app, factory, 0x100000, 3M, +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +model, data, spiffs, 0x10000, 0xF0000, +factory, app, factory, 0x100000, 3M, diff --git a/partitions/v1/4m_esp-hi.csv b/partitions/v1/4m_esp-hi.csv index 90c9c43..47e6a1f 100644 --- a/partitions/v1/4m_esp-hi.csv +++ b/partitions/v1/4m_esp-hi.csv @@ -1,8 +1,8 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x4000, -otadata, data, ota, 0xd000, 0x2000, -phy_init, data, phy, 0xf000, 0x1000, -model, data, spiffs, 0x10000, 0xD0000, -factory, app, factory, 0xe0000, 2200K, -assets_A, data, spiffs, , 700K, +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +model, data, spiffs, 0x10000, 0xD0000, +factory, app, factory, 0xe0000, 2200K, +assets_A, data, spiffs, , 700K, diff --git a/partitions/v1/8m.csv b/partitions/v1/8m.csv index 1e0e943..9974e01 100644 --- a/partitions/v1/8m.csv +++ b/partitions/v1/8m.csv @@ -1,8 +1,8 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x4000, -otadata, data, ota, 0xd000, 0x2000, -phy_init, data, phy, 0xf000, 0x1000, -model, data, spiffs, 0x10000, 0xF0000, -ota_0, app, ota_0, 0x100000, 0x380000, -ota_1, app, ota_1, 0x480000, 0x380000, +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +model, data, spiffs, 0x10000, 0xF0000, +ota_0, app, ota_0, 0x100000, 0x380000, +ota_1, app, ota_1, 0x480000, 0x380000, diff --git a/partitions/v2/16m.csv b/partitions/v2/16m.csv index bdfe26b..e3dce44 100644 --- a/partitions/v2/16m.csv +++ b/partitions/v2/16m.csv @@ -1,9 +1,8 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x4000, -otadata, data, ota, 0xd000, 0x2000, -phy_init, data, phy, 0xf000, 0x1000, -model, data, spiffs, 0x10000, 0xF0000, -ota_0, app, ota_0, 0x100000, 4M, -ota_1, app, ota_1, 0x500000, 4M, -assets, data, spiffs, 0x900000, 7M +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +ota_0, app, ota_0, 0x20000, 0x3f0000, +ota_1, app, ota_1, , 0x3f0000, +assets, data, spiffs, 0x800000, 8M diff --git a/partitions/v2/16m_c3.csv b/partitions/v2/16m_c3.csv index be67e32..7e08588 100644 --- a/partitions/v2/16m_c3.csv +++ b/partitions/v2/16m_c3.csv @@ -1,9 +1,8 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x4000, -otadata, data, ota, 0xd000, 0x2000, -phy_init, data, phy, 0xf000, 0x1000, -model, data, spiffs, 0x10000, 0xF0000, -ota_0, app, ota_0, 0x100000, 4M, -ota_1, app, ota_1, 0x500000, 4M, -assets, data, spiffs, 0x900000, 4000K +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +ota_0, app, ota_0, 0x20000, 0x3f0000, +ota_1, app, ota_1, , 0x3f0000, +assets, data, spiffs, 0x800000, 4000K diff --git a/partitions/v2/32m.csv b/partitions/v2/32m.csv index 27a814f..7b7dda4 100644 --- a/partitions/v2/32m.csv +++ b/partitions/v2/32m.csv @@ -1,10 +1,9 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvsfactory, data, nvs, , 200K, -nvs, data, nvs, , 840K, -otadata, data, ota, , 0x2000, -phy_init, data, phy, , 0x1000, -model, data, spiffs, , 0xF0000, -ota_0, app, ota_0, 0x200000, 4M, -ota_1, app, ota_1, 0x600000, 4M, -assets, data, spiffs, 0xA00000, 16M +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvsfactory, data, nvs, , 200K, +nvs, data, nvs, , 840K, +otadata, data, ota, , 0x2000, +phy_init, data, phy, , 0x1000, +ota_0, app, ota_0, 0x200000, 4M, +ota_1, app, ota_1, 0x600000, 4M, +assets, data, spiffs, 0xA00000, 16M diff --git a/partitions/v2/4m.csv b/partitions/v2/4m.csv new file mode 100644 index 0000000..ad9f065 --- /dev/null +++ b/partitions/v2/4m.csv @@ -0,0 +1,7 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x270000, +assets, data, spiffs, 0x280000, 0x180000, diff --git a/partitions/v2/8m.csv b/partitions/v2/8m.csv index a896a33..a37c8f8 100644 --- a/partitions/v2/8m.csv +++ b/partitions/v2/8m.csv @@ -1,9 +1,8 @@ -# ESP-IDF Partition Table -# Name, Type, SubType, Offset, Size, Flags -nvs, data, nvs, 0x9000, 0x4000, -otadata, data, ota, 0xd000, 0x2000, -phy_init, data, phy, 0xf000, 0x1000, -model, data, spiffs, 0x10000, 0xF0000, -ota_0, app, ota_0, 0x100000, 3M, -ota_1, app, ota_1, 0x400000, 3M, -assets, data, spiffs, 0x700000, 1M +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +ota_0, app, ota_0, 0x20000, 0x2f0000, +ota_1, app, ota_1, , 0x2f0000, +assets, data, spiffs, 0x600000, 2M diff --git a/partitions/v2/README.md b/partitions/v2/README.md index a373e53..6aed9a8 100644 --- a/partitions/v2/README.md +++ b/partitions/v2/README.md @@ -1,46 +1,107 @@ -# Version 2 Partition Table - -This version introduces significant improvements over v1 by adding an `assets` partition to support network-loadable content. - -## Key Changes from v1 - -### Added Assets Partition -The v2 partition table includes a new `assets` partition that stores: -- **Wake word models**: Customizable wake word models that can be loaded from the network -- **Theme files**: Complete theming system including: - - Fonts - - Audio effects - - Background images - - Custom emoji packs - -### Partition Layout Comparison - -#### v1 Layout (16MB) -- `nvs`: 16KB (non-volatile storage) -- `otadata`: 8KB (OTA data) -- `phy_init`: 4KB (PHY initialization data) -- `model`: 960KB (model storage) -- `ota_0`: 6MB (application partition 0) -- `ota_1`: 6MB (application partition 1) - -#### v2 Layout (16MB) -- `nvs`: 16KB (non-volatile storage) -- `otadata`: 8KB (OTA data) -- `phy_init`: 4KB (PHY initialization data) -- `model`: 960KB (model storage) -- `ota_0`: 4MB (application partition 0) -- `ota_1`: 4MB (application partition 1) -- `assets`: 7MB (network-loadable assets) - -### Benefits - -1. **Dynamic Content**: Users can download and update wake word models and themes without reflashing -2. **Reduced App Size**: Application partitions are smaller, allowing more space for assets -3. **Customization**: Support for custom themes and wake words enhances user experience -4. **Network Flexibility**: Assets can be updated independently of the main application - -### Available Configurations - -- `8m.csv`: For 8MB flash devices -- `16m.csv`: For 16MB flash devices (standard) -- `16m_c3.csv`: For 16MB flash devices with ESP32-C3 optimization \ No newline at end of file +# Version 2 Partition Table + +This version introduces significant improvements over v1 by adding an `assets` partition to support network-loadable content and optimizing partition layouts for different flash sizes. + +## Key Changes from v1 + +### Major Improvements +1. **Added Assets Partition**: New `assets` partition for network-loadable content +2. **Replaced Model Partition**: The old `model` partition (960KB) is replaced with a larger `assets` partition +3. **Optimized App Partitions**: Reduced application partition sizes to accommodate assets +4. **Enhanced Flexibility**: Support for dynamic content updates without reflashing + +### Assets Partition Features +The `assets` partition stores: +- **Wake word models**: Customizable wake word models that can be loaded from the network +- **Theme files**: Complete theming system including: + - Fonts (text and icon fonts) + - Audio effects and sound files + - Background images and UI elements + - Custom emoji packs + - Language configuration files +- **Dynamic Content**: All content can be updated over-the-air via HTTP downloads + +## Partition Layout Comparison + +### v1 Layout (16MB) +- `nvs`: 16KB (non-volatile storage) +- `otadata`: 8KB (OTA data) +- `phy_init`: 4KB (PHY initialization data) +- `model`: 960KB (model storage - fixed content) +- `ota_0`: 6MB (application partition 0) +- `ota_1`: 6MB (application partition 1) + +### v2 Layout (16MB) +- `nvs`: 16KB (non-volatile storage) +- `otadata`: 8KB (OTA data) +- `phy_init`: 4KB (PHY initialization data) +- `ota_0`: 4MB (application partition 0) +- `ota_1`: 4MB (application partition 1) +- `assets`: 8MB (network-loadable assets) + +## Available Configurations + +### 8MB Flash Devices (`8m.csv`) +- `nvs`: 16KB +- `otadata`: 8KB +- `phy_init`: 4KB +- `ota_0`: 3MB +- `ota_1`: 3MB +- `assets`: 2MB + +### 16MB Flash Devices (`16m.csv`) - Standard +- `nvs`: 16KB +- `otadata`: 8KB +- `phy_init`: 4KB +- `ota_0`: 4MB +- `ota_1`: 4MB +- `assets`: 8MB + +### 16MB Flash Devices (`16m_c3.csv`) - ESP32-C3 Optimized +- `nvs`: 16KB +- `otadata`: 8KB +- `phy_init`: 4KB +- `ota_0`: 4MB +- `ota_1`: 4MB +- `assets`: 4MB (4000K - limited by available mmap pages) + +### 32MB Flash Devices (`32m.csv`) +- `nvsfactory`: 200KB +- `nvs`: 840KB +- `otadata`: 8KB +- `phy_init`: 4KB +- `ota_0`: 4MB +- `ota_1`: 4MB +- `assets`: 16MB + +## Benefits + +1. **Dynamic Content Management**: Users can download and update wake word models, themes, and other assets without reflashing the device +2. **Reduced App Size**: Application partitions are optimized, allowing more space for dynamic content +3. **Enhanced Customization**: Support for custom themes, wake words, and language packs enhances user experience +4. **Network Flexibility**: Assets can be updated independently of the main application firmware +5. **Better Resource Utilization**: Efficient use of flash memory with configurable asset storage +6. **OTA Asset Updates**: Assets can be updated over-the-air via HTTP downloads + +## Technical Details + +- **Partition Type**: Assets partition uses `spiffs` subtype for SPIFFS filesystem compatibility +- **Memory Mapping**: Assets are memory-mapped for efficient access during runtime +- **Checksum Validation**: Built-in integrity checking ensures asset data validity +- **Progressive Download**: Assets can be downloaded progressively with progress tracking +- **Fallback Support**: Graceful fallback to default assets if network updates fail + +## Migration from v1 + +When upgrading from v1 to v2: +1. **Backup Important Data**: Ensure any important data in the old `model` partition is backed up +2. **Flash New Partition Table**: Use the appropriate v2 partition table for your flash size +3. **Download Assets**: The device will automatically download required assets on first boot +4. **Verify Functionality**: Ensure all features work correctly with the new partition layout + +## Usage Notes + +- The `assets` partition size varies by configuration to optimize for different flash sizes +- ESP32-C3 devices use a smaller assets partition (4MB) due to limited available mmap pages in the system +- 32MB devices get the largest assets partition (16MB) for maximum content storage +- All partition tables maintain proper alignment for optimal flash performance \ No newline at end of file diff --git a/scripts/Image_Converter/LVGLImage.py b/scripts/Image_Converter/LVGLImage.py index b2ffbb3..f1de12f 100644 --- a/scripts/Image_Converter/LVGLImage.py +++ b/scripts/Image_Converter/LVGLImage.py @@ -1,1426 +1,1426 @@ -#!/usr/bin/env python3 -import os -import logging -import argparse -import subprocess -from os import path -from enum import Enum -from typing import List -from pathlib import Path - -try: - import png -except ImportError: - raise ImportError("Need pypng package, do `pip3 install pypng`") - -try: - import lz4.block -except ImportError: - raise ImportError("Need lz4 package, do `pip3 install lz4`") - - -def uint8_t(val) -> bytes: - return val.to_bytes(1, byteorder='little') - - -def uint16_t(val) -> bytes: - return val.to_bytes(2, byteorder='little') - - -def uint24_t(val) -> bytes: - return val.to_bytes(3, byteorder='little') - - -def uint32_t(val) -> bytes: - try: - return val.to_bytes(4, byteorder='little') - except OverflowError: - raise ParameterError(f"overflow: {hex(val)}") - - -def color_pre_multiply(r, g, b, a, background): - bb = background & 0xff - bg = (background >> 8) & 0xff - br = (background >> 16) & 0xff - - return ((r * a + (255 - a) * br) >> 8, (g * a + (255 - a) * bg) >> 8, - (b * a + (255 - a) * bb) >> 8, a) - - -class Error(Exception): - - def __str__(self): - return self.__class__.__name__ + ': ' + ' '.join(self.args) - - -class FormatError(Error): - """ - Problem with input filename format. - BIN filename does not conform to standard lvgl bin image format - """ - - -class ParameterError(Error): - """ - Parameter for LVGL image not correct - """ - - -class PngQuant: - """ - Compress PNG file to 8bit mode using `pngquant` - """ - - def __init__(self, ncolors=256, dither=True, exec_path="") -> None: - executable = path.join(exec_path, "pngquant") - self.cmd = (f"{executable} {'--nofs' if not dither else ''} " - f"{ncolors} --force - < ") - - def convert(self, filename) -> bytes: - if not os.path.isfile(filename): - raise BaseException(f"file not found: {filename}") - - try: - compressed = subprocess.check_output( - f'{self.cmd} "{str(filename)}"', - stderr=subprocess.STDOUT, - shell=True) - except subprocess.CalledProcessError: - raise BaseException( - "cannot find pngquant tool, install it via " - "`sudo apt install pngquant` for debian " - "or `brew install pngquant` for macintosh " - "For windows, you may need to download pngquant.exe from " - "https://pngquant.org/, and put it in your PATH.") - - return compressed - - -class CompressMethod(Enum): - NONE = 0x00 - RLE = 0x01 - LZ4 = 0x02 - - -class ColorFormat(Enum): - UNKNOWN = 0x00 - RAW = 0x01, - RAW_ALPHA = 0x02, - L8 = 0x06 - I1 = 0x07 - I2 = 0x08 - I4 = 0x09 - I8 = 0x0A - A1 = 0x0B - A2 = 0x0C - A4 = 0x0D - A8 = 0x0E - ARGB8888 = 0x10 - XRGB8888 = 0x11 - RGB565 = 0x12 - ARGB8565 = 0x13 - RGB565A8 = 0x14 - RGB888 = 0x0F - - @property - def bpp(self) -> int: - """ - Return bit per pixel for this cf - """ - cf_map = { - ColorFormat.L8: 8, - ColorFormat.I1: 1, - ColorFormat.I2: 2, - ColorFormat.I4: 4, - ColorFormat.I8: 8, - ColorFormat.A1: 1, - ColorFormat.A2: 2, - ColorFormat.A4: 4, - ColorFormat.A8: 8, - ColorFormat.ARGB8888: 32, - ColorFormat.XRGB8888: 32, - ColorFormat.RGB565: 16, - ColorFormat.RGB565A8: 16, # 16bpp + a8 map - ColorFormat.ARGB8565: 24, - ColorFormat.RGB888: 24, - } - - return cf_map[self] if self in cf_map else 0 - - @property - def ncolors(self) -> int: - """ - Return number of colors in palette if cf is indexed1/2/4/8. - Return zero if cf is not indexed format - """ - - cf_map = { - ColorFormat.I1: 2, - ColorFormat.I2: 4, - ColorFormat.I4: 16, - ColorFormat.I8: 256, - } - return cf_map.get(self, 0) - - @property - def is_indexed(self) -> bool: - """ - Return if cf is indexed color format - """ - return self.ncolors != 0 - - @property - def is_alpha_only(self) -> bool: - return ColorFormat.A1.value <= self.value <= ColorFormat.A8.value - - @property - def has_alpha(self) -> bool: - return self.is_alpha_only or self.is_indexed or self in ( - ColorFormat.ARGB8888, - ColorFormat.XRGB8888, # const alpha: 0xff - ColorFormat.ARGB8565, - ColorFormat.RGB565A8) - - @property - def is_colormap(self) -> bool: - return self in (ColorFormat.ARGB8888, ColorFormat.RGB888, - ColorFormat.XRGB8888, ColorFormat.RGB565A8, - ColorFormat.ARGB8565, ColorFormat.RGB565) - - @property - def is_luma_only(self) -> bool: - return self in (ColorFormat.L8, ) - - -def bit_extend(value, bpp): - """ - Extend value from bpp to 8 bit with interpolation to reduce rounding error. - """ - - if value == 0: - return 0 - - res = value - bpp_now = bpp - while bpp_now < 8: - res |= value << (8 - bpp_now) - bpp_now += bpp - - return res - - -def unpack_colors(data: bytes, cf: ColorFormat, w) -> List: - """ - Unpack lvgl 1/2/4/8/16/32 bpp color to png color: alpha map, grey scale, - or R,G,B,(A) map - """ - ret = [] - bpp = cf.bpp - if bpp == 8: - ret = data - elif bpp == 4: - if cf == ColorFormat.A4: - values = [x * 17 for x in range(16)] - else: - values = [x for x in range(16)] - - for p in data: - for i in range(2): - ret.append(values[(p >> (4 - i * 4)) & 0x0f]) - if len(ret) % w == 0: - break - - elif bpp == 2: - if cf == ColorFormat.A2: - values = [x * 85 for x in range(4)] - else: # must be ColorFormat.I2 - values = [x for x in range(4)] - for p in data: - for i in range(4): - ret.append(values[(p >> (6 - i * 2)) & 0x03]) - if len(ret) % w == 0: - break - elif bpp == 1: - if cf == ColorFormat.A1: - values = [0, 255] - else: - values = [0, 1] - for p in data: - for i in range(8): - ret.append(values[(p >> (7 - i)) & 0x01]) - if len(ret) % w == 0: - break - elif bpp == 16: - # This is RGB565 - pixels = [(data[2 * i + 1] << 8) | data[2 * i] - for i in range(len(data) // 2)] - - for p in pixels: - ret.append(bit_extend((p >> 11) & 0x1f, 5)) # R - ret.append(bit_extend((p >> 5) & 0x3f, 6)) # G - ret.append(bit_extend((p >> 0) & 0x1f, 5)) # B - elif bpp == 24: - if cf == ColorFormat.RGB888: - B = data[0::3] - G = data[1::3] - R = data[2::3] - for r, g, b in zip(R, G, B): - ret += [r, g, b] - elif cf == ColorFormat.RGB565A8: - alpha_size = len(data) // 3 - pixel_alpha = data[-alpha_size:] - pixel_data = data[:-alpha_size] - pixels = [(pixel_data[2 * i + 1] << 8) | pixel_data[2 * i] - for i in range(len(pixel_data) // 2)] - - for a, p in zip(pixel_alpha, pixels): - ret.append(bit_extend((p >> 11) & 0x1f, 5)) # R - ret.append(bit_extend((p >> 5) & 0x3f, 6)) # G - ret.append(bit_extend((p >> 0) & 0x1f, 5)) # B - ret.append(a) - elif cf == ColorFormat.ARGB8565: - L = data[0::3] - H = data[1::3] - A = data[2::3] - - for h, l, a in zip(H, L, A): - p = (h << 8) | (l) - ret.append(bit_extend((p >> 11) & 0x1f, 5)) # R - ret.append(bit_extend((p >> 5) & 0x3f, 6)) # G - ret.append(bit_extend((p >> 0) & 0x1f, 5)) # B - ret.append(a) # A - - elif bpp == 32: - B = data[0::4] - G = data[1::4] - R = data[2::4] - A = data[3::4] - for r, g, b, a in zip(R, G, B, A): - ret += [r, g, b, a] - else: - assert 0 - - return ret - - -def write_c_array_file( - w: int, h: int, - stride: int, - cf: ColorFormat, - filename: str, - premultiplied: bool, - compress: CompressMethod, - data: bytes): - varname = path.basename(filename).split('.')[0] - varname = varname.replace("-", "_") - varname = varname.replace(".", "_") - - flags = "0" - if compress is not CompressMethod.NONE: - flags += " | LV_IMAGE_FLAGS_COMPRESSED" - if premultiplied: - flags += " | LV_IMAGE_FLAGS_PREMULTIPLIED" - - macro = "LV_ATTRIBUTE_" + varname.upper() - header = f''' -#if defined(LV_LVGL_H_INCLUDE_SIMPLE) -#include "lvgl.h" -#elif defined(LV_BUILD_TEST) -#include "../lvgl.h" -#else -#include "lvgl/lvgl.h" -#endif - - -#ifndef LV_ATTRIBUTE_MEM_ALIGN -#define LV_ATTRIBUTE_MEM_ALIGN -#endif - -#ifndef {macro} -#define {macro} -#endif - -static const -LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST {macro} -uint8_t {varname}_map[] = {{ -''' - - ending = f''' -}}; - -const lv_image_dsc_t {varname} = {{ - .header.magic = LV_IMAGE_HEADER_MAGIC, - .header.cf = LV_COLOR_FORMAT_{cf.name}, - .header.flags = {flags}, - .header.w = {w}, - .header.h = {h}, - .header.stride = {stride}, - .data_size = sizeof({varname}_map), - .data = {varname}_map, -}}; - -''' - - def write_binary(f, data, stride): - stride = 16 if stride == 0 else stride - for i, v in enumerate(data): - if i % stride == 0: - f.write("\n ") - f.write(f"0x{v:02x},") - f.write("\n") - - with open(filename, "w+") as f: - f.write(header) - - if compress != CompressMethod.NONE: - write_binary(f, data, 16) - else: - # write palette separately - ncolors = cf.ncolors - if ncolors: - write_binary(f, data[:ncolors * 4], 16) - - write_binary(f, data[ncolors * 4:], stride) - - f.write(ending) - - -class LVGLImageHeader: - - def __init__(self, - cf: ColorFormat = ColorFormat.UNKNOWN, - w: int = 0, - h: int = 0, - stride: int = 0, - align: int = 1, - flags: int = 0): - self.cf = cf - self.flags = flags - self.w = w & 0xffff - self.h = h & 0xffff - if w > 0xffff or h > 0xffff: - raise ParameterError(f"w, h overflow: {w}x{h}") - if align < 1: - # stride align in bytes must be larger than 1 - raise ParameterError(f"Invalid stride align: {align}") - - self.stride = self.stride_align(align) if stride == 0 else stride - - def stride_align(self, align: int) -> int: - stride = self.stride_default - if align == 1: - pass - elif align > 1: - stride = (stride + align - 1) // align - stride *= align - else: - raise ParameterError(f"Invalid stride align: {align}") - - self.stride = stride - return stride - - @property - def stride_default(self) -> int: - return (self.w * self.cf.bpp + 7) // 8 - - @property - def binary(self) -> bytearray: - binary = bytearray() - binary += uint8_t(0x19) # magic number for lvgl version 9 - binary += uint8_t(self.cf.value) - binary += uint16_t(self.flags) # 16bits flags - - binary += uint16_t(self.w) # 16bits width - binary += uint16_t(self.h) # 16bits height - binary += uint16_t(self.stride) # 16bits stride - - binary += uint16_t(0) # 16bits reserved - return binary - - def from_binary(self, data: bytes): - if len(data) < 12: - raise FormatError("invalid header length") - - try: - self.cf = ColorFormat(data[1] & 0x1f) # color format - except ValueError as exc: - raise FormatError(f"invalid color format: {hex(data[0])}") from exc - self.w = int.from_bytes(data[4:6], 'little') - self.h = int.from_bytes(data[6:8], 'little') - self.stride = int.from_bytes(data[8:10], 'little') - return self - - -class LVGLCompressData: - - def __init__(self, - cf: ColorFormat, - method: CompressMethod, - raw_data: bytes = b''): - self.blk_size = (cf.bpp + 7) // 8 - self.compress = method - self.raw_data = raw_data - self.raw_data_len = len(raw_data) - self.compressed = self._compress(raw_data) - - def _compress(self, raw_data: bytes) -> bytearray: - if self.compress == CompressMethod.NONE: - return raw_data - - if self.compress == CompressMethod.RLE: - # RLE compression performs on pixel unit, pad data to pixel unit - pad = b'\x00' * 0 - if self.raw_data_len % self.blk_size: - pad = b'\x00' * (self.blk_size - self.raw_data_len % self.blk_size) - compressed = RLEImage().rle_compress(raw_data + pad, self.blk_size) - elif self.compress == CompressMethod.LZ4: - compressed = lz4.block.compress(raw_data, store_size=False) - else: - raise ParameterError(f"Invalid compress method: {self.compress}") - - self.compressed_len = len(compressed) - - bin = bytearray() - bin += uint32_t(self.compress.value) - bin += uint32_t(self.compressed_len) - bin += uint32_t(self.raw_data_len) - bin += compressed - return bin - - -class LVGLImage: - - def __init__(self, - cf: ColorFormat = ColorFormat.UNKNOWN, - w: int = 0, - h: int = 0, - data: bytes = b'') -> None: - self.stride = 0 # default no valid stride value - self.premultiplied = False - self.rgb565_dither = False - self.set_data(cf, w, h, data) - - def __repr__(self) -> str: - return (f"'LVGL image {self.w}x{self.h}, {self.cf.name}, " - f"{'Pre-multiplied, ' if self.premultiplied else ''}" - f"stride: {self.stride} " - f"(12+{self.data_len})Byte'") - - def adjust_stride(self, stride: int = 0, align: int = 1): - """ - Stride can be set directly, or by stride alignment in bytes - """ - if self.stride == 0: - # stride can only be 0, when LVGLImage is created with empty data - logging.warning("Cannot adjust stride for empty image") - return - - if align >= 1 and stride == 0: - # The header with specified stride alignment - header = LVGLImageHeader(self.cf, self.w, self.h, align=align) - stride = header.stride - elif stride > 0: - pass - else: - raise ParameterError(f"Invalid parameter, align:{align}," - f" stride:{stride}") - - if self.stride == stride: - return # no stride adjustment - - # if current image is empty, no need to do anything - if self.data_len == 0: - self.stride = 0 - return - - current = LVGLImageHeader(self.cf, self.w, self.h, stride=self.stride) - - if stride < current.stride_default: - raise ParameterError(f"Stride is too small:{stride}, " - f"minimal:{current.stride_default}") - - def change_stride(data: bytearray, h, current_stride, new_stride): - data_in = data - data_out = [] # stride adjusted new data - if new_stride < current_stride: # remove padding byte - for i in range(h): - start = i * current_stride - end = start + new_stride - data_out.append(data_in[start:end]) - else: # adding more padding bytes - padding = b'\x00' * (new_stride - current_stride) - for i in range(h): - data_out.append(data_in[i * current_stride:(i + 1) * - current_stride]) - data_out.append(padding) - return b''.join(data_out) - - palette_size = self.cf.ncolors * 4 - data_out = [self.data[:palette_size]] - data_out.append( - change_stride(self.data[palette_size:], self.h, current.stride, - stride)) - - # deal with alpha map for RGB565A8 - if self.cf == ColorFormat.RGB565A8: - logging.warning("handle RGB565A8 alpha map") - a8_stride = self.stride // 2 - a8_map = self.data[-a8_stride * self.h:] - data_out.append( - change_stride(a8_map, self.h, current.stride // 2, - stride // 2)) - - self.stride = stride - self.data = bytearray(b''.join(data_out)) - - def premultiply(self): - """ - Pre-multiply image RGB data with alpha, set corresponding image header flags - """ - if self.premultiplied: - raise ParameterError("Image already pre-multiplied") - - if not self.cf.has_alpha: - raise ParameterError(f"Image has no alpha channel: {self.cf.name}") - - if self.cf.is_indexed: - - def multiply(r, g, b, a): - r, g, b = (r * a) >> 8, (g * a) >> 8, (b * a) >> 8 - return uint8_t(b) + uint8_t(g) + uint8_t(r) + uint8_t(a) - - # process the palette only. - palette_size = self.cf.ncolors * 4 - palette = self.data[:palette_size] - palette = [ - multiply(palette[i], palette[i + 1], palette[i + 2], - palette[i + 3]) for i in range(0, len(palette), 4) - ] - palette = b''.join(palette) - self.data = palette + self.data[palette_size:] - elif self.cf is ColorFormat.ARGB8888: - - def multiply(b, g, r, a): - r, g, b = (r * a) >> 8, (g * a) >> 8, (b * a) >> 8 - return uint32_t((a << 24) | (r << 16) | (g << 8) | (b << 0)) - - line_width = self.w * 4 - for h in range(self.h): - offset = h * self.stride - map = self.data[offset:offset + self.stride] - - processed = b''.join([ - multiply(map[i], map[i + 1], map[i + 2], map[i + 3]) - for i in range(0, line_width, 4) - ]) - self.data[offset:offset + line_width] = processed - elif self.cf is ColorFormat.RGB565A8: - - def multiply(data, a): - r = (data >> 11) & 0x1f - g = (data >> 5) & 0x3f - b = (data >> 0) & 0x1f - - r, g, b = (r * a) // 255, (g * a) // 255, (b * a) // 255 - return uint16_t((r << 11) | (g << 5) | (b << 0)) - - line_width = self.w * 2 - for h in range(self.h): - # alpha map offset for this line - offset = self.h * self.stride + h * (self.stride // 2) - a = self.data[offset:offset + self.stride // 2] - - # RGB map offset - offset = h * self.stride - rgb = self.data[offset:offset + self.stride] - - processed = b''.join([ - multiply((rgb[i + 1] << 8) | rgb[i], a[i // 2]) - for i in range(0, line_width, 2) - ]) - self.data[offset:offset + line_width] = processed - elif self.cf is ColorFormat.ARGB8565: - - def multiply(data, a): - r = (data >> 11) & 0x1f - g = (data >> 5) & 0x3f - b = (data >> 0) & 0x1f - - r, g, b = (r * a) // 255, (g * a) // 255, (b * a) // 255 - return uint24_t((a << 16) | (r << 11) | (g << 5) | (b << 0)) - - line_width = self.w * 3 - for h in range(self.h): - offset = h * self.stride - map = self.data[offset:offset + self.stride] - - processed = b''.join([ - multiply((map[i + 1] << 8) | map[i], map[i + 2]) - for i in range(0, line_width, 3) - ]) - self.data[offset:offset + line_width] = processed - else: - raise ParameterError(f"Not supported yet: {self.cf.name}") - - self.premultiplied = True - - @property - def data_len(self) -> int: - """ - Return data_len in byte of this image, excluding image header - """ - - # palette is always in ARGB format, 4Byte per color - p = self.cf.ncolors * 4 if self.is_indexed and self.w * self.h else 0 - p += self.stride * self.h - if self.cf is ColorFormat.RGB565A8: - a8_stride = self.stride // 2 - p += a8_stride * self.h - return p - - @property - def header(self) -> bytearray: - return LVGLImageHeader(self.cf, self.w, self.h) - - @property - def is_indexed(self): - return self.cf.is_indexed - - def set_data(self, - cf: ColorFormat, - w: int, - h: int, - data: bytes, - stride: int = 0): - """ - Directly set LVGL image parameters - """ - - if w > 0xffff or h > 0xffff: - raise ParameterError(f"w, h overflow: {w}x{h}") - - self.cf = cf - self.w = w - self.h = h - - # if stride is 0, then it's aligned to 1byte by default, - # let image header handle it - self.stride = LVGLImageHeader(cf, w, h, stride, align=1).stride - - if self.data_len != len(data): - raise ParameterError(f"{self} data length error got: {len(data)}, " - f"expect: {self.data_len}, {self}") - - self.data = data - - return self - - def from_data(self, data: bytes): - header = LVGLImageHeader().from_binary(data) - return self.set_data(header.cf, header.w, header.h, - data[len(header.binary):], header.stride) - - def from_bin(self, filename: str): - """ - Read from existing bin file and update image parameters - """ - - if not filename.endswith(".bin"): - raise FormatError("filename not ended with '.bin'") - - with open(filename, "rb") as f: - data = f.read() - return self.from_data(data) - - def _check_ext(self, filename: str, ext): - if not filename.lower().endswith(ext): - raise FormatError(f"filename not ended with {ext}") - - def _check_dir(self, filename: str): - dir = path.dirname(filename) - if dir and not path.exists(dir): - logging.info(f"mkdir of {dir} for {filename}") - os.makedirs(dir) - - def to_bin(self, - filename: str, - compress: CompressMethod = CompressMethod.NONE): - """ - Write this image to file, filename should be ended with '.bin' - """ - self._check_ext(filename, ".bin") - self._check_dir(filename) - - with open(filename, "wb+") as f: - bin = bytearray() - flags = 0 - flags |= 0x08 if compress != CompressMethod.NONE else 0 - flags |= 0x01 if self.premultiplied else 0 - - header = LVGLImageHeader(self.cf, - self.w, - self.h, - self.stride, - flags=flags) - bin += header.binary - compressed = LVGLCompressData(self.cf, compress, self.data) - bin += compressed.compressed - - f.write(bin) - - return self - - def to_c_array(self, - filename: str, - compress: CompressMethod = CompressMethod.NONE): - self._check_ext(filename, ".c") - self._check_dir(filename) - - if compress != CompressMethod.NONE: - data = LVGLCompressData(self.cf, compress, self.data).compressed - else: - data = self.data - write_c_array_file(self.w, self.h, self.stride, self.cf, filename, - self.premultiplied, - compress, data) - - def to_png(self, filename: str): - self._check_ext(filename, ".png") - self._check_dir(filename) - - old_stride = self.stride - self.adjust_stride(align=1) - if self.cf.is_indexed: - data = self.data - # Separate lvgl bin image data to palette and bitmap - # The palette is in format of [(RGBA), (RGBA)...]. - # LVGL palette is in format of B,G,R,A,... - palette = [(data[i * 4 + 2], data[i * 4 + 1], data[i * 4 + 0], - data[i * 4 + 3]) for i in range(self.cf.ncolors)] - - data = data[self.cf.ncolors * 4:] - - encoder = png.Writer(self.w, - self.h, - palette=palette, - bitdepth=self.cf.bpp) - # separate packed data to plain data - data = unpack_colors(data, self.cf, self.w) - elif self.cf.is_alpha_only: - # separate packed data to plain data - transparency = unpack_colors(self.data, self.cf, self.w) - data = [] - for a in transparency: - data += [0, 0, 0, a] - encoder = png.Writer(self.w, self.h, greyscale=False, alpha=True) - elif self.cf == ColorFormat.L8: - # to grayscale - encoder = png.Writer(self.w, - self.h, - bitdepth=self.cf.bpp, - greyscale=True, - alpha=False) - data = self.data - elif self.cf.is_colormap: - encoder = png.Writer(self.w, - self.h, - alpha=self.cf.has_alpha, - greyscale=False) - data = unpack_colors(self.data, self.cf, self.w) - else: - logging.warning(f"missing logic: {self.cf.name}") - return - - with open(filename, "wb") as f: - encoder.write_array(f, data) - - self.adjust_stride(stride=old_stride) - - def from_png(self, - filename: str, - cf: ColorFormat = None, - background: int = 0x00_00_00, - rgb565_dither=False): - """ - Create lvgl image from png file. - If cf is none, used I1/2/4/8 based on palette size - """ - - self.background = background - self.rgb565_dither = rgb565_dither - - if cf is None: # guess cf from filename - # split filename string and match with ColorFormat to check - # which cf to use - names = str(path.basename(filename)).split(".") - for c in names[1:-1]: - if c in ColorFormat.__members__: - cf = ColorFormat[c] - break - - if cf is None or cf.is_indexed: # palette mode - self._png_to_indexed(cf, filename) - elif cf.is_alpha_only: - self._png_to_alpha_only(cf, filename) - elif cf.is_luma_only: - self._png_to_luma_only(cf, filename) - elif cf.is_colormap: - self._png_to_colormap(cf, filename) - else: - logging.warning(f"missing logic: {cf.name}") - - logging.info(f"from png: {filename}, cf: {self.cf.name}") - return self - - def _png_to_indexed(self, cf: ColorFormat, filename: str): - # convert to palette mode - auto_cf = cf is None - - # read the image data to get the metadata - reader = png.Reader(filename=filename) - w, h, rows, metadata = reader.read() - - # to preserve original palette data only convert the image if needed. For this - # check if image has a palette and the requested palette size equals the existing one - if not 'palette' in metadata or not auto_cf and len(metadata['palette']) != 2 ** cf.bpp: - # reread and convert file - reader = png.Reader( - bytes=PngQuant(256 if auto_cf else cf.ncolors).convert(filename)) - w, h, rows, _ = reader.read() - - palette = reader.palette(alpha="force") # always return alpha - - palette_len = len(palette) - if auto_cf: - if palette_len <= 2: - cf = ColorFormat.I1 - elif palette_len <= 4: - cf = ColorFormat.I2 - elif palette_len <= 16: - cf = ColorFormat.I4 - else: - cf = ColorFormat.I8 - - if palette_len != cf.ncolors: - if not auto_cf: - logging.warning( - f"{path.basename(filename)} palette: {palette_len}, " - f"extended to: {cf.ncolors}") - palette += [(255, 255, 255, 0)] * (cf.ncolors - palette_len) - - # Assemble lvgl image palette from PNG palette. - # PNG palette is a list of tuple(R,G,B,A) - - rawdata = bytearray() - for (r, g, b, a) in palette: - rawdata += uint32_t((a << 24) | (r << 16) | (g << 8) | (b << 0)) - - # pack data if not in I8 format - if cf == ColorFormat.I8: - for e in rows: - rawdata += e - else: - for e in png.pack_rows(rows, cf.bpp): - rawdata += e - - self.set_data(cf, w, h, rawdata) - - def _png_to_alpha_only(self, cf: ColorFormat, filename: str): - reader = png.Reader(str(filename)) - w, h, rows, info = reader.asRGBA8() - if not info['alpha']: - raise FormatError(f"{filename} has no alpha channel") - - rawdata = bytearray() - if cf == ColorFormat.A8: - for row in rows: - A = row[3::4] - for e in A: - rawdata += uint8_t(e) - else: - shift = 8 - cf.bpp - mask = 2**cf.bpp - 1 - rows = [[(a >> shift) & mask for a in row[3::4]] for row in rows] - for row in png.pack_rows(rows, cf.bpp): - rawdata += row - - self.set_data(cf, w, h, rawdata) - - def sRGB_to_linear(self, x): - if x < 0.04045: - return x / 12.92 - return pow((x + 0.055) / 1.055, 2.4) - - def linear_to_sRGB(self, y): - if y <= 0.0031308: - return 12.92 * y - return 1.055 * pow(y, 1 / 2.4) - 0.055 - - def _png_to_luma_only(self, cf: ColorFormat, filename: str): - reader = png.Reader(str(filename)) - w, h, rows, info = reader.asRGBA8() - rawdata = bytearray() - for row in rows: - R = row[0::4] - G = row[1::4] - B = row[2::4] - A = row[3::4] - for r, g, b, a in zip(R, G, B, A): - r, g, b, a = color_pre_multiply(r, g, b, a, self.background) - r = self.sRGB_to_linear(r / 255.0) - g = self.sRGB_to_linear(g / 255.0) - b = self.sRGB_to_linear(b / 255.0) - luma = 0.2126 * r + 0.7152 * g + 0.0722 * b - rawdata += uint8_t(int(self.linear_to_sRGB(luma) * 255)) - - self.set_data(ColorFormat.L8, w, h, rawdata) - - def _png_to_colormap(self, cf, filename: str): - - if cf == ColorFormat.ARGB8888: - - def pack(r, g, b, a): - return uint32_t((a << 24) | (r << 16) | (g << 8) | (b << 0)) - elif cf == ColorFormat.XRGB8888: - - def pack(r, g, b, a): - r, g, b, a = color_pre_multiply(r, g, b, a, self.background) - return uint32_t((0xff << 24) | (r << 16) | (g << 8) | (b << 0)) - elif cf == ColorFormat.RGB888: - - def pack(r, g, b, a): - r, g, b, a = color_pre_multiply(r, g, b, a, self.background) - return uint24_t((r << 16) | (g << 8) | (b << 0)) - elif cf == ColorFormat.RGB565: - - def pack(r, g, b, a): - r, g, b, a = color_pre_multiply(r, g, b, a, self.background) - color = (r >> 3) << 11 - color |= (g >> 2) << 5 - color |= (b >> 3) << 0 - return uint16_t(color) - - elif cf == ColorFormat.RGB565A8: - - def pack(r, g, b, a): - color = (r >> 3) << 11 - color |= (g >> 2) << 5 - color |= (b >> 3) << 0 - return uint16_t(color) - elif cf == ColorFormat.ARGB8565: - - def pack(r, g, b, a): - color = (r >> 3) << 11 - color |= (g >> 2) << 5 - color |= (b >> 3) << 0 - return uint24_t((a << 16) | color) - else: - raise FormatError(f"Invalid color format: {cf.name}") - - reader = png.Reader(str(filename)) - w, h, rows, _ = reader.asRGBA8() - rawdata = bytearray() - alpha = bytearray() - for y, row in enumerate(rows): - R = row[0::4] - G = row[1::4] - B = row[2::4] - A = row[3::4] - for x, (r, g, b, a) in enumerate(zip(R, G, B, A)): - if cf == ColorFormat.RGB565A8: - alpha += uint8_t(a) - - if ( - self.rgb565_dither and - cf in (ColorFormat.RGB565, ColorFormat.RGB565A8, ColorFormat.ARGB8565) - ): - treshold_id = ((y & 7) << 3) + (x & 7) - - r = min(r + red_thresh[treshold_id], 0xFF) & 0xF8 - g = min(g + green_thresh[treshold_id], 0xFF) & 0xFC - b = min(b + blue_thresh[treshold_id], 0xFF) & 0xF8 - - rawdata += pack(r, g, b, a) - - if cf == ColorFormat.RGB565A8: - rawdata += alpha - - self.set_data(cf, w, h, rawdata) - - -red_thresh = [ - 1, 7, 3, 5, 0, 8, 2, 6, - 7, 1, 5, 3, 8, 0, 6, 2, - 3, 5, 0, 8, 2, 6, 1, 7, - 5, 3, 8, 0, 6, 2, 7, 1, - 0, 8, 2, 6, 1, 7, 3, 5, - 8, 0, 6, 2, 7, 1, 5, 3, - 2, 6, 1, 7, 3, 5, 0, 8, - 6, 2, 7, 1, 5, 3, 8, 0 -] - -green_thresh = [ - 1, 3, 2, 2, 3, 1, 2, 2, - 2, 2, 0, 4, 2, 2, 4, 0, - 3, 1, 2, 2, 1, 3, 2, 2, - 2, 2, 4, 0, 2, 2, 0, 4, - 1, 3, 2, 2, 3, 1, 2, 2, - 2, 2, 0, 4, 2, 2, 4, 0, - 3, 1, 2, 2, 1, 3, 2, 2, - 2, 2, 4, 0, 2, 2, 0, 4 -] - -blue_thresh = [ - 5, 3, 8, 0, 6, 2, 7, 1, - 3, 5, 0, 8, 2, 6, 1, 7, - 8, 0, 6, 2, 7, 1, 5, 3, - 0, 8, 2, 6, 1, 7, 3, 5, - 6, 2, 7, 1, 5, 3, 8, 0, - 2, 6, 1, 7, 3, 5, 0, 8, - 7, 1, 5, 3, 8, 0, 6, 2, - 1, 7, 3, 5, 0, 8, 2, 6 -] - - -class RLEHeader: - - def __init__(self, blksize: int, len: int): - self.blksize = blksize - self.len = len - - @property - def binary(self): - magic = 0x5aa521e0 - - rle_header = self.blksize - rle_header |= (self.len & 0xffffff) << 4 - - binary = bytearray() - binary.extend(uint32_t(magic)) - binary.extend(uint32_t(rle_header)) - return binary - - -class RLEImage(LVGLImage): - - def __init__(self, - cf: ColorFormat = ColorFormat.UNKNOWN, - w: int = 0, - h: int = 0, - data: bytes = b'') -> None: - super().__init__(cf, w, h, data) - - def to_rle(self, filename: str): - """ - Compress this image to file, filename should be ended with '.rle' - """ - self._check_ext(filename, ".rle") - self._check_dir(filename) - - # compress image data excluding lvgl image header - blksize = (self.cf.bpp + 7) // 8 - compressed = self.rle_compress(self.data, blksize) - with open(filename, "wb+") as f: - header = RLEHeader(blksize, len(self.data)).binary - header.extend(self.header.binary) - f.write(header) - f.write(compressed) - - def rle_compress(self, data: bytearray, blksize: int, threshold=16): - index = 0 - data_len = len(data) - compressed_data = [] - memview = memoryview(data) - while index < data_len: - repeat_cnt = self.get_repeat_count(memview[index:], blksize) - if repeat_cnt == 0: - # done - break - elif repeat_cnt < threshold: - nonrepeat_cnt = self.get_nonrepeat_count( - memview[index:], blksize, threshold) - ctrl_byte = uint8_t(nonrepeat_cnt | 0x80) - compressed_data.append(ctrl_byte) - compressed_data.append(memview[index:index + - nonrepeat_cnt * blksize]) - index += nonrepeat_cnt * blksize - else: - ctrl_byte = uint8_t(repeat_cnt) - compressed_data.append(ctrl_byte) - compressed_data.append(memview[index:index + blksize]) - index += repeat_cnt * blksize - - return b"".join(compressed_data) - - def get_repeat_count(self, data: bytearray, blksize: int): - if len(data) < blksize: - return 0 - - start = data[:blksize] - index = 0 - repeat_cnt = 0 - value = 0 - - while index < len(data): - value = data[index:index + blksize] - - if value == start: - repeat_cnt += 1 - if repeat_cnt == 127: # limit max repeat count to max value of signed char. - break - else: - break - index += blksize - - return repeat_cnt - - def get_nonrepeat_count(self, data: bytearray, blksize: int, threshold): - if len(data) < blksize: - return 0 - - pre_value = data[:blksize] - - index = 0 - nonrepeat_count = 0 - - repeat_cnt = 0 - while True: - value = data[index:index + blksize] - if value == pre_value: - repeat_cnt += 1 - if repeat_cnt > threshold: - # repeat found. - break - else: - pre_value = value - nonrepeat_count += 1 + repeat_cnt - repeat_cnt = 0 - if nonrepeat_count >= 127: # limit max repeat count to max value of signed char. - nonrepeat_count = 127 - break - - index += blksize # move to next position - if index >= len(data): # data end - nonrepeat_count += repeat_cnt - break - - return nonrepeat_count - - -class RAWImage(): - ''' - RAW image is an exception to LVGL image, it has color format of RAW or RAW_ALPHA. - It has same image header as LVGL image, but the data is pure raw data from file. - It does not support stride adjustment etc. features for LVGL image. - It only supports convert an image to C array with RAW or RAW_ALPHA format. - ''' - CF_SUPPORTED = (ColorFormat.RAW, ColorFormat.RAW_ALPHA) - - class NotSupported(NotImplementedError): - pass - - def __init__(self, - cf: ColorFormat = ColorFormat.UNKNOWN, - data: bytes = b'') -> None: - self.cf = cf - self.data = data - - def to_c_array(self, - filename: str): - # Image size is set to zero, to let PNG or JPEG decoder to handle it - # Stride is meaningless for RAW image - write_c_array_file(0, 0, 0, self.cf, filename, - False, CompressMethod.NONE, self.data) - - def from_file(self, - filename: str, - cf: ColorFormat = None): - if cf not in RAWImage.CF_SUPPORTED: - raise RAWImage.NotSupported(f"Invalid color format: {cf.name}") - - with open(filename, "rb") as f: - self.data = f.read() - self.cf = cf - return self - - -class OutputFormat(Enum): - C_ARRAY = "C" - BIN_FILE = "BIN" - PNG_FILE = "PNG" # convert to lvgl image and then to png - - -class PNGConverter: - - def __init__(self, - files: List, - cf: ColorFormat, - ofmt: OutputFormat, - odir: str, - background: int = 0x00, - align: int = 1, - premultiply: bool = False, - compress: CompressMethod = CompressMethod.NONE, - keep_folder=True, - rgb565_dither=False) -> None: - self.files = files - self.cf = cf - self.ofmt = ofmt - self.output = odir - self.pngquant = None - self.keep_folder = keep_folder - self.align = align - self.premultiply = premultiply - self.compress = compress - self.background = background - self.rgb565_dither = rgb565_dither - - def _replace_ext(self, input, ext): - if self.keep_folder: - name, _ = path.splitext(input) - else: - name, _ = path.splitext(path.basename(input)) - output = name + ext - output = path.join(self.output, output) - return output - - def convert(self): - output = [] - for f in self.files: - if self.cf in (ColorFormat.RAW, ColorFormat.RAW_ALPHA): - # Process RAW image explicitly - img = RAWImage().from_file(f, self.cf) - img.to_c_array(self._replace_ext(f, ".c")) - else: - img = LVGLImage().from_png(f, self.cf, background=self.background, rgb565_dither=self.rgb565_dither) - img.adjust_stride(align=self.align) - - if self.premultiply: - img.premultiply() - output.append((f, img)) - if self.ofmt == OutputFormat.BIN_FILE: - img.to_bin(self._replace_ext(f, ".bin"), - compress=self.compress) - elif self.ofmt == OutputFormat.C_ARRAY: - img.to_c_array(self._replace_ext(f, ".c"), - compress=self.compress) - elif self.ofmt == OutputFormat.PNG_FILE: - img.to_png(self._replace_ext(f, ".png")) - - return output - - -def main(): - parser = argparse.ArgumentParser(description='LVGL PNG to bin image tool.') - parser.add_argument('--ofmt', - help="output filename format, C or BIN", - default="BIN", - choices=["C", "BIN", "PNG"]) - parser.add_argument( - '--cf', - help=("bin image color format, use AUTO for automatically " - "choose from I1/2/4/8"), - default="I8", - choices=[ - "L8", "I1", "I2", "I4", "I8", "A1", "A2", "A4", "A8", "ARGB8888", - "XRGB8888", "RGB565", "RGB565A8", "ARGB8565", "RGB888", "AUTO", - "RAW", "RAW_ALPHA" - ]) - - parser.add_argument('--rgb565dither', action='store_true', - help="use dithering to correct banding in gradients", default=False) - - parser.add_argument('--premultiply', action='store_true', - help="pre-multiply color with alpha", default=False) - - parser.add_argument('--compress', - help=("Binary data compress method, default to NONE"), - default="NONE", - choices=["NONE", "RLE", "LZ4"]) - - parser.add_argument('--align', - help="stride alignment in bytes for bin image", - default=1, - type=int, - metavar='byte', - nargs='?') - parser.add_argument('--background', - help="Background color for formats without alpha", - default=0x00_00_00, - type=lambda x: int(x, 0), - metavar='color', - nargs='?') - parser.add_argument('-o', - '--output', - default="./output", - help="Select the output folder, default to ./output") - parser.add_argument('-v', '--verbose', action='store_true') - parser.add_argument( - 'input', help="the filename or folder to be recursively converted") - - args = parser.parse_args() - - if path.isfile(args.input): - files = [args.input] - elif path.isdir(args.input): - files = list(Path(args.input).rglob("*.[pP][nN][gG]")) - else: - raise BaseException(f"invalid input: {args.input}") - - if args.verbose: - logging.basicConfig(level=logging.INFO) - - logging.info(f"options: {args.__dict__}, files:{[str(f) for f in files]}") - - if args.cf == "AUTO": - cf = None - else: - cf = ColorFormat[args.cf] - - ofmt = OutputFormat(args.ofmt) if cf not in ( - ColorFormat.RAW, ColorFormat.RAW_ALPHA) else OutputFormat.C_ARRAY - compress = CompressMethod[args.compress] - - converter = PNGConverter(files, - cf, - ofmt, - args.output, - background=args.background, - align=args.align, - premultiply=args.premultiply, - compress=compress, - keep_folder=False, - rgb565_dither=args.rgb565dither) - output = converter.convert() - for f, img in output: - logging.info(f"len: {img.data_len} for {path.basename(f)} ") - - print(f"done {len(files)} files") - - -def test(): - logging.basicConfig(level=logging.INFO) - f = "pngs/cogwheel.RGB565A8.png" - img = LVGLImage().from_png(f, - cf=ColorFormat.ARGB8565, - background=0xFF_FF_00, - rgb565_dither=True) - img.adjust_stride(align=16) - img.premultiply() - img.to_bin("output/cogwheel.ARGB8565.bin") - img.to_c_array("output/cogwheel-abc.c") # file name is used as c var name - img.to_png("output/cogwheel.ARGB8565.png.png") # convert back to png - - -def test_raw(): - logging.basicConfig(level=logging.INFO) - f = "pngs/cogwheel.RGB565A8.png" - img = RAWImage().from_file(f, - cf=ColorFormat.RAW_ALPHA) - img.to_c_array("output/cogwheel-raw.c") - - -if __name__ == "__main__": - # test() - # test_raw() - main() +#!/usr/bin/env python3 +import os +import logging +import argparse +import subprocess +from os import path +from enum import Enum +from typing import List +from pathlib import Path + +try: + import png +except ImportError: + raise ImportError("Need pypng package, do `pip3 install pypng`") + +try: + import lz4.block +except ImportError: + raise ImportError("Need lz4 package, do `pip3 install lz4`") + + +def uint8_t(val) -> bytes: + return val.to_bytes(1, byteorder='little') + + +def uint16_t(val) -> bytes: + return val.to_bytes(2, byteorder='little') + + +def uint24_t(val) -> bytes: + return val.to_bytes(3, byteorder='little') + + +def uint32_t(val) -> bytes: + try: + return val.to_bytes(4, byteorder='little') + except OverflowError: + raise ParameterError(f"overflow: {hex(val)}") + + +def color_pre_multiply(r, g, b, a, background): + bb = background & 0xff + bg = (background >> 8) & 0xff + br = (background >> 16) & 0xff + + return ((r * a + (255 - a) * br) >> 8, (g * a + (255 - a) * bg) >> 8, + (b * a + (255 - a) * bb) >> 8, a) + + +class Error(Exception): + + def __str__(self): + return self.__class__.__name__ + ': ' + ' '.join(self.args) + + +class FormatError(Error): + """ + Problem with input filename format. + BIN filename does not conform to standard lvgl bin image format + """ + + +class ParameterError(Error): + """ + Parameter for LVGL image not correct + """ + + +class PngQuant: + """ + Compress PNG file to 8bit mode using `pngquant` + """ + + def __init__(self, ncolors=256, dither=True, exec_path="") -> None: + executable = path.join(exec_path, "pngquant") + self.cmd = (f"{executable} {'--nofs' if not dither else ''} " + f"{ncolors} --force - < ") + + def convert(self, filename) -> bytes: + if not os.path.isfile(filename): + raise BaseException(f"file not found: {filename}") + + try: + compressed = subprocess.check_output( + f'{self.cmd} "{str(filename)}"', + stderr=subprocess.STDOUT, + shell=True) + except subprocess.CalledProcessError: + raise BaseException( + "cannot find pngquant tool, install it via " + "`sudo apt install pngquant` for debian " + "or `brew install pngquant` for macintosh " + "For windows, you may need to download pngquant.exe from " + "https://pngquant.org/, and put it in your PATH.") + + return compressed + + +class CompressMethod(Enum): + NONE = 0x00 + RLE = 0x01 + LZ4 = 0x02 + + +class ColorFormat(Enum): + UNKNOWN = 0x00 + RAW = 0x01, + RAW_ALPHA = 0x02, + L8 = 0x06 + I1 = 0x07 + I2 = 0x08 + I4 = 0x09 + I8 = 0x0A + A1 = 0x0B + A2 = 0x0C + A4 = 0x0D + A8 = 0x0E + ARGB8888 = 0x10 + XRGB8888 = 0x11 + RGB565 = 0x12 + ARGB8565 = 0x13 + RGB565A8 = 0x14 + RGB888 = 0x0F + + @property + def bpp(self) -> int: + """ + Return bit per pixel for this cf + """ + cf_map = { + ColorFormat.L8: 8, + ColorFormat.I1: 1, + ColorFormat.I2: 2, + ColorFormat.I4: 4, + ColorFormat.I8: 8, + ColorFormat.A1: 1, + ColorFormat.A2: 2, + ColorFormat.A4: 4, + ColorFormat.A8: 8, + ColorFormat.ARGB8888: 32, + ColorFormat.XRGB8888: 32, + ColorFormat.RGB565: 16, + ColorFormat.RGB565A8: 16, # 16bpp + a8 map + ColorFormat.ARGB8565: 24, + ColorFormat.RGB888: 24, + } + + return cf_map[self] if self in cf_map else 0 + + @property + def ncolors(self) -> int: + """ + Return number of colors in palette if cf is indexed1/2/4/8. + Return zero if cf is not indexed format + """ + + cf_map = { + ColorFormat.I1: 2, + ColorFormat.I2: 4, + ColorFormat.I4: 16, + ColorFormat.I8: 256, + } + return cf_map.get(self, 0) + + @property + def is_indexed(self) -> bool: + """ + Return if cf is indexed color format + """ + return self.ncolors != 0 + + @property + def is_alpha_only(self) -> bool: + return ColorFormat.A1.value <= self.value <= ColorFormat.A8.value + + @property + def has_alpha(self) -> bool: + return self.is_alpha_only or self.is_indexed or self in ( + ColorFormat.ARGB8888, + ColorFormat.XRGB8888, # const alpha: 0xff + ColorFormat.ARGB8565, + ColorFormat.RGB565A8) + + @property + def is_colormap(self) -> bool: + return self in (ColorFormat.ARGB8888, ColorFormat.RGB888, + ColorFormat.XRGB8888, ColorFormat.RGB565A8, + ColorFormat.ARGB8565, ColorFormat.RGB565) + + @property + def is_luma_only(self) -> bool: + return self in (ColorFormat.L8, ) + + +def bit_extend(value, bpp): + """ + Extend value from bpp to 8 bit with interpolation to reduce rounding error. + """ + + if value == 0: + return 0 + + res = value + bpp_now = bpp + while bpp_now < 8: + res |= value << (8 - bpp_now) + bpp_now += bpp + + return res + + +def unpack_colors(data: bytes, cf: ColorFormat, w) -> List: + """ + Unpack lvgl 1/2/4/8/16/32 bpp color to png color: alpha map, grey scale, + or R,G,B,(A) map + """ + ret = [] + bpp = cf.bpp + if bpp == 8: + ret = data + elif bpp == 4: + if cf == ColorFormat.A4: + values = [x * 17 for x in range(16)] + else: + values = [x for x in range(16)] + + for p in data: + for i in range(2): + ret.append(values[(p >> (4 - i * 4)) & 0x0f]) + if len(ret) % w == 0: + break + + elif bpp == 2: + if cf == ColorFormat.A2: + values = [x * 85 for x in range(4)] + else: # must be ColorFormat.I2 + values = [x for x in range(4)] + for p in data: + for i in range(4): + ret.append(values[(p >> (6 - i * 2)) & 0x03]) + if len(ret) % w == 0: + break + elif bpp == 1: + if cf == ColorFormat.A1: + values = [0, 255] + else: + values = [0, 1] + for p in data: + for i in range(8): + ret.append(values[(p >> (7 - i)) & 0x01]) + if len(ret) % w == 0: + break + elif bpp == 16: + # This is RGB565 + pixels = [(data[2 * i + 1] << 8) | data[2 * i] + for i in range(len(data) // 2)] + + for p in pixels: + ret.append(bit_extend((p >> 11) & 0x1f, 5)) # R + ret.append(bit_extend((p >> 5) & 0x3f, 6)) # G + ret.append(bit_extend((p >> 0) & 0x1f, 5)) # B + elif bpp == 24: + if cf == ColorFormat.RGB888: + B = data[0::3] + G = data[1::3] + R = data[2::3] + for r, g, b in zip(R, G, B): + ret += [r, g, b] + elif cf == ColorFormat.RGB565A8: + alpha_size = len(data) // 3 + pixel_alpha = data[-alpha_size:] + pixel_data = data[:-alpha_size] + pixels = [(pixel_data[2 * i + 1] << 8) | pixel_data[2 * i] + for i in range(len(pixel_data) // 2)] + + for a, p in zip(pixel_alpha, pixels): + ret.append(bit_extend((p >> 11) & 0x1f, 5)) # R + ret.append(bit_extend((p >> 5) & 0x3f, 6)) # G + ret.append(bit_extend((p >> 0) & 0x1f, 5)) # B + ret.append(a) + elif cf == ColorFormat.ARGB8565: + L = data[0::3] + H = data[1::3] + A = data[2::3] + + for h, l, a in zip(H, L, A): + p = (h << 8) | (l) + ret.append(bit_extend((p >> 11) & 0x1f, 5)) # R + ret.append(bit_extend((p >> 5) & 0x3f, 6)) # G + ret.append(bit_extend((p >> 0) & 0x1f, 5)) # B + ret.append(a) # A + + elif bpp == 32: + B = data[0::4] + G = data[1::4] + R = data[2::4] + A = data[3::4] + for r, g, b, a in zip(R, G, B, A): + ret += [r, g, b, a] + else: + assert 0 + + return ret + + +def write_c_array_file( + w: int, h: int, + stride: int, + cf: ColorFormat, + filename: str, + premultiplied: bool, + compress: CompressMethod, + data: bytes): + varname = path.basename(filename).split('.')[0] + varname = varname.replace("-", "_") + varname = varname.replace(".", "_") + + flags = "0" + if compress is not CompressMethod.NONE: + flags += " | LV_IMAGE_FLAGS_COMPRESSED" + if premultiplied: + flags += " | LV_IMAGE_FLAGS_PREMULTIPLIED" + + macro = "LV_ATTRIBUTE_" + varname.upper() + header = f''' +#if defined(LV_LVGL_H_INCLUDE_SIMPLE) +#include "lvgl.h" +#elif defined(LV_BUILD_TEST) +#include "../lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + + +#ifndef LV_ATTRIBUTE_MEM_ALIGN +#define LV_ATTRIBUTE_MEM_ALIGN +#endif + +#ifndef {macro} +#define {macro} +#endif + +static const +LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST {macro} +uint8_t {varname}_map[] = {{ +''' + + ending = f''' +}}; + +const lv_image_dsc_t {varname} = {{ + .header.magic = LV_IMAGE_HEADER_MAGIC, + .header.cf = LV_COLOR_FORMAT_{cf.name}, + .header.flags = {flags}, + .header.w = {w}, + .header.h = {h}, + .header.stride = {stride}, + .data_size = sizeof({varname}_map), + .data = {varname}_map, +}}; + +''' + + def write_binary(f, data, stride): + stride = 16 if stride == 0 else stride + for i, v in enumerate(data): + if i % stride == 0: + f.write("\n ") + f.write(f"0x{v:02x},") + f.write("\n") + + with open(filename, "w+") as f: + f.write(header) + + if compress != CompressMethod.NONE: + write_binary(f, data, 16) + else: + # write palette separately + ncolors = cf.ncolors + if ncolors: + write_binary(f, data[:ncolors * 4], 16) + + write_binary(f, data[ncolors * 4:], stride) + + f.write(ending) + + +class LVGLImageHeader: + + def __init__(self, + cf: ColorFormat = ColorFormat.UNKNOWN, + w: int = 0, + h: int = 0, + stride: int = 0, + align: int = 1, + flags: int = 0): + self.cf = cf + self.flags = flags + self.w = w & 0xffff + self.h = h & 0xffff + if w > 0xffff or h > 0xffff: + raise ParameterError(f"w, h overflow: {w}x{h}") + if align < 1: + # stride align in bytes must be larger than 1 + raise ParameterError(f"Invalid stride align: {align}") + + self.stride = self.stride_align(align) if stride == 0 else stride + + def stride_align(self, align: int) -> int: + stride = self.stride_default + if align == 1: + pass + elif align > 1: + stride = (stride + align - 1) // align + stride *= align + else: + raise ParameterError(f"Invalid stride align: {align}") + + self.stride = stride + return stride + + @property + def stride_default(self) -> int: + return (self.w * self.cf.bpp + 7) // 8 + + @property + def binary(self) -> bytearray: + binary = bytearray() + binary += uint8_t(0x19) # magic number for lvgl version 9 + binary += uint8_t(self.cf.value) + binary += uint16_t(self.flags) # 16bits flags + + binary += uint16_t(self.w) # 16bits width + binary += uint16_t(self.h) # 16bits height + binary += uint16_t(self.stride) # 16bits stride + + binary += uint16_t(0) # 16bits reserved + return binary + + def from_binary(self, data: bytes): + if len(data) < 12: + raise FormatError("invalid header length") + + try: + self.cf = ColorFormat(data[1] & 0x1f) # color format + except ValueError as exc: + raise FormatError(f"invalid color format: {hex(data[0])}") from exc + self.w = int.from_bytes(data[4:6], 'little') + self.h = int.from_bytes(data[6:8], 'little') + self.stride = int.from_bytes(data[8:10], 'little') + return self + + +class LVGLCompressData: + + def __init__(self, + cf: ColorFormat, + method: CompressMethod, + raw_data: bytes = b''): + self.blk_size = (cf.bpp + 7) // 8 + self.compress = method + self.raw_data = raw_data + self.raw_data_len = len(raw_data) + self.compressed = self._compress(raw_data) + + def _compress(self, raw_data: bytes) -> bytearray: + if self.compress == CompressMethod.NONE: + return raw_data + + if self.compress == CompressMethod.RLE: + # RLE compression performs on pixel unit, pad data to pixel unit + pad = b'\x00' * 0 + if self.raw_data_len % self.blk_size: + pad = b'\x00' * (self.blk_size - self.raw_data_len % self.blk_size) + compressed = RLEImage().rle_compress(raw_data + pad, self.blk_size) + elif self.compress == CompressMethod.LZ4: + compressed = lz4.block.compress(raw_data, store_size=False) + else: + raise ParameterError(f"Invalid compress method: {self.compress}") + + self.compressed_len = len(compressed) + + bin = bytearray() + bin += uint32_t(self.compress.value) + bin += uint32_t(self.compressed_len) + bin += uint32_t(self.raw_data_len) + bin += compressed + return bin + + +class LVGLImage: + + def __init__(self, + cf: ColorFormat = ColorFormat.UNKNOWN, + w: int = 0, + h: int = 0, + data: bytes = b'') -> None: + self.stride = 0 # default no valid stride value + self.premultiplied = False + self.rgb565_dither = False + self.set_data(cf, w, h, data) + + def __repr__(self) -> str: + return (f"'LVGL image {self.w}x{self.h}, {self.cf.name}, " + f"{'Pre-multiplied, ' if self.premultiplied else ''}" + f"stride: {self.stride} " + f"(12+{self.data_len})Byte'") + + def adjust_stride(self, stride: int = 0, align: int = 1): + """ + Stride can be set directly, or by stride alignment in bytes + """ + if self.stride == 0: + # stride can only be 0, when LVGLImage is created with empty data + logging.warning("Cannot adjust stride for empty image") + return + + if align >= 1 and stride == 0: + # The header with specified stride alignment + header = LVGLImageHeader(self.cf, self.w, self.h, align=align) + stride = header.stride + elif stride > 0: + pass + else: + raise ParameterError(f"Invalid parameter, align:{align}," + f" stride:{stride}") + + if self.stride == stride: + return # no stride adjustment + + # if current image is empty, no need to do anything + if self.data_len == 0: + self.stride = 0 + return + + current = LVGLImageHeader(self.cf, self.w, self.h, stride=self.stride) + + if stride < current.stride_default: + raise ParameterError(f"Stride is too small:{stride}, " + f"minimal:{current.stride_default}") + + def change_stride(data: bytearray, h, current_stride, new_stride): + data_in = data + data_out = [] # stride adjusted new data + if new_stride < current_stride: # remove padding byte + for i in range(h): + start = i * current_stride + end = start + new_stride + data_out.append(data_in[start:end]) + else: # adding more padding bytes + padding = b'\x00' * (new_stride - current_stride) + for i in range(h): + data_out.append(data_in[i * current_stride:(i + 1) * + current_stride]) + data_out.append(padding) + return b''.join(data_out) + + palette_size = self.cf.ncolors * 4 + data_out = [self.data[:palette_size]] + data_out.append( + change_stride(self.data[palette_size:], self.h, current.stride, + stride)) + + # deal with alpha map for RGB565A8 + if self.cf == ColorFormat.RGB565A8: + logging.warning("handle RGB565A8 alpha map") + a8_stride = self.stride // 2 + a8_map = self.data[-a8_stride * self.h:] + data_out.append( + change_stride(a8_map, self.h, current.stride // 2, + stride // 2)) + + self.stride = stride + self.data = bytearray(b''.join(data_out)) + + def premultiply(self): + """ + Pre-multiply image RGB data with alpha, set corresponding image header flags + """ + if self.premultiplied: + raise ParameterError("Image already pre-multiplied") + + if not self.cf.has_alpha: + raise ParameterError(f"Image has no alpha channel: {self.cf.name}") + + if self.cf.is_indexed: + + def multiply(r, g, b, a): + r, g, b = (r * a) >> 8, (g * a) >> 8, (b * a) >> 8 + return uint8_t(b) + uint8_t(g) + uint8_t(r) + uint8_t(a) + + # process the palette only. + palette_size = self.cf.ncolors * 4 + palette = self.data[:palette_size] + palette = [ + multiply(palette[i], palette[i + 1], palette[i + 2], + palette[i + 3]) for i in range(0, len(palette), 4) + ] + palette = b''.join(palette) + self.data = palette + self.data[palette_size:] + elif self.cf is ColorFormat.ARGB8888: + + def multiply(b, g, r, a): + r, g, b = (r * a) >> 8, (g * a) >> 8, (b * a) >> 8 + return uint32_t((a << 24) | (r << 16) | (g << 8) | (b << 0)) + + line_width = self.w * 4 + for h in range(self.h): + offset = h * self.stride + map = self.data[offset:offset + self.stride] + + processed = b''.join([ + multiply(map[i], map[i + 1], map[i + 2], map[i + 3]) + for i in range(0, line_width, 4) + ]) + self.data[offset:offset + line_width] = processed + elif self.cf is ColorFormat.RGB565A8: + + def multiply(data, a): + r = (data >> 11) & 0x1f + g = (data >> 5) & 0x3f + b = (data >> 0) & 0x1f + + r, g, b = (r * a) // 255, (g * a) // 255, (b * a) // 255 + return uint16_t((r << 11) | (g << 5) | (b << 0)) + + line_width = self.w * 2 + for h in range(self.h): + # alpha map offset for this line + offset = self.h * self.stride + h * (self.stride // 2) + a = self.data[offset:offset + self.stride // 2] + + # RGB map offset + offset = h * self.stride + rgb = self.data[offset:offset + self.stride] + + processed = b''.join([ + multiply((rgb[i + 1] << 8) | rgb[i], a[i // 2]) + for i in range(0, line_width, 2) + ]) + self.data[offset:offset + line_width] = processed + elif self.cf is ColorFormat.ARGB8565: + + def multiply(data, a): + r = (data >> 11) & 0x1f + g = (data >> 5) & 0x3f + b = (data >> 0) & 0x1f + + r, g, b = (r * a) // 255, (g * a) // 255, (b * a) // 255 + return uint24_t((a << 16) | (r << 11) | (g << 5) | (b << 0)) + + line_width = self.w * 3 + for h in range(self.h): + offset = h * self.stride + map = self.data[offset:offset + self.stride] + + processed = b''.join([ + multiply((map[i + 1] << 8) | map[i], map[i + 2]) + for i in range(0, line_width, 3) + ]) + self.data[offset:offset + line_width] = processed + else: + raise ParameterError(f"Not supported yet: {self.cf.name}") + + self.premultiplied = True + + @property + def data_len(self) -> int: + """ + Return data_len in byte of this image, excluding image header + """ + + # palette is always in ARGB format, 4Byte per color + p = self.cf.ncolors * 4 if self.is_indexed and self.w * self.h else 0 + p += self.stride * self.h + if self.cf is ColorFormat.RGB565A8: + a8_stride = self.stride // 2 + p += a8_stride * self.h + return p + + @property + def header(self) -> bytearray: + return LVGLImageHeader(self.cf, self.w, self.h) + + @property + def is_indexed(self): + return self.cf.is_indexed + + def set_data(self, + cf: ColorFormat, + w: int, + h: int, + data: bytes, + stride: int = 0): + """ + Directly set LVGL image parameters + """ + + if w > 0xffff or h > 0xffff: + raise ParameterError(f"w, h overflow: {w}x{h}") + + self.cf = cf + self.w = w + self.h = h + + # if stride is 0, then it's aligned to 1byte by default, + # let image header handle it + self.stride = LVGLImageHeader(cf, w, h, stride, align=1).stride + + if self.data_len != len(data): + raise ParameterError(f"{self} data length error got: {len(data)}, " + f"expect: {self.data_len}, {self}") + + self.data = data + + return self + + def from_data(self, data: bytes): + header = LVGLImageHeader().from_binary(data) + return self.set_data(header.cf, header.w, header.h, + data[len(header.binary):], header.stride) + + def from_bin(self, filename: str): + """ + Read from existing bin file and update image parameters + """ + + if not filename.endswith(".bin"): + raise FormatError("filename not ended with '.bin'") + + with open(filename, "rb") as f: + data = f.read() + return self.from_data(data) + + def _check_ext(self, filename: str, ext): + if not filename.lower().endswith(ext): + raise FormatError(f"filename not ended with {ext}") + + def _check_dir(self, filename: str): + dir = path.dirname(filename) + if dir and not path.exists(dir): + logging.info(f"mkdir of {dir} for {filename}") + os.makedirs(dir) + + def to_bin(self, + filename: str, + compress: CompressMethod = CompressMethod.NONE): + """ + Write this image to file, filename should be ended with '.bin' + """ + self._check_ext(filename, ".bin") + self._check_dir(filename) + + with open(filename, "wb+") as f: + bin = bytearray() + flags = 0 + flags |= 0x08 if compress != CompressMethod.NONE else 0 + flags |= 0x01 if self.premultiplied else 0 + + header = LVGLImageHeader(self.cf, + self.w, + self.h, + self.stride, + flags=flags) + bin += header.binary + compressed = LVGLCompressData(self.cf, compress, self.data) + bin += compressed.compressed + + f.write(bin) + + return self + + def to_c_array(self, + filename: str, + compress: CompressMethod = CompressMethod.NONE): + self._check_ext(filename, ".c") + self._check_dir(filename) + + if compress != CompressMethod.NONE: + data = LVGLCompressData(self.cf, compress, self.data).compressed + else: + data = self.data + write_c_array_file(self.w, self.h, self.stride, self.cf, filename, + self.premultiplied, + compress, data) + + def to_png(self, filename: str): + self._check_ext(filename, ".png") + self._check_dir(filename) + + old_stride = self.stride + self.adjust_stride(align=1) + if self.cf.is_indexed: + data = self.data + # Separate lvgl bin image data to palette and bitmap + # The palette is in format of [(RGBA), (RGBA)...]. + # LVGL palette is in format of B,G,R,A,... + palette = [(data[i * 4 + 2], data[i * 4 + 1], data[i * 4 + 0], + data[i * 4 + 3]) for i in range(self.cf.ncolors)] + + data = data[self.cf.ncolors * 4:] + + encoder = png.Writer(self.w, + self.h, + palette=palette, + bitdepth=self.cf.bpp) + # separate packed data to plain data + data = unpack_colors(data, self.cf, self.w) + elif self.cf.is_alpha_only: + # separate packed data to plain data + transparency = unpack_colors(self.data, self.cf, self.w) + data = [] + for a in transparency: + data += [0, 0, 0, a] + encoder = png.Writer(self.w, self.h, greyscale=False, alpha=True) + elif self.cf == ColorFormat.L8: + # to grayscale + encoder = png.Writer(self.w, + self.h, + bitdepth=self.cf.bpp, + greyscale=True, + alpha=False) + data = self.data + elif self.cf.is_colormap: + encoder = png.Writer(self.w, + self.h, + alpha=self.cf.has_alpha, + greyscale=False) + data = unpack_colors(self.data, self.cf, self.w) + else: + logging.warning(f"missing logic: {self.cf.name}") + return + + with open(filename, "wb") as f: + encoder.write_array(f, data) + + self.adjust_stride(stride=old_stride) + + def from_png(self, + filename: str, + cf: ColorFormat = None, + background: int = 0x00_00_00, + rgb565_dither=False): + """ + Create lvgl image from png file. + If cf is none, used I1/2/4/8 based on palette size + """ + + self.background = background + self.rgb565_dither = rgb565_dither + + if cf is None: # guess cf from filename + # split filename string and match with ColorFormat to check + # which cf to use + names = str(path.basename(filename)).split(".") + for c in names[1:-1]: + if c in ColorFormat.__members__: + cf = ColorFormat[c] + break + + if cf is None or cf.is_indexed: # palette mode + self._png_to_indexed(cf, filename) + elif cf.is_alpha_only: + self._png_to_alpha_only(cf, filename) + elif cf.is_luma_only: + self._png_to_luma_only(cf, filename) + elif cf.is_colormap: + self._png_to_colormap(cf, filename) + else: + logging.warning(f"missing logic: {cf.name}") + + logging.info(f"from png: {filename}, cf: {self.cf.name}") + return self + + def _png_to_indexed(self, cf: ColorFormat, filename: str): + # convert to palette mode + auto_cf = cf is None + + # read the image data to get the metadata + reader = png.Reader(filename=filename) + w, h, rows, metadata = reader.read() + + # to preserve original palette data only convert the image if needed. For this + # check if image has a palette and the requested palette size equals the existing one + if not 'palette' in metadata or not auto_cf and len(metadata['palette']) != 2 ** cf.bpp: + # reread and convert file + reader = png.Reader( + bytes=PngQuant(256 if auto_cf else cf.ncolors).convert(filename)) + w, h, rows, _ = reader.read() + + palette = reader.palette(alpha="force") # always return alpha + + palette_len = len(palette) + if auto_cf: + if palette_len <= 2: + cf = ColorFormat.I1 + elif palette_len <= 4: + cf = ColorFormat.I2 + elif palette_len <= 16: + cf = ColorFormat.I4 + else: + cf = ColorFormat.I8 + + if palette_len != cf.ncolors: + if not auto_cf: + logging.warning( + f"{path.basename(filename)} palette: {palette_len}, " + f"extended to: {cf.ncolors}") + palette += [(255, 255, 255, 0)] * (cf.ncolors - palette_len) + + # Assemble lvgl image palette from PNG palette. + # PNG palette is a list of tuple(R,G,B,A) + + rawdata = bytearray() + for (r, g, b, a) in palette: + rawdata += uint32_t((a << 24) | (r << 16) | (g << 8) | (b << 0)) + + # pack data if not in I8 format + if cf == ColorFormat.I8: + for e in rows: + rawdata += e + else: + for e in png.pack_rows(rows, cf.bpp): + rawdata += e + + self.set_data(cf, w, h, rawdata) + + def _png_to_alpha_only(self, cf: ColorFormat, filename: str): + reader = png.Reader(str(filename)) + w, h, rows, info = reader.asRGBA8() + if not info['alpha']: + raise FormatError(f"{filename} has no alpha channel") + + rawdata = bytearray() + if cf == ColorFormat.A8: + for row in rows: + A = row[3::4] + for e in A: + rawdata += uint8_t(e) + else: + shift = 8 - cf.bpp + mask = 2**cf.bpp - 1 + rows = [[(a >> shift) & mask for a in row[3::4]] for row in rows] + for row in png.pack_rows(rows, cf.bpp): + rawdata += row + + self.set_data(cf, w, h, rawdata) + + def sRGB_to_linear(self, x): + if x < 0.04045: + return x / 12.92 + return pow((x + 0.055) / 1.055, 2.4) + + def linear_to_sRGB(self, y): + if y <= 0.0031308: + return 12.92 * y + return 1.055 * pow(y, 1 / 2.4) - 0.055 + + def _png_to_luma_only(self, cf: ColorFormat, filename: str): + reader = png.Reader(str(filename)) + w, h, rows, info = reader.asRGBA8() + rawdata = bytearray() + for row in rows: + R = row[0::4] + G = row[1::4] + B = row[2::4] + A = row[3::4] + for r, g, b, a in zip(R, G, B, A): + r, g, b, a = color_pre_multiply(r, g, b, a, self.background) + r = self.sRGB_to_linear(r / 255.0) + g = self.sRGB_to_linear(g / 255.0) + b = self.sRGB_to_linear(b / 255.0) + luma = 0.2126 * r + 0.7152 * g + 0.0722 * b + rawdata += uint8_t(int(self.linear_to_sRGB(luma) * 255)) + + self.set_data(ColorFormat.L8, w, h, rawdata) + + def _png_to_colormap(self, cf, filename: str): + + if cf == ColorFormat.ARGB8888: + + def pack(r, g, b, a): + return uint32_t((a << 24) | (r << 16) | (g << 8) | (b << 0)) + elif cf == ColorFormat.XRGB8888: + + def pack(r, g, b, a): + r, g, b, a = color_pre_multiply(r, g, b, a, self.background) + return uint32_t((0xff << 24) | (r << 16) | (g << 8) | (b << 0)) + elif cf == ColorFormat.RGB888: + + def pack(r, g, b, a): + r, g, b, a = color_pre_multiply(r, g, b, a, self.background) + return uint24_t((r << 16) | (g << 8) | (b << 0)) + elif cf == ColorFormat.RGB565: + + def pack(r, g, b, a): + r, g, b, a = color_pre_multiply(r, g, b, a, self.background) + color = (r >> 3) << 11 + color |= (g >> 2) << 5 + color |= (b >> 3) << 0 + return uint16_t(color) + + elif cf == ColorFormat.RGB565A8: + + def pack(r, g, b, a): + color = (r >> 3) << 11 + color |= (g >> 2) << 5 + color |= (b >> 3) << 0 + return uint16_t(color) + elif cf == ColorFormat.ARGB8565: + + def pack(r, g, b, a): + color = (r >> 3) << 11 + color |= (g >> 2) << 5 + color |= (b >> 3) << 0 + return uint24_t((a << 16) | color) + else: + raise FormatError(f"Invalid color format: {cf.name}") + + reader = png.Reader(str(filename)) + w, h, rows, _ = reader.asRGBA8() + rawdata = bytearray() + alpha = bytearray() + for y, row in enumerate(rows): + R = row[0::4] + G = row[1::4] + B = row[2::4] + A = row[3::4] + for x, (r, g, b, a) in enumerate(zip(R, G, B, A)): + if cf == ColorFormat.RGB565A8: + alpha += uint8_t(a) + + if ( + self.rgb565_dither and + cf in (ColorFormat.RGB565, ColorFormat.RGB565A8, ColorFormat.ARGB8565) + ): + treshold_id = ((y & 7) << 3) + (x & 7) + + r = min(r + red_thresh[treshold_id], 0xFF) & 0xF8 + g = min(g + green_thresh[treshold_id], 0xFF) & 0xFC + b = min(b + blue_thresh[treshold_id], 0xFF) & 0xF8 + + rawdata += pack(r, g, b, a) + + if cf == ColorFormat.RGB565A8: + rawdata += alpha + + self.set_data(cf, w, h, rawdata) + + +red_thresh = [ + 1, 7, 3, 5, 0, 8, 2, 6, + 7, 1, 5, 3, 8, 0, 6, 2, + 3, 5, 0, 8, 2, 6, 1, 7, + 5, 3, 8, 0, 6, 2, 7, 1, + 0, 8, 2, 6, 1, 7, 3, 5, + 8, 0, 6, 2, 7, 1, 5, 3, + 2, 6, 1, 7, 3, 5, 0, 8, + 6, 2, 7, 1, 5, 3, 8, 0 +] + +green_thresh = [ + 1, 3, 2, 2, 3, 1, 2, 2, + 2, 2, 0, 4, 2, 2, 4, 0, + 3, 1, 2, 2, 1, 3, 2, 2, + 2, 2, 4, 0, 2, 2, 0, 4, + 1, 3, 2, 2, 3, 1, 2, 2, + 2, 2, 0, 4, 2, 2, 4, 0, + 3, 1, 2, 2, 1, 3, 2, 2, + 2, 2, 4, 0, 2, 2, 0, 4 +] + +blue_thresh = [ + 5, 3, 8, 0, 6, 2, 7, 1, + 3, 5, 0, 8, 2, 6, 1, 7, + 8, 0, 6, 2, 7, 1, 5, 3, + 0, 8, 2, 6, 1, 7, 3, 5, + 6, 2, 7, 1, 5, 3, 8, 0, + 2, 6, 1, 7, 3, 5, 0, 8, + 7, 1, 5, 3, 8, 0, 6, 2, + 1, 7, 3, 5, 0, 8, 2, 6 +] + + +class RLEHeader: + + def __init__(self, blksize: int, len: int): + self.blksize = blksize + self.len = len + + @property + def binary(self): + magic = 0x5aa521e0 + + rle_header = self.blksize + rle_header |= (self.len & 0xffffff) << 4 + + binary = bytearray() + binary.extend(uint32_t(magic)) + binary.extend(uint32_t(rle_header)) + return binary + + +class RLEImage(LVGLImage): + + def __init__(self, + cf: ColorFormat = ColorFormat.UNKNOWN, + w: int = 0, + h: int = 0, + data: bytes = b'') -> None: + super().__init__(cf, w, h, data) + + def to_rle(self, filename: str): + """ + Compress this image to file, filename should be ended with '.rle' + """ + self._check_ext(filename, ".rle") + self._check_dir(filename) + + # compress image data excluding lvgl image header + blksize = (self.cf.bpp + 7) // 8 + compressed = self.rle_compress(self.data, blksize) + with open(filename, "wb+") as f: + header = RLEHeader(blksize, len(self.data)).binary + header.extend(self.header.binary) + f.write(header) + f.write(compressed) + + def rle_compress(self, data: bytearray, blksize: int, threshold=16): + index = 0 + data_len = len(data) + compressed_data = [] + memview = memoryview(data) + while index < data_len: + repeat_cnt = self.get_repeat_count(memview[index:], blksize) + if repeat_cnt == 0: + # done + break + elif repeat_cnt < threshold: + nonrepeat_cnt = self.get_nonrepeat_count( + memview[index:], blksize, threshold) + ctrl_byte = uint8_t(nonrepeat_cnt | 0x80) + compressed_data.append(ctrl_byte) + compressed_data.append(memview[index:index + + nonrepeat_cnt * blksize]) + index += nonrepeat_cnt * blksize + else: + ctrl_byte = uint8_t(repeat_cnt) + compressed_data.append(ctrl_byte) + compressed_data.append(memview[index:index + blksize]) + index += repeat_cnt * blksize + + return b"".join(compressed_data) + + def get_repeat_count(self, data: bytearray, blksize: int): + if len(data) < blksize: + return 0 + + start = data[:blksize] + index = 0 + repeat_cnt = 0 + value = 0 + + while index < len(data): + value = data[index:index + blksize] + + if value == start: + repeat_cnt += 1 + if repeat_cnt == 127: # limit max repeat count to max value of signed char. + break + else: + break + index += blksize + + return repeat_cnt + + def get_nonrepeat_count(self, data: bytearray, blksize: int, threshold): + if len(data) < blksize: + return 0 + + pre_value = data[:blksize] + + index = 0 + nonrepeat_count = 0 + + repeat_cnt = 0 + while True: + value = data[index:index + blksize] + if value == pre_value: + repeat_cnt += 1 + if repeat_cnt > threshold: + # repeat found. + break + else: + pre_value = value + nonrepeat_count += 1 + repeat_cnt + repeat_cnt = 0 + if nonrepeat_count >= 127: # limit max repeat count to max value of signed char. + nonrepeat_count = 127 + break + + index += blksize # move to next position + if index >= len(data): # data end + nonrepeat_count += repeat_cnt + break + + return nonrepeat_count + + +class RAWImage(): + ''' + RAW image is an exception to LVGL image, it has color format of RAW or RAW_ALPHA. + It has same image header as LVGL image, but the data is pure raw data from file. + It does not support stride adjustment etc. features for LVGL image. + It only supports convert an image to C array with RAW or RAW_ALPHA format. + ''' + CF_SUPPORTED = (ColorFormat.RAW, ColorFormat.RAW_ALPHA) + + class NotSupported(NotImplementedError): + pass + + def __init__(self, + cf: ColorFormat = ColorFormat.UNKNOWN, + data: bytes = b'') -> None: + self.cf = cf + self.data = data + + def to_c_array(self, + filename: str): + # Image size is set to zero, to let PNG or JPEG decoder to handle it + # Stride is meaningless for RAW image + write_c_array_file(0, 0, 0, self.cf, filename, + False, CompressMethod.NONE, self.data) + + def from_file(self, + filename: str, + cf: ColorFormat = None): + if cf not in RAWImage.CF_SUPPORTED: + raise RAWImage.NotSupported(f"Invalid color format: {cf.name}") + + with open(filename, "rb") as f: + self.data = f.read() + self.cf = cf + return self + + +class OutputFormat(Enum): + C_ARRAY = "C" + BIN_FILE = "BIN" + PNG_FILE = "PNG" # convert to lvgl image and then to png + + +class PNGConverter: + + def __init__(self, + files: List, + cf: ColorFormat, + ofmt: OutputFormat, + odir: str, + background: int = 0x00, + align: int = 1, + premultiply: bool = False, + compress: CompressMethod = CompressMethod.NONE, + keep_folder=True, + rgb565_dither=False) -> None: + self.files = files + self.cf = cf + self.ofmt = ofmt + self.output = odir + self.pngquant = None + self.keep_folder = keep_folder + self.align = align + self.premultiply = premultiply + self.compress = compress + self.background = background + self.rgb565_dither = rgb565_dither + + def _replace_ext(self, input, ext): + if self.keep_folder: + name, _ = path.splitext(input) + else: + name, _ = path.splitext(path.basename(input)) + output = name + ext + output = path.join(self.output, output) + return output + + def convert(self): + output = [] + for f in self.files: + if self.cf in (ColorFormat.RAW, ColorFormat.RAW_ALPHA): + # Process RAW image explicitly + img = RAWImage().from_file(f, self.cf) + img.to_c_array(self._replace_ext(f, ".c")) + else: + img = LVGLImage().from_png(f, self.cf, background=self.background, rgb565_dither=self.rgb565_dither) + img.adjust_stride(align=self.align) + + if self.premultiply: + img.premultiply() + output.append((f, img)) + if self.ofmt == OutputFormat.BIN_FILE: + img.to_bin(self._replace_ext(f, ".bin"), + compress=self.compress) + elif self.ofmt == OutputFormat.C_ARRAY: + img.to_c_array(self._replace_ext(f, ".c"), + compress=self.compress) + elif self.ofmt == OutputFormat.PNG_FILE: + img.to_png(self._replace_ext(f, ".png")) + + return output + + +def main(): + parser = argparse.ArgumentParser(description='LVGL PNG to bin image tool.') + parser.add_argument('--ofmt', + help="output filename format, C or BIN", + default="BIN", + choices=["C", "BIN", "PNG"]) + parser.add_argument( + '--cf', + help=("bin image color format, use AUTO for automatically " + "choose from I1/2/4/8"), + default="I8", + choices=[ + "L8", "I1", "I2", "I4", "I8", "A1", "A2", "A4", "A8", "ARGB8888", + "XRGB8888", "RGB565", "RGB565A8", "ARGB8565", "RGB888", "AUTO", + "RAW", "RAW_ALPHA" + ]) + + parser.add_argument('--rgb565dither', action='store_true', + help="use dithering to correct banding in gradients", default=False) + + parser.add_argument('--premultiply', action='store_true', + help="pre-multiply color with alpha", default=False) + + parser.add_argument('--compress', + help=("Binary data compress method, default to NONE"), + default="NONE", + choices=["NONE", "RLE", "LZ4"]) + + parser.add_argument('--align', + help="stride alignment in bytes for bin image", + default=1, + type=int, + metavar='byte', + nargs='?') + parser.add_argument('--background', + help="Background color for formats without alpha", + default=0x00_00_00, + type=lambda x: int(x, 0), + metavar='color', + nargs='?') + parser.add_argument('-o', + '--output', + default="./output", + help="Select the output folder, default to ./output") + parser.add_argument('-v', '--verbose', action='store_true') + parser.add_argument( + 'input', help="the filename or folder to be recursively converted") + + args = parser.parse_args() + + if path.isfile(args.input): + files = [args.input] + elif path.isdir(args.input): + files = list(Path(args.input).rglob("*.[pP][nN][gG]")) + else: + raise BaseException(f"invalid input: {args.input}") + + if args.verbose: + logging.basicConfig(level=logging.INFO) + + logging.info(f"options: {args.__dict__}, files:{[str(f) for f in files]}") + + if args.cf == "AUTO": + cf = None + else: + cf = ColorFormat[args.cf] + + ofmt = OutputFormat(args.ofmt) if cf not in ( + ColorFormat.RAW, ColorFormat.RAW_ALPHA) else OutputFormat.C_ARRAY + compress = CompressMethod[args.compress] + + converter = PNGConverter(files, + cf, + ofmt, + args.output, + background=args.background, + align=args.align, + premultiply=args.premultiply, + compress=compress, + keep_folder=False, + rgb565_dither=args.rgb565dither) + output = converter.convert() + for f, img in output: + logging.info(f"len: {img.data_len} for {path.basename(f)} ") + + print(f"done {len(files)} files") + + +def test(): + logging.basicConfig(level=logging.INFO) + f = "pngs/cogwheel.RGB565A8.png" + img = LVGLImage().from_png(f, + cf=ColorFormat.ARGB8565, + background=0xFF_FF_00, + rgb565_dither=True) + img.adjust_stride(align=16) + img.premultiply() + img.to_bin("output/cogwheel.ARGB8565.bin") + img.to_c_array("output/cogwheel-abc.c") # file name is used as c var name + img.to_png("output/cogwheel.ARGB8565.png.png") # convert back to png + + +def test_raw(): + logging.basicConfig(level=logging.INFO) + f = "pngs/cogwheel.RGB565A8.png" + img = RAWImage().from_file(f, + cf=ColorFormat.RAW_ALPHA) + img.to_c_array("output/cogwheel-raw.c") + + +if __name__ == "__main__": + # test() + # test_raw() + main() diff --git a/scripts/Image_Converter/README.md b/scripts/Image_Converter/README.md index 62a02cd..4b2b450 100644 --- a/scripts/Image_Converter/README.md +++ b/scripts/Image_Converter/README.md @@ -1,45 +1,45 @@ -# LVGL图片转换工具 - -这个目录包含两个用于处理和转换图片为LVGL格式的Python脚本: - -## 1. LVGLImage (LVGLImage.py) - -引用自LVGL[官方repo](https://github.com/lvgl/lvgl)的转换脚本[LVGLImage.py](https://github.com/lvgl/lvgl/blob/master/scripts/LVGLImage.py) - -## 2. LVGL图片转换工具 (lvgl_tools_gui.py) - -调用`LVGLImage.py`,将图片批量转换为LVGL图片格式 -可用于修改小智的默认表情,具体修改教程[在这里](https://www.bilibili.com/video/BV12FQkYeEJ3/) - -### 特性 - -- 图形化操作,界面更友好 -- 支持批量转换图片 -- 自动识别图片格式并选择最佳的颜色格式转换 -- 多分辨率支持 - -### 使用方法 - -创建虚拟环境 -```bash -# 创建 venv -python -m venv venv -# 激活环境 -source venv/bin/activate # Linux/Mac -venv\Scripts\activate # Windows -``` - -安装依赖 -```bash -pip install -r requirements.txt -``` - -运行转换工具 - -```bash -# 激活环境 -source venv/bin/activate # Linux/Mac -venv\Scripts\activate # Windows -# 运行 -python lvgl_tools_gui.py -``` +# LVGL图片转换工具 + +这个目录包含两个用于处理和转换图片为LVGL格式的Python脚本: + +## 1. LVGLImage (LVGLImage.py) + +引用自LVGL[官方repo](https://github.com/lvgl/lvgl)的转换脚本[LVGLImage.py](https://github.com/lvgl/lvgl/blob/master/scripts/LVGLImage.py) + +## 2. LVGL图片转换工具 (lvgl_tools_gui.py) + +调用`LVGLImage.py`,将图片批量转换为LVGL图片格式 +可用于修改小智的默认表情,具体修改教程[在这里](https://www.bilibili.com/video/BV12FQkYeEJ3/) + +### 特性 + +- 图形化操作,界面更友好 +- 支持批量转换图片 +- 自动识别图片格式并选择最佳的颜色格式转换 +- 多分辨率支持 + +### 使用方法 + +创建虚拟环境 +```bash +# 创建 venv +python -m venv venv +# 激活环境 +source venv/bin/activate # Linux/Mac +venv\Scripts\activate # Windows +``` + +安装依赖 +```bash +pip install -r requirements.txt +``` + +运行转换工具 + +```bash +# 激活环境 +source venv/bin/activate # Linux/Mac +venv\Scripts\activate # Windows +# 运行 +python lvgl_tools_gui.py +``` diff --git a/scripts/Image_Converter/lvgl_tools_gui.py b/scripts/Image_Converter/lvgl_tools_gui.py index de7c1f2..7aa85f2 100644 --- a/scripts/Image_Converter/lvgl_tools_gui.py +++ b/scripts/Image_Converter/lvgl_tools_gui.py @@ -1,253 +1,253 @@ -import tkinter as tk -from tkinter import ttk, filedialog, messagebox -from PIL import Image -import os -import tempfile -import sys -from LVGLImage import LVGLImage, ColorFormat, CompressMethod - -HELP_TEXT = """LVGL图片转换工具使用说明: - -1. 添加文件:点击“添加文件”按钮选择需要转换的图片,支持批量导入 - -2. 移除文件:在列表中选中文件前的复选框“[ ]”(选中后会变成“[√]”),点击“移除选中”可删除选定文件 - -3. 设置分辨率:选择需要的分辨率,如128x128 - 建议根据自己的设备的屏幕分辨率来选择。过大和过小都会影响显示效果。 - -4. 颜色格式:选择“自动识别”会根据图片是否透明自动选择,或手动指定 - 除非你了解这个选项,否则建议使用自动识别,不然可能会出现一些意想不到的问题…… - -5. 压缩方式:选择NONE或RLE压缩 - 除非你了解这个选项,否则建议保持默认NONE不压缩 - -6. 输出目录:设置转换后文件的保存路径 - 默认为程序所在目录下的output文件夹 - -7. 转换:点击“转换全部”或“转换选中”开始转换 -""" - -class ImageConverterApp: - def __init__(self, root): - self.root = root - self.root.title("LVGL图片转换工具") - self.root.geometry("750x650") - - # 初始化变量 - self.output_dir = tk.StringVar(value=os.path.abspath("output")) - self.resolution = tk.StringVar(value="128x128") - self.color_format = tk.StringVar(value="自动识别") - self.compress_method = tk.StringVar(value="NONE") - - # 创建UI组件 - self.create_widgets() - self.redirect_output() - - def create_widgets(self): - # 参数设置框架 - settings_frame = ttk.LabelFrame(self.root, text="转换设置") - settings_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew") - - # 分辨率设置 - ttk.Label(settings_frame, text="分辨率:").grid(row=0, column=0, padx=2) - ttk.Combobox(settings_frame, textvariable=self.resolution, - values=["512x512", "256x256", "128x128", "64x64", "32x32"], width=8).grid(row=0, column=1, padx=2) - - # 颜色格式 - ttk.Label(settings_frame, text="颜色格式:").grid(row=0, column=2, padx=2) - ttk.Combobox(settings_frame, textvariable=self.color_format, - values=["自动识别", "RGB565", "RGB565A8"], width=10).grid(row=0, column=3, padx=2) - - # 压缩方式 - ttk.Label(settings_frame, text="压缩方式:").grid(row=0, column=4, padx=2) - ttk.Combobox(settings_frame, textvariable=self.compress_method, - values=["NONE", "RLE"], width=8).grid(row=0, column=5, padx=2) - - # 文件操作框架 - file_frame = ttk.LabelFrame(self.root, text="选取文件") - file_frame.grid(row=1, column=0, padx=10, pady=5, sticky="nsew") - - # 文件操作按钮 - btn_frame = ttk.Frame(file_frame) - btn_frame.pack(fill=tk.X, pady=2) - ttk.Button(btn_frame, text="添加文件", command=self.select_files).pack(side=tk.LEFT, padx=2) - ttk.Button(btn_frame, text="移除选中", command=self.remove_selected).pack(side=tk.LEFT, padx=2) - ttk.Button(btn_frame, text="清空列表", command=self.clear_files).pack(side=tk.LEFT, padx=2) - - # 文件列表(Treeview) - self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"), - show="headings", height=10) - self.tree.heading("selected", text="选择", anchor=tk.W) - self.tree.heading("filename", text="文件名", anchor=tk.W) - self.tree.column("selected", width=60, anchor=tk.W) - self.tree.column("filename", width=600, anchor=tk.W) - self.tree.pack(fill=tk.BOTH, expand=True) - self.tree.bind("", self.on_tree_click) - - # 输出目录 - output_frame = ttk.LabelFrame(self.root, text="输出目录") - output_frame.grid(row=2, column=0, padx=10, pady=5, sticky="ew") - ttk.Entry(output_frame, textvariable=self.output_dir, width=60).pack(side=tk.LEFT, padx=5) - ttk.Button(output_frame, text="浏览", command=self.select_output_dir).pack(side=tk.RIGHT, padx=5) - - # 转换按钮和帮助按钮 - convert_frame = ttk.Frame(self.root) - convert_frame.grid(row=3, column=0, padx=10, pady=10) - ttk.Button(convert_frame, text="转换全部文件", command=lambda: self.start_conversion(True)).pack(side=tk.LEFT, padx=5) - ttk.Button(convert_frame, text="转换选中文件", command=lambda: self.start_conversion(False)).pack(side=tk.LEFT, padx=5) - ttk.Button(convert_frame, text="帮助", command=self.show_help).pack(side=tk.RIGHT, padx=5) - - # 日志区域(新增清空按钮部分) - log_frame = ttk.LabelFrame(self.root, text="日志") - log_frame.grid(row=4, column=0, padx=10, pady=5, sticky="nsew") - - # 添加按钮框架 - log_btn_frame = ttk.Frame(log_frame) - log_btn_frame.pack(fill=tk.X, side=tk.BOTTOM) - - # 清空日志按钮 - ttk.Button(log_btn_frame, text="清空日志", command=self.clear_log).pack(side=tk.RIGHT, padx=5, pady=2) - - self.log_text = tk.Text(log_frame, height=15) - self.log_text.pack(fill=tk.BOTH, expand=True) - - # 布局配置 - self.root.columnconfigure(0, weight=1) - self.root.rowconfigure(1, weight=1) - self.root.rowconfigure(4, weight=1) - - def clear_log(self): - """清空日志内容""" - self.log_text.delete(1.0, tk.END) - - def show_help(self): - messagebox.showinfo("帮助", HELP_TEXT) - - def redirect_output(self): - class StdoutRedirector: - def __init__(self, text_widget): - self.text_widget = text_widget - self.original_stdout = sys.stdout - - def write(self, message): - self.text_widget.insert(tk.END, message) - self.text_widget.see(tk.END) - self.original_stdout.write(message) - - def flush(self): - self.original_stdout.flush() - - sys.stdout = StdoutRedirector(self.log_text) - - def on_tree_click(self, event): - region = self.tree.identify("region", event.x, event.y) - if region == "cell": - col = self.tree.identify_column(event.x) - item = self.tree.identify_row(event.y) - if col == "#1": # 点击的是选中列 - current_val = self.tree.item(item, "values")[0] - new_val = "[√]" if current_val == "[ ]" else "[ ]" - self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1])) - - def select_output_dir(self): - path = filedialog.askdirectory() - if path: - self.output_dir.set(path) - - def select_files(self): - files = filedialog.askopenfilenames(filetypes=[("图片文件", "*.png;*.jpg;*.jpeg;*.bmp;*.gif")]) - for f in files: - self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,)) - - def remove_selected(self): - to_remove = [] - for item in self.tree.get_children(): - if self.tree.item(item, "values")[0] == "[√]": - to_remove.append(item) - for item in reversed(to_remove): - self.tree.delete(item) - - def clear_files(self): - for item in self.tree.get_children(): - self.tree.delete(item) - - def start_conversion(self, convert_all): - input_files = [ - self.tree.item(item, "tags")[0] - for item in self.tree.get_children() - if convert_all or self.tree.item(item, "values")[0] == "[√]" - ] - - if not input_files: - msg = "没有找到可转换的文件" if convert_all else "没有选中任何文件" - messagebox.showwarning("警告", msg) - return - - os.makedirs(self.output_dir.get(), exist_ok=True) - - # 解析转换参数 - width, height = map(int, self.resolution.get().split('x')) - compress = CompressMethod.RLE if self.compress_method.get() == "RLE" else CompressMethod.NONE - - # 执行转换 - self.convert_images(input_files, width, height, compress) - - def convert_images(self, input_files, width, height, compress): - success_count = 0 - total_files = len(input_files) - - for idx, file_path in enumerate(input_files): - try: - print(f"正在处理: {os.path.basename(file_path)}") - - with Image.open(file_path) as img: - # 调整图片大小 - img = img.resize((width, height), Image.Resampling.LANCZOS) - - # 处理颜色格式 - color_format_str = self.color_format.get() - if color_format_str == "自动识别": - # 检测透明通道 - has_alpha = img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info) - if has_alpha: - img = img.convert('RGBA') - cf = ColorFormat.RGB565A8 - else: - img = img.convert('RGB') - cf = ColorFormat.RGB565 - else: - if color_format_str == "RGB565A8": - img = img.convert('RGBA') - cf = ColorFormat.RGB565A8 - else: - img = img.convert('RGB') - cf = ColorFormat.RGB565 - - # 保存调整后的图片 - base_name = os.path.splitext(os.path.basename(file_path))[0] - output_image_path = os.path.join(self.output_dir.get(), f"{base_name}_{width}x{height}.png") - img.save(output_image_path, 'PNG') - - # 创建临时文件 - with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmpfile: - temp_path = tmpfile.name - img.save(temp_path, 'PNG') - - # 转换为LVGL C数组 - lvgl_img = LVGLImage().from_png(temp_path, cf=cf) - output_c_path = os.path.join(self.output_dir.get(), f"{base_name}.c") - lvgl_img.to_c_array(output_c_path, compress=compress) - - success_count += 1 - os.unlink(temp_path) - print(f"成功转换: {base_name}.c\n") - - except Exception as e: - print(f"转换失败: {str(e)}\n") - - print(f"转换完成! 成功 {success_count}/{total_files} 个文件\n") - -if __name__ == "__main__": - root = tk.Tk() - app = ImageConverterApp(root) - root.mainloop() +import tkinter as tk +from tkinter import ttk, filedialog, messagebox +from PIL import Image +import os +import tempfile +import sys +from LVGLImage import LVGLImage, ColorFormat, CompressMethod + +HELP_TEXT = """LVGL图片转换工具使用说明: + +1. 添加文件:点击“添加文件”按钮选择需要转换的图片,支持批量导入 + +2. 移除文件:在列表中选中文件前的复选框“[ ]”(选中后会变成“[√]”),点击“移除选中”可删除选定文件 + +3. 设置分辨率:选择需要的分辨率,如128x128 + 建议根据自己的设备的屏幕分辨率来选择。过大和过小都会影响显示效果。 + +4. 颜色格式:选择“自动识别”会根据图片是否透明自动选择,或手动指定 + 除非你了解这个选项,否则建议使用自动识别,不然可能会出现一些意想不到的问题…… + +5. 压缩方式:选择NONE或RLE压缩 + 除非你了解这个选项,否则建议保持默认NONE不压缩 + +6. 输出目录:设置转换后文件的保存路径 + 默认为程序所在目录下的output文件夹 + +7. 转换:点击“转换全部”或“转换选中”开始转换 +""" + +class ImageConverterApp: + def __init__(self, root): + self.root = root + self.root.title("LVGL图片转换工具") + self.root.geometry("750x650") + + # 初始化变量 + self.output_dir = tk.StringVar(value=os.path.abspath("output")) + self.resolution = tk.StringVar(value="128x128") + self.color_format = tk.StringVar(value="自动识别") + self.compress_method = tk.StringVar(value="NONE") + + # 创建UI组件 + self.create_widgets() + self.redirect_output() + + def create_widgets(self): + # 参数设置框架 + settings_frame = ttk.LabelFrame(self.root, text="转换设置") + settings_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew") + + # 分辨率设置 + ttk.Label(settings_frame, text="分辨率:").grid(row=0, column=0, padx=2) + ttk.Combobox(settings_frame, textvariable=self.resolution, + values=["512x512", "256x256", "128x128", "64x64", "32x32"], width=8).grid(row=0, column=1, padx=2) + + # 颜色格式 + ttk.Label(settings_frame, text="颜色格式:").grid(row=0, column=2, padx=2) + ttk.Combobox(settings_frame, textvariable=self.color_format, + values=["自动识别", "RGB565", "RGB565A8"], width=10).grid(row=0, column=3, padx=2) + + # 压缩方式 + ttk.Label(settings_frame, text="压缩方式:").grid(row=0, column=4, padx=2) + ttk.Combobox(settings_frame, textvariable=self.compress_method, + values=["NONE", "RLE"], width=8).grid(row=0, column=5, padx=2) + + # 文件操作框架 + file_frame = ttk.LabelFrame(self.root, text="选取文件") + file_frame.grid(row=1, column=0, padx=10, pady=5, sticky="nsew") + + # 文件操作按钮 + btn_frame = ttk.Frame(file_frame) + btn_frame.pack(fill=tk.X, pady=2) + ttk.Button(btn_frame, text="添加文件", command=self.select_files).pack(side=tk.LEFT, padx=2) + ttk.Button(btn_frame, text="移除选中", command=self.remove_selected).pack(side=tk.LEFT, padx=2) + ttk.Button(btn_frame, text="清空列表", command=self.clear_files).pack(side=tk.LEFT, padx=2) + + # 文件列表(Treeview) + self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"), + show="headings", height=10) + self.tree.heading("selected", text="选择", anchor=tk.W) + self.tree.heading("filename", text="文件名", anchor=tk.W) + self.tree.column("selected", width=60, anchor=tk.W) + self.tree.column("filename", width=600, anchor=tk.W) + self.tree.pack(fill=tk.BOTH, expand=True) + self.tree.bind("", self.on_tree_click) + + # 输出目录 + output_frame = ttk.LabelFrame(self.root, text="输出目录") + output_frame.grid(row=2, column=0, padx=10, pady=5, sticky="ew") + ttk.Entry(output_frame, textvariable=self.output_dir, width=60).pack(side=tk.LEFT, padx=5) + ttk.Button(output_frame, text="浏览", command=self.select_output_dir).pack(side=tk.RIGHT, padx=5) + + # 转换按钮和帮助按钮 + convert_frame = ttk.Frame(self.root) + convert_frame.grid(row=3, column=0, padx=10, pady=10) + ttk.Button(convert_frame, text="转换全部文件", command=lambda: self.start_conversion(True)).pack(side=tk.LEFT, padx=5) + ttk.Button(convert_frame, text="转换选中文件", command=lambda: self.start_conversion(False)).pack(side=tk.LEFT, padx=5) + ttk.Button(convert_frame, text="帮助", command=self.show_help).pack(side=tk.RIGHT, padx=5) + + # 日志区域(新增清空按钮部分) + log_frame = ttk.LabelFrame(self.root, text="日志") + log_frame.grid(row=4, column=0, padx=10, pady=5, sticky="nsew") + + # 添加按钮框架 + log_btn_frame = ttk.Frame(log_frame) + log_btn_frame.pack(fill=tk.X, side=tk.BOTTOM) + + # 清空日志按钮 + ttk.Button(log_btn_frame, text="清空日志", command=self.clear_log).pack(side=tk.RIGHT, padx=5, pady=2) + + self.log_text = tk.Text(log_frame, height=15) + self.log_text.pack(fill=tk.BOTH, expand=True) + + # 布局配置 + self.root.columnconfigure(0, weight=1) + self.root.rowconfigure(1, weight=1) + self.root.rowconfigure(4, weight=1) + + def clear_log(self): + """清空日志内容""" + self.log_text.delete(1.0, tk.END) + + def show_help(self): + messagebox.showinfo("帮助", HELP_TEXT) + + def redirect_output(self): + class StdoutRedirector: + def __init__(self, text_widget): + self.text_widget = text_widget + self.original_stdout = sys.stdout + + def write(self, message): + self.text_widget.insert(tk.END, message) + self.text_widget.see(tk.END) + self.original_stdout.write(message) + + def flush(self): + self.original_stdout.flush() + + sys.stdout = StdoutRedirector(self.log_text) + + def on_tree_click(self, event): + region = self.tree.identify("region", event.x, event.y) + if region == "cell": + col = self.tree.identify_column(event.x) + item = self.tree.identify_row(event.y) + if col == "#1": # 点击的是选中列 + current_val = self.tree.item(item, "values")[0] + new_val = "[√]" if current_val == "[ ]" else "[ ]" + self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1])) + + def select_output_dir(self): + path = filedialog.askdirectory() + if path: + self.output_dir.set(path) + + def select_files(self): + files = filedialog.askopenfilenames(filetypes=[("图片文件", "*.png;*.jpg;*.jpeg;*.bmp;*.gif")]) + for f in files: + self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,)) + + def remove_selected(self): + to_remove = [] + for item in self.tree.get_children(): + if self.tree.item(item, "values")[0] == "[√]": + to_remove.append(item) + for item in reversed(to_remove): + self.tree.delete(item) + + def clear_files(self): + for item in self.tree.get_children(): + self.tree.delete(item) + + def start_conversion(self, convert_all): + input_files = [ + self.tree.item(item, "tags")[0] + for item in self.tree.get_children() + if convert_all or self.tree.item(item, "values")[0] == "[√]" + ] + + if not input_files: + msg = "没有找到可转换的文件" if convert_all else "没有选中任何文件" + messagebox.showwarning("警告", msg) + return + + os.makedirs(self.output_dir.get(), exist_ok=True) + + # 解析转换参数 + width, height = map(int, self.resolution.get().split('x')) + compress = CompressMethod.RLE if self.compress_method.get() == "RLE" else CompressMethod.NONE + + # 执行转换 + self.convert_images(input_files, width, height, compress) + + def convert_images(self, input_files, width, height, compress): + success_count = 0 + total_files = len(input_files) + + for idx, file_path in enumerate(input_files): + try: + print(f"正在处理: {os.path.basename(file_path)}") + + with Image.open(file_path) as img: + # 调整图片大小 + img = img.resize((width, height), Image.Resampling.LANCZOS) + + # 处理颜色格式 + color_format_str = self.color_format.get() + if color_format_str == "自动识别": + # 检测透明通道 + has_alpha = img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info) + if has_alpha: + img = img.convert('RGBA') + cf = ColorFormat.RGB565A8 + else: + img = img.convert('RGB') + cf = ColorFormat.RGB565 + else: + if color_format_str == "RGB565A8": + img = img.convert('RGBA') + cf = ColorFormat.RGB565A8 + else: + img = img.convert('RGB') + cf = ColorFormat.RGB565 + + # 保存调整后的图片 + base_name = os.path.splitext(os.path.basename(file_path))[0] + output_image_path = os.path.join(self.output_dir.get(), f"{base_name}_{width}x{height}.png") + img.save(output_image_path, 'PNG') + + # 创建临时文件 + with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmpfile: + temp_path = tmpfile.name + img.save(temp_path, 'PNG') + + # 转换为LVGL C数组 + lvgl_img = LVGLImage().from_png(temp_path, cf=cf) + output_c_path = os.path.join(self.output_dir.get(), f"{base_name}.c") + lvgl_img.to_c_array(output_c_path, compress=compress) + + success_count += 1 + os.unlink(temp_path) + print(f"成功转换: {base_name}.c\n") + + except Exception as e: + print(f"转换失败: {str(e)}\n") + + print(f"转换完成! 成功 {success_count}/{total_files} 个文件\n") + +if __name__ == "__main__": + root = tk.Tk() + app = ImageConverterApp(root) + root.mainloop() diff --git a/scripts/Image_Converter/requirements.txt b/scripts/Image_Converter/requirements.txt index 5dbc310..931d3e8 100644 --- a/scripts/Image_Converter/requirements.txt +++ b/scripts/Image_Converter/requirements.txt @@ -1,3 +1,3 @@ -lz4==4.4.4 -Pillow==11.3.0 -pypng==0.20220715.0 +lz4==4.4.4 +Pillow==11.3.0 +pypng==0.20220715.0 diff --git a/scripts/acoustic_check/demod.py b/scripts/acoustic_check/demod.py index 89c8196..8af3242 100644 --- a/scripts/acoustic_check/demod.py +++ b/scripts/acoustic_check/demod.py @@ -1,280 +1,280 @@ -""" -实时AFSK解调器 - 基于Goertzel算法 -""" - -import numpy as np -from collections import deque - - -class TraceGoertzel: - """实时Goertzel算法实现""" - - def __init__(self, freq: float, n: int): - """ - 初始化Goertzel算法 - - Args: - freq: 归一化频率 (目标频率/采样频率) - n: 窗口大小 - """ - self.freq = freq - self.n = n - - # 预计算系数 - 与参考代码一致 - self.k = int(freq * n) - self.w = 2.0 * np.pi * freq - self.cw = np.cos(self.w) - self.sw = np.sin(self.w) - self.c = 2.0 * self.cw - - # 初始化状态变量 - 使用deque存储最近两个值 - self.zs = deque([0.0, 0.0], maxlen=2) - - def reset(self): - """重置算法状态""" - self.zs.clear() - self.zs.extend([0.0, 0.0]) - - def __call__(self, xs): - """ - 处理一组采样点 - 与参考代码一致的接口 - - Args: - xs: 采样点序列 - - Returns: - 计算出的振幅 - """ - self.reset() - for x in xs: - z1, z2 = self.zs[-1], self.zs[-2] # Z[-1], Z[-2] - z0 = x + self.c * z1 - z2 # S[n] = x[n] + C * S[n-1] - S[n-2] - self.zs.append(float(z0)) # 更新序列 - return self.amp - - @property - def amp(self) -> float: - """计算当前振幅 - 与参考代码一致""" - z1, z2 = self.zs[-1], self.zs[-2] - ip = self.cw * z1 - z2 - qp = self.sw * z1 - return np.sqrt(ip**2 + qp**2) / (self.n / 2.0) - - -class PairGoertzel: - """双频Goertzel解调器""" - - def __init__(self, f_sample: int, f_space: int, f_mark: int, - bit_rate: int, win_size: int): - """ - 初始化双频解调器 - - Args: - f_sample: 采样频率 - f_space: Space频率 (通常对应0) - f_mark: Mark频率 (通常对应1) - bit_rate: 比特率 - win_size: Goertzel窗口大小 - """ - assert f_sample % bit_rate == 0, "采样频率必须是比特率的整数倍" - - self.Fs = f_sample - self.F0 = f_space - self.F1 = f_mark - self.bit_rate = bit_rate - self.n_per_bit = int(f_sample // bit_rate) # 每个比特的采样点数 - - # 计算归一化频率 - f1 = f_mark / f_sample - f0 = f_space / f_sample - - # 初始化Goertzel算法 - self.g0 = TraceGoertzel(freq=f0, n=win_size) - self.g1 = TraceGoertzel(freq=f1, n=win_size) - - # 输入缓冲区 - self.in_buffer = deque(maxlen=win_size) - self.out_count = 0 - - print(f"PairGoertzel initialized: f0={f0:.6f}, f1={f1:.6f}, win_size={win_size}, n_per_bit={self.n_per_bit}") - - def __call__(self, s: float): - """ - 处理单个采样点 - 与参考代码一致的接口 - - Args: - s: 采样点值 - - Returns: - (amp0, amp1, p1_prob) - 空间频率振幅,标记频率振幅,标记概率 - """ - self.in_buffer.append(s) - self.out_count += 1 - - amp0, amp1, p1_prob = 0, 0, None - - # 每个比特周期输出一次结果 - if self.out_count >= self.n_per_bit: - amp0 = self.g0(self.in_buffer) # 计算space频率振幅 - amp1 = self.g1(self.in_buffer) # 计算mark频率振幅 - p1_prob = amp1 / (amp0 + amp1 + 1e-8) # 计算mark概率 - self.out_count = 0 - - return amp0, amp1, p1_prob - - -class RealTimeAFSKDecoder: - """实时AFSK解码器 - 基于起始帧触发""" - - def __init__(self, f_sample: int = 16000, mark_freq: int = 1800, - space_freq: int = 1500, bitrate: int = 100, - s_goertzel: int = 9, threshold: float = 0.5): - """ - 初始化实时AFSK解码器 - - Args: - f_sample: 采样频率 - mark_freq: Mark频率 - space_freq: Space频率 - bitrate: 比特率 - s_goertzel: Goertzel窗口大小系数 (win_size = f_sample // mark_freq * s_goertzel) - threshold: 判决门限 - """ - self.f_sample = f_sample - self.mark_freq = mark_freq - self.space_freq = space_freq - self.bitrate = bitrate - self.threshold = threshold - - # 计算窗口大小 - 与参考代码一致 - win_size = int(f_sample / mark_freq * s_goertzel) - - # 初始化解调器 - self.demodulator = PairGoertzel(f_sample, space_freq, mark_freq, - bitrate, win_size) - - # 帧定义 - 与参考代码一致 - self.start_bytes = b'\x01\x02' - self.end_bytes = b'\x03\x04' - self.start_bits = "".join(format(int(x), '08b') for x in self.start_bytes) - self.end_bits = "".join(format(int(x), '08b') for x in self.end_bytes) - - # 状态机 - self.state = "idle" # idle / entering - - # 存储解调结果 - self.buffer_prelude:deque = deque(maxlen=len(self.start_bits)) # 判断是否启动 - self.indicators = [] # 存储概率序列 - self.signal_bits = "" # 存储比特序列 - self.text_cache = "" - - # 解码结果 - self.decoded_messages = [] - self.total_bits_received = 0 - - print(f"Decoder initialized: win_size={win_size}") - print(f"Start frame: {self.start_bits} (from {self.start_bytes.hex()})") - print(f"End frame: {self.end_bits} (from {self.end_bytes.hex()})") - - def process_audio(self, samples: np.array) -> str: - """ - 处理音频数据并返回解码文本 - - Args: - audio_data: 音频字节数据 (16-bit PCM) - - Returns: - 新解码的文本 - """ - new_text = "" - # 逐个处理采样点 - for sample in samples: - amp0, amp1, p1_prob = self.demodulator(sample) - # 如果有概率输出,记录并判决 - if p1_prob is not None: - bit = '1' if p1_prob > self.threshold else '0' - match self.state: - case "idle": - self.buffer_prelude.append(bit) - pass - case "entering": - self.buffer_prelude.append(bit) - self.signal_bits += bit - self.total_bits_received += 1 - case _: - pass - self.indicators.append(p1_prob) - - # 检查状态机 - if self.state == "idle" and "".join(self.buffer_prelude) == self.start_bits: - self.state = "entering" - self.text_cache = "" - self.signal_bits = "" # 清空比特序列 - self.buffer_prelude.clear() - elif self.state == "entering" and ("".join(self.buffer_prelude) == self.end_bits or len(self.signal_bits) >= 256): - self.state = "idle" - self.buffer_prelude.clear() - - # 每收集一定数量的比特后尝试解码 - if len(self.signal_bits) >= 8: - text = self._decode_bits_to_text(self.signal_bits) - if len(text) > len(self.text_cache): - new_text = text[len(self.text_cache) - len(text):] - self.text_cache = text - return new_text - - def _decode_bits_to_text(self, bits: str) -> str: - """ - 将比特串解码为文本 - - Args: - bits: 比特串 - - Returns: - 解码出的文本 - """ - if len(bits) < 8: - return "" - - decoded_text = "" - byte_count = len(bits) // 8 - - for i in range(byte_count): - # 提取8位 - byte_bits = bits[i*8:(i+1)*8] - - # 位转字节 - byte_val = int(byte_bits, 2) - - # 尝试解码为ASCII字符 - if 32 <= byte_val <= 126: # 可打印ASCII字符 - decoded_text += chr(byte_val) - elif byte_val == 0: # NULL字符,忽略 - continue - else: - # 非可打印字符pass,以十六进制显示 - pass - # decoded_text += f"\\x{byte_val:02X}" - - return decoded_text - - def clear(self): - """清空解码状态""" - self.indicators = [] - self.signal_bits = "" - self.decoded_messages = [] - self.total_bits_received = 0 - print("解码器状态已清空") - - def get_stats(self) -> dict: - """获取解码统计信息""" - return { - 'prelude_bits': "".join(self.buffer_prelude), - "state": self.state, - 'total_chars': sum(len(msg) for msg in self.text_cache), - 'buffer_bits': len(self.signal_bits), - 'mark_freq': self.mark_freq, - 'space_freq': self.space_freq, - 'bitrate': self.bitrate, - 'threshold': self.threshold, - } +""" +实时AFSK解调器 - 基于Goertzel算法 +""" + +import numpy as np +from collections import deque + + +class TraceGoertzel: + """实时Goertzel算法实现""" + + def __init__(self, freq: float, n: int): + """ + 初始化Goertzel算法 + + Args: + freq: 归一化频率 (目标频率/采样频率) + n: 窗口大小 + """ + self.freq = freq + self.n = n + + # 预计算系数 - 与参考代码一致 + self.k = int(freq * n) + self.w = 2.0 * np.pi * freq + self.cw = np.cos(self.w) + self.sw = np.sin(self.w) + self.c = 2.0 * self.cw + + # 初始化状态变量 - 使用deque存储最近两个值 + self.zs = deque([0.0, 0.0], maxlen=2) + + def reset(self): + """重置算法状态""" + self.zs.clear() + self.zs.extend([0.0, 0.0]) + + def __call__(self, xs): + """ + 处理一组采样点 - 与参考代码一致的接口 + + Args: + xs: 采样点序列 + + Returns: + 计算出的振幅 + """ + self.reset() + for x in xs: + z1, z2 = self.zs[-1], self.zs[-2] # Z[-1], Z[-2] + z0 = x + self.c * z1 - z2 # S[n] = x[n] + C * S[n-1] - S[n-2] + self.zs.append(float(z0)) # 更新序列 + return self.amp + + @property + def amp(self) -> float: + """计算当前振幅 - 与参考代码一致""" + z1, z2 = self.zs[-1], self.zs[-2] + ip = self.cw * z1 - z2 + qp = self.sw * z1 + return np.sqrt(ip**2 + qp**2) / (self.n / 2.0) + + +class PairGoertzel: + """双频Goertzel解调器""" + + def __init__(self, f_sample: int, f_space: int, f_mark: int, + bit_rate: int, win_size: int): + """ + 初始化双频解调器 + + Args: + f_sample: 采样频率 + f_space: Space频率 (通常对应0) + f_mark: Mark频率 (通常对应1) + bit_rate: 比特率 + win_size: Goertzel窗口大小 + """ + assert f_sample % bit_rate == 0, "采样频率必须是比特率的整数倍" + + self.Fs = f_sample + self.F0 = f_space + self.F1 = f_mark + self.bit_rate = bit_rate + self.n_per_bit = int(f_sample // bit_rate) # 每个比特的采样点数 + + # 计算归一化频率 + f1 = f_mark / f_sample + f0 = f_space / f_sample + + # 初始化Goertzel算法 + self.g0 = TraceGoertzel(freq=f0, n=win_size) + self.g1 = TraceGoertzel(freq=f1, n=win_size) + + # 输入缓冲区 + self.in_buffer = deque(maxlen=win_size) + self.out_count = 0 + + print(f"PairGoertzel initialized: f0={f0:.6f}, f1={f1:.6f}, win_size={win_size}, n_per_bit={self.n_per_bit}") + + def __call__(self, s: float): + """ + 处理单个采样点 - 与参考代码一致的接口 + + Args: + s: 采样点值 + + Returns: + (amp0, amp1, p1_prob) - 空间频率振幅,标记频率振幅,标记概率 + """ + self.in_buffer.append(s) + self.out_count += 1 + + amp0, amp1, p1_prob = 0, 0, None + + # 每个比特周期输出一次结果 + if self.out_count >= self.n_per_bit: + amp0 = self.g0(self.in_buffer) # 计算space频率振幅 + amp1 = self.g1(self.in_buffer) # 计算mark频率振幅 + p1_prob = amp1 / (amp0 + amp1 + 1e-8) # 计算mark概率 + self.out_count = 0 + + return amp0, amp1, p1_prob + + +class RealTimeAFSKDecoder: + """实时AFSK解码器 - 基于起始帧触发""" + + def __init__(self, f_sample: int = 16000, mark_freq: int = 1800, + space_freq: int = 1500, bitrate: int = 100, + s_goertzel: int = 9, threshold: float = 0.5): + """ + 初始化实时AFSK解码器 + + Args: + f_sample: 采样频率 + mark_freq: Mark频率 + space_freq: Space频率 + bitrate: 比特率 + s_goertzel: Goertzel窗口大小系数 (win_size = f_sample // mark_freq * s_goertzel) + threshold: 判决门限 + """ + self.f_sample = f_sample + self.mark_freq = mark_freq + self.space_freq = space_freq + self.bitrate = bitrate + self.threshold = threshold + + # 计算窗口大小 - 与参考代码一致 + win_size = int(f_sample / mark_freq * s_goertzel) + + # 初始化解调器 + self.demodulator = PairGoertzel(f_sample, space_freq, mark_freq, + bitrate, win_size) + + # 帧定义 - 与参考代码一致 + self.start_bytes = b'\x01\x02' + self.end_bytes = b'\x03\x04' + self.start_bits = "".join(format(int(x), '08b') for x in self.start_bytes) + self.end_bits = "".join(format(int(x), '08b') for x in self.end_bytes) + + # 状态机 + self.state = "idle" # idle / entering + + # 存储解调结果 + self.buffer_prelude:deque = deque(maxlen=len(self.start_bits)) # 判断是否启动 + self.indicators = [] # 存储概率序列 + self.signal_bits = "" # 存储比特序列 + self.text_cache = "" + + # 解码结果 + self.decoded_messages = [] + self.total_bits_received = 0 + + print(f"Decoder initialized: win_size={win_size}") + print(f"Start frame: {self.start_bits} (from {self.start_bytes.hex()})") + print(f"End frame: {self.end_bits} (from {self.end_bytes.hex()})") + + def process_audio(self, samples: np.array) -> str: + """ + 处理音频数据并返回解码文本 + + Args: + audio_data: 音频字节数据 (16-bit PCM) + + Returns: + 新解码的文本 + """ + new_text = "" + # 逐个处理采样点 + for sample in samples: + amp0, amp1, p1_prob = self.demodulator(sample) + # 如果有概率输出,记录并判决 + if p1_prob is not None: + bit = '1' if p1_prob > self.threshold else '0' + match self.state: + case "idle": + self.buffer_prelude.append(bit) + pass + case "entering": + self.buffer_prelude.append(bit) + self.signal_bits += bit + self.total_bits_received += 1 + case _: + pass + self.indicators.append(p1_prob) + + # 检查状态机 + if self.state == "idle" and "".join(self.buffer_prelude) == self.start_bits: + self.state = "entering" + self.text_cache = "" + self.signal_bits = "" # 清空比特序列 + self.buffer_prelude.clear() + elif self.state == "entering" and ("".join(self.buffer_prelude) == self.end_bits or len(self.signal_bits) >= 256): + self.state = "idle" + self.buffer_prelude.clear() + + # 每收集一定数量的比特后尝试解码 + if len(self.signal_bits) >= 8: + text = self._decode_bits_to_text(self.signal_bits) + if len(text) > len(self.text_cache): + new_text = text[len(self.text_cache) - len(text):] + self.text_cache = text + return new_text + + def _decode_bits_to_text(self, bits: str) -> str: + """ + 将比特串解码为文本 + + Args: + bits: 比特串 + + Returns: + 解码出的文本 + """ + if len(bits) < 8: + return "" + + decoded_text = "" + byte_count = len(bits) // 8 + + for i in range(byte_count): + # 提取8位 + byte_bits = bits[i*8:(i+1)*8] + + # 位转字节 + byte_val = int(byte_bits, 2) + + # 尝试解码为ASCII字符 + if 32 <= byte_val <= 126: # 可打印ASCII字符 + decoded_text += chr(byte_val) + elif byte_val == 0: # NULL字符,忽略 + continue + else: + # 非可打印字符pass,以十六进制显示 + pass + # decoded_text += f"\\x{byte_val:02X}" + + return decoded_text + + def clear(self): + """清空解码状态""" + self.indicators = [] + self.signal_bits = "" + self.decoded_messages = [] + self.total_bits_received = 0 + print("解码器状态已清空") + + def get_stats(self) -> dict: + """获取解码统计信息""" + return { + 'prelude_bits': "".join(self.buffer_prelude), + "state": self.state, + 'total_chars': sum(len(msg) for msg in self.text_cache), + 'buffer_bits': len(self.signal_bits), + 'mark_freq': self.mark_freq, + 'space_freq': self.space_freq, + 'bitrate': self.bitrate, + 'threshold': self.threshold, + } diff --git a/scripts/acoustic_check/graphic.py b/scripts/acoustic_check/graphic.py index e60cf06..c73293c 100644 --- a/scripts/acoustic_check/graphic.py +++ b/scripts/acoustic_check/graphic.py @@ -1,444 +1,444 @@ -import sys -import numpy as np -import asyncio -import wave -from collections import deque -import qasync - -import matplotlib -matplotlib.use('qtagg') - -from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar # noqa: F401 -from matplotlib.figure import Figure - -from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QWidget, - QHBoxLayout, QLineEdit, QPushButton, QLabel, QTextEdit) -from PyQt6.QtCore import QTimer - -# 导入解码器 -from demod import RealTimeAFSKDecoder - - -class UDPServerProtocol(asyncio.DatagramProtocol): - """UDP服务器协议类""" - def __init__(self, data_queue): - self.client_address = None - self.data_queue: deque = data_queue - - def connection_made(self, transport): - self.transport = transport - - def datagram_received(self, data, addr): - # 如果还没有客户端地址,记录第一个连接的客户端 - if self.client_address is None: - self.client_address = addr - print(f"接受来自 {addr} 的连接") - - # 只处理来自已记录客户端的数据 - if addr == self.client_address: - # 将接收到的音频数据添加到队列 - self.data_queue.extend(data) - else: - print(f"忽略来自未知地址 {addr} 的数据") - - -class MatplotlibWidget(QWidget): - def __init__(self, parent=None): - super().__init__(parent) - - # 创建 Matplotlib 的 Figure 对象 - self.figure = Figure() - - # 创建 FigureCanvas 对象,它是 Figure 的 QWidget 容器 - self.canvas = FigureCanvas(self.figure) - - # 创建 Matplotlib 的导航工具栏 - # self.toolbar = NavigationToolbar(self.canvas, self) - self.toolbar = None - - # 创建布局 - layout = QVBoxLayout() - layout.addWidget(self.toolbar) - layout.addWidget(self.canvas) - self.setLayout(layout) - - # 初始化音频数据参数 - self.freq = 16000 # 采样频率 - self.time_window = 20 # 显示时间窗口 - self.wave_data = deque(maxlen=self.freq * self.time_window * 2) # 缓冲队列, 用于分发计算/绘图 - self.signals = deque(maxlen=self.freq * self.time_window) # 双端队列存储信号数据 - - # 创建包含两个子图的画布 - self.ax1 = self.figure.add_subplot(2, 1, 1) - self.ax2 = self.figure.add_subplot(2, 1, 2) - - # 时域子图 - self.ax1.set_title('Real-time Audio Waveform') - self.ax1.set_xlabel('Sample Index') - self.ax1.set_ylabel('Amplitude') - self.line_time, = self.ax1.plot([], []) - self.ax1.grid(True, alpha=0.3) - - # 频域子图 - self.ax2.set_title('Real-time Frequency Spectrum') - self.ax2.set_xlabel('Frequency (Hz)') - self.ax2.set_ylabel('Magnitude') - self.line_freq, = self.ax2.plot([], []) - self.ax2.grid(True, alpha=0.3) - - self.figure.tight_layout() - - # 定时器用于更新图表 - self.timer = QTimer(self) - self.timer.setInterval(100) # 100毫秒更新一次 - self.timer.timeout.connect(self.update_plot) - - # 初始化AFSK解码器 - self.decoder = RealTimeAFSKDecoder( - f_sample=self.freq, - mark_freq=1800, - space_freq=1500, - bitrate=100, - s_goertzel=9, - threshold=0.5 - ) - - # 解码结果回调 - self.decode_callback = None - - def start_plotting(self): - """开始绘图""" - self.timer.start() - - def stop_plotting(self): - """停止绘图""" - self.timer.stop() - - def update_plot(self): - """更新绘图数据""" - if len(self.wave_data) >= 2: - # 进行实时解码 - # 获取最新的音频数据进行解码 - even = len(self.wave_data) // 2 * 2 - print(f"length of wave_data: {len(self.wave_data)}") - drained = [self.wave_data.popleft() for _ in range(even)] - signal = np.frombuffer(bytearray(drained), dtype=' 0: - # 只显示最近的一段数据,避免图表过于密集 - signal = np.array(self.signals) - max_samples = min(len(signal), self.freq * self.time_window) - if len(signal) > max_samples: - signal = signal[-max_samples:] - - # 更新时域图 - x = np.arange(len(signal)) - self.line_time.set_data(x, signal) - - # 自动调整时域坐标轴范围 - if len(signal) > 0: - self.ax1.set_xlim(0, len(signal)) - y_min, y_max = np.min(signal), np.max(signal) - if y_min != y_max: - margin = (y_max - y_min) * 0.1 - self.ax1.set_ylim(y_min - margin, y_max + margin) - else: - self.ax1.set_ylim(-1, 1) - - # 计算频谱(短时离散傅立叶变换) - if len(signal) > 1: - # 计算FFT - fft_signal = np.abs(np.fft.fft(signal)) - frequencies = np.fft.fftfreq(len(signal), 1/self.freq) - - # 只取正频率部分 - positive_freq_idx = frequencies >= 0 - freq_positive = frequencies[positive_freq_idx] - fft_positive = fft_signal[positive_freq_idx] - - # 更新频域图 - self.line_freq.set_data(freq_positive, fft_positive) - - # 自动调整频域坐标轴范围 - if len(fft_positive) > 0: - # 限制频率显示范围到0-4000Hz,避免过于密集 - max_freq_show = min(4000, self.freq // 2) - freq_mask = freq_positive <= max_freq_show - if np.any(freq_mask): - self.ax2.set_xlim(0, max_freq_show) - fft_masked = fft_positive[freq_mask] - if len(fft_masked) > 0: - fft_max = np.max(fft_masked) - if fft_max > 0: - self.ax2.set_ylim(0, fft_max * 1.1) - else: - self.ax2.set_ylim(0, 1) - - self.canvas.draw() - - -class MainWindow(QMainWindow): - def __init__(self): - super().__init__() - self.setWindowTitle("Acoustic Check") - self.setGeometry(100, 100, 1000, 800) - - # 主窗口部件 - main_widget = QWidget() - self.setCentralWidget(main_widget) - - # 主布局 - main_layout = QVBoxLayout(main_widget) - - # 绘图区域 - self.matplotlib_widget = MatplotlibWidget() - main_layout.addWidget(self.matplotlib_widget) - - # 控制面板 - control_panel = QWidget() - control_layout = QHBoxLayout(control_panel) - - # 监听地址和端口输入 - control_layout.addWidget(QLabel("监听地址:")) - self.address_input = QLineEdit("0.0.0.0") - self.address_input.setFixedWidth(120) - control_layout.addWidget(self.address_input) - - control_layout.addWidget(QLabel("端口:")) - self.port_input = QLineEdit("8000") - self.port_input.setFixedWidth(80) - control_layout.addWidget(self.port_input) - - # 监听按钮 - self.listen_button = QPushButton("开始监听") - self.listen_button.clicked.connect(self.toggle_listening) - control_layout.addWidget(self.listen_button) - - # 状态标签 - self.status_label = QLabel("状态: 未连接") - control_layout.addWidget(self.status_label) - - # 数据统计标签 - self.data_label = QLabel("接收数据: 0 bytes") - control_layout.addWidget(self.data_label) - - # 保存按钮 - self.save_button = QPushButton("保存音频") - self.save_button.clicked.connect(self.save_audio) - self.save_button.setEnabled(False) - control_layout.addWidget(self.save_button) - - control_layout.addStretch() # 添加弹性空间 - - main_layout.addWidget(control_panel) - - # 解码显示区域 - decode_panel = QWidget() - decode_layout = QVBoxLayout(decode_panel) - - # 解码标题 - decode_title = QLabel("实时AFSK解码结果:") - decode_title.setStyleSheet("font-weight: bold; font-size: 14px;") - decode_layout.addWidget(decode_title) - - # 解码文本显示 - self.decode_text = QTextEdit() - self.decode_text.setMaximumHeight(150) - self.decode_text.setReadOnly(True) - self.decode_text.setStyleSheet("font-family: 'Courier New', monospace; font-size: 12px;") - decode_layout.addWidget(self.decode_text) - - # 解码控制按钮 - decode_control_layout = QHBoxLayout() - - # 清空按钮 - self.clear_decode_button = QPushButton("清空解码") - self.clear_decode_button.clicked.connect(self.clear_decode_text) - decode_control_layout.addWidget(self.clear_decode_button) - - # 解码统计标签 - self.decode_stats_label = QLabel("解码统计: 0 bits, 0 chars") - decode_control_layout.addWidget(self.decode_stats_label) - - decode_control_layout.addStretch() - decode_layout.addLayout(decode_control_layout) - - main_layout.addWidget(decode_panel) - - # 设置解码回调 - self.matplotlib_widget.decode_callback = self.on_decode_text - - # UDP相关属性 - self.udp_transport = None - self.is_listening = False - - # 数据统计定时器 - self.stats_timer = QTimer(self) - self.stats_timer.setInterval(1000) # 每秒更新一次统计 - self.stats_timer.timeout.connect(self.update_stats) - - def on_decode_text(self, new_text: str): - """解码文本回调""" - if new_text: - # 添加新解码的文本 - current_text = self.decode_text.toPlainText() - updated_text = current_text + new_text - - # 限制文本长度,保留最新的1000个字符 - if len(updated_text) > 1000: - updated_text = updated_text[-1000:] - - self.decode_text.setPlainText(updated_text) - - # 滚动到底部 - cursor = self.decode_text.textCursor() - cursor.movePosition(cursor.MoveOperation.End) - self.decode_text.setTextCursor(cursor) - - def clear_decode_text(self): - """清空解码文本""" - self.decode_text.clear() - if hasattr(self.matplotlib_widget, 'decoder'): - self.matplotlib_widget.decoder.clear() - self.decode_stats_label.setText("解码统计: 0 bits, 0 chars") - - def update_decode_stats(self): - """更新解码统计""" - if hasattr(self.matplotlib_widget, 'decoder'): - stats = self.matplotlib_widget.decoder.get_stats() - stats_text = ( - f"前置: {stats['prelude_bits']} , 已接收{stats['total_chars']} chars, " - f"缓冲: {stats['buffer_bits']} bits, 状态: {stats['state']}" - ) - self.decode_stats_label.setText(stats_text) - - def toggle_listening(self): - """切换监听状态""" - if not self.is_listening: - self.start_listening() - else: - self.stop_listening() - - async def start_listening_async(self): - """异步启动UDP监听""" - try: - address = self.address_input.text().strip() - port = int(self.port_input.text().strip()) - - loop = asyncio.get_running_loop() - self.udp_transport, protocol = await loop.create_datagram_endpoint( - lambda: UDPServerProtocol(self.matplotlib_widget.wave_data), - local_addr=(address, port) - ) - - self.status_label.setText(f"状态: 监听中 ({address}:{port})") - print(f"UDP服务器启动, 监听 {address}:{port}") - - except Exception as e: - self.status_label.setText(f"状态: 启动失败 - {str(e)}") - print(f"UDP服务器启动失败: {e}") - self.is_listening = False - self.listen_button.setText("开始监听") - self.address_input.setEnabled(True) - self.port_input.setEnabled(True) - - def start_listening(self): - """开始监听""" - try: - int(self.port_input.text().strip()) # 验证端口号格式 - except ValueError: - self.status_label.setText("状态: 端口号必须是数字") - return - - self.is_listening = True - self.listen_button.setText("停止监听") - self.address_input.setEnabled(False) - self.port_input.setEnabled(False) - self.save_button.setEnabled(True) - - # 清空数据队列 - self.matplotlib_widget.wave_data.clear() - - # 启动绘图和统计更新 - self.matplotlib_widget.start_plotting() - self.stats_timer.start() - - # 异步启动UDP服务器 - loop = asyncio.get_event_loop() - loop.create_task(self.start_listening_async()) - - def stop_listening(self): - """停止监听""" - self.is_listening = False - self.listen_button.setText("开始监听") - self.address_input.setEnabled(True) - self.port_input.setEnabled(True) - - # 停止UDP服务器 - if self.udp_transport: - self.udp_transport.close() - self.udp_transport = None - - # 停止绘图和统计更新 - self.matplotlib_widget.stop_plotting() - self.matplotlib_widget.wave_data.clear() - self.stats_timer.stop() - - self.status_label.setText("状态: 已停止") - - def update_stats(self): - """更新数据统计""" - data_size = len(self.matplotlib_widget.signals) - self.data_label.setText(f"接收数据: {data_size} 采样") - - # 更新解码统计 - self.update_decode_stats() - - def save_audio(self): - """保存音频数据""" - if len(self.matplotlib_widget.signals) > 0: - try: - signal_data = np.array(self.matplotlib_widget.signals) - - # 保存为WAV文件 - with wave.open("received_audio.wav", "wb") as wf: - wf.setnchannels(1) # 单声道 - wf.setsampwidth(2) # 采样宽度为2字节 - wf.setframerate(self.matplotlib_widget.freq) # 设置采样率 - wf.writeframes(signal_data.tobytes()) # 写入数据 - - self.status_label.setText("状态: 音频已保存为 received_audio.wav") - print("音频已保存为 received_audio.wav") - - except Exception as e: - self.status_label.setText(f"状态: 保存失败 - {str(e)}") - print(f"保存音频失败: {e}") - else: - self.status_label.setText("状态: 没有足够的数据可保存") - - -async def main(): - """异步主函数""" - app = QApplication(sys.argv) - - # 设置异步事件循环 - loop = qasync.QEventLoop(app) - asyncio.set_event_loop(loop) - - window = MainWindow() - window.show() - - try: - with loop: - await loop.run_forever() - except KeyboardInterrupt: - print("程序被用户中断") - finally: - # 确保清理资源 - if window.udp_transport: +import sys +import numpy as np +import asyncio +import wave +from collections import deque +import qasync + +import matplotlib +matplotlib.use('qtagg') + +from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar # noqa: F401 +from matplotlib.figure import Figure + +from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QWidget, + QHBoxLayout, QLineEdit, QPushButton, QLabel, QTextEdit) +from PyQt6.QtCore import QTimer + +# 导入解码器 +from demod import RealTimeAFSKDecoder + + +class UDPServerProtocol(asyncio.DatagramProtocol): + """UDP服务器协议类""" + def __init__(self, data_queue): + self.client_address = None + self.data_queue: deque = data_queue + + def connection_made(self, transport): + self.transport = transport + + def datagram_received(self, data, addr): + # 如果还没有客户端地址,记录第一个连接的客户端 + if self.client_address is None: + self.client_address = addr + print(f"接受来自 {addr} 的连接") + + # 只处理来自已记录客户端的数据 + if addr == self.client_address: + # 将接收到的音频数据添加到队列 + self.data_queue.extend(data) + else: + print(f"忽略来自未知地址 {addr} 的数据") + + +class MatplotlibWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + + # 创建 Matplotlib 的 Figure 对象 + self.figure = Figure() + + # 创建 FigureCanvas 对象,它是 Figure 的 QWidget 容器 + self.canvas = FigureCanvas(self.figure) + + # 创建 Matplotlib 的导航工具栏 + # self.toolbar = NavigationToolbar(self.canvas, self) + self.toolbar = None + + # 创建布局 + layout = QVBoxLayout() + layout.addWidget(self.toolbar) + layout.addWidget(self.canvas) + self.setLayout(layout) + + # 初始化音频数据参数 + self.freq = 16000 # 采样频率 + self.time_window = 20 # 显示时间窗口 + self.wave_data = deque(maxlen=self.freq * self.time_window * 2) # 缓冲队列, 用于分发计算/绘图 + self.signals = deque(maxlen=self.freq * self.time_window) # 双端队列存储信号数据 + + # 创建包含两个子图的画布 + self.ax1 = self.figure.add_subplot(2, 1, 1) + self.ax2 = self.figure.add_subplot(2, 1, 2) + + # 时域子图 + self.ax1.set_title('Real-time Audio Waveform') + self.ax1.set_xlabel('Sample Index') + self.ax1.set_ylabel('Amplitude') + self.line_time, = self.ax1.plot([], []) + self.ax1.grid(True, alpha=0.3) + + # 频域子图 + self.ax2.set_title('Real-time Frequency Spectrum') + self.ax2.set_xlabel('Frequency (Hz)') + self.ax2.set_ylabel('Magnitude') + self.line_freq, = self.ax2.plot([], []) + self.ax2.grid(True, alpha=0.3) + + self.figure.tight_layout() + + # 定时器用于更新图表 + self.timer = QTimer(self) + self.timer.setInterval(100) # 100毫秒更新一次 + self.timer.timeout.connect(self.update_plot) + + # 初始化AFSK解码器 + self.decoder = RealTimeAFSKDecoder( + f_sample=self.freq, + mark_freq=1800, + space_freq=1500, + bitrate=100, + s_goertzel=9, + threshold=0.5 + ) + + # 解码结果回调 + self.decode_callback = None + + def start_plotting(self): + """开始绘图""" + self.timer.start() + + def stop_plotting(self): + """停止绘图""" + self.timer.stop() + + def update_plot(self): + """更新绘图数据""" + if len(self.wave_data) >= 2: + # 进行实时解码 + # 获取最新的音频数据进行解码 + even = len(self.wave_data) // 2 * 2 + print(f"length of wave_data: {len(self.wave_data)}") + drained = [self.wave_data.popleft() for _ in range(even)] + signal = np.frombuffer(bytearray(drained), dtype=' 0: + # 只显示最近的一段数据,避免图表过于密集 + signal = np.array(self.signals) + max_samples = min(len(signal), self.freq * self.time_window) + if len(signal) > max_samples: + signal = signal[-max_samples:] + + # 更新时域图 + x = np.arange(len(signal)) + self.line_time.set_data(x, signal) + + # 自动调整时域坐标轴范围 + if len(signal) > 0: + self.ax1.set_xlim(0, len(signal)) + y_min, y_max = np.min(signal), np.max(signal) + if y_min != y_max: + margin = (y_max - y_min) * 0.1 + self.ax1.set_ylim(y_min - margin, y_max + margin) + else: + self.ax1.set_ylim(-1, 1) + + # 计算频谱(短时离散傅立叶变换) + if len(signal) > 1: + # 计算FFT + fft_signal = np.abs(np.fft.fft(signal)) + frequencies = np.fft.fftfreq(len(signal), 1/self.freq) + + # 只取正频率部分 + positive_freq_idx = frequencies >= 0 + freq_positive = frequencies[positive_freq_idx] + fft_positive = fft_signal[positive_freq_idx] + + # 更新频域图 + self.line_freq.set_data(freq_positive, fft_positive) + + # 自动调整频域坐标轴范围 + if len(fft_positive) > 0: + # 限制频率显示范围到0-4000Hz,避免过于密集 + max_freq_show = min(4000, self.freq // 2) + freq_mask = freq_positive <= max_freq_show + if np.any(freq_mask): + self.ax2.set_xlim(0, max_freq_show) + fft_masked = fft_positive[freq_mask] + if len(fft_masked) > 0: + fft_max = np.max(fft_masked) + if fft_max > 0: + self.ax2.set_ylim(0, fft_max * 1.1) + else: + self.ax2.set_ylim(0, 1) + + self.canvas.draw() + + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Acoustic Check") + self.setGeometry(100, 100, 1000, 800) + + # 主窗口部件 + main_widget = QWidget() + self.setCentralWidget(main_widget) + + # 主布局 + main_layout = QVBoxLayout(main_widget) + + # 绘图区域 + self.matplotlib_widget = MatplotlibWidget() + main_layout.addWidget(self.matplotlib_widget) + + # 控制面板 + control_panel = QWidget() + control_layout = QHBoxLayout(control_panel) + + # 监听地址和端口输入 + control_layout.addWidget(QLabel("监听地址:")) + self.address_input = QLineEdit("0.0.0.0") + self.address_input.setFixedWidth(120) + control_layout.addWidget(self.address_input) + + control_layout.addWidget(QLabel("端口:")) + self.port_input = QLineEdit("8000") + self.port_input.setFixedWidth(80) + control_layout.addWidget(self.port_input) + + # 监听按钮 + self.listen_button = QPushButton("开始监听") + self.listen_button.clicked.connect(self.toggle_listening) + control_layout.addWidget(self.listen_button) + + # 状态标签 + self.status_label = QLabel("状态: 未连接") + control_layout.addWidget(self.status_label) + + # 数据统计标签 + self.data_label = QLabel("接收数据: 0 bytes") + control_layout.addWidget(self.data_label) + + # 保存按钮 + self.save_button = QPushButton("保存音频") + self.save_button.clicked.connect(self.save_audio) + self.save_button.setEnabled(False) + control_layout.addWidget(self.save_button) + + control_layout.addStretch() # 添加弹性空间 + + main_layout.addWidget(control_panel) + + # 解码显示区域 + decode_panel = QWidget() + decode_layout = QVBoxLayout(decode_panel) + + # 解码标题 + decode_title = QLabel("实时AFSK解码结果:") + decode_title.setStyleSheet("font-weight: bold; font-size: 14px;") + decode_layout.addWidget(decode_title) + + # 解码文本显示 + self.decode_text = QTextEdit() + self.decode_text.setMaximumHeight(150) + self.decode_text.setReadOnly(True) + self.decode_text.setStyleSheet("font-family: 'Courier New', monospace; font-size: 12px;") + decode_layout.addWidget(self.decode_text) + + # 解码控制按钮 + decode_control_layout = QHBoxLayout() + + # 清空按钮 + self.clear_decode_button = QPushButton("清空解码") + self.clear_decode_button.clicked.connect(self.clear_decode_text) + decode_control_layout.addWidget(self.clear_decode_button) + + # 解码统计标签 + self.decode_stats_label = QLabel("解码统计: 0 bits, 0 chars") + decode_control_layout.addWidget(self.decode_stats_label) + + decode_control_layout.addStretch() + decode_layout.addLayout(decode_control_layout) + + main_layout.addWidget(decode_panel) + + # 设置解码回调 + self.matplotlib_widget.decode_callback = self.on_decode_text + + # UDP相关属性 + self.udp_transport = None + self.is_listening = False + + # 数据统计定时器 + self.stats_timer = QTimer(self) + self.stats_timer.setInterval(1000) # 每秒更新一次统计 + self.stats_timer.timeout.connect(self.update_stats) + + def on_decode_text(self, new_text: str): + """解码文本回调""" + if new_text: + # 添加新解码的文本 + current_text = self.decode_text.toPlainText() + updated_text = current_text + new_text + + # 限制文本长度,保留最新的1000个字符 + if len(updated_text) > 1000: + updated_text = updated_text[-1000:] + + self.decode_text.setPlainText(updated_text) + + # 滚动到底部 + cursor = self.decode_text.textCursor() + cursor.movePosition(cursor.MoveOperation.End) + self.decode_text.setTextCursor(cursor) + + def clear_decode_text(self): + """清空解码文本""" + self.decode_text.clear() + if hasattr(self.matplotlib_widget, 'decoder'): + self.matplotlib_widget.decoder.clear() + self.decode_stats_label.setText("解码统计: 0 bits, 0 chars") + + def update_decode_stats(self): + """更新解码统计""" + if hasattr(self.matplotlib_widget, 'decoder'): + stats = self.matplotlib_widget.decoder.get_stats() + stats_text = ( + f"前置: {stats['prelude_bits']} , 已接收{stats['total_chars']} chars, " + f"缓冲: {stats['buffer_bits']} bits, 状态: {stats['state']}" + ) + self.decode_stats_label.setText(stats_text) + + def toggle_listening(self): + """切换监听状态""" + if not self.is_listening: + self.start_listening() + else: + self.stop_listening() + + async def start_listening_async(self): + """异步启动UDP监听""" + try: + address = self.address_input.text().strip() + port = int(self.port_input.text().strip()) + + loop = asyncio.get_running_loop() + self.udp_transport, protocol = await loop.create_datagram_endpoint( + lambda: UDPServerProtocol(self.matplotlib_widget.wave_data), + local_addr=(address, port) + ) + + self.status_label.setText(f"状态: 监听中 ({address}:{port})") + print(f"UDP服务器启动, 监听 {address}:{port}") + + except Exception as e: + self.status_label.setText(f"状态: 启动失败 - {str(e)}") + print(f"UDP服务器启动失败: {e}") + self.is_listening = False + self.listen_button.setText("开始监听") + self.address_input.setEnabled(True) + self.port_input.setEnabled(True) + + def start_listening(self): + """开始监听""" + try: + int(self.port_input.text().strip()) # 验证端口号格式 + except ValueError: + self.status_label.setText("状态: 端口号必须是数字") + return + + self.is_listening = True + self.listen_button.setText("停止监听") + self.address_input.setEnabled(False) + self.port_input.setEnabled(False) + self.save_button.setEnabled(True) + + # 清空数据队列 + self.matplotlib_widget.wave_data.clear() + + # 启动绘图和统计更新 + self.matplotlib_widget.start_plotting() + self.stats_timer.start() + + # 异步启动UDP服务器 + loop = asyncio.get_event_loop() + loop.create_task(self.start_listening_async()) + + def stop_listening(self): + """停止监听""" + self.is_listening = False + self.listen_button.setText("开始监听") + self.address_input.setEnabled(True) + self.port_input.setEnabled(True) + + # 停止UDP服务器 + if self.udp_transport: + self.udp_transport.close() + self.udp_transport = None + + # 停止绘图和统计更新 + self.matplotlib_widget.stop_plotting() + self.matplotlib_widget.wave_data.clear() + self.stats_timer.stop() + + self.status_label.setText("状态: 已停止") + + def update_stats(self): + """更新数据统计""" + data_size = len(self.matplotlib_widget.signals) + self.data_label.setText(f"接收数据: {data_size} 采样") + + # 更新解码统计 + self.update_decode_stats() + + def save_audio(self): + """保存音频数据""" + if len(self.matplotlib_widget.signals) > 0: + try: + signal_data = np.array(self.matplotlib_widget.signals) + + # 保存为WAV文件 + with wave.open("received_audio.wav", "wb") as wf: + wf.setnchannels(1) # 单声道 + wf.setsampwidth(2) # 采样宽度为2字节 + wf.setframerate(self.matplotlib_widget.freq) # 设置采样率 + wf.writeframes(signal_data.tobytes()) # 写入数据 + + self.status_label.setText("状态: 音频已保存为 received_audio.wav") + print("音频已保存为 received_audio.wav") + + except Exception as e: + self.status_label.setText(f"状态: 保存失败 - {str(e)}") + print(f"保存音频失败: {e}") + else: + self.status_label.setText("状态: 没有足够的数据可保存") + + +async def main(): + """异步主函数""" + app = QApplication(sys.argv) + + # 设置异步事件循环 + loop = qasync.QEventLoop(app) + asyncio.set_event_loop(loop) + + window = MainWindow() + window.show() + + try: + with loop: + await loop.run_forever() + except KeyboardInterrupt: + print("程序被用户中断") + finally: + # 确保清理资源 + if window.udp_transport: window.udp_transport.close() \ No newline at end of file diff --git a/scripts/acoustic_check/main.py b/scripts/acoustic_check/main.py index 63ec849..08ae4ef 100644 --- a/scripts/acoustic_check/main.py +++ b/scripts/acoustic_check/main.py @@ -1,18 +1,18 @@ -#!/usr/bin/env python3 -""" -音频实时监听与绘图系统主程序 -基于Qt GUI + Matplotlib + UDP接收 + AFSK解码字符串 -""" - -import sys -import asyncio -from graphic import main - -if __name__ == '__main__': - try: - asyncio.run(main()) - except KeyboardInterrupt: - print("程序被用户中断") - except Exception as e: - print(f"程序执行出错: {e}") - sys.exit(1) +#!/usr/bin/env python3 +""" +音频实时监听与绘图系统主程序 +基于Qt GUI + Matplotlib + UDP接收 + AFSK解码字符串 +""" + +import sys +import asyncio +from graphic import main + +if __name__ == '__main__': + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("程序被用户中断") + except Exception as e: + print(f"程序执行出错: {e}") + sys.exit(1) diff --git a/scripts/acoustic_check/readme.md b/scripts/acoustic_check/readme.md index 9eb9c7c..20a18be 100644 --- a/scripts/acoustic_check/readme.md +++ b/scripts/acoustic_check/readme.md @@ -1,23 +1,23 @@ -# 声波测试 -该gui用于测试接受小智设备通过`udp`回传的`pcm`转时域/频域, 可以保存窗口长度的声音, 用于判断噪音频率分布和测试声波传输ascii的准确度, - -固件测试需要打开`USE_AUDIO_DEBUGGER`, 并设置好`AUDIO_DEBUG_UDP_SERVER`是本机地址. -声波`demod`可以通过`sonic_wifi_config.html`或者上传至`PinMe`的[小智声波配网](https://iqf7jnhi.pinit.eth.limo)来输出声波测试 - -# 声波解码测试记录 - -> `✓`代表在I2S DIN接收原始PCM信号时就能成功解码, `△`代表需要降噪或额外操作可稳定解码, `X`代表降噪后效果也不好(可能能解部分但非常不稳定)。 -> 个别ADC需要I2C配置阶段做更精细的降噪调整, 由于设备不通用暂只按照boards内提供的config测试 - -| 设备 | ADC | MIC | 效果 | 备注 | -| ---- | ---- | --- | --- | ---- | -| bread-compact | INMP441 | 集成MEMEMIC | ✓ | -| atk-dnesp32s3-box | ES8311 | | ✓ | -| magiclick-2p5 | ES8311 | | ✓ | -| lichuang-dev | ES7210 | | △ | 测试时需要关掉INPUT_REFERENCE -| kevin-box-2 | ES7210 | | △ | 测试时需要关掉INPUT_REFERENCE -| m5stack-core-s3 | ES7210 | | △ | 测试时需要关掉INPUT_REFERENCE -| xmini-c3 | ES8311 | | △ | 需降噪 -| atoms3r-echo-base | ES8311 | | △ | 需降噪 -| atk-dnesp32s3-box0 | ES8311 | | X | 能接收且解码, 但是丢包率很高 +# 声波测试 +该gui用于测试接受小智设备通过`udp`回传的`pcm`转时域/频域, 可以保存窗口长度的声音, 用于判断噪音频率分布和测试声波传输ascii的准确度, + +固件测试需要打开`USE_AUDIO_DEBUGGER`, 并设置好`AUDIO_DEBUG_UDP_SERVER`是本机地址. +声波`demod`可以通过`sonic_wifi_config.html`或者上传至`PinMe`的[小智声波配网](https://iqf7jnhi.pinit.eth.limo)来输出声波测试 + +# 声波解码测试记录 + +> `✓`代表在I2S DIN接收原始PCM信号时就能成功解码, `△`代表需要降噪或额外操作可稳定解码, `X`代表降噪后效果也不好(可能能解部分但非常不稳定)。 +> 个别ADC需要I2C配置阶段做更精细的降噪调整, 由于设备不通用暂只按照boards内提供的config测试 + +| 设备 | ADC | MIC | 效果 | 备注 | +| ---- | ---- | --- | --- | ---- | +| bread-compact | INMP441 | 集成MEMEMIC | ✓ | +| atk-dnesp32s3-box | ES8311 | | ✓ | +| magiclick-2p5 | ES8311 | | ✓ | +| lichuang-dev | ES7210 | | △ | 测试时需要关掉INPUT_REFERENCE +| kevin-box-2 | ES7210 | | △ | 测试时需要关掉INPUT_REFERENCE +| m5stack-core-s3 | ES7210 | | △ | 测试时需要关掉INPUT_REFERENCE +| xmini-c3 | ES8311 | | △ | 需降噪 +| atoms3r-echo-base | ES8311 | | △ | 需降噪 +| atk-dnesp32s3-box0 | ES8311 | | X | 能接收且解码, 但是丢包率很高 | movecall-moji-esp32s3 | ES8311 | | X | 能接收且解码, 但是丢包率很高 \ No newline at end of file diff --git a/scripts/acoustic_check/requirements.txt b/scripts/acoustic_check/requirements.txt index 91bc5ec..3531b32 100644 --- a/scripts/acoustic_check/requirements.txt +++ b/scripts/acoustic_check/requirements.txt @@ -1,4 +1,4 @@ -matplotlib==3.10.5 -numpy==2.3.2 -PyQt6==6.9.1 -qasync==0.27.1 +matplotlib==3.10.5 +numpy==2.3.2 +PyQt6==6.9.1 +qasync==0.27.1 diff --git a/scripts/audio_debug_server.py b/scripts/audio_debug_server.py index 872c490..85d7ecf 100644 --- a/scripts/audio_debug_server.py +++ b/scripts/audio_debug_server.py @@ -1,54 +1,54 @@ -import socket -import wave -import argparse - - -''' - Create a UDP socket and bind it to the server's IP:8000. - Listen for incoming messages and print them to the console. - Save the audio to a WAV file. -''' -def main(samplerate, channels): - # Create a UDP socket - server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - server_socket.bind(('0.0.0.0', 8000)) - - # Create WAV file with parameters - filename = f"{samplerate}_{channels}.wav" - wav_file = wave.open(filename, "wb") - wav_file.setnchannels(channels) # channels parameter - wav_file.setsampwidth(2) # 2 bytes per sample (16-bit) - wav_file.setframerate(samplerate) # samplerate parameter - - print(f"Start saving audio from 0.0.0.0:8000 to {filename}...") - - try: - while True: - # Receive a message from the client - message, address = server_socket.recvfrom(8000) - - # Write PCM data to WAV file - wav_file.writeframes(message) - - # Print length of the message - print(f"Received {len(message)} bytes from {address}") - - except KeyboardInterrupt: - print("\nStopping recording...") - - finally: - # Close files and socket - wav_file.close() - server_socket.close() - print(f"WAV file '{filename}' saved successfully") - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='UDP音频数据接收器,保存为WAV文件') - parser.add_argument('--samplerate', '-s', type=int, default=16000, - help='采样率 (默认: 16000)') - parser.add_argument('--channels', '-c', type=int, default=2, - help='声道数 (默认: 2)') - - args = parser.parse_args() - main(args.samplerate, args.channels) +import socket +import wave +import argparse + + +''' + Create a UDP socket and bind it to the server's IP:8000. + Listen for incoming messages and print them to the console. + Save the audio to a WAV file. +''' +def main(samplerate, channels): + # Create a UDP socket + server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + server_socket.bind(('0.0.0.0', 8000)) + + # Create WAV file with parameters + filename = f"{samplerate}_{channels}.wav" + wav_file = wave.open(filename, "wb") + wav_file.setnchannels(channels) # channels parameter + wav_file.setsampwidth(2) # 2 bytes per sample (16-bit) + wav_file.setframerate(samplerate) # samplerate parameter + + print(f"Start saving audio from 0.0.0.0:8000 to {filename}...") + + try: + while True: + # Receive a message from the client + message, address = server_socket.recvfrom(8000) + + # Write PCM data to WAV file + wav_file.writeframes(message) + + # Print length of the message + print(f"Received {len(message)} bytes from {address}") + + except KeyboardInterrupt: + print("\nStopping recording...") + + finally: + # Close files and socket + wav_file.close() + server_socket.close() + print(f"WAV file '{filename}' saved successfully") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='UDP音频数据接收器,保存为WAV文件') + parser.add_argument('--samplerate', '-s', type=int, default=16000, + help='采样率 (默认: 16000)') + parser.add_argument('--channels', '-c', type=int, default=2, + help='声道数 (默认: 2)') + + args = parser.parse_args() + main(args.samplerate, args.channels) diff --git a/scripts/build_default_assets.py b/scripts/build_default_assets.py new file mode 100644 index 0000000..c8797de --- /dev/null +++ b/scripts/build_default_assets.py @@ -0,0 +1,882 @@ +#!/usr/bin/env python3 +""" +Build default assets based on configuration + +This script reads configuration from sdkconfig and builds the appropriate assets.bin +for the current board configuration. + +Usage: + ./build_default_assets.py --sdkconfig --builtin_text_font \ + --default_emoji_collection --output +""" + +import argparse +import io +import os +import shutil +import sys +import json +import struct +from datetime import datetime + + +# ============================================================================= +# Pack model functions (from pack_model.py) +# ============================================================================= + +def struct_pack_string(string, max_len=None): + """ + pack string to binary data. + if max_len is None, max_len = len(string) + 1 + else len(string) < max_len, the left will be padded by struct.pack('x') + """ + if max_len == None : + max_len = len(string) + else: + assert len(string) <= max_len + + left_num = max_len - len(string) + out_bytes = None + for char in string: + if out_bytes == None: + out_bytes = struct.pack('b', ord(char)) + else: + out_bytes += struct.pack('b', ord(char)) + for i in range(left_num): + out_bytes += struct.pack('x') + return out_bytes + + +def read_data(filename): + """Read binary data, like index and mndata""" + data = None + with open(filename, "rb") as f: + data = f.read() + return data + + +def pack_models(model_path, out_file="srmodels.bin"): + """ + Pack all models into one binary file by the following format: + { + model_num: int + model1_info: model_info_t + model2_info: model_info_t + ... + model1_index,model1_data,model1_MODEL_INFO + model1_index,model1_data,model1_MODEL_INFO + ... + }model_pack_t + + { + model_name: char[32] + file_number: int + file1_name: char[32] + file1_start: int + file1_len: int + file2_name: char[32] + file2_start: int // data_len = info_start - data_start + file2_len: int + ... + }model_info_t + """ + models = {} + file_num = 0 + model_num = 0 + for root, dirs, _ in os.walk(model_path): + for model_name in dirs: + models[model_name] = {} + model_dir = os.path.join(root, model_name) + model_num += 1 + for _, _, files in os.walk(model_dir): + for file_name in files: + file_num += 1 + file_path = os.path.join(model_dir, file_name) + models[model_name][file_name] = read_data(file_path) + + model_num = len(models) + header_len = 4 + model_num*(32+4) + file_num*(32+4+4) + out_bin = struct.pack('I', model_num) # model number + data_bin = None + for key in models: + model_bin = struct_pack_string(key, 32) # + model name + model_bin += struct.pack('I', len(models[key])) # + file number in this model + + for file_name in models[key]: + model_bin += struct_pack_string(file_name, 32) # + file name + if data_bin == None: + model_bin += struct.pack('I', header_len) + data_bin = models[key][file_name] + model_bin += struct.pack('I', len(models[key][file_name])) + else: + model_bin += struct.pack('I', header_len+len(data_bin)) + data_bin += models[key][file_name] + model_bin += struct.pack('I', len(models[key][file_name])) + + out_bin += model_bin + assert len(out_bin) == header_len + if data_bin != None: + out_bin += data_bin + + out_file = os.path.join(model_path, out_file) + with open(out_file, "wb") as f: + f.write(out_bin) + + +# ============================================================================= +# Build assets functions (from build.py) +# ============================================================================= + +def ensure_dir(directory): + """Ensure directory exists, create if not""" + os.makedirs(directory, exist_ok=True) + + +def copy_file(src, dst): + """Copy file""" + if os.path.exists(src): + shutil.copy2(src, dst) + print(f"Copied: {src} -> {dst}") + return True + else: + print(f"Warning: Source file does not exist: {src}") + return False + + +def copy_directory(src, dst): + """Copy directory""" + if os.path.exists(src): + shutil.copytree(src, dst, dirs_exist_ok=True) + print(f"Copied directory: {src} -> {dst}") + return True + else: + print(f"Warning: Source directory does not exist: {src}") + return False + + +def process_sr_models(wakenet_model_dirs, multinet_model_dirs, build_dir, assets_dir): + """Process SR models (wakenet and multinet) and generate srmodels.bin""" + if not wakenet_model_dirs and not multinet_model_dirs: + return None + + # Create SR models build directory + sr_models_build_dir = os.path.join(build_dir, "srmodels") + if os.path.exists(sr_models_build_dir): + shutil.rmtree(sr_models_build_dir) + os.makedirs(sr_models_build_dir) + + models_processed = 0 + + # Copy wakenet models if available + if wakenet_model_dirs: + for wakenet_model_dir in wakenet_model_dirs: + wakenet_name = os.path.basename(wakenet_model_dir) + wakenet_dst = os.path.join(sr_models_build_dir, wakenet_name) + if copy_directory(wakenet_model_dir, wakenet_dst): + models_processed += 1 + print(f"Added wakenet model: {wakenet_name}") + + # Copy multinet models if available + if multinet_model_dirs: + for multinet_model_dir in multinet_model_dirs: + multinet_name = os.path.basename(multinet_model_dir) + multinet_dst = os.path.join(sr_models_build_dir, multinet_name) + if copy_directory(multinet_model_dir, multinet_dst): + models_processed += 1 + print(f"Added multinet model: {multinet_name}") + + if models_processed == 0: + print("Warning: No SR models were successfully processed") + return None + + # Use pack_models function to generate srmodels.bin + srmodels_output = os.path.join(sr_models_build_dir, "srmodels.bin") + try: + pack_models(sr_models_build_dir, "srmodels.bin") + print(f"Generated: {srmodels_output}") + # Copy srmodels.bin to assets directory + copy_file(srmodels_output, os.path.join(assets_dir, "srmodels.bin")) + return "srmodels.bin" + except Exception as e: + print(f"Error: Failed to generate srmodels.bin: {e}") + return None + + +def process_text_font(text_font_file, assets_dir): + """Process text_font parameter""" + if not text_font_file: + return None + + # Copy input file to build/assets directory + font_filename = os.path.basename(text_font_file) + font_dst = os.path.join(assets_dir, font_filename) + if copy_file(text_font_file, font_dst): + return font_filename + return None + + +def process_emoji_collection(emoji_collection_dir, assets_dir): + """Process emoji_collection parameter""" + if not emoji_collection_dir: + return [] + + emoji_list = [] + + # Copy each image from input directory to build/assets directory + for root, dirs, files in os.walk(emoji_collection_dir): + for file in files: + if file.lower().endswith(('.png', '.gif')): + # Copy file + src_file = os.path.join(root, file) + dst_file = os.path.join(assets_dir, file) + if copy_file(src_file, dst_file): + # Get filename without extension + filename_without_ext = os.path.splitext(file)[0] + + # Add to emoji list + emoji_list.append({ + "name": filename_without_ext, + "file": file + }) + + return emoji_list + + +def process_extra_files(extra_files_dir, assets_dir): + """Process default_assets_extra_files parameter""" + if not extra_files_dir: + return [] + + if not os.path.exists(extra_files_dir): + print(f"Warning: Extra files directory not found: {extra_files_dir}") + return [] + + extra_files_list = [] + + # Copy each file from input directory to build/assets directory + for root, dirs, files in os.walk(extra_files_dir): + for file in files: + # Skip hidden files and directories + if file.startswith('.'): + continue + + # Copy file + src_file = os.path.join(root, file) + dst_file = os.path.join(assets_dir, file) + if copy_file(src_file, dst_file): + extra_files_list.append(file) + + if extra_files_list: + print(f"Processed {len(extra_files_list)} extra files from: {extra_files_dir}") + + return extra_files_list + + +def generate_index_json(assets_dir, srmodels, text_font, emoji_collection, extra_files=None, multinet_model_info=None): + """Generate index.json file""" + index_data = { + "version": 1 + } + + if srmodels: + index_data["srmodels"] = srmodels + + if text_font: + index_data["text_font"] = text_font + + if emoji_collection: + index_data["emoji_collection"] = emoji_collection + + if extra_files: + index_data["extra_files"] = extra_files + + if multinet_model_info: + index_data["multinet_model"] = multinet_model_info + + # Write index.json + index_path = os.path.join(assets_dir, "index.json") + with open(index_path, 'w', encoding='utf-8') as f: + json.dump(index_data, f, indent=4, ensure_ascii=False) + + print(f"Generated: {index_path}") + + +def generate_config_json(build_dir, assets_dir): + """Generate config.json file""" + config_data = { + "include_path": os.path.join(build_dir, "include"), + "assets_path": assets_dir, + "image_file": os.path.join(build_dir, "output", "assets.bin"), + "lvgl_ver": "9.3.0", + "assets_size": "0x400000", + "support_format": ".png, .gif, .jpg, .bin, .json", + "name_length": "32", + "split_height": "0", + "support_qoi": False, + "support_spng": False, + "support_sjpg": False, + "support_sqoi": False, + "support_raw": False, + "support_raw_dither": False, + "support_raw_bgr": False + } + + # Write config.json + config_path = os.path.join(build_dir, "config.json") + with open(config_path, 'w', encoding='utf-8') as f: + json.dump(config_data, f, indent=4, ensure_ascii=False) + + print(f"Generated: {config_path}") + return config_path + + +# ============================================================================= +# Simplified SPIFFS assets generation (from spiffs_assets_gen.py) +# ============================================================================= + +def compute_checksum(data): + checksum = sum(data) & 0xFFFF + return checksum + + +def sort_key(filename): + basename, extension = os.path.splitext(filename) + return extension, basename + + +def pack_assets_simple(target_path, include_path, out_file, assets_path, max_name_len=32): + """ + Simplified version of pack_assets that handles basic file packing + """ + merged_data = bytearray() + file_info_list = [] + skip_files = ['config.json'] + + # Ensure output directory exists + os.makedirs(os.path.dirname(out_file), exist_ok=True) + os.makedirs(include_path, exist_ok=True) + + file_list = sorted(os.listdir(target_path), key=sort_key) + for filename in file_list: + if filename in skip_files: + continue + + file_path = os.path.join(target_path, filename) + if not os.path.isfile(file_path): + continue + + file_name = os.path.basename(file_path) + file_size = os.path.getsize(file_path) + + file_info_list.append((file_name, len(merged_data), file_size, 0, 0)) + # Add 0x5A5A prefix to merged_data + merged_data.extend(b'\x5A' * 2) + + with open(file_path, 'rb') as bin_file: + bin_data = bin_file.read() + + merged_data.extend(bin_data) + + total_files = len(file_info_list) + + mmap_table = bytearray() + for file_name, offset, file_size, width, height in file_info_list: + if len(file_name) > max_name_len: + print(f'Warning: "{file_name}" exceeds {max_name_len} bytes and will be truncated.') + fixed_name = file_name.ljust(max_name_len, '\0')[:max_name_len] + mmap_table.extend(fixed_name.encode('utf-8')) + mmap_table.extend(file_size.to_bytes(4, byteorder='little')) + mmap_table.extend(offset.to_bytes(4, byteorder='little')) + mmap_table.extend(width.to_bytes(2, byteorder='little')) + mmap_table.extend(height.to_bytes(2, byteorder='little')) + + combined_data = mmap_table + merged_data + combined_checksum = compute_checksum(combined_data) + combined_data_length = len(combined_data).to_bytes(4, byteorder='little') + header_data = total_files.to_bytes(4, byteorder='little') + combined_checksum.to_bytes(4, byteorder='little') + final_data = header_data + combined_data_length + combined_data + + with open(out_file, 'wb') as output_bin: + output_bin.write(final_data) + + # Generate header file + current_year = datetime.now().year + asset_name = os.path.basename(assets_path) + header_file_path = os.path.join(include_path, f'mmap_generate_{asset_name}.h') + with open(header_file_path, 'w') as output_header: + output_header.write('/*\n') + output_header.write(' * SPDX-FileCopyrightText: 2022-{} Espressif Systems (Shanghai) CO LTD\n'.format(current_year)) + output_header.write(' *\n') + output_header.write(' * SPDX-License-Identifier: Apache-2.0\n') + output_header.write(' */\n\n') + output_header.write('/**\n') + output_header.write(' * @file\n') + output_header.write(" * @brief This file was generated by esp_mmap_assets, don't modify it\n") + output_header.write(' */\n\n') + output_header.write('#pragma once\n\n') + output_header.write("#include \"esp_mmap_assets.h\"\n\n") + output_header.write(f'#define MMAP_{asset_name.upper()}_FILES {total_files}\n') + output_header.write(f'#define MMAP_{asset_name.upper()}_CHECKSUM 0x{combined_checksum:04X}\n\n') + output_header.write(f'enum MMAP_{asset_name.upper()}_LISTS {{\n') + + for i, (file_name, _, _, _, _) in enumerate(file_info_list): + enum_name = file_name.replace('.', '_') + output_header.write(f' MMAP_{asset_name.upper()}_{enum_name.upper()} = {i}, /*!< {file_name} */\n') + + output_header.write('};\n') + + print(f'All files have been merged into {os.path.basename(out_file)}') + + +# ============================================================================= +# Configuration and main functions +# ============================================================================= + +def read_wakenet_from_sdkconfig(sdkconfig_path): + """ + Read wakenet models from sdkconfig (based on movemodel.py logic) + Returns a list of wakenet model names + """ + if not os.path.exists(sdkconfig_path): + print(f"Warning: sdkconfig file not found: {sdkconfig_path}") + return [] + + models = [] + with io.open(sdkconfig_path, "r") as f: + for label in f: + label = label.strip("\n") + if 'CONFIG_SR_WN' in label and '#' not in label[0]: + if '_NONE' in label: + continue + if '=' in label: + label = label.split("=")[0] + if '_MULTI' in label: + label = label[:-6] + model_name = label.split("_SR_WN_")[-1].lower() + models.append(model_name) + + return models + + +def read_multinet_from_sdkconfig(sdkconfig_path): + """ + Read multinet models from sdkconfig (based on movemodel.py logic) + Returns a list of multinet model names + """ + if not os.path.exists(sdkconfig_path): + print(f"Warning: sdkconfig file not found: {sdkconfig_path}") + return [] + + with io.open(sdkconfig_path, "r") as f: + models_string = '' + for label in f: + label = label.strip("\n") + if 'CONFIG_SR_MN' in label and label[0] != '#': + models_string += label + + models = [] + if "CONFIG_SR_MN_CN_MULTINET3_SINGLE_RECOGNITION" in models_string: + models.append('mn3_cn') + elif "CONFIG_SR_MN_CN_MULTINET4_5_SINGLE_RECOGNITION_QUANT8" in models_string: + models.append('mn4q8_cn') + elif "CONFIG_SR_MN_CN_MULTINET4_5_SINGLE_RECOGNITION" in models_string: + models.append('mn4_cn') + elif "CONFIG_SR_MN_CN_MULTINET5_RECOGNITION_QUANT8" in models_string: + models.append('mn5q8_cn') + elif "CONFIG_SR_MN_CN_MULTINET6_QUANT" in models_string: + models.append('mn6_cn') + elif "CONFIG_SR_MN_CN_MULTINET6_AC_QUANT" in models_string: + models.append('mn6_cn_ac') + elif "CONFIG_SR_MN_CN_MULTINET7_QUANT" in models_string: + models.append('mn7_cn') + elif "CONFIG_SR_MN_CN_MULTINET7_AC_QUANT" in models_string: + models.append('mn7_cn_ac') + + if "CONFIG_SR_MN_EN_MULTINET5_SINGLE_RECOGNITION_QUANT8" in models_string: + models.append('mn5q8_en') + elif "CONFIG_SR_MN_EN_MULTINET5_SINGLE_RECOGNITION" in models_string: + models.append('mn5_en') + elif "CONFIG_SR_MN_EN_MULTINET6_QUANT" in models_string: + models.append('mn6_en') + elif "CONFIG_SR_MN_EN_MULTINET7_QUANT" in models_string: + models.append('mn7_en') + + if "MULTINET6" in models_string or "MULTINET7" in models_string: + models.append('fst') + + return models + + +def read_wake_word_type_from_sdkconfig(sdkconfig_path): + """ + Read wake word type configuration from sdkconfig + Returns a dict with wake word type info + """ + if not os.path.exists(sdkconfig_path): + print(f"Warning: sdkconfig file not found: {sdkconfig_path}") + return { + 'use_esp_wake_word': False, + 'use_afe_wake_word': False, + 'use_custom_wake_word': False, + 'wake_word_disabled': True + } + + config_values = { + 'use_esp_wake_word': False, + 'use_afe_wake_word': False, + 'use_custom_wake_word': False, + 'wake_word_disabled': False + } + + with io.open(sdkconfig_path, "r") as f: + for line in f: + line = line.strip("\n") + if line.startswith('#'): + continue + + # Check for wake word type configuration + if 'CONFIG_USE_ESP_WAKE_WORD=y' in line: + config_values['use_esp_wake_word'] = True + elif 'CONFIG_USE_AFE_WAKE_WORD=y' in line: + config_values['use_afe_wake_word'] = True + elif 'CONFIG_USE_CUSTOM_WAKE_WORD=y' in line: + config_values['use_custom_wake_word'] = True + elif 'CONFIG_WAKE_WORD_DISABLED=y' in line: + config_values['wake_word_disabled'] = True + + return config_values + + +def read_custom_wake_word_from_sdkconfig(sdkconfig_path): + """ + Read custom wake word configuration from sdkconfig + Returns a dict with custom wake word info or None if not configured + """ + if not os.path.exists(sdkconfig_path): + print(f"Warning: sdkconfig file not found: {sdkconfig_path}") + return None + + config_values = {} + with io.open(sdkconfig_path, "r") as f: + for line in f: + line = line.strip("\n") + if line.startswith('#') or '=' not in line: + continue + + # Check for custom wake word configuration + if 'CONFIG_USE_CUSTOM_WAKE_WORD=y' in line: + config_values['use_custom_wake_word'] = True + elif 'CONFIG_CUSTOM_WAKE_WORD=' in line and not line.startswith('#'): + # Extract string value (remove quotes) + value = line.split('=', 1)[1].strip('"') + config_values['wake_word'] = value + elif 'CONFIG_CUSTOM_WAKE_WORD_DISPLAY=' in line and not line.startswith('#'): + # Extract string value (remove quotes) + value = line.split('=', 1)[1].strip('"') + config_values['display'] = value + elif 'CONFIG_CUSTOM_WAKE_WORD_THRESHOLD=' in line and not line.startswith('#'): + # Extract numeric value + value = line.split('=', 1)[1] + try: + config_values['threshold'] = int(value) + except ValueError: + try: + config_values['threshold'] = float(value) + except ValueError: + print(f"Warning: Invalid threshold value: {value}") + config_values['threshold'] = 20 # default (will be converted to 0.2) + + # Return config only if custom wake word is enabled and required fields are present + if (config_values.get('use_custom_wake_word', False) and + 'wake_word' in config_values and + 'display' in config_values and + 'threshold' in config_values): + return { + 'wake_word': config_values['wake_word'], + 'display': config_values['display'], + 'threshold': config_values['threshold'] / 100.0 # Convert to decimal (20 -> 0.2) + } + + return None + + +def get_language_from_multinet_models(multinet_models): + """ + Determine language from multinet model names + Returns 'cn', 'en', or None + """ + if not multinet_models: + return None + + # Check for Chinese models + cn_indicators = ['_cn', 'cn_'] + en_indicators = ['_en', 'en_'] + + has_cn = any(any(indicator in model for indicator in cn_indicators) for model in multinet_models) + has_en = any(any(indicator in model for indicator in en_indicators) for model in multinet_models) + + # If both or neither, default to cn + if has_cn and not has_en: + return 'cn' + elif has_en and not has_cn: + return 'en' + else: + return 'cn' # Default to Chinese + + +def get_wakenet_model_paths(model_names, esp_sr_model_path): + """ + Get the full paths to the wakenet model directories + Returns a list of valid model paths + """ + if not model_names: + return [] + + valid_paths = [] + for model_name in model_names: + wakenet_model_path = os.path.join(esp_sr_model_path, 'wakenet_model', model_name) + if os.path.exists(wakenet_model_path): + valid_paths.append(wakenet_model_path) + else: + print(f"Warning: Wakenet model directory not found: {wakenet_model_path}") + + return valid_paths + + +def get_multinet_model_paths(model_names, esp_sr_model_path): + """ + Get the full paths to the multinet model directories + Returns a list of valid model paths + """ + if not model_names: + return [] + + valid_paths = [] + for model_name in model_names: + multinet_model_path = os.path.join(esp_sr_model_path, 'multinet_model', model_name) + if os.path.exists(multinet_model_path): + valid_paths.append(multinet_model_path) + else: + print(f"Warning: Multinet model directory not found: {multinet_model_path}") + + return valid_paths + + +def get_text_font_path(builtin_text_font, xiaozhi_fonts_path): + """ + Get the text font path if needed + Returns the font file path or None if no font is needed + """ + if not builtin_text_font or 'basic' not in builtin_text_font: + return None + + # Convert from basic to common font name + # e.g., font_puhui_basic_16_4 -> font_puhui_common_16_4.bin + font_name = builtin_text_font.replace('basic', 'common') + '.bin' + font_path = os.path.join(xiaozhi_fonts_path, 'cbin', font_name) + + if os.path.exists(font_path): + return font_path + else: + print(f"Warning: Font file not found: {font_path}") + return None + + +def get_emoji_collection_path(default_emoji_collection, xiaozhi_fonts_path): + """ + Get the emoji collection path if needed + Returns the emoji directory path or None if no emoji collection is needed + """ + if not default_emoji_collection: + return None + + emoji_path = os.path.join(xiaozhi_fonts_path, 'png', default_emoji_collection) + if os.path.exists(emoji_path): + return emoji_path + else: + print(f"Warning: Emoji collection directory not found: {emoji_path}") + return None + + +def build_assets_integrated(wakenet_model_paths, multinet_model_paths, text_font_path, emoji_collection_path, extra_files_path, output_path, multinet_model_info=None): + """ + Build assets using integrated functions (no external dependencies) + """ + # Create temporary build directory + temp_build_dir = os.path.join(os.path.dirname(output_path), "temp_build") + assets_dir = os.path.join(temp_build_dir, "assets") + + try: + # Clean and create directories + if os.path.exists(temp_build_dir): + shutil.rmtree(temp_build_dir) + ensure_dir(temp_build_dir) + ensure_dir(assets_dir) + + print("Starting to build assets...") + + # Process each component + srmodels = process_sr_models(wakenet_model_paths, multinet_model_paths, temp_build_dir, assets_dir) if (wakenet_model_paths or multinet_model_paths) else None + text_font = process_text_font(text_font_path, assets_dir) if text_font_path else None + emoji_collection = process_emoji_collection(emoji_collection_path, assets_dir) if emoji_collection_path else None + extra_files = process_extra_files(extra_files_path, assets_dir) if extra_files_path else None + + # Generate index.json + generate_index_json(assets_dir, srmodels, text_font, emoji_collection, extra_files, multinet_model_info) + + # Generate config.json for packing + config_path = generate_config_json(temp_build_dir, assets_dir) + + # Load config and pack assets + with open(config_path, 'r') as f: + config_data = json.load(f) + + # Use simplified packing function + include_path = config_data['include_path'] + image_file = config_data['image_file'] + pack_assets_simple(assets_dir, include_path, image_file, "assets", int(config_data['name_length'])) + + # Copy final assets.bin to output location + if os.path.exists(image_file): + shutil.copy2(image_file, output_path) + print(f"Successfully generated assets.bin: {output_path}") + + # Show size information + total_size = os.path.getsize(output_path) + print(f"Assets file size: {total_size / 1024:.2f}K ({total_size} bytes)") + + return True + else: + print(f"Error: Generated assets.bin not found: {image_file}") + return False + + except Exception as e: + print(f"Error: Failed to build assets: {e}") + return False + finally: + # Clean up temporary directory + if os.path.exists(temp_build_dir): + shutil.rmtree(temp_build_dir) + + +def main(): + parser = argparse.ArgumentParser(description='Build default assets based on configuration') + parser.add_argument('--sdkconfig', required=True, help='Path to sdkconfig file') + parser.add_argument('--builtin_text_font', help='Builtin text font name (e.g., font_puhui_basic_16_4)') + parser.add_argument('--emoji_collection', help='Default emoji collection name (e.g., emojis_32)') + parser.add_argument('--output', required=True, help='Output path for assets.bin') + parser.add_argument('--esp_sr_model_path', help='Path to ESP-SR model directory') + parser.add_argument('--xiaozhi_fonts_path', help='Path to xiaozhi-fonts component directory') + parser.add_argument('--extra_files', help='Path to extra files directory to be included in assets') + + args = parser.parse_args() + + # Set default paths if not provided + if not args.esp_sr_model_path or not args.xiaozhi_fonts_path: + # Calculate project root from script location + script_dir = os.path.dirname(os.path.abspath(__file__)) + project_root = os.path.dirname(script_dir) + + if not args.esp_sr_model_path: + args.esp_sr_model_path = os.path.join(project_root, "managed_components", "espressif__esp-sr", "model") + + if not args.xiaozhi_fonts_path: + args.xiaozhi_fonts_path = os.path.join(project_root, "components", "xiaozhi-fonts") + + print("Building default assets...") + print(f" sdkconfig: {args.sdkconfig}") + print(f" builtin_text_font: {args.builtin_text_font}") + print(f" emoji_collection: {args.emoji_collection}") + print(f" output: {args.output}") + + # Read wake word type configuration from sdkconfig + wake_word_config = read_wake_word_type_from_sdkconfig(args.sdkconfig) + + # Read SR models from sdkconfig + wakenet_model_names = read_wakenet_from_sdkconfig(args.sdkconfig) + multinet_model_names = read_multinet_from_sdkconfig(args.sdkconfig) + + # Apply wake word logic to decide which models to package + wakenet_model_paths = [] + multinet_model_paths = [] + + # 1. Only package wakenet models if USE_ESP_WAKE_WORD=y or USE_AFE_WAKE_WORD=y + if wake_word_config['use_esp_wake_word'] or wake_word_config['use_afe_wake_word']: + wakenet_model_paths = get_wakenet_model_paths(wakenet_model_names, args.esp_sr_model_path) + elif wakenet_model_names: + print(f" Note: Found wakenet models {wakenet_model_names} but wake word type is not ESP/AFE, skipping") + + # 2. Error check: if USE_CUSTOM_WAKE_WORD=y but no multinet models selected, report error + if wake_word_config['use_custom_wake_word'] and not multinet_model_names: + print("Error: USE_CUSTOM_WAKE_WORD is enabled but no multinet models are selected in sdkconfig") + print("Please select appropriate CONFIG_SR_MN_* options in menuconfig, or disable USE_CUSTOM_WAKE_WORD") + sys.exit(1) + + # 3. Only package multinet models if USE_CUSTOM_WAKE_WORD=y + if wake_word_config['use_custom_wake_word']: + multinet_model_paths = get_multinet_model_paths(multinet_model_names, args.esp_sr_model_path) + elif multinet_model_names: + print(f" Note: Found multinet models {multinet_model_names} but USE_CUSTOM_WAKE_WORD is disabled, skipping") + + # Print model information (only for models that will actually be packaged) + if wakenet_model_paths: + print(f" wakenet models: {', '.join(wakenet_model_names)} (will be packaged)") + if multinet_model_paths: + print(f" multinet models: {', '.join(multinet_model_names)} (will be packaged)") + + # Get text font path if needed + text_font_path = get_text_font_path(args.builtin_text_font, args.xiaozhi_fonts_path) + + # Get emoji collection path if needed + emoji_collection_path = get_emoji_collection_path(args.emoji_collection, args.xiaozhi_fonts_path) + + # Get extra files path if provided + extra_files_path = args.extra_files + + # Read custom wake word configuration + custom_wake_word_config = read_custom_wake_word_from_sdkconfig(args.sdkconfig) + multinet_model_info = None + + if custom_wake_word_config and multinet_model_paths: + # Determine language from multinet models + language = get_language_from_multinet_models(multinet_model_names) + + # Build multinet_model info structure + multinet_model_info = { + "language": language, + "duration": 3000, # Default duration in ms + "threshold": custom_wake_word_config['threshold'], + "commands": [ + { + "command": custom_wake_word_config['wake_word'], + "text": custom_wake_word_config['display'], + "action": "wake" + } + ] + } + print(f" custom wake word: {custom_wake_word_config['wake_word']} ({custom_wake_word_config['display']})") + print(f" wake word language: {language}") + print(f" wake word threshold: {custom_wake_word_config['threshold']}") + + # Check if we have anything to build + if not wakenet_model_paths and not multinet_model_paths and not text_font_path and not emoji_collection_path and not extra_files_path and not multinet_model_info: + print("Warning: No assets to build (no SR models, text font, emoji collection, extra files, or custom wake word)") + # Create an empty assets.bin file + os.makedirs(os.path.dirname(args.output), exist_ok=True) + with open(args.output, 'wb') as f: + pass # Create empty file + print(f"Created empty assets.bin: {args.output}") + return + + # Build the assets + success = build_assets_integrated(wakenet_model_paths, multinet_model_paths, text_font_path, emoji_collection_path, + extra_files_path, args.output, multinet_model_info) + + if not success: + sys.exit(1) + + print("Build completed successfully!") + + +if __name__ == "__main__": + main() diff --git a/scripts/flash.sh b/scripts/flash.sh deleted file mode 100755 index 444ed47..0000000 --- a/scripts/flash.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -esptool.py -p /dev/ttyACM0 -b 2000000 write_flash 0 ../releases/v0.9.9_bread-compact-wifi/merged-binary.bin diff --git a/scripts/gen_lang.py b/scripts/gen_lang.py index 81c0326..828bd14 100644 --- a/scripts/gen_lang.py +++ b/scripts/gen_lang.py @@ -1,187 +1,187 @@ -#!/usr/bin/env python3 -import argparse -import json -import os - -HEADER_TEMPLATE = """// Auto-generated language config -// Language: {lang_code} with en-US fallback -#pragma once - -#include - -#ifndef {lang_code_for_font} - #define {lang_code_for_font} // 預設語言 -#endif - -namespace Lang {{ - // 语言元数据 - constexpr const char* CODE = "{lang_code}"; - - // 字符串资源 (en-US as fallback for missing keys) - namespace Strings {{ -{strings} - }} - - // 音效资源 (en-US as fallback for missing audio files) - namespace Sounds {{ -{sounds} - }} -}} -""" - -def load_base_language(assets_dir): - """加载 en-US 基准语言数据""" - base_lang_path = os.path.join(assets_dir, 'locales', 'en-US', 'language.json') - if os.path.exists(base_lang_path): - try: - with open(base_lang_path, 'r', encoding='utf-8') as f: - base_data = json.load(f) - print(f"Loaded base language en-US with {len(base_data.get('strings', {}))} strings") - return base_data - except json.JSONDecodeError as e: - print(f"Warning: Failed to parse en-US language file: {e}") - else: - print("Warning: en-US base language file not found, fallback mechanism disabled") - return {'strings': {}} - -def get_sound_files(directory): - """获取目录中的音效文件列表""" - if not os.path.exists(directory): - return [] - return [f for f in os.listdir(directory) if f.endswith('.ogg')] - -def generate_header(lang_code, output_path): - # 从输出路径推导项目结构 - # output_path 通常是 main/assets/lang_config.h - main_dir = os.path.dirname(output_path) # main/assets - if os.path.basename(main_dir) == 'assets': - main_dir = os.path.dirname(main_dir) # main - project_dir = os.path.dirname(main_dir) # 项目根目录 - assets_dir = os.path.join(main_dir, 'assets') - - # 构建语言JSON文件路径 - input_path = os.path.join(assets_dir, 'locales', lang_code, 'language.json') - - print(f"Processing language: {lang_code}") - print(f"Input file path: {input_path}") - print(f"Output file path: {output_path}") - - if not os.path.exists(input_path): - raise FileNotFoundError(f"Language file not found: {input_path}") - - with open(input_path, 'r', encoding='utf-8') as f: - data = json.load(f) - - # 验证数据结构 - if 'language' not in data or 'strings' not in data: - raise ValueError("Invalid JSON structure") - - # 加载 en-US 基准语言数据 - base_data = load_base_language(assets_dir) - - # 合并字符串:以 en-US 为基准,用户语言覆盖 - base_strings = base_data.get('strings', {}) - user_strings = data['strings'] - merged_strings = base_strings.copy() - merged_strings.update(user_strings) - - # 统计信息 - base_count = len(base_strings) - user_count = len(user_strings) - total_count = len(merged_strings) - fallback_count = total_count - user_count - - print(f"Language {lang_code} string statistics:") - print(f" - Base language (en-US): {base_count} strings") - print(f" - User language: {user_count} strings") - print(f" - Total: {total_count} strings") - if fallback_count > 0: - print(f" - Fallback to en-US: {fallback_count} strings") - - # 生成字符串常量 - strings = [] - sounds = [] - for key, value in merged_strings.items(): - value = value.replace('"', '\\"') - strings.append(f' constexpr const char* {key.upper()} = "{value}";') - - # 收集音效文件:以 en-US 为基准,用户语言覆盖 - current_lang_dir = os.path.join(assets_dir, 'locales', lang_code) - base_lang_dir = os.path.join(assets_dir, 'locales', 'en-US') - common_dir = os.path.join(assets_dir, 'common') - - # 获取所有可能的音效文件 - base_sounds = get_sound_files(base_lang_dir) - current_sounds = get_sound_files(current_lang_dir) - common_sounds = get_sound_files(common_dir) - - # 合并音效文件列表:用户语言覆盖基准语言 - all_sound_files = set(base_sounds) - all_sound_files.update(current_sounds) - - # 音效统计信息 - base_sound_count = len(base_sounds) - user_sound_count = len(current_sounds) - common_sound_count = len(common_sounds) - sound_fallback_count = len(set(base_sounds) - set(current_sounds)) - - print(f"Language {lang_code} sound statistics:") - print(f" - Base language (en-US): {base_sound_count} sounds") - print(f" - User language: {user_sound_count} sounds") - print(f" - Common sounds: {common_sound_count} sounds") - if sound_fallback_count > 0: - print(f" - Sound fallback to en-US: {sound_fallback_count} sounds") - - # 生成语言特定音效常量 - for file in sorted(all_sound_files): - base_name = os.path.splitext(file)[0] - # 优先使用当前语言的音效,如果不存在则回退到 en-US - if file in current_sounds: - sound_lang = lang_code.replace('-', '_').lower() - else: - sound_lang = 'en_us' - - sounds.append(f''' - extern const char ogg_{base_name}_start[] asm("_binary_{base_name}_ogg_start"); - extern const char ogg_{base_name}_end[] asm("_binary_{base_name}_ogg_end"); - static const std::string_view OGG_{base_name.upper()} {{ - static_cast(ogg_{base_name}_start), - static_cast(ogg_{base_name}_end - ogg_{base_name}_start) - }};''') - - # 生成公共音效常量 - for file in sorted(common_sounds): - base_name = os.path.splitext(file)[0] - sounds.append(f''' - extern const char ogg_{base_name}_start[] asm("_binary_{base_name}_ogg_start"); - extern const char ogg_{base_name}_end[] asm("_binary_{base_name}_ogg_end"); - static const std::string_view OGG_{base_name.upper()} {{ - static_cast(ogg_{base_name}_start), - static_cast(ogg_{base_name}_end - ogg_{base_name}_start) - }};''') - - # 填充模板 - content = HEADER_TEMPLATE.format( - lang_code=lang_code, - lang_code_for_font=lang_code.replace('-', '_').lower(), - strings="\n".join(sorted(strings)), - sounds="\n".join(sorted(sounds)) - ) - - # 写入文件 - os.makedirs(os.path.dirname(output_path), exist_ok=True) - with open(output_path, 'w', encoding='utf-8') as f: - f.write(content) - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Generate language configuration header file with en-US fallback") - parser.add_argument("--language", required=True, help="Language code (e.g: zh-CN, en-US, ja-JP)") - parser.add_argument("--output", required=True, help="Output header file path") - args = parser.parse_args() - - try: - generate_header(args.language, args.output) - print(f"Successfully generated language config file: {args.output}") - except Exception as e: - print(f"Error: {e}") +#!/usr/bin/env python3 +import argparse +import json +import os + +HEADER_TEMPLATE = """// Auto-generated language config +// Language: {lang_code} with en-US fallback +#pragma once + +#include + +#ifndef {lang_code_for_font} + #define {lang_code_for_font} // 預設語言 +#endif + +namespace Lang {{ + // 语言元数据 + constexpr const char* CODE = "{lang_code}"; + + // 字符串资源 (en-US as fallback for missing keys) + namespace Strings {{ +{strings} + }} + + // 音效资源 (en-US as fallback for missing audio files) + namespace Sounds {{ +{sounds} + }} +}} +""" + +def load_base_language(assets_dir): + """加载 en-US 基准语言数据""" + base_lang_path = os.path.join(assets_dir, 'locales', 'en-US', 'language.json') + if os.path.exists(base_lang_path): + try: + with open(base_lang_path, 'r', encoding='utf-8') as f: + base_data = json.load(f) + print(f"Loaded base language en-US with {len(base_data.get('strings', {}))} strings") + return base_data + except json.JSONDecodeError as e: + print(f"Warning: Failed to parse en-US language file: {e}") + else: + print("Warning: en-US base language file not found, fallback mechanism disabled") + return {'strings': {}} + +def get_sound_files(directory): + """获取目录中的音效文件列表""" + if not os.path.exists(directory): + return [] + return [f for f in os.listdir(directory) if f.endswith('.ogg')] + +def generate_header(lang_code, output_path): + # 从输出路径推导项目结构 + # output_path 通常是 main/assets/lang_config.h + main_dir = os.path.dirname(output_path) # main/assets + if os.path.basename(main_dir) == 'assets': + main_dir = os.path.dirname(main_dir) # main + project_dir = os.path.dirname(main_dir) # 项目根目录 + assets_dir = os.path.join(main_dir, 'assets') + + # 构建语言JSON文件路径 + input_path = os.path.join(assets_dir, 'locales', lang_code, 'language.json') + + print(f"Processing language: {lang_code}") + print(f"Input file path: {input_path}") + print(f"Output file path: {output_path}") + + if not os.path.exists(input_path): + raise FileNotFoundError(f"Language file not found: {input_path}") + + with open(input_path, 'r', encoding='utf-8') as f: + data = json.load(f) + + # 验证数据结构 + if 'language' not in data or 'strings' not in data: + raise ValueError("Invalid JSON structure") + + # 加载 en-US 基准语言数据 + base_data = load_base_language(assets_dir) + + # 合并字符串:以 en-US 为基准,用户语言覆盖 + base_strings = base_data.get('strings', {}) + user_strings = data['strings'] + merged_strings = base_strings.copy() + merged_strings.update(user_strings) + + # 统计信息 + base_count = len(base_strings) + user_count = len(user_strings) + total_count = len(merged_strings) + fallback_count = total_count - user_count + + print(f"Language {lang_code} string statistics:") + print(f" - Base language (en-US): {base_count} strings") + print(f" - User language: {user_count} strings") + print(f" - Total: {total_count} strings") + if fallback_count > 0: + print(f" - Fallback to en-US: {fallback_count} strings") + + # 生成字符串常量 + strings = [] + sounds = [] + for key, value in merged_strings.items(): + value = value.replace('"', '\\"') + strings.append(f' constexpr const char* {key.upper()} = "{value}";') + + # 收集音效文件:以 en-US 为基准,用户语言覆盖 + current_lang_dir = os.path.join(assets_dir, 'locales', lang_code) + base_lang_dir = os.path.join(assets_dir, 'locales', 'en-US') + common_dir = os.path.join(assets_dir, 'common') + + # 获取所有可能的音效文件 + base_sounds = get_sound_files(base_lang_dir) + current_sounds = get_sound_files(current_lang_dir) + common_sounds = get_sound_files(common_dir) + + # 合并音效文件列表:用户语言覆盖基准语言 + all_sound_files = set(base_sounds) + all_sound_files.update(current_sounds) + + # 音效统计信息 + base_sound_count = len(base_sounds) + user_sound_count = len(current_sounds) + common_sound_count = len(common_sounds) + sound_fallback_count = len(set(base_sounds) - set(current_sounds)) + + print(f"Language {lang_code} sound statistics:") + print(f" - Base language (en-US): {base_sound_count} sounds") + print(f" - User language: {user_sound_count} sounds") + print(f" - Common sounds: {common_sound_count} sounds") + if sound_fallback_count > 0: + print(f" - Sound fallback to en-US: {sound_fallback_count} sounds") + + # 生成语言特定音效常量 + for file in sorted(all_sound_files): + base_name = os.path.splitext(file)[0] + # 优先使用当前语言的音效,如果不存在则回退到 en-US + if file in current_sounds: + sound_lang = lang_code.replace('-', '_').lower() + else: + sound_lang = 'en_us' + + sounds.append(f''' + extern const char ogg_{base_name}_start[] asm("_binary_{base_name}_ogg_start"); + extern const char ogg_{base_name}_end[] asm("_binary_{base_name}_ogg_end"); + static const std::string_view OGG_{base_name.upper()} {{ + static_cast(ogg_{base_name}_start), + static_cast(ogg_{base_name}_end - ogg_{base_name}_start) + }};''') + + # 生成公共音效常量 + for file in sorted(common_sounds): + base_name = os.path.splitext(file)[0] + sounds.append(f''' + extern const char ogg_{base_name}_start[] asm("_binary_{base_name}_ogg_start"); + extern const char ogg_{base_name}_end[] asm("_binary_{base_name}_ogg_end"); + static const std::string_view OGG_{base_name.upper()} {{ + static_cast(ogg_{base_name}_start), + static_cast(ogg_{base_name}_end - ogg_{base_name}_start) + }};''') + + # 填充模板 + content = HEADER_TEMPLATE.format( + lang_code=lang_code, + lang_code_for_font=lang_code.replace('-', '_').lower(), + strings="\n".join(sorted(strings)), + sounds="\n".join(sorted(sounds)) + ) + + # 写入文件 + os.makedirs(os.path.dirname(output_path), exist_ok=True) + with open(output_path, 'w', encoding='utf-8') as f: + f.write(content) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate language configuration header file with en-US fallback") + parser.add_argument("--language", required=True, help="Language code (e.g: zh-CN, en-US, ja-JP)") + parser.add_argument("--output", required=True, help="Output header file path") + args = parser.parse_args() + + try: + generate_header(args.language, args.output) + print(f"Successfully generated language config file: {args.output}") + except Exception as e: + print(f"Error: {e}") exit(1) \ No newline at end of file diff --git a/scripts/mp3_to_ogg.sh b/scripts/mp3_to_ogg.sh index 2cb870a..a2aec32 100644 --- a/scripts/mp3_to_ogg.sh +++ b/scripts/mp3_to_ogg.sh @@ -1,3 +1,3 @@ -#!/bin/sh -# mp3_to_ogg.sh -ffmpeg -i $1 -c:a libopus -b:a 16k -ac 1 -ar 16000 -frame_duration 60 $2 +#!/bin/sh +# mp3_to_ogg.sh +ffmpeg -i $1 -c:a libopus -b:a 16k -ac 1 -ar 16000 -frame_duration 60 $2 diff --git a/scripts/ogg_converter/README.md b/scripts/ogg_converter/README.md index 2272d8d..c22926d 100644 --- a/scripts/ogg_converter/README.md +++ b/scripts/ogg_converter/README.md @@ -1,29 +1,29 @@ -# ogg_covertor 小智AI OGG 批量转换器 - -本脚本为OGG批量转换工具,支持将输入的音频文件转换为小智可使用的OGG格式 -基于Python第三方库`ffmpeg-python`实现 -支持OGG和音频之间的互转,响度调节等功能 - -# 创建并激活虚拟环境 - -```bash -# 创建虚拟环境 -python -m venv venv -# 激活虚拟环境 -source venv/bin/activate # Mac/Linux -venv\Scripts\activate # Windows -``` - -# 安装依赖 - -请在虚拟环境中执行 - -```bash -pip install ffmpeg-python -``` - -# 运行脚本 -```bash -python ogg_covertor.py -``` - +# ogg_covertor 小智AI OGG 批量转换器 + +本脚本为OGG批量转换工具,支持将输入的音频文件转换为小智可使用的OGG格式 +基于Python第三方库`ffmpeg-python`实现 +支持OGG和音频之间的互转,响度调节等功能 + +# 创建并激活虚拟环境 + +```bash +# 创建虚拟环境 +python -m venv venv +# 激活虚拟环境 +source venv/bin/activate # Mac/Linux +venv\Scripts\activate # Windows +``` + +# 安装依赖 + +请在虚拟环境中执行 + +```bash +pip install ffmpeg-python +``` + +# 运行脚本 +```bash +python ogg_covertor.py +``` + diff --git a/scripts/ogg_converter/xiaozhi_ogg_converter.py b/scripts/ogg_converter/xiaozhi_ogg_converter.py index 5c3ddb2..eb75e0a 100644 --- a/scripts/ogg_converter/xiaozhi_ogg_converter.py +++ b/scripts/ogg_converter/xiaozhi_ogg_converter.py @@ -1,230 +1,230 @@ -import tkinter as tk -from tkinter import ttk, filedialog, messagebox -import os -import threading -import sys -import ffmpeg - -class AudioConverterApp: - def __init__(self, master): - self.master = master - master.title("小智AI OGG音频批量转换工具") - master.geometry("680x600") # 调整窗口高度 - - # 初始化变量 - self.mode = tk.StringVar(value="audio_to_ogg") - self.output_dir = tk.StringVar() - self.output_dir.set(os.path.abspath("output")) - self.enable_loudnorm = tk.BooleanVar(value=True) - self.target_lufs = tk.DoubleVar(value=-16.0) - - # 创建UI组件 - self.create_widgets() - self.redirect_output() - - def create_widgets(self): - # 模式选择 - mode_frame = ttk.LabelFrame(self.master, text="转换模式") - mode_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew") - - ttk.Radiobutton(mode_frame, text="音频转到OGG", variable=self.mode, - value="audio_to_ogg", command=self.toggle_settings, - width=12).grid(row=0, column=0, padx=5) - ttk.Radiobutton(mode_frame, text="OGG转回音频", variable=self.mode, - value="ogg_to_audio", command=self.toggle_settings, - width=12).grid(row=0, column=1, padx=5) - - # 响度设置 - self.loudnorm_frame = ttk.Frame(self.master) - self.loudnorm_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew") - - ttk.Checkbutton(self.loudnorm_frame, text="启用响度调整", - variable=self.enable_loudnorm, width=15 - ).grid(row=0, column=0, padx=2) - ttk.Entry(self.loudnorm_frame, textvariable=self.target_lufs, - width=6).grid(row=0, column=1, padx=2) - ttk.Label(self.loudnorm_frame, text="LUFS").grid(row=0, column=2, padx=2) - - # 文件选择 - file_frame = ttk.LabelFrame(self.master, text="输入文件") - file_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew") - - # 文件操作按钮 - ttk.Button(file_frame, text="选择文件", command=self.select_files, - width=12).grid(row=0, column=0, padx=5, pady=2) - ttk.Button(file_frame, text="移除选中", command=self.remove_selected, - width=12).grid(row=0, column=1, padx=5, pady=2) - ttk.Button(file_frame, text="清空列表", command=self.clear_files, - width=12).grid(row=0, column=2, padx=5, pady=2) - - # 文件列表(使用Treeview) - self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"), - show="headings", height=8) - self.tree.heading("selected", text="选中", anchor=tk.W) - self.tree.heading("filename", text="文件名", anchor=tk.W) - self.tree.column("selected", width=60, anchor=tk.W) - self.tree.column("filename", width=600, anchor=tk.W) - self.tree.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=5, pady=2) - self.tree.bind("", self.on_tree_click) - - # 输出目录 - output_frame = ttk.LabelFrame(self.master, text="输出目录") - output_frame.grid(row=3, column=0, padx=10, pady=5, sticky="ew") - - ttk.Entry(output_frame, textvariable=self.output_dir, width=60 - ).grid(row=0, column=0, padx=5, sticky="ew") - ttk.Button(output_frame, text="浏览", command=self.select_output_dir, - width=8).grid(row=0, column=1, padx=5) - - # 转换按钮区域 - button_frame = ttk.Frame(self.master) - button_frame.grid(row=4, column=0, padx=10, pady=10, sticky="ew") - - ttk.Button(button_frame, text="转换全部文件", command=lambda: self.start_conversion(True), - width=15).pack(side=tk.LEFT, padx=5) - ttk.Button(button_frame, text="转换选中文件", command=lambda: self.start_conversion(False), - width=15).pack(side=tk.LEFT, padx=5) - - # 日志区域 - log_frame = ttk.LabelFrame(self.master, text="日志") - log_frame.grid(row=5, column=0, padx=10, pady=5, sticky="nsew") - - self.log_text = tk.Text(log_frame, height=14, width=80) - self.log_text.pack(fill=tk.BOTH, expand=True) - - # 配置布局权重 - self.master.columnconfigure(0, weight=1) - self.master.rowconfigure(2, weight=1) - self.master.rowconfigure(5, weight=3) - file_frame.columnconfigure(0, weight=1) - file_frame.rowconfigure(1, weight=1) - - def toggle_settings(self): - if self.mode.get() == "audio_to_ogg": - self.loudnorm_frame.grid() - else: - self.loudnorm_frame.grid_remove() - - def select_files(self): - file_types = [ - ("音频文件", "*.wav *.mogg *.ogg *.flac") if self.mode.get() == "audio_to_ogg" - else ("ogg文件", "*.ogg") - ] - - files = filedialog.askopenfilenames(filetypes=file_types) - for f in files: - self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,)) - - def on_tree_click(self, event): - """处理复选框点击事件""" - region = self.tree.identify("region", event.x, event.y) - if region == "cell": - col = self.tree.identify_column(event.x) - item = self.tree.identify_row(event.y) - if col == "#1": # 点击的是选中列 - current_val = self.tree.item(item, "values")[0] - new_val = "[√]" if current_val == "[ ]" else "[ ]" - self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1])) - - def remove_selected(self): - """移除选中的文件""" - to_remove = [] - for item in self.tree.get_children(): - if self.tree.item(item, "values")[0] == "[√]": - to_remove.append(item) - for item in reversed(to_remove): - self.tree.delete(item) - - def clear_files(self): - """清空所有文件""" - for item in self.tree.get_children(): - self.tree.delete(item) - - def select_output_dir(self): - path = filedialog.askdirectory() - if path: - self.output_dir.set(path) - - def redirect_output(self): - class StdoutRedirector: - def __init__(self, text_widget): - self.text_widget = text_widget - self.original_stdout = sys.stdout - - def write(self, message): - self.text_widget.insert(tk.END, message) - self.text_widget.see(tk.END) - self.original_stdout.write(message) - - def flush(self): - self.original_stdout.flush() - - sys.stdout = StdoutRedirector(self.log_text) - - def start_conversion(self, convert_all): - """开始转换""" - input_files = [] - for item in self.tree.get_children(): - if convert_all or self.tree.item(item, "values")[0] == "[√]": - input_files.append(self.tree.item(item, "tags")[0]) - - if not input_files: - msg = "没有找到可转换的文件" if convert_all else "没有选中任何文件" - messagebox.showwarning("警告", msg) - return - - os.makedirs(self.output_dir.get(), exist_ok=True) - - try: - if self.mode.get() == "audio_to_ogg": - target_lufs = self.target_lufs.get() if self.enable_loudnorm.get() else None - thread = threading.Thread(target=self.convert_audio_to_ogg, args=(target_lufs, input_files)) - else: - thread = threading.Thread(target=self.convert_ogg_to_audio, args=(input_files,)) - - thread.start() - except Exception as e: - print(f"转换初始化失败: {str(e)}") - - def convert_audio_to_ogg(self, target_lufs, input_files): - """音频转到ogg转换逻辑""" - for input_path in input_files: - try: - filename = os.path.basename(input_path) - base_name = os.path.splitext(filename)[0] - output_path = os.path.join(self.output_dir.get(), f"{base_name}.ogg") - - print(f"正在转换: {filename}") - ( - ffmpeg - .input(input_path) - .output(output_path, acodec='libopus', audio_bitrate='16k', ac=1, ar=16000, frame_duration=60) - .run(overwrite_output=True) - ) - print(f"转换成功: {filename}\n") - except Exception as e: - print(f"转换失败: {str(e)}\n") - - def convert_ogg_to_audio(self, input_files): - """ogg转回音频转换逻辑""" - for input_path in input_files: - try: - filename = os.path.basename(input_path) - base_name = os.path.splitext(filename)[0] - output_path = os.path.join(self.output_dir.get(), f"{base_name}.ogg") - - print(f"正在转换: {filename}") - ( - ffmpeg - .input(input_path) - .output(output_path, acodec='libopus', audio_bitrate='16k', ac=1, ar=16000, frame_duration=60) - .run(overwrite_output=True) - ) - print(f"转换成功: {filename}\n") - except Exception as e: - print(f"转换失败: {str(e)}\n") - -if __name__ == "__main__": - root = tk.Tk() - app = AudioConverterApp(root) - root.mainloop() +import tkinter as tk +from tkinter import ttk, filedialog, messagebox +import os +import threading +import sys +import ffmpeg + +class AudioConverterApp: + def __init__(self, master): + self.master = master + master.title("小智AI OGG音频批量转换工具") + master.geometry("680x600") # 调整窗口高度 + + # 初始化变量 + self.mode = tk.StringVar(value="audio_to_ogg") + self.output_dir = tk.StringVar() + self.output_dir.set(os.path.abspath("output")) + self.enable_loudnorm = tk.BooleanVar(value=True) + self.target_lufs = tk.DoubleVar(value=-16.0) + + # 创建UI组件 + self.create_widgets() + self.redirect_output() + + def create_widgets(self): + # 模式选择 + mode_frame = ttk.LabelFrame(self.master, text="转换模式") + mode_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew") + + ttk.Radiobutton(mode_frame, text="音频转到OGG", variable=self.mode, + value="audio_to_ogg", command=self.toggle_settings, + width=12).grid(row=0, column=0, padx=5) + ttk.Radiobutton(mode_frame, text="OGG转回音频", variable=self.mode, + value="ogg_to_audio", command=self.toggle_settings, + width=12).grid(row=0, column=1, padx=5) + + # 响度设置 + self.loudnorm_frame = ttk.Frame(self.master) + self.loudnorm_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew") + + ttk.Checkbutton(self.loudnorm_frame, text="启用响度调整", + variable=self.enable_loudnorm, width=15 + ).grid(row=0, column=0, padx=2) + ttk.Entry(self.loudnorm_frame, textvariable=self.target_lufs, + width=6).grid(row=0, column=1, padx=2) + ttk.Label(self.loudnorm_frame, text="LUFS").grid(row=0, column=2, padx=2) + + # 文件选择 + file_frame = ttk.LabelFrame(self.master, text="输入文件") + file_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew") + + # 文件操作按钮 + ttk.Button(file_frame, text="选择文件", command=self.select_files, + width=12).grid(row=0, column=0, padx=5, pady=2) + ttk.Button(file_frame, text="移除选中", command=self.remove_selected, + width=12).grid(row=0, column=1, padx=5, pady=2) + ttk.Button(file_frame, text="清空列表", command=self.clear_files, + width=12).grid(row=0, column=2, padx=5, pady=2) + + # 文件列表(使用Treeview) + self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"), + show="headings", height=8) + self.tree.heading("selected", text="选中", anchor=tk.W) + self.tree.heading("filename", text="文件名", anchor=tk.W) + self.tree.column("selected", width=60, anchor=tk.W) + self.tree.column("filename", width=600, anchor=tk.W) + self.tree.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=5, pady=2) + self.tree.bind("", self.on_tree_click) + + # 输出目录 + output_frame = ttk.LabelFrame(self.master, text="输出目录") + output_frame.grid(row=3, column=0, padx=10, pady=5, sticky="ew") + + ttk.Entry(output_frame, textvariable=self.output_dir, width=60 + ).grid(row=0, column=0, padx=5, sticky="ew") + ttk.Button(output_frame, text="浏览", command=self.select_output_dir, + width=8).grid(row=0, column=1, padx=5) + + # 转换按钮区域 + button_frame = ttk.Frame(self.master) + button_frame.grid(row=4, column=0, padx=10, pady=10, sticky="ew") + + ttk.Button(button_frame, text="转换全部文件", command=lambda: self.start_conversion(True), + width=15).pack(side=tk.LEFT, padx=5) + ttk.Button(button_frame, text="转换选中文件", command=lambda: self.start_conversion(False), + width=15).pack(side=tk.LEFT, padx=5) + + # 日志区域 + log_frame = ttk.LabelFrame(self.master, text="日志") + log_frame.grid(row=5, column=0, padx=10, pady=5, sticky="nsew") + + self.log_text = tk.Text(log_frame, height=14, width=80) + self.log_text.pack(fill=tk.BOTH, expand=True) + + # 配置布局权重 + self.master.columnconfigure(0, weight=1) + self.master.rowconfigure(2, weight=1) + self.master.rowconfigure(5, weight=3) + file_frame.columnconfigure(0, weight=1) + file_frame.rowconfigure(1, weight=1) + + def toggle_settings(self): + if self.mode.get() == "audio_to_ogg": + self.loudnorm_frame.grid() + else: + self.loudnorm_frame.grid_remove() + + def select_files(self): + file_types = [ + ("音频文件", "*.wav *.mogg *.ogg *.flac") if self.mode.get() == "audio_to_ogg" + else ("ogg文件", "*.ogg") + ] + + files = filedialog.askopenfilenames(filetypes=file_types) + for f in files: + self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,)) + + def on_tree_click(self, event): + """处理复选框点击事件""" + region = self.tree.identify("region", event.x, event.y) + if region == "cell": + col = self.tree.identify_column(event.x) + item = self.tree.identify_row(event.y) + if col == "#1": # 点击的是选中列 + current_val = self.tree.item(item, "values")[0] + new_val = "[√]" if current_val == "[ ]" else "[ ]" + self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1])) + + def remove_selected(self): + """移除选中的文件""" + to_remove = [] + for item in self.tree.get_children(): + if self.tree.item(item, "values")[0] == "[√]": + to_remove.append(item) + for item in reversed(to_remove): + self.tree.delete(item) + + def clear_files(self): + """清空所有文件""" + for item in self.tree.get_children(): + self.tree.delete(item) + + def select_output_dir(self): + path = filedialog.askdirectory() + if path: + self.output_dir.set(path) + + def redirect_output(self): + class StdoutRedirector: + def __init__(self, text_widget): + self.text_widget = text_widget + self.original_stdout = sys.stdout + + def write(self, message): + self.text_widget.insert(tk.END, message) + self.text_widget.see(tk.END) + self.original_stdout.write(message) + + def flush(self): + self.original_stdout.flush() + + sys.stdout = StdoutRedirector(self.log_text) + + def start_conversion(self, convert_all): + """开始转换""" + input_files = [] + for item in self.tree.get_children(): + if convert_all or self.tree.item(item, "values")[0] == "[√]": + input_files.append(self.tree.item(item, "tags")[0]) + + if not input_files: + msg = "没有找到可转换的文件" if convert_all else "没有选中任何文件" + messagebox.showwarning("警告", msg) + return + + os.makedirs(self.output_dir.get(), exist_ok=True) + + try: + if self.mode.get() == "audio_to_ogg": + target_lufs = self.target_lufs.get() if self.enable_loudnorm.get() else None + thread = threading.Thread(target=self.convert_audio_to_ogg, args=(target_lufs, input_files)) + else: + thread = threading.Thread(target=self.convert_ogg_to_audio, args=(input_files,)) + + thread.start() + except Exception as e: + print(f"转换初始化失败: {str(e)}") + + def convert_audio_to_ogg(self, target_lufs, input_files): + """音频转到ogg转换逻辑""" + for input_path in input_files: + try: + filename = os.path.basename(input_path) + base_name = os.path.splitext(filename)[0] + output_path = os.path.join(self.output_dir.get(), f"{base_name}.ogg") + + print(f"正在转换: {filename}") + ( + ffmpeg + .input(input_path) + .output(output_path, acodec='libopus', audio_bitrate='16k', ac=1, ar=16000, frame_duration=60) + .run(overwrite_output=True) + ) + print(f"转换成功: {filename}\n") + except Exception as e: + print(f"转换失败: {str(e)}\n") + + def convert_ogg_to_audio(self, input_files): + """ogg转回音频转换逻辑""" + for input_path in input_files: + try: + filename = os.path.basename(input_path) + base_name = os.path.splitext(filename)[0] + output_path = os.path.join(self.output_dir.get(), f"{base_name}.ogg") + + print(f"正在转换: {filename}") + ( + ffmpeg + .input(input_path) + .output(output_path, acodec='libopus', audio_bitrate='16k', ac=1, ar=16000, frame_duration=60) + .run(overwrite_output=True) + ) + print(f"转换成功: {filename}\n") + except Exception as e: + print(f"转换失败: {str(e)}\n") + +if __name__ == "__main__": + root = tk.Tk() + app = AudioConverterApp(root) + root.mainloop() diff --git a/scripts/p3_tools/README.md b/scripts/p3_tools/README.md index 0ee279c..130c9d7 100644 --- a/scripts/p3_tools/README.md +++ b/scripts/p3_tools/README.md @@ -1,95 +1,95 @@ -# P3音频格式转换与播放工具 - -这个目录包含两个用于处理P3格式音频文件的Python脚本: - -## 1. 音频转换工具 (convert_audio_to_p3.py) - -将普通音频文件转换为P3格式(4字节header + Opus数据包的流式结构)并进行响度标准化。 - -### 使用方法 - -```bash -python convert_audio_to_p3.py <输入音频文件> <输出P3文件> [-l LUFS] [-d] -``` - -其中,可选选项 `-l` 用于指定响度标准化的目标响度,默认为 -16 LUFS;可选选项 `-d` 可以禁用响度标准化。 - -如果输入的音频文件符合下面的任一条件,建议使用 `-d` 禁用响度标准化: -- 音频过短 -- 音频已经调整过响度 -- 音频来自默认 TTS (小智当前使用的 TTS 的默认响度已是 -16 LUFS) - -例如: -```bash -python convert_audio_to_p3.py input.mp3 output.p3 -``` - -## 2. P3音频播放工具 (play_p3.py) - -播放P3格式的音频文件。 - -### 特性 - -- 解码并播放P3格式的音频文件 -- 在播放结束或用户中断时应用淡出效果,避免破音 -- 支持通过命令行参数指定要播放的文件 - -### 使用方法 - -```bash -python play_p3.py -``` - -例如: -```bash -python play_p3.py output.p3 -``` - -## 3. 音频转回工具 (convert_p3_to_audio.py) - -将P3格式转换回普通音频文件。 - -### 使用方法 - -```bash -python convert_p3_to_audio.py <输入P3文件> <输出音频文件> -``` - -输出音频文件需要有扩展名。 - -例如: -```bash -python convert_p3_to_audio.py input.p3 output.wav -``` -## 4. 音频/P3批量转换工具 - -一个图形化的工具,支持批量转换音频到P3,P3到音频 - -![](./img/img.png) - -### 使用方法: -```bash -python batch_convert_gui.py -``` - -## 依赖安装 - -在使用这些脚本前,请确保安装了所需的Python库: - -```bash -pip install librosa opuslib numpy tqdm sounddevice pyloudnorm soundfile -``` - -或者使用提供的requirements.txt文件: - -```bash -pip install -r requirements.txt -``` - -## P3格式说明 - -P3格式是一种简单的流式音频格式,结构如下: -- 每个音频帧由一个4字节的头部和一个Opus编码的数据包组成 -- 头部格式:[1字节类型, 1字节保留, 2字节长度] -- 采样率固定为16000Hz,单声道 +# P3音频格式转换与播放工具 + +这个目录包含两个用于处理P3格式音频文件的Python脚本: + +## 1. 音频转换工具 (convert_audio_to_p3.py) + +将普通音频文件转换为P3格式(4字节header + Opus数据包的流式结构)并进行响度标准化。 + +### 使用方法 + +```bash +python convert_audio_to_p3.py <输入音频文件> <输出P3文件> [-l LUFS] [-d] +``` + +其中,可选选项 `-l` 用于指定响度标准化的目标响度,默认为 -16 LUFS;可选选项 `-d` 可以禁用响度标准化。 + +如果输入的音频文件符合下面的任一条件,建议使用 `-d` 禁用响度标准化: +- 音频过短 +- 音频已经调整过响度 +- 音频来自默认 TTS (小智当前使用的 TTS 的默认响度已是 -16 LUFS) + +例如: +```bash +python convert_audio_to_p3.py input.mp3 output.p3 +``` + +## 2. P3音频播放工具 (play_p3.py) + +播放P3格式的音频文件。 + +### 特性 + +- 解码并播放P3格式的音频文件 +- 在播放结束或用户中断时应用淡出效果,避免破音 +- 支持通过命令行参数指定要播放的文件 + +### 使用方法 + +```bash +python play_p3.py +``` + +例如: +```bash +python play_p3.py output.p3 +``` + +## 3. 音频转回工具 (convert_p3_to_audio.py) + +将P3格式转换回普通音频文件。 + +### 使用方法 + +```bash +python convert_p3_to_audio.py <输入P3文件> <输出音频文件> +``` + +输出音频文件需要有扩展名。 + +例如: +```bash +python convert_p3_to_audio.py input.p3 output.wav +``` +## 4. 音频/P3批量转换工具 + +一个图形化的工具,支持批量转换音频到P3,P3到音频 + +![](./img/img.png) + +### 使用方法: +```bash +python batch_convert_gui.py +``` + +## 依赖安装 + +在使用这些脚本前,请确保安装了所需的Python库: + +```bash +pip install librosa opuslib numpy tqdm sounddevice pyloudnorm soundfile +``` + +或者使用提供的requirements.txt文件: + +```bash +pip install -r requirements.txt +``` + +## P3格式说明 + +P3格式是一种简单的流式音频格式,结构如下: +- 每个音频帧由一个4字节的头部和一个Opus编码的数据包组成 +- 头部格式:[1字节类型, 1字节保留, 2字节长度] +- 采样率固定为16000Hz,单声道 - 每帧时长为60ms \ No newline at end of file diff --git a/scripts/p3_tools/batch_convert_gui.py b/scripts/p3_tools/batch_convert_gui.py index 8555e55..c5ba959 100644 --- a/scripts/p3_tools/batch_convert_gui.py +++ b/scripts/p3_tools/batch_convert_gui.py @@ -1,221 +1,221 @@ -import tkinter as tk -from tkinter import ttk, filedialog, messagebox -import os -import threading -import sys -from convert_audio_to_p3 import encode_audio_to_opus -from convert_p3_to_audio import decode_p3_to_audio - -class AudioConverterApp: - def __init__(self, master): - self.master = master - master.title("音频/P3 批量转换工具") - master.geometry("680x600") # 调整窗口高度 - - # 初始化变量 - self.mode = tk.StringVar(value="audio_to_p3") - self.output_dir = tk.StringVar() - self.output_dir.set(os.path.abspath("output")) - self.enable_loudnorm = tk.BooleanVar(value=True) - self.target_lufs = tk.DoubleVar(value=-16.0) - - # 创建UI组件 - self.create_widgets() - self.redirect_output() - - def create_widgets(self): - # 模式选择 - mode_frame = ttk.LabelFrame(self.master, text="转换模式") - mode_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew") - - ttk.Radiobutton(mode_frame, text="音频转P3", variable=self.mode, - value="audio_to_p3", command=self.toggle_settings, - width=12).grid(row=0, column=0, padx=5) - ttk.Radiobutton(mode_frame, text="P3转音频", variable=self.mode, - value="p3_to_audio", command=self.toggle_settings, - width=12).grid(row=0, column=1, padx=5) - - # 响度设置 - self.loudnorm_frame = ttk.Frame(self.master) - self.loudnorm_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew") - - ttk.Checkbutton(self.loudnorm_frame, text="启用响度调整", - variable=self.enable_loudnorm, width=15 - ).grid(row=0, column=0, padx=2) - ttk.Entry(self.loudnorm_frame, textvariable=self.target_lufs, - width=6).grid(row=0, column=1, padx=2) - ttk.Label(self.loudnorm_frame, text="LUFS").grid(row=0, column=2, padx=2) - - # 文件选择 - file_frame = ttk.LabelFrame(self.master, text="输入文件") - file_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew") - - # 文件操作按钮 - ttk.Button(file_frame, text="选择文件", command=self.select_files, - width=12).grid(row=0, column=0, padx=5, pady=2) - ttk.Button(file_frame, text="移除选中", command=self.remove_selected, - width=12).grid(row=0, column=1, padx=5, pady=2) - ttk.Button(file_frame, text="清空列表", command=self.clear_files, - width=12).grid(row=0, column=2, padx=5, pady=2) - - # 文件列表(使用Treeview) - self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"), - show="headings", height=8) - self.tree.heading("selected", text="选中", anchor=tk.W) - self.tree.heading("filename", text="文件名", anchor=tk.W) - self.tree.column("selected", width=60, anchor=tk.W) - self.tree.column("filename", width=600, anchor=tk.W) - self.tree.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=5, pady=2) - self.tree.bind("", self.on_tree_click) - - # 输出目录 - output_frame = ttk.LabelFrame(self.master, text="输出目录") - output_frame.grid(row=3, column=0, padx=10, pady=5, sticky="ew") - - ttk.Entry(output_frame, textvariable=self.output_dir, width=60 - ).grid(row=0, column=0, padx=5, sticky="ew") - ttk.Button(output_frame, text="浏览", command=self.select_output_dir, - width=8).grid(row=0, column=1, padx=5) - - # 转换按钮区域 - button_frame = ttk.Frame(self.master) - button_frame.grid(row=4, column=0, padx=10, pady=10, sticky="ew") - - ttk.Button(button_frame, text="转换全部文件", command=lambda: self.start_conversion(True), - width=15).pack(side=tk.LEFT, padx=5) - ttk.Button(button_frame, text="转换选中文件", command=lambda: self.start_conversion(False), - width=15).pack(side=tk.LEFT, padx=5) - - # 日志区域 - log_frame = ttk.LabelFrame(self.master, text="日志") - log_frame.grid(row=5, column=0, padx=10, pady=5, sticky="nsew") - - self.log_text = tk.Text(log_frame, height=14, width=80) - self.log_text.pack(fill=tk.BOTH, expand=True) - - # 配置布局权重 - self.master.columnconfigure(0, weight=1) - self.master.rowconfigure(2, weight=1) - self.master.rowconfigure(5, weight=3) - file_frame.columnconfigure(0, weight=1) - file_frame.rowconfigure(1, weight=1) - - def toggle_settings(self): - if self.mode.get() == "audio_to_p3": - self.loudnorm_frame.grid() - else: - self.loudnorm_frame.grid_remove() - - def select_files(self): - file_types = [ - ("音频文件", "*.wav *.mp3 *.ogg *.flac") if self.mode.get() == "audio_to_p3" - else ("P3文件", "*.p3") - ] - - files = filedialog.askopenfilenames(filetypes=file_types) - for f in files: - self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,)) - - def on_tree_click(self, event): - """处理复选框点击事件""" - region = self.tree.identify("region", event.x, event.y) - if region == "cell": - col = self.tree.identify_column(event.x) - item = self.tree.identify_row(event.y) - if col == "#1": # 点击的是选中列 - current_val = self.tree.item(item, "values")[0] - new_val = "[√]" if current_val == "[ ]" else "[ ]" - self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1])) - - def remove_selected(self): - """移除选中的文件""" - to_remove = [] - for item in self.tree.get_children(): - if self.tree.item(item, "values")[0] == "[√]": - to_remove.append(item) - for item in reversed(to_remove): - self.tree.delete(item) - - def clear_files(self): - """清空所有文件""" - for item in self.tree.get_children(): - self.tree.delete(item) - - def select_output_dir(self): - path = filedialog.askdirectory() - if path: - self.output_dir.set(path) - - def redirect_output(self): - class StdoutRedirector: - def __init__(self, text_widget): - self.text_widget = text_widget - self.original_stdout = sys.stdout - - def write(self, message): - self.text_widget.insert(tk.END, message) - self.text_widget.see(tk.END) - self.original_stdout.write(message) - - def flush(self): - self.original_stdout.flush() - - sys.stdout = StdoutRedirector(self.log_text) - - def start_conversion(self, convert_all): - """开始转换""" - input_files = [] - for item in self.tree.get_children(): - if convert_all or self.tree.item(item, "values")[0] == "[√]": - input_files.append(self.tree.item(item, "tags")[0]) - - if not input_files: - msg = "没有找到可转换的文件" if convert_all else "没有选中任何文件" - messagebox.showwarning("警告", msg) - return - - os.makedirs(self.output_dir.get(), exist_ok=True) - - try: - if self.mode.get() == "audio_to_p3": - target_lufs = self.target_lufs.get() if self.enable_loudnorm.get() else None - thread = threading.Thread(target=self.convert_audio_to_p3, args=(target_lufs, input_files)) - else: - thread = threading.Thread(target=self.convert_p3_to_audio, args=(input_files,)) - - thread.start() - except Exception as e: - print(f"转换初始化失败: {str(e)}") - - def convert_audio_to_p3(self, target_lufs, input_files): - """音频转P3转换逻辑""" - for input_path in input_files: - try: - filename = os.path.basename(input_path) - base_name = os.path.splitext(filename)[0] - output_path = os.path.join(self.output_dir.get(), f"{base_name}.p3") - - print(f"正在转换: {filename}") - encode_audio_to_opus(input_path, output_path, target_lufs) - print(f"转换成功: {filename}\n") - except Exception as e: - print(f"转换失败: {str(e)}\n") - - def convert_p3_to_audio(self, input_files): - """P3转音频转换逻辑""" - for input_path in input_files: - try: - filename = os.path.basename(input_path) - base_name = os.path.splitext(filename)[0] - output_path = os.path.join(self.output_dir.get(), f"{base_name}.wav") - - print(f"正在转换: {filename}") - decode_p3_to_audio(input_path, output_path) - print(f"转换成功: {filename}\n") - except Exception as e: - print(f"转换失败: {str(e)}\n") - -if __name__ == "__main__": - root = tk.Tk() - app = AudioConverterApp(root) +import tkinter as tk +from tkinter import ttk, filedialog, messagebox +import os +import threading +import sys +from convert_audio_to_p3 import encode_audio_to_opus +from convert_p3_to_audio import decode_p3_to_audio + +class AudioConverterApp: + def __init__(self, master): + self.master = master + master.title("音频/P3 批量转换工具") + master.geometry("680x600") # 调整窗口高度 + + # 初始化变量 + self.mode = tk.StringVar(value="audio_to_p3") + self.output_dir = tk.StringVar() + self.output_dir.set(os.path.abspath("output")) + self.enable_loudnorm = tk.BooleanVar(value=True) + self.target_lufs = tk.DoubleVar(value=-16.0) + + # 创建UI组件 + self.create_widgets() + self.redirect_output() + + def create_widgets(self): + # 模式选择 + mode_frame = ttk.LabelFrame(self.master, text="转换模式") + mode_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew") + + ttk.Radiobutton(mode_frame, text="音频转P3", variable=self.mode, + value="audio_to_p3", command=self.toggle_settings, + width=12).grid(row=0, column=0, padx=5) + ttk.Radiobutton(mode_frame, text="P3转音频", variable=self.mode, + value="p3_to_audio", command=self.toggle_settings, + width=12).grid(row=0, column=1, padx=5) + + # 响度设置 + self.loudnorm_frame = ttk.Frame(self.master) + self.loudnorm_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew") + + ttk.Checkbutton(self.loudnorm_frame, text="启用响度调整", + variable=self.enable_loudnorm, width=15 + ).grid(row=0, column=0, padx=2) + ttk.Entry(self.loudnorm_frame, textvariable=self.target_lufs, + width=6).grid(row=0, column=1, padx=2) + ttk.Label(self.loudnorm_frame, text="LUFS").grid(row=0, column=2, padx=2) + + # 文件选择 + file_frame = ttk.LabelFrame(self.master, text="输入文件") + file_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew") + + # 文件操作按钮 + ttk.Button(file_frame, text="选择文件", command=self.select_files, + width=12).grid(row=0, column=0, padx=5, pady=2) + ttk.Button(file_frame, text="移除选中", command=self.remove_selected, + width=12).grid(row=0, column=1, padx=5, pady=2) + ttk.Button(file_frame, text="清空列表", command=self.clear_files, + width=12).grid(row=0, column=2, padx=5, pady=2) + + # 文件列表(使用Treeview) + self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"), + show="headings", height=8) + self.tree.heading("selected", text="选中", anchor=tk.W) + self.tree.heading("filename", text="文件名", anchor=tk.W) + self.tree.column("selected", width=60, anchor=tk.W) + self.tree.column("filename", width=600, anchor=tk.W) + self.tree.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=5, pady=2) + self.tree.bind("", self.on_tree_click) + + # 输出目录 + output_frame = ttk.LabelFrame(self.master, text="输出目录") + output_frame.grid(row=3, column=0, padx=10, pady=5, sticky="ew") + + ttk.Entry(output_frame, textvariable=self.output_dir, width=60 + ).grid(row=0, column=0, padx=5, sticky="ew") + ttk.Button(output_frame, text="浏览", command=self.select_output_dir, + width=8).grid(row=0, column=1, padx=5) + + # 转换按钮区域 + button_frame = ttk.Frame(self.master) + button_frame.grid(row=4, column=0, padx=10, pady=10, sticky="ew") + + ttk.Button(button_frame, text="转换全部文件", command=lambda: self.start_conversion(True), + width=15).pack(side=tk.LEFT, padx=5) + ttk.Button(button_frame, text="转换选中文件", command=lambda: self.start_conversion(False), + width=15).pack(side=tk.LEFT, padx=5) + + # 日志区域 + log_frame = ttk.LabelFrame(self.master, text="日志") + log_frame.grid(row=5, column=0, padx=10, pady=5, sticky="nsew") + + self.log_text = tk.Text(log_frame, height=14, width=80) + self.log_text.pack(fill=tk.BOTH, expand=True) + + # 配置布局权重 + self.master.columnconfigure(0, weight=1) + self.master.rowconfigure(2, weight=1) + self.master.rowconfigure(5, weight=3) + file_frame.columnconfigure(0, weight=1) + file_frame.rowconfigure(1, weight=1) + + def toggle_settings(self): + if self.mode.get() == "audio_to_p3": + self.loudnorm_frame.grid() + else: + self.loudnorm_frame.grid_remove() + + def select_files(self): + file_types = [ + ("音频文件", "*.wav *.mp3 *.ogg *.flac") if self.mode.get() == "audio_to_p3" + else ("P3文件", "*.p3") + ] + + files = filedialog.askopenfilenames(filetypes=file_types) + for f in files: + self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,)) + + def on_tree_click(self, event): + """处理复选框点击事件""" + region = self.tree.identify("region", event.x, event.y) + if region == "cell": + col = self.tree.identify_column(event.x) + item = self.tree.identify_row(event.y) + if col == "#1": # 点击的是选中列 + current_val = self.tree.item(item, "values")[0] + new_val = "[√]" if current_val == "[ ]" else "[ ]" + self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1])) + + def remove_selected(self): + """移除选中的文件""" + to_remove = [] + for item in self.tree.get_children(): + if self.tree.item(item, "values")[0] == "[√]": + to_remove.append(item) + for item in reversed(to_remove): + self.tree.delete(item) + + def clear_files(self): + """清空所有文件""" + for item in self.tree.get_children(): + self.tree.delete(item) + + def select_output_dir(self): + path = filedialog.askdirectory() + if path: + self.output_dir.set(path) + + def redirect_output(self): + class StdoutRedirector: + def __init__(self, text_widget): + self.text_widget = text_widget + self.original_stdout = sys.stdout + + def write(self, message): + self.text_widget.insert(tk.END, message) + self.text_widget.see(tk.END) + self.original_stdout.write(message) + + def flush(self): + self.original_stdout.flush() + + sys.stdout = StdoutRedirector(self.log_text) + + def start_conversion(self, convert_all): + """开始转换""" + input_files = [] + for item in self.tree.get_children(): + if convert_all or self.tree.item(item, "values")[0] == "[√]": + input_files.append(self.tree.item(item, "tags")[0]) + + if not input_files: + msg = "没有找到可转换的文件" if convert_all else "没有选中任何文件" + messagebox.showwarning("警告", msg) + return + + os.makedirs(self.output_dir.get(), exist_ok=True) + + try: + if self.mode.get() == "audio_to_p3": + target_lufs = self.target_lufs.get() if self.enable_loudnorm.get() else None + thread = threading.Thread(target=self.convert_audio_to_p3, args=(target_lufs, input_files)) + else: + thread = threading.Thread(target=self.convert_p3_to_audio, args=(input_files,)) + + thread.start() + except Exception as e: + print(f"转换初始化失败: {str(e)}") + + def convert_audio_to_p3(self, target_lufs, input_files): + """音频转P3转换逻辑""" + for input_path in input_files: + try: + filename = os.path.basename(input_path) + base_name = os.path.splitext(filename)[0] + output_path = os.path.join(self.output_dir.get(), f"{base_name}.p3") + + print(f"正在转换: {filename}") + encode_audio_to_opus(input_path, output_path, target_lufs) + print(f"转换成功: {filename}\n") + except Exception as e: + print(f"转换失败: {str(e)}\n") + + def convert_p3_to_audio(self, input_files): + """P3转音频转换逻辑""" + for input_path in input_files: + try: + filename = os.path.basename(input_path) + base_name = os.path.splitext(filename)[0] + output_path = os.path.join(self.output_dir.get(), f"{base_name}.wav") + + print(f"正在转换: {filename}") + decode_p3_to_audio(input_path, output_path) + print(f"转换成功: {filename}\n") + except Exception as e: + print(f"转换失败: {str(e)}\n") + +if __name__ == "__main__": + root = tk.Tk() + app = AudioConverterApp(root) root.mainloop() \ No newline at end of file diff --git a/scripts/p3_tools/convert_audio_to_p3.py b/scripts/p3_tools/convert_audio_to_p3.py index 519d662..0776d34 100644 --- a/scripts/p3_tools/convert_audio_to_p3.py +++ b/scripts/p3_tools/convert_audio_to_p3.py @@ -1,62 +1,62 @@ -# convert audio files to protocol v3 stream -import librosa -import opuslib -import struct -import sys -import tqdm -import numpy as np -import argparse -import pyloudnorm as pyln - -def encode_audio_to_opus(input_file, output_file, target_lufs=None): - # Load audio file using librosa - audio, sample_rate = librosa.load(input_file, sr=None, mono=False, dtype=np.float32) - - # Convert to mono if stereo - if audio.ndim == 2: - audio = librosa.to_mono(audio) - - if target_lufs is not None: - print("Note: Automatic loudness adjustment is enabled, which may cause", file=sys.stderr) - print(" audio distortion. If the input audio has already been ", file=sys.stderr) - print(" loudness-adjusted or if the input audio is TTS audio, ", file=sys.stderr) - print(" please use the `-d` parameter to disable loudness adjustment.", file=sys.stderr) - meter = pyln.Meter(sample_rate) - current_loudness = meter.integrated_loudness(audio) - audio = pyln.normalize.loudness(audio, current_loudness, target_lufs) - print(f"Adjusted loudness: {current_loudness:.1f} LUFS -> {target_lufs} LUFS") - - # Convert sample rate to 16000Hz if necessary - target_sample_rate = 16000 - if sample_rate != target_sample_rate: - audio = librosa.resample(audio, orig_sr=sample_rate, target_sr=target_sample_rate) - sample_rate = target_sample_rate - - # Convert audio data back to int16 after processing - audio = (audio * 32767).astype(np.int16) - - # Initialize Opus encoder - encoder = opuslib.Encoder(sample_rate, 1, opuslib.APPLICATION_AUDIO) - - # Encode and save - with open(output_file, 'wb') as f: - duration = 60 # 60ms per frame - frame_size = int(sample_rate * duration / 1000) - for i in tqdm.tqdm(range(0, len(audio) - frame_size, frame_size)): - frame = audio[i:i + frame_size] - opus_data = encoder.encode(frame.tobytes(), frame_size=frame_size) - packet = struct.pack('>BBH', 0, 0, len(opus_data)) + opus_data - f.write(packet) - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Convert audio to Opus with loudness normalization') - parser.add_argument('input_file', help='Input audio file') - parser.add_argument('output_file', help='Output .opus file') - parser.add_argument('-l', '--lufs', type=float, default=-16.0, - help='Target loudness in LUFS (default: -16)') - parser.add_argument('-d', '--disable-loudnorm', action='store_true', - help='Disable loudness normalization') - args = parser.parse_args() - - target_lufs = None if args.disable_loudnorm else args.lufs +# convert audio files to protocol v3 stream +import librosa +import opuslib +import struct +import sys +import tqdm +import numpy as np +import argparse +import pyloudnorm as pyln + +def encode_audio_to_opus(input_file, output_file, target_lufs=None): + # Load audio file using librosa + audio, sample_rate = librosa.load(input_file, sr=None, mono=False, dtype=np.float32) + + # Convert to mono if stereo + if audio.ndim == 2: + audio = librosa.to_mono(audio) + + if target_lufs is not None: + print("Note: Automatic loudness adjustment is enabled, which may cause", file=sys.stderr) + print(" audio distortion. If the input audio has already been ", file=sys.stderr) + print(" loudness-adjusted or if the input audio is TTS audio, ", file=sys.stderr) + print(" please use the `-d` parameter to disable loudness adjustment.", file=sys.stderr) + meter = pyln.Meter(sample_rate) + current_loudness = meter.integrated_loudness(audio) + audio = pyln.normalize.loudness(audio, current_loudness, target_lufs) + print(f"Adjusted loudness: {current_loudness:.1f} LUFS -> {target_lufs} LUFS") + + # Convert sample rate to 16000Hz if necessary + target_sample_rate = 16000 + if sample_rate != target_sample_rate: + audio = librosa.resample(audio, orig_sr=sample_rate, target_sr=target_sample_rate) + sample_rate = target_sample_rate + + # Convert audio data back to int16 after processing + audio = (audio * 32767).astype(np.int16) + + # Initialize Opus encoder + encoder = opuslib.Encoder(sample_rate, 1, opuslib.APPLICATION_AUDIO) + + # Encode and save + with open(output_file, 'wb') as f: + duration = 60 # 60ms per frame + frame_size = int(sample_rate * duration / 1000) + for i in tqdm.tqdm(range(0, len(audio) - frame_size, frame_size)): + frame = audio[i:i + frame_size] + opus_data = encoder.encode(frame.tobytes(), frame_size=frame_size) + packet = struct.pack('>BBH', 0, 0, len(opus_data)) + opus_data + f.write(packet) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Convert audio to Opus with loudness normalization') + parser.add_argument('input_file', help='Input audio file') + parser.add_argument('output_file', help='Output .opus file') + parser.add_argument('-l', '--lufs', type=float, default=-16.0, + help='Target loudness in LUFS (default: -16)') + parser.add_argument('-d', '--disable-loudnorm', action='store_true', + help='Disable loudness normalization') + args = parser.parse_args() + + target_lufs = None if args.disable_loudnorm else args.lufs encode_audio_to_opus(args.input_file, args.output_file, target_lufs) \ No newline at end of file diff --git a/scripts/p3_tools/convert_p3_to_audio.py b/scripts/p3_tools/convert_p3_to_audio.py index f870b01..7f431ad 100644 --- a/scripts/p3_tools/convert_p3_to_audio.py +++ b/scripts/p3_tools/convert_p3_to_audio.py @@ -1,51 +1,51 @@ -import struct -import sys -import opuslib -import numpy as np -from tqdm import tqdm -import soundfile as sf - - -def decode_p3_to_audio(input_file, output_file): - sample_rate = 16000 - channels = 1 - decoder = opuslib.Decoder(sample_rate, channels) - - pcm_frames = [] - frame_size = int(sample_rate * 60 / 1000) - - with open(input_file, "rb") as f: - f.seek(0, 2) - total_size = f.tell() - f.seek(0) - - with tqdm(total=total_size, unit="B", unit_scale=True) as pbar: - while True: - header = f.read(4) - if not header or len(header) < 4: - break - - pkt_type, reserved, opus_len = struct.unpack(">BBH", header) - opus_data = f.read(opus_len) - if len(opus_data) != opus_len: - break - - pcm = decoder.decode(opus_data, frame_size) - pcm_frames.append(np.frombuffer(pcm, dtype=np.int16)) - - pbar.update(4 + opus_len) - - if not pcm_frames: - raise ValueError("No valid audio data found") - - pcm_data = np.concatenate(pcm_frames) - - sf.write(output_file, pcm_data, sample_rate, subtype="PCM_16") - - -if __name__ == "__main__": - if len(sys.argv) != 3: - print("Usage: python convert_p3_to_audio.py ") - sys.exit(1) - - decode_p3_to_audio(sys.argv[1], sys.argv[2]) +import struct +import sys +import opuslib +import numpy as np +from tqdm import tqdm +import soundfile as sf + + +def decode_p3_to_audio(input_file, output_file): + sample_rate = 16000 + channels = 1 + decoder = opuslib.Decoder(sample_rate, channels) + + pcm_frames = [] + frame_size = int(sample_rate * 60 / 1000) + + with open(input_file, "rb") as f: + f.seek(0, 2) + total_size = f.tell() + f.seek(0) + + with tqdm(total=total_size, unit="B", unit_scale=True) as pbar: + while True: + header = f.read(4) + if not header or len(header) < 4: + break + + pkt_type, reserved, opus_len = struct.unpack(">BBH", header) + opus_data = f.read(opus_len) + if len(opus_data) != opus_len: + break + + pcm = decoder.decode(opus_data, frame_size) + pcm_frames.append(np.frombuffer(pcm, dtype=np.int16)) + + pbar.update(4 + opus_len) + + if not pcm_frames: + raise ValueError("No valid audio data found") + + pcm_data = np.concatenate(pcm_frames) + + sf.write(output_file, pcm_data, sample_rate, subtype="PCM_16") + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("Usage: python convert_p3_to_audio.py ") + sys.exit(1) + + decode_p3_to_audio(sys.argv[1], sys.argv[2]) diff --git a/scripts/p3_tools/p3_gui_player.py b/scripts/p3_tools/p3_gui_player.py index 3bbc8a3..54e34cc 100644 --- a/scripts/p3_tools/p3_gui_player.py +++ b/scripts/p3_tools/p3_gui_player.py @@ -1,241 +1,241 @@ -import tkinter as tk -from tkinter import filedialog, messagebox -import threading -import time -import opuslib -import struct -import numpy as np -import sounddevice as sd -import os - - -def play_p3_file(input_file, stop_event=None, pause_event=None): - """ - 播放p3格式的音频文件 - p3格式: [1字节类型, 1字节保留, 2字节长度, Opus数据] - """ - # 初始化Opus解码器 - sample_rate = 16000 # 采样率固定为16000Hz - channels = 1 # 单声道 - decoder = opuslib.Decoder(sample_rate, channels) - - # 帧大小 (60ms) - frame_size = int(sample_rate * 60 / 1000) - - # 打开音频流 - stream = sd.OutputStream( - samplerate=sample_rate, - channels=channels, - dtype='int16' - ) - stream.start() - - try: - with open(input_file, 'rb') as f: - print(f"正在播放: {input_file}") - - while True: - if stop_event and stop_event.is_set(): - break - - if pause_event and pause_event.is_set(): - time.sleep(0.1) - continue - - # 读取头部 (4字节) - header = f.read(4) - if not header or len(header) < 4: - break - - # 解析头部 - packet_type, reserved, data_len = struct.unpack('>BBH', header) - - # 读取Opus数据 - opus_data = f.read(data_len) - if not opus_data or len(opus_data) < data_len: - break - - # 解码Opus数据 - pcm_data = decoder.decode(opus_data, frame_size) - - # 将字节转换为numpy数组 - audio_array = np.frombuffer(pcm_data, dtype=np.int16) - - # 播放音频 - stream.write(audio_array) - - except KeyboardInterrupt: - print("\n播放已停止") - finally: - stream.stop() - stream.close() - print("播放完成") - - -class P3PlayerApp: - def __init__(self, root): - self.root = root - self.root.title("P3 文件简易播放器") - self.root.geometry("500x400") - - self.playlist = [] - self.current_index = 0 - self.is_playing = False - self.is_paused = False - self.stop_event = threading.Event() - self.pause_event = threading.Event() - self.loop_playback = tk.BooleanVar(value=False) # 循环播放复选框的状态 - - # 创建界面组件 - self.create_widgets() - - def create_widgets(self): - # 播放列表 - self.playlist_label = tk.Label(self.root, text="播放列表:") - self.playlist_label.pack(pady=5) - - self.playlist_frame = tk.Frame(self.root) - self.playlist_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) - - self.playlist_listbox = tk.Listbox(self.playlist_frame, selectmode=tk.SINGLE) - self.playlist_listbox.pack(fill=tk.BOTH, expand=True) - - # 复选框和移除按钮 - self.checkbox_frame = tk.Frame(self.root) - self.checkbox_frame.pack(pady=5) - - self.remove_button = tk.Button(self.checkbox_frame, text="移除文件", command=self.remove_files) - self.remove_button.pack(side=tk.LEFT, padx=5) - - # 循环播放复选框 - self.loop_checkbox = tk.Checkbutton(self.checkbox_frame, text="循环播放", variable=self.loop_playback) - self.loop_checkbox.pack(side=tk.LEFT, padx=5) - - # 控制按钮 - self.control_frame = tk.Frame(self.root) - self.control_frame.pack(pady=10) - - self.add_button = tk.Button(self.control_frame, text="添加文件", command=self.add_file) - self.add_button.grid(row=0, column=0, padx=5) - - self.play_button = tk.Button(self.control_frame, text="播放", command=self.play) - self.play_button.grid(row=0, column=1, padx=5) - - self.pause_button = tk.Button(self.control_frame, text="暂停", command=self.pause) - self.pause_button.grid(row=0, column=2, padx=5) - - self.stop_button = tk.Button(self.control_frame, text="停止", command=self.stop) - self.stop_button.grid(row=0, column=3, padx=5) - - # 状态标签 - self.status_label = tk.Label(self.root, text="未在播放", fg="blue") - self.status_label.pack(pady=10) - - def add_file(self): - files = filedialog.askopenfilenames(filetypes=[("P3 文件", "*.p3")]) - if files: - self.playlist.extend(files) - self.update_playlist() - - def update_playlist(self): - self.playlist_listbox.delete(0, tk.END) - for file in self.playlist: - self.playlist_listbox.insert(tk.END, os.path.basename(file)) # 仅显示文件名 - - def update_status(self, status_text, color="blue"): - """更新状态标签的内容""" - self.status_label.config(text=status_text, fg=color) - - def play(self): - if not self.playlist: - messagebox.showwarning("警告", "播放列表为空!") - return - - if self.is_paused: - self.is_paused = False - self.pause_event.clear() - self.update_status(f"正在播放:{os.path.basename(self.playlist[self.current_index])}", "green") - return - - if self.is_playing: - return - - self.is_playing = True - self.stop_event.clear() - self.pause_event.clear() - self.current_index = self.playlist_listbox.curselection()[0] if self.playlist_listbox.curselection() else 0 - self.play_thread = threading.Thread(target=self.play_audio, daemon=True) - self.play_thread.start() - self.update_status(f"正在播放:{os.path.basename(self.playlist[self.current_index])}", "green") - - def play_audio(self): - while True: - if self.stop_event.is_set(): - break - - if self.pause_event.is_set(): - time.sleep(0.1) - continue - - # 检查当前索引是否有效 - if self.current_index >= len(self.playlist): - if self.loop_playback.get(): # 如果勾选了循环播放 - self.current_index = 0 # 回到第一首 - else: - break # 否则停止播放 - - file = self.playlist[self.current_index] - self.playlist_listbox.selection_clear(0, tk.END) - self.playlist_listbox.selection_set(self.current_index) - self.playlist_listbox.activate(self.current_index) - self.update_status(f"正在播放:{os.path.basename(self.playlist[self.current_index])}", "green") - play_p3_file(file, self.stop_event, self.pause_event) - - if self.stop_event.is_set(): - break - - if not self.loop_playback.get(): # 如果没有勾选循环播放 - break # 播放完当前文件后停止 - - self.current_index += 1 - if self.current_index >= len(self.playlist): - if self.loop_playback.get(): # 如果勾选了循环播放 - self.current_index = 0 # 回到第一首 - - self.is_playing = False - self.is_paused = False - self.update_status("播放已停止", "red") - - def pause(self): - if self.is_playing: - self.is_paused = not self.is_paused - if self.is_paused: - self.pause_event.set() - self.update_status("播放已暂停", "orange") - else: - self.pause_event.clear() - self.update_status(f"正在播放:{os.path.basename(self.playlist[self.current_index])}", "green") - - def stop(self): - if self.is_playing or self.is_paused: - self.is_playing = False - self.is_paused = False - self.stop_event.set() - self.pause_event.clear() - self.update_status("播放已停止", "red") - - def remove_files(self): - selected_indices = self.playlist_listbox.curselection() - if not selected_indices: - messagebox.showwarning("警告", "请先选择要移除的文件!") - return - - for index in reversed(selected_indices): - self.playlist.pop(index) - self.update_playlist() - - -if __name__ == "__main__": - root = tk.Tk() - app = P3PlayerApp(root) - root.mainloop() +import tkinter as tk +from tkinter import filedialog, messagebox +import threading +import time +import opuslib +import struct +import numpy as np +import sounddevice as sd +import os + + +def play_p3_file(input_file, stop_event=None, pause_event=None): + """ + 播放p3格式的音频文件 + p3格式: [1字节类型, 1字节保留, 2字节长度, Opus数据] + """ + # 初始化Opus解码器 + sample_rate = 16000 # 采样率固定为16000Hz + channels = 1 # 单声道 + decoder = opuslib.Decoder(sample_rate, channels) + + # 帧大小 (60ms) + frame_size = int(sample_rate * 60 / 1000) + + # 打开音频流 + stream = sd.OutputStream( + samplerate=sample_rate, + channels=channels, + dtype='int16' + ) + stream.start() + + try: + with open(input_file, 'rb') as f: + print(f"正在播放: {input_file}") + + while True: + if stop_event and stop_event.is_set(): + break + + if pause_event and pause_event.is_set(): + time.sleep(0.1) + continue + + # 读取头部 (4字节) + header = f.read(4) + if not header or len(header) < 4: + break + + # 解析头部 + packet_type, reserved, data_len = struct.unpack('>BBH', header) + + # 读取Opus数据 + opus_data = f.read(data_len) + if not opus_data or len(opus_data) < data_len: + break + + # 解码Opus数据 + pcm_data = decoder.decode(opus_data, frame_size) + + # 将字节转换为numpy数组 + audio_array = np.frombuffer(pcm_data, dtype=np.int16) + + # 播放音频 + stream.write(audio_array) + + except KeyboardInterrupt: + print("\n播放已停止") + finally: + stream.stop() + stream.close() + print("播放完成") + + +class P3PlayerApp: + def __init__(self, root): + self.root = root + self.root.title("P3 文件简易播放器") + self.root.geometry("500x400") + + self.playlist = [] + self.current_index = 0 + self.is_playing = False + self.is_paused = False + self.stop_event = threading.Event() + self.pause_event = threading.Event() + self.loop_playback = tk.BooleanVar(value=False) # 循环播放复选框的状态 + + # 创建界面组件 + self.create_widgets() + + def create_widgets(self): + # 播放列表 + self.playlist_label = tk.Label(self.root, text="播放列表:") + self.playlist_label.pack(pady=5) + + self.playlist_frame = tk.Frame(self.root) + self.playlist_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) + + self.playlist_listbox = tk.Listbox(self.playlist_frame, selectmode=tk.SINGLE) + self.playlist_listbox.pack(fill=tk.BOTH, expand=True) + + # 复选框和移除按钮 + self.checkbox_frame = tk.Frame(self.root) + self.checkbox_frame.pack(pady=5) + + self.remove_button = tk.Button(self.checkbox_frame, text="移除文件", command=self.remove_files) + self.remove_button.pack(side=tk.LEFT, padx=5) + + # 循环播放复选框 + self.loop_checkbox = tk.Checkbutton(self.checkbox_frame, text="循环播放", variable=self.loop_playback) + self.loop_checkbox.pack(side=tk.LEFT, padx=5) + + # 控制按钮 + self.control_frame = tk.Frame(self.root) + self.control_frame.pack(pady=10) + + self.add_button = tk.Button(self.control_frame, text="添加文件", command=self.add_file) + self.add_button.grid(row=0, column=0, padx=5) + + self.play_button = tk.Button(self.control_frame, text="播放", command=self.play) + self.play_button.grid(row=0, column=1, padx=5) + + self.pause_button = tk.Button(self.control_frame, text="暂停", command=self.pause) + self.pause_button.grid(row=0, column=2, padx=5) + + self.stop_button = tk.Button(self.control_frame, text="停止", command=self.stop) + self.stop_button.grid(row=0, column=3, padx=5) + + # 状态标签 + self.status_label = tk.Label(self.root, text="未在播放", fg="blue") + self.status_label.pack(pady=10) + + def add_file(self): + files = filedialog.askopenfilenames(filetypes=[("P3 文件", "*.p3")]) + if files: + self.playlist.extend(files) + self.update_playlist() + + def update_playlist(self): + self.playlist_listbox.delete(0, tk.END) + for file in self.playlist: + self.playlist_listbox.insert(tk.END, os.path.basename(file)) # 仅显示文件名 + + def update_status(self, status_text, color="blue"): + """更新状态标签的内容""" + self.status_label.config(text=status_text, fg=color) + + def play(self): + if not self.playlist: + messagebox.showwarning("警告", "播放列表为空!") + return + + if self.is_paused: + self.is_paused = False + self.pause_event.clear() + self.update_status(f"正在播放:{os.path.basename(self.playlist[self.current_index])}", "green") + return + + if self.is_playing: + return + + self.is_playing = True + self.stop_event.clear() + self.pause_event.clear() + self.current_index = self.playlist_listbox.curselection()[0] if self.playlist_listbox.curselection() else 0 + self.play_thread = threading.Thread(target=self.play_audio, daemon=True) + self.play_thread.start() + self.update_status(f"正在播放:{os.path.basename(self.playlist[self.current_index])}", "green") + + def play_audio(self): + while True: + if self.stop_event.is_set(): + break + + if self.pause_event.is_set(): + time.sleep(0.1) + continue + + # 检查当前索引是否有效 + if self.current_index >= len(self.playlist): + if self.loop_playback.get(): # 如果勾选了循环播放 + self.current_index = 0 # 回到第一首 + else: + break # 否则停止播放 + + file = self.playlist[self.current_index] + self.playlist_listbox.selection_clear(0, tk.END) + self.playlist_listbox.selection_set(self.current_index) + self.playlist_listbox.activate(self.current_index) + self.update_status(f"正在播放:{os.path.basename(self.playlist[self.current_index])}", "green") + play_p3_file(file, self.stop_event, self.pause_event) + + if self.stop_event.is_set(): + break + + if not self.loop_playback.get(): # 如果没有勾选循环播放 + break # 播放完当前文件后停止 + + self.current_index += 1 + if self.current_index >= len(self.playlist): + if self.loop_playback.get(): # 如果勾选了循环播放 + self.current_index = 0 # 回到第一首 + + self.is_playing = False + self.is_paused = False + self.update_status("播放已停止", "red") + + def pause(self): + if self.is_playing: + self.is_paused = not self.is_paused + if self.is_paused: + self.pause_event.set() + self.update_status("播放已暂停", "orange") + else: + self.pause_event.clear() + self.update_status(f"正在播放:{os.path.basename(self.playlist[self.current_index])}", "green") + + def stop(self): + if self.is_playing or self.is_paused: + self.is_playing = False + self.is_paused = False + self.stop_event.set() + self.pause_event.clear() + self.update_status("播放已停止", "red") + + def remove_files(self): + selected_indices = self.playlist_listbox.curselection() + if not selected_indices: + messagebox.showwarning("警告", "请先选择要移除的文件!") + return + + for index in reversed(selected_indices): + self.playlist.pop(index) + self.update_playlist() + + +if __name__ == "__main__": + root = tk.Tk() + app = P3PlayerApp(root) + root.mainloop() diff --git a/scripts/p3_tools/play_p3.py b/scripts/p3_tools/play_p3.py index 3c9ec81..84606f2 100644 --- a/scripts/p3_tools/play_p3.py +++ b/scripts/p3_tools/play_p3.py @@ -1,71 +1,71 @@ -# 播放p3格式的音频文件 -import opuslib -import struct -import numpy as np -import sounddevice as sd -import argparse - -def play_p3_file(input_file): - """ - 播放p3格式的音频文件 - p3格式: [1字节类型, 1字节保留, 2字节长度, Opus数据] - """ - # 初始化Opus解码器 - sample_rate = 16000 # 采样率固定为16000Hz - channels = 1 # 单声道 - decoder = opuslib.Decoder(sample_rate, channels) - - # 帧大小 (60ms) - frame_size = int(sample_rate * 60 / 1000) - - # 打开音频流 - stream = sd.OutputStream( - samplerate=sample_rate, - channels=channels, - dtype='int16' - ) - stream.start() - - try: - with open(input_file, 'rb') as f: - print(f"正在播放: {input_file}") - - while True: - # 读取头部 (4字节) - header = f.read(4) - if not header or len(header) < 4: - break - - # 解析头部 - packet_type, reserved, data_len = struct.unpack('>BBH', header) - - # 读取Opus数据 - opus_data = f.read(data_len) - if not opus_data or len(opus_data) < data_len: - break - - # 解码Opus数据 - pcm_data = decoder.decode(opus_data, frame_size) - - # 将字节转换为numpy数组 - audio_array = np.frombuffer(pcm_data, dtype=np.int16) - - # 播放音频 - stream.write(audio_array) - - except KeyboardInterrupt: - print("\n播放已停止") - finally: - stream.stop() - stream.close() - print("播放完成") - -def main(): - parser = argparse.ArgumentParser(description='播放p3格式的音频文件') - parser.add_argument('input_file', help='输入的p3文件路径') - args = parser.parse_args() - - play_p3_file(args.input_file) - -if __name__ == "__main__": - main() +# 播放p3格式的音频文件 +import opuslib +import struct +import numpy as np +import sounddevice as sd +import argparse + +def play_p3_file(input_file): + """ + 播放p3格式的音频文件 + p3格式: [1字节类型, 1字节保留, 2字节长度, Opus数据] + """ + # 初始化Opus解码器 + sample_rate = 16000 # 采样率固定为16000Hz + channels = 1 # 单声道 + decoder = opuslib.Decoder(sample_rate, channels) + + # 帧大小 (60ms) + frame_size = int(sample_rate * 60 / 1000) + + # 打开音频流 + stream = sd.OutputStream( + samplerate=sample_rate, + channels=channels, + dtype='int16' + ) + stream.start() + + try: + with open(input_file, 'rb') as f: + print(f"正在播放: {input_file}") + + while True: + # 读取头部 (4字节) + header = f.read(4) + if not header or len(header) < 4: + break + + # 解析头部 + packet_type, reserved, data_len = struct.unpack('>BBH', header) + + # 读取Opus数据 + opus_data = f.read(data_len) + if not opus_data or len(opus_data) < data_len: + break + + # 解码Opus数据 + pcm_data = decoder.decode(opus_data, frame_size) + + # 将字节转换为numpy数组 + audio_array = np.frombuffer(pcm_data, dtype=np.int16) + + # 播放音频 + stream.write(audio_array) + + except KeyboardInterrupt: + print("\n播放已停止") + finally: + stream.stop() + stream.close() + print("播放完成") + +def main(): + parser = argparse.ArgumentParser(description='播放p3格式的音频文件') + parser.add_argument('input_file', help='输入的p3文件路径') + args = parser.parse_args() + + play_p3_file(args.input_file) + +if __name__ == "__main__": + main() diff --git a/scripts/p3_tools/requirements.txt b/scripts/p3_tools/requirements.txt index d76d4cd..11d3d0d 100644 --- a/scripts/p3_tools/requirements.txt +++ b/scripts/p3_tools/requirements.txt @@ -1,7 +1,7 @@ -librosa>=0.9.2 -opuslib>=3.0.1 -numpy>=1.20.0 -tqdm>=4.62.0 -sounddevice>=0.4.4 -pyloudnorm>=0.1.1 -soundfile>=0.13.1 +librosa>=0.9.2 +opuslib>=3.0.1 +numpy>=1.20.0 +tqdm>=4.62.0 +sounddevice>=0.4.4 +pyloudnorm>=0.1.1 +soundfile>=0.13.1 diff --git a/scripts/release.py b/scripts/release.py old mode 100755 new mode 100644 index fdceedf..6a1fe01 --- a/scripts/release.py +++ b/scripts/release.py @@ -1,153 +1,262 @@ -import sys -import os -import json -import zipfile -import argparse - -# 切换到项目根目录 -os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -def get_board_type(): - with open("build/compile_commands.json") as f: - data = json.load(f) - for item in data: - if not item["file"].endswith("main.cc"): - continue - command = item["command"] - # extract -DBOARD_TYPE=xxx - board_type = command.split("-DBOARD_TYPE=\\\"")[1].split("\\\"")[0].strip() - return board_type - return None - -def get_project_version(): - with open("CMakeLists.txt") as f: - for line in f: - if line.startswith("set(PROJECT_VER"): - return line.split("\"")[1].split("\"")[0].strip() - return None - -def merge_bin(): - if os.system("idf.py merge-bin") != 0: - print("merge bin failed") - sys.exit(1) - -def zip_bin(board_type, project_version): - if not os.path.exists("releases"): - os.makedirs("releases") - output_path = f"releases/v{project_version}_{board_type}.zip" - if os.path.exists(output_path): - os.remove(output_path) - with zipfile.ZipFile(output_path, 'w', compression=zipfile.ZIP_DEFLATED) as zipf: - zipf.write("build/merged-binary.bin", arcname="merged-binary.bin") - print(f"zip bin to {output_path} done") - - -def release_current(): - merge_bin() - board_type = get_board_type() - print("board type:", board_type) - project_version = get_project_version() - print("project version:", project_version) - zip_bin(board_type, project_version) - -def get_all_board_types(): - board_configs = {} - with open("main/CMakeLists.txt", encoding='utf-8') as f: - lines = f.readlines() - for i, line in enumerate(lines): - # 查找 if(CONFIG_BOARD_TYPE_*) 行 - if "if(CONFIG_BOARD_TYPE_" in line: - config_name = line.strip().split("if(")[1].split(")")[0] - # 查找下一行的 set(BOARD_TYPE "xxx") - next_line = lines[i + 1].strip() - if next_line.startswith("set(BOARD_TYPE"): - board_type = next_line.split('"')[1] - board_configs[config_name] = board_type - return board_configs - -def release(board_type, board_config, config_filename="config.json"): - config_path = f"main/boards/{board_type}/{config_filename}" - if not os.path.exists(config_path): - print(f"跳过 {board_type} 因为 {config_filename} 不存在") - return - - # Print Project Version - project_version = get_project_version() - print(f"Project Version: {project_version}", config_path) - - with open(config_path, "r") as f: - config = json.load(f) - target = config["target"] - builds = config["builds"] - - for build in builds: - name = build["name"] - if not name.startswith(board_type): - raise ValueError(f"name {name} 必须以 {board_type} 开头") - output_path = f"releases/v{project_version}_{name}.zip" - if os.path.exists(output_path): - print(f"跳过 {board_type} 因为 {output_path} 已存在") - continue - - sdkconfig_append = [f"{board_config}=y"] - for append in build.get("sdkconfig_append", []): - sdkconfig_append.append(append) - print(f"name: {name}") - print(f"target: {target}") - for append in sdkconfig_append: - print(f"sdkconfig_append: {append}") - # unset IDF_TARGET - os.environ.pop("IDF_TARGET", None) - # Call set-target - if os.system(f"idf.py set-target {target}") != 0: - print("set-target failed") - sys.exit(1) - # Append sdkconfig - with open("sdkconfig", "a") as f: - f.write("\n") - for append in sdkconfig_append: - f.write(f"{append}\n") - # Build with macro BOARD_NAME defined to name - if os.system(f"idf.py -DBOARD_NAME={name} build") != 0: - print("build failed") - sys.exit(1) - # Call merge-bin - if os.system("idf.py merge-bin") != 0: - print("merge-bin failed") - sys.exit(1) - # Zip bin - zip_bin(name, project_version) - print("-" * 80) - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("board", nargs="?", default=None, help="板子类型或 all") - parser.add_argument("-c", "--config", default="config.json", help="指定 config 文件名,默认 config.json") - parser.add_argument("--list-boards", action="store_true", help="列出所有支持的 board 列表") - parser.add_argument("--json", action="store_true", help="配合 --list-boards,JSON 格式输出") - args = parser.parse_args() - - if args.list_boards: - board_configs = get_all_board_types() - boards = list(board_configs.values()) - if args.json: - print(json.dumps(boards)) - else: - for board in boards: - print(board) - sys.exit(0) - - if args.board: - board_configs = get_all_board_types() - found = False - for board_config, board_type in board_configs.items(): - if args.board == 'all' or board_type == args.board: - release(board_type, board_config, config_filename=args.config) - found = True - if not found: - print(f"未找到板子类型: {args.board}") - print("可用的板子类型:") - for board_type in board_configs.values(): - print(f" {board_type}") - else: - release_current() +import sys +import os +import json +import zipfile +import argparse +from pathlib import Path +from typing import Optional + +# Switch to project root directory +os.chdir(Path(__file__).resolve().parent.parent) + +################################################################################ +# Common utility functions +################################################################################ + +def get_board_type_from_compile_commands() -> Optional[str]: + """Parse the current compiled BOARD_TYPE from build/compile_commands.json""" + compile_file = Path("build/compile_commands.json") + if not compile_file.exists(): + return None + with compile_file.open() as f: + data = json.load(f) + for item in data: + if not item["file"].endswith("main.cc"): + continue + cmd = item["command"] + if "-DBOARD_TYPE=\\\"" in cmd: + return cmd.split("-DBOARD_TYPE=\\\"")[1].split("\\\"")[0].strip() + return None + + +def get_project_version() -> Optional[str]: + """Read set(PROJECT_VER "x.y.z") from root CMakeLists.txt""" + with Path("CMakeLists.txt").open() as f: + for line in f: + if line.startswith("set(PROJECT_VER"): + return line.split("\"")[1] + return None + + +def merge_bin() -> None: + if os.system("idf.py merge-bin") != 0: + print("merge-bin failed", file=sys.stderr) + sys.exit(1) + + +def zip_bin(name: str, version: str) -> None: + """Zip build/merged-binary.bin to releases/v{version}_{name}.zip""" + out_dir = Path("releases") + out_dir.mkdir(exist_ok=True) + output_path = out_dir / f"v{version}_{name}.zip" + + if output_path.exists(): + output_path.unlink() + + with zipfile.ZipFile(output_path, "w", compression=zipfile.ZIP_DEFLATED) as zipf: + zipf.write("build/merged-binary.bin", arcname="merged-binary.bin") + print(f"zip bin to {output_path} done") + +################################################################################ +# board / variant related functions +################################################################################ + +_BOARDS_DIR = Path("main/boards") + + +def _collect_variants(config_filename: str = "config.json") -> list[dict[str, str]]: + """Traverse all boards under main/boards, collect variant information. + + Return example: + [{"board": "bread-compact-ml307", "name": "bread-compact-ml307"}, ...] + """ + variants: list[dict[str, str]] = [] + for board_path in _BOARDS_DIR.iterdir(): + if not board_path.is_dir(): + continue + if board_path.name == "common": + continue + cfg_path = board_path / config_filename + if not cfg_path.exists(): + print(f"[WARN] {cfg_path} does not exist, skip", file=sys.stderr) + continue + try: + with cfg_path.open() as f: + cfg = json.load(f) + for build in cfg.get("builds", []): + variants.append({"board": board_path.name, "name": build["name"]}) + except Exception as e: + print(f"[ERROR] 解析 {cfg_path} 失败: {e}", file=sys.stderr) + return variants + + +def _parse_board_config_map() -> dict[str, str]: + """Build the mapping of CONFIG_BOARD_TYPE_xxx and board_type from main/CMakeLists.txt""" + cmake_file = Path("main/CMakeLists.txt") + mapping: dict[str, str] = {} + lines = cmake_file.read_text(encoding="utf-8").splitlines() + for idx, line in enumerate(lines): + if "if(CONFIG_BOARD_TYPE_" in line: + config_name = line.strip().split("if(")[1].split(")")[0] + if idx + 1 < len(lines): + next_line = lines[idx + 1].strip() + if next_line.startswith("set(BOARD_TYPE"): + board_type = next_line.split('"')[1] + mapping[config_name] = board_type + return mapping + + +def _find_board_config(board_type: str) -> Optional[str]: + """Find the corresponding CONFIG_BOARD_TYPE_xxx for the given board_type""" + for config, b_type in _parse_board_config_map().items(): + if b_type == board_type: + return config + return None + +################################################################################ +# Check board_type in CMakeLists +################################################################################ + +def _board_type_exists(board_type: str) -> bool: + cmake_file = Path("main/CMakeLists.txt") + pattern = f'set(BOARD_TYPE "{board_type}")' + return pattern in cmake_file.read_text(encoding="utf-8") + +################################################################################ +# Compile implementation +################################################################################ + +def release(board_type: str, config_filename: str = "config.json", *, filter_name: Optional[str] = None) -> None: + """Compile and package all/specified variants of the specified board_type + + Args: + board_type: directory name under main/boards + config_filename: config.json name (default: config.json) + filter_name: if specified, only compile the build["name"] that matches + """ + cfg_path = _BOARDS_DIR / board_type / config_filename + if not cfg_path.exists(): + print(f"[WARN] {cfg_path} 不存在,跳过 {board_type}") + return + + project_version = get_project_version() + print(f"Project Version: {project_version} ({cfg_path})") + + with cfg_path.open() as f: + cfg = json.load(f) + target = cfg["target"] + + builds = cfg.get("builds", []) + if filter_name: + builds = [b for b in builds if b["name"] == filter_name] + if not builds: + print(f"[ERROR] 未在 {board_type} 的 {config_filename} 中找到变体 {filter_name}", file=sys.stderr) + sys.exit(1) + + for build in builds: + name = build["name"] + if not name.startswith(board_type): + raise ValueError(f"build.name {name} 必须以 {board_type} 开头") + + output_path = Path("releases") / f"v{project_version}_{name}.zip" + if output_path.exists(): + print(f"跳过 {name} 因为 {output_path} 已存在") + continue + + # Process sdkconfig_append + board_type_config = _find_board_config(board_type) + sdkconfig_append = [f"{board_type_config}=y"] + sdkconfig_append.extend(build.get("sdkconfig_append", [])) + + print("-" * 80) + print(f"name: {name}") + print(f"target: {target}") + for item in sdkconfig_append: + print(f"sdkconfig_append: {item}") + + os.environ.pop("IDF_TARGET", None) + + # Call set-target + if os.system(f"idf.py set-target {target}") != 0: + print("set-target failed", file=sys.stderr) + sys.exit(1) + + # Append sdkconfig + with Path("sdkconfig").open("a") as f: + f.write("\n") + f.write("# Append by release.py\n") + for append in sdkconfig_append: + f.write(f"{append}\n") + # Build with macro BOARD_NAME defined to name + if os.system(f"idf.py -DBOARD_NAME={name} build") != 0: + print("build failed") + sys.exit(1) + + # merge-bin + merge_bin() + + # Zip + zip_bin(name, project_version) + +################################################################################ +# CLI entry +################################################################################ + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("board", nargs="?", default=None, help="板子类型或 all") + parser.add_argument("-c", "--config", default="config.json", help="指定 config 文件名,默认 config.json") + parser.add_argument("--list-boards", action="store_true", help="列出所有支持的 board 及变体列表") + parser.add_argument("--json", action="store_true", help="配合 --list-boards,JSON 格式输出") + parser.add_argument("--name", help="指定变体名称,仅编译匹配的变体") + + args = parser.parse_args() + + # List mode + if args.list_boards: + variants = _collect_variants(config_filename=args.config) + if args.json: + print(json.dumps(variants)) + else: + for v in variants: + print(f"{v['board']}: {v['name']}") + sys.exit(0) + + # Current directory firmware packaging mode + if args.board is None: + merge_bin() + curr_board_type = get_board_type_from_compile_commands() + if curr_board_type is None: + print("未能从 compile_commands.json 解析 board_type", file=sys.stderr) + sys.exit(1) + project_ver = get_project_version() + zip_bin(curr_board_type, project_ver) + sys.exit(0) + + # Compile mode + board_type_input: str = args.board + name_filter: str | None = args.name + + # Check board_type in CMakeLists + if board_type_input != "all" and not _board_type_exists(board_type_input): + print(f"[ERROR] main/CMakeLists.txt 中未找到 board_type {board_type_input}", file=sys.stderr) + sys.exit(1) + + variants_all = _collect_variants(config_filename=args.config) + + # Filter board_type list + target_board_types: set[str] + if board_type_input == "all": + target_board_types = {v["board"] for v in variants_all} + else: + target_board_types = {board_type_input} + + for bt in sorted(target_board_types): + if not _board_type_exists(bt): + print(f"[ERROR] main/CMakeLists.txt 中未找到 board_type {bt}", file=sys.stderr) + sys.exit(1) + cfg_path = _BOARDS_DIR / bt / args.config + if bt == board_type_input and not cfg_path.exists(): + print(f"开发板 {bt} 未定义 {args.config} 配置文件,跳过") + sys.exit(0) + release(bt, config_filename=args.config, filter_name=name_filter if bt == board_type_input else None) diff --git a/scripts/sonic_wifi_config.html b/scripts/sonic_wifi_config.html index b5c5093..bfcdf0c 100644 --- a/scripts/sonic_wifi_config.html +++ b/scripts/sonic_wifi_config.html @@ -1,208 +1,208 @@ - - - - - 小智声波配网 - - - - -
-

📶 小智声波配网

- - - - - - -
- -
- - - - -
- - - - + + + + + 小智声波配网 + + + + +
+

📶 小智声波配网

+ + + + + + +
+ +
+ + + + +
+ + + + diff --git a/scripts/spiffs_assets/README.md b/scripts/spiffs_assets/README.md new file mode 100644 index 0000000..def6a83 --- /dev/null +++ b/scripts/spiffs_assets/README.md @@ -0,0 +1,110 @@ +# SPIFFS Assets Builder + +这个脚本用于构建 ESP32 项目的 SPIFFS 资源分区,将各种资源文件打包成可在设备上使用的格式。 + +## 功能特性 + +- 处理唤醒网络模型 (WakeNet Model) +- 集成文本字体文件 +- 处理表情符号图片集合 +- 自动生成资源索引文件 +- 打包生成最终的 `assets.bin` 文件 + +## 依赖要求 + +- Python 3.6+ +- 相关资源文件 + +## 使用方法 + +### 基本语法 + +```bash +./build.py --wakenet_model \ + --text_font \ + --emoji_collection +``` + +### 参数说明 + +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `--wakenet_model` | 目录路径 | 否 | 唤醒网络模型目录路径 | +| `--text_font` | 文件路径 | 否 | 文本字体文件路径 | +| `--emoji_collection` | 目录路径 | 否 | 表情符号图片集合目录路径 | + +### 使用示例 + +```bash +# 完整参数示例 +./build.py \ + --wakenet_model ../../managed_components/espressif__esp-sr/model/wakenet_model/wn9_nihaoxiaozhi_tts \ + --text_font ../../components/xiaozhi-fonts/build/font_puhui_common_20_4.bin \ + --emoji_collection ../../components/xiaozhi-fonts/build/emojis_64/ + +# 仅处理字体文件 +./build.py --text_font ../../components/xiaozhi-fonts/build/font_puhui_common_20_4.bin + +# 仅处理表情符号 +./build.py --emoji_collection ../../components/xiaozhi-fonts/build/emojis_64/ +``` + +## 工作流程 + +1. **创建构建目录结构** + - `build/` - 主构建目录 + - `build/assets/` - 资源文件目录 + - `build/output/` - 输出文件目录 + +2. **处理唤醒网络模型** + - 复制模型文件到构建目录 + - 使用 `pack_model.py` 生成 `srmodels.bin` + - 将生成的模型文件复制到资源目录 + +3. **处理文本字体** + - 复制字体文件到资源目录 + - 支持 `.bin` 格式的字体文件 + +4. **处理表情符号集合** + - 扫描指定目录中的图片文件 + - 支持 `.png` 和 `.gif` 格式 + - 自动生成表情符号索引 + +5. **生成配置文件** + - `index.json` - 资源索引文件 + - `config.json` - 构建配置文件 + +6. **打包最终资源** + - 使用 `spiffs_assets_gen.py` 生成 `assets.bin` + - 复制到构建根目录 + +## 输出文件 + +构建完成后,会在 `build/` 目录下生成以下文件: + +- `assets/` - 所有资源文件 +- `assets.bin` - 最终的 SPIFFS 资源文件 +- `config.json` - 构建配置 +- `output/` - 中间输出文件 + +## 支持的资源格式 + +- **模型文件**: `.bin` (通过 pack_model.py 处理) +- **字体文件**: `.bin` +- **图片文件**: `.png`, `.gif` +- **配置文件**: `.json` + +## 错误处理 + +脚本包含完善的错误处理机制: + +- 检查源文件/目录是否存在 +- 验证子进程执行结果 +- 提供详细的错误信息和警告 + +## 注意事项 + +1. 确保所有依赖的 Python 脚本都在同一目录下 +2. 资源文件路径使用绝对路径或相对于脚本目录的路径 +3. 构建过程会清理之前的构建文件 +4. 生成的 `assets.bin` 文件大小受 SPIFFS 分区大小限制 diff --git a/scripts/spiffs_assets/build.py b/scripts/spiffs_assets/build.py new file mode 100644 index 0000000..45cb05c --- /dev/null +++ b/scripts/spiffs_assets/build.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python3 +""" +Build the spiffs assets partition + +Usage: + ./build.py --wakenet_model \ + --text_font \ + --emoji_collection + +Example: + ./build.py --wakenet_model ../../managed_components/espressif__esp-sr/model/wakenet_model/wn9_nihaoxiaozhi_tts \ + --text_font ../../components/xiaozhi-fonts/build/font_puhui_common_20_4.bin \ + --emoji_collection ../../components/xiaozhi-fonts/build/emojis_64/ +""" + +import os +import sys +import shutil +import argparse +import subprocess +import json +from pathlib import Path + + +def ensure_dir(directory): + """Ensure directory exists, create if not""" + os.makedirs(directory, exist_ok=True) + + +def copy_file(src, dst): + """Copy file""" + if os.path.exists(src): + shutil.copy2(src, dst) + print(f"Copied: {src} -> {dst}") + else: + print(f"Warning: Source file does not exist: {src}") + + +def copy_directory(src, dst): + """Copy directory""" + if os.path.exists(src): + shutil.copytree(src, dst, dirs_exist_ok=True) + print(f"Copied directory: {src} -> {dst}") + else: + print(f"Warning: Source directory does not exist: {src}") + + +def process_wakenet_model(wakenet_model_dir, build_dir, assets_dir): + """Process wakenet_model parameter""" + if not wakenet_model_dir: + return None + + # Copy input directory to build directory + wakenet_build_dir = os.path.join(build_dir, "wakenet_model") + if os.path.exists(wakenet_build_dir): + shutil.rmtree(wakenet_build_dir) + copy_directory(wakenet_model_dir, os.path.join(wakenet_build_dir, os.path.basename(wakenet_model_dir))) + + # Use pack_model.py to generate srmodels.bin + srmodels_output = os.path.join(wakenet_build_dir, "srmodels.bin") + try: + subprocess.run([ + sys.executable, "pack_model.py", + "-m", wakenet_build_dir, + "-o", "srmodels.bin" + ], check=True, cwd=os.path.dirname(__file__)) + print(f"Generated: {srmodels_output}") + # Copy srmodels.bin to assets directory + copy_file(srmodels_output, os.path.join(assets_dir, "srmodels.bin")) + return "srmodels.bin" + except subprocess.CalledProcessError as e: + print(f"Error: Failed to generate srmodels.bin: {e}") + return None + + +def process_text_font(text_font_file, assets_dir): + """Process text_font parameter""" + if not text_font_file: + return None + + # Copy input file to build/assets directory + font_filename = os.path.basename(text_font_file) + font_dst = os.path.join(assets_dir, font_filename) + copy_file(text_font_file, font_dst) + + return font_filename + + +def process_emoji_collection(emoji_collection_dir, assets_dir): + """Process emoji_collection parameter""" + if not emoji_collection_dir: + return [] + + emoji_list = [] + + # Copy each image from input directory to build/assets directory + for root, dirs, files in os.walk(emoji_collection_dir): + for file in files: + if file.lower().endswith(('.png', '.gif')): + # Copy file + src_file = os.path.join(root, file) + dst_file = os.path.join(assets_dir, file) + copy_file(src_file, dst_file) + + # Get filename without extension + filename_without_ext = os.path.splitext(file)[0] + + # Add to emoji list + emoji_list.append({ + "name": filename_without_ext, + "file": file + }) + + return emoji_list + +def load_emoji_config(emoji_collection_dir): + """Load emoji config from config.json file""" + config_path = os.path.join(emoji_collection_dir, "emote.json") + if not os.path.exists(config_path): + print(f"Warning: Config file not found: {config_path}") + return {} + + try: + with open(config_path, 'r', encoding='utf-8') as f: + config_data = json.load(f) + + # Convert list format to dict for easy lookup + config_dict = {} + for item in config_data: + if "emote" in item: + config_dict[item["emote"]] = item + + return config_dict + except Exception as e: + print(f"Error loading config file {config_path}: {e}") + return {} + +def process_board_emoji_collection(emoji_collection_dir, target_board_dir, assets_dir): + """Process emoji_collection parameter""" + if not emoji_collection_dir: + return [] + + emoji_config = load_emoji_config(target_board_dir) + print(f"Loaded emoji config with {len(emoji_config)} entries") + + emoji_list = [] + + for emote_name, config in emoji_config.items(): + + if "src" not in config: + print(f"Error: No src field found for emote '{emote_name}' in config") + continue + + eaf_file_path = os.path.join(emoji_collection_dir, config["src"]) + file_exists = os.path.exists(eaf_file_path) + + if not file_exists: + print(f"Warning: EAF file not found for emote '{emote_name}': {eaf_file_path}") + else: + # Copy eaf file to assets directory + copy_file(eaf_file_path, os.path.join(assets_dir, config["src"])) + + # Create emoji entry with src as file (merge file and src) + emoji_entry = { + "name": emote_name, + "file": config["src"] # Use src as the actual file + } + + eaf_properties = {} + + if not file_exists: + eaf_properties["lack"] = True + + if "loop" in config: + eaf_properties["loop"] = config["loop"] + + if "fps" in config: + eaf_properties["fps"] = config["fps"] + + if eaf_properties: + emoji_entry["eaf"] = eaf_properties + + status = "MISSING" if not file_exists else "OK" + eaf_info = emoji_entry.get('eaf', {}) + print(f"emote '{emote_name}': file='{emoji_entry['file']}', status={status}, lack={eaf_info.get('lack', False)}, loop={eaf_info.get('loop', 'none')}, fps={eaf_info.get('fps', 'none')}") + + emoji_list.append(emoji_entry) + + print(f"Successfully processed {len(emoji_list)} emotes from config") + return emoji_list + +def process_board_icon_collection(icon_collection_dir, assets_dir): + """Process emoji_collection parameter""" + if not icon_collection_dir: + return [] + + icon_list = [] + + for root, dirs, files in os.walk(icon_collection_dir): + for file in files: + if file.lower().endswith(('.bin')) or file.lower() == 'listen.eaf': + src_file = os.path.join(root, file) + dst_file = os.path.join(assets_dir, file) + copy_file(src_file, dst_file) + + filename_without_ext = os.path.splitext(file)[0] + + icon_list.append({ + "name": filename_without_ext, + "file": file + }) + + return icon_list +def process_board_layout(layout_json_file, assets_dir): + """Process layout_json parameter""" + if not layout_json_file: + print(f"Warning: Layout json file not provided") + return [] + + print(f"Processing layout_json: {layout_json_file}") + print(f"assets_dir: {assets_dir}") + + if os.path.isdir(layout_json_file): + layout_json_path = os.path.join(layout_json_file, "layout.json") + if not os.path.exists(layout_json_path): + print(f"Warning: layout.json not found in directory: {layout_json_file}") + return [] + layout_json_file = layout_json_path + elif not os.path.isfile(layout_json_file): + print(f"Warning: Layout json file not found: {layout_json_file}") + return [] + + try: + with open(layout_json_file, 'r', encoding='utf-8') as f: + layout_data = json.load(f) + + # Layout data is now directly an array, no need to get "layout" key + layout_items = layout_data if isinstance(layout_data, list) else layout_data.get("layout", []) + + processed_layout = [] + for item in layout_items: + processed_item = { + "name": item.get("name", ""), + "align": item.get("align", ""), + "x": item.get("x", 0), + "y": item.get("y", 0) + } + + if "width" in item: + processed_item["width"] = item["width"] + if "height" in item: + processed_item["height"] = item["height"] + + processed_layout.append(processed_item) + + print(f"Processed {len(processed_layout)} layout elements") + return processed_layout + + except Exception as e: + print(f"Error reading/processing layout.json: {e}") + return [] + +def process_board_collection(target_board_dir, res_path, assets_dir): + """Process board collection - merge icon, emoji, and layout processing""" + + # Process all collections + if os.path.exists(res_path) and os.path.exists(target_board_dir): + emoji_collection = process_board_emoji_collection(res_path, target_board_dir, assets_dir) + icon_collection = process_board_icon_collection(res_path, assets_dir) + layout_json = process_board_layout(target_board_dir, assets_dir) + else: + print(f"Warning: EAF directory not found: {res_path} or {target_board_dir}") + emoji_collection = [] + icon_collection = [] + layout_json = [] + + return emoji_collection, icon_collection, layout_json + +def generate_index_json(assets_dir, srmodels, text_font, emoji_collection, icon_collection, layout_json): + """Generate index.json file""" + index_data = { + "version": 1 + } + + if srmodels: + index_data["srmodels"] = srmodels + + if text_font: + index_data["text_font"] = text_font + + if emoji_collection: + index_data["emoji_collection"] = emoji_collection + + if icon_collection: + index_data["icon_collection"] = icon_collection + + if layout_json: + index_data["layout"] = layout_json + + # Write index.json + index_path = os.path.join(assets_dir, "index.json") + with open(index_path, 'w', encoding='utf-8') as f: + json.dump(index_data, f, indent=4, ensure_ascii=False) + + print(f"Generated: {index_path}") + + +def generate_config_json(build_dir, assets_dir): + """Generate config.json file""" + # Get absolute path of current working directory + workspace_dir = os.path.abspath(os.path.join(os.path.dirname(__file__))) + + config_data = { + "include_path": os.path.join(workspace_dir, "build/include"), + "assets_path": os.path.join(workspace_dir, "build/assets"), + "image_file": os.path.join(workspace_dir, "build/output/assets.bin"), + "lvgl_ver": "9.3.0", + "assets_size": "0x400000", + "support_format": ".png, .gif, .jpg, .bin, .json, .eaf", + "name_length": "32", + "split_height": "0", + "support_qoi": False, + "support_spng": False, + "support_sjpg": False, + "support_sqoi": False, + "support_raw": False, + "support_raw_dither": False, + "support_raw_bgr": False + } + + # Write config.json + config_path = os.path.join(build_dir, "config.json") + with open(config_path, 'w', encoding='utf-8') as f: + json.dump(config_data, f, indent=4, ensure_ascii=False) + + print(f"Generated: {config_path}") + return config_path + + +def main(): + parser = argparse.ArgumentParser(description='Build the spiffs assets partition') + parser.add_argument('--wakenet_model', help='Path to wakenet model directory') + parser.add_argument('--text_font', help='Path to text font file') + parser.add_argument('--emoji_collection', help='Path to emoji collection directory') + + parser.add_argument('--res_path', help='Path to res directory') + parser.add_argument('--target_board', help='Path to target board directory') + + args = parser.parse_args() + + # Get script directory + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # Set directory paths + build_dir = os.path.join(script_dir, "build") + assets_dir = os.path.join(build_dir, "assets") + if os.path.exists(assets_dir): + shutil.rmtree(assets_dir) + + # Ensure directories exist + ensure_dir(build_dir) + ensure_dir(assets_dir) + + print("Starting to build SPIFFS assets partition...") + + # Process each parameter + srmodels = process_wakenet_model(args.wakenet_model, build_dir, assets_dir) + text_font = process_text_font(args.text_font, assets_dir) + + if(args.target_board): + emoji_collection, icon_collection, layout_json = process_board_collection(args.target_board, args.res_path, assets_dir) + else: + emoji_collection = process_emoji_collection(args.emoji_collection, assets_dir) + icon_collection = [] + layout_json = [] + + # Generate index.json + generate_index_json(assets_dir, srmodels, text_font, emoji_collection, icon_collection, layout_json) + + # Generate config.json + config_path = generate_config_json(build_dir, assets_dir) + + # Use spiffs_assets_gen.py to package final build/assets.bin + try: + subprocess.run([ + sys.executable, "spiffs_assets_gen.py", + "--config", config_path + ], check=True, cwd=script_dir) + print("Successfully packaged assets.bin") + except subprocess.CalledProcessError as e: + print(f"Error: Failed to package assets.bin: {e}") + sys.exit(1) + + # Copy build/output/assets.bin to build/assets.bin + shutil.copy(os.path.join(build_dir, "output", "assets.bin"), os.path.join(build_dir, "assets.bin")) + print("Build completed!") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/spiffs_assets/build_all.py b/scripts/spiffs_assets/build_all.py new file mode 100644 index 0000000..ca02d64 --- /dev/null +++ b/scripts/spiffs_assets/build_all.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +""" +Build multiple spiffs assets partitions with different parameter combinations + +This script calls build.py with different combinations of: +- wakenet_models +- text_fonts +- emoji_collections + +And generates assets.bin files with names like: +wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-emojis_32.bin +""" + +import os +import sys +import shutil +import subprocess +import argparse +from pathlib import Path + + +def ensure_dir(directory): + """Ensure directory exists, create if not""" + os.makedirs(directory, exist_ok=True) + + +def get_file_path(base_dir, filename): + """Get full path for a file, handling 'none' case""" + if filename == "none": + return None + return os.path.join(base_dir, f"{filename}.bin" if not filename.startswith("emojis_") else filename) + + +def build_assets(wakenet_model, text_font, emoji_collection, target_board, build_dir, final_dir): + """Build assets.bin using build.py with given parameters""" + + # Prepare arguments for build.py + cmd = [sys.executable, "build.py"] + + if wakenet_model != "none": + wakenet_path = os.path.join("../../managed_components/espressif__esp-sr/model/wakenet_model", wakenet_model) + cmd.extend(["--wakenet_model", wakenet_path]) + + if text_font != "none": + text_font_path = os.path.join("../../components/78__xiaozhi-fonts/cbin", f"{text_font}.bin") + cmd.extend(["--text_font", text_font_path]) + + if emoji_collection != "none": + emoji_path = os.path.join("../../components/xiaozhi-fonts/build", emoji_collection) + cmd.extend(["--emoji_collection", emoji_path]) + + if target_board != "none": + res_path = os.path.join("../../managed_components/espressif2022__esp_emote_gfx/emoji_large", "") + cmd.extend(["--res_path", res_path]) + + target_board_path = os.path.join("../../main/boards/", f"{target_board}") + cmd.extend(["--target_board", target_board_path]) + + print(f"\n正在构建: {wakenet_model}-{text_font}-{emoji_collection}-{target_board}") + print(f"执行命令: {' '.join(cmd)}") + + try: + # Run build.py + result = subprocess.run(cmd, check=True, cwd=os.path.dirname(__file__)) + + # Generate output filename + if(target_board != "none"): + output_name = f"{wakenet_model}-{text_font}-{target_board}.bin" + else: + output_name = f"{wakenet_model}-{text_font}-{emoji_collection}.bin" + + # Copy generated assets.bin to final directory with new name + src_path = os.path.join(build_dir, "assets.bin") + dst_path = os.path.join(final_dir, output_name) + + if os.path.exists(src_path): + shutil.copy2(src_path, dst_path) + print(f"✓ 成功生成: {output_name}") + return True + else: + print(f"✗ 错误: 未找到生成的 assets.bin 文件") + return False + + except subprocess.CalledProcessError as e: + print(f"✗ 构建失败: {e}") + return False + except Exception as e: + print(f"✗ 未知错误: {e}") + return False + + +def main(): + # Parse command line arguments + parser = argparse.ArgumentParser(description='构建多个 SPIFFS assets 分区') + parser.add_argument('--mode', + choices=['emoji_collections', 'emoji_target_boards'], + default='emoji_collections', + help='选择运行模式: emoji_collections 或 emoji_target_boards (默认: emoji_collections)') + + args = parser.parse_args() + + # Configuration + wakenet_models = [ + "none", + "wn9_nihaoxiaozhi_tts", + "wn9s_nihaoxiaozhi" + ] + + text_fonts = [ + "none", + "font_puhui_common_14_1", + "font_puhui_common_16_4", + "font_puhui_common_20_4", + "font_puhui_common_30_4", + ] + + emoji_collections = [ + "none", + "emojis_32", + "emojis_64", + ] + + emoji_target_boards = [ + "esp-box-3", + "echoear", + ] + + # Get script directory + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # Set directory paths + build_dir = os.path.join(script_dir, "build") + final_dir = os.path.join(build_dir, "final") + + # Ensure directories exist + ensure_dir(build_dir) + ensure_dir(final_dir) + + print("开始构建多个 SPIFFS assets 分区...") + print(f"运行模式: {args.mode}") + print(f"输出目录: {final_dir}") + + # Track successful builds + successful_builds = 0 + + if args.mode == 'emoji_collections': + # Calculate total combinations for emoji_collections mode + total_combinations = len(wakenet_models) * len(text_fonts) * len(emoji_collections) + + # Build all combinations with emoji_collections + for wakenet_model in wakenet_models: + for text_font in text_fonts: + for emoji_collection in emoji_collections: + if build_assets(wakenet_model, text_font, emoji_collection, "none", build_dir, final_dir): + successful_builds += 1 + + elif args.mode == 'emoji_target_boards': + # Calculate total combinations for emoji_target_boards mode + total_combinations = len(wakenet_models) * len(text_fonts) * len(emoji_target_boards) + + # Build all combinations with emoji_target_boards + for wakenet_model in wakenet_models: + for text_font in text_fonts: + for emoji_target_board in emoji_target_boards: + if build_assets(wakenet_model, text_font, "none", emoji_target_board, build_dir, final_dir): + successful_builds += 1 + + print(f"\n构建完成!") + print(f"成功构建: {successful_builds}/{total_combinations}") + print(f"输出文件位置: {final_dir}") + + # List generated files + if os.path.exists(final_dir): + files = [f for f in os.listdir(final_dir) if f.endswith('.bin')] + if files: + print("\n生成的文件:") + for file in sorted(files): + file_size = os.path.getsize(os.path.join(final_dir, file)) + print(f" {file} ({file_size:,} bytes)") + else: + print("\n未找到生成的 .bin 文件") + + +if __name__ == "__main__": + main() + + diff --git a/scripts/spiffs_assets/pack_model.py b/scripts/spiffs_assets/pack_model.py new file mode 100644 index 0000000..a6cfec0 --- /dev/null +++ b/scripts/spiffs_assets/pack_model.py @@ -0,0 +1,123 @@ +import os +import struct +import argparse + + +def struct_pack_string(string, max_len=None): + """ + pack string to binary data. + if max_len is None, max_len = len(string) + 1 + else len(string) < max_len, the left will be padded by struct.pack('x') + + string: input python string + max_len: output + """ + + if max_len == None : + max_len = len(string) + else: + assert len(string) <= max_len + + left_num = max_len - len(string) + out_bytes = None + for char in string: + if out_bytes == None: + out_bytes = struct.pack('b', ord(char)) + else: + out_bytes += struct.pack('b', ord(char)) + for i in range(left_num): + out_bytes += struct.pack('x') + return out_bytes + +def read_data(filename): + """ + Read binary data, like index and mndata + """ + data = None + with open(filename, "rb") as f: + data = f.read() + return data + +def pack_models(model_path, out_file="srmodels.bin"): + """ + Pack all models into one binary file by the following format: + { + model_num: int + model1_info: model_info_t + model2_info: model_info_t + ... + model1_index,model1_data,model1_MODEL_INFO + model1_index,model1_data,model1_MODEL_INFO + ... + }model_pack_t + + { + model_name: char[32] + file_number: int + file1_name: char[32] + file1_start: int + file1_len: int + file2_name: char[32] + file2_start: int // data_len = info_start - data_start + file2_len: int + ... + }model_info_t + + model_path: the path of models + out_file: the ouput binary filename + """ + + models = {} + file_num = 0 + model_num = 0 + for root, dirs, _ in os.walk(model_path): + for model_name in dirs: + models[model_name] = {} + model_dir = os.path.join(root, model_name) + model_num += 1 + for _, _, files in os.walk(model_dir): + for file_name in files: + file_num += 1 + file_path = os.path.join(model_dir, file_name) + models[model_name][file_name] = read_data(file_path) + + model_num = len(models) + header_len = 4 + model_num*(32+4) + file_num*(32+4+4) + out_bin = struct.pack('I', model_num) # model number + data_bin = None + for key in models: + model_bin = struct_pack_string(key, 32) # + model name + model_bin += struct.pack('I', len(models[key])) # + file number in this model + + for file_name in models[key]: + model_bin += struct_pack_string(file_name, 32) # + file name + if data_bin == None: + model_bin += struct.pack('I', header_len) + data_bin = models[key][file_name] + model_bin += struct.pack('I', len(models[key][file_name])) + # print(file_name, header_len, len(models[key][file_name]), len(data_bin)) + else: + model_bin += struct.pack('I', header_len+len(data_bin)) + # print(file_name, header_len+len(data_bin), len(models[key][file_name])) + data_bin += models[key][file_name] + model_bin += struct.pack('I', len(models[key][file_name])) + + out_bin += model_bin + assert len(out_bin) == header_len + if data_bin != None: + out_bin += data_bin + + out_file = os.path.join(model_path, out_file) + with open(out_file, "wb") as f: + f.write(out_bin) + + +if __name__ == "__main__": + # input parameter + parser = argparse.ArgumentParser(description='Model package tool') + parser.add_argument('-m', '--model_path', help="the path of model files") + parser.add_argument('-o', '--out_file', default="srmodels.bin", help="the path of binary file") + args = parser.parse_args() + + # convert(args.model_path, args.out_file) + pack_models(model_path=args.model_path, out_file=args.out_file) diff --git a/scripts/spiffs_assets/spiffs_assets_gen.py b/scripts/spiffs_assets/spiffs_assets_gen.py new file mode 100644 index 0000000..f0b5dff --- /dev/null +++ b/scripts/spiffs_assets/spiffs_assets_gen.py @@ -0,0 +1,647 @@ +# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import io +import os +import argparse +import json +import shutil +import math +import sys +import time +import numpy as np +import importlib +import subprocess +import urllib.request + +from PIL import Image +from datetime import datetime +from dataclasses import dataclass +from typing import List +from pathlib import Path +from packaging import version + +sys.dont_write_bytecode = True + +GREEN = '\033[1;32m' +RED = '\033[1;31m' +RESET = '\033[0m' + +@dataclass +class AssetCopyConfig: + assets_path: str + target_path: str + spng_enable: bool + sjpg_enable: bool + qoi_enable: bool + sqoi_enable: bool + row_enable: bool + support_format: List[str] + split_height: int + +@dataclass +class PackModelsConfig: + target_path: str + include_path: str + image_file: str + assets_path: str + name_length: int + +def generate_header_filename(path): + asset_name = os.path.basename(path) + + header_filename = f'mmap_generate_{asset_name}.h' + return header_filename + +def compute_checksum(data): + checksum = sum(data) & 0xFFFF + return checksum + +def sort_key(filename): + basename, extension = os.path.splitext(filename) + return extension, basename + +def download_v8_script(convert_path): + """ + Ensure that the lvgl_image_converter repository is present at the specified path. + If not, clone the repository. Then, checkout to a specific commit. + + Parameters: + - convert_path (str): The directory path where lvgl_image_converter should be located. + """ + + # Check if convert_path is not empty + if convert_path: + # If the directory does not exist, create it and clone the repository + if not os.path.exists(convert_path): + os.makedirs(convert_path, exist_ok=True) + try: + subprocess.run( + ['git', 'clone', 'https://github.com/W-Mai/lvgl_image_converter.git', convert_path], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True + ) + except subprocess.CalledProcessError as e: + print(f'Git clone failed: {e}') + sys.exit(1) + + # Checkout to the specific commit + try: + subprocess.run( + ['git', 'checkout', '9174634e9dcc1b21a63668969406897aad650f35'], + cwd=convert_path, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True + ) + except subprocess.CalledProcessError as e: + print(f'Failed to checkout to the specific commit: {e}') + sys.exit(1) + else: + print('Error: convert_path is NULL') + sys.exit(1) + +def download_v9_script(url: str, destination: str) -> None: + """ + Download a Python script from a URL to a local destination. + + Parameters: + - url (str): URL to download the script from. + - destination (str): Local path to save the downloaded script. + + Raises: + - Exception: If the download fails. + """ + file_path = Path(destination) + + # Check if the file already exists + if file_path.exists(): + if file_path.is_file(): + return + + try: + # Create the parent directories if they do not exist + file_path.parent.mkdir(parents=True, exist_ok=True) + + # Open the URL and retrieve the data + with urllib.request.urlopen(url) as response, open(file_path, 'wb') as out_file: + data = response.read() # Read the entire response + out_file.write(data) # Write data to the local file + + except urllib.error.HTTPError as e: + print(f'HTTP Error: {e.code} - {e.reason} when accessing {url}') + sys.exit(1) + except urllib.error.URLError as e: + print(f'URL Error: {e.reason} when accessing {url}') + sys.exit(1) + except Exception as e: + print(f'An unexpected error occurred: {e}') + sys.exit(1) + +def split_image(im, block_size, input_dir, ext, convert_to_qoi): + """Splits the image into blocks based on the block size.""" + width, height = im.size + + if block_size: + splits = math.ceil(height / block_size) + else: + splits = 1 + + for i in range(splits): + if i < splits - 1: + crop = im.crop((0, i * block_size, width, (i + 1) * block_size)) + else: + crop = im.crop((0, i * block_size, width, height)) + + output_path = os.path.join(input_dir, str(i) + ext) + crop.save(output_path, quality=100) + + qoi_module = importlib.import_module('qoi-conv.qoi') + Qoi = qoi_module.Qoi + replace_extension = qoi_module.replace_extension + + if convert_to_qoi: + with Image.open(output_path) as img: + if img.mode != 'RGBA': + img = img.convert('RGBA') + + img_data = np.asarray(img) + out_path = qoi_module.replace_extension(output_path, 'qoi') + new_image = qoi_module.Qoi().save(out_path, img_data) + os.remove(output_path) + + + return width, height, splits + +def create_header(width, height, splits, split_height, lenbuf, ext): + """Creates the header for the output file based on the format.""" + header = bytearray() + + if ext.lower() == '.jpg': + header += bytearray('_SJPG__'.encode('UTF-8')) + elif ext.lower() == '.png': + header += bytearray('_SPNG__'.encode('UTF-8')) + elif ext.lower() == '.qoi': + header += bytearray('_SQOI__'.encode('UTF-8')) + + # 6 BYTES VERSION + header += bytearray(('\x00V1.00\x00').encode('UTF-8')) + + # WIDTH 2 BYTES + header += width.to_bytes(2, byteorder='little') + + # HEIGHT 2 BYTES + header += height.to_bytes(2, byteorder='little') + + # NUMBER OF ITEMS 2 BYTES + header += splits.to_bytes(2, byteorder='little') + + # SPLIT HEIGHT 2 BYTES + header += split_height.to_bytes(2, byteorder='little') + + for item_len in lenbuf: + # LENGTH 2 BYTES + header += item_len.to_bytes(2, byteorder='little') + + return header + +def save_image(output_file_path, header, split_data): + """Saves the image with the constructed header and split data.""" + with open(output_file_path, 'wb') as f: + if header is not None: + f.write(header + split_data) + else: + f.write(split_data) + +def handle_lvgl_version_v9(input_file: str, input_dir: str, + input_filename: str, convert_path: str) -> None: + """ + Handle conversion for LVGL versions greater than 9.0. + + Parameters: + - input_file (str): Path to the input image file. + - input_dir (str): Directory of the input file. + - input_filename (str): Name of the input file. + - convert_path (str): Path for conversion scripts and outputs. + """ + + convert_file = os.path.join(convert_path, 'LVGLImage.py') + lvgl_image_url = 'https://raw.githubusercontent.com/lvgl/lvgl/master/scripts/LVGLImage.py' + + download_v9_script(url=lvgl_image_url, destination=convert_file) + lvgl_script = Path(convert_file) + + cmd = [ + 'python', + str(lvgl_script), + '--ofmt', 'BIN', + '--cf', config_data['support_raw_cf'], + '--compress', 'NONE', + '--output', str(input_dir), + input_file + ] + + try: + result = subprocess.run( + cmd, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + print(f'Completed {input_filename} -> BIN') + except subprocess.CalledProcessError as e: + print('An error occurred while executing LVGLImage.py:') + print(e.stderr) + sys.exit(e.returncode) + +def handle_lvgl_version_v8(input_file: str, input_dir: str, input_filename: str, convert_path: str) -> None: + """ + Handle conversion for supported LVGL versions (<= 9.0). + + Parameters: + - input_file (str): Path to the input image file. + - input_dir (str): Directory of the input file. + - input_filename (str): Name of the input file. + - convert_path (str): Path for conversion scripts and outputs. + """ + + download_v8_script(convert_path=convert_path) + + if convert_path not in sys.path: + sys.path.append(convert_path) + + try: + import lv_img_conv + except ImportError as e: + print(f"Failed to import 'lv_img_conv' from '{convert_path}': {e}") + sys.exit(1) + + try: + lv_img_conv.conv_one_file( + root=Path(input_dir), + filepath=Path(input_file), + f=config_data['support_raw_ff'], + cf=config_data['support_raw_cf'], + ff='BIN', + dither=config_data['support_raw_dither'], + bgr_mode=config_data['support_raw_bgr'], + ) + print(f'Completed {input_filename} -> BIN') + except KeyError as e: + print(f'Missing configuration key: {e}') + sys.exit(1) + except Exception as e: + print(f'An error occurred during conversion: {e}') + sys.exit(1) + +def process_image(input_file, height_str, output_extension, convert_to_qoi=False): + """Main function to process the image and save it as .sjpg, .spng, or .sqoi.""" + try: + SPLIT_HEIGHT = int(height_str) + if SPLIT_HEIGHT < 0: + raise ValueError('Height must be a positive integer') + except ValueError as e: + print('Error: Height must be a positive integer') + sys.exit(1) + + input_dir, input_filename = os.path.split(input_file) + base_filename, ext = os.path.splitext(input_filename) + OUTPUT_FILE_NAME = base_filename + + try: + im = Image.open(input_file) + except Exception as e: + print('Error:', e) + sys.exit(0) + + width, height, splits = split_image(im, SPLIT_HEIGHT, input_dir, ext, convert_to_qoi) + + split_data = bytearray() + lenbuf = [] + + if convert_to_qoi: + ext = '.qoi' + + for i in range(splits): + with open(os.path.join(input_dir, str(i) + ext), 'rb') as f: + a = f.read() + split_data += a + lenbuf.append(len(a)) + os.remove(os.path.join(input_dir, str(i) + ext)) + + header = None + if splits == 1 and convert_to_qoi: + output_file_path = os.path.join(input_dir, OUTPUT_FILE_NAME + ext) + else: + header = create_header(width, height, splits, SPLIT_HEIGHT, lenbuf, ext) + output_file_path = os.path.join(input_dir, OUTPUT_FILE_NAME + output_extension) + + save_image(output_file_path, header, split_data) + + print('Completed', input_filename, '->', os.path.basename(output_file_path)) + +def convert_image_to_qoi(input_file, height_str): + process_image(input_file, height_str, '.sqoi', convert_to_qoi=True) + +def convert_image_to_simg(input_file, height_str): + input_dir, input_filename = os.path.split(input_file) + _, ext = os.path.splitext(input_filename) + output_extension = '.sjpg' if ext.lower() == '.jpg' else '.spng' + process_image(input_file, height_str, output_extension, convert_to_qoi=False) + +def convert_image_to_raw(input_file: str) -> None: + """ + Convert an image to raw binary format compatible with LVGL. + + Parameters: + - input_file (str): Path to the input image file. + + Raises: + - FileNotFoundError: If required scripts are not found. + - subprocess.CalledProcessError: If the external conversion script fails. + - KeyError: If required keys are missing in config_data. + """ + input_dir, input_filename = os.path.split(input_file) + _, ext = os.path.splitext(input_filename) + convert_path = os.path.join(os.path.dirname(input_file), 'lvgl_image_converter') + lvgl_ver_str = config_data.get('lvgl_ver', '9.0.0') + + try: + lvgl_version = version.parse(lvgl_ver_str) + except version.InvalidVersion: + print(f'Invalid LVGL version format: {lvgl_ver_str}') + sys.exit(1) + + if lvgl_version >= version.parse('9.0.0'): + handle_lvgl_version_v9( + input_file=input_file, + input_dir=input_dir, + input_filename=input_filename, + convert_path=convert_path + ) + else: + handle_lvgl_version_v8( + input_file=input_file, + input_dir=input_dir, + input_filename=input_filename, + convert_path=convert_path + ) + +def pack_assets(config: PackModelsConfig): + """ + Pack models based on the provided configuration. + """ + + target_path = config.target_path + assets_include_path = config.include_path + out_file = config.image_file + assets_path = config.assets_path + max_name_len = config.name_length + + merged_data = bytearray() + file_info_list = [] + skip_files = ['config.json', 'lvgl_image_converter'] + + file_list = sorted(os.listdir(target_path), key=sort_key) + for filename in file_list: + if filename in skip_files: + continue + + file_path = os.path.join(target_path, filename) + file_name = os.path.basename(file_path) + file_size = os.path.getsize(file_path) + + try: + img = Image.open(file_path) + width, height = img.size + except Exception as e: + # print("Error:", e) + _, file_extension = os.path.splitext(file_path) + if file_extension.lower() in ['.sjpg', '.spng', '.sqoi']: + offset = 14 + with open(file_path, 'rb') as f: + f.seek(offset) + width_bytes = f.read(2) + height_bytes = f.read(2) + width = int.from_bytes(width_bytes, byteorder='little') + height = int.from_bytes(height_bytes, byteorder='little') + else: + width, height = 0, 0 + + file_info_list.append((file_name, len(merged_data), file_size, width, height)) + # Add 0x5A5A prefix to merged_data + merged_data.extend(b'\x5A' * 2) + + with open(file_path, 'rb') as bin_file: + bin_data = bin_file.read() + + merged_data.extend(bin_data) + + total_files = len(file_info_list) + + mmap_table = bytearray() + for file_name, offset, file_size, width, height in file_info_list: + if len(file_name) > int(max_name_len): + print(f'\033[1;33mWarn:\033[0m "{file_name}" exceeds {max_name_len} bytes and will be truncated.') + fixed_name = file_name.ljust(int(max_name_len), '\0')[:int(max_name_len)] + mmap_table.extend(fixed_name.encode('utf-8')) + mmap_table.extend(file_size.to_bytes(4, byteorder='little')) + mmap_table.extend(offset.to_bytes(4, byteorder='little')) + mmap_table.extend(width.to_bytes(2, byteorder='little')) + mmap_table.extend(height.to_bytes(2, byteorder='little')) + + combined_data = mmap_table + merged_data + combined_checksum = compute_checksum(combined_data) + combined_data_length = len(combined_data).to_bytes(4, byteorder='little') + header_data = total_files.to_bytes(4, byteorder='little') + combined_checksum.to_bytes(4, byteorder='little') + final_data = header_data + combined_data_length + combined_data + + with open(out_file, 'wb') as output_bin: + output_bin.write(final_data) + + os.makedirs(assets_include_path, exist_ok=True) + current_year = datetime.now().year + + asset_name = os.path.basename(assets_path) + file_path = os.path.join(assets_include_path, f'mmap_generate_{asset_name}.h') + with open(file_path, 'w') as output_header: + output_header.write('/*\n') + output_header.write(' * SPDX-FileCopyrightText: 2022-{} Espressif Systems (Shanghai) CO LTD\n'.format(current_year)) + output_header.write(' *\n') + output_header.write(' * SPDX-License-Identifier: Apache-2.0\n') + output_header.write(' */\n\n') + output_header.write('/**\n') + output_header.write(' * @file\n') + output_header.write(" * @brief This file was generated by esp_mmap_assets, don't modify it\n") + output_header.write(' */\n\n') + output_header.write('#pragma once\n\n') + output_header.write("#include \"esp_mmap_assets.h\"\n\n") + output_header.write(f'#define MMAP_{asset_name.upper()}_FILES {total_files}\n') + output_header.write(f'#define MMAP_{asset_name.upper()}_CHECKSUM 0x{combined_checksum:04X}\n\n') + output_header.write(f'enum MMAP_{asset_name.upper()}_LISTS {{\n') + + for i, (file_name, _, _, _, _) in enumerate(file_info_list): + enum_name = file_name.replace('.', '_') + output_header.write(f' MMAP_{asset_name.upper()}_{enum_name.upper()} = {i}, /*!< {file_name} */\n') + + output_header.write('};\n') + + print(f'All bin files have been merged into {os.path.basename(out_file)}') + +def copy_assets(config: AssetCopyConfig): + """ + Copy assets to target_path based on the provided configuration. + """ + format_tuple = tuple(config.support_format) + assets_path = config.assets_path + target_path = config.target_path + + for filename in os.listdir(assets_path): + if any(filename.endswith(suffix) for suffix in format_tuple): + source_file = os.path.join(assets_path, filename) + target_file = os.path.join(target_path, filename) + shutil.copyfile(source_file, target_file) + + conversion_map = { + '.jpg': [ + (config.sjpg_enable, convert_image_to_simg), + (config.qoi_enable, convert_image_to_qoi), + ], + '.png': [ + (config.spng_enable, convert_image_to_simg), + (config.qoi_enable, convert_image_to_qoi), + ], + } + + file_ext = os.path.splitext(filename)[1].lower() + conversions = conversion_map.get(file_ext, []) + converted = False + + for enable_flag, convert_func in conversions: + if enable_flag: + convert_func(target_file, config.split_height) + os.remove(target_file) + converted = True + break + + if not converted and config.row_enable: + convert_image_to_raw(target_file) + os.remove(target_file) + else: + print(f'No match found for file: {filename}, format_tuple: {format_tuple}') + +def process_assets_build(config_data): + assets_path = config_data['assets_path'] + image_file = config_data['image_file'] + target_path = os.path.dirname(image_file) + include_path = config_data['include_path'] + name_length = config_data['name_length'] + split_height = config_data['split_height'] + support_format = [fmt.strip() for fmt in config_data['support_format'].split(',')] + + copy_config = AssetCopyConfig( + assets_path=assets_path, + target_path=target_path, + spng_enable=config_data['support_spng'], + sjpg_enable=config_data['support_sjpg'], + qoi_enable=config_data['support_qoi'], + sqoi_enable=config_data['support_sqoi'], + row_enable=config_data['support_raw'], + support_format=support_format, + split_height=split_height + ) + + pack_config = PackModelsConfig( + target_path=target_path, + include_path=include_path, + image_file=image_file, + assets_path=assets_path, + name_length=name_length + ) + + print('--support_format:', support_format) + + if '.jpg' in support_format or '.png' in support_format: + print('--support_spng:', copy_config.spng_enable) + print('--support_sjpg:', copy_config.sjpg_enable) + print('--support_qoi:', copy_config.qoi_enable) + print('--support_raw:', copy_config.row_enable) + + if copy_config.sqoi_enable: + print('--support_sqoi:', copy_config.sqoi_enable) + if copy_config.spng_enable or copy_config.sjpg_enable or copy_config.sqoi_enable: + print('--split_height:', copy_config.split_height) + if copy_config.row_enable: + print('--lvgl_version:', config_data['lvgl_ver']) + + if not os.path.exists(target_path): + os.makedirs(target_path, exist_ok=True) + for filename in os.listdir(target_path): + file_path = os.path.join(target_path, filename) + if os.path.isfile(file_path) or os.path.islink(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + + copy_assets(copy_config) + pack_assets(pack_config) + + total_size = os.path.getsize(os.path.join(target_path, image_file)) + recommended_size = math.ceil(total_size / 1024) + partition_size = math.ceil(int(config_data['assets_size'], 16)) + + print(f'{"Total size:":<30} {GREEN}{total_size / 1024:>8.2f}K ({total_size}){RESET}') + print(f'{"Partition size:":<30} {GREEN}{partition_size / 1024:>8.2f}K ({partition_size}){RESET}') + + if int(config_data['assets_size'], 16) <= total_size: + print(f'Recommended partition size: {GREEN}{recommended_size}K{RESET}') + print(f'{RED}Error:Binary size exceeds partition size.{RESET}') + sys.exit(1) + +def process_assets_merge(config_data): + app_bin_path = config_data['app_bin_path'] + image_file = config_data['image_file'] + target_path = os.path.dirname(image_file) + + combined_bin_path = os.path.join(target_path, 'combined.bin') + append_bin_path = os.path.join(target_path, image_file) + + app_size = os.path.getsize(app_bin_path) + asset_size = os.path.getsize(append_bin_path) + total_size = asset_size + app_size + recommended_size = math.ceil(total_size / 1024) + partition_size = math.ceil(int(config_data['assets_size'], 16)) + + print(f'{"Asset size:":<30} {GREEN}{asset_size / 1024:>8.2f}K ({asset_size}){RESET}') + print(f'{"App size:":<30} {GREEN}{app_size / 1024:>8.2f}K ({app_size}){RESET}') + print(f'{"Total size:":<30} {GREEN}{total_size / 1024:>8.2f}K ({total_size}){RESET}') + print(f'{"Partition size:":<30} {GREEN}{partition_size / 1024:>8.2f}K ({partition_size}){RESET}') + + if total_size > partition_size: + print(f'Recommended partition size: {GREEN}{recommended_size}K{RESET}') + print(f'{RED}Error:Binary size exceeds partition size.{RESET}') + sys.exit(1) + + with open(combined_bin_path, 'wb') as combined_bin: + with open(app_bin_path, 'rb') as app_bin: + combined_bin.write(app_bin.read()) + with open(append_bin_path, 'rb') as img_bin: + combined_bin.write(img_bin.read()) + + shutil.move(combined_bin_path, app_bin_path) + print(f'Append bin created: {os.path.basename(app_bin_path)}') + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Move and Pack assets.') + parser.add_argument('--config', required=True, help='Path to the configuration file') + parser.add_argument('--merge', action='store_true', help='Merge assets with app binary') + args = parser.parse_args() + + with open(args.config, 'r') as f: + config_data = json.load(f) + + if args.merge: + process_assets_merge(config_data) + else: + process_assets_build(config_data) diff --git a/scripts/versions.py b/scripts/versions.py index a110b80..abccf31 100644 --- a/scripts/versions.py +++ b/scripts/versions.py @@ -1,247 +1,247 @@ -#! /usr/bin/env python3 -from dotenv import load_dotenv -load_dotenv() - -import os -import struct -import zipfile -import oss2 -import json -import requests -from requests.exceptions import RequestException - -# 切换到项目根目录 -os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -def get_chip_id_string(chip_id): - return { - 0x0000: "esp32", - 0x0002: "esp32s2", - 0x0005: "esp32c3", - 0x0009: "esp32s3", - 0x000C: "esp32c2", - 0x000D: "esp32c6", - 0x0010: "esp32h2", - 0x0011: "esp32c5", - 0x0012: "esp32p4", - 0x0017: "esp32c5", - }[chip_id] - -def get_flash_size(flash_size): - MB = 1024 * 1024 - return { - 0x00: 1 * MB, - 0x01: 2 * MB, - 0x02: 4 * MB, - 0x03: 8 * MB, - 0x04: 16 * MB, - 0x05: 32 * MB, - 0x06: 64 * MB, - 0x07: 128 * MB, - }[flash_size] - -def get_app_desc(data): - magic = struct.unpack("> 4) - chip_id = get_chip_id_string(app_data[0xC]) - # get segments - segment_count = app_data[0x1] - segments = [] - offset = 0x18 - image_size = 0x18 - for i in range(segment_count): - segment_size = struct.unpack("> 4) + chip_id = get_chip_id_string(app_data[0xC]) + # get segments + segment_count = app_data[0x1] + segments = [] + offset = 0x18 + image_size = 0x18 + for i in range(segment_count): + segment_size = struct.unpack("