From 035f1e8ceaa852153eeffcbd8076e823a008c0d6 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 23 Jun 2026 20:38:23 +0800 Subject: [PATCH] Initial ESP32 RLCD metrics workspace --- .gitignore | 40 + CMakeLists.txt | 10 + dependencies.lock | 32 + docs/zhipu-coding-plan-usage-api.md | 548 +++ frontend-preview/active-screen.json | 21 + frontend-preview/app.js | 1063 +++++ frontend-preview/firmwares.json | 28 + frontend-preview/index.html | 125 + frontend-preview/styles.css | 828 ++++ frontend-preview/templates.json | 98 + main/CMakeLists.txt | 25 + main/battery_monitor.c | 230 + main/battery_monitor.h | 14 + main/generated/ui_config.h | 30 + main/idf_component.yml | 8 + main/main.c | 123 + main/metrics_ble.c | 271 ++ main/metrics_ble.h | 16 + main/metrics_protocol.c | 551 +++ main/metrics_protocol.h | 15 + main/metrics_serial.c | 122 + main/metrics_serial.h | 16 + main/metrics_wifi.c | 148 + main/metrics_wifi.h | 11 + main/st7305_rlcd.c | 365 ++ main/st7305_rlcd.h | 98 + main/temp_humidity_monitor.c | 159 + main/temp_humidity_monitor.h | 14 + main/ui_screen.c | 795 ++++ main/ui_screen.h | 50 + partitions.csv | 4 + scripts/README.md | 15 + sdkconfig | 5177 +++++++++++++++++++++++ sdkconfig.defaults | 66 + tools/pc-metrics/push-metrics-ble.py | 786 ++++ tools/pc-metrics/push-metrics-http.ps1 | 100 + tools/pc-metrics/requirements-ble.txt | 2 + tools/pc-metrics/serve-metrics-http.ps1 | 124 + tools/pc-metrics/start-ap-push.ps1 | 41 + tools/pc-metrics/start-ble-metrics.ps1 | 89 + tools/pc-metrics/start-http-metrics.ps1 | 37 + tools/pc-metrics/start-metrics.ps1 | 39 + tools/pc-metrics/stop-ap-push.ps1 | 22 + tools/pc-metrics/stop-ble-metrics.ps1 | 42 + tools/pc-metrics/stop-http-metrics.ps1 | 22 + tools/pc-metrics/stop-metrics.ps1 | 22 + tools/pc-metrics/stream-metrics.ps1 | 90 + tools/preview-bridge/server.py | 351 ++ 48 files changed, 12883 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 dependencies.lock create mode 100644 docs/zhipu-coding-plan-usage-api.md create mode 100644 frontend-preview/active-screen.json create mode 100644 frontend-preview/app.js create mode 100644 frontend-preview/firmwares.json create mode 100644 frontend-preview/index.html create mode 100644 frontend-preview/styles.css create mode 100644 frontend-preview/templates.json create mode 100644 main/CMakeLists.txt create mode 100644 main/battery_monitor.c create mode 100644 main/battery_monitor.h create mode 100644 main/generated/ui_config.h create mode 100644 main/idf_component.yml create mode 100644 main/main.c create mode 100644 main/metrics_ble.c create mode 100644 main/metrics_ble.h create mode 100644 main/metrics_protocol.c create mode 100644 main/metrics_protocol.h create mode 100644 main/metrics_serial.c create mode 100644 main/metrics_serial.h create mode 100644 main/metrics_wifi.c create mode 100644 main/metrics_wifi.h create mode 100644 main/st7305_rlcd.c create mode 100644 main/st7305_rlcd.h create mode 100644 main/temp_humidity_monitor.c create mode 100644 main/temp_humidity_monitor.h create mode 100644 main/ui_screen.c create mode 100644 main/ui_screen.h create mode 100644 partitions.csv create mode 100644 scripts/README.md create mode 100644 sdkconfig create mode 100644 sdkconfig.defaults create mode 100644 tools/pc-metrics/push-metrics-ble.py create mode 100644 tools/pc-metrics/push-metrics-http.ps1 create mode 100644 tools/pc-metrics/requirements-ble.txt create mode 100644 tools/pc-metrics/serve-metrics-http.ps1 create mode 100644 tools/pc-metrics/start-ap-push.ps1 create mode 100644 tools/pc-metrics/start-ble-metrics.ps1 create mode 100644 tools/pc-metrics/start-http-metrics.ps1 create mode 100644 tools/pc-metrics/start-metrics.ps1 create mode 100644 tools/pc-metrics/stop-ap-push.ps1 create mode 100644 tools/pc-metrics/stop-ble-metrics.ps1 create mode 100644 tools/pc-metrics/stop-http-metrics.ps1 create mode 100644 tools/pc-metrics/stop-metrics.ps1 create mode 100644 tools/pc-metrics/stream-metrics.ps1 create mode 100644 tools/preview-bridge/server.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c0ce8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Local/private config +main/wifi_private.h +.env +.env.* +*.local +.agents/ +.codex/ + +# Build outputs and generated binaries +build/ +managed_components/ +*.bin +*.elf +*.map +*.uf2 +build-output.log +build-error.log +sdkconfig.old +nul + +# Logs and runtime state +*.log +*.err.log +*.pid + +# Python caches and virtual environments +__pycache__/ +*.py[cod] +.venv/ +.venv-*/ +tools/pc-metrics/.venv-ble/ + +# Local reference repositories, diagnostics, and archived one-off scripts +espcc-switch/ +diagnostics/ +scripts/_archive_*/ + +# OS/editor noise +.DS_Store +Thumbs.db diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a09c72b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +# ============================================================ +# ESP32-S3-RLCD-4.2 横屏时钟项目 +# 顶层 CMake 配置 +# ============================================================ +cmake_minimum_required(VERSION 3.16) + +# 引入 ESP-IDF 的构建系统(必须) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +project(rlcd_clock) diff --git a/dependencies.lock b/dependencies.lock new file mode 100644 index 0000000..b5d7fb3 --- /dev/null +++ b/dependencies.lock @@ -0,0 +1,32 @@ +dependencies: + espressif/esp_lvgl_port: + component_hash: 3264cc85d86a7c32b67d26a1353a57c67e7986bb293ebb1731801936b056e632 + 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.6.3 + idf: + source: + type: idf + version: 6.0.1 + lvgl/lvgl: + component_hash: b702d642e03e95928046d5c6726558e6444e112420c77efa5fdb6650b0a13c5d + dependencies: [] + source: + registry_url: https://components.espressif.com/ + type: service + version: 9.3.0 +direct_dependencies: +- espressif/esp_lvgl_port +- lvgl/lvgl +manifest_hash: 1c62eac3ca543fee78ebb97c6da4d93abeb675b6441edac6782a75d7457d809e +target: esp32s3 +version: 3.0.0 diff --git a/docs/zhipu-coding-plan-usage-api.md b/docs/zhipu-coding-plan-usage-api.md new file mode 100644 index 0000000..e3cef71 --- /dev/null +++ b/docs/zhipu-coding-plan-usage-api.md @@ -0,0 +1,548 @@ +# 智谱 GLM Coding Plan 用量查询对接文档 + +> **文档目的**:为后续开发者(或 AI 助手)提供智谱 GLM Coding Plan 用量查询的完整对接方案,基于 cc-switch 源码逆向 + 官方文档查证,内容**经过用户实测验证**(用户在 cc-switch 里能看到自己的套餐用量)。 +> +> **权威来源**:以 `farion1231/cc-switch` 源码实现为准。该实现用户已验证可用。 +> +> **本地参考源码**:`D:\AI Code\ESP-32\espcc-switch\`(已克隆,可直接查阅) + +--- + +## 一、背景 + +ESP32-S3-RLCD-4.2 小屏需要显示智谱 GLM Coding Plan 的: +- 5 小时限额已用百分比 + 重置时间 +- 周限额已用百分比 + 重置时间(新套餐才有) +- token 消耗统计 +- 套餐层级 + +智谱官方**没有公开的用量查询 API 文档**,但通过逆向 cc-switch 找到了实际可用的工作端点。 + +## 二、关键结论(一句话总结) + +| 问题 | 答案 | +|------|------| +| 用什么端点查? | `GET /api/monitor/usage/quota/limit` | +| 怎么鉴权? | **纯 API Key**,放 Authorization 头,**不加 `Bearer ` 前缀** | +| 需要浏览器 Cookie 吗? | **不需要**(跟之前社区工具 coding-plan-monitor 不一样) | +| ESP-32 能直接用吗? | **能**,纯 HTTP GET,`esp_http_client` 可 1:1 复现 | +| 怎么区分 5h vs 周? | 看 `limits[].unit` 字段:**3=5h, 6=周** | +| 怎么判断老/新套餐? | `limits[]` 里 `TOKENS_LIMIT` 条目数量(1=老无周, 2=新有周) | +| 能拿到绝对 token 数吗? | **不能**,quota/limit 只给百分比。绝对 token 要调 `model-usage` 端点 | + +--- + +## 三、核心 API + +### 端点 + +``` +国内:GET https://open.bigmodel.cn/api/monitor/usage/quota/limit +国际:GET https://api.z.ai/api/monitor/usage/quota/limit +``` + +**域名选择**: +- 用户的 API Key 配在 `open.bigmodel.cn` → 用国内 +- 用户的 API Key 配在 `api.z.ai` → 用国际 +- 两条路径(path)完全一致,只是域名不同 + +**域名选择参考代码**(来自 cc-switch `coding_plan.rs:302-308`): +```rust +fn zhipu_quota_base(base_url: &str) -> &str { + if base_url.contains("bigmodel.cn") { + "https://open.bigmodel.cn" + } else { + "https://api.z.ai" + } +} +``` + +### 请求 + +```http +GET /api/monitor/usage/quota/limit HTTP/1.1 +Host: open.bigmodel.cn +Authorization: <你的智谱 API_KEY> +Content-Type: application/json +Accept-Language: en-US,en +``` + +**关键点**: +- 方法:`GET` +- 鉴权:`Authorization` 头直接放 API Key 原值,**不要加 `Bearer ` 前缀** + - cc-switch 在 `coding_plan.rs:319` 显式注释:`// 注意:智谱不加 Bearer 前缀` + - 这跟 OpenAI、Anthropic 等标准 Bearer 鉴权不同,**是个例外** +- Content-Type: `application/json` +- Accept-Language: `en-US,en`(cc-switch 加了这个,建议照抄) +- **无请求体** +- **无查询参数** +- 建议超时:15 秒 +- **不需要浏览器 Cookie** +- **不需要伪装 User-Agent** + +### 返回 JSON 结构 + +```jsonc +{ + "success": true, + "msg": "操作成功", + "data": { + "level": "Max", + "limits": [ + { + "type": "TOKENS_LIMIT", + "unit": 3, + "number": 5, + "percentage": 32.5, + "nextResetTime": 1773734366338 + }, + { + "type": "TOKENS_LIMIT", + "unit": 6, + "number": 7, + "percentage": 45.0, + "nextResetTime": 1774663282997 + } + ] + } +} +``` + +### 字段说明 + +| 字段路径 | 类型 | 含义 | +|---------|------|------| +| `success` | bool | 业务级成功标记,false 时读 `msg` | +| `msg` | string | 错误文案(success=false 时有用) | +| `data.level` | string | 套餐层级(如 `"Max"` / `"Pro"` / `"Lite"`) | +| `data.limits[]` | array | 限额窗口数组 | +| `data.limits[].type` | string | `"TOKENS_LIMIT"`(我们要的) / `"TIME_LIMIT"`(使用时长,跳过) | +| `data.limits[].unit` | int | **窗口类型,核心字段**:`3` = 5 小时,`6` = 每周 | +| `data.limits[].number` | int | 数量(5h 时为 5,周时为 7) | +| `data.limits[].percentage` | float | 已用百分比 0-100 | +| `data.limits[].nextResetTime` | int(ms) | 重置毫秒时间戳;0% 用量时可能缺失 | + +--- + +## 四、字段→业务含义映射 + +| 屏幕显示 | 字段路径 | 备注 | +|---------|----------|------| +| 5 小时限额已用% | `limits[type=TOKENS_LIMIT && unit=3].percentage` | 0-100 | +| 5 小时重置时间 | `limits[type=TOKENS_LIMIT && unit=3].nextResetTime` | ms 时间戳 | +| 周限额已用% | `limits[type=TOKENS_LIMIT && unit=6].percentage` | **新套餐才有**,老套餐查不到 | +| 周限额重置时间 | `limits[type=TOKENS_LIMIT && unit=6].nextResetTime` | 同上 | +| 套餐层级 | `data.level` | 字符串 | + +### 时间戳转换规则(用于显示) + +根据之前 UI 设计讨论的结果,重置时间的显示策略: +- 距离当前时间 **≤ 24 小时** → 显示 `HH:MM`(如 `18:42`) +- 距离当前时间 **> 24 小时** → 显示 `MM-DD`(如 `06-28`) + +参考实现(`formatResetTime`): +```javascript +function formatResetTime(resetMs) { + const diff = resetMs - Date.now(); + const absDiff = Math.abs(diff); + const date = new Date(resetMs); + const pad = n => String(n).padStart(2, '0'); + if (absDiff <= 24 * 3600 * 1000) { + return `${pad(date.getHours())}:${pad(date.getMinutes())}`; + } else { + return `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; + } +} +``` + +ESP-32 C 实现可用 `` 的 `localtime_r` + `difftime`。 + +--- + +## 五、⚠️ 三个必避的坑 + +### 坑 1:必须用 `unit` 字段区分 5h 和周,**不能按重置时间排序** + +**问题**:cc-switch 在 issue #3036 踩过——每周周期末尾,周窗口会比 5 小时窗口**更早**重置,按 `nextResetTime` 升序排序必然把"5h"和"周"标反。 + +**正确做法**:只看 `unit` 字段: +- `unit == 3` → 5 小时窗口 +- `unit == 6` → 周窗口 + +**只有当 `unit` 缺失或未识别时**,才回落到启发式(无 reset 优先归 5h,其余按 reset 升序填空槽)。 + +**源码引用**:`cc-switch/src-tauri/src/services/coding_plan.rs:226-234` 有显式注释说明这个坑。 + +### 坑 2:老套餐 vs 新套餐的判定 + +**判定方式不是某个标志位,而是 `limits[]` 数组里 `type==TOKENS_LIMIT` 条目的数量**: + +- **老套餐**(2026-02-12 前订阅):只返回 **1 条** `TOKENS_LIMIT`(只有 5h,无周) +- **新套餐**:返回 **2 条** `TOKENS_LIMIT`(5h + 周) + +**当前用户**:用的是老套餐(无周限额),所以查出来只会看到 1 条 `TOKENS_LIMIT`,没有 `unit=6` 的条目。 + +**代码处理建议**:用两个可空 slot(`five_hour` / `weekly`),有就填、没有就留空。屏幕显示时: +- 5h 没查到 → 显示 "—" +- 周限额没查到 → 显示 "无限制" 或 "—" 或 "OLD PLAN" + +**源码参考**:`cc-switch/src-tauri/src/services/coding_plan.rs:279-291` 实现了天然降级逻辑。 + +### 坑 3:智谱接口**只给百分比,不给绝对 token 数** + +**问题**:cc-switch 在 `coding_plan.rs:282-289` 明确:`used_value_usd` / `max_value_usd` 都是 `None`。智谱接口只返回 `percentage`,**不返回绝对 token 数**。 + +**影响**:之前 UI 设计里说的"token 消耗按 M/B 显示"**不能从 quota/limit 接口拿到**。 + +**解决方案**:要拿绝对 token 数,得调另一个端点: +``` +GET /api/monitor/usage/model-usage?startTime=...&endTime=... +``` +- 参数:`startTime` / `endTime` URL 编码后的 `"yyyy-MM-dd HH:mm:ss"` 格式 +- 官方插件 zai-coding-plugins 的默认窗口:昨天当前小时整点 → 今天当前小时末秒 +- **cc-switch 没用这个接口,所以返回字段结构需要实测确认**(官方插件只是 `JSON.stringify` 原样打印,没固定字段名) + +**建议**: +1. 先做百分比显示(已用 X% + 重置时间),这是 quota/limit 能直接拿到的 +2. 绝对 token 数等 model-usage 端点字段确认后再加,或者干脆**本地累加**(每次调 LLM 时把 `response.usage.total_tokens` 累计起来,自己统计今日/本周消耗) + +--- + +## 六、ESP-32 实现参考(伪代码) + +```c +#include "esp_http_client.h" +#include "cJSON.h" + +// 1. 构造请求 +esp_http_client_config_t config = { + .url = "https://open.bigmodel.cn/api/monitor/usage/quota/limit", + .method = HTTP_METHOD_GET, + .timeout_ms = 15000, + .cert_pem = bigmodel_root_ca, // 需要 HTTPS 根证书 + .user_agent = "esp32-rlcd", // 随便写,没限制 +}; + +esp_http_client_handle_t client = esp_http_client_init(&config); + +// 关键:Authorization 不加 Bearer 前缀! +char auth_header[256]; +snprintf(auth_header, sizeof(auth_header), "%s", api_key); +esp_http_client_set_header(client, "Authorization", auth_header); +esp_http_client_set_header(client, "Content-Type", "application/json"); +esp_http_client_set_header(client, "Accept-Language", "en-US,en"); + +// 2. 发请求 +esp_err_t err = esp_http_client_perform(client); +if (err != ESP_OK) { /* 错误处理 */ } + +int status = esp_http_client_get_status_code(client); +if (status == 401 || status == 403) { + // 凭据失效 +} else if (status != 200) { + // 其他错误 +} + +// 3. 读响应体 +char buffer[4096]; +int len = esp_http_client_read(client, buffer, sizeof(buffer) - 1); +buffer[len] = '\0'; + +// 4. 解析 JSON +cJSON *root = cJSON_Parse(buffer); +cJSON *data = cJSON_GetObjectItem(root, "data"); +cJSON *level = cJSON_GetObjectItem(data, "level"); +const char *plan_level = level->valuestring; // "Max" + +cJSON *limits = cJSON_GetObjectItem(data, "limits"); +cJSON *limit; +cJSON_ArrayForEach(limit, limits) { + cJSON *type = cJSON_GetObjectItem(limit, "type"); + if (!cJSON_IsString(type) || strcmp(type->valuestring, "TOKENS_LIMIT") != 0) { + continue; // 跳过 TIME_LIMIT + } + + cJSON *unit = cJSON_GetObjectItem(limit, "unit"); + cJSON *pct = cJSON_GetObjectItem(limit, "percentage"); + cJSON *reset = cJSON_GetObjectItem(limit, "nextResetTime"); + + switch (unit->valueint) { + case 3: // 5 小时窗口 + five_hour_pct = pct->valuedouble; + five_hour_reset_ms = reset ? reset->valuedouble : 0; + break; + case 6: // 周窗口(新套餐才有) + weekly_pct = pct->valuedouble; + weekly_reset_ms = reset ? reset->valuedouble : 0; + break; + } +} + +cJSON_Delete(root); +esp_http_client_cleanup(client); + +// 5. 转换时间戳为显示字符串(24h 内显示 HH:MM, 否则 MM-DD) +// 使用 的 localtime_r + difftime +``` + +--- + +## 七、鉴权验证(写代码前先做) + +写代码前,**先用 curl 验证 API Key 能不能直接调通**。这是最省时间的步骤——避免代码写完才发现鉴权走不通。 + +```bash +curl -X GET \ + -H "Authorization: <你的智谱 API_KEY>" \ + -H "Content-Type: application/json" \ + -H "Accept-Language: en-US,en" \ + https://open.bigmodel.cn/api/monitor/usage/quota/limit +``` + +**预期响应**(老套餐): +```json +{ + "success": true, + "msg": "操作成功", + "data": { + "level": "Max", + "limits": [ + { + "type": "TOKENS_LIMIT", + "unit": 3, + "number": 5, + "percentage": 32.5, + "nextResetTime": 1773734366338 + } + ] + } +} +``` + +**判定**: +- ✅ 返回 200 + 有 `data.limits` → API Key 路线可行,ESP-32 可以直接复现 +- ❌ 返回 401/403 → API Key 无效或权限不够 +- ❌ 返回空 body 或非 JSON → 可能被反爬,但根据 cc-switch 经验这种情况**不应该发生**(cc-switch 用纯 API Key 跑通了) + +--- + +## 八、源码引用清单 + +**权威实现**(cc-switch,用户已验证): + +| 文件 | 行 | 作用 | +|------|-----|------| +| `src-tauri/src/services/coding_plan.rs` | 302-324 | `query_zhipu` 端点+请求头构造 | +| `src-tauri/src/services/coding_plan.rs` | 319 | "// 注意:智谱不加 Bearer 前缀" | +| `src-tauri/src/services/coding_plan.rs` | 218-224 | `classify_zhipu_window` 按 unit 分类 | +| `src-tauri/src/services/coding_plan.rs` | 226-234 | unit 字段坑的注释(issue #3036) | +| `src-tauri/src/services/coding_plan.rs` | 238-292 | `parse_zhipu_token_tiers` 解析逻辑 | +| `src-tauri/src/services/coding_plan.rs` | 279-291 | 老/新套餐天然降级(可空 slot) | +| `src-tauri/src/services/coding_plan.rs` | 282-289 | 只读 percentage,绝对值为 None | +| `src-tauri/src/services/subscription.rs` | 301, 308 | `TIER_FIVE_HOUR` / `TIER_WEEKLY_LIMIT` 常量 | +| `src-tauri/src/services/coding_plan.rs` | 1224-1240 | 测试:老套餐单 tier 降级到 5h | +| `src-tauri/src/services/coding_plan.rs` | 1316-1333 | 测试:unit 字段覆盖 reset 排序 | + +**本地路径**:`D:\AI Code\ESP-32\espcc-switch\`(已克隆) + +**辅助参考**(官方插件): +- `zai-coding-plugins/plugins/glm-plan-usage/skills/usage-query-skill/scripts/query-usage.mjs` — 调用了 3 个端点(quota/limit + model-usage + tool-usage),quota/limit 字段处理跟 cc-switch 一致 + +**辅助参考**(社区工具,鉴权方式不同,**不要照抄**): +- `JinHanAI/coding-plan-monitor/src/providers/zhipu.ts` — 走浏览器 Cookie,不支持 API Key 直连 + +--- + +## 九、官方文档(背景参考) + +- [智谱AI开放文档 - Coding Plan FAQ](https://docs.bigmodel.cn/cn/coding-plan/faq) — 5h + 周限额机制说明 +- [智谱AI开放文档 - 套餐概览](https://docs.bigmodel.cn/cn/coding-plan/overview) — 各套餐限额参考表 +- [智谱AI开放文档 - 用量查询插件](https://docs.bigmodel.cn/cn/coding-plan/extension/usage-query-plugin) — 官方插件介绍 +- [智谱AI开放文档 - HTTP API 调用](https://docs.bigmodel.cn/cn/guide/develop/http/introduction) — 基础 HTTP 调用规范 +- [cc-switch GitHub](https://github.com/farion1231/cc-switch) — 权威实现来源 +- [cc-switch Issue #1857 - 用量统计与计费追踪](https://github.com/farion1231/cc-switch/issues/1857) +- [cc-switch Issue #1588 - 智谱用量查询脚本示例](https://github.com/farion1231/cc-switch/issues/1588) + +--- + +## 十、对接 Checklist(给 codex 用的执行清单) + +- [ ] **Step 1**:用 curl 验证 API Key 能调通(参考第七节) +- [ ] **Step 2**:在 ESP-32 项目里加 HTTPS 根证书(open.bigmodel.cn 的根 CA) +- [ ] **Step 3**:实现 `zhipu_query_quota()` 函数,封装 HTTP GET + JSON 解析 +- [ ] **Step 4**:实现 `unit` 字段分类(3=5h, 6=周),不要按时间排序 +- [ ] **Step 5**:实现老/新套餐降级(周限额查不到时显示"无限制") +- [ ] **Step 6**:实现时间戳→显示字符串转换(24h 内 HH:MM,否则 MM-DD) +- [ ] **Step 7**:绝对 token 数(如果需要)→ 调 `model-usage` 端点实测字段,或本地累加每次 LLM 调用的 `usage.total_tokens` +- [ ] **Step 8**:把查询结果通过 BLE/USB 推送到板子(参考现有 `metrics_*` 模块的推送方式) + +--- + +## 附:接口对比速查表 + +| 项目 | 端点 | 鉴权 | 用户验证 | 推荐用 | +|------|------|------|----------|--------| +| **cc-switch** | `/api/monitor/usage/quota/limit` | 纯 API Key(无 Bearer) | ✅ 用户已验证 | ✅ **本文档基准** | +| zai-coding-plugins(官方) | 同上 + `model-usage` + `tool-usage` | API Key(无 Bearer) | ✅ 官方插件 | 辅助参考 | +| coding-plan-monitor(社区) | 同上 | 浏览器 Cookie | ⚠️ 仅浏览器 | ❌ 不照抄 | + +--- + +## 十一、token 使用量获取方案 + +> **核心结论**:`quota/limit` 端点**只返回百分比,不返回绝对 token 数**。要拿"今日用了多少 token",有**三种方案**,各有取舍。 + +### 三种方案对比 + +| 方案 | 原理 | 优点 | 缺点 | 推荐度 | +|------|------|------|------|--------| +| **A. 调 model-usage 端点** | 查询时间窗口内的 token 消耗明细 | 服务端权威数据,无需本地存储 | ⚠️ 字段结构官方未公开,需实测 | ⭐⭐⭐ | +| **B. 本地累加(推荐)** | 每次调 LLM 时累计响应里的 `usage.totalTokens` | 字段确定(智谱对话补全 API 标准),实现简单 | 只统计"经过本代理的"调用,跨设备/跨工具会漏 | ⭐⭐⭐⭐⭐ | +| **C. 只显示百分比** | 直接用 quota/limit 的 `percentage` | 零开发量,已有数据 | 没有绝对值,信息量少 | ⭐⭐ | + +### 方案 A:model-usage 端点(需实测字段) + +**端点**: +``` +GET https://open.bigmodel.cn/api/monitor/usage/model-usage?startTime=...&endTime=... +``` + +**请求**(跟 quota/limit 同款鉴权): +```http +GET /api/monitor/usage/model-usage?startTime=2026-06-23%2000%3A00%3A00&endTime=2026-06-23%2023%3A59%3A59 HTTP/1.1 +Host: open.bigmodel.cn +Authorization: ← 不加 Bearer +Content-Type: application/json +Accept-Language: en-US,en +``` + +**参数**: +- `startTime` / `endTime`:URL 编码后的 `"yyyy-MM-dd HH:mm:ss"` 格式 +- 官方插件默认窗口:昨天当前小时整点 → 今天当前小时末秒 + +**返回 JSON 结构(⚠️ 推断,需实测)**: +```jsonc +{ + "success": true, + "code": 200, + "msg": "ok", + "data": { + "usageList": [ // 字段名推断, 实测可能不同 + { + "modelName": "glm-4.6", + "totalTokens": 123456, + "promptTokens": 100000, // 智谱用驼峰命名(不是 prompt_tokens) + "completionTokens": 23456 + } + ] + } +} +``` + +**⚠️ 字段未公开的证据**: +- 官方插件 `query-usage.mjs:167` 调 model-usage 时**没传 postProcessor**,line 147 直接 `JSON.stringify(outputData)` 原样打印给 LLM +- 官方 agent 文档 `usage-query-agent.md` 也没描述字段结构 +- 说明官方故意不固定字段文档(可能经常变),让 LLM 运行时自适应解读 + +**实测步骤**(必做): +```bash +curl -X GET \ + -H "Authorization: <你的 API_KEY>" \ + -H "Content-Type: application/json" \ + -H "Accept-Language: en-US,en" \ + "https://open.bigmodel.cn/api/monitor/usage/model-usage?startTime=$(date -d 'yesterday' +%Y-%m-%d)%2000%3A00%3A00&endTime=$(date +%Y-%m-%d)%2023%3A59%3A59" +``` +拿到真实 JSON 后,根据实际字段名写解析代码。 + +### 方案 B:本地累加(推荐 ⭐) + +**原理**:智谱对话补全 API 的**每次响应**都带 `usage` 字段,本地累加即可。 + +**智谱对话补全响应里的 usage 字段**(官方文档确认): +```jsonc +{ + "choices": [ ... ], + "usage": { + "promptTokens": 123, // 输入 token(驼峰命名!) + "completionTokens": 456, // 输出 token + "totalTokens": 579 // 总计 + } +} +``` + +**⚠️ 命名坑**:智谱用**驼峰**(`promptTokens`),不是 OpenAI 的下划线(`prompt_tokens`)。解析时别搞混。 + +**实现方式**(cc-switch 就是这么做的): +- cc-switch 在 `src-tauri/src/proxy/usage/parser.rs:17-22` 定义了 `TokenUsage` 结构 +- 每次代理 LLM 请求时,从响应里解析 `usage`,写入本地 SQLite(`usage_stats.rs`) +- 查询时 `get_usage_summary` 聚合统计 + +**ESP-32 上的简化实现**: +- 如果电脑端有代理服务(像 cc-switch 那样),在代理层累加 token,定期推给板子 +- 如果板子自己调 LLM,在板子上累加(但 ESP-32 存储有限,建议只存今日/本周两个计数器) + +**为什么推荐**: +1. 字段确定(智谱对话补全 API 标准,有官方文档) +2. 实现简单(就是累加) +3. 不依赖未公开的 model-usage 端点 +4. cc-switch 验证过这条路可行 + +### 方案 C:只显示百分比 + +直接用 `quota/limit` 的 `TOKENS_LIMIT.percentage`,显示"5H 已用 32%"。 + +适合 MVP 阶段——零开发量,数据现成。后续再加方案 A 或 B 补绝对值。 + +### 给 codex 的建议 + +1. **第一阶段**:用方案 C(百分比),先把 quota/limit 跑通 +2. **第二阶段**:如果电脑端有代理服务,用方案 B(本地累加)——字段确定、实现简单 +3. **第三阶段**:如果需要服务端权威数据,用方案 A——但**必须先 curl 实测字段**,不要按本文档的推断结构写代码 + +--- + +## 十二、cc-switch 的 token 统计实现(方案 B 参考) + +cc-switch 不调 model-usage 端点,而是**本地 SQLite 累加**。这是方案 B 的最佳参考。 + +### 核心文件 + +| 文件 | 作用 | +|------|------| +| `src-tauri/src/proxy/usage/parser.rs` | 从 LLM 响应解析 `TokenUsage`(支持 Claude/OpenAI 格式) | +| `src-tauri/src/services/usage_stats.rs` | SQLite 存储 + 聚合查询(`get_usage_summary`) | +| `src-tauri/src/services/session_usage_codex.rs` | Codex 专用 session 用量 | + +### TokenUsage 结构(parser.rs:17-22) + +```rust +pub struct TokenUsage { + pub input_tokens: u32, + pub output_tokens: u32, + pub cache_read_tokens: u32, + pub cache_creation_tokens: u32, +} +``` + +### 解析入口 + +- `from_claude_response`(parser.rs:66)— 解析 Claude API 响应的 `usage.input_tokens` / `output_tokens` +- 也支持 OpenAI 兼容格式(`prompt_tokens` / `completion_tokens`) + +### 统计查询 + +`get_usage_summary`(usage_stats.rs:505-664)支持按时间范围聚合,返回各模型的 token 总量。 + +**本地路径**:`D:\AI Code\ESP-32\espcc-switch\src-tauri\src\proxy\usage\parser.rs` + +--- + +## Sources(token 使用量部分) + +- [智谱AI开放文档 - 核心参数(Token 定义)](https://docs.bigmodel.cn/cn/guide/start/concept-param) — promptTokens/completionTokens/totalTokens 定义 +- [智谱AI开放文档 - 对话补全 API](https://docs.bigmodel.cn/api-reference/%E6%A8%A1%E5%9E%8B-api/%E5%AF%B9%E8%AF%9D%E8%A1%A5%E5%85%A8) — usage 字段在响应里的位置 +- [智谱AI开放文档 - API 价格](https://open.bigmodel.cn/pricing) — token 计费说明 +- [GitHub Issue zhayujie/CowAgent#2289](https://github.com/zhayujie/CowAgent/issues/2289) — 智谱驼峰命名 vs OpenAI 下划线命名的兼容性问题 +- [cc-switch Discussion #1038 - 智谱用量查询脚本](https://github.com/farion1231/cc-switch/discussions/1038) — model-usage 端点调用示例 +- 本地源码:`D:\esp\zai-coding-plugins\plugins\glm-plan-usage\skills\usage-query-skill\scripts\query-usage.mjs`(官方插件,证实 model-usage 字段未公开) +- 本地源码:`D:\AI Code\ESP-32\espcc-switch\src-tauri\src\proxy\usage\parser.rs`(本地累加实现) diff --git a/frontend-preview/active-screen.json b/frontend-preview/active-screen.json new file mode 100644 index 0000000..0232461 --- /dev/null +++ b/frontend-preview/active-screen.json @@ -0,0 +1,21 @@ +{ + "templateId": "dual-metric", + "title": "ROOM STATUS", + "orientation": "landscape", + "density": "comfortable", + "effects": { + "ghost": true, + "grid": true + }, + "fields": { + "status": "SENSOR", + "primaryLabel": "TEMP", + "primaryValue": "26", + "primaryUnit": "C", + "secondaryLabel": "HUM", + "secondaryValue": "58", + "secondaryUnit": "%", + "footerLeft": "Last refresh 2m", + "footerRight": "Battery 86%" + } +} diff --git a/frontend-preview/app.js b/frontend-preview/app.js new file mode 100644 index 0000000..ef3a310 --- /dev/null +++ b/frontend-preview/app.js @@ -0,0 +1,1063 @@ +/* ============================================================ + * ESP32-S3-RLCD-4.2 Workbench — Canvas 像素级预览 + * + * 架构: + * 1. RLCDSimulator - 400×300 1-bit 像素级 Canvas 渲染器 + * 2. 位图字体 - 5×7 / 8×16 两套像素字体, hex 存储 + * 3. renderDashboard - 仪表盘绘制(性能/Codex/智谱 三段) + * 4. 控制面板 - 字段编辑实时重绘 + 桥接 API 保留 + * + * 这个文件是单文件应用, 不依赖外部库 + * ============================================================ */ + +'use strict'; + +/* ============================================================ + * Section 1: 位图字体 (5×7 和 8×16) + * + * 每个字符用 hex 数组表示, 每行 1 字节(8 像素) + * 5×7 字体: 每字符 7 字节, 有效位是低 5 位 + * 8×16 字体: 每字符 16 字节, 全 8 位有效 + * + * 只覆盖仪表盘用到的字符: 0-9 A-Z 标点 符号 + * ============================================================ */ + +// 5×7 像素字体 (主用, 小字) +// 每字符 7 字节, bit0-bit4 是 5 列像素, bit0=最左列 +// 来源: 经典 LCD 字体, 改造适配仪表盘 +const FONT_5x7 = { + ' ': [0x00,0x00,0x00,0x00,0x00,0x00,0x00], + '0': [0x0e,0x11,0x13,0x15,0x19,0x11,0x0e], + '1': [0x04,0x0c,0x04,0x04,0x04,0x04,0x0e], + '2': [0x0e,0x11,0x01,0x02,0x04,0x08,0x1f], + '3': [0x1f,0x02,0x04,0x02,0x01,0x11,0x0e], + '4': [0x02,0x06,0x0a,0x12,0x1f,0x02,0x02], + '5': [0x1f,0x10,0x1e,0x01,0x01,0x11,0x0e], + '6': [0x06,0x08,0x10,0x1e,0x11,0x11,0x0e], + '7': [0x1f,0x01,0x02,0x04,0x08,0x08,0x08], + '8': [0x0e,0x11,0x11,0x0e,0x11,0x11,0x0e], + '9': [0x0e,0x11,0x11,0x0f,0x01,0x02,0x0c], + 'A': [0x0e,0x11,0x11,0x11,0x1f,0x11,0x11], + 'B': [0x1e,0x11,0x11,0x1e,0x11,0x11,0x1e], + 'C': [0x0e,0x11,0x10,0x10,0x10,0x11,0x0e], + 'D': [0x1c,0x12,0x11,0x11,0x11,0x12,0x1c], + 'E': [0x1f,0x10,0x10,0x1e,0x10,0x10,0x1f], + 'F': [0x1f,0x10,0x10,0x1e,0x10,0x10,0x10], + 'G': [0x0e,0x11,0x10,0x17,0x11,0x11,0x0f], + 'H': [0x11,0x11,0x11,0x1f,0x11,0x11,0x11], + 'I': [0x0e,0x04,0x04,0x04,0x04,0x04,0x0e], + 'J': [0x01,0x01,0x01,0x01,0x11,0x11,0x0e], + 'K': [0x11,0x12,0x14,0x18,0x14,0x12,0x11], + 'L': [0x10,0x10,0x10,0x10,0x10,0x10,0x1f], + 'M': [0x11,0x1b,0x15,0x15,0x11,0x11,0x11], + 'N': [0x11,0x11,0x19,0x15,0x13,0x11,0x11], + 'O': [0x0e,0x11,0x11,0x11,0x11,0x11,0x0e], + 'P': [0x1e,0x11,0x11,0x1e,0x10,0x10,0x10], + 'Q': [0x0e,0x11,0x11,0x11,0x15,0x12,0x0d], + 'R': [0x1e,0x11,0x11,0x1e,0x14,0x12,0x11], + 'S': [0x0f,0x10,0x10,0x0e,0x01,0x01,0x1e], + 'T': [0x1f,0x04,0x04,0x04,0x04,0x04,0x04], + 'U': [0x11,0x11,0x11,0x11,0x11,0x11,0x0e], + 'V': [0x11,0x11,0x11,0x11,0x11,0x0a,0x04], + 'W': [0x11,0x11,0x11,0x15,0x15,0x15,0x0a], + 'X': [0x11,0x11,0x0a,0x04,0x0a,0x11,0x11], + 'Y': [0x11,0x11,0x11,0x0a,0x04,0x04,0x04], + 'Z': [0x1f,0x01,0x02,0x04,0x08,0x10,0x1f], + '%': [0x1c,0x22,0x02,0x04,0x08,0x10,0x3e], // 用 5x7 近似 + '.': [0x00,0x00,0x00,0x00,0x00,0x0c,0x0c], + ':': [0x00,0x0a,0x0a,0x00,0x0a,0x0a,0x00], + '/': [0x01,0x02,0x02,0x04,0x04,0x08,0x08], + '-': [0x00,0x00,0x00,0x1f,0x00,0x00,0x00], + '+': [0x00,0x04,0x04,0x1e,0x04,0x04,0x00], + '(': [0x02,0x04,0x08,0x08,0x08,0x04,0x02], + ')': [0x08,0x04,0x02,0x02,0x02,0x04,0x08], + '*': [0x00,0x11,0x0a,0x1f,0x0a,0x11,0x00], + '!': [0x04,0x04,0x04,0x04,0x04,0x00,0x04], + '?': [0x0e,0x11,0x01,0x02,0x04,0x00,0x04], + '#': [0x0a,0x0a,0x1f,0x0a,0x1f,0x0a,0x0a], + '&': [0x0e,0x11,0x10,0x0a,0x14,0x12,0x0d], + '=': [0x00,0x00,0x1f,0x00,0x1f,0x00,0x00], + '|': [0x04,0x04,0x04,0x04,0x04,0x04,0x04], +}; + +// 8×16 像素字体 (大字用, 主数字) +// 每字符严格 16 字节, 8 列全部有效 +// 来源: 经典 VGA 8x16 字符子集, 仪表盘用到的字符全覆盖 +const FONT_8x16 = { + ' ': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + '0': [0x00,0x7e,0xff,0xc3,0xc3,0xdb,0xdb,0xdb,0xdb,0xc3,0xc3,0xff,0x7e,0x00,0x00,0x00], + '1': [0x00,0x08,0x0c,0x0e,0xff,0xff,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00], + '2': [0x00,0x7e,0xff,0x03,0x03,0x07,0x0e,0x1c,0x38,0x70,0xe0,0xfe,0xfe,0x00,0x00,0x00], + '3': [0x00,0x7e,0xff,0x03,0x03,0x07,0x1e,0x1e,0x07,0x03,0x03,0xff,0x7e,0x00,0x00,0x00], + '4': [0x00,0x06,0x0e,0x1e,0x36,0x66,0xc6,0xff,0xff,0x06,0x06,0x06,0x06,0x00,0x00,0x00], + '5': [0x00,0xff,0xff,0xc0,0xc0,0xfe,0xff,0x03,0x03,0x03,0x03,0xff,0x7e,0x00,0x00,0x00], + '6': [0x00,0x07,0x0e,0x1c,0x38,0x70,0xfe,0xff,0xc3,0xc3,0xc3,0xff,0x7e,0x00,0x00,0x00], + '7': [0x00,0xff,0xff,0x03,0x03,0x06,0x06,0x0c,0x0c,0x18,0x18,0x30,0x30,0x00,0x00,0x00], + '8': [0x00,0x7e,0xff,0xc3,0xc3,0xff,0x7e,0x7e,0xff,0xc3,0xc3,0xff,0x7e,0x00,0x00,0x00], + '9': [0x00,0x7e,0xff,0xc3,0xc3,0xff,0x7f,0x0e,0x1c,0x38,0x70,0xe0,0xc0,0x00,0x00,0x00], + '%': [0x00,0x60,0x66,0x66,0x06,0x0c,0x18,0x30,0x60,0xc0,0x86,0x86,0x06,0x00,0x00,0x00], + '.': [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x3c,0x18,0x00,0x00], + ':': [0x00,0x00,0x00,0x00,0x18,0x3c,0x18,0x00,0x00,0x18,0x3c,0x18,0x00,0x00,0x00,0x00], + '/': [0x00,0x03,0x03,0x03,0x06,0x06,0x0c,0x0c,0x18,0x18,0x30,0x30,0x60,0x60,0x00,0x00], + '-': [0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00], + '+': [0x00,0x00,0x00,0x00,0x18,0x18,0x18,0xff,0xff,0x18,0x18,0x18,0x00,0x00,0x00,0x00], + 'h': [0x00,0xc0,0xc0,0xc0,0xde,0xe7,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0x00,0x00,0x00], + 'H': [0x00,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xff,0xff,0xc3,0xc3,0xc3,0xc3,0x00,0x00,0x00], + 'z': [0x00,0x00,0x00,0x00,0xfe,0xfe,0x06,0x0c,0x18,0x30,0x60,0xfe,0xfe,0x00,0x00,0x00], + 'Z': [0x00,0x7e,0xff,0x03,0x03,0x06,0x0c,0x18,0x30,0x60,0xc0,0xff,0x7e,0x00,0x00,0x00], + 'G': [0x00,0x7e,0xff,0xc3,0xc0,0xc0,0xc0,0xdf,0xdf,0xc3,0xc3,0xff,0x7e,0x00,0x00,0x00], + 'g': [0x00,0x00,0x00,0x7f,0xff,0xc3,0xc3,0xff,0x7f,0x03,0x03,0xff,0x7e,0x00,0x00,0x00], + 'M': [0x00,0xc3,0xe7,0xff,0xdb,0xdb,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0x00,0x00,0x00], + 'm': [0x00,0x00,0x00,0xde,0xe7,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0x00,0x00,0x00], +}; + +/* ============================================================ + * Section 2: RLCDSimulator - 400×300 1-bit Canvas 渲染器 + * ============================================================ */ + +class RLCDSimulator { + constructor(canvas) { + this.W = 400; + this.H = 300; + this.canvas = canvas; + this.canvas.width = this.W; + this.canvas.height = this.H; + this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); + this.ctx.imageSmoothingEnabled = false; + this.imageData = this.ctx.createImageData(this.W, this.H); + this.invert = false; + this.clear(true); + } + + // 设置反色(白底黑字 ↔ 黑底白字) + setInvert(v) { + this.invert = !!v; + } + + // 清屏 (white = 0=白, 1=黑; invert 会反转) + clear(white = true) { + const v = white ? 255 : 0; + const actual = this.invert ? (255 - v) : v; + const data = this.imageData.data; + for (let i = 0; i < data.length; i += 4) { + data[i] = actual; data[i + 1] = actual; data[i + 2] = actual; data[i + 3] = 255; + } + } + + // 写一个像素 (on=true=黑, false=白) + setPixel(x, y, on) { + x = x | 0; y = y | 0; + if (x < 0 || x >= this.W || y < 0 || y >= this.H) return; + const isOn = this.invert ? !on : on; + const v = isOn ? 0 : 255; + const idx = (y * this.W + x) * 4; + this.imageData.data[idx] = v; + this.imageData.data[idx + 1] = v; + this.imageData.data[idx + 2] = v; + this.imageData.data[idx + 3] = 255; + } + + // 填充矩形 + fillRect(x, y, w, h, on) { + for (let yy = y; yy < y + h; yy++) { + for (let xx = x; xx < x + w; xx++) { + this.setPixel(xx, yy, on); + } + } + } + + // 描边矩形 (1px 边框) + strokeRect(x, y, w, h, on) { + this.fillRect(x, y, w, 1, on); + this.fillRect(x, y + h - 1, w, 1, on); + this.fillRect(x, y, 1, h, on); + this.fillRect(x + w - 1, y, 1, h, on); + } + + // 画线 (Bresenham) + line(x0, y0, x1, y1, on) { + x0 = x0 | 0; y0 = y0 | 0; x1 = x1 | 0; y1 = y1 | 0; + const dx = Math.abs(x1 - x0); + const dy = -Math.abs(y1 - y0); + const sx = x0 < x1 ? 1 : -1; + const sy = y0 < y1 ? 1 : -1; + let err = dx + dy; + while (true) { + this.setPixel(x0, y0, on); + if (x0 === x1 && y0 === y1) break; + const e2 = 2 * err; + if (e2 >= dy) { err += dy; x0 += sx; } + if (e2 <= dx) { err += dx; y0 += sy; } + } + } + + // 画粗线 (多次 setPixel 偏移) + lineThick(x0, y0, x1, y1, on, thickness) { + if (thickness <= 1) { this.line(x0, y0, x1, y1, on); return; } + const half = (thickness - 1) / 2; + for (let oy = -Math.floor(half); oy <= Math.ceil(half); oy++) { + for (let ox = -Math.floor(half); ox <= Math.ceil(half); ox++) { + if (ox * ox + oy * oy <= half * half + 0.5) { + this.line(x0 + ox, y0 + oy, x1 + ox, y1 + oy, on); + } + } + } + } + + // 绘制文本 (5×7 字体) + text5x7(x, y, str, on) { + this.textSize(str, x, y, 8, on); // 改用真实 8pt(近似 5x7 视觉大小,但清晰得多) + } + + // 绘制文本 (8×16 字体, 用于大数字) + text8x16(x, y, str, on) { + this.textSize(str, x, y, 16, on); + } + + // 通用文本绘制:用 Canvas fillText 渲染,再阈值化合成到 imageData + // size 对应 LVGL montserrat 字号(14/20/28/48) + // 这样字号真实反映板子效果 + textSize(str, x, y, size, on) { + str = String(str); + if (str.length === 0) return; + // 用一个离屏 canvas 渲染文字 + const off = document.createElement('canvas'); + // 估算文字尺寸(字符宽 ≈ size * 0.6,留 20% 余量) + const approxW = Math.ceil(str.length * size * 0.7) + 4; + const approxH = size + 4; + off.width = approxW; + off.height = approxH; + const octx = off.getContext('2d'); + octx.imageSmoothingEnabled = false; + octx.fillStyle = '#fff'; + octx.fillRect(0, 0, approxW, approxH); + octx.fillStyle = '#000'; + octx.font = `700 ${size}px "Montserrat", "Segoe UI", -apple-system, sans-serif`; + octx.textBaseline = 'top'; + octx.fillText(str, 1, 1); + // 读像素, 按亮度阈值化合成到主 imageData + // 注意: 背景是不透明白色, alpha 恒为 255, 必须检测 RGB 亮度 + const src = octx.getImageData(0, 0, approxW, approxH).data; + for (let py = 0; py < approxH; py++) { + for (let px = 0; px < approxW; px++) { + const tx = x + px; + const ty = y + py; + if (tx < 0 || tx >= this.W || ty < 0 || ty >= this.H) continue; + const idx = (py * approxW + px) * 4; + // 取 R 通道(背景白=255, 字黑<128), AA 边缘是灰色(128-254) + const r = src[idx]; + // r < 160 = 字的核心或较深 AA 边缘 = 黑像素 + if (r < 160) { + this.setPixel(tx, ty, on); + } + } + } + } + + // 测量文本宽度(跟 textSize 配套) + textWidth(str, size) { + return Math.ceil(String(str).length * size * 0.62); + } + + // 等宽字体文本 - 终端 CLI 风格核心 + // 等宽字每个字符宽度固定, 用它画表格天然对齐 + textMono(str, x, y, size, on, weight = '400') { + str = String(str); + if (str.length === 0) return; + const off = document.createElement('canvas'); + // 等宽字字符宽 ≈ size * 0.6 + const approxW = Math.ceil(str.length * size * 0.65) + 4; + const approxH = size + 6; + off.width = approxW; + off.height = approxH; + const octx = off.getContext('2d'); + octx.imageSmoothingEnabled = false; + octx.fillStyle = '#fff'; + octx.fillRect(0, 0, approxW, approxH); + octx.fillStyle = '#000'; + // 优先用 Consolas(Windows) / Menlo(Mac) / Courier, 都是等宽 + octx.font = `${weight} ${size}px "Consolas", "Menlo", "Courier New", monospace`; + octx.textBaseline = 'top'; + octx.fillText(str, 2, 2); + const src = octx.getImageData(0, 0, approxW, approxH).data; + for (let py = 0; py < approxH; py++) { + for (let px = 0; px < approxW; px++) { + const tx = x + px; + const ty = y + py; + if (tx < 0 || tx >= this.W || ty < 0 || ty >= this.H) continue; + const r = src[(py * approxW + px) * 4]; + if (r < 160) this.setPixel(tx, ty, on); + } + } + } + + // 等宽字体宽度(每字符固定宽) + textMonoWidth(str, size) { + return Math.ceil(String(str).length * size * 0.62); + } + + // 进度条 (1px 边框 + 填充) + bar(x, y, w, h, percent, on) { + const p = Math.max(0, Math.min(100, percent)); + this.strokeRect(x, y, w, h, on); // 边框同色(白底上画黑框) + const fillW = Math.max(0, Math.round((w - 2) * p / 100)); + if (fillW > 0) this.fillRect(x + 1, y + 1, fillW, h - 2, on); + } + + // 画水平细分隔线 + hline(x, y, w, on) { + this.fillRect(x, y, w, 1, on); + } + + // 提交到 Canvas + flush() { + this.ctx.putImageData(this.imageData, 0, 0); + } + + // 测量文本宽度(向后兼容旧 API) + textWidth5x7(str) { return this.textWidth(str, 8); } + textWidth8x16(str) { return this.textWidth(str, 16); } +} + +/* ============================================================ + * Section 3: 仪表盘绘制 + * + * 布局 (400×300 横屏): + * y=0~20 顶部状态条 (电量/连接/温湿度) + * y=22 分隔线 + * y=24~86 CPU 区 (大字% + GHz + 曲线) + * y=88 分隔线 + * y=90~110 MEM+DSK+NET 集成行 + * y=112 分隔线 + * y=114~170 Codex 区 + * y=172 分隔线 + * y=174~296 智谱区 + * ============================================================ */ + +/* ============================================================ + * Section 3: 仪表盘绘制 - 终端 CLI 风格 + 纵向左右二分构图 + * + * 布局思路: + * - 左半屏(0~200):CPU 主角 - 大数字 + 频率 + 曲线 + * - 右半屏(200~400):其它指标纵向堆叠 - MEM/DSK/NET/CODEX/智谱 + * - 顶部状态条横跨全宽 + * - 底部 token 进度条横跨全宽 + * + * 这样横向不再拥挤, 纵向充分利用 300 高度 + * + * 布局 (400×300 横屏): + * y=0~22 顶部状态栏(全宽) + * y=24 分隔线 + * y=26~198 主区(左右二分) + * 左 (0~198): CPU 大字 + 频率 + 曲线 + * 右 (200~400): MEM/DSK/NET 纵向堆叠 + * y=200 分隔线 + * y=204~238 CODEX 行 + * y=240 分隔线 + * y=244~278 智谱行 + * y=280 分隔线 + * y=284~296 TOKEN 进度条(全宽) + * ============================================================ */ + +function renderDashboard(sim, data) { + sim.clear(true); + const ON = true; + + const mono = (str, x, y, size, weight) => sim.textMono(str, x, y, size, ON, weight); + const monoW = (str, size) => sim.textMonoWidth(str, size); + + // ============ 4 宫格分隔线(细十字线) ============ + // 竖中线 x=200,全高 + sim.fillRect(200, 0, 1, 300, ON); + // 横中线 y=170(上半 170 高 / 下半 130 高) + sim.fillRect(0, 170, 400, 1, ON); + + // ============ 左上格 - CPU (0~200, 0~170) ============ + // 状态条融合在格顶 (y=4~18) + mono(`[SYS] ${data.battery}% ${data.connection}`, 4, 4, 14, '700'); + mono(`${data.temp}C ${data.humidity}%`, 4, 20, 14); + // CPU 标签 + mono('CPU', 8, 40, 14, '700'); + // 大字 CPU% (36pt, 适配 200 宽格) + mono(`${data.cpuPercent}%`, 8, 56, 36, '700'); + // 频率 + mono(`${data.cpuGhz}GHz`, 8, 96, 20, '700'); + // CPU 曲线(左上格内) + drawCpuCurve(sim, 8, 122, 184, 40, data.cpuHistory, ON); + + // ============ 右上格 - MEM/DSK/NET (200~400, 0~170) ============ + // 右上角时间 + const clock = data.clock || '12:00'; + mono(clock, 400 - monoW(clock, 14) - 4, 4, 14, '700'); + + // MEM + mono('MEM', 208, 24, 14, '700'); + mono(`${data.memPercent}%`, 400 - monoW(`${data.memPercent}%`, 14) - 4, 24, 14, '700'); + mono(`${data.memUsed}/${data.memTotal}`, 208, 42, 14); + // MEM 横向柱(格内宽 184) + sim.bar(208, 60, 184, 8, data.memPercent, ON); + + // DSK + mono('DSK', 208, 76, 14, '700'); + mono(`${data.diskText}`, 208, 92, 14); + mono(`${data.diskRW}`, 208, 108, 14); + + // NET + mono('NET', 208, 124, 14, '700'); + mono(`D ${data.netDown}`, 208, 140, 14); + mono(`U ${data.netUp}`, 400 - monoW(`U ${data.netUp}`, 14) - 4, 140, 14); + + // ============ 左下格 - CODEX (0~200, 170~300) ============ + drawUsageBars(sim, 6, 178, 188, { + title: 'CODEX', + subTitle: '用量', + topBar: { + used: data.codex5hUsed, + limit: data.codex5hLimit, + resetMs: data.codex5hResetMs, + unit: 'h', + }, + botBar: { + used: data.codexWeekUsed, + limit: data.codexWeekLimit, + resetMs: data.codexWeekResetMs, + unit: 'h', + }, + }, ON); + + // ============ 右下格 - 智谱 (200~400, 170~300) ============ + drawUsageBars(sim, 206, 178, 188, { + title: '智谱', + subTitle: 'token', + topBar: { + used: data.zhipu5hUsed, + limit: data.zhipu5hLimit, + resetMs: data.zhipu5hResetMs, + unit: 'M', + }, + botBar: { + used: data.zhipuWeekUsed, + limit: data.zhipuWeekLimit, + resetMs: data.zhipuWeekResetMs, + unit: 'M', + }, + }, ON); +} + +// ASCII 进度条: [######### ] 50% +function asciiBar(percent, width) { + const p = Math.max(0, Math.min(100, percent)); + const filled = Math.round(width * p / 100); + const empty = width - filled; + return '[' + '#'.repeat(filled) + ' '.repeat(empty) + ']'; +} + +// 智能日期格式化: 距离 ≤ 24h 显示时间, > 24h 显示日期 +function formatResetTime(resetMs) { + const now = Date.now(); + const diff = resetMs - now; // 正数=未来, 负数=过去 + const absDiff = Math.abs(diff); + const date = new Date(resetMs); + const pad = (n) => String(n).padStart(2, '0'); + if (absDiff <= 24 * 3600 * 1000) { + // 24 小时内: 显示时间 HH:MM + return `${pad(date.getHours())}:${pad(date.getMinutes())}`; + } else { + // 超过 24 小时: 显示日期 MM-DD + return `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; + } +} + +// 横向双柱状图(上下两条, 每条柱子水平延伸) +// 样式参考用户给的: +// CODEX token消耗 +// [######### ] 64% 16:41 +// [########## ] 75% 06-28 +// +// 参数: +// sim, x, y, w, on +// title - 区段标题(如 'CODEX' / '智谱') +// subTitle - 副标题(如 'token消耗', 显示在标题右侧) +// topBar - { used, limit, resetMs, unit } 上柱(5h) +// botBar - { used, limit, resetMs, unit } 下柱(周) +function drawUsageBars(sim, x, y, w, opts, on) { + const mono = (s, xx, yy, sz, weight) => sim.textMono(s, xx, yy, sz, on, weight); + const monoW = (s, sz) => sim.textMonoWidth(s, sz); + + const barH = 10; // 柱子高度 + const gap = 6; // 柱子之间垂直间距 + + // 第一行:标题 + 副标题 + mono(opts.title, x, y, 14, '700'); + if (opts.subTitle) { + mono(opts.subTitle, x + w - monoW(opts.subTitle, 14), y, 14, '700'); + } + + // 上柱(5h) + const y2 = y + 18; + // 柱子宽度:总宽减去右侧"64% 16:41"的预留空间(约 90px) + const rightReserve = 90; + const barW = w - rightReserve; + const topPct = opts.topBar.used / Math.max(0.001, opts.topBar.limit) * 100; + const topPctInt = Math.round(topPct); + sim.bar(x, y2, barW, barH, Math.min(100, topPct), on); + // 右侧:百分比 + 倒计时 + const topReset = formatResetTime(opts.topBar.resetMs); + const topRightStr = `${String(topPctInt).padStart(3)}% ${topReset}`; + mono(topRightStr, x + w - monoW(topRightStr, 14), y2 - 2, 14); + + // 下柱(周) + const y3 = y2 + barH + gap; + const botPct = opts.botBar.used / Math.max(0.001, opts.botBar.limit) * 100; + const botPctInt = Math.round(botPct); + sim.bar(x, y3, barW, barH, Math.min(100, botPct), on); + const botReset = formatResetTime(opts.botBar.resetMs); + const botRightStr = `${String(botPctInt).padStart(3)}% ${botReset}`; + mono(botRightStr, x + w - monoW(botRightStr, 14), y3 - 2, 14); +} + +// CPU 曲线绘制(终端风:粗折线) +function drawCpuCurve(sim, x, y, w, h, history, on) { + if (!history || history.length === 0) return; + const n = history.length; + for (let i = 0; i < n - 1; i++) { + const x0 = x + Math.round(i * w / (n - 1)); + const x1 = x + Math.round((i + 1) * w / (n - 1)); + const v0 = Math.max(0, Math.min(100, history[i])); + const v1 = Math.max(0, Math.min(100, history[i + 1])); + const y0 = y + h - Math.round(v0 * h / 100); + const y1 = y + h - Math.round(v1 * h / 100); + sim.lineThick(x0, y0, x1, y1, on, 2); + } +} + +/* ============================================================ + * Section 4: 默认数据 & 状态 + * ============================================================ */ + +const DEFAULT_DATA = { + // 顶部状态条 + battery: 87, + connection: 'USB', // USB / BLE / WIFI / AP + temp: 24.3, + humidity: 58, + clock: '14:32', + + // CPU + cpuPercent: 42, + cpuGhz: '3.61', + cpuHistory: generateDefaultCpuHistory(), + + // 内存 + memPercent: 68, + memUsed: '10.9G', + memTotal: '16.0G', + + // 硬盘 + diskText: 'C:62% D:46%', + diskRW: 'R:1.2M W:0.4M', + + // 网络 + netDown: '2.4M', + netUp: '0.3M', + + // Codex + codexToday: 5.0, + codexWeek: 32, + codexResetIn: '04:12', + codexResetsAvail: 8, + codexResetsTotal: 10, + // 新增:柱状图用的限额数据 + codex5hUsed: 3.2, // 5h 已用 + codex5hLimit: 5.0, // 5h 限额 + codexWeekUsed: 32, // 周已用 + codexWeekLimit: 40, // 周限额 + codex5hResetMs: Date.now() + 2 * 3600 * 1000, // 5h 下次重置(2h 后) + codexWeekResetMs: Date.now() + 3 * 24 * 3600 * 1000, // 周下次重置(3 天后) + + // 智谱 + zhipuStatus: 0, // 0=OK 1=SLOW 2=ERR + zhipuBalance: '4.82', + zhipuCallsToday: 289, + zhipuLatency: 420, + zhipuPlan: 'PRO', + zhipuTokensUsed: 1.2, // M + zhipuTokensTotal: 5.0, // M + // 新增:智谱限额柱状图(跟 Codex 同款样式) + zhipu5hUsed: 2.4, + zhipu5hLimit: 5.0, + zhipuWeekUsed: 18, + zhipuWeekLimit: 40, + zhipu5hResetMs: Date.now() + 5 * 3600 * 1000, // 5h 后重置 + zhipuWeekResetMs: Date.now() + 5 * 24 * 3600 * 1000, // 5 天后重置 + + // 其他 + model: 'mainline', + uptime: '04:12:35', +}; + +function generateDefaultCpuHistory() { + // 生成 32 个点的仿真 CPU 曲线 + const arr = []; + let v = 35; + for (let i = 0; i < 32; i++) { + v += (Math.random() - 0.5) * 18; + v = Math.max(8, Math.min(95, v)); + arr.push(Math.round(v)); + } + return arr; +} + +// 当前状态 +let data = structuredClone(DEFAULT_DATA); +let zoom = 2; +let showGrid = true; +let showGhost = false; +let invertColors = false; +let bridgeOnline = false; + +let firmwares = [ + { + id: 'factory-v1', + name: '微雪出厂固件 V1', + type: 'whole-image', + path: 'D:\\esp\\waveshare-rlcd-4.2\\03_Firmware\\01_Factory_V1.bin', + offset: '0x0', + ready: true, + description: '官方出厂整包', + }, + { + id: 'custom-workbench', + name: '自制仪表盘固件', + type: 'idf-project', + projectPath: 'D:\\AI Code\\ESP-32', + artifact: 'build\\rlcd_clock.bin', + ready: false, + description: '当前预览配置编译产物', + }, +]; +let selectedFirmwareId = 'custom-workbench'; + +/* ============================================================ + * Section 5: DOM 工具 & 渲染 + * ============================================================ */ + +const $ = (id) => document.getElementById(id); + +const els = { + canvas: $('screen'), + canvasFrame: $('canvasFrame'), + bridgeState: $('bridgeState'), + zoomLabel: $('zoomLabel'), + dataBody: $('dataBody'), + firmwareList: $('firmwareList'), + portInput: $('portInput'), + logBox: $('logBox'), + flashModal: $('flashModal'), + flashSummary: $('flashSummary'), +}; + +const sim = new RLCDSimulator(els.canvas); + +function setLog(msg) { + els.logBox.textContent = msg; + // 自动展开日志卡片 + $('logCard').classList.remove('collapsed'); +} + +function updateBridgeState() { + els.bridgeState.classList.toggle('ok', bridgeOnline); + els.bridgeState.classList.toggle('bad', !bridgeOnline); + els.bridgeState.textContent = bridgeOnline ? '桥接已连接' : '桥接未连接'; +} + +function applyZoom() { + // 缩放整个设备外壳(包括 bezel + canvas),而不是只缩放 canvas + document.getElementById('deviceShell').style.transform = `scale(${zoom})`; + els.zoomLabel.textContent = `${Math.round(zoom * 100)}%`; +} + +function renderScreen() { + sim.setInvert(invertColors); + sim.clear(!invertColors); + renderDashboard(sim, data); + sim.flush(); + + els.canvasFrame.classList.toggle('show-grid', showGrid); + // 残影效果(简化:半透明叠加,这里只切 class) + els.canvasFrame.classList.toggle('ghost', showGhost); +} + +/* ============================================================ + * Section 6: 字段编辑面板(自动从 DEFAULT_DATA 生成) + * ============================================================ */ + +// 字段定义(分组, 每组有标题 + 字段列表) +const FIELD_GROUPS = [ + { + title: '顶部状态条', + fields: [ + { key: 'battery', label: '电量 %', type: 'number', min: 0, max: 100 }, + { key: 'connection', label: '连接', type: 'select', options: ['USB', 'BLE', 'WIFI', 'AP'] }, + { key: 'temp', label: '温度 C', type: 'number', step: 0.1 }, + { key: 'humidity', label: '湿度 %', type: 'number', min: 0, max: 100 }, + { key: 'clock', label: '时间', type: 'text' }, + ], + }, + { + title: 'CPU', + fields: [ + { key: 'cpuPercent', label: '使用率 %', type: 'range', min: 0, max: 100 }, + { key: 'cpuGhz', label: '频率 GHz', type: 'text' }, + ], + }, + { + title: '内存 / 硬盘 / 网络', + fields: [ + { key: 'memPercent', label: '内存 %', type: 'range', min: 0, max: 100 }, + { key: 'memUsed', label: '已用', type: 'text' }, + { key: 'memTotal', label: '总量', type: 'text' }, + { key: 'diskText', label: '盘符', type: 'text' }, + { key: 'diskRW', label: '读写', type: 'text' }, + { key: 'netDown', label: '下行', type: 'text' }, + { key: 'netUp', label: '上行', type: 'text' }, + ], + }, + { + title: 'Codex', + fields: [ + { key: 'codexToday', label: '今日 h', type: 'number', step: 0.1 }, + { key: 'codexWeek', label: '周 h', type: 'number' }, + { key: 'codexResetIn', label: '重置倒计时', type: 'text' }, + { key: 'codexResetsAvail', label: '可用次数', type: 'number', min: 0 }, + { key: 'codexResetsTotal', label: '总次数', type: 'number', min: 1 }, + ], + }, + { + title: '智谱', + fields: [ + { key: 'zhipuStatus', label: '状态', type: 'select', options: [0, 1, 2], labels: ['正常', '降速', '异常'] }, + { key: 'zhipuBalance', label: '余额', type: 'text' }, + { key: 'zhipuCallsToday', label: '今日调用', type: 'number' }, + { key: 'zhipuLatency', label: '延迟 ms', type: 'number' }, + { key: 'zhipuPlan', label: '套餐', type: 'text' }, + { key: 'zhipuTokensUsed', label: '已用 M', type: 'number', step: 0.1 }, + { key: 'zhipuTokensTotal', label: '总额 M', type: 'number', step: 0.1 }, + ], + }, +]; + +function renderDataPanel() { + els.dataBody.innerHTML = ''; + for (const group of FIELD_GROUPS) { + const groupEl = document.createElement('div'); + groupEl.className = 'data-group'; + + const title = document.createElement('div'); + title.className = 'data-group-title'; + title.textContent = group.title; + groupEl.appendChild(title); + + for (const f of group.fields) { + const fieldEl = document.createElement('div'); + fieldEl.className = 'field'; + + const label = document.createElement('label'); + label.textContent = f.label; + label.htmlFor = `f-${f.key}`; + fieldEl.appendChild(label); + + let input; + if (f.type === 'select') { + input = document.createElement('select'); + for (let i = 0; i < f.options.length; i++) { + const opt = document.createElement('option'); + opt.value = f.options[i]; + opt.textContent = f.labels ? f.labels[i] : f.options[i]; + input.appendChild(opt); + } + input.value = String(data[f.key]); + } else { + input = document.createElement('input'); + input.type = f.type === 'range' ? 'range' : (f.type === 'number' ? 'number' : 'text'); + if (f.min !== undefined) input.min = f.min; + if (f.max !== undefined) input.max = f.max; + if (f.step !== undefined) input.step = f.step; + input.value = data[f.key]; + } + input.id = `f-${f.key}`; + input.addEventListener('input', () => { + let v = input.value; + if (f.type === 'number' || f.type === 'range') { + v = parseFloat(v); + if (isNaN(v)) v = 0; + } + data[f.key] = v; + renderScreen(); + }); + fieldEl.appendChild(input); + + groupEl.appendChild(fieldEl); + } + + els.dataBody.appendChild(groupEl); + } +} + +/* ============================================================ + * Section 7: 固件列表渲染 + * ============================================================ */ + +function renderFirmwares() { + els.firmwareList.innerHTML = ''; + for (const fw of firmwares) { + const item = document.createElement('div'); + item.className = 'fw-item' + (fw.id === selectedFirmwareId ? ' active' : ''); + + const name = document.createElement('div'); + name.className = 'fw-name'; + name.textContent = fw.name; + + const badge = document.createElement('span'); + badge.className = 'badge' + (fw.ready ? '' : ' warn'); + badge.textContent = fw.ready ? '可用' : '需编译'; + + item.appendChild(name); + item.appendChild(badge); + + item.addEventListener('click', () => { + selectedFirmwareId = fw.id; + renderFirmwares(); + }); + + els.firmwareList.appendChild(item); + } +} + +/* ============================================================ + * Section 8: 桥接 API 调用 (保留) + * ============================================================ */ + +const API_BASE = 'http://127.0.0.1:8788'; + +async function fetchJson(path, options) { + try { + const response = await fetch(`${API_BASE}${path}`, options); + if (!response.ok) throw new Error(`${response.status} ${response.statusText}`); + bridgeOnline = true; + return await response.json(); + } catch (err) { + bridgeOnline = false; + throw err; + } +} + +async function refreshFirmwares() { + try { + const result = await fetchJson('/api/firmwares'); + if (result.firmwares || Array.isArray(result)) { + firmwares = result.firmwares || result; + } + } catch { + // 静默失败, 用默认列表 + } + updateBridgeState(); +} + +async function saveScreen() { + localStorage.setItem('rlcd-dashboard-data', JSON.stringify(data)); + try { + await fetchJson('/api/screen', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ dashboard: data }), + }); + setLog('配置已保存到 active-screen.json'); + } catch { + setLog('桥接未连接, 已暂存到浏览器本地'); + } + updateBridgeState(); +} + +async function generateCode() { + await saveScreen(); + try { + const result = await fetchJson('/api/generate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ dashboard: data }), + }); + setLog(result.message || '固件 UI 配置已生成'); + } catch { + setLog('生成失败: 桥接服务不可用'); + } + updateBridgeState(); +} + +async function buildCustomFirmware() { + await generateCode(); + setLog('正在编译自制固件...'); + try { + const result = await fetchJson('/api/build', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ firmwareId: 'custom-workbench' }), + }); + setLog(result.output || result.message || '编译完成'); + await refreshFirmwares(); + renderFirmwares(); + } catch { + setLog('编译失败: 桥接服务不可用'); + } + updateBridgeState(); +} + +function openFlashModal() { + const fw = firmwares.find(f => f.id === selectedFirmwareId) || firmwares[0]; + if (!fw) return; + const port = els.portInput.value.trim() || 'COM3'; + els.flashSummary.textContent = `端口 ${port} · 固件 ${fw.name} · 类型 ${ + fw.type === 'whole-image' ? '官方整包' : '自制工程' + }`; + els.flashModal.hidden = false; +} + +async function flashSelectedFirmware() { + const fw = firmwares.find(f => f.id === selectedFirmwareId) || firmwares[0]; + const port = els.portInput.value.trim() || 'COM3'; + els.flashModal.hidden = true; + setLog('正在烧录, 请不要拔掉开发板...'); + try { + const result = await fetchJson('/api/flash', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ firmwareId: fw.id, port, confirmed: true }), + }); + setLog(result.output || result.message || '烧录完成'); + } catch { + setLog('烧录失败: 桥接服务或端口不可用'); + } + updateBridgeState(); +} + +/* ============================================================ + * Section 9: 事件绑定 + * ============================================================ */ + +// 缩放 +$('zoomIn').addEventListener('click', () => { + zoom = Math.min(6, zoom + 0.5); + applyZoom(); +}); +$('zoomOut').addEventListener('click', () => { + zoom = Math.max(1, zoom - 0.5); + applyZoom(); +}); + +// 滚轮缩放(在整个设备外壳上) +$('deviceShell').addEventListener('wheel', (e) => { + e.preventDefault(); + if (e.deltaY < 0) zoom = Math.min(6, zoom + 0.25); + else zoom = Math.max(1, zoom - 0.25); + applyZoom(); +}, { passive: false }); + +// 切换开关 +function setupToggle(id, getter, setter) { + const btn = $(id); + const sync = () => btn.classList.toggle('on', getter()); + sync(); + btn.addEventListener('click', () => { + setter(!getter()); + sync(); + renderScreen(); + }); +} +setupToggle('gridToggle', () => showGrid, (v) => showGrid = v); +setupToggle('ghostToggle', () => showGhost, (v) => showGhost = v); +setupToggle('invertToggle', () => invertColors, (v) => invertColors = v); + +// 卡片折叠 +function setupCollapsible(cardId, headId) { + $(headId).addEventListener('click', () => { + $(cardId).classList.toggle('collapsed'); + }); +} +setupCollapsible('dataCard', 'dataCardHead'); +setupCollapsible('opCard', 'opCardHead'); +setupCollapsible('logCard', 'logCardHead'); + +// 按钮 +$('saveButton').addEventListener('click', saveScreen); +$('resetButton').addEventListener('click', () => { + data = structuredClone(DEFAULT_DATA); + data.cpuHistory = generateDefaultCpuHistory(); + localStorage.removeItem('rlcd-dashboard-data'); + renderDataPanel(); + renderScreen(); + setLog('已重置为默认数据'); +}); +$('generateButton').addEventListener('click', generateCode); +$('buildButton').addEventListener('click', buildCustomFirmware); +$('flashButton').addEventListener('click', openFlashModal); +$('refreshFirmwareButton')?.addEventListener('click', async () => { + await refreshFirmwares(); + renderFirmwares(); +}); +$('cancelFlashButton').addEventListener('click', () => { + els.flashModal.hidden = true; +}); +$('confirmFlashButton').addEventListener('click', flashSelectedFirmware); + +/* ============================================================ + * Section 10: 模拟实时数据(可选, 让曲线动起来) + * ============================================================ */ + +let simulating = true; +function simulateTick() { + if (!simulating) return; + // CPU 曲线推进 + if (data.cpuHistory.length >= 32) data.cpuHistory.shift(); + const last = data.cpuHistory[data.cpuHistory.length - 1] || 40; + let next = last + (Math.random() - 0.5) * 20; + next = Math.max(5, Math.min(95, Math.round(next))); + data.cpuHistory.push(next); + data.cpuPercent = next; + + // 同步面板上的 slider + const cpuInput = $('f-cpuPercent'); + if (cpuInput) cpuInput.value = next; + + renderScreen(); +} + +setInterval(simulateTick, 1500); + +/* ============================================================ + * Section 11: 启动 + * ============================================================ */ + +async function init() { + // 尝试从 localStorage 恢复 + try { + const saved = localStorage.getItem('rlcd-dashboard-data'); + if (saved) { + const parsed = JSON.parse(saved); + data = { ...structuredClone(DEFAULT_DATA), ...parsed }; + if (!data.cpuHistory || data.cpuHistory.length === 0) { + data.cpuHistory = generateDefaultCpuHistory(); + } + } + } catch { /* ignore */ } + + renderDataPanel(); + renderFirmwares(); + applyZoom(); + renderScreen(); + updateBridgeState(); + + // 尝试连桥接(失败也无所谓) + try { await refreshFirmwares(); renderFirmwares(); } catch {} +} + +init(); diff --git a/frontend-preview/firmwares.json b/frontend-preview/firmwares.json new file mode 100644 index 0000000..56980b0 --- /dev/null +++ b/frontend-preview/firmwares.json @@ -0,0 +1,28 @@ +{ + "firmwares": [ + { + "id": "factory-v1", + "name": "微雪出厂固件 V1", + "type": "whole-image", + "path": "D:\\esp\\waveshare-rlcd-4.2\\03_Firmware\\01_Factory_V1.bin", + "offset": "0x0", + "description": "官方出厂界面整包。" + }, + { + "id": "xiaozhi-v2", + "name": "微雪 XiaoZhi V2.1.0", + "type": "whole-image", + "path": "D:\\esp\\waveshare-rlcd-4.2\\03_Firmware\\02_XiaoZhi_V2.1.0.bin", + "offset": "0x0", + "description": "官方 XiaoZhi 示例整包。" + }, + { + "id": "custom-workbench", + "name": "自制模板固件", + "type": "idf-project", + "projectPath": "D:\\AI Code\\ESP-32", + "artifact": "build\\rlcd_clock.bin", + "description": "由当前预览配置生成并编译。" + } + ] +} diff --git a/frontend-preview/index.html b/frontend-preview/index.html new file mode 100644 index 0000000..7614ecf --- /dev/null +++ b/frontend-preview/index.html @@ -0,0 +1,125 @@ + + + + + + ESP32-S3-RLCD Workbench + + + +
+ +
+ +
+
+ ESP32-S3-RLCD-4.2 + Workbench · Canvas Pixel Preview +
+ 桥接服务检测中 +
+ + +
+
+
+
+ +
+
+
+ 400 × 300 · ST7305 + + monochrome RLCD +
+
+ + +
+
+ + 100% + +
+ + + +
+
+
+ + + +
+ + + + + + + diff --git a/frontend-preview/styles.css b/frontend-preview/styles.css new file mode 100644 index 0000000..4a2943a --- /dev/null +++ b/frontend-preview/styles.css @@ -0,0 +1,828 @@ +/* ============================================================ + ESP32-S3-RLCD Workbench — macOS 柔和风 + Canvas 像素级预览 + 浅色毛玻璃工具面板 + ============================================================ */ + +:root { + /* 背景层 */ + --bg-gradient-start: #f5f5f7; + --bg-gradient-end: #e8e8ed; + --noise-opacity: 0.04; + + /* 表面层(毛玻璃卡片) */ + --surface: rgba(255, 255, 255, 0.72); + --surface-strong: rgba(255, 255, 255, 0.92); + --surface-sunken: rgba(0, 0, 0, 0.03); + + /* 文字 */ + --ink: #1d1d1f; + --ink-soft: #424245; + --ink-mute: #86868b; + --ink-faint: #aeaeb2; + + /* 线条 */ + --hairline: rgba(0, 0, 0, 0.08); + --hairline-strong: rgba(0, 0, 0, 0.14); + + /* 强调色(iOS 系统色) */ + --accent: #007aff; + --accent-soft: rgba(0, 122, 255, 0.12); + --accent-strong: #0051d5; + --success: #34c759; + --warning: #ff9f0a; + --danger: #ff3b30; + --danger-soft: rgba(255, 59, 48, 0.12); + + /* 设备外壳 */ + --device-shell: #2c2c2e; + --device-bezel: #1c1c1e; + + /* 屏幕底色(反射屏灰白) */ + --screen-bg: #d8d8d0; + + /* 阴影 */ + --shadow-card: 0 8px 24px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04); + --shadow-device: 0 18px 50px rgba(0, 0, 0, 0.18), 0 4px 12px rgba(0, 0, 0, 0.08); + + /* 圆角 */ + --r-card: 12px; + --r-button: 8px; + --r-pill: 999px; + --r-device: 18px; + --r-bezel: 10px; + + color-scheme: light; + font-family: + -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", + "Segoe UI", "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei", + sans-serif; + font-size: 13px; + line-height: 1.45; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +[hidden] { + display: none !important; +} + +body { + margin: 0; + min-height: 100vh; + color: var(--ink); + background: + radial-gradient(circle at 20% 0%, rgba(0, 122, 255, 0.04), transparent 50%), + radial-gradient(circle at 80% 100%, rgba(255, 55, 95, 0.03), transparent 50%), + linear-gradient(180deg, var(--bg-gradient-start), var(--bg-gradient-end)); + background-attachment: fixed; +} + +button, +input, +select, +textarea { + font: inherit; + color: inherit; +} + +button { + cursor: pointer; + -webkit-appearance: none; + appearance: none; +} + +/* ============================================================ + 主布局 + ============================================================ */ + +.workbench { + min-height: 100vh; + display: grid; + grid-template-columns: minmax(0, 1fr) 360px; + gap: 16px; + padding: 16px; +} + +.stage { + display: grid; + grid-template-rows: auto 1fr; + gap: 14px; + min-width: 0; +} + +.panel { + display: flex; + flex-direction: column; + gap: 12px; + min-width: 0; + max-height: calc(100vh - 32px); + overflow-y: auto; + padding-right: 4px; +} + +/* ============================================================ + 顶部 Header + ============================================================ */ + +.header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 10px 16px; + background: var(--surface); + backdrop-filter: blur(20px) saturate(180%); + -webkit-backdrop-filter: blur(20px) saturate(180%); + border: 1px solid var(--hairline); + border-radius: var(--r-card); + box-shadow: var(--shadow-card); +} + +.header-title { + display: flex; + align-items: baseline; + gap: 10px; + min-width: 0; +} + +.header-title .product { + font-size: 14px; + font-weight: 600; + letter-spacing: -0.01em; +} + +.header-title .sub { + font-size: 11px; + color: var(--ink-mute); + font-weight: 400; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.bridge-pill { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px 4px 8px; + background: var(--surface-sunken); + border: 1px solid var(--hairline); + border-radius: var(--r-pill); + font-size: 11px; + color: var(--ink-mute); + white-space: nowrap; + transition: all 160ms ease; +} + +.bridge-pill::before { + content: ""; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--ink-faint); + transition: all 160ms ease; +} + +.bridge-pill.ok { + color: var(--success); + border-color: rgba(52, 199, 89, 0.3); + background: rgba(52, 199, 89, 0.08); +} + +.bridge-pill.ok::before { + background: var(--success); + box-shadow: 0 0 0 3px rgba(52, 199, 89, 0.18); +} + +.bridge-pill.bad { + color: var(--danger); + border-color: rgba(255, 59, 48, 0.24); + background: var(--danger-soft); +} + +.bridge-pill.bad::before { + background: var(--danger); +} + +/* ============================================================ + 舞台 - 设备预览 + ============================================================ */ + +.stage-area { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + padding: 24px 16px; + background: var(--surface); + backdrop-filter: blur(20px) saturate(180%); + -webkit-backdrop-filter: blur(20px) saturate(180%); + border: 1px solid var(--hairline); + border-radius: var(--r-card); + box-shadow: var(--shadow-card); + min-height: 0; +} + +/* 设备外壳 - 模拟开发板外观 */ +.device-shell { + position: relative; + padding: 14px 14px 10px; + background: linear-gradient(160deg, #3a3a3c, var(--device-shell)); + border-radius: var(--r-device); + box-shadow: var(--shadow-device); + border: 1px solid #000; +} + +/* 屏幕外框(黑色 bezel) */ +.screen-bezel { + padding: 14px; + background: var(--device-bezel); + border-radius: var(--r-bezel); + border: 1px solid #000; + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04); +} + +/* Canvas 容器 - 关键:固定 400×300, scale 放大整个 device-shell */ +.canvas-frame { + position: relative; + width: 400px; + height: 300px; + background: var(--screen-bg); + border: 1px solid #000; + overflow: hidden; + /* 反射屏的微妙纹理 */ + background-image: + repeating-linear-gradient( + 45deg, + transparent 0 2px, + rgba(0, 0, 0, 0.015) 2px 4px + ); +} + +/* Canvas 本身 - 物理像素 400×300, 不单独缩放 */ +.canvas-frame canvas { + display: block; + width: 400px; + height: 300px; + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; +} + +/* 关键:scale 应用到整个 device-shell, 让外框+bezel+canvas 一起放大 */ +.device-shell { + transform-origin: center center; + transition: transform 120ms ease-out; +} + +/* 像素网格叠加(可选) */ +.canvas-frame.show-grid::after { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background-image: + linear-gradient(rgba(0, 122, 255, 0.08) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 122, 255, 0.08) 1px, transparent 1px); + background-size: 8px 8px; +} + +/* 设备底部信息条 */ +.device-meta { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 8px; + padding: 0 4px; + color: rgba(255, 255, 255, 0.42); + font-size: 10px; + font-weight: 500; + letter-spacing: 0.02em; +} + +.device-meta .dots { + display: flex; + gap: 4px; +} + +.device-meta .dots span { + width: 4px; + height: 4px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); +} + +/* ============================================================ + 预览控件 + ============================================================ */ + +.preview-controls { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + justify-content: center; +} + +.zoom-group { + display: inline-flex; + align-items: center; + gap: 0; + background: var(--surface-sunken); + border: 1px solid var(--hairline); + border-radius: var(--r-button); + overflow: hidden; +} + +.zoom-btn { + width: 28px; + height: 28px; + border: 0; + background: transparent; + color: var(--ink-soft); + font-size: 16px; + font-weight: 500; + display: grid; + place-items: center; + transition: background 120ms ease; +} + +.zoom-btn:hover { + background: rgba(0, 0, 0, 0.05); +} + +.zoom-btn:active { + background: rgba(0, 0, 0, 0.08); +} + +.zoom-label { + padding: 0 10px; + font-size: 11px; + color: var(--ink-mute); + font-variant-numeric: tabular-nums; + min-width: 48px; + text-align: center; +} + +.toggle-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + background: var(--surface-sunken); + border: 1px solid var(--hairline); + border-radius: var(--r-button); + font-size: 11px; + color: var(--ink-mute); + cursor: pointer; + user-select: none; + transition: all 120ms ease; +} + +.toggle-chip:hover { + background: rgba(0, 0, 0, 0.05); +} + +.toggle-chip.on { + background: var(--accent-soft); + border-color: rgba(0, 122, 255, 0.24); + color: var(--accent-strong); +} + +.toggle-chip .dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--ink-faint); + transition: all 120ms ease; +} + +.toggle-chip.on .dot { + background: var(--accent); + box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2); +} + +/* ============================================================ + 右侧控制面板 + ============================================================ */ + +.card { + background: var(--surface); + backdrop-filter: blur(20px) saturate(180%); + -webkit-backdrop-filter: blur(20px) saturate(180%); + border: 1px solid var(--hairline); + border-radius: var(--r-card); + box-shadow: var(--shadow-card); + overflow: hidden; +} + +.card-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 10px 14px; + border-bottom: 1px solid var(--hairline); + background: rgba(255, 255, 255, 0.4); + cursor: pointer; + user-select: none; + transition: background 120ms ease; +} + +.card-head:hover { + background: rgba(255, 255, 255, 0.6); +} + +.card-head h2 { + margin: 0; + font-size: 11px; + font-weight: 600; + color: var(--ink-soft); + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.card-head .chevron { + color: var(--ink-faint); + font-size: 10px; + transition: transform 160ms ease; +} + +.card.collapsed .chevron { + transform: rotate(-90deg); +} + +.card-body { + padding: 12px 14px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.card.collapsed .card-body { + display: none; +} + +/* 数据字段分组 */ +.data-group { + padding-top: 8px; + border-top: 1px solid var(--hairline); +} + +.data-group:first-child { + padding-top: 0; + border-top: 0; +} + +.data-group-title { + font-size: 10px; + font-weight: 600; + color: var(--ink-mute); + letter-spacing: 0.06em; + text-transform: uppercase; + margin-bottom: 8px; +} + +/* 字段输入 */ +.field { + display: grid; + grid-template-columns: 88px 1fr; + align-items: center; + gap: 8px; + margin-bottom: 6px; +} + +.field label { + font-size: 11px; + color: var(--ink-soft); + font-weight: 400; +} + +.field input[type="text"], +.field input[type="number"], +.field select { + width: 100%; + height: 26px; + padding: 0 8px; + background: var(--surface-strong); + border: 1px solid var(--hairline); + border-radius: 6px; + font-size: 11px; + font-variant-numeric: tabular-nums; + color: var(--ink); + transition: all 120ms ease; +} + +.field input:focus, +.field select:focus { + outline: 0; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft); +} + +.field input[type="range"] { + width: 100%; + height: 4px; + -webkit-appearance: none; + appearance: none; + background: var(--hairline); + border-radius: var(--r-pill); + outline: 0; +} + +.field input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--accent); + cursor: pointer; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); +} + +.field input[type="range"]::-moz-range-thumb { + width: 14px; + height: 14px; + border: 0; + border-radius: 50%; + background: var(--accent); + cursor: pointer; +} + +/* 按钮 */ +.btn-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.btn-row.three { + grid-template-columns: 1fr 1fr 1fr; +} + +.btn { + height: 32px; + border-radius: var(--r-button); + border: 1px solid transparent; + font-size: 12px; + font-weight: 500; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + transition: all 120ms ease; + white-space: nowrap; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-primary { + background: var(--accent); + color: #fff; + border-color: var(--accent); +} + +.btn-primary:hover:not(:disabled) { + background: var(--accent-strong); + border-color: var(--accent-strong); +} + +.btn-secondary { + background: var(--surface-strong); + color: var(--ink); + border-color: var(--hairline-strong); +} + +.btn-secondary:hover:not(:disabled) { + background: #fff; + border-color: rgba(0, 0, 0, 0.2); +} + +.btn-danger { + background: var(--danger); + color: #fff; + border-color: var(--danger); +} + +.btn-danger:hover:not(:disabled) { + background: #d70015; + border-color: #d70015; +} + +.btn-text { + background: transparent; + color: var(--accent); + border: 0; + padding: 2px 4px; + height: auto; + font-size: 11px; +} + +.btn-text:hover:not(:disabled) { + color: var(--accent-strong); + text-decoration: underline; +} + +/* 固件列表 */ +.fw-list { + display: flex; + flex-direction: column; + gap: 4px; +} + +.fw-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 8px 10px; + background: var(--surface-strong); + border: 1px solid var(--hairline); + border-radius: 6px; + cursor: pointer; + transition: all 120ms ease; +} + +.fw-item:hover { + background: #fff; +} + +.fw-item.active { + border-color: var(--accent); + background: var(--accent-soft); +} + +.fw-name { + font-size: 11px; + font-weight: 500; + color: var(--ink); + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.fw-meta { + font-size: 10px; + color: var(--ink-mute); +} + +.badge { + flex-shrink: 0; + padding: 1px 6px; + border-radius: var(--r-pill); + font-size: 9px; + font-weight: 600; + letter-spacing: 0.02em; + background: rgba(0, 122, 255, 0.12); + color: var(--accent); +} + +.badge.warn { + background: rgba(255, 159, 10, 0.16); + color: var(--warning); +} + +.badge.muted { + background: var(--surface-sunken); + color: var(--ink-mute); +} + +/* 日志框 */ +.log-box { + margin-top: 8px; + padding: 8px 10px; + background: var(--surface-sunken); + border: 1px solid var(--hairline); + border-radius: 6px; + font-family: "SF Mono", ui-monospace, Menlo, Consolas, monospace; + font-size: 10px; + line-height: 1.5; + color: var(--ink-soft); + min-height: 60px; + max-height: 140px; + overflow-y: auto; + white-space: pre-wrap; + word-break: break-all; +} + +.log-box:empty::before { + content: "等待操作…"; + color: var(--ink-faint); + font-style: italic; +} + +/* ============================================================ + 模态框 + ============================================================ */ + +.modal-backdrop { + position: fixed; + inset: 0; + z-index: 100; + display: grid; + place-items: center; + background: rgba(0, 0, 0, 0.32); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + padding: 20px; + animation: backdrop-fade 160ms ease; +} + +@keyframes backdrop-fade { + from { opacity: 0; } + to { opacity: 1; } +} + +.modal { + width: min(380px, 100%); + background: var(--surface-strong); + backdrop-filter: blur(40px) saturate(180%); + -webkit-backdrop-filter: blur(40px) saturate(180%); + border: 1px solid var(--hairline); + border-radius: 14px; + box-shadow: 0 24px 60px rgba(0, 0, 0, 0.24); + padding: 18px; + animation: modal-rise 200ms ease; +} + +@keyframes modal-rise { + from { transform: translateY(8px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +.modal h2 { + margin: 0 0 6px; + font-size: 15px; + font-weight: 600; +} + +.modal p { + margin: 0; + color: var(--ink-soft); + font-size: 12px; + line-height: 1.5; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 16px; +} + +/* ============================================================ + 响应式 + ============================================================ */ + +@media (max-width: 1100px) { + .workbench { + grid-template-columns: 1fr; + } + .panel { + max-height: none; + } +} + +@media (max-width: 640px) { + .workbench { + padding: 10px; + } + .device-shell { + transform: scale(0.85); + transform-origin: top center; + } +} + +/* 滚动条(macOS 风) */ +.panel::-webkit-scrollbar, +.log-box::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +.panel::-webkit-scrollbar-thumb, +.log-box::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.18); + border-radius: 3px; +} + +.panel::-webkit-scrollbar-thumb:hover, +.log-box::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.3); +} + +.panel::-webkit-scrollbar-track, +.log-box::-webkit-scrollbar-track { + background: transparent; +} diff --git a/frontend-preview/templates.json b/frontend-preview/templates.json new file mode 100644 index 0000000..a08357a --- /dev/null +++ b/frontend-preview/templates.json @@ -0,0 +1,98 @@ +{ + "templates": [ + { + "id": "clock", + "name": "时钟状态屏", + "tag": "Clock", + "description": "大号时间、日期、状态和底部说明。", + "defaults": { + "title": "LOCAL CLOCK", + "orientation": "landscape", + "density": "comfortable", + "effects": { "ghost": true, "grid": true }, + "fields": { + "status": "ONLINE", + "time": "12:00", + "date": "2026-06-20", + "footerLeft": "ESP32-S3-RLCD", + "footerRight": "COM3" + } + }, + "fields": [ + { "key": "status", "label": "状态", "type": "text", "span": "half" }, + { "key": "time", "label": "时间", "type": "text", "span": "half" }, + { "key": "date", "label": "日期", "type": "text", "span": "full" }, + { "key": "footerLeft", "label": "底部左侧", "type": "text", "span": "half" }, + { "key": "footerRight", "label": "底部右侧", "type": "text", "span": "half" } + ] + }, + { + "id": "dual-metric", + "name": "双大数字屏", + "tag": "Dual", + "description": "两个核心数字并排展示,适合温湿度、计数、状态值。", + "defaults": { + "title": "ROOM STATUS", + "orientation": "landscape", + "density": "comfortable", + "effects": { "ghost": true, "grid": true }, + "fields": { + "status": "SENSOR", + "primaryLabel": "TEMP", + "primaryValue": "26", + "primaryUnit": "C", + "secondaryLabel": "HUM", + "secondaryValue": "58", + "secondaryUnit": "%", + "footerLeft": "Last refresh 2m", + "footerRight": "Battery 86%" + } + }, + "fields": [ + { "key": "status", "label": "状态", "type": "text", "span": "half" }, + { "key": "primaryLabel", "label": "数字一名称", "type": "text", "span": "half" }, + { "key": "primaryValue", "label": "数字一数值", "type": "number", "span": "half", "min": -99, "max": 999 }, + { "key": "primaryUnit", "label": "数字一单位", "type": "text", "span": "half" }, + { "key": "secondaryLabel", "label": "数字二名称", "type": "text", "span": "half" }, + { "key": "secondaryValue", "label": "数字二数值", "type": "number", "span": "half", "min": -99, "max": 999 }, + { "key": "secondaryUnit", "label": "数字二单位", "type": "text", "span": "half" }, + { "key": "footerLeft", "label": "底部左侧", "type": "text", "span": "half" }, + { "key": "footerRight", "label": "底部右侧", "type": "text", "span": "half" } + ] + }, + { + "id": "meter", + "name": "仪表状态屏", + "tag": "Meter", + "description": "两条进度仪表,适合电量、容量、任务进度。", + "defaults": { + "title": "SYSTEM LOAD", + "orientation": "landscape", + "density": "comfortable", + "effects": { "ghost": true, "grid": true }, + "fields": { + "status": "LIVE", + "primaryLabel": "CPU", + "primaryValue": "64", + "primaryUnit": "%", + "secondaryLabel": "BATTERY", + "secondaryValue": "86", + "secondaryUnit": "%", + "footerLeft": "LAN READY", + "footerRight": "Refresh 5m" + } + }, + "fields": [ + { "key": "status", "label": "状态", "type": "text", "span": "half" }, + { "key": "primaryLabel", "label": "仪表一名称", "type": "text", "span": "half" }, + { "key": "primaryValue", "label": "仪表一进度", "type": "range", "span": "full", "min": 0, "max": 100 }, + { "key": "primaryUnit", "label": "仪表一单位", "type": "text", "span": "half" }, + { "key": "secondaryLabel", "label": "仪表二名称", "type": "text", "span": "half" }, + { "key": "secondaryValue", "label": "仪表二进度", "type": "range", "span": "full", "min": 0, "max": 100 }, + { "key": "secondaryUnit", "label": "仪表二单位", "type": "text", "span": "half" }, + { "key": "footerLeft", "label": "底部左侧", "type": "text", "span": "half" }, + { "key": "footerRight", "label": "底部右侧", "type": "text", "span": "half" } + ] + } + ] +} diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..7ad48e8 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,25 @@ +# ============================================================ +# main 组件 CMakeLists +# 编译 main.c 和 st7305_rlcd.c 两个源文件 +# ============================================================ +idf_component_register( + SRCS + "battery_monitor.c" + "main.c" + "metrics_ble.c" + "metrics_protocol.c" + "metrics_serial.c" + "temp_humidity_monitor.c" + "ui_screen.c" + "st7305_rlcd.c" + INCLUDE_DIRS + "." + "generated" + REQUIRES + esp_driver_uart + esp_driver_usb_serial_jtag + esp_driver_i2c + esp_adc + bt + nvs_flash +) diff --git a/main/battery_monitor.c b/main/battery_monitor.c new file mode 100644 index 0000000..95d3ba6 --- /dev/null +++ b/main/battery_monitor.c @@ -0,0 +1,230 @@ +#include "battery_monitor.h" + +#include + +#include "esp_adc/adc_cali.h" +#include "esp_adc/adc_cali_scheme.h" +#include "esp_adc/adc_oneshot.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "freertos/task.h" + +static const char *TAG = "battery"; + +#define BATTERY_ADC_UNIT ADC_UNIT_1 +#define BATTERY_ADC_CHANNEL ADC_CHANNEL_3 +#define BATTERY_ADC_ATTEN ADC_ATTEN_DB_12 +#define BATTERY_DIVIDER 3 + +static adc_oneshot_unit_handle_t s_adc_handle; +static adc_cali_handle_t s_cali_handle; +static bool s_cali_enabled; +static portMUX_TYPE s_battery_mux = portMUX_INITIALIZER_UNLOCKED; +static int s_latest_percent; +static int s_latest_millivolts; +static bool s_has_latest; + +typedef struct { + int millivolts; + int raw_avg; + int pin_millivolts; + int samples; +} battery_reading_t; + +typedef struct { + int millivolts; + int percent; +} battery_curve_point_t; + +static const battery_curve_point_t s_battery_curve[] = { + { 4200, 100 }, + { 4150, 95 }, + { 4110, 90 }, + { 4080, 85 }, + { 4020, 80 }, + { 3980, 75 }, + { 3920, 70 }, + { 3870, 60 }, + { 3820, 50 }, + { 3790, 40 }, + { 3770, 30 }, + { 3740, 20 }, + { 3700, 10 }, + { 3600, 5 }, + { 3300, 0 }, +}; + +static int clamp_percent(int value) +{ + if (value < 0) { + return 0; + } + if (value > 100) { + return 100; + } + return value; +} + +static int battery_percent_from_mv(int mv) +{ + if (mv <= 0) { + return 0; + } + + const int count = sizeof(s_battery_curve) / sizeof(s_battery_curve[0]); + if (mv >= s_battery_curve[0].millivolts) { + return s_battery_curve[0].percent; + } + + for (int i = 1; i < count; i++) { + const battery_curve_point_t high = s_battery_curve[i - 1]; + const battery_curve_point_t low = s_battery_curve[i]; + if (mv >= low.millivolts) { + int mv_range = high.millivolts - low.millivolts; + int percent_range = high.percent - low.percent; + int percent = low.percent; + if (mv_range > 0) { + percent += ((mv - low.millivolts) * percent_range + mv_range / 2) / mv_range; + } + return clamp_percent(percent); + } + } + + return s_battery_curve[count - 1].percent; +} + +static esp_err_t battery_adc_init(void) +{ + adc_oneshot_unit_init_cfg_t unit_config = { + .unit_id = BATTERY_ADC_UNIT, + .clk_src = ADC_RTC_CLK_SRC_DEFAULT, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_RETURN_ON_ERROR(adc_oneshot_new_unit(&unit_config, &s_adc_handle), TAG, "adc unit init failed"); + + adc_oneshot_chan_cfg_t channel_config = { + .atten = BATTERY_ADC_ATTEN, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ESP_RETURN_ON_ERROR(adc_oneshot_config_channel(s_adc_handle, BATTERY_ADC_CHANNEL, &channel_config), + TAG, "adc channel config failed"); + +#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = BATTERY_ADC_UNIT, + .chan = BATTERY_ADC_CHANNEL, + .atten = BATTERY_ADC_ATTEN, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + esp_err_t cali_ret = adc_cali_create_scheme_curve_fitting(&cali_config, &s_cali_handle); + s_cali_enabled = (cali_ret == ESP_OK); + if (!s_cali_enabled) { + ESP_LOGW(TAG, "ADC calibration unavailable: %s", esp_err_to_name(cali_ret)); + } +#endif + return ESP_OK; +} + +static battery_reading_t read_battery(void) +{ + int raw_sum = 0; + int mv_sum = 0; + int samples = 0; + battery_reading_t reading = { 0 }; + + for (int i = 0; i < 8; i++) { + int raw = 0; + if (adc_oneshot_read(s_adc_handle, BATTERY_ADC_CHANNEL, &raw) != ESP_OK) { + continue; + } + + raw_sum += raw; + samples++; + + if (s_cali_enabled) { + int pin_mv = 0; + if (adc_cali_raw_to_voltage(s_cali_handle, raw, &pin_mv) == ESP_OK) { + mv_sum += pin_mv; + } + } + } + + if (samples == 0) { + return reading; + } + + reading.samples = samples; + reading.raw_avg = raw_sum / samples; + + if (s_cali_enabled && mv_sum > 0) { + reading.pin_millivolts = mv_sum / samples; + reading.millivolts = reading.pin_millivolts * BATTERY_DIVIDER; + return reading; + } + + reading.pin_millivolts = (reading.raw_avg * 3100) / 4095; + reading.millivolts = reading.pin_millivolts * BATTERY_DIVIDER; + return reading; +} + +static void battery_task(void *arg) +{ + (void)arg; + int log_ticks = 0; + + while (1) { + battery_reading_t reading = read_battery(); + int percent = battery_percent_from_mv(reading.millivolts); + portENTER_CRITICAL(&s_battery_mux); + s_latest_percent = percent; + s_latest_millivolts = reading.millivolts; + s_has_latest = true; + portEXIT_CRITICAL(&s_battery_mux); + + if (log_ticks < 6 || (log_ticks % 6) == 0) { + ESP_LOGI(TAG, "adc raw=%d pin_mv=%d battery_mv=%d percent=%d cali=%d samples=%d", + reading.raw_avg, reading.pin_millivolts, reading.millivolts, + percent, s_cali_enabled ? 1 : 0, reading.samples); + } + log_ticks++; + vTaskDelay(pdMS_TO_TICKS(10000)); + } +} + +void battery_monitor_start(void) +{ + esp_err_t ret = battery_adc_init(); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "battery monitor disabled: %s", esp_err_to_name(ret)); + portENTER_CRITICAL(&s_battery_mux); + s_latest_percent = 0; + s_latest_millivolts = 0; + s_has_latest = true; + portEXIT_CRITICAL(&s_battery_mux); + return; + } + + xTaskCreate(battery_task, "battery", 4096, NULL, 2, NULL); +} + +bool battery_monitor_take_latest(int *percent, int *millivolts) +{ + if (percent == NULL || millivolts == NULL) { + return false; + } + + bool has_latest = false; + portENTER_CRITICAL(&s_battery_mux); + if (s_has_latest) { + *percent = s_latest_percent; + *millivolts = s_latest_millivolts; + s_has_latest = false; + has_latest = true; + } + portEXIT_CRITICAL(&s_battery_mux); + + return has_latest; +} diff --git a/main/battery_monitor.h b/main/battery_monitor.h new file mode 100644 index 0000000..d8f97ff --- /dev/null +++ b/main/battery_monitor.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void battery_monitor_start(void); +bool battery_monitor_take_latest(int *percent, int *millivolts); + +#ifdef __cplusplus +} +#endif diff --git a/main/generated/ui_config.h b/main/generated/ui_config.h new file mode 100644 index 0000000..f8aab57 --- /dev/null +++ b/main/generated/ui_config.h @@ -0,0 +1,30 @@ +/* Generated by tools/preview-bridge/server.py. */ +#pragma once + +#define UI_TEMPLATE_CLOCK 1 +#define UI_TEMPLATE_DUAL_METRIC 2 +#define UI_TEMPLATE_METER 3 +#define UI_ACTIVE_TEMPLATE UI_TEMPLATE_DUAL_METRIC + +#define UI_ORIENTATION_LANDSCAPE 0 +#define UI_ORIENTATION_PORTRAIT 1 +#define UI_ACTIVE_ORIENTATION 0 + +#define UI_DENSITY_COMFORTABLE 0 +#define UI_DENSITY_COMPACT 1 +#define UI_ACTIVE_DENSITY 0 + +#define UI_TITLE_TEXT "ROOM STATUS" +#define UI_STATUS_TEXT "SENSOR" +#define UI_TIME_TEXT "12:00" +#define UI_DATE_TEXT "2026-06-20" +#define UI_PRIMARY_LABEL_TEXT "TEMP" +#define UI_PRIMARY_VALUE_TEXT "26" +#define UI_PRIMARY_VALUE_INT 26 +#define UI_PRIMARY_UNIT_TEXT "C" +#define UI_SECONDARY_LABEL_TEXT "HUM" +#define UI_SECONDARY_VALUE_TEXT "58" +#define UI_SECONDARY_VALUE_INT 58 +#define UI_SECONDARY_UNIT_TEXT "%" +#define UI_FOOTER_LEFT_TEXT "Last refresh 2m" +#define UI_FOOTER_RIGHT_TEXT "Battery 86%" diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..4023dc8 --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,8 @@ +## IDF Component Manager Manifest File +## 声明我们这个 main 组件依赖的外部组件 +dependencies: + # LVGL 图形库(官方 XiaoZhi 用 9.3,我们跟进) + lvgl/lvgl: "~9.3.0" + # esp_lvgl_port - 乐鑫提供的 LVGL 与 ESP-IDF 集成胶水层 + # (帮我们管 LVGL 任务、锁、tick) + espressif/esp_lvgl_port: "~2.6.0" diff --git a/main/main.c b/main/main.c new file mode 100644 index 0000000..4155f2d --- /dev/null +++ b/main/main.c @@ -0,0 +1,123 @@ +#include + +#include "esp_err.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "lvgl.h" + +#include "battery_monitor.h" +#include "metrics_ble.h" +#include "metrics_serial.h" +#include "st7305_rlcd.h" +#include "temp_humidity_monitor.h" +#include "ui_screen.h" + +static const char *TAG = "rlcd_app"; + +static void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *color_p) +{ + uint16_t *buffer = (uint16_t *)color_p; + + for (int y = area->y1; y <= area->y2; y++) { + for (int x = area->x1; x <= area->x2; x++) { + uint8_t color = (*buffer < 0x7FFF) ? ST7305_COLOR_BLACK : ST7305_COLOR_WHITE; + st7305_rlcd_set_pixel(x, y, color); + buffer++; + } + } + + st7305_rlcd_flush(); + lv_display_flush_ready(disp); +} + +static void lvgl_tick_task(void *arg) +{ + (void)arg; + while (1) { + vTaskDelay(pdMS_TO_TICKS(5)); + lv_lock(); + lv_tick_inc(5); + lv_unlock(); + } +} + +static esp_err_t init_lvgl(void) +{ + lv_init(); + + lv_display_t *disp = lv_display_create(st7305_rlcd_get_width(), st7305_rlcd_get_height()); + lv_display_set_flush_cb(disp, lvgl_flush_cb); + + size_t buffer_size = LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565) + * st7305_rlcd_get_width() + * st7305_rlcd_get_height(); + uint8_t *buffer = (uint8_t *)heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM); + if (buffer == NULL) { + ESP_LOGE(TAG, "Failed to allocate LVGL buffer."); + return ESP_ERR_NO_MEM; + } + + lv_display_set_buffers(disp, buffer, NULL, buffer_size, LV_DISPLAY_RENDER_MODE_PARTIAL); + xTaskCreate(lvgl_tick_task, "lv_tick", 4096, NULL, 1, NULL); + return ESP_OK; +} + +void app_main(void) +{ + ESP_LOGI(TAG, "Starting ESP32-S3-RLCD workbench firmware."); + + ESP_ERROR_CHECK(st7305_rlcd_init()); + ESP_ERROR_CHECK(init_lvgl()); + + lv_lock(); + ui_screen_create(); + lv_unlock(); + + metrics_serial_start(); + metrics_ble_start(); + battery_monitor_start(); + temp_humidity_monitor_start(); + + int frame_counter = 0; + uint32_t ble_ui_update_count = 0; + while (1) { + vTaskDelay(pdMS_TO_TICKS(30)); + + ui_metrics_t metrics; + if (metrics_ble_take_latest(&metrics)) { + ui_screen_update_metrics_from_source(&metrics, "BLE"); + ble_ui_update_count++; + if ((ble_ui_update_count % 10) == 0) { + ESP_LOGI(TAG, "BLE UI updated: %lu", (unsigned long)ble_ui_update_count); + } + } + + if (metrics_serial_take_latest(&metrics)) { + ui_screen_update_metrics_from_source(&metrics, "USB"); + } + + int battery_percent = 0; + int battery_mv = 0; + if (battery_monitor_take_latest(&battery_percent, &battery_mv)) { + ui_screen_update_battery(battery_percent, battery_mv); + } + + int temperature_c10 = 0; + int humidity_percent = 0; + if (temp_humidity_monitor_take_latest(&temperature_c10, &humidity_percent)) { + ui_screen_update_environment(temperature_c10, humidity_percent); + } + + lv_lock(); + lv_timer_handler(); + + frame_counter++; + if (frame_counter >= 33) { + frame_counter = 0; + ui_screen_tick(); + } + lv_unlock(); + } +} diff --git a/main/metrics_ble.c b/main/metrics_ble.c new file mode 100644 index 0000000..bd335c4 --- /dev/null +++ b/main/metrics_ble.c @@ -0,0 +1,271 @@ +#include "metrics_ble.h" + +#include +#include + +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "host/ble_gap.h" +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "host/util/util.h" +#include "metrics_protocol.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "nvs_flash.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" +#include "ui_screen.h" + +static const char *TAG = "metrics_ble"; +static const char *DEVICE_NAME = "RLCD-Metrics"; + +// Service: 9f4af548-1a14-4fc1-8f5a-4b9f00000001 +// Write char: 9f4af548-1a14-4fc1-8f5a-4b9f00000002 +static const ble_uuid128_t metrics_svc_uuid = + BLE_UUID128_INIT(0x01, 0x00, 0x00, 0x00, 0x9f, 0x4b, 0x5a, 0x8f, + 0xc1, 0x4f, 0x14, 0x1a, 0x48, 0xf5, 0x4a, 0x9f); +static const ble_uuid128_t metrics_chr_uuid = + BLE_UUID128_INIT(0x02, 0x00, 0x00, 0x00, 0x9f, 0x4b, 0x5a, 0x8f, + 0xc1, 0x4f, 0x14, 0x1a, 0x48, 0xf5, 0x4a, 0x9f); + +static uint8_t own_addr_type; +static uint16_t metrics_chr_val_handle; +static QueueHandle_t s_metrics_queue; +static uint32_t s_ble_write_count; + +void ble_store_config_init(void); + +static int metrics_gap_event(struct ble_gap_event *event, void *arg); + +static const char *metrics_phy_name(uint8_t phy) +{ + switch (phy) { + case BLE_GAP_LE_PHY_1M: + return "1M"; + case BLE_GAP_LE_PHY_2M: + return "2M"; + case BLE_GAP_LE_PHY_CODED: + return "CODED"; + default: + return "UNKNOWN"; + } +} + +static void metrics_request_2m_phy(uint16_t conn_handle) +{ + int rc = ble_gap_set_prefered_le_phy(conn_handle, + BLE_GAP_LE_PHY_2M_MASK, + BLE_GAP_LE_PHY_2M_MASK, + BLE_GAP_LE_PHY_CODED_ANY); + if (rc == 0) { + ESP_LOGI(TAG, "requested BLE 5 2M PHY"); + } else { + ESP_LOGW(TAG, "request BLE 5 2M PHY failed: %d", rc); + } +} + +static int metrics_gatt_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + (void)conn_handle; + (void)arg; + + if (ctxt->op != BLE_GATT_ACCESS_OP_WRITE_CHR || attr_handle != metrics_chr_val_handle) { + return BLE_ATT_ERR_UNLIKELY; + } + + char line[512] = {0}; + uint16_t len = 0; + int rc = ble_hs_mbuf_to_flat(ctxt->om, line, sizeof(line) - 1, &len); + if (rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + line[len] = '\0'; + + ui_metrics_t metrics; + if (!metrics_parse_line(line, &metrics)) { + ESP_LOGW(TAG, "bad BLE metrics: %s", line); + ui_screen_set_status("BAD BLE DATA"); + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + if (s_metrics_queue != NULL) { + xQueueOverwrite(s_metrics_queue, &metrics); + } + + s_ble_write_count++; + if ((s_ble_write_count % 10) == 0) { + ESP_LOGI(TAG, "BLE metrics received: %lu", (unsigned long)s_ble_write_count); + } + return 0; +} + +static const struct ble_gatt_svc_def metrics_gatt_svcs[] = { + { + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &metrics_svc_uuid.u, + .characteristics = (struct ble_gatt_chr_def[]) { + { + .uuid = &metrics_chr_uuid.u, + .access_cb = metrics_gatt_access, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP, + .val_handle = &metrics_chr_val_handle, + }, + {0}, + }, + }, + {0}, +}; + +static void metrics_advertise(void) +{ + struct ble_hs_adv_fields fields; + memset(&fields, 0, sizeof(fields)); + + fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; + fields.name = (uint8_t *)DEVICE_NAME; + fields.name_len = strlen(DEVICE_NAME); + fields.name_is_complete = 1; + + int rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + ESP_LOGE(TAG, "set adv fields failed: %d", rc); + return; + } + + struct ble_gap_adv_params adv_params; + memset(&adv_params, 0, sizeof(adv_params)); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + + rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, + &adv_params, metrics_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "start advertising failed: %d", rc); + return; + } + + ESP_LOGI(TAG, "BLE advertising as %s", DEVICE_NAME); +} + +static int metrics_gap_event(struct ble_gap_event *event, void *arg) +{ + (void)arg; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + if (event->connect.status == 0) { + ESP_LOGI(TAG, "BLE connected"); + metrics_request_2m_phy(event->connect.conn_handle); + } else { + metrics_advertise(); + } + return 0; + + case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: + ESP_LOGI(TAG, "BLE PHY updated: status=%d tx=%s rx=%s", + event->phy_updated.status, + metrics_phy_name(event->phy_updated.tx_phy), + metrics_phy_name(event->phy_updated.rx_phy)); + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGI(TAG, "BLE disconnected"); + metrics_advertise(); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + metrics_advertise(); + return 0; + + default: + return 0; + } +} + +static void metrics_on_reset(int reason) +{ + ESP_LOGE(TAG, "BLE reset: %d", reason); +} + +static void metrics_on_sync(void) +{ + int rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + rc = ble_hs_id_infer_auto(0, &own_addr_type); + assert(rc == 0); + + rc = ble_gap_set_prefered_default_le_phy(BLE_GAP_LE_PHY_2M_MASK, + BLE_GAP_LE_PHY_2M_MASK); + if (rc == 0) { + ESP_LOGI(TAG, "BLE 5 default PHY preference set to 2M"); + } else { + ESP_LOGW(TAG, "set BLE 5 default PHY preference failed: %d", rc); + } + + metrics_advertise(); +} + +static void metrics_host_task(void *param) +{ + (void)param; + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +void metrics_ble_start(void) +{ + esp_err_t nvs_ret = nvs_flash_init(); + if (nvs_ret == ESP_ERR_NVS_NO_FREE_PAGES || nvs_ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + nvs_ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(nvs_ret); + + if (s_metrics_queue == NULL) { + s_metrics_queue = xQueueCreate(1, sizeof(ui_metrics_t)); + if (s_metrics_queue == NULL) { + ESP_LOGE(TAG, "failed to create BLE metrics queue"); + return; + } + } + + int rc = nimble_port_init(); + if (rc != ESP_OK) { + ESP_LOGE(TAG, "nimble init failed: %d", rc); + return; + } + + ble_hs_cfg.reset_cb = metrics_on_reset; + ble_hs_cfg.sync_cb = metrics_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + ble_svc_gap_init(); + ble_svc_gatt_init(); + + rc = ble_gatts_count_cfg(metrics_gatt_svcs); + assert(rc == 0); + + rc = ble_gatts_add_svcs(metrics_gatt_svcs); + assert(rc == 0); + + rc = ble_svc_gap_device_name_set(DEVICE_NAME); + assert(rc == 0); + + ble_store_config_init(); + nimble_port_freertos_init(metrics_host_task); +} + +bool metrics_ble_take_latest(ui_metrics_t *metrics) +{ + if (metrics == NULL || s_metrics_queue == NULL) { + return false; + } + + return xQueueReceive(s_metrics_queue, metrics, 0) == pdTRUE; +} diff --git a/main/metrics_ble.h b/main/metrics_ble.h new file mode 100644 index 0000000..866e519 --- /dev/null +++ b/main/metrics_ble.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "ui_screen.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void metrics_ble_start(void); +bool metrics_ble_take_latest(ui_metrics_t *metrics); + +#ifdef __cplusplus +} +#endif diff --git a/main/metrics_protocol.c b/main/metrics_protocol.c new file mode 100644 index 0000000..137088c --- /dev/null +++ b/main/metrics_protocol.c @@ -0,0 +1,551 @@ +#include "metrics_protocol.h" + +#include +#include +#include + +static int clamp_percent(int value) +{ + if (value < 0) { + return 0; + } + if (value > 100) { + return 100; + } + return value; +} + +static bool parse_int_token(char **cursor, int *out) +{ + char *token = strsep(cursor, ","); + if (token == NULL || token[0] == '\0') { + return false; + } + + char *end = NULL; + long value = strtol(token, &end, 10); + if (end == token || *end != '\0') { + return false; + } + + *out = (int)value; + return true; +} + +static void set_disk_text(ui_metrics_t *metrics, const char *disk_text) +{ + if (disk_text == NULL || disk_text[0] == '\0') { + metrics->disk_text[0] = '\0'; + return; + } + + snprintf(metrics->disk_text, sizeof(metrics->disk_text), "%s", disk_text); +} + +static void set_optional_defaults(ui_metrics_t *metrics) +{ + metrics->codex_5h_percent = -1; + metrics->codex_week_percent = -1; + metrics->codex_5h_reset_mins = -1; + metrics->codex_week_reset_mins = -1; + metrics->codex_reset_credits = -1; + metrics->codex_today_tokens_m10 = -1; + metrics->codex_daily_count = 0; + for (int i = 0; i < UI_CODEX_DAILY_BAR_COUNT; i++) { + metrics->codex_daily_percent[i] = -1; + } + metrics->zhipu_status = 0; + metrics->zhipu_quota_percent = -1; + metrics->zhipu_latency_ms = -1; + metrics->zhipu_5h_percent = -1; + metrics->zhipu_week_percent = -1; + metrics->zhipu_5h_reset_mins = -1; + metrics->zhipu_week_reset_mins = -1; +} + +static void parse_codex_daily_bars(ui_metrics_t *metrics, const char *daily_text) +{ + metrics->codex_daily_count = 0; + for (int i = 0; i < UI_CODEX_DAILY_BAR_COUNT; i++) { + metrics->codex_daily_percent[i] = -1; + } + + if (daily_text == NULL || daily_text[0] == '\0') { + return; + } + + char buffer[96]; + snprintf(buffer, sizeof(buffer), "%s", daily_text); + + char *cursor = buffer; + char *token = NULL; + while ((token = strsep(&cursor, ":")) != NULL && metrics->codex_daily_count < UI_CODEX_DAILY_BAR_COUNT) { + if (token[0] == '\0') { + continue; + } + + char *end = NULL; + long value = strtol(token, &end, 10); + if (end == token || *end != '\0') { + continue; + } + + metrics->codex_daily_percent[metrics->codex_daily_count] = clamp_percent((int)value); + metrics->codex_daily_count++; + } +} + +static bool parse_sys2_line(const char *line, ui_metrics_t *metrics) +{ + char buffer[320]; + snprintf(buffer, sizeof(buffer), "%s", line); + + char *cursor = buffer; + char *tag = strsep(&cursor, ","); + if (tag == NULL || strcmp(tag, "SYS2") != 0) { + return false; + } + + int cpu = 0; + int cpu_mhz = 0; + int mem = 0; + int mem_used = 0; + int mem_total = 0; + int disk_count = 0; + int disk_percent = 0; + int disk_read = 0; + int disk_write = 0; + int rx = 0; + int tx = 0; + + if (!parse_int_token(&cursor, &cpu) || + !parse_int_token(&cursor, &cpu_mhz) || + !parse_int_token(&cursor, &mem) || + !parse_int_token(&cursor, &mem_used) || + !parse_int_token(&cursor, &mem_total) || + !parse_int_token(&cursor, &disk_count) || + !parse_int_token(&cursor, &disk_percent) || + !parse_int_token(&cursor, &disk_read) || + !parse_int_token(&cursor, &disk_write) || + !parse_int_token(&cursor, &rx) || + !parse_int_token(&cursor, &tx)) { + return false; + } + + metrics->cpu_percent = clamp_percent(cpu); + metrics->cpu_mhz = cpu_mhz; + metrics->memory_percent = clamp_percent(mem); + metrics->memory_used_mb = mem_used; + metrics->memory_total_mb = mem_total; + metrics->disk_count = disk_count; + metrics->disk_percent = clamp_percent(disk_percent); + metrics->disk_read_kbps = disk_read; + metrics->disk_write_kbps = disk_write; + metrics->rx_kbps = rx; + metrics->tx_kbps = tx; + set_disk_text(metrics, cursor); + return true; +} + +static bool parse_sys3_line(const char *line, ui_metrics_t *metrics) +{ + char buffer[384]; + snprintf(buffer, sizeof(buffer), "%s", line); + + char *cursor = buffer; + char *tag = strsep(&cursor, ","); + if (tag == NULL || strcmp(tag, "SYS3") != 0) { + return false; + } + + int cpu = 0; + int cpu_mhz = 0; + int mem = 0; + int mem_used = 0; + int mem_total = 0; + int disk_count = 0; + int disk_percent = 0; + int disk_read = 0; + int disk_write = 0; + int rx = 0; + int tx = 0; + int codex_5h = -1; + int codex_week = -1; + int codex_5h_reset = -1; + int codex_week_reset = -1; + int codex_credits = -1; + int zhipu_status = 0; + int zhipu_quota = -1; + int zhipu_latency = -1; + + if (!parse_int_token(&cursor, &cpu) || + !parse_int_token(&cursor, &cpu_mhz) || + !parse_int_token(&cursor, &mem) || + !parse_int_token(&cursor, &mem_used) || + !parse_int_token(&cursor, &mem_total) || + !parse_int_token(&cursor, &disk_count) || + !parse_int_token(&cursor, &disk_percent) || + !parse_int_token(&cursor, &disk_read) || + !parse_int_token(&cursor, &disk_write) || + !parse_int_token(&cursor, &rx) || + !parse_int_token(&cursor, &tx) || + !parse_int_token(&cursor, &codex_5h) || + !parse_int_token(&cursor, &codex_week) || + !parse_int_token(&cursor, &codex_5h_reset) || + !parse_int_token(&cursor, &codex_week_reset) || + !parse_int_token(&cursor, &codex_credits) || + !parse_int_token(&cursor, &zhipu_status) || + !parse_int_token(&cursor, &zhipu_quota) || + !parse_int_token(&cursor, &zhipu_latency)) { + return false; + } + + metrics->cpu_percent = clamp_percent(cpu); + metrics->cpu_mhz = cpu_mhz; + metrics->memory_percent = clamp_percent(mem); + metrics->memory_used_mb = mem_used; + metrics->memory_total_mb = mem_total; + metrics->disk_count = disk_count; + metrics->disk_percent = clamp_percent(disk_percent); + metrics->disk_read_kbps = disk_read; + metrics->disk_write_kbps = disk_write; + metrics->rx_kbps = rx; + metrics->tx_kbps = tx; + metrics->codex_5h_percent = (codex_5h >= 0) ? clamp_percent(codex_5h) : -1; + metrics->codex_week_percent = (codex_week >= 0) ? clamp_percent(codex_week) : -1; + metrics->codex_5h_reset_mins = codex_5h_reset; + metrics->codex_week_reset_mins = codex_week_reset; + metrics->codex_reset_credits = codex_credits; + metrics->zhipu_status = zhipu_status; + metrics->zhipu_quota_percent = (zhipu_quota >= 0) ? clamp_percent(zhipu_quota) : -1; + metrics->zhipu_latency_ms = zhipu_latency; + metrics->zhipu_5h_percent = metrics->zhipu_quota_percent; + metrics->zhipu_week_percent = -1; + metrics->zhipu_5h_reset_mins = -1; + metrics->zhipu_week_reset_mins = -1; + set_disk_text(metrics, cursor); + return true; +} + +static bool parse_sys4_line(const char *line, ui_metrics_t *metrics) +{ + char buffer[384]; + snprintf(buffer, sizeof(buffer), "%s", line); + + char *cursor = buffer; + char *tag = strsep(&cursor, ","); + if (tag == NULL || strcmp(tag, "SYS4") != 0) { + return false; + } + + int cpu = 0; + int cpu_mhz = 0; + int mem = 0; + int mem_used = 0; + int mem_total = 0; + int disk_count = 0; + int disk_percent = 0; + int disk_read = 0; + int disk_write = 0; + int rx = 0; + int tx = 0; + int codex_5h = -1; + int codex_week = -1; + int codex_5h_reset = -1; + int codex_week_reset = -1; + int codex_credits = -1; + int zhipu_status = 0; + int zhipu_5h = -1; + int zhipu_week = -1; + int zhipu_5h_reset = -1; + int zhipu_week_reset = -1; + + if (!parse_int_token(&cursor, &cpu) || + !parse_int_token(&cursor, &cpu_mhz) || + !parse_int_token(&cursor, &mem) || + !parse_int_token(&cursor, &mem_used) || + !parse_int_token(&cursor, &mem_total) || + !parse_int_token(&cursor, &disk_count) || + !parse_int_token(&cursor, &disk_percent) || + !parse_int_token(&cursor, &disk_read) || + !parse_int_token(&cursor, &disk_write) || + !parse_int_token(&cursor, &rx) || + !parse_int_token(&cursor, &tx) || + !parse_int_token(&cursor, &codex_5h) || + !parse_int_token(&cursor, &codex_week) || + !parse_int_token(&cursor, &codex_5h_reset) || + !parse_int_token(&cursor, &codex_week_reset) || + !parse_int_token(&cursor, &codex_credits) || + !parse_int_token(&cursor, &zhipu_status) || + !parse_int_token(&cursor, &zhipu_5h) || + !parse_int_token(&cursor, &zhipu_week) || + !parse_int_token(&cursor, &zhipu_5h_reset) || + !parse_int_token(&cursor, &zhipu_week_reset)) { + return false; + } + + metrics->cpu_percent = clamp_percent(cpu); + metrics->cpu_mhz = cpu_mhz; + metrics->memory_percent = clamp_percent(mem); + metrics->memory_used_mb = mem_used; + metrics->memory_total_mb = mem_total; + metrics->disk_count = disk_count; + metrics->disk_percent = clamp_percent(disk_percent); + metrics->disk_read_kbps = disk_read; + metrics->disk_write_kbps = disk_write; + metrics->rx_kbps = rx; + metrics->tx_kbps = tx; + metrics->codex_5h_percent = (codex_5h >= 0) ? clamp_percent(codex_5h) : -1; + metrics->codex_week_percent = (codex_week >= 0) ? clamp_percent(codex_week) : -1; + metrics->codex_5h_reset_mins = codex_5h_reset; + metrics->codex_week_reset_mins = codex_week_reset; + metrics->codex_reset_credits = codex_credits; + metrics->zhipu_status = zhipu_status; + metrics->zhipu_5h_percent = (zhipu_5h >= 0) ? clamp_percent(zhipu_5h) : -1; + metrics->zhipu_week_percent = (zhipu_week >= 0) ? clamp_percent(zhipu_week) : -1; + metrics->zhipu_5h_reset_mins = zhipu_5h_reset; + metrics->zhipu_week_reset_mins = zhipu_week_reset; + metrics->zhipu_quota_percent = metrics->zhipu_5h_percent; + metrics->zhipu_latency_ms = -1; + set_disk_text(metrics, cursor); + return true; +} + +static bool parse_sys5_line(const char *line, ui_metrics_t *metrics) +{ + char buffer[384]; + snprintf(buffer, sizeof(buffer), "%s", line); + + char *cursor = buffer; + char *tag = strsep(&cursor, ","); + if (tag == NULL || strcmp(tag, "SYS5") != 0) { + return false; + } + + int cpu = 0; + int cpu_mhz = 0; + int mem = 0; + int mem_used = 0; + int mem_total = 0; + int disk_count = 0; + int disk_percent = 0; + int disk_read = 0; + int disk_write = 0; + int rx = 0; + int tx = 0; + int codex_5h = -1; + int codex_week = -1; + int codex_5h_reset = -1; + int codex_week_reset = -1; + int codex_credits = -1; + int codex_today_tokens_m10 = -1; + int zhipu_status = 0; + int zhipu_5h = -1; + int zhipu_week = -1; + int zhipu_5h_reset = -1; + int zhipu_week_reset = -1; + + if (!parse_int_token(&cursor, &cpu) || + !parse_int_token(&cursor, &cpu_mhz) || + !parse_int_token(&cursor, &mem) || + !parse_int_token(&cursor, &mem_used) || + !parse_int_token(&cursor, &mem_total) || + !parse_int_token(&cursor, &disk_count) || + !parse_int_token(&cursor, &disk_percent) || + !parse_int_token(&cursor, &disk_read) || + !parse_int_token(&cursor, &disk_write) || + !parse_int_token(&cursor, &rx) || + !parse_int_token(&cursor, &tx) || + !parse_int_token(&cursor, &codex_5h) || + !parse_int_token(&cursor, &codex_week) || + !parse_int_token(&cursor, &codex_5h_reset) || + !parse_int_token(&cursor, &codex_week_reset) || + !parse_int_token(&cursor, &codex_credits) || + !parse_int_token(&cursor, &codex_today_tokens_m10) || + !parse_int_token(&cursor, &zhipu_status) || + !parse_int_token(&cursor, &zhipu_5h) || + !parse_int_token(&cursor, &zhipu_week) || + !parse_int_token(&cursor, &zhipu_5h_reset) || + !parse_int_token(&cursor, &zhipu_week_reset)) { + return false; + } + + metrics->cpu_percent = clamp_percent(cpu); + metrics->cpu_mhz = cpu_mhz; + metrics->memory_percent = clamp_percent(mem); + metrics->memory_used_mb = mem_used; + metrics->memory_total_mb = mem_total; + metrics->disk_count = disk_count; + metrics->disk_percent = clamp_percent(disk_percent); + metrics->disk_read_kbps = disk_read; + metrics->disk_write_kbps = disk_write; + metrics->rx_kbps = rx; + metrics->tx_kbps = tx; + metrics->codex_5h_percent = (codex_5h >= 0) ? clamp_percent(codex_5h) : -1; + metrics->codex_week_percent = (codex_week >= 0) ? clamp_percent(codex_week) : -1; + metrics->codex_5h_reset_mins = codex_5h_reset; + metrics->codex_week_reset_mins = codex_week_reset; + metrics->codex_reset_credits = codex_credits; + metrics->codex_today_tokens_m10 = codex_today_tokens_m10; + metrics->zhipu_status = zhipu_status; + metrics->zhipu_5h_percent = (zhipu_5h >= 0) ? clamp_percent(zhipu_5h) : -1; + metrics->zhipu_week_percent = (zhipu_week >= 0) ? clamp_percent(zhipu_week) : -1; + metrics->zhipu_5h_reset_mins = zhipu_5h_reset; + metrics->zhipu_week_reset_mins = zhipu_week_reset; + metrics->zhipu_quota_percent = metrics->zhipu_5h_percent; + metrics->zhipu_latency_ms = -1; + set_disk_text(metrics, cursor); + return true; +} + +static bool parse_sys6_line(const char *line, ui_metrics_t *metrics) +{ + char buffer[512]; + snprintf(buffer, sizeof(buffer), "%s", line); + + char *cursor = buffer; + char *tag = strsep(&cursor, ","); + if (tag == NULL || strcmp(tag, "SYS6") != 0) { + return false; + } + + int cpu = 0; + int cpu_mhz = 0; + int mem = 0; + int mem_used = 0; + int mem_total = 0; + int disk_count = 0; + int disk_percent = 0; + int disk_read = 0; + int disk_write = 0; + int rx = 0; + int tx = 0; + int codex_5h = -1; + int codex_week = -1; + int codex_5h_reset = -1; + int codex_week_reset = -1; + int codex_credits = -1; + int codex_today_tokens_m10 = -1; + int zhipu_status = 0; + int zhipu_5h = -1; + int zhipu_week = -1; + int zhipu_5h_reset = -1; + int zhipu_week_reset = -1; + + if (!parse_int_token(&cursor, &cpu) || + !parse_int_token(&cursor, &cpu_mhz) || + !parse_int_token(&cursor, &mem) || + !parse_int_token(&cursor, &mem_used) || + !parse_int_token(&cursor, &mem_total) || + !parse_int_token(&cursor, &disk_count) || + !parse_int_token(&cursor, &disk_percent) || + !parse_int_token(&cursor, &disk_read) || + !parse_int_token(&cursor, &disk_write) || + !parse_int_token(&cursor, &rx) || + !parse_int_token(&cursor, &tx) || + !parse_int_token(&cursor, &codex_5h) || + !parse_int_token(&cursor, &codex_week) || + !parse_int_token(&cursor, &codex_5h_reset) || + !parse_int_token(&cursor, &codex_week_reset) || + !parse_int_token(&cursor, &codex_credits) || + !parse_int_token(&cursor, &codex_today_tokens_m10)) { + return false; + } + + char *daily_text = strsep(&cursor, ","); + if (daily_text == NULL || + !parse_int_token(&cursor, &zhipu_status) || + !parse_int_token(&cursor, &zhipu_5h) || + !parse_int_token(&cursor, &zhipu_week) || + !parse_int_token(&cursor, &zhipu_5h_reset) || + !parse_int_token(&cursor, &zhipu_week_reset)) { + return false; + } + + metrics->cpu_percent = clamp_percent(cpu); + metrics->cpu_mhz = cpu_mhz; + metrics->memory_percent = clamp_percent(mem); + metrics->memory_used_mb = mem_used; + metrics->memory_total_mb = mem_total; + metrics->disk_count = disk_count; + metrics->disk_percent = clamp_percent(disk_percent); + metrics->disk_read_kbps = disk_read; + metrics->disk_write_kbps = disk_write; + metrics->rx_kbps = rx; + metrics->tx_kbps = tx; + metrics->codex_5h_percent = (codex_5h >= 0) ? clamp_percent(codex_5h) : -1; + metrics->codex_week_percent = (codex_week >= 0) ? clamp_percent(codex_week) : -1; + metrics->codex_5h_reset_mins = codex_5h_reset; + metrics->codex_week_reset_mins = codex_week_reset; + metrics->codex_reset_credits = codex_credits; + metrics->codex_today_tokens_m10 = codex_today_tokens_m10; + parse_codex_daily_bars(metrics, daily_text); + metrics->zhipu_status = zhipu_status; + metrics->zhipu_5h_percent = (zhipu_5h >= 0) ? clamp_percent(zhipu_5h) : -1; + metrics->zhipu_week_percent = (zhipu_week >= 0) ? clamp_percent(zhipu_week) : -1; + metrics->zhipu_5h_reset_mins = zhipu_5h_reset; + metrics->zhipu_week_reset_mins = zhipu_week_reset; + metrics->zhipu_quota_percent = metrics->zhipu_5h_percent; + metrics->zhipu_latency_ms = -1; + set_disk_text(metrics, cursor); + return true; +} + +static bool parse_legacy_sys_line(const char *line, ui_metrics_t *metrics) +{ + int cpu = 0; + int mem = 0; + int disk = 0; + int rx = 0; + int tx = 0; + int matched = sscanf(line, "SYS,%d,%d,%d,%d,%d", &cpu, &mem, &disk, &rx, &tx); + if (matched != 5) { + return false; + } + + metrics->cpu_percent = clamp_percent(cpu); + metrics->cpu_mhz = 0; + metrics->memory_percent = clamp_percent(mem); + metrics->memory_used_mb = 0; + metrics->memory_total_mb = 0; + metrics->disk_percent = clamp_percent(disk); + metrics->disk_read_kbps = 0; + metrics->disk_write_kbps = 0; + metrics->rx_kbps = rx; + metrics->tx_kbps = tx; + metrics->disk_count = 1; + snprintf(metrics->disk_text, sizeof(metrics->disk_text), "C:%d:-1:-1", metrics->disk_percent); + return true; +} + +bool metrics_parse_line(const char *line, ui_metrics_t *metrics) +{ + if (line == NULL || metrics == NULL) { + return false; + } + + memset(metrics, 0, sizeof(*metrics)); + set_optional_defaults(metrics); + + if (strncmp(line, "SYS6,", 5) == 0) { + return parse_sys6_line(line, metrics); + } + + if (strncmp(line, "SYS5,", 5) == 0) { + return parse_sys5_line(line, metrics); + } + + if (strncmp(line, "SYS4,", 5) == 0) { + return parse_sys4_line(line, metrics); + } + + if (strncmp(line, "SYS3,", 5) == 0) { + return parse_sys3_line(line, metrics); + } + + if (strncmp(line, "SYS2,", 5) == 0) { + return parse_sys2_line(line, metrics); + } + + return parse_legacy_sys_line(line, metrics); +} diff --git a/main/metrics_protocol.h b/main/metrics_protocol.h new file mode 100644 index 0000000..99d307c --- /dev/null +++ b/main/metrics_protocol.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "ui_screen.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool metrics_parse_line(const char *line, ui_metrics_t *metrics); + +#ifdef __cplusplus +} +#endif diff --git a/main/metrics_serial.c b/main/metrics_serial.c new file mode 100644 index 0000000..8c6489f --- /dev/null +++ b/main/metrics_serial.c @@ -0,0 +1,122 @@ +#include "metrics_serial.h" + +#include +#include +#include + +#include "driver/uart.h" +#include "driver/usb_serial_jtag.h" +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "metrics_protocol.h" +#include "ui_screen.h" + +static const char *TAG = "metrics_serial"; +static QueueHandle_t s_serial_metrics_queue; + +static void consume_byte(char ch, char *line, size_t *length) +{ + if (ch == '\r') { + return; + } + + if (ch == '\n') { + line[*length] = '\0'; + *length = 0; + + ui_metrics_t metrics; + if (metrics_parse_line(line, &metrics)) { + if (s_serial_metrics_queue != NULL) { + xQueueOverwrite(s_serial_metrics_queue, &metrics); + } + } + return; + } + + if (*length < 511) { + line[*length] = ch; + *length += 1; + } else { + *length = 0; + } +} + +static void usb_serial_task(void *arg) +{ + (void)arg; + char line[512]; + size_t length = 0; + uint8_t buffer[64]; + + while (1) { + int read = usb_serial_jtag_read_bytes(buffer, sizeof(buffer), pdMS_TO_TICKS(100)); + for (int i = 0; i < read; i++) { + consume_byte((char)buffer[i], line, &length); + } + } +} + +static void uart_serial_task(void *arg) +{ + (void)arg; + char line[512]; + size_t length = 0; + uint8_t buffer[64]; + + while (1) { + int read = uart_read_bytes(UART_NUM_0, buffer, sizeof(buffer), pdMS_TO_TICKS(100)); + for (int i = 0; i < read; i++) { + consume_byte((char)buffer[i], line, &length); + } + } +} + +void metrics_serial_start(void) +{ + if (s_serial_metrics_queue == NULL) { + s_serial_metrics_queue = xQueueCreate(1, sizeof(ui_metrics_t)); + if (s_serial_metrics_queue == NULL) { + ESP_LOGW(TAG, "serial metrics queue unavailable"); + return; + } + } + + usb_serial_jtag_driver_config_t usb_config = { + .tx_buffer_size = 256, + .rx_buffer_size = 1024, + }; + esp_err_t usb_ret = usb_serial_jtag_driver_install(&usb_config); + if (usb_ret != ESP_OK && usb_ret != ESP_ERR_INVALID_STATE) { + ESP_LOGW(TAG, "USB Serial/JTAG install failed: %s", esp_err_to_name(usb_ret)); + } else { + xTaskCreate(usb_serial_task, "metrics_usb", 4096, NULL, 4, NULL); + } + + const uart_config_t uart_config = { + .baud_rate = 115200, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_DEFAULT, + }; + uart_param_config(UART_NUM_0, &uart_config); + esp_err_t uart_ret = uart_driver_install(UART_NUM_0, 1024, 0, 0, NULL, 0); + if (uart_ret != ESP_OK && uart_ret != ESP_ERR_INVALID_STATE) { + ESP_LOGW(TAG, "UART0 install failed: %s", esp_err_to_name(uart_ret)); + } else { + xTaskCreate(uart_serial_task, "metrics_uart", 4096, NULL, 4, NULL); + } +} + +bool metrics_serial_take_latest(ui_metrics_t *metrics) +{ + if (metrics == NULL || s_serial_metrics_queue == NULL) { + return false; + } + + return xQueueReceive(s_serial_metrics_queue, metrics, 0) == pdTRUE; +} diff --git a/main/metrics_serial.h b/main/metrics_serial.h new file mode 100644 index 0000000..f6ccc54 --- /dev/null +++ b/main/metrics_serial.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "ui_screen.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void metrics_serial_start(void); +bool metrics_serial_take_latest(ui_metrics_t *metrics); + +#ifdef __cplusplus +} +#endif diff --git a/main/metrics_wifi.c b/main/metrics_wifi.c new file mode 100644 index 0000000..90e0752 --- /dev/null +++ b/main/metrics_wifi.c @@ -0,0 +1,148 @@ +#include "metrics_wifi.h" + +#include + +#include "esp_err.h" +#include "esp_event.h" +#include "esp_http_server.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_wifi.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "metrics_protocol.h" +#include "nvs_flash.h" +#include "ui_screen.h" + +static const char *TAG = "metrics_ap"; + +#define METRICS_AP_SSID "RLCD-Metrics" +#define METRICS_AP_PASSWORD "rlcd123456" +#define METRICS_AP_CHANNEL 6 +#define METRICS_AP_MAX_CLIENTS 2 + +static esp_err_t send_text(httpd_req_t *req, const char *text) +{ + httpd_resp_set_type(req, "text/plain; charset=utf-8"); + return httpd_resp_sendstr(req, text); +} + +static esp_err_t health_handler(httpd_req_t *req) +{ + return send_text(req, "ok\n"); +} + +static esp_err_t metrics_post_handler(httpd_req_t *req) +{ + char line[512] = {0}; + int total = 0; + + while (total < (int)sizeof(line) - 1) { + int read = httpd_req_recv(req, line + total, (sizeof(line) - 1) - total); + if (read <= 0) { + if (read == HTTPD_SOCK_ERR_TIMEOUT) { + continue; + } + break; + } + total += read; + if (total >= req->content_len) { + break; + } + } + line[total] = '\0'; + + ui_metrics_t metrics; + if (!metrics_parse_line(line, &metrics)) { + ui_screen_set_status("BAD WIFI DATA"); + httpd_resp_set_status(req, "400 Bad Request"); + return send_text(req, "bad data\n"); + } + + ui_screen_update_metrics_from_source(&metrics, "LIVE FROM AP"); + return send_text(req, "ok\n"); +} + +static esp_err_t start_http_server(void) +{ + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = 80; + config.ctrl_port = 32768; + config.stack_size = 6144; + + httpd_handle_t server = NULL; + esp_err_t ret = httpd_start(&server, &config); + if (ret != ESP_OK) { + return ret; + } + + httpd_uri_t health_uri = { + .uri = "/health", + .method = HTTP_GET, + .handler = health_handler, + .user_ctx = NULL, + }; + httpd_register_uri_handler(server, &health_uri); + + httpd_uri_t metrics_uri = { + .uri = "/metrics", + .method = HTTP_POST, + .handler = metrics_post_handler, + .user_ctx = NULL, + }; + httpd_register_uri_handler(server, &metrics_uri); + + return ESP_OK; +} + +static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + (void)arg; + (void)event_data; + + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) { + ui_screen_set_status("PC CONNECTED"); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) { + ui_screen_set_status("AP WAITING PC"); + } +} + +void metrics_wifi_start(void) +{ + esp_err_t nvs_ret = nvs_flash_init(); + if (nvs_ret == ESP_ERR_NVS_NO_FREE_PAGES || nvs_ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + nvs_ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(nvs_ret); + + ESP_ERROR_CHECK(esp_netif_init()); + esp_err_t loop_ret = esp_event_loop_create_default(); + if (loop_ret != ESP_OK && loop_ret != ESP_ERR_INVALID_STATE) { + ESP_ERROR_CHECK(loop_ret); + } + + esp_netif_create_default_wifi_ap(); + + wifi_init_config_t init_config = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&init_config)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); + + wifi_config_t ap_config = {0}; + strlcpy((char *)ap_config.ap.ssid, METRICS_AP_SSID, sizeof(ap_config.ap.ssid)); + strlcpy((char *)ap_config.ap.password, METRICS_AP_PASSWORD, sizeof(ap_config.ap.password)); + ap_config.ap.ssid_len = strlen(METRICS_AP_SSID); + ap_config.ap.channel = METRICS_AP_CHANNEL; + ap_config.ap.max_connection = METRICS_AP_MAX_CLIENTS; + ap_config.ap.authmode = WIFI_AUTH_WPA2_PSK; + ap_config.ap.pmf_cfg.required = false; + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + + ESP_ERROR_CHECK(start_http_server()); + + ESP_LOGI(TAG, "AP started: SSID=%s password=%s url=http://192.168.4.1/metrics", METRICS_AP_SSID, METRICS_AP_PASSWORD); + ui_screen_set_status("AP RLCD-Metrics"); +} diff --git a/main/metrics_wifi.h b/main/metrics_wifi.h new file mode 100644 index 0000000..2db90df --- /dev/null +++ b/main/metrics_wifi.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void metrics_wifi_start(void); + +#ifdef __cplusplus +} +#endif diff --git a/main/st7305_rlcd.c b/main/st7305_rlcd.c new file mode 100644 index 0000000..e26150c --- /dev/null +++ b/main/st7305_rlcd.c @@ -0,0 +1,365 @@ +/** + * @file st7305_rlcd.c + * @brief ST7305 反射式 LCD 驱动实现(纯 C) + * + * 移植自微雪官方 custom_lcd_display.cc。 + * 关键逻辑(初始化序列、横屏 LUT、像素打包)100% 保留, + * 只是把 C++ class 改成模块级全局变量 + 函数。 + */ + +#include "st7305_rlcd.h" + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "driver/gpio.h" + +static const char *TAG = "st7305"; + +/* =========================================================== + * 模块内部状态(等价于原来 C++ 类的成员变量) + * =========================================================== */ + +/* SPI 设备句柄:用 ESP-IDF 的 esp_lcd panel io 框架来管 SPI 通信 */ +static esp_lcd_panel_io_handle_t s_io_handle = NULL; + +/* 显存:每 8 像素打包成 1 字节,所以显存大小 = W*H/8 = 400*300/8 = 15000 字节。 + * 这个 buffer 放在 PSRAM 里(8MB PSRAM 完全够)。 */ +#define DISPLAY_BUFFER_LEN ((ST7305_RLCD_WIDTH * ST7305_RLCD_HEIGHT) / 8) +static uint8_t *s_disp_buffer = NULL; + +/* 横屏 LUT:把 (x,y) 像素坐标映射到 (字节索引, 位掩码)。 + * ST7305 的显存布局很奇怪(2列×4行打包),没有 LUT 的话 + * 每次画一个像素都要重新算一遍位运算,LUT 让它变成 O(1) 查表。 + * 索引方式:pixel_index = x * HEIGHT + y(跟官方一致) */ +static uint16_t *s_pixel_index_lut = NULL; /* 大小: W*H 个 uint16 */ +static uint8_t *s_pixel_bit_lut = NULL; /* 大小: W*H 个 uint8 */ + +/* =========================================================== + * SPI 底层:发命令 / 发数据 + * 用 esp_lcd panel io 框架,它会自动管 DC 引脚 + * (DC=0 表示命令, DC=1 表示数据) + * =========================================================== */ +static void send_command(uint8_t cmd) +{ + /* 用 -1 作为 dis_flags 表示"不带参数的命令" */ + esp_lcd_panel_io_tx_param(s_io_handle, cmd, NULL, 0); +} + +static void send_data(uint8_t data) +{ + esp_lcd_panel_io_tx_param(s_io_handle, -1, &data, 1); +} + +static void send_buffer(uint8_t *data, int len) +{ + esp_lcd_panel_io_tx_color(s_io_handle, -1, data, len); +} + +/* =========================================================== + * 硬件复位(官方 RLCD_Reset 翻译) + * =========================================================== */ +static void hardware_reset(void) +{ + /* RST 引脚高 → 低 → 高 的脉冲,跟官方时序一致 */ + gpio_set_level(ST7305_RLCD_PIN_RST, 1); + vTaskDelay(pdMS_TO_TICKS(50)); + gpio_set_level(ST7305_RLCD_PIN_RST, 0); + vTaskDelay(pdMS_TO_TICKS(20)); + gpio_set_level(ST7305_RLCD_PIN_RST, 1); + vTaskDelay(pdMS_TO_TICKS(50)); +} + +/* =========================================================== + * ST7305 初始化序列(官方 RLCD_Init 完整翻译) + * 这 50+ 条寄存器命令是从微雪官方代码原样复制来的, + * 不要随便改顺序或值 —— 它们是微雪调好的参数。 + * 每条命令的中文注释参考 ST7305 datasheet 和官方命名。 + * =========================================================== */ +static void init_display(void) +{ + /* NVM Load Control - 从非易失存储加载设置 */ + send_command(0xD6); + send_data(0x17); + send_data(0x02); + + /* Booster Enable - 使能电荷泵 */ + send_command(0xD1); + send_data(0x01); + + /* Gate Voltage Control - VGH/VGL 电压 */ + send_command(0xC0); + send_data(0x11); + send_data(0x04); + + /* VSHP Setting - 正向源电压(高功耗模式) */ + send_command(0xC1); + send_data(0x69); send_data(0x69); send_data(0x69); send_data(0x69); + + /* VSLP Setting - 正向源电压(低功耗模式) */ + send_command(0xC2); + send_data(0x19); send_data(0x19); send_data(0x19); send_data(0x19); + + /* VSHN Setting - 负向源电压(高功耗模式) */ + send_command(0xC4); + send_data(0x4B); send_data(0x4B); send_data(0x4B); send_data(0x4B); + + /* VSLN Setting - 负向源电压(低功耗模式) */ + send_command(0xC5); + send_data(0x19); send_data(0x19); send_data(0x19); send_data(0x19); + + /* OSC Setting - 振荡器频率控制 */ + send_command(0xD8); + send_data(0x80); + send_data(0xE9); + + /* Frame Rate Control - 帧率 */ + send_command(0xB2); + send_data(0x02); + + /* Gate EQ Control (High Power Mode) - 更新周期时序 */ + send_command(0xB3); + send_data(0xE5); send_data(0xF6); send_data(0x05); send_data(0x46); + send_data(0x77); send_data(0x77); send_data(0x77); send_data(0x77); + send_data(0x76); send_data(0x45); + + /* Gate EQ Control (Low Power Mode) - 更新周期时序 */ + send_command(0xB4); + send_data(0x05); send_data(0x46); send_data(0x77); send_data(0x77); + send_data(0x77); send_data(0x77); send_data(0x76); send_data(0x45); + + /* Gate Timing Control */ + send_command(0x62); + send_data(0x32); send_data(0x03); send_data(0x1F); + + /* Source EQ Enable */ + send_command(0xB7); + send_data(0x13); + + /* Gate Line Setting - 门极线数(板子相关) + * 0x64 = 100,100*3 = 300 行,对应 300 高 */ + send_command(0xB0); + send_data(0x64); + + /* Sleep Out - 退出睡眠(datasheet 要求 200ms 延时) */ + send_command(0x11); + vTaskDelay(pdMS_TO_TICKS(200)); + + /* Source Voltage Select - 选 VSHP1/VSLP1/VSHN1/VSLN1 */ + send_command(0xC9); + send_data(0x00); + + /* MADCTL (Memory Data Access Control) - MX=1, DO=1 */ + send_command(0x36); + send_data(0x48); + + /* 1-bit 单色模式 */ + send_command(0x3A); + send_data(0x11); + + /* Gamma Mode - 单色 */ + send_command(0xB9); + send_data(0x20); + + /* Panel Setting - 1-dot 反转,帧反转,隔行 */ + send_command(0xB8); + send_data(0x29); + + /* Display Inversion On */ + send_command(0x21); + + /* Column Address Set - 板子相关 */ + send_command(0x2A); + send_data(0x12); + send_data(0x2A); + + /* Row Address Set - 板子相关 */ + send_command(0x2B); + send_data(0x00); + send_data(0xC7); + + /* Tearing Effect Line On */ + send_command(0x35); + send_data(0x00); + + /* Auto Power Down Control */ + send_command(0xD0); + send_data(0xFF); + + /* High Power Mode On */ + send_command(0x38); + + /* Display On */ + send_command(0x29); +} + +/* =========================================================== + * 横屏像素 LUT 构造(官方 InitLandscapeLUT 翻译) + * + * ST7305 横屏显存布局:每字节装 8 个像素,排列是 2列×4行 的块 + * + * 一个字节(8 bit)的物理含义: + * Bit 7: (row 0, col 0) Bit 6: (row 0, col 1) + * Bit 5: (row 1, col 0) Bit 4: (row 1, col 1) + * Bit 3: (row 2, col 0) Bit 2: (row 2, col 1) + * Bit 1: (row 3, col 0) Bit 0: (row 3, col 1) + * + * 所以给一个逻辑坐标 (x, y),要算出: + * 1. 它在显存里的字节索引 + * 2. 在该字节里的位掩码 + * 这就是 LUT 干的事,预计算一次,后面画像素就 O(1) 查表。 + * =========================================================== */ +static void init_landscape_lut(void) +{ + const uint16_t H4 = ST7305_RLCD_HEIGHT >> 2; /* 高度方向块数 = 300/4 = 75 */ + + for (uint16_t y = 0; y < ST7305_RLCD_HEIGHT; y++) { + /* y 方向:ST7305 是从下往上扫的,所以要反转 */ + uint16_t inv_y = ST7305_RLCD_HEIGHT - 1 - y; + uint16_t block_y = inv_y >> 2; /* 在哪个 4 行块里 */ + uint8_t local_y = inv_y & 3; /* 块内第几行 */ + + for (uint16_t x = 0; x < ST7305_RLCD_WIDTH; x++) { + uint16_t byte_x = x >> 1; /* 2 列共用 1 字节 */ + uint8_t local_x = x & 1; /* 块内第几列 */ + + /* 字节索引 = 列块号 * 高度块数 + 行块号 */ + uint32_t buffer_idx = (uint32_t)byte_x * H4 + block_y; + /* 位掩码:bit 位置 = 7 - (local_y * 2 + local_x) */ + uint8_t bit_pos = 7 - ((local_y << 1) | local_x); + uint8_t bit_mask = (uint8_t)(1u << bit_pos); + + /* LUT 索引方式:x * HEIGHT + y(跟官方一致) */ + uint32_t pixel_idx = (uint32_t)x * ST7305_RLCD_HEIGHT + y; + s_pixel_index_lut[pixel_idx] = (uint16_t)buffer_idx; + s_pixel_bit_lut[pixel_idx] = bit_mask; + } + } +} + +/* =========================================================== + * 对外 API 实现 + * =========================================================== */ + +esp_err_t st7305_rlcd_init(void) +{ + esp_err_t ret; + + ESP_LOGI(TAG, "Step 1: 配置 RST 引脚为输出"); + gpio_config_t gpio_conf = { + .pin_bit_mask = (1ULL << ST7305_RLCD_PIN_RST), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config(&gpio_conf); + + ESP_LOGI(TAG, "Step 2: 初始化 SPI 总线 (SPI3, MOSI=%d, SCK=%d)", + ST7305_RLCD_PIN_MOSI, ST7305_RLCD_PIN_SCK); + spi_bus_config_t buscfg = {0}; + buscfg.mosi_io_num = ST7305_RLCD_PIN_MOSI; + buscfg.miso_io_num = -1; /* 屏幕只写不读,不需要 MISO */ + buscfg.sclk_io_num = ST7305_RLCD_PIN_SCK; + buscfg.quadwp_io_num = -1; + buscfg.quadhd_io_num = -1; + buscfg.max_transfer_sz = ST7305_RLCD_WIDTH * ST7305_RLCD_HEIGHT; /* 一次传整屏 */ + ret = spi_bus_initialize(ST7305_RLCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "SPI 总线初始化失败: %s", esp_err_to_name(ret)); + return ret; + } + + ESP_LOGI(TAG, "Step 3: 把屏幕挂到 SPI 总线上(用 esp_lcd panel io 框架)"); + esp_lcd_panel_io_spi_config_t io_config = {0}; + io_config.dc_gpio_num = ST7305_RLCD_PIN_DC; + io_config.cs_gpio_num = ST7305_RLCD_PIN_CS; + io_config.pclk_hz = ST7305_RLCD_SPI_HZ; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + io_config.spi_mode = 0; + io_config.trans_queue_depth = 7; + ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)ST7305_RLCD_SPI_HOST, + &io_config, &s_io_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "panel io 创建失败: %s", esp_err_to_name(ret)); + return ret; + } + + ESP_LOGI(TAG, "Step 4: 分配显存到 PSRAM (%d 字节)", DISPLAY_BUFFER_LEN); + s_disp_buffer = (uint8_t *)heap_caps_malloc(DISPLAY_BUFFER_LEN, MALLOC_CAP_SPIRAM); + if (s_disp_buffer == NULL) { + ESP_LOGE(TAG, "显存分配失败(PSRAM?)"); + return ESP_ERR_NO_MEM; + } + + ESP_LOGI(TAG, "Step 5: 分配像素 LUT (index=%dKB, bit=%dKB)", + (ST7305_RLCD_WIDTH * ST7305_RLCD_HEIGHT * 2) / 1024, + (ST7305_RLCD_WIDTH * ST7305_RLCD_HEIGHT) / 1024); + uint32_t total_pixels = (uint32_t)ST7305_RLCD_WIDTH * ST7305_RLCD_HEIGHT; + s_pixel_index_lut = (uint16_t *)heap_caps_malloc(total_pixels * sizeof(uint16_t), + MALLOC_CAP_SPIRAM); + s_pixel_bit_lut = (uint8_t *)heap_caps_malloc(total_pixels * sizeof(uint8_t), + MALLOC_CAP_SPIRAM); + if (!s_pixel_index_lut || !s_pixel_bit_lut) { + ESP_LOGE(TAG, "LUT 分配失败(PSRAM?)"); + return ESP_ERR_NO_MEM; + } + + ESP_LOGI(TAG, "Step 6: 构建横屏 LUT"); + init_landscape_lut(); + + ESP_LOGI(TAG, "Step 7: 硬件复位 + 发送 ST7305 初始化序列"); + hardware_reset(); + init_display(); + + ESP_LOGI(TAG, "Step 8: 清屏(全白)"); + st7305_rlcd_clear(ST7305_COLOR_WHITE); + st7305_rlcd_flush(); + + ESP_LOGI(TAG, "屏幕初始化完成 ✓"); + return ESP_OK; +} + +void st7305_rlcd_set_pixel(int x, int y, uint8_t color) +{ + if (x < 0 || x >= ST7305_RLCD_WIDTH) return; + if (y < 0 || y >= ST7305_RLCD_HEIGHT) return; + + /* O(1) 查 LUT */ + uint32_t pixel_idx = (uint32_t)x * ST7305_RLCD_HEIGHT + y; + uint16_t buffer_idx = s_pixel_index_lut[pixel_idx]; + uint8_t bit_mask = s_pixel_bit_lut[pixel_idx]; + + uint8_t *p = &s_disp_buffer[buffer_idx]; + if (color == ST7305_COLOR_BLACK) { + *p &= ~bit_mask; /* 该位清零 = 像素亮(黑) */ + } else { + *p |= bit_mask; /* 该位置 1 = 像素灭(白) */ + } +} + +void st7305_rlcd_flush(void) +{ + /* 官方 RLCD_Display:每次刷新都重新设置地址窗口,然后发整屏数据 */ + send_command(0x2A); /* Column Address Set */ + send_data(0x12); + send_data(0x2A); + + send_command(0x2B); /* Page/Row Address Set */ + send_data(0x00); + send_data(0xC7); + + send_command(0x2C); /* Memory Write */ + send_buffer(s_disp_buffer, DISPLAY_BUFFER_LEN); +} + +void st7305_rlcd_clear(uint8_t color) +{ + memset(s_disp_buffer, color, DISPLAY_BUFFER_LEN); +} + +int st7305_rlcd_get_width(void) { return ST7305_RLCD_WIDTH; } +int st7305_rlcd_get_height(void) { return ST7305_RLCD_HEIGHT; } diff --git a/main/st7305_rlcd.h b/main/st7305_rlcd.h new file mode 100644 index 0000000..b6e1db4 --- /dev/null +++ b/main/st7305_rlcd.h @@ -0,0 +1,98 @@ +/** + * @file st7305_rlcd.h + * @brief ST7305 反射式 LCD 驱动(纯 C, ESP-IDF 原生) + * + * 移植自微雪官方 waveshareteam/ESP32-S3-RLCD-4.2 仓库里的 + * 02_Example/XiaoZhi/.../boards/waveshare-s3-rlcd-4.2/custom_lcd_display.cc + * + * 改动: + * - C++ 改纯 C + * - 去掉 LcdDisplay / WifiBoard 等 XiaoZhi 框架依赖 + * - 去掉 LVGL 集成(由调用方自己接,本驱动只负责把像素写到显存并刷新) + * - 横屏 LUT 算法原样保留(InitLandscapeLUT) + * - ST7305 寄存器初始化序列原样保留(RLCD_Init) + * + * 板子:Waveshare ESP32-S3-RLCD-4.2 + * - 屏幕:4.2 寸 400×300 黑白反射屏(ST7305) + * - 接口:SPI3, 40MHz + * - 引脚:DC=5, CS=40, SCK=11, MOSI=12, RST=41 + * + * 显存布局(横屏,关键!): + * 每字节装 8 个像素,排列是 2列×4行 的块 + * 详见 st7305_rlcd.c 里 init_landscape_lut() 的注释 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "esp_lcd_panel_io.h" +#include "driver/spi_master.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* =========================================================== + * 屏幕参数(来自官方 config.h) + * =========================================================== */ +#define ST7305_RLCD_WIDTH 400 /* 横屏宽 */ +#define ST7305_RLCD_HEIGHT 300 /* 横屏高 */ + +#define ST7305_RLCD_SPI_HOST SPI3_HOST +#define ST7305_RLCD_SPI_HZ (40 * 1000 * 1000) /* 40 MHz */ + +#define ST7305_RLCD_PIN_DC 5 +#define ST7305_RLCD_PIN_CS 40 +#define ST7305_RLCD_PIN_SCK 11 +#define ST7305_RLCD_PIN_MOSI 12 +#define ST7305_RLCD_PIN_RST 41 + +/* =========================================================== + * 颜色(反射屏只有黑白两色) + * =========================================================== */ +typedef enum { + ST7305_COLOR_WHITE = 0xFF, /* 像素关闭 = 白(反射屏底色) */ + ST7305_COLOR_BLACK = 0x00, /* 像素点亮 = 黑 */ +} st7305_color_t; + +/* =========================================================== + * 对外 API + * =========================================================== */ + +/** + * 初始化屏幕:配置 SPI 总线、复位 ST7305、发送初始化序列、 + * 分配显存、构建横屏像素 LUT、清屏。 + * 整个流程只调用一次,在 app_main 开头。 + */ +esp_err_t st7305_rlcd_init(void); + +/** + * 把 (x, y) 处的像素设成指定颜色。 + * 颜色取值见 st7305_color_t。 + * 越界坐标会被忽略。 + */ +void st7305_rlcd_set_pixel(int x, int y, uint8_t color); + +/** + * 把整个显存一次性推到屏幕(SPI DMA 传输)。 + * 调完才会真正在屏幕上看到变化。 + */ +void st7305_rlcd_flush(void); + +/** + * 用指定颜色填满整个显存(不会自动刷新到屏幕, + * 还要再调 st7305_rlcd_flush)。 + */ +void st7305_rlcd_clear(uint8_t color); + +/** + * 获取屏幕宽度 / 高度(主要是给 LVGL 用)。 + */ +int st7305_rlcd_get_width(void); +int st7305_rlcd_get_height(void); + +#ifdef __cplusplus +} +#endif diff --git a/main/temp_humidity_monitor.c b/main/temp_humidity_monitor.c new file mode 100644 index 0000000..46137bb --- /dev/null +++ b/main/temp_humidity_monitor.c @@ -0,0 +1,159 @@ +#include "temp_humidity_monitor.h" + +#include + +#include "driver/i2c_master.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "freertos/task.h" + +static const char *TAG = "shtc3"; + +#define SHTC3_I2C_PORT I2C_NUM_0 +#define SHTC3_I2C_SDA GPIO_NUM_13 +#define SHTC3_I2C_SCL GPIO_NUM_14 +#define SHTC3_I2C_ADDRESS 0x70 +#define SHTC3_I2C_HZ 100000 +#define SHTC3_WAKEUP_CMD 0x3517 +#define SHTC3_SLEEP_CMD 0xB098 +#define SHTC3_MEASURE_CMD 0x7866 + +static i2c_master_bus_handle_t s_i2c_bus; +static i2c_master_dev_handle_t s_shtc3_dev; +static portMUX_TYPE s_env_mux = portMUX_INITIALIZER_UNLOCKED; +static int s_latest_temperature_c10; +static int s_latest_humidity_percent; +static bool s_has_latest; + +static uint8_t shtc3_crc(const uint8_t *data) +{ + uint8_t crc = 0xFF; + + for (int i = 0; i < 2; i++) { + crc ^= data[i]; + for (int bit = 0; bit < 8; bit++) { + if ((crc & 0x80) != 0) { + crc = (uint8_t)((crc << 1) ^ 0x31); + } else { + crc <<= 1; + } + } + } + + return crc; +} + +static esp_err_t shtc3_send_command(uint16_t command) +{ + uint8_t data[2] = { + (uint8_t)(command >> 8), + (uint8_t)(command & 0xFF), + }; + return i2c_master_transmit(s_shtc3_dev, data, sizeof(data), 1000); +} + +static esp_err_t shtc3_read_once(int *temperature_c10, int *humidity_percent) +{ + uint8_t data[6] = { 0 }; + + ESP_RETURN_ON_ERROR(shtc3_send_command(SHTC3_WAKEUP_CMD), TAG, "wake failed"); + vTaskDelay(pdMS_TO_TICKS(2)); + ESP_RETURN_ON_ERROR(shtc3_send_command(SHTC3_MEASURE_CMD), TAG, "measure failed"); + vTaskDelay(pdMS_TO_TICKS(15)); + ESP_RETURN_ON_ERROR(i2c_master_receive(s_shtc3_dev, data, sizeof(data), 1000), TAG, "read failed"); + (void)shtc3_send_command(SHTC3_SLEEP_CMD); + + if (shtc3_crc(&data[0]) != data[2] || shtc3_crc(&data[3]) != data[5]) { + return ESP_ERR_INVALID_CRC; + } + + uint16_t raw_temp = ((uint16_t)data[0] << 8) | data[1]; + uint16_t raw_humidity = ((uint16_t)data[3] << 8) | data[4]; + *temperature_c10 = -450 + (int)((1750L * raw_temp + 32767) / 65535); + *humidity_percent = (int)((100L * raw_humidity + 32767) / 65535); + if (*humidity_percent < 0) { + *humidity_percent = 0; + } else if (*humidity_percent > 100) { + *humidity_percent = 100; + } + + return ESP_OK; +} + +static esp_err_t shtc3_init(void) +{ + i2c_master_bus_config_t bus_config = { + .i2c_port = SHTC3_I2C_PORT, + .sda_io_num = SHTC3_I2C_SDA, + .scl_io_num = SHTC3_I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + ESP_RETURN_ON_ERROR(i2c_new_master_bus(&bus_config, &s_i2c_bus), TAG, "i2c bus init failed"); + + i2c_device_config_t device_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = SHTC3_I2C_ADDRESS, + .scl_speed_hz = SHTC3_I2C_HZ, + }; + ESP_RETURN_ON_ERROR(i2c_master_bus_add_device(s_i2c_bus, &device_config, &s_shtc3_dev), + TAG, + "shtc3 device add failed"); + return ESP_OK; +} + +static void temp_humidity_task(void *arg) +{ + (void)arg; + + while (1) { + int temperature_c10 = 0; + int humidity_percent = 0; + esp_err_t ret = shtc3_read_once(&temperature_c10, &humidity_percent); + if (ret == ESP_OK) { + portENTER_CRITICAL(&s_env_mux); + s_latest_temperature_c10 = temperature_c10; + s_latest_humidity_percent = humidity_percent; + s_has_latest = true; + portEXIT_CRITICAL(&s_env_mux); + } else { + ESP_LOGW(TAG, "read disabled this round: %s", esp_err_to_name(ret)); + } + + vTaskDelay(pdMS_TO_TICKS(10000)); + } +} + +void temp_humidity_monitor_start(void) +{ + esp_err_t ret = shtc3_init(); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "temperature/humidity monitor disabled: %s", esp_err_to_name(ret)); + return; + } + + xTaskCreate(temp_humidity_task, "shtc3", 4096, NULL, 2, NULL); +} + +bool temp_humidity_monitor_take_latest(int *temperature_c10, int *humidity_percent) +{ + if (temperature_c10 == NULL || humidity_percent == NULL) { + return false; + } + + bool has_latest = false; + portENTER_CRITICAL(&s_env_mux); + if (s_has_latest) { + *temperature_c10 = s_latest_temperature_c10; + *humidity_percent = s_latest_humidity_percent; + s_has_latest = false; + has_latest = true; + } + portEXIT_CRITICAL(&s_env_mux); + + return has_latest; +} diff --git a/main/temp_humidity_monitor.h b/main/temp_humidity_monitor.h new file mode 100644 index 0000000..817f68e --- /dev/null +++ b/main/temp_humidity_monitor.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void temp_humidity_monitor_start(void); +bool temp_humidity_monitor_take_latest(int *temperature_c10, int *humidity_percent); + +#ifdef __cplusplus +} +#endif diff --git a/main/ui_screen.c b/main/ui_screen.c new file mode 100644 index 0000000..e137f64 --- /dev/null +++ b/main/ui_screen.c @@ -0,0 +1,795 @@ +#include "ui_screen.h" + +#include +#include +#include +#include + +#include "lvgl.h" + +#define CPU_HISTORY_POINTS 64 +#define CPU_CHART_W 184 +#define CPU_CHART_H 58 +#define HALO_LABEL_COUNT 8 +#define DISK_ROW_COUNT 4 +#define CODEX_DAILY_BAR_W 8 +#define CODEX_DAILY_BAR_GAP 5 +#define CODEX_DAILY_BAR_BASE_Y 292 +#define CODEX_DAILY_BAR_MAX_H 40 + +typedef enum { + CONN_USB = 0, + CONN_WIFI, + CONN_BLE, +} connection_slot_t; + +static lv_obj_t *s_status_label; +static lv_obj_t *s_battery_label; +static lv_obj_t *s_conn_label; +static lv_obj_t *s_env_label; +static lv_obj_t *s_cpu_value_label; +static lv_obj_t *s_cpu_freq_label; +static lv_obj_t *s_cpu_value_halo[HALO_LABEL_COUNT]; +static lv_obj_t *s_cpu_freq_halo[HALO_LABEL_COUNT]; +static lv_obj_t *s_cpu_line; +static lv_point_precise_t s_cpu_points[CPU_HISTORY_POINTS]; +static int s_cpu_history[CPU_HISTORY_POINTS]; +static lv_obj_t *s_memory_value_label; +static lv_obj_t *s_memory_value_halo[HALO_LABEL_COUNT]; +static lv_obj_t *s_memory_detail_label; +static lv_obj_t *s_memory_bar; +static lv_obj_t *s_disk_name_labels[DISK_ROW_COUNT]; +static lv_obj_t *s_disk_rate_labels[DISK_ROW_COUNT]; +static lv_obj_t *s_disk_bars[DISK_ROW_COUNT]; +static lv_obj_t *s_net_line_label; +static lv_obj_t *s_codex_5h_label; +static lv_obj_t *s_codex_week_label; +static lv_obj_t *s_codex_5h_value_label; +static lv_obj_t *s_codex_week_value_label; +static lv_obj_t *s_codex_5h_reset_label; +static lv_obj_t *s_codex_week_reset_label; +static lv_obj_t *s_codex_token_label; +static lv_obj_t *s_codex_5h_bar; +static lv_obj_t *s_codex_week_bar; +static lv_obj_t *s_codex_daily_bars[UI_CODEX_DAILY_BAR_COUNT]; +static lv_obj_t *s_zhipu_status_label; +static lv_obj_t *s_zhipu_5h_label; +static lv_obj_t *s_zhipu_week_label; +static lv_obj_t *s_zhipu_5h_value_label; +static lv_obj_t *s_zhipu_week_value_label; +static lv_obj_t *s_zhipu_5h_reset_label; +static lv_obj_t *s_zhipu_week_reset_label; +static lv_obj_t *s_zhipu_5h_bar; +static lv_obj_t *s_zhipu_week_bar; +static int s_age_seconds; +static connection_slot_t s_active_connection = CONN_USB; + +typedef struct { + int x; + int y; +} halo_offset_t; + +static const halo_offset_t s_halo_offsets[HALO_LABEL_COUNT] = { + { -1, 0 }, + { 1, 0 }, + { 0, -1 }, + { 0, 1 }, + { -1, -1 }, + { 1, -1 }, + { -1, 1 }, + { 1, 1 }, +}; + +static lv_color_t ink(void) +{ + return lv_color_black(); +} + +static lv_color_t paper(void) +{ + return lv_color_white(); +} + +static int clamp_percent(int value) +{ + if (value < 0) { + return 0; + } + if (value > 100) { + return 100; + } + return value; +} + +static lv_obj_t *make_colored_label(lv_obj_t *parent, const char *text, const lv_font_t *font, lv_color_t color) +{ + lv_obj_t *label = lv_label_create(parent); + lv_label_set_text(label, text); + lv_label_set_long_mode(label, LV_LABEL_LONG_DOT); + lv_obj_set_style_text_font(label, font, 0); + lv_obj_set_style_text_color(label, color, 0); + lv_obj_set_style_pad_all(label, 0, 0); + return label; +} + +static lv_obj_t *make_label(lv_obj_t *parent, const char *text, const lv_font_t *font) +{ + return make_colored_label(parent, text, font, ink()); +} + +static void make_label_halo(lv_obj_t *parent, + lv_obj_t *halos[HALO_LABEL_COUNT], + const char *text, + const lv_font_t *font, + int width, + lv_align_t align, + int x, + int y, + int spread, + lv_text_align_t text_align) +{ + for (int i = 0; i < HALO_LABEL_COUNT; i++) { + halos[i] = make_colored_label(parent, text, font, paper()); + lv_obj_set_width(halos[i], width); + lv_obj_set_style_text_align(halos[i], text_align, 0); + lv_obj_align(halos[i], align, x + s_halo_offsets[i].x * spread, y + s_halo_offsets[i].y * spread); + } +} + +static void set_label_with_halo(lv_obj_t *label, lv_obj_t *halos[HALO_LABEL_COUNT], const char *text) +{ + for (int i = 0; i < HALO_LABEL_COUNT; i++) { + if (halos[i] != NULL) { + lv_label_set_text(halos[i], text); + } + } + lv_label_set_text(label, text); +} + +static void set_bar_percent(lv_obj_t *bar, int percent) +{ + if (bar == NULL) { + return; + } + lv_bar_set_value(bar, percent >= 0 ? clamp_percent(percent) : 0, LV_ANIM_OFF); +} + +static lv_obj_t *make_rule(lv_obj_t *parent, int x, int y, int w, int h) +{ + lv_obj_t *rule = lv_obj_create(parent); + lv_obj_set_size(rule, w, h); + lv_obj_align(rule, LV_ALIGN_TOP_LEFT, x, y); + lv_obj_set_style_bg_color(rule, ink(), 0); + lv_obj_set_style_bg_opa(rule, LV_OPA_COVER, 0); + lv_obj_set_style_border_width(rule, 0, 0); + lv_obj_set_style_radius(rule, 0, 0); + lv_obj_set_style_pad_all(rule, 0, 0); + lv_obj_clear_flag(rule, LV_OBJ_FLAG_SCROLLABLE); + return rule; +} + +static lv_obj_t *make_bar(lv_obj_t *parent, int w, int h) +{ + lv_obj_t *bar = lv_bar_create(parent); + lv_obj_set_size(bar, w, h); + lv_bar_set_range(bar, 0, 100); + lv_bar_set_value(bar, 0, LV_ANIM_OFF); + lv_obj_set_style_bg_color(bar, paper(), 0); + lv_obj_set_style_border_color(bar, ink(), 0); + lv_obj_set_style_border_width(bar, 1, 0); + lv_obj_set_style_radius(bar, 1, 0); + lv_obj_set_style_bg_color(bar, ink(), LV_PART_INDICATOR); + lv_obj_set_style_radius(bar, 1, LV_PART_INDICATOR); + return bar; +} + +static void format_capacity(int mb, char *out, size_t out_size) +{ + if (mb <= 0) { + snprintf(out, out_size, "--"); + return; + } + + if (mb >= 1024) { + int gb10 = (mb * 10 + 512) / 1024; + snprintf(out, out_size, "%d.%dGB", gb10 / 10, gb10 % 10); + } else { + snprintf(out, out_size, "%dMB", mb); + } +} + +static void format_rate_short(int kbps, char *out, size_t out_size) +{ + if (kbps < 0) { + snprintf(out, out_size, "--"); + return; + } + + if (kbps >= 1024 * 1024) { + int gb10 = (kbps * 10 + (1024 * 512)) / (1024 * 1024); + if (gb10 >= 100) { + snprintf(out, out_size, "%dG", gb10 / 10); + } else { + snprintf(out, out_size, "%d.%dG", gb10 / 10, gb10 % 10); + } + } else if (kbps >= 1000) { + int mb10 = (kbps * 10 + 512) / 1024; + if (mb10 >= 100) { + snprintf(out, out_size, "%dM", mb10 / 10); + } else { + snprintf(out, out_size, "%d.%dM", mb10 / 10, mb10 % 10); + } + } else { + snprintf(out, out_size, "%dK", kbps); + } +} + +static void format_reset_mins(int mins, char *out, size_t out_size) +{ + if (mins < 0) { + snprintf(out, out_size, "--"); + } else if (mins >= 1440) { + int days = mins / 1440; + int hours = (mins % 1440) / 60; + snprintf(out, out_size, "%dd%dh", days, hours); + } else if (mins >= 60) { + snprintf(out, out_size, "%dh%02d", mins / 60, mins % 60); + } else { + snprintf(out, out_size, "%dm", mins); + } +} + +static void format_percent_compact(int percent, char *out, size_t out_size) +{ + if (percent < 0) { + snprintf(out, out_size, "--"); + } else { + snprintf(out, out_size, "%d%%", clamp_percent(percent)); + } +} + +static void format_tokens_m10(int tokens_m10, char *out, size_t out_size) +{ + if (tokens_m10 < 0) { + snprintf(out, out_size, "--"); + } else if (tokens_m10 >= 10000) { + snprintf(out, out_size, "%d.%dB", tokens_m10 / 10000, (tokens_m10 % 10000) / 1000); + } else if (tokens_m10 >= 1000) { + snprintf(out, out_size, "%dM", (tokens_m10 + 5) / 10); + } else { + snprintf(out, out_size, "%d.%dM", tokens_m10 / 10, tokens_m10 % 10); + } +} + +static const char *zhipu_status_text(int status) +{ + switch (status) { + case 1: + return "KEY"; + case 2: + return "ERR"; + default: + return "WAIT"; + } +} + +static const char *connection_symbol(connection_slot_t active) +{ + switch (active) { + case CONN_BLE: + return LV_SYMBOL_BLUETOOTH; + case CONN_WIFI: + return LV_SYMBOL_WIFI; + case CONN_USB: + default: + return LV_SYMBOL_USB; + } +} + +static void set_connection(connection_slot_t active) +{ + s_active_connection = active; + if (s_conn_label != NULL) { + lv_label_set_text(s_conn_label, connection_symbol(active)); + } +} + +static connection_slot_t connection_from_source(const char *source) +{ + if (source == NULL) { + return CONN_USB; + } + if (strstr(source, "BLE") != NULL) { + return CONN_BLE; + } + if (strstr(source, "WIFI") != NULL || strstr(source, "AP") != NULL) { + return CONN_WIFI; + } + return CONN_USB; +} + +static void set_history_curve(int *history, lv_point_precise_t *points, int count, + int chart_w, int chart_h, int percent, lv_obj_t *line) +{ + for (int i = 0; i < count - 1; i++) { + history[i] = history[i + 1]; + } + history[count - 1] = clamp_percent(percent); + + for (int i = 0; i < count; i++) { + points[i].x = (i * chart_w) / (count - 1); + points[i].y = chart_h - ((history[i] * chart_h) / 100); + } + + lv_line_set_points_mutable(line, points, count); + lv_obj_invalidate(line); +} + +static void set_cpu_curve(int cpu_percent) +{ + set_history_curve(s_cpu_history, s_cpu_points, CPU_HISTORY_POINTS, + CPU_CHART_W, CPU_CHART_H, cpu_percent, s_cpu_line); +} + +static int parse_int_field(char *field, int fallback) +{ + if (field == NULL || field[0] == '\0') { + return fallback; + } + + char *end = NULL; + long value = strtol(field, &end, 10); + if (end == field) { + return fallback; + } + + return (int)value; +} + +static int parse_disk_token(const char *token, char *name, size_t name_size, + int *percent, int *read_kbps, int *write_kbps) +{ + char buffer[40]; + snprintf(buffer, sizeof(buffer), "%s", token); + + char *cursor = buffer; + char *disk_name = strsep(&cursor, ":"); + char *usage = strsep(&cursor, ":"); + char *read_rate = strsep(&cursor, ":"); + char *write_rate = strsep(&cursor, ":"); + + if (disk_name == NULL || disk_name[0] == '\0' || usage == NULL) { + return 0; + } + + size_t len = strlen(disk_name); + if (len >= name_size) { + len = name_size - 1; + } + memcpy(name, disk_name, len); + name[len] = '\0'; + + *percent = clamp_percent(parse_int_field(usage, 0)); + *read_kbps = parse_int_field(read_rate, -1); + *write_kbps = parse_int_field(write_rate, -1); + return 1; +} + +static void update_disk_rows(const char *disk_text) +{ + char buffer[UI_MAX_DISK_TEXT]; + char name[8]; + int row = 0; + + snprintf(buffer, sizeof(buffer), "%s", disk_text && disk_text[0] ? disk_text : ""); + char *cursor = buffer; + char *token = strtok(cursor, " "); + while (token != NULL && row < DISK_ROW_COUNT) { + int percent = 0; + int read_kbps = -1; + int write_kbps = -1; + if (parse_disk_token(token, name, sizeof(name), &percent, &read_kbps, &write_kbps)) { + char text[28]; + char read[10]; + char write[10]; + snprintf(text, sizeof(text), "%s", name); + lv_label_set_text(s_disk_name_labels[row], text); + format_rate_short(read_kbps, read, sizeof(read)); + format_rate_short(write_kbps, write, sizeof(write)); + snprintf(text, sizeof(text), "%s/%s", read, write); + lv_label_set_text(s_disk_rate_labels[row], text); + lv_bar_set_value(s_disk_bars[row], percent, LV_ANIM_OFF); + lv_obj_clear_flag(s_disk_name_labels[row], LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(s_disk_rate_labels[row], LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(s_disk_bars[row], LV_OBJ_FLAG_HIDDEN); + row++; + } + token = strtok(NULL, " "); + } + + for (int i = row; i < DISK_ROW_COUNT; i++) { + lv_obj_add_flag(s_disk_name_labels[i], LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(s_disk_rate_labels[i], LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(s_disk_bars[i], LV_OBJ_FLAG_HIDDEN); + } +} + +static void update_codex_panel(const ui_metrics_t *metrics) +{ + char text[40]; + char value[12]; + char reset_5h[12]; + char reset_week[12]; + + format_percent_compact(metrics->codex_5h_percent, value, sizeof(value)); + lv_label_set_text(s_codex_5h_value_label, value); + set_bar_percent(s_codex_5h_bar, metrics->codex_5h_percent); + + format_percent_compact(metrics->codex_week_percent, value, sizeof(value)); + lv_label_set_text(s_codex_week_value_label, value); + set_bar_percent(s_codex_week_bar, metrics->codex_week_percent); + + format_reset_mins(metrics->codex_5h_reset_mins, reset_5h, sizeof(reset_5h)); + format_reset_mins(metrics->codex_week_reset_mins, reset_week, sizeof(reset_week)); + lv_label_set_text(s_codex_5h_reset_label, reset_5h); + lv_label_set_text(s_codex_week_reset_label, reset_week); + + format_tokens_m10(metrics->codex_today_tokens_m10, value, sizeof(value)); + snprintf(text, sizeof(text), "TOK %s", value); + lv_label_set_text(s_codex_token_label, text); + + for (int i = 0; i < UI_CODEX_DAILY_BAR_COUNT; i++) { + lv_obj_t *bar = s_codex_daily_bars[i]; + if (bar == NULL) { + continue; + } + + if (i >= metrics->codex_daily_count || metrics->codex_daily_percent[i] < 0) { + lv_obj_add_flag(bar, LV_OBJ_FLAG_HIDDEN); + continue; + } + + int percent = clamp_percent(metrics->codex_daily_percent[i]); + int height = percent <= 0 ? 1 : 1 + ((percent * (CODEX_DAILY_BAR_MAX_H - 1)) + 50) / 100; + int x = 10 + i * (CODEX_DAILY_BAR_W + CODEX_DAILY_BAR_GAP); + int y = CODEX_DAILY_BAR_BASE_Y - height; + lv_obj_set_size(bar, CODEX_DAILY_BAR_W, height); + lv_obj_align(bar, LV_ALIGN_TOP_LEFT, x, y); + lv_obj_clear_flag(bar, LV_OBJ_FLAG_HIDDEN); + } +} + +static void update_zhipu_panel(const ui_metrics_t *metrics) +{ + char value[12]; + char reset[12]; + char text[24]; + + snprintf(text, sizeof(text), "STAT %s", zhipu_status_text(metrics->zhipu_status)); + lv_label_set_text(s_zhipu_status_label, text); + + format_percent_compact(metrics->zhipu_5h_percent, value, sizeof(value)); + lv_label_set_text(s_zhipu_5h_value_label, value); + set_bar_percent(s_zhipu_5h_bar, metrics->zhipu_5h_percent); + format_reset_mins(metrics->zhipu_5h_reset_mins, reset, sizeof(reset)); + lv_label_set_text(s_zhipu_5h_reset_label, reset); + + format_percent_compact(metrics->zhipu_week_percent, value, sizeof(value)); + lv_label_set_text(s_zhipu_week_value_label, value); + set_bar_percent(s_zhipu_week_bar, metrics->zhipu_week_percent); + format_reset_mins(metrics->zhipu_week_reset_mins, reset, sizeof(reset)); + lv_label_set_text(s_zhipu_week_reset_label, reset); +} + +static void update_net_line(const ui_metrics_t *metrics) +{ + char rx[12]; + char tx[12]; + char text[44]; + + format_rate_short(metrics->rx_kbps, rx, sizeof(rx)); + format_rate_short(metrics->tx_kbps, tx, sizeof(tx)); + snprintf(text, sizeof(text), "D %s U %s", rx, tx); + + size_t text_len = strlen(text); + const lv_font_t *font = &lv_font_montserrat_20; + if (text_len >= 18) { + font = &lv_font_montserrat_14; + } + + lv_obj_set_style_text_font(s_net_line_label, font, 0); + lv_label_set_long_mode(s_net_line_label, LV_LABEL_LONG_CLIP); + lv_label_set_text(s_net_line_label, text); +} + +void ui_screen_create(void) +{ + lv_obj_t *screen = lv_screen_active(); + lv_obj_set_style_bg_color(screen, paper(), 0); + lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0); + lv_obj_clear_flag(screen, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_clean(screen); + + make_rule(screen, 200, 6, 1, 288); + make_rule(screen, 6, 174, 388, 1); + + s_status_label = make_label(screen, "WAITING", &lv_font_montserrat_14); + lv_obj_add_flag(s_status_label, LV_OBJ_FLAG_HIDDEN); + + s_cpu_line = lv_line_create(screen); + lv_obj_set_size(s_cpu_line, CPU_CHART_W + 2, CPU_CHART_H + 2); + lv_obj_align(s_cpu_line, LV_ALIGN_TOP_LEFT, 8, 112); + lv_obj_set_style_line_color(s_cpu_line, ink(), 0); + lv_obj_set_style_line_width(s_cpu_line, 3, 0); + for (int i = 0; i < CPU_HISTORY_POINTS; i++) { + s_cpu_history[i] = 0; + } + set_cpu_curve(0); + + s_battery_label = make_label(screen, LV_SYMBOL_BATTERY_EMPTY " --%", &lv_font_montserrat_14); + lv_obj_set_width(s_battery_label, 54); + lv_obj_align(s_battery_label, LV_ALIGN_TOP_LEFT, 8, 8); + + s_conn_label = make_label(screen, LV_SYMBOL_USB, &lv_font_montserrat_14); + lv_obj_set_width(s_conn_label, 22); + lv_obj_align(s_conn_label, LV_ALIGN_TOP_LEFT, 58, 8); + + s_env_label = make_label(screen, "--C --%", &lv_font_montserrat_14); + lv_obj_set_width(s_env_label, 110); + lv_obj_align(s_env_label, LV_ALIGN_TOP_LEFT, 82, 8); + + lv_obj_t *cpu_title = make_label(screen, "CPU", &lv_font_montserrat_14); + lv_obj_align(cpu_title, LV_ALIGN_TOP_LEFT, 8, 36); + + make_label_halo(screen, s_cpu_value_halo, "--%", &lv_font_montserrat_48, 126, + LV_ALIGN_TOP_LEFT, 8, 52, 3, LV_TEXT_ALIGN_LEFT); + s_cpu_value_label = make_label(screen, "--%", &lv_font_montserrat_48); + lv_obj_set_width(s_cpu_value_label, 126); + lv_obj_set_style_text_align(s_cpu_value_label, LV_TEXT_ALIGN_LEFT, 0); + lv_obj_align(s_cpu_value_label, LV_ALIGN_TOP_LEFT, 8, 52); + + make_label_halo(screen, s_cpu_freq_halo, "--GHz", &lv_font_montserrat_28, 128, + LV_ALIGN_TOP_LEFT, 8, 96, 2, LV_TEXT_ALIGN_LEFT); + s_cpu_freq_label = make_label(screen, "--GHz", &lv_font_montserrat_28); + lv_obj_set_width(s_cpu_freq_label, 128); + lv_obj_align(s_cpu_freq_label, LV_ALIGN_TOP_LEFT, 8, 96); + + lv_obj_t *memory_title = make_label(screen, "MEM", &lv_font_montserrat_14); + lv_obj_align(memory_title, LV_ALIGN_TOP_LEFT, 208, 8); + make_label_halo(screen, s_memory_value_halo, "--%", &lv_font_montserrat_20, 50, + LV_ALIGN_TOP_RIGHT, -8, 8, 2, LV_TEXT_ALIGN_RIGHT); + s_memory_value_label = make_label(screen, "--%", &lv_font_montserrat_20); + lv_obj_set_width(s_memory_value_label, 50); + lv_obj_set_style_text_align(s_memory_value_label, LV_TEXT_ALIGN_RIGHT, 0); + lv_obj_align(s_memory_value_label, LV_ALIGN_TOP_RIGHT, -8, 8); + s_memory_detail_label = make_label(screen, "--/--", &lv_font_montserrat_14); + lv_obj_set_width(s_memory_detail_label, 178); + lv_obj_align(s_memory_detail_label, LV_ALIGN_TOP_LEFT, 208, 24); + s_memory_bar = make_bar(screen, 126, 10); + lv_obj_align(s_memory_bar, LV_ALIGN_TOP_LEFT, 262, 46); + + lv_obj_t *disk_title = make_label(screen, "DSK", &lv_font_montserrat_14); + lv_obj_align(disk_title, LV_ALIGN_TOP_LEFT, 208, 64); + + for (int i = 0; i < DISK_ROW_COUNT; i++) { + int y = 80 + i * 16; + s_disk_name_labels[i] = make_label(screen, "--", &lv_font_montserrat_14); + lv_obj_set_width(s_disk_name_labels[i], 16); + lv_obj_align(s_disk_name_labels[i], LV_ALIGN_TOP_LEFT, 208, y); + s_disk_bars[i] = make_bar(screen, 56, 8); + lv_obj_align(s_disk_bars[i], LV_ALIGN_TOP_LEFT, 226, y + 4); + s_disk_rate_labels[i] = make_label(screen, "--/--", &lv_font_montserrat_14); + lv_label_set_long_mode(s_disk_rate_labels[i], LV_LABEL_LONG_CLIP); + lv_obj_set_width(s_disk_rate_labels[i], 104); + lv_obj_align(s_disk_rate_labels[i], LV_ALIGN_TOP_LEFT, 288, y); + } + + s_net_line_label = make_label(screen, "D -- U --", &lv_font_montserrat_20); + lv_label_set_long_mode(s_net_line_label, LV_LABEL_LONG_CLIP); + lv_obj_set_width(s_net_line_label, 184); + lv_obj_set_height(s_net_line_label, 24); + lv_obj_align(s_net_line_label, LV_ALIGN_TOP_LEFT, 208, 150); + + lv_obj_t *codex_title = make_label(screen, "CODEX", &lv_font_montserrat_14); + lv_obj_align(codex_title, LV_ALIGN_TOP_LEFT, 8, 184); + + s_codex_5h_label = make_label(screen, "5H", &lv_font_montserrat_14); + lv_obj_set_width(s_codex_5h_label, 28); + lv_obj_align(s_codex_5h_label, LV_ALIGN_TOP_LEFT, 8, 204); + s_codex_5h_bar = make_bar(screen, 76, 8); + lv_obj_align(s_codex_5h_bar, LV_ALIGN_TOP_LEFT, 36, 208); + s_codex_5h_value_label = make_label(screen, "--", &lv_font_montserrat_14); + lv_obj_set_width(s_codex_5h_value_label, 38); + lv_obj_align(s_codex_5h_value_label, LV_ALIGN_TOP_LEFT, 118, 204); + s_codex_5h_reset_label = make_label(screen, "--", &lv_font_montserrat_14); + lv_label_set_long_mode(s_codex_5h_reset_label, LV_LABEL_LONG_CLIP); + lv_obj_set_width(s_codex_5h_reset_label, 38); + lv_obj_align(s_codex_5h_reset_label, LV_ALIGN_TOP_LEFT, 158, 204); + + s_codex_week_label = make_label(screen, "WK", &lv_font_montserrat_14); + lv_obj_set_width(s_codex_week_label, 28); + lv_obj_align(s_codex_week_label, LV_ALIGN_TOP_LEFT, 8, 224); + s_codex_week_bar = make_bar(screen, 76, 8); + lv_obj_align(s_codex_week_bar, LV_ALIGN_TOP_LEFT, 36, 228); + s_codex_week_value_label = make_label(screen, "--", &lv_font_montserrat_14); + lv_obj_set_width(s_codex_week_value_label, 38); + lv_obj_align(s_codex_week_value_label, LV_ALIGN_TOP_LEFT, 118, 224); + s_codex_week_reset_label = make_label(screen, "--", &lv_font_montserrat_14); + lv_label_set_long_mode(s_codex_week_reset_label, LV_LABEL_LONG_CLIP); + lv_obj_set_width(s_codex_week_reset_label, 38); + lv_obj_align(s_codex_week_reset_label, LV_ALIGN_TOP_LEFT, 158, 224); + + s_codex_token_label = make_label(screen, "TOK --", &lv_font_montserrat_14); + lv_obj_set_width(s_codex_token_label, 96); + lv_obj_set_style_text_align(s_codex_token_label, LV_TEXT_ALIGN_RIGHT, 0); + lv_obj_align(s_codex_token_label, LV_ALIGN_TOP_RIGHT, -204, 184); + + make_rule(screen, 8, CODEX_DAILY_BAR_BASE_Y, 188, 1); + for (int i = 0; i < UI_CODEX_DAILY_BAR_COUNT; i++) { + s_codex_daily_bars[i] = lv_obj_create(screen); + lv_obj_set_size(s_codex_daily_bars[i], CODEX_DAILY_BAR_W, 1); + lv_obj_align(s_codex_daily_bars[i], LV_ALIGN_TOP_LEFT, + 10 + i * (CODEX_DAILY_BAR_W + CODEX_DAILY_BAR_GAP), + CODEX_DAILY_BAR_BASE_Y - 1); + lv_obj_set_style_bg_color(s_codex_daily_bars[i], ink(), 0); + lv_obj_set_style_bg_opa(s_codex_daily_bars[i], LV_OPA_COVER, 0); + lv_obj_set_style_border_width(s_codex_daily_bars[i], 0, 0); + lv_obj_set_style_radius(s_codex_daily_bars[i], 0, 0); + lv_obj_set_style_pad_all(s_codex_daily_bars[i], 0, 0); + lv_obj_clear_flag(s_codex_daily_bars[i], LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(s_codex_daily_bars[i], LV_OBJ_FLAG_HIDDEN); + } + + lv_obj_t *zhipu_title = make_label(screen, "ZHIPU", &lv_font_montserrat_14); + lv_obj_align(zhipu_title, LV_ALIGN_TOP_LEFT, 208, 184); + s_zhipu_status_label = make_label(screen, "STAT WAIT", &lv_font_montserrat_14); + lv_obj_set_width(s_zhipu_status_label, 96); + lv_obj_set_style_text_align(s_zhipu_status_label, LV_TEXT_ALIGN_RIGHT, 0); + lv_obj_align(s_zhipu_status_label, LV_ALIGN_TOP_RIGHT, -8, 184); + s_zhipu_5h_label = make_label(screen, "5H", &lv_font_montserrat_14); + lv_obj_set_width(s_zhipu_5h_label, 28); + lv_obj_align(s_zhipu_5h_label, LV_ALIGN_TOP_LEFT, 208, 214); + s_zhipu_5h_bar = make_bar(screen, 74, 9); + lv_obj_align(s_zhipu_5h_bar, LV_ALIGN_TOP_LEFT, 238, 218); + s_zhipu_5h_value_label = make_label(screen, "--", &lv_font_montserrat_14); + lv_obj_set_width(s_zhipu_5h_value_label, 38); + lv_obj_align(s_zhipu_5h_value_label, LV_ALIGN_TOP_LEFT, 318, 214); + s_zhipu_5h_reset_label = make_label(screen, "--", &lv_font_montserrat_14); + lv_label_set_long_mode(s_zhipu_5h_reset_label, LV_LABEL_LONG_CLIP); + lv_obj_set_width(s_zhipu_5h_reset_label, 38); + lv_obj_align(s_zhipu_5h_reset_label, LV_ALIGN_TOP_LEFT, 358, 214); + + s_zhipu_week_label = make_label(screen, "WK", &lv_font_montserrat_14); + lv_obj_set_width(s_zhipu_week_label, 28); + lv_obj_align(s_zhipu_week_label, LV_ALIGN_TOP_LEFT, 208, 242); + s_zhipu_week_bar = make_bar(screen, 74, 9); + lv_obj_align(s_zhipu_week_bar, LV_ALIGN_TOP_LEFT, 238, 246); + s_zhipu_week_value_label = make_label(screen, "--", &lv_font_montserrat_14); + lv_obj_set_width(s_zhipu_week_value_label, 38); + lv_obj_align(s_zhipu_week_value_label, LV_ALIGN_TOP_LEFT, 318, 242); + s_zhipu_week_reset_label = make_label(screen, "--", &lv_font_montserrat_14); + lv_label_set_long_mode(s_zhipu_week_reset_label, LV_LABEL_LONG_CLIP); + lv_obj_set_width(s_zhipu_week_reset_label, 38); + lv_obj_align(s_zhipu_week_reset_label, LV_ALIGN_TOP_LEFT, 358, 242); +} + +void ui_screen_tick(void) +{ + s_age_seconds++; + if (s_age_seconds > 5 && s_status_label != NULL) { + lv_label_set_text(s_status_label, "STALE"); + } +} + +void ui_screen_set_status(const char *status) +{ + if (s_status_label == NULL || status == NULL) { + return; + } + + lv_lock(); + lv_label_set_text(s_status_label, status); + lv_unlock(); +} + +void ui_screen_update_battery(int percent, int millivolts) +{ + if (s_battery_label == NULL) { + return; + } + + const char *symbol = LV_SYMBOL_BATTERY_EMPTY; + percent = clamp_percent(percent); + if (percent >= 90) { + symbol = LV_SYMBOL_BATTERY_FULL; + } else if (percent >= 65) { + symbol = LV_SYMBOL_BATTERY_3; + } else if (percent >= 35) { + symbol = LV_SYMBOL_BATTERY_2; + } else if (percent >= 10) { + symbol = LV_SYMBOL_BATTERY_1; + } + + char text[24]; + if (millivolts > 0) { + snprintf(text, sizeof(text), "%s %d%%", symbol, percent); + } else { + snprintf(text, sizeof(text), "%s --%%", symbol); + } + + lv_lock(); + lv_label_set_text(s_battery_label, text); + lv_unlock(); +} + +void ui_screen_update_environment(int temperature_c10, int humidity_percent) +{ + if (s_env_label == NULL) { + return; + } + + if (humidity_percent < 0) { + humidity_percent = 0; + } else if (humidity_percent > 100) { + humidity_percent = 100; + } + + char text[32]; + int abs_temp_c10 = abs(temperature_c10); + if (temperature_c10 < 0) { + snprintf(text, sizeof(text), "-%d.%dC %d%%", abs_temp_c10 / 10, abs_temp_c10 % 10, humidity_percent); + } else { + snprintf(text, sizeof(text), "%d.%dC %d%%", abs_temp_c10 / 10, abs_temp_c10 % 10, humidity_percent); + } + + lv_lock(); + lv_label_set_text(s_env_label, text); + lv_unlock(); +} + +void ui_screen_update_metrics_from_source(const ui_metrics_t *metrics, const char *source) +{ + if (metrics == NULL || s_cpu_value_label == NULL) { + return; + } + + char text[96]; + char used[16]; + char total[16]; + + lv_lock(); + s_age_seconds = 0; + lv_label_set_text(s_status_label, source ? source : "LIVE"); + set_connection(connection_from_source(source)); + + snprintf(text, sizeof(text), "%d%%", clamp_percent(metrics->cpu_percent)); + set_label_with_halo(s_cpu_value_label, s_cpu_value_halo, text); + if (metrics->cpu_mhz > 0) { + snprintf(text, sizeof(text), "%d.%02dGHz", metrics->cpu_mhz / 1000, (metrics->cpu_mhz % 1000) / 10); + } else { + snprintf(text, sizeof(text), "--GHz"); + } + set_label_with_halo(s_cpu_freq_label, s_cpu_freq_halo, text); + set_cpu_curve(metrics->cpu_percent); + + snprintf(text, sizeof(text), "%d%%", clamp_percent(metrics->memory_percent)); + set_label_with_halo(s_memory_value_label, s_memory_value_halo, text); + format_capacity(metrics->memory_used_mb, used, sizeof(used)); + format_capacity(metrics->memory_total_mb, total, sizeof(total)); + snprintf(text, sizeof(text), "%s / %s", used, total); + lv_label_set_text(s_memory_detail_label, text); + set_bar_percent(s_memory_bar, metrics->memory_percent); + + update_disk_rows(metrics->disk_text); + + update_net_line(metrics); + update_codex_panel(metrics); + update_zhipu_panel(metrics); + lv_obj_invalidate(lv_screen_active()); + lv_unlock(); +} + +void ui_screen_update_metrics(const ui_metrics_t *metrics) +{ + ui_screen_update_metrics_from_source(metrics, "USB"); +} diff --git a/main/ui_screen.h b/main/ui_screen.h new file mode 100644 index 0000000..ee69526 --- /dev/null +++ b/main/ui_screen.h @@ -0,0 +1,50 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define UI_MAX_DISK_TEXT 128 +#define UI_CODEX_DAILY_BAR_COUNT 14 + +typedef struct { + int cpu_percent; + int cpu_mhz; + int memory_percent; + int memory_used_mb; + int memory_total_mb; + int disk_percent; + int disk_read_kbps; + int disk_write_kbps; + int rx_kbps; + int tx_kbps; + int disk_count; + int codex_5h_percent; + int codex_week_percent; + int codex_5h_reset_mins; + int codex_week_reset_mins; + int codex_reset_credits; + int codex_today_tokens_m10; + int codex_daily_count; + int codex_daily_percent[UI_CODEX_DAILY_BAR_COUNT]; + int zhipu_status; + int zhipu_quota_percent; + int zhipu_latency_ms; + int zhipu_5h_percent; + int zhipu_week_percent; + int zhipu_5h_reset_mins; + int zhipu_week_reset_mins; + char disk_text[UI_MAX_DISK_TEXT]; +} ui_metrics_t; + +void ui_screen_create(void); +void ui_screen_tick(void); +void ui_screen_update_metrics(const ui_metrics_t *metrics); +void ui_screen_update_metrics_from_source(const ui_metrics_t *metrics, const char *source); +void ui_screen_set_status(const char *status); +void ui_screen_update_battery(int percent, int millivolts); +void ui_screen_update_environment(int temperature_c10, int humidity_percent); + +#ifdef __cplusplus +} +#endif diff --git a/partitions.csv b/partitions.csv new file mode 100644 index 0000000..163e311 --- /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, 0x400000, diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..78b3585 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,15 @@ +# Scripts + +Old one-off diagnostic scripts are archived in `_archive_2026-06-20`. + +Use the RLCD workbench bridge as the normal entry point: + +```powershell +python .\tools\preview-bridge\server.py +``` + +Then open: + +```text +http://127.0.0.1:8788 +``` diff --git a/sdkconfig b/sdkconfig new file mode 100644 index 0000000..b12f8b8 --- /dev/null +++ b/sdkconfig @@ -0,0 +1,5177 @@ +# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) 6.0.1 Project Configuration +# +# default: +CONFIG_SOC_ADC_SUPPORTED=y +# default: +CONFIG_SOC_UART_SUPPORTED=y +# default: +CONFIG_SOC_PCNT_SUPPORTED=y +# default: +CONFIG_SOC_PHY_SUPPORTED=y +# default: +CONFIG_SOC_WIFI_SUPPORTED=y +# default: +CONFIG_SOC_TWAI_SUPPORTED=y +# default: +CONFIG_SOC_GDMA_SUPPORTED=y +# default: +CONFIG_SOC_UHCI_SUPPORTED=y +# default: +CONFIG_SOC_AHB_GDMA_SUPPORTED=y +# default: +CONFIG_SOC_GPTIMER_SUPPORTED=y +# default: +CONFIG_SOC_LCDCAM_CAM_SUPPORTED=y +# default: +CONFIG_SOC_LCDCAM_I80_LCD_SUPPORTED=y +# default: +CONFIG_SOC_LCDCAM_RGB_LCD_SUPPORTED=y +# default: +CONFIG_SOC_LCD_I80_SUPPORTED=y +# default: +CONFIG_SOC_LCD_RGB_SUPPORTED=y +# default: +CONFIG_SOC_MCPWM_SUPPORTED=y +# default: +CONFIG_SOC_DEDICATED_GPIO_SUPPORTED=y +# default: +CONFIG_SOC_CACHE_SUPPORT_WRAP=y +# default: +CONFIG_SOC_ULP_SUPPORTED=y +# default: +CONFIG_SOC_ULP_FSM_SUPPORTED=y +# default: +CONFIG_SOC_RISCV_COPROC_SUPPORTED=y +# default: +CONFIG_SOC_BT_SUPPORTED=y +# default: +CONFIG_SOC_USB_OTG_SUPPORTED=y +# default: +CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED=y +# default: +CONFIG_SOC_CCOMP_TIMER_SUPPORTED=y +# default: +CONFIG_SOC_ASYNC_MEMCPY_SUPPORTED=y +# default: +CONFIG_SOC_SUPPORTS_SECURE_DL_MODE=y +# default: +CONFIG_SOC_EFUSE_KEY_PURPOSE_FIELD=y +# default: +CONFIG_SOC_EFUSE_SUPPORTED=y +# default: +CONFIG_SOC_SDMMC_HOST_SUPPORTED=y +# default: +CONFIG_SOC_RTC_FAST_MEM_SUPPORTED=y +# default: +CONFIG_SOC_RTC_SLOW_MEM_SUPPORTED=y +# default: +CONFIG_SOC_RTC_MEM_SUPPORTED=y +# default: +CONFIG_SOC_RTC_TIMER_V1_SUPPORTED=y +# default: +CONFIG_SOC_PSRAM_DMA_CAPABLE=y +# default: +CONFIG_SOC_XT_WDT_SUPPORTED=y +# default: +CONFIG_SOC_I2S_SUPPORTED=y +# default: +CONFIG_SOC_RMT_SUPPORTED=y +# default: +CONFIG_SOC_SDM_SUPPORTED=y +# default: +CONFIG_SOC_GPSPI_SUPPORTED=y +# default: +CONFIG_SOC_LEDC_SUPPORTED=y +# default: +CONFIG_SOC_I2C_SUPPORTED=y +# default: +CONFIG_SOC_SYSTIMER_SUPPORTED=y +# default: +CONFIG_SOC_SUPPORT_COEXISTENCE=y +# default: +CONFIG_SOC_TEMP_SENSOR_SUPPORTED=y +# default: +CONFIG_SOC_AES_SUPPORTED=y +# default: +CONFIG_SOC_MPI_SUPPORTED=y +# default: +CONFIG_SOC_SHA_SUPPORTED=y +# default: +CONFIG_SOC_HMAC_SUPPORTED=y +# default: +CONFIG_SOC_DIG_SIGN_SUPPORTED=y +# default: +CONFIG_SOC_FLASH_ENC_SUPPORTED=y +# default: +CONFIG_SOC_SECURE_BOOT_SUPPORTED=y +# default: +CONFIG_SOC_MEMPROT_SUPPORTED=y +# default: +CONFIG_SOC_TOUCH_SENSOR_SUPPORTED=y +# default: +CONFIG_SOC_BOD_SUPPORTED=y +# default: +CONFIG_SOC_CLK_TREE_SUPPORTED=y +# default: +CONFIG_SOC_MPU_SUPPORTED=y +# default: +CONFIG_SOC_WDT_SUPPORTED=y +# default: +CONFIG_SOC_SPI_FLASH_SUPPORTED=y +# default: +CONFIG_SOC_RNG_SUPPORTED=y +# default: +CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y +# default: +CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y +# default: +CONFIG_SOC_LP_PERIPH_SHARE_INTERRUPT=y +# default: +CONFIG_SOC_PM_SUPPORTED=y +# default: +CONFIG_SOC_SIMD_INSTRUCTION_SUPPORTED=y +# default: +CONFIG_SOC_XTAL_SUPPORT_40M=y +# default: +CONFIG_SOC_APPCPU_HAS_CLOCK_GATING_BUG=y +# default: +CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y +# default: +CONFIG_SOC_ADC_DIG_CTRL_SUPPORTED=y +# default: +CONFIG_SOC_ADC_ARBITER_SUPPORTED=y +# default: +CONFIG_SOC_ADC_DIG_IIR_FILTER_SUPPORTED=y +# default: +CONFIG_SOC_ADC_MONITOR_SUPPORTED=y +# default: +CONFIG_SOC_ADC_DMA_SUPPORTED=y +# default: +CONFIG_SOC_ADC_PERIPH_NUM=2 +# default: +CONFIG_SOC_ADC_MAX_CHANNEL_NUM=10 +# default: +CONFIG_SOC_ADC_ATTEN_NUM=4 +# default: +CONFIG_SOC_ADC_DIGI_CONTROLLER_NUM=2 +# default: +CONFIG_SOC_ADC_PATT_LEN_MAX=24 +# default: +CONFIG_SOC_ADC_DIGI_MIN_BITWIDTH=12 +# default: +CONFIG_SOC_ADC_DIGI_MAX_BITWIDTH=12 +# default: +CONFIG_SOC_ADC_DIGI_RESULT_BYTES=4 +# default: +CONFIG_SOC_ADC_DIGI_DATA_BYTES_PER_CONV=4 +# default: +CONFIG_SOC_ADC_DIGI_IIR_FILTER_NUM=2 +# default: +CONFIG_SOC_ADC_DIGI_MONITOR_NUM=2 +# default: +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH=83333 +# default: +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW=611 +# default: +CONFIG_SOC_ADC_RTC_MIN_BITWIDTH=12 +# default: +CONFIG_SOC_ADC_RTC_MAX_BITWIDTH=12 +# default: +CONFIG_SOC_ADC_CALIBRATION_V1_SUPPORTED=y +# default: +CONFIG_SOC_ADC_SELF_HW_CALI_SUPPORTED=y +# default: +CONFIG_SOC_ADC_SHARED_POWER=y +# default: +CONFIG_SOC_APB_BACKUP_DMA=y +# default: +CONFIG_SOC_BROWNOUT_RESET_SUPPORTED=y +# default: +CONFIG_SOC_CACHE_WRITEBACK_SUPPORTED=y +# default: +CONFIG_SOC_CACHE_FREEZE_SUPPORTED=y +# default: +CONFIG_SOC_CACHE_ACS_INVALID_STATE_ON_PANIC=y +# default: +CONFIG_SOC_CPU_CORES_NUM=2 +# default: +CONFIG_SOC_CPU_INTR_NUM=32 +# default: +CONFIG_SOC_CPU_HAS_FPU=y +# default: +CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y +# default: +CONFIG_SOC_CPU_BREAKPOINTS_NUM=2 +# default: +CONFIG_SOC_CPU_WATCHPOINTS_NUM=2 +# default: +CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=0x40 +# default: +CONFIG_SOC_SIMD_PREFERRED_DATA_ALIGNMENT=16 +# default: +CONFIG_SOC_DS_SIGNATURE_MAX_BIT_LEN=4096 +# default: +CONFIG_SOC_DS_KEY_PARAM_MD_IV_LENGTH=16 +# default: +CONFIG_SOC_DS_KEY_CHECK_MAX_WAIT_US=1100 +# default: +CONFIG_SOC_AHB_GDMA_VERSION=1 +# default: +CONFIG_SOC_GPIO_PORT=1 +# default: +CONFIG_SOC_GPIO_PIN_COUNT=49 +# default: +CONFIG_SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER=y +# default: +CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y +# default: +CONFIG_SOC_GPIO_VALID_GPIO_MASK=0x1FFFFFFFFFFFF +# default: +CONFIG_SOC_GPIO_IN_RANGE_MAX=48 +# default: +CONFIG_SOC_GPIO_OUT_RANGE_MAX=48 +# default: +CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0x0001FFFFFC000000 +# default: +CONFIG_SOC_GPIO_CLOCKOUT_BY_IO_MUX=y +# default: +CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=3 +# default: +CONFIG_SOC_I2C_NUM=2 +# default: +CONFIG_SOC_HP_I2C_NUM=2 +# default: +CONFIG_SOC_I2C_SUPPORT_XTAL=y +# default: +CONFIG_SOC_I2C_SUPPORT_RTC=y +# default: +CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y +# default: +CONFIG_SOC_I2C_SUPPORT_SLAVE=y +# default: +CONFIG_SOC_I2C_SLAVE_SUPPORT_BROADCAST=y +# default: +CONFIG_SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE=y +# default: +CONFIG_SOC_I2S_HW_VERSION_2=y +# default: +CONFIG_SOC_I2S_SUPPORTS_PCM=y +# default: +CONFIG_SOC_I2S_SUPPORTS_PDM=y +# default: +CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y +# default: +CONFIG_SOC_I2S_SUPPORTS_PCM2PDM=y +# default: +CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y +# default: +CONFIG_SOC_I2S_SUPPORTS_PDM2PCM=y +# default: +CONFIG_SOC_I2S_PDM_MAX_TX_LINES=2 +# default: +CONFIG_SOC_I2S_PDM_MAX_RX_LINES=4 +# default: +CONFIG_SOC_I2S_SUPPORTS_TDM=y +# default: +CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y +# default: +CONFIG_SOC_LEDC_SUPPORT_XTAL_CLOCK=y +# default: +CONFIG_SOC_LEDC_TIMER_NUM=4 +# default: +CONFIG_SOC_LEDC_CHANNEL_NUM=8 +# default: +CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=14 +# default: +CONFIG_SOC_LEDC_SUPPORT_FADE_STOP=y +# default: +CONFIG_SOC_MCPWM_SWSYNC_CAN_PROPAGATE=y +# default: +CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=1 +# default: +CONFIG_SOC_MMU_PERIPH_NUM=1 +# default: +CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 +# default: +CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 +# default: +CONFIG_SOC_RMT_MEM_WORDS_PER_CHANNEL=48 +# default: +CONFIG_SOC_RMT_SUPPORT_RX_PINGPONG=y +# default: +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_COUNT=y +# default: +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP=y +# default: +CONFIG_SOC_RMT_SUPPORT_DMA=y +# default: +CONFIG_SOC_RTC_CNTL_CPU_PD_DMA_BUS_WIDTH=128 +# default: +CONFIG_SOC_RTC_CNTL_CPU_PD_REG_FILE_NUM=549 +# default: +CONFIG_SOC_RTC_CNTL_TAGMEM_PD_DMA_BUS_WIDTH=128 +# default: +CONFIG_SOC_RTC_CNTL_NEEDS_ATOMIC_ACCESS=y +# default: +CONFIG_SOC_RTCIO_PIN_COUNT=22 +# default: +CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y +# default: +CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y +# default: +CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y +# default: +CONFIG_SOC_LP_IO_CLOCK_IS_INDEPENDENT=y +# default: +CONFIG_SOC_SPI_PERIPH_NUM=3 +# default: +CONFIG_SOC_SPI_MAX_CS_NUM=6 +# default: +CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64 +# default: +CONFIG_SOC_SPI_SUPPORT_DDRCLK=y +# default: +CONFIG_SOC_SPI_SLAVE_SUPPORT_SEG_TRANS=y +# default: +CONFIG_SOC_SPI_SUPPORT_CD_SIG=y +# default: +CONFIG_SOC_SPI_SUPPORT_CONTINUOUS_TRANS=y +# default: +CONFIG_SOC_SPI_SUPPORT_SLAVE_HD_VER2=y +# default: +CONFIG_SOC_SPI_SUPPORT_CLK_APB=y +# default: +CONFIG_SOC_SPI_SUPPORT_CLK_XTAL=y +# default: +CONFIG_SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUT=y +# default: +CONFIG_SOC_MEMSPI_IS_INDEPENDENT=y +# default: +CONFIG_SOC_SPI_MAX_PRE_DIVIDER=16 +# default: +CONFIG_SOC_SPI_SUPPORT_OCT=y +# default: +CONFIG_SOC_SPI_SCT_SUPPORTED=y +# default: +CONFIG_SOC_SPI_SCT_REG_NUM=14 +# default: +CONFIG_SOC_SPI_SCT_BUFFER_NUM_MAX=y +# default: +CONFIG_SOC_SPI_SCT_CONF_BITLEN_MAX=0x3FFFA +# default: +CONFIG_SOC_SPIRAM_SUPPORTED=y +# default: +CONFIG_SOC_SPIRAM_XIP_SUPPORTED=y +# default: +CONFIG_SOC_SYSTIMER_COUNTER_NUM=2 +# default: +CONFIG_SOC_SYSTIMER_ALARM_NUM=3 +# default: +CONFIG_SOC_SYSTIMER_BIT_WIDTH_LO=32 +# default: +CONFIG_SOC_SYSTIMER_BIT_WIDTH_HI=20 +# default: +CONFIG_SOC_SYSTIMER_FIXED_DIVIDER=y +# default: +CONFIG_SOC_SYSTIMER_INT_LEVEL=y +# default: +CONFIG_SOC_SYSTIMER_ALARM_MISS_COMPENSATE=y +# default: +CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 +# default: +CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 +# default: +CONFIG_SOC_TOUCH_SENSOR_VERSION=2 +# default: +CONFIG_SOC_TOUCH_MIN_CHAN_ID=1 +# default: +CONFIG_SOC_TOUCH_MAX_CHAN_ID=14 +# default: +CONFIG_SOC_TOUCH_SUPPORT_BENCHMARK=y +# default: +CONFIG_SOC_TOUCH_SUPPORT_SLEEP_WAKEUP=y +# default: +CONFIG_SOC_TOUCH_SUPPORT_WATERPROOF=y +# default: +CONFIG_SOC_TOUCH_SUPPORT_PROX_SENSING=y +# default: +CONFIG_SOC_TOUCH_SUPPORT_DENOISE_CHAN=y +# default: +CONFIG_SOC_TOUCH_PROXIMITY_CHANNEL_NUM=3 +# default: +CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=1 +# default: +CONFIG_SOC_TWAI_CONTROLLER_NUM=1 +# default: +CONFIG_SOC_TWAI_MASK_FILTER_NUM=1 +# default: +CONFIG_SOC_UART_NUM=3 +# default: +CONFIG_SOC_UART_HP_NUM=3 +# default: +CONFIG_SOC_UART_FIFO_LEN=128 +# default: +CONFIG_SOC_UART_BITRATE_MAX=5000000 +# default: +CONFIG_SOC_UART_SUPPORT_WAKEUP_INT=y +# default: +CONFIG_SOC_UART_SUPPORT_APB_CLK=y +# default: +CONFIG_SOC_UART_SUPPORT_RTC_CLK=y +# default: +CONFIG_SOC_UART_SUPPORT_XTAL_CLK=y +# default: +CONFIG_SOC_UART_WAKEUP_SUPPORT_ACTIVE_THRESH_MODE=y +# default: +CONFIG_SOC_UHCI_NUM=1 +# default: +CONFIG_SOC_USB_OTG_PERIPH_NUM=1 +# default: +CONFIG_SOC_SHA_DMA_MAX_BUFFER_SIZE=3968 +# default: +CONFIG_SOC_SHA_SUPPORT_DMA=y +# default: +CONFIG_SOC_SHA_SUPPORT_RESUME=y +# default: +CONFIG_SOC_SHA_GDMA=y +# default: +CONFIG_SOC_SHA_SUPPORT_SHA1=y +# default: +CONFIG_SOC_SHA_SUPPORT_SHA224=y +# default: +CONFIG_SOC_SHA_SUPPORT_SHA256=y +# default: +CONFIG_SOC_SHA_SUPPORT_SHA384=y +# default: +CONFIG_SOC_SHA_SUPPORT_SHA512=y +# default: +CONFIG_SOC_SHA_SUPPORT_SHA512_224=y +# default: +CONFIG_SOC_SHA_SUPPORT_SHA512_256=y +# default: +CONFIG_SOC_SHA_SUPPORT_SHA512_T=y +# default: +CONFIG_SOC_MPI_MEM_BLOCKS_NUM=4 +# default: +CONFIG_SOC_MPI_OPERATIONS_NUM=3 +# default: +CONFIG_SOC_RSA_MAX_BIT_LEN=4096 +# default: +CONFIG_SOC_AES_SUPPORT_DMA=y +# default: +CONFIG_SOC_AES_GDMA=y +# default: +CONFIG_SOC_AES_SUPPORT_AES_128=y +# default: +CONFIG_SOC_AES_SUPPORT_AES_256=y +# default: +CONFIG_SOC_PM_SUPPORT_EXT0_WAKEUP=y +# default: +CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP=y +# default: +CONFIG_SOC_PM_SUPPORT_EXT_WAKEUP=y +# default: +CONFIG_SOC_PM_SUPPORT_WIFI_WAKEUP=y +# default: +CONFIG_SOC_PM_SUPPORT_BT_WAKEUP=y +# default: +CONFIG_SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP=y +# default: +CONFIG_SOC_PM_SUPPORT_CPU_PD=y +# default: +CONFIG_SOC_PM_SUPPORT_TAGMEM_PD=y +# default: +CONFIG_SOC_PM_SUPPORT_RTC_PERIPH_PD=y +# default: +CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y +# default: +CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y +# default: +CONFIG_SOC_PM_SUPPORT_MAC_BB_PD=y +# default: +CONFIG_SOC_PM_SUPPORT_MODEM_PD=y +# default: +CONFIG_SOC_CONFIGURABLE_VDDSDIO_SUPPORTED=y +# default: +CONFIG_SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_ONLY=y +# default: +CONFIG_SOC_PM_CPU_RETENTION_BY_RTCCNTL=y +# default: +CONFIG_SOC_PM_MODEM_RETENTION_BY_BACKUPDMA=y +# default: +CONFIG_SOC_PM_MODEM_PD_BY_SW=y +# default: +CONFIG_SOC_CLK_RC_FAST_D256_SUPPORTED=y +# default: +CONFIG_SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256=y +# default: +CONFIG_SOC_CLK_RC_FAST_SUPPORT_CALIBRATION=y +# default: +CONFIG_SOC_CLK_XTAL32K_SUPPORTED=y +# default: +CONFIG_SOC_CLK_LP_FAST_SUPPORT_XTAL_D2=y +# default: +CONFIG_SOC_EFUSE_DIS_DOWNLOAD_ICACHE=y +# default: +CONFIG_SOC_EFUSE_DIS_DOWNLOAD_DCACHE=y +# default: +CONFIG_SOC_EFUSE_HARD_DIS_JTAG=y +# default: +CONFIG_SOC_EFUSE_DIS_USB_JTAG=y +# default: +CONFIG_SOC_EFUSE_SOFT_DIS_JTAG=y +# default: +CONFIG_SOC_EFUSE_DIS_DIRECT_BOOT=y +# default: +CONFIG_SOC_EFUSE_DIS_ICACHE=y +# default: +CONFIG_SOC_EFUSE_BLOCK9_KEY_PURPOSE_QUIRK=y +# default: +CONFIG_SOC_EFUSE_XTS_AES_KEY_128=y +# default: +CONFIG_SOC_EFUSE_XTS_AES_KEY_256=y +# default: +CONFIG_SOC_SECURE_BOOT_V2_RSA=y +# default: +CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=3 +# default: +CONFIG_SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS=y +# default: +CONFIG_SOC_SUPPORT_SECURE_BOOT_REVOKE_KEY=y +# default: +CONFIG_SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX=64 +# default: +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES=y +# default: +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_OPTIONS=y +# default: +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_128=y +# default: +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_256=y +# default: +CONFIG_SOC_MEMPROT_CPU_PREFETCH_PAD_SIZE=16 +# default: +CONFIG_SOC_MEMPROT_MEM_ALIGN_SIZE=256 +# default: +CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21 +# default: +CONFIG_SOC_MAC_BB_PD_MEM_SIZE=192 +# default: +CONFIG_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 +# default: +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_WAIT_IDLE=y +# default: +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND=y +# default: +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_RESUME=y +# default: +CONFIG_SOC_SPI_MEM_SUPPORT_SW_SUSPEND=y +# default: +CONFIG_SOC_SPI_MEM_SUPPORT_FLASH_OPI_MODE=y +# default: +CONFIG_SOC_SPI_MEM_SUPPORT_TIMING_TUNING=y +# default: +CONFIG_SOC_SPI_MEM_SUPPORT_CONFIG_GPIO_BY_EFUSE=y +# default: +CONFIG_SOC_SPI_MEM_SUPPORT_WRAP=y +# default: +CONFIG_SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY=y +# default: +CONFIG_SOC_MEMSPI_CORE_CLK_SHARED_WITH_PSRAM=y +# default: +CONFIG_SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP=y +# default: +CONFIG_SOC_SPI_MEM_FLASH_SUPPORT_HPM=y +# default: +CONFIG_SOC_MEMSPI_ENCRYPTION_ALIGNMENT=16 +# default: +CONFIG_SOC_COEX_HW_PTI=y +# default: +CONFIG_SOC_EXTERNAL_COEX_LEADER_TX_LINE=y +# default: +CONFIG_SOC_SDMMC_USE_GPIO_MATRIX=y +# default: +CONFIG_SOC_SDMMC_NUM_SLOTS=2 +# default: +CONFIG_SOC_SDMMC_DATA_WIDTH_MAX=8 +# default: +CONFIG_SOC_SDMMC_DELAY_PHASE_NUM=4 +# default: +CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_FAST_RC=y +# default: +CONFIG_SOC_WIFI_HW_TSF=y +# default: +CONFIG_SOC_WIFI_FTM_SUPPORT=y +# default: +CONFIG_SOC_WIFI_GCMP_SUPPORT=y +# default: +CONFIG_SOC_WIFI_WAPI_SUPPORT=y +# default: +CONFIG_SOC_WIFI_TXOP_SUPPORT=y +# default: +CONFIG_SOC_WIFI_CSI_SUPPORT=y +# default: +CONFIG_SOC_WIFI_MESH_SUPPORT=y +# default: +CONFIG_SOC_WIFI_SUPPORT_VARIABLE_BEACON_WINDOW=y +# default: +CONFIG_SOC_WIFI_PHY_NEEDS_USB_WORKAROUND=y +# default: +CONFIG_SOC_BLE_SUPPORTED=y +# default: +CONFIG_SOC_BLE_MESH_SUPPORTED=y +# default: +CONFIG_SOC_BLE_50_SUPPORTED=y +# default: +CONFIG_SOC_BLE_DEVICE_PRIVACY_SUPPORTED=y +# default: +CONFIG_SOC_BLUFI_SUPPORTED=y +# default: +CONFIG_SOC_ULP_HAS_ADC=y +# default: +CONFIG_SOC_PHY_COMBO_MODULE=y +# default: +CONFIG_SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV=y +# default: +CONFIG_IDF_CMAKE=y +# default: +CONFIG_IDF_TOOLCHAIN="gcc" +# default: +CONFIG_IDF_TOOLCHAIN_GCC=y +# default: +CONFIG_IDF_TARGET_ARCH_XTENSA=y +# default: +CONFIG_IDF_TARGET_ARCH="xtensa" +# default: +CONFIG_IDF_TARGET="esp32s3" +# default: +CONFIG_IDF_INIT_VERSION="6.0.1" +# default: +CONFIG_IDF_TARGET_ESP32S3=y +# default: +CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009 + +# +# Build type +# +# default: +CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y +# default: +# CONFIG_APP_BUILD_TYPE_RAM is not set +# default: +CONFIG_APP_BUILD_GENERATE_BINARIES=y +# default: +CONFIG_APP_BUILD_BOOTLOADER=y +# default: +CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y +# default: +# CONFIG_APP_REPRODUCIBLE_BUILD is not set +# default: +# CONFIG_APP_NO_BLOBS is not set +# end of Build type + +# +# App Update config +# +# end of App Update config + +# +# Bootloader config +# + +# +# Bootloader manager +# +# default: +CONFIG_BOOTLOADER_COMPILE_TIME_DATE=y +# default: +CONFIG_BOOTLOADER_PROJECT_VER=1 +# end of Bootloader manager + +# +# Application Rollback +# +# default: +# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set +# end of Application Rollback + +# +# Recovery Bootloader and Rollback +# +# end of Recovery Bootloader and Rollback + +# default: +CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x0 +# default: +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +# default: +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set +# default: +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set + +# +# Log +# +# default: +CONFIG_BOOTLOADER_LOG_VERSION_1=y +# default: +CONFIG_BOOTLOADER_LOG_VERSION=1 +# default: +# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set +# default: +# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set +# default: +# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set +# default: +CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y +# default: +# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set +# default: +# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set +# default: +CONFIG_BOOTLOADER_LOG_LEVEL=3 + +# +# Format +# +# default: +# CONFIG_BOOTLOADER_LOG_COLORS is not set +# default: +CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y +# end of Format + +# +# Settings +# +# default: +CONFIG_BOOTLOADER_LOG_MODE_TEXT_EN=y +# default: +CONFIG_BOOTLOADER_LOG_MODE_TEXT=y +# end of Settings +# end of Log + +# default: +CONFIG_BOOTLOADER_CPU_CLK_FREQ_MHZ=80 + +# +# Serial Flash Configurations +# +# default: +# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set +# default: +CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y +# end of Serial Flash Configurations + +# default: +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y +# default: +# CONFIG_BOOTLOADER_FACTORY_RESET is not set +# default: +# CONFIG_BOOTLOADER_APP_TEST is not set +# default: +CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y +# default: +CONFIG_BOOTLOADER_WDT_ENABLE=y +# default: +# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set +# default: +CONFIG_BOOTLOADER_WDT_TIME_MS=9000 +# default: +# CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP is not set +# default: +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON is not set +# default: +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set +# default: +CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0 +# default: +# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set +# end of Bootloader config + +# +# Security features +# +# default: +CONFIG_SECURE_BOOT_V2_RSA_SUPPORTED=y +# default: +CONFIG_SECURE_BOOT_V2_PREFERRED=y +# default: +# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set +# default: +# CONFIG_SECURE_BOOT is not set +# default: +# CONFIG_SECURE_FLASH_ENC_ENABLED is not set +# default: +CONFIG_SECURE_ROM_DL_MODE_ENABLED=y +# end of Security features + +# +# Application manager +# +# default: +CONFIG_APP_COMPILE_TIME_DATE=y +# default: +# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set +# default: +# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set +# default: +# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set +# default: +CONFIG_APP_RETRIEVE_LEN_ELF_SHA=9 +# end of Application manager + +# default: +CONFIG_ESP_ROM_HAS_CRC_LE=y +# default: +CONFIG_ESP_ROM_HAS_CRC_BE=y +# default: +CONFIG_ESP_ROM_HAS_MZ_CRC32=y +# default: +CONFIG_ESP_ROM_HAS_JPEG_DECODE=y +# default: +CONFIG_ESP_ROM_UART_CLK_IS_XTAL=y +# default: +CONFIG_ESP_ROM_HAS_RETARGETABLE_LOCKING=y +# default: +CONFIG_ESP_ROM_USB_OTG_NUM=3 +# default: +CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=4 +# default: +CONFIG_ESP_ROM_HAS_ERASE_0_REGION_BUG=y +# default: +CONFIG_ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV=y +# default: +CONFIG_ESP_ROM_GET_CLK_FREQ=y +# default: +CONFIG_ESP_ROM_HAS_HAL_WDT=y +# default: +CONFIG_ESP_ROM_NEEDS_SWSETUP_WORKAROUND=y +# default: +CONFIG_ESP_ROM_HAS_LAYOUT_TABLE=y +# default: +CONFIG_ESP_ROM_HAS_SPI_FLASH=y +# default: +CONFIG_ESP_ROM_HAS_SPI_FLASH_MMAP=y +# default: +CONFIG_ESP_ROM_HAS_ETS_PRINTF_BUG=y +# default: +CONFIG_ESP_ROM_HAS_NEWLIB=y +# default: +CONFIG_ESP_ROM_HAS_NEWLIB_NANO_FORMAT=y +# default: +CONFIG_ESP_ROM_HAS_NEWLIB_32BIT_TIME=y +# default: +CONFIG_ESP_ROM_NEEDS_SET_CACHE_MMU_SIZE=y +# default: +CONFIG_ESP_ROM_RAM_APP_NEEDS_MMU_INIT=y +# default: +CONFIG_ESP_ROM_HAS_FLASH_COUNT_PAGES_BUG=y +# default: +CONFIG_ESP_ROM_HAS_CACHE_SUSPEND_WAITI_BUG=y +# default: +CONFIG_ESP_ROM_HAS_CACHE_WRITEBACK_BUG=y +# default: +CONFIG_ESP_ROM_HAS_SW_FLOAT=y +# default: +CONFIG_ESP_ROM_HAS_VERSION=y +# default: +CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB=y +# default: +CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y +# default: +CONFIG_ESP_ROM_CONSOLE_OUTPUT_SECONDARY=y + +# +# Boot ROM Behavior +# +# default: +CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y +# default: +# CONFIG_BOOT_ROM_LOG_ALWAYS_OFF is not set +# default: +# CONFIG_BOOT_ROM_LOG_ON_GPIO_HIGH is not set +# default: +# CONFIG_BOOT_ROM_LOG_ON_GPIO_LOW is not set +# end of Boot ROM Behavior + +# +# Serial flasher config +# +# default: +# CONFIG_ESPTOOLPY_NO_STUB is not set +# default: +# CONFIG_ESPTOOLPY_OCT_FLASH is not set +# default: +CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=y +# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set +# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set +# default: +CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y +# default: +CONFIG_ESPTOOLPY_FLASHMODE="dio" +# CONFIG_ESPTOOLPY_FLASHFREQ_120M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +# CONFIG_ESPTOOLPY_FLASHFREQ_40M is not set +# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set +# default: +CONFIG_ESPTOOLPY_FLASHFREQ="80m" +# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set +# default: +CONFIG_ESPTOOLPY_FLASHSIZE="16MB" +# default: +# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set +# default: +CONFIG_ESPTOOLPY_BEFORE_RESET=y +# default: +# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set +# default: +CONFIG_ESPTOOLPY_BEFORE="default-reset" +# default: +CONFIG_ESPTOOLPY_AFTER_RESET=y +# default: +# CONFIG_ESPTOOLPY_AFTER_NORESET is not set +# default: +CONFIG_ESPTOOLPY_AFTER="hard-reset" +# default: +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +# end of Serial flasher config + +# +# Partition Table +# +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +# default: +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +# default: +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +# default: +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# Compiler options +# +CONFIG_COMPILER_OPTIMIZATION_DEBUG=y +# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set +# CONFIG_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_COMPILER_OPTIMIZATION_NONE is not set +# default: +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +# default: +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set +# default: +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set +# default: +# CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE is not set +# default: +CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y +# default: +CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 +# default: +# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set +# default: +CONFIG_COMPILER_HIDE_PATHS_MACROS=y +# default: +# CONFIG_COMPILER_CXX_EXCEPTIONS is not set +# default: +# CONFIG_COMPILER_CXX_RTTI is not set +# default: +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +# default: +# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set +# default: +# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set +# default: +# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set +# default: +# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set +# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set +# default: +# CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS is not set +# default: +# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set +# default: +# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set +# default: +# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set +# default: +# CONFIG_COMPILER_DISABLE_GCC15_WARNINGS is not set +# default: +# CONFIG_COMPILER_DUMP_RTL_FILES is not set +# default: +CONFIG_COMPILER_RT_LIB_GCCLIB=y +# default: +CONFIG_COMPILER_RT_LIB_NAME="gcc" +# default: +CONFIG_COMPILER_ORPHAN_SECTIONS_ERROR=y +# default: +# CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING is not set +# default: +# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set +# default: +# CONFIG_COMPILER_STATIC_ANALYZER is not set +# default: +CONFIG_COMPILER_CXX_GLIBCXX_CONSTEXPR_NO_CHANGE=y +# default: +# CONFIG_COMPILER_CXX_GLIBCXX_CONSTEXPR_COLD_CONSTEXPR is not set +# default: +# CONFIG_COMPILER_CXX_GLIBCXX_CONSTEXPR_COLD is not set +# end of Compiler options + +# +# Component config +# + +# +# Bluetooth +# +CONFIG_BT_ENABLED=y +# CONFIG_BT_BLUEDROID_ENABLED is not set +CONFIG_BT_NIMBLE_ENABLED=y +# CONFIG_BT_CONTROLLER_ONLY is not set +# default: +CONFIG_BT_CONTROLLER_ENABLED=y +# default: +# CONFIG_BT_CONTROLLER_DISABLED is not set + +# +# NimBLE Options +# + +# +# General +# +# default: +CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# default: +# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL is not set +# default: +# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set +# default: +CONFIG_BT_NIMBLE_PINNED_TO_CORE=0 +# default: +CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y +# default: +# CONFIG_BT_NIMBLE_PINNED_TO_CORE_1 is not set +CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=4096 +# default: +CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE=y +# end of General + +# +# Roles and Profiles +# +# CONFIG_BT_NIMBLE_ROLE_CENTRAL is not set +# default: +CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y +# default: +CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y +# CONFIG_BT_NIMBLE_ROLE_OBSERVER is not set +# default: +CONFIG_BT_NIMBLE_GATT_SERVER=y +# end of Roles and Profiles + +# +# Security (SMP) +# +# default: +CONFIG_BT_NIMBLE_SECURITY_ENABLE=y +# default: +CONFIG_BT_NIMBLE_SM_LEGACY=y +# default: +CONFIG_BT_NIMBLE_SM_SC=y +# default: +# CONFIG_BT_NIMBLE_SM_SC_DEBUG_KEYS is not set +# default: +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_ENCRYPTION=y +# default: +CONFIG_BT_NIMBLE_SM_LVL=0 +# default: +CONFIG_BT_NIMBLE_SM_SC_ONLY=0 +# default: +# CONFIG_BT_NIMBLE_SMP_ID_RESET is not set +# default: +# CONFIG_BT_NIMBLE_NVS_PERSIST is not set +CONFIG_BT_NIMBLE_MAX_BONDS=1 +# default: +# CONFIG_BT_NIMBLE_HANDLE_REPEAT_PAIRING_DELETION is not set +# end of Security (SMP) + +# +# GAP +# +# default: +CONFIG_BT_NIMBLE_RPA_TIMEOUT=900 +# default: +CONFIG_BT_NIMBLE_WHITELIST_SIZE=12 +# default: +CONFIG_BT_NIMBLE_ENABLE_CONN_REATTEMPT=y +# default: +CONFIG_BT_NIMBLE_MAX_CONN_REATTEMPT=3 +# default: +CONFIG_BT_NIMBLE_HS_PVCY=y +# default: +# CONFIG_BT_NIMBLE_HOST_ALLOW_CONNECT_WITH_SCAN is not set +# default: +# CONFIG_BT_NIMBLE_HOST_QUEUE_CONG_CHECK is not set +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 +CONFIG_BT_NIMBLE_MAX_CCCDS=1 +# default: +# CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS is not set +# default: +CONFIG_BT_NIMBLE_HS_STOP_TIMEOUT_MS=2000 +# default: +CONFIG_BT_NIMBLE_USE_ESP_TIMER=y +# default: +# CONFIG_BT_NIMBLE_HS_FLOW_CTRL is not set +# end of GAP + +# +# GATT / ATT +# +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=185 +# default: +CONFIG_BT_NIMBLE_ATT_MAX_PREP_ENTRIES=64 +# default: +CONFIG_BT_NIMBLE_GATT_MAX_PROCS=4 +# default: +# CONFIG_BT_NIMBLE_BLE_GATT_BLOB_TRANSFER is not set +# default: +# CONFIG_BT_NIMBLE_GATT_CACHING is not set +# default: +# CONFIG_BT_NIMBLE_INCL_SVC_DISCOVERY is not set +# end of GATT / ATT + +# +# L2CAP +# +# default: +CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=0 +# end of L2CAP + +# +# Memory Settings +# +# default: +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=12 +# default: +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=256 +# default: +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=24 +# default: +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=320 +# default: +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 +# default: +CONFIG_BT_NIMBLE_TRANSPORT_ACL_SIZE=255 +# default: +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70 +# default: +CONFIG_BT_NIMBLE_TRANSPORT_EVT_COUNT=30 +# default: +CONFIG_BT_NIMBLE_TRANSPORT_EVT_DISCARD_COUNT=8 +# default: +CONFIG_BT_NIMBLE_L2CAP_COC_SDU_BUFF_COUNT=1 +# default: +# CONFIG_BT_NIMBLE_MEMPOOL_RUNTIME_ALLOC is not set +# end of Memory Settings + +# +# BLE 5.x Features +# +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_2M_PHY=y +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_CODED_PHY=y +# CONFIG_BT_NIMBLE_EXT_ADV is not set +# CONFIG_BT_NIMBLE_EXT_SCAN is not set +# CONFIG_BT_NIMBLE_ISO is not set +# end of BLE 5.x Features + +# +# Services +# +# default: +CONFIG_BT_NIMBLE_PROX_SERVICE=y +# default: +CONFIG_BT_NIMBLE_ANS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_CTS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_HTP_SERVICE=y +# default: +CONFIG_BT_NIMBLE_IPSS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_TPS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_IAS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_LLS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_SPS_SERVICE=y +# default: +CONFIG_BT_NIMBLE_HR_SERVICE=y +# default: +# CONFIG_BT_NIMBLE_HID_SERVICE is not set +# default: +CONFIG_BT_NIMBLE_BAS_SERVICE=y +# default: +# CONFIG_BT_NIMBLE_SVC_BAS_BATTERY_LEVEL_NOTIFY is not set +# default: +CONFIG_BT_NIMBLE_DIS_SERVICE=y +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_MANUFACTURER_NAME is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_SERIAL_NUMBER is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_HARDWARE_REVISION is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_FIRMWARE_REVISION is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_SOFTWARE_REVISION is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_SYSTEM_ID is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_PNP_ID is not set +# default: +# CONFIG_BT_NIMBLE_SVC_DIS_INCLUDED is not set +# default: +CONFIG_BT_NIMBLE_GAP_SERVICE=y +# default: +CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" +# default: +CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_ENC=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHEN=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHOR=0 +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_GATT_SECURITY_LEVEL is not set +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_RPA_ONLY is not set +# default: +CONFIG_BT_NIMBLE_SVC_GAP_CAR_CHAR_NOT_SUPP=y +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_CAR_NOT_SUPP is not set +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_CAR_SUPP is not set +# default: +CONFIG_BT_NIMBLE_SVC_GAP_CENT_ADDR_RESOLUTION=-1 + +# +# GAP Appearance write permissions +# +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE is not set +# default: +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ENC=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHN=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHR=0 +# end of GAP Appearance write permissions + +# +# GAP device name write permissions +# +# default: +# CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE is not set +# end of GAP device name write permissions + +# +# Peripheral Preferred Connection Parameters (PPCP) settings +# +# default: +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SLAVE_LATENCY=0 +# default: +CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SUPERVISION_TMO=0 +# end of Peripheral Preferred Connection Parameters (PPCP) settings +# end of Services + +# +# Extra Features +# +# default: +# CONFIG_BT_NIMBLE_DYNAMIC_SERVICE is not set +# default: +# CONFIG_BT_NIMBLE_BLUFI_ENABLE is not set +# default: +# CONFIG_BT_NIMBLE_ENC_ADV_DATA is not set +# default: +# CONFIG_BT_NIMBLE_ADV_UUID_CONCAT is not set +# default: +# CONFIG_BT_NIMBLE_GATTC_PROC_PREEMPTION_PROTECT is not set +# default: +# CONFIG_BT_NIMBLE_GATTC_AUTO_PAIR is not set +# default: +CONFIG_BT_NIMBLE_EATT_CHAN_NUM=0 +# default: +# CONFIG_BT_NIMBLE_SUBRATE is not set +# default: +# CONFIG_BT_NIMBLE_STATIC_PASSKEY is not set +# default: +CONFIG_BT_NIMBLE_DTM_MODE_TEST=y +# default: +CONFIG_BT_NIMBLE_MEM_OPTIMIZATION=y +# default: +CONFIG_BT_NIMBLE_STATIC_TO_DYNAMIC=y +# default: +CONFIG_BT_NIMBLE_SM_SIGN_CNT=y +# default: +CONFIG_BT_NIMBLE_CPFD_CAFD=y +# default: +CONFIG_BT_NIMBLE_RECONFIG_MTU=y +# default: +# CONFIG_BT_NIMBLE_LOW_SPEED_MODE is not set +# end of Extra Features + +# +# NimBLE Mesh +# +# default: +# CONFIG_BT_NIMBLE_MESH is not set +# end of NimBLE Mesh + +# +# Host-controller Transport +# +# default: +CONFIG_UART_HW_FLOWCTRL_DISABLE=y +# default: +# CONFIG_UART_HW_FLOWCTRL_CTS_RTS is not set +# default: +CONFIG_BT_NIMBLE_HCI_UART_FLOW_CTRL=0 +# default: +CONFIG_BT_NIMBLE_HCI_UART_RTS_PIN=19 +# default: +CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23 +# end of Host-controller Transport + +# +# Debugging/Testing +# +# default: +# CONFIG_BT_NIMBLE_MEM_DEBUG is not set +# default: +# CONFIG_BT_NIMBLE_LOG_LEVEL_NONE is not set +# default: +# CONFIG_BT_NIMBLE_LOG_LEVEL_ERROR is not set +# default: +# CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING is not set +# default: +CONFIG_BT_NIMBLE_LOG_LEVEL_INFO=y +# default: +# CONFIG_BT_NIMBLE_LOG_LEVEL_DEBUG is not set +# default: +CONFIG_BT_NIMBLE_LOG_LEVEL=1 +# default: +CONFIG_BT_NIMBLE_PRINT_ERR_NAME=y +# default: +# CONFIG_BT_NIMBLE_DEBUG is not set +# default: +# CONFIG_BT_NIMBLE_TEST_THROUGHPUT_TEST is not set +# end of Debugging/Testing + +# +# Vendor / Optimization +# +# default: +# CONFIG_BT_NIMBLE_VS_SUPPORT is not set +# default: +# CONFIG_BT_NIMBLE_HIGH_DUTY_ADV_ITVL is not set +# end of Vendor / Optimization + +# +# Helper Utils +# +# default: +CONFIG_BT_NIMBLE_CHK_HOST_STATUS=y +# default: +CONFIG_BT_NIMBLE_UTIL_API=y +# default: +CONFIG_BT_NIMBLE_EXTRA_ADV_FIELDS=y +# end of Helper Utils +# end of NimBLE Options + +# +# Controller Options +# +# default: +CONFIG_BT_CTRL_MODE_EFF=1 +# default: +CONFIG_BT_CTRL_BLE_MAX_ACT=6 +# default: +CONFIG_BT_CTRL_BLE_MAX_ACT_EFF=6 +# default: +CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=0 +# default: +CONFIG_BT_CTRL_PINNED_TO_CORE_0=y +# default: +# CONFIG_BT_CTRL_PINNED_TO_CORE_1 is not set +# default: +CONFIG_BT_CTRL_PINNED_TO_CORE=0 +# default: +CONFIG_BT_CTRL_HCI_MODE_VHCI=y +# default: +# CONFIG_BT_CTRL_HCI_MODE_UART_H4 is not set +# default: +CONFIG_BT_CTRL_HCI_TL=1 +# default: +CONFIG_BT_CTRL_ADV_DUP_FILT_MAX=30 +# default: +CONFIG_BT_BLE_CCA_MODE_NONE=y +# default: +# CONFIG_BT_BLE_CCA_MODE_HW is not set +# default: +# CONFIG_BT_BLE_CCA_MODE_SW is not set +# default: +CONFIG_BT_BLE_CCA_MODE=0 +# default: +CONFIG_BT_CTRL_HW_CCA_VAL=75 +# default: +CONFIG_BT_CTRL_HW_CCA_EFF=0 +# default: +CONFIG_BT_CTRL_CE_LENGTH_TYPE_ORIG=y +# default: +# CONFIG_BT_CTRL_CE_LENGTH_TYPE_CE is not set +# default: +# CONFIG_BT_CTRL_CE_LENGTH_TYPE_SD is not set +# default: +CONFIG_BT_CTRL_CE_LENGTH_TYPE_EFF=0 +# default: +CONFIG_BT_CTRL_TX_ANTENNA_INDEX_0=y +# default: +# CONFIG_BT_CTRL_TX_ANTENNA_INDEX_1 is not set +# default: +CONFIG_BT_CTRL_TX_ANTENNA_INDEX_EFF=0 +# default: +CONFIG_BT_CTRL_RX_ANTENNA_INDEX_0=y +# default: +# CONFIG_BT_CTRL_RX_ANTENNA_INDEX_1 is not set +# default: +CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0 +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N24 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N21 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N18 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N15 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N12 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N9 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N6 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N3 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set +# default: +CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set +# default: +# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set +# default: +CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11 +# default: +CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y +# default: +CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 +# default: +CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 +# default: +CONFIG_BT_CTRL_BLE_SCAN_DUPL=y +# default: +CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DEVICE=y +# default: +# CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DATA is not set +# default: +# CONFIG_BT_CTRL_SCAN_DUPL_TYPE_DATA_DEVICE is not set +# default: +CONFIG_BT_CTRL_SCAN_DUPL_TYPE=0 +# default: +CONFIG_BT_CTRL_SCAN_DUPL_CACHE_SIZE=100 +# default: +CONFIG_BT_CTRL_DUPL_SCAN_CACHE_REFRESH_PERIOD=0 +# default: +# CONFIG_BT_CTRL_BLE_MESH_SCAN_DUPL_EN is not set +# default: +# CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EN is not set +# default: +CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_DIS=y +# default: +CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EFF=0 + +# +# MODEM SLEEP Options +# +# default: +# CONFIG_BT_CTRL_MODEM_SLEEP is not set +# end of MODEM SLEEP Options + +# default: +CONFIG_BT_CTRL_SLEEP_MODE_EFF=0 +# default: +CONFIG_BT_CTRL_SLEEP_CLOCK_EFF=0 +# default: +CONFIG_BT_CTRL_HCI_TL_EFF=1 +# default: +# CONFIG_BT_CTRL_AGC_RECORRECT_EN is not set +# default: +# CONFIG_BT_CTRL_SCAN_BACKOFF_UPPERLIMITMAX is not set +# default: +# CONFIG_BT_BLE_ADV_DATA_LENGTH_ZERO_AUX is not set +# default: +CONFIG_BT_CTRL_CHAN_ASS_EN=y +# default: +CONFIG_BT_CTRL_LE_PING_EN=y + +# +# BLE disconnects when Instant Passed (0x28) occurs +# +# default: +# CONFIG_BT_CTRL_BLE_LLCP_CONN_UPDATE is not set +# default: +# CONFIG_BT_CTRL_BLE_LLCP_CHAN_MAP_UPDATE is not set +# default: +# CONFIG_BT_CTRL_BLE_LLCP_PHY_UPDATE is not set +# end of BLE disconnects when Instant Passed (0x28) occurs + +# default: +# CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY is not set +# default: +CONFIG_BT_CTRL_DTM_ENABLE=y +# default: +CONFIG_BT_CTRL_BLE_MASTER=y +# default: +# CONFIG_BT_CTRL_BLE_TEST is not set +# default: +CONFIG_BT_CTRL_BLE_SCAN=y +# default: +CONFIG_BT_CTRL_BLE_SECURITY_ENABLE=y +# default: +CONFIG_BT_CTRL_BLE_ADV=y +# default: +# CONFIG_BT_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS is not set + +# +# Controller debug log Options (Experimental) +# +# end of Controller debug log Options (Experimental) +# end of Controller Options + +# +# Common Options +# +# default: +CONFIG_BT_ALARM_MAX_NUM=50 +# default: +CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT=y +# default: +# CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS is not set + +# +# BLE Log +# +# default: +# CONFIG_BLE_LOG_ENABLED is not set +# end of BLE Log + +# default: +# CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set +# default: +# CONFIG_BT_BLE_LOG_UHCI_OUT_ENABLED is not set +# default: +# CONFIG_BT_LE_USED_MEM_STATISTICS_ENABLED is not set +# end of Common Options + +# default: +# CONFIG_BT_HCI_LOG_DEBUG_EN is not set +# end of Bluetooth + +# default: +# CONFIG_BLE_MESH is not set + +# +# Console Library +# +# default: +# CONFIG_CONSOLE_SORTED_HELP is not set +# end of Console Library + +# +# Legacy Driver Configurations +# + +# +# Legacy TWAI Driver Configurations +# +# default: +# CONFIG_TWAI_ISR_IN_IRAM_LEGACY is not set +# default: +# CONFIG_TWAI_SUPPRESS_DEPRECATE_WARN is not set +# default: +# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy TWAI Driver Configurations + +# +# Legacy I2C Driver Configurations +# +# default: +# CONFIG_I2C_SUPPRESS_DEPRECATE_WARN is not set +# default: +# CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy I2C Driver Configurations + +# +# Legacy Touch Sensor Driver Configurations +# +# default: +# CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARN is not set +# default: +# CONFIG_TOUCH_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy Touch Sensor Driver Configurations +# end of Legacy Driver Configurations + +# +# eFuse Bit Manager +# +# default: +# CONFIG_EFUSE_CUSTOM_TABLE is not set +# default: +# CONFIG_EFUSE_VIRTUAL is not set +# default: +CONFIG_EFUSE_MAX_BLK_LEN=256 +# end of eFuse Bit Manager + +# +# ESP-TLS +# +# default: +CONFIG_ESP_TLS_USING_MBEDTLS=y +# default: +# CONFIG_ESP_TLS_CUSTOM_STACK is not set +# default: +# CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set +# default: +CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y +# default: +# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set +# default: +# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set +# default: +# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set +# default: +# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set +# default: +# CONFIG_ESP_TLS_PSK_VERIFICATION is not set +# default: +# CONFIG_ESP_TLS_INSECURE is not set +# default: +CONFIG_ESP_TLS_DYN_BUF_STRATEGY_SUPPORTED=y +# end of ESP-TLS + +# +# ADC and ADC Calibration +# +# default: +# CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM is not set +# default: +# CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is not set +# default: +# CONFIG_ADC_CONTINUOUS_FORCE_USE_ADC2_ON_C3_S3 is not set +# default: +# CONFIG_ADC_ENABLE_DEBUG_LOG is not set +# end of ADC and ADC Calibration + +# +# Wireless Coexistence +# +# default: +CONFIG_ESP_COEX_ENABLED=y +# default: +CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y +# default: +# CONFIG_ESP_COEX_POWER_MANAGEMENT is not set +# default: +# CONFIG_ESP_COEX_GPIO_DEBUG is not set +# end of Wireless Coexistence + +# +# Common ESP-related +# +# default: +CONFIG_ESP_ERR_TO_NAME_LOOKUP=y +# end of Common ESP-related + +# +# ESP-Driver:Camera Controller Configurations +# +# default: +# CONFIG_CAM_CTLR_DVP_CAM_ISR_CACHE_SAFE is not set +# end of ESP-Driver:Camera Controller Configurations + +# +# GDMA Configurations +# +# default: +CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y +# default: +CONFIG_GDMA_ISR_HANDLER_IN_IRAM=y +# default: +CONFIG_GDMA_OBJ_DRAM_SAFE=y +# default: +# CONFIG_GDMA_ENABLE_DEBUG_LOG is not set +# end of GDMA Configurations + +# +# ESP-Driver:GPIO Configurations +# +# default: +# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:GPIO Configurations + +# +# ESP-Driver:GPTimer Configurations +# +# default: +CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y +# default: +# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set +# default: +# CONFIG_GPTIMER_ISR_CACHE_SAFE is not set +# default: +CONFIG_GPTIMER_OBJ_CACHE_SAFE=y +# default: +# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:GPTimer Configurations + +# +# ESP-Driver:I2C Configurations +# +# default: +# CONFIG_I2C_ISR_IRAM_SAFE is not set +# default: +# CONFIG_I2C_ENABLE_DEBUG_LOG is not set +# default: +CONFIG_I2C_MASTER_ISR_HANDLER_IN_IRAM=y +# end of ESP-Driver:I2C Configurations + +# +# ESP-Driver:I2S Configurations +# +# default: +# CONFIG_I2S_ISR_IRAM_SAFE is not set +# default: +# CONFIG_I2S_CTRL_FUNC_IN_IRAM is not set +# default: +# CONFIG_I2S_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:I2S Configurations + +# +# ESP-Driver:I3C Master Configurations +# +# default: +# CONFIG_I3C_MASTER_ISR_CACHE_SAFE is not set +# default: +# CONFIG_I3C_MASTER_ENABLE_DEBUG_LOG is not set +# default: +# CONFIG_I3C_MASTER_ISR_HANDLER_IN_IRAM is not set +# end of ESP-Driver:I3C Master Configurations + +# +# ESP-Driver:LEDC Configurations +# +# default: +# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:LEDC Configurations + +# +# ESP-Driver:MCPWM Configurations +# +# default: +CONFIG_MCPWM_ISR_HANDLER_IN_IRAM=y +# default: +# CONFIG_MCPWM_ISR_CACHE_SAFE is not set +# default: +# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set +# default: +CONFIG_MCPWM_OBJ_CACHE_SAFE=y +# default: +# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:MCPWM Configurations + +# +# ESP-Driver:PCNT Configurations +# +# default: +# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set +# default: +# CONFIG_PCNT_ISR_IRAM_SAFE is not set +# default: +# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:PCNT Configurations + +# +# ESP-Driver:RMT Configurations +# +# default: +CONFIG_RMT_ENCODER_FUNC_IN_IRAM=y +# default: +CONFIG_RMT_TX_ISR_HANDLER_IN_IRAM=y +# default: +CONFIG_RMT_RX_ISR_HANDLER_IN_IRAM=y +# default: +# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set +# default: +# CONFIG_RMT_TX_ISR_CACHE_SAFE is not set +# default: +# CONFIG_RMT_RX_ISR_CACHE_SAFE is not set +# default: +CONFIG_RMT_OBJ_CACHE_SAFE=y +# default: +# CONFIG_RMT_ENABLE_DEBUG_LOG is not set +# default: +# CONFIG_RMT_ISR_IRAM_SAFE is not set +# end of ESP-Driver:RMT Configurations + +# +# ESP-Driver:Sigma Delta Modulator Configurations +# +# default: +# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set +# default: +# CONFIG_SDM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Sigma Delta Modulator Configurations + +# +# ESP-Driver:SD Host SDMMC Controller Configurations +# +# default: +# CONFIG_SD_HOST_SDMMC_ISR_CACHE_SAFE is not set +# end of ESP-Driver:SD Host SDMMC Controller Configurations + +# +# ESP-Driver:SPI Configurations +# +# default: +CONFIG_SPI_MASTER_ISR_IN_IRAM=y +# default: +# CONFIG_SPI_SLAVE_IN_IRAM is not set +# default: +CONFIG_SPI_SLAVE_ISR_IN_IRAM=y +# end of ESP-Driver:SPI Configurations + +# +# ESP-Driver:Touch Sensor Configurations +# +# default: +# CONFIG_TOUCH_CTRL_FUNC_IN_IRAM is not set +# default: +# CONFIG_TOUCH_ISR_IRAM_SAFE is not set +# default: +# CONFIG_TOUCH_ENABLE_DEBUG_LOG is not set +# default: +# CONFIG_TOUCH_SKIP_FSM_CHECK is not set +# end of ESP-Driver:Touch Sensor Configurations + +# +# ESP-Driver:Temperature Sensor Configurations +# +# default: +# CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Temperature Sensor Configurations + +# +# ESP-Driver:TWAI Configurations +# +# default: +# CONFIG_TWAI_ISR_IN_IRAM is not set +# default: +# CONFIG_TWAI_IO_FUNC_IN_IRAM is not set +# default: +# CONFIG_TWAI_ISR_CACHE_SAFE is not set +# default: +# CONFIG_TWAI_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:TWAI Configurations + +# +# ESP-Driver:UART Configurations +# +# default: +# CONFIG_UART_ISR_IN_IRAM is not set +# end of ESP-Driver:UART Configurations + +# +# ESP-Driver:UHCI Configurations +# +# default: +# CONFIG_UHCI_ISR_HANDLER_IN_IRAM is not set +# default: +# CONFIG_UHCI_ISR_CACHE_SAFE is not set +# default: +# CONFIG_UHCI_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:UHCI Configurations + +# +# ESP-Driver:USB Serial/JTAG Configuration +# +# default: +CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y +# end of ESP-Driver:USB Serial/JTAG Configuration + +# +# Ethernet +# +# default: +CONFIG_ETH_ENABLED=y +# default: +CONFIG_ETH_USE_SPI_ETHERNET=y +# default: +# CONFIG_ETH_USE_OPENETH is not set +# default: +# CONFIG_ETH_TRANSMIT_MUTEX is not set +# end of Ethernet + +# +# Event Loop Library +# +# default: +# CONFIG_ESP_EVENT_LOOP_PROFILING is not set +# default: +CONFIG_ESP_EVENT_POST_FROM_ISR=y +# default: +CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y +# end of Event Loop Library + +# +# GDB Stub +# +# default: +CONFIG_ESP_GDBSTUB_ENABLED=y +# default: +# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set +# default: +CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y +# default: +CONFIG_ESP_GDBSTUB_MAX_TASKS=32 +# end of GDB Stub + +# +# ESP HID +# +# default: +CONFIG_ESPHID_TASK_SIZE_BT=2048 +# default: +CONFIG_ESPHID_TASK_SIZE_BLE=4096 +# end of ESP HID + +# +# ESP HTTP client +# +# default: +CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y +# default: +# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set +# default: +# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set +# default: +# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set +# default: +# CONFIG_ESP_HTTP_CLIENT_ENABLE_GET_CONTENT_RANGE is not set +# default: +CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000 +# default: +# CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS is not set +# end of ESP HTTP client + +# +# HTTP Server +# +# default: +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 +# default: +CONFIG_HTTPD_MAX_URI_LEN=512 +# default: +CONFIG_HTTPD_ERR_RESP_NO_DELAY=y +# default: +CONFIG_HTTPD_PURGE_BUF_LEN=32 +# default: +# CONFIG_HTTPD_LOG_PURGE_DATA is not set +# default: +# CONFIG_HTTPD_WS_SUPPORT is not set +# default: +# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set +# default: +CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000 +# end of HTTP Server + +# +# ESP HTTPS OTA +# +# default: +# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set +# default: +# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set +# default: +CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000 +# default: +# CONFIG_ESP_HTTPS_OTA_ENABLE_PARTIAL_DOWNLOAD is not set +# end of ESP HTTPS OTA + +# +# ESP HTTPS server +# +# default: +# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set +# default: +CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000 +# default: +# CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK is not set +# end of ESP HTTPS server + +# +# Hardware Settings +# +# default: +CONFIG_ESP_HW_SUPPORT_FUNC_IN_IRAM=y + +# +# Chip revision +# +# default: +CONFIG_ESP32S3_REV_MIN_0=y +# default: +# CONFIG_ESP32S3_REV_MIN_1 is not set +# default: +# CONFIG_ESP32S3_REV_MIN_2 is not set +# default: +CONFIG_ESP32S3_REV_MIN_FULL=0 +# default: +CONFIG_ESP_REV_MIN_FULL=0 + +# +# Maximum Supported ESP32-S3 Revision (Rev v0.99) +# +# default: +CONFIG_ESP32S3_REV_MAX_FULL=99 +# default: +CONFIG_ESP_REV_MAX_FULL=99 +# default: +CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0 +# default: +CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL=199 + +# +# Maximum Supported ESP32-S3 eFuse Block Revision (eFuse Block Rev v1.99) +# +# end of Chip revision + +# +# MAC Config +# +# default: +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y +# default: +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y +# default: +CONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y +# default: +CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y +# default: +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES_FOUR=y +# default: +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES=4 +# default: +# CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_TWO is not set +# default: +CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_FOUR=y +# default: +CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4 +# default: +# CONFIG_ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC is not set +# end of MAC Config + +# +# Sleep Config +# +# default: +CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y +# default: +CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y +# default: +CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y +# default: +CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y +# default: +CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y +# default: +CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=2000 +# default: +# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set +# default: +# CONFIG_ESP_SLEEP_DEBUG is not set +# default: +CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y +# end of Sleep Config + +# +# RTC Clock Config +# +# default: +CONFIG_RTC_CLK_SRC_INT_RC=y +# default: +# CONFIG_RTC_CLK_SRC_EXT_CRYS is not set +# default: +# CONFIG_RTC_CLK_SRC_EXT_OSC is not set +# default: +# CONFIG_RTC_CLK_SRC_INT_8MD256 is not set +# default: +CONFIG_RTC_CLK_CAL_CYCLES=1024 +# default: +CONFIG_RTC_CLK_FUNC_IN_IRAM=y +# default: +CONFIG_RTC_TIME_FUNC_IN_IRAM=y +# end of RTC Clock Config + +# +# Peripheral Control +# +# default: +CONFIG_ESP_PERIPH_CTRL_FUNC_IN_IRAM=y +# default: +CONFIG_ESP_REGI2C_CTRL_FUNC_IN_IRAM=y +# end of Peripheral Control + +# +# Main XTAL Config +# +# default: +CONFIG_XTAL_FREQ_40=y +# default: +CONFIG_XTAL_FREQ=40 +# end of Main XTAL Config + +# +# Power Supplier +# + +# +# Brownout Detector +# +# default: +CONFIG_ESP_BROWNOUT_DET=y +# default: +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7=y +# default: +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set +# default: +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set +# default: +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4 is not set +# default: +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3 is not set +# default: +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2 is not set +# default: +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_1 is not set +# default: +CONFIG_ESP_BROWNOUT_DET_LVL=7 +# default: +CONFIG_ESP_BROWNOUT_USE_INTR=y +# end of Brownout Detector +# end of Power Supplier + +# default: +CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y +# default: +CONFIG_ESP_INTR_IN_IRAM=y +# end of Hardware Settings + +# +# ESP-Driver:LCD Controller Configurations +# +# default: +# CONFIG_LCD_RGB_ISR_IRAM_SAFE is not set +# default: +# CONFIG_LCD_RGB_RESTART_IN_VSYNC is not set +# default: +# CONFIG_LCD_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:LCD Controller Configurations + +# +# LibC +# +# default: +# CONFIG_LIBC_NEWLIB is not set +# default: +CONFIG_LIBC_PICOLIBC=y +# default: +CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY=y +# default: +CONFIG_LIBC_MISC_IN_IRAM=y +# default: +CONFIG_LIBC_LOCKS_PLACE_IN_IRAM=y +# default: +CONFIG_LIBC_STDOUT_LINE_ENDING_CRLF=y +# default: +# CONFIG_LIBC_STDOUT_LINE_ENDING_LF is not set +# default: +# CONFIG_LIBC_STDOUT_LINE_ENDING_CR is not set +# default: +# CONFIG_LIBC_STDIN_LINE_ENDING_CRLF is not set +# default: +# CONFIG_LIBC_STDIN_LINE_ENDING_LF is not set +# default: +CONFIG_LIBC_STDIN_LINE_ENDING_CR=y +# default: +CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y +# default: +# CONFIG_LIBC_TIME_SYSCALL_USE_RTC is not set +# default: +# CONFIG_LIBC_TIME_SYSCALL_USE_HRT is not set +# default: +# CONFIG_LIBC_TIME_SYSCALL_USE_NONE is not set +# default: +CONFIG_LIBC_ASSERT_BUFFER_SIZE=200 +# end of LibC + +# default: +CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y + +# +# ESP-MM: Memory Management Configurations +# +# default: +# CONFIG_ESP_MM_CACHE_MSYNC_C2M_CHUNKED_OPS is not set +# end of ESP-MM: Memory Management Configurations + +# +# ESP NETIF Adapter +# +# default: +CONFIG_ESP_NETIF_LOST_IP_TIMER_ENABLE=y +# default: +CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 +# default: +# CONFIG_ESP_NETIF_PROVIDE_CUSTOM_IMPLEMENTATION is not set +# default: +CONFIG_ESP_NETIF_TCPIP_LWIP=y +# default: +# CONFIG_ESP_NETIF_LOOPBACK is not set +# default: +CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y +# default: +CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y +# default: +CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS=y +# default: +# CONFIG_ESP_NETIF_L2_TAP is not set +# default: +# CONFIG_ESP_NETIF_BRIDGE_EN is not set +# default: +# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set +# end of ESP NETIF Adapter + +# +# Partition API Configuration +# +# end of Partition API Configuration + +# +# PHY +# +# default: +CONFIG_ESP_PHY_ENABLED=y +# default: +CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y +# default: +# CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set +# default: +CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20 +# default: +CONFIG_ESP_PHY_MAX_TX_POWER=20 +# default: +# CONFIG_ESP_PHY_REDUCE_TX_POWER is not set +# default: +CONFIG_ESP_PHY_ENABLE_USB=y +# default: +# CONFIG_ESP_PHY_ENABLE_CERT_TEST is not set +# default: +CONFIG_ESP_PHY_RF_CAL_PARTIAL=y +# default: +# CONFIG_ESP_PHY_RF_CAL_NONE is not set +# default: +# CONFIG_ESP_PHY_RF_CAL_FULL is not set +# default: +CONFIG_ESP_PHY_CALIBRATION_MODE=0 +# default: +CONFIG_ESP_PHY_PLL_TRACK_PERIOD_MS=1000 +# default: +# CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set +# default: +# CONFIG_ESP_PHY_RECORD_USED_TIME is not set +# default: +CONFIG_ESP_PHY_IRAM_OPT=y +# default: +# CONFIG_ESP_PHY_DEBUG is not set +# end of PHY + +# +# Power Management +# +# default: +# CONFIG_PM_SLEEP_FUNC_IN_IRAM is not set +# default: +# CONFIG_PM_ENABLE is not set +# default: +# CONFIG_PM_SLP_IRAM_OPT is not set +# default: +CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y +# default: +CONFIG_PM_RESTORE_CACHE_TAGMEM_AFTER_LIGHT_SLEEP=y +# default: +CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU=y +# end of Power Management + +# +# ESP PSRAM +# +CONFIG_SPIRAM=y + +# +# SPI RAM config +# +# CONFIG_SPIRAM_MODE_QUAD is not set +CONFIG_SPIRAM_MODE_OCT=y +# default: +CONFIG_SPIRAM_TYPE_AUTO=y +# default: +# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set +# default: +CONFIG_SPIRAM_CLK_IO=30 +# default: +CONFIG_SPIRAM_CS_IO=26 +# default: +# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set +# default: +# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set +# default: +# CONFIG_SPIRAM_RODATA is not set +CONFIG_SPIRAM_SPEED_80M=y +# CONFIG_SPIRAM_SPEED_40M is not set +# default: +CONFIG_SPIRAM_SPEED=80 +# default: +# CONFIG_SPIRAM_ECC_ENABLE is not set +# default: +CONFIG_SPIRAM_BOOT_HW_INIT=y +# default: +CONFIG_SPIRAM_BOOT_INIT=y +# default: +CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y +# default: +# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set +# CONFIG_SPIRAM_USE_MEMMAP is not set +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +CONFIG_SPIRAM_USE_MALLOC=y +# default: +CONFIG_SPIRAM_MEMTEST=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +# default: +# CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP is not set +# default: +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 +# default: +# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set +# default: +# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set +# end of SPI RAM config +# end of ESP PSRAM + +# +# ESP Ringbuf +# +# default: +# CONFIG_RINGBUF_IN_IRAM is not set +# default: +# CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH is not set +# end of ESP Ringbuf + +# +# ESP-ROM +# +# default: +CONFIG_ESP_ROM_PRINT_IN_IRAM=y +# end of ESP-ROM + +# +# ESP Security Specific +# +# end of ESP Security Specific + +# +# ESP-STDIO +# +# default: +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +# default: +# CONFIG_ESP_CONSOLE_USB_CDC is not set +# default: +# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set +# default: +# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set +# default: +# CONFIG_ESP_CONSOLE_NONE is not set +# default: +# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set +# default: +CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y +# default: +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y +# default: +CONFIG_ESP_CONSOLE_UART=y +# default: +CONFIG_ESP_CONSOLE_UART_NUM=0 +# default: +CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0 +# default: +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +# end of ESP-STDIO + +# +# ESP System Settings +# +# default: +# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set +# default: +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y +# default: +# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 is not set +# default: +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160 + +# +# Cache config +# +# default: +CONFIG_ESP32S3_INSTRUCTION_CACHE_16KB=y +# default: +# CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB is not set +# default: +CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE=0x4000 +# default: +# CONFIG_ESP32S3_INSTRUCTION_CACHE_4WAYS is not set +# default: +CONFIG_ESP32S3_INSTRUCTION_CACHE_8WAYS=y +# default: +CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS=8 +# default: +# CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_16B is not set +# default: +CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_32B=y +# default: +CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE=32 +# default: +# CONFIG_ESP32S3_DATA_CACHE_16KB is not set +# default: +CONFIG_ESP32S3_DATA_CACHE_32KB=y +# default: +# CONFIG_ESP32S3_DATA_CACHE_64KB is not set +# default: +CONFIG_ESP32S3_DATA_CACHE_SIZE=0x8000 +# default: +# CONFIG_ESP32S3_DATA_CACHE_4WAYS is not set +# default: +CONFIG_ESP32S3_DATA_CACHE_8WAYS=y +# default: +CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS=8 +# default: +# CONFIG_ESP32S3_DATA_CACHE_LINE_16B is not set +# default: +CONFIG_ESP32S3_DATA_CACHE_LINE_32B=y +# default: +# CONFIG_ESP32S3_DATA_CACHE_LINE_64B is not set +# default: +CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE=32 +# end of Cache config + +# +# Memory +# +# default: +# CONFIG_ESP32S3_RTCDATA_IN_FAST_MEM is not set +# default: +# CONFIG_ESP32S3_USE_FIXED_STATIC_RAM_SIZE is not set +# end of Memory + +# +# Trace memory +# +# default: +# CONFIG_ESP32S3_TRAX is not set +# default: +CONFIG_ESP32S3_TRACEMEM_RESERVE_DRAM=0x0 +# end of Trace memory + +# default: +CONFIG_ESP_SYSTEM_IN_IRAM=y +# default: +# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set +# default: +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +# default: +# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set +# default: +# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set +# default: +CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 +# default: +CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y +# default: +CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y +# default: +CONFIG_ESP_SYSTEM_MEMPROT=y +# default: +CONFIG_ESP_SYSTEM_MEMPROT_PMS=y +# default: +CONFIG_ESP_SYSTEM_MEMPROT_PMS_LOCK=y +# default: +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +# default: +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=32768 +# default: +CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y +# default: +# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set +# default: +# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set +# default: +CONFIG_ESP_MAIN_TASK_AFFINITY=0x0 +# default: +CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 +# default: +CONFIG_ESP_INT_WDT=y +# default: +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +# default: +CONFIG_ESP_INT_WDT_CHECK_CPU1=y +# default: +CONFIG_ESP_TASK_WDT_EN=y +# default: +CONFIG_ESP_TASK_WDT_INIT=y +# default: +# CONFIG_ESP_TASK_WDT_PANIC is not set +# default: +CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +# default: +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +# default: +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# default: +# CONFIG_ESP_PANIC_HANDLER_IRAM is not set +# default: +# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set +# default: +CONFIG_ESP_DEBUG_OCDAWARE=y +# default: +CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y +# default: +CONFIG_ESP_SYSTEM_BBPLL_RECALIB=y +# end of ESP System Settings + +# +# IPC (Inter-Processor Call) +# +# default: +CONFIG_ESP_IPC_ENABLE=y +# default: +CONFIG_ESP_IPC_TASK_STACK_SIZE=1280 +# default: +CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y +# default: +CONFIG_ESP_IPC_ISR_ENABLE=y +# end of IPC (Inter-Processor Call) + +# +# ESP Timer (High Resolution Timer) +# +# default: +CONFIG_ESP_TIMER_IN_IRAM=y +# default: +# CONFIG_ESP_TIMER_PROFILING is not set +# default: +CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y +# default: +CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y +# default: +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 +# default: +CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1 +# default: +# CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL is not set +# default: +CONFIG_ESP_TIMER_TASK_AFFINITY=0x0 +# default: +CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y +# default: +CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y +# default: +# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set +# default: +CONFIG_ESP_TIMER_IMPL_SYSTIMER=y +# end of ESP Timer (High Resolution Timer) + +# +# ESP Trace Configuration +# +# default: +# CONFIG_ESP_TRACE_LIB_EXTERNAL is not set +# default: +CONFIG_ESP_TRACE_LIB_NONE=y +# default: +CONFIG_ESP_TRACE_LIB_NAME="none" +# default: +# CONFIG_ESP_TRACE_TRANSPORT_APPTRACE is not set +# default: +CONFIG_ESP_TRACE_TRANSPORT_NONE=y +# default: +CONFIG_ESP_TRACE_TRANSPORT_NAME="none" +# end of ESP Trace Configuration + +# +# Wi-Fi +# +# default: +CONFIG_ESP_WIFI_ENABLED=y +# default: +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10 +# default: +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +# default: +# CONFIG_ESP_WIFI_STATIC_TX_BUFFER is not set +# default: +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y +# default: +CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1 +# default: +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +# default: +CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER=y +# default: +# CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER is not set +# default: +CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0 +# default: +CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5 +# default: +# CONFIG_ESP_WIFI_CSI_ENABLED is not set +# default: +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y +# default: +CONFIG_ESP_WIFI_TX_BA_WIN=6 +# default: +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y +# default: +CONFIG_ESP_WIFI_RX_BA_WIN=6 +# default: +CONFIG_ESP_WIFI_NVS_ENABLED=y +# default: +CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y +# default: +# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1 is not set +# default: +CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752 +# default: +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 +# default: +CONFIG_ESP_WIFI_IRAM_OPT=y +# default: +# CONFIG_ESP_WIFI_EXTRA_IRAM_OPT is not set +# default: +CONFIG_ESP_WIFI_RX_IRAM_OPT=y +# default: +CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y +# default: +CONFIG_ESP_WIFI_ENABLE_SAE_H2E=y +# default: +CONFIG_ESP_WIFI_ENABLE_SAE_PK=y +# default: +CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y +# default: +CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=y +# default: +CONFIG_ESP_WIFI_WPA3_COMPATIBLE_SUPPORT=y +# default: +# CONFIG_ESP_WIFI_SLP_IRAM_OPT is not set +# default: +CONFIG_ESP_WIFI_SLP_DEFAULT_MIN_ACTIVE_TIME=50 +# default: +# CONFIG_ESP_WIFI_BSS_MAX_IDLE_SUPPORT is not set +# default: +CONFIG_ESP_WIFI_SLP_DEFAULT_MAX_ACTIVE_TIME=10 +# default: +CONFIG_ESP_WIFI_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 +# default: +# CONFIG_ESP_WIFI_FTM_ENABLE is not set +# default: +CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y +# default: +# CONFIG_ESP_WIFI_GCMP_SUPPORT is not set +# default: +CONFIG_ESP_WIFI_GMAC_SUPPORT=y +# default: +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y +# default: +# CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set +# default: +CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7 +# default: +CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y +# default: +CONFIG_ESP_WIFI_MBEDTLS_TLS_CLIENT=y +# default: +# CONFIG_ESP_WIFI_WAPI_PSK is not set +# default: +# CONFIG_ESP_WIFI_SUITE_B_192 is not set +# default: +# CONFIG_ESP_WIFI_11KV_SUPPORT is not set +# default: +# CONFIG_ESP_WIFI_MBO_SUPPORT is not set +# default: +# CONFIG_ESP_WIFI_DPP_SUPPORT is not set +# default: +# CONFIG_ESP_WIFI_11R_SUPPORT is not set +# default: +# CONFIG_ESP_WIFI_WPS_SOFTAP_REGISTRAR is not set + +# +# WPS Configuration Options +# +# default: +# CONFIG_ESP_WIFI_WPS_STRICT is not set +# default: +# CONFIG_ESP_WIFI_WPS_PASSPHRASE is not set +# default: +# CONFIG_ESP_WIFI_WPS_RECONNECT_ON_FAIL is not set +# end of WPS Configuration Options + +# default: +# CONFIG_ESP_WIFI_DEBUG_PRINT is not set +# default: +CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=y +# default: +# CONFIG_ESP_WIFI_ENT_FREE_DYNAMIC_BUFFER is not set +# default: +# CONFIG_ESP_WIFI_PASSIVE_HIDDEN_AP_SUPPORT is not set +# end of Wi-Fi + +# +# Core dump +# +# default: +# CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH is not set +# default: +# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set +# default: +CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y +# end of Core dump + +# +# FAT Filesystem support +# +# default: +CONFIG_FATFS_VOLUME_COUNT=2 +# default: +# CONFIG_FATFS_LFN_NONE is not set +# default: +CONFIG_FATFS_LFN_HEAP=y +# default: +# CONFIG_FATFS_LFN_STACK is not set +# default: +# CONFIG_FATFS_SECTOR_512 is not set +# default: +CONFIG_FATFS_SECTOR_4096=y +# default: +# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set +# default: +CONFIG_FATFS_CODEPAGE_437=y +# default: +# CONFIG_FATFS_CODEPAGE_720 is not set +# default: +# CONFIG_FATFS_CODEPAGE_737 is not set +# default: +# CONFIG_FATFS_CODEPAGE_771 is not set +# default: +# CONFIG_FATFS_CODEPAGE_775 is not set +# default: +# CONFIG_FATFS_CODEPAGE_850 is not set +# default: +# CONFIG_FATFS_CODEPAGE_852 is not set +# default: +# CONFIG_FATFS_CODEPAGE_855 is not set +# default: +# CONFIG_FATFS_CODEPAGE_857 is not set +# default: +# CONFIG_FATFS_CODEPAGE_860 is not set +# default: +# CONFIG_FATFS_CODEPAGE_861 is not set +# default: +# CONFIG_FATFS_CODEPAGE_862 is not set +# default: +# CONFIG_FATFS_CODEPAGE_863 is not set +# default: +# CONFIG_FATFS_CODEPAGE_864 is not set +# default: +# CONFIG_FATFS_CODEPAGE_865 is not set +# default: +# CONFIG_FATFS_CODEPAGE_866 is not set +# default: +# CONFIG_FATFS_CODEPAGE_869 is not set +# default: +# CONFIG_FATFS_CODEPAGE_932 is not set +# default: +# CONFIG_FATFS_CODEPAGE_936 is not set +# default: +# CONFIG_FATFS_CODEPAGE_949 is not set +# default: +# CONFIG_FATFS_CODEPAGE_950 is not set +# default: +CONFIG_FATFS_CODEPAGE=437 +# default: +CONFIG_FATFS_MAX_LFN=255 +# default: +CONFIG_FATFS_API_ENCODING_ANSI_OEM=y +# default: +# CONFIG_FATFS_API_ENCODING_UTF_8 is not set +# default: +CONFIG_FATFS_FS_LOCK=0 +# default: +CONFIG_FATFS_TIMEOUT_MS=10000 +# default: +CONFIG_FATFS_PER_FILE_CACHE=y +# default: +CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y +# default: +# CONFIG_FATFS_USE_FASTSEEK is not set +# default: +CONFIG_FATFS_USE_STRFUNC_NONE=y +# default: +# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set +# default: +# CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV is not set +# default: +CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0 +# default: +# CONFIG_FATFS_IMMEDIATE_FSYNC is not set +# default: +# CONFIG_FATFS_USE_LABEL is not set +# default: +CONFIG_FATFS_LINK_LOCK=y +# default: +CONFIG_FATFS_USE_DYN_BUFFERS=y + +# +# File system free space calculation behavior +# +# default: +CONFIG_FATFS_DONT_TRUST_FREE_CLUSTER_CNT=0 +# default: +CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0 +# end of File system free space calculation behavior +# end of FAT Filesystem support + +# +# FreeRTOS +# + +# +# Kernel +# +# default: +# CONFIG_FREERTOS_SMP is not set +# default: +# CONFIG_FREERTOS_UNICORE is not set +CONFIG_FREERTOS_HZ=1000 +# default: +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set +# default: +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set +# default: +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y +# default: +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 +# default: +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 +# default: +# CONFIG_FREERTOS_USE_IDLE_HOOK is not set +# default: +# CONFIG_FREERTOS_USE_TICK_HOOK is not set +# default: +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +# default: +# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set +# default: +CONFIG_FREERTOS_USE_TIMERS=y +# default: +CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc" +# default: +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set +# default: +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set +# default: +CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY=y +# default: +CONFIG_FREERTOS_TIMER_SERVICE_TASK_CORE_AFFINITY=0x7FFFFFFF +# default: +CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 +# default: +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 +# default: +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 +# default: +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +# default: +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 +# default: +# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set +# default: +# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set +# default: +# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set +# default: +# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set +# end of Kernel + +# +# Port +# +# default: +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y +# default: +# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set +# default: +CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y +# default: +# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set +# default: +CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y +# default: +CONFIG_FREERTOS_ISR_STACKSIZE=1536 +# default: +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y +# default: +# CONFIG_FREERTOS_FPU_IN_ISR is not set +# default: +CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y +# default: +CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y +# default: +# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set +# default: +CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y +# default: +# CONFIG_FREERTOS_IN_IRAM is not set +# default: +# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set +# end of Port + +# +# Extra +# +# default: +CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y +# end of Extra + +# default: +CONFIG_FREERTOS_PORT=y +# default: +CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF +# default: +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y +# default: +CONFIG_FREERTOS_DEBUG_OCDAWARE=y +# default: +CONFIG_FREERTOS_NUMBER_OF_CORES=2 +# end of FreeRTOS + +# +# Hardware Abstraction Layer (HAL) and Low Level (LL) +# +# default: +CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y +# default: +# CONFIG_HAL_ASSERTION_DISABLE is not set +# default: +# CONFIG_HAL_ASSERTION_SILENT is not set +# default: +# CONFIG_HAL_ASSERTION_ENABLE is not set +# default: +CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 +# default: +CONFIG_HAL_WDT_USE_ROM_IMPL=y +# default: +CONFIG_HAL_GPIO_USE_ROM_IMPL=y +# end of Hardware Abstraction Layer (HAL) and Low Level (LL) + +# +# Heap memory debugging +# +# default: +CONFIG_HEAP_POISONING_DISABLED=y +# default: +# CONFIG_HEAP_POISONING_LIGHT is not set +# default: +# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set +# default: +CONFIG_HEAP_TRACING_OFF=y +# default: +# CONFIG_HEAP_TRACING_STANDALONE is not set +# default: +# CONFIG_HEAP_TRACING_TOHOST is not set +# default: +# CONFIG_HEAP_USE_HOOKS is not set +# default: +# CONFIG_HEAP_TASK_TRACKING is not set +# default: +# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set +# default: +# CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set +# end of Heap memory debugging + +# +# Log +# +# default: +CONFIG_LOG_VERSION_1=y +# default: +# CONFIG_LOG_VERSION_2 is not set +# default: +CONFIG_LOG_VERSION=1 + +# +# Log Level +# +# default: +# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set +# default: +# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set +# default: +# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set +# default: +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +# default: +# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +# default: +# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set +# default: +CONFIG_LOG_DEFAULT_LEVEL=3 +# default: +CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y +# default: +# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set +# default: +# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set +# default: +CONFIG_LOG_MAXIMUM_LEVEL=3 + +# +# Level Settings +# +# default: +# CONFIG_LOG_MASTER_LEVEL is not set +# default: +CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y +# default: +# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set +# default: +# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set +# default: +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y +# default: +# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set +# default: +CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y +# default: +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31 +# end of Level Settings +# end of Log Level + +# +# Format +# +# default: +# CONFIG_LOG_COLORS is not set +# default: +CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y +# default: +# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set +# end of Format + +# +# Settings +# +# default: +CONFIG_LOG_MODE_TEXT_EN=y +# default: +CONFIG_LOG_MODE_TEXT=y +# end of Settings + +# default: +CONFIG_LOG_IN_IRAM=y +# end of Log + +# +# LWIP +# +# default: +CONFIG_LWIP_ENABLE=y +# default: +CONFIG_LWIP_LOCAL_HOSTNAME="espressif" +# default: +CONFIG_LWIP_TCPIP_TASK_PRIO=18 +# default: +# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set +# default: +# CONFIG_LWIP_CHECK_THREAD_SAFETY is not set +# default: +CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y +# default: +# CONFIG_LWIP_L2_TO_L3_COPY is not set +# default: +# CONFIG_LWIP_IRAM_OPTIMIZATION is not set +# default: +# CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION is not set +# default: +CONFIG_LWIP_TIMERS_ONDEMAND=y +# default: +CONFIG_LWIP_ND6=y +# default: +# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set +# default: +CONFIG_LWIP_MAX_SOCKETS=10 +# default: +# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set +# default: +# CONFIG_LWIP_SO_LINGER is not set +# default: +CONFIG_LWIP_SO_REUSE=y +# default: +CONFIG_LWIP_SO_REUSE_RXTOALL=y +# default: +# CONFIG_LWIP_SO_RCVBUF is not set +# default: +# CONFIG_LWIP_NETBUF_RECVINFO is not set +# default: +CONFIG_LWIP_IP_DEFAULT_TTL=64 +# default: +CONFIG_LWIP_IP4_FRAG=y +# default: +CONFIG_LWIP_IP6_FRAG=y +# default: +# CONFIG_LWIP_IP4_REASSEMBLY is not set +# default: +# CONFIG_LWIP_IP6_REASSEMBLY is not set +# default: +CONFIG_LWIP_IP_REASS_MAX_PBUFS=10 +# default: +CONFIG_LWIP_IPV6_DUP_DETECT_ATTEMPTS=1 +# default: +# CONFIG_LWIP_IP_FORWARD is not set +# default: +# CONFIG_LWIP_STATS is not set +# default: +CONFIG_LWIP_ESP_GRATUITOUS_ARP=y +# default: +CONFIG_LWIP_GARP_TMR_INTERVAL=60 +# default: +CONFIG_LWIP_ESP_MLDV6_REPORT=y +# default: +CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 +# default: +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +# default: +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +# default: +# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set +# default: +# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set +# default: +# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set +# default: +CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y +# default: +# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set +# default: +CONFIG_LWIP_DHCP_OPTIONS_LEN=69 +# default: +CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0 +# default: +CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 + +# +# DHCP server +# +# default: +CONFIG_LWIP_DHCPS=y +# default: +CONFIG_LWIP_DHCPS_REPORT_CLIENT_HOSTNAME=y +# default: +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +# default: +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +# default: +CONFIG_LWIP_DHCPS_MAX_HOSTNAME_LEN=64 +# default: +CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y +# end of DHCP server + +# default: +# CONFIG_LWIP_AUTOIP is not set +# default: +CONFIG_LWIP_IPV4=y +# default: +CONFIG_LWIP_IPV6=y +# default: +# CONFIG_LWIP_IPV6_AUTOCONFIG is not set +# default: +CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 +# default: +# CONFIG_LWIP_IPV6_FORWARD is not set +# default: +# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set +# default: +# CONFIG_LWIP_NETIF_LINK_CALLBACK is not set +# default: +CONFIG_LWIP_NETIF_LOOPBACK=y +# default: +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +# default: +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +# default: +CONFIG_LWIP_MAX_LISTENING_TCP=16 +# default: +CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y +# default: +CONFIG_LWIP_TCP_MAXRTX=12 +# default: +CONFIG_LWIP_TCP_SYNMAXRTX=12 +# default: +CONFIG_LWIP_TCP_MSS=1440 +# default: +CONFIG_LWIP_TCP_TMR_INTERVAL=250 +# default: +CONFIG_LWIP_TCP_MSL=60000 +# default: +CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT=20000 +# default: +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760 +# default: +CONFIG_LWIP_TCP_WND_DEFAULT=5760 +# default: +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +# default: +CONFIG_LWIP_TCP_ACCEPTMBOX_SIZE=6 +# default: +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +# default: +CONFIG_LWIP_TCP_OOSEQ_TIMEOUT=6 +# default: +CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=4 +# default: +# CONFIG_LWIP_TCP_SACK_OUT is not set +# default: +CONFIG_LWIP_TCP_OVERSIZE_MSS=y +# default: +# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set +# default: +# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set +# default: +CONFIG_LWIP_TCP_RTO_TIME=1500 +# end of TCP + +# +# UDP +# +# default: +CONFIG_LWIP_MAX_UDP_PCBS=16 +# default: +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +# end of UDP + +# +# Checksums +# +# default: +# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set +# default: +# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set +# default: +CONFIG_LWIP_CHECKSUM_CHECK_ICMP=y +# end of Checksums + +# default: +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 +# default: +CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# default: +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set +# default: +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set +# default: +CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF +# default: +CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 +# default: +CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 +# default: +CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5 +# default: +CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3 +# default: +CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10 +# default: +# CONFIG_LWIP_IPV6_ND6_ROUTE_INFO_OPTION_SUPPORT is not set +# default: +# CONFIG_LWIP_PPP_SUPPORT is not set +# default: +# CONFIG_LWIP_SLIP_SUPPORT is not set + +# +# ICMP +# +# default: +CONFIG_LWIP_ICMP=y +# default: +# CONFIG_LWIP_MULTICAST_PING is not set +# default: +# CONFIG_LWIP_BROADCAST_PING is not set +# end of ICMP + +# +# LWIP RAW API +# +# default: +CONFIG_LWIP_MAX_RAW_PCBS=16 +# end of LWIP RAW API + +# +# SNTP +# +# default: +CONFIG_LWIP_SNTP_MAX_SERVERS=1 +# default: +# CONFIG_LWIP_DHCP_GET_NTP_SRV is not set +# default: +CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000 +# default: +CONFIG_LWIP_SNTP_STARTUP_DELAY=y +# default: +CONFIG_LWIP_SNTP_MAXIMUM_STARTUP_DELAY=5000 +# end of SNTP + +# +# DNS +# +# default: +CONFIG_LWIP_DNS_MAX_HOST_IP=1 +# default: +CONFIG_LWIP_DNS_MAX_SERVERS=3 +# default: +# CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set +# default: +# CONFIG_LWIP_DNS_SETSERVER_WITH_NETIF is not set +# default: +# CONFIG_LWIP_USE_ESP_GETADDRINFO is not set +# end of DNS + +# default: +CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7 +# default: +CONFIG_LWIP_ESP_LWIP_ASSERT=y + +# +# Hooks +# +# default: +# CONFIG_LWIP_HOOK_TCP_ISN_NONE is not set +# default: +CONFIG_LWIP_HOOK_TCP_ISN_DEFAULT=y +# default: +# CONFIG_LWIP_HOOK_TCP_ISN_CUSTOM is not set +# default: +CONFIG_LWIP_HOOK_IP6_ROUTE_NONE=y +# default: +# CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT is not set +# default: +# CONFIG_LWIP_HOOK_IP6_ROUTE_CUSTOM is not set +# default: +CONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y +# default: +# CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT is not set +# default: +# CONFIG_LWIP_HOOK_ND6_GET_GW_CUSTOM is not set +# default: +CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_NONE=y +# default: +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_DEFAULT is not set +# default: +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM is not set +# default: +CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_NONE=y +# default: +# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_DEFAULT is not set +# default: +# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_CUSTOM is not set +# default: +CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y +# default: +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set +# default: +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set +# default: +CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_NONE=y +# default: +# CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_CUSTOM is not set +# default: +# CONFIG_LWIP_HOOK_IP6_INPUT_NONE is not set +# default: +CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y +# default: +# CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set +# end of Hooks + +# default: +# CONFIG_LWIP_DEBUG is not set +# end of LWIP + +# +# mbedTLS +# + +# +# Core Configuration +# +# default: +CONFIG_MBEDTLS_VER_4_X_SUPPORT=y +# default: +# CONFIG_MBEDTLS_COMPILER_OPTIMIZATION_NONE is not set +# default: +CONFIG_MBEDTLS_COMPILER_OPTIMIZATION_SIZE=y +# default: +# CONFIG_MBEDTLS_COMPILER_OPTIMIZATION_PERF is not set +# default: +CONFIG_MBEDTLS_FS_IO=y +# default: +CONFIG_MBEDTLS_THREADING_C=y +# default: +# CONFIG_MBEDTLS_THREADING_ALT is not set +# default: +CONFIG_MBEDTLS_THREADING_PTHREAD=y +# default: +CONFIG_MBEDTLS_ERROR_STRINGS=y +# default: +CONFIG_MBEDTLS_VERSION_C=y +# default: +CONFIG_MBEDTLS_HAVE_TIME=y +# default: +# CONFIG_MBEDTLS_PLATFORM_TIME_ALT is not set +# default: +# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set +# default: +CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y +# default: +# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set +# default: +# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set +# default: +# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set +# default: +CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y +# default: +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 +# default: +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 +# default: +# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set +# default: +# CONFIG_MBEDTLS_VERSION_FEATURES is not set +# default: +# CONFIG_MBEDTLS_DEBUG is not set +# default: +CONFIG_MBEDTLS_SELF_TEST=y +# end of Core Configuration + +# +# Certificates +# +# default: +CONFIG_MBEDTLS_X509_USE_C=y +# default: +CONFIG_MBEDTLS_PEM_PARSE_C=y +# default: +CONFIG_MBEDTLS_PEM_WRITE_C=y +# default: +CONFIG_MBEDTLS_PK_C=y +# default: +CONFIG_MBEDTLS_PK_PARSE_C=y +# default: +CONFIG_MBEDTLS_PK_WRITE_C=y +# default: +# CONFIG_MBEDTLS_X509_REMOVE_INFO is not set +# default: +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +# default: +CONFIG_MBEDTLS_X509_CRT_PARSE_C=y +# default: +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +# default: +# CONFIG_MBEDTLS_X509_CREATE_C is not set +# default: +CONFIG_MBEDTLS_X509_RSASSA_PSS_SUPPORT=y +# default: +# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set +# default: +CONFIG_MBEDTLS_ASN1_PARSE_C=y +# default: +CONFIG_MBEDTLS_ASN1_WRITE_C=y +# default: +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y + +# +# Certificate Bundle Configuration +# +# default: +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y +# default: +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set +# default: +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set +# default: +# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set +# default: +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST is not set +# default: +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 +# end of Certificate Bundle Configuration + +# default: +# CONFIG_MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION is not set +# default: +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY is not set +# end of Certificates + +# default: +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Protocol Configuration +# +# default: +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +# default: +# CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 is not set +# default: +# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set +# default: +CONFIG_MBEDTLS_TLS_SERVER=y +# default: +CONFIG_MBEDTLS_TLS_CLIENT=y +# default: +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +# default: +# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set +# default: +# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set +# default: +# CONFIG_MBEDTLS_TLS_DISABLED is not set +# default: +# CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE is not set +# default: +# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set +# default: +CONFIG_MBEDTLS_SSL_CACHE_C=y +# default: +CONFIG_MBEDTLS_SSL_ALL_ALERT_MESSAGES=y + +# +# TLS Key Exchange Configuration +# +# default: +# CONFIG_MBEDTLS_PSK_MODES is not set +# default: +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +# default: +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +# default: +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +# default: +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +# end of TLS Key Exchange Configuration + +# default: +CONFIG_MBEDTLS_SSL_SERVER_NAME_INDICATION=y +# default: +CONFIG_MBEDTLS_SSL_ALPN=y +# default: +CONFIG_MBEDTLS_SSL_MAX_FRAGMENT_LENGTH=y +# default: +# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set +# default: +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +# default: +CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y +# default: +CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y +# default: +# CONFIG_MBEDTLS_SSL_KEYING_MATERIAL_EXPORT is not set +# end of TLS Protocol Configuration + +# default: +# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set + +# +# Symmetric Ciphers +# +# default: +CONFIG_MBEDTLS_AES_C=y +# default: +# CONFIG_MBEDTLS_CAMELLIA_C is not set +# default: +# CONFIG_MBEDTLS_ARIA_C is not set +# default: +# CONFIG_MBEDTLS_DES_C is not set +# default: +CONFIG_MBEDTLS_CCM_C=y +# default: +CONFIG_MBEDTLS_CIPHER_MODE_CBC=y +# default: +CONFIG_MBEDTLS_CIPHER_MODE_CFB=y +# default: +CONFIG_MBEDTLS_CIPHER_MODE_CTR=y +# default: +CONFIG_MBEDTLS_CIPHER_MODE_OFB=y +# default: +CONFIG_MBEDTLS_CIPHER_MODE_XTS=y +# default: +CONFIG_MBEDTLS_GCM_C=y +# default: +# CONFIG_MBEDTLS_NIST_KW_C is not set +# default: +CONFIG_MBEDTLS_AES_ROM_TABLES=y +# default: +# CONFIG_MBEDTLS_AES_FEWER_TABLES is not set +# default: +# CONFIG_MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH is not set +# default: +CONFIG_MBEDTLS_CMAC_C=y +# end of Symmetric Ciphers + +# +# Asymmetric Ciphers +# +# default: +CONFIG_MBEDTLS_BIGNUM_C=y +# default: +CONFIG_MBEDTLS_RSA_C=y +# default: +CONFIG_MBEDTLS_ECP_C=y + +# +# Supported Curves +# +# default: +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +# default: +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +# default: +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +# default: +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +# default: +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +# default: +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +# default: +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +# default: +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +# end of Supported Curves + +# +# Elliptic Curve Ciphers Configuration +# +# default: +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y +# default: +# CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM is not set +# default: +# CONFIG_MBEDTLS_DHM_C is not set +# default: +CONFIG_MBEDTLS_ECDH_C=y +# default: +# CONFIG_MBEDTLS_ECJPAKE_C is not set +# default: +CONFIG_MBEDTLS_ECDSA_C=y +# default: +CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y +# default: +CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y +# default: +CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y +# default: +# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set +# end of Elliptic Curve Ciphers Configuration +# end of Asymmetric Ciphers + +# +# Hash functions +# +# default: +# CONFIG_MBEDTLS_RIPEMD160_C is not set +# default: +CONFIG_MBEDTLS_MD_C=y +# default: +CONFIG_MBEDTLS_MD5_C=y +# default: +CONFIG_MBEDTLS_SHA1_C=y +# default: +# CONFIG_MBEDTLS_SHA224_C is not set +# default: +CONFIG_MBEDTLS_SHA256_C=y +# default: +CONFIG_MBEDTLS_SHA384_C=y +# default: +CONFIG_MBEDTLS_SHA512_C=y +# default: +# CONFIG_MBEDTLS_SHA3_C is not set +# default: +CONFIG_MBEDTLS_ROM_MD5=y +# end of Hash functions + +# +# Hardware Acceleration +# +# default: +CONFIG_MBEDTLS_HARDWARE_SHA=y +# default: +CONFIG_MBEDTLS_HARDWARE_MPI=y +# default: +CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI=y +# default: +CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y +# default: +CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 +# default: +CONFIG_MBEDTLS_HARDWARE_AES=y +# default: +# CONFIG_MBEDTLS_AES_SOFT_FALLBACK is not set +# default: +CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y +# default: +CONFIG_MBEDTLS_AES_USE_INTERRUPT=y +# default: +CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 +# default: +CONFIG_MBEDTLS_AES_HW_SMALL_DATA_LEN_OPTIM=y +# default: +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set +# default: +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set +# default: +CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL=y +# end of Hardware Acceleration + +# +# Entropy and Random Number Generation +# +# default: +# CONFIG_MBEDTLS_ENTROPY_FORCE_SHA256 is not set +# default: +CONFIG_MBEDTLS_CTR_DRBG_C=y +# default: +CONFIG_MBEDTLS_HMAC_DRBG_C=y +# end of Entropy and Random Number Generation + +# +# Encoding/Decoding +# +# default: +CONFIG_MBEDTLS_BASE64_C=y +# default: +CONFIG_MBEDTLS_PKCS5_C=y +# default: +CONFIG_MBEDTLS_PKCS7_C=y +# default: +CONFIG_MBEDTLS_PKCS1_V15=y +# default: +CONFIG_MBEDTLS_PKCS1_V21=y +# end of Encoding/Decoding + +# +# Stream Cipher +# +# default: +# CONFIG_MBEDTLS_CHACHA20_C is not set +# end of Stream Cipher +# end of mbedTLS + +# +# NVS +# +# default: +# CONFIG_NVS_ENCRYPTION is not set +# default: +# CONFIG_NVS_ASSERT_ERROR_CHECK is not set +# default: +# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set +# default: +# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set +# default: +# CONFIG_NVS_BDL_STACK is not set +# default: +# CONFIG_NVS_FLASH_VERIFY_ERASE is not set +# end of NVS + +# +# OpenThread +# +# default: +# CONFIG_OPENTHREAD_ENABLED is not set + +# +# OpenThread Spinel +# +# default: +# CONFIG_OPENTHREAD_SPINEL_ONLY is not set +# end of OpenThread Spinel + +# default: +# CONFIG_OPENTHREAD_DEBUG is not set +# end of OpenThread + +# +# Protocomm +# +# default: +# CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0 is not set +# default: +# CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 is not set +# default: +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y +# default: +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y +# end of Protocomm + +# +# PThreads +# +# default: +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +# default: +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +# default: +CONFIG_PTHREAD_STACK_MIN=768 +# default: +CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y +# default: +# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set +# default: +# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set +# default: +CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 +# default: +CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" +# end of PThreads + +# +# SD Protocol Layer Configuration +# +# default: +CONFIG_SD_ENABLE_SDIO_SUPPORT=y +# end of SD Protocol Layer Configuration + +# +# MMU Config +# +# default: +CONFIG_MMU_PAGE_SIZE_64KB=y +# default: +CONFIG_MMU_PAGE_MODE="64KB" +# default: +CONFIG_MMU_PAGE_SIZE=0x10000 +# end of MMU Config + +# +# Main Flash configuration +# + +# +# SPI Flash behavior when brownout +# +# default: +CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y +# default: +CONFIG_SPI_FLASH_BROWNOUT_RESET=y +# end of SPI Flash behavior when brownout + +# +# Optional and Experimental Features (READ DOCS FIRST) +# + +# +# Features here require specific hardware (READ DOCS FIRST!) +# +# default: +# CONFIG_SPI_FLASH_HPM_ENA is not set +# default: +CONFIG_SPI_FLASH_HPM_AUTO=y +# default: +# CONFIG_SPI_FLASH_HPM_DIS is not set +# default: +CONFIG_SPI_FLASH_HPM_ON=y +# default: +CONFIG_SPI_FLASH_HPM_DC_AUTO=y +# default: +# CONFIG_SPI_FLASH_HPM_DC_DISABLE is not set +# default: +# CONFIG_SPI_FLASH_AUTO_SUSPEND is not set +# default: +CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 +# default: +# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set +# default: +# CONFIG_SPI_FLASH_FORCE_ENABLE_C6_H2_SUSPEND is not set +# default: +CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM=y +# end of Optional and Experimental Features (READ DOCS FIRST) +# end of Main Flash configuration + +# +# SPI Flash driver +# +# default: +# CONFIG_SPI_FLASH_VERIFY_WRITE is not set +# default: +# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +# default: +# CONFIG_SPI_FLASH_ROM_IMPL is not set +# default: +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y +# default: +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set +# default: +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set +# default: +# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set +# default: +CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y +# default: +CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 +# default: +CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 +# default: +CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 +# default: +# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set +# default: +# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set +# default: +# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set + +# +# Auto-detect flash chips +# +# default: +CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORT_ENABLED=y +# default: +CONFIG_SPI_FLASH_VENDOR_GD_SUPPORT_ENABLED=y +# default: +CONFIG_SPI_FLASH_VENDOR_ISSI_SUPPORT_ENABLED=y +# default: +CONFIG_SPI_FLASH_VENDOR_MXIC_SUPPORT_ENABLED=y +# default: +CONFIG_SPI_FLASH_VENDOR_WINBOND_SUPPORT_ENABLED=y +# default: +CONFIG_SPI_FLASH_VENDOR_BOYA_SUPPORT_ENABLED=y +# default: +CONFIG_SPI_FLASH_VENDOR_TH_SUPPORT_ENABLED=y +# default: +CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y +# default: +CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y +# default: +CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y +# default: +CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP=y +# default: +CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP=y +# default: +CONFIG_SPI_FLASH_SUPPORT_TH_CHIP=y +# default: +CONFIG_SPI_FLASH_SUPPORT_MXIC_OPI_CHIP=y +# end of Auto-detect flash chips + +# default: +CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y +# end of SPI Flash driver + +# +# SPIFFS Configuration +# +# default: +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +# default: +CONFIG_SPIFFS_CACHE=y +# default: +CONFIG_SPIFFS_CACHE_WR=y +# default: +# CONFIG_SPIFFS_CACHE_STATS is not set +# end of SPIFFS Cache Configuration + +# default: +CONFIG_SPIFFS_PAGE_CHECK=y +# default: +CONFIG_SPIFFS_GC_MAX_RUNS=10 +# default: +# CONFIG_SPIFFS_GC_STATS is not set +# default: +CONFIG_SPIFFS_PAGE_SIZE=256 +# default: +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +# default: +# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set +# default: +CONFIG_SPIFFS_USE_MAGIC=y +# default: +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y +# default: +CONFIG_SPIFFS_META_LENGTH=4 +# default: +CONFIG_SPIFFS_USE_MTIME=y + +# +# Debug Configuration +# +# default: +# CONFIG_SPIFFS_DBG is not set +# default: +# CONFIG_SPIFFS_API_DBG is not set +# default: +# CONFIG_SPIFFS_GC_DBG is not set +# default: +# CONFIG_SPIFFS_CACHE_DBG is not set +# default: +# CONFIG_SPIFFS_CHECK_DBG is not set +# default: +# CONFIG_SPIFFS_TEST_VISUALISATION is not set +# end of Debug Configuration +# end of SPIFFS Configuration + +# +# TCP Transport +# + +# +# Websocket +# +# default: +CONFIG_WS_TRANSPORT=y +# default: +CONFIG_WS_BUFFER_SIZE=1024 +# default: +# CONFIG_WS_DYNAMIC_BUFFER is not set +# end of Websocket +# end of TCP Transport + +# +# Ultra Low Power (ULP) Co-processor +# +# default: +# CONFIG_ULP_COPROC_ENABLED is not set + +# +# ULP Debugging Options +# +# end of ULP Debugging Options +# end of Ultra Low Power (ULP) Co-processor + +# +# Unity unit testing library +# +# default: +CONFIG_UNITY_ENABLE_FLOAT=y +# default: +CONFIG_UNITY_ENABLE_DOUBLE=y +# default: +# CONFIG_UNITY_ENABLE_64BIT is not set +# default: +# CONFIG_UNITY_ENABLE_COLOR is not set +# default: +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y +# default: +# CONFIG_UNITY_ENABLE_FIXTURE is not set +# default: +# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set +# end of Unity unit testing library + +# +# Virtual file system +# +# default: +CONFIG_VFS_SUPPORT_IO=y +# default: +CONFIG_VFS_SUPPORT_DIR=y +# default: +CONFIG_VFS_SUPPORT_SELECT=y +# default: +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +# default: +# CONFIG_VFS_SELECT_IN_RAM is not set +# default: +# CONFIG_VFS_SUPPORT_TERMIOS is not set +# default: +CONFIG_VFS_MAX_COUNT=8 +# default: +# CONFIG_VFS_SUPPRESS_CTX_DEPRECATION is not set + +# +# Host File System I/O (Semihosting) +# +# default: +CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# end of Host File System I/O (Semihosting) + +# default: +CONFIG_VFS_INITIALIZE_DEV_NULL=y +# end of Virtual file system + +# +# Wear Levelling +# +# default: +# CONFIG_WL_SECTOR_SIZE_512 is not set +# default: +CONFIG_WL_SECTOR_SIZE_4096=y +# default: +CONFIG_WL_SECTOR_SIZE=4096 +# end of Wear Levelling + +# +# ESP LVGL PORT +# +# end of ESP LVGL PORT + +# +# LVGL configuration +# +# default: +CONFIG_LV_CONF_SKIP=y +# default: +# CONFIG_LV_CONF_MINIMAL is not set + +# +# Color Settings +# +# default: +# CONFIG_LV_COLOR_DEPTH_32 is not set +# default: +# CONFIG_LV_COLOR_DEPTH_24 is not set +# default: +CONFIG_LV_COLOR_DEPTH_16=y +# default: +# CONFIG_LV_COLOR_DEPTH_8 is not set +# default: +# CONFIG_LV_COLOR_DEPTH_1 is not set +# default: +CONFIG_LV_COLOR_DEPTH=16 +# end of Color Settings + +# +# Memory Settings +# +# default: +CONFIG_LV_USE_BUILTIN_MALLOC=y +# default: +# CONFIG_LV_USE_CLIB_MALLOC is not set +# default: +# CONFIG_LV_USE_MICROPYTHON_MALLOC is not set +# default: +# CONFIG_LV_USE_RTTHREAD_MALLOC is not set +# default: +# CONFIG_LV_USE_CUSTOM_MALLOC is not set +# default: +CONFIG_LV_USE_BUILTIN_STRING=y +# default: +# CONFIG_LV_USE_CLIB_STRING is not set +# default: +# CONFIG_LV_USE_CUSTOM_STRING is not set +# default: +CONFIG_LV_USE_BUILTIN_SPRINTF=y +# default: +# CONFIG_LV_USE_CLIB_SPRINTF is not set +# default: +# CONFIG_LV_USE_CUSTOM_SPRINTF is not set +# default: +CONFIG_LV_MEM_SIZE_KILOBYTES=64 +# default: +CONFIG_LV_MEM_POOL_EXPAND_SIZE_KILOBYTES=0 +# default: +CONFIG_LV_MEM_ADR=0x0 +# end of Memory Settings + +# +# HAL Settings +# +# default: +CONFIG_LV_DEF_REFR_PERIOD=33 +# default: +CONFIG_LV_DPI_DEF=130 +# end of HAL Settings + +# +# Operating System (OS) +# +# default: +CONFIG_LV_OS_NONE=y +# default: +# CONFIG_LV_OS_PTHREAD is not set +# default: +# CONFIG_LV_OS_FREERTOS is not set +# default: +# CONFIG_LV_OS_CMSIS_RTOS2 is not set +# default: +# CONFIG_LV_OS_RTTHREAD is not set +# default: +# CONFIG_LV_OS_WINDOWS is not set +# default: +# CONFIG_LV_OS_MQX is not set +# default: +# CONFIG_LV_OS_CUSTOM is not set +# end of Operating System (OS) + +# +# Rendering Configuration +# +# default: +CONFIG_LV_DRAW_BUF_STRIDE_ALIGN=1 +# default: +CONFIG_LV_DRAW_BUF_ALIGN=4 +# default: +CONFIG_LV_DRAW_LAYER_SIMPLE_BUF_SIZE=24576 +# default: +CONFIG_LV_DRAW_LAYER_MAX_MEMORY=0 +# default: +CONFIG_LV_DRAW_THREAD_STACK_SIZE=8192 +# default: +CONFIG_LV_DRAW_THREAD_PRIO=3 +# default: +CONFIG_LV_USE_DRAW_SW=y +# default: +CONFIG_LV_DRAW_SW_SUPPORT_RGB565=y +# default: +CONFIG_LV_DRAW_SW_SUPPORT_RGB565A8=y +# default: +CONFIG_LV_DRAW_SW_SUPPORT_RGB888=y +# default: +CONFIG_LV_DRAW_SW_SUPPORT_XRGB8888=y +# default: +CONFIG_LV_DRAW_SW_SUPPORT_ARGB8888=y +# default: +CONFIG_LV_DRAW_SW_SUPPORT_L8=y +# default: +CONFIG_LV_DRAW_SW_SUPPORT_AL88=y +# default: +CONFIG_LV_DRAW_SW_SUPPORT_A8=y +# default: +CONFIG_LV_DRAW_SW_SUPPORT_I1=y +# default: +CONFIG_LV_DRAW_SW_I1_LUM_THRESHOLD=127 +# default: +CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=1 +# default: +# CONFIG_LV_USE_DRAW_ARM2D_SYNC is not set +# default: +# CONFIG_LV_USE_NATIVE_HELIUM_ASM is not set +# default: +CONFIG_LV_DRAW_SW_COMPLEX=y +# default: +# CONFIG_LV_USE_DRAW_SW_COMPLEX_GRADIENTS is not set +# default: +CONFIG_LV_DRAW_SW_SHADOW_CACHE_SIZE=0 +# default: +CONFIG_LV_DRAW_SW_CIRCLE_CACHE_SIZE=4 +# default: +CONFIG_LV_DRAW_SW_ASM_NONE=y +# default: +# CONFIG_LV_DRAW_SW_ASM_NEON is not set +# default: +# CONFIG_LV_DRAW_SW_ASM_HELIUM is not set +# default: +# CONFIG_LV_DRAW_SW_ASM_CUSTOM is not set +# default: +CONFIG_LV_USE_DRAW_SW_ASM=0 +# default: +# CONFIG_LV_USE_DRAW_VGLITE is not set +# default: +# CONFIG_LV_USE_PXP is not set +# default: +# CONFIG_LV_USE_DRAW_G2D is not set +# default: +# CONFIG_LV_USE_DRAW_DAVE2D is not set +# default: +# CONFIG_LV_USE_DRAW_SDL is not set +# default: +# CONFIG_LV_USE_DRAW_VG_LITE is not set +# default: +# CONFIG_LV_USE_VECTOR_GRAPHIC is not set +# default: +# CONFIG_LV_USE_DRAW_DMA2D is not set +# end of Rendering Configuration + +# +# Feature Configuration +# + +# +# Logging +# +# default: +# CONFIG_LV_USE_LOG is not set +# end of Logging + +# +# Asserts +# +# default: +CONFIG_LV_USE_ASSERT_NULL=y +# default: +CONFIG_LV_USE_ASSERT_MALLOC=y +# default: +# CONFIG_LV_USE_ASSERT_STYLE is not set +# default: +# CONFIG_LV_USE_ASSERT_MEM_INTEGRITY is not set +# default: +# CONFIG_LV_USE_ASSERT_OBJ is not set +# default: +CONFIG_LV_ASSERT_HANDLER_INCLUDE="assert.h" +# end of Asserts + +# +# Debug +# +# default: +# CONFIG_LV_USE_REFR_DEBUG is not set +# default: +# CONFIG_LV_USE_LAYER_DEBUG is not set +# default: +# CONFIG_LV_USE_PARALLEL_DRAW_DEBUG is not set +# end of Debug + +# +# Others +# +# default: +# CONFIG_LV_ENABLE_GLOBAL_CUSTOM is not set +# default: +CONFIG_LV_CACHE_DEF_SIZE=0 +# default: +CONFIG_LV_IMAGE_HEADER_CACHE_DEF_CNT=0 +# default: +CONFIG_LV_GRADIENT_MAX_STOPS=2 +# default: +CONFIG_LV_COLOR_MIX_ROUND_OFS=128 +# default: +# CONFIG_LV_OBJ_STYLE_CACHE is not set +# default: +# CONFIG_LV_USE_OBJ_ID is not set +# default: +# CONFIG_LV_USE_OBJ_NAME is not set +# default: +# CONFIG_LV_USE_OBJ_PROPERTY is not set +# end of Others +# end of Feature Configuration + +# +# Compiler Settings +# +# default: +# CONFIG_LV_BIG_ENDIAN_SYSTEM is not set +# default: +CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1 +# default: +# CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM is not set +# default: +# CONFIG_LV_USE_FLOAT is not set +# default: +# CONFIG_LV_USE_MATRIX is not set +# default: +# CONFIG_LV_USE_PRIVATE_API is not set +# end of Compiler Settings + +# +# Font Usage +# + +# +# Enable built-in fonts +# +# default: +# CONFIG_LV_FONT_MONTSERRAT_8 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_10 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_12 is not set +CONFIG_LV_FONT_MONTSERRAT_14=y +# default: +# CONFIG_LV_FONT_MONTSERRAT_16 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_18 is not set +CONFIG_LV_FONT_MONTSERRAT_20=y +# default: +# CONFIG_LV_FONT_MONTSERRAT_22 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_24 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_26 is not set +CONFIG_LV_FONT_MONTSERRAT_28=y +# default: +# CONFIG_LV_FONT_MONTSERRAT_30 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_32 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_34 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_36 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_38 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_40 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_42 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_44 is not set +# default: +# CONFIG_LV_FONT_MONTSERRAT_46 is not set +CONFIG_LV_FONT_MONTSERRAT_48=y +# default: +# CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set +# default: +# CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set +# default: +# CONFIG_LV_FONT_SIMSUN_14_CJK is not set +# default: +# CONFIG_LV_FONT_SIMSUN_16_CJK is not set +# default: +# CONFIG_LV_FONT_SOURCE_HAN_SANS_SC_14_CJK is not set +# default: +# CONFIG_LV_FONT_SOURCE_HAN_SANS_SC_16_CJK is not set +# default: +# CONFIG_LV_FONT_UNSCII_8 is not set +# default: +# CONFIG_LV_FONT_UNSCII_16 is not set +# end of Enable built-in fonts + +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_10 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set +# default: +CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_22 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_24 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_26 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_30 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_32 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_34 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_36 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_38 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_40 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_42 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_44 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_46 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_48 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set +# default: +# CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set +# default: +# CONFIG_LV_FONT_DEFAULT_SIMSUN_14_CJK is not set +# default: +# CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK is not set +# default: +# CONFIG_LV_FONT_DEFAULT_SOURCE_HAN_SANS_SC_14_CJK is not set +# default: +# CONFIG_LV_FONT_DEFAULT_SOURCE_HAN_SANS_SC_16_CJK is not set +# default: +# CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set +# default: +# CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set +# default: +# CONFIG_LV_FONT_FMT_TXT_LARGE is not set +# default: +# CONFIG_LV_USE_FONT_COMPRESSED is not set +# default: +CONFIG_LV_USE_FONT_PLACEHOLDER=y + +# +# Enable static fonts +# +# end of Enable static fonts +# end of Font Usage + +# +# Text Settings +# +# default: +CONFIG_LV_TXT_ENC_UTF8=y +# default: +# CONFIG_LV_TXT_ENC_ASCII is not set +# default: +CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_)}" +# default: +CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0 +# default: +CONFIG_LV_TXT_COLOR_CMD="#" +# default: +# CONFIG_LV_USE_BIDI is not set +# default: +# CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set +# end of Text Settings + +# +# Widget Usage +# +# default: +CONFIG_LV_WIDGETS_HAS_DEFAULT_VALUE=y +# default: +CONFIG_LV_USE_ANIMIMG=y +# default: +CONFIG_LV_USE_ARC=y +# default: +CONFIG_LV_USE_BAR=y +# default: +CONFIG_LV_USE_BUTTON=y +# default: +CONFIG_LV_USE_BUTTONMATRIX=y +# default: +CONFIG_LV_USE_CALENDAR=y +# default: +# CONFIG_LV_CALENDAR_WEEK_STARTS_MONDAY is not set + +# +# Days name configuration +# +# default: +CONFIG_LV_MONDAY_STR="Mo" +# default: +CONFIG_LV_TUESDAY_STR="Tu" +# default: +CONFIG_LV_WEDNESDAY_STR="We" +# default: +CONFIG_LV_THURSDAY_STR="Th" +# default: +CONFIG_LV_FRIDAY_STR="Fr" +# default: +CONFIG_LV_SATURDAY_STR="Sa" +# default: +CONFIG_LV_SUNDAY_STR="Su" +# end of Days name configuration + +# default: +CONFIG_LV_USE_CALENDAR_HEADER_ARROW=y +# default: +CONFIG_LV_USE_CALENDAR_HEADER_DROPDOWN=y +# default: +# CONFIG_LV_USE_CALENDAR_CHINESE is not set +# default: +CONFIG_LV_USE_CANVAS=y +# default: +CONFIG_LV_USE_CHART=y +# default: +CONFIG_LV_USE_CHECKBOX=y +# default: +CONFIG_LV_USE_DROPDOWN=y +# default: +CONFIG_LV_USE_IMAGE=y +# default: +CONFIG_LV_USE_IMAGEBUTTON=y +# default: +CONFIG_LV_USE_KEYBOARD=y +# default: +CONFIG_LV_USE_LABEL=y +# default: +CONFIG_LV_LABEL_TEXT_SELECTION=y +# default: +CONFIG_LV_LABEL_LONG_TXT_HINT=y +# default: +CONFIG_LV_LABEL_WAIT_CHAR_COUNT=3 +# default: +CONFIG_LV_USE_LED=y +# default: +CONFIG_LV_USE_LINE=y +# default: +CONFIG_LV_USE_LIST=y +# default: +CONFIG_LV_USE_MENU=y +# default: +CONFIG_LV_USE_MSGBOX=y +# default: +CONFIG_LV_USE_ROLLER=y +# default: +CONFIG_LV_USE_SCALE=y +# default: +CONFIG_LV_USE_SLIDER=y +# default: +CONFIG_LV_USE_SPAN=y +# default: +CONFIG_LV_SPAN_SNIPPET_STACK_SIZE=64 +# default: +CONFIG_LV_USE_SPINBOX=y +# default: +CONFIG_LV_USE_SPINNER=y +# default: +CONFIG_LV_USE_SWITCH=y +# default: +CONFIG_LV_USE_TEXTAREA=y +# default: +CONFIG_LV_TEXTAREA_DEF_PWD_SHOW_TIME=1500 +# default: +CONFIG_LV_USE_TABLE=y +# default: +CONFIG_LV_USE_TABVIEW=y +# default: +CONFIG_LV_USE_TILEVIEW=y +# default: +CONFIG_LV_USE_WIN=y +# end of Widget Usage + +# +# Themes +# +# default: +CONFIG_LV_USE_THEME_DEFAULT=y +# default: +# CONFIG_LV_THEME_DEFAULT_DARK is not set +# default: +CONFIG_LV_THEME_DEFAULT_GROW=y +# default: +CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=80 +# default: +CONFIG_LV_USE_THEME_SIMPLE=y +# default: +# CONFIG_LV_USE_THEME_MONO is not set +# end of Themes + +# +# Layouts +# +# default: +CONFIG_LV_USE_FLEX=y +# default: +CONFIG_LV_USE_GRID=y +# end of Layouts + +# +# 3rd Party Libraries +# +# default: +CONFIG_LV_FS_DEFAULT_DRIVER_LETTER=0 +# default: +# CONFIG_LV_USE_FS_STDIO is not set +# default: +# CONFIG_LV_USE_FS_POSIX is not set +# default: +# CONFIG_LV_USE_FS_WIN32 is not set +# default: +# CONFIG_LV_USE_FS_FATFS is not set +# default: +# CONFIG_LV_USE_FS_MEMFS is not set +# default: +# CONFIG_LV_USE_FS_LITTLEFS is not set +# default: +# CONFIG_LV_USE_FS_ARDUINO_ESP_LITTLEFS is not set +# default: +# CONFIG_LV_USE_FS_ARDUINO_SD is not set +# default: +# CONFIG_LV_USE_FS_UEFI is not set +# default: +# CONFIG_LV_USE_LODEPNG is not set +# default: +# CONFIG_LV_USE_LIBPNG is not set +# default: +# CONFIG_LV_USE_BMP is not set +# default: +# CONFIG_LV_USE_TJPGD is not set +# default: +# CONFIG_LV_USE_LIBJPEG_TURBO is not set +# default: +# CONFIG_LV_USE_GIF is not set +# default: +# CONFIG_LV_BIN_DECODER_RAM_LOAD is not set +# default: +# CONFIG_LV_USE_RLE is not set +# default: +# CONFIG_LV_USE_QRCODE is not set +# default: +# CONFIG_LV_USE_BARCODE is not set +# default: +# CONFIG_LV_USE_FREETYPE is not set +# default: +# CONFIG_LV_USE_TINY_TTF is not set +# default: +# CONFIG_LV_USE_RLOTTIE is not set +# default: +# CONFIG_LV_USE_THORVG is not set +# default: +# CONFIG_LV_USE_LZ4 is not set +# default: +# CONFIG_LV_USE_FFMPEG is not set +# end of 3rd Party Libraries + +# +# Others +# +# default: +# CONFIG_LV_USE_SNAPSHOT is not set +# default: +# CONFIG_LV_USE_SYSMON is not set +# default: +# CONFIG_LV_USE_PROFILER is not set +# default: +# CONFIG_LV_USE_MONKEY is not set +# default: +# CONFIG_LV_USE_GRIDNAV is not set +# default: +# CONFIG_LV_USE_FRAGMENT is not set +# default: +# CONFIG_LV_USE_IMGFONT is not set +# default: +CONFIG_LV_USE_OBSERVER=y +# default: +# CONFIG_LV_USE_IME_PINYIN is not set +# default: +# CONFIG_LV_USE_FILE_EXPLORER is not set +# default: +# CONFIG_LV_USE_FONT_MANAGER is not set +# default: +# CONFIG_LV_USE_TEST is not set +# default: +# CONFIG_LV_USE_XML is not set +# default: +# CONFIG_LV_USE_COLOR_FILTER is not set +# default: +CONFIG_LVGL_VERSION_MAJOR=9 +# default: +CONFIG_LVGL_VERSION_MINOR=3 +# default: +CONFIG_LVGL_VERSION_PATCH=0 +# end of Others + +# +# Devices +# +# default: +# CONFIG_LV_USE_SDL is not set +# default: +# CONFIG_LV_USE_X11 is not set +# default: +# CONFIG_LV_USE_WAYLAND is not set +# default: +# CONFIG_LV_USE_LINUX_FBDEV is not set +# default: +# CONFIG_LV_USE_NUTTX is not set +# default: +# CONFIG_LV_USE_LINUX_DRM is not set +# default: +# CONFIG_LV_USE_TFT_ESPI is not set +# default: +# CONFIG_LV_USE_EVDEV is not set +# default: +# CONFIG_LV_USE_LIBINPUT is not set +# default: +# CONFIG_LV_USE_ST7735 is not set +# default: +# CONFIG_LV_USE_ST7789 is not set +# default: +# CONFIG_LV_USE_ST7796 is not set +# default: +# CONFIG_LV_USE_ILI9341 is not set +# default: +# CONFIG_LV_USE_GENERIC_MIPI is not set +# default: +# CONFIG_LV_USE_RENESAS_GLCDC is not set +# default: +# CONFIG_LV_USE_ST_LTDC is not set +# default: +# CONFIG_LV_USE_FT81X is not set +# default: +# CONFIG_LV_USE_UEFI is not set +# default: +# CONFIG_LV_USE_OPENGLES is not set +# default: +# CONFIG_LV_USE_QNX is not set +# end of Devices + +# +# Examples +# +# default: +CONFIG_LV_BUILD_EXAMPLES=y +# end of Examples + +# +# Demos +# +# default: +CONFIG_LV_BUILD_DEMOS=y +# default: +# CONFIG_LV_USE_DEMO_WIDGETS is not set +# default: +# CONFIG_LV_USE_DEMO_KEYPAD_AND_ENCODER is not set +# default: +# CONFIG_LV_USE_DEMO_BENCHMARK is not set +# default: +# CONFIG_LV_USE_DEMO_RENDER is not set +# default: +# CONFIG_LV_USE_DEMO_SCROLL is not set +# default: +# CONFIG_LV_USE_DEMO_STRESS is not set +# default: +# CONFIG_LV_USE_DEMO_MUSIC is not set +# default: +# CONFIG_LV_USE_DEMO_FLEX_LAYOUT is not set +# default: +# CONFIG_LV_USE_DEMO_MULTILANG is not set +# default: +# CONFIG_LV_USE_DEMO_SMARTWATCH is not set +# default: +# CONFIG_LV_USE_DEMO_EBIKE is not set +# default: +# CONFIG_LV_USE_DEMO_HIGH_RES is not set +# end of Demos +# end of LVGL configuration +# end of Component config + +# default: +# CONFIG_IDF_EXPERIMENTAL_FEATURES is not set + +# Deprecated options for backward compatibility +# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set +# CONFIG_NO_BLOBS is not set +# CONFIG_APP_ROLLBACK_ENABLE is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set +CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y +# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL=3 +# CONFIG_FLASH_ENCRYPTION_ENABLED is not set +# CONFIG_FLASHMODE_QIO is not set +# CONFIG_FLASHMODE_QOUT is not set +CONFIG_FLASHMODE_DIO=y +# CONFIG_FLASHMODE_DOUT is not set +CONFIG_MONITOR_BAUD=115200 +CONFIG_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y +# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set +CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y +# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set +CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_CXX_EXCEPTIONS is not set +CONFIG_STACK_CHECK_NONE=y +# CONFIG_STACK_CHECK_NORM is not set +# CONFIG_STACK_CHECK_STRONG is not set +# CONFIG_STACK_CHECK_ALL is not set +# CONFIG_WARN_WRITE_STRINGS is not set +# CONFIG_BLUEDROID_ENABLED is not set +CONFIG_NIMBLE_ENABLED=y +CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# CONFIG_NIMBLE_MEM_ALLOC_MODE_EXTERNAL is not set +# CONFIG_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set +CONFIG_NIMBLE_PINNED_TO_CORE=0 +CONFIG_NIMBLE_PINNED_TO_CORE_0=y +# CONFIG_NIMBLE_PINNED_TO_CORE_1 is not set +CONFIG_NIMBLE_TASK_STACK_SIZE=4096 +CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096 +# CONFIG_NIMBLE_ROLE_CENTRAL is not set +CONFIG_NIMBLE_ROLE_PERIPHERAL=y +CONFIG_NIMBLE_ROLE_BROADCASTER=y +# CONFIG_NIMBLE_ROLE_OBSERVER is not set +CONFIG_NIMBLE_SM_LEGACY=y +CONFIG_NIMBLE_SM_SC=y +# CONFIG_NIMBLE_SM_SC_DEBUG_KEYS is not set +CONFIG_BT_NIMBLE_SM_SC_LVL=0 +# CONFIG_NIMBLE_NVS_PERSIST is not set +CONFIG_NIMBLE_MAX_BONDS=1 +CONFIG_NIMBLE_RPA_TIMEOUT=900 +CONFIG_NIMBLE_MAX_CONNECTIONS=1 +CONFIG_NIMBLE_MAX_CCCDS=1 +# CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS is not set +# CONFIG_NIMBLE_HS_FLOW_CTRL is not set +CONFIG_NIMBLE_ATT_PREFERRED_MTU=185 +CONFIG_NIMBLE_L2CAP_COC_MAX_NUM=0 +CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_ACL_BUF_COUNT=24 +CONFIG_BT_NIMBLE_ACL_BUF_SIZE=255 +CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70 +CONFIG_BT_NIMBLE_HCI_EVT_HI_BUF_COUNT=30 +CONFIG_BT_NIMBLE_HCI_EVT_LO_BUF_COUNT=8 +CONFIG_NIMBLE_SVC_GAP_DEVICE_NAME="nimble" +CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31 +CONFIG_NIMBLE_SVC_GAP_APPEARANCE=0x0 +# CONFIG_NIMBLE_MESH is not set +# CONFIG_NIMBLE_DEBUG is not set +# CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_EN is not set +CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_DIS=y +CONFIG_SW_COEXIST_ENABLE=y +CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y +CONFIG_ESP_WIFI_SW_COEXIST_ENABLE=y +# CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE is not set +# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set +# CONFIG_MCPWM_ISR_IRAM_SAFE is not set +# CONFIG_EVENT_LOOP_PROFILING is not set +CONFIG_POST_EVENTS_FROM_ISR=y +CONFIG_POST_EVENTS_FROM_IRAM_ISR=y +CONFIG_GDBSTUB_SUPPORT_TASKS=y +CONFIG_GDBSTUB_MAX_TASKS=32 +# CONFIG_OTA_ALLOW_HTTP is not set +CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_ESP32S3_RTC_CLK_CAL_CYCLES=1024 +CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y +CONFIG_BROWNOUT_DET=y +CONFIG_ESP32S3_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_7=y +CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_1 is not set +CONFIG_BROWNOUT_DET_LVL=7 +CONFIG_ESP32S3_BROWNOUT_DET_LVL=7 +CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y +CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_SYSTIMER=y +CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_FRC1=y +# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_SYSTIMER is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_FRC1 is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_NONE is not set +CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y +# CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set +CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP32_PHY_MAX_TX_POWER=20 +# CONFIG_REDUCE_PHY_TX_POWER is not set +# CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set +CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y +CONFIG_PM_POWER_DOWN_TAGMEM_IN_LIGHT_SLEEP=y +CONFIG_ESP32S3_SPIRAM_SUPPORT=y +CONFIG_DEFAULT_PSRAM_CLK_IO=30 +CONFIG_DEFAULT_PSRAM_CS_IO=26 +CONFIG_CONSOLE_UART_DEFAULT=y +# CONFIG_CONSOLE_UART_CUSTOM is not set +# CONFIG_CONSOLE_UART_NONE is not set +# CONFIG_ESP_CONSOLE_UART_NONE is not set +CONFIG_CONSOLE_UART=y +CONFIG_CONSOLE_UART_NUM=0 +CONFIG_CONSOLE_UART_BAUDRATE=115200 +# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_80 is not set +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160=y +# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240 is not set +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=160 +CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT=y +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_VIA_TEE=y +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=y +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y +CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_MAIN_TASK_STACK_SIZE=32768 +CONFIG_INT_WDT=y +CONFIG_INT_WDT_TIMEOUT_MS=300 +CONFIG_INT_WDT_CHECK_CPU1=y +CONFIG_TASK_WDT=y +CONFIG_ESP_TASK_WDT=y +# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_TIMEOUT_S=5 +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP32S3_DEBUG_OCDAWARE=y +CONFIG_IPC_TASK_STACK_SIZE=1280 +CONFIG_TIMER_TASK_STACK_SIZE=3584 +# CONFIG_ESP32_APPTRACE_ENABLE is not set +CONFIG_ESP32_WIFI_ENABLED=y +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +# CONFIG_ESP32_WIFI_CSI_ENABLED is not set +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y +# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_WPA_MBEDTLS_CRYPTO=y +CONFIG_WPA_MBEDTLS_TLS_CLIENT=y +# CONFIG_WPA_WAPI_PSK is not set +# CONFIG_WPA_SUITE_B_192 is not set +# CONFIG_WPA_11KV_SUPPORT is not set +# CONFIG_WPA_MBO_SUPPORT is not set +# CONFIG_WPA_DPP_SUPPORT is not set +# CONFIG_WPA_11R_SUPPORT is not set +# CONFIG_WPA_WPS_SOFTAP_REGISTRAR is not set +# CONFIG_WPA_WPS_STRICT is not set +# CONFIG_WPA_DEBUG_PRINT is not set +# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set +# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set +CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y +CONFIG_TIMER_TASK_PRIORITY=1 +CONFIG_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_TIMER_QUEUE_LENGTH=10 +# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set +CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y +# CONFIG_HAL_ASSERTION_SILIENT is not set +# CONFIG_L2_TO_L3_COPY is not set +CONFIG_ESP_GRATUITOUS_ARP=y +CONFIG_GARP_TMR_INTERVAL=60 +CONFIG_TCPIP_RECVMBOX_SIZE=32 +CONFIG_TCP_MAXRTX=12 +CONFIG_TCP_SYNMAXRTX=12 +CONFIG_TCP_MSS=1440 +CONFIG_TCP_MSL=60000 +CONFIG_TCP_SND_BUF_DEFAULT=5760 +CONFIG_TCP_WND_DEFAULT=5760 +CONFIG_TCP_RECVMBOX_SIZE=6 +CONFIG_TCP_QUEUE_OOSEQ=y +CONFIG_TCP_OVERSIZE_MSS=y +# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_TCP_OVERSIZE_DISABLE is not set +CONFIG_UDP_RECVMBOX_SIZE=6 +CONFIG_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF +# CONFIG_PPP_SUPPORT is not set +CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_ESP32_PTHREAD_STACK_MIN=768 +CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set +CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set +CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y +# CONFIG_SUPPORT_TERMIOS is not set +CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# End of deprecated options diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..525938d --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,66 @@ +# ============================================================ +# sdkconfig.defaults - 板级默认配置 +# 对应 Waveshare ESP32-S3-RLCD-4.2 (ESP32-S3-WROOM-1-N16R8) +# 16MB Flash / 8MB PSRAM (Octal) +# ============================================================ + +# --- 目标芯片 --- +CONFIG_IDF_TARGET="esp32s3" + +# --- 主任务栈:LVGL 渲染需要大量栈,默认 4KB 不够,调到 16KB --- +CONFIG_ESP_MAIN_TASK_STACK_SIZE=32768 + +# --- 编译优化:用 -O0 而不是 -Og,规避 esp_lcd_panel_rgb.c 触发的 gcc ICE --- +# CONFIG_COMPILER_OPTIMIZATION_NONE is not set +CONFIG_COMPILER_OPTIMIZATION_SIZE=n +CONFIG_COMPILER_OPTIMIZATION_PERF=n +CONFIG_COMPILER_OPTIMIZATION_DEBUG=y +CONFIG_COMPILER_WARN_WRITE_STRINGS=n + +# --- Flash 配置 (16MB) --- +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHSIZE="16MB" + +# --- Partition table: HTTP/AP firmware is larger than the 1MB default app slot --- +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" + +# --- PSRAM 配置 (8MB Octal PSRAM) --- +# N16R8 模块用的是 Octal PSRAM(R8 = Octal, R2 = Quad) +# 注意 ESP-IDF v6.0 里选项名是 SPIRAM_MODE_OCT(不是 _OCTAL) +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +# CONFIG_SPIRAM_MODE_QUAD is not set +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 + +# --- FreeRTOS --- +CONFIG_FREERTOS_HZ=1000 + +# --- LVGL 字体(开大字号给时钟数字用) --- +CONFIG_LV_FONT_MONTSERRAT_14=y +CONFIG_LV_FONT_MONTSERRAT_20=y +CONFIG_LV_FONT_MONTSERRAT_28=y +CONFIG_LV_FONT_MONTSERRAT_48=y + +# --- 关闭不需要的功能(减小固件体积) --- +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 +CONFIG_BT_NIMBLE_MAX_BONDS=1 +CONFIG_BT_NIMBLE_MAX_CCCDS=1 +CONFIG_BT_NIMBLE_ROLE_CENTRAL=n +CONFIG_BT_NIMBLE_ROLE_OBSERVER=n +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=185 +CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=4096 +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_2M_PHY=y +CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_CODED_PHY=y +# CONFIG_BT_NIMBLE_EXT_ADV is not set diff --git a/tools/pc-metrics/push-metrics-ble.py b/tools/pc-metrics/push-metrics-ble.py new file mode 100644 index 0000000..50b9adb --- /dev/null +++ b/tools/pc-metrics/push-metrics-ble.py @@ -0,0 +1,786 @@ +import argparse +import asyncio +import ctypes +from datetime import date, timedelta +import json +import os +from pathlib import Path +import queue +import subprocess +import sys +import threading +import time +import urllib.error +import urllib.request +from ctypes import wintypes + +import psutil +from bleak import BleakClient, BleakScanner + + +DEFAULT_DEVICE_NAME = "RLCD-Metrics" +DEFAULT_CHAR_UUID = "9f4af548-1a14-4fc1-8f5a-4b9f00000002" +MAX_DISK_ROWS = 4 +MAX_DISK_TEXT = 124 +CODEX_DAILY_BAR_COUNT = 14 +CODEX_REFRESH_SECONDS = 60.0 +CODEX_USAGE_REFRESH_SECONDS = 300.0 +ZHIPU_REFRESH_SECONDS = 60.0 + + +def clamp_percent(value: float) -> int: + if value != value: + return 0 + return max(0, min(100, int(round(value)))) + + +def mb(value: float) -> int: + return int(round(value / 1024 / 1024)) + + +def clamp_kbps(value: float) -> int: + return max(0, min(999999, int(round(value)))) + + +class _PdhCounterValue(ctypes.Structure): + _fields_ = [ + ("status", wintypes.LONG), + ("double_value", ctypes.c_double), + ] + + +class CpuPercentReader: + def __init__(self) -> None: + self.pdh = None + self.query = ctypes.c_void_p() + self.counter = ctypes.c_void_p() + self.available = False + + if sys.platform != "win32": + return + + try: + self.pdh = ctypes.WinDLL("pdh") + self.pdh.PdhOpenQueryW.restype = wintypes.LONG + self.pdh.PdhOpenQueryW.argtypes = [ + wintypes.LPCWSTR, + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_void_p), + ] + self.pdh.PdhCollectQueryData.restype = wintypes.LONG + self.pdh.PdhCollectQueryData.argtypes = [ctypes.c_void_p] + self.pdh.PdhGetFormattedCounterValue.restype = wintypes.LONG + self.pdh.PdhGetFormattedCounterValue.argtypes = [ + ctypes.c_void_p, + wintypes.DWORD, + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(_PdhCounterValue), + ] + + add_counter = getattr(self.pdh, "PdhAddEnglishCounterW", self.pdh.PdhAddCounterW) + add_counter.restype = wintypes.LONG + add_counter.argtypes = [ + ctypes.c_void_p, + wintypes.LPCWSTR, + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_void_p), + ] + + if self.pdh.PdhOpenQueryW(None, None, ctypes.byref(self.query)) != 0: + return + if add_counter( + self.query, + r"\Processor Information(_Total)\% Processor Utility", + None, + ctypes.byref(self.counter), + ) != 0: + return + + self.pdh.PdhCollectQueryData(self.query) + self.available = True + except OSError: + self.available = False + + def read_percent(self) -> int: + if self.available and self.pdh is not None: + value = _PdhCounterValue() + counter_type = wintypes.DWORD() + if self.pdh.PdhCollectQueryData(self.query) == 0: + rc = self.pdh.PdhGetFormattedCounterValue( + self.counter, + 0x00000200, + ctypes.byref(counter_type), + ctypes.byref(value), + ) + if rc == 0 and value.status == 0 and value.double_value >= 0: + return clamp_percent(value.double_value) + + return clamp_percent(psutil.cpu_percent(interval=None)) + + +class CpuFrequencyReader: + def __init__(self) -> None: + self.pdh = None + self.query = ctypes.c_void_p() + self.counter = ctypes.c_void_p() + self.available = False + + if sys.platform != "win32": + return + + try: + self.pdh = ctypes.WinDLL("pdh") + self.pdh.PdhOpenQueryW.restype = wintypes.LONG + self.pdh.PdhOpenQueryW.argtypes = [ + wintypes.LPCWSTR, + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_void_p), + ] + self.pdh.PdhCollectQueryData.restype = wintypes.LONG + self.pdh.PdhCollectQueryData.argtypes = [ctypes.c_void_p] + self.pdh.PdhGetFormattedCounterValue.restype = wintypes.LONG + self.pdh.PdhGetFormattedCounterValue.argtypes = [ + ctypes.c_void_p, + wintypes.DWORD, + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(_PdhCounterValue), + ] + + add_counter = getattr(self.pdh, "PdhAddEnglishCounterW", self.pdh.PdhAddCounterW) + add_counter.restype = wintypes.LONG + add_counter.argtypes = [ + ctypes.c_void_p, + wintypes.LPCWSTR, + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_void_p), + ] + + if self.pdh.PdhOpenQueryW(None, None, ctypes.byref(self.query)) != 0: + return + if add_counter( + self.query, + r"\Processor Information(_Total)\Actual Frequency", + None, + ctypes.byref(self.counter), + ) != 0: + return + + self.pdh.PdhCollectQueryData(self.query) + self.available = True + except OSError: + self.available = False + + def read_mhz(self) -> int: + if self.available and self.pdh is not None: + value = _PdhCounterValue() + counter_type = wintypes.DWORD() + if self.pdh.PdhCollectQueryData(self.query) == 0: + rc = self.pdh.PdhGetFormattedCounterValue( + self.counter, + 0x00000200, + ctypes.byref(counter_type), + ctypes.byref(value), + ) + if rc == 0 and value.status == 0 and value.double_value > 0: + return int(round(value.double_value)) + + freq = psutil.cpu_freq() + return int(round(freq.current)) if freq and freq.current else 0 + + +class LogicalDiskRateReader: + def __init__(self, labels: list[str]) -> None: + self.pdh = None + self.query = ctypes.c_void_p() + self.counters: dict[str, tuple[ctypes.c_void_p, ctypes.c_void_p]] = {} + self.available = False + + if sys.platform != "win32" or not labels: + return + + try: + self.pdh = ctypes.WinDLL("pdh") + self.pdh.PdhOpenQueryW.restype = wintypes.LONG + self.pdh.PdhOpenQueryW.argtypes = [ + wintypes.LPCWSTR, + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_void_p), + ] + self.pdh.PdhCollectQueryData.restype = wintypes.LONG + self.pdh.PdhCollectQueryData.argtypes = [ctypes.c_void_p] + self.pdh.PdhGetFormattedCounterValue.restype = wintypes.LONG + self.pdh.PdhGetFormattedCounterValue.argtypes = [ + ctypes.c_void_p, + wintypes.DWORD, + ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(_PdhCounterValue), + ] + + add_counter = getattr(self.pdh, "PdhAddEnglishCounterW", self.pdh.PdhAddCounterW) + add_counter.restype = wintypes.LONG + add_counter.argtypes = [ + ctypes.c_void_p, + wintypes.LPCWSTR, + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_void_p), + ] + + if self.pdh.PdhOpenQueryW(None, None, ctypes.byref(self.query)) != 0: + return + + for label in labels: + read_counter = ctypes.c_void_p() + write_counter = ctypes.c_void_p() + read_path = rf"\LogicalDisk({label}:)\Disk Read Bytes/sec" + write_path = rf"\LogicalDisk({label}:)\Disk Write Bytes/sec" + if add_counter(self.query, read_path, None, ctypes.byref(read_counter)) != 0: + continue + if add_counter(self.query, write_path, None, ctypes.byref(write_counter)) != 0: + continue + self.counters[label] = (read_counter, write_counter) + + if self.counters: + self.pdh.PdhCollectQueryData(self.query) + self.available = True + except OSError: + self.available = False + + def _read_kbps(self, counter: ctypes.c_void_p) -> int: + if self.pdh is None: + return -1 + + value = _PdhCounterValue() + counter_type = wintypes.DWORD() + rc = self.pdh.PdhGetFormattedCounterValue( + counter, + 0x00000200, + ctypes.byref(counter_type), + ctypes.byref(value), + ) + if rc != 0 or value.status != 0 or value.double_value < 0: + return -1 + return clamp_kbps(value.double_value / 1024) + + def read_rates(self) -> dict[str, tuple[int, int]]: + if not self.available or self.pdh is None: + return {} + + if self.pdh.PdhCollectQueryData(self.query) != 0: + return {} + + rates: dict[str, tuple[int, int]] = {} + for label, (read_counter, write_counter) in self.counters.items(): + rates[label] = (self._read_kbps(read_counter), self._read_kbps(write_counter)) + return rates + + +class CodexRateLimitReader: + def __init__(self, refresh_seconds: float = CODEX_REFRESH_SECONDS) -> None: + self.refresh_seconds = refresh_seconds + self.next_refresh = 0.0 + self.cached = (-1, -1, -1, -1, -1) + self.usage_refresh_seconds = CODEX_USAGE_REFRESH_SECONDS + self.next_usage_refresh = 0.0 + self.usage_cached = (-1, "") + self.process: subprocess.Popen | None = None + self.responses: queue.Queue[dict] = queue.Queue() + self.next_id = 1 + self.initialized = False + + def _find_codex_exe(self) -> str | None: + local_app_data = os.environ.get("LOCALAPPDATA") + if local_app_data: + root = Path(local_app_data) / "OpenAI" / "Codex" / "bin" + candidates = sorted(root.glob("*/codex.exe"), key=lambda p: p.stat().st_mtime, reverse=True) + if candidates: + return str(candidates[0]) + return "codex.exe" + + def _reader_loop(self, stdout) -> None: + for line in stdout: + line = line.strip() + if not line: + continue + try: + self.responses.put(json.loads(line)) + except json.JSONDecodeError: + continue + + def _stop(self) -> None: + if self.process is not None: + try: + self.process.kill() + except OSError: + pass + self.process = None + self.initialized = False + + def _send(self, method: str, params: dict | None = None, expect_response: bool = True) -> int | None: + if self.process is None or self.process.stdin is None: + return None + + request_id = None + message: dict = {"method": method} + if params is not None: + message["params"] = params + if expect_response: + request_id = self.next_id + self.next_id += 1 + message["id"] = request_id + + self.process.stdin.write(json.dumps(message, separators=(",", ":")) + "\n") + self.process.stdin.flush() + return request_id + + def _wait_for(self, request_id: int, timeout: float) -> dict | None: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + try: + message = self.responses.get(timeout=max(0.05, deadline - time.monotonic())) + except queue.Empty: + return None + if message.get("id") == request_id: + return message + return None + + def _ensure_started(self) -> bool: + if self.process is not None and self.process.poll() is None and self.initialized: + return True + + self._stop() + codex_exe = self._find_codex_exe() + if not codex_exe: + return False + + creationflags = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 + try: + self.process = subprocess.Popen( + [codex_exe, "app-server", "--stdio"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True, + encoding="utf-8", + bufsize=1, + creationflags=creationflags, + ) + except OSError: + self.process = None + return False + + assert self.process.stdout is not None + threading.Thread(target=self._reader_loop, args=(self.process.stdout,), daemon=True).start() + + init_id = self._send( + "initialize", + { + "clientInfo": { + "name": "rlcd_metrics_bridge", + "title": "RLCD Metrics Bridge", + "version": "0.1.0", + } + }, + ) + if init_id is None or self._wait_for(init_id, 5.0) is None: + self._stop() + return False + + self._send("initialized", {}, expect_response=False) + self.initialized = True + return True + + @staticmethod + def _mins_until(epoch_seconds) -> int: + if not isinstance(epoch_seconds, (int, float)): + return -1 + return max(0, min(99999, int(round((epoch_seconds - time.time()) / 60)))) + + @staticmethod + def _read_percent(value) -> int: + if not isinstance(value, (int, float)): + return -1 + return clamp_percent(value) + + def read(self) -> tuple[int, int, int, int, int]: + now = time.monotonic() + if now < self.next_refresh: + return self.cached + + self.next_refresh = now + self.refresh_seconds + if not self._ensure_started(): + return self.cached + + request_id = self._send("account/rateLimits/read") + if request_id is None: + return self.cached + + message = self._wait_for(request_id, 8.0) + result = message.get("result") if message else None + rate_limits = result.get("rateLimits") if isinstance(result, dict) else None + if not isinstance(rate_limits, dict): + self._stop() + return self.cached + + primary = rate_limits.get("primary") or {} + secondary = rate_limits.get("secondary") or {} + reset_credits = result.get("rateLimitResetCredits") or {} + + self.cached = ( + self._read_percent(primary.get("usedPercent")), + self._read_percent(secondary.get("usedPercent")), + self._mins_until(primary.get("resetsAt")), + self._mins_until(secondary.get("resetsAt")), + int(reset_credits.get("availableCount", -1)) if isinstance(reset_credits, dict) else -1, + ) + return self.cached + + @staticmethod + def _tokens_to_m10(tokens) -> int: + if not isinstance(tokens, (int, float)) or tokens < 0: + return -1 + return max(0, min(2147483647, int(round(tokens / 100000)))) + + @staticmethod + def _daily_bars_from_buckets(buckets: list[dict]) -> tuple[int, str]: + today = date.today() + wanted_days = [(today - timedelta(days=offset)).isoformat() for offset in range(CODEX_DAILY_BAR_COUNT - 1, -1, -1)] + tokens_by_day: dict[str, int] = {} + + for bucket in buckets: + if not isinstance(bucket, dict): + continue + start_date = bucket.get("startDate") + tokens = bucket.get("tokens") + if isinstance(start_date, str) and isinstance(tokens, (int, float)) and tokens >= 0: + tokens_by_day[start_date] = int(tokens) + + values = [tokens_by_day.get(day, 0) for day in wanted_days] + max_value = max(values) if values else 0 + if max_value > 0: + percents = [max(0, min(100, int(round(value * 100 / max_value)))) for value in values] + else: + percents = [0 for _ in values] + + today_tokens_m10 = CodexRateLimitReader._tokens_to_m10(tokens_by_day.get(today.isoformat(), -1)) + return today_tokens_m10, ":".join(str(value) for value in percents) + + def read_usage_snapshot(self) -> tuple[int, str]: + now = time.monotonic() + if now < self.next_usage_refresh: + return self.usage_cached + + self.next_usage_refresh = now + self.usage_refresh_seconds + if not self._ensure_started(): + return self.usage_cached + + request_id = self._send("account/usage/read") + if request_id is None: + return self.usage_cached + + message = self._wait_for(request_id, 8.0) + result = message.get("result") if message else None + buckets = result.get("dailyUsageBuckets") if isinstance(result, dict) else None + if not isinstance(buckets, list) or not buckets: + return self.usage_cached + + self.usage_cached = self._daily_bars_from_buckets(buckets) + return self.usage_cached + + +class ZhipuStatusReader: + def __init__(self, refresh_seconds: float = ZHIPU_REFRESH_SECONDS) -> None: + self.refresh_seconds = refresh_seconds + self.next_refresh = 0.0 + self.cached = (0, -1, -1, -1, -1) + + @staticmethod + def _quota_url() -> str: + base_url = os.environ.get("ZHIPU_BASE_URL", "https://open.bigmodel.cn").strip() + if "bigmodel.cn" in base_url.lower(): + host = "https://open.bigmodel.cn" + else: + host = "https://api.z.ai" + return f"{host}/api/monitor/usage/quota/limit" + + @staticmethod + def _mins_until(epoch_ms) -> int: + if not isinstance(epoch_ms, (int, float)) or epoch_ms <= 0: + return -1 + return max(0, min(99999, int(round((epoch_ms / 1000 - time.time()) / 60)))) + + @staticmethod + def _read_percent(value) -> int: + if isinstance(value, (int, float)): + return clamp_percent(value) + try: + return clamp_percent(float(value)) + except (TypeError, ValueError): + return -1 + + def _query(self, api_key: str) -> tuple[int, int, int, int, int]: + request = urllib.request.Request( + self._quota_url(), + headers={ + "Authorization": api_key, + "Content-Type": "application/json", + "Accept-Language": "en-US,en", + }, + method="GET", + ) + + try: + with urllib.request.urlopen(request, timeout=15) as response: + body = response.read().decode("utf-8", errors="replace") + except urllib.error.HTTPError as exc: + if exc.code in (401, 403): + return (2, -1, -1, -1, -1) + return (2, -1, -1, -1, -1) + except OSError: + return (2, -1, -1, -1, -1) + + try: + payload = json.loads(body) + except json.JSONDecodeError: + return (2, -1, -1, -1, -1) + + if payload.get("success") is False: + return (2, -1, -1, -1, -1) + + data = payload.get("data") + limits = data.get("limits") if isinstance(data, dict) else None + if not isinstance(limits, list): + return (2, -1, -1, -1, -1) + + five_percent = -1 + week_percent = -1 + five_reset = -1 + week_reset = -1 + + for item in limits: + if not isinstance(item, dict): + continue + if str(item.get("type", "")).upper() != "TOKENS_LIMIT": + continue + unit = item.get("unit") + percent = self._read_percent(item.get("percentage")) + if percent < 0: + continue + reset_mins = self._mins_until(item.get("nextResetTime")) + if unit == 3 and five_percent < 0: + five_percent = percent + five_reset = reset_mins + elif unit == 6 and week_percent < 0: + week_percent = percent + week_reset = reset_mins + + return (1, five_percent, week_percent, five_reset, week_reset) + + def read(self) -> tuple[int, int, int, int, int]: + api_key = os.environ.get("ZHIPU_API_KEY", "").strip() + if not api_key: + self.cached = (0, -1, -1, -1, -1) + self.next_refresh = time.monotonic() + self.refresh_seconds + return self.cached + + now = time.monotonic() + if now < self.next_refresh: + return self.cached + + self.next_refresh = now + self.refresh_seconds + self.cached = self._query(api_key) + return self.cached + + +class MetricsSampler: + def __init__(self, disk_path: str) -> None: + self.disk_path = disk_path + self.last_net = psutil.net_io_counters() + self.last_disk = psutil.disk_io_counters() + self.last_ts = time.monotonic() + self.cpu_percent = CpuPercentReader() + self.cpu_frequency = CpuFrequencyReader() + self.disk_rates = LogicalDiskRateReader(self._disk_labels()) + self.codex = CodexRateLimitReader() + self.zhipu = ZhipuStatusReader() + psutil.cpu_percent(interval=None) + + def _disk_labels(self) -> list[str]: + labels = [] + seen = set() + + for part in psutil.disk_partitions(all=False): + device = (part.device or part.mountpoint or "").upper() + if not device or device in seen: + continue + seen.add(device) + + opts = (part.opts or "").lower() + if "cdrom" in opts or not part.fstype: + continue + + label = device.replace("\\", "").replace("/", "") + if label.endswith(":"): + label = label[:-1] + if label: + labels.append(label) + + return labels + + def _disk_summary(self, rates_by_label: dict[str, tuple[int, int]]) -> tuple[int, int, str]: + total = 0 + used = 0 + parts = [] + seen = set() + + for part in psutil.disk_partitions(all=False): + device = (part.device or part.mountpoint or "").upper() + if not device or device in seen: + continue + seen.add(device) + + opts = (part.opts or "").lower() + if "cdrom" in opts or not part.fstype: + continue + + try: + usage = psutil.disk_usage(part.mountpoint) + except OSError: + continue + + total += usage.total + used += usage.used + label = device.replace("\\", "").replace("/", "") + if label.endswith(":"): + label = label[:-1] + read_kbps, write_kbps = rates_by_label.get(label, (-1, -1)) + parts.append(f"{label}:{clamp_percent(usage.percent)}:{read_kbps}:{write_kbps}") + + if total <= 0: + try: + usage = psutil.disk_usage(self.disk_path) + return 1, clamp_percent(usage.percent), f"C:{clamp_percent(usage.percent)}:-1:-1" + except OSError: + return 0, 0, "" + + disk_percent = clamp_percent((used / total) * 100) + selected = [] + text_len = 0 + for part in parts[:MAX_DISK_ROWS]: + extra = len(part) + (1 if selected else 0) + if text_len + extra > MAX_DISK_TEXT: + break + selected.append(part) + text_len += extra + text = " ".join(selected) + return len(parts), disk_percent, text + + def sample_line(self) -> str: + now = time.monotonic() + elapsed = max(0.001, now - self.last_ts) + + net = psutil.net_io_counters() + rx_kbps = int(max(0, round((net.bytes_recv - self.last_net.bytes_recv) / elapsed / 1024))) + tx_kbps = int(max(0, round((net.bytes_sent - self.last_net.bytes_sent) / elapsed / 1024))) + self.last_net = net + + disk_io = psutil.disk_io_counters() + if disk_io is None or self.last_disk is None: + disk_read_kbps = 0 + disk_write_kbps = 0 + else: + disk_read_kbps = int(max(0, round((disk_io.read_bytes - self.last_disk.read_bytes) / elapsed / 1024))) + disk_write_kbps = int(max(0, round((disk_io.write_bytes - self.last_disk.write_bytes) / elapsed / 1024))) + self.last_disk = disk_io + self.last_ts = now + disk_rates = self.disk_rates.read_rates() + + cpu = self.cpu_percent.read_percent() + cpu_mhz = self.cpu_frequency.read_mhz() + + mem_info = psutil.virtual_memory() + mem_percent = clamp_percent(mem_info.percent) + mem_used_mb = mb(mem_info.used) + mem_total_mb = mb(mem_info.total) + + disk_count, disk_percent, disk_text = self._disk_summary(disk_rates) + codex_5h, codex_week, codex_5h_reset, codex_week_reset, codex_credits = self.codex.read() + codex_today_tokens_m10, codex_daily_bars = self.codex.read_usage_snapshot() + zhipu_status, zhipu_5h, zhipu_week, zhipu_5h_reset, zhipu_week_reset = self.zhipu.read() + + return ( + f"SYS6,{cpu},{cpu_mhz},{mem_percent},{mem_used_mb},{mem_total_mb}," + f"{disk_count},{disk_percent},{disk_read_kbps},{disk_write_kbps}," + f"{rx_kbps},{tx_kbps}," + f"{codex_5h},{codex_week},{codex_5h_reset},{codex_week_reset},{codex_credits},{codex_today_tokens_m10}," + f"{codex_daily_bars}," + f"{zhipu_status},{zhipu_5h},{zhipu_week},{zhipu_5h_reset},{zhipu_week_reset},{disk_text}" + ) + + +async def find_device(name: str, address: str | None, timeout: float): + if address: + return await BleakScanner.find_device_by_address(address, timeout=timeout) + + def match(device, adv): + return device.name == name or adv.local_name == name + + return await BleakScanner.find_device_by_filter(match, timeout=timeout) + + +async def push_loop(args) -> int: + sampler = MetricsSampler(args.disk) + + while True: + print(f"Scanning for {args.name}...", flush=True) + device = await find_device(args.name, args.address, args.scan_timeout) + if device is None: + print(f"Device not found: {args.name}", flush=True) + if args.once: + return 2 + await asyncio.sleep(args.retry_seconds) + continue + + print(f"Connecting to {device.name or args.name} ({device.address})...", flush=True) + try: + async with BleakClient(device, timeout=args.connect_timeout) as client: + print("BLE connected. Streaming metrics.", flush=True) + while True: + line = sampler.sample_line() + await client.write_gatt_char(args.char_uuid, line.encode("ascii"), response=True) + print(line, flush=True) + if args.once: + return 0 + await asyncio.sleep(args.interval) + except KeyboardInterrupt: + return 130 + except Exception as exc: + print(f"BLE push failed: {exc}", file=sys.stderr, flush=True) + if args.once: + return 3 + await asyncio.sleep(args.retry_seconds) + + +def parse_args(): + parser = argparse.ArgumentParser(description="Push PC metrics to ESP32 RLCD over BLE.") + parser.add_argument("--name", default=DEFAULT_DEVICE_NAME) + parser.add_argument("--address", default=None) + parser.add_argument("--char-uuid", default=DEFAULT_CHAR_UUID) + parser.add_argument("--interval", type=float, default=1.0) + parser.add_argument("--scan-timeout", type=float, default=20.0) + parser.add_argument("--connect-timeout", type=float, default=20.0) + parser.add_argument("--retry-seconds", type=float, default=5.0) + parser.add_argument("--disk", default="C:\\") + parser.add_argument("--once", action="store_true") + return parser.parse_args() + + +def main() -> int: + args = parse_args() + try: + return asyncio.run(push_loop(args)) + except KeyboardInterrupt: + return 130 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/pc-metrics/push-metrics-http.ps1 b/tools/pc-metrics/push-metrics-http.ps1 new file mode 100644 index 0000000..6730bd1 --- /dev/null +++ b/tools/pc-metrics/push-metrics-http.ps1 @@ -0,0 +1,100 @@ +param( + [string]$HostName = "192.168.4.1", + [int]$Port = 80, + [double]$IntervalSeconds = 1 +) + +$ErrorActionPreference = "Stop" + +function Get-Percent { + param([double]$Value) + if ([double]::IsNaN($Value) -or [double]::IsInfinity($Value)) { + return 0 + } + $rounded = [math]::Round($Value) + if ($rounded -lt 0) { return 0 } + if ($rounded -gt 100) { return 100 } + return [int]$rounded +} + +function Get-SystemMetricLine { + $cpu = Get-CimInstance Win32_PerfFormattedData_PerfOS_Processor | + Where-Object { $_.Name -eq "_Total" } | + Select-Object -First 1 + + $os = Get-CimInstance Win32_OperatingSystem + $totalMemory = [double]$os.TotalVisibleMemorySize + $freeMemory = [double]$os.FreePhysicalMemory + $memoryPercent = if ($totalMemory -gt 0) { + (($totalMemory - $freeMemory) / $totalMemory) * 100 + } else { + 0 + } + + $disk = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" + $diskPercent = if ($disk -and [double]$disk.Size -gt 0) { + (([double]$disk.Size - [double]$disk.FreeSpace) / [double]$disk.Size) * 100 + } else { + 0 + } + + $network = Get-CimInstance Win32_PerfFormattedData_Tcpip_NetworkInterface | + Where-Object { + $_.Name -notmatch "Loopback|isatap|Teredo" -and + ([double]$_.BytesReceivedPersec -gt 0 -or [double]$_.BytesSentPersec -gt 0) + } + + $rxBytes = 0.0 + $txBytes = 0.0 + foreach ($adapter in $network) { + $rxBytes += [double]$adapter.BytesReceivedPersec + $txBytes += [double]$adapter.BytesSentPersec + } + + $cpuPercent = Get-Percent ([double]$cpu.PercentProcessorTime) + $memPercent = Get-Percent $memoryPercent + $diskUsedPercent = Get-Percent $diskPercent + $rxKbps = [int][math]::Max(0, [math]::Round($rxBytes / 1024)) + $txKbps = [int][math]::Max(0, [math]::Round($txBytes / 1024)) + + return "SYS,$cpuPercent,$memPercent,$diskUsedPercent,$rxKbps,$txKbps" +} + +function Send-MetricLine { + param([string]$Line) + + $body = [System.Text.Encoding]::ASCII.GetBytes($Line) + $header = "POST /metrics HTTP/1.1`r`nHost: $HostName`r`nContent-Type: text/plain`r`nContent-Length: $($body.Length)`r`nConnection: close`r`n`r`n" + $headerBytes = [System.Text.Encoding]::ASCII.GetBytes($header) + + $client = [System.Net.Sockets.TcpClient]::new() + $connect = $client.BeginConnect($HostName, $Port, $null, $null) + if (-not $connect.AsyncWaitHandle.WaitOne(1200)) { + $client.Close() + throw "connect timeout" + } + $client.EndConnect($connect) + $client.SendTimeout = 1200 + $client.ReceiveTimeout = 1200 + + try { + $stream = $client.GetStream() + $stream.Write($headerBytes, 0, $headerBytes.Length) + $stream.Write($body, 0, $body.Length) + $stream.Flush() + } finally { + $client.Close() + } +} + +Write-Host "Pushing PC metrics to http://$HostName/metrics. Press Ctrl+C to stop." +while ($true) { + $line = Get-SystemMetricLine + try { + Send-MetricLine -Line $line + Write-Host $line + } catch { + Write-Host "send failed: $($_.Exception.Message)" + } + Start-Sleep -Seconds $IntervalSeconds +} diff --git a/tools/pc-metrics/requirements-ble.txt b/tools/pc-metrics/requirements-ble.txt new file mode 100644 index 0000000..e8780ac --- /dev/null +++ b/tools/pc-metrics/requirements-ble.txt @@ -0,0 +1,2 @@ +bleak>=0.22,<2 +psutil>=5.9,<8 diff --git a/tools/pc-metrics/serve-metrics-http.ps1 b/tools/pc-metrics/serve-metrics-http.ps1 new file mode 100644 index 0000000..baba236 --- /dev/null +++ b/tools/pc-metrics/serve-metrics-http.ps1 @@ -0,0 +1,124 @@ +param( + [int]$Port = 8789 +) + +$ErrorActionPreference = "Stop" + +function Get-Percent { + param([double]$Value) + if ([double]::IsNaN($Value) -or [double]::IsInfinity($Value)) { + return 0 + } + $rounded = [math]::Round($Value) + if ($rounded -lt 0) { return 0 } + if ($rounded -gt 100) { return 100 } + return [int]$rounded +} + +function Get-SystemMetrics { + $cpu = Get-CimInstance Win32_PerfFormattedData_PerfOS_Processor | + Where-Object { $_.Name -eq "_Total" } | + Select-Object -First 1 + + $os = Get-CimInstance Win32_OperatingSystem + $totalMemory = [double]$os.TotalVisibleMemorySize + $freeMemory = [double]$os.FreePhysicalMemory + $memoryPercent = if ($totalMemory -gt 0) { + (($totalMemory - $freeMemory) / $totalMemory) * 100 + } else { + 0 + } + + $disk = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" + $diskPercent = if ($disk -and [double]$disk.Size -gt 0) { + (([double]$disk.Size - [double]$disk.FreeSpace) / [double]$disk.Size) * 100 + } else { + 0 + } + + $network = Get-CimInstance Win32_PerfFormattedData_Tcpip_NetworkInterface | + Where-Object { + $_.Name -notmatch "Loopback|isatap|Teredo" -and + ([double]$_.BytesReceivedPersec -gt 0 -or [double]$_.BytesSentPersec -gt 0) + } + + $rxBytes = 0.0 + $txBytes = 0.0 + foreach ($adapter in $network) { + $rxBytes += [double]$adapter.BytesReceivedPersec + $txBytes += [double]$adapter.BytesSentPersec + } + + return [ordered]@{ + cpu = Get-Percent ([double]$cpu.PercentProcessorTime) + mem = Get-Percent $memoryPercent + disk = Get-Percent $diskPercent + rx = [int][math]::Max(0, [math]::Round($rxBytes / 1024)) + tx = [int][math]::Max(0, [math]::Round($txBytes / 1024)) + } +} + +function Get-MetricsLine { + $m = Get-SystemMetrics + return "SYS,$($m.cpu),$($m.mem),$($m.disk),$($m.rx),$($m.tx)" +} + +function Send-Response { + param( + [System.Net.Sockets.TcpClient]$Client, + [string]$Status, + [string]$ContentType, + [string]$Body + ) + + $bodyBytes = [System.Text.Encoding]::UTF8.GetBytes($Body) + $header = "HTTP/1.1 $Status`r`nContent-Type: $ContentType; charset=utf-8`r`nContent-Length: $($bodyBytes.Length)`r`nConnection: close`r`nCache-Control: no-store`r`n`r`n" + $headerBytes = [System.Text.Encoding]::ASCII.GetBytes($header) + $stream = $Client.GetStream() + $stream.Write($headerBytes, 0, $headerBytes.Length) + $stream.Write($bodyBytes, 0, $bodyBytes.Length) + $stream.Flush() +} + +$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Any, $Port) +$listener.Start() +Write-Host "PC metrics HTTP server listening on 0.0.0.0:$Port" +Write-Host "Text endpoint: /metrics.txt" +Write-Host "JSON endpoint: /metrics.json" + +try { + while ($true) { + $client = $listener.AcceptTcpClient() + try { + $stream = $client.GetStream() + $buffer = New-Object byte[] 2048 + $read = $stream.Read($buffer, 0, $buffer.Length) + $request = [System.Text.Encoding]::ASCII.GetString($buffer, 0, $read) + $firstLine = ($request -split "`r?`n")[0] + $path = "/" + if ($firstLine -match "^[A-Z]+\\s+([^\\s]+)") { + $path = $Matches[1] + } + + if ($path -eq "/health") { + Send-Response $client "200 OK" "text/plain" "ok`n" + } elseif ($path -eq "/metrics.json") { + $metrics = Get-SystemMetrics + Send-Response $client "200 OK" "application/json" (($metrics | ConvertTo-Json -Compress) + "`n") + } elseif ($path -eq "/" -or $path -eq "/metrics.txt") { + Send-Response $client "200 OK" "text/plain" ((Get-MetricsLine) + "`n") + } else { + Send-Response $client "404 Not Found" "text/plain" "not found`n" + } + } catch { + try { + Send-Response $client "500 Internal Server Error" "text/plain" "error`n" + } catch { + } + } finally { + $client.Close() + } + } +} finally { + $listener.Stop() +} diff --git a/tools/pc-metrics/start-ap-push.ps1 b/tools/pc-metrics/start-ap-push.ps1 new file mode 100644 index 0000000..6f13bc2 --- /dev/null +++ b/tools/pc-metrics/start-ap-push.ps1 @@ -0,0 +1,41 @@ +param( + [string]$HostName = "192.168.4.1", + [int]$Port = 80, + [double]$IntervalSeconds = 1 +) + +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$pushScript = Join-Path $scriptDir "push-metrics-http.ps1" +$pidFile = Join-Path $scriptDir "metrics-ap-push.pid" +$logFile = Join-Path $scriptDir "metrics-ap-push.log" +$errFile = Join-Path $scriptDir "metrics-ap-push.err.log" + +if (Test-Path $pidFile) { + $oldPid = Get-Content $pidFile -ErrorAction SilentlyContinue + if ($oldPid -and (Get-Process -Id ([int]$oldPid) -ErrorAction SilentlyContinue)) { + Write-Host "AP metrics push is already running. PID: $oldPid" + exit 0 + } +} + +$args = @( + "-NoProfile", + "-ExecutionPolicy", "Bypass", + "-File", "`"$pushScript`"", + "-HostName", $HostName, + "-Port", $Port, + "-IntervalSeconds", $IntervalSeconds +) + +$process = Start-Process powershell.exe ` + -ArgumentList $args ` + -WindowStyle Hidden ` + -RedirectStandardOutput $logFile ` + -RedirectStandardError $errFile ` + -PassThru + +Set-Content -Path $pidFile -Value $process.Id -Encoding ascii +Write-Host "AP metrics push started. PID: $($process.Id)" +Write-Host "Log: $logFile" diff --git a/tools/pc-metrics/start-ble-metrics.ps1 b/tools/pc-metrics/start-ble-metrics.ps1 new file mode 100644 index 0000000..a74c3e6 --- /dev/null +++ b/tools/pc-metrics/start-ble-metrics.ps1 @@ -0,0 +1,89 @@ +param( + [string]$Name = "RLCD-Metrics", + [double]$IntervalSeconds = 1, + [string]$Disk = "C:\" +) + +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$venvDir = Join-Path $scriptDir ".venv-ble" +$venvPythonExe = Join-Path $venvDir "Scripts\python.exe" +$pythonExe = $venvPythonExe +$venvSitePackages = Join-Path $venvDir "Lib\site-packages" +$requirements = Join-Path $scriptDir "requirements-ble.txt" +$pushScript = Join-Path $scriptDir "push-metrics-ble.py" +$pidFile = Join-Path $scriptDir "metrics-ble.pid" +$logFile = Join-Path $scriptDir "metrics-ble.log" +$errFile = Join-Path $scriptDir "metrics-ble.err.log" + +function Get-MetricsProcesses { + Get-CimInstance Win32_Process -Filter "name='python.exe'" | + Where-Object { $_.CommandLine -and $_.CommandLine.Contains($pushScript) } +} + +if (-not (Test-Path $pythonExe)) { + python -m venv $venvDir + & $venvPythonExe -m pip install --upgrade pip + & $venvPythonExe -m pip install -r $requirements +} + +$venvConfig = Join-Path $venvDir "pyvenv.cfg" +if (Test-Path $venvConfig) { + $executableLine = Get-Content $venvConfig | + Where-Object { $_ -match '^executable\s*=' } | + Select-Object -First 1 + if ($executableLine) { + $basePython = ($executableLine -replace '^executable\s*=\s*', '').Trim() + if ($basePython -and (Test-Path $basePython)) { + $pythonExe = $basePython + } + } +} + +$running = @(Get-MetricsProcesses) +if ($running.Count -gt 0) { + $existingPid = $running[0].ProcessId + Set-Content -Path $pidFile -Value $existingPid -Encoding ascii + Write-Host "BLE metrics stream is already running. PID: $existingPid" + exit 0 +} + +if (Test-Path $pidFile) { + $oldPid = Get-Content $pidFile -ErrorAction SilentlyContinue + if ($oldPid -and (Get-Process -Id ([int]$oldPid) -ErrorAction SilentlyContinue)) { + Write-Host "BLE metrics stream is already running. PID: $oldPid" + exit 0 + } +} + +$args = @( + "`"$pushScript`"", + "--name", $Name, + "--interval", $IntervalSeconds, + "--disk", $Disk +) + +$oldPythonPath = $env:PYTHONPATH +if ($pythonExe -ne $venvPythonExe -and (Test-Path $venvSitePackages)) { + if ($oldPythonPath) { + $env:PYTHONPATH = "$venvSitePackages;$oldPythonPath" + } else { + $env:PYTHONPATH = $venvSitePackages + } +} + +try { + $process = Start-Process $pythonExe ` + -ArgumentList $args ` + -WindowStyle Hidden ` + -RedirectStandardOutput $logFile ` + -RedirectStandardError $errFile ` + -PassThru +} finally { + $env:PYTHONPATH = $oldPythonPath +} + +Set-Content -Path $pidFile -Value $process.Id -Encoding ascii +Write-Host "BLE metrics stream started. PID: $($process.Id)" +Write-Host "Log: $logFile" diff --git a/tools/pc-metrics/start-http-metrics.ps1 b/tools/pc-metrics/start-http-metrics.ps1 new file mode 100644 index 0000000..6e069ab --- /dev/null +++ b/tools/pc-metrics/start-http-metrics.ps1 @@ -0,0 +1,37 @@ +param( + [int]$Port = 8789 +) + +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$serverScript = Join-Path $scriptDir "serve-metrics-http.ps1" +$pidFile = Join-Path $scriptDir "metrics-http.pid" +$logFile = Join-Path $scriptDir "metrics-http.log" +$errFile = Join-Path $scriptDir "metrics-http.err.log" + +if (Test-Path $pidFile) { + $oldPid = Get-Content $pidFile -ErrorAction SilentlyContinue + if ($oldPid -and (Get-Process -Id ([int]$oldPid) -ErrorAction SilentlyContinue)) { + Write-Host "Metrics HTTP server is already running. PID: $oldPid" + exit 0 + } +} + +$args = @( + "-NoProfile", + "-ExecutionPolicy", "Bypass", + "-File", "`"$serverScript`"", + "-Port", $Port +) + +$process = Start-Process powershell.exe ` + -ArgumentList $args ` + -WindowStyle Hidden ` + -RedirectStandardOutput $logFile ` + -RedirectStandardError $errFile ` + -PassThru + +Set-Content -Path $pidFile -Value $process.Id -Encoding ascii +Write-Host "Metrics HTTP server started. PID: $($process.Id)" +Write-Host "URL: http://0.0.0.0:$Port/metrics.txt" diff --git a/tools/pc-metrics/start-metrics.ps1 b/tools/pc-metrics/start-metrics.ps1 new file mode 100644 index 0000000..271d8dd --- /dev/null +++ b/tools/pc-metrics/start-metrics.ps1 @@ -0,0 +1,39 @@ +param( + [string]$Port = "COM3", + [double]$IntervalSeconds = 1 +) + +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$streamScript = Join-Path $scriptDir "stream-metrics.ps1" +$pidFile = Join-Path $scriptDir "metrics-stream.pid" +$logFile = Join-Path $scriptDir "metrics-stream.log" +$errFile = Join-Path $scriptDir "metrics-stream.err.log" + +if (Test-Path $pidFile) { + $oldPid = Get-Content $pidFile -ErrorAction SilentlyContinue + if ($oldPid -and (Get-Process -Id ([int]$oldPid) -ErrorAction SilentlyContinue)) { + Write-Host "Metrics stream is already running. PID: $oldPid" + exit 0 + } +} + +$args = @( + "-NoProfile", + "-ExecutionPolicy", "Bypass", + "-File", "`"$streamScript`"", + "-Port", $Port, + "-IntervalSeconds", $IntervalSeconds +) + +$process = Start-Process powershell.exe ` + -ArgumentList $args ` + -WindowStyle Hidden ` + -RedirectStandardOutput $logFile ` + -RedirectStandardError $errFile ` + -PassThru + +Set-Content -Path $pidFile -Value $process.Id -Encoding ascii +Write-Host "Metrics stream started. PID: $($process.Id)" +Write-Host "Log: $logFile" diff --git a/tools/pc-metrics/stop-ap-push.ps1 b/tools/pc-metrics/stop-ap-push.ps1 new file mode 100644 index 0000000..74f3aad --- /dev/null +++ b/tools/pc-metrics/stop-ap-push.ps1 @@ -0,0 +1,22 @@ +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$pidFile = Join-Path $scriptDir "metrics-ap-push.pid" + +if (-not (Test-Path $pidFile)) { + Write-Host "AP metrics push is not running." + exit 0 +} + +$pidValue = Get-Content $pidFile -ErrorAction SilentlyContinue +if ($pidValue) { + $process = Get-Process -Id ([int]$pidValue) -ErrorAction SilentlyContinue + if ($process) { + Stop-Process -Id $process.Id -Force + Write-Host "AP metrics push stopped. PID: $($process.Id)" + } else { + Write-Host "AP metrics push process was already gone." + } +} + +Remove-Item -LiteralPath $pidFile -Force -ErrorAction SilentlyContinue diff --git a/tools/pc-metrics/stop-ble-metrics.ps1 b/tools/pc-metrics/stop-ble-metrics.ps1 new file mode 100644 index 0000000..7a68cdf --- /dev/null +++ b/tools/pc-metrics/stop-ble-metrics.ps1 @@ -0,0 +1,42 @@ +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$pidFile = Join-Path $scriptDir "metrics-ble.pid" +$pushScript = Join-Path $scriptDir "push-metrics-ble.py" + +function Get-MetricsProcesses { + Get-CimInstance Win32_Process -Filter "name='python.exe'" | + Where-Object { $_.CommandLine -and $_.CommandLine.Contains($pushScript) } +} + +$processIds = @() +if (Test-Path $pidFile) { + $pidValue = Get-Content $pidFile -ErrorAction SilentlyContinue + if ($pidValue) { + $processIds += [int]$pidValue + } +} + +$processIds += @(Get-MetricsProcesses | Select-Object -ExpandProperty ProcessId) +$processIds = @($processIds | Sort-Object -Unique) + +if ($processIds.Count -eq 0) { + Write-Host "BLE metrics stream is not running." + Remove-Item -LiteralPath $pidFile -Force -ErrorAction SilentlyContinue + exit 0 +} + +$children = Get-CimInstance Win32_Process | + Where-Object { $processIds -contains $_.ParentProcessId } | + Select-Object -ExpandProperty ProcessId + +foreach ($childId in @($children | Sort-Object -Unique)) { + Stop-Process -Id $childId -Force -ErrorAction SilentlyContinue +} + +foreach ($processId in $processIds) { + Stop-Process -Id $processId -Force -ErrorAction SilentlyContinue + Write-Host "BLE metrics stream stopped. PID: $processId" +} + +Remove-Item -LiteralPath $pidFile -Force -ErrorAction SilentlyContinue diff --git a/tools/pc-metrics/stop-http-metrics.ps1 b/tools/pc-metrics/stop-http-metrics.ps1 new file mode 100644 index 0000000..056e600 --- /dev/null +++ b/tools/pc-metrics/stop-http-metrics.ps1 @@ -0,0 +1,22 @@ +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$pidFile = Join-Path $scriptDir "metrics-http.pid" + +if (-not (Test-Path $pidFile)) { + Write-Host "Metrics HTTP server is not running." + exit 0 +} + +$pidValue = Get-Content $pidFile -ErrorAction SilentlyContinue +if ($pidValue) { + $process = Get-Process -Id ([int]$pidValue) -ErrorAction SilentlyContinue + if ($process) { + Stop-Process -Id $process.Id -Force + Write-Host "Metrics HTTP server stopped. PID: $($process.Id)" + } else { + Write-Host "Metrics HTTP server process was already gone." + } +} + +Remove-Item -LiteralPath $pidFile -Force -ErrorAction SilentlyContinue diff --git a/tools/pc-metrics/stop-metrics.ps1 b/tools/pc-metrics/stop-metrics.ps1 new file mode 100644 index 0000000..a52fddf --- /dev/null +++ b/tools/pc-metrics/stop-metrics.ps1 @@ -0,0 +1,22 @@ +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$pidFile = Join-Path $scriptDir "metrics-stream.pid" + +if (-not (Test-Path $pidFile)) { + Write-Host "Metrics stream is not running." + exit 0 +} + +$pidValue = Get-Content $pidFile -ErrorAction SilentlyContinue +if ($pidValue) { + $process = Get-Process -Id ([int]$pidValue) -ErrorAction SilentlyContinue + if ($process) { + Stop-Process -Id $process.Id -Force + Write-Host "Metrics stream stopped. PID: $($process.Id)" + } else { + Write-Host "Metrics stream process was already gone." + } +} + +Remove-Item -LiteralPath $pidFile -Force -ErrorAction SilentlyContinue diff --git a/tools/pc-metrics/stream-metrics.ps1 b/tools/pc-metrics/stream-metrics.ps1 new file mode 100644 index 0000000..e3a85f6 --- /dev/null +++ b/tools/pc-metrics/stream-metrics.ps1 @@ -0,0 +1,90 @@ +param( + [string]$Port = "COM3", + [int]$BaudRate = 115200, + [double]$IntervalSeconds = 1 +) + +$ErrorActionPreference = "Stop" + +function Get-Percent { + param([double]$Value) + if ([double]::IsNaN($Value) -or [double]::IsInfinity($Value)) { + return 0 + } + $rounded = [math]::Round($Value) + if ($rounded -lt 0) { return 0 } + if ($rounded -gt 100) { return 100 } + return [int]$rounded +} + +function Get-SystemMetricLine { + $cpu = Get-CimInstance Win32_PerfFormattedData_PerfOS_Processor | + Where-Object { $_.Name -eq "_Total" } | + Select-Object -First 1 + + $os = Get-CimInstance Win32_OperatingSystem + $totalMemory = [double]$os.TotalVisibleMemorySize + $freeMemory = [double]$os.FreePhysicalMemory + $memoryPercent = if ($totalMemory -gt 0) { + (($totalMemory - $freeMemory) / $totalMemory) * 100 + } else { + 0 + } + + $disk = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" + $diskPercent = if ($disk -and [double]$disk.Size -gt 0) { + (([double]$disk.Size - [double]$disk.FreeSpace) / [double]$disk.Size) * 100 + } else { + 0 + } + + $network = Get-CimInstance Win32_PerfFormattedData_Tcpip_NetworkInterface | + Where-Object { + $_.Name -notmatch "Loopback|isatap|Teredo" -and + ([double]$_.BytesReceivedPersec -gt 0 -or [double]$_.BytesSentPersec -gt 0) + } + + $rxBytes = 0.0 + $txBytes = 0.0 + foreach ($adapter in $network) { + $rxBytes += [double]$adapter.BytesReceivedPersec + $txBytes += [double]$adapter.BytesSentPersec + } + + $cpuPercent = Get-Percent ([double]$cpu.PercentProcessorTime) + $memPercent = Get-Percent $memoryPercent + $diskUsedPercent = Get-Percent $diskPercent + $rxKbps = [int][math]::Max(0, [math]::Round($rxBytes / 1024)) + $txKbps = [int][math]::Max(0, [math]::Round($txBytes / 1024)) + + return "SYS,$cpuPercent,$memPercent,$diskUsedPercent,$rxKbps,$txKbps" +} + +$serial = [System.IO.Ports.SerialPort]::new( + $Port, + $BaudRate, + [System.IO.Ports.Parity]::None, + 8, + [System.IO.Ports.StopBits]::One +) +$serial.NewLine = "`n" +$serial.DtrEnable = $false +$serial.RtsEnable = $false +$serial.WriteTimeout = 1000 + +try { + $serial.Open() + Write-Host "Streaming PC metrics to $Port at $BaudRate baud. Press Ctrl+C to stop." + + while ($true) { + $line = Get-SystemMetricLine + $serial.WriteLine($line) + Write-Host $line + Start-Sleep -Seconds $IntervalSeconds + } +} +finally { + if ($serial.IsOpen) { + $serial.Close() + } +} diff --git a/tools/preview-bridge/server.py b/tools/preview-bridge/server.py new file mode 100644 index 0000000..53ae78b --- /dev/null +++ b/tools/preview-bridge/server.py @@ -0,0 +1,351 @@ +from __future__ import annotations + +import json +import os +import re +import subprocess +import sys +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from typing import Any +from urllib.parse import unquote, urlparse + + +PROJECT_ROOT = Path(__file__).resolve().parents[2] +FRONTEND_DIR = PROJECT_ROOT / "frontend-preview" +MAIN_DIR = PROJECT_ROOT / "main" +GENERATED_DIR = MAIN_DIR / "generated" +ACTIVE_SCREEN_PATH = FRONTEND_DIR / "active-screen.json" +TEMPLATES_PATH = FRONTEND_DIR / "templates.json" +FIRMWARES_PATH = FRONTEND_DIR / "firmwares.json" +UI_CONFIG_PATH = GENERATED_DIR / "ui_config.h" + +DEFAULT_PORT = 8788 +IDF_VERSION = "v6.0.1" +EIM_EXE = Path( + r"C:\Users\54972\AppData\Local\Microsoft\WinGet\Packages" + r"\Espressif.EIM-CLI_Microsoft.Winget.Source_8wekyb3d8bbwe\eim.exe" +) + +TEMPLATE_IDS = { + "clock": "UI_TEMPLATE_CLOCK", + "dual-metric": "UI_TEMPLATE_DUAL_METRIC", + "meter": "UI_TEMPLATE_METER", +} + + +def read_json(path: Path, fallback: Any) -> Any: + try: + return json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError: + return fallback + + +def write_json(path: Path, data: Any) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +def response_bytes(data: Any) -> bytes: + return json.dumps(data, ensure_ascii=False, indent=2).encode("utf-8") + + +def c_string(value: Any, limit: int = 48) -> str: + text = str(value or "")[:limit] + text = text.replace("\\", "\\\\").replace('"', '\\"') + text = text.replace("\r", " ").replace("\n", " ") + return f'"{text}"' + + +def c_int(value: Any, default: int = 0, minimum: int = -999, maximum: int = 999) -> int: + try: + number = int(float(str(value).strip())) + except (TypeError, ValueError): + number = default + return max(minimum, min(maximum, number)) + + +def normalize_screen(screen: dict[str, Any]) -> dict[str, Any]: + screen = dict(screen or {}) + fields = dict(screen.get("fields") or {}) + effects = dict(screen.get("effects") or {}) + template_id = screen.get("templateId") or "dual-metric" + if template_id not in TEMPLATE_IDS: + template_id = "dual-metric" + orientation = screen.get("orientation") if screen.get("orientation") in {"landscape", "portrait"} else "landscape" + density = screen.get("density") if screen.get("density") in {"comfortable", "compact"} else "comfortable" + return { + "templateId": template_id, + "title": str(screen.get("title") or "RLCD STATUS"), + "orientation": orientation, + "density": density, + "effects": { + "ghost": bool(effects.get("ghost", True)), + "grid": bool(effects.get("grid", True)), + }, + "fields": fields, + } + + +def firmware_catalog() -> list[dict[str, Any]]: + data = read_json(FIRMWARES_PATH, {"firmwares": []}) + result: list[dict[str, Any]] = [] + for item in data.get("firmwares", []): + firmware = dict(item) + if firmware.get("type") == "whole-image": + path = Path(firmware.get("path", "")) + firmware["ready"] = path.exists() and path.is_file() + firmware["size"] = path.stat().st_size if firmware["ready"] else 0 + elif firmware.get("type") == "idf-project": + project_path = Path(firmware.get("projectPath", "")) + artifact = project_path / firmware.get("artifact", "") + firmware["ready"] = artifact.exists() and artifact.is_file() + firmware["size"] = artifact.stat().st_size if firmware["ready"] else 0 + else: + firmware["ready"] = False + result.append(firmware) + return result + + +def find_firmware(firmware_id: str) -> dict[str, Any]: + for firmware in firmware_catalog(): + if firmware.get("id") == firmware_id: + return firmware + raise ValueError(f"Unknown firmware id: {firmware_id}") + + +def ensure_known_path(path: Path, allowed_roots: list[Path]) -> Path: + resolved = path.resolve() + for root in allowed_roots: + try: + resolved.relative_to(root.resolve()) + return resolved + except ValueError: + continue + raise ValueError(f"Path is not allowlisted: {resolved}") + + +def validate_port(port: str) -> str: + port = (port or "COM3").strip().upper() + if not re.fullmatch(r"COM\d{1,3}", port): + raise ValueError("Only COM ports are allowed on this Windows workbench.") + return port + + +def run_eim(command: str, timeout: int) -> dict[str, Any]: + if not EIM_EXE.exists(): + raise RuntimeError(f"EIM CLI not found: {EIM_EXE}") + completed = subprocess.run( + [str(EIM_EXE), "run", command, IDF_VERSION], + cwd=str(PROJECT_ROOT), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + timeout=timeout, + check=False, + ) + output = completed.stdout[-12000:] if completed.stdout else "" + return { + "ok": completed.returncode == 0, + "returncode": completed.returncode, + "output": output, + } + + +def generate_ui_config(screen: dict[str, Any]) -> str: + screen = normalize_screen(screen) + fields = screen["fields"] + template_macro = TEMPLATE_IDS[screen["templateId"]] + orientation = 1 if screen["orientation"] == "portrait" else 0 + density = 1 if screen["density"] == "compact" else 0 + primary_value = c_int(fields.get("primaryValue"), 0, -999, 999) + secondary_value = c_int(fields.get("secondaryValue"), 0, -999, 999) + time_text = fields.get("time") or "12:00" + + lines = [ + "/* Generated by tools/preview-bridge/server.py. */", + "#pragma once", + "", + "#define UI_TEMPLATE_CLOCK 1", + "#define UI_TEMPLATE_DUAL_METRIC 2", + "#define UI_TEMPLATE_METER 3", + f"#define UI_ACTIVE_TEMPLATE {template_macro}", + "", + "#define UI_ORIENTATION_LANDSCAPE 0", + "#define UI_ORIENTATION_PORTRAIT 1", + f"#define UI_ACTIVE_ORIENTATION {orientation}", + "", + "#define UI_DENSITY_COMFORTABLE 0", + "#define UI_DENSITY_COMPACT 1", + f"#define UI_ACTIVE_DENSITY {density}", + "", + f"#define UI_TITLE_TEXT {c_string(screen.get('title'), 32)}", + f"#define UI_STATUS_TEXT {c_string(fields.get('status') or 'READY', 18)}", + f"#define UI_TIME_TEXT {c_string(time_text, 12)}", + f"#define UI_DATE_TEXT {c_string(fields.get('date') or '2026-06-20', 24)}", + f"#define UI_PRIMARY_LABEL_TEXT {c_string(fields.get('primaryLabel') or 'A', 18)}", + f"#define UI_PRIMARY_VALUE_TEXT {c_string(fields.get('primaryValue') or '0', 10)}", + f"#define UI_PRIMARY_VALUE_INT {primary_value}", + f"#define UI_PRIMARY_UNIT_TEXT {c_string(fields.get('primaryUnit') or '', 8)}", + f"#define UI_SECONDARY_LABEL_TEXT {c_string(fields.get('secondaryLabel') or 'B', 18)}", + f"#define UI_SECONDARY_VALUE_TEXT {c_string(fields.get('secondaryValue') or '0', 10)}", + f"#define UI_SECONDARY_VALUE_INT {secondary_value}", + f"#define UI_SECONDARY_UNIT_TEXT {c_string(fields.get('secondaryUnit') or '', 8)}", + f"#define UI_FOOTER_LEFT_TEXT {c_string(fields.get('footerLeft') or '', 32)}", + f"#define UI_FOOTER_RIGHT_TEXT {c_string(fields.get('footerRight') or '', 32)}", + "", + ] + return "\n".join(lines) + + +def save_generated_config(screen: dict[str, Any]) -> None: + GENERATED_DIR.mkdir(parents=True, exist_ok=True) + UI_CONFIG_PATH.write_text(generate_ui_config(screen), encoding="utf-8") + + +class WorkbenchHandler(SimpleHTTPRequestHandler): + server_version = "RLCDWorkbench/1.0" + + def end_headers(self) -> None: + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + self.send_header("Access-Control-Allow-Headers", "Content-Type") + super().end_headers() + + def do_OPTIONS(self) -> None: + self.send_response(204) + self.end_headers() + + def do_GET(self) -> None: + parsed = urlparse(self.path) + if parsed.path.startswith("/api/"): + self.handle_api_get(parsed.path) + return + self.serve_frontend(parsed.path) + + def do_POST(self) -> None: + parsed = urlparse(self.path) + if parsed.path.startswith("/api/"): + self.handle_api_post(parsed.path) + return + self.send_error(404) + + def read_body_json(self) -> Any: + length = int(self.headers.get("Content-Length", "0")) + if length <= 0: + return {} + raw = self.rfile.read(length).decode("utf-8") + return json.loads(raw or "{}") + + def send_json(self, data: Any, status: int = 200) -> None: + body = response_bytes(data) + self.send_response(status) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def send_error_json(self, message: str, status: int = 400) -> None: + self.send_json({"ok": False, "error": message}, status=status) + + def handle_api_get(self, path: str) -> None: + try: + if path == "/api/health": + self.send_json({"ok": True, "projectRoot": str(PROJECT_ROOT)}) + elif path == "/api/templates": + self.send_json(read_json(TEMPLATES_PATH, {"templates": []})) + elif path == "/api/screen": + self.send_json({"screen": normalize_screen(read_json(ACTIVE_SCREEN_PATH, {}))}) + elif path == "/api/firmwares": + self.send_json({"firmwares": firmware_catalog()}) + else: + self.send_error_json("Unknown API endpoint", 404) + except Exception as exc: # noqa: BLE001 + self.send_error_json(str(exc), 500) + + def handle_api_post(self, path: str) -> None: + try: + body = self.read_body_json() + if path == "/api/screen": + screen = normalize_screen(body) + write_json(ACTIVE_SCREEN_PATH, screen) + self.send_json({"ok": True, "message": "Screen config saved."}) + elif path == "/api/generate": + screen = normalize_screen(body.get("screen") or read_json(ACTIVE_SCREEN_PATH, {})) + write_json(ACTIVE_SCREEN_PATH, screen) + save_generated_config(screen) + self.send_json({"ok": True, "message": f"Generated {UI_CONFIG_PATH}"}) + elif path == "/api/build": + screen = normalize_screen(read_json(ACTIVE_SCREEN_PATH, {})) + save_generated_config(screen) + result = run_eim("idf.py build", timeout=900) + self.send_json(result, status=200 if result["ok"] else 500) + elif path == "/api/flash": + self.flash_firmware(body) + else: + self.send_error_json("Unknown API endpoint", 404) + except subprocess.TimeoutExpired: + self.send_error_json("Command timed out.", 504) + except Exception as exc: # noqa: BLE001 + self.send_error_json(str(exc), 500) + + def flash_firmware(self, body: dict[str, Any]) -> None: + if body.get("confirmed") is not True: + raise ValueError("Flash requires explicit confirmation.") + port = validate_port(str(body.get("port") or "COM3")) + firmware = find_firmware(str(body.get("firmwareId") or "")) + firmware_type = firmware.get("type") + + if firmware_type == "whole-image": + path = ensure_known_path( + Path(firmware.get("path", "")), + [Path(r"D:\esp\waveshare-rlcd-4.2\03_Firmware")], + ) + if not path.exists(): + raise FileNotFoundError(path) + offset = firmware.get("offset") or "0x0" + command = f'esptool.py --chip esp32s3 -p {port} -b 460800 write_flash {offset} "{path}"' + result = run_eim(command, timeout=900) + elif firmware_type == "idf-project": + project_path = ensure_known_path(Path(firmware.get("projectPath", "")), [PROJECT_ROOT]) + if project_path != PROJECT_ROOT: + raise ValueError("Only the current ESP-IDF project can be flashed.") + result = run_eim(f"idf.py -p {port} flash", timeout=900) + else: + raise ValueError(f"Unsupported firmware type: {firmware_type}") + + self.send_json(result, status=200 if result["ok"] else 500) + + def serve_frontend(self, raw_path: str) -> None: + path = unquote(raw_path or "/") + if path == "/": + path = "/index.html" + candidate = (FRONTEND_DIR / path.lstrip("/")).resolve() + try: + candidate.relative_to(FRONTEND_DIR.resolve()) + except ValueError: + self.send_error(403) + return + if not candidate.exists() or not candidate.is_file(): + self.send_error(404) + return + self.path = "/" + str(candidate.relative_to(FRONTEND_DIR)).replace(os.sep, "/") + self.directory = str(FRONTEND_DIR) + return super().do_GET() + + +def main() -> int: + port = int(os.environ.get("RLCD_WORKBENCH_PORT", DEFAULT_PORT)) + address = ("127.0.0.1", port) + httpd = ThreadingHTTPServer(address, WorkbenchHandler) + print(f"RLCD workbench bridge: http://127.0.0.1:{port}") + print(f"Project root: {PROJECT_ROOT}") + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\nStopping bridge.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())