From 2f56316c18cf9531f593052cfee8dfb868dd2691 Mon Sep 17 00:00:00 2001 From: Wang Beihong Date: Wed, 11 Mar 2026 20:14:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=88=9D=E4=BB=A3=E7=9A=84?= =?UTF-8?q?=E9=9B=8F=E5=BD=A2=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clangd | 2 + .gitignore | 78 + .vscode/launch.json | 10 + .vscode/settings.json | 21 + CMakeLists.txt | 6 + CONSOLE_SIMPLE_INIT_BLOG_ZH.md | 164 + CONSOLE_SIMPLE_INIT_TUTORIAL.md | 174 + README.md | 219 ++ components/console_user_cmds/CMakeLists.txt | 3 + .../console_user_cmds/console_user_cmds.c | 293 ++ .../include/console_user_cmds.h | 5 + components/i2c_master_messager/CMakeLists.txt | 5 + .../i2c_master_messager/Kconfig.projbuild | 38 + components/i2c_master_messager/README.md | 109 + .../i2c_master_messager/i2c_master_messager.c | 356 ++ .../include/i2c_master_messager.h | 57 + components/io_device_control/CMakeLists.txt | 3 + components/io_device_control/README.md | 50 + .../include/io_device_control.h | 28 + .../io_device_control/io_device_control.c | 106 + components/lvgl_st7735s_use/CMakeLists.txt | 4 + components/lvgl_st7735s_use/README.md | 105 + .../include/lvgl_st7735s_use.h | 56 + .../lvgl_st7735s_use/lvgl_st7735s_use.c | 246 ++ components/mqtt_control/CMakeLists.txt | 3 + .../mqtt_control/include/mqtt_control.h | 59 + components/mqtt_control/mqtt_control.c | 396 ++ components/ui/.eez-project-build | 17 + components/ui/CMakeLists.txt | 5 + components/ui/actions.h | 14 + components/ui/fonts.h | 26 + components/ui/images.c | 5 + components/ui/images.h | 24 + components/ui/screens.c | 215 + components/ui/screens.h | 39 + components/ui/structs.h | 4 + components/ui/styles.c | 6 + components/ui/styles.h | 14 + components/ui/ui.c | 32 + components/ui/ui.h | 21 + components/ui/ui_font_24.c | 3477 +++++++++++++++++ components/ui/vars.c | 36 + components/ui/vars.h | 34 + components/wifi-connect/BLOG.md | 365 ++ components/wifi-connect/CMakeLists.txt | 5 + components/wifi-connect/Kconfig.projbuild | 87 + components/wifi-connect/QUICK_POSTER.md | 28 + components/wifi-connect/README.md | 165 + components/wifi-connect/USER_GUIDE.md | 82 + .../wifi-connect/include/wifi-connect.h | 34 + components/wifi-connect/wifi-connect.c | 1263 ++++++ dependencies.lock | 101 + main/CMakeLists.txt | 4 + main/auto_alerts.c | 144 + main/auto_alerts.h | 45 + main/auto_ctrl_thresholds.c | 199 + main/auto_ctrl_thresholds.h | 41 + main/idf_component.yml | 23 + main/main.c | 780 ++++ main/status_web.c | 584 +++ main/status_web.h | 40 + note.txt | 35 + partitions.csv | 4 + 63 files changed, 10594 insertions(+) create mode 100644 .clangd create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 CMakeLists.txt create mode 100644 CONSOLE_SIMPLE_INIT_BLOG_ZH.md create mode 100644 CONSOLE_SIMPLE_INIT_TUTORIAL.md create mode 100644 README.md create mode 100644 components/console_user_cmds/CMakeLists.txt create mode 100644 components/console_user_cmds/console_user_cmds.c create mode 100644 components/console_user_cmds/include/console_user_cmds.h create mode 100644 components/i2c_master_messager/CMakeLists.txt create mode 100644 components/i2c_master_messager/Kconfig.projbuild create mode 100644 components/i2c_master_messager/README.md create mode 100644 components/i2c_master_messager/i2c_master_messager.c create mode 100644 components/i2c_master_messager/include/i2c_master_messager.h create mode 100644 components/io_device_control/CMakeLists.txt create mode 100644 components/io_device_control/README.md create mode 100644 components/io_device_control/include/io_device_control.h create mode 100644 components/io_device_control/io_device_control.c create mode 100644 components/lvgl_st7735s_use/CMakeLists.txt create mode 100644 components/lvgl_st7735s_use/README.md create mode 100644 components/lvgl_st7735s_use/include/lvgl_st7735s_use.h create mode 100644 components/lvgl_st7735s_use/lvgl_st7735s_use.c create mode 100644 components/mqtt_control/CMakeLists.txt create mode 100644 components/mqtt_control/include/mqtt_control.h create mode 100644 components/mqtt_control/mqtt_control.c create mode 100644 components/ui/.eez-project-build create mode 100644 components/ui/CMakeLists.txt create mode 100644 components/ui/actions.h create mode 100644 components/ui/fonts.h create mode 100644 components/ui/images.c create mode 100644 components/ui/images.h create mode 100644 components/ui/screens.c create mode 100644 components/ui/screens.h create mode 100644 components/ui/structs.h create mode 100644 components/ui/styles.c create mode 100644 components/ui/styles.h create mode 100644 components/ui/ui.c create mode 100644 components/ui/ui.h create mode 100644 components/ui/ui_font_24.c create mode 100644 components/ui/vars.c create mode 100644 components/ui/vars.h create mode 100644 components/wifi-connect/BLOG.md create mode 100644 components/wifi-connect/CMakeLists.txt create mode 100644 components/wifi-connect/Kconfig.projbuild create mode 100644 components/wifi-connect/QUICK_POSTER.md create mode 100644 components/wifi-connect/README.md create mode 100644 components/wifi-connect/USER_GUIDE.md create mode 100644 components/wifi-connect/include/wifi-connect.h create mode 100644 components/wifi-connect/wifi-connect.c create mode 100644 dependencies.lock create mode 100644 main/CMakeLists.txt create mode 100644 main/auto_alerts.c create mode 100644 main/auto_alerts.h create mode 100644 main/auto_ctrl_thresholds.c create mode 100644 main/auto_ctrl_thresholds.h create mode 100644 main/idf_component.yml create mode 100644 main/main.c create mode 100644 main/status_web.c create mode 100644 main/status_web.h create mode 100644 note.txt create mode 100644 partitions.csv 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/.gitignore b/.gitignore new file mode 100644 index 0000000..5883e0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,78 @@ +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Directory metadata +.directory + +# Temporary files +*~ +*.swp +*.swo +*.bak +*.tmp + +# Log files +*.log + +# Build artifacts and directories +**/build/ +build/ +*.o +*.a +*.out +*.exe # For any host-side utilities compiled on Windows + +# ESP-IDF specific build outputs +*.bin +*.elf +*.map +flasher_args.json # Generated in build directory +sdkconfig.old +sdkconfig + +# ESP-IDF dependencies +# For older versions or manual component management +/components/.idf/ +**/components/.idf/ +# For modern ESP-IDF component manager +managed_components/ +# If ESP-IDF tools are installed/referenced locally to the project +.espressif/ + +# CMake generated files +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +install_manifest.txt +CTestTestfile.cmake + +# Python environment files +*.pyc +*.pyo +*.pyd +__pycache__/ +*.egg-info/ +dist/ + +# Virtual environment folders +venv/ +.venv/ +env/ + +# Language Servers +.clangd/ +.ccls-cache/ +compile_commands.json + +# Windows specific +Thumbs.db +ehthumbs.db +Desktop.ini + +# User-specific configuration files +*.user +*.workspace # General workspace files, can be from various tools +*.suo # Visual Studio Solution User Options +*.sln.docstates # Visual Studio diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..95f6def --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..62de6ce --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + "C_Cpp.intelliSenseEngine": "default", + "idf.openOcdConfigs": [ + "board/esp32c3-builtin.cfg" + ], + "idf.port": "/dev/ttyACM0", + "idf.currentSetup": "/home/beihong/esp/v5.5.2/esp-idf", + "idf.customExtraVars": { + "IDF_TARGET": "esp32c3" + }, + "idf.buildPath": "/home/beihong/esp_projects/BotanicalBuddy/build", + "C_Cpp.default.compileCommands": "/home/beihong/esp_projects/BotanicalBuddy/build/compile_commands.json", + "clangd.path": "/home/beihong/.espressif/tools/esp-clang/esp-19.1.2_20250312/esp-clang/bin/clangd", + "clangd.arguments": [ + "--background-index", + "--query-driver=**", + "--compile-commands-dir=/home/beihong/esp_projects/BotanicalBuddy/build" + ], + "idf.flashType": "UART", + "C_Cpp.default.compilerPath": "/home/beihong/.espressif/tools/riscv32-esp-elf/esp-14.2.0_20251107/riscv32-esp-elf/bin/riscv32-esp-elf-g++" +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0b0fec3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(BotanicalBuddy) diff --git a/CONSOLE_SIMPLE_INIT_BLOG_ZH.md b/CONSOLE_SIMPLE_INIT_BLOG_ZH.md new file mode 100644 index 0000000..3392de0 --- /dev/null +++ b/CONSOLE_SIMPLE_INIT_BLOG_ZH.md @@ -0,0 +1,164 @@ +# 用 `console_simple_init` 给 ESP-IDF 项目加一个可交互控制台(中文实践) + +很多时候我们在调 ESP32 项目时,会遇到这种场景: + +- 想临时执行一个命令看状态 +- 想在线触发某个动作(比如开关某个外设) +- 不想每次都改代码、烧录、再看日志 + +这时候,一个可交互的 Console 就非常有价值。 + +这篇文章记录一个通用做法:在任何 ESP-IDF 项目里,用 `espressif/console_simple_init` 快速接入命令行控制台。 + +## 一、为什么用它 + +原生 `esp_console` 功能很完整,但初始化流程相对分散。`console_simple_init` 的价值在于把常用步骤封装成了 4 个 API: + +- `console_cmd_init()`:初始化控制台 +- `console_cmd_user_register()`:注册用户命令 +- `console_cmd_all_register()`:自动注册插件命令 +- `console_cmd_start()`:启动 REPL + +一句话:快速可用,适合先跑通再扩展。 + +## 二、接入步骤 + +### 1. 添加组件依赖 + +```bash +idf.py add-dependency "espressif/console_simple_init^1.1.0" +``` + +或者在 `idf_component.yml` 里手动添加: + +```yaml +dependencies: + idf: ">=5.0" + espressif/console_simple_init: ^1.1.0 +``` + +### 2. 在 CMake 里声明依赖 + +在你的组件 `CMakeLists.txt`(例如 `main/CMakeLists.txt`)里: + +```cmake +idf_component_register( + SRCS "main.c" + INCLUDE_DIRS "." + REQUIRES console_simple_init console +) +``` + +这里建议把 `console` 显式写上,能避免一类常见的 include/IntelliSense 问题(后文会讲)。 + +### 3. 在代码里初始化并注册命令 + +```c +#include +#include "esp_check.h" +#include "console_simple_init.h" + +static int cmd_hello(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("hello from console\n"); + return 0; +} + +void app_main(void) +{ + // 你的项目里需确保 NVS 和默认事件循环已初始化 + + ESP_ERROR_CHECK(console_cmd_init()); + ESP_ERROR_CHECK(console_cmd_user_register("hello", cmd_hello)); + ESP_ERROR_CHECK(console_cmd_all_register()); // 可选 + ESP_ERROR_CHECK(console_cmd_start()); +} +``` + +### 4. 烧录后验证 + +```bash +idf.py flash monitor +``` + +在 `esp>` 提示符输入: + +- `help` +- `hello` + +如果能看到输出,说明接入成功。 + +## 三、一个很常见的坑 + +### 现象 + +- `#include "console_simple_init.h"` 报 include 错 +- 或提示找不到 `esp_console.h` + +### 本质 + +`console_simple_init.h` 会依赖 `esp_console.h`。如果你的组件没有显式依赖 `console`,编辑器索引有时会解析不到。 + +### 解决 + +1. CMake 增加 `REQUIRES console` +2. 执行: + +```bash +idf.py reconfigure +idf.py build +``` + +3. 在 VS Code 刷新索引(Reset IntelliSense Database + Reload Window) + +## 四、另一个常见坑:串口写入超时 + +### 现象 + +Monitor 日志里反复出现: + +- `Writing to serial is timing out...` + +### 本质 + +Console 所用的通道(UART / USB CDC / USB Serial/JTAG)和你当前 monitor 连接端口不一致。 + +### 解决 + +在 `menuconfig` 里把 console 输出通道配置成和你实际连接一致: + +- `Component config -> ESP System Settings -> Channel for console output` + +改完后重新 build + flash。 + +## 五、为什么它适合做“运维入口” + +当项目复杂起来后,你会很自然地需要这些命令: + +- `status`:看系统状态 +- `sensor`:看传感器实时值 +- `pump on/off`:控制执行器 +- `wifi status`:看联网状态 + +有了 console,这些能力都能在不改 UI 的情况下快速加上。 + +## 六、我建议的演进路线 + +1. 先做 1~2 个命令跑通链路 +2. 加参数解析和错误提示 +3. 按模块分组命令(sensor/io/net) +4. 给危险动作加确认机制 + +## 七、总结 + +`console_simple_init` 的优势不是“功能比 `esp_console` 更多”,而是把接入门槛降得很低: + +- 依赖加上 +- 几个 API 调用 +- 很快就能得到可交互的调试入口 + +对于早期开发和现场调试,它能显著减少反复烧录的成本。 + + diff --git a/CONSOLE_SIMPLE_INIT_TUTORIAL.md b/CONSOLE_SIMPLE_INIT_TUTORIAL.md new file mode 100644 index 0000000..4a551fa --- /dev/null +++ b/CONSOLE_SIMPLE_INIT_TUTORIAL.md @@ -0,0 +1,174 @@ +# ESP-IDF `console_simple_init` Tutorial (Project-Independent) + +This guide is a standalone tutorial for adding and using `espressif/console_simple_init` in any ESP-IDF project. + +## 1. What This Component Does + +`console_simple_init` is a convenience wrapper around ESP-IDF console/REPL setup. + +It provides these APIs: + +- `console_cmd_init()` +- `console_cmd_user_register()` +- `console_cmd_all_register()` +- `console_cmd_start()` + +Header: + +```c +#include "console_simple_init.h" +``` + +## 2. Add Dependency + +Run: + +```bash +idf.py add-dependency "espressif/console_simple_init^1.1.0" +``` + +Or edit your component manifest (`idf_component.yml`): + +```yaml +dependencies: + idf: ">=5.0" + espressif/console_simple_init: ^1.1.0 +``` + +## 3. Declare CMake Dependency + +In your component `CMakeLists.txt` (for example `main/CMakeLists.txt`): + +```cmake +idf_component_register( + SRCS "main.c" + INCLUDE_DIRS "." + REQUIRES console_simple_init console +) +``` + +Why include `console` explicitly? + +- `console_simple_init.h` uses `esp_console.h`. +- Explicit `REQUIRES console` avoids include path and IntelliSense issues. + +## 4. Minimal Working Example + +```c +#include +#include "esp_check.h" +#include "console_simple_init.h" + +static int cmd_hello(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("hello from console\n"); + return 0; +} + +void app_main(void) +{ + // Ensure required system init is done in your app: + // - NVS initialized + // - default event loop created + + ESP_ERROR_CHECK(console_cmd_init()); + ESP_ERROR_CHECK(console_cmd_user_register("hello", cmd_hello)); + ESP_ERROR_CHECK(console_cmd_all_register()); // optional + ESP_ERROR_CHECK(console_cmd_start()); +} +``` + +## 5. Preconditions + +Before starting console, make sure your app has initialized: + +- NVS (`nvs_flash_init`) +- default event loop (`esp_event_loop_create_default`) + +If your project already has a network/bootstrap module, these may already be done. + +## 6. How to Use at Runtime + +1. Flash firmware. +2. Open monitor (`idf.py monitor`). +3. At prompt `esp>`, type: + +```text +help +hello +``` + +## 7. Console Channel Selection (Important) + +Pick the console channel to match your physical connection: + +- UART +- USB CDC +- USB Serial/JTAG + +If channel and monitor port do not match, you may see warnings like write timeout when typing commands. + +Configure via `menuconfig`: + +- `Component config -> ESP System Settings -> Channel for console output` + +Then rebuild and flash. + +## 8. Common Issues + +### Issue A: `#include` errors for `console_simple_init.h` or `esp_console.h` + +Checklist: + +- Added dependency in `idf_component.yml` +- Added `REQUIRES console_simple_init console` in `CMakeLists.txt` +- Re-run: + +```bash +idf.py reconfigure +idf.py build +``` + +- Refresh editor index/IntelliSense + +### Issue B: Serial port busy while flashing + +Another monitor/process holds the port. + +- Close monitor first +- Retry `idf.py flash` + +### Issue C: Console starts but input behaves poorly + +Your terminal may not support escape sequences/history editing. + +Use a terminal that supports VT sequences. + +## 9. Next Step Ideas + +- Add commands for system status (`status`) +- Add commands for peripheral control (`pump on`, `light off`) +- Add argument parsing and help text +- Group commands by module for maintainability + +## 10. Quick Command Template + +Use this template to add a command quickly: + +```c +static int cmd_name(int argc, char **argv) +{ + // parse args + // run logic + // print result + return 0; +} + +ESP_ERROR_CHECK(console_cmd_user_register("name", cmd_name)); +``` + +--- + +This tutorial is intentionally generic so it can be reused in any ESP-IDF codebase. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f3b6ea --- /dev/null +++ b/README.md @@ -0,0 +1,219 @@ +# BotanicalBuddy + +基于 ESP-IDF 的智能盆栽系统固件项目(ESP32-C3)。 + +当前结论:单片机端核心功能已完成,可直接联调 App/小程序侧。 + +## 固件完成度 + +- 环境采集:空气温湿度、光照强度 +- 本地显示:LCD + LVGL 多页面轮播 +- 设备控制:风扇、补光灯、加热、制冷(高电平有效) +- 自动控制:阈值 + 回差控制 +- 手动控制:MQTT 远程开关风扇/灯/加热/制冷 +- 模式切换:`auto` / `manual` +- 告警推送:超阈值边沿事件上报 +- 状态上报:周期性遥测(含模式与执行器状态) +- Wi-Fi 配网:SoftAP + Captive Portal +- 状态网页:独立 HTTP 状态看板与 JSON API(端口 8080) + +## 系统架构 + +- `main/`:业务编排、控制循环、MQTT 回调对接 +- `components/wifi-connect/`:配网与路由连接 +- `components/lvgl_st7735s_use/`:LCD 与 LVGL 端口 +- `components/ui/`:界面对象与变量绑定 +- `components/i2c_master_messager/`:AHT30、BH1750 采集 +- `components/io_device_control/`:风扇/补光灯/加热/制冷 GPIO 控制 +- `components/mqtt_control/`:MQTT 连接、发布、控制指令解析 +- `main/auto_ctrl_thresholds.*`:阈值存取与校验 +- `main/auto_alerts.*`:告警判定与回调分发 +- `main/status_web.*`:独立状态网页服务(HTTP 8080) + +## 状态网页(独立于配网页) + +- 配网页面:`http://192.168.4.1`(仅 SoftAP 配网阶段) +- 状态页面:`http://<设备STA_IP>:8080/` +- 状态 API:`http://<设备STA_IP>:8080/api/status` + +说明: +- 两个网页服务独立运行,端口不同、职责不同。 +- 状态页用于运行态观测,不承载 Wi-Fi 配网流程。 + +`/api/status` 当前主要字段: +- `temp`、`hum`、`lux`:传感器字符串值 +- `fan`、`light`、`hot`、`cool`:执行器状态(`on/off`) +- `mode`:控制模式(`auto/manual`) +- `light_on`、`light_off`:光照阈值 +- `hot_on_temp`、`hot_off_temp`:加热温度阈值(摄氏度) +- `cool_on_temp`、`cool_off_temp`:制冷温度阈值(摄氏度) +- `fan_on_hum`、`fan_off_hum`:风扇湿度阈值(%RH) +- `wifi_status`:Wi-Fi 状态(`idle/provisioning/connecting/connected/failed/timeout`) +- `sta_ip`:STA 当前 IP +- `mqtt_connected`:MQTT 连接状态(布尔) +- `i2c_ready`:关键外设初始化状态(布尔) +- `loop_counter`:主循环计数 +- `uptime_ms`:设备运行时长(毫秒) +- `free_heap`、`min_free_heap`、`largest_block`:堆内存指标 +- `app_version`:固件版本字符串 +- `snapshot_update_ms`、`snapshot_update_count`、`snapshot_age_ms`:状态快照时间与更新统计 + +## 运行逻辑 + +1. 上电初始化 Wi-Fi、LCD、传感器、IO。 +2. Wi-Fi 连通后启动 MQTT 与 Console。 +3. 主循环每 1s 执行: + - 采集传感器并刷新 UI 数据。 + - 若 `mode=auto`,按阈值进行风扇/灯/加热/制冷自动控制。 + - 进行告警边沿判定并发布告警消息。 + - 每 5s 发布一次状态遥测消息。 +4. 收到 MQTT 控制消息时: + - 可切模式(`auto/manual`)。 + - 可更新阈值(8 个阈值需同条下发)。 + - 可手动控制风扇/补光灯/加热/制冷开关。 + +## 开发环境 + +- Linux +- ESP-IDF `v5.5.2` +- 目标芯片:`esp32c3` + +## 编译与烧录 + +1. 配置环境变量 +```bash +export IDF_PATH=/home/beihong/esp/v5.5.2/esp-idf +source $IDF_PATH/export.sh +``` + +2. 构建 +```bash +idf.py set-target esp32c3 +idf.py build +``` + +3. 烧录并监视日志 +```bash +idf.py -p /dev/ttyACM0 flash monitor +``` + +## MQTT 协议 + +### ESP32 -> WEX + +1. 告警消息主题:`topic/alert/esp32_iothome_001` + +```json +{ + "metric": "light", + "state": "alarm" +} +``` + +字段: +- `metric`:`light` +- `state`:`normal` 或 `alarm` + +2. 状态消息主题:`topic/sensor/esp32_BotanicalBuddy_001` + +```json +{ + "temp": "34.3", + "hum": "30.5", + "lux": "40", + "fan": "on", + "light": "off", + "hot": "off", + "cool": "off", + "mode": "auto", + "light_on": 100.0, + "light_off": 350.0, + "hot_on_temp": 18.0, + "hot_off_temp": 20.0, + "cool_on_temp": 30.0, + "cool_off_temp": 28.0, + "fan_on_hum": 80.0, + "fan_off_hum": 70.0 +} +``` + +字段: +- `fan`:`on/off` +- `light`:`on/off` +- `hot`:`on/off` +- `cool`:`on/off` +- `mode`:`auto/manual` +- `light_on`:光照低阈值(低于该值自动开灯) +- `light_off`:光照高阈值(高于该值自动关灯) +- `hot_on_temp` / `hot_off_temp`:加热开关阈值(含回差) +- `cool_on_temp` / `cool_off_temp`:制冷开关阈值(含回差) +- `fan_on_hum` / `fan_off_hum`:风扇开关阈值(含回差) + +### WEX -> ESP32 + +控制主题:`topic/control/esp32_BotanicalBuddy_001` + +1. 切换模式 +```json +{ "mode": "manual" } +``` +```json +{ "mode": "auto" } +``` + +2. 手动控制(建议先切到 `manual`) +```json +{ "fan": "on", "light": "off", "hot": "off", "cool": "off" } +``` + +3. 更新自动阈值(8 个字段需同时下发) +```json +{ + "light_on": 100, + "light_off": 350, + "hot_on_temp": 18, + "hot_off_temp": 20, + "cool_on_temp": 30, + "cool_off_temp": 28, + "fan_on_hum": 80, + "fan_off_hum": 70 +} +``` + +4. 混合下发(同一条消息可同时包含模式、阈值、手动开关) +```json +{ + "mode": "auto", + "light_on": 100, + "light_off": 350, + "hot_on_temp": 18, + "hot_off_temp": 20, + "cool_on_temp": 30, + "cool_off_temp": 28, + "fan_on_hum": 80, + "fan_off_hum": 70, + "fan": "off", + "light": "on", + "hot": "off", + "cool": "off" +} +``` + +兼容输入: +- `fan/light/hot/cool` 支持 `on/off`、`true/false`、`1/0` +- `mode` 支持 `auto/manual`,也兼容 `true/false`、`1/0`(`true/1=auto`) + +## 联调建议 + +1. 先下发 `{"mode":"manual"}`,验证手动风扇/灯/加热/制冷控制。 +2. 再下发阈值并切 `{"mode":"auto"}`,观察自动控制接管。 +3. 注意阈值含回差: + - 光照:`light_on` 开灯,`light_off` 关灯 + - 温度:`hot_on_temp/hot_off_temp` 与 `cool_on_temp/cool_off_temp` + - 湿度:`fan_on_hum` 开风扇,`fan_off_hum` 关风扇 + +## 说明 + +- 当前 README 聚焦单片机固件能力与联调协议。 +- App/小程序页面与云端业务可按本协议直接对接。 + diff --git a/components/console_user_cmds/CMakeLists.txt b/components/console_user_cmds/CMakeLists.txt new file mode 100644 index 0000000..3306495 --- /dev/null +++ b/components/console_user_cmds/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "console_user_cmds.c" + INCLUDE_DIRS "include" + REQUIRES console_simple_init console i2c_master_messager io_device_control wifi-connect) diff --git a/components/console_user_cmds/console_user_cmds.c b/components/console_user_cmds/console_user_cmds.c new file mode 100644 index 0000000..6e36e44 --- /dev/null +++ b/components/console_user_cmds/console_user_cmds.c @@ -0,0 +1,293 @@ +#include +#include +#include + +#include "esp_check.h" +#include "console_simple_init.h" +#include "console_user_cmds.h" +#include "i2c_master_messager.h" +#include "io_device_control.h" +#include "wifi-connect.h" + +static const char *wifi_status_to_str(wifi_connect_status_t status) +{ + switch (status) { + case WIFI_CONNECT_STATUS_IDLE: + return "idle"; + case WIFI_CONNECT_STATUS_PROVISIONING: + return "provisioning"; + case WIFI_CONNECT_STATUS_CONNECTING: + return "connecting"; + case WIFI_CONNECT_STATUS_CONNECTED: + return "connected"; + case WIFI_CONNECT_STATUS_FAILED: + return "failed"; + case WIFI_CONNECT_STATUS_TIMEOUT: + return "timeout"; + default: + return "unknown"; + } +} + +// hello: 最小可用命令,用于验证 console 链路是否正常。 +static int cmd_hello(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("hello from BotanicalBuddy\n"); + return 0; +} + +// sensor: 读取一次传感器缓存数据并打印,便于快速排查现场状态。 +static int cmd_sensor(int argc, char **argv) +{ + (void)argc; + (void)argv; + + i2c_master_messager_data_t data = {0}; + esp_err_t ret = i2c_master_messager_get_data(&data); + if (ret != ESP_OK) { + printf("sensor read failed: %s\n", esp_err_to_name(ret)); + return 1; + } + + if (data.bh1750.valid) { + printf("BH1750: lux=%.1f, ts=%lld ms\n", + data.bh1750.lux, + (long long)data.bh1750.last_update_ms); + } else { + printf("BH1750: invalid, err=%s\n", esp_err_to_name(data.bh1750.last_error)); + } + + if (data.aht30.valid) { + printf("AHT30: temp=%.1f C, hum=%.1f %%, ts=%lld ms\n", + data.aht30.temperature_c, + data.aht30.humidity_rh, + (long long)data.aht30.last_update_ms); + } else { + printf("AHT30: invalid, err=%s\n", esp_err_to_name(data.aht30.last_error)); + } + + return 0; +} + +static bool parse_on_off_arg(const char *arg, bool *on) +{ + if (strcmp(arg, "on") == 0 || strcmp(arg, "1") == 0) { + *on = true; + return true; + } + if (strcmp(arg, "off") == 0 || strcmp(arg, "0") == 0) { + *on = false; + return true; + } + return false; +} + +// fan: 控制风扇开关,参数支持 on/off 或 1/0。 +static int cmd_fan(int argc, char **argv) +{ + if (argc < 2) { + printf("usage: fan \n"); + return 1; + } + + bool on = false; + if (!parse_on_off_arg(argv[1], &on)) { + printf("invalid arg: %s\n", argv[1]); + printf("usage: fan \n"); + return 1; + } + + esp_err_t ret = io_device_control_set_fan(on); + if (ret != ESP_OK) { + printf("set fan failed: %s\n", esp_err_to_name(ret)); + return 1; + } + + printf("fan: %s\n", on ? "on" : "off"); + return 0; +} + +// light: 控制补光灯开关,参数支持 on/off 或 1/0。 +static int cmd_light(int argc, char **argv) +{ + if (argc < 2) { + printf("usage: light \n"); + return 1; + } + + bool on = false; + if (!parse_on_off_arg(argv[1], &on)) { + printf("invalid arg: %s\n", argv[1]); + printf("usage: light \n"); + return 1; + } + + esp_err_t ret = io_device_control_set_light(on); + if (ret != ESP_OK) { + printf("set light failed: %s\n", esp_err_to_name(ret)); + return 1; + } + + printf("light: %s\n", on ? "on" : "off"); + return 0; +} + +// hot: 控制加热开关,参数支持 on/off 或 1/0。 +static int cmd_hot(int argc, char **argv) +{ + if (argc < 2) { + printf("usage: hot \n"); + return 1; + } + + bool on = false; + if (!parse_on_off_arg(argv[1], &on)) { + printf("invalid arg: %s\n", argv[1]); + printf("usage: hot \n"); + return 1; + } + + esp_err_t ret = io_device_control_set_hot(on); + if (ret != ESP_OK) { + printf("set hot failed: %s\n", esp_err_to_name(ret)); + return 1; + } + + printf("hot: %s\n", on ? "on" : "off"); + return 0; +} + +// cool: 控制制冷开关,参数支持 on/off 或 1/0。 +static int cmd_cool(int argc, char **argv) +{ + if (argc < 2) { + printf("usage: cool \n"); + return 1; + } + + bool on = false; + if (!parse_on_off_arg(argv[1], &on)) { + printf("invalid arg: %s\n", argv[1]); + printf("usage: cool \n"); + return 1; + } + + esp_err_t ret = io_device_control_set_cool(on); + if (ret != ESP_OK) { + printf("set cool failed: %s\n", esp_err_to_name(ret)); + return 1; + } + + printf("cool: %s\n", on ? "on" : "off"); + return 0; +} + +// wifi: 查询或控制配网状态,支持 status/start/stop/clear 子命令。 +static int cmd_wifi(int argc, char **argv) +{ + if (argc < 2 || strcmp(argv[1], "status") == 0) { + wifi_connect_config_t cfg = {0}; + esp_err_t cfg_ret = wifi_connect_get_config(&cfg); + printf("wifi status: %s\n", wifi_status_to_str(wifi_connect_get_status())); + if (cfg_ret == ESP_OK && cfg.has_config) { + printf("saved ssid: %s\n", cfg.ssid); + } else { + printf("saved config: none\n"); + } + return 0; + } + + if (strcmp(argv[1], "start") == 0) { + esp_err_t ret = wifi_connect_start(); + if (ret != ESP_OK) { + printf("wifi start failed: %s\n", esp_err_to_name(ret)); + return 1; + } + printf("wifi start requested\n"); + return 0; + } + + if (strcmp(argv[1], "stop") == 0) { + esp_err_t ret = wifi_connect_stop(); + if (ret != ESP_OK) { + printf("wifi stop failed: %s\n", esp_err_to_name(ret)); + return 1; + } + printf("wifi stop requested\n"); + return 0; + } + + if (strcmp(argv[1], "clear") == 0) { + esp_err_t ret = wifi_connect_clear_config(); + if (ret != ESP_OK) { + printf("wifi clear failed: %s\n", esp_err_to_name(ret)); + return 1; + } + printf("wifi config cleared\n"); + return 0; + } + + printf("usage: wifi \n"); + return 1; +} + +esp_err_t console_user_cmds_register(void) +{ + const esp_console_cmd_t hello_cmd = { + .command = "hello", + .help = "打印欢迎信息。用法: hello", + .func = cmd_hello, + }; + ESP_RETURN_ON_ERROR(esp_console_cmd_register(&hello_cmd), "console_user_cmds", "register hello failed"); + + const esp_console_cmd_t sensor_cmd = { + .command = "sensor", + .help = "打印当前传感器缓存数据。用法: sensor", + .func = cmd_sensor, + }; + ESP_RETURN_ON_ERROR(esp_console_cmd_register(&sensor_cmd), "console_user_cmds", "register sensor failed"); + + const esp_console_cmd_t fan_cmd = { + .command = "fan", + .help = "控制风扇。用法: fan ", + .hint = "", + .func = cmd_fan, + }; + ESP_RETURN_ON_ERROR(esp_console_cmd_register(&fan_cmd), "console_user_cmds", "register fan failed"); + + const esp_console_cmd_t light_cmd = { + .command = "light", + .help = "控制补光灯。用法: light ", + .hint = "", + .func = cmd_light, + }; + ESP_RETURN_ON_ERROR(esp_console_cmd_register(&light_cmd), "console_user_cmds", "register light failed"); + + const esp_console_cmd_t hot_cmd = { + .command = "hot", + .help = "控制加热。用法: hot ", + .hint = "", + .func = cmd_hot, + }; + ESP_RETURN_ON_ERROR(esp_console_cmd_register(&hot_cmd), "console_user_cmds", "register hot failed"); + + const esp_console_cmd_t cool_cmd = { + .command = "cool", + .help = "控制制冷。用法: cool ", + .hint = "", + .func = cmd_cool, + }; + ESP_RETURN_ON_ERROR(esp_console_cmd_register(&cool_cmd), "console_user_cmds", "register cool failed"); + + const esp_console_cmd_t wifi_cmd = { + .command = "wifi", + .help = "Wi-Fi 状态与控制。用法: wifi ", + .hint = "", + .func = cmd_wifi, + }; + ESP_RETURN_ON_ERROR(esp_console_cmd_register(&wifi_cmd), "console_user_cmds", "register wifi failed"); + + return ESP_OK; +} diff --git a/components/console_user_cmds/include/console_user_cmds.h b/components/console_user_cmds/include/console_user_cmds.h new file mode 100644 index 0000000..a4009c8 --- /dev/null +++ b/components/console_user_cmds/include/console_user_cmds.h @@ -0,0 +1,5 @@ +#pragma once + +#include "esp_err.h" + +esp_err_t console_user_cmds_register(void); diff --git a/components/i2c_master_messager/CMakeLists.txt b/components/i2c_master_messager/CMakeLists.txt new file mode 100644 index 0000000..09ca934 --- /dev/null +++ b/components/i2c_master_messager/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "i2c_master_messager.c" + INCLUDE_DIRS "include" + REQUIRES bh1750 k0i05__esp_ahtxx esp_driver_i2c esp_driver_gpio esp_timer +) diff --git a/components/i2c_master_messager/Kconfig.projbuild b/components/i2c_master_messager/Kconfig.projbuild new file mode 100644 index 0000000..fe82e9c --- /dev/null +++ b/components/i2c_master_messager/Kconfig.projbuild @@ -0,0 +1,38 @@ +menu "I2C 传感器管理" + +config I2C_MASTER_MESSAGER_ENABLE_INTERNAL_PULLUP + bool "启用 I2C 内部上拉电阻" + default y + help + 启用后,SCL/SDA 会使用芯片内部上拉。 + 如果你的硬件已经有外部上拉电阻,通常也可以关闭该选项。 + +config I2C_MASTER_MESSAGER_BH1750_ENABLE + bool "启用 BH1750 光照传感器" + default y + help + 关闭后将不会初始化与读取 BH1750。 + +config I2C_MASTER_MESSAGER_BH1750_READ_PERIOD_MS + int "BH1750 采样周期 (ms)" + range 100 10000 + default 500 + depends on I2C_MASTER_MESSAGER_BH1750_ENABLE + help + BH1750 的轮询间隔,单位毫秒。 + +config I2C_MASTER_MESSAGER_AHT30_ENABLE + bool "启用 AHT30 温湿度传感器" + default y + help + 关闭后将不会初始化与读取 AHT30。 + +config I2C_MASTER_MESSAGER_AHT30_READ_PERIOD_MS + int "AHT30 采样周期 (ms)" + range 100 10000 + default 2000 + depends on I2C_MASTER_MESSAGER_AHT30_ENABLE + help + AHT30 的轮询间隔,单位毫秒。 + +endmenu diff --git a/components/i2c_master_messager/README.md b/components/i2c_master_messager/README.md new file mode 100644 index 0000000..18b2b4c --- /dev/null +++ b/components/i2c_master_messager/README.md @@ -0,0 +1,109 @@ +# i2c_master_messager + +`i2c_master_messager` 用于统一管理工程中的 I2C 传感器。 +当前已接入: + +- BH1750 光照传感器(使用驱动默认地址) +- AHT30 温湿度传感器(使用驱动默认地址) + +设计目标: + +- 提供统一的数据结构,方便其他模块读取 +- 提供独立采集任务,周期性更新数据 +- 为后续新增其他 I2C 传感器预留扩展位置 +- 各传感器驱动相互独立,管理层只做调度与数据汇总 +- 支持每个传感器独立采样周期(例如光照快采、温湿度慢采) + +## 驱动分层 + +- `k0i05__esp_ahtxx`:AHT30 驱动组件(通过组件管理器引入) +- `bh1750`:使用 ESP 组件管理器驱动 +- `i2c_master_messager.c`:统一总线管理、任务轮询、线程安全数据缓存 + +## 对外数据结构 + +头文件:`include/i2c_master_messager.h` + +- `i2c_master_messager_data_t` + - `bh1750.lux`:光照强度(lx) + - `bh1750.valid`:当前数据是否有效 + - `bh1750.last_update_ms`:最后一次成功更新时间(毫秒) + - `bh1750.last_error`:最后一次采集错误码 + - `aht30.temperature_c`:温度(摄氏度) + - `aht30.humidity_rh`:湿度(%RH) + - `aht30.valid`:当前数据是否有效 + - `aht30.last_update_ms`:最后一次成功更新时间(毫秒) + - `aht30.last_error`:最后一次采集错误码 + +后续新增传感器时,建议继续在 `i2c_master_messager_data_t` 中增加对应字段。 + +## API + +- `esp_err_t i2c_master_messager_init(const i2c_master_messager_config_t *config);` + - 初始化 I2C 总线(传感器驱动在任务中懒初始化) +- `esp_err_t i2c_master_messager_start(void);` + - 启动循环采集任务 +- `esp_err_t i2c_master_messager_stop(void);` + - 停止采集任务 +- `esp_err_t i2c_master_messager_get_data(i2c_master_messager_data_t *out_data);` + - 读取当前缓存数据(线程安全) +- `esp_err_t i2c_master_messager_deinit(void);` + - 释放总线和传感器资源 + +## menuconfig 配置 + +路径:`Component config -> I2C Master Messager` + +- `启用 BH1750 光照传感器`:开关 BH1750 +- `启用 I2C 内部上拉电阻`:控制是否打开芯片内部上拉 +- `BH1750 采样周期 (ms)`:光照采样周期 +- `启用 AHT30 温湿度传感器`:开关 AHT30 +- `AHT30 采样周期 (ms)`:温湿度采样周期 + +这两个周期相互独立,可按业务需求分别调优。 + +## 使用示例 + +```c +#include "esp_check.h" +#include "i2c_master_messager.h" + +void app_main(void) +{ + i2c_master_messager_config_t cfg = { + .i2c_port = I2C_NUM_0, + .scl_io_num = GPIO_NUM_5, + .sda_io_num = GPIO_NUM_4, + .i2c_enable_internal_pullup = true, + .bh1750_enable = true, + .aht30_enable = true, + .bh1750_read_period_ms = 500, + .aht30_read_period_ms = 2000, + .bh1750_mode = BH1750_CONTINUE_1LX_RES, + }; + + ESP_ERROR_CHECK(i2c_master_messager_init(&cfg)); + ESP_ERROR_CHECK(i2c_master_messager_start()); + + while (1) { + i2c_master_messager_data_t data; + if (i2c_master_messager_get_data(&data) == ESP_OK && data.bh1750.valid && data.aht30.valid) { + printf("BH1750: %.2f lx, AHT30: %.2f C %.2f %%RH\n", + data.bh1750.lux, + data.aht30.temperature_c, + data.aht30.humidity_rh); + } + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} +``` + +## 扩展建议 + +新增其他传感器时,建议按以下步骤扩展: + +1. 在 `i2c_master_messager_data_t` 中增加该传感器的数据结构 +2. 在 `i2c_master_messager_config_t` 中增加该传感器配置项 +3. 在任务循环中按需完成驱动初始化与重试 +4. 在采集任务中加入周期读取逻辑并更新共享数据 +5. 在 `deinit()` 中释放对应资源 diff --git a/components/i2c_master_messager/i2c_master_messager.c b/components/i2c_master_messager/i2c_master_messager.c new file mode 100644 index 0000000..6113d95 --- /dev/null +++ b/components/i2c_master_messager/i2c_master_messager.c @@ -0,0 +1,356 @@ +#include + +#include "driver/i2c_master.h" +#include "ahtxx.h" +#include "esp_check.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "i2c_master_messager.h" + +static const char *TAG = "i2c_master_messager"; +#define BH1750_REINIT_INTERVAL_MS (3000) +#define AHT30_REINIT_INTERVAL_MS (3000) + +typedef struct { + bool initialized; + bool owns_i2c_bus; + bool bh1750_ready; + bool aht30_ready; + i2c_master_messager_config_t config; + i2c_master_bus_handle_t i2c_bus; + bh1750_handle_t bh1750; + ahtxx_handle_t aht30; + i2c_master_messager_data_t data; + SemaphoreHandle_t lock; + TaskHandle_t task_handle; +} i2c_master_messager_ctx_t; + +static i2c_master_messager_ctx_t g_ctx; + +static esp_err_t i2c_master_messager_try_init_bh1750(void) +{ + if (!g_ctx.config.bh1750_enable) { + return ESP_ERR_NOT_SUPPORTED; + } + + if (g_ctx.bh1750 != NULL) { + bh1750_delete(g_ctx.bh1750); + g_ctx.bh1750 = NULL; + } + + esp_err_t ret = bh1750_create(g_ctx.i2c_bus, BH1750_I2C_ADDRESS_DEFAULT, &g_ctx.bh1750); + if (ret != ESP_OK) { + return ret; + } + + ret = bh1750_power_on(g_ctx.bh1750); + if (ret != ESP_OK) { + bh1750_delete(g_ctx.bh1750); + g_ctx.bh1750 = NULL; + return ret; + } + + ret = bh1750_set_measure_mode(g_ctx.bh1750, g_ctx.config.bh1750_mode); + if (ret != ESP_OK) { + bh1750_delete(g_ctx.bh1750); + g_ctx.bh1750 = NULL; + return ret; + } + + g_ctx.bh1750_ready = true; + return ESP_OK; +} + +static esp_err_t i2c_master_messager_try_init_aht30(void) +{ + if (!g_ctx.config.aht30_enable) { + return ESP_ERR_NOT_SUPPORTED; + } + + if (g_ctx.aht30 != NULL) { + ahtxx_delete(g_ctx.aht30); + g_ctx.aht30 = NULL; + } + + ahtxx_config_t aht_cfg = I2C_AHT30_CONFIG_DEFAULT; + esp_err_t ret = ahtxx_init(g_ctx.i2c_bus, &aht_cfg, &g_ctx.aht30); + if (ret != ESP_OK) { + return ret; + } + + g_ctx.aht30_ready = true; + return ESP_OK; +} + +static void i2c_master_messager_task(void *arg) +{ + (void)arg; + int64_t last_bh1750_reinit_ms = 0; + int64_t last_bh1750_read_ms = 0; + int64_t last_aht30_read_ms = 0; + int64_t last_aht30_reinit_ms = 0; + while (1) { + int64_t now_ms = esp_timer_get_time() / 1000; + + if (g_ctx.config.bh1750_enable && !g_ctx.bh1750_ready && + (now_ms - last_bh1750_reinit_ms) >= BH1750_REINIT_INTERVAL_MS) { + last_bh1750_reinit_ms = now_ms; + esp_err_t init_ret = i2c_master_messager_try_init_bh1750(); + if (init_ret == ESP_OK) { + ESP_LOGI(TAG, "BH1750 reinit success"); + } else { + ESP_LOGW(TAG, "BH1750 reinit failed: %s", esp_err_to_name(init_ret)); + if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) { + g_ctx.data.bh1750.valid = false; + g_ctx.data.bh1750.last_error = init_ret; + xSemaphoreGive(g_ctx.lock); + } + } + } + + if (g_ctx.bh1750_ready && g_ctx.bh1750 != NULL && + (now_ms - last_bh1750_read_ms) >= g_ctx.config.bh1750_read_period_ms) { + float lux = 0.0f; + esp_err_t bh1750_ret = bh1750_get_data(g_ctx.bh1750, &lux); + last_bh1750_read_ms = now_ms; + + if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) { + g_ctx.data.bh1750.valid = (bh1750_ret == ESP_OK); + g_ctx.data.bh1750.last_error = bh1750_ret; + if (bh1750_ret == ESP_OK) { + g_ctx.data.bh1750.lux = lux; + g_ctx.data.bh1750.last_update_ms = now_ms; + } + xSemaphoreGive(g_ctx.lock); + } + + if (bh1750_ret != ESP_OK) { + ESP_LOGW(TAG, "bh1750_get_data failed: %s", esp_err_to_name(bh1750_ret)); + } + } + + if (g_ctx.config.aht30_enable && !g_ctx.aht30_ready && + (now_ms - last_aht30_reinit_ms) >= AHT30_REINIT_INTERVAL_MS) { + last_aht30_reinit_ms = now_ms; + esp_err_t init_ret = i2c_master_messager_try_init_aht30(); + if (init_ret == ESP_OK) { + ESP_LOGI(TAG, "AHT30 reinit success"); + } else { + ESP_LOGW(TAG, "AHT30 reinit failed: %s", esp_err_to_name(init_ret)); + if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) { + g_ctx.data.aht30.valid = false; + g_ctx.data.aht30.last_error = init_ret; + xSemaphoreGive(g_ctx.lock); + } + } + } + + if (g_ctx.aht30_ready && g_ctx.aht30 != NULL && + (now_ms - last_aht30_read_ms) >= g_ctx.config.aht30_read_period_ms) { + float temperature_c = 0.0f; + float humidity_rh = 0.0f; + esp_err_t aht30_ret = ahtxx_get_measurement(g_ctx.aht30, &temperature_c, &humidity_rh); + last_aht30_read_ms = now_ms; + + if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) { + g_ctx.data.aht30.valid = (aht30_ret == ESP_OK); + g_ctx.data.aht30.last_error = aht30_ret; + if (aht30_ret == ESP_OK) { + g_ctx.data.aht30.temperature_c = temperature_c; + g_ctx.data.aht30.humidity_rh = humidity_rh; + g_ctx.data.aht30.last_update_ms = now_ms; + } + xSemaphoreGive(g_ctx.lock); + } + + if (aht30_ret != ESP_OK) { + ESP_LOGW(TAG, "ahtxx_get_measurement failed: %s", esp_err_to_name(aht30_ret)); + if (aht30_ret == ESP_ERR_INVALID_STATE || aht30_ret == ESP_ERR_TIMEOUT) { + if (g_ctx.aht30 != NULL) { + ahtxx_delete(g_ctx.aht30); + g_ctx.aht30 = NULL; + } + g_ctx.aht30_ready = false; + } + } + } + + vTaskDelay(pdMS_TO_TICKS(I2C_MASTER_MESSAGER_MIN_PERIOD_MS)); + } +} + +esp_err_t i2c_master_messager_init(const i2c_master_messager_config_t *config) +{ + ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "config is null"); + ESP_RETURN_ON_FALSE(config->bh1750_enable || config->aht30_enable, + ESP_ERR_INVALID_ARG, + TAG, + "at least one sensor must be enabled"); + ESP_RETURN_ON_FALSE(!config->bh1750_enable || + config->bh1750_read_period_ms >= I2C_MASTER_MESSAGER_MIN_PERIOD_MS, + ESP_ERR_INVALID_ARG, + TAG, + "bh1750_read_period_ms too small"); + ESP_RETURN_ON_FALSE(!config->aht30_enable || + config->aht30_read_period_ms >= I2C_MASTER_MESSAGER_MIN_PERIOD_MS, + ESP_ERR_INVALID_ARG, + TAG, + "aht30_read_period_ms too small"); + + if (g_ctx.initialized) { + return ESP_ERR_INVALID_STATE; + } + + memset(&g_ctx, 0, sizeof(g_ctx)); + g_ctx.config = *config; + + g_ctx.lock = xSemaphoreCreateMutex(); + ESP_RETURN_ON_FALSE(g_ctx.lock != NULL, ESP_ERR_NO_MEM, TAG, "failed to create mutex"); + + const i2c_master_bus_config_t bus_cfg = { + .i2c_port = config->i2c_port, + .sda_io_num = config->sda_io_num, + .scl_io_num = config->scl_io_num, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = config->i2c_enable_internal_pullup, + }; + + esp_err_t ret = i2c_new_master_bus(&bus_cfg, &g_ctx.i2c_bus); + if (ret != ESP_OK) { + if (ret == ESP_ERR_INVALID_STATE) { + ESP_LOGW(TAG, + "i2c port %d already initialized, trying to reuse existing master bus", + config->i2c_port); + ret = i2c_master_get_bus_handle(config->i2c_port, &g_ctx.i2c_bus); + if (ret != ESP_OK || g_ctx.i2c_bus == NULL) { + ESP_LOGE(TAG, + "failed to reuse i2c bus on port %d: %s", + config->i2c_port, + esp_err_to_name(ret)); + vSemaphoreDelete(g_ctx.lock); + g_ctx.lock = NULL; + return (ret == ESP_OK) ? ESP_ERR_INVALID_STATE : ret; + } + g_ctx.owns_i2c_bus = false; + } else { + ESP_LOGE(TAG, "i2c_new_master_bus failed: %s", esp_err_to_name(ret)); + vSemaphoreDelete(g_ctx.lock); + g_ctx.lock = NULL; + return ret; + } + } else { + g_ctx.owns_i2c_bus = true; + } + + if (!config->bh1750_enable) { + g_ctx.data.bh1750.valid = false; + g_ctx.data.bh1750.last_error = ESP_ERR_NOT_SUPPORTED; + } else { + g_ctx.data.bh1750.valid = false; + g_ctx.data.bh1750.last_error = ESP_ERR_INVALID_STATE; + } + + if (!config->aht30_enable) { + g_ctx.data.aht30.valid = false; + g_ctx.data.aht30.last_error = ESP_ERR_NOT_SUPPORTED; + } else { + g_ctx.data.aht30.valid = false; + g_ctx.data.aht30.last_error = ESP_ERR_INVALID_STATE; + } + + g_ctx.initialized = true; + ESP_LOGI(TAG, + "initialized: port=%d scl=%d sda=%d pullup_int=%d bh1750(en=%d,ready=%d,default,%ums) aht30(en=%d,ready=%d,default,%ums)", + config->i2c_port, + config->scl_io_num, + config->sda_io_num, + config->i2c_enable_internal_pullup, + config->bh1750_enable, + g_ctx.bh1750_ready, + config->bh1750_read_period_ms, + config->aht30_enable, + g_ctx.aht30_ready, + config->aht30_read_period_ms); + ESP_LOGI(TAG, + "i2c bus is ready; sensor drivers will initialize lazily in task loop"); + return ESP_OK; +} + +esp_err_t i2c_master_messager_start(void) +{ + ESP_RETURN_ON_FALSE(g_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized"); + if (g_ctx.task_handle != NULL) { + return ESP_OK; + } + + BaseType_t ok = xTaskCreate(i2c_master_messager_task, + "i2c_msg_task", + 4096, + NULL, + 5, + &g_ctx.task_handle); + ESP_RETURN_ON_FALSE(ok == pdPASS, ESP_ERR_NO_MEM, TAG, "failed to create task"); + return ESP_OK; +} + +esp_err_t i2c_master_messager_stop(void) +{ + ESP_RETURN_ON_FALSE(g_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized"); + if (g_ctx.task_handle != NULL) { + vTaskDelete(g_ctx.task_handle); + g_ctx.task_handle = NULL; + } + return ESP_OK; +} + +esp_err_t i2c_master_messager_get_data(i2c_master_messager_data_t *out_data) +{ + ESP_RETURN_ON_FALSE(g_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized"); + ESP_RETURN_ON_FALSE(out_data != NULL, ESP_ERR_INVALID_ARG, TAG, "out_data is null"); + ESP_RETURN_ON_FALSE(g_ctx.lock != NULL, ESP_ERR_INVALID_STATE, TAG, "lock not ready"); + + ESP_RETURN_ON_FALSE(xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE, + ESP_ERR_TIMEOUT, + TAG, + "failed to lock shared data"); + *out_data = g_ctx.data; + xSemaphoreGive(g_ctx.lock); + return ESP_OK; +} + +esp_err_t i2c_master_messager_deinit(void) +{ + if (!g_ctx.initialized) { + return ESP_OK; + } + + i2c_master_messager_stop(); + + if (g_ctx.bh1750 != NULL) { + bh1750_delete(g_ctx.bh1750); + g_ctx.bh1750 = NULL; + } + + if (g_ctx.aht30 != NULL) { + ahtxx_delete(g_ctx.aht30); + g_ctx.aht30 = NULL; + } + + if (g_ctx.i2c_bus != NULL && g_ctx.owns_i2c_bus) { + i2c_del_master_bus(g_ctx.i2c_bus); + } + g_ctx.i2c_bus = NULL; + g_ctx.owns_i2c_bus = false; + + if (g_ctx.lock != NULL) { + vSemaphoreDelete(g_ctx.lock); + g_ctx.lock = NULL; + } + + memset(&g_ctx, 0, sizeof(g_ctx)); + return ESP_OK; +} diff --git a/components/i2c_master_messager/include/i2c_master_messager.h b/components/i2c_master_messager/include/i2c_master_messager.h new file mode 100644 index 0000000..f6424e6 --- /dev/null +++ b/components/i2c_master_messager/include/i2c_master_messager.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +#include "bh1750.h" +#include "driver/gpio.h" +#include "driver/i2c_types.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + i2c_port_num_t i2c_port; + gpio_num_t scl_io_num; + gpio_num_t sda_io_num; + bool i2c_enable_internal_pullup; + bool bh1750_enable; + bool aht30_enable; + uint16_t bh1750_read_period_ms; + uint16_t aht30_read_period_ms; + bh1750_measure_mode_t bh1750_mode; +} i2c_master_messager_config_t; + +typedef struct { + float lux; + bool valid; + int64_t last_update_ms; + esp_err_t last_error; +} i2c_bh1750_data_t; + +typedef struct { + float temperature_c; + float humidity_rh; + bool valid; + int64_t last_update_ms; + esp_err_t last_error; +} i2c_aht30_data_t; + +typedef struct { + i2c_bh1750_data_t bh1750; + i2c_aht30_data_t aht30; +} i2c_master_messager_data_t; + +#define I2C_MASTER_MESSAGER_MIN_PERIOD_MS (100) + +esp_err_t i2c_master_messager_init(const i2c_master_messager_config_t *config); +esp_err_t i2c_master_messager_start(void); +esp_err_t i2c_master_messager_stop(void); +esp_err_t i2c_master_messager_get_data(i2c_master_messager_data_t *out_data); +esp_err_t i2c_master_messager_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/io_device_control/CMakeLists.txt b/components/io_device_control/CMakeLists.txt new file mode 100644 index 0000000..8ba3d7b --- /dev/null +++ b/components/io_device_control/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "io_device_control.c" + INCLUDE_DIRS "include" + REQUIRES esp_driver_gpio) diff --git a/components/io_device_control/README.md b/components/io_device_control/README.md new file mode 100644 index 0000000..4c45ab0 --- /dev/null +++ b/components/io_device_control/README.md @@ -0,0 +1,50 @@ +# io_device_control + +`io_device_control` 组件用于统一管理项目中的简单 IO 外设控制。 + +当前定义: + +- `GPIO1`:风扇控制(高电平有效) +- `GPIO0`:光照控制(高电平有效) +- `GPIO12`:加热控制(高电平有效) +- `GPIO13`:制冷控制(高电平有效) + +## 对外接口 + +头文件:`include/io_device_control.h` + +- `esp_err_t io_device_control_init(void);` + - 初始化 GPIO 输出方向,并将风扇/光照/加热/制冷默认置为关闭(低电平)。 +- `esp_err_t io_device_control_set_fan(bool on);` + - 控制风扇开关,`true` 为开,`false` 为关。 +- `esp_err_t io_device_control_set_light(bool on);` + - 控制光照开关,`true` 为开,`false` 为关。 +- `esp_err_t io_device_control_set_hot(bool on);` + - 控制加热开关,`true` 为开,`false` 为关。 +- `esp_err_t io_device_control_set_cool(bool on);` + - 控制制冷开关,`true` 为开,`false` 为关。 + +## 使用方式 + +在 `app_main` 中先初始化,后续在业务逻辑中按需调用控制接口。 + +```c +#include "esp_check.h" +#include "io_device_control.h" + +void app_main(void) +{ + ESP_ERROR_CHECK(io_device_control_init()); + + // 后续按需调用 + // ESP_ERROR_CHECK(io_device_control_set_fan(true)); + // ESP_ERROR_CHECK(io_device_control_set_light(true)); + // ESP_ERROR_CHECK(io_device_control_set_hot(true)); + // ESP_ERROR_CHECK(io_device_control_set_cool(true)); +} +``` + +## 注意事项 + +- 控制接口在未初始化时会返回 `ESP_ERR_INVALID_STATE`。 +- 若硬件驱动电路为反相,请在硬件层或组件内部统一处理,不建议在业务层散落取反逻辑。 diff --git a/components/io_device_control/include/io_device_control.h b/components/io_device_control/include/io_device_control.h new file mode 100644 index 0000000..8f9d873 --- /dev/null +++ b/components/io_device_control/include/io_device_control.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Initializes fan/light/hot/cool outputs and sets all devices off by default. +esp_err_t io_device_control_init(void); + +// High level control APIs, all are active-high outputs. +esp_err_t io_device_control_set_fan(bool on); +esp_err_t io_device_control_set_light(bool on); +esp_err_t io_device_control_set_hot(bool on); +esp_err_t io_device_control_set_cool(bool on); + +// Read current output states from GPIO. +esp_err_t io_device_control_get_states(bool *fan_on, + bool *light_on, + bool *hot_on, + bool *cool_on); + +#ifdef __cplusplus +} +#endif diff --git a/components/io_device_control/io_device_control.c b/components/io_device_control/io_device_control.c new file mode 100644 index 0000000..66bf416 --- /dev/null +++ b/components/io_device_control/io_device_control.c @@ -0,0 +1,106 @@ +#include + +#include "driver/gpio.h" +#include "esp_check.h" +#include "esp_log.h" +#include "io_device_control.h" + +static const char *TAG = "io_device_control"; + +#define IO_DEVICE_FAN_GPIO GPIO_NUM_1 +#define IO_DEVICE_LIGHT_GPIO GPIO_NUM_0 +#define IO_DEVICE_HOT_GPIO GPIO_NUM_12 +#define IO_DEVICE_COOL_GPIO GPIO_NUM_13 + +static bool s_inited = false; + +static esp_err_t io_device_control_set_level(gpio_num_t pin, bool on) +{ + ESP_RETURN_ON_FALSE(s_inited, ESP_ERR_INVALID_STATE, TAG, "not initialized"); + return gpio_set_level(pin, on ? 1 : 0); +} + +esp_err_t io_device_control_init(void) +{ + if (s_inited) { + return ESP_OK; + } + + const gpio_config_t out_cfg = { + .pin_bit_mask = (1ULL << IO_DEVICE_FAN_GPIO) | + (1ULL << IO_DEVICE_LIGHT_GPIO) | + (1ULL << IO_DEVICE_HOT_GPIO) | + (1ULL << IO_DEVICE_COOL_GPIO), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + + ESP_RETURN_ON_ERROR(gpio_config(&out_cfg), TAG, "gpio_config failed"); + + // Active-high outputs; default low keeps devices off at boot. + ESP_RETURN_ON_ERROR(gpio_set_level(IO_DEVICE_FAN_GPIO, 0), TAG, "set fan default failed"); + ESP_RETURN_ON_ERROR(gpio_set_level(IO_DEVICE_LIGHT_GPIO, 0), TAG, "set light default failed"); + ESP_RETURN_ON_ERROR(gpio_set_level(IO_DEVICE_HOT_GPIO, 0), TAG, "set hot default failed"); + ESP_RETURN_ON_ERROR(gpio_set_level(IO_DEVICE_COOL_GPIO, 0), TAG, "set cool default failed"); + + s_inited = true; + ESP_LOGI(TAG, + "initialized: fan=GPIO%d light=GPIO%d hot=GPIO%d cool=GPIO%d active_high=1", + IO_DEVICE_FAN_GPIO, + IO_DEVICE_LIGHT_GPIO, + IO_DEVICE_HOT_GPIO, + IO_DEVICE_COOL_GPIO); + return ESP_OK; +} + +esp_err_t io_device_control_set_fan(bool on) +{ + ESP_RETURN_ON_ERROR(io_device_control_set_level(IO_DEVICE_FAN_GPIO, on), + TAG, + "set fan failed"); + return ESP_OK; +} + +esp_err_t io_device_control_set_light(bool on) +{ + ESP_RETURN_ON_ERROR(io_device_control_set_level(IO_DEVICE_LIGHT_GPIO, on), + TAG, + "set light failed"); + return ESP_OK; +} + +esp_err_t io_device_control_set_hot(bool on) +{ + ESP_RETURN_ON_ERROR(io_device_control_set_level(IO_DEVICE_HOT_GPIO, on), + TAG, + "set hot failed"); + return ESP_OK; +} + +esp_err_t io_device_control_set_cool(bool on) +{ + ESP_RETURN_ON_ERROR(io_device_control_set_level(IO_DEVICE_COOL_GPIO, on), + TAG, + "set cool failed"); + return ESP_OK; +} + +esp_err_t io_device_control_get_states(bool *fan_on, + bool *light_on, + bool *hot_on, + bool *cool_on) +{ + ESP_RETURN_ON_FALSE(fan_on != NULL && light_on != NULL && hot_on != NULL && cool_on != NULL, + ESP_ERR_INVALID_ARG, + TAG, + "null state pointer"); + ESP_RETURN_ON_FALSE(s_inited, ESP_ERR_INVALID_STATE, TAG, "not initialized"); + + *fan_on = (gpio_get_level(IO_DEVICE_FAN_GPIO) != 0); + *light_on = (gpio_get_level(IO_DEVICE_LIGHT_GPIO) != 0); + *hot_on = (gpio_get_level(IO_DEVICE_HOT_GPIO) != 0); + *cool_on = (gpio_get_level(IO_DEVICE_COOL_GPIO) != 0); + return ESP_OK; +} diff --git a/components/lvgl_st7735s_use/CMakeLists.txt b/components/lvgl_st7735s_use/CMakeLists.txt new file mode 100644 index 0000000..033bd9c --- /dev/null +++ b/components/lvgl_st7735s_use/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "lvgl_st7735s_use.c" + INCLUDE_DIRS "include" + REQUIRES driver esp_lcd esp_lvgl_port + ) \ No newline at end of file diff --git a/components/lvgl_st7735s_use/README.md b/components/lvgl_st7735s_use/README.md new file mode 100644 index 0000000..befc238 --- /dev/null +++ b/components/lvgl_st7735s_use/README.md @@ -0,0 +1,105 @@ +# lvgl_st7735s_use 组件说明 + +`lvgl_st7735s_use` 是项目中的 LCD 显示组件,基于 `esp_lcd + esp_lvgl_port`,用于快速驱动 ST77xx 系列 SPI 屏并显示 LVGL 界面。 + +--- + +## 功能概览 + +- 初始化 SPI LCD(含背光、面板、显示偏移) +- 初始化 LVGL 端口并注册显示设备 +- 默认创建一个居中标签用于快速验证显示链路 +- 提供运行时更新中心文本接口 +- 支持可配置方向、镜像与偏移 +- 支持可选三色测试图(调试用) + +--- + +## 对外 API + +头文件:`include/lvgl_st7735s_use.h` + +- `esp_err_t start_lvgl_demo(void);` + - 完成 LCD + LVGL 初始化并创建默认界面 + +- `esp_err_t lvgl_st7735s_set_center_text(const char *text);` + - 运行时更新中心标签文字(线程安全,内部已加锁) + +--- + +## 关键配置项(可直接改宏) + +在 `include/lvgl_st7735s_use.h` 中: + +### 1) 屏幕与 SPI + +- `EXAMPLE_LCD_H_RES` / `EXAMPLE_LCD_V_RES` +- `EXAMPLE_LCD_PIXEL_CLK_HZ` +- `EXAMPLE_LCD_SPI_NUM` +- `EXAMPLE_LCD_CMD_BITS` / `EXAMPLE_LCD_PARAM_BITS` + +建议:首次点亮优先用较低时钟(如 `10MHz`),稳定后再升频。 + +### 2) 方向与偏移(重点) + +- `EXAMPLE_LCD_GAP_X` +- `EXAMPLE_LCD_GAP_Y` +- `EXAMPLE_LCD_ROT_SWAP_XY` +- `EXAMPLE_LCD_ROT_MIRROR_X` +- `EXAMPLE_LCD_ROT_MIRROR_Y` + +说明: +- 当前项目已验证一组可用参数(顺时针 90° + 26 偏移)。 +- 若出现“文字偏移/边缘花屏/方向反了”,优先微调上述宏,不要同时在多层重复旋转。 + +### 3) 调试项 + +- `EXAMPLE_LCD_ENABLE_COLOR_TEST` + - `1`:上电先画 RGB 三色测试图(便于确认硬件链路) + - `0`:跳过测试,直接进入 LVGL + +--- + +## 在主程序中调用 + +```c +#include "esp_check.h" +#include "lvgl_st7735s_use.h" + +void app_main(void) +{ + ESP_ERROR_CHECK(start_lvgl_demo()); + ESP_ERROR_CHECK(lvgl_st7735s_set_center_text("BotanicalBuddy")); +} +``` + +--- + +## 常见问题 + +### 1) 背光亮但没有内容 + +优先排查: +- 面板型号与驱动是否匹配(ST7735S / ST7789) +- SPI 模式、时钟是否过高 +- 方向/偏移参数是否正确 + +### 2) 文字方向反了或显示偏移 + +优先调整: +- `EXAMPLE_LCD_ROT_*` +- `EXAMPLE_LCD_GAP_X / EXAMPLE_LCD_GAP_Y` + +### 3) 想快速确认硬件链路是否通 + +把 `EXAMPLE_LCD_ENABLE_COLOR_TEST` 设为 `1`,观察是否能显示三色图。 + +--- + +## 依赖 + +由组件 `CMakeLists.txt` 声明: + +- `driver` +- `esp_lcd` +- `esp_lvgl_port` diff --git a/components/lvgl_st7735s_use/include/lvgl_st7735s_use.h b/components/lvgl_st7735s_use/include/lvgl_st7735s_use.h new file mode 100644 index 0000000..e6d9ccc --- /dev/null +++ b/components/lvgl_st7735s_use/include/lvgl_st7735s_use.h @@ -0,0 +1,56 @@ + +// SPDX-License-Identifier: MIT +#pragma once + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* LCD size */ +#define EXAMPLE_LCD_H_RES (160) +#define EXAMPLE_LCD_V_RES (80) + +/* LCD SPI总线配置 */ +#define EXAMPLE_LCD_SPI_NUM (SPI2_HOST) // 使用SPI2主机接口进行通信 + +/* LCD显示参数配置 */ +#define EXAMPLE_LCD_PIXEL_CLK_HZ (10 * 1000 * 1000) // 先用10MHz提高兼容性,点亮后再逐步升频 + +/* LCD命令和参数配置 */ +#define EXAMPLE_LCD_CMD_BITS (8) // 命令位数为8位,用于发送LCD控制命令 +#define EXAMPLE_LCD_PARAM_BITS (8) // 参数位数为8位,用于发送命令参数 + +/* LCD颜色和缓冲区配置 */ +#define EXAMPLE_LCD_BITS_PER_PIXEL (16) // 每个像素使用16位颜色(RGB565格式) +#define EXAMPLE_LCD_DRAW_BUFF_DOUBLE (1) // 启用双缓冲模式,提高显示流畅度 +#define EXAMPLE_LCD_DRAW_BUFF_HEIGHT (50) // 绘图缓冲区高度为50行,影响刷新性能 + +/* LCD背光配置 */ +#define EXAMPLE_LCD_BL_ON_LEVEL (1) // 背光开启电平为高电平(1) + +/* LCD方向/偏移配置(当前为顺时针90°,并保留26偏移) */ +#define EXAMPLE_LCD_GAP_X (1) +#define EXAMPLE_LCD_GAP_Y (26) +#define EXAMPLE_LCD_ROT_SWAP_XY (1) +#define EXAMPLE_LCD_ROT_MIRROR_X (1) +#define EXAMPLE_LCD_ROT_MIRROR_Y (0) + +/* 调试项:上电后是否先显示三色测试图 */ +#define EXAMPLE_LCD_ENABLE_COLOR_TEST (0) + +/* LCD pins */ +#define EXAMPLE_LCD_GPIO_SCLK (GPIO_NUM_2) +#define EXAMPLE_LCD_GPIO_MOSI (GPIO_NUM_3) +#define EXAMPLE_LCD_GPIO_RST (GPIO_NUM_10) +#define EXAMPLE_LCD_GPIO_DC (GPIO_NUM_8) +#define EXAMPLE_LCD_GPIO_CS (GPIO_NUM_7) +#define EXAMPLE_LCD_GPIO_BL (GPIO_NUM_6) + +esp_err_t start_lvgl_demo(void); +esp_err_t lvgl_st7735s_set_center_text(const char *text); + +#ifdef __cplusplus +} +#endif diff --git a/components/lvgl_st7735s_use/lvgl_st7735s_use.c b/components/lvgl_st7735s_use/lvgl_st7735s_use.c new file mode 100644 index 0000000..420f820 --- /dev/null +++ b/components/lvgl_st7735s_use/lvgl_st7735s_use.c @@ -0,0 +1,246 @@ +#include +#include +#include "lvgl_st7735s_use.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "lvgl_st7735s_use"; + +static esp_lcd_panel_io_handle_t lcd_io = NULL; +static esp_lcd_panel_handle_t lcd_panel = NULL; +static lv_display_t *lvgl_disp = NULL; +static lv_obj_t *s_center_label = NULL; + +#if EXAMPLE_LCD_ENABLE_COLOR_TEST +static esp_err_t app_lcd_color_test(void) +{ + const size_t pixels = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES; + uint16_t *frame = calloc(pixels, sizeof(uint16_t)); + ESP_RETURN_ON_FALSE(frame != NULL, ESP_ERR_NO_MEM, TAG, "分配测试帧缓冲失败"); + + for (int y = 0; y < EXAMPLE_LCD_V_RES; y++) { + for (int x = 0; x < EXAMPLE_LCD_H_RES; x++) { + uint16_t color; + if (x < EXAMPLE_LCD_H_RES / 3) { + color = 0xF800; // 红 + } else if (x < (EXAMPLE_LCD_H_RES * 2) / 3) { + color = 0x07E0; // 绿 + } else { + color = 0x001F; // 蓝 + } + frame[y * EXAMPLE_LCD_H_RES + x] = color; + } + } + + esp_err_t err = esp_lcd_panel_draw_bitmap(lcd_panel, 0, 0, EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES, frame); + free(frame); + ESP_RETURN_ON_ERROR(err, TAG, "三色测试绘制失败"); + + ESP_LOGI(TAG, "LCD三色测试图已发送"); + return ESP_OK; +} +#endif + +/** + * @brief 初始化LCD硬件和SPI接口 + * + * 该函数负责初始化LCD所需的GPIO、SPI总线,并配置LCD面板 + * 包括背光控制、SPI总线配置、面板IO配置和面板驱动安装 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功 + */ +static esp_err_t app_lcd_init(void) +{ + esp_err_t ret = ESP_OK; + + gpio_config_t bk_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << EXAMPLE_LCD_GPIO_BL + }; + ESP_ERROR_CHECK(gpio_config(&bk_gpio_config)); + + ESP_LOGI(TAG, "初始化SPI总线"); + const spi_bus_config_t buscfg = { + .sclk_io_num = EXAMPLE_LCD_GPIO_SCLK, + .mosi_io_num = EXAMPLE_LCD_GPIO_MOSI, + .miso_io_num = GPIO_NUM_NC, + .quadwp_io_num = GPIO_NUM_NC, + .quadhd_io_num = GPIO_NUM_NC, + .max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_DRAW_BUFF_HEIGHT * sizeof(uint16_t), + }; + ESP_RETURN_ON_ERROR(spi_bus_initialize(EXAMPLE_LCD_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI初始化失败"); + + ESP_LOGI(TAG, "安装面板IO"); + const esp_lcd_panel_io_spi_config_t io_config = { + .dc_gpio_num = EXAMPLE_LCD_GPIO_DC, + .cs_gpio_num = EXAMPLE_LCD_GPIO_CS, + .pclk_hz = EXAMPLE_LCD_PIXEL_CLK_HZ, + .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, + .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS, + .spi_mode = 0, + .trans_queue_depth = 10, + }; + ESP_GOTO_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)EXAMPLE_LCD_SPI_NUM, &io_config, &lcd_io), err, TAG, "创建面板IO失败"); + + ESP_LOGI(TAG, "安装LCD驱动"); + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = EXAMPLE_LCD_GPIO_RST, +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0) + .rgb_endian = LCD_RGB_ENDIAN_RGB, +#else + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR, +#endif + .bits_per_pixel = EXAMPLE_LCD_BITS_PER_PIXEL, + }; + + ESP_GOTO_ON_ERROR(esp_lcd_new_panel_st7789(lcd_io, &panel_config, &lcd_panel), err, TAG, "创建面板失败"); + ESP_GOTO_ON_ERROR(esp_lcd_panel_reset(lcd_panel), err, TAG, "面板复位失败"); + ESP_GOTO_ON_ERROR(esp_lcd_panel_init(lcd_panel), err, TAG, "面板初始化失败"); + ESP_GOTO_ON_ERROR(esp_lcd_panel_swap_xy(lcd_panel, false), err, TAG, "设置面板swap_xy失败"); + ESP_GOTO_ON_ERROR(esp_lcd_panel_mirror(lcd_panel, false, false), err, TAG, "设置面板镜像失败"); + + ESP_GOTO_ON_ERROR(esp_lcd_panel_set_gap(lcd_panel, EXAMPLE_LCD_GAP_X, EXAMPLE_LCD_GAP_Y), err, TAG, "设置显示偏移失败"); + ESP_LOGI(TAG, "面板基准参数已应用: gap=(%d,%d)", EXAMPLE_LCD_GAP_X, EXAMPLE_LCD_GAP_Y); + + ESP_GOTO_ON_ERROR(esp_lcd_panel_invert_color(lcd_panel, true), err, TAG, "设置反色失败"); + ESP_GOTO_ON_ERROR(esp_lcd_panel_disp_on_off(lcd_panel, true), err, TAG, "打开显示失败"); + + ESP_RETURN_ON_ERROR(gpio_set_level(EXAMPLE_LCD_GPIO_BL, EXAMPLE_LCD_BL_ON_LEVEL), TAG, "背光引脚置位失败"); + ESP_LOGI(TAG, "背光已打开,电平=%d", EXAMPLE_LCD_BL_ON_LEVEL); + + return ret; + +// 错误处理标签,用于清理资源 +err: + if (lcd_panel) { + esp_lcd_panel_del(lcd_panel); + lcd_panel = NULL; + } + if (lcd_io) { + esp_lcd_panel_io_del(lcd_io); + lcd_io = NULL; + } + spi_bus_free(EXAMPLE_LCD_SPI_NUM); + return ret; +} + +/** + * @brief 初始化LVGL图形库 + * + * 该函数负责初始化LVGL库,并配置显示设备 + * 包括LVGL任务配置、显示缓冲区配置和旋转设置 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功 + */ +static esp_err_t app_lvgl_init(void) +{ + const lvgl_port_cfg_t lvgl_cfg = { + .task_priority = 4, + .task_stack = 4096, + .task_affinity = -1, + .task_max_sleep_ms = 500, + .timer_period_ms = 5 + }; + ESP_RETURN_ON_ERROR(lvgl_port_init(&lvgl_cfg), TAG, "LVGL端口初始化失败"); + + ESP_LOGI(TAG, "添加LCD屏幕"); + const lvgl_port_display_cfg_t disp_cfg = { + .io_handle = lcd_io, + .panel_handle = lcd_panel, + .buffer_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_DRAW_BUFF_HEIGHT, + .double_buffer = EXAMPLE_LCD_DRAW_BUFF_DOUBLE, + .hres = EXAMPLE_LCD_H_RES, + .vres = EXAMPLE_LCD_V_RES, + .monochrome = false, +#if LVGL_VERSION_MAJOR >= 9 + .color_format = LV_COLOR_FORMAT_RGB565, +#endif + .rotation = { + .swap_xy = EXAMPLE_LCD_ROT_SWAP_XY, + .mirror_x = EXAMPLE_LCD_ROT_MIRROR_X, + .mirror_y = EXAMPLE_LCD_ROT_MIRROR_Y, + }, + .flags = { + .buff_dma = true, +#if LVGL_VERSION_MAJOR >= 9 + .swap_bytes = false, +#endif + }}; + + lvgl_disp = lvgl_port_add_disp(&disp_cfg); + ESP_RETURN_ON_FALSE(lvgl_disp != NULL, ESP_FAIL, TAG, "添加LVGL显示设备失败"); + ESP_LOGI(TAG, "LVGL旋转已应用: swap_xy=%d mirror_x=%d mirror_y=%d", + EXAMPLE_LCD_ROT_SWAP_XY, EXAMPLE_LCD_ROT_MIRROR_X, EXAMPLE_LCD_ROT_MIRROR_Y); + + return ESP_OK; +} + +/** + * @brief 创建并显示LVGL主界面 + * + * 该函数负责创建LVGL的用户界面元素,包括图像、标签和按钮 + * 并设置它们的位置和属性 + */ +static void app_main_display(void) +{ + lv_obj_t *scr = lv_scr_act(); + + lvgl_port_lock(0); + + lv_obj_set_style_bg_color(scr, lv_color_white(), 0); + lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0); + + s_center_label = lv_label_create(scr); + lv_label_set_text(s_center_label, "BotanicalBuddy\nloading..."); + lv_label_set_recolor(s_center_label, false); + lv_label_set_long_mode(s_center_label, LV_LABEL_LONG_WRAP); + lv_obj_set_size(s_center_label, EXAMPLE_LCD_H_RES - 6, EXAMPLE_LCD_V_RES - 6); + lv_obj_set_style_text_color(s_center_label, lv_color_black(), 0); + lv_obj_set_style_text_font(s_center_label, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_align(s_center_label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_pad_all(s_center_label, 0, 0); + lv_obj_align(s_center_label, LV_ALIGN_CENTER, 0, 0); + + lvgl_port_unlock(); +} + +/** + * @brief 启动LVGL演示程序 + * + * 该函数是程序的入口点,负责初始化LCD硬件、LVGL库,并显示主界面 + */ +esp_err_t lvgl_st7735s_set_center_text(const char *text) +{ + ESP_RETURN_ON_FALSE(text != NULL, ESP_ERR_INVALID_ARG, TAG, "text is null"); + ESP_RETURN_ON_FALSE(s_center_label != NULL, ESP_ERR_INVALID_STATE, TAG, "label not ready"); + + lvgl_port_lock(0); + lv_label_set_text(s_center_label, text); + lv_obj_align(s_center_label, LV_ALIGN_CENTER, 0, 0); + lvgl_port_unlock(); + return ESP_OK; +} + +esp_err_t start_lvgl_demo(void) +{ + ESP_RETURN_ON_ERROR(app_lcd_init(), TAG, "LCD初始化失败"); + +#if EXAMPLE_LCD_ENABLE_COLOR_TEST + ESP_RETURN_ON_ERROR(app_lcd_color_test(), TAG, "LCD测试图绘制失败"); + vTaskDelay(pdMS_TO_TICKS(300)); +#endif + + ESP_RETURN_ON_ERROR(app_lvgl_init(), TAG, "LVGL初始化失败"); + + app_main_display(); + return ESP_OK; +} diff --git a/components/mqtt_control/CMakeLists.txt b/components/mqtt_control/CMakeLists.txt new file mode 100644 index 0000000..1211480 --- /dev/null +++ b/components/mqtt_control/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "mqtt_control.c" + INCLUDE_DIRS "include" + REQUIRES mqtt cjson) diff --git a/components/mqtt_control/include/mqtt_control.h b/components/mqtt_control/include/mqtt_control.h new file mode 100644 index 0000000..59d2c86 --- /dev/null +++ b/components/mqtt_control/include/mqtt_control.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include "esp_err.h" +#include "mqtt_client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + bool has_mode; + bool auto_mode; + + bool has_thresholds; + float light_on_lux; + float light_off_lux; + float hot_on_temp_c; + float hot_off_temp_c; + float cool_on_temp_c; + float cool_off_temp_c; + float fan_on_hum_pct; + float fan_off_hum_pct; + + bool has_fan; + bool fan_on; + + bool has_light; + bool light_on; + + bool has_hot; + bool hot_on; + + bool has_cool; + bool cool_on; +} mqtt_control_command_t; + +typedef esp_err_t (*mqtt_control_command_handler_t)(const mqtt_control_command_t *cmd, void *user_ctx); + +esp_err_t mqtt_control_start(void); +esp_err_t mqtt_control_stop(void); + +esp_err_t mqtt_control_register_command_handler(mqtt_control_command_handler_t handler, void *user_ctx); + +bool mqtt_control_is_connected(void); + +// Generic publish API for any topic. +esp_err_t mqtt_control_publish(const char *topic, + const char *payload, + int qos, + int retain); + +// Publish telemetry payload to default sensor topic. +esp_err_t mqtt_control_publish_sensor(const char *payload, int qos, int retain); + +#ifdef __cplusplus +} +#endif diff --git a/components/mqtt_control/mqtt_control.c b/components/mqtt_control/mqtt_control.c new file mode 100644 index 0000000..1817057 --- /dev/null +++ b/components/mqtt_control/mqtt_control.c @@ -0,0 +1,396 @@ +#include +#include +#include +#include + +#include "cJSON.h" +#include "esp_check.h" +#include "esp_log.h" +#include "esp_mac.h" + +#include "mqtt_control.h" + +// MQTT 服务器地址(协议+域名+端口) +#define MQTT_BROKER_URL "mqtt://beihong.wang:1883" +// MQTT 用户名 +#define MQTT_USERNAME "BotanicalBuddy" +// MQTT 密码 +#define MQTT_PASSWORD "YTGui8979HI" +// 传感器数据发布主题 +#define MQTT_SENSOR_TOPIC "topic/sensor/esp32_BotanicalBuddy_001" +// 控制指令订阅主题 +#define MQTT_CONTROL_TOPIC "topic/control/esp32_BotanicalBuddy_001" + + +static const char *TAG = "mqtt_control"; // 日志标签 + +static esp_mqtt_client_handle_t g_mqtt_client = NULL; // 全局 MQTT 客户端句柄 +static bool g_mqtt_connected = false; // MQTT 连接状态标志 +static mqtt_control_command_handler_t g_cmd_handler = NULL; +static void *g_cmd_user_ctx = NULL; + +static bool json_read_bool(cJSON *root, const char *key, bool *out) +{ + cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key); + if (item == NULL) + { + return false; + } + if (cJSON_IsBool(item)) + { + *out = cJSON_IsTrue(item); + return true; + } + if (cJSON_IsNumber(item)) + { + *out = (item->valuedouble != 0.0); + return true; + } + if (cJSON_IsString(item) && item->valuestring != NULL) + { + if (strcasecmp(item->valuestring, "on") == 0 || + strcasecmp(item->valuestring, "true") == 0 || + strcmp(item->valuestring, "1") == 0) + { + *out = true; + return true; + } + if (strcasecmp(item->valuestring, "off") == 0 || + strcasecmp(item->valuestring, "false") == 0 || + strcmp(item->valuestring, "0") == 0) + { + *out = false; + return true; + } + } + return false; +} + +static bool json_read_float(cJSON *root, const char *key, float *out) +{ + cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key); + if (!cJSON_IsNumber(item)) + { + return false; + } + *out = (float)item->valuedouble; + return true; +} + +static bool json_read_mode_auto(cJSON *root, const char *key, bool *out_auto) +{ + cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key); + if (item == NULL) + { + return false; + } + + if (cJSON_IsString(item) && item->valuestring != NULL) + { + if (strcasecmp(item->valuestring, "auto") == 0) + { + *out_auto = true; + return true; + } + if (strcasecmp(item->valuestring, "manual") == 0) + { + *out_auto = false; + return true; + } + } + + if (cJSON_IsBool(item)) + { + *out_auto = cJSON_IsTrue(item); + return true; + } + + if (cJSON_IsNumber(item)) + { + *out_auto = (item->valuedouble != 0.0); + return true; + } + + return false; +} + +static esp_err_t mqtt_parse_control_command(const char *data, int data_len, mqtt_control_command_t *out_cmd) +{ + ESP_RETURN_ON_FALSE(data != NULL && data_len > 0, ESP_ERR_INVALID_ARG, TAG, "invalid mqtt data"); + ESP_RETURN_ON_FALSE(out_cmd != NULL, ESP_ERR_INVALID_ARG, TAG, "out_cmd is null"); + + memset(out_cmd, 0, sizeof(*out_cmd)); + + cJSON *root = cJSON_ParseWithLength(data, (size_t)data_len); + ESP_RETURN_ON_FALSE(root != NULL, ESP_ERR_INVALID_ARG, TAG, "control json parse failed"); + + float light_on_lux = 0.0f; + float light_off_lux = 0.0f; + float hot_on_temp_c = 0.0f; + float hot_off_temp_c = 0.0f; + float cool_on_temp_c = 0.0f; + float cool_off_temp_c = 0.0f; + float fan_on_hum_pct = 0.0f; + float fan_off_hum_pct = 0.0f; + + bool has_light_on = json_read_float(root, "light_on", &light_on_lux); + bool has_light_off = json_read_float(root, "light_off", &light_off_lux); + bool has_hot_on = json_read_float(root, "hot_on_temp", &hot_on_temp_c); + bool has_hot_off = json_read_float(root, "hot_off_temp", &hot_off_temp_c); + bool has_cool_on = json_read_float(root, "cool_on_temp", &cool_on_temp_c); + bool has_cool_off = json_read_float(root, "cool_off_temp", &cool_off_temp_c); + bool has_fan_hum_on = json_read_float(root, "fan_on_hum", &fan_on_hum_pct); + bool has_fan_hum_off = json_read_float(root, "fan_off_hum", &fan_off_hum_pct); + + out_cmd->has_mode = json_read_mode_auto(root, "mode", &out_cmd->auto_mode); + + if (has_light_on && has_light_off && has_hot_on && has_hot_off && + has_cool_on && has_cool_off && has_fan_hum_on && has_fan_hum_off) + { + out_cmd->has_thresholds = true; + out_cmd->light_on_lux = light_on_lux; + out_cmd->light_off_lux = light_off_lux; + out_cmd->hot_on_temp_c = hot_on_temp_c; + out_cmd->hot_off_temp_c = hot_off_temp_c; + out_cmd->cool_on_temp_c = cool_on_temp_c; + out_cmd->cool_off_temp_c = cool_off_temp_c; + out_cmd->fan_on_hum_pct = fan_on_hum_pct; + out_cmd->fan_off_hum_pct = fan_off_hum_pct; + } + + out_cmd->has_fan = json_read_bool(root, "fan", &out_cmd->fan_on); + if (!out_cmd->has_fan) { + out_cmd->has_fan = json_read_bool(root, "pump", &out_cmd->fan_on); + } + out_cmd->has_light = json_read_bool(root, "light", &out_cmd->light_on); + out_cmd->has_hot = json_read_bool(root, "hot", &out_cmd->hot_on); + out_cmd->has_cool = json_read_bool(root, "cool", &out_cmd->cool_on); + + cJSON_Delete(root); + + ESP_RETURN_ON_FALSE(out_cmd->has_mode || out_cmd->has_thresholds || out_cmd->has_fan || out_cmd->has_light || + out_cmd->has_hot || out_cmd->has_cool, + ESP_ERR_INVALID_ARG, + TAG, + "no valid control fields in payload"); + return ESP_OK; +} + +/** + * @brief 判断接收到的 MQTT 主题是否与预期主题匹配 + * + * @param event_topic 事件中的主题字符串 + * @param event_topic_len 事件中主题的长度 + * @param expected 预期的主题字符串 + * @return true 匹配成功;false 匹配失败 + */ +static bool mqtt_topic_match(const char *event_topic, int event_topic_len, const char *expected) +{ + size_t expected_len = strlen(expected); + return expected_len == (size_t)event_topic_len && strncmp(event_topic, expected, expected_len) == 0; +} + +/** + * @brief MQTT 事件处理回调函数 + * + * 处理连接、断开、订阅、数据接收等事件。 + * + * @param handler_args 用户传入的参数(未使用) + * @param base 事件基类型(ESP-MQTT) + * @param event_id 具体事件 ID + * @param event_data 事件数据指针 + */ +static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + (void)handler_args; + ESP_LOGD(TAG, "event base=%s id=%" PRIi32, base, event_id); + + esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data; + esp_mqtt_client_handle_t client = event->client; + + switch ((esp_mqtt_event_id_t)event_id) + { + case MQTT_EVENT_CONNECTED: { + g_mqtt_connected = true; + ESP_LOGI(TAG, "MQTT connected"); + // 连接成功后订阅控制主题 + int msg_id = esp_mqtt_client_subscribe(client, MQTT_CONTROL_TOPIC, 1); + ESP_LOGI(TAG, "subscribe topic=%s msg_id=%d", MQTT_CONTROL_TOPIC, msg_id); + break; + } + + case MQTT_EVENT_DISCONNECTED: + g_mqtt_connected = false; + ESP_LOGW(TAG, "MQTT disconnected"); + break; + + case MQTT_EVENT_SUBSCRIBED: + ESP_LOGI(TAG, "MQTT subscribed msg_id=%d", event->msg_id); + break; + + case MQTT_EVENT_DATA: + ESP_LOGI(TAG, "MQTT data topic=%.*s data=%.*s", + event->topic_len, + event->topic, + event->data_len, + event->data); + + // 如果是控制主题的数据,则解析控制命令(待实现) + if (mqtt_topic_match(event->topic, event->topic_len, MQTT_CONTROL_TOPIC)) + { + mqtt_control_command_t cmd = {0}; + esp_err_t parse_ret = mqtt_parse_control_command(event->data, event->data_len, &cmd); + if (parse_ret != ESP_OK) + { + ESP_LOGW(TAG, "控制命令解析失败: %s", esp_err_to_name(parse_ret)); + break; + } + + if (g_cmd_handler != NULL) + { + esp_err_t handle_ret = g_cmd_handler(&cmd, g_cmd_user_ctx); + if (handle_ret != ESP_OK) + { + ESP_LOGW(TAG, "控制命令处理失败: %s", esp_err_to_name(handle_ret)); + } + } + else + { + ESP_LOGW(TAG, "未注册控制命令处理器,忽略控制消息"); + } + } + break; + + case MQTT_EVENT_ERROR: + ESP_LOGE(TAG, "MQTT error type=%d", event->error_handle ? event->error_handle->error_type : -1); + break; + + default: + break; + } +} + +/** + * @brief 启动 MQTT 客户端 + * + * 初始化客户端、注册事件回调、启动连接。 + * + * @return esp_err_t 启动结果,ESP_OK 表示成功 + */ +esp_err_t mqtt_control_start(void) +{ + if (g_mqtt_client != NULL) + { + return ESP_OK; + } + + // 生成基于 MAC 地址后三字节的唯一客户端 ID + char client_id[32] = {0}; + uint8_t mac[6] = {0}; + ESP_RETURN_ON_ERROR(esp_read_mac(mac, ESP_MAC_WIFI_STA), TAG, "read mac failed"); + snprintf(client_id, sizeof(client_id), "esp32_%02x%02x%02x", mac[3], mac[4], mac[5]); + + // 配置 MQTT 客户端参数 + esp_mqtt_client_config_t mqtt_cfg = { + .broker.address.uri = MQTT_BROKER_URL, + .credentials.username = MQTT_USERNAME, + .credentials.client_id = client_id, + .credentials.authentication.password = MQTT_PASSWORD, + }; + + g_mqtt_client = esp_mqtt_client_init(&mqtt_cfg); + ESP_RETURN_ON_FALSE(g_mqtt_client != NULL, ESP_FAIL, TAG, "mqtt client init failed"); + + ESP_RETURN_ON_ERROR(esp_mqtt_client_register_event(g_mqtt_client, + ESP_EVENT_ANY_ID, + mqtt_event_handler, + NULL), + TAG, + "register event failed"); + + ESP_RETURN_ON_ERROR(esp_mqtt_client_start(g_mqtt_client), TAG, "start mqtt client failed"); + ESP_LOGI(TAG, "MQTT started with client_id=%s", client_id); + return ESP_OK; +} + +esp_err_t mqtt_control_register_command_handler(mqtt_control_command_handler_t handler, void *user_ctx) +{ + g_cmd_handler = handler; + g_cmd_user_ctx = user_ctx; + return ESP_OK; +} + +/** + * @brief 停止并销毁 MQTT 客户端 + * + * @return esp_err_t 停止结果,ESP_OK 表示成功 + */ +esp_err_t mqtt_control_stop(void) +{ + if (g_mqtt_client == NULL) + { + return ESP_OK; + } + + esp_err_t ret = esp_mqtt_client_stop(g_mqtt_client); + if (ret != ESP_OK) + { + return ret; + } + + ret = esp_mqtt_client_destroy(g_mqtt_client); + if (ret != ESP_OK) + { + return ret; + } + + g_mqtt_client = NULL; + g_mqtt_connected = false; + return ESP_OK; +} + +/** + * @brief 查询 MQTT 当前连接状态 + * + * @return true 已连接;false 未连接 + */ +bool mqtt_control_is_connected(void) +{ + return g_mqtt_connected; +} + +/** + * @brief 发布 MQTT 消息到指定主题 + * + * @param topic 目标主题 + * @param payload 消息载荷 + * @param qos 服务质量等级(0,1,2) + * @param retain 是否保留消息 + * @return esp_err_t 发布结果 + */ +esp_err_t mqtt_control_publish(const char *topic, + const char *payload, + int qos, + int retain) +{ + ESP_RETURN_ON_FALSE(topic != NULL, ESP_ERR_INVALID_ARG, TAG, "topic is null"); + ESP_RETURN_ON_FALSE(payload != NULL, ESP_ERR_INVALID_ARG, TAG, "payload is null"); + ESP_RETURN_ON_FALSE(g_mqtt_client != NULL, ESP_ERR_INVALID_STATE, TAG, "mqtt not started"); + + int msg_id = esp_mqtt_client_publish(g_mqtt_client, topic, payload, 0, qos, retain); + ESP_RETURN_ON_FALSE(msg_id >= 0, ESP_FAIL, TAG, "publish failed"); + return ESP_OK; +} + +/** + * @brief 发布传感器数据到预定义的传感器主题 + * + * @param payload 传感器数据字符串 + * @param qos 服务质量 + * @param retain 是否保留消息 + * @return esp_err_t 发布结果 + */ +esp_err_t mqtt_control_publish_sensor(const char *payload, int qos, int retain) +{ + return mqtt_control_publish(MQTT_SENSOR_TOPIC, payload, qos, retain); +} \ No newline at end of file diff --git a/components/ui/.eez-project-build b/components/ui/.eez-project-build new file mode 100644 index 0000000..67f9efd --- /dev/null +++ b/components/ui/.eez-project-build @@ -0,0 +1,17 @@ +{ + "files": [ + "actions.h", + "fonts.h", + "images.c", + "images.h", + "screens.c", + "screens.h", + "structs.h", + "styles.c", + "styles.h", + "ui.c", + "ui.h", + "ui_font_24.c", + "vars.h" + ] +} \ No newline at end of file diff --git a/components/ui/CMakeLists.txt b/components/ui/CMakeLists.txt new file mode 100644 index 0000000..f799562 --- /dev/null +++ b/components/ui/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "." + INCLUDE_DIRS "." + REQUIRES lvgl esp_lvgl_port +) \ No newline at end of file diff --git a/components/ui/actions.h b/components/ui/actions.h new file mode 100644 index 0000000..5e0df4a --- /dev/null +++ b/components/ui/actions.h @@ -0,0 +1,14 @@ +#ifndef EEZ_LVGL_UI_EVENTS_H +#define EEZ_LVGL_UI_EVENTS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif /*EEZ_LVGL_UI_EVENTS_H*/ \ No newline at end of file diff --git a/components/ui/fonts.h b/components/ui/fonts.h new file mode 100644 index 0000000..e22237f --- /dev/null +++ b/components/ui/fonts.h @@ -0,0 +1,26 @@ +#ifndef EEZ_LVGL_UI_FONTS_H +#define EEZ_LVGL_UI_FONTS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const lv_font_t ui_font_24; + +#ifndef EXT_FONT_DESC_T +#define EXT_FONT_DESC_T +typedef struct _ext_font_desc_t { + const char *name; + const void *font_ptr; +} ext_font_desc_t; +#endif + +extern ext_font_desc_t fonts[]; + +#ifdef __cplusplus +} +#endif + +#endif /*EEZ_LVGL_UI_FONTS_H*/ \ No newline at end of file diff --git a/components/ui/images.c b/components/ui/images.c new file mode 100644 index 0000000..38d810c --- /dev/null +++ b/components/ui/images.c @@ -0,0 +1,5 @@ +#include "images.h" + +const ext_img_desc_t images[1] = { + 0 +}; \ No newline at end of file diff --git a/components/ui/images.h b/components/ui/images.h new file mode 100644 index 0000000..3aa52ad --- /dev/null +++ b/components/ui/images.h @@ -0,0 +1,24 @@ +#ifndef EEZ_LVGL_UI_IMAGES_H +#define EEZ_LVGL_UI_IMAGES_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef EXT_IMG_DESC_T +#define EXT_IMG_DESC_T +typedef struct _ext_img_desc_t { + const char *name; + const lv_img_dsc_t *img_dsc; +} ext_img_desc_t; +#endif + +extern const ext_img_desc_t images[1]; + +#ifdef __cplusplus +} +#endif + +#endif /*EEZ_LVGL_UI_IMAGES_H*/ \ No newline at end of file diff --git a/components/ui/screens.c b/components/ui/screens.c new file mode 100644 index 0000000..a10a04e --- /dev/null +++ b/components/ui/screens.c @@ -0,0 +1,215 @@ +#include + +#include "screens.h" +#include "images.h" +#include "fonts.h" +#include "actions.h" +#include "vars.h" +#include "styles.h" +#include "ui.h" + +#include + +objects_t objects; + +// +// Event handlers +// + +lv_obj_t *tick_value_change_obj; + +// +// Screens +// + +void create_screen_main() { + lv_obj_t *obj = lv_obj_create(0); + objects.main = obj; + lv_obj_set_pos(obj, 0, 0); + lv_obj_set_size(obj, 160, 80); + lv_obj_set_style_bg_color(obj, lv_color_hex(0xff72c801), LV_PART_MAIN | LV_STATE_DEFAULT); + { + lv_obj_t *parent_obj = obj; + { + lv_obj_t *obj = lv_label_create(parent_obj); + lv_obj_set_pos(obj, 6, 0); + lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_text_font(obj, &ui_font_24, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_label_set_text(obj, "温度"); + } + { + lv_obj_t *obj = lv_label_create(parent_obj); + objects.obj0 = obj; + lv_obj_set_pos(obj, 74, 0); + lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_text_font(obj, &ui_font_24, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_label_set_text(obj, ""); + } + { + lv_obj_t *obj = lv_label_create(parent_obj); + lv_obj_set_pos(obj, 6, 26); + lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_text_font(obj, &ui_font_24, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_label_set_text(obj, "湿度"); + } + { + lv_obj_t *obj = lv_label_create(parent_obj); + lv_obj_set_pos(obj, 6, 52); + lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_text_font(obj, &ui_font_24, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_label_set_text(obj, "光强"); + } + { + lv_obj_t *obj = lv_label_create(parent_obj); + objects.obj1 = obj; + lv_obj_set_pos(obj, 74, 26); + lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_text_font(obj, &ui_font_24, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_label_set_text(obj, ""); + } + { + lv_obj_t *obj = lv_label_create(parent_obj); + objects.obj2 = obj; + lv_obj_set_pos(obj, 74, 52); + lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_style_text_font(obj, &ui_font_24, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_label_set_text(obj, ""); + } + } + + tick_screen_main(); +} + +void tick_screen_main() { + { + const char *new_val = get_var_air_temperature(); + const char *cur_val = lv_label_get_text(objects.obj0); + if (strcmp(new_val, cur_val) != 0) { + tick_value_change_obj = objects.obj0; + lv_label_set_text(objects.obj0, new_val); + tick_value_change_obj = NULL; + } + } + { + const char *new_val = get_var_air_humidity(); + const char *cur_val = lv_label_get_text(objects.obj1); + if (strcmp(new_val, cur_val) != 0) { + tick_value_change_obj = objects.obj1; + lv_label_set_text(objects.obj1, new_val); + tick_value_change_obj = NULL; + } + } + { + const char *new_val = get_var_light_intensity(); + const char *cur_val = lv_label_get_text(objects.obj2); + if (strcmp(new_val, cur_val) != 0) { + tick_value_change_obj = objects.obj2; + lv_label_set_text(objects.obj2, new_val); + tick_value_change_obj = NULL; + } + } +} + +typedef void (*tick_screen_func_t)(); +tick_screen_func_t tick_screen_funcs[] = { + tick_screen_main, +}; +void tick_screen(int screen_index) { + tick_screen_funcs[screen_index](); +} +void tick_screen_by_id(enum ScreensEnum screenId) { + tick_screen_funcs[screenId - 1](); +} + +// +// Fonts +// + +ext_font_desc_t fonts[] = { + { "24", &ui_font_24 }, +#if LV_FONT_MONTSERRAT_8 + { "MONTSERRAT_8", &lv_font_montserrat_8 }, +#endif +#if LV_FONT_MONTSERRAT_10 + { "MONTSERRAT_10", &lv_font_montserrat_10 }, +#endif +#if LV_FONT_MONTSERRAT_12 + { "MONTSERRAT_12", &lv_font_montserrat_12 }, +#endif +#if LV_FONT_MONTSERRAT_14 + { "MONTSERRAT_14", &lv_font_montserrat_14 }, +#endif +#if LV_FONT_MONTSERRAT_16 + { "MONTSERRAT_16", &lv_font_montserrat_16 }, +#endif +#if LV_FONT_MONTSERRAT_18 + { "MONTSERRAT_18", &lv_font_montserrat_18 }, +#endif +#if LV_FONT_MONTSERRAT_20 + { "MONTSERRAT_20", &lv_font_montserrat_20 }, +#endif +#if LV_FONT_MONTSERRAT_22 + { "MONTSERRAT_22", &lv_font_montserrat_22 }, +#endif +#if LV_FONT_MONTSERRAT_24 + { "MONTSERRAT_24", &lv_font_montserrat_24 }, +#endif +#if LV_FONT_MONTSERRAT_26 + { "MONTSERRAT_26", &lv_font_montserrat_26 }, +#endif +#if LV_FONT_MONTSERRAT_28 + { "MONTSERRAT_28", &lv_font_montserrat_28 }, +#endif +#if LV_FONT_MONTSERRAT_30 + { "MONTSERRAT_30", &lv_font_montserrat_30 }, +#endif +#if LV_FONT_MONTSERRAT_32 + { "MONTSERRAT_32", &lv_font_montserrat_32 }, +#endif +#if LV_FONT_MONTSERRAT_34 + { "MONTSERRAT_34", &lv_font_montserrat_34 }, +#endif +#if LV_FONT_MONTSERRAT_36 + { "MONTSERRAT_36", &lv_font_montserrat_36 }, +#endif +#if LV_FONT_MONTSERRAT_38 + { "MONTSERRAT_38", &lv_font_montserrat_38 }, +#endif +#if LV_FONT_MONTSERRAT_40 + { "MONTSERRAT_40", &lv_font_montserrat_40 }, +#endif +#if LV_FONT_MONTSERRAT_42 + { "MONTSERRAT_42", &lv_font_montserrat_42 }, +#endif +#if LV_FONT_MONTSERRAT_44 + { "MONTSERRAT_44", &lv_font_montserrat_44 }, +#endif +#if LV_FONT_MONTSERRAT_46 + { "MONTSERRAT_46", &lv_font_montserrat_46 }, +#endif +#if LV_FONT_MONTSERRAT_48 + { "MONTSERRAT_48", &lv_font_montserrat_48 }, +#endif +}; + +// +// Color themes +// + +uint32_t active_theme_index = 0; + +// +// +// + +void create_screens() { + +// Set default LVGL theme + lv_display_t *dispp = lv_display_get_default(); + lv_theme_t *theme = lv_theme_default_init(dispp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED), false, LV_FONT_DEFAULT); + lv_display_set_theme(dispp, theme); + + // Initialize screens + // Create screens + create_screen_main(); +} \ No newline at end of file diff --git a/components/ui/screens.h b/components/ui/screens.h new file mode 100644 index 0000000..20a7984 --- /dev/null +++ b/components/ui/screens.h @@ -0,0 +1,39 @@ +#ifndef EEZ_LVGL_UI_SCREENS_H +#define EEZ_LVGL_UI_SCREENS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Screens + +enum ScreensEnum { + _SCREEN_ID_FIRST = 1, + SCREEN_ID_MAIN = 1, + _SCREEN_ID_LAST = 1 +}; + +typedef struct _objects_t { + lv_obj_t *main; + lv_obj_t *obj0; + lv_obj_t *obj1; + lv_obj_t *obj2; +} objects_t; + +extern objects_t objects; + +void create_screen_main(); +void tick_screen_main(); + +void tick_screen_by_id(enum ScreensEnum screenId); +void tick_screen(int screen_index); + +void create_screens(); + +#ifdef __cplusplus +} +#endif + +#endif /*EEZ_LVGL_UI_SCREENS_H*/ \ No newline at end of file diff --git a/components/ui/structs.h b/components/ui/structs.h new file mode 100644 index 0000000..8d8441b --- /dev/null +++ b/components/ui/structs.h @@ -0,0 +1,4 @@ +#ifndef EEZ_LVGL_UI_STRUCTS_H +#define EEZ_LVGL_UI_STRUCTS_H + +#endif /*EEZ_LVGL_UI_STRUCTS_H*/ \ No newline at end of file diff --git a/components/ui/styles.c b/components/ui/styles.c new file mode 100644 index 0000000..78cf152 --- /dev/null +++ b/components/ui/styles.c @@ -0,0 +1,6 @@ +#include "styles.h" +#include "images.h" +#include "fonts.h" + +#include "ui.h" +#include "screens.h" \ No newline at end of file diff --git a/components/ui/styles.h b/components/ui/styles.h new file mode 100644 index 0000000..f4d548d --- /dev/null +++ b/components/ui/styles.h @@ -0,0 +1,14 @@ +#ifndef EEZ_LVGL_UI_STYLES_H +#define EEZ_LVGL_UI_STYLES_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif /*EEZ_LVGL_UI_STYLES_H*/ \ No newline at end of file diff --git a/components/ui/ui.c b/components/ui/ui.c new file mode 100644 index 0000000..b2d7ab3 --- /dev/null +++ b/components/ui/ui.c @@ -0,0 +1,32 @@ +#include "ui.h" +#include "screens.h" +#include "images.h" +#include "actions.h" +#include "vars.h" + +#include + +static int16_t currentScreen = -1; + +static lv_obj_t *getLvglObjectFromIndex(int32_t index) { + if (index == -1) { + return 0; + } + return ((lv_obj_t **)&objects)[index]; +} + +void loadScreen(enum ScreensEnum screenId) { + currentScreen = screenId - 1; + lv_obj_t *screen = getLvglObjectFromIndex(currentScreen); + lv_scr_load_anim(screen, LV_SCR_LOAD_ANIM_FADE_IN, 200, 0, false); +} + +void ui_init() { + create_screens(); + loadScreen(SCREEN_ID_MAIN); + +} + +void ui_tick() { + tick_screen(currentScreen); +} \ No newline at end of file diff --git a/components/ui/ui.h b/components/ui/ui.h new file mode 100644 index 0000000..0462f26 --- /dev/null +++ b/components/ui/ui.h @@ -0,0 +1,21 @@ +#ifndef EEZ_LVGL_UI_GUI_H +#define EEZ_LVGL_UI_GUI_H + +#include + +#include "screens.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void ui_init(); +void ui_tick(); + +void loadScreen(enum ScreensEnum screenId); + +#ifdef __cplusplus +} +#endif + +#endif // EEZ_LVGL_UI_GUI_H \ No newline at end of file diff --git a/components/ui/ui_font_24.c b/components/ui/ui_font_24.c new file mode 100644 index 0000000..0bf394d --- /dev/null +++ b/components/ui/ui_font_24.c @@ -0,0 +1,3477 @@ +/******************************************************************************* + * Size: 24 px + * Bpp: 8 + * Opts: --bpp 8 --size 24 --no-compress --font ..\09_SourceHanSansSC\OTF\SimplifiedChinese\SourceHanSansSC-Medium.otf --symbols 温湿度光照强 --range 32-127 --format lvgl + ******************************************************************************/ + +#ifdef __has_include + #if __has_include("lvgl.h") + #ifndef LV_LVGL_H_INCLUDE_SIMPLE + #define LV_LVGL_H_INCLUDE_SIMPLE + #endif + #endif +#endif + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE + #include "lvgl.h" +#else + #include "lvgl.h" +#endif + +#ifndef UI_FONT_24 +#define UI_FONT_24 1 +#endif + +#if UI_FONT_24 + +/*----------------- + * BITMAPS + *----------------*/ + +/*Store the image of the glyphs*/ +static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { + /* U+0020 " " */ + + /* U+0021 "!" */ + 0x44, 0xff, 0xff, 0x80, 0x3b, 0xff, 0xff, 0x7b, + 0x33, 0xff, 0xff, 0x76, 0x2b, 0xff, 0xff, 0x6d, + 0x21, 0xff, 0xff, 0x63, 0x17, 0xff, 0xff, 0x58, + 0xd, 0xff, 0xff, 0x4e, 0x4, 0xff, 0xff, 0x43, + 0x0, 0xfa, 0xff, 0x39, 0x0, 0xf0, 0xff, 0x2e, + 0x0, 0xe7, 0xff, 0x24, 0x0, 0xdd, 0xff, 0x19, + 0x0, 0xbd, 0xe3, 0xe, 0x0, 0x0, 0x2, 0x0, + 0x2c, 0xe0, 0xf2, 0x56, 0xa1, 0xff, 0xff, 0xe1, + 0x9e, 0xff, 0xff, 0xdc, 0x26, 0xd5, 0xe8, 0x4d, + + /* U+0022 "\"" */ + 0xd9, 0xff, 0xf8, 0x0, 0x0, 0x98, 0xff, 0xff, + 0x40, 0xd4, 0xff, 0xf3, 0x0, 0x0, 0x8f, 0xff, + 0xff, 0x3b, 0xce, 0xff, 0xee, 0x0, 0x0, 0x87, + 0xff, 0xff, 0x36, 0xbc, 0xff, 0xdb, 0x0, 0x0, + 0x74, 0xff, 0xff, 0x23, 0x9e, 0xff, 0xc0, 0x0, + 0x0, 0x56, 0xff, 0xfe, 0x7, 0x81, 0xff, 0xa4, + 0x0, 0x0, 0x39, 0xff, 0xe8, 0x0, 0x64, 0xff, + 0x89, 0x0, 0x0, 0x1c, 0xff, 0xcb, 0x0, 0x3f, + 0xdf, 0x61, 0x0, 0x0, 0x3, 0xdd, 0x9a, 0x0, + + /* U+0023 "#" */ + 0x0, 0x0, 0x0, 0x0, 0x84, 0xff, 0x2f, 0x0, + 0x0, 0x7b, 0xff, 0x38, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xab, 0xfe, 0xa, 0x0, 0x0, 0xa1, 0xff, + 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd2, 0xe1, + 0x0, 0x0, 0x0, 0xc7, 0xf1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0xf6, 0xba, 0x0, 0x0, 0x0, + 0xed, 0xcd, 0x0, 0x0, 0x0, 0x83, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe3, 0x0, 0x7b, 0xef, 0xf4, 0xff, 0xf6, 0xef, + 0xef, 0xf3, 0xff, 0xf8, 0xef, 0xd5, 0x0, 0x0, + 0x0, 0x57, 0xff, 0x5b, 0x0, 0x0, 0x4c, 0xff, + 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x73, 0xff, + 0x3f, 0x0, 0x0, 0x69, 0xff, 0x54, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x8f, 0xff, 0x23, 0x0, 0x0, + 0x86, 0xff, 0x36, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xab, 0xff, 0x8, 0x0, 0x0, 0xa3, 0xff, 0x18, + 0x0, 0x0, 0xe, 0xe7, 0xe7, 0xf9, 0xff, 0xe7, + 0xe7, 0xe7, 0xf8, 0xff, 0xe8, 0xe7, 0x4f, 0xf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x57, 0x0, 0x0, 0x4, 0xfd, + 0xb8, 0x0, 0x0, 0x0, 0xf6, 0xc1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1d, 0xff, 0x9c, 0x0, 0x0, + 0x12, 0xff, 0xa6, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x38, 0xff, 0x7f, 0x0, 0x0, 0x2e, 0xff, 0x8a, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x54, 0xff, 0x62, + 0x0, 0x0, 0x4a, 0xff, 0x6f, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x6f, 0xff, 0x46, 0x0, 0x0, 0x66, + 0xff, 0x54, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8a, + 0xff, 0x29, 0x0, 0x0, 0x82, 0xff, 0x39, 0x0, + 0x0, 0x0, + + /* U+0024 "$" */ + 0x0, 0x0, 0x0, 0x0, 0xb, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb, 0xff, 0xdf, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x16, 0xff, 0xe6, 0xb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0xa2, + 0xf6, 0xff, 0xff, 0xf6, 0xab, 0x2c, 0x0, 0x0, + 0x0, 0xd, 0xd7, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf0, 0x41, 0x0, 0x0, 0x81, 0xff, 0xff, + 0xaa, 0x32, 0x2f, 0x67, 0xe3, 0xfd, 0x53, 0x0, + 0x0, 0xd5, 0xff, 0xdd, 0x4, 0x0, 0x0, 0x0, + 0x16, 0x54, 0x0, 0x0, 0x1, 0xf9, 0xff, 0xac, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xe6, 0xff, 0xe0, 0x4, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa6, 0xff, 0xff, + 0xa5, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1d, 0xee, 0xff, 0xff, 0xde, 0x4a, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0xc8, + 0xff, 0xff, 0xff, 0xa6, 0xe, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x5, 0x88, 0xfc, 0xff, 0xff, + 0xdc, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x31, 0xc9, 0xff, 0xff, 0xf8, 0x29, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x8d, + 0xff, 0xff, 0xb2, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xd7, 0xff, 0xed, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xa9, 0xff, 0xf9, 0x2, 0x0, 0x72, 0x25, 0x0, + 0x0, 0x0, 0x0, 0x5, 0xdd, 0xff, 0xd6, 0x0, + 0x5b, 0xff, 0xf7, 0x88, 0x49, 0x1f, 0x36, 0xae, + 0xff, 0xff, 0x7c, 0x0, 0x34, 0xdd, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xd3, 0xb, 0x0, + 0x0, 0xf, 0x95, 0xe3, 0xff, 0xff, 0xff, 0xf3, + 0x9d, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x31, 0xff, 0xe5, 0x9, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb, 0xff, 0xdf, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0025 "%" */ + 0x0, 0x0, 0x1a, 0xa8, 0xe5, 0xe3, 0xa6, 0x18, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18, 0xf4, + 0xb2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18, + 0xe7, 0xff, 0xd1, 0xd3, 0xff, 0xe6, 0x16, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x96, 0xfd, 0x2c, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x90, 0xff, 0x97, + 0x0, 0x0, 0x9a, 0xff, 0x8d, 0x0, 0x0, 0x0, + 0x0, 0x24, 0xfb, 0x9f, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xe7, 0xff, 0x1d, 0x0, 0x0, + 0x21, 0xff, 0xe5, 0x0, 0x0, 0x0, 0x0, 0xa9, + 0xf8, 0x1e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9, 0xff, 0xf3, 0x0, 0x0, 0x0, 0x0, 0xf7, + 0xfe, 0x9, 0x0, 0x0, 0x34, 0xfe, 0x8c, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xff, + 0xe7, 0x0, 0x0, 0x0, 0x0, 0xeb, 0xff, 0x19, + 0x0, 0x0, 0xbc, 0xf0, 0x12, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2, 0xf9, 0xfb, 0x1, + 0x0, 0x0, 0x3, 0xfd, 0xf8, 0x2, 0x0, 0x46, + 0xff, 0x79, 0x0, 0x18, 0x71, 0xa0, 0x76, 0x1f, + 0x0, 0x0, 0x0, 0xd2, 0xff, 0x3e, 0x0, 0x0, + 0x42, 0xff, 0xd0, 0x0, 0x1, 0xce, 0xe5, 0xa, + 0x2a, 0xe2, 0xff, 0xff, 0xff, 0xeb, 0x37, 0x0, + 0x0, 0x61, 0xff, 0xce, 0x22, 0x24, 0xd0, 0xff, + 0x5e, 0x0, 0x59, 0xff, 0x65, 0x0, 0xc0, 0xff, + 0x7f, 0x12, 0x6b, 0xfe, 0xd4, 0x1, 0x0, 0x4, + 0xa8, 0xff, 0xff, 0xff, 0xff, 0xa4, 0x3, 0x5, + 0xdd, 0xd8, 0x4, 0x31, 0xff, 0xd9, 0x0, 0x0, + 0x0, 0xbf, 0xff, 0x46, 0x0, 0x0, 0x0, 0x51, + 0x8d, 0x8c, 0x4f, 0x0, 0x0, 0x6c, 0xff, 0x52, + 0x0, 0x62, 0xff, 0x94, 0x0, 0x0, 0x0, 0x77, + 0xff, 0x78, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xc, 0xe9, 0xc9, 0x0, 0x0, 0x81, + 0xff, 0x7c, 0x0, 0x0, 0x0, 0x5e, 0xff, 0x97, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0x40, 0x0, 0x0, 0x88, 0xff, 0x77, + 0x0, 0x0, 0x0, 0x58, 0xff, 0x9f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x15, 0xf3, 0xb6, + 0x0, 0x0, 0x0, 0x6a, 0xff, 0x8c, 0x0, 0x0, + 0x0, 0x6f, 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x92, 0xfd, 0x2f, 0x0, 0x0, + 0x0, 0x45, 0xff, 0xc2, 0x0, 0x0, 0x0, 0xa7, + 0xff, 0x5b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x22, 0xfa, 0xa3, 0x0, 0x0, 0x0, 0x0, 0x3, + 0xde, 0xfe, 0x3e, 0x0, 0x2d, 0xf6, 0xec, 0xa, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa5, 0xf9, + 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x56, 0xfc, + 0xf8, 0xc6, 0xf4, 0xff, 0x69, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x30, 0xfe, 0x90, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x4b, 0xc1, 0xf1, + 0xc7, 0x59, 0x0, 0x0, + + /* U+0026 "&" */ + 0x0, 0x0, 0x0, 0x0, 0xc, 0x8d, 0xd4, 0xf4, + 0xcf, 0x64, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb, 0xd8, 0xff, 0xff, + 0xfe, 0xff, 0xff, 0x75, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x85, 0xff, 0xf6, + 0x49, 0x7, 0x90, 0xff, 0xef, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc9, 0xff, + 0x98, 0x0, 0x0, 0x2c, 0xff, 0xff, 0x17, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdf, + 0xff, 0x74, 0x0, 0x0, 0x3b, 0xff, 0xf0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb9, 0xff, 0x91, 0x0, 0x4, 0xbb, 0xff, 0x91, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x79, 0xff, 0xdc, 0x16, 0xbf, 0xff, 0xc8, + 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x16, 0xf9, 0xff, 0xed, 0xff, 0xa1, + 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3d, 0xf2, 0xff, 0xff, 0x8a, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x4e, 0xcf, 0xcf, + 0x20, 0x0, 0x0, 0x5b, 0xf8, 0xff, 0xff, 0xff, + 0xdb, 0x14, 0x0, 0x0, 0x0, 0x0, 0xba, 0xff, + 0xcd, 0x0, 0x0, 0x50, 0xff, 0xff, 0xca, 0x4f, + 0xfa, 0xff, 0xc5, 0x8, 0x0, 0x0, 0x22, 0xfe, + 0xff, 0x6a, 0x0, 0x0, 0xda, 0xff, 0xe4, 0xf, + 0x0, 0x64, 0xff, 0xff, 0xc3, 0x12, 0x0, 0xb1, + 0xff, 0xea, 0xf, 0x0, 0x14, 0xff, 0xff, 0x8d, + 0x0, 0x0, 0x0, 0x82, 0xff, 0xff, 0xd8, 0x73, + 0xff, 0xff, 0x59, 0x0, 0x0, 0x1b, 0xff, 0xff, + 0x9a, 0x0, 0x0, 0x0, 0x0, 0x74, 0xff, 0xff, + 0xff, 0xff, 0xb6, 0x0, 0x0, 0x0, 0x0, 0xe8, + 0xff, 0xeb, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x9d, + 0xff, 0xff, 0xff, 0xb9, 0x2b, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0xff, 0xe6, 0x76, 0x4b, 0x68, 0xb9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xb9, 0x36, + 0x0, 0x2, 0xad, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xb5, 0x29, 0x85, 0xf7, 0xff, 0xff, + 0x3f, 0x0, 0x0, 0x0, 0x48, 0xb2, 0xda, 0xf8, + 0xe8, 0xb5, 0x55, 0x0, 0x0, 0x0, 0x1f, 0x85, + 0xd0, 0x4, + + /* U+0027 "'" */ + 0xd9, 0xff, 0xf8, 0xd4, 0xff, 0xf3, 0xce, 0xff, + 0xee, 0xbc, 0xff, 0xdb, 0x9e, 0xff, 0xc0, 0x81, + 0xff, 0xa4, 0x64, 0xff, 0x89, 0x3f, 0xdf, 0x61, + + /* U+0028 "(" */ + 0x0, 0x0, 0x0, 0x30, 0x20, 0x0, 0x0, 0x0, + 0x1, 0xd1, 0xfb, 0x37, 0x0, 0x0, 0x58, 0xff, + 0xd7, 0x2, 0x0, 0x0, 0xd5, 0xff, 0x5e, 0x0, + 0x0, 0x36, 0xff, 0xef, 0x5, 0x0, 0x0, 0x94, + 0xff, 0xa3, 0x0, 0x0, 0x1, 0xea, 0xff, 0x52, + 0x0, 0x0, 0x26, 0xff, 0xff, 0x12, 0x0, 0x0, + 0x5c, 0xff, 0xe2, 0x0, 0x0, 0x0, 0x91, 0xff, + 0xb4, 0x0, 0x0, 0x0, 0xac, 0xff, 0x93, 0x0, + 0x0, 0x0, 0xbe, 0xff, 0x84, 0x0, 0x0, 0x0, + 0xcf, 0xff, 0x75, 0x0, 0x0, 0x0, 0xd6, 0xff, + 0x70, 0x0, 0x0, 0x0, 0xc6, 0xff, 0x7d, 0x0, + 0x0, 0x0, 0xb5, 0xff, 0x8b, 0x0, 0x0, 0x0, + 0xa3, 0xff, 0xa1, 0x0, 0x0, 0x0, 0x77, 0xff, + 0xcc, 0x0, 0x0, 0x0, 0x40, 0xff, 0xf7, 0x3, + 0x0, 0x0, 0xc, 0xfd, 0xff, 0x2f, 0x0, 0x0, + 0x0, 0xc0, 0xff, 0x7c, 0x0, 0x0, 0x0, 0x62, + 0xff, 0xcd, 0x0, 0x0, 0x0, 0xe, 0xf6, 0xff, + 0x28, 0x0, 0x0, 0x0, 0x95, 0xff, 0x9f, 0x0, + 0x0, 0x0, 0x19, 0xf6, 0xfb, 0x20, 0x0, 0x0, + 0x0, 0x80, 0x90, 0x18, + + /* U+0029 ")" */ + 0x2, 0x4b, 0x1, 0x0, 0x0, 0x0, 0xac, 0xff, + 0x56, 0x0, 0x0, 0x0, 0x52, 0xff, 0xd9, 0x3, + 0x0, 0x0, 0x1, 0xd5, 0xff, 0x5c, 0x0, 0x0, + 0x0, 0x6c, 0xff, 0xbd, 0x0, 0x0, 0x0, 0x1c, + 0xfe, 0xfe, 0x1e, 0x0, 0x0, 0x0, 0xc9, 0xff, + 0x75, 0x0, 0x0, 0x0, 0x87, 0xff, 0xaf, 0x0, + 0x0, 0x0, 0x59, 0xff, 0xe6, 0x0, 0x0, 0x0, + 0x2b, 0xff, 0xff, 0x1c, 0x0, 0x0, 0x9, 0xff, + 0xff, 0x37, 0x0, 0x0, 0x0, 0xfa, 0xff, 0x48, + 0x0, 0x0, 0x0, 0xeb, 0xff, 0x5a, 0x0, 0x0, + 0x0, 0xe5, 0xff, 0x62, 0x0, 0x0, 0x0, 0xf3, + 0xff, 0x51, 0x0, 0x0, 0x3, 0xff, 0xff, 0x3f, + 0x0, 0x0, 0x17, 0xff, 0xff, 0x2d, 0x0, 0x0, + 0x43, 0xff, 0xfa, 0x7, 0x0, 0x0, 0x71, 0xff, + 0xca, 0x0, 0x0, 0x0, 0xa5, 0xff, 0x93, 0x0, + 0x0, 0x5, 0xef, 0xff, 0x49, 0x0, 0x0, 0x45, + 0xff, 0xe7, 0x3, 0x0, 0x0, 0x9f, 0xff, 0x8b, + 0x0, 0x0, 0x1e, 0xfa, 0xfa, 0x21, 0x0, 0x0, + 0x94, 0xff, 0x94, 0x0, 0x0, 0x0, 0x4e, 0xc0, + 0x19, 0x0, 0x0, 0x0, + + /* U+002A "*" */ + 0x0, 0x0, 0x0, 0x7, 0xff, 0x9d, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1c, 0xff, 0xb3, + 0x0, 0x0, 0x0, 0x0, 0x21, 0x70, 0x31, 0x35, + 0xff, 0xc9, 0xf, 0x4a, 0x6a, 0x0, 0x77, 0xff, + 0xff, 0xf9, 0xff, 0xfc, 0xfd, 0xff, 0xfc, 0x12, + 0xa, 0x6b, 0xdb, 0xff, 0xff, 0xff, 0xfd, 0xaf, + 0x3d, 0x0, 0x0, 0x0, 0x23, 0xfc, 0xff, 0xff, + 0xb8, 0x0, 0x0, 0x0, 0x0, 0x0, 0xac, 0xff, + 0xba, 0xee, 0xff, 0x45, 0x0, 0x0, 0x0, 0x38, + 0xff, 0xd1, 0xc, 0x43, 0xfb, 0xd2, 0x0, 0x0, + 0x0, 0x4, 0x7f, 0x1e, 0x0, 0x0, 0x61, 0x40, + 0x0, 0x0, + + /* U+002B "+" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x31, 0xd3, 0xbc, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3b, 0xff, 0xe3, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3b, + 0xff, 0xe3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3b, 0xff, 0xe3, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x3b, 0xff, 0xe3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x13, 0x13, 0x13, 0x13, 0x4b, 0xff, + 0xe6, 0x13, 0x13, 0x13, 0x13, 0xf, 0x17, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xc3, 0x17, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3b, 0xff, 0xe3, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3b, 0xff, 0xe3, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3b, + 0xff, 0xe3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3b, 0xff, 0xe3, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x3b, 0xff, 0xe3, 0x0, 0x0, 0x0, 0x0, + 0x0, + + /* U+002C "," */ + 0x0, 0x33, 0x91, 0x6d, 0x1, 0x14, 0xf7, 0xff, + 0xff, 0x69, 0x26, 0xff, 0xff, 0xff, 0xb9, 0x0, + 0x87, 0xec, 0xff, 0xcb, 0x0, 0x0, 0x33, 0xff, + 0xab, 0x0, 0x0, 0x8c, 0xff, 0x6d, 0x0, 0x62, + 0xfc, 0xe7, 0xa, 0x87, 0xff, 0xef, 0x32, 0x0, + 0x4d, 0xaa, 0x23, 0x0, 0x0, + + /* U+002D "-" */ + 0x18, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0xd, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x77, 0xdf, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x77, + + /* U+002E "." */ + 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x9a, 0xfb, + 0xbb, 0x8, 0x2d, 0xff, 0xff, 0xff, 0x5c, 0x29, + 0xff, 0xff, 0xff, 0x58, 0x0, 0x8d, 0xf2, 0xaf, + 0x5, + + /* U+002F "/" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f, 0xff, + 0xb8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x74, + 0xff, 0x73, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xba, 0xff, 0x2e, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x8, 0xf7, 0xe8, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x45, 0xff, 0xa3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8b, 0xff, 0x5e, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xd1, 0xff, 0x1a, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x17, 0xff, 0xd4, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x5c, 0xff, 0x8e, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa2, 0xff, 0x49, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe7, 0xf9, + 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2d, 0xff, + 0xbf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x73, + 0xff, 0x7a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb9, 0xff, 0x34, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x8, 0xf6, 0xed, 0x2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x44, 0xff, 0xaa, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8a, 0xff, 0x65, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xd0, 0xff, 0x20, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x17, 0xfe, 0xda, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x5b, 0xff, 0x95, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa1, 0xff, 0x50, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe6, 0xfc, + 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2c, 0xff, + 0xc5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x72, + 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x53, 0x7f, 0x26, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, + + /* U+0030 "0" */ + 0x0, 0x0, 0x1, 0x67, 0xcc, 0xf4, 0xec, 0xb4, + 0x3b, 0x0, 0x0, 0x0, 0x0, 0x2, 0xac, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0x5f, 0x0, 0x0, + 0x0, 0x74, 0xff, 0xff, 0xc6, 0x60, 0x73, 0xe8, + 0xff, 0xfa, 0x26, 0x0, 0x8, 0xee, 0xff, 0xd6, + 0xd, 0x0, 0x0, 0x36, 0xf9, 0xff, 0xa2, 0x0, + 0x4e, 0xff, 0xff, 0x5e, 0x0, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xf2, 0x7, 0x8d, 0xff, 0xff, 0x18, + 0x0, 0x0, 0x0, 0x0, 0x66, 0xff, 0xff, 0x38, + 0xb7, 0xff, 0xef, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3e, 0xff, 0xff, 0x62, 0xce, 0xff, 0xd8, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x26, 0xff, 0xff, 0x79, + 0xda, 0xff, 0xcf, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1c, 0xff, 0xff, 0x86, 0xd9, 0xff, 0xcf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1d, 0xff, 0xff, 0x84, + 0xcc, 0xff, 0xda, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x28, 0xff, 0xff, 0x77, 0xb2, 0xff, 0xf3, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x41, 0xff, 0xff, 0x5d, + 0x88, 0xff, 0xff, 0x26, 0x0, 0x0, 0x0, 0x0, + 0x73, 0xff, 0xff, 0x33, 0x46, 0xff, 0xff, 0x73, + 0x0, 0x0, 0x0, 0x0, 0xc0, 0xff, 0xee, 0x4, + 0x5, 0xe7, 0xff, 0xdd, 0x11, 0x0, 0x0, 0x3d, + 0xfc, 0xff, 0x9a, 0x0, 0x0, 0x69, 0xff, 0xff, + 0xcb, 0x64, 0x78, 0xeb, 0xff, 0xf7, 0x21, 0x0, + 0x0, 0x0, 0xa3, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfb, 0x58, 0x0, 0x0, 0x0, 0x0, 0x0, 0x63, + 0xcb, 0xf5, 0xed, 0xb3, 0x37, 0x0, 0x0, 0x0, + + /* U+0031 "1" */ + 0x0, 0x0, 0x10, 0x71, 0xe7, 0xff, 0xb7, 0x0, + 0x0, 0x0, 0x0, 0x26, 0xcb, 0xfd, 0xff, 0xff, + 0xff, 0xb7, 0x0, 0x0, 0x0, 0x0, 0x3b, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xb7, 0x0, 0x0, 0x0, + 0x0, 0x9, 0x27, 0x27, 0x38, 0xff, 0xff, 0xb7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, + 0xff, 0xff, 0xb7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x13, 0xff, 0xff, 0xb7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, 0xff, 0xff, + 0xb7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x13, 0xff, 0xff, 0xb7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x13, 0xff, 0xff, 0xb7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, 0xff, + 0xff, 0xb7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x13, 0xff, 0xff, 0xb7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x13, 0xff, 0xff, 0xb7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, + 0xff, 0xff, 0xb7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x13, 0xff, 0xff, 0xb7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, 0xff, 0xff, + 0xb7, 0x0, 0x0, 0x0, 0x0, 0x57, 0x5b, 0x5b, + 0x68, 0xff, 0xff, 0xd1, 0x5b, 0x5b, 0x5b, 0xc, + 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x23, 0xf3, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x23, + + /* U+0032 "2" */ + 0x0, 0x0, 0x0, 0x48, 0xb2, 0xe4, 0xf3, 0xca, + 0x8f, 0x12, 0x0, 0x0, 0x0, 0x0, 0x7, 0xa1, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x35, + 0x0, 0x0, 0x0, 0xb0, 0xff, 0xfe, 0xab, 0x61, + 0x69, 0xba, 0xff, 0xff, 0xd5, 0x6, 0x0, 0x0, + 0x4d, 0xf1, 0x59, 0x0, 0x0, 0x0, 0x0, 0xaa, + 0xff, 0xff, 0x52, 0x0, 0x0, 0x0, 0xf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3b, 0xff, 0xff, 0x80, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1f, 0xff, 0xff, 0x9b, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4b, 0xff, + 0xff, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x85, 0xff, 0xff, 0x3c, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, + 0xee, 0xff, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x96, 0xff, 0xff, 0x42, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x44, 0xfc, 0xff, 0xaf, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2c, 0xf1, 0xff, 0xd9, + 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1a, 0xe2, 0xff, 0xf1, 0x28, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1a, 0xd6, 0xff, 0xf5, + 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x21, 0xdc, 0xff, 0xf5, 0x42, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x35, 0xe8, 0xff, 0xff, + 0x8b, 0x5a, 0x6d, 0x73, 0x73, 0x73, 0x73, 0x38, + 0x0, 0xe9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x7b, 0x0, 0xef, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x7b, + + /* U+0033 "3" */ + 0x0, 0x0, 0x0, 0x4b, 0xb2, 0xe6, 0xf6, 0xd9, + 0xae, 0x41, 0x0, 0x0, 0x0, 0x0, 0xf, 0xa2, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x95, + 0x0, 0x0, 0x0, 0x66, 0xff, 0xfe, 0xaf, 0x76, + 0x5b, 0x8c, 0xf4, 0xff, 0xff, 0x55, 0x0, 0x0, + 0x0, 0x67, 0x38, 0x0, 0x0, 0x0, 0x0, 0x54, + 0xff, 0xff, 0x9c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1a, 0xff, 0xff, 0xb3, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x67, 0xff, 0xff, 0x79, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5, 0x19, 0x39, 0x90, 0xfa, 0xff, + 0xd3, 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaf, + 0xff, 0xff, 0xff, 0xf6, 0x91, 0xf, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xaf, 0xff, 0xff, 0xff, + 0xfa, 0xa4, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1e, 0x41, 0x5b, 0xad, 0xfc, 0xff, 0xeb, + 0x2f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x42, 0xfb, 0xff, 0xc7, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xaa, 0xff, 0xff, 0x25, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x84, 0xff, 0xff, + 0x4b, 0x0, 0xd, 0x1c, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xa7, 0xff, 0xff, 0x2b, 0x0, 0xa2, + 0xe9, 0x3f, 0x0, 0x0, 0x0, 0x0, 0x36, 0xfa, + 0xff, 0xf3, 0x2, 0x20, 0xfa, 0xff, 0xfd, 0xb4, + 0x75, 0x6d, 0x9d, 0xfa, 0xff, 0xff, 0x6e, 0x0, + 0x0, 0x46, 0xe6, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0x96, 0x0, 0x0, 0x0, 0x0, 0xf, + 0x70, 0xc3, 0xe7, 0xf7, 0xd5, 0xa9, 0x36, 0x0, + 0x0, 0x0, + + /* U+0034 "4" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xca, + 0xff, 0xff, 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6f, 0xff, 0xff, 0xff, 0xbb, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1d, + 0xf3, 0xff, 0xf5, 0xff, 0xbb, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xc2, 0xc8, + 0xff, 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x56, 0xff, 0xff, 0x3f, 0xd2, 0xff, 0xbb, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x10, 0xe7, 0xff, 0xb6, + 0x0, 0xd8, 0xff, 0xbb, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x9a, 0xff, 0xf3, 0x1f, 0x0, 0xdb, 0xff, + 0xbb, 0x0, 0x0, 0x0, 0x0, 0x3e, 0xfe, 0xff, + 0x6a, 0x0, 0x0, 0xdb, 0xff, 0xbb, 0x0, 0x0, + 0x0, 0x7, 0xd8, 0xff, 0xc1, 0x1, 0x0, 0x0, + 0xdb, 0xff, 0xbb, 0x0, 0x0, 0x0, 0x81, 0xff, + 0xf6, 0x25, 0x0, 0x0, 0x0, 0xdb, 0xff, 0xbb, + 0x0, 0x0, 0x2a, 0xf9, 0xff, 0xa4, 0x3b, 0x3b, + 0x3b, 0x3b, 0xe4, 0xff, 0xcb, 0x3b, 0x39, 0x83, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf3, 0x83, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xdb, 0xff, 0xbb, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdb, 0xff, + 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xdb, 0xff, 0xbb, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xdb, 0xff, 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xdb, 0xff, 0xbb, + 0x0, 0x0, + + /* U+0035 "5" */ + 0x0, 0x0, 0x70, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x8b, 0x0, 0x0, 0x0, 0x81, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x8b, 0x0, 0x0, 0x0, 0x92, 0xff, 0xf9, 0x6b, + 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x3b, 0x0, 0x0, + 0x0, 0xa3, 0xff, 0xdf, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb4, 0xff, + 0xc6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc4, 0xff, 0xad, 0x0, 0xb, + 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xd5, 0xff, 0xd4, 0xd7, 0xff, 0xfd, 0xdb, 0x81, + 0x8, 0x0, 0x0, 0x0, 0x0, 0xe6, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xd7, 0xc, 0x0, + 0x0, 0x0, 0x61, 0xdc, 0x6b, 0x20, 0xf, 0x46, + 0xd6, 0xff, 0xff, 0x92, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xf1, 0xff, + 0xfd, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9a, 0xff, 0xff, 0x31, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x74, 0xff, 0xff, 0x57, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x87, 0xff, 0xff, + 0x3c, 0x0, 0xa, 0xc, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xc4, 0xff, 0xfe, 0xe, 0x0, 0x9b, + 0xd6, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x67, 0xff, + 0xff, 0xc5, 0x0, 0x25, 0xfd, 0xff, 0xf8, 0xa8, + 0x72, 0x72, 0xb2, 0xff, 0xff, 0xf8, 0x2a, 0x0, + 0x0, 0x4d, 0xe8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf0, 0x58, 0x0, 0x0, 0x0, 0x0, 0xf, + 0x70, 0xc2, 0xe6, 0xf5, 0xcc, 0x96, 0x1a, 0x0, + 0x0, 0x0, + + /* U+0036 "6" */ + 0x0, 0x0, 0x0, 0x9, 0x78, 0xce, 0xf4, 0xef, + 0xc4, 0x61, 0x0, 0x0, 0x0, 0x0, 0x21, 0xdb, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaf, 0x10, + 0x0, 0xb, 0xdc, 0xff, 0xff, 0xb1, 0x70, 0x68, + 0xa1, 0xfb, 0xb9, 0xb, 0x0, 0x87, 0xff, 0xff, + 0x5f, 0x0, 0x0, 0x0, 0x0, 0x27, 0x6, 0x0, + 0x7, 0xec, 0xff, 0xb2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x45, 0xff, 0xff, 0x3b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7b, 0xff, 0xf9, 0x5, 0x47, 0xc2, 0xf4, 0xda, + 0xa8, 0x24, 0x0, 0x0, 0x9f, 0xff, 0xe4, 0x71, + 0xfd, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x47, 0x0, + 0xaf, 0xff, 0xfe, 0xff, 0xa7, 0x35, 0x25, 0x72, + 0xf9, 0xff, 0xdb, 0x5, 0xb7, 0xff, 0xff, 0x87, + 0x0, 0x0, 0x0, 0x0, 0x77, 0xff, 0xff, 0x53, + 0xaa, 0xff, 0xf4, 0x2, 0x0, 0x0, 0x0, 0x0, + 0x18, 0xff, 0xff, 0x7a, 0x94, 0xff, 0xff, 0x15, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xf6, 0xff, 0x9e, + 0x64, 0xff, 0xff, 0x41, 0x0, 0x0, 0x0, 0x0, + 0x5, 0xfc, 0xff, 0x8f, 0x22, 0xfe, 0xff, 0x9b, + 0x0, 0x0, 0x0, 0x0, 0x2d, 0xff, 0xff, 0x64, + 0x0, 0xbd, 0xff, 0xf6, 0x29, 0x0, 0x0, 0x0, + 0x9f, 0xff, 0xfc, 0x28, 0x0, 0x30, 0xfb, 0xff, + 0xe8, 0x6b, 0x45, 0x9a, 0xff, 0xff, 0x95, 0x0, + 0x0, 0x0, 0x59, 0xfa, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xc1, 0x11, 0x0, 0x0, 0x0, 0x0, 0x2d, + 0xa5, 0xe7, 0xf3, 0xc3, 0x76, 0x3, 0x0, 0x0, + + /* U+0037 "7" */ + 0xcb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x8b, 0xcb, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x86, + 0x56, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, + 0xd2, 0xff, 0xe8, 0x17, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2e, 0xfc, 0xff, 0x48, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xc3, + 0xff, 0xa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5f, 0xff, 0xf9, 0x1c, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xdd, 0xff, + 0xa7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x46, 0xff, 0xff, 0x40, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xac, 0xff, 0xd9, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x13, 0xfa, 0xff, 0x84, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x4f, 0xff, 0xff, 0x51, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x87, 0xff, 0xff, 0x21, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xbf, 0xff, 0xef, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xf3, 0xff, 0xc7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xe, 0xff, 0xff, 0xaf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26, + 0xff, 0xff, 0x9a, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3d, 0xff, 0xff, 0x8b, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x54, + 0xff, 0xff, 0x7e, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0038 "8" */ + 0x0, 0x0, 0x5, 0x6e, 0xc4, 0xed, 0xe8, 0xbd, + 0x5e, 0x0, 0x0, 0x0, 0x0, 0xa, 0xce, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0x0, 0x0, + 0x0, 0x96, 0xff, 0xff, 0x83, 0x23, 0x2f, 0xa1, + 0xff, 0xff, 0x64, 0x0, 0x0, 0xe8, 0xff, 0xab, + 0x0, 0x0, 0x0, 0x0, 0xc8, 0xff, 0xb6, 0x0, + 0x11, 0xff, 0xff, 0x6d, 0x0, 0x0, 0x0, 0x0, + 0x7c, 0xff, 0xdf, 0x0, 0x1, 0xf3, 0xff, 0x8b, + 0x0, 0x0, 0x0, 0x0, 0x84, 0xff, 0xc5, 0x0, + 0x0, 0x96, 0xff, 0xeb, 0x1f, 0x0, 0x0, 0x0, + 0xbd, 0xff, 0x69, 0x0, 0x0, 0xb, 0xc1, 0xff, + 0xe5, 0x4f, 0x0, 0x4c, 0xff, 0xb1, 0x4, 0x0, + 0x0, 0x0, 0x10, 0xea, 0xff, 0xff, 0xd1, 0xeb, + 0xce, 0x7, 0x0, 0x0, 0x0, 0x1b, 0xce, 0xff, + 0xac, 0xd3, 0xff, 0xff, 0xfc, 0x6c, 0x0, 0x0, + 0xa, 0xd9, 0xff, 0x97, 0x0, 0x0, 0x47, 0xd3, + 0xff, 0xff, 0x8b, 0x0, 0x78, 0xff, 0xe1, 0x3, + 0x0, 0x0, 0x0, 0x9, 0xc2, 0xff, 0xfd, 0x2a, + 0xc0, 0xff, 0x9d, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3e, 0xff, 0xff, 0x6f, 0xd5, 0xff, 0xa7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1c, 0xff, 0xff, 0x86, + 0xa0, 0xff, 0xee, 0x1e, 0x0, 0x0, 0x0, 0x0, + 0x66, 0xff, 0xff, 0x54, 0x3c, 0xfb, 0xff, 0xe0, + 0x5f, 0x25, 0x28, 0x74, 0xf5, 0xff, 0xdf, 0xf, + 0x0, 0x64, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xe8, 0x30, 0x0, 0x0, 0x0, 0x1f, 0x96, + 0xcc, 0xf1, 0xe7, 0xc1, 0x7b, 0xb, 0x0, 0x0, + + /* U+0039 "9" */ + 0x0, 0x0, 0x21, 0xa0, 0xd7, 0xf6, 0xd8, 0x87, + 0x10, 0x0, 0x0, 0x0, 0x0, 0x51, 0xf3, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xe4, 0x27, 0x0, 0x0, + 0x19, 0xef, 0xff, 0xeb, 0x6b, 0x47, 0x82, 0xf9, + 0xff, 0xdb, 0x8, 0x0, 0x92, 0xff, 0xfe, 0x31, + 0x0, 0x0, 0x0, 0x4a, 0xff, 0xff, 0x75, 0x0, + 0xc7, 0xff, 0xc3, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc1, 0xff, 0xd8, 0x0, 0xf0, 0xff, 0xa1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x77, 0xff, 0xff, 0x18, + 0xda, 0xff, 0xb5, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x48, 0xff, 0xff, 0x46, 0xb2, 0xff, 0xf3, 0x16, + 0x0, 0x0, 0x0, 0x4, 0xb2, 0xff, 0xff, 0x58, + 0x4e, 0xff, 0xff, 0xce, 0x43, 0x23, 0x5c, 0xc9, + 0xfe, 0xff, 0xff, 0x63, 0x0, 0xa4, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe7, 0x71, 0xff, 0xff, 0x56, + 0x0, 0x0, 0x5e, 0xc1, 0xec, 0xec, 0xa6, 0x1e, + 0x50, 0xff, 0xff, 0x46, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7a, 0xff, 0xff, 0x1c, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbb, 0xff, 0xe8, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x25, 0xfc, 0xff, 0x94, 0x0, + 0x0, 0x50, 0x35, 0x0, 0x0, 0x0, 0x15, 0xcb, + 0xff, 0xfc, 0x2d, 0x0, 0x3d, 0xf8, 0xfa, 0xa5, + 0x70, 0x8c, 0xe1, 0xff, 0xff, 0x8d, 0x0, 0x0, + 0x34, 0xdd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xa5, 0x3, 0x0, 0x0, 0x0, 0xe, 0x87, 0xd6, + 0xf6, 0xef, 0xbc, 0x54, 0x0, 0x0, 0x0, 0x0, + + /* U+003A ":" */ + 0x0, 0x8e, 0xf1, 0xaf, 0x5, 0x2a, 0xff, 0xff, + 0xff, 0x58, 0x2d, 0xff, 0xff, 0xff, 0x5c, 0x0, + 0x96, 0xf6, 0xb7, 0x8, 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, 0x2, + 0x0, 0x0, 0x0, 0x9a, 0xfb, 0xbb, 0x8, 0x2d, + 0xff, 0xff, 0xff, 0x5c, 0x29, 0xff, 0xff, 0xff, + 0x58, 0x0, 0x8d, 0xf2, 0xaf, 0x5, + + /* U+003B ";" */ + 0x0, 0x8e, 0xf1, 0xaf, 0x5, 0x2a, 0xff, 0xff, + 0xff, 0x58, 0x2d, 0xff, 0xff, 0xff, 0x5c, 0x0, + 0x96, 0xf6, 0xb7, 0x8, 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, 0x33, 0x91, 0x6d, 0x1, 0x14, + 0xf7, 0xff, 0xff, 0x69, 0x26, 0xff, 0xff, 0xff, + 0xb9, 0x0, 0x87, 0xec, 0xff, 0xcb, 0x0, 0x0, + 0x33, 0xff, 0xab, 0x0, 0x0, 0x8c, 0xff, 0x6d, + 0x0, 0x62, 0xfc, 0xe7, 0xa, 0x87, 0xff, 0xef, + 0x32, 0x0, 0x4d, 0xaa, 0x23, 0x0, 0x0, + + /* U+003C "<" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36, + 0x9d, 0xb9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xd, 0x68, 0xcf, 0xff, 0xff, 0xc3, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x34, 0x9a, 0xf4, 0xff, + 0xff, 0xfe, 0xc1, 0x53, 0x0, 0x0, 0xc, 0x66, + 0xcd, 0xff, 0xff, 0xff, 0xdf, 0x80, 0x22, 0x0, + 0x0, 0x9, 0x98, 0xf2, 0xff, 0xff, 0xf5, 0xa2, + 0x40, 0x1, 0x0, 0x0, 0x0, 0x0, 0x17, 0xff, + 0xff, 0xf3, 0x6c, 0xe, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x13, 0xf8, 0xff, 0xff, 0xe9, + 0x8d, 0x2c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x14, 0x74, 0xda, 0xff, 0xff, 0xff, 0xcb, + 0x6b, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x43, 0xaa, 0xfa, 0xff, 0xff, 0xf9, 0xac, + 0x4c, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x17, 0x78, 0xde, 0xff, 0xff, 0xff, 0xb9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x47, 0xae, 0xfb, 0xc3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x19, + 0x56, + + /* U+003D "=" */ + 0x17, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xc3, 0x17, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xc3, 0x1, 0xf, 0xf, 0xf, 0xf, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xc, 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, 0x17, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xc3, 0x17, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, + 0x2, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x12, + + /* U+003E ">" */ + 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xe0, 0x7b, + 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x17, 0xff, 0xff, 0xfb, 0xad, 0x46, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, + 0x80, 0xdf, 0xff, 0xff, 0xff, 0xde, 0x79, 0x17, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3f, + 0x9f, 0xf3, 0xff, 0xff, 0xfa, 0xab, 0x44, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x5e, + 0xc0, 0xfe, 0xff, 0xff, 0xdc, 0x64, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x23, 0x97, + 0xff, 0xff, 0xc3, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x3, 0x4a, 0xaa, 0xf9, 0xff, 0xff, 0xb8, + 0x0, 0x0, 0x0, 0x0, 0x2a, 0x89, 0xe6, 0xff, + 0xff, 0xfe, 0xba, 0x53, 0x4, 0x0, 0x12, 0x6b, + 0xca, 0xff, 0xff, 0xff, 0xe9, 0x88, 0x22, 0x0, + 0x0, 0x0, 0x14, 0xf9, 0xff, 0xff, 0xfe, 0xbe, + 0x57, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, + 0xff, 0xec, 0x8c, 0x26, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xd, 0x5b, 0x7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, + + /* U+003F "?" */ + 0x0, 0x7, 0x76, 0xcf, 0xf4, 0xe6, 0xbe, 0x51, + 0x0, 0x0, 0x18, 0xc6, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x8d, 0x0, 0x9f, 0xff, 0xec, 0x92, + 0x61, 0x8b, 0xf7, 0xff, 0xff, 0x32, 0x4, 0x79, + 0x1b, 0x0, 0x0, 0x0, 0x68, 0xff, 0xff, 0x6e, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2c, 0xff, + 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x68, 0xff, 0xff, 0x43, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x21, 0xeb, 0xff, 0xb6, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xe, 0xd6, 0xff, 0xdb, 0x10, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xa1, 0xff, 0xf6, 0x2d, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x49, 0xff, 0xff, + 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa7, + 0xff, 0xe1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xbd, 0xff, 0xb8, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb, 0xf, 0xb, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xa9, + 0xfb, 0xb2, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3d, 0xff, 0xff, 0xff, 0x4d, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3a, 0xff, 0xff, 0xff, 0x48, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x9c, 0xf2, 0xa5, + 0x2, 0x0, 0x0, 0x0, + + /* U+0040 "@" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x19, + 0x69, 0xb6, 0xda, 0xf5, 0xf1, 0xdb, 0xa5, 0x5a, + 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x12, 0x8d, 0xf9, 0xff, 0xff, 0xf8, + 0xe0, 0xeb, 0xfe, 0xff, 0xff, 0xdc, 0x45, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x31, 0xe4, + 0xff, 0xef, 0x83, 0x2d, 0x1, 0x0, 0x0, 0xb, + 0x4e, 0xcb, 0xff, 0xfc, 0x66, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3e, 0xf4, 0xff, 0xa5, 0x14, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x81, + 0xfc, 0xfc, 0x3f, 0x0, 0x0, 0x0, 0x1c, 0xe9, + 0xff, 0x81, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x74, 0xff, 0xd6, + 0x2, 0x0, 0x0, 0xbc, 0xff, 0xa0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x6, 0xdc, 0xff, 0x4e, 0x0, 0x40, + 0xff, 0xe1, 0x9, 0x0, 0x0, 0x0, 0x6, 0x62, + 0xa3, 0x9d, 0x3b, 0x63, 0xb7, 0x31, 0x0, 0x0, + 0x5e, 0xff, 0x99, 0x0, 0xb8, 0xff, 0x65, 0x0, + 0x0, 0x0, 0xf, 0xbf, 0xff, 0xff, 0xff, 0xf7, + 0xe4, 0xff, 0x19, 0x0, 0x0, 0x2b, 0xff, 0xd3, + 0xc, 0xf9, 0xf3, 0x6, 0x0, 0x0, 0x2, 0xca, + 0xff, 0xb8, 0x33, 0x1c, 0xc2, 0xff, 0xe8, 0x0, + 0x0, 0x0, 0x7, 0xfc, 0xea, 0x4e, 0xff, 0xb0, + 0x0, 0x0, 0x0, 0x5c, 0xff, 0xd0, 0x8, 0x0, + 0x0, 0x61, 0xff, 0xb5, 0x0, 0x0, 0x0, 0x0, + 0xe5, 0xf5, 0x78, 0xff, 0x77, 0x0, 0x0, 0x0, + 0xd4, 0xff, 0x57, 0x0, 0x0, 0x0, 0x8e, 0xff, + 0x83, 0x0, 0x0, 0x0, 0x4, 0xfb, 0xe2, 0x8f, + 0xff, 0x5f, 0x0, 0x0, 0xa, 0xfd, 0xff, 0xf, + 0x0, 0x0, 0x0, 0xbc, 0xff, 0x51, 0x0, 0x0, + 0x0, 0x24, 0xff, 0xb8, 0xa2, 0xff, 0x4d, 0x0, + 0x0, 0x2f, 0xff, 0xf4, 0x0, 0x0, 0x0, 0x0, + 0xea, 0xff, 0x26, 0x0, 0x0, 0x0, 0x81, 0xff, + 0x6d, 0x95, 0xff, 0x5b, 0x0, 0x0, 0x1d, 0xff, + 0xff, 0x1a, 0x0, 0x0, 0x58, 0xff, 0xff, 0x15, + 0x0, 0x0, 0x1b, 0xee, 0xdc, 0x7, 0x80, 0xff, + 0x77, 0x0, 0x0, 0x1, 0xe0, 0xff, 0xbf, 0x4e, + 0x88, 0xfc, 0xf0, 0xff, 0x7b, 0xf, 0x45, 0xdf, + 0xfe, 0x4d, 0x0, 0x52, 0xff, 0xb6, 0x0, 0x0, + 0x0, 0x4f, 0xfa, 0xff, 0xff, 0xff, 0x93, 0x37, + 0xfa, 0xff, 0xff, 0xff, 0xee, 0x4d, 0x0, 0x0, + 0xf, 0xfb, 0xf7, 0x18, 0x0, 0x0, 0x0, 0x33, + 0x8b, 0x9b, 0x4c, 0x0, 0x0, 0x2d, 0x77, 0x9b, + 0x66, 0x1a, 0x0, 0x0, 0x0, 0x0, 0xaa, 0xff, + 0x95, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2a, 0xfc, 0xff, 0x58, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x6e, 0xff, 0xfc, 0x76, 0x3, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7d, + 0xfe, 0xff, 0xdb, 0x74, 0x30, 0x10, 0xa, 0x2e, + 0x5b, 0xc1, 0xde, 0x2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x46, 0xd1, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, + 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x42, 0x8b, 0xbe, 0xd4, + 0xd2, 0xb1, 0x8e, 0x38, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, + + /* U+0041 "A" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x4e, 0xff, 0xff, + 0xff, 0x3c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xa1, 0xff, 0xff, 0xff, + 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5, 0xef, 0xff, 0xc8, 0xff, 0xe2, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x48, 0xff, 0xfa, 0x3f, 0xff, 0xff, 0x36, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9b, 0xff, 0xc1, 0x1, 0xe9, 0xff, 0x89, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xea, + 0xff, 0x7b, 0x0, 0xa4, 0xff, 0xdc, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x42, 0xff, 0xff, + 0x34, 0x0, 0x5d, 0xff, 0xff, 0x2f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x95, 0xff, 0xec, 0x2, + 0x0, 0x18, 0xfe, 0xff, 0x82, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2, 0xe6, 0xff, 0xa7, 0x0, 0x0, + 0x0, 0xd0, 0xff, 0xd5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x3c, 0xff, 0xff, 0x5d, 0x0, 0x0, 0x0, + 0x88, 0xff, 0xff, 0x29, 0x0, 0x0, 0x0, 0x0, + 0x8f, 0xff, 0xff, 0x5d, 0x47, 0x47, 0x47, 0x7d, + 0xff, 0xff, 0x7c, 0x0, 0x0, 0x0, 0x0, 0xe1, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xcf, 0x0, 0x0, 0x0, 0x36, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x22, 0x0, 0x0, 0x89, 0xff, 0xff, 0x30, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x5e, 0xff, 0xff, + 0x75, 0x0, 0x0, 0xdc, 0xff, 0xe4, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x15, 0xfd, 0xff, 0xc8, + 0x0, 0x30, 0xff, 0xff, 0x98, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xc6, 0xff, 0xfe, 0x1c, + 0x83, 0xff, 0xff, 0x4c, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7a, 0xff, 0xff, 0x6e, 0xd6, + 0xff, 0xf7, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2e, 0xff, 0xff, 0xc1, + + /* U+0042 "B" */ + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xd8, + 0xb1, 0x57, 0x8, 0x0, 0x0, 0xab, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, + 0xf, 0x0, 0xab, 0xff, 0xff, 0x54, 0x3f, 0x41, + 0x52, 0x7d, 0xe0, 0xff, 0xff, 0xa0, 0x0, 0xab, + 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x18, + 0xf1, 0xff, 0xe3, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xc2, 0xff, 0xfb, + 0x3, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, + 0x0, 0xb, 0xe9, 0xff, 0xcb, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x3, 0x17, 0x49, 0xc5, 0xff, + 0xfd, 0x4b, 0x0, 0xab, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xdc, 0x4a, 0x0, 0x0, + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfb, 0xb5, 0x28, 0x0, 0xab, 0xff, 0xff, + 0x46, 0x2f, 0x30, 0x3f, 0x5a, 0xac, 0xfc, 0xff, + 0xf1, 0x26, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x60, 0xff, 0xff, 0xa7, 0xab, + 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xe6, 0xff, 0xe0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xca, 0xff, + 0xf7, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x4, 0xf4, 0xff, 0xde, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, + 0xff, 0xff, 0x97, 0xab, 0xff, 0xff, 0x58, 0x43, + 0x44, 0x53, 0x72, 0xce, 0xff, 0xff, 0xe2, 0x15, + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xe0, 0x34, 0x0, 0xab, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf6, 0xd7, 0xb6, 0x63, 0x5, + 0x0, 0x0, + + /* U+0043 "C" */ + 0x0, 0x0, 0x0, 0x0, 0xf, 0x75, 0xc3, 0xe9, + 0xf7, 0xdd, 0x99, 0x2a, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x53, 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf5, 0x5b, 0x0, 0x0, 0x0, 0x61, 0xfe, + 0xff, 0xff, 0xed, 0xb6, 0x91, 0xbc, 0xf3, 0xff, + 0xf5, 0x1e, 0x0, 0x2d, 0xf7, 0xff, 0xff, 0x9a, + 0x7, 0x0, 0x0, 0x0, 0x19, 0xb1, 0x4a, 0x0, + 0x0, 0xaf, 0xff, 0xff, 0x8f, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xfe, + 0xff, 0xf0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x5d, 0xff, 0xff, 0x94, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x89, 0xff, 0xff, 0x6b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9d, 0xff, 0xff, 0x48, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9e, 0xff, + 0xff, 0x4a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x8b, 0xff, 0xff, 0x6c, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x62, 0xff, 0xff, 0x94, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x20, 0xff, 0xff, 0xf0, 0x11, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbb, + 0xff, 0xff, 0x8d, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xd, 0x0, 0x0, 0x38, 0xfc, 0xff, + 0xff, 0x99, 0x6, 0x0, 0x0, 0x0, 0xf, 0xab, + 0xd5, 0x13, 0x0, 0x0, 0x73, 0xff, 0xff, 0xff, + 0xee, 0xba, 0x95, 0xbf, 0xf3, 0xff, 0xff, 0x73, + 0x0, 0x0, 0x0, 0x64, 0xf3, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfb, 0x81, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x16, 0x7f, 0xca, 0xed, 0xf8, 0xde, + 0x9a, 0x2f, 0x0, 0x0, + + /* U+0044 "D" */ + 0xab, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xe0, 0xaf, + 0x69, 0xa, 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xeb, + 0x51, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x6d, + 0x5d, 0x7c, 0xa5, 0xe6, 0xff, 0xff, 0xfe, 0x60, + 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0xe, 0x9a, 0xff, 0xff, 0xf6, 0x26, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa9, 0xff, 0xff, 0x9c, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x23, + 0xfa, 0xff, 0xf4, 0x4, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc5, 0xff, + 0xff, 0x34, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xa4, 0xff, 0xff, 0x55, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x83, 0xff, 0xff, 0x67, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x87, 0xff, 0xff, 0x65, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa8, 0xff, + 0xff, 0x53, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xca, 0xff, 0xff, 0x2d, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2e, 0xfd, 0xff, 0xef, 0x2, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb4, + 0xff, 0xff, 0x91, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x12, 0xa9, 0xff, 0xff, 0xf2, + 0x1e, 0x0, 0xab, 0xff, 0xff, 0x6d, 0x5d, 0x7d, + 0xa8, 0xe9, 0xff, 0xff, 0xfd, 0x56, 0x0, 0x0, + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xe8, 0x4b, 0x0, 0x0, 0x0, 0xab, 0xff, + 0xff, 0xff, 0xff, 0xf7, 0xe3, 0xb3, 0x6a, 0xa, + 0x0, 0x0, 0x0, 0x0, + + /* U+0045 "E" */ + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xc3, 0x0, 0xab, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x0, + 0xab, 0xff, 0xff, 0x7c, 0x6b, 0x6b, 0x6b, 0x6b, + 0x6b, 0x6b, 0x52, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x97, 0x0, 0x0, + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x97, 0x0, 0x0, 0xab, 0xff, 0xff, 0x7f, + 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x42, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x83, + 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x3, + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x7, 0xab, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7, + + /* U+0046 "F" */ + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xcb, 0xab, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xcb, 0xab, 0xff, + 0xff, 0x7c, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, + 0x56, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, + 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, + 0x78, 0x67, 0x67, 0x67, 0x67, 0x67, 0x44, 0x0, + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xa7, 0x0, 0xab, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xa7, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, + 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, + 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0047 "G" */ + 0x0, 0x0, 0x0, 0x0, 0x8, 0x68, 0xb7, 0xe3, + 0xf8, 0xe6, 0xbb, 0x61, 0x4, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x46, 0xe4, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xcf, 0x20, 0x0, 0x0, 0x0, + 0x56, 0xfb, 0xff, 0xff, 0xf2, 0xc1, 0x92, 0xa7, + 0xd4, 0xff, 0xff, 0x94, 0x0, 0x0, 0x28, 0xf4, + 0xff, 0xff, 0xa9, 0xf, 0x0, 0x0, 0x0, 0x0, + 0x4d, 0x9c, 0x5, 0x0, 0x0, 0xab, 0xff, 0xff, + 0x99, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x18, 0xfd, 0xff, 0xf1, 0x15, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5b, 0xff, 0xff, 0x96, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x88, 0xff, 0xff, 0x6d, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9d, 0xff, 0xff, 0x48, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x9e, + 0xff, 0xff, 0x49, 0x0, 0x0, 0x0, 0x0, 0xe7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x8b, 0xff, + 0xff, 0x6b, 0x0, 0x0, 0x0, 0x0, 0x5a, 0x63, + 0x63, 0xa5, 0xff, 0xff, 0x1f, 0x62, 0xff, 0xff, + 0x93, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6b, 0xff, 0xff, 0x1f, 0x20, 0xff, 0xff, 0xf0, + 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6b, + 0xff, 0xff, 0x1f, 0x0, 0xb9, 0xff, 0xff, 0x91, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6b, 0xff, + 0xff, 0x1f, 0x0, 0x35, 0xfa, 0xff, 0xff, 0xa7, + 0xe, 0x0, 0x0, 0x0, 0x2, 0x90, 0xff, 0xff, + 0x1f, 0x0, 0x0, 0x6a, 0xff, 0xff, 0xff, 0xf4, + 0xc6, 0x98, 0xa1, 0xd9, 0xff, 0xff, 0xff, 0x1c, + 0x0, 0x0, 0x0, 0x59, 0xee, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x5b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x10, 0x74, 0xc0, 0xe8, 0xfa, + 0xe8, 0xc2, 0x76, 0x11, 0x0, 0x0, + + /* U+0048 "H" */ + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x53, 0xff, 0xff, 0x73, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x53, 0xff, 0xff, 0x73, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x53, 0xff, + 0xff, 0x73, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x53, 0xff, 0xff, 0x73, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x53, 0xff, 0xff, 0x73, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x53, 0xff, 0xff, 0x73, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x53, 0xff, + 0xff, 0x73, 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x73, + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x73, 0xab, 0xff, + 0xff, 0x91, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0xac, 0xff, 0xff, 0x73, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x53, 0xff, + 0xff, 0x73, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x53, 0xff, 0xff, 0x73, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x53, 0xff, 0xff, 0x73, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x53, 0xff, 0xff, 0x73, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x53, 0xff, + 0xff, 0x73, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x53, 0xff, 0xff, 0x73, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x53, 0xff, 0xff, 0x73, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x53, 0xff, 0xff, 0x73, + + /* U+0049 "I" */ + 0xab, 0xff, 0xff, 0x1b, 0xab, 0xff, 0xff, 0x1b, + 0xab, 0xff, 0xff, 0x1b, 0xab, 0xff, 0xff, 0x1b, + 0xab, 0xff, 0xff, 0x1b, 0xab, 0xff, 0xff, 0x1b, + 0xab, 0xff, 0xff, 0x1b, 0xab, 0xff, 0xff, 0x1b, + 0xab, 0xff, 0xff, 0x1b, 0xab, 0xff, 0xff, 0x1b, + 0xab, 0xff, 0xff, 0x1b, 0xab, 0xff, 0xff, 0x1b, + 0xab, 0xff, 0xff, 0x1b, 0xab, 0xff, 0xff, 0x1b, + 0xab, 0xff, 0xff, 0x1b, 0xab, 0xff, 0xff, 0x1b, + 0xab, 0xff, 0xff, 0x1b, 0xab, 0xff, 0xff, 0x1b, + + /* U+004A "J" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xd7, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xd7, 0xff, 0xf7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd7, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xd7, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xd7, 0xff, 0xf7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd7, + 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xd7, 0xff, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xd7, 0xff, 0xf7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xd7, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xd7, 0xff, 0xf7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd7, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xd8, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe8, 0xff, 0xeb, 0x0, + 0x0, 0x1b, 0x0, 0x0, 0x0, 0x0, 0xd, 0xfd, + 0xff, 0xc9, 0x8, 0x90, 0xfb, 0x57, 0x0, 0x0, + 0x0, 0x7f, 0xff, 0xff, 0x98, 0x6, 0xd3, 0xff, + 0xfd, 0xc9, 0x95, 0xbc, 0xff, 0xff, 0xfb, 0x23, + 0x0, 0x2a, 0xe4, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfb, 0x6d, 0x0, 0x0, 0x0, 0x18, 0x8c, 0xdb, + 0xf6, 0xd7, 0xa6, 0x3b, 0x0, 0x0, + + /* U+004B "K" */ + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x84, 0xff, 0xff, 0xa2, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x4e, 0xfe, + 0xff, 0xce, 0x9, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x25, 0xf0, 0xff, 0xed, 0x20, + 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0xb, 0xd3, 0xff, 0xfd, 0x46, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0xa6, 0xff, + 0xff, 0x78, 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x70, 0xff, 0xff, 0xad, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x3d, 0xfb, 0xff, 0xd7, 0xd, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xab, 0xff, 0xff, 0x35, 0xe7, 0xff, + 0xff, 0xd4, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xff, 0xd9, 0xff, 0xff, 0xff, 0xff, + 0x6b, 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, + 0xff, 0xff, 0xff, 0xa3, 0xec, 0xff, 0xed, 0x12, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0xff, + 0xc4, 0x6, 0x6c, 0xff, 0xff, 0x93, 0x0, 0x0, + 0x0, 0x0, 0xab, 0xff, 0xff, 0xe3, 0x17, 0x0, + 0x4, 0xd7, 0xff, 0xfb, 0x2b, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x3e, 0x0, 0x0, 0x0, 0x4b, + 0xff, 0xff, 0xbb, 0x0, 0x0, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0xbb, 0xff, + 0xff, 0x4e, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x2d, 0xfc, 0xff, 0xdb, + 0x6, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x99, 0xff, 0xff, 0x75, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x17, 0xf2, 0xff, 0xf2, 0x17, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x78, 0xff, 0xff, 0x9d, + + /* U+004C "L" */ + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, + 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, + 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, + 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, + 0x83, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x44, + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x97, 0xab, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x97, + + /* U+004D "M" */ + 0xab, 0xff, 0xff, 0xb8, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xcd, 0xff, 0xff, 0x9b, + 0xab, 0xff, 0xff, 0xfc, 0x16, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x26, 0xff, 0xff, 0xff, 0x9b, + 0xab, 0xff, 0xff, 0xff, 0x6d, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7f, 0xff, 0xff, 0xff, 0x9b, + 0xab, 0xff, 0xde, 0xff, 0xc7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xd8, 0xff, 0xd8, 0xff, 0x9b, + 0xab, 0xff, 0xa3, 0xfa, 0xff, 0x23, 0x0, 0x0, + 0x0, 0x0, 0x31, 0xff, 0xe2, 0xb4, 0xff, 0x9b, + 0xab, 0xff, 0xa7, 0xba, 0xff, 0x7c, 0x0, 0x0, + 0x0, 0x0, 0x8a, 0xff, 0x91, 0xc9, 0xff, 0x9b, + 0xab, 0xff, 0xba, 0x68, 0xff, 0xd7, 0x0, 0x0, + 0x0, 0x1, 0xe1, 0xff, 0x3f, 0xdc, 0xff, 0x9b, + 0xab, 0xff, 0xc5, 0x19, 0xfd, 0xff, 0x31, 0x0, + 0x0, 0x3c, 0xff, 0xeb, 0x3, 0xe7, 0xff, 0x9b, + 0xab, 0xff, 0xd0, 0x0, 0xbd, 0xff, 0x8c, 0x0, + 0x0, 0x96, 0xff, 0x94, 0x0, 0xf1, 0xff, 0x9b, + 0xab, 0xff, 0xd7, 0x0, 0x60, 0xff, 0xe3, 0x1, + 0x2, 0xe9, 0xff, 0x38, 0x0, 0xf7, 0xff, 0x9b, + 0xab, 0xff, 0xd7, 0x0, 0xd, 0xf6, 0xff, 0x37, + 0x3c, 0xff, 0xdc, 0x0, 0x0, 0xf7, 0xff, 0x9b, + 0xab, 0xff, 0xd7, 0x0, 0x0, 0xa6, 0xff, 0x8a, + 0x8d, 0xff, 0x80, 0x0, 0x0, 0xf7, 0xff, 0x9b, + 0xab, 0xff, 0xd7, 0x0, 0x0, 0x49, 0xff, 0xde, + 0xdf, 0xff, 0x25, 0x0, 0x0, 0xf7, 0xff, 0x9b, + 0xab, 0xff, 0xd7, 0x0, 0x0, 0x4, 0xe9, 0xff, + 0xff, 0xc9, 0x0, 0x0, 0x0, 0xf7, 0xff, 0x9b, + 0xab, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x90, 0xff, + 0xff, 0x6d, 0x0, 0x0, 0x0, 0xf7, 0xff, 0x9b, + 0xab, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x33, 0xff, + 0xfb, 0x16, 0x0, 0x0, 0x0, 0xf7, 0xff, 0x9b, + 0xab, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x41, + 0x39, 0x0, 0x0, 0x0, 0x0, 0xf7, 0xff, 0x9b, + 0xab, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xf7, 0xff, 0x9b, + + /* U+004E "N" */ + 0xab, 0xff, 0xff, 0x75, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5f, 0xff, 0xff, 0x47, 0xab, 0xff, + 0xff, 0xf0, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x5f, 0xff, 0xff, 0x47, 0xab, 0xff, 0xff, 0xff, + 0x91, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f, 0xff, + 0xff, 0x47, 0xab, 0xff, 0xee, 0xff, 0xfa, 0x25, + 0x0, 0x0, 0x0, 0x0, 0x5f, 0xff, 0xff, 0x47, + 0xab, 0xff, 0xae, 0xd6, 0xff, 0xad, 0x0, 0x0, + 0x0, 0x0, 0x5f, 0xff, 0xff, 0x47, 0xab, 0xff, + 0xbb, 0x5c, 0xff, 0xff, 0x3b, 0x0, 0x0, 0x0, + 0x5f, 0xff, 0xff, 0x47, 0xab, 0xff, 0xca, 0x3, + 0xdb, 0xff, 0xc8, 0x0, 0x0, 0x0, 0x5f, 0xff, + 0xff, 0x47, 0xab, 0xff, 0xd8, 0x0, 0x5f, 0xff, + 0xff, 0x56, 0x0, 0x0, 0x5f, 0xff, 0xff, 0x47, + 0xab, 0xff, 0xe7, 0x0, 0x2, 0xd1, 0xff, 0xde, + 0x6, 0x0, 0x5c, 0xff, 0xff, 0x47, 0xab, 0xff, + 0xf5, 0x0, 0x0, 0x45, 0xff, 0xff, 0x72, 0x0, + 0x4d, 0xff, 0xff, 0x47, 0xab, 0xff, 0xf7, 0x0, + 0x0, 0x0, 0xb7, 0xff, 0xee, 0x10, 0x3d, 0xff, + 0xff, 0x47, 0xab, 0xff, 0xf7, 0x0, 0x0, 0x0, + 0x2d, 0xfd, 0xff, 0x7e, 0x2c, 0xff, 0xff, 0x47, + 0xab, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x9c, + 0xff, 0xee, 0x28, 0xff, 0xff, 0x47, 0xab, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0x19, 0xf4, 0xff, + 0x83, 0xff, 0xff, 0x47, 0xab, 0xff, 0xf7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x80, 0xff, 0xee, 0xff, + 0xff, 0x47, 0xab, 0xff, 0xf7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb, 0xe7, 0xff, 0xff, 0xff, 0x47, + 0xab, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x64, 0xff, 0xff, 0xff, 0x47, 0xab, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, + 0xd3, 0xff, 0xff, 0x47, + + /* U+004F "O" */ + 0x0, 0x0, 0x0, 0x0, 0x29, 0x8f, 0xd5, 0xf1, + 0xf4, 0xd9, 0x97, 0x33, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x84, 0xfb, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x97, 0x4, 0x0, 0x0, + 0x0, 0x0, 0x93, 0xff, 0xff, 0xff, 0xdd, 0xa3, + 0x9e, 0xd7, 0xff, 0xff, 0xff, 0xab, 0x0, 0x0, + 0x0, 0x4b, 0xff, 0xff, 0xfc, 0x6d, 0x0, 0x0, + 0x0, 0x0, 0x59, 0xf7, 0xff, 0xff, 0x63, 0x0, + 0x0, 0xcb, 0xff, 0xff, 0x6c, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x53, 0xff, 0xff, 0xdf, 0x3, + 0x2b, 0xff, 0xff, 0xe4, 0x6, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0xd1, 0xff, 0xff, 0x42, + 0x69, 0xff, 0xff, 0x89, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, 0xff, 0x7f, + 0x8e, 0xff, 0xff, 0x65, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x4b, 0xff, 0xff, 0xa4, + 0xa0, 0xff, 0xff, 0x45, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2a, 0xff, 0xff, 0xb7, + 0x9d, 0xff, 0xff, 0x49, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x2f, 0xff, 0xff, 0xb4, + 0x8b, 0xff, 0xff, 0x69, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x50, 0xff, 0xff, 0xa1, + 0x61, 0xff, 0xff, 0x90, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x77, 0xff, 0xff, 0x76, + 0x21, 0xfe, 0xff, 0xec, 0xb, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3, 0xdc, 0xff, 0xff, 0x38, + 0x0, 0xa5, 0xff, 0xff, 0x7a, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x61, 0xff, 0xff, 0xbe, 0x0, + 0x0, 0x1f, 0xf8, 0xff, 0xfe, 0x78, 0x0, 0x0, + 0x0, 0x0, 0x64, 0xfb, 0xff, 0xfe, 0x33, 0x0, + 0x0, 0x0, 0x7c, 0xff, 0xff, 0xff, 0xe3, 0xa7, + 0xa2, 0xdd, 0xff, 0xff, 0xff, 0x97, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x78, 0xf9, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfd, 0x8d, 0x2, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x25, 0x8d, 0xd5, 0xf2, + 0xf5, 0xd9, 0x95, 0x2f, 0x0, 0x0, 0x0, 0x0, + + /* U+0050 "P" */ + 0xab, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xe9, 0xcf, + 0xa7, 0x40, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, + 0x7, 0x0, 0xab, 0xff, 0xff, 0x66, 0x53, 0x57, + 0x6b, 0x95, 0xed, 0xff, 0xff, 0xa5, 0x0, 0xab, + 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x17, + 0xdf, 0xff, 0xff, 0x28, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7a, 0xff, 0xff, + 0x5d, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5c, 0xff, 0xff, 0x6d, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x85, + 0xff, 0xff, 0x55, 0xab, 0xff, 0xff, 0x1b, 0x0, + 0x0, 0x0, 0x0, 0x26, 0xf0, 0xff, 0xfb, 0x14, + 0xab, 0xff, 0xff, 0x6a, 0x57, 0x5a, 0x71, 0xa1, + 0xf9, 0xff, 0xff, 0x72, 0x0, 0xab, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x93, + 0x0, 0x0, 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe8, 0xc8, 0x99, 0x27, 0x0, 0x0, 0x0, 0xab, + 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, + 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, + + /* U+0051 "Q" */ + 0x0, 0x0, 0x0, 0x0, 0x22, 0x8a, 0xd3, 0xf1, + 0xf3, 0xd7, 0x91, 0x2b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x70, 0xf7, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfb, 0x83, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x77, 0xff, 0xff, 0xff, + 0xe3, 0xa5, 0xa0, 0xdd, 0xff, 0xff, 0xff, 0x8f, + 0x0, 0x0, 0x0, 0x0, 0x32, 0xfb, 0xff, 0xff, + 0x7f, 0x0, 0x0, 0x0, 0x0, 0x6a, 0xfd, 0xff, + 0xff, 0x47, 0x0, 0x0, 0x0, 0xb1, 0xff, 0xff, + 0x84, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6b, + 0xff, 0xff, 0xc8, 0x0, 0x0, 0x18, 0xfd, 0xff, + 0xf4, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6, 0xe6, 0xff, 0xff, 0x2d, 0x0, 0x54, 0xff, + 0xff, 0x9b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x82, 0xff, 0xff, 0x6b, 0x0, 0x85, + 0xff, 0xff, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x57, 0xff, 0xff, 0x9b, 0x0, + 0x98, 0xff, 0xff, 0x52, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x38, 0xff, 0xff, 0xae, + 0x0, 0xa1, 0xff, 0xff, 0x3f, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x23, 0xff, 0xff, + 0xb8, 0x0, 0x84, 0xff, 0xff, 0x57, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0xff, + 0xff, 0x9c, 0x0, 0x64, 0xff, 0xff, 0x74, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5a, + 0xff, 0xff, 0x7c, 0x0, 0x44, 0xff, 0xff, 0xa1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x88, 0xff, 0xff, 0x5d, 0x0, 0xc, 0xef, 0xff, + 0xf7, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7, 0xe9, 0xff, 0xf8, 0x17, 0x0, 0x0, 0x86, + 0xff, 0xff, 0x85, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x6d, 0xff, 0xff, 0xa0, 0x0, 0x0, 0x0, + 0x19, 0xf7, 0xff, 0xff, 0x77, 0x0, 0x0, 0x0, + 0x0, 0x62, 0xfc, 0xff, 0xfc, 0x30, 0x0, 0x0, + 0x0, 0x0, 0x4b, 0xf8, 0xff, 0xff, 0xd6, 0x95, + 0x90, 0xcf, 0xff, 0xff, 0xfd, 0x5c, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x47, 0xf4, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf3, 0x63, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0x83, + 0xd1, 0xff, 0xff, 0xf1, 0x72, 0xf, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x7, 0xdd, 0xff, 0xfe, 0x4a, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4a, 0xfd, 0xff, 0xfb, 0x8d, + 0x30, 0xa, 0x13, 0x2a, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x58, 0xfb, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf5, 0x5, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f, + 0xad, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x34, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x12, 0x38, 0x57, 0x4b, 0x20, 0x0, + + /* U+0052 "R" */ + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0xda, + 0xbc, 0x62, 0x9, 0x0, 0x0, 0x0, 0xab, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe4, 0x1e, 0x0, 0x0, 0xab, 0xff, 0xff, 0x66, + 0x53, 0x54, 0x63, 0x83, 0xd9, 0xff, 0xff, 0xd3, + 0x4, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x6, 0xc1, 0xff, 0xff, 0x30, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x5f, 0xff, 0xff, 0x65, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5d, + 0xff, 0xff, 0x6c, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x0, 0x1, 0xb2, 0xff, 0xff, + 0x45, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x13, 0x3c, 0xad, 0xff, 0xff, 0xd9, 0x4, 0x0, + 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xee, 0x39, 0x0, 0x0, 0xab, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xa0, + 0x24, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x63, + 0x4f, 0x50, 0xe3, 0xff, 0xf9, 0x20, 0x0, 0x0, + 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x66, 0xff, 0xff, 0xa2, 0x0, 0x0, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x5, 0xdd, + 0xff, 0xfd, 0x2d, 0x0, 0x0, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x5d, 0xff, 0xff, + 0xb4, 0x0, 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, + 0x0, 0x0, 0x0, 0x2, 0xd6, 0xff, 0xff, 0x3e, + 0x0, 0x0, 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x54, 0xff, 0xff, 0xc7, 0x0, 0x0, + 0xab, 0xff, 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, + 0x1, 0xce, 0xff, 0xff, 0x51, 0x0, 0xab, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4a, + 0xff, 0xff, 0xd7, 0x3, + + /* U+0053 "S" */ + 0x0, 0x0, 0x0, 0x30, 0xa2, 0xcf, 0xf5, 0xe9, + 0xbf, 0x69, 0x4, 0x0, 0x0, 0x0, 0x0, 0x8c, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xca, + 0x27, 0x0, 0x0, 0x66, 0xff, 0xff, 0xfc, 0xb4, + 0x8e, 0xad, 0xe2, 0xff, 0xff, 0xba, 0x0, 0x0, + 0xd7, 0xff, 0xfb, 0x3d, 0x0, 0x0, 0x0, 0x1, + 0x61, 0xc9, 0x15, 0x0, 0xf, 0xfe, 0xff, 0xcc, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x4, 0xf7, 0xff, 0xec, 0xa, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb5, + 0xff, 0xff, 0xc4, 0x33, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x21, 0xec, 0xff, 0xff, + 0xff, 0xb5, 0x48, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x21, 0xc1, 0xff, 0xff, 0xff, 0xff, + 0xdd, 0x68, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x41, 0xaf, 0xfc, 0xff, 0xff, 0xff, 0xd4, + 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1e, 0x8b, 0xf4, 0xff, 0xff, 0xd5, 0x4, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1d, + 0xdb, 0xff, 0xff, 0x53, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x62, 0xff, 0xff, + 0x80, 0x0, 0x38, 0x7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5e, 0xff, 0xff, 0x76, 0x3f, 0xf9, + 0xd8, 0x3e, 0x0, 0x0, 0x0, 0x0, 0x13, 0xd2, + 0xff, 0xff, 0x3e, 0x9b, 0xff, 0xff, 0xff, 0xd5, + 0x9f, 0x8f, 0xab, 0xf3, 0xff, 0xff, 0xb5, 0x1, + 0x1, 0x7e, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xc1, 0x12, 0x0, 0x0, 0x0, 0x1c, + 0x7c, 0xc7, 0xea, 0xf6, 0xd4, 0xad, 0x50, 0x0, + 0x0, 0x0, + + /* U+0054 "T" */ + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xeb, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xeb, 0x1a, 0x6b, 0x6b, 0x6b, + 0x6b, 0x79, 0xff, 0xff, 0xd8, 0x6b, 0x6b, 0x6b, + 0x6b, 0x63, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, + 0xff, 0xff, 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xff, 0xff, + 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x17, 0xff, 0xff, 0xbb, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x17, 0xff, 0xff, 0xbb, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, + 0xff, 0xff, 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xff, 0xff, + 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x17, 0xff, 0xff, 0xbb, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x17, 0xff, 0xff, 0xbb, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, + 0xff, 0xff, 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xff, 0xff, + 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x17, 0xff, 0xff, 0xbb, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x17, 0xff, 0xff, 0xbb, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, + 0xff, 0xff, 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xff, 0xff, + 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x17, 0xff, 0xff, 0xbb, 0x0, + 0x0, 0x0, 0x0, 0x0, + + /* U+0055 "U" */ + 0xc3, 0xff, 0xff, 0x3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x53, 0xff, 0xff, 0x5b, 0xc3, 0xff, + 0xff, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x53, 0xff, 0xff, 0x5b, 0xc3, 0xff, 0xff, 0x3, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x53, 0xff, + 0xff, 0x5b, 0xc3, 0xff, 0xff, 0x3, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x53, 0xff, 0xff, 0x5b, + 0xc3, 0xff, 0xff, 0x3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x53, 0xff, 0xff, 0x5b, 0xc3, 0xff, + 0xff, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x53, 0xff, 0xff, 0x5b, 0xc3, 0xff, 0xff, 0x3, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x53, 0xff, + 0xff, 0x5b, 0xc3, 0xff, 0xff, 0x3, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x53, 0xff, 0xff, 0x5b, + 0xc3, 0xff, 0xff, 0x3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x53, 0xff, 0xff, 0x5b, 0xc3, 0xff, + 0xff, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x53, 0xff, 0xff, 0x5b, 0xbf, 0xff, 0xff, 0xa, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5a, 0xff, + 0xff, 0x56, 0xb0, 0xff, 0xff, 0x25, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x77, 0xff, 0xff, 0x46, + 0x93, 0xff, 0xff, 0x45, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x97, 0xff, 0xff, 0x29, 0x5c, 0xff, + 0xff, 0xa5, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, + 0xec, 0xff, 0xf1, 0x2, 0xe, 0xf3, 0xff, 0xfd, + 0x57, 0x0, 0x0, 0x0, 0x9, 0xa2, 0xff, 0xff, + 0x98, 0x0, 0x0, 0x7b, 0xff, 0xff, 0xff, 0xc4, + 0x96, 0xa3, 0xe0, 0xff, 0xff, 0xf2, 0x21, 0x0, + 0x0, 0x2, 0x9c, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf2, 0x47, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x46, 0xa3, 0xcb, 0xf2, 0xe4, 0xbc, 0x88, + 0x1b, 0x0, 0x0, 0x0, + + /* U+0056 "V" */ + 0x1, 0xe4, 0xff, 0xfb, 0xe, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xad, 0xff, 0xff, 0x2a, + 0x0, 0x97, 0xff, 0xff, 0x4e, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x5, 0xf2, 0xff, 0xda, 0x0, + 0x0, 0x4a, 0xff, 0xff, 0x92, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x41, 0xff, 0xff, 0x89, 0x0, + 0x0, 0x8, 0xf4, 0xff, 0xd7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x8b, 0xff, 0xff, 0x38, 0x0, + 0x0, 0x0, 0xaf, 0xff, 0xff, 0x1c, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xd5, 0xff, 0xe6, 0x1, 0x0, + 0x0, 0x0, 0x61, 0xff, 0xff, 0x60, 0x0, 0x0, + 0x0, 0x0, 0x20, 0xff, 0xff, 0x97, 0x0, 0x0, + 0x0, 0x0, 0x16, 0xfd, 0xff, 0xa5, 0x0, 0x0, + 0x0, 0x0, 0x6a, 0xff, 0xff, 0x46, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc6, 0xff, 0xe9, 0x1, 0x0, + 0x0, 0x0, 0xb4, 0xff, 0xf0, 0x6, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x79, 0xff, 0xff, 0x2e, 0x0, + 0x0, 0x8, 0xf6, 0xff, 0xa5, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2b, 0xff, 0xff, 0x74, 0x0, + 0x0, 0x48, 0xff, 0xff, 0x54, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xdd, 0xff, 0xba, 0x0, + 0x0, 0x90, 0xff, 0xf8, 0xc, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x90, 0xff, 0xf7, 0x9, + 0x0, 0xd7, 0xff, 0xb3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x42, 0xff, 0xff, 0x48, + 0x1f, 0xff, 0xff, 0x62, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x5, 0xf0, 0xff, 0x8f, + 0x67, 0xff, 0xfd, 0x15, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xa7, 0xff, 0xd6, + 0xae, 0xff, 0xc1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x5a, 0xff, 0xff, + 0xf9, 0xff, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0xfb, 0xff, + 0xff, 0xff, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbe, 0xff, + 0xff, 0xcf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0057 "W" */ + 0x5b, 0xff, 0xff, 0x7d, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8c, 0xff, 0xff, 0x29, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xe0, 0xff, 0xc5, 0x28, 0xff, 0xff, + 0xa9, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcb, 0xff, + 0xff, 0x67, 0x0, 0x0, 0x0, 0x0, 0x10, 0xff, + 0xff, 0x93, 0x2, 0xf4, 0xff, 0xd5, 0x0, 0x0, + 0x0, 0x0, 0xe, 0xfc, 0xff, 0xff, 0xa4, 0x0, + 0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, 0x61, 0x0, + 0xc4, 0xff, 0xfb, 0x6, 0x0, 0x0, 0x0, 0x4b, + 0xff, 0xfc, 0xff, 0xe1, 0x0, 0x0, 0x0, 0x0, + 0x6e, 0xff, 0xff, 0x2f, 0x0, 0x92, 0xff, 0xff, + 0x2d, 0x0, 0x0, 0x0, 0x8b, 0xff, 0xa9, 0xff, + 0xff, 0x1e, 0x0, 0x0, 0x0, 0x9e, 0xff, 0xf8, + 0x5, 0x0, 0x5f, 0xff, 0xff, 0x59, 0x0, 0x0, + 0x0, 0xca, 0xff, 0x53, 0xef, 0xff, 0x5c, 0x0, + 0x0, 0x0, 0xcd, 0xff, 0xcb, 0x0, 0x0, 0x2d, + 0xff, 0xff, 0x85, 0x0, 0x0, 0xe, 0xfc, 0xff, + 0x1e, 0xbf, 0xff, 0x99, 0x0, 0x0, 0x4, 0xf8, + 0xff, 0x99, 0x0, 0x0, 0x4, 0xf7, 0xff, 0xb1, + 0x0, 0x0, 0x4a, 0xff, 0xea, 0x0, 0x8d, 0xff, + 0xd6, 0x0, 0x0, 0x2c, 0xff, 0xff, 0x67, 0x0, + 0x0, 0x0, 0xc9, 0xff, 0xdd, 0x0, 0x0, 0x8a, + 0xff, 0xb2, 0x0, 0x56, 0xff, 0xff, 0x14, 0x0, + 0x5c, 0xff, 0xff, 0x35, 0x0, 0x0, 0x0, 0x97, + 0xff, 0xfe, 0xb, 0x0, 0xc7, 0xff, 0x75, 0x0, + 0x1a, 0xff, 0xff, 0x4f, 0x0, 0x89, 0xff, 0xfb, + 0x8, 0x0, 0x0, 0x0, 0x64, 0xff, 0xff, 0x33, + 0x4, 0xf7, 0xff, 0x38, 0x0, 0x0, 0xdd, 0xff, + 0x84, 0x0, 0xb1, 0xff, 0xd1, 0x0, 0x0, 0x0, + 0x0, 0x32, 0xff, 0xff, 0x5c, 0x2f, 0xff, 0xf6, + 0x5, 0x0, 0x0, 0xa0, 0xff, 0xb9, 0x0, 0xd9, + 0xff, 0x9f, 0x0, 0x0, 0x0, 0x0, 0x6, 0xfa, + 0xff, 0x85, 0x63, 0xff, 0xbe, 0x0, 0x0, 0x0, + 0x63, 0xff, 0xee, 0x5, 0xfb, 0xff, 0x6d, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xce, 0xff, 0xaf, 0x97, + 0xff, 0x81, 0x0, 0x0, 0x0, 0x26, 0xff, 0xff, + 0x4c, 0xff, 0xff, 0x3b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x9b, 0xff, 0xd8, 0xca, 0xff, 0x44, 0x0, + 0x0, 0x0, 0x0, 0xe8, 0xff, 0xa8, 0xff, 0xfd, + 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x69, 0xff, + 0xfd, 0xfc, 0xfb, 0xc, 0x0, 0x0, 0x0, 0x0, + 0xac, 0xff, 0xfb, 0xff, 0xd7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x37, 0xff, 0xff, 0xff, 0xca, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, 0xff, + 0xff, 0xa5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9, 0xfc, 0xff, 0xff, 0x8d, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x32, 0xff, 0xff, 0xff, 0x74, 0x0, + 0x0, 0x0, + + /* U+0058 "X" */ + 0x15, 0xf1, 0xff, 0xf5, 0x16, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8f, 0xff, 0xff, 0x56, 0x0, 0x7b, + 0xff, 0xff, 0x8c, 0x0, 0x0, 0x0, 0x0, 0x17, + 0xf6, 0xff, 0xcc, 0x1, 0x0, 0xa, 0xe5, 0xff, + 0xf6, 0x17, 0x0, 0x0, 0x0, 0x8b, 0xff, 0xff, + 0x44, 0x0, 0x0, 0x0, 0x64, 0xff, 0xff, 0x8e, + 0x0, 0x0, 0x14, 0xf4, 0xff, 0xbc, 0x0, 0x0, + 0x0, 0x0, 0x3, 0xd5, 0xff, 0xf6, 0x19, 0x0, + 0x86, 0xff, 0xfe, 0x34, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x4d, 0xff, 0xff, 0x92, 0xd, 0xf0, 0xff, + 0xa9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc1, 0xff, 0xf7, 0x86, 0xff, 0xfb, 0x25, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x37, 0xff, + 0xff, 0xff, 0xff, 0x97, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xb5, 0xff, 0xff, + 0xf9, 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xe, 0xeb, 0xff, 0xff, 0xff, 0x3c, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x84, 0xff, 0xf1, 0xef, 0xff, 0xc9, 0x1, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xf5, 0xff, + 0x87, 0x7d, 0xff, 0xff, 0x57, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9c, 0xff, 0xf8, 0x19, 0xf, + 0xef, 0xff, 0xde, 0x6, 0x0, 0x0, 0x0, 0x0, + 0x2a, 0xfc, 0xff, 0x9a, 0x0, 0x0, 0x7f, 0xff, + 0xff, 0x72, 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, + 0xfa, 0x20, 0x0, 0x0, 0xe, 0xed, 0xff, 0xee, + 0x11, 0x0, 0x0, 0x3f, 0xff, 0xff, 0x99, 0x0, + 0x0, 0x0, 0x0, 0x75, 0xff, 0xff, 0x8d, 0x0, + 0x0, 0xc9, 0xff, 0xf9, 0x1f, 0x0, 0x0, 0x0, + 0x0, 0x9, 0xe6, 0xff, 0xf9, 0x22, 0x56, 0xff, + 0xff, 0x97, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x6a, 0xff, 0xff, 0xa8, + + /* U+0059 "Y" */ + 0x1, 0xd5, 0xff, 0xfd, 0x20, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc7, 0xff, 0xfb, 0x1f, 0x0, + 0x5e, 0xff, 0xff, 0x8a, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x35, 0xff, 0xff, 0xa0, 0x0, 0x0, 0x4, + 0xe0, 0xff, 0xed, 0x9, 0x0, 0x0, 0x0, 0x0, + 0xa4, 0xff, 0xfe, 0x29, 0x0, 0x0, 0x0, 0x6b, + 0xff, 0xff, 0x64, 0x0, 0x0, 0x0, 0x18, 0xf9, + 0xff, 0xac, 0x0, 0x0, 0x0, 0x0, 0x8, 0xe9, + 0xff, 0xd1, 0x0, 0x0, 0x0, 0x81, 0xff, 0xff, + 0x34, 0x0, 0x0, 0x0, 0x0, 0x0, 0x78, 0xff, + 0xff, 0x3c, 0x0, 0x5, 0xe7, 0xff, 0xb9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xe, 0xf0, 0xff, + 0xa7, 0x0, 0x59, 0xff, 0xff, 0x3f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x85, 0xff, 0xfa, + 0x17, 0xc5, 0xff, 0xc6, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x15, 0xf6, 0xff, 0xae, + 0xff, 0xff, 0x4c, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x93, 0xff, 0xff, 0xff, + 0xd2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1e, 0xfa, 0xff, 0xff, 0x58, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc6, 0xff, 0xff, 0x6, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xc3, 0xff, 0xff, 0x3, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xc3, 0xff, 0xff, 0x3, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xc3, 0xff, 0xff, 0x3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc3, + 0xff, 0xff, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc3, 0xff, + 0xff, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xc3, 0xff, 0xff, + 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+005A "Z" */ + 0xf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x6b, 0xf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x66, 0x6, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, + 0x6b, 0x6b, 0x8e, 0xff, 0xff, 0xe1, 0xc, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbb, + 0xff, 0xff, 0x4a, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x5f, 0xff, 0xff, 0xa5, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x15, + 0xec, 0xff, 0xec, 0x15, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xa6, 0xff, 0xff, 0x5d, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x49, 0xff, 0xff, 0xba, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb, 0xe0, 0xff, 0xf5, + 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x8f, 0xff, 0xff, 0x72, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x35, 0xfd, 0xff, + 0xcb, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x4, 0xd1, 0xff, 0xfb, 0x2f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x79, 0xff, + 0xff, 0x86, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x25, 0xf7, 0xff, 0xda, 0x8, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbf, + 0xff, 0xfe, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x63, 0xff, 0xff, 0xdd, 0x73, + 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x3b, + 0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x83, 0xdf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x83, + + /* U+005B "[" */ + 0x7f, 0xff, 0xff, 0xff, 0xff, 0x93, 0x7f, 0xff, + 0xd2, 0x97, 0x97, 0x57, 0x7f, 0xff, 0x8f, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0x8f, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0x8f, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0x8f, 0x0, 0x0, 0x0, 0x7f, 0xff, 0x8f, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0x8f, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0x8f, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0x8f, 0x0, 0x0, 0x0, 0x7f, 0xff, 0x8f, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0x8f, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0x8f, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0x8f, 0x0, 0x0, 0x0, 0x7f, 0xff, 0x8f, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0x8f, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0x8f, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0x8f, 0x0, 0x0, 0x0, 0x7f, 0xff, 0x8f, 0x0, + 0x0, 0x0, 0x7f, 0xff, 0x8f, 0x0, 0x0, 0x0, + 0x7f, 0xff, 0x8f, 0x0, 0x0, 0x0, 0x7f, 0xff, + 0x8f, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xd2, 0x97, + 0x97, 0x57, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x93, + + /* U+005C "\\" */ + 0x5d, 0xff, 0x89, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x18, 0xff, 0xcf, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xd2, 0xfe, 0x16, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8c, + 0xff, 0x5a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x47, 0xff, 0x9f, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0xf8, 0xe4, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbc, 0xff, + 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x76, 0xff, 0x6f, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x31, 0xff, 0xb5, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0xea, 0xf4, 0x6, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa5, + 0xff, 0x3f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x60, 0xff, 0x85, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1b, 0xff, 0xca, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd5, 0xfd, + 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x8f, 0xff, 0x55, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x4a, 0xff, 0x9a, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb, 0xf9, 0xe0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbf, + 0xff, 0x25, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x79, 0xff, 0x6a, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x33, 0xff, 0xb0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xec, 0xf1, + 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xa8, 0xff, 0x3b, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x63, 0xff, 0x80, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1e, 0xff, 0xc5, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x74, + 0x7c, 0x1, + + /* U+005D "]" */ + 0xb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb, 0x7, + 0x97, 0x97, 0x9c, 0xff, 0xff, 0xb, 0x0, 0x0, + 0x0, 0xb, 0xff, 0xff, 0xb, 0x0, 0x0, 0x0, + 0xb, 0xff, 0xff, 0xb, 0x0, 0x0, 0x0, 0xb, + 0xff, 0xff, 0xb, 0x0, 0x0, 0x0, 0xb, 0xff, + 0xff, 0xb, 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, + 0xb, 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, 0xb, + 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, 0xb, 0x0, + 0x0, 0x0, 0xb, 0xff, 0xff, 0xb, 0x0, 0x0, + 0x0, 0xb, 0xff, 0xff, 0xb, 0x0, 0x0, 0x0, + 0xb, 0xff, 0xff, 0xb, 0x0, 0x0, 0x0, 0xb, + 0xff, 0xff, 0xb, 0x0, 0x0, 0x0, 0xb, 0xff, + 0xff, 0xb, 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, + 0xb, 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, 0xb, + 0x0, 0x0, 0x0, 0xb, 0xff, 0xff, 0xb, 0x0, + 0x0, 0x0, 0xb, 0xff, 0xff, 0xb, 0x0, 0x0, + 0x0, 0xb, 0xff, 0xff, 0xb, 0x0, 0x0, 0x0, + 0xb, 0xff, 0xff, 0xb, 0x0, 0x0, 0x0, 0xb, + 0xff, 0xff, 0xb, 0x0, 0x0, 0x0, 0xb, 0xff, + 0xff, 0xb, 0x7, 0x97, 0x97, 0x9c, 0xff, 0xff, + 0xb, 0xb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb, + + /* U+005E "^" */ + 0x0, 0x0, 0x0, 0x0, 0x88, 0xff, 0xff, 0x32, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, + 0xe5, 0xff, 0xff, 0x92, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x48, 0xff, 0xee, 0xff, 0xec, + 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa8, + 0xff, 0x83, 0xd1, 0xff, 0x52, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x10, 0xf7, 0xff, 0x2b, 0x7a, 0xff, + 0xb2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x68, 0xff, + 0xd3, 0x0, 0x23, 0xff, 0xfb, 0x17, 0x0, 0x0, + 0x0, 0x0, 0xc8, 0xff, 0x7b, 0x0, 0x0, 0xcb, + 0xff, 0x72, 0x0, 0x0, 0x0, 0x28, 0xff, 0xff, + 0x21, 0x0, 0x0, 0x71, 0xff, 0xd3, 0x0, 0x0, + 0x0, 0x88, 0xff, 0xc5, 0x0, 0x0, 0x0, 0x19, + 0xfd, 0xff, 0x32, 0x0, 0x3, 0xe5, 0xff, 0x6b, + 0x0, 0x0, 0x0, 0x0, 0xbc, 0xff, 0x92, 0x0, + 0x48, 0xff, 0xfb, 0x15, 0x0, 0x0, 0x0, 0x0, + 0x61, 0xff, 0xec, 0x6, + + /* U+005F "_" */ + 0x85, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, + 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0x23, 0xa7, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x2b, + + /* U+0060 "`" */ + 0xbb, 0xff, 0xff, 0x81, 0x0, 0x0, 0x0, 0x10, + 0xd2, 0xff, 0xfb, 0x33, 0x0, 0x0, 0x0, 0x19, + 0xdd, 0xff, 0xd4, 0x7, 0x0, 0x0, 0x0, 0x23, + 0xe7, 0xff, 0x88, 0x0, 0x0, 0x0, 0x0, 0x2e, + 0xef, 0xfc, 0x38, + + /* U+0061 "a" */ + 0x0, 0x0, 0x1b, 0x85, 0xcb, 0xee, 0xef, 0xc1, + 0x6f, 0x1, 0x0, 0x6, 0x8e, 0xf7, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xad, 0x0, 0x0, 0xc1, + 0xff, 0xd2, 0x8f, 0x62, 0x7c, 0xeb, 0xff, 0xff, + 0x46, 0x0, 0x2f, 0x50, 0x1, 0x0, 0x0, 0x0, + 0x33, 0xff, 0xff, 0xad, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xde, 0xff, 0xd0, 0x0, + 0x0, 0x0, 0x0, 0x23, 0x60, 0x94, 0xb9, 0xf6, + 0xff, 0xee, 0x0, 0x0, 0x57, 0xcd, 0xff, 0xff, + 0xff, 0xff, 0xfe, 0xff, 0xf7, 0x0, 0x9c, 0xff, + 0xff, 0xe1, 0x80, 0x43, 0x15, 0xc7, 0xff, 0xf7, + 0x47, 0xff, 0xff, 0xa2, 0x3, 0x0, 0x0, 0x0, + 0xc7, 0xff, 0xf7, 0x88, 0xff, 0xff, 0x16, 0x0, + 0x0, 0x0, 0x0, 0xc7, 0xff, 0xf7, 0x9e, 0xff, + 0xff, 0x1f, 0x0, 0x0, 0x0, 0x50, 0xf2, 0xff, + 0xf7, 0x6d, 0xff, 0xff, 0xd1, 0x70, 0x65, 0xac, + 0xff, 0xff, 0xff, 0xf7, 0x9, 0xdb, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xc1, 0x97, 0xff, 0xf7, 0x0, + 0x15, 0x9c, 0xe0, 0xf5, 0xcb, 0x66, 0x1, 0x57, + 0xff, 0xf7, + + /* U+0062 "b" */ + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xd5, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xcb, 0x0, 0x61, 0xb5, 0xee, 0xd1, + 0x9a, 0x1e, 0x0, 0x0, 0xe7, 0xff, 0xd6, 0xb9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe8, 0x37, 0x0, + 0xe7, 0xff, 0xff, 0xff, 0xcd, 0x80, 0x7e, 0xd0, + 0xff, 0xff, 0xda, 0x2, 0xe7, 0xff, 0xfd, 0x7c, + 0x1, 0x0, 0x0, 0x9, 0xc4, 0xff, 0xff, 0x57, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4c, 0xff, 0xff, 0xa2, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x10, 0xff, 0xff, 0xc2, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xf8, 0xff, 0xe1, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7, 0xfe, 0xff, 0xce, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x26, 0xff, 0xff, 0xa7, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7e, 0xff, 0xff, 0x7f, + 0xe7, 0xff, 0xed, 0x31, 0x0, 0x0, 0x0, 0x2d, + 0xed, 0xff, 0xf7, 0x1e, 0xe7, 0xff, 0xff, 0xfc, + 0xa9, 0x76, 0x96, 0xf1, 0xff, 0xff, 0x86, 0x0, + 0xe7, 0xff, 0xb3, 0xe4, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xa5, 0x9, 0x0, 0xe7, 0xff, 0x56, 0x11, + 0x90, 0xd4, 0xee, 0xb7, 0x62, 0x0, 0x0, 0x0, + + /* U+0063 "c" */ + 0x0, 0x0, 0x0, 0x1f, 0x93, 0xc5, 0xf1, 0xd6, + 0xa3, 0x2d, 0x0, 0x0, 0x0, 0x69, 0xf3, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf9, 0x44, 0x0, 0x51, + 0xff, 0xff, 0xff, 0xad, 0x70, 0x7d, 0xd2, 0xca, + 0x7, 0x11, 0xe6, 0xff, 0xfd, 0x54, 0x0, 0x0, + 0x0, 0x5, 0x14, 0x0, 0x73, 0xff, 0xff, 0x9f, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb3, + 0xff, 0xff, 0x3d, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xcd, 0xff, 0xff, 0x16, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xc3, 0xff, 0xff, + 0x15, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x9a, 0xff, 0xff, 0x3b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x70, 0xff, 0xff, 0x99, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x19, 0xf1, + 0xff, 0xfb, 0x4a, 0x0, 0x0, 0x0, 0x1, 0x3e, + 0x0, 0x0, 0x6d, 0xff, 0xff, 0xfd, 0xa9, 0x70, + 0x82, 0xcb, 0xfe, 0x43, 0x0, 0x3, 0x85, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, 0x5d, 0x0, + 0x0, 0x0, 0x36, 0xa0, 0xcf, 0xf3, 0xcd, 0x98, + 0x23, 0x0, + + /* U+0064 "d" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xb3, 0xff, 0xff, 0xf, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, + 0xff, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb3, 0xff, 0xff, 0xf, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, + 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb0, 0xff, 0xff, 0xf, 0x0, 0x0, + 0x0, 0x52, 0xb0, 0xea, 0xdc, 0xa5, 0x24, 0xa6, + 0xff, 0xff, 0xf, 0x0, 0x4, 0x90, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf4, 0xda, 0xff, 0xff, 0xf, + 0x0, 0x6e, 0xff, 0xff, 0xf8, 0x99, 0x6d, 0x96, + 0xf3, 0xff, 0xff, 0xff, 0xf, 0x12, 0xed, 0xff, + 0xf9, 0x3c, 0x0, 0x0, 0x0, 0x1e, 0xd8, 0xff, + 0xff, 0xf, 0x65, 0xff, 0xff, 0x98, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, 0x8f, + 0xff, 0xff, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb3, 0xff, 0xff, 0xf, 0xb7, 0xff, 0xff, 0x1f, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, + 0xf, 0xbb, 0xff, 0xff, 0x1d, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, 0x9a, 0xff, + 0xff, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb3, + 0xff, 0xff, 0xf, 0x79, 0xff, 0xff, 0x84, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb4, 0xff, 0xff, 0xf, + 0x29, 0xfd, 0xff, 0xec, 0x27, 0x0, 0x0, 0x0, + 0x57, 0xf7, 0xff, 0xff, 0xf, 0x0, 0xa6, 0xff, + 0xff, 0xed, 0x94, 0x79, 0xb5, 0xfe, 0xff, 0xff, + 0xff, 0xf, 0x0, 0x19, 0xc9, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xde, 0x99, 0xff, 0xff, 0xf, 0x0, + 0x0, 0x7, 0x82, 0xc3, 0xf3, 0xe2, 0x8f, 0xf, + 0x44, 0xff, 0xff, 0xf, + + /* U+0065 "e" */ + 0x0, 0x0, 0x0, 0x36, 0xa1, 0xd9, 0xec, 0xb9, + 0x73, 0x2, 0x0, 0x0, 0x0, 0x0, 0x78, 0xfb, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xba, 0x10, 0x0, + 0x0, 0x5e, 0xff, 0xff, 0xd8, 0x64, 0x50, 0x94, + 0xfe, 0xff, 0x8b, 0x0, 0x11, 0xe9, 0xff, 0xd4, + 0x9, 0x0, 0x0, 0x0, 0x7a, 0xff, 0xf5, 0x16, + 0x73, 0xff, 0xff, 0x44, 0x0, 0x0, 0x0, 0x0, + 0xc, 0xfd, 0xff, 0x4f, 0xb2, 0xff, 0xf4, 0x6, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xe3, 0xff, 0x71, + 0xcd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x8c, 0xc2, 0xff, 0xff, 0xfb, + 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0x79, + 0x97, 0xff, 0xf0, 0x4, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x6c, 0xff, 0xff, 0x45, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x14, 0xec, 0xff, 0xdf, 0x16, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x4, 0x0, 0x0, 0x62, 0xff, 0xff, + 0xee, 0x82, 0x50, 0x53, 0x7b, 0xd9, 0x7a, 0x0, + 0x0, 0x1, 0x78, 0xf9, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xc3, 0x0, 0x0, 0x0, 0x0, 0x2b, + 0x9b, 0xcb, 0xf4, 0xda, 0xaf, 0x59, 0x1, 0x0, + + /* U+0066 "f" */ + 0x0, 0x0, 0x0, 0x1, 0x6f, 0xc9, 0xf3, 0xeb, + 0xb2, 0x1, 0x0, 0x0, 0x0, 0x95, 0xff, 0xff, + 0xff, 0xff, 0xbf, 0x0, 0x0, 0x0, 0x21, 0xfc, + 0xff, 0xeb, 0x67, 0x59, 0x40, 0x0, 0x0, 0x0, + 0x54, 0xff, 0xff, 0x73, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x71, 0xff, 0xff, 0x4f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x73, 0xff, 0xff, 0x4b, + 0x0, 0x0, 0x0, 0x0, 0x57, 0xe7, 0xfc, 0xff, + 0xff, 0xff, 0xff, 0xf7, 0x0, 0x0, 0x67, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x0, 0x0, + 0x20, 0x4f, 0x9f, 0xff, 0xff, 0x84, 0x4f, 0x4d, + 0x0, 0x0, 0x0, 0x0, 0x73, 0xff, 0xff, 0x4b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x73, 0xff, + 0xff, 0x4b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x73, 0xff, 0xff, 0x4b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x73, 0xff, 0xff, 0x4b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x73, 0xff, 0xff, 0x4b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x73, 0xff, + 0xff, 0x4b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x73, 0xff, 0xff, 0x4b, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x73, 0xff, 0xff, 0x4b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x73, 0xff, 0xff, 0x4b, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x73, 0xff, + 0xff, 0x4b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x73, 0xff, 0xff, 0x4b, 0x0, 0x0, 0x0, 0x0, + + /* U+0067 "g" */ + 0x0, 0x0, 0x9, 0x7f, 0xc9, 0xf4, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x4b, 0x0, 0x18, 0xde, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x4b, 0x0, 0xb9, 0xff, 0xf7, 0x61, 0xf, + 0x31, 0xcd, 0xff, 0xf0, 0x40, 0x37, 0x10, 0x20, + 0xff, 0xff, 0x88, 0x0, 0x0, 0x0, 0x26, 0xfe, + 0xff, 0x64, 0x0, 0x0, 0x52, 0xff, 0xff, 0x46, + 0x0, 0x0, 0x0, 0x0, 0xe3, 0xff, 0x94, 0x0, + 0x0, 0x5c, 0xff, 0xff, 0x47, 0x0, 0x0, 0x0, + 0x0, 0xe4, 0xff, 0x84, 0x0, 0x0, 0x27, 0xff, + 0xff, 0x8c, 0x0, 0x0, 0x0, 0x2a, 0xff, 0xff, + 0x56, 0x0, 0x0, 0x0, 0xa4, 0xff, 0xf9, 0x65, + 0xf, 0x32, 0xd1, 0xff, 0xdc, 0xa, 0x0, 0x0, + 0x0, 0x9, 0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xea, 0x39, 0x0, 0x0, 0x0, 0x0, 0x66, 0xfd, + 0xc5, 0x9b, 0xcd, 0xb8, 0x85, 0x12, 0x0, 0x0, + 0x0, 0x0, 0x6, 0xee, 0xff, 0x33, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe, + 0xfb, 0xff, 0xb9, 0x5e, 0x4d, 0x4b, 0x4b, 0x39, + 0x1f, 0x0, 0x0, 0x0, 0x0, 0x92, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd2, 0x46, + 0x0, 0x0, 0x47, 0xfe, 0xe9, 0xe6, 0xfd, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf4, 0x23, 0x3e, 0xf7, + 0xe9, 0x1c, 0x0, 0x0, 0x0, 0x3, 0x20, 0xa9, + 0xff, 0xff, 0x65, 0xba, 0xff, 0x97, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3c, 0xff, 0xff, 0x69, + 0xce, 0xff, 0xac, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x85, 0xff, 0xff, 0x28, 0x91, 0xff, 0xff, + 0x85, 0x17, 0x0, 0x0, 0x1e, 0x8d, 0xfe, 0xff, + 0x98, 0x0, 0x12, 0xd0, 0xff, 0xff, 0xff, 0xf0, + 0xf4, 0xff, 0xff, 0xfc, 0x9a, 0x3, 0x0, 0x0, + 0xb, 0x66, 0xc0, 0xe2, 0xf8, 0xe2, 0xbf, 0x91, + 0x23, 0x0, 0x0, 0x0, + + /* U+0068 "h" */ + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xd5, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xcb, 0x0, 0x3f, 0xb7, 0xee, 0xe6, + 0xb1, 0x25, 0x0, 0x0, 0xe7, 0xff, 0xc2, 0x74, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0xee, 0x21, 0x0, + 0xe7, 0xff, 0xfc, 0xff, 0xec, 0xa5, 0x90, 0xe8, + 0xff, 0xff, 0x8e, 0x0, 0xe7, 0xff, 0xff, 0xbd, + 0xf, 0x0, 0x0, 0x28, 0xfe, 0xff, 0xdc, 0x0, + 0xe7, 0xff, 0xe2, 0x5, 0x0, 0x0, 0x0, 0x0, + 0xd2, 0xff, 0xf7, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xba, 0xff, 0xff, 0xd, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb3, 0xff, 0xff, 0xf, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb3, 0xff, 0xff, 0xf, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb3, 0xff, 0xff, 0xf, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb3, 0xff, 0xff, 0xf, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, + + /* U+0069 "i" */ + 0x0, 0x98, 0xe7, 0x7f, 0x0, 0x27, 0xff, 0xff, + 0xfb, 0xf, 0x7, 0xee, 0xff, 0xd7, 0x0, 0x0, + 0x15, 0x53, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe7, + 0xff, 0xd7, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0xe7, 0xff, 0xd7, 0x0, 0x0, 0xe7, 0xff, + 0xd7, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0xe7, 0xff, 0xd7, + 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, 0x0, 0xe7, + 0xff, 0xd7, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0xe7, 0xff, 0xd7, 0x0, 0x0, 0xe7, 0xff, + 0xd7, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, + + /* U+006A "j" */ + 0x0, 0x0, 0x0, 0x0, 0x93, 0xe7, 0x84, 0x0, + 0x0, 0x0, 0x0, 0x23, 0xff, 0xff, 0xfd, 0x15, + 0x0, 0x0, 0x0, 0x5, 0xea, 0xff, 0xdd, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x14, 0x53, 0x10, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xdf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xee, 0xff, 0xd3, 0x0, + 0x0, 0x0, 0x0, 0x12, 0xfd, 0xff, 0xb7, 0x0, + 0x0, 0x4f, 0x51, 0xac, 0xff, 0xff, 0x76, 0x0, + 0x0, 0xe2, 0xff, 0xff, 0xff, 0xe7, 0x11, 0x0, + 0xe, 0xd2, 0xf7, 0xe1, 0xaa, 0x1c, 0x0, 0x0, + + /* U+006B "k" */ + 0xe7, 0xff, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xcb, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xcb, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xcb, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x5c, + 0xff, 0xff, 0xae, 0x0, 0xe7, 0xff, 0xcb, 0x0, + 0x0, 0x0, 0x29, 0xf4, 0xff, 0xd9, 0xe, 0x0, + 0xe7, 0xff, 0xcb, 0x0, 0x0, 0xa, 0xd4, 0xff, + 0xf4, 0x2b, 0x0, 0x0, 0xe7, 0xff, 0xcb, 0x0, + 0x0, 0xa1, 0xff, 0xff, 0x58, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xcb, 0x0, 0x62, 0xff, 0xff, 0x8e, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xcb, 0x2d, + 0xf6, 0xff, 0xd1, 0x4, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd9, 0xd8, 0xff, 0xff, 0xf5, 0x1e, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xff, 0xff, + 0xf5, 0xf7, 0xff, 0xac, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xff, 0xfe, 0x55, 0x84, 0xff, 0xff, + 0x46, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xff, 0x80, + 0x0, 0xc, 0xe8, 0xff, 0xd9, 0x6, 0x0, 0x0, + 0xe7, 0xff, 0xd8, 0x1, 0x0, 0x0, 0x64, 0xff, + 0xff, 0x79, 0x0, 0x0, 0xe7, 0xff, 0xcb, 0x0, + 0x0, 0x0, 0x2, 0xd1, 0xff, 0xf5, 0x1e, 0x0, + 0xe7, 0xff, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x44, + 0xff, 0xff, 0xac, 0x0, 0xe7, 0xff, 0xcb, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb4, 0xff, 0xff, 0x47, + + /* U+006C "l" */ + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0xe7, 0xff, 0xd7, + 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, 0x0, 0xe7, + 0xff, 0xd7, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0xe7, 0xff, 0xd7, 0x0, 0x0, 0xe7, 0xff, + 0xd7, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0xe7, 0xff, 0xd7, + 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, 0x0, 0xe7, + 0xff, 0xd7, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0xe7, 0xff, 0xd7, 0x0, 0x0, 0xe7, 0xff, + 0xd7, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, 0x0, + 0xe4, 0xff, 0xdc, 0x0, 0x0, 0xce, 0xff, 0xfd, + 0x67, 0x0, 0x88, 0xff, 0xff, 0xff, 0x1b, 0xd, + 0xa7, 0xef, 0xef, 0x33, + + /* U+006D "m" */ + 0xe7, 0xff, 0x64, 0x0, 0x4e, 0xc4, 0xf4, 0xe5, + 0x9b, 0x13, 0x0, 0x0, 0x50, 0xc6, 0xf3, 0xd4, + 0x8a, 0x5, 0x0, 0xe7, 0xff, 0x80, 0x81, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xc9, 0x2, 0x7e, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xb3, 0x0, 0xe7, 0xff, + 0xf4, 0xff, 0xe6, 0x97, 0xa1, 0xfc, 0xff, 0xff, + 0xc8, 0xff, 0xe3, 0x94, 0xa6, 0xfe, 0xff, 0xff, + 0x34, 0xe7, 0xff, 0xff, 0xb7, 0xb, 0x0, 0x0, + 0x71, 0xff, 0xff, 0xff, 0xab, 0x8, 0x0, 0x0, + 0x85, 0xff, 0xff, 0x83, 0xe7, 0xff, 0xe2, 0x5, + 0x0, 0x0, 0x0, 0x1b, 0xff, 0xff, 0xd1, 0x2, + 0x0, 0x0, 0x0, 0x32, 0xff, 0xff, 0x9e, 0xe7, + 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x4, 0xfe, + 0xff, 0xc3, 0x0, 0x0, 0x0, 0x0, 0x1a, 0xff, + 0xff, 0xb5, 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xfb, 0xff, 0xc3, 0x0, 0x0, 0x0, + 0x0, 0x13, 0xff, 0xff, 0xb7, 0xe7, 0xff, 0xd7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xfb, 0xff, 0xc3, + 0x0, 0x0, 0x0, 0x0, 0x13, 0xff, 0xff, 0xb7, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xfb, 0xff, 0xc3, 0x0, 0x0, 0x0, 0x0, 0x13, + 0xff, 0xff, 0xb7, 0xe7, 0xff, 0xd7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xfb, 0xff, 0xc3, 0x0, 0x0, + 0x0, 0x0, 0x13, 0xff, 0xff, 0xb7, 0xe7, 0xff, + 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfb, 0xff, + 0xc3, 0x0, 0x0, 0x0, 0x0, 0x13, 0xff, 0xff, + 0xb7, 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xfb, 0xff, 0xc3, 0x0, 0x0, 0x0, 0x0, + 0x13, 0xff, 0xff, 0xb7, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xfb, 0xff, 0xc3, 0x0, + 0x0, 0x0, 0x0, 0x13, 0xff, 0xff, 0xb7, 0xe7, + 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfb, + 0xff, 0xc3, 0x0, 0x0, 0x0, 0x0, 0x13, 0xff, + 0xff, 0xb7, + + /* U+006E "n" */ + 0xe7, 0xff, 0x64, 0x0, 0x43, 0xb9, 0xee, 0xe6, + 0xb1, 0x25, 0x0, 0x0, 0xe7, 0xff, 0x81, 0x7d, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xee, 0x21, 0x0, + 0xe7, 0xff, 0xf6, 0xff, 0xec, 0xa5, 0x90, 0xe8, + 0xff, 0xff, 0x8e, 0x0, 0xe7, 0xff, 0xff, 0xbd, + 0xf, 0x0, 0x0, 0x28, 0xfe, 0xff, 0xdc, 0x0, + 0xe7, 0xff, 0xe2, 0x5, 0x0, 0x0, 0x0, 0x0, + 0xd2, 0xff, 0xf7, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xba, 0xff, 0xff, 0xd, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb3, 0xff, 0xff, 0xf, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb3, 0xff, 0xff, 0xf, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb3, 0xff, 0xff, 0xf, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb3, 0xff, 0xff, 0xf, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, + + /* U+006F "o" */ + 0x0, 0x0, 0x0, 0x2f, 0x9e, 0xd1, 0xf2, 0xc5, + 0x8e, 0x15, 0x0, 0x0, 0x0, 0x0, 0x0, 0x79, + 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x4c, + 0x0, 0x0, 0x0, 0x5f, 0xff, 0xff, 0xf1, 0x93, + 0x6c, 0xa5, 0xfe, 0xff, 0xf8, 0x25, 0x0, 0x13, + 0xeb, 0xff, 0xf0, 0x2e, 0x0, 0x0, 0x0, 0x5c, + 0xff, 0xff, 0xbc, 0x0, 0x75, 0xff, 0xff, 0x85, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xc4, 0xff, 0xff, + 0x31, 0xb3, 0xff, 0xff, 0x33, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x71, 0xff, 0xff, 0x6f, 0xcd, 0xff, + 0xff, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, + 0xff, 0xff, 0x88, 0xcd, 0xff, 0xff, 0x12, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x50, 0xff, 0xff, 0x88, + 0xb3, 0xff, 0xff, 0x32, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x70, 0xff, 0xff, 0x6f, 0x75, 0xff, 0xff, + 0x84, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc3, 0xff, + 0xff, 0x31, 0x13, 0xeb, 0xff, 0xef, 0x2d, 0x0, + 0x0, 0x0, 0x5b, 0xff, 0xff, 0xbc, 0x0, 0x0, + 0x5f, 0xff, 0xff, 0xf1, 0x94, 0x6f, 0xa6, 0xfe, + 0xff, 0xf8, 0x26, 0x0, 0x0, 0x0, 0x7a, 0xfa, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x4c, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x30, 0xa0, 0xd3, 0xf2, + 0xc7, 0x8f, 0x16, 0x0, 0x0, 0x0, + + /* U+0070 "p" */ + 0xe7, 0xff, 0x68, 0x3, 0x6b, 0xcc, 0xf5, 0xd3, + 0x9b, 0x1f, 0x0, 0x0, 0xe7, 0xff, 0xb0, 0xcb, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xea, 0x39, 0x0, + 0xe7, 0xff, 0xff, 0xff, 0xce, 0x80, 0x7e, 0xd0, + 0xff, 0xff, 0xdb, 0x3, 0xe7, 0xff, 0xfd, 0x7d, + 0x2, 0x0, 0x0, 0x9, 0xc4, 0xff, 0xff, 0x57, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4c, 0xff, 0xff, 0xa2, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x10, 0xff, 0xff, 0xc2, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xf8, 0xff, 0xe1, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7, 0xfe, 0xff, 0xce, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x26, 0xff, 0xff, 0xa7, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7e, 0xff, 0xff, 0x7f, + 0xe7, 0xff, 0xed, 0x31, 0x0, 0x0, 0x0, 0x2d, + 0xed, 0xff, 0xf7, 0x1e, 0xe7, 0xff, 0xff, 0xfd, + 0xaa, 0x76, 0x96, 0xf1, 0xff, 0xff, 0x86, 0x0, + 0xe7, 0xff, 0xe5, 0xe0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xa5, 0x9, 0x0, 0xe7, 0xff, 0xcd, 0xd, + 0x8b, 0xd4, 0xee, 0xb7, 0x62, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd3, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xe7, 0xff, 0xd7, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0071 "q" */ + 0x0, 0x0, 0x0, 0x52, 0xb0, 0xea, 0xdf, 0xae, + 0x33, 0x35, 0xff, 0xff, 0xf, 0x0, 0x4, 0x90, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xc2, 0xff, + 0xff, 0xf, 0x0, 0x6e, 0xff, 0xff, 0xf8, 0x99, + 0x6d, 0x96, 0xf3, 0xff, 0xff, 0xff, 0xf, 0x12, + 0xed, 0xff, 0xf9, 0x3c, 0x0, 0x0, 0x0, 0x1e, + 0xd8, 0xff, 0xff, 0xf, 0x65, 0xff, 0xff, 0x98, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, + 0xf, 0x8f, 0xff, 0xff, 0x43, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, 0xb7, 0xff, + 0xff, 0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb3, + 0xff, 0xff, 0xf, 0xbb, 0xff, 0xff, 0x1d, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, + 0x9a, 0xff, 0xff, 0x3a, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xb3, 0xff, 0xff, 0xf, 0x79, 0xff, 0xff, + 0x84, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb4, 0xff, + 0xff, 0xf, 0x29, 0xfd, 0xff, 0xec, 0x27, 0x0, + 0x0, 0x0, 0x57, 0xf7, 0xff, 0xff, 0xf, 0x0, + 0xa6, 0xff, 0xff, 0xed, 0x94, 0x79, 0xb5, 0xfe, + 0xff, 0xff, 0xff, 0xf, 0x0, 0x19, 0xc9, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xd5, 0xc3, 0xff, 0xff, + 0xf, 0x0, 0x0, 0x7, 0x82, 0xc3, 0xf3, 0xdf, + 0x87, 0xa, 0xa8, 0xff, 0xff, 0xf, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb0, + 0xff, 0xff, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xb3, 0xff, 0xff, 0xf, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb3, 0xff, + 0xff, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb3, 0xff, 0xff, 0xf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb3, 0xff, 0xff, 0xf, + + /* U+0072 "r" */ + 0xe7, 0xff, 0x61, 0x0, 0x59, 0xd1, 0xfc, 0xbf, + 0xe7, 0xff, 0x77, 0x76, 0xff, 0xff, 0xff, 0x89, + 0xe7, 0xff, 0xc1, 0xfc, 0xf9, 0xa8, 0x9c, 0x40, + 0xe7, 0xff, 0xff, 0xed, 0x2c, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xff, 0x58, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xe5, 0x2, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe7, 0xff, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+0073 "s" */ + 0x0, 0x0, 0x0, 0x54, 0xbe, 0xe6, 0xed, 0xbd, + 0x82, 0xe, 0x0, 0x0, 0x0, 0x99, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xe6, 0x29, 0x0, 0x3d, + 0xff, 0xff, 0xc7, 0x4c, 0x3e, 0x69, 0xd5, 0xba, + 0x2, 0x0, 0x7a, 0xff, 0xff, 0x25, 0x0, 0x0, + 0x0, 0x3, 0xc, 0x0, 0x0, 0x6d, 0xff, 0xff, + 0x54, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x20, 0xf1, 0xff, 0xfa, 0x9b, 0x36, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x42, 0xe7, 0xff, 0xff, + 0xff, 0xce, 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x10, 0x77, 0xe2, 0xff, 0xff, 0xff, 0x9a, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x5a, 0xdf, + 0xff, 0xff, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x24, 0xff, 0xff, 0x97, 0x0, 0x2d, + 0x40, 0x0, 0x0, 0x0, 0x0, 0x13, 0xfe, 0xff, + 0x91, 0xd, 0xda, 0xff, 0xac, 0x64, 0x3c, 0x4c, + 0xc2, 0xff, 0xff, 0x4c, 0x8, 0x89, 0xfc, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x99, 0x0, 0x0, + 0x0, 0x34, 0xa5, 0xe2, 0xf8, 0xdf, 0xb8, 0x4c, + 0x0, 0x0, + + /* U+0074 "t" */ + 0x0, 0x0, 0x12, 0xff, 0xff, 0x47, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x25, 0xff, 0xff, 0x47, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x39, 0xff, + 0xff, 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x4d, 0xff, 0xff, 0x47, 0x0, 0x0, 0x0, 0x0, + 0x57, 0xe4, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xb7, 0x0, 0x67, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xb7, 0x0, 0x20, 0x4f, 0xa5, 0xff, + 0xff, 0x81, 0x4f, 0x4f, 0x39, 0x0, 0x0, 0x0, + 0x7b, 0xff, 0xff, 0x47, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7b, 0xff, 0xff, 0x47, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x7b, 0xff, 0xff, 0x47, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7b, 0xff, + 0xff, 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x7b, 0xff, 0xff, 0x47, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7b, 0xff, 0xff, 0x47, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x73, 0xff, 0xff, 0x4f, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x58, 0xff, + 0xff, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x21, 0xfd, 0xff, 0xf1, 0x70, 0x5b, 0x5f, 0x0, + 0x0, 0x0, 0x0, 0xa0, 0xff, 0xff, 0xff, 0xff, + 0xe5, 0x0, 0x0, 0x0, 0x0, 0x3, 0x7f, 0xd0, + 0xf6, 0xe4, 0xa5, 0x9, + + /* U+0075 "u" */ + 0x1b, 0xff, 0xff, 0xaf, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xef, 0xff, 0xd7, 0x1b, 0xff, 0xff, 0xaf, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff, 0xd7, + 0x1b, 0xff, 0xff, 0xaf, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xef, 0xff, 0xd7, 0x1b, 0xff, 0xff, 0xaf, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff, 0xd7, + 0x1b, 0xff, 0xff, 0xaf, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xef, 0xff, 0xd7, 0x1b, 0xff, 0xff, 0xaf, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff, 0xd7, + 0x1b, 0xff, 0xff, 0xaf, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xef, 0xff, 0xd7, 0x1b, 0xff, 0xff, 0xaf, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff, 0xd7, + 0x1a, 0xff, 0xff, 0xb5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xef, 0xff, 0xd7, 0x6, 0xfe, 0xff, 0xcd, + 0x0, 0x0, 0x0, 0x0, 0x15, 0xf7, 0xff, 0xd7, + 0x0, 0xe9, 0xff, 0xfd, 0x25, 0x0, 0x0, 0x13, + 0xc8, 0xff, 0xff, 0xd7, 0x0, 0x9d, 0xff, 0xff, + 0xe5, 0x8e, 0x93, 0xea, 0xfc, 0xe4, 0xff, 0xd7, + 0x0, 0x2a, 0xf2, 0xff, 0xff, 0xff, 0xff, 0xf7, + 0x50, 0x93, 0xff, 0xd7, 0x0, 0x0, 0x2c, 0xb5, + 0xe8, 0xed, 0xb4, 0x35, 0x0, 0x7b, 0xff, 0xd7, + + /* U+0076 "v" */ + 0x7e, 0xff, 0xff, 0x49, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x18, 0xfe, 0xff, 0x95, 0x29, 0xff, 0xff, + 0x96, 0x0, 0x0, 0x0, 0x0, 0x0, 0x63, 0xff, + 0xff, 0x42, 0x0, 0xd4, 0xff, 0xe3, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb0, 0xff, 0xec, 0x3, 0x0, + 0x80, 0xff, 0xff, 0x30, 0x0, 0x0, 0x0, 0x8, + 0xf4, 0xff, 0x9c, 0x0, 0x0, 0x2b, 0xff, 0xff, + 0x7d, 0x0, 0x0, 0x0, 0x4a, 0xff, 0xff, 0x49, + 0x0, 0x0, 0x0, 0xd7, 0xff, 0xca, 0x0, 0x0, + 0x0, 0x97, 0xff, 0xf0, 0x6, 0x0, 0x0, 0x0, + 0x82, 0xff, 0xfe, 0x18, 0x0, 0x0, 0xe3, 0xff, + 0xa2, 0x0, 0x0, 0x0, 0x0, 0x2e, 0xff, 0xff, + 0x63, 0x0, 0x2f, 0xff, 0xff, 0x4f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xd9, 0xff, 0xa9, 0x0, 0x78, + 0xff, 0xf4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x84, 0xff, 0xed, 0x2, 0xbf, 0xff, 0xa9, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x30, 0xff, 0xff, + 0x42, 0xf9, 0xff, 0x56, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xdb, 0xff, 0xca, 0xff, 0xf7, + 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x87, 0xff, 0xff, 0xff, 0xb0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x32, 0xff, 0xff, + 0xff, 0x5d, 0x0, 0x0, 0x0, 0x0, + + /* U+0077 "w" */ + 0x3b, 0xff, 0xff, 0x8c, 0x0, 0x0, 0x0, 0x0, + 0x64, 0xff, 0xff, 0x66, 0x0, 0x0, 0x0, 0x0, + 0x7d, 0xff, 0xff, 0x1f, 0x5, 0xf4, 0xff, 0xc8, + 0x0, 0x0, 0x0, 0x0, 0xa3, 0xff, 0xff, 0xa6, + 0x0, 0x0, 0x0, 0x0, 0xb6, 0xff, 0xe1, 0x0, + 0x0, 0xb9, 0xff, 0xfa, 0x9, 0x0, 0x0, 0x0, + 0xe2, 0xff, 0xff, 0xe6, 0x0, 0x0, 0x0, 0x1, + 0xee, 0xff, 0xa2, 0x0, 0x0, 0x78, 0xff, 0xff, + 0x3e, 0x0, 0x0, 0x22, 0xff, 0xc5, 0xeb, 0xff, + 0x26, 0x0, 0x0, 0x29, 0xff, 0xff, 0x63, 0x0, + 0x0, 0x37, 0xff, 0xff, 0x79, 0x0, 0x0, 0x61, + 0xff, 0x90, 0xb6, 0xff, 0x66, 0x0, 0x0, 0x63, + 0xff, 0xff, 0x24, 0x0, 0x0, 0x3, 0xf2, 0xff, + 0xb4, 0x0, 0x0, 0xa0, 0xff, 0x5d, 0x81, 0xff, + 0xa6, 0x0, 0x0, 0x9c, 0xff, 0xe5, 0x0, 0x0, + 0x0, 0x0, 0xb5, 0xff, 0xee, 0x1, 0x0, 0xe0, + 0xff, 0x2b, 0x4c, 0xff, 0xe6, 0x0, 0x0, 0xd6, + 0xff, 0xa6, 0x0, 0x0, 0x0, 0x0, 0x73, 0xff, + 0xff, 0x29, 0x1e, 0xff, 0xf2, 0x3, 0x14, 0xff, + 0xff, 0x26, 0x10, 0xfe, 0xff, 0x67, 0x0, 0x0, + 0x0, 0x0, 0x32, 0xff, 0xff, 0x59, 0x55, 0xff, + 0xb8, 0x0, 0x0, 0xd5, 0xff, 0x60, 0x3f, 0xff, + 0xff, 0x29, 0x0, 0x0, 0x0, 0x0, 0x2, 0xee, + 0xff, 0x87, 0x8a, 0xff, 0x7b, 0x0, 0x0, 0x97, + 0xff, 0x99, 0x6f, 0xff, 0xe9, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb0, 0xff, 0xb6, 0xbf, 0xff, + 0x3f, 0x0, 0x0, 0x59, 0xff, 0xd2, 0x9e, 0xff, + 0xab, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f, + 0xff, 0xeb, 0xf3, 0xf9, 0x9, 0x0, 0x0, 0x1b, + 0xff, 0xfd, 0xdf, 0xff, 0x6c, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2e, 0xff, 0xff, 0xff, 0xc6, + 0x0, 0x0, 0x0, 0x0, 0xdd, 0xff, 0xff, 0xff, + 0x2d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0xeb, 0xff, 0xff, 0x89, 0x0, 0x0, 0x0, 0x0, + 0x9f, 0xff, 0xff, 0xed, 0x1, 0x0, 0x0, 0x0, + + /* U+0078 "x" */ + 0x17, 0xf1, 0xff, 0xe6, 0xb, 0x0, 0x0, 0x0, + 0x2c, 0xfe, 0xff, 0xaa, 0x0, 0x0, 0x72, 0xff, + 0xff, 0x7f, 0x0, 0x0, 0x0, 0xa8, 0xff, 0xf8, + 0x22, 0x0, 0x0, 0x4, 0xd6, 0xff, 0xf4, 0x18, + 0x0, 0x28, 0xfd, 0xff, 0x89, 0x0, 0x0, 0x0, + 0x0, 0x44, 0xff, 0xff, 0x96, 0x0, 0xa0, 0xff, + 0xea, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0xad, + 0xff, 0xfa, 0x3f, 0xfa, 0xff, 0x67, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1f, 0xf6, 0xff, 0xf6, + 0xff, 0xd4, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8c, 0xff, 0xff, 0xff, 0x46, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xd3, + 0xff, 0xff, 0xff, 0x47, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x6b, 0xff, 0xf6, 0xe2, 0xff, + 0xdb, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, + 0xed, 0xff, 0x8e, 0x56, 0xff, 0xff, 0x7e, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x94, 0xff, 0xf6, 0x18, + 0x0, 0xc7, 0xff, 0xf7, 0x22, 0x0, 0x0, 0x0, + 0x2c, 0xfc, 0xff, 0x8e, 0x0, 0x0, 0x38, 0xfe, + 0xff, 0xb6, 0x0, 0x0, 0x0, 0xbc, 0xff, 0xf4, + 0x15, 0x0, 0x0, 0x0, 0xa2, 0xff, 0xff, 0x51, + 0x0, 0x51, 0xff, 0xff, 0x85, 0x0, 0x0, 0x0, + 0x0, 0x1a, 0xf3, 0xff, 0xe2, 0xb, + + /* U+0079 "y" */ + 0x78, 0xff, 0xff, 0x4d, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x14, 0xfd, 0xff, 0x95, 0x1c, 0xfd, 0xff, + 0xa2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5c, 0xff, + 0xff, 0x42, 0x0, 0xba, 0xff, 0xf0, 0x6, 0x0, + 0x0, 0x0, 0x0, 0xa7, 0xff, 0xeb, 0x3, 0x0, + 0x5b, 0xff, 0xff, 0x4c, 0x0, 0x0, 0x0, 0x3, + 0xee, 0xff, 0x9b, 0x0, 0x0, 0xa, 0xf2, 0xff, + 0xa0, 0x0, 0x0, 0x0, 0x3d, 0xff, 0xff, 0x48, + 0x0, 0x0, 0x0, 0x9d, 0xff, 0xef, 0x6, 0x0, + 0x0, 0x88, 0xff, 0xef, 0x5, 0x0, 0x0, 0x0, + 0x3e, 0xff, 0xff, 0x4a, 0x0, 0x0, 0xd3, 0xff, + 0xa2, 0x0, 0x0, 0x0, 0x0, 0x1, 0xde, 0xff, + 0x9e, 0x0, 0x1c, 0xff, 0xff, 0x4e, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x80, 0xff, 0xeb, 0x3, 0x5f, + 0xff, 0xf3, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x22, 0xfe, 0xff, 0x3d, 0xa1, 0xff, 0xa8, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc2, 0xff, + 0x8d, 0xe3, 0xff, 0x54, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x63, 0xff, 0xf2, 0xff, 0xf6, + 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xe, 0xf6, 0xff, 0xff, 0xae, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa5, 0xff, + 0xff, 0x5b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x90, 0xff, 0xf9, 0xf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, + 0xea, 0xff, 0xa7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x83, 0xff, 0xff, 0x3d, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3b, 0x5d, + 0x93, 0xfd, 0xff, 0xb2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xac, 0xff, 0xff, 0xff, 0xde, + 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xb4, 0xf2, 0xd8, 0x96, 0xf, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, + + /* U+007A "z" */ + 0x0, 0x63, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xdf, 0x0, 0x0, 0x63, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0x0, + 0x0, 0x1f, 0x4f, 0x4f, 0x4f, 0x4f, 0x50, 0xe1, + 0xff, 0xff, 0x44, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5e, 0xff, 0xff, 0x9f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x15, 0xec, 0xff, + 0xea, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa6, 0xff, 0xff, 0x58, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x4a, 0xff, 0xff, 0xb5, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, + 0xe1, 0xff, 0xf3, 0x1e, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x91, 0xff, 0xff, 0x6d, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x38, 0xfd, + 0xff, 0xc8, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x5, 0xd3, 0xff, 0xfa, 0x2c, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7d, 0xff, 0xff, + 0xb7, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x7, + 0x11, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x1b, 0x1b, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, + + /* U+007B "{" */ + 0x0, 0x0, 0x0, 0x11, 0x9f, 0xe6, 0xff, 0x93, + 0x0, 0x0, 0x0, 0x96, 0xff, 0xf9, 0xa9, 0x57, + 0x0, 0x0, 0x0, 0xe6, 0xff, 0x72, 0x0, 0x0, + 0x0, 0x0, 0x4, 0xfd, 0xff, 0x43, 0x0, 0x0, + 0x0, 0x0, 0x5, 0xff, 0xff, 0x3e, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xfa, 0xff, 0x45, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xee, 0xff, 0x4c, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xe2, 0xff, 0x52, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xd7, 0xff, 0x59, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xde, 0xff, 0x4f, 0x0, 0x0, + 0x0, 0x0, 0x58, 0xff, 0xff, 0x25, 0x0, 0x0, + 0x13, 0xe3, 0xff, 0xfb, 0x87, 0x0, 0x0, 0x0, + 0x17, 0xff, 0xff, 0xe5, 0x51, 0x0, 0x0, 0x0, + 0x0, 0x18, 0x8a, 0xff, 0xfa, 0x16, 0x0, 0x0, + 0x0, 0x0, 0x1, 0xe8, 0xff, 0x4b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xd5, 0xff, 0x59, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xe0, 0xff, 0x53, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xec, 0xff, 0x4d, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xf8, 0xff, 0x46, 0x0, 0x0, + 0x0, 0x0, 0x4, 0xff, 0xff, 0x3f, 0x0, 0x0, + 0x0, 0x0, 0x5, 0xfe, 0xff, 0x42, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xe8, 0xff, 0x71, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9d, 0xff, 0xf8, 0xa9, 0x57, + 0x0, 0x0, 0x0, 0x14, 0xa5, 0xe9, 0xff, 0x93, + + /* U+007C "|" */ + 0x8b, 0xff, 0x4f, 0x8b, 0xff, 0x4f, 0x8b, 0xff, + 0x4f, 0x8b, 0xff, 0x4f, 0x8b, 0xff, 0x4f, 0x8b, + 0xff, 0x4f, 0x8b, 0xff, 0x4f, 0x8b, 0xff, 0x4f, + 0x8b, 0xff, 0x4f, 0x8b, 0xff, 0x4f, 0x8b, 0xff, + 0x4f, 0x8b, 0xff, 0x4f, 0x8b, 0xff, 0x4f, 0x8b, + 0xff, 0x4f, 0x8b, 0xff, 0x4f, 0x8b, 0xff, 0x4f, + 0x8b, 0xff, 0x4f, 0x8b, 0xff, 0x4f, 0x8b, 0xff, + 0x4f, 0x8b, 0xff, 0x4f, 0x8b, 0xff, 0x4f, 0x8b, + 0xff, 0x4f, 0x8b, 0xff, 0x4f, 0x8b, 0xff, 0x4f, + 0x8b, 0xff, 0x4f, 0x8b, 0xff, 0x4f, 0x8b, 0xff, + 0x4f, 0x8b, 0xff, 0x4f, + + /* U+007D "}" */ + 0xb, 0xff, 0xf8, 0xd4, 0x53, 0x0, 0x0, 0x0, + 0x7, 0x9b, 0xd1, 0xff, 0xfa, 0x20, 0x0, 0x0, + 0x0, 0x0, 0x4, 0xe5, 0xff, 0x6a, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb8, 0xff, 0x85, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb3, 0xff, 0x88, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xba, 0xff, 0x7c, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc0, 0xff, 0x71, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc7, 0xff, 0x65, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xcd, 0xff, 0x5a, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc5, 0xff, 0x63, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9b, 0xff, 0xcc, 0x13, 0x0, + 0x0, 0x0, 0x0, 0x22, 0xd9, 0xff, 0xf7, 0x87, + 0x0, 0x0, 0x0, 0xc, 0xa5, 0xff, 0xff, 0x9f, + 0x0, 0x0, 0x0, 0x88, 0xff, 0xe2, 0x40, 0x8, + 0x0, 0x0, 0x0, 0xc1, 0xff, 0x6e, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xcd, 0xff, 0x58, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc8, 0xff, 0x63, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc1, 0xff, 0x70, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xbb, 0xff, 0x7b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb4, 0xff, 0x86, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xb8, 0xff, 0x87, 0x0, 0x0, + 0x0, 0x0, 0x4, 0xe3, 0xff, 0x6b, 0x0, 0x0, + 0x7, 0x9b, 0xd0, 0xff, 0xfc, 0x26, 0x0, 0x0, + 0xb, 0xff, 0xf8, 0xd7, 0x5a, 0x0, 0x0, 0x0, + + /* U+007E "~" */ + 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x68, + 0xe7, 0xff, 0xe4, 0x76, 0x6, 0x0, 0x0, 0x4, + 0x47, 0x0, 0x0, 0x7d, 0xff, 0xff, 0xf6, 0xff, + 0xff, 0xd3, 0x53, 0x34, 0xaa, 0xff, 0x78, 0x2, + 0xb0, 0xf4, 0x41, 0x2, 0x43, 0xda, 0xff, 0xff, + 0xff, 0xff, 0xe7, 0x1f, 0x0, 0x0, 0x26, 0x0, + 0x0, 0x0, 0x9, 0x81, 0xd8, 0xf2, 0xac, 0x1d, + 0x0, + + /* U+5149 "光" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x4, 0x33, 0x33, 0xa, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, + 0x0, 0x0, 0x17, 0xff, 0xff, 0x33, 0x0, 0x0, + 0x0, 0x0, 0xa, 0x1e, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x50, 0xc9, 0xa4, 0x0, 0x0, + 0x0, 0x0, 0x17, 0xff, 0xff, 0x33, 0x0, 0x0, + 0x0, 0x0, 0x67, 0xfe, 0xc3, 0x1b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4d, 0xff, 0xfe, 0x35, 0x0, + 0x0, 0x0, 0x17, 0xff, 0xff, 0x33, 0x0, 0x0, + 0x0, 0x0, 0xd2, 0xff, 0xcd, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xc7, 0xff, 0xc3, 0x0, + 0x0, 0x0, 0x17, 0xff, 0xff, 0x33, 0x0, 0x0, + 0x0, 0x3e, 0xff, 0xff, 0x49, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x42, 0xff, 0xff, 0x3a, + 0x0, 0x0, 0x17, 0xff, 0xff, 0x33, 0x0, 0x0, + 0x0, 0xb6, 0xff, 0xc5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xd0, 0xff, 0xa0, + 0x0, 0x0, 0x17, 0xff, 0xff, 0x33, 0x0, 0x0, + 0x39, 0xff, 0xfd, 0x34, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x6a, 0xff, 0xed, + 0x7, 0x0, 0x17, 0xff, 0xff, 0x33, 0x0, 0x0, + 0xb9, 0xff, 0x9a, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x18, 0x6d, 0x11, + 0x0, 0x0, 0x17, 0xff, 0xff, 0x33, 0x0, 0x0, + 0xa, 0x5d, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x20, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x47, 0xff, 0xff, 0x5d, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x2f, 0x0, + 0x0, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xeb, 0x0, + 0x0, 0x98, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf9, + 0xff, 0xff, 0xf3, 0xf3, 0xf3, 0xf3, 0xff, 0xff, + 0xf7, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xe0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x83, + 0xff, 0xea, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa7, + 0xff, 0xc4, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcb, + 0xff, 0x9e, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xfb, + 0xff, 0x74, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x77, 0xff, + 0xfd, 0x1d, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xdd, 0xff, + 0xb6, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0x47, 0x0, 0x0, 0x0, 0x0, 0xb7, 0x67, 0xc, + 0x0, 0x0, 0x0, 0x0, 0x3, 0xa5, 0xff, 0xff, + 0x4e, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0x47, 0x0, 0x0, 0x0, 0x0, 0xe9, 0xff, 0x35, + 0x0, 0x0, 0x0, 0x1a, 0xb5, 0xff, 0xff, 0xa6, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0x48, 0x0, 0x0, 0x0, 0x7, 0xfd, 0xff, 0x1c, + 0x0, 0x28, 0x8a, 0xf4, 0xff, 0xff, 0xb3, 0x9, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf2, 0xff, + 0x8b, 0x26, 0x23, 0x23, 0x68, 0xff, 0xec, 0x1, + 0x5, 0xca, 0xff, 0xff, 0xf7, 0x7d, 0x4, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc9, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9d, 0x0, + 0x0, 0x22, 0xe6, 0x8e, 0x21, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36, 0xca, + 0xf3, 0xff, 0xff, 0xff, 0xf3, 0xb8, 0x14, 0x0, + 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + + /* U+5EA6 "度" */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3, 0x31, 0x32, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xbd, 0xff, 0xdc, 0x3, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x5a, 0xff, 0xff, 0x57, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd3, 0x0, + 0x0, 0x0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd3, 0x0, + 0x0, 0x0, 0x1f, 0xff, 0xff, 0x3c, 0x1b, 0x1b, + 0x1b, 0x91, 0x9b, 0x39, 0x1b, 0x1b, 0x1b, 0x1b, + 0x43, 0x9b, 0x91, 0x1b, 0x1b, 0x1b, 0x17, 0x0, + 0x0, 0x0, 0x1f, 0xff, 0xff, 0x23, 0x0, 0x0, + 0x0, 0xeb, 0xff, 0x3b, 0x0, 0x0, 0x0, 0x0, + 0x4f, 0xff, 0xeb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1f, 0xff, 0xff, 0x5e, 0xa3, 0xa3, + 0xa3, 0xf8, 0xff, 0xb9, 0xa3, 0xa3, 0xa3, 0xa3, + 0xc0, 0xff, 0xf8, 0xa3, 0xa3, 0xa3, 0x5c, 0x0, + 0x0, 0x0, 0x1f, 0xff, 0xff, 0x7f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0x0, + 0x0, 0x0, 0x21, 0xff, 0xff, 0x34, 0x2f, 0x2f, + 0x2f, 0xef, 0xff, 0x60, 0x2f, 0x2f, 0x2f, 0x2f, + 0x70, 0xff, 0xef, 0x2f, 0x2f, 0x2f, 0x1a, 0x0, + 0x0, 0x0, 0x26, 0xff, 0xff, 0x1c, 0x0, 0x0, + 0x0, 0xeb, 0xff, 0x3b, 0x0, 0x0, 0x0, 0x0, + 0x4f, 0xff, 0xeb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x2b, 0xff, 0xff, 0x15, 0x0, 0x0, + 0x0, 0xeb, 0xff, 0xbf, 0xab, 0xab, 0xab, 0xab, + 0xc6, 0xff, 0xeb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x33, 0xff, 0xff, 0xd, 0x0, 0x0, + 0x0, 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xeb, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x47, 0xff, 0xf8, 0x0, 0x0, 0x0, + 0x0, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x5c, 0xff, 0xe0, 0x12, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xae, 0x56, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x74, 0xff, 0xc8, 0x1b, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xbd, 0x2, 0x0, 0x0, + 0x0, 0x0, 0xa1, 0xff, 0xa6, 0x4, 0x2b, 0x33, + 0xe1, 0xff, 0xa2, 0x2c, 0x2b, 0x2b, 0x2b, 0x2b, + 0x31, 0xbf, 0xff, 0xe9, 0x18, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xd4, 0xff, 0x72, 0x0, 0x0, 0x0, + 0x53, 0xfc, 0xff, 0xa4, 0x15, 0x0, 0x0, 0x29, + 0xc5, 0xff, 0xf5, 0x43, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa, 0xfc, 0xff, 0x3e, 0x0, 0x0, 0x0, + 0x0, 0x52, 0xf1, 0xff, 0xf2, 0x82, 0xa4, 0xfd, + 0xff, 0xd4, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x5b, 0xff, 0xfa, 0xd, 0x0, 0x0, 0x0, + 0x0, 0x7, 0x5e, 0xfa, 0xff, 0xff, 0xff, 0xff, + 0xde, 0x2d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xc0, 0xff, 0xae, 0xb, 0x5a, 0x7a, 0x9a, + 0xc6, 0xf8, 0xff, 0xff, 0xff, 0xea, 0xfc, 0xff, + 0xff, 0xff, 0xe6, 0xac, 0x85, 0x65, 0x45, 0x5, + 0x17, 0xfa, 0xff, 0x4b, 0x2, 0xd9, 0xff, 0xff, + 0xff, 0xfd, 0xc8, 0x7d, 0x32, 0x1, 0x15, 0x63, + 0xb4, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xa9, 0x0, + 0x0, 0x1a, 0x89, 0x3, 0x0, 0x4c, 0x85, 0x5b, + 0x31, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x9, 0x33, 0x5e, 0x89, 0xb0, 0x16, 0x0, + + /* U+5F3A "强" */ + 0x0, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, + 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0xa7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf7, 0x0, 0x5e, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, + 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0x3b, 0x0, 0x0, + 0x0, 0xa7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf7, 0x0, 0x5f, 0xff, 0xf8, 0xe3, 0xe3, 0xe3, + 0xe3, 0xe3, 0xe3, 0xfd, 0xff, 0x3b, 0x0, 0x0, + 0x0, 0xd, 0x13, 0x13, 0x13, 0x13, 0x2d, 0xff, + 0xf7, 0x0, 0x5f, 0xff, 0xb7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xeb, 0xff, 0x3b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0xff, + 0xf7, 0x0, 0x5f, 0xff, 0xb7, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xeb, 0xff, 0x3b, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0xff, + 0xf7, 0x0, 0x5f, 0xff, 0xf2, 0xcf, 0xcf, 0xcf, + 0xcf, 0xcf, 0xcf, 0xfc, 0xff, 0x3b, 0x0, 0x0, + 0x0, 0x22, 0xe3, 0xe3, 0xe3, 0xe3, 0xe7, 0xff, + 0xf7, 0x0, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x3b, 0x0, 0x0, + 0x0, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf7, 0x0, 0x4, 0xb, 0xb, 0xb, 0x17, 0xff, + 0xff, 0x26, 0xb, 0xb, 0xb, 0x2, 0x0, 0x0, + 0x0, 0x55, 0xff, 0xce, 0x4b, 0x4b, 0x4b, 0x4b, + 0x49, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xff, + 0xff, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x6c, 0xff, 0xa1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xc3, 0xd7, 0xd7, 0xd7, 0xd9, 0xff, + 0xff, 0xdc, 0xd7, 0xd7, 0xd7, 0xc3, 0x0, 0x0, + 0x0, 0x87, 0xff, 0x85, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0x0, 0x0, + 0x0, 0xa8, 0xff, 0x80, 0x23, 0x23, 0x23, 0x23, + 0x22, 0x0, 0xe7, 0xff, 0x13, 0x0, 0xb, 0xff, + 0xff, 0x1b, 0x0, 0x2b, 0xff, 0xe7, 0x0, 0x0, + 0x0, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xed, 0x0, 0xe7, 0xff, 0x13, 0x0, 0xb, 0xff, + 0xff, 0x1b, 0x0, 0x2b, 0xff, 0xe7, 0x0, 0x0, + 0x0, 0xdb, 0xe7, 0xe7, 0xe7, 0xe7, 0xf0, 0xff, + 0xdb, 0x0, 0xe7, 0xff, 0x13, 0x0, 0xb, 0xff, + 0xff, 0x1b, 0x0, 0x2b, 0xff, 0xe7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x67, 0xff, + 0xcc, 0x0, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7b, 0xff, + 0xb9, 0x0, 0xc3, 0xd7, 0xd7, 0xd7, 0xd9, 0xff, + 0xff, 0xdc, 0xd7, 0xd7, 0xd7, 0xc3, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x91, 0xff, + 0xa3, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xff, + 0xff, 0x1b, 0x17, 0x95, 0xb6, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb0, 0xff, + 0x85, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xff, + 0xff, 0x1b, 0x7, 0xda, 0xff, 0x63, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdc, 0xff, + 0x67, 0x0, 0x0, 0x0, 0x0, 0xb, 0x23, 0xff, + 0xff, 0x57, 0x52, 0x9f, 0xff, 0xeb, 0x11, 0x0, + 0x0, 0x0, 0x12, 0xe, 0x6, 0x4d, 0xff, 0xff, + 0x33, 0xaa, 0xe1, 0xef, 0xfd, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7a, 0x0, + 0x0, 0x0, 0x66, 0xff, 0xff, 0xff, 0xff, 0xe2, + 0x4, 0xb1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, + 0xeb, 0xd9, 0xc7, 0xb6, 0xb7, 0xff, 0xe5, 0x5, + 0x0, 0x0, 0x1c, 0xfe, 0xff, 0xf8, 0xcc, 0x39, + 0x0, 0x47, 0x63, 0x4d, 0x37, 0x23, 0xf, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x5, 0xef, 0xc8, 0x1c, + 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, + + /* U+6E29 "温" */ + 0x0, 0x0, 0x6, 0x94, 0x3d, 0x0, 0x0, 0x0, + 0x0, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, + 0xb, 0xb, 0xb, 0xb, 0x8, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x8e, 0xff, 0xff, 0xc4, 0x3a, 0x0, + 0xf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xbf, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xf, 0x74, 0xe2, 0xff, 0xff, 0x7a, + 0xf, 0xff, 0xff, 0xdb, 0xd7, 0xd7, 0xd7, 0xd7, + 0xd7, 0xd7, 0xeb, 0xff, 0xbf, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x6, 0x71, 0xdd, 0x23, + 0xf, 0xff, 0xff, 0x13, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7b, 0xff, 0xbf, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, + 0xf, 0xff, 0xff, 0xae, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xd2, 0xff, 0xbf, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xbf, 0x0, 0x0, 0x0, + 0x0, 0x2f, 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xf, 0xff, 0xff, 0x2a, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x88, 0xff, 0xbf, 0x0, 0x0, 0x0, + 0xa, 0xd8, 0xff, 0xcf, 0x51, 0x0, 0x0, 0x0, + 0xf, 0xff, 0xff, 0x13, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7b, 0xff, 0xbf, 0x0, 0x0, 0x0, + 0xb, 0x7e, 0xf1, 0xff, 0xff, 0xbc, 0x1a, 0x0, + 0xf, 0xff, 0xff, 0xdb, 0xd7, 0xd7, 0xd7, 0xd7, + 0xd7, 0xd7, 0xeb, 0xff, 0xbf, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x12, 0x92, 0xfd, 0xe6, 0x10, 0x0, + 0xf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xbf, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3d, 0x47, 0x0, 0x0, + 0x0, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, + 0xb, 0xb, 0xb, 0xb, 0x8, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xa, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, + 0xb, 0xb, 0xb, 0xb, 0xb, 0x6, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x31, 0x0, 0x0, + 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x19, 0xf5, 0xa1, 0x5, + 0xdb, 0xff, 0xe9, 0xe3, 0xfd, 0xfa, 0xe3, 0xeb, + 0xff, 0xf1, 0xe3, 0xf4, 0xff, 0x8f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x96, 0xff, 0xd6, 0x2, + 0xdb, 0xff, 0x2f, 0x0, 0xeb, 0xcf, 0x0, 0x43, + 0xff, 0x7b, 0x0, 0x97, 0xff, 0x8f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x22, 0xfa, 0xff, 0x56, 0x0, + 0xdb, 0xff, 0x2f, 0x0, 0xeb, 0xcf, 0x0, 0x43, + 0xff, 0x7b, 0x0, 0x97, 0xff, 0x8f, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xa5, 0xff, 0xd3, 0x1, 0x0, + 0xdb, 0xff, 0x2f, 0x0, 0xeb, 0xcf, 0x0, 0x43, + 0xff, 0x7b, 0x0, 0x97, 0xff, 0x8f, 0x0, 0x0, + 0x0, 0x0, 0x39, 0xfe, 0xff, 0x53, 0x0, 0x0, + 0xdb, 0xff, 0x2f, 0x0, 0xeb, 0xcf, 0x0, 0x43, + 0xff, 0x7b, 0x0, 0x97, 0xff, 0x8f, 0x0, 0x0, + 0x0, 0x2, 0xcc, 0xff, 0xc5, 0x0, 0x0, 0x0, + 0xdb, 0xff, 0x2f, 0x0, 0xeb, 0xcf, 0x0, 0x43, + 0xff, 0x7b, 0x0, 0x97, 0xff, 0x8f, 0x0, 0x0, + 0x0, 0x6e, 0xff, 0xfe, 0x37, 0x0, 0xc0, 0xe7, + 0xfc, 0xff, 0xec, 0xe7, 0xfe, 0xfb, 0xe7, 0xee, + 0xff, 0xf3, 0xe7, 0xf6, 0xff, 0xf5, 0xe7, 0x12, + 0x0, 0x2e, 0xd5, 0xa5, 0x0, 0x0, 0xd3, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, + 0x0, 0x0, 0x8, 0x14, 0x0, 0x0, 0x10, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x1, + + /* U+6E7F "湿" */ + 0x0, 0x0, 0xa, 0x99, 0x3a, 0x0, 0x0, 0x0, + 0x4, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, + 0xb, 0xb, 0xb, 0xb, 0xb, 0x5, 0x0, 0x0, + 0x0, 0x0, 0x9a, 0xff, 0xff, 0xbe, 0x2e, 0x0, + 0x57, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x73, 0x0, 0x0, + 0x0, 0x0, 0x16, 0x84, 0xef, 0xff, 0xfb, 0x4b, + 0x57, 0xff, 0xf6, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, + 0xd3, 0xd3, 0xd3, 0xf6, 0xff, 0x73, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x11, 0x95, 0xc5, 0x9, + 0x57, 0xff, 0xc7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc7, 0xff, 0x73, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, + 0x57, 0xff, 0xea, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0xea, 0xff, 0x73, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x57, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x73, 0x0, 0x0, + 0x0, 0x5a, 0xb8, 0x3a, 0x0, 0x0, 0x0, 0x0, + 0x57, 0xff, 0xd0, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0xd0, 0xff, 0x73, 0x0, 0x0, + 0xe, 0xec, 0xff, 0xff, 0xb6, 0x22, 0x0, 0x0, + 0x57, 0xff, 0xc7, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xc7, 0xff, 0x73, 0x0, 0x0, + 0x0, 0x1e, 0x9f, 0xfd, 0xff, 0xf2, 0x20, 0x0, + 0x57, 0xff, 0xf6, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, + 0xd3, 0xd3, 0xd3, 0xf6, 0xff, 0x73, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x38, 0xdb, 0xa4, 0x0, 0x0, + 0x57, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x73, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x10, 0xd, 0x0, 0x0, + 0x5, 0xf, 0xf, 0x17, 0x53, 0x52, 0xf, 0xf, + 0x53, 0x53, 0x17, 0xf, 0xf, 0x7, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x0, 0x1b, 0xff, 0xfb, 0x0, 0x0, + 0xff, 0xff, 0x1b, 0x0, 0xe, 0x2, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x33, 0x46, 0x0, 0x37, + 0xc6, 0x9a, 0x0, 0x1b, 0xff, 0xfb, 0x0, 0x0, + 0xff, 0xff, 0x1b, 0x0, 0xa3, 0xea, 0x75, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xa7, 0xfe, 0x64, 0x1d, + 0xfb, 0xf8, 0x18, 0x1b, 0xff, 0xfb, 0x0, 0x0, + 0xff, 0xff, 0x1b, 0x7, 0xf1, 0xff, 0x51, 0x0, + 0x0, 0x0, 0x0, 0x19, 0xfa, 0xff, 0x48, 0x0, + 0xaa, 0xff, 0x85, 0x1b, 0xff, 0xfb, 0x0, 0x0, + 0xff, 0xff, 0x1b, 0x50, 0xff, 0xdb, 0x2, 0x0, + 0x0, 0x0, 0x0, 0x82, 0xff, 0xdf, 0x2, 0x0, + 0x41, 0xff, 0xde, 0x1c, 0xff, 0xfb, 0x0, 0x0, + 0xff, 0xff, 0x1b, 0xb4, 0xff, 0x68, 0x0, 0x0, + 0x0, 0x0, 0x8, 0xe9, 0xff, 0x78, 0x0, 0x0, + 0x5, 0xf2, 0xff, 0x47, 0xff, 0xfb, 0x0, 0x0, + 0xff, 0xff, 0x40, 0xfd, 0xe4, 0x7, 0x0, 0x0, + 0x0, 0x0, 0x6c, 0xff, 0xf8, 0x16, 0x0, 0x0, + 0x0, 0xb1, 0xc9, 0x51, 0xff, 0xfb, 0x0, 0x0, + 0xff, 0xff, 0x67, 0xde, 0x64, 0x0, 0x0, 0x0, + 0x0, 0x7, 0xe2, 0xff, 0x9a, 0x0, 0x0, 0x0, + 0x0, 0x9, 0x0, 0x1b, 0xff, 0xfb, 0x0, 0x0, + 0xff, 0xff, 0x1b, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x6f, 0xff, 0xfe, 0x27, 0x0, 0xa3, 0xe7, + 0xe7, 0xe7, 0xe7, 0xea, 0xff, 0xff, 0xe7, 0xe7, + 0xff, 0xff, 0xea, 0xe7, 0xe7, 0xe7, 0xe7, 0x12, + 0x0, 0x1d, 0xb5, 0xaf, 0x0, 0x0, 0xb3, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, + 0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0xe, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x1, + + /* U+7167 "照" */ + 0x0, 0x0, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, + 0xf, 0x2, 0x18, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x6, 0x0, + 0x0, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x2f, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x36, 0x0, 0x0, + 0xef, 0xff, 0xf6, 0xf3, 0xf3, 0xff, 0xff, 0x2f, + 0xc7, 0xdf, 0xdf, 0xe5, 0xff, 0xff, 0xe1, 0xdf, + 0xdf, 0xe1, 0xff, 0xff, 0x22, 0x0, 0x0, 0xef, + 0xff, 0x33, 0x0, 0x0, 0xeb, 0xff, 0x2f, 0x0, + 0x0, 0x0, 0x5e, 0xff, 0xe0, 0x0, 0x0, 0x0, + 0x18, 0xff, 0xff, 0xd, 0x0, 0x0, 0xef, 0xff, + 0x33, 0x0, 0x0, 0xeb, 0xff, 0x2f, 0x0, 0x0, + 0xb, 0xd5, 0xff, 0x8b, 0x0, 0x0, 0x0, 0x3a, + 0xff, 0xef, 0x0, 0x0, 0x0, 0xef, 0xff, 0x33, + 0x0, 0x0, 0xeb, 0xff, 0x2f, 0x0, 0x23, 0xc2, + 0xff, 0xe3, 0xf, 0x23, 0x33, 0x30, 0xa7, 0xff, + 0xc4, 0x0, 0x0, 0x0, 0xef, 0xff, 0xad, 0x97, + 0x97, 0xf7, 0xff, 0x6c, 0xae, 0xf8, 0xff, 0xda, + 0x2f, 0x0, 0x5a, 0xff, 0xff, 0xff, 0xff, 0x69, + 0x0, 0x0, 0x0, 0xef, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x48, 0xe5, 0xe2, 0x81, 0xd, 0x0, + 0x0, 0xc, 0x76, 0x77, 0x6c, 0x3b, 0x0, 0x0, + 0x0, 0x0, 0xef, 0xff, 0x6a, 0x43, 0x43, 0xf1, + 0xff, 0x2f, 0x37, 0xa6, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x57, 0x0, 0x0, + 0x0, 0xef, 0xff, 0x33, 0x0, 0x0, 0xeb, 0xff, + 0x2f, 0x2b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x87, 0x0, 0x0, 0x0, + 0xef, 0xff, 0x33, 0x0, 0x0, 0xeb, 0xff, 0x2f, + 0x2b, 0xff, 0xf9, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0xc9, 0xff, 0x87, 0x0, 0x0, 0x0, 0xef, + 0xff, 0x33, 0x0, 0x0, 0xeb, 0xff, 0x2f, 0x2b, + 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xbb, 0xff, 0x87, 0x0, 0x0, 0x0, 0xef, 0xff, + 0x70, 0x4b, 0x4b, 0xf1, 0xff, 0x2f, 0x2b, 0xff, + 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbb, + 0xff, 0x87, 0x0, 0x0, 0x0, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x2f, 0x2b, 0xff, 0xf9, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0xc7, 0xff, + 0x87, 0x0, 0x0, 0x0, 0xef, 0xff, 0xe9, 0xe3, + 0xe3, 0xe3, 0xe3, 0x2a, 0x2b, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, + 0x0, 0x0, 0x0, 0xef, 0xff, 0x33, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1e, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0x5f, 0x0, + 0x0, 0x0, 0xb, 0x24, 0x6d, 0x25, 0x0, 0x0, + 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x22, 0x7, + 0x0, 0x0, 0x2c, 0x91, 0x25, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x95, 0xff, 0xe5, 0x8, 0x18, 0xd8, + 0xf5, 0x8, 0x0, 0x0, 0xd9, 0xff, 0x57, 0x0, + 0x2, 0xd0, 0xff, 0xbe, 0x0, 0x0, 0x0, 0x0, + 0x23, 0xfa, 0xff, 0x6b, 0x0, 0x5, 0xfd, 0xff, + 0x30, 0x0, 0x0, 0x98, 0xff, 0xb3, 0x0, 0x0, + 0x3c, 0xff, 0xff, 0x62, 0x0, 0x0, 0x1, 0xb7, + 0xff, 0xe1, 0x6, 0x0, 0x0, 0xe1, 0xff, 0x59, + 0x0, 0x0, 0x46, 0xff, 0xfb, 0xd, 0x0, 0x0, + 0xa2, 0xff, 0xe8, 0xc, 0x0, 0x70, 0xff, 0xff, + 0x50, 0x0, 0x0, 0x0, 0xcd, 0xff, 0x72, 0x0, + 0x0, 0xa, 0xfb, 0xff, 0x4c, 0x0, 0x0, 0x20, + 0xfa, 0xff, 0x7f, 0x0, 0x98, 0xf9, 0xaa, 0x0, + 0x0, 0x0, 0x0, 0xbe, 0xf2, 0x6f, 0x0, 0x0, + 0x0, 0xc5, 0xc9, 0x50, 0x0, 0x0, 0x0, 0x9b, + 0xe5, 0x6e, 0x0, 0x0, 0x18, 0x13, 0x0, 0x0, + 0x0, 0x0, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0x6, + 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 = 86, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 0, .adv_w = 132, .box_w = 4, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 72, .adv_w = 199, .box_w = 9, .box_h = 8, .ofs_x = 2, .ofs_y = 12}, + {.bitmap_index = 144, .adv_w = 219, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 378, .adv_w = 219, .box_w = 12, .box_h = 24, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 666, .adv_w = 361, .box_w = 22, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1062, .adv_w = 271, .box_w = 17, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1368, .adv_w = 114, .box_w = 3, .box_h = 8, .ofs_x = 2, .ofs_y = 12}, + {.bitmap_index = 1392, .adv_w = 137, .box_w = 6, .box_h = 26, .ofs_x = 2, .ofs_y = -5}, + {.bitmap_index = 1548, .adv_w = 137, .box_w = 6, .box_h = 26, .ofs_x = 1, .ofs_y = -5}, + {.bitmap_index = 1704, .adv_w = 186, .box_w = 10, .box_h = 9, .ofs_x = 1, .ofs_y = 11}, + {.bitmap_index = 1794, .adv_w = 219, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = 3}, + {.bitmap_index = 1963, .adv_w = 114, .box_w = 5, .box_h = 9, .ofs_x = 1, .ofs_y = -5}, + {.bitmap_index = 2008, .adv_w = 137, .box_w = 7, .box_h = 3, .ofs_x = 1, .ofs_y = 6}, + {.bitmap_index = 2029, .adv_w = 114, .box_w = 5, .box_h = 5, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2054, .adv_w = 150, .box_w = 9, .box_h = 25, .ofs_x = 0, .ofs_y = -5}, + {.bitmap_index = 2279, .adv_w = 219, .box_w = 12, .box_h = 18, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 2495, .adv_w = 219, .box_w = 11, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 2693, .adv_w = 219, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2927, .adv_w = 219, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3161, .adv_w = 219, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3395, .adv_w = 219, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3629, .adv_w = 219, .box_w = 12, .box_h = 18, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3845, .adv_w = 219, .box_w = 12, .box_h = 18, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 4061, .adv_w = 219, .box_w = 12, .box_h = 18, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 4277, .adv_w = 219, .box_w = 12, .box_h = 18, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 4493, .adv_w = 114, .box_w = 5, .box_h = 14, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 4563, .adv_w = 114, .box_w = 5, .box_h = 19, .ofs_x = 1, .ofs_y = -5}, + {.bitmap_index = 4658, .adv_w = 219, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = 3}, + {.bitmap_index = 4827, .adv_w = 219, .box_w = 13, .box_h = 9, .ofs_x = 0, .ofs_y = 5}, + {.bitmap_index = 4944, .adv_w = 219, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = 3}, + {.bitmap_index = 5113, .adv_w = 189, .box_w = 10, .box_h = 18, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 5293, .adv_w = 373, .box_w = 21, .box_h = 23, .ofs_x = 1, .ofs_y = -5}, + {.bitmap_index = 5776, .adv_w = 239, .box_w = 15, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 6046, .adv_w = 256, .box_w = 13, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 6280, .adv_w = 248, .box_w = 14, .box_h = 18, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 6532, .adv_w = 268, .box_w = 14, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 6784, .adv_w = 230, .box_w = 12, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 7000, .adv_w = 217, .box_w = 11, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 7198, .adv_w = 269, .box_w = 15, .box_h = 18, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 7468, .adv_w = 285, .box_w = 14, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 7720, .adv_w = 119, .box_w = 4, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 7792, .adv_w = 211, .box_w = 11, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 7990, .adv_w = 255, .box_w = 14, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 8242, .adv_w = 214, .box_w = 11, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 8440, .adv_w = 319, .box_w = 16, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 8728, .adv_w = 282, .box_w = 14, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 8980, .adv_w = 290, .box_w = 16, .box_h = 18, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 9268, .adv_w = 249, .box_w = 13, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 9502, .adv_w = 290, .box_w = 17, .box_h = 24, .ofs_x = 1, .ofs_y = -6}, + {.bitmap_index = 9910, .adv_w = 252, .box_w = 14, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 10162, .adv_w = 233, .box_w = 13, .box_h = 18, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 10396, .adv_w = 235, .box_w = 14, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 10648, .adv_w = 281, .box_w = 14, .box_h = 18, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 10900, .adv_w = 228, .box_w = 16, .box_h = 18, .ofs_x = -1, .ofs_y = 0}, + {.bitmap_index = 11188, .adv_w = 343, .box_w = 21, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 11566, .adv_w = 229, .box_w = 14, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 11818, .adv_w = 212, .box_w = 15, .box_h = 18, .ofs_x = -1, .ofs_y = 0}, + {.bitmap_index = 12088, .adv_w = 233, .box_w = 13, .box_h = 18, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 12322, .adv_w = 137, .box_w = 6, .box_h = 24, .ofs_x = 2, .ofs_y = -4}, + {.bitmap_index = 12466, .adv_w = 150, .box_w = 10, .box_h = 25, .ofs_x = 0, .ofs_y = -5}, + {.bitmap_index = 12716, .adv_w = 137, .box_w = 7, .box_h = 24, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 12884, .adv_w = 219, .box_w = 12, .box_h = 11, .ofs_x = 1, .ofs_y = 8}, + {.bitmap_index = 13016, .adv_w = 216, .box_w = 14, .box_h = 2, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 13044, .adv_w = 236, .box_w = 7, .box_h = 5, .ofs_x = 3, .ofs_y = 16}, + {.bitmap_index = 13079, .adv_w = 221, .box_w = 11, .box_h = 14, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 13233, .adv_w = 242, .box_w = 12, .box_h = 20, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 13473, .adv_w = 199, .box_w = 11, .box_h = 14, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 13627, .adv_w = 242, .box_w = 13, .box_h = 20, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 13887, .adv_w = 217, .box_w = 12, .box_h = 14, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 14055, .adv_w = 133, .box_w = 10, .box_h = 20, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 14255, .adv_w = 222, .box_w = 13, .box_h = 20, .ofs_x = 1, .ofs_y = -6}, + {.bitmap_index = 14515, .adv_w = 238, .box_w = 12, .box_h = 20, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 14755, .adv_w = 111, .box_w = 5, .box_h = 20, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 14855, .adv_w = 111, .box_w = 8, .box_h = 26, .ofs_x = -2, .ofs_y = -6}, + {.bitmap_index = 15063, .adv_w = 221, .box_w = 12, .box_h = 20, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 15303, .adv_w = 114, .box_w = 5, .box_h = 20, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 15403, .adv_w = 362, .box_w = 19, .box_h = 14, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 15669, .adv_w = 240, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 15837, .adv_w = 236, .box_w = 13, .box_h = 14, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 16019, .adv_w = 242, .box_w = 12, .box_h = 20, .ofs_x = 2, .ofs_y = -6}, + {.bitmap_index = 16259, .adv_w = 242, .box_w = 13, .box_h = 20, .ofs_x = 1, .ofs_y = -6}, + {.bitmap_index = 16519, .adv_w = 157, .box_w = 8, .box_h = 14, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 16631, .adv_w = 184, .box_w = 11, .box_h = 14, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 16785, .adv_w = 152, .box_w = 10, .box_h = 18, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 16965, .adv_w = 238, .box_w = 12, .box_h = 14, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 17133, .adv_w = 209, .box_w = 13, .box_h = 14, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 17315, .adv_w = 318, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 17595, .adv_w = 202, .box_w = 13, .box_h = 14, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 17777, .adv_w = 209, .box_w = 13, .box_h = 20, .ofs_x = 0, .ofs_y = -6}, + {.bitmap_index = 18037, .adv_w = 189, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 18205, .adv_w = 137, .box_w = 8, .box_h = 24, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 18397, .adv_w = 108, .box_w = 3, .box_h = 28, .ofs_x = 2, .ofs_y = -7}, + {.bitmap_index = 18481, .adv_w = 137, .box_w = 8, .box_h = 24, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 18673, .adv_w = 219, .box_w = 13, .box_h = 5, .ofs_x = 0, .ofs_y = 7}, + {.bitmap_index = 18738, .adv_w = 384, .box_w = 24, .box_h = 24, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 19314, .adv_w = 384, .box_w = 24, .box_h = 23, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19866, .adv_w = 384, .box_w = 24, .box_h = 23, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 20418, .adv_w = 384, .box_w = 24, .box_h = 22, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20946, .adv_w = 384, .box_w = 24, .box_h = 22, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21474, .adv_w = 384, .box_w = 23, .box_h = 23, .ofs_x = 0, .ofs_y = -3} +}; + +/*--------------------- + * CHARACTER MAPPING + *--------------------*/ + +static const uint16_t unicode_list_1[] = { + 0x0, 0xd5d, 0xdf1, 0x1ce0, 0x1d36, 0x201e +}; + +/*Collect the unicode lists and glyph_id offsets*/ +static const lv_font_fmt_txt_cmap_t cmaps[] = +{ + { + .range_start = 32, .range_length = 95, .glyph_id_start = 1, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 20809, .range_length = 8223, .glyph_id_start = 96, + .unicode_list = unicode_list_1, .glyph_id_ofs_list = NULL, .list_length = 6, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + } +}; + +/*----------------- + * KERNING + *----------------*/ + +/*Map glyph_ids to kern left classes*/ +static const uint8_t kern_left_class_mapping[] = +{ + 0, 0, 0, 1, 0, 0, 0, 0, + 1, 2, 0, 0, 0, 3, 4, 3, + 5, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 6, 0, 0, 0, + 0, 0, 7, 8, 9, 10, 11, 12, + 13, 0, 0, 14, 15, 16, 0, 0, + 10, 17, 10, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 2, 27, 0, 0, + 0, 0, 28, 29, 30, 0, 31, 32, + 33, 34, 0, 0, 35, 36, 34, 34, + 29, 29, 37, 38, 39, 40, 37, 41, + 42, 43, 44, 45, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0 +}; + +/*Map glyph_ids to kern right classes*/ +static const uint8_t kern_right_class_mapping[] = +{ + 0, 0, 1, 2, 0, 0, 0, 0, + 2, 0, 3, 4, 0, 5, 6, 7, + 8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 9, 10, 0, 0, 0, + 11, 0, 12, 0, 13, 0, 0, 0, + 13, 0, 0, 14, 0, 0, 0, 0, + 13, 0, 13, 0, 15, 16, 17, 18, + 19, 20, 21, 22, 0, 23, 3, 0, + 0, 0, 24, 0, 25, 25, 25, 26, + 27, 0, 28, 29, 0, 0, 30, 30, + 25, 30, 25, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0 +}; + +/*Kern values between classes*/ +static const int8_t kern_class_values[] = +{ + 0, 0, 0, 0, -52, 0, -52, 0, + 0, 0, 0, -26, 0, -43, -4, 0, + 0, 0, 0, -4, 0, 0, 0, 0, + -11, 0, 0, 0, 0, 0, -8, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -8, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 35, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -46, 0, -64, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -43, -10, -30, -15, 0, + -43, 0, 0, 0, -5, 0, 0, 0, + 13, 0, 0, -22, 0, -18, -11, 0, + -8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -8, + -9, -22, 0, -8, -4, -13, -30, -8, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, -11, 0, -4, 0, -7, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -17, -4, -35, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -11, + -12, 0, -4, 8, 8, 0, 0, 2, + -8, 0, 0, 0, 0, 0, 0, 0, + 0, -18, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, -13, 0, 0, 0, 0, 0, + 0, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -26, 0, -39, + 0, 0, 0, 0, 0, 0, -13, -2, + -4, 0, 0, -26, -6, -7, 0, 0, + -7, -3, -17, 9, 0, -4, 0, 0, + 0, 0, 9, -7, -2, -4, -2, -2, + -4, 0, 0, 0, 0, -13, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -7, + -7, -11, 0, -3, -2, -2, -7, -2, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, -4, 0, -7, -4, -4, -7, 0, + 0, 0, 0, 0, 0, -13, 0, 0, + 0, 0, 0, 0, -13, -4, -11, -7, + -7, -2, -2, -2, -4, -4, 0, 0, + 0, 0, -8, 0, 0, 0, 0, -13, + -4, -7, -4, 0, -7, 0, 0, 0, + 0, -13, 0, 0, 0, -7, 0, 0, + 0, -4, 0, -19, 0, -11, 0, -4, + -2, -9, -8, -8, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, -7, 0, -4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, -4, 0, 0, 0, + 0, 0, 0, -11, 0, -4, 0, -15, + -4, 0, 0, 0, 0, 0, -34, 0, + -34, -27, 0, 0, 0, -17, -4, -59, + -10, 0, 0, 0, 0, -11, -2, -13, + 0, -15, -7, 0, -11, 0, 0, -8, + -10, -4, -7, -10, -8, -13, -8, -16, + 0, 0, 0, -11, 0, 0, 0, 0, + 0, 0, 0, -2, 0, 0, 0, -8, + 0, -7, -2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, -11, 0, -11, 0, 0, 0, + 0, 0, 0, -17, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -11, 0, -17, + 0, -16, 0, 0, 0, 0, -4, -4, + -10, 0, -5, -9, -7, -6, -4, 0, + -8, 0, 0, 0, -4, 0, 0, 0, + -4, 0, 0, -17, -6, -10, -8, -8, + -10, -7, 0, -45, 0, -68, 0, -22, + 0, 0, 0, 0, -17, 0, -13, 0, + -10, -52, -13, -33, -25, 0, -34, 0, + -35, 0, -5, -7, -2, 0, 0, 0, + 0, -10, -4, -18, -15, 0, -18, 0, + 0, 0, 0, 0, -51, -13, -51, -29, + 0, 0, 0, -22, 0, -63, -4, -9, + 0, 0, 0, -11, -4, -30, 0, -17, + -10, 0, -11, 0, 0, 0, -4, 0, + 0, 0, 0, -7, 0, -8, 0, 0, + 0, -4, 0, -13, 0, 0, 0, 0, + 0, -2, 0, -7, -6, -7, 0, 0, + 2, -2, -2, -4, 0, -2, -4, 0, + -2, 0, 0, 0, 0, 0, 0, 0, + 0, -4, 0, -4, 0, 0, 0, -7, + 0, 7, 0, 0, 0, 0, 0, 0, + 0, -7, -7, -8, 0, 0, 0, 0, + -7, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -11, 0, 0, 0, 0, + 0, -2, 0, 0, 0, 0, -47, -30, + -47, -36, -8, -8, 0, -17, -11, -54, + -15, 0, 0, 0, 0, -8, -7, -22, + 0, -30, -30, -7, -30, 0, 0, -20, + -25, -7, -20, -13, -13, -15, -13, -31, + 0, 0, 0, 0, -8, 0, -8, -12, + 0, 0, 0, -7, 0, -22, -4, 0, + 0, -2, 0, -4, -7, 0, 0, -2, + 0, 0, -4, 0, 0, 0, -2, 0, + 0, 0, 0, -4, 0, 0, 0, 0, + 0, 0, -30, -8, -30, -18, 0, 0, + 0, -7, -4, -30, -4, 0, -4, 4, + 0, 0, 0, -8, 0, -11, -7, 0, + -9, 0, 0, -8, -6, 0, -13, -4, + -4, -7, -4, -10, 0, 0, 0, 0, + -15, -4, -15, -10, 0, 0, 0, 0, + -2, -26, -2, 0, 0, 0, 0, 0, + 0, -2, 0, -7, 0, 0, -4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, -4, 0, -4, 0, -4, 0, -13, + 0, 0, 0, 0, 0, 0, -9, -2, + -7, -8, -4, 0, 0, 0, 0, 0, + 0, -4, -4, -8, 0, 0, 0, 0, + 0, -8, -4, -8, -7, -4, -8, -7, + 0, 0, 0, 0, -42, -30, -42, -26, + -12, -12, -4, -7, -7, -43, -7, -7, + -4, 0, 0, 0, 0, -11, 0, -30, + -20, 0, -26, 0, 0, -17, -20, -15, + -15, -7, -11, -15, -7, -22, 0, 0, + 0, 0, 0, -13, 0, 0, 0, 0, + 0, -2, -8, -13, -13, 0, -4, -2, + -2, 0, -7, -7, 0, -7, -8, -8, + -6, 0, 0, 0, 0, -7, -9, -7, + -7, -11, -7, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -35, -11, -22, -11, 0, + -30, 0, 0, 0, 0, 0, 13, 0, + 30, 0, 0, 0, 0, -8, -4, 0, + 4, 0, 0, 0, 0, -22, 0, 0, + 0, 0, 0, 0, -8, 0, 0, 0, + 0, -11, 0, -8, -2, 0, -11, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, -7, 0, 0, 0, 0, 0, 0, + 0, -11, 0, -8, -4, 2, -4, 0, + 0, 0, -8, 0, 0, 0, 0, -25, + 0, -8, 0, -2, -21, 0, -13, -6, + 0, -1, 0, 0, 0, 0, -1, -9, + 0, -2, -2, -9, -2, -3, 0, 0, + 0, 0, 0, -10, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -8, 0, -7, + 0, 0, -11, 0, 0, -4, -10, 0, + -4, 0, 0, 0, 0, -4, 0, 2, + 2, 2, 2, 0, 0, 0, 0, -13, + 0, 4, 0, 0, 0, 0, -4, 0, + 0, -8, -8, -11, 0, -8, -4, 0, + -13, 0, -10, -6, 0, -1, -4, 0, + 0, 0, 0, -4, 0, 0, 0, -4, + 0, 0, 5, 17, 20, 0, -23, -7, + -23, -5, 0, 0, 11, 0, 0, 0, + 0, 20, 0, 28, 20, 13, 24, 0, + 23, -8, -4, 0, -6, 0, -4, 0, + -2, 0, 0, 4, 0, -2, 0, -7, + 0, 0, 5, -13, 0, 0, 0, 18, + 0, 0, -17, 0, 0, 0, 0, -13, + 0, 0, 0, 0, -7, 0, 0, -7, + -7, 0, 0, 0, 17, 0, 0, 0, + 0, -2, -2, 0, 5, -7, 0, 0, + 0, -13, 0, 0, 0, 0, 0, 0, + -4, 0, 0, 0, 0, -11, 0, -4, + 0, 0, -8, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -9, + 4, -25, 4, 0, 4, 4, -8, 0, + 0, 0, 0, -17, 0, 0, 0, 0, + -6, 0, 0, -4, -9, 0, -4, 0, + -4, 0, 0, -11, -7, 0, 0, -4, + 0, -4, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -9, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -8, + 0, -7, 0, 0, -13, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, -29, -11, -29, -13, 8, 8, + 0, -7, 0, -26, 0, 0, 0, 0, + 0, 0, 0, -4, 4, -11, -4, 0, + -4, 0, 0, 0, -2, 0, 0, 8, + 7, 0, 8, -2, 0, 0, 0, -18, + 0, 4, 0, 0, 0, 0, -7, 0, + 0, 0, 0, -11, 0, -4, 0, 0, + -8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -8, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -13, + 0, 2, 5, 5, -13, 0, 0, 0, + 0, -7, 0, 0, 0, 0, -2, 0, + 0, -9, -7, 0, -4, 0, 0, 0, + -4, -8, 0, 0, 0, -7, 0, 0, + 0, 0, 0, -5, -18, -4, -18, -8, + 0, 0, 0, -5, 0, -17, 0, -8, + 0, -4, 0, 0, -7, -4, 0, -8, + -2, 0, 0, 0, -4, 0, 0, 0, + 0, 0, 0, 0, 0, -11, 0, 0, + 0, -5, -23, 0, -23, -3, 0, 0, + 0, -2, 0, -13, 0, -11, 0, -4, + 0, -7, -11, 0, 0, -4, -2, 0, + 0, 0, -4, 0, 0, 0, 0, 0, + 0, 0, 0, -9, -7, 0, 0, -10, + 4, -7, -4, 0, 0, 4, 0, 0, + -4, 0, -2, -13, 0, -7, 0, -4, + -15, 0, 0, -4, -9, 0, 0, 0, + 0, 0, 0, -11, 0, 0, 0, 0, + -2, 0, 0, 0, 0, 0, -18, 0, + -18, -5, 0, 0, 0, 0, 0, -17, + 0, -8, 0, -2, 0, -2, -4, 0, + 0, -8, -2, 0, 0, 0, -4, 0, + 0, 0, 0, 0, 0, -7, 0, -11, + 0, 0, 0, 0, 0, -8, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -11, + 0, 0, 0, 0, -11, 0, 0, -9, + -4, 0, -2, 0, 0, 0, 0, 0, + -4, -2, 0, 0, -2, 0 +}; + +/*Collect the kern class' data in one place*/ +static const lv_font_fmt_txt_kern_classes_t kern_classes = +{ + .class_pair_values = kern_class_values, + .left_class_mapping = kern_left_class_mapping, + .right_class_mapping = kern_right_class_mapping, + .left_class_cnt = 45, + .right_class_cnt = 38, +}; + +/*-------------------- + * 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 = &kern_classes, + .kern_scale = 16, + .cmap_num = 2, + .bpp = 8, + .kern_classes = 1, + .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_24 = { +#else +lv_font_t ui_font_24 = { +#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 = 28, /*The maximum line height required by the font*/ + .base_line = 7, /*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 = -3, + .underline_thickness = 1, +#endif + .dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */ +#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9 + .fallback = NULL, +#endif + .user_data = NULL, +}; + +#endif /*#if UI_FONT_24*/ \ No newline at end of file diff --git a/components/ui/vars.c b/components/ui/vars.c new file mode 100644 index 0000000..dd3af6d --- /dev/null +++ b/components/ui/vars.c @@ -0,0 +1,36 @@ +#include +#include "vars.h" + +char air_temperature[100] = { 0 }; + +const char *get_var_air_temperature() { + return air_temperature; +} + +void set_var_air_temperature(const char *value) { + strncpy(air_temperature, value, sizeof(air_temperature) / sizeof(char)); + air_temperature[sizeof(air_temperature) / sizeof(char) - 1] = 0; +} + +char air_humidity[100] = { 0 }; + +const char *get_var_air_humidity() { + return air_humidity; +} + +void set_var_air_humidity(const char *value) { + strncpy(air_humidity, value, sizeof(air_humidity) / sizeof(char)); + air_humidity[sizeof(air_humidity) / sizeof(char) - 1] = 0; +} + +char light_intensity[100] = { 0 }; + +const char *get_var_light_intensity() { + return light_intensity; +} + +void set_var_light_intensity(const char *value) { + strncpy(light_intensity, value, sizeof(light_intensity) / sizeof(char)); + light_intensity[sizeof(light_intensity) / sizeof(char) - 1] = 0; +} + diff --git a/components/ui/vars.h b/components/ui/vars.h new file mode 100644 index 0000000..e38d453 --- /dev/null +++ b/components/ui/vars.h @@ -0,0 +1,34 @@ +#ifndef EEZ_LVGL_UI_VARS_H +#define EEZ_LVGL_UI_VARS_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// enum declarations + +// Flow global variables + +enum FlowGlobalVariables { + FLOW_GLOBAL_VARIABLE_AIR_TEMPERATURE = 0, + FLOW_GLOBAL_VARIABLE_AIR_HUMIDITY = 1, + FLOW_GLOBAL_VARIABLE_LIGHT_INTENSITY = 2 +}; + +// Native global variables + +extern const char *get_var_air_temperature(); +extern void set_var_air_temperature(const char *value); +extern const char *get_var_air_humidity(); +extern void set_var_air_humidity(const char *value); +extern const char *get_var_light_intensity(); +extern void set_var_light_intensity(const char *value); + +#ifdef __cplusplus +} +#endif + +#endif /*EEZ_LVGL_UI_VARS_H*/ \ No newline at end of file diff --git a/components/wifi-connect/BLOG.md b/components/wifi-connect/BLOG.md new file mode 100644 index 0000000..faaaa1d --- /dev/null +++ b/components/wifi-connect/BLOG.md @@ -0,0 +1,365 @@ +# ESP32 配网组件设计实践:聚焦功能与实现,而不是项目绑定 + +很多 ESP32 设备在开发阶段都把“配网”当成一个小功能,但真正落地后会发现: + +- 用户第一次接入要顺畅 +- 失败后要能恢复 +- 日志要便于现场排障 + +这篇文章只讲配网组件本身,聚焦能力设计和实现思路,不依赖具体业务项目。 + +--- + +## 一、目标能力定义 + +一个可落地的配网组件,建议至少包含以下能力: + +- 按键触发配网 +- 常驻配网模式(可选) +- SoftAP + Web Portal 配网 +- DNS 劫持与常见 Captive Portal 兼容 +- 凭据持久化(NVS)与重启自动重连 +- 清除历史配置(API + Web) +- 状态机与可读日志 + +这几项组合起来,才能覆盖“首次成功 + 失败恢复 + 现场维护”三个关键场景。 + +图示(整体功能目标关联): + +```mermaid +flowchart TD + A[按键任务/业务触发] --> B[进入 provisioning] + B --> C[启动 SoftAP] + B --> D[启动 HTTP Server] + B --> E[启动 DNS Hijack] + D --> F[/api/scan] + D --> G[/api/connect] + D --> H[/api/status] + D --> I[/api/clear] + G --> J[设置 STA 参数] + J --> K[发起 esp_wifi_connect] + K --> L{Wi-Fi/IP 事件} + L -->|成功| M[状态=connected] + L -->|失败| N[状态=failed] + I --> O[清除 NVS 凭据] + O --> P[清空运行态缓存] + P --> B +``` + +--- + +## 二、组件架构(通用) + +```text +[按键任务] --> [进入配网] + | + +--> SoftAP + HTTP Server + DNS Hijack + +[Web] -- /api/scan --> Wi-Fi 扫描 +[Web] -- /api/connect --> 设置 STA 并发起连接 +[Web] -- /api/status --> 轮询状态 +[Web] -- /api/clear --> 清除已保存配置 + +[Wi-Fi/IP 事件] --> 更新状态机 + 打印日志 + 保存凭据 +``` + +推荐状态机: + +- `idle` +- `provisioning` +- `connecting` +- `connected` +- `failed` +- `timeout` + +图示(状态机): + +```mermaid +stateDiagram-v2 + [*] --> idle + idle --> provisioning: wifi_connect_start() + provisioning --> connecting: POST /api/connect + connecting --> connected: GOT_IP + connecting --> failed: AUTH_FAIL / NO_AP / ... + connecting --> timeout: connect timeout + failed --> provisioning: POST /api/clear + timeout --> provisioning: POST /api/clear + connected --> provisioning: 常驻配网继续开放入口(可选) + connected --> idle: stop provisioning(按策略) +``` + +上图展示了组件的主要数据流与恢复路径。 + +--- + +## 三、对外 API 设计建议 + +推荐保持“少而稳”的接口: + +```c +esp_err_t wifi_connect_init(void); +esp_err_t wifi_connect_start(void); +esp_err_t wifi_connect_stop(void); +wifi_connect_status_t wifi_connect_get_status(void); +esp_err_t wifi_connect_get_config(wifi_connect_config_t *config); +esp_err_t wifi_connect_clear_config(void); +``` + +设计原则: + +- `init` 只做初始化和基础恢复 +- `start/stop` 控制配网生命周期 +- `get_status` 作为 UI/接口层统一读取入口 +- `clear_config` 提供失败恢复通道 + +可以展示一段“典型调用顺序”代码: + +```c +ESP_ERROR_CHECK(wifi_connect_init()); + +// 按键触发或业务触发时 +ESP_ERROR_CHECK(wifi_connect_start()); + +// UI 侧轮询状态 +wifi_connect_status_t st = wifi_connect_get_status(); + +// 需要恢复出厂配网时 +ESP_ERROR_CHECK(wifi_connect_clear_config()); +``` + +图示(API 生命周期时序): + +```mermaid +sequenceDiagram + participant App as 应用层 + participant WC as wifi-connect + participant WiFi as esp_wifi + participant Web as 配网页面 + + App->>WC: wifi_connect_init() + WC-->>App: ESP_OK + + App->>WC: wifi_connect_start() + WC->>WiFi: 开启 APSTA / 事件注册 + WC-->>App: ESP_OK + + Web->>WC: POST /api/connect(ssid,pass) + WC->>WiFi: esp_wifi_disconnect() + WC->>WiFi: esp_wifi_set_config() + WC->>WiFi: esp_wifi_connect() + + WiFi-->>WC: WIFI_EVENT / IP_EVENT + alt 获取到 IP + WC-->>Web: status=connected + else 连接失败或超时 + WC-->>Web: status=failed|timeout + end + + Web->>WC: POST /api/clear + WC-->>Web: status=provisioning +``` + +上图对应完整 API 生命周期(init -> start -> connecting -> connected/failed -> clear)。 + +--- + +## 四、关键实现点 + +### 1) Web 配网页面保持轻量 + +不一定要引入前端框架。对于资源受限设备,内嵌简洁 HTML/JS 往往更稳定。 + +建议页面只保留核心动作: + +- 扫描网络 +- 提交连接 +- 查看状态 +- 清除配置 + +### 2) Captive Portal 兼容是体验关键 + +仅提供首页 URL 通常不够。建议额外处理: + +- 常见探测路径(如 `generate_204` 等) +- 未知路径统一 302 到配网页 + +这样手机系统弹门户页面成功率会明显提高。 + +示例代码(伪代码): + +```c +static esp_err_t captive_redirect_handler(httpd_req_t *req) +{ + httpd_resp_set_status(req, "302 Found"); + httpd_resp_set_hdr(req, "Location", "http://192.168.4.1/"); + return httpd_resp_send(req, NULL, 0); +} + +// 注册常见探测路径 +httpd_register_uri_handler(server, &uri_generate_204); +httpd_register_uri_handler(server, &uri_hotspot_detect); +httpd_register_uri_handler(server, &uri_ncsi); +``` + +可直接使用以下流程图(对应 Captive Portal 重定向路径): + +```mermaid +flowchart LR + A[手机连接设备 AP] --> B[访问任意域名] + B --> C[DNS Hijack 返回 192.168.4.1] + C --> D[HTTP 探测路径请求
/generate_204 等] + D --> E[302 Location: http://192.168.4.1/] + E --> F[打开配网页面] + F --> G[扫描 / 连接 / 清除配置] +``` + +### 3) 连接前主动断开旧连接 + +这是一个高频坑:设备已有 STA 连接时,直接连接新 AP 可能导致超时或异常状态。 + +建议在 `esp_wifi_set_config + esp_wifi_connect` 前先执行 `esp_wifi_disconnect()`,并对异常返回做日志记录。 + +示例代码: + +```c +esp_err_t err = esp_wifi_disconnect(); +if (err != ESP_OK && err != ESP_ERR_WIFI_NOT_CONNECT) { + ESP_LOGW(TAG, "disconnect before reconnect failed: %s", esp_err_to_name(err)); +} + +ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &cfg)); +ESP_ERROR_CHECK(esp_wifi_connect()); +``` + +### 4) “清除配置”必须打通全链路 + +建议同时提供: + +- SDK API:`wifi_connect_clear_config()` +- HTTP API:`POST /api/clear` +- 页面按钮:`清除已保存` + +这样现场人员无需改固件即可恢复设备。 + +建议把清除动作写成“存储层 + 运行态”两段: + +```c +esp_err_t wifi_connect_clear_config(void) +{ + // 1) 清 NVS 凭据 + ESP_RETURN_ON_ERROR(nvs_erase_key(nvs, "ssid"), TAG, "erase ssid failed"); + ESP_RETURN_ON_ERROR(nvs_erase_key(nvs, "pass"), TAG, "erase pass failed"); + ESP_RETURN_ON_ERROR(nvs_commit(nvs), TAG, "nvs commit failed"); + + // 2) 清内存态并回到 provisioning + memset(&s_ctx.pending_cfg, 0, sizeof(s_ctx.pending_cfg)); + s_ctx.status = WIFI_CONNECT_STATUS_PROVISIONING; + return ESP_OK; +} +``` + +可直接使用以下流程图(对应 clear 后状态回到 provisioning): + +```mermaid +flowchart TD + A[开始清除配置] --> B[擦除 NVS:ssid] + B --> C[擦除 NVS:pass] + C --> D[nvs_commit] + D --> E[清空 pending 配置缓存] + E --> F[清空错误原因/中间状态] + F --> G[状态切回 provisioning] + G --> H[前端轮询显示 可重新配网] +``` + +--- + +## 五、日志策略(非常重要) + +建议日志遵循“状态 + 原因”格式,例如: + +- `【状态】配网已启动:热点已开启,SSID=...` +- `【状态】开始连接路由器:目标网络=...` +- `【状态】联网成功:获取 IP=...` +- `【状态】连接失败:原因=...` + +这样做的收益是: + +- 开发调试快 +- 测试可直接定位阶段 +- 现场人员无需先理解底层驱动日志 + +可展示一个统一日志函数风格: + +```c +static void log_state_i(const char *title, const char *detail) +{ + ESP_LOGI(TAG, "【状态】%s:%s", title, detail ? detail : "-"); +} +``` + +如需补充非 Mermaid 图,建议仅放一张关键串口日志截图(启动配网、连接中、成功/失败重试)。 + +--- + +## 六、常见问题与排障思路 + +### 问题 1:手机连上 AP 但页面不弹 + +排查: + +- 手动访问 `http://192.168.4.1` +- 检查 DNS 劫持和门户探测路径是否启用 +- 检查 HTTP 服务是否启动成功 + +### 问题 2:提交密码后长时间连接失败 + +排查: + +- 是否先断开旧 STA +- 是否正确处理了连接超时和重试 +- 失败原因是否上报到状态机和前端 + +### 问题 3:配网失败后无法恢复 + +排查: + +- NVS 清除逻辑是否真正执行 +- 内存态缓存是否同时清空 +- 配网状态是否回到 `provisioning` + +--- + +## 七、测试清单(可复用) + +建议每次迭代最少覆盖: + +1. 首次配网成功 +2. 密码错误后重试成功 +3. 连接旧网状态下切换新网成功 +4. 清除配置后重新配网成功 +5. 重启后自动重连 +6. 空闲超时与手动停止路径可用 + +这 6 条通过后,组件稳定性通常会显著提升。 + +--- + +## 八、可继续增强的方向 + +- 配网页面安全增强(鉴权/会话) +- 多语言提示 +- 更细粒度错误码映射 +- BLE 辅助配网 +- 命令行/远程维护接口联动 + +--- + +## 结语 + +配网组件的核心价值,不是“让设备连上一次网”,而是: + +- 功能完整 +- 异常可恢复 +- 排障可落地 + +当这三件事做好后,它才是一个能复用、能维护、能上线的基础能力组件。 \ No newline at end of file diff --git a/components/wifi-connect/CMakeLists.txt b/components/wifi-connect/CMakeLists.txt new file mode 100644 index 0000000..0437836 --- /dev/null +++ b/components/wifi-connect/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "wifi-connect.c" + INCLUDE_DIRS "include" + REQUIRES esp_wifi esp_timer esp_event esp_netif nvs_flash esp_http_server lwip driver +) diff --git a/components/wifi-connect/Kconfig.projbuild b/components/wifi-connect/Kconfig.projbuild new file mode 100644 index 0000000..e4780ca --- /dev/null +++ b/components/wifi-connect/Kconfig.projbuild @@ -0,0 +1,87 @@ +menu "WiFi Connect" + +choice WIFI_CONNECT_PROVISION_MODE + prompt "Provisioning mode" + default WIFI_CONNECT_PROVISION_MODE_BUTTON + help + Select how provisioning mode is entered. + +config WIFI_CONNECT_PROVISION_MODE_BUTTON + bool "Button triggered (default)" + help + Enter provisioning only when button long-press is detected. + +config WIFI_CONNECT_PROVISION_MODE_ALWAYS_ON + bool "Always-on provisioning" + help + Start provisioning automatically on boot and keep it running. + Provisioning will not auto-stop after idle timeout or STA connect. + +endchoice + +config WIFI_CONNECT_BUTTON_GPIO + int "Provision button GPIO" + range 0 21 + default 9 + help + GPIO used for entering provisioning mode. + +config WIFI_CONNECT_BUTTON_ACTIVE_LEVEL + int "Provision button active level" + range 0 1 + default 0 + help + Active level of provisioning button. 0 means active-low. + +config WIFI_CONNECT_DEBOUNCE_MS + int "Button debounce time (ms)" + range 10 200 + default 40 + +config WIFI_CONNECT_LONG_PRESS_MS + int "Button long press trigger time (ms)" + range 500 10000 + default 2000 + +config WIFI_CONNECT_BUTTON_STARTUP_GUARD_MS + int "Button startup guard time (ms)" + range 0 30000 + default 5000 + help + Ignore button long-press detection during startup guard window. + Useful when button pin is shared with other peripherals. + +config WIFI_CONNECT_BUTTON_RELEASE_ARM_MS + int "Button release arm time (ms)" + range 20 2000 + default 200 + help + Require button to stay in released level for this duration + before long-press detection is armed. + +config WIFI_CONNECT_CONNECT_TIMEOUT_SEC + int "Wi-Fi connect timeout (sec)" + range 5 180 + default 30 + +config WIFI_CONNECT_IDLE_TIMEOUT_SEC + int "Provisioning idle timeout (sec)" + range 30 1800 + default 300 + +config WIFI_CONNECT_MAX_SCAN_RESULTS + int "Maximum scan results" + range 5 50 + default 20 + +config WIFI_CONNECT_AP_MAX_CONNECTIONS + int "SoftAP max connections" + range 1 10 + default 4 + +config WIFI_CONNECT_AP_GRACEFUL_STOP_SEC + int "AP keep-alive after STA connected (sec)" + range 0 120 + default 15 + +endmenu diff --git a/components/wifi-connect/QUICK_POSTER.md b/components/wifi-connect/QUICK_POSTER.md new file mode 100644 index 0000000..af3d6c7 --- /dev/null +++ b/components/wifi-connect/QUICK_POSTER.md @@ -0,0 +1,28 @@ +# BotanicalBuddy 配网四步卡(张贴版) + +## 第 1 步:长按按键 +长按设备配网键约 2 秒,进入配网模式。 + +## 第 2 步:连接热点 +手机连接 Wi-Fi:ESP32-xxxxxx + +## 第 3 步:打开页面 +浏览器访问:http://192.168.4.1 + +## 第 4 步:提交路由器信息 +选择家里 Wi-Fi,输入密码,点击“连接”。 + +--- + +## 失败时先做这三件事 +1. 确认访问的是 http://192.168.4.1(不是 https) +2. 确认输入的 Wi-Fi 密码正确 +3. 路由器使用 2.4G,设备离路由器更近一点再试 + +--- + +## 二维码占位(贴纸用) +建议制作一个二维码,内容指向: +http://192.168.4.1 + +打印时把二维码贴在本段下方空白区域即可。 diff --git a/components/wifi-connect/README.md b/components/wifi-connect/README.md new file mode 100644 index 0000000..865c19c --- /dev/null +++ b/components/wifi-connect/README.md @@ -0,0 +1,165 @@ +# wifi-connect 组件说明 + +`wifi-connect` 是一个基于 ESP-IDF 的 Wi-Fi 配网组件,支持: + +- 长按按键进入配网模式 +- 启动 SoftAP + Captive Portal(网页配网) +- 手机连接热点后,通过网页扫描并选择路由器 +- 保存 Wi-Fi 凭据到 NVS +- 下次开机自动重连 +- 支持两种配网模式:按键触发 / 常驻配网 + +面向最终用户的一页版操作说明见:`USER_GUIDE.md` +现场打印张贴版(四步卡)见:`QUICK_POSTER.md` + +--- + +## 目录结构 + +- `wifi-connect.c`:组件主实现(按键、APSTA、HTTP、DNS、状态机) +- `include/wifi-connect.h`:对外 API +- `Kconfig.projbuild`:组件配置项 +- `CMakeLists.txt`:组件构建依赖 + +--- + +## 对外 API + +头文件:`include/wifi-connect.h` + +- `esp_err_t wifi_connect_init(void);` + - 初始化组件(NVS、Wi-Fi、事件、按键任务等) + - 尝试自动连接已保存网络 + +- `esp_err_t wifi_connect_start(void);` + - 启动配网(APSTA + HTTP + DNS) + +- `esp_err_t wifi_connect_stop(void);` + - 停止配网(关闭热点与相关服务) + +- `wifi_connect_status_t wifi_connect_get_status(void);` + - 获取当前状态:`idle / provisioning / connecting / connected / failed / timeout` + +- `esp_err_t wifi_connect_get_config(wifi_connect_config_t *config);` + - 读取已保存的 Wi-Fi 凭据 + +- `esp_err_t wifi_connect_clear_config(void);` + - 清除已保存的 Wi-Fi 凭据(SSID/密码) + +--- + +## 快速使用 + +在 `main/main.c`: + +```c +#include "esp_check.h" +#include "wifi-connect.h" + +void app_main(void) +{ + ESP_ERROR_CHECK(wifi_connect_init()); +} +``` + +运行后: + +1. 选择配网模式: + - 按键触发模式:长按配置按键进入配网 + - 常驻配网模式:上电自动进入配网 +2. 手机连接 `ESP32-xxxxxx` 热点 +3. 打开 `http://192.168.4.1` +4. 选择 Wi-Fi 并输入密码提交 +5. 配网行为: + - 按键触发模式:连接成功后按配置自动关闭热点 + - 常驻配网模式:配网热点保持开启,不自动关闭 + +如需清空历史凭据,可在配网页面点击“清除已保存”。 + +--- + +## Kconfig 配置项 + +在 `idf.py menuconfig` 中:`WiFi Connect` 菜单 + +- `Provisioning mode`:配网模式(二选一) + - `Button triggered`:按键触发配网(默认) + - `Always-on provisioning`:常驻配网(上电自动进入且不自动关闭) + +- `WIFI_CONNECT_BUTTON_GPIO`:进入配网的按键 GPIO +- `WIFI_CONNECT_BUTTON_ACTIVE_LEVEL`:按键有效电平 +- `WIFI_CONNECT_DEBOUNCE_MS`:按键去抖时间 +- `WIFI_CONNECT_LONG_PRESS_MS`:长按触发时长 +- `WIFI_CONNECT_BUTTON_STARTUP_GUARD_MS`:上电保护窗口(该时间内忽略长按检测) +- `WIFI_CONNECT_BUTTON_RELEASE_ARM_MS`:松手解锁时间(先稳定松手再允许长按触发) +- `WIFI_CONNECT_CONNECT_TIMEOUT_SEC`:连接路由器超时 +- `WIFI_CONNECT_IDLE_TIMEOUT_SEC`:配网页面空闲超时 +- `WIFI_CONNECT_MAX_SCAN_RESULTS`:扫描网络最大数量 +- `WIFI_CONNECT_AP_MAX_CONNECTIONS`:SoftAP 最大连接数 +- `WIFI_CONNECT_AP_GRACEFUL_STOP_SEC`:联网成功后 AP 延迟关闭秒数 + +--- + +## 日志与状态说明(中文) + +组件会输出统一中文状态日志,例如: + +- `【状态】wifi-connect 初始化完成` +- `【状态】检测到按键长按:开始进入配网模式` +- `【状态】配网已启动:配网热点已开启,SSID=...` +- `【状态】开始连接路由器:收到配网请求,目标网络=...` +- `【状态】联网成功:已连接 ...,获取 IP=...` +- `【状态】配网已停止:热点已关闭,设备继续以 STA 模式运行` + +说明:ESP-IDF 驱动层(如 `wifi:`、`esp_netif_lwip:`)仍会输出英文日志,这是框架默认行为。 + +--- + +## 常见问题 + +### 1) 手机连上热点但不自动弹出页面 + +- 手动访问:`http://192.168.4.1` +- 确认手机没有强制使用 HTTPS +- 查看串口是否有 `配网已启动`、`DNS 劫持服务已启动` 日志 + +### 2) 提交后连接失败 + +- 检查密码是否正确 +- 查看日志中的失败原因码(`连接失败,原因=...`) +- 检查路由器是否禁用了新设备接入 +- 若曾保存过旧配置,可先在页面点击“清除已保存”后再重试 + +### 4) 按键未按下却误触发配网 + +- 常见原因是按键引脚与 LCD/外设复用,初始化期间电平抖动被误判为长按 +- 可增大 `WIFI_CONNECT_BUTTON_STARTUP_GUARD_MS`(如 8000~10000) +- 可增大 `WIFI_CONNECT_BUTTON_RELEASE_ARM_MS`(如 300~500) +- 若硬件允许,优先给配网按键使用独立 GPIO + +### 5) 成功后热点消失是否正常 + +- 在按键触发模式下:正常,可通过 `WIFI_CONNECT_AP_GRACEFUL_STOP_SEC` 调整关闭延时 +- 在常驻配网模式下:热点不会自动关闭 + +--- + +## 依赖 + +由 `CMakeLists.txt` 声明: + +- `esp_wifi` +- `esp_timer` +- `esp_event` +- `esp_netif` +- `nvs_flash` +- `esp_http_server` +- `lwip` +- `driver` + +--- + +## 版本建议 + +- 推荐 ESP-IDF `v5.5.x` +- 当前项目验证环境:`esp-idf v5.5.2`(ESP32-C3) diff --git a/components/wifi-connect/USER_GUIDE.md b/components/wifi-connect/USER_GUIDE.md new file mode 100644 index 0000000..d0b7e70 --- /dev/null +++ b/components/wifi-connect/USER_GUIDE.md @@ -0,0 +1,82 @@ +# BotanicalBuddy 配网操作手册(用户版) + +> 适用对象:现场测试、家人用户、非开发人员 +> 目标:让设备快速连上家里 Wi-Fi + +--- + +## 1. 开始前准备 + +- 手机已打开 Wi-Fi +- 记住家里 Wi-Fi 名称和密码 +- 设备已上电 + +--- + +## 2. 进入配网模式 + +有两种工作模式,请按项目配置使用: + +1. 按键触发模式:长按设备上的配网按键(约 2 秒) +2. 常驻配网模式:设备上电后会自动开启配网 +3. 当看到“配网已启动”后,手机 Wi-Fi 列表会出现:`ESP32-xxxxxx` + +--- + +## 3. 手机连接设备热点 + +1. 在手机 Wi-Fi 中连接 `ESP32-xxxxxx` +2. 若系统自动弹出页面,直接进入下一步 +3. 若没有自动弹出,手动打开浏览器输入: + +`http://192.168.4.1` + +> 注意:必须是 `http`,不要用 `https` + +--- + +## 4. 选择路由器并提交 + +1. 点击“扫描网络” +2. 选择你家的 Wi-Fi +3. 输入 Wi-Fi 密码 +4. 点击“连接” + +成功后页面会提示连接成功: + +- 按键触发模式:设备热点会在几秒后自动关闭(正常现象) +- 常驻配网模式:设备热点保持开启(正常现象) + +--- + +## 5. 成功后的现象 + +- 设备不再广播 `ESP32-xxxxxx` +- 串口会显示“联网成功,获取 IP=... ” +- 设备进入正常工作状态 + +--- + +## 6. 常见问题 + +### Q1:手机连上热点但打不开页面 + +- 手动访问:`http://192.168.4.1` +- 关闭手机“私人 DNS / 智能网络切换 / VPN”后重试 +- 确认没有强制跳 HTTPS + +### Q2:提示连接失败 + +- 检查 Wi-Fi 密码是否正确 +- 确认路由器 2.4G 可用(ESP32-C3 使用 2.4G) +- 路由器信号太弱时,靠近路由器后重试 + +### Q3:配网成功后热点消失了 + +- 这是正常设计:设备连上路由器后会自动关闭配网热点 + +--- + +## 7. 一句话速记 + +长按按键 → 连 `ESP32-xxxxxx` → 打开 `http://192.168.4.1` → 选 Wi-Fi + 输密码 → 等待成功提示。 diff --git a/components/wifi-connect/include/wifi-connect.h b/components/wifi-connect/include/wifi-connect.h new file mode 100644 index 0000000..37a325f --- /dev/null +++ b/components/wifi-connect/include/wifi-connect.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + WIFI_CONNECT_STATUS_IDLE = 0, + WIFI_CONNECT_STATUS_PROVISIONING, + WIFI_CONNECT_STATUS_CONNECTING, + WIFI_CONNECT_STATUS_CONNECTED, + WIFI_CONNECT_STATUS_FAILED, + WIFI_CONNECT_STATUS_TIMEOUT, +} wifi_connect_status_t; + +typedef struct { + bool has_config; + char ssid[33]; + char password[65]; +} wifi_connect_config_t; + +esp_err_t wifi_connect_init(void); +esp_err_t wifi_connect_start(void); +esp_err_t wifi_connect_stop(void); +wifi_connect_status_t wifi_connect_get_status(void); +esp_err_t wifi_connect_get_config(wifi_connect_config_t *config); +esp_err_t wifi_connect_clear_config(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/wifi-connect/wifi-connect.c b/components/wifi-connect/wifi-connect.c new file mode 100644 index 0000000..7a28332 --- /dev/null +++ b/components/wifi-connect/wifi-connect.c @@ -0,0 +1,1263 @@ +#include +#include +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include "driver/gpio.h" +#include "esp_check.h" +#include "esp_event.h" +#include "esp_http_server.h" +#include "esp_log.h" +#include "esp_mac.h" +#include "esp_netif.h" +#include "esp_netif_ip_addr.h" +#include "esp_timer.h" +#include "esp_wifi.h" +#include "nvs.h" +#include "nvs_flash.h" + +#include "lwip/sockets.h" +#include "lwip/inet.h" + +#include "wifi-connect.h" + +#define WIFI_CONNECT_NVS_NAMESPACE "wifi_connect" +#define WIFI_CONNECT_NVS_KEY_SSID "ssid" +#define WIFI_CONNECT_NVS_KEY_PASS "pass" + +#define WIFI_CONNECT_HTTP_BUF_SIZE 256 + +#ifndef CONFIG_WIFI_CONNECT_BUTTON_STARTUP_GUARD_MS +#define CONFIG_WIFI_CONNECT_BUTTON_STARTUP_GUARD_MS 5000 +#endif + +#ifndef CONFIG_WIFI_CONNECT_BUTTON_RELEASE_ARM_MS +#define CONFIG_WIFI_CONNECT_BUTTON_RELEASE_ARM_MS 200 +#endif + +static const char *TAG = "wifi_connect"; + +static inline bool wifi_connect_is_always_on_mode(void) +{ +#if CONFIG_WIFI_CONNECT_PROVISION_MODE_ALWAYS_ON + return true; +#else + return false; +#endif +} + +static inline bool wifi_connect_is_button_mode(void) +{ +#if CONFIG_WIFI_CONNECT_PROVISION_MODE_BUTTON + return true; +#else + return false; +#endif +} + +static void wifi_connect_log_state_i(const char *state, const char *detail) +{ + if (detail != NULL && detail[0] != '\0') { + ESP_LOGI(TAG, "【状态】%s:%s", state, detail); + } else { + ESP_LOGI(TAG, "【状态】%s", state); + } +} + +static void wifi_connect_log_state_w(const char *state, const char *detail) +{ + if (detail != NULL && detail[0] != '\0') { + ESP_LOGW(TAG, "【状态】%s:%s", state, detail); + } else { + ESP_LOGW(TAG, "【状态】%s", state); + } +} + +static void wifi_connect_log_state_e(const char *state, const char *detail) +{ + if (detail != NULL && detail[0] != '\0') { + ESP_LOGE(TAG, "【状态】%s:%s", state, detail); + } else { + ESP_LOGE(TAG, "【状态】%s", state); + } +} + +typedef struct { + // 当前配网组件运行状态 + wifi_connect_status_t status; + bool initialized; + bool wifi_started; + bool provisioning_active; + bool sta_connected; + bool sta_connect_requested; + bool auto_connecting; + + esp_netif_t *sta_netif; + esp_netif_t *ap_netif; + httpd_handle_t http_server; + esp_event_handler_instance_t wifi_event_instance; + esp_event_handler_instance_t ip_event_instance; + + TaskHandle_t button_task; + TaskHandle_t dns_task; + + SemaphoreHandle_t lock; + + esp_timer_handle_t connect_timer; + esp_timer_handle_t idle_timer; + esp_timer_handle_t ap_stop_timer; + + int dns_sock; + bool dns_running; + + char ap_ssid[32]; + char pending_ssid[33]; + char pending_password[65]; + char last_error[96]; +} wifi_connect_ctx_t; + +static wifi_connect_ctx_t s_ctx = { + .status = WIFI_CONNECT_STATUS_IDLE, + .dns_sock = -1, +}; + +// 配网页面(内嵌 HTML + JS) +static const char *s_html_page = + "" + "" + "ESP32 Wi-Fi Setup" + "
" + "

连接 Wi-Fi

请选择网络并输入密码。

" + "
" + "" + "" + "
" + "
" + "
"; + +static void wifi_connect_set_status_locked(wifi_connect_status_t status) +{ + s_ctx.status = status; +} + +static void wifi_connect_set_error_locked(const char *message) +{ + if (message == NULL) { + s_ctx.last_error[0] = '\0'; + return; + } + snprintf(s_ctx.last_error, sizeof(s_ctx.last_error), "%s", message); +} + +static void wifi_connect_refresh_idle_timeout(void) +{ + if (wifi_connect_is_always_on_mode()) { + return; + } + if (s_ctx.idle_timer == NULL) { + return; + } + esp_timer_stop(s_ctx.idle_timer); + esp_timer_start_once(s_ctx.idle_timer, (uint64_t)CONFIG_WIFI_CONNECT_IDLE_TIMEOUT_SEC * 1000000ULL); +} + +static esp_err_t wifi_connect_save_credentials(const char *ssid, const char *password) +{ + nvs_handle_t handle; + ESP_RETURN_ON_ERROR(nvs_open(WIFI_CONNECT_NVS_NAMESPACE, NVS_READWRITE, &handle), TAG, "open nvs failed"); + esp_err_t err = nvs_set_str(handle, WIFI_CONNECT_NVS_KEY_SSID, ssid); + if (err == ESP_OK) { + err = nvs_set_str(handle, WIFI_CONNECT_NVS_KEY_PASS, password); + } + if (err == ESP_OK) { + err = nvs_commit(handle); + } + nvs_close(handle); + return err; +} + +esp_err_t wifi_connect_clear_config(void) +{ + nvs_handle_t handle; + ESP_RETURN_ON_ERROR(nvs_open(WIFI_CONNECT_NVS_NAMESPACE, NVS_READWRITE, &handle), TAG, "open nvs failed"); + + esp_err_t err_ssid = nvs_erase_key(handle, WIFI_CONNECT_NVS_KEY_SSID); + esp_err_t err_pass = nvs_erase_key(handle, WIFI_CONNECT_NVS_KEY_PASS); + + if (err_ssid != ESP_OK && err_ssid != ESP_ERR_NVS_NOT_FOUND) { + nvs_close(handle); + return err_ssid; + } + if (err_pass != ESP_OK && err_pass != ESP_ERR_NVS_NOT_FOUND) { + nvs_close(handle); + return err_pass; + } + + esp_err_t err = nvs_commit(handle); + nvs_close(handle); + if (err != ESP_OK) { + return err; + } + + if (s_ctx.initialized && s_ctx.lock != NULL) { + bool should_disconnect = false; + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + s_ctx.pending_ssid[0] = '\0'; + s_ctx.pending_password[0] = '\0'; + wifi_connect_set_error_locked(NULL); + if (s_ctx.provisioning_active) { + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_PROVISIONING); + } + if (s_ctx.status == WIFI_CONNECT_STATUS_CONNECTING) { + s_ctx.sta_connect_requested = false; + s_ctx.auto_connecting = false; + wifi_connect_set_status_locked(s_ctx.provisioning_active ? WIFI_CONNECT_STATUS_PROVISIONING : WIFI_CONNECT_STATUS_IDLE); + should_disconnect = true; + } + xSemaphoreGive(s_ctx.lock); + + if (should_disconnect) { + esp_wifi_disconnect(); + } + } + + wifi_connect_log_state_i("已清除保存的 Wi-Fi 配置", "下次上电将不会自动重连"); + return ESP_OK; +} + +esp_err_t wifi_connect_get_config(wifi_connect_config_t *config) +{ + ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "config is null"); + memset(config, 0, sizeof(*config)); + + nvs_handle_t handle; + esp_err_t err = nvs_open(WIFI_CONNECT_NVS_NAMESPACE, NVS_READONLY, &handle); + if (err != ESP_OK) { + return err; + } + + size_t ssid_len = sizeof(config->ssid); + size_t pass_len = sizeof(config->password); + err = nvs_get_str(handle, WIFI_CONNECT_NVS_KEY_SSID, config->ssid, &ssid_len); + if (err == ESP_OK) { + err = nvs_get_str(handle, WIFI_CONNECT_NVS_KEY_PASS, config->password, &pass_len); + } + nvs_close(handle); + config->has_config = (err == ESP_OK && config->ssid[0] != '\0'); + return err; +} + +static const char *wifi_connect_status_to_string(wifi_connect_status_t status) +{ + switch (status) { + case WIFI_CONNECT_STATUS_IDLE: return "idle"; + case WIFI_CONNECT_STATUS_PROVISIONING: return "provisioning"; + case WIFI_CONNECT_STATUS_CONNECTING: return "connecting"; + case WIFI_CONNECT_STATUS_CONNECTED: return "connected"; + case WIFI_CONNECT_STATUS_FAILED: return "failed"; + case WIFI_CONNECT_STATUS_TIMEOUT: return "timeout"; + default: return "unknown"; + } +} + +wifi_connect_status_t wifi_connect_get_status(void) +{ + if (!s_ctx.initialized || s_ctx.lock == NULL) { + return WIFI_CONNECT_STATUS_IDLE; + } + + wifi_connect_status_t status; + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + status = s_ctx.status; + xSemaphoreGive(s_ctx.lock); + return status; +} + +static esp_err_t wifi_connect_send_json(httpd_req_t *req, const char *json) +{ + httpd_resp_set_type(req, "application/json"); + return httpd_resp_sendstr(req, json); +} + +static void wifi_connect_json_escape(const char *src, char *dst, size_t dst_size) +{ + size_t j = 0; + for (size_t i = 0; src != NULL && src[i] != '\0' && j + 2 < dst_size; ++i) { + char c = src[i]; + if (c == '"' || c == '\\') { + dst[j++] = '\\'; + dst[j++] = c; + } else if ((unsigned char)c >= 32 && (unsigned char)c <= 126) { + dst[j++] = c; + } + } + dst[j] = '\0'; +} + +static const char *wifi_connect_auth_to_string(wifi_auth_mode_t auth) +{ + switch (auth) { + case WIFI_AUTH_OPEN: return "OPEN"; + case WIFI_AUTH_WEP: return "WEP"; + case WIFI_AUTH_WPA_PSK: return "WPA"; + case WIFI_AUTH_WPA2_PSK: return "WPA2"; + case WIFI_AUTH_WPA_WPA2_PSK: return "WPA/WPA2"; + case WIFI_AUTH_WPA2_ENTERPRISE: return "WPA2-ENT"; + case WIFI_AUTH_WPA3_PSK: return "WPA3"; + case WIFI_AUTH_WPA2_WPA3_PSK: return "WPA2/WPA3"; + default: return "UNKNOWN"; + } +} + +static esp_err_t wifi_connect_http_scan_handler(httpd_req_t *req) +{ + wifi_connect_refresh_idle_timeout(); + + wifi_scan_config_t scan_cfg = { + .show_hidden = false, + }; + esp_err_t err = esp_wifi_scan_start(&scan_cfg, true); + if (err != ESP_OK) { + return wifi_connect_send_json(req, "{\"networks\":[],\"error\":\"scan_failed\"}"); + } + + uint16_t count = CONFIG_WIFI_CONNECT_MAX_SCAN_RESULTS; + wifi_ap_record_t *records = calloc(count, sizeof(wifi_ap_record_t)); + if (records == NULL) { + return ESP_ERR_NO_MEM; + } + + err = esp_wifi_scan_get_ap_records(&count, records); + if (err != ESP_OK) { + free(records); + return wifi_connect_send_json(req, "{\"networks\":[],\"error\":\"scan_read_failed\"}"); + } + + size_t out_size = 512 + count * 96; + char *out = calloc(1, out_size); + if (out == NULL) { + free(records); + return ESP_ERR_NO_MEM; + } + + size_t used = snprintf(out, out_size, "{\"networks\":["); + for (uint16_t i = 0; i < count && used + 96 < out_size; ++i) { + char ssid[65] = {0}; + char escaped[130] = {0}; + snprintf(ssid, sizeof(ssid), "%s", (char *)records[i].ssid); + wifi_connect_json_escape(ssid, escaped, sizeof(escaped)); + used += snprintf(out + used, out_size - used, + "%s{\"ssid\":\"%s\",\"rssi\":%d,\"auth\":\"%s\"}", + (i == 0 ? "" : ","), escaped, records[i].rssi, + wifi_connect_auth_to_string(records[i].authmode)); + } + snprintf(out + used, out_size - used, "]}"); + + err = wifi_connect_send_json(req, out); + free(out); + free(records); + return err; +} + +static bool wifi_connect_extract_json_string(const char *json, const char *key, char *out, size_t out_len) +{ + char pattern[32]; + snprintf(pattern, sizeof(pattern), "\"%s\":\"", key); + const char *start = strstr(json, pattern); + if (start == NULL) { + return false; + } + start += strlen(pattern); + size_t idx = 0; + while (*start != '\0' && *start != '"' && idx + 1 < out_len) { + if (*start == '\\' && *(start + 1) != '\0') { + start++; + } + out[idx++] = *start++; + } + out[idx] = '\0'; + return idx > 0 || strcmp(key, "password") == 0; +} + +static esp_err_t wifi_connect_apply_sta_credentials(const char *ssid, const char *password) +{ + wifi_config_t sta_cfg = {0}; + snprintf((char *)sta_cfg.sta.ssid, sizeof(sta_cfg.sta.ssid), "%s", ssid); + snprintf((char *)sta_cfg.sta.password, sizeof(sta_cfg.sta.password), "%s", password); + sta_cfg.sta.scan_method = WIFI_FAST_SCAN; + sta_cfg.sta.threshold.authmode = WIFI_AUTH_OPEN; + sta_cfg.sta.pmf_cfg.capable = true; + sta_cfg.sta.pmf_cfg.required = false; + + esp_err_t dis_err = esp_wifi_disconnect(); + if (dis_err != ESP_OK && dis_err != ESP_ERR_WIFI_NOT_CONNECT) { + char dis_msg[96] = {0}; + snprintf(dis_msg, sizeof(dis_msg), "切换网络前断开 STA 失败,错误=%s", esp_err_to_name(dis_err)); + wifi_connect_log_state_w("预断开当前连接失败", dis_msg); + } + + ESP_RETURN_ON_ERROR(esp_wifi_set_config(WIFI_IF_STA, &sta_cfg), TAG, "set sta config failed"); + ESP_RETURN_ON_ERROR(esp_wifi_connect(), TAG, "wifi connect failed"); + return ESP_OK; +} + +static esp_err_t wifi_connect_try_auto_connect(void) +{ + wifi_connect_config_t config = {0}; + esp_err_t err = wifi_connect_get_config(&config); + if (err != ESP_OK || !config.has_config) { + wifi_connect_log_state_i("未发现已保存的 Wi-Fi 配置", "设备保持待机"); + return ESP_OK; + } + + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + snprintf(s_ctx.pending_ssid, sizeof(s_ctx.pending_ssid), "%s", config.ssid); + snprintf(s_ctx.pending_password, sizeof(s_ctx.pending_password), "%s", config.password); + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_CONNECTING); + wifi_connect_set_error_locked(NULL); + s_ctx.sta_connect_requested = true; + s_ctx.auto_connecting = true; + xSemaphoreGive(s_ctx.lock); + + err = wifi_connect_apply_sta_credentials(config.ssid, config.password); + if (err != ESP_OK) { + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + s_ctx.sta_connect_requested = false; + s_ctx.auto_connecting = false; + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_IDLE); + wifi_connect_set_error_locked(NULL); + xSemaphoreGive(s_ctx.lock); + char msg[96] = {0}; + snprintf(msg, sizeof(msg), "自动重连启动失败,错误=%s", esp_err_to_name(err)); + wifi_connect_log_state_w("自动重连失败", msg); + return err; + } + + esp_timer_stop(s_ctx.connect_timer); + esp_timer_start_once(s_ctx.connect_timer, (uint64_t)CONFIG_WIFI_CONNECT_CONNECT_TIMEOUT_SEC * 1000000ULL); + char msg[96] = {0}; + snprintf(msg, sizeof(msg), "尝试连接已保存网络:%s", config.ssid); + wifi_connect_log_state_i("自动重连中", msg); + return ESP_OK; +} + +static esp_err_t wifi_connect_http_connect_handler(httpd_req_t *req) +{ + wifi_connect_refresh_idle_timeout(); + + if (req->content_len <= 0 || req->content_len >= WIFI_CONNECT_HTTP_BUF_SIZE) { + return wifi_connect_send_json(req, "{\"ok\":false,\"error\":\"invalid_payload\"}"); + } + + char body[WIFI_CONNECT_HTTP_BUF_SIZE] = {0}; + int received = httpd_req_recv(req, body, sizeof(body) - 1); + if (received <= 0) { + return wifi_connect_send_json(req, "{\"ok\":false,\"error\":\"read_failed\"}"); + } + body[received] = '\0'; + + char ssid[33] = {0}; + char password[65] = {0}; + if (!wifi_connect_extract_json_string(body, "ssid", ssid, sizeof(ssid))) { + return wifi_connect_send_json(req, "{\"ok\":false,\"error\":\"ssid_missing\"}"); + } + wifi_connect_extract_json_string(body, "password", password, sizeof(password)); + char req_msg[96] = {0}; + snprintf(req_msg, sizeof(req_msg), "收到配网请求,目标网络:%s", ssid); + wifi_connect_log_state_i("开始连接路由器", req_msg); + + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + snprintf(s_ctx.pending_ssid, sizeof(s_ctx.pending_ssid), "%s", ssid); + snprintf(s_ctx.pending_password, sizeof(s_ctx.pending_password), "%s", password); + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_CONNECTING); + wifi_connect_set_error_locked(NULL); + s_ctx.sta_connect_requested = true; + s_ctx.auto_connecting = false; + xSemaphoreGive(s_ctx.lock); + + esp_err_t err = wifi_connect_apply_sta_credentials(ssid, password); + if (err != ESP_OK) { + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_FAILED); + wifi_connect_set_error_locked("启动连接失败"); + xSemaphoreGive(s_ctx.lock); + char err_msg[96] = {0}; + snprintf(err_msg, sizeof(err_msg), "提交连接失败,错误=%s", esp_err_to_name(err)); + wifi_connect_log_state_w("连接启动失败", err_msg); + return wifi_connect_send_json(req, "{\"ok\":false,\"error\":\"connect_start_failed\"}"); + } + + esp_timer_stop(s_ctx.connect_timer); + esp_timer_start_once(s_ctx.connect_timer, (uint64_t)CONFIG_WIFI_CONNECT_CONNECT_TIMEOUT_SEC * 1000000ULL); + return wifi_connect_send_json(req, "{\"ok\":true}"); +} + +static esp_err_t wifi_connect_http_status_handler(httpd_req_t *req) +{ + wifi_connect_refresh_idle_timeout(); + + wifi_connect_status_t status; + char error[96] = {0}; + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + status = s_ctx.status; + snprintf(error, sizeof(error), "%s", s_ctx.last_error); + xSemaphoreGive(s_ctx.lock); + + char escaped[192] = {0}; + wifi_connect_json_escape(error, escaped, sizeof(escaped)); + + char payload[260]; + snprintf(payload, sizeof(payload), "{\"status\":\"%s\",\"error\":\"%s\"}", + wifi_connect_status_to_string(status), escaped); + return wifi_connect_send_json(req, payload); +} + +static esp_err_t wifi_connect_http_clear_handler(httpd_req_t *req) +{ + wifi_connect_refresh_idle_timeout(); + esp_err_t err = wifi_connect_clear_config(); + if (err != ESP_OK) { + return wifi_connect_send_json(req, "{\"ok\":false,\"error\":\"clear_failed\"}"); + } + return wifi_connect_send_json(req, "{\"ok\":true}"); +} + +static esp_err_t wifi_connect_http_index_handler(httpd_req_t *req) +{ + wifi_connect_refresh_idle_timeout(); + httpd_resp_set_type(req, "text/html"); + return httpd_resp_send(req, s_html_page, HTTPD_RESP_USE_STRLEN); +} + +static void wifi_connect_get_ap_http_url(char *out, size_t out_len) +{ + esp_netif_ip_info_t ip_info = {0}; + if (s_ctx.ap_netif != NULL && esp_netif_get_ip_info(s_ctx.ap_netif, &ip_info) == ESP_OK) { + uint32_t ip = ntohl(ip_info.ip.addr); + snprintf(out, out_len, "http://%" PRIu32 ".%" PRIu32 ".%" PRIu32 ".%" PRIu32 "/", + (ip >> 24) & 0xFF, + (ip >> 16) & 0xFF, + (ip >> 8) & 0xFF, + ip & 0xFF); + return; + } + snprintf(out, out_len, "http://192.168.4.1/"); +} + +static esp_err_t wifi_connect_http_probe_handler(httpd_req_t *req) +{ + wifi_connect_refresh_idle_timeout(); + char location[48] = {0}; + wifi_connect_get_ap_http_url(location, sizeof(location)); + httpd_resp_set_status(req, "302 Found"); + httpd_resp_set_hdr(req, "Location", location); + httpd_resp_set_hdr(req, "Cache-Control", "no-store"); + return httpd_resp_send(req, NULL, 0); +} + +static esp_err_t wifi_connect_http_start(void) +{ + esp_err_t ret = ESP_OK; + + if (s_ctx.http_server != NULL) { + return ESP_OK; + } + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.max_uri_handlers = 20; + config.uri_match_fn = httpd_uri_match_wildcard; + ESP_RETURN_ON_ERROR(httpd_start(&s_ctx.http_server, &config), TAG, "start http server failed"); + + const httpd_uri_t index_uri = { + .uri = "/", + .method = HTTP_GET, + .handler = wifi_connect_http_index_handler, + }; + const httpd_uri_t scan_uri = { + .uri = "/api/scan", + .method = HTTP_GET, + .handler = wifi_connect_http_scan_handler, + }; + const httpd_uri_t connect_uri = { + .uri = "/api/connect", + .method = HTTP_POST, + .handler = wifi_connect_http_connect_handler, + }; + const httpd_uri_t status_uri = { + .uri = "/api/status", + .method = HTTP_GET, + .handler = wifi_connect_http_status_handler, + }; + const httpd_uri_t clear_uri = { + .uri = "/api/clear", + .method = HTTP_POST, + .handler = wifi_connect_http_clear_handler, + }; + const httpd_uri_t probe_1 = { + .uri = "/generate_204", + .method = HTTP_GET, + .handler = wifi_connect_http_probe_handler, + }; + const httpd_uri_t probe_2 = { + .uri = "/hotspot-detect.html", + .method = HTTP_GET, + .handler = wifi_connect_http_probe_handler, + }; + const httpd_uri_t probe_3 = { + .uri = "/ncsi.txt", + .method = HTTP_GET, + .handler = wifi_connect_http_probe_handler, + }; + const httpd_uri_t probe_4 = { + .uri = "/connecttest.txt", + .method = HTTP_GET, + .handler = wifi_connect_http_probe_handler, + }; + const httpd_uri_t probe_5 = { + .uri = "/redirect", + .method = HTTP_GET, + .handler = wifi_connect_http_probe_handler, + }; + const httpd_uri_t probe_6 = { + .uri = "/canonical.html", + .method = HTTP_GET, + .handler = wifi_connect_http_probe_handler, + }; + const httpd_uri_t probe_7 = { + .uri = "/mobile/status.php", + .method = HTTP_GET, + .handler = wifi_connect_http_probe_handler, + }; + const httpd_uri_t probe_8 = { + .uri = "/success.txt", + .method = HTTP_GET, + .handler = wifi_connect_http_probe_handler, + }; + const httpd_uri_t probe_9 = { + .uri = "/library/test/success.html", + .method = HTTP_GET, + .handler = wifi_connect_http_probe_handler, + }; + const httpd_uri_t wildcard = { + .uri = "/*", + .method = HTTP_GET, + .handler = wifi_connect_http_probe_handler, + }; + + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &index_uri), fail, TAG, "register / failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &scan_uri), fail, TAG, "register /api/scan failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &connect_uri), fail, TAG, "register /api/connect failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &status_uri), fail, TAG, "register /api/status failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &clear_uri), fail, TAG, "register /api/clear failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_1), fail, TAG, "register /generate_204 failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_2), fail, TAG, "register /hotspot-detect.html failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_3), fail, TAG, "register /ncsi.txt failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_4), fail, TAG, "register /connecttest.txt failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_5), fail, TAG, "register /redirect failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_6), fail, TAG, "register /canonical.html failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_7), fail, TAG, "register /mobile/status.php failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_8), fail, TAG, "register /success.txt failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &probe_9), fail, TAG, "register /library/test/success.html failed"); + ESP_GOTO_ON_ERROR(httpd_register_uri_handler(s_ctx.http_server, &wildcard), fail, TAG, "register wildcard failed"); + + return ESP_OK; + +fail: + httpd_stop(s_ctx.http_server); + s_ctx.http_server = NULL; + return ret; +} + +static void wifi_connect_http_stop(void) +{ + if (s_ctx.http_server != NULL) { + httpd_stop(s_ctx.http_server); + s_ctx.http_server = NULL; + } +} + +static size_t wifi_connect_build_dns_response(const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_max, uint32_t ip_addr) +{ + if (req_len < 12 || resp_max < 64) { + return 0; + } + + const size_t q_offset = 12; + size_t q_name_end = q_offset; + while (q_name_end < req_len && req[q_name_end] != 0) { + q_name_end += req[q_name_end] + 1; + } + if (q_name_end + 5 >= req_len) { + return 0; + } + size_t question_len = (q_name_end + 5) - q_offset; + + resp[0] = req[0]; + resp[1] = req[1]; + resp[2] = 0x81; + resp[3] = 0x80; + resp[4] = 0x00; + resp[5] = 0x01; + resp[6] = 0x00; + resp[7] = 0x01; + resp[8] = 0x00; + resp[9] = 0x00; + resp[10] = 0x00; + resp[11] = 0x00; + + memcpy(&resp[12], &req[q_offset], question_len); + size_t pos = 12 + question_len; + if (pos + 16 > resp_max) { + return 0; + } + + resp[pos++] = 0xC0; + resp[pos++] = 0x0C; + resp[pos++] = 0x00; + resp[pos++] = 0x01; + resp[pos++] = 0x00; + resp[pos++] = 0x01; + resp[pos++] = 0x00; + resp[pos++] = 0x00; + resp[pos++] = 0x00; + resp[pos++] = 0x3C; + resp[pos++] = 0x00; + resp[pos++] = 0x04; + resp[pos++] = (ip_addr >> 24) & 0xFF; + resp[pos++] = (ip_addr >> 16) & 0xFF; + resp[pos++] = (ip_addr >> 8) & 0xFF; + resp[pos++] = (ip_addr) & 0xFF; + + return pos; +} + +static void wifi_connect_dns_task(void *arg) +{ + (void)arg; + uint8_t rx_buf[256]; + uint8_t tx_buf[512]; + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(53), + .sin_addr.s_addr = htonl(INADDR_ANY), + }; + + s_ctx.dns_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (s_ctx.dns_sock < 0) { + wifi_connect_log_state_e("DNS 服务启动失败", "创建 socket 失败"); + s_ctx.dns_running = false; + vTaskDelete(NULL); + return; + } + + if (bind(s_ctx.dns_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + char err_msg[96] = {0}; + snprintf(err_msg, sizeof(err_msg), "绑定 53 端口失败,errno=%d", errno); + wifi_connect_log_state_e("DNS 服务启动失败", err_msg); + close(s_ctx.dns_sock); + s_ctx.dns_sock = -1; + s_ctx.dns_running = false; + vTaskDelete(NULL); + return; + } + wifi_connect_log_state_i("DNS 劫持服务已启动", "手机访问任意域名将跳转配网页面"); + + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(s_ctx.ap_netif, &ip_info); + uint32_t ip = ntohl(ip_info.ip.addr); + + while (s_ctx.dns_running) { + struct sockaddr_in from_addr; + socklen_t from_len = sizeof(from_addr); + struct timeval tv = {.tv_sec = 1, .tv_usec = 0}; + setsockopt(s_ctx.dns_sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + int len = recvfrom(s_ctx.dns_sock, rx_buf, sizeof(rx_buf), 0, + (struct sockaddr *)&from_addr, &from_len); + if (len <= 0) { + continue; + } + + size_t resp_len = wifi_connect_build_dns_response(rx_buf, (size_t)len, tx_buf, sizeof(tx_buf), ip); + if (resp_len > 0) { + sendto(s_ctx.dns_sock, tx_buf, resp_len, 0, (struct sockaddr *)&from_addr, from_len); + } + } + + close(s_ctx.dns_sock); + s_ctx.dns_sock = -1; + vTaskDelete(NULL); +} + +static esp_err_t wifi_connect_dns_start(void) +{ + if (s_ctx.dns_running) { + return ESP_OK; + } + s_ctx.dns_running = true; + BaseType_t ok = xTaskCreate(wifi_connect_dns_task, "wifi_dns", 4096, NULL, 4, &s_ctx.dns_task); + if (ok != pdPASS) { + s_ctx.dns_running = false; + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +static void wifi_connect_dns_stop(void) +{ + if (!s_ctx.dns_running) { + return; + } + s_ctx.dns_running = false; + if (s_ctx.dns_sock >= 0) { + shutdown(s_ctx.dns_sock, 0); + } + s_ctx.dns_task = NULL; +} + +static void wifi_connect_connect_timeout_cb(void *arg) +{ + (void)arg; + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + if (s_ctx.status == WIFI_CONNECT_STATUS_CONNECTING) { + if (s_ctx.auto_connecting) { + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_IDLE); + wifi_connect_set_error_locked(NULL); + s_ctx.auto_connecting = false; + wifi_connect_log_state_w("自动重连超时", "回到待机状态"); + } else { + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_FAILED); + wifi_connect_set_error_locked("连接超时"); + wifi_connect_log_state_w("连接路由器超时", "请确认密码和路由器信号"); + } + s_ctx.sta_connect_requested = false; + esp_wifi_disconnect(); + } + xSemaphoreGive(s_ctx.lock); +} + +static void wifi_connect_ap_stop_timer_cb(void *arg) +{ + (void)arg; + if (wifi_connect_is_always_on_mode()) { + return; + } + wifi_connect_stop(); +} + +static void wifi_connect_idle_timeout_cb(void *arg) +{ + (void)arg; + if (wifi_connect_is_always_on_mode()) { + return; + } + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + bool should_stop = s_ctx.provisioning_active; + if (should_stop) { + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_TIMEOUT); + wifi_connect_set_error_locked("配网空闲超时"); + wifi_connect_log_state_w("配网超时", "长时间无操作,正在关闭配网热点"); + } + xSemaphoreGive(s_ctx.lock); + + if (should_stop) { + wifi_connect_stop(); + } +} + +static void wifi_connect_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + (void)arg; + // STA 获取到 IP:判定联网成功,并根据配置决定是否关闭配网热点 + + if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t *got_ip = (ip_event_got_ip_t *)event_data; + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + bool should_save = (s_ctx.status == WIFI_CONNECT_STATUS_CONNECTING || s_ctx.status == WIFI_CONNECT_STATUS_PROVISIONING); + bool provisioning_active = s_ctx.provisioning_active; + s_ctx.sta_connected = true; + s_ctx.sta_connect_requested = false; + s_ctx.auto_connecting = false; + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_CONNECTED); + wifi_connect_set_error_locked(NULL); + char ssid[33]; + char password[65]; + snprintf(ssid, sizeof(ssid), "%s", s_ctx.pending_ssid); + snprintf(password, sizeof(password), "%s", s_ctx.pending_password); + xSemaphoreGive(s_ctx.lock); + char success_msg[128] = {0}; + snprintf(success_msg, sizeof(success_msg), "已连接 %s,获取 IP=" IPSTR, ssid, IP2STR(&got_ip->ip_info.ip)); + wifi_connect_log_state_i("联网成功", success_msg); + + esp_timer_stop(s_ctx.connect_timer); + if (should_save) { + esp_err_t err = wifi_connect_save_credentials(ssid, password); + if (err != ESP_OK) { + char save_msg[96] = {0}; + snprintf(save_msg, sizeof(save_msg), "保存凭据失败,错误=%s", esp_err_to_name(err)); + wifi_connect_log_state_w("保存 Wi-Fi 信息失败", save_msg); + } + } + + if (provisioning_active) { + if (wifi_connect_is_always_on_mode()) { + wifi_connect_log_state_i("常驻配网模式", "联网成功后保持配网热点开启"); + } else { + if (CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC == 0) { + wifi_connect_stop(); + } else { + esp_timer_stop(s_ctx.ap_stop_timer); + esp_timer_start_once(s_ctx.ap_stop_timer, (uint64_t)CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC * 1000000ULL); + } + } + } + return; + } + + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + // 仅在“正在连接”阶段把断开视为失败;避免影响普通联网后的波动处理 + wifi_event_sta_disconnected_t *dis = (wifi_event_sta_disconnected_t *)event_data; + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + bool connecting = (s_ctx.status == WIFI_CONNECT_STATUS_CONNECTING); + bool auto_connecting = s_ctx.auto_connecting; + s_ctx.sta_connected = false; + if (connecting) { + if (auto_connecting) { + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_IDLE); + wifi_connect_set_error_locked(NULL); + s_ctx.auto_connecting = false; + char dis_msg[96] = {0}; + snprintf(dis_msg, sizeof(dis_msg), "自动重连断开,原因=%d", dis->reason); + wifi_connect_log_state_w("自动重连中断", dis_msg); + } else { + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_FAILED); + snprintf(s_ctx.last_error, sizeof(s_ctx.last_error), "连接失败,原因=%d", dis->reason); + char dis_msg[96] = {0}; + snprintf(dis_msg, sizeof(dis_msg), "连接失败,原因=%d", dis->reason); + wifi_connect_log_state_w("连接路由器失败", dis_msg); + } + s_ctx.sta_connect_requested = false; + esp_timer_stop(s_ctx.connect_timer); + } + xSemaphoreGive(s_ctx.lock); + } +} + +static void wifi_connect_generate_ap_ssid(char *out, size_t out_len) +{ + uint8_t mac[6] = {0}; + esp_wifi_get_mac(WIFI_IF_STA, mac); + snprintf(out, out_len, "ESP32-%02X%02X%02X", mac[3], mac[4], mac[5]); +} + +static esp_err_t wifi_connect_start_apsta_locked(void) +{ + wifi_config_t ap_cfg = {0}; + wifi_connect_generate_ap_ssid(s_ctx.ap_ssid, sizeof(s_ctx.ap_ssid)); + snprintf((char *)ap_cfg.ap.ssid, sizeof(ap_cfg.ap.ssid), "%s", s_ctx.ap_ssid); + ap_cfg.ap.ssid_len = strlen(s_ctx.ap_ssid); + ap_cfg.ap.channel = 1; + ap_cfg.ap.authmode = WIFI_AUTH_OPEN; + ap_cfg.ap.max_connection = CONFIG_WIFI_CONNECT_AP_MAX_CONNECTIONS; + ap_cfg.ap.pmf_cfg.required = false; + + ESP_RETURN_ON_ERROR(esp_wifi_set_mode(WIFI_MODE_APSTA), TAG, "set mode apsta failed"); + ESP_RETURN_ON_ERROR(esp_wifi_set_config(WIFI_IF_AP, &ap_cfg), TAG, "set ap config failed"); + + if (!s_ctx.wifi_started) { + ESP_RETURN_ON_ERROR(esp_wifi_start(), TAG, "wifi start failed"); + s_ctx.wifi_started = true; + } + return ESP_OK; +} + +static void wifi_connect_button_task(void *arg) +{ + (void)arg; + + const TickType_t interval = pdMS_TO_TICKS(CONFIG_WIFI_CONNECT_DEBOUNCE_MS); + const TickType_t startup_guard_ticks = pdMS_TO_TICKS(CONFIG_WIFI_CONNECT_BUTTON_STARTUP_GUARD_MS); + const TickType_t release_arm_ticks = pdMS_TO_TICKS(CONFIG_WIFI_CONNECT_BUTTON_RELEASE_ARM_MS); + int stable_level = gpio_get_level(CONFIG_WIFI_CONNECT_BUTTON_GPIO); + int last_level = stable_level; + TickType_t changed_at = xTaskGetTickCount(); + TickType_t low_since = 0; + TickType_t released_since = 0; + TickType_t startup_at = changed_at; + bool triggered = false; + bool armed = false; + + wifi_connect_log_state_i("按键防误触保护", "已启用上电保护窗口与松手解锁机制"); + + while (true) { + vTaskDelay(interval); + int level = gpio_get_level(CONFIG_WIFI_CONNECT_BUTTON_GPIO); + TickType_t now = xTaskGetTickCount(); + + if (level != last_level) { + last_level = level; + changed_at = now; + } + + if ((now - changed_at) >= interval && stable_level != level) { + stable_level = level; + if (stable_level == CONFIG_WIFI_CONNECT_BUTTON_ACTIVE_LEVEL) { + low_since = now; + released_since = 0; + triggered = false; + } else { + low_since = 0; + released_since = now; + triggered = false; + armed = false; + } + } + + if (!armed && stable_level != CONFIG_WIFI_CONNECT_BUTTON_ACTIVE_LEVEL && released_since != 0) { + if ((now - released_since) >= release_arm_ticks) { + armed = true; + } + } + + if ((now - startup_at) < startup_guard_ticks) { + continue; + } + + if (armed && stable_level == CONFIG_WIFI_CONNECT_BUTTON_ACTIVE_LEVEL && low_since != 0 && !triggered) { + TickType_t held = now - low_since; + if (held >= pdMS_TO_TICKS(CONFIG_WIFI_CONNECT_LONG_PRESS_MS)) { + triggered = true; + armed = false; + wifi_connect_log_state_i("检测到按键长按", "开始进入配网模式"); + wifi_connect_start(); + } + } + } +} + +esp_err_t wifi_connect_start(void) +{ + ESP_RETURN_ON_FALSE(s_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized"); + // 启动 AP+STA、HTTP 配网页面和 DNS 劫持,进入可配网状态 + + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + if (s_ctx.provisioning_active) { + xSemaphoreGive(s_ctx.lock); + return ESP_OK; + } + esp_err_t err = wifi_connect_start_apsta_locked(); + if (err != ESP_OK) { + xSemaphoreGive(s_ctx.lock); + return err; + } + + err = wifi_connect_http_start(); + if (err != ESP_OK) { + xSemaphoreGive(s_ctx.lock); + return err; + } + + err = wifi_connect_dns_start(); + if (err != ESP_OK) { + wifi_connect_http_stop(); + xSemaphoreGive(s_ctx.lock); + return err; + } + + s_ctx.provisioning_active = true; + s_ctx.sta_connect_requested = false; + s_ctx.auto_connecting = false; + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_PROVISIONING); + wifi_connect_set_error_locked(NULL); + xSemaphoreGive(s_ctx.lock); + + wifi_connect_refresh_idle_timeout(); + char ap_msg[96] = {0}; + snprintf(ap_msg, sizeof(ap_msg), "配网热点已开启,SSID=%s,访问 http://192.168.4.1", s_ctx.ap_ssid); + wifi_connect_log_state_i("配网已启动", ap_msg); + if (wifi_connect_is_always_on_mode()) { + wifi_connect_log_state_i("当前模式", "常驻配网模式(不会自动关闭)"); + } + return ESP_OK; +} + +esp_err_t wifi_connect_stop(void) +{ + if (!s_ctx.initialized) { + return ESP_ERR_INVALID_STATE; + } + // 停止配网相关服务,若已联网则回到 STA 模式 + + xSemaphoreTake(s_ctx.lock, portMAX_DELAY); + s_ctx.provisioning_active = false; + s_ctx.sta_connect_requested = false; + s_ctx.auto_connecting = false; + + esp_timer_stop(s_ctx.connect_timer); + esp_timer_stop(s_ctx.idle_timer); + esp_timer_stop(s_ctx.ap_stop_timer); + + wifi_connect_http_stop(); + wifi_connect_dns_stop(); + + if (s_ctx.sta_connected) { + esp_wifi_set_mode(WIFI_MODE_STA); + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_CONNECTED); + } else if (s_ctx.status != WIFI_CONNECT_STATUS_TIMEOUT) { + wifi_connect_set_status_locked(WIFI_CONNECT_STATUS_IDLE); + } + + xSemaphoreGive(s_ctx.lock); + wifi_connect_log_state_i("配网已停止", "热点已关闭,设备继续以 STA 模式运行"); + return ESP_OK; +} + +esp_err_t wifi_connect_init(void) +{ + if (s_ctx.initialized) { + return ESP_OK; + } + // 一次性初始化 NVS/Wi-Fi/事件/按键任务,并尝试自动连接已保存网络 + + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_RETURN_ON_ERROR(err, TAG, "nvs init failed"); + + ESP_RETURN_ON_ERROR(esp_netif_init(), TAG, "netif init failed"); + err = esp_event_loop_create_default(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_RETURN_ON_ERROR(err, TAG, "event loop create failed"); + } + + wifi_init_config_t wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_RETURN_ON_ERROR(esp_wifi_init(&wifi_init_cfg), TAG, "wifi init failed"); + ESP_RETURN_ON_ERROR(esp_wifi_set_storage(WIFI_STORAGE_RAM), TAG, "wifi storage set failed"); + + s_ctx.sta_netif = esp_netif_create_default_wifi_sta(); + s_ctx.ap_netif = esp_netif_create_default_wifi_ap(); + + ESP_RETURN_ON_ERROR(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, + &wifi_connect_event_handler, NULL, &s_ctx.wifi_event_instance), + TAG, "register wifi handler failed"); + ESP_RETURN_ON_ERROR(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, + &wifi_connect_event_handler, NULL, &s_ctx.ip_event_instance), + TAG, "register ip handler failed"); + + ESP_RETURN_ON_ERROR(esp_wifi_set_mode(WIFI_MODE_STA), TAG, "set mode sta failed"); + ESP_RETURN_ON_ERROR(esp_wifi_start(), TAG, "wifi start failed"); + s_ctx.wifi_started = true; + + if (wifi_connect_is_button_mode()) { + gpio_config_t io = { + .pin_bit_mask = (1ULL << CONFIG_WIFI_CONNECT_BUTTON_GPIO), + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_RETURN_ON_ERROR(gpio_config(&io), TAG, "button gpio config failed"); + } + + s_ctx.lock = xSemaphoreCreateMutex(); + ESP_RETURN_ON_FALSE(s_ctx.lock != NULL, ESP_ERR_NO_MEM, TAG, "create lock failed"); + + esp_timer_create_args_t connect_timer_args = { + .callback = wifi_connect_connect_timeout_cb, + .name = "wifi_conn_to", + }; + esp_timer_create_args_t idle_timer_args = { + .callback = wifi_connect_idle_timeout_cb, + .name = "wifi_idle_to", + }; + esp_timer_create_args_t ap_stop_timer_args = { + .callback = wifi_connect_ap_stop_timer_cb, + .name = "wifi_ap_stop", + }; + ESP_RETURN_ON_ERROR(esp_timer_create(&connect_timer_args, &s_ctx.connect_timer), TAG, "connect timer create failed"); + ESP_RETURN_ON_ERROR(esp_timer_create(&idle_timer_args, &s_ctx.idle_timer), TAG, "idle timer create failed"); + ESP_RETURN_ON_ERROR(esp_timer_create(&ap_stop_timer_args, &s_ctx.ap_stop_timer), TAG, "ap stop timer create failed"); + + if (wifi_connect_is_button_mode()) { + BaseType_t ok = xTaskCreate(wifi_connect_button_task, "wifi_btn", 3072, NULL, 4, &s_ctx.button_task); + ESP_RETURN_ON_FALSE(ok == pdPASS, ESP_ERR_NO_MEM, TAG, "button task create failed"); + } + + s_ctx.initialized = true; + + if (wifi_connect_is_always_on_mode()) { + wifi_connect_log_state_i("配网模式", "常驻配网(上电自动开启且不会自动关闭)"); + } else { + wifi_connect_log_state_i("配网模式", "按键触发配网(长按进入)"); + } + + err = wifi_connect_try_auto_connect(); + if (err != ESP_OK) { + char skip_msg[96] = {0}; + snprintf(skip_msg, sizeof(skip_msg), "自动重连已跳过,错误=%s", esp_err_to_name(err)); + wifi_connect_log_state_w("初始化后自动重连未执行", skip_msg); + } + + if (wifi_connect_is_always_on_mode()) { + wifi_connect_log_state_i("wifi-connect 初始化完成", "常驻配网模式已启用"); + err = wifi_connect_start(); + if (err != ESP_OK) { + char mode_msg[96] = {0}; + snprintf(mode_msg, sizeof(mode_msg), "自动开启配网失败,错误=%s", esp_err_to_name(err)); + wifi_connect_log_state_w("常驻配网启动失败", mode_msg); + return err; + } + } else { + wifi_connect_log_state_i("wifi-connect 初始化完成", "长按按键可进入配网"); + } + return ESP_OK; +} diff --git a/dependencies.lock b/dependencies.lock new file mode 100644 index 0000000..3af5c9f --- /dev/null +++ b/dependencies.lock @@ -0,0 +1,101 @@ +dependencies: + espressif/bh1750: + component_hash: e898130f6b2fc4bc0d6022a2e431752bae808b9c94d4cc91596e36ecaf4cb7c6 + dependencies: + - name: idf + require: private + version: '>=5.3' + source: + registry_url: https://components.espressif.com/ + type: service + version: 2.0.0 + espressif/cjson: + component_hash: 002c6d1872ee4c97d333938ebe107a29841cc847f9de89e676714bd2844057ea + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.7.19~1 + espressif/console_simple_init: + component_hash: b488b12318f3cb6e0b55b034bd12956926d45f0e1396442e820f8ece4776c306 + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.1.0 + espressif/esp_lvgl_port: + component_hash: b6360960f47b6776462e7092861b3ea66477ffb762a01baa0aecbb3d74cd50f4 + dependencies: + - name: idf + require: private + version: '>=5.1' + - name: lvgl/lvgl + registry_url: https://components.espressif.com + require: public + version: '>=8,<10' + source: + registry_url: https://components.espressif.com/ + type: service + version: 2.7.2 + espressif/mqtt: + component_hash: ffdad5659706b4dc14bc63f8eb73ef765efa015bf7e9adf71c813d52a2dc9342 + dependencies: + - name: idf + require: private + version: '>=5.3' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.0.0 + idf: + source: + type: idf + version: 5.5.2 + k0i05/esp_ahtxx: + component_hash: 34ecd4cc05b54a8ee64a813f80cf2b8efea6f22ecdbf7244640fc29627416fed + dependencies: + - name: idf + require: private + version: '>5.3.0' + - name: k0i05/esp_type_utils + registry_url: https://components.espressif.com + require: private + version: '>=1.0.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.2.7 + k0i05/esp_type_utils: + component_hash: 95d8ec40268e045f7e264d8035f451e53844b4a2f6d5f112ece6645c5effd639 + dependencies: + - name: idf + require: private + version: '>5.3.0' + source: + registry_url: https://components.espressif.com + type: service + version: 1.2.7 + lvgl/lvgl: + component_hash: 184e532558c1c45fefed631f3e235423d22582aafb4630f3e8885c35281a49ae + dependencies: [] + source: + registry_url: https://components.espressif.com + type: service + version: 9.5.0 +direct_dependencies: +- espressif/bh1750 +- espressif/cjson +- espressif/console_simple_init +- espressif/esp_lvgl_port +- espressif/mqtt +- idf +- k0i05/esp_ahtxx +manifest_hash: 718977b7c70d2e199530b4f98a537ecc03c07999f59c844987823a832f51b9b0 +target: esp32c3 +version: 2.0.0 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..5b41271 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "main.c" "auto_ctrl_thresholds.c" "auto_alerts.c" "status_web.c" + INCLUDE_DIRS "." + REQUIRES wifi-connect mqtt_control esp_lvgl_port lvgl_st7735s_use i2c_master_messager io_device_control console_simple_init console console_user_cmds ui esp_app_format cjson + ) diff --git a/main/auto_alerts.c b/main/auto_alerts.c new file mode 100644 index 0000000..3dbe981 --- /dev/null +++ b/main/auto_alerts.c @@ -0,0 +1,144 @@ +#include "auto_alerts.h" + +#include "esp_check.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" + +static const char *TAG = "auto_alerts"; // 日志标签 + +// 用于保护全局状态的自旋锁(临界区) +static portMUX_TYPE s_alerts_lock = portMUX_INITIALIZER_UNLOCKED; +// 用户注册的回调函数 +static auto_alert_callback_t s_callback = NULL; +// 回调函数的用户上下文指针 +static void *s_user_ctx = NULL; + +// 光照强度告警是否已激活 +static bool s_light_alarm_active = false; + +/** + * @brief 发送自动告警事件 + * + * @param metric 告警指标类型(如光照强度) + * @param state 告警状态(告警或恢复正常) + * @param value 当前测量值 + * @param threshold 触发告警的阈值 + */ +static void auto_alerts_emit(auto_alert_metric_t metric, + auto_alert_state_t state, + float value, + float threshold) +{ + auto_alert_event_t event = { + .metric = metric, + .state = state, + .value = value, + .threshold = threshold, + .timestamp_ms = esp_timer_get_time() / 1000, // 转换为毫秒时间戳 + }; + + auto_alert_callback_t callback = NULL; + void *user_ctx = NULL; + + // 进入临界区,安全读取回调和上下文 + taskENTER_CRITICAL(&s_alerts_lock); + callback = s_callback; + user_ctx = s_user_ctx; + taskEXIT_CRITICAL(&s_alerts_lock); + + if (callback != NULL) + { + callback(&event, user_ctx); // 调用用户注册的回调函数 + } + + // 打印日志信息 + ESP_LOGI(TAG, + "alert metric=%d state=%d value=%.1f threshold=%.1f", + (int)event.metric, + (int)event.state, + event.value, + event.threshold); +} + +/** + * @brief 初始化自动告警模块 + * + * 将所有告警状态重置为未激活。 + */ +void auto_alerts_init(void) +{ + taskENTER_CRITICAL(&s_alerts_lock); + s_light_alarm_active = false; + taskEXIT_CRITICAL(&s_alerts_lock); +} + +/** + * @brief 注册自动告警回调函数 + * + * @param callback 用户定义的回调函数 + * @param user_ctx 用户上下文指针 + * @return esp_err_t 总是返回 ESP_OK + */ +esp_err_t auto_alerts_register_callback(auto_alert_callback_t callback, void *user_ctx) +{ + taskENTER_CRITICAL(&s_alerts_lock); + s_callback = callback; + s_user_ctx = user_ctx; + taskEXIT_CRITICAL(&s_alerts_lock); + return ESP_OK; +} + +/** + * @brief 根据当前传感器数据和阈值评估是否触发或解除告警 + * + * @param light_valid 光照数据是否有效 + * @param light_lux 当前光照强度(单位:lux) + * @param thresholds 自动控制阈值配置结构体指针 + */ +void auto_alerts_evaluate(bool light_valid, + float light_lux, + const auto_ctrl_thresholds_t *thresholds) +{ + if (thresholds == NULL) + { + return; // 阈值为空,直接返回 + } + + // 处理光照强度告警逻辑 + if (light_valid) + { + bool emit_alarm = false; // 是否需要触发告警 + bool emit_recover = false; // 是否需要恢复通知 + + taskENTER_CRITICAL(&s_alerts_lock); + // 如果当前未告警,且光照强度低于开启补光灯的阈值,则触发告警 + if (!s_light_alarm_active && light_lux < thresholds->light_on_lux_below) + { + s_light_alarm_active = true; + emit_alarm = true; + } + // 如果当前处于告警状态,且光照强度高于关闭补光灯的阈值,则恢复 + else if (s_light_alarm_active && light_lux > thresholds->light_off_lux_above) + { + s_light_alarm_active = false; + emit_recover = true; + } + taskEXIT_CRITICAL(&s_alerts_lock); + + if (emit_alarm) + { + auto_alerts_emit(AUTO_ALERT_METRIC_LIGHT_INTENSITY, + AUTO_ALERT_STATE_ALARM, + light_lux, + thresholds->light_on_lux_below); + } + if (emit_recover) + { + auto_alerts_emit(AUTO_ALERT_METRIC_LIGHT_INTENSITY, + AUTO_ALERT_STATE_NORMAL, + light_lux, + thresholds->light_off_lux_above); + } + } +} \ No newline at end of file diff --git a/main/auto_alerts.h b/main/auto_alerts.h new file mode 100644 index 0000000..6ede308 --- /dev/null +++ b/main/auto_alerts.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#include "auto_ctrl_thresholds.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + AUTO_ALERT_METRIC_LIGHT_INTENSITY = 1, +} auto_alert_metric_t; + +typedef enum { + AUTO_ALERT_STATE_NORMAL = 0, + AUTO_ALERT_STATE_ALARM = 1, +} auto_alert_state_t; + +typedef struct { + auto_alert_metric_t metric; + auto_alert_state_t state; + float value; + float threshold; + int64_t timestamp_ms; +} auto_alert_event_t; + +typedef void (*auto_alert_callback_t)(const auto_alert_event_t *event, void *user_ctx); + +// Reset internal state at boot. +void auto_alerts_init(void); + +// Register callback sink (e.g. MQTT publisher). Passing NULL clears callback. +esp_err_t auto_alerts_register_callback(auto_alert_callback_t callback, void *user_ctx); + +// Evaluate current sensor values and emit edge-triggered alert events. +void auto_alerts_evaluate(bool light_valid, + float light_lux, + const auto_ctrl_thresholds_t *thresholds); + +#ifdef __cplusplus +} +#endif diff --git a/main/auto_ctrl_thresholds.c b/main/auto_ctrl_thresholds.c new file mode 100644 index 0000000..a3f3f42 --- /dev/null +++ b/main/auto_ctrl_thresholds.c @@ -0,0 +1,199 @@ +#include "auto_ctrl_thresholds.h" + +#include "freertos/FreeRTOS.h" +#include "esp_check.h" + +// 默认光照强度低于此值时开启补光灯(单位:lux) +#define DEFAULT_LIGHT_ON_LUX_BELOW 200.0f +// 默认光照强度高于此值时关闭补光灯(单位:lux) +#define DEFAULT_LIGHT_OFF_LUX_ABOVE 350.0f +// 默认温度低于此值时开启加热(单位:摄氏度) +#define DEFAULT_HOT_ON_TEMP_BELOW_C 18.0f +// 默认温度高于此值时关闭加热(单位:摄氏度) +#define DEFAULT_HOT_OFF_TEMP_ABOVE_C 20.0f +// 默认温度高于此值时开启制冷(单位:摄氏度) +#define DEFAULT_COOL_ON_TEMP_ABOVE_C 30.0f +// 默认温度低于此值时关闭制冷(单位:摄氏度) +#define DEFAULT_COOL_OFF_TEMP_BELOW_C 28.0f +// 默认湿度高于此值时开启风扇(单位:%RH) +#define DEFAULT_FAN_ON_HUMIDITY_ABOVE_PCT 80.0f +// 默认湿度低于此值时关闭风扇(单位:%RH) +#define DEFAULT_FAN_OFF_HUMIDITY_BELOW_PCT 70.0f + +// 用于保护阈值数据的自旋锁(临界区) +static portMUX_TYPE s_thresholds_lock = portMUX_INITIALIZER_UNLOCKED; + +// 全局阈值配置结构体,初始化为默认值 +static auto_ctrl_thresholds_t s_thresholds = { + .light_on_lux_below = DEFAULT_LIGHT_ON_LUX_BELOW, + .light_off_lux_above = DEFAULT_LIGHT_OFF_LUX_ABOVE, + .hot_on_temp_below_c = DEFAULT_HOT_ON_TEMP_BELOW_C, + .hot_off_temp_above_c = DEFAULT_HOT_OFF_TEMP_ABOVE_C, + .cool_on_temp_above_c = DEFAULT_COOL_ON_TEMP_ABOVE_C, + .cool_off_temp_below_c = DEFAULT_COOL_OFF_TEMP_BELOW_C, + .fan_on_humidity_above_pct = DEFAULT_FAN_ON_HUMIDITY_ABOVE_PCT, + .fan_off_humidity_below_pct = DEFAULT_FAN_OFF_HUMIDITY_BELOW_PCT, +}; + +/** + * @brief 验证自动控制阈值配置的有效性 + * + * 检查指针非空、数值范围合法、启停阈值满足 on < off 等条件。 + * + * @param cfg 待验证的阈值配置指针 + * @return esp_err_t 验证结果,ESP_OK 表示有效 + */ +static esp_err_t auto_ctrl_thresholds_validate(const auto_ctrl_thresholds_t *cfg) +{ + ESP_RETURN_ON_FALSE(cfg != NULL, ESP_ERR_INVALID_ARG, "auto_ctrl_thresholds", "cfg is null"); + + ESP_RETURN_ON_FALSE(cfg->light_on_lux_below >= 0.0f, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "light_on_lux_below out of range"); + ESP_RETURN_ON_FALSE(cfg->light_off_lux_above >= 0.0f, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "light_off_lux_above out of range"); + ESP_RETURN_ON_FALSE(cfg->light_on_lux_below < cfg->light_off_lux_above, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "light thresholds must satisfy on < off"); + + ESP_RETURN_ON_FALSE(cfg->hot_on_temp_below_c >= -40.0f && cfg->hot_on_temp_below_c <= 125.0f, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "hot_on_temp_below_c out of range"); + ESP_RETURN_ON_FALSE(cfg->hot_off_temp_above_c >= -40.0f && cfg->hot_off_temp_above_c <= 125.0f, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "hot_off_temp_above_c out of range"); + ESP_RETURN_ON_FALSE(cfg->hot_on_temp_below_c < cfg->hot_off_temp_above_c, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "hot thresholds must satisfy on < off"); + + ESP_RETURN_ON_FALSE(cfg->cool_off_temp_below_c >= -40.0f && cfg->cool_off_temp_below_c <= 125.0f, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "cool_off_temp_below_c out of range"); + ESP_RETURN_ON_FALSE(cfg->cool_on_temp_above_c >= -40.0f && cfg->cool_on_temp_above_c <= 125.0f, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "cool_on_temp_above_c out of range"); + ESP_RETURN_ON_FALSE(cfg->cool_off_temp_below_c < cfg->cool_on_temp_above_c, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "cool thresholds must satisfy off < on"); + + ESP_RETURN_ON_FALSE(cfg->hot_off_temp_above_c <= cfg->cool_off_temp_below_c, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "temperature thresholds overlap excessively"); + + ESP_RETURN_ON_FALSE(cfg->fan_on_humidity_above_pct >= 0.0f && cfg->fan_on_humidity_above_pct <= 100.0f, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "fan_on_humidity_above_pct out of range"); + ESP_RETURN_ON_FALSE(cfg->fan_off_humidity_below_pct >= 0.0f && cfg->fan_off_humidity_below_pct <= 100.0f, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "fan_off_humidity_below_pct out of range"); + ESP_RETURN_ON_FALSE(cfg->fan_off_humidity_below_pct < cfg->fan_on_humidity_above_pct, + ESP_ERR_INVALID_ARG, + "auto_ctrl_thresholds", + "fan humidity thresholds must satisfy off < on"); + + return ESP_OK; +} + +/** + * @brief 初始化阈值为默认值 + * + * 将全局阈值结构体重置为预设的默认配置。 + */ +void auto_ctrl_thresholds_init_defaults(void) +{ + const auto_ctrl_thresholds_t defaults = { + .light_on_lux_below = DEFAULT_LIGHT_ON_LUX_BELOW, + .light_off_lux_above = DEFAULT_LIGHT_OFF_LUX_ABOVE, + .hot_on_temp_below_c = DEFAULT_HOT_ON_TEMP_BELOW_C, + .hot_off_temp_above_c = DEFAULT_HOT_OFF_TEMP_ABOVE_C, + .cool_on_temp_above_c = DEFAULT_COOL_ON_TEMP_ABOVE_C, + .cool_off_temp_below_c = DEFAULT_COOL_OFF_TEMP_BELOW_C, + .fan_on_humidity_above_pct = DEFAULT_FAN_ON_HUMIDITY_ABOVE_PCT, + .fan_off_humidity_below_pct = DEFAULT_FAN_OFF_HUMIDITY_BELOW_PCT, + }; + + taskENTER_CRITICAL(&s_thresholds_lock); + s_thresholds = defaults; + taskEXIT_CRITICAL(&s_thresholds_lock); +} + +/** + * @brief 获取当前阈值配置 + * + * 安全地复制当前阈值到输出参数中。 + * + * @param out 输出参数,指向接收阈值的结构体 + */ +void auto_ctrl_thresholds_get(auto_ctrl_thresholds_t *out) +{ + if (out == NULL) { + return; + } + + taskENTER_CRITICAL(&s_thresholds_lock); + *out = s_thresholds; + taskEXIT_CRITICAL(&s_thresholds_lock); +} + +/** + * @brief 设置新的阈值配置 + * + * 验证输入配置有效性后,安全更新全局阈值。 + * + * @param cfg 新的阈值配置指针 + * @return esp_err_t 设置结果,ESP_OK 表示成功 + */ +esp_err_t auto_ctrl_thresholds_set(const auto_ctrl_thresholds_t *cfg) +{ + ESP_RETURN_ON_ERROR(auto_ctrl_thresholds_validate(cfg), "auto_ctrl_thresholds", "invalid thresholds"); + + taskENTER_CRITICAL(&s_thresholds_lock); + s_thresholds = *cfg; + taskEXIT_CRITICAL(&s_thresholds_lock); + return ESP_OK; +} + +/** + * @brief 通过独立参数设置阈值 + * + * 提供一种更便捷的阈值设置方式,内部封装为结构体后调用 set 接口。 + * + * @param light_on_lux_below 补光灯开启光照阈值(lux) + * @param light_off_lux_above 补光灯关闭光照阈值(lux) + * @return esp_err_t 设置结果 + */ +esp_err_t auto_ctrl_thresholds_set_values(float light_on_lux_below, + float light_off_lux_above, + float hot_on_temp_below_c, + float hot_off_temp_above_c, + float cool_on_temp_above_c, + float cool_off_temp_below_c, + float fan_on_humidity_above_pct, + float fan_off_humidity_below_pct) +{ + const auto_ctrl_thresholds_t cfg = { + .light_on_lux_below = light_on_lux_below, + .light_off_lux_above = light_off_lux_above, + .hot_on_temp_below_c = hot_on_temp_below_c, + .hot_off_temp_above_c = hot_off_temp_above_c, + .cool_on_temp_above_c = cool_on_temp_above_c, + .cool_off_temp_below_c = cool_off_temp_below_c, + .fan_on_humidity_above_pct = fan_on_humidity_above_pct, + .fan_off_humidity_below_pct = fan_off_humidity_below_pct, + }; + + return auto_ctrl_thresholds_set(&cfg); +} \ No newline at end of file diff --git a/main/auto_ctrl_thresholds.h b/main/auto_ctrl_thresholds.h new file mode 100644 index 0000000..e96ca3d --- /dev/null +++ b/main/auto_ctrl_thresholds.h @@ -0,0 +1,41 @@ +#pragma once + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + float light_on_lux_below; + float light_off_lux_above; + float hot_on_temp_below_c; + float hot_off_temp_above_c; + float cool_on_temp_above_c; + float cool_off_temp_below_c; + float fan_on_humidity_above_pct; + float fan_off_humidity_below_pct; +} auto_ctrl_thresholds_t; + +// Initializes default thresholds once at boot. +void auto_ctrl_thresholds_init_defaults(void); + +// Thread-safe snapshot read, intended for control loop usage. +void auto_ctrl_thresholds_get(auto_ctrl_thresholds_t *out); + +// Thread-safe full update with range/order validation. +esp_err_t auto_ctrl_thresholds_set(const auto_ctrl_thresholds_t *cfg); + +// Convenience API for MQTT callback usage. +esp_err_t auto_ctrl_thresholds_set_values(float light_on_lux_below, + float light_off_lux_above, + float hot_on_temp_below_c, + float hot_off_temp_above_c, + float cool_on_temp_above_c, + float cool_off_temp_below_c, + float fan_on_humidity_above_pct, + float fan_off_humidity_below_pct); + +#ifdef __cplusplus +} +#endif diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..ce12739 --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,23 @@ +## IDF Component Manager Manifest File +dependencies: + ## Required IDF version + idf: + version: '>=4.1.0' + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true + espressif/esp_lvgl_port: ^2.7.2 + espressif/bh1750: ^2.0.0 + k0i05/esp_ahtxx: ^1.2.7 + espressif/console_simple_init: ^1.1.0 + + espressif/mqtt: ^1.0.0 + espressif/cjson: ^1.7.19 diff --git a/main/main.c b/main/main.c new file mode 100644 index 0000000..742ee93 --- /dev/null +++ b/main/main.c @@ -0,0 +1,780 @@ +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_check.h" +#include "esp_log.h" +#include "wifi-connect.h" +#include "lvgl_st7735s_use.h" +#include "i2c_master_messager.h" +#include "io_device_control.h" +#include "console_simple_init.h" // 提供 console_cmd_user_register 和 console_cmd_all_register +#include "console_user_cmds.h" +#include "ui.h" // 使用EEZStudio提供的ui组件,便于后续扩展 +#include "esp_lvgl_port.h" +#include "vars.h" // 定义全局变量接口 +#include "auto_ctrl_thresholds.h" +#include "auto_alerts.h" +#include "mqtt_control.h" // MQTT 控制接口 +#include "status_web.h" + +// 配置宏定义:BH1750 光照传感器是否启用(默认禁用) +#ifndef CONFIG_I2C_MASTER_MESSAGER_BH1750_ENABLE +#define CONFIG_I2C_MASTER_MESSAGER_BH1750_ENABLE 0 +#endif + +// 配置宏定义:AHT30 温湿度传感器是否启用(默认禁用) +#ifndef CONFIG_I2C_MASTER_MESSAGER_AHT30_ENABLE +#define CONFIG_I2C_MASTER_MESSAGER_AHT30_ENABLE 0 +#endif + +// 配置宏定义:BH1750 读取周期(毫秒,默认500ms) +#ifndef CONFIG_I2C_MASTER_MESSAGER_BH1750_READ_PERIOD_MS +#define CONFIG_I2C_MASTER_MESSAGER_BH1750_READ_PERIOD_MS 500 +#endif + +// 配置宏定义:AHT30 读取周期(毫秒,默认2000ms) +#ifndef CONFIG_I2C_MASTER_MESSAGER_AHT30_READ_PERIOD_MS +#define CONFIG_I2C_MASTER_MESSAGER_AHT30_READ_PERIOD_MS 2000 +#endif + +// 配置宏定义:I2C 是否启用内部上拉电阻(默认启用) +#ifndef CONFIG_I2C_MASTER_MESSAGER_ENABLE_INTERNAL_PULLUP +#define CONFIG_I2C_MASTER_MESSAGER_ENABLE_INTERNAL_PULLUP 1 +#endif + +// I2C 端口配置 +#define BOTANY_I2C_PORT I2C_NUM_0 +// I2C SCL 引脚 +#define BOTANY_I2C_SCL_GPIO GPIO_NUM_5 +// I2C SDA 引脚 +#define BOTANY_I2C_SDA_GPIO GPIO_NUM_4 +// BH1750 使能标志 +#define BOTANY_BH1750_ENABLE CONFIG_I2C_MASTER_MESSAGER_BH1750_ENABLE +// AHT30 使能标志 +#define BOTANY_AHT30_ENABLE CONFIG_I2C_MASTER_MESSAGER_AHT30_ENABLE +// BH1750 读取周期 +#define BOTANY_BH1750_PERIOD_MS CONFIG_I2C_MASTER_MESSAGER_BH1750_READ_PERIOD_MS +// AHT30 读取周期 +#define BOTANY_AHT30_PERIOD_MS CONFIG_I2C_MASTER_MESSAGER_AHT30_READ_PERIOD_MS +// I2C 内部上拉使能 +#define BOTANY_I2C_INTERNAL_PULLUP CONFIG_I2C_MASTER_MESSAGER_ENABLE_INTERNAL_PULLUP +// MQTT 告警主题 +#define BOTANY_MQTT_ALERT_TOPIC "topic/alert/esp32_iothome_001" +// MQTT 遥测数据上报周期(毫秒) +#define BOTANY_MQTT_TELEMETRY_PERIOD_MS 5000 +#define BOTANY_STATUS_WEB_PORT 8080 + +// 日志标签 +static const char *TAG = "main"; + +// 全局变量:存储空气温度字符串 +static char s_air_temp[16]; +// 全局变量:存储空气湿度字符串 +static char s_air_hum[16]; +// 全局变量:存储光照强度字符串 +static char s_lux[16]; +// 全局变量:风扇状态(true=开启,false=关闭) +static bool s_fan_on = false; +// 全局变量:补光灯状态(true=开启,false=关闭) +static bool s_light_on = false; +// 全局变量:加热状态(true=开启,false=关闭) +static bool s_hot_on = false; +// 全局变量:制冷状态(true=开启,false=关闭) +static bool s_cool_on = false; +// 全局变量:自动控制模式使能(true=自动,false=手动) +static bool s_auto_control_enabled = true; +static bool s_i2c_ready = false; +static uint32_t s_main_loop_counter = 0; + +/** + * @brief 发布当前完整状态快照(含阈值)到传感器主题 + */ +static esp_err_t publish_telemetry_snapshot(void) +{ + if (!mqtt_control_is_connected()) + { + return ESP_ERR_INVALID_STATE; + } + + auto_ctrl_thresholds_t thresholds = {0}; + auto_ctrl_thresholds_get(&thresholds); + + char telemetry_payload[512] = {0}; + int len = snprintf(telemetry_payload, + sizeof(telemetry_payload), + "{\"temp\":\"%s\",\"hum\":\"%s\",\"lux\":\"%s\",\"fan\":\"%s\",\"light\":\"%s\",\"hot\":\"%s\",\"cool\":\"%s\",\"mode\":\"%s\",\"light_on\":%.1f,\"light_off\":%.1f,\"hot_on_temp\":%.1f,\"hot_off_temp\":%.1f,\"cool_on_temp\":%.1f,\"cool_off_temp\":%.1f,\"fan_on_hum\":%.1f,\"fan_off_hum\":%.1f}", + s_air_temp, + s_air_hum, + s_lux, + s_fan_on ? "on" : "off", + s_light_on ? "on" : "off", + s_hot_on ? "on" : "off", + s_cool_on ? "on" : "off", + s_auto_control_enabled ? "auto" : "manual", + thresholds.light_on_lux_below, + thresholds.light_off_lux_above, + thresholds.hot_on_temp_below_c, + thresholds.hot_off_temp_above_c, + thresholds.cool_on_temp_above_c, + thresholds.cool_off_temp_below_c, + thresholds.fan_on_humidity_above_pct, + thresholds.fan_off_humidity_below_pct); + if (len <= 0 || len >= (int)sizeof(telemetry_payload)) + { + return ESP_ERR_INVALID_SIZE; + } + + return mqtt_control_publish_sensor(telemetry_payload, 0, 0); +} + +static void update_status_web_snapshot(void) +{ + status_web_snapshot_t snap = {0}; + snprintf(snap.temp, sizeof(snap.temp), "%s", s_air_temp[0] ? s_air_temp : "--"); + snprintf(snap.hum, sizeof(snap.hum), "%s", s_air_hum[0] ? s_air_hum : "--"); + snprintf(snap.lux, sizeof(snap.lux), "%s", s_lux[0] ? s_lux : "--"); + snap.fan_on = s_fan_on; + snap.light_on = s_light_on; + snap.hot_on = s_hot_on; + snap.cool_on = s_cool_on; + snap.auto_mode = s_auto_control_enabled; + + auto_ctrl_thresholds_t thresholds = {0}; + auto_ctrl_thresholds_get(&thresholds); + snap.light_on_threshold = thresholds.light_on_lux_below; + snap.light_off_threshold = thresholds.light_off_lux_above; + snap.hot_on_temp_threshold = thresholds.hot_on_temp_below_c; + snap.hot_off_temp_threshold = thresholds.hot_off_temp_above_c; + snap.cool_on_temp_threshold = thresholds.cool_on_temp_above_c; + snap.cool_off_temp_threshold = thresholds.cool_off_temp_below_c; + snap.fan_on_hum_threshold = thresholds.fan_on_humidity_above_pct; + snap.fan_off_hum_threshold = thresholds.fan_off_humidity_below_pct; + snap.i2c_ready = s_i2c_ready; + snap.loop_counter = s_main_loop_counter; + + esp_err_t ret = status_web_update(&snap); + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) + { + ESP_LOGW(TAG, "status web update failed: %s", esp_err_to_name(ret)); + } +} + +/** + * @brief MQTT 控制命令处理函数 + * + * 处理来自 MQTT 的控制命令,包括模式切换、阈值更新、水泵和补光灯控制。 + * + * @param cmd 指向 MQTT 控制命令结构体的指针 + * @param user_ctx 用户上下文(未使用) + * @return esp_err_t 处理结果 + */ +static esp_err_t mqtt_control_command_handler(const mqtt_control_command_t *cmd, void *user_ctx) +{ + (void)user_ctx; + ESP_RETURN_ON_FALSE(cmd != NULL, ESP_ERR_INVALID_ARG, TAG, "cmd is null"); + esp_err_t final_ret = ESP_OK; + bool has_any_control = cmd->has_mode || cmd->has_thresholds || cmd->has_fan || cmd->has_light || cmd->has_hot || cmd->has_cool; + + // 处理模式切换命令 + if (cmd->has_mode) + { + s_auto_control_enabled = cmd->auto_mode; + ESP_LOGI(TAG, "MQTT 控制模式切换: %s", s_auto_control_enabled ? "auto" : "manual"); + } + + // 处理阈值更新命令 + if (cmd->has_thresholds) + { + esp_err_t ret = auto_ctrl_thresholds_set_values(cmd->light_on_lux, + cmd->light_off_lux, + cmd->hot_on_temp_c, + cmd->hot_off_temp_c, + cmd->cool_on_temp_c, + cmd->cool_off_temp_c, + cmd->fan_on_hum_pct, + cmd->fan_off_hum_pct); + if (ret == ESP_OK) + { + ESP_LOGI(TAG, + "MQTT 更新阈值: light(%.1f/%.1f) hot(%.1f/%.1f) cool(%.1f/%.1f) fan_hum(%.1f/%.1f)", + cmd->light_on_lux, + cmd->light_off_lux, + cmd->hot_on_temp_c, + cmd->hot_off_temp_c, + cmd->cool_on_temp_c, + cmd->cool_off_temp_c, + cmd->fan_on_hum_pct, + cmd->fan_off_hum_pct); + } + else + { + ESP_LOGE(TAG, "设置阈值失败: %s", esp_err_to_name(ret)); + final_ret = ret; + } + } + + // 处理风扇控制命令 + if (cmd->has_fan) + { + esp_err_t ret = io_device_control_set_fan(cmd->fan_on); + if (ret == ESP_OK) + { + s_fan_on = cmd->fan_on; + ESP_LOGI(TAG, "MQTT 控制风扇: %s", cmd->fan_on ? "on" : "off"); + } + else + { + ESP_LOGE(TAG, "MQTT 控制风扇失败: %s", esp_err_to_name(ret)); + final_ret = ret; + } + } + + // 处理补光灯控制命令 + if (cmd->has_light) + { + esp_err_t ret = io_device_control_set_light(cmd->light_on); + if (ret == ESP_OK) + { + s_light_on = cmd->light_on; + ESP_LOGI(TAG, "MQTT 控制补光灯: %s", cmd->light_on ? "on" : "off"); + } + else + { + ESP_LOGE(TAG, "MQTT 控制补光灯失败: %s", esp_err_to_name(ret)); + final_ret = ret; + } + } + + if (cmd->has_hot) + { + esp_err_t ret = io_device_control_set_hot(cmd->hot_on); + if (ret == ESP_OK) + { + s_hot_on = cmd->hot_on; + if (s_hot_on) { + s_cool_on = false; + (void)io_device_control_set_cool(false); + } + ESP_LOGI(TAG, "MQTT 控制加热: %s", cmd->hot_on ? "on" : "off"); + } + else + { + ESP_LOGE(TAG, "MQTT 控制加热失败: %s", esp_err_to_name(ret)); + final_ret = ret; + } + } + + if (cmd->has_cool) + { + esp_err_t ret = io_device_control_set_cool(cmd->cool_on); + if (ret == ESP_OK) + { + s_cool_on = cmd->cool_on; + if (s_cool_on) { + s_hot_on = false; + (void)io_device_control_set_hot(false); + } + ESP_LOGI(TAG, "MQTT 控制制冷: %s", cmd->cool_on ? "on" : "off"); + } + else + { + ESP_LOGE(TAG, "MQTT 控制制冷失败: %s", esp_err_to_name(ret)); + final_ret = ret; + } + } + + // 任何控制指令处理后都立即上报最新状态(含阈值)作为回复。 + if (has_any_control) + { + esp_err_t pub_ret = publish_telemetry_snapshot(); + if (pub_ret != ESP_OK) + { + ESP_LOGW(TAG, "控制后立即上报失败: %s", esp_err_to_name(pub_ret)); + if (final_ret == ESP_OK) + { + final_ret = pub_ret; + } + } + } + + return final_ret; +} + +/** + * @brief 将告警指标类型转换为字符串 + * + * @param metric 告警指标类型 + * @return const char* 对应的字符串表示 + */ +static const char *alert_metric_text(auto_alert_metric_t metric) +{ + switch (metric) + { + case AUTO_ALERT_METRIC_LIGHT_INTENSITY: + return "light"; + default: + return "unknown"; + } +} + +/** + * @brief 将告警状态转换为字符串 + * + * @param state 告警状态 + * @return const char* 对应的字符串表示 + */ +static const char *alert_state_text(auto_alert_state_t state) +{ + switch (state) + { + case AUTO_ALERT_STATE_NORMAL: + return "normal"; + case AUTO_ALERT_STATE_ALARM: + return "alarm"; + default: + return "unknown"; + } +} + +/** + * @brief 自动告警 MQTT 回调函数 + * + * 当自动告警模块触发事件时,通过此函数将告警信息以 JSON 格式发布到 MQTT。 + * + * @param event 指向告警事件结构体的指针 + * @param user_ctx 用户上下文(未使用) + */ +static void auto_alert_mqtt_callback(const auto_alert_event_t *event, void *user_ctx) +{ + (void)user_ctx; + if (event == NULL) + { + return; + } + + // 使用明文发送报警简单的 JSON 字符串,格式示例:{"metric":"light","state":"alarm"} + char payload[64] = {0}; + int len = snprintf(payload, + sizeof(payload), + "{\"metric\":\"%s\",\"state\":\"%s\"}", + alert_metric_text(event->metric), + alert_state_text(event->state)); + if (len <= 0 || len >= (int)sizeof(payload)) + { + return; + } + + if (!mqtt_control_is_connected()) + { + return; + } + + esp_err_t ret = mqtt_control_publish(BOTANY_MQTT_ALERT_TOPIC, payload, 1, 0); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "告警 MQTT 发布失败: %s", esp_err_to_name(ret)); + } +} + +/** + * @brief 自动控制逻辑更新函数 + * + * 根据当前传感器数据和阈值,决定是否需要开启或关闭风扇、补光灯、加热和制冷。 + * + * @param temp_valid 温度数据是否有效 + * @param temp_c 当前温度(摄氏度) + * @param hum_valid 湿度数据是否有效 + * @param hum_pct 当前湿度(%RH) + * @param light_valid 光照数据是否有效 + * @param light_lux 当前光照强度(lux) + * @param thresholds 指向阈值配置结构体的指针 + * @param fan_on 指向当前风扇状态的指针(输入/输出) + * @param light_on 指向当前补光灯状态的指针(输入/输出) + * @param hot_on 指向当前加热状态的指针(输入/输出) + * @param cool_on 指向当前制冷状态的指针(输入/输出) + */ +static void auto_control_update(bool temp_valid, + float temp_c, + bool hum_valid, + float hum_pct, + bool light_valid, + float light_lux, + const auto_ctrl_thresholds_t *thresholds, + bool *fan_on, + bool *light_on, + bool *hot_on, + bool *cool_on) +{ + bool desired_fan = *fan_on; + bool desired_light = *light_on; + bool desired_hot = *hot_on; + bool desired_cool = *cool_on; + + // 根据湿度决定风扇状态 + if (hum_valid) + { + if (!desired_fan && hum_pct > thresholds->fan_on_humidity_above_pct) + { + desired_fan = true; + } + else if (desired_fan && hum_pct < thresholds->fan_off_humidity_below_pct) + { + desired_fan = false; + } + } + + // 根据温度决定加热/制冷状态 + if (temp_valid) + { + if (!desired_hot && temp_c < thresholds->hot_on_temp_below_c) + { + desired_hot = true; + } + else if (desired_hot && temp_c > thresholds->hot_off_temp_above_c) + { + desired_hot = false; + } + + if (!desired_cool && temp_c > thresholds->cool_on_temp_above_c) + { + desired_cool = true; + } + else if (desired_cool && temp_c < thresholds->cool_off_temp_below_c) + { + desired_cool = false; + } + } + + // 防止加热与制冷同时开启 + if (desired_hot) + { + desired_cool = false; + } + else if (desired_cool) + { + desired_hot = false; + } + + // 根据光照强度决定补光灯状态 + if (light_valid) + { + if (!desired_light && light_lux < thresholds->light_on_lux_below) + { + desired_light = true; + } + else if (desired_light && light_lux > thresholds->light_off_lux_above) + { + desired_light = false; + } + } + + // 如果补光灯状态需要改变,则执行控制 + if (desired_light != *light_on) + { + esp_err_t ret = io_device_control_set_light(desired_light); + if (ret == ESP_OK) + { + *light_on = desired_light; + ESP_LOGI(TAG, + "自动控制: 补光灯%s (光照=%.1f lux)", + desired_light ? "开启" : "关闭", + light_lux); + } + else + { + ESP_LOGE(TAG, "自动控制: 补光灯控制失败: %s", esp_err_to_name(ret)); + } + } + + if (desired_hot != *hot_on) + { + esp_err_t ret = io_device_control_set_hot(desired_hot); + if (ret == ESP_OK) + { + *hot_on = desired_hot; + ESP_LOGI(TAG, + "自动控制: 加热%s (温度=%.1f C)", + desired_hot ? "开启" : "关闭", + temp_c); + } + else + { + ESP_LOGE(TAG, "自动控制: 加热控制失败: %s", esp_err_to_name(ret)); + } + } + + if (desired_cool != *cool_on) + { + esp_err_t ret = io_device_control_set_cool(desired_cool); + if (ret == ESP_OK) + { + *cool_on = desired_cool; + ESP_LOGI(TAG, + "自动控制: 制冷%s (温度=%.1f C)", + desired_cool ? "开启" : "关闭", + temp_c); + } + else + { + ESP_LOGE(TAG, "自动控制: 制冷控制失败: %s", esp_err_to_name(ret)); + } + } + + if (desired_fan != *fan_on) + { + esp_err_t ret = io_device_control_set_fan(desired_fan); + if (ret == ESP_OK) + { + *fan_on = desired_fan; + ESP_LOGI(TAG, + "自动控制: 风扇%s (湿度=%.1f%%)", + desired_fan ? "开启" : "关闭", + hum_pct); + } + else + { + ESP_LOGE(TAG, "自动控制: 风扇控制失败: %s", esp_err_to_name(ret)); + } + } +} + +/** + * @brief UI 任务函数 + * + * 单页面模式下仅刷新 UI;多页面时每3秒切换一次。 + * + * @param arg 任务参数(未使用) + */ +static void ui_task(void *arg) +{ + (void)arg; + + const bool multi_screen = (_SCREEN_ID_LAST > _SCREEN_ID_FIRST); + uint32_t elapsed_ms = 0; + enum ScreensEnum current = SCREEN_ID_MAIN; + const uint32_t switch_period_ms = 3000; // 每3秒切一次 + + for (;;) + { + lvgl_port_lock(0); + ui_tick(); + + elapsed_ms += 20; + if (multi_screen && elapsed_ms >= switch_period_ms) { + elapsed_ms = 0; + + // 多页面时按顺序轮播 + if (current >= _SCREEN_ID_LAST) { + current = _SCREEN_ID_FIRST; + } else { + current = (enum ScreensEnum)(current + 1); + } + + loadScreen(current); + } + + lvgl_port_unlock(); + vTaskDelay(pdMS_TO_TICKS(20)); + } +} + +/** + * @brief 等待 Wi-Fi 连接成功 + * + * 在初始化 console 之前,确保 Wi-Fi 已连接成功,最多等待120秒。 + */ +static void wait_for_wifi_connected(void) +{ + const uint32_t timeout_s = 120; + uint32_t elapsed_half_s = 0; + + ESP_LOGI(TAG, "等待 Wi-Fi 连接成功后再初始化 console..."); + while (wifi_connect_get_status() != WIFI_CONNECT_STATUS_CONNECTED) + { + if (elapsed_half_s >= (timeout_s * 2)) + { + ESP_LOGW(TAG, "等待 Wi-Fi 超时(%" PRIu32 "s),继续初始化 console", timeout_s); + return; + } + + vTaskDelay(pdMS_TO_TICKS(500)); + elapsed_half_s++; + + // 每 5 秒打印一次等待状态,避免日志刷屏。 + if ((elapsed_half_s % 10) == 0) + { + ESP_LOGI(TAG, "仍在等待 Wi-Fi 连接(%" PRIu32 "s)", elapsed_half_s / 2); + } + } + + ESP_LOGI(TAG, "Wi-Fi 已连接,开始初始化 console"); +} + +/** + * @brief 主函数 + * + * 系统启动入口,初始化所有组件并进入主循环。 + */ +void app_main(void) +{ + // 初始化 Wi-Fi 配网组件,支持长按按键进入配网 + ESP_ERROR_CHECK(wifi_connect_init()); + printf("设备启动完成:长按按键进入配网模式,手机连接 ESP32-* 后访问 http://192.168.4.1\n"); + + // 启动 LVGL 演示程序,显示简单的界面 + ESP_ERROR_CHECK(start_lvgl_demo()); + + // 初始化 UI 组件(需在 LVGL 锁内进行对象创建) + lvgl_port_lock(0); + ui_init(); + lvgl_port_unlock(); + + BaseType_t ui_task_ok = xTaskCreate(ui_task, "ui_task", 4096, NULL, 5, NULL); + ESP_ERROR_CHECK(ui_task_ok == pdPASS ? ESP_OK : ESP_FAIL); + + // 初始化 IO 设备控制组件(风扇/补光灯/加热/制冷,高电平有效) + ESP_ERROR_CHECK(io_device_control_init()); + + i2c_master_messager_config_t i2c_cfg = { + .i2c_port = BOTANY_I2C_PORT, + .scl_io_num = BOTANY_I2C_SCL_GPIO, + .sda_io_num = BOTANY_I2C_SDA_GPIO, + .i2c_enable_internal_pullup = BOTANY_I2C_INTERNAL_PULLUP, + .bh1750_enable = BOTANY_BH1750_ENABLE, + .aht30_enable = BOTANY_AHT30_ENABLE, + .bh1750_read_period_ms = BOTANY_BH1750_PERIOD_MS, + .aht30_read_period_ms = BOTANY_AHT30_PERIOD_MS, + .bh1750_mode = BH1750_CONTINUE_1LX_RES, + + }; + + bool i2c_ready = false; + esp_err_t ret = i2c_master_messager_init(&i2c_cfg); + if (ret == ESP_OK) + { + ret = i2c_master_messager_start(); + } + + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "I2C 传感器管理启动失败: %s", esp_err_to_name(ret)); + ESP_LOGW(TAG, "请检查 I2C 引脚/上拉电阻/端口占用情况,系统将继续运行但不采集传感器"); + ESP_ERROR_CHECK(lvgl_st7735s_set_center_text("I2C init failed")); + } + else + { + i2c_ready = true; + s_i2c_ready = true; + } + + // 按需求:仅在 Wi-Fi 确认连通后再初始化 MQTT和console。 + wait_for_wifi_connected(); + + // 独立状态网页(端口 8080),与配网页面(端口 80)互不干扰。 + ESP_ERROR_CHECK(status_web_start(BOTANY_STATUS_WEB_PORT)); + ESP_ERROR_CHECK(status_web_register_control_handler(mqtt_control_command_handler, NULL)); + + ESP_ERROR_CHECK(mqtt_control_register_command_handler(mqtt_control_command_handler, NULL)); + ESP_ERROR_CHECK(mqtt_control_start()); // 启动 MQTT 客户端 + + ESP_ERROR_CHECK(console_cmd_init()); + ESP_ERROR_CHECK(console_user_cmds_register()); + ESP_ERROR_CHECK(console_cmd_all_register()); // 可选:自动注册插件命令 + ESP_ERROR_CHECK(console_cmd_start()); + + auto_ctrl_thresholds_init_defaults(); + auto_alerts_init(); + ESP_ERROR_CHECK(auto_alerts_register_callback(auto_alert_mqtt_callback, NULL)); + auto_ctrl_thresholds_t thresholds = {0}; + auto_ctrl_thresholds_get(&thresholds); + + uint32_t telemetry_elapsed_ms = 0; + ESP_LOGI(TAG, + "自动控制阈值: light(%.1f/%.1f) hot(%.1f/%.1f) cool(%.1f/%.1f) fan_hum(%.1f/%.1f)", + thresholds.light_on_lux_below, + thresholds.light_off_lux_above, + thresholds.hot_on_temp_below_c, + thresholds.hot_off_temp_above_c, + thresholds.cool_on_temp_above_c, + thresholds.cool_off_temp_below_c, + thresholds.fan_on_humidity_above_pct, + thresholds.fan_off_humidity_below_pct); + + for (;;) + { + s_main_loop_counter++; + + // 预留给 MQTT 回调动态更新阈值:每个周期读取最新配置。 + auto_ctrl_thresholds_get(&thresholds); + + bool light_valid = false; + float light_lux = 0.0f; + bool temp_valid = false; + float temp_c = 0.0f; + bool hum_valid = false; + float hum_pct = 0.0f; + + i2c_master_messager_data_t sensor_data = {0}; + if (i2c_ready && i2c_master_messager_get_data(&sensor_data) == ESP_OK) + { + // 读取成功 + if (sensor_data.aht30.valid) + { + temp_valid = true; + hum_valid = true; + temp_c = sensor_data.aht30.temperature_c; + hum_pct = sensor_data.aht30.humidity_rh; + snprintf(s_air_temp, sizeof(s_air_temp), "%.1f", sensor_data.aht30.temperature_c); + set_var_air_temperature(s_air_temp); + + snprintf(s_air_hum, sizeof(s_air_hum), "%.1f", sensor_data.aht30.humidity_rh); + set_var_air_humidity(s_air_hum); + } + if (sensor_data.bh1750.valid) + { + light_valid = true; + light_lux = sensor_data.bh1750.lux; + snprintf(s_lux, sizeof(s_lux), "%.0f", sensor_data.bh1750.lux); + set_var_light_intensity(s_lux); + } + } + + if (s_auto_control_enabled) + { + auto_control_update(temp_valid, + temp_c, + hum_valid, + hum_pct, + light_valid, + light_lux, + &thresholds, + &s_fan_on, + &s_light_on, + &s_hot_on, + &s_cool_on); + + } + + // 预留给 MQTT:回调注册后可在此处收到边沿告警事件并发布。 + auto_alerts_evaluate(light_valid, + light_lux, + &thresholds); + + update_status_web_snapshot(); + + telemetry_elapsed_ms += 1000; + if (telemetry_elapsed_ms >= BOTANY_MQTT_TELEMETRY_PERIOD_MS) + { + telemetry_elapsed_ms = 0; + esp_err_t pub_ret = publish_telemetry_snapshot(); + if (pub_ret != ESP_OK && pub_ret != ESP_ERR_INVALID_STATE) + { + ESP_LOGW(TAG, "周期状态上报失败: %s", esp_err_to_name(pub_ret)); + } + } + + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} \ No newline at end of file diff --git a/main/status_web.c b/main/status_web.c new file mode 100644 index 0000000..b783bd5 --- /dev/null +++ b/main/status_web.c @@ -0,0 +1,584 @@ +#include "status_web.h" + +#include +#include +#include + +#include "cJSON.h" +#include "auto_ctrl_thresholds.h" +#include "esp_check.h" +#include "esp_http_server.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_netif_ip_addr.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "mqtt_control.h" +#include "wifi-connect.h" + +static const char *TAG = "status_web"; + +static httpd_handle_t s_server = NULL; +static SemaphoreHandle_t s_lock = NULL; +static uint64_t s_snapshot_update_ms = 0; +static mqtt_control_command_handler_t s_control_handler = NULL; +static void *s_control_user_ctx = NULL; +static status_web_snapshot_t s_snapshot = { + .temp = "--", + .hum = "--", + .lux = "--", + .fan_on = false, + .light_on = false, + .hot_on = false, + .cool_on = false, + .auto_mode = true, + .light_on_threshold = 100.0f, + .light_off_threshold = 350.0f, + .hot_on_temp_threshold = 18.0f, + .hot_off_temp_threshold = 20.0f, + .cool_on_temp_threshold = 30.0f, + .cool_off_temp_threshold = 28.0f, + .fan_on_hum_threshold = 80.0f, + .fan_off_hum_threshold = 70.0f, + .i2c_ready = false, + .loop_counter = 0, +}; + +static const char *wifi_status_text(wifi_connect_status_t status) +{ + switch (status) { + case WIFI_CONNECT_STATUS_IDLE: return "idle"; + case WIFI_CONNECT_STATUS_PROVISIONING: return "provisioning"; + case WIFI_CONNECT_STATUS_CONNECTING: return "connecting"; + case WIFI_CONNECT_STATUS_CONNECTED: return "connected"; + case WIFI_CONNECT_STATUS_FAILED: return "failed"; + case WIFI_CONNECT_STATUS_TIMEOUT: return "timeout"; + default: return "unknown"; + } +} + +static bool json_read_number(cJSON *root, const char *key, float *out) +{ + cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key); + if (!cJSON_IsNumber(item) || out == NULL) { + return false; + } + *out = (float)item->valuedouble; + return true; +} + +static bool json_read_bool(cJSON *root, const char *key, bool *out) +{ + cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key); + if (item == NULL || out == NULL) { + return false; + } + + if (cJSON_IsBool(item)) { + *out = cJSON_IsTrue(item); + return true; + } + if (cJSON_IsNumber(item)) { + *out = (item->valuedouble != 0.0); + return true; + } + if (cJSON_IsString(item) && item->valuestring != NULL) { + const char *s = item->valuestring; + if (strcasecmp(s, "on") == 0 || strcasecmp(s, "true") == 0 || strcmp(s, "1") == 0) { + *out = true; + return true; + } + if (strcasecmp(s, "off") == 0 || strcasecmp(s, "false") == 0 || strcmp(s, "0") == 0) { + *out = false; + return true; + } + } + return false; +} + +static bool json_read_mode_auto(cJSON *root, const char *key, bool *out_auto) +{ + cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key); + if (item == NULL || out_auto == NULL) { + return false; + } + + if (cJSON_IsString(item) && item->valuestring != NULL) { + if (strcasecmp(item->valuestring, "auto") == 0) { + *out_auto = true; + return true; + } + if (strcasecmp(item->valuestring, "manual") == 0) { + *out_auto = false; + return true; + } + } + + if (cJSON_IsBool(item)) { + *out_auto = cJSON_IsTrue(item); + return true; + } + if (cJSON_IsNumber(item)) { + *out_auto = (item->valuedouble != 0.0); + return true; + } + return false; +} + +static void get_sta_ip_text(char *out, size_t out_size) +{ + if (out == NULL || out_size == 0) { + return; + } + + snprintf(out, out_size, "--"); + esp_netif_t *sta = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); + if (sta == NULL) { + return; + } + + esp_netif_ip_info_t ip_info; + if (esp_netif_get_ip_info(sta, &ip_info) == ESP_OK) { + snprintf(out, + out_size, + IPSTR, + IP2STR(&ip_info.ip)); + } +} + +static const char *s_page_html = + "" + "" + "BotanicalBuddy Status" + "
" + "

智能粮仓终端设备状态总览

" + "
独立状态服务(port 8080),每3秒自动刷新
" + "

传感与控制

" + "
空气温度
--
" + "
空气湿度
--
" + "
光照强度
--
" + "
风扇
--
" + "
补光灯
--
" + "
加热
--
" + "
制冷
--
" + "
控制模式
--
manual
" + "
light_on/off
--
" + "
hot_on/off (C)
--
" + "
cool_on/off (C)
--
" + "
fan_hum_on/off (%)
--
" + "
" + "

参数设置

" + "
light_on
" + "
light_off
" + "
hot_on_temp
" + "
hot_off_temp
" + "
cool_on_temp
" + "
cool_off_temp
" + "
fan_on_hum
" + "
fan_off_hum
" + "
" + "

快捷控制

" + "
模式
" + "
风扇
" + "
补光灯
" + "
加热
" + "
制冷
" + "
" + "

连接与系统

" + "
Wi-Fi 状态
--
" + "
STA IP
--
" + "
MQTT 连接
--
" + "
I2C Ready
--
" + "
运行时长
--
" + "
" + "" + "
"; + +static esp_err_t status_root_handler(httpd_req_t *req) +{ + httpd_resp_set_type(req, "text/html"); + return httpd_resp_send(req, s_page_html, HTTPD_RESP_USE_STRLEN); +} + +static esp_err_t status_favicon_handler(httpd_req_t *req) +{ + httpd_resp_set_status(req, "204 No Content"); + return httpd_resp_send(req, NULL, 0); +} + +static esp_err_t status_api_handler(httpd_req_t *req) +{ + status_web_snapshot_t snap; + uint64_t snapshot_update_ms = 0; + xSemaphoreTake(s_lock, portMAX_DELAY); + snap = s_snapshot; + snapshot_update_ms = s_snapshot_update_ms; + xSemaphoreGive(s_lock); + + uint64_t now_ms = (uint64_t)(esp_timer_get_time() / 1000); + const bool mqtt_connected = mqtt_control_is_connected(); + const wifi_connect_status_t wifi_status = wifi_connect_get_status(); + char ip_text[16] = {0}; + get_sta_ip_text(ip_text, sizeof(ip_text)); + + uint64_t uptime_ms = now_ms; + if (snapshot_update_ms > 0 && now_ms < snapshot_update_ms) { + uptime_ms = snapshot_update_ms; + } + + char json[620]; + int len = snprintf(json, + sizeof(json), + "{\"temp\":\"%s\",\"hum\":\"%s\",\"lux\":\"%s\",\"fan\":\"%s\",\"light\":\"%s\",\"hot\":\"%s\",\"cool\":\"%s\",\"mode\":\"%s\",\"light_on\":%.1f,\"light_off\":%.1f,\"hot_on_temp\":%.1f,\"hot_off_temp\":%.1f,\"cool_on_temp\":%.1f,\"cool_off_temp\":%.1f,\"fan_on_hum\":%.1f,\"fan_off_hum\":%.1f,\"wifi_status\":\"%s\",\"sta_ip\":\"%s\",\"mqtt_connected\":%s,\"i2c_ready\":%s,\"uptime_ms\":%llu}", + snap.temp, + snap.hum, + snap.lux, + snap.fan_on ? "on" : "off", + snap.light_on ? "on" : "off", + snap.hot_on ? "on" : "off", + snap.cool_on ? "on" : "off", + snap.auto_mode ? "auto" : "manual", + snap.light_on_threshold, + snap.light_off_threshold, + snap.hot_on_temp_threshold, + snap.hot_off_temp_threshold, + snap.cool_on_temp_threshold, + snap.cool_off_temp_threshold, + snap.fan_on_hum_threshold, + snap.fan_off_hum_threshold, + wifi_status_text(wifi_status), + ip_text, + mqtt_connected ? "true" : "false", + snap.i2c_ready ? "true" : "false", + (unsigned long long)uptime_ms); + if (len <= 0 || len >= (int)sizeof(json)) { + return ESP_FAIL; + } + + httpd_resp_set_type(req, "application/json"); + return httpd_resp_sendstr(req, json); +} + +static esp_err_t status_config_handler(httpd_req_t *req) +{ + if (req->content_len <= 0 || req->content_len > 512) { + httpd_resp_set_status(req, "400 Bad Request"); + return httpd_resp_sendstr(req, "{\"ok\":false,\"error\":\"invalid content length\"}"); + } + + char body[513] = {0}; + int received = 0; + while (received < req->content_len) { + int ret = httpd_req_recv(req, body + received, req->content_len - received); + if (ret <= 0) { + httpd_resp_set_status(req, "400 Bad Request"); + return httpd_resp_sendstr(req, "{\"ok\":false,\"error\":\"read body failed\"}"); + } + received += ret; + } + + cJSON *root = cJSON_ParseWithLength(body, (size_t)req->content_len); + if (root == NULL) { + httpd_resp_set_status(req, "400 Bad Request"); + return httpd_resp_sendstr(req, "{\"ok\":false,\"error\":\"invalid json\"}"); + } + + float light_on = 0.0f; + float light_off = 0.0f; + float hot_on = 0.0f; + float hot_off = 0.0f; + float cool_on = 0.0f; + float cool_off = 0.0f; + float fan_on = 0.0f; + float fan_off = 0.0f; + + bool ok = json_read_number(root, "light_on", &light_on) && + json_read_number(root, "light_off", &light_off) && + json_read_number(root, "hot_on_temp", &hot_on) && + json_read_number(root, "hot_off_temp", &hot_off) && + json_read_number(root, "cool_on_temp", &cool_on) && + json_read_number(root, "cool_off_temp", &cool_off) && + json_read_number(root, "fan_on_hum", &fan_on) && + json_read_number(root, "fan_off_hum", &fan_off); + + if (!ok) { + cJSON_Delete(root); + httpd_resp_set_status(req, "400 Bad Request"); + return httpd_resp_sendstr(req, "{\"ok\":false,\"error\":\"missing threshold fields\"}"); + } + + esp_err_t set_ret = auto_ctrl_thresholds_set_values(light_on, + light_off, + hot_on, + hot_off, + cool_on, + cool_off, + fan_on, + fan_off); + cJSON_Delete(root); + + if (set_ret != ESP_OK) { + ESP_LOGW(TAG, + "web config reject: light(%.1f/%.1f) hot(%.1f/%.1f) cool(%.1f/%.1f) fan_hum(%.1f/%.1f), err=%s", + light_on, + light_off, + hot_on, + hot_off, + cool_on, + cool_off, + fan_on, + fan_off, + esp_err_to_name(set_ret)); + httpd_resp_set_status(req, "400 Bad Request"); + return httpd_resp_sendstr(req, "{\"ok\":false,\"error\":\"invalid threshold range\"}"); + } + + xSemaphoreTake(s_lock, portMAX_DELAY); + s_snapshot.light_on_threshold = light_on; + s_snapshot.light_off_threshold = light_off; + s_snapshot.hot_on_temp_threshold = hot_on; + s_snapshot.hot_off_temp_threshold = hot_off; + s_snapshot.cool_on_temp_threshold = cool_on; + s_snapshot.cool_off_temp_threshold = cool_off; + s_snapshot.fan_on_hum_threshold = fan_on; + s_snapshot.fan_off_hum_threshold = fan_off; + s_snapshot_update_ms = (uint64_t)(esp_timer_get_time() / 1000); + xSemaphoreGive(s_lock); + + ESP_LOGI(TAG, + "web config saved: light(%.1f/%.1f) hot(%.1f/%.1f) cool(%.1f/%.1f) fan_hum(%.1f/%.1f)", + light_on, + light_off, + hot_on, + hot_off, + cool_on, + cool_off, + fan_on, + fan_off); + + httpd_resp_set_type(req, "application/json"); + return httpd_resp_sendstr(req, "{\"ok\":true}"); +} + +static esp_err_t status_control_handler(httpd_req_t *req) +{ + if (s_control_handler == NULL) { + httpd_resp_set_status(req, "503 Service Unavailable"); + return httpd_resp_sendstr(req, "{\"ok\":false,\"error\":\"control handler not ready\"}"); + } + + if (req->content_len <= 0 || req->content_len > 256) { + httpd_resp_set_status(req, "400 Bad Request"); + return httpd_resp_sendstr(req, "{\"ok\":false,\"error\":\"invalid content length\"}"); + } + + char body[257] = {0}; + int received = 0; + while (received < req->content_len) { + int ret = httpd_req_recv(req, body + received, req->content_len - received); + if (ret <= 0) { + httpd_resp_set_status(req, "400 Bad Request"); + return httpd_resp_sendstr(req, "{\"ok\":false,\"error\":\"read body failed\"}"); + } + received += ret; + } + + cJSON *root = cJSON_ParseWithLength(body, (size_t)req->content_len); + if (root == NULL) { + httpd_resp_set_status(req, "400 Bad Request"); + return httpd_resp_sendstr(req, "{\"ok\":false,\"error\":\"invalid json\"}"); + } + + mqtt_control_command_t cmd = {0}; + cmd.has_mode = json_read_mode_auto(root, "mode", &cmd.auto_mode); + cmd.has_fan = json_read_bool(root, "fan", &cmd.fan_on); + cmd.has_light = json_read_bool(root, "light", &cmd.light_on); + cmd.has_hot = json_read_bool(root, "hot", &cmd.hot_on); + cmd.has_cool = json_read_bool(root, "cool", &cmd.cool_on); + cJSON_Delete(root); + + if (!(cmd.has_mode || cmd.has_fan || cmd.has_light || cmd.has_hot || cmd.has_cool)) { + httpd_resp_set_status(req, "400 Bad Request"); + return httpd_resp_sendstr(req, "{\"ok\":false,\"error\":\"no valid control fields\"}"); + } + + esp_err_t ret = s_control_handler(&cmd, s_control_user_ctx); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "web control apply failed: %s", esp_err_to_name(ret)); + httpd_resp_set_status(req, "500 Internal Server Error"); + return httpd_resp_sendstr(req, "{\"ok\":false,\"error\":\"control apply failed\"}"); + } + + xSemaphoreTake(s_lock, portMAX_DELAY); + if (cmd.has_mode) { + s_snapshot.auto_mode = cmd.auto_mode; + } + if (cmd.has_fan) { + s_snapshot.fan_on = cmd.fan_on; + } + if (cmd.has_light) { + s_snapshot.light_on = cmd.light_on; + } + if (cmd.has_hot) { + s_snapshot.hot_on = cmd.hot_on; + } + if (cmd.has_cool) { + s_snapshot.cool_on = cmd.cool_on; + } + s_snapshot_update_ms = (uint64_t)(esp_timer_get_time() / 1000); + xSemaphoreGive(s_lock); + + ESP_LOGI(TAG, + "web control ok: mode=%s fan=%s light=%s hot=%s cool=%s", + cmd.has_mode ? (cmd.auto_mode ? "auto" : "manual") : "-", + cmd.has_fan ? (cmd.fan_on ? "on" : "off") : "-", + cmd.has_light ? (cmd.light_on ? "on" : "off") : "-", + cmd.has_hot ? (cmd.hot_on ? "on" : "off") : "-", + cmd.has_cool ? (cmd.cool_on ? "on" : "off") : "-"); + + httpd_resp_set_type(req, "application/json"); + return httpd_resp_sendstr(req, "{\"ok\":true}"); +} + +esp_err_t status_web_start(uint16_t port) +{ + if (s_server != NULL) { + return ESP_OK; + } + + if (s_lock == NULL) { + s_lock = xSemaphoreCreateMutex(); + ESP_RETURN_ON_FALSE(s_lock != NULL, ESP_ERR_NO_MEM, TAG, "create mutex failed"); + } + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = port; + config.ctrl_port = (uint16_t)(port + 1); + config.lru_purge_enable = true; + // Keep this <= (LWIP_MAX_SOCKETS - 3 internal sockets). + // Current target allows 7 total, so 4 is the safe upper bound. + config.max_open_sockets = 4; + + ESP_RETURN_ON_ERROR(httpd_start(&s_server, &config), TAG, "httpd_start failed"); + + const httpd_uri_t root = { + .uri = "/", + .method = HTTP_GET, + .handler = status_root_handler, + .user_ctx = NULL, + }; + const httpd_uri_t api = { + .uri = "/api/status", + .method = HTTP_GET, + .handler = status_api_handler, + .user_ctx = NULL, + }; + const httpd_uri_t icon = { + .uri = "/favicon.ico", + .method = HTTP_GET, + .handler = status_favicon_handler, + .user_ctx = NULL, + }; + const httpd_uri_t cfg = { + .uri = "/api/config", + .method = HTTP_POST, + .handler = status_config_handler, + .user_ctx = NULL, + }; + const httpd_uri_t ctrl = { + .uri = "/api/control", + .method = HTTP_POST, + .handler = status_control_handler, + .user_ctx = NULL, + }; + + ESP_RETURN_ON_ERROR(httpd_register_uri_handler(s_server, &root), TAG, "register root failed"); + ESP_RETURN_ON_ERROR(httpd_register_uri_handler(s_server, &icon), TAG, "register favicon failed"); + ESP_RETURN_ON_ERROR(httpd_register_uri_handler(s_server, &api), TAG, "register api failed"); + ESP_RETURN_ON_ERROR(httpd_register_uri_handler(s_server, &cfg), TAG, "register config failed"); + ESP_RETURN_ON_ERROR(httpd_register_uri_handler(s_server, &ctrl), TAG, "register control failed"); + + ESP_LOGI(TAG, "status web started at port %u", (unsigned)port); + return ESP_OK; +} + +esp_err_t status_web_update(const status_web_snapshot_t *snapshot) +{ + ESP_RETURN_ON_FALSE(snapshot != NULL, ESP_ERR_INVALID_ARG, TAG, "snapshot is null"); + ESP_RETURN_ON_FALSE(s_lock != NULL, ESP_ERR_INVALID_STATE, TAG, "status web not started"); + + xSemaphoreTake(s_lock, portMAX_DELAY); + s_snapshot = *snapshot; + s_snapshot_update_ms = (uint64_t)(esp_timer_get_time() / 1000); + xSemaphoreGive(s_lock); + return ESP_OK; +} + +esp_err_t status_web_register_control_handler(mqtt_control_command_handler_t handler, void *user_ctx) +{ + s_control_handler = handler; + s_control_user_ctx = user_ctx; + return ESP_OK; +} diff --git a/main/status_web.h b/main/status_web.h new file mode 100644 index 0000000..da967e4 --- /dev/null +++ b/main/status_web.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include "esp_err.h" +#include "mqtt_control.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char temp[16]; + char hum[16]; + char lux[16]; + bool fan_on; + bool light_on; + bool hot_on; + bool cool_on; + bool auto_mode; + float light_on_threshold; + float light_off_threshold; + float hot_on_temp_threshold; + float hot_off_temp_threshold; + float cool_on_temp_threshold; + float cool_off_temp_threshold; + float fan_on_hum_threshold; + float fan_off_hum_threshold; + bool i2c_ready; + uint32_t loop_counter; +} status_web_snapshot_t; + +esp_err_t status_web_start(uint16_t port); +esp_err_t status_web_update(const status_web_snapshot_t *snapshot); +esp_err_t status_web_register_control_handler(mqtt_control_command_handler_t handler, void *user_ctx); + +#ifdef __cplusplus +} +#endif diff --git a/note.txt b/note.txt new file mode 100644 index 0000000..264fb52 --- /dev/null +++ b/note.txt @@ -0,0 +1,35 @@ +功能说明书 +一.实时监测功能 +1.烟雾浓度监测:实时显示当前烟雾浓度值(单位:ppm)。 +2.有害气体浓度监测:实时显示当前有害气体浓度值(单位:ppm)。 +3.温湿度监测:实时显示当前环境温度和湿度(单位:℃,%)。--------------------------->yes +4.光照检测:检测当前光照强度。--------------------------->yes +5.环境监测:实时检测并显示是否有人。 +6.额外:屏幕 --------------------------->yes +二.阈值管理功能 +1.阈值配置:支持为烟雾浓度、有害气体浓度、温度、湿度设置安全阈值。 +2.阈值显示:在界面中直观展示各指标的预设阈值,便于快速核对。 +三.执行控制功能 +1.制热控制:通过继电器输出控制制热设备,当温度低于设定阈值时自动或手动启动制热。 +2.制冷控制:通过继电器输出控制制冷设备,当温度高于设定阈值时自动或手动启动制冷。 +3.风扇控制:通过继电器输出控制通风风扇,用于调节温湿度或排出有害气体。 +4.自动联动逻辑:支持根据监测数据自动触发继电器动作,实现环境自动调节。 +四.APP远程控制与显示功能 +1.手机端 UI 实时显示:APP 界面实时显示温度、湿度、烟雾、有害气体、火情等监测数据,界面清晰直观。 +2.远程手动控制:通过 APP 可手动开关继电器,控制制热、制冷、风扇等执行器。 +3.状态同步显示:APP 实时同步设备运行状态,用户可随时查看当前设备是否工作。 +4.数据刷新与更新:APP 自动刷新传感器数据,保证显示信息与现场一致。 +5.历史数据记录与显示:执行器状态以及传感器数据 +6.针对于粮仓,应再添加一个关于粮食作物的一个增删改查界面,记录粮食种类,入仓时间,保质时间,储藏时间之类 +五.语音播报功能 +1.实时数据播报:可主动或手动触发,语音播报当前烟雾、有害气体、温度、湿度的实时数值及火情状态。 +2.异常预警播报:当监测指标超出设定阈值或检测到火情时,自动触发语音报警,提示异常类型(如高温、高湿、火情)及对应数值。 +六.数据处理功能 +1.数据采集:实时采集温度、湿度、烟雾、有害气体等传感器数据。 +2.数据滤波:去除干扰数据,保证数据稳定准确。 +3.数据存储:保存历史数据,方便查看和追溯。 +4.数据统计:对数据进行简单统计,便于分析。 +七.门未关识别功能 +通过传感器实时监测门体开合状态,门未关或长时间开启时自动告警,及时提醒用户,保障安全、节能防盗。 +八.称重模块 +称重模块可实时精准采集重量信号,完成信号处理、清零去皮、校准保护,并输出信号、实现数据远传与联动控制,支持多模块组网称重。 \ No newline at end of file diff --git a/partitions.csv b/partitions.csv new file mode 100644 index 0000000..6e9a520 --- /dev/null +++ b/partitions.csv @@ -0,0 +1,4 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x200000,