diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..907ab0c --- /dev/null +++ b/.clang-tidy @@ -0,0 +1 @@ +Checks: '-clang-diagnostic-builtin-macro-redefined' diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..437f255 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Remove: [-f*, -m*] diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..dafb8ad --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +ARG DOCKER_TAG=latest +FROM espressif/idf:${DOCKER_TAG} + +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +RUN apt-get update -y && apt-get install udev -y + +RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc + +ENTRYPOINT [ "/opt/esp/entrypoint.sh" ] + +CMD ["/bin/bash", "-c"] \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..478f2d2 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,30 @@ +{ + "name": "ESP-IDF QEMU", + "build": { + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "idf.espIdfPath": "/opt/esp/idf", + "idf.toolsPath": "/opt/esp", + "idf.gitPath": "/usr/bin/git" + }, + "extensions": [ + "espressif.esp-idf-extension", + "espressif.esp-idf-web", + "ms-vscode.cpptools", + "streetsidesoftware.code-spell-checker", + "mhutchie.git-graph", + "oderwat.indent-rainbow", + "SirTori.indenticator", + "christian-kohler.path-intellisense", + "esbenp.prettier-vscode", + "redhat.vscode-yaml" + + ] + } + }, + "runArgs": ["--privileged"] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92e5491 --- /dev/null +++ b/.gitignore @@ -0,0 +1,90 @@ +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Directory metadata +.directory + +# Temporary files +*~ +*.swp +*.swo +*.bak +*.tmp + +# Log files +*.log + +# Build artifacts and directories +**/build/ +build/ +*.o +*.a +*.out +*.exe # For any host-side utilities compiled on Windows + +# ESP-IDF specific build outputs +*.bin +*.elf +*.map +flasher_args.json # Generated in build directory +sdkconfig.old +sdkconfig + +# ESP-IDF dependencies +# For older versions or manual component management +/components/.idf/ +**/components/.idf/ +# For modern ESP-IDF component manager +managed_components/ +# If ESP-IDF tools are installed/referenced locally to the project +.espressif/ + +# CMake generated files +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +install_manifest.txt +CTestTestfile.cmake + +# Python environment files +*.pyc +*.pyo +*.pyd +__pycache__/ +*.egg-info/ +dist/ + +# Virtual environment folders +venv/ +.venv/ +env/ + +# Language Servers +.clangd/ +.ccls-cache/ +compile_commands.json + +# Windows specific +Thumbs.db +ehthumbs.db +Desktop.ini + +# User-specific configuration files +*.user +*.workspace # General workspace files, can be from various tools +*.suo # Visual Studio Solution User Options +*.sln.docstates # Visual Studio + +# cache files +.cache/ + +# vscode settings +.vscode/ + +# sample code +sample-code/ + +.env +*.env diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..19d3ac1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,43 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) +# target_compile_options(${COMPONENT_LIB} PRIVATE -std=c++23) + +# Define the path to your .env file +set(ENV_FILE "${CMAKE_SOURCE_DIR}/.env") + +# Check if the .env file exists +if(EXISTS ${ENV_FILE}) + # Read the .env file line by line + file(STRINGS ${ENV_FILE} ENV_VARS) + + foreach(VAR ${ENV_VARS}) + # Use regex to extract the key and value + if (VAR MATCHES "([^=]+)=(.*)") + set(ENV{${CMAKE_MATCH_1}} ${CMAKE_MATCH_2}) + message(STATUS "Loaded environment variable from .env: ${CMAKE_MATCH_1}") + endif() + endforeach() +else() + message(STATUS ".env file not found at ${ENV_FILE}") +endif() + +# If build-time WiFi environment variables were loaded above, expose them +# as compile-time definitions so C++ can use them. +if(DEFINED ENV{WIFI_SSID}) + add_compile_definitions(BUILD_WIFI_SSID="$ENV{WIFI_SSID}") + message(STATUS "Added BUILD_WIFI_SSID compile definition") +else() + message(STATUS "WIFI_SSID not defined; skipping BUILD_WIFI_SSID compile definition") +endif() +if(DEFINED ENV{WIFI_PASSWORD}) + add_compile_definitions(BUILD_WIFI_PASSWORD="$ENV{WIFI_PASSWORD}") + message(STATUS "Added BUILD_WIFI_PASSWORD compile definition") +else() + message(STATUS "WIFI_PASSWORD not defined; skipping BUILD_WIFI_PASSWORD compile definition") +endif() + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(ink-board) diff --git a/assets/mtr_line_station.json b/assets/mtr_line_station.json new file mode 100644 index 0000000..bbc925a --- /dev/null +++ b/assets/mtr_line_station.json @@ -0,0 +1,556 @@ +{ + "AEL": { + "name": "機場快綫", + "code": "AEL", + "line_color": "#00888A", + "stations": [ + { + "code": "HOK", + "name": "香港" + }, + { + "code": "KOW", + "name": "九龍" + }, + { + "code": "TSY", + "name": "青衣" + }, + { + "code": "AIR", + "name": "機場" + }, + { + "code": "AWE", + "name": "博覽館" + } + ] + }, + "TCL": { + "name": "東涌綫", + "code": "TCL", + "line_color": "#F38B00", + "stations": [ + { + "code": "HOK", + "name": "香港" + }, + { + "code": "KOW", + "name": "九龍" + }, + { + "code": "OLY", + "name": "奧運" + }, + { + "code": "NAC", + "name": "南昌" + }, + { + "code": "LAK", + "name": "荔景" + }, + { + "code": "TSY", + "name": "青衣" + }, + { + "code": "SUN", + "name": "欣澳" + }, + { + "code": "TUC", + "name": "東涌" + } + ] + }, + "TML": { + "name": "屯馬綫", + "code": "TML", + "line_color": "#9A3820", + "stations": [ + { + "code": "WKS", + "name": "烏溪沙" + }, + { + "code": "MOS", + "name": "馬鞍山" + }, + { + "code": "HEO", + "name": "恆安" + }, + { + "code": "TSH", + "name": "大水坑" + }, + { + "code": "SHM", + "name": "石門" + }, + { + "code": "CIO", + "name": "第一城" + }, + { + "code": "STW", + "name": "沙田圍" + }, + { + "code": "CKT", + "name": "車公廟" + }, + { + "code": "TAW", + "name": "大圍" + }, + { + "code": "HIK", + "name": "顯徑" + }, + { + "code": "DIH", + "name": "鑽石山" + }, + { + "code": "KAT", + "name": "啟德" + }, + { + "code": "SUW", + "name": "宋皇臺" + }, + { + "code": "TKW", + "name": "土瓜灣" + }, + { + "code": "HOM", + "name": "何文田" + }, + { + "code": "HUH", + "name": "紅磡" + }, + { + "code": "ETS", + "name": "尖東" + }, + { + "code": "AUS", + "name": "柯士甸" + }, + { + "code": "NAC", + "name": "南昌" + }, + { + "code": "MEF", + "name": "美孚" + }, + { + "code": "TWW", + "name": "荃灣西" + }, + { + "code": "KSR", + "name": "錦上路" + }, + { + "code": "YUL", + "name": "元朗" + }, + { + "code": "LOP", + "name": "朗屏" + }, + { + "code": "TIS", + "name": "天水圍" + }, + { + "code": "SIH", + "name": "兆康" + }, + { + "code": "TUM", + "name": "屯門" + } + ] + }, + "TKL": { + "name": "將軍澳綫", + "code": "TKL", + "line_color": "#A35EB5", + "stations": [ + { + "code": "NOP", + "name": "北角" + }, + { + "code": "QUB", + "name": "鰂魚涌" + }, + { + "code": "YAT", + "name": "油塘" + }, + { + "code": "TIK", + "name": "調景嶺" + }, + { + "code": "TKO", + "name": "將軍澳" + }, + { + "code": "LHP", + "name": "康城" + }, + { + "code": "HAH", + "name": "坑口" + }, + { + "code": "POA", + "name": "寶琳" + } + ] + }, + "EAL": { + "name": "東鐵綫", + "code": "EAL", + "line_color": "#53B7E8", + "stations": [ + { + "code": "ADM", + "name": "金鐘" + }, + { + "code": "EXC", + "name": "會展" + }, + { + "code": "HUH", + "name": "紅磡" + }, + { + "code": "MKK", + "name": "旺角東" + }, + { + "code": "KOT", + "name": "九龍塘" + }, + { + "code": "TAW", + "name": "大圍" + }, + { + "code": "SHT", + "name": "沙田" + }, + { + "code": "FOT", + "name": "火炭" + }, + { + "code": "RAC", + "name": "馬場" + }, + { + "code": "UNI", + "name": "大學" + }, + { + "code": "TAP", + "name": "大埔墟" + }, + { + "code": "TWO", + "name": "太和" + }, + { + "code": "FAN", + "name": "粉嶺" + }, + { + "code": "SHS", + "name": "上水" + }, + { + "code": "LOW", + "name": "羅湖" + }, + { + "code": "LMC", + "name": "落馬洲" + } + ] + }, + "SIL": { + "name": "南港島綫", + "code": "SIL", + "line_color": "#B6BD00", + "stations": [ + { + "code": "ADM", + "name": "金鐘" + }, + { + "code": "OCP", + "name": "海洋公園" + }, + { + "code": "WCH", + "name": "黃竹坑" + }, + { + "code": "LET", + "name": "利東" + }, + { + "code": "SOH", + "name": "海怡半島" + } + ] + }, + "TWL": { + "name": "荃灣綫", + "code": "TWL", + "line_color": "#E2231A", + "stations": [ + { + "code": "CEN", + "name": "中環" + }, + { + "code": "ADM", + "name": "金鐘" + }, + { + "code": "TST", + "name": "尖沙咀" + }, + { + "code": "JOR", + "name": "佐敦" + }, + { + "code": "YMT", + "name": "油麻地" + }, + { + "code": "MOK", + "name": "旺角" + }, + { + "code": "PRE", + "name": "太子" + }, + { + "code": "SSP", + "name": "深水埗" + }, + { + "code": "CSW", + "name": "長沙灣" + }, + { + "code": "LCK", + "name": "荔枝角" + }, + { + "code": "MEF", + "name": "美孚" + }, + { + "code": "LAK", + "name": "荔景" + }, + { + "code": "KWF", + "name": "葵芳" + }, + { + "code": "KWH", + "name": "葵興" + }, + { + "code": "TWH", + "name": "大窩口" + }, + { + "code": "TSW", + "name": "荃灣" + } + ] + }, + "ISL": { + "name": "港島綫", + "code": "ISL", + "line_color": "#007DC5", + "stations": [ + { + "code": "KET", + "name": "堅尼地城" + }, + { + "code": "HKU", + "name": "香港大學" + }, + { + "code": "SYP", + "name": "西營盤" + }, + { + "code": "SHW", + "name": "上環" + }, + { + "code": "CEN", + "name": "中環" + }, + { + "code": "ADM", + "name": "金鐘" + }, + { + "code": "WAC", + "name": "灣仔" + }, + { + "code": "CAB", + "name": "銅鑼灣" + }, + { + "code": "TIH", + "name": "天后" + }, + { + "code": "FOH", + "name": "炮台山" + }, + { + "code": "NOP", + "name": "北角" + }, + { + "code": "QUB", + "name": "鰂魚涌" + }, + { + "code": "TAK", + "name": "太古" + }, + { + "code": "SWH", + "name": "西灣河" + }, + { + "code": "SKW", + "name": "筲箕灣" + }, + { + "code": "HFC", + "name": "杏花邨" + }, + { + "code": "CHW", + "name": "柴灣" + } + ] + }, + "KTL": { + "name": "觀塘綫", + "code": "KTL", + "line_color": "#00AB4E", + "stations": [ + { + "code": "WHA", + "name": "黃埔" + }, + { + "code": "HOM", + "name": "何文田" + }, + { + "code": "YMT", + "name": "油麻地" + }, + { + "code": "MOK", + "name": "旺角" + }, + { + "code": "PRE", + "name": "太子" + }, + { + "code": "SKM", + "name": "石硤尾" + }, + { + "code": "KOT", + "name": "九龍塘" + }, + { + "code": "LOF", + "name": "樂富" + }, + { + "code": "WTS", + "name": "黃大仙" + }, + { + "code": "DIH", + "name": "鑽石山" + }, + { + "code": "CHH", + "name": "彩虹" + }, + { + "code": "KOB", + "name": "九龍灣" + }, + { + "code": "NTK", + "name": "牛頭角" + }, + { + "code": "KWT", + "name": "觀塘" + }, + { + "code": "LAT", + "name": "藍田" + }, + { + "code": "YAT", + "name": "油塘" + }, + { + "code": "TIK", + "name": "調景嶺" + } + ] + }, + "DRL": { + "name": "迪士尼綫", + "code": "DRL", + "line_color": "#F550A6", + "stations": [ + { + "code": "SUN", + "name": "欣澳" + }, + { + "code": "DIS", + "name": "迪士尼" + } + ] + } +} \ No newline at end of file diff --git a/dependencies.lock b/dependencies.lock new file mode 100644 index 0000000..0686d75 --- /dev/null +++ b/dependencies.lock @@ -0,0 +1,69 @@ +dependencies: + espressif/cjson: + component_hash: 9372811fb197926f522c467627cf4a8e72b681e0366e17879631da801103aef3 + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.7.19 + espressif/esp_lcd_touch: + component_hash: 3f85a7d95af876f1a6ecca8eb90a81614890d0f03a038390804e5a77e2caf862 + dependencies: + - name: idf + require: private + version: '>=4.4.2' + source: + registry_url: https://components.espressif.com + type: service + version: 1.2.1 + espressif/esp_lcd_touch_gt911: + component_hash: be02e243d18b9a661bc13b0d22c0a5cfa3f708cf04d6eb059772276c8c8a4d76 + dependencies: + - name: espressif/esp_lcd_touch + registry_url: https://components.espressif.com + require: public + version: ^1.2.0 + - name: idf + require: private + version: '>=4.4.2' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.2.0~1 + espressif/esp_lvgl_port: + component_hash: f872401524cb645ee6ff1c9242d44fb4ddcfd4d37d7be8b9ed3f4e85a404efcd + dependencies: + - name: idf + require: private + version: '>=5.1' + - name: lvgl/lvgl + registry_url: https://components.espressif.com + require: public + version: '>=8,<10' + source: + registry_url: https://components.espressif.com/ + type: service + version: 2.7.0 + idf: + source: + type: idf + version: 5.5.2 + lvgl/lvgl: + component_hash: 17e68bfd21f0edf4c3ee838e2273da840bf3930e5dbc3bfa6c1190c3aed41f9f + dependencies: [] + source: + registry_url: https://components.espressif.com/ + type: service + version: 9.4.0 +direct_dependencies: +- espressif/cjson +- espressif/esp_lcd_touch_gt911 +- espressif/esp_lvgl_port +- idf +- lvgl/lvgl +manifest_hash: 2010806782b4d2486b02b853afa44a545717d3d0593eb60f9aa6e5c696270f8f +target: esp32s3 +version: 2.0.0 diff --git a/diagram.json b/diagram.json new file mode 100644 index 0000000..c0e3bd4 --- /dev/null +++ b/diagram.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "author": "GW_ MC", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-s3-devkitc-1", + "id": "esp", + "top": 0, + "left": 0, + "attrs": {} + } + ], + "connections": [ + [ + "esp:TX", + "$serialMonitor:RX", + "", + [] + ], + [ + "esp:RX", + "$serialMonitor:TX", + "", + [] + ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..0683fb1 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,41 @@ +set(requires esp-tls spi_flash nvs_flash esp_event esp_netif esp_http_client esp_wifi esp_psram esp_lvgl_port) +file(GLOB SRCS "main.cpp" "*.cpp" "*.c" "**/*.cpp" "**/*.cpp" "ui/**/*.cpp" "ui/**/*.c" "external/**/*.cpp" "external/**/*.c") + + +# Path to the source JSON in this component +set(ASSETS_SRC_DIR ${CMAKE_CURRENT_LIST_DIR}/../assets) +set(ASSETS_BINARY_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/assets) +set(MTR_JSON_SRC ${ASSETS_SRC_DIR}/MTR_LINE_STATION.json) +set(MTR_JSON_HEADER ${ASSETS_BINARY_OUTPUT_DIR}/MTR_LINE_STATION.h) +set(CUSTOM_CMAKE_MODULES_DIR ${CMAKE_CURRENT_LIST_DIR}/cmake) + +## Generate a minified header at configure time using Python +find_package(Python3 COMPONENTS Interpreter) +file(MAKE_DIRECTORY ${ASSETS_BINARY_OUTPUT_DIR}) +if (Python3_Interpreter_FOUND) + execute_process( + COMMAND ${Python3_EXECUTABLE} -c "import json,sys,io; sys.stdout.write(json.dumps(json.load(open(sys.argv[1], 'r', encoding='utf-8')),separators=(',',':')))" + "${MTR_JSON_SRC}" + RESULT_VARIABLE _mtr_json_minify_result + OUTPUT_VARIABLE MTR_JSON_MINIFIED + ERROR_VARIABLE _mtr_json_minify_error + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (_mtr_json_minify_result) + message(WARNING "Python minify failed (code=${_mtr_json_minify_result}): ${_mtr_json_minify_error}\nEmbedding original ${MTR_JSON_SRC} instead.") + file(READ ${MTR_JSON_SRC} MTR_JSON_MINIFIED) + elseif (NOT MTR_JSON_MINIFIED) + message(WARNING "Python minified output empty; embedding original ${MTR_JSON_SRC} instead.") + file(READ ${MTR_JSON_SRC} MTR_JSON_MINIFIED) + endif() +else() + message(WARNING "Python3 not found; embedding original JSON without minification.") + file(READ ${MTR_JSON_SRC} MTR_JSON_MINIFIED) +endif() + +file(WRITE ${MTR_JSON_HEADER} "#pragma once\nstatic const char MTR_LINE_STATION_JSON[] = R\"json(${MTR_JSON_MINIFIED})json\";\n") + + +idf_component_register(SRCS ${SRCS} + PRIV_REQUIRES ${requires} + INCLUDE_DIRS "." "${CMAKE_CURRENT_BINARY_DIR}" "display" "network" "ui" "ui/apps" "io" "common" "external") diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild new file mode 100644 index 0000000..8d0286e --- /dev/null +++ b/main/Kconfig.projbuild @@ -0,0 +1,9 @@ +menu "ink-board Configuration" + + config PARTIAL_REFRESH_LIMIT + int "Partial Refresh Limit" + default 20 + range 5 100 + help "Number of partial updates before full refresh" + +endmenu \ No newline at end of file diff --git a/main/cmake/write_json_header.cmake b/main/cmake/write_json_header.cmake new file mode 100644 index 0000000..7c74067 --- /dev/null +++ b/main/cmake/write_json_header.cmake @@ -0,0 +1,20 @@ +if(NOT DEFINED INPUT) + message(FATAL_ERROR "write_json_header.cmake: INPUT not defined") +endif() +if(NOT DEFINED OUTPUT) + message(FATAL_ERROR "write_json_header.cmake: OUTPUT not defined") +endif() + +find_package(Python3 COMPONENTS Interpreter REQUIRED) + +execute_process( + COMMAND ${Python3_EXECUTABLE} -c "import json,sys;print(json.dumps(json.load(open(sys.argv[1])),separators=(', ',':')) )" ${INPUT} + OUTPUT_VARIABLE MINIFIED_JSON + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +if(NOT MINIFIED_JSON) + message(FATAL_ERROR "write_json_header.cmake: failed to minify ${INPUT}") +endif() + +file(WRITE ${OUTPUT} "#pragma once\nstatic const char MTR_LINE_STATION_JSON[] = R\"json(${MINIFIED_JSON})json\";\n") diff --git a/main/common/constants.h b/main/common/constants.h new file mode 100644 index 0000000..894b1a7 --- /dev/null +++ b/main/common/constants.h @@ -0,0 +1,17 @@ +#pragma once +// 800x480 = 384,000 pixels + +#define EINK_WIDTH 800 +#define EINK_HEIGHT 480 + +#define CORE_0 0 +#define CORE_1 1 + +#define SYSTEM_SHUTDOWN_BIT (1 << 0) +#define SYSTEM_RESTART_BIT (1 << 1) +#define SYSTEM_START_BIT (1 << 2) +// +#define DISPLAY_READY_BIT (1 << 1) +#define TOUCH_CALIBRATED_BIT (1 << 2) +#define STORAGE_READY_BIT (1 << 3) +#define NETWORK_READY_BIT (1 << 4) \ No newline at end of file diff --git a/main/common/queue_defs.h b/main/common/queue_defs.h new file mode 100644 index 0000000..ac9d9ef --- /dev/null +++ b/main/common/queue_defs.h @@ -0,0 +1,31 @@ +#pragma once +#include + +typedef enum { + CMD_DISPLAY_UPDATE, + CMD_SAVE_DATA, + CMD_LOAD_DATA, + CMD_REFRESH_DISPLAY, + CMD_SYSTEM_STATUS, +} cmd_type_t; + +typedef struct { + cmd_type_t type; + uint32_t id; + void* data; + size_t len; + QueueHandle_t reply_to; // NULL if one-way +} async_cmd_t; + +extern QueueHandle_t command_queue; + +typedef struct { + uint16_t x, y; + lv_indev_state_t state; // LV_INDEV_STATE_PR/REL + uint32_t timestamp; + uint8_t gesture; // TAP, SWIPE, LONG_PRESS +} touch_event_t; + +extern QueueHandle_t touch_queue; + +extern EventGroupHandle_t system_event_group; \ No newline at end of file diff --git a/main/common/semaphore_guard.h b/main/common/semaphore_guard.h new file mode 100644 index 0000000..14aabec --- /dev/null +++ b/main/common/semaphore_guard.h @@ -0,0 +1,49 @@ +#pragma once +#include "freertos/semphr.h" +#include "freertos/portmacro.h" +#include "esp_log.h" + +struct SemaphoreGuard { +public: + + SemaphoreGuard(SemaphoreHandle_t semaphore) : semaphore(semaphore) { } + + portBASE_TYPE take(TickType_t ticks_to_wait = portMAX_DELAY) { + if (this->semaphore == nullptr) { + ESP_LOGE("SemaphoreGuard", "Attempted to take a null semaphore"); + return pdFALSE; + } + portBASE_TYPE result = xSemaphoreTake(this->semaphore, ticks_to_wait); + taken = (result == pdTRUE); + return result; + } + + ~SemaphoreGuard() { + if (taken) { + xSemaphoreGive(this->semaphore); + } + } + + // allow move semantics + SemaphoreGuard(SemaphoreGuard&& other) noexcept + : semaphore(other.semaphore), taken(other.taken) { + other.taken = false; + } + SemaphoreGuard& operator=(SemaphoreGuard&& other) noexcept { + if (this != &other) { + // move from other + taken = other.taken; + other.taken = false; + semaphore = other.semaphore; + other.semaphore = nullptr; + } + return *this; + } + +private: + // prevent copying + SemaphoreGuard(const SemaphoreGuard&) = delete; + SemaphoreGuard& operator=(const SemaphoreGuard&) = delete; + SemaphoreHandle_t semaphore = nullptr; + bool taken = false; +}; diff --git a/main/display/constants.h b/main/display/constants.h new file mode 100644 index 0000000..d41bfb0 --- /dev/null +++ b/main/display/constants.h @@ -0,0 +1,14 @@ +#pragma once +#include "driver/spi_master.h" +#include "driver/gpio.h" + +#define PIN_TOUCH_IRQ GPIO_NUM_4 +#define PIN_TOUCH_SDA GPIO_NUM_5 +#define PIN_TOUCH_SCL GPIO_NUM_6 +#define PIN_BUSY GPIO_NUM_7 +#define PIN_RST GPIO_NUM_8 +#define PIN_DC GPIO_NUM_9 +#define PIN_CS GPIO_NUM_10 +#define PIN_MOSI GPIO_NUM_11 +#define PIN_SCK GPIO_NUM_12 +#define PIN_TOUCH_RST GPIO_NUM_13 diff --git a/main/display/display.cpp.old b/main/display/display.cpp.old new file mode 100644 index 0000000..679d506 --- /dev/null +++ b/main/display/display.cpp.old @@ -0,0 +1,199 @@ +#include "display/display.h" +#include "common/constants.h" +#include "esp_log.h" +#include "esp_lcd_touch_gt911.h" + +#define BUSY_ACTIVE_LEVEL 0 // BUSY pin is active low +#define BUSY_INACTIVE_LEVEL 1 + +DisplayHandler::~DisplayHandler() { + if (_spi_mutex != nullptr) { + vSemaphoreDelete(_spi_mutex); + } + if (_spi != nullptr) { + spi_bus_remove_device(_spi); + } + if (_tp_handle != nullptr) { + esp_lcd_touch_del(_tp_handle); + } + if (_tp_io_handle != nullptr) { + esp_lcd_panel_io_del(_tp_io_handle); + } +} + +void DisplayHandler::init_devices(bool set_display_ready /*= true*/) { + ESP_LOGI("DisplayHandler", "Initializing display and touch..."); + _epd_init(); + _touch_init(); + ESP_LOGI("DisplayHandler", "Display and touch initialized."); + if (set_display_ready) { + ESP_LOGI("DisplayHandler", "Setting display ready bit."); + xEventGroupSetBits(_system_event_group, DISPLAY_READY_BIT | TOUCH_CALIBRATED_BIT); + } +} + + +void DisplayHandler::epd_write_cmd(uint8_t cmd) { + ESP_LOGI("DisplayHandler", "epd_write_cmd: waiting to send 0x%02X", cmd); + if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) { + ESP_LOGE("DisplayHandler", "SPI mutex timeout for cmd 0x%02X", cmd); + return; + } + _dangerous_epd_write_cmd_without_lock(cmd); + xSemaphoreGive(_spi_mutex); + ESP_LOGI("DisplayHandler", "epd_write_cmd: 0x%02X done", cmd); +} + +void DisplayHandler::epd_write_data(uint8_t data) { + ESP_LOGI("DisplayHandler", "epd_write_data: waiting to send 0x%02X", data); + if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) { + ESP_LOGE("DisplayHandler", "SPI mutex timeout for data 0x%02X", data); + return; + } + _dangerous_epd_write_data_without_lock(data); + xSemaphoreGive(_spi_mutex); + ESP_LOGI("DisplayHandler", "epd_write_data: 0x%02X done", data); +} + +void DisplayHandler::epd_write_cmd_with_data(uint8_t cmd, const uint8_t* data, size_t data_len) { + ESP_LOGI("DisplayHandler", "epd_write_cmd_with_data: waiting to send cmd 0x%02X with %u bytes of data", cmd, (unsigned)data_len); + if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) { + ESP_LOGE("DisplayHandler", "SPI mutex timeout for cmd with data 0x%02X", cmd); + return; + } + _dangerous_epd_write_cmd_without_lock(cmd); + for (size_t i = 0; i < data_len; ++i) { + _dangerous_epd_write_data_without_lock(data[i]); + } + xSemaphoreGive(_spi_mutex); + ESP_LOGI("DisplayHandler", "epd_write_cmd_with_data: cmd 0x%02X with %u bytes of data done", cmd, (unsigned)data_len); +} + +// +// Private methods +// + +void DisplayHandler::_dangerous_epd_write_cmd_without_lock(uint8_t cmd) { + ESP_LOGI("DisplayHandler", "_dangerous_epd_write_cmd_without_lock: sending 0x%02X", cmd); + gpio_set_level(PIN_DC, 0); // Command mode + spi_transaction_t t {}; + t.length = 8;t.tx_buffer = &cmd; + esp_err_t err = spi_device_polling_transmit(_spi, &t); + if (err != ESP_OK) { + ESP_LOGE("DisplayHandler", "Failed to send data 0x%02X", cmd); + } else { + ESP_LOGI("DisplayHandler", "_dangerous_epd_write_cmd_without_lock: 0x%02X sent", cmd); + } +} + +void DisplayHandler::_dangerous_epd_write_data_without_lock(uint8_t data) { + ESP_LOGI("DisplayHandler", "_dangerous_epd_write_data_without_lock: sending 0x%02X", data); + gpio_set_level(PIN_DC, 1); // Data mode + spi_transaction_t t = { }; + t.length = 8; t.tx_buffer = &data; + esp_err_t err = spi_device_polling_transmit(_spi, &t); + if (err != ESP_OK) { + ESP_LOGE("DisplayHandler", "Failed to send data 0x%02X", data); + } else { + ESP_LOGI("DisplayHandler", "_dangerous_epd_write_data_without_lock: 0x%02X sent", data); + } +} + +// required to be called by inheriting class after SPI device is created +void DisplayHandler::_epd_init(void) { + ESP_LOGI("DisplayHandler", "Initializing EPD..."); + // 1. Hardware Reset + gpio_set_level(PIN_RST, 0); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(PIN_RST, 1); + vTaskDelay(pdMS_TO_TICKS(10)); + + // 2. Initialization Sequence + const uint8_t panel_setting_data[] = { 0x1F }; + epd_write_cmd_with_data(0x00, panel_setting_data, 1); // Panel Setting + vTaskDelay(pdMS_TO_TICKS(10)); + const uint8_t vcom_data[] = { 0x10, 0x07 }; + epd_write_cmd_with_data(0x50, vcom_data, 2); // VCOM + vTaskDelay(pdMS_TO_TICKS(10)); + epd_write_cmd(0x04); // Power ON + vTaskDelay(pdMS_TO_TICKS(100)); // Wait for power on + + // Check BUSY pin with detailed logging + ESP_LOGI("DisplayHandler", "Waiting for EPD to be ready after power on..."); + ESP_LOGI("DisplayHandler", "BUSY pin level after power on: %d (0=BUSY, 1=FREE)", gpio_get_level(PIN_BUSY)); + + int busy_timeout = 0; + while (gpio_get_level(PIN_BUSY) == BUSY_ACTIVE_LEVEL) { // BUSY is active LOW + vTaskDelay(pdMS_TO_TICKS(10)); + busy_timeout++; + if (busy_timeout > 500) { // 5 second timeout + ESP_LOGE("DisplayHandler", "EPD power on timeout! BUSY pin stuck at 0"); + break; + } + if (busy_timeout % 50 == 0) { // Log every 500ms + ESP_LOGW("DisplayHandler", "Still waiting for EPD power on, timeout: %d/500", busy_timeout); + } + } + ESP_LOGI("DisplayHandler", "EPD power on complete after %d * 10ms, BUSY pin: %d", busy_timeout, gpio_get_level(PIN_BUSY)); + const uint8_t booster_data[] = { 0x27, 0x27, 0x18, 0x17 }; + epd_write_cmd_with_data(0x06, booster_data, 4); // Booster Soft Start + vTaskDelay(pdMS_TO_TICKS(10)); + + // Enhanced display drive commands + const uint8_t e0_data[] = { 0x02 }; + epd_write_cmd_with_data(0xE0, e0_data, 1); + const uint8_t e5_data[] = { 0x5A }; + epd_write_cmd_with_data(0xE5, e5_data, 1); +} + +void DisplayHandler::_touch_init(void) { + ESP_LOGI("DisplayHandler", "Initializing touch..."); + + // 1. Initialize I2C Bus + i2c_config_t conf = {}; + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = PIN_TOUCH_SDA; + conf.scl_io_num = PIN_TOUCH_SCL; + conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf.master.clk_speed = 400000; + + i2c_param_config(I2C_NUM_0, &conf); + i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0); + ESP_LOGI("DisplayHandler", "I2C driver installed"); + + // 2. Initialize GT911 + ESP_LOGI("DisplayHandler", "Initializing GT911 touch controller..."); + esp_lcd_panel_io_i2c_config_t tp_io_config = {}; + // temporarily disable -Wmissing-field-initializers, as ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG macro does not set all fields +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + esp_lcd_panel_io_i2c_config_t default_tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); +#pragma GCC diagnostic pop + tp_io_config.dev_addr = default_tp_io_config.dev_addr; + tp_io_config.control_phase_bytes = default_tp_io_config.control_phase_bytes; + tp_io_config.dc_bit_offset = default_tp_io_config.dc_bit_offset; + tp_io_config.lcd_cmd_bits = default_tp_io_config.lcd_cmd_bits; + tp_io_config.flags = default_tp_io_config.flags; + esp_lcd_new_panel_io_i2c(I2C_NUM_0, &tp_io_config, &_tp_io_handle); + + // GT911-specific config with I2C address (0x5D = INT low during reset) + static esp_lcd_touch_io_gt911_config_t gt911_config = { + .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS // 0x5D + }; + + esp_lcd_touch_config_t tp_cfg = {}; + tp_cfg.x_max = 800; + tp_cfg.y_max = 480; + tp_cfg.rst_gpio_num = PIN_TOUCH_RST; + tp_cfg.int_gpio_num = PIN_TOUCH_IRQ; + tp_cfg.driver_data = >911_config; // Pass GT911-specific config for automatic reset + + esp_err_t touch_ret = esp_lcd_touch_new_i2c_gt911(_tp_io_handle, &tp_cfg, &_tp_handle); + if (touch_ret == ESP_OK && _tp_handle != nullptr) { + ESP_LOGI("DisplayHandler", "GT911 touch controller initialized successfully"); + } else { + ESP_LOGE("DisplayHandler", "GT911 touch controller initialization failed: %s", esp_err_to_name(touch_ret)); + _tp_handle = nullptr; + } +} diff --git a/main/display/display.h.old b/main/display/display.h.old new file mode 100644 index 0000000..dcffdf2 --- /dev/null +++ b/main/display/display.h.old @@ -0,0 +1,42 @@ +#pragma once +#include "driver/spi_master.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_lcd_touch_gt911.h" +#include "display/constants.h" +#include + +class DisplayHandler { +public: + DisplayHandler( + EventGroupHandle_t system_event_group + ) : _system_event_group(system_event_group) { } + virtual ~DisplayHandler(); + + // required to be called by inheriting class after SPI device is created + // set set_display_ready to false if further initialization is needed before marking display ready + virtual void init_devices(bool set_display_ready = true); + +protected: + // Allow derived classes to access touch handle + esp_lcd_touch_handle_t get_touch_handle() const { return _tp_handle; } + + void epd_write_cmd(uint8_t cmd); + void epd_write_data(uint8_t data); + + void epd_write_cmd_with_data(uint8_t cmd, const uint8_t* data, size_t data_len); + +protected: + SemaphoreHandle_t _spi_mutex = xSemaphoreCreateMutex(); + spi_device_handle_t _spi = nullptr; + EventGroupHandle_t _system_event_group = nullptr; + esp_lcd_panel_io_handle_t _tp_io_handle = nullptr; + esp_lcd_touch_handle_t _tp_handle = nullptr; + + void _dangerous_epd_write_cmd_without_lock(uint8_t cmd); + void _dangerous_epd_write_data_without_lock(uint8_t data); + + void _epd_init(void); + void _touch_init(void); +}; diff --git a/main/display/eink_display_handler.cpp b/main/display/eink_display_handler.cpp new file mode 100644 index 0000000..ea52f54 --- /dev/null +++ b/main/display/eink_display_handler.cpp @@ -0,0 +1,942 @@ +#include "display/eink_display_handler.h" +#include "display/constants.h" +#include "common/constants.h" +#include "esp_lcd_touch_gt911.h" +#include "esp_log.h" +#include +#include +#include "common/semaphore_guard.h" + +#define TAG "EInkDisplayHandler" +#define DISPLAY_BUFFER_SIZE (EINK_HEIGHT* EINK_WIDTH) / 8 // 1 bit per pixels +#define MINIMUM_PIN_SETUP_DELAY_MS 10 +#define MINIMUM_POWER_ON_DELAY_MS 100 +#define BUSY_ACTIVE_LEVEL 0 // BUSY pin is active low +#define BUSY_INACTIVE_LEVEL 1 +#define DMA_TRANSFER_CHUNK_SIZE 4096 // 4KB chunk size for DMA transfers + +static uint8_t white_data[DISPLAY_BUFFER_SIZE]; // all white data +static uint8_t black_data[DISPLAY_BUFFER_SIZE]; // all black data + +EInkDisplayHandler::EInkDisplayHandler() { + memset(white_data, 0xFF, sizeof(white_data)); + memset(black_data, 0x00, sizeof(black_data)); + spi_mutex_ = xSemaphoreCreateMutex(); + if (spi_mutex_ == nullptr) { + ESP_LOGE(TAG, "Failed to create SPI mutex"); + } + spi_transaction_mutex_ = xSemaphoreCreateMutex(); + if (spi_transaction_mutex_ == nullptr) { + ESP_LOGE(TAG, "Failed to create SPI transaction mutex"); + } + refresh_mutex_ = xSemaphoreCreateMutex(); + if (refresh_mutex_ == nullptr) { + ESP_LOGE(TAG, "Failed to create refresh mutex"); + } +} +EInkDisplayHandler::~EInkDisplayHandler() { + if (spi_mutex_ != nullptr) { + vSemaphoreDelete(spi_mutex_); + } + if (spi_transaction_mutex_ != nullptr) { + vSemaphoreDelete(spi_transaction_mutex_); + } + if (refresh_mutex_ != nullptr) { + vSemaphoreDelete(refresh_mutex_); + } + if (spi_ != nullptr) { + spi_bus_remove_device(spi_); + } + if (tp_handle_ != nullptr) { + esp_lcd_touch_del(tp_handle_); + } + if (tp_io_handle_ != nullptr) { + esp_lcd_panel_io_del(tp_io_handle_); + } +} + +esp_err_t EInkDisplayHandler::deep_sleep_display(void) { + ESP_LOGI(TAG, "Putting display into deep sleep mode..."); + if (is_deep_sleep_) { + ESP_LOGI(TAG, "Display is already in deep sleep mode"); + return ESP_OK; + } + { + esp_err_t err = ESP_OK; + TransactionGuard transaction_guard(*this); + err = transaction_guard.begin(pdMS_TO_TICKS(5000)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to begin transaction for deep sleep: %s", esp_err_to_name(err)); + return err; + } + wait_for_idle(); + + err = epd_write_cmd(0x02, transaction_guard.transaction_id()); // power off + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send power off command: %s", esp_err_to_name(err)); + return err; + } + wait_for_idle(); + err = epd_write_cmd(0x07, transaction_guard.transaction_id()); //deep sleep + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send deep sleep command: %s", esp_err_to_name(err)); + return err; + } + err = epd_write_data(0xA5, transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send deep sleep data: %s", esp_err_to_name(err)); + return err; + } + is_deep_sleep_ = true; + return err; + } +} + +esp_err_t EInkDisplayHandler::refresh_display() { + esp_err_t err = ESP_OK; + + if (is_deep_sleep_) { + epd_init_(); + } + + { + ESP_LOGI(TAG, "Waiting for display to be idle..."); + TransactionGuard transaction_guard(*this); + err = transaction_guard.begin(pdMS_TO_TICKS(10000)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to begin transaction for display refresh: %s", esp_err_to_name(err)); + return err; + } + wait_for_idle(); + ESP_LOGI(TAG, "Starting display refresh..."); + err = epd_write_cmd(0x92, transaction_guard.transaction_id()); // enter normal mode + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to enter normal mode: %s", esp_err_to_name(err)); + return err; + } + err = epd_write_cmd(0x12, transaction_guard.transaction_id()); // display refresh + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send display refresh command: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); // at least 200us delay + wait_for_idle(); + } + + { + SemaphoreGuard guard(refresh_mutex_); + if (guard.take(pdMS_TO_TICKS(5000)) != pdTRUE) { + ESP_LOGE(TAG, "Refresh mutex timeout in refresh_display"); + return ESP_ERR_TIMEOUT; + } + partial_refresh_count_ = 0; + force_full_refresh_ = false; + } + + err = deep_sleep_display(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to enter deep sleep after refresh: %s", esp_err_to_name(err)); + return err; + } + + ESP_LOGI(TAG, "Refresh complete"); + return ESP_OK; +} + +esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool white_basemap) { + ESP_LOGI(TAG, "Starting full refresh (3 seconds)..."); + esp_err_t err = ESP_OK; + + if (is_deep_sleep_) { + epd_init_(); + } + + { + TransactionGuard transaction_guard(*this); + err = transaction_guard.begin(pdMS_TO_TICKS(10000)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to begin transaction for full refresh: %s", esp_err_to_name(err)); + return err; + } + + wait_for_idle(); + // Step 0: Enter normal mode + err = epd_write_cmd(0x92, transaction_guard.transaction_id()); // enter normal mode + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to enter normal mode: %s", esp_err_to_name(err)); + return err; + } + // Step 1: Write old data (0x10) - Arduino uses 0xFF (all white) for base map + { + err = epd_write_cmd(0x10, transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send old data command: %s", esp_err_to_name(err)); + return err; + } + err = transfer_spi_data(white_basemap ? white_data : black_data, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send all white data (0xFF) + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send all white data for old data: %s", esp_err_to_name(err)); + return err; + } + } + + // Step 2: Write new data (0x13) + { + err = epd_write_cmd(0x13, transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send new data command: %s", esp_err_to_name(err)); + return err; + } + + err = transfer_spi_data(framebuffer, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send new framebuffer data + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send framebuffer data for new data: %s", esp_err_to_name(err)); + return err; + } + } + // Step 3: Trigger display refresh (DRF) + err = epd_write_cmd(0x12, transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send display refresh command: %s", esp_err_to_name(err)); + return err; + } + + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); // at least 200us delay + ESP_LOGI(TAG, "Display refresh triggered, BUSY pin: %d", gpio_get_level(PIN_BUSY)); + + // Wait for refresh to complete + wait_for_idle(); + } + + err = deep_sleep_display(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to enter deep sleep after full refresh: %s", esp_err_to_name(err)); + return err; + } + + ESP_LOGI(TAG, "Full refresh complete"); + return ESP_OK; +} + +esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* partial_framebuffer, const RefreshArea& area) { + ESP_LOGI(TAG, "Starting partial refresh (0.3 seconds)..."); + esp_err_t err = ESP_OK; + + // Calculate partial buffer size based on the refresh area + const uint32_t area_width_bytes = (area.x2 - area.x1 + 1) / 8; + const uint32_t area_height = area.y2 - area.y1 + 1; + const size_t partial_buffer_size = area_width_bytes * area_height; + + { + TransactionGuard transaction_guard(*this); + err = transaction_guard.begin(pdMS_TO_TICKS(5000)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to begin transaction for partial refresh: %s", esp_err_to_name(err)); + return err; + } + + // Wake display from deep sleep INSIDE the transaction to prevent race conditions + if (is_deep_sleep_) { + err = epd_init_partial_internal_(transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize EPD for partial refresh: %s", esp_err_to_name(err)); + return err; + } + } + + wait_for_idle(); + + // Step 1 VCOM setting + std::vector vcom_data = { 0xA9, 0x07 }; + err = epd_write_cmd_with_data(0x50, vcom_data, transaction_guard.transaction_id()); // VCOM for partial refresh + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set VCOM for partial refresh: %s", esp_err_to_name(err)); + return err; + } + // Step 2: Enter partial refresh mode + err = epd_write_cmd(0x91, transaction_guard.transaction_id()); // Enter partial mode + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to enter partial refresh mode: %s", esp_err_to_name(err)); + return err; + } + // Step 3: Set partial window + { + if (area.x1 % 8 != 0 || area.x2 % 8 != 7) { + ESP_LOGE(TAG, "Partial refresh area x1 and x2 must be byte-aligned (x1 %% 8 == 0 and x2 %% 8 == 7)"); + return ESP_ERR_INVALID_ARG; + } + // ------DD + // DDDDD000 + // ------DD + // DDDDD111 + // ------DD + // DDDDDDDD + // ------DD + // DDDDDDDD + // -------D + + // area should be multiple of 8 in x direction + const int32_t x_bank_start = area.x1 >> 3; + const int32_t x_bank_end = area.x2 >> 3; + std::vector window_data = { + // x start, [9:8] bit -> 6 and 7 bits of x_bank_start + static_cast((x_bank_start >> 5) & 0x03), + // x start, [7:3] bit + 3 bits of 0 -> 5 bits of x_bank_start and pad 3 LSBs as 0 + static_cast((x_bank_start & 0x1F) << 3), + // x end, [9:8] bit + static_cast((x_bank_end >> 5) & 0x03), + // x end, [7:3] bit + 3 bits of 1 + static_cast(((x_bank_end & 0x1F) << 3) | 0x07), + // y start, [9:8] bit + static_cast((area.y1 >> 8) & 0x03), + // y start, [7:0] bit + static_cast(area.y1 & 0xFF), + // y end, [9:8] bit + static_cast((area.y2 >> 8) & 0x03), + // y end, [7:0] bit + static_cast(area.y2 & 0xFF), + 0x01 // Gates scan both inside and outside of the partial window + }; + ESP_LOGI(TAG, "Setting partial window: x1=%d, y1=%d, x2=%d, y2=%d", + area.x1, area.y1, area.x2, area.y2); + ESP_LOGI(TAG, "Partial window data: %02X %02X %02X %02X %02X %02X %02X %02X", + window_data[0], window_data[1], window_data[2], window_data[3], window_data[4], + window_data[5], window_data[6], window_data[7]); + err = epd_write_cmd_with_data(0x90, window_data, transaction_guard.transaction_id()); // Set partial window + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send set partial window command: %s", esp_err_to_name(err)); + return err; + } + } + + // Step 4: Write new data (0x13) + { + err = epd_write_cmd(0x13, transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send new data command for partial refresh: %s", esp_err_to_name(err)); + return err; + } + + // Send only the partial area data, not the full display buffer + ESP_LOGI(TAG, "Sending partial buffer: %zu bytes (area: %dx%d)", + partial_buffer_size, area_width_bytes * 8, area_height); + err = transfer_spi_data(partial_framebuffer, partial_buffer_size, transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send partial_framebuffer data for partial refresh: %s", esp_err_to_name(err)); + return err; + } + } + + // Step 5: Trigger partial display refresh (DRF) by ending the data write + err = epd_write_cmd(0x11, transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send display refresh command for partial refresh: %s", esp_err_to_name(err)); + return err; + } + + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); // at least 200us delay + + wait_for_idle(); + // Step 6: Exit partial mode + err = epd_write_cmd(0x92, transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to exit partial refresh mode: %s", esp_err_to_name(err)); + return err; + } + } + ESP_LOGI(TAG, "Partial refresh complete"); + if (force_full_refresh_) { + ESP_LOGI(TAG, "Full refresh already requested, skipping partial refresh count increment"); + err = refresh_display(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to perform forced full refresh: %s", esp_err_to_name(err)); + return err; + } + return ESP_OK; + } + { + SemaphoreGuard guard(refresh_mutex_); + if (guard.take(pdMS_TO_TICKS(5000)) != pdTRUE) { + ESP_LOGE(TAG, "Refresh mutex timeout in partial_refresh"); + return ESP_ERR_TIMEOUT; + } + + if (partial_refresh_count_ < UINT32_MAX) { + partial_refresh_count_++; + } + if (partial_refresh_count_ >= PARTIAL_REFRESH_THRESHOLD) { + ESP_LOGI(TAG, "Partial refresh count %u reached threshold %u, next refresh will be full", + partial_refresh_count_, PARTIAL_REFRESH_THRESHOLD); + force_full_refresh_ = true; + partial_refresh_count_ = 0; + } + } + + err = deep_sleep_display(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to enter deep sleep after partial refresh: %s", esp_err_to_name(err)); + return err; + } + + return ESP_OK; +} + +esp_err_t EInkDisplayHandler::clear_display(void) { + ESP_LOGI(TAG, "Clearing display to all white..."); + + esp_err_t err = full_write(black_data, false); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to clear display: %s", esp_err_to_name(err)); + return err; + } + ESP_LOGI(TAG, "Display cleared to all white"); + return ESP_OK; +} + + + +// Request a full refresh on next flush +void EInkDisplayHandler::request_full_refresh(void) { + SemaphoreGuard guard(refresh_mutex_); + if (guard.take(pdMS_TO_TICKS(100))) { + force_full_refresh_ = true; + partial_refresh_count_ = 0; + ESP_LOGI(TAG, "Full refresh requested"); + } else { + ESP_LOGE(TAG, "Failed to take refresh mutex to request full refresh"); + } +} + +// Check if display is busy (refreshing) +bool EInkDisplayHandler::is_busy(void) const { + return gpio_get_level(PIN_BUSY) == BUSY_ACTIVE_LEVEL; // BUSY is active LOW +} +void EInkDisplayHandler::wait_for_idle(void) const { + ESP_LOGI(TAG, "Waiting for display ready (BUSY pin)..."); + int initial_level = gpio_get_level(PIN_BUSY); + ESP_LOGI(TAG, "Initial BUSY pin level: %d (0=BUSY, 1=FREE)", initial_level); + + // If already free, no need to wait + if (initial_level == BUSY_INACTIVE_LEVEL) { + ESP_LOGI(TAG, "Display already ready (BUSY pin = 1)"); + return; + } + while (gpio_get_level(PIN_BUSY) != BUSY_INACTIVE_LEVEL) { + vTaskDelay(pdMS_TO_TICKS(10)); + } + ESP_LOGI(TAG, "Display is now ready (BUSY pin = 1)"); +} + + +esp_err_t EInkDisplayHandler::init_devices(EventGroupHandle_t system_event_group) { + esp_err_t err; + err = init_display_pins_(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display pins: %s", esp_err_to_name(err)); + return err; + } + err = epd_init_(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize EPD: %s", esp_err_to_name(err)); + return err; + } + err = init_touch_(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize touch: %s", esp_err_to_name(err)); + return err; + } + err = deep_sleep_display(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to put display into deep sleep: %s", esp_err_to_name(err)); + return err; + } + + // if system_event_group is provided, set display ready bits + if (system_event_group != nullptr) { + // Indicate that display is ready + xEventGroupSetBits(system_event_group, DISPLAY_READY_BIT | TOUCH_CALIBRATED_BIT); + ESP_LOGI(TAG, "Display marked as ready"); + } + return ESP_OK; +} + +esp_err_t EInkDisplayHandler::init_display_pins_(void) { + ESP_LOGI(TAG, "Initializing E-Ink display handler..."); + + esp_err_t ret; + + // Initialize GPIO pins + gpio_config_t io_conf = {}; + io_conf.pin_bit_mask = (1ULL << PIN_DC) | (1ULL << PIN_RST); + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.intr_type = GPIO_INTR_DISABLE; + ret = gpio_config(&io_conf); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure GPIO pins: %s", esp_err_to_name(ret)); + return ret; + } + + // Configure BUSY pin as input (no pull-up like sample code) + io_conf.pin_bit_mask = (1ULL << PIN_BUSY); + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + ret = gpio_config(&io_conf); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure BUSY pin: %s", esp_err_to_name(ret)); + return ret; + } + + // Initialize SPI bus + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = 11; // MOSI pin + buscfg.miso_io_num = -1; // No MISO for e-paper + buscfg.sclk_io_num = 12; // SCK pin + buscfg.quadwp_io_num = -1; + buscfg.quadhd_io_num = -1; + buscfg.max_transfer_sz = DISPLAY_BUFFER_SIZE; + + ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret)); + return ret; + } + + // Add SPI device + spi_device_interface_config_t devcfg = {}; + devcfg.clock_speed_hz = 10 * 1000 * 1000; // 10 MHz + devcfg.mode = 0; // SPI mode 0 + devcfg.spics_io_num = PIN_CS; + devcfg.queue_size = 7; // Queue size for non-blocking transactions + devcfg.pre_cb = nullptr; + + ret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi_); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret)); + return ret; + } + return ESP_OK; +} + + +// required to be called by inheriting class after SPI device is created +esp_err_t EInkDisplayHandler::epd_init_(void) { + ESP_LOGI(TAG, "Initializing EPD..."); + esp_err_t err; + + { + TransactionGuard transaction_guard(*this); + esp_err_t begin_err = transaction_guard.begin(); + if (begin_err != ESP_OK) { + ESP_LOGE(TAG, "Failed to begin transaction: %s", esp_err_to_name(begin_err)); + return begin_err; + } + + // 1. Hardware Reset + err = gpio_set_level(PIN_RST, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set PIN_RST low: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + err = gpio_set_level(PIN_RST, 1); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set PIN_RST high: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + + // 2. Initialization Sequence + std::vector panel_setting_data = { 0x1F }; + err = epd_write_cmd_with_data(0x00, panel_setting_data, transaction_guard.transaction_id()); // Panel Setting + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Panel Setting command: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + std::vector vcom_data = { 0x10, 0x07 }; + err = epd_write_cmd_with_data(0x50, vcom_data, transaction_guard.transaction_id()); // VCOM + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send VCOM command: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + err = epd_write_cmd(0x04, transaction_guard.transaction_id()); // Power ON + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Power ON command: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_POWER_ON_DELAY_MS)); // Wait for power on + + // Check BUSY pin with detailed logging + ESP_LOGI(TAG, "Waiting for EPD to be ready after power on..."); + ESP_LOGI(TAG, "BUSY pin level after power on: %d (0=BUSY, 1=FREE)", gpio_get_level(PIN_BUSY)); + + int busy_timeout = 0; + while (gpio_get_level(PIN_BUSY) == BUSY_ACTIVE_LEVEL) { // BUSY is active LOW + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + busy_timeout++; + if (busy_timeout > 500) { // 5 second timeout + ESP_LOGE(TAG, "EPD power on timeout! BUSY pin stuck at 0"); + return ESP_ERR_TIMEOUT; + } + if (busy_timeout % 50 == 0) { // Log every 500ms + ESP_LOGW(TAG, "Still waiting for EPD power on, timeout: %d/500", busy_timeout); + } + } + ESP_LOGI(TAG, "EPD power on complete after %d * 10ms, BUSY pin: %d", busy_timeout, gpio_get_level(PIN_BUSY)); + std::vector booster_data = { 0x27, 0x27, 0x18, 0x17 }; + err = epd_write_cmd_with_data(0x06, booster_data, transaction_guard.transaction_id()); // Booster Soft Start + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Booster Soft Start command: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + + // Enhanced display drive commands + std::vector e0_data = { 0x02 }; + err = epd_write_cmd_with_data(0xE0, e0_data, transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command: %s", esp_err_to_name(err)); + return err; + } + std::vector e5_data = { 0x5A }; + err = epd_write_cmd_with_data(0xE5, e5_data, transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command: %s", esp_err_to_name(err)); + return err; + } + } + is_deep_sleep_ = false; + return err; +} + +esp_err_t EInkDisplayHandler::epd_init_partial_(void) { + TransactionGuard transaction_guard(*this); + esp_err_t begin_err = transaction_guard.begin(); + if (begin_err != ESP_OK) { + ESP_LOGE(TAG, "Failed to begin transaction: %s", esp_err_to_name(begin_err)); + return begin_err; + } + return epd_init_partial_internal_(transaction_guard.transaction_id()); +} + +// Internal version that uses an existing transaction (no separate TransactionGuard) +esp_err_t EInkDisplayHandler::epd_init_partial_internal_(uint32_t transaction_id) { + ESP_LOGI(TAG, "Initializing EPD for partial refresh (internal)..."); + esp_err_t err = ESP_OK; + + // 1. Hardware Reset + err = gpio_set_level(PIN_RST, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set PIN_RST low: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + err = gpio_set_level(PIN_RST, 1); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set PIN_RST high: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + + // 2. Panel Setting + std::vector panel_setting_data = { 0x1F }; + err = epd_write_cmd_with_data(0x00, panel_setting_data, transaction_id); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Panel Setting command: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + + // 3. Power ON + err = epd_write_cmd(0x04, transaction_id); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Power ON command: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_POWER_ON_DELAY_MS)); + wait_for_idle(); + + // 4. Partial initialization sequence - Enhanced Display Drive + std::vector e0_data = { 0x02 }; + err = epd_write_cmd_with_data(0xE0, e0_data, transaction_id); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command (E0): %s", esp_err_to_name(err)); + return err; + } + + std::vector e5_data = { 0x6E }; + err = epd_write_cmd_with_data(0xE5, e5_data, transaction_id); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command (E5): %s", esp_err_to_name(err)); + return err; + } + + is_deep_sleep_ = false; + ESP_LOGI(TAG, "EPD partial init (internal) complete"); + return ESP_OK; +} + +esp_err_t EInkDisplayHandler::init_touch_() { + ESP_LOGI(TAG, "Initializing touch..."); + esp_err_t err; + + // 1. Initialize I2C Bus + i2c_config_t conf = {}; + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = PIN_TOUCH_SDA; + conf.scl_io_num = PIN_TOUCH_SCL; + conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf.master.clk_speed = 400000; + + err = i2c_param_config(I2C_NUM_0, &conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure I2C parameters: %s", esp_err_to_name(err)); + return err; + } + err = i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to install I2C driver: %s", esp_err_to_name(err)); + return err; + } + ESP_LOGI("DisplayHandler", "I2C driver installed"); + + // 2. Initialize GT911 + ESP_LOGI("DisplayHandler", "Initializing GT911 touch controller..."); + esp_lcd_panel_io_i2c_config_t tp_io_config = {}; + // temporarily disable -Wmissing-field-initializers, as ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG macro does not set all fields +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + esp_lcd_panel_io_i2c_config_t default_tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); +#pragma GCC diagnostic pop + tp_io_config.dev_addr = default_tp_io_config.dev_addr; + tp_io_config.control_phase_bytes = default_tp_io_config.control_phase_bytes; + tp_io_config.dc_bit_offset = default_tp_io_config.dc_bit_offset; + tp_io_config.lcd_cmd_bits = default_tp_io_config.lcd_cmd_bits; + tp_io_config.flags = default_tp_io_config.flags; + esp_lcd_new_panel_io_i2c(I2C_NUM_0, &tp_io_config, &tp_io_handle_); + + // GT911-specific config with I2C address (0x5D = INT low during reset) + static esp_lcd_touch_io_gt911_config_t gt911_config = { + .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS // 0x5D + }; + + esp_lcd_touch_config_t tp_cfg = {}; + tp_cfg.x_max = DISPLAY_WIDTH; + tp_cfg.y_max = DISPLAY_HEIGHT; + tp_cfg.rst_gpio_num = PIN_TOUCH_RST; + tp_cfg.int_gpio_num = PIN_TOUCH_IRQ; + tp_cfg.driver_data = >911_config; // Pass GT911-specific config for automatic reset + + err = esp_lcd_touch_new_i2c_gt911(tp_io_handle_, &tp_cfg, &tp_handle_); + if (err == ESP_OK && tp_handle_ != nullptr) { + ESP_LOGI("DisplayHandler", "GT911 touch controller initialized successfully"); + } else { + ESP_LOGE("DisplayHandler", "GT911 touch controller initialization failed: %s", esp_err_to_name(err)); + tp_handle_ = nullptr; + } + return err; +} + + +esp_err_t EInkDisplayHandler::epd_write_cmd(const uint8_t cmd, uint32_t transaction_id) { + ESP_LOGI(TAG, "epd_write_cmd: waiting to send 0x%02X", cmd); + + SemaphoreGuard transaction_guard(spi_transaction_mutex_); + esp_err_t err = + wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending cmd 0x%02X: %s", + cmd, esp_err_to_name(err)); + return err; + } + + SemaphoreGuard guard(spi_mutex_); + if (!guard.take(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "SPI mutex timeout for cmd 0x%02X", cmd); + return ESP_ERR_TIMEOUT; + } + err = dangerous_epd_write_cmd_without_lock_(cmd); + ESP_LOGI(TAG, "epd_write_cmd: 0x%02X done", cmd); + return err; +} + +esp_err_t EInkDisplayHandler::epd_write_data(const uint8_t data, uint32_t transaction_id) { + ESP_LOGI(TAG, "epd_write_data: waiting to send 0x%02X", data); + SemaphoreGuard transaction_guard(spi_transaction_mutex_); + esp_err_t err = + wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending data 0x%02X: %s", + data, esp_err_to_name(err)); + return err; + } + SemaphoreGuard guard(spi_mutex_); + if (!guard.take(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "SPI mutex timeout for data 0x%02X", data); + return ESP_ERR_TIMEOUT; + } + err = dangerous_epd_write_data_without_lock_(data); + ESP_LOGI(TAG, "epd_write_data: 0x%02X done", data); + return err; +} + +esp_err_t EInkDisplayHandler::epd_write_cmd_with_data(const uint8_t cmd, std::vector& data, uint32_t transaction_id) { + const size_t data_len = data.size(); + ESP_LOGI(TAG, "epd_write_cmd_with_data: waiting to send cmd 0x%02X with %u bytes of data", cmd, data_len); + + SemaphoreGuard transaction_guard(spi_transaction_mutex_); + esp_err_t err = + wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending cmd 0x%02X: %s, with data", + cmd, esp_err_to_name(err)); + return err; + } + + SemaphoreGuard guard(spi_mutex_); + if (!guard.take(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "SPI mutex timeout for cmd with data 0x%02X", cmd); + return ESP_ERR_TIMEOUT; + } + err = dangerous_epd_write_cmd_without_lock_(cmd); + if (err != ESP_OK) { + return err; + }; + for (size_t i = 0; i < data_len; ++i) { + err = dangerous_epd_write_data_without_lock_(data[i]); + if (err != ESP_OK) { + return err; + } + } + ESP_LOGI(TAG, "epd_write_cmd_with_data: cmd 0x%02X with %u bytes of data done", cmd, data_len); + return ESP_OK; +} + + +esp_err_t EInkDisplayHandler::dangerous_epd_write_cmd_without_lock_(const uint8_t cmd) { + ESP_LOGI(TAG, "dangerous_epd_write_cmd_without_lock_: sending 0x%02X", cmd); + gpio_set_level(PIN_DC, 0); // Command mode + spi_transaction_t t {}; + t.length = 8;t.tx_buffer = &cmd; + esp_err_t err = spi_device_polling_transmit(spi_, &t); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send data 0x%02X", cmd); + } else { + ESP_LOGI(TAG, "dangerous_epd_write_cmd_without_lock_: 0x%02X sent", cmd); + } + return err; +} + +esp_err_t EInkDisplayHandler::dangerous_epd_write_data_without_lock_(const uint8_t data) { + ESP_LOGI(TAG, "dangerous_epd_write_data_without_lock_: sending 0x%02X", data); + gpio_set_level(PIN_DC, 1); // Data mode + spi_transaction_t t = { }; + t.length = 8; t.tx_buffer = &data; + esp_err_t err = spi_device_polling_transmit(spi_, &t); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send data 0x%02X", data); + } else { + ESP_LOGI(TAG, "dangerous_epd_write_data_without_lock_: 0x%02X sent", data); + } + return err; +} + +esp_err_t EInkDisplayHandler::transfer_spi_data(const uint8_t* data, const size_t& length, uint32_t transaction_id) { + ESP_LOGI(TAG, "transfer_spi_data: waiting to send %zu bytes of data", length); + + SemaphoreGuard transaction_guard(spi_transaction_mutex_); + esp_err_t err = + wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending data of %zu bytes: %s", + length, esp_err_to_name(err)); + return err; + } + SemaphoreGuard guard(spi_mutex_); + if (!guard.take(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "SPI mutex timeout for data transfer of %zu bytes", length); + return ESP_ERR_TIMEOUT; + } + ESP_LOGI(TAG, "transfer_spi_data: starting to send %zu bytes of data", length); + + size_t offset = 0; + size_t remaining = length; + gpio_set_level(PIN_DC, 1); // Data mode + while (remaining > 0) { + size_t transfer_size = (remaining < DMA_TRANSFER_CHUNK_SIZE) ? remaining : DMA_TRANSFER_CHUNK_SIZE; + + spi_transaction_t t = {}; + t.length = transfer_size * 8; // Length in bits + t.tx_buffer = data + offset; + + esp_err_t ret = spi_device_polling_transmit(spi_, &t); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to send SPI chunk at offset %zu: %s", offset, esp_err_to_name(ret)); + return ret; + } + + remaining -= transfer_size; + offset += transfer_size; + + // Yield every 16KB to prevent watchdog timeout + if (offset % (16 * 1024) == 0) { + ESP_LOGI(TAG, "New data progress: %zu/%zu bytes sent, yielding...", offset, length); + vTaskDelay(pdMS_TO_TICKS(1)); + } + } + + ESP_LOGI(TAG, "transfer_spi_data: completed sending %zu bytes of data", length); + return ESP_OK; +} + +esp_err_t EInkDisplayHandler::begin_transaction_(TickType_t timeout, uint32_t& out_id) { + ESP_LOGI(TAG, "begin_transaction_: waiting to obtain transaction mutex"); + if (xSemaphoreTake(spi_transaction_mutex_, timeout) != pdTRUE) { + ESP_LOGE(TAG, "begin_transaction_: transaction mutex timeout"); + return ESP_ERR_TIMEOUT; + } + + out_id = ++spi_transaction_id; + ESP_LOGI(TAG, "begin_transaction_: transaction mutex obtained"); + return ESP_OK; +} + +esp_err_t EInkDisplayHandler::end_transaction_(void) { + ESP_LOGI(TAG, "end_transaction_: releasing transaction mutex"); + if (xSemaphoreGive(spi_transaction_mutex_) != pdTRUE) { + ESP_LOGE(TAG, "end_transaction_: failed to release transaction mutex"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "end_transaction_: transaction mutex released"); + return ESP_OK; +} + +esp_err_t EInkDisplayHandler::wait_for_transaction_end_(TickType_t timeout, uint32_t awaiting_transaction_id, SemaphoreGuard& out_transaction_guard) { + // Validate transaction ID if provided + if (awaiting_transaction_id != 0 && awaiting_transaction_id != spi_transaction_id) { + // Invalid transaction ID + ESP_LOGE(TAG, "Invalid transaction ID 0x%08X while waiting, current transaction ID: 0x%08X", + awaiting_transaction_id, spi_transaction_id); + return ESP_ERR_INVALID_ARG; + } + SemaphoreGuard transaction_guard(spi_transaction_mutex_); + if (awaiting_transaction_id == 0) { + // wait for current transaction to complete + ESP_LOGV(TAG, "Waiting for current transaction 0x%08X to complete", + spi_transaction_id); + // take the mutex to ensure no transaction is active + if (!transaction_guard.take(timeout)) { + ESP_LOGE(TAG, "SPI transaction mutex timeout while waiting for transaction end"); + return ESP_ERR_TIMEOUT; + } + } + // awaited_transaction_id is valid and matches current transaction ID or 0 + out_transaction_guard = std::move(transaction_guard); + return ESP_OK; +} \ No newline at end of file diff --git a/main/display/eink_display_handler.cpp.old b/main/display/eink_display_handler.cpp.old new file mode 100644 index 0000000..8f6d855 --- /dev/null +++ b/main/display/eink_display_handler.cpp.old @@ -0,0 +1,661 @@ +#include "display/eink_display_handler.h" +#include "display/constants.h" +#include "common/constants.h" +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "esp_task_wdt.h" +#include + +#define TAG "EInkDisplayHandler" +#define BUSY_ACTIVE_LEVEL 0 // BUSY pin is active low +#define BUSY_INACTIVE_LEVEL 1 + +EInkDisplayHandler::EInkDisplayHandler(EventGroupHandle_t system_event_group) + : DisplayHandler(system_event_group) { + _refresh_mutex = xSemaphoreCreateMutex(); + if (_refresh_mutex == nullptr) { + ESP_LOGE(TAG, "Failed to create refresh mutex"); + } +} + +EInkDisplayHandler::~EInkDisplayHandler() { + if (_refresh_task_handle != nullptr) { + vTaskDelete(_refresh_task_handle); + } + if (_touch_task_handle != nullptr) { + vTaskDelete(_touch_task_handle); + } + if (_refresh_queue != nullptr) { + vQueueDelete(_refresh_queue); + } + if (_lvgl_display != nullptr) { + lv_display_delete(_lvgl_display); + _lvgl_display = nullptr; + if (_lvgl_draw_buf != nullptr) { + lv_draw_buf_destroy(_lvgl_draw_buf); + _lvgl_draw_buf = nullptr; + } + } + if (_lvgl_touch_indev != nullptr) { + lvgl_port_remove_touch(_lvgl_touch_indev); + } + if (_framebuffer != nullptr) { + heap_caps_free(_framebuffer); + } + if (_refresh_mutex != nullptr) { + vSemaphoreDelete(_refresh_mutex); + } +} + +void EInkDisplayHandler::init() { + ESP_LOGI(TAG, "Initializing E-Ink display handler..."); + + // Initialize GPIO pins + gpio_config_t io_conf = {}; + io_conf.pin_bit_mask = (1ULL << PIN_DC) | (1ULL << PIN_RST); + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.intr_type = GPIO_INTR_DISABLE; + gpio_config(&io_conf); + + // Configure BUSY pin as input (no pull-up like sample code) + io_conf.pin_bit_mask = (1ULL << PIN_BUSY); + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + // Initialize SPI bus + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = 11; // MOSI pin + buscfg.miso_io_num = -1; // No MISO for e-paper + buscfg.sclk_io_num = 12; // SCK pin + buscfg.quadwp_io_num = -1; + buscfg.quadhd_io_num = -1; + buscfg.max_transfer_sz = DISPLAY_BUFFER_SIZE; + + esp_err_t ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret)); + return; + } + + // Add SPI device + spi_device_interface_config_t devcfg = {}; + devcfg.clock_speed_hz = 6 * 1000 * 1000; // 6 MHz (reduced for reliability) + devcfg.mode = 0; // SPI mode 0 + devcfg.spics_io_num = PIN_CS; + devcfg.queue_size = 7; // Queue size for non-blocking transactions + devcfg.pre_cb = nullptr; + + ret = spi_bus_add_device(SPI2_HOST, &devcfg, &_spi); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret)); + return; + } + + // Initialize base display and touch devices + init_devices(false); // Don't set ready bit yet + + // Create refresh queue (queue 5 refresh requests) + _refresh_queue = xQueueCreate(5, sizeof(bool)); + if (_refresh_queue == nullptr) { + ESP_LOGE(TAG, "Failed to create refresh queue"); + return; + } + + // Create refresh task + BaseType_t ret_task = xTaskCreatePinnedToCore( + _refresh_task, + "eink_refresh", + 8192, + this, + 5, // Priority - lower than LVGL task + &_refresh_task_handle, + 1 // Pin to core 1 + ); + if (ret_task != pdPASS) { + ESP_LOGE(TAG, "Failed to create refresh task"); + return; + } + + // Allocate framebuffer - try PSRAM first, fallback to internal RAM + // Note: Internal framebuffer excludes the 8-byte palette (raw pixel data only) + const size_t fb_size = DISPLAY_BUFFER_SIZE - 8; // Exclude palette from internal storage + _framebuffer = (uint8_t*)heap_caps_malloc(fb_size, MALLOC_CAP_SPIRAM); + if (_framebuffer != nullptr) { + _framebuffer_in_psram = true; + ESP_LOGI(TAG, "Framebuffer allocated in PSRAM (%zu bytes, LVGL buffer: %d bytes)", + fb_size, DISPLAY_BUFFER_SIZE); + } else { + ESP_LOGW(TAG, "PSRAM not available, allocating framebuffer in internal RAM"); + _framebuffer = (uint8_t*)heap_caps_malloc(fb_size, MALLOC_CAP_INTERNAL); + _framebuffer_in_psram = false; + if (_framebuffer == nullptr) { + ESP_LOGE(TAG, "Failed to allocate framebuffer"); + return; + } + ESP_LOGI(TAG, "Framebuffer allocated in internal RAM (%zu bytes, LVGL buffer: %d bytes)", + fb_size, DISPLAY_BUFFER_SIZE); + } + memset(_framebuffer, 0xFF, fb_size); // Initialize to white + + // Perform initial full refresh to clear display BEFORE creating LVGL display + // This prevents LVGL from trying to render during the initial clear + ESP_LOGI(TAG, "Performing initial display clear..."); + _perform_full_refresh(_framebuffer); + ESP_LOGI(TAG, "Initial display clear complete"); + + // Create LVGL display manually (no esp_lcd panel for e-paper) + lv_display_t* disp = lv_display_create(DISPLAY_WIDTH, DISPLAY_HEIGHT); + if (disp == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL display"); + return; + } + + /* 1-bit e-paper display */ + lv_display_set_color_format(disp, LV_COLOR_FORMAT_I1); + + /* Disable antialiasing for monochrome display to ensure crisp 1px lines */ + lv_display_set_antialiasing(disp, false); + + /* Create a draw buffer covering ~40 lines */ + _lvgl_draw_buf = lv_draw_buf_create(DISPLAY_WIDTH, DISPLAY_HEIGHT, LV_COLOR_FORMAT_I1, LV_STRIDE_AUTO); + if (_lvgl_draw_buf == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL draw buffer"); + lv_display_delete(disp); + return; + } + + lv_display_set_draw_buffers(disp, _lvgl_draw_buf, NULL); + lv_display_set_render_mode(disp, LV_DISPLAY_RENDER_MODE_DIRECT); + + // Set custom flush callback and user data + lv_display_set_flush_cb(disp, _lvgl_flush_cb); + lv_display_set_user_data(disp, this); + + _lvgl_display = disp; + + ESP_LOGI(TAG, "LVGL display registered"); + + // Register GT911 touch input with LVGL, only if touch handle is valid + esp_lcd_touch_handle_t tp_handle = get_touch_handle(); + if (tp_handle == nullptr) { + ESP_LOGE(TAG, "Touch handle is NULL — touch initialization failed; skipping LVGL touch registration"); + } else { + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = _lvgl_display, + .handle = tp_handle, + .scale = {}, // Default scaling + }; + + _lvgl_touch_indev = lvgl_port_add_touch(&touch_cfg); + if (_lvgl_touch_indev == nullptr) { + ESP_LOGE(TAG, "Failed to register LVGL touch input"); + return; + } + + // Override touch read callback to check BUSY pin + lv_indev_set_read_cb(_lvgl_touch_indev, _lvgl_touch_read_cb); + lv_indev_set_user_data(_lvgl_touch_indev, this); + + ESP_LOGI(TAG, "LVGL touch input registered"); + } + + // Set display ready bits + xEventGroupSetBits(_system_event_group, DISPLAY_READY_BIT | TOUCH_CALIBRATED_BIT); + ESP_LOGI(TAG, "E-Ink display handler initialized successfully"); +} + +void EInkDisplayHandler::start_touch_task() { + // Note: With lvgl_port_add_touch, the ESP-IDF LVGL port handles touch reading internally + // We don't need a separate touch task unless we want custom processing + ESP_LOGI(TAG, "Touch input handled by LVGL port"); +} + +void EInkDisplayHandler::request_full_refresh() { + if (xSemaphoreTake(_refresh_mutex, pdMS_TO_TICKS(100)) == pdTRUE) { + _force_full_refresh = true; + _partial_refresh_count = 0; + xSemaphoreGive(_refresh_mutex); + ESP_LOGI(TAG, "Full refresh requested"); + } +} + +bool EInkDisplayHandler::is_busy() const { + return gpio_get_level(PIN_BUSY) == BUSY_ACTIVE_LEVEL; // BUSY is active LOW +} + +void EInkDisplayHandler::_lvgl_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) { + EInkDisplayHandler* handler = static_cast(lv_display_get_user_data(disp)); + if (handler == nullptr) { + ESP_LOGE(TAG, "Invalid handler in flush callback"); + lv_display_flush_ready(disp); + return; + } + + // Check if display is busy with detailed logging + int busy_level = gpio_get_level(PIN_BUSY); + ESP_LOGI(TAG, "Flush callback: BUSY pin = %d, is_busy() = %d", busy_level, handler->is_busy()); + + if (handler->is_busy()) { + ESP_LOGW(TAG, "Display busy (BUSY pin = 0), skipping flush"); + lv_display_flush_ready(disp); + return; + } + + // Wait for any ongoing refresh to complete + handler->_wait_for_busy(); + + bool perform_full_refresh = false; + + if (xSemaphoreTake(handler->_refresh_mutex, pdMS_TO_TICKS(100)) == pdTRUE) { + // Check if full refresh is needed + if (handler->_force_full_refresh) { + perform_full_refresh = true; + handler->_force_full_refresh = false; + handler->_partial_refresh_count = 0; + } else { + handler->_partial_refresh_count++; + if (handler->_partial_refresh_count >= PARTIAL_REFRESH_THRESHOLD) { + perform_full_refresh = true; + handler->_partial_refresh_count = 0; + } + } + xSemaphoreGive(handler->_refresh_mutex); + } + + // Copy LVGL buffer to framebuffer + // For 1-bit mode, LVGL provides data in packed format (8 pixels per byte) + // CRITICAL: Skip first 8 bytes (LVGL I1 palette) as per LVGL documentation + uint8_t* pixel_data = px_map + 8; // Skip 8-byte palette + int32_t w = lv_area_get_width(area); + int32_t h = lv_area_get_height(area); + + ESP_LOGI(TAG, "Flushing area: x=%d, y=%d, w=%d, h=%d, full_refresh=%d", + area->x1, area->y1, w, h, perform_full_refresh); + ESP_LOGI(TAG, "Buffer: px_map=%p, pixel_data=%p, palette skipped: %d bytes", + (void*)px_map, (void*)pixel_data, 8); + + // Check if this is a full screen update - if so, simple copy + if (area->x1 == 0 && area->y1 == 0 && w == DISPLAY_WIDTH && h == DISPLAY_HEIGHT) { + ESP_LOGI(TAG, "Full screen update, direct copy (skipping palette)"); + memcpy(handler->_framebuffer, pixel_data, DISPLAY_BUFFER_SIZE - 8); + } else { + ESP_LOGI(TAG, "Partial area update"); + // In DIRECT render mode, px_map points to the full screen buffer + // The stride is always the full display width + const uint32_t stride = DISPLAY_WIDTH / 8; // 800 / 8 = 100 bytes per row + + // Check if we can do row-by-row copy (byte-aligned on both x1 and width) + bool byte_aligned = (area->x1 % 8 == 0) && (w % 8 == 0); + + if (byte_aligned) { + // Optimized: byte-aligned row copy + ESP_LOGI(TAG, "Byte-aligned copy: x=%ld, y=%ld, w=%ld, h=%ld", + (long)area->x1, (long)area->y1, (long)w, (long)h); + + uint32_t x_byte = area->x1 / 8; + uint32_t width_bytes = w / 8; + + for (int32_t y = 0; y < h; y++) { + int32_t fb_y = area->y1 + y; + if (fb_y >= DISPLAY_HEIGHT) break; + + uint8_t* src = pixel_data + (fb_y * stride + x_byte); + uint8_t* dst = handler->_framebuffer + (fb_y * stride + x_byte); + memcpy(dst, src, width_bytes); + } + } else { + // Bit-level copy for non-aligned regions + ESP_LOGI(TAG, "Bit-level copy: x=%ld, y=%ld, w=%ld, h=%ld", + (long)area->x1, (long)area->y1, (long)w, (long)h); + + for (int32_t y = 0; y < h; y++) { + int32_t fb_y = area->y1 + y; + if (fb_y >= DISPLAY_HEIGHT) break; + + for (int32_t x = 0; x < w; x++) { + int32_t fb_x = area->x1 + x; + if (fb_x >= DISPLAY_WIDTH) break; + + // Get pixel from source buffer (using full screen coordinates) + size_t src_byte_idx = fb_y * stride + (fb_x / 8); + size_t src_bit_idx = fb_x % 8; + uint8_t src_bit = (pixel_data[src_byte_idx] >> (7 - src_bit_idx)) & 0x01; + + // Set pixel in destination buffer + size_t dst_byte_idx = fb_y * stride + (fb_x / 8); + size_t dst_bit_idx = fb_x % 8; + + if (dst_byte_idx < (DISPLAY_BUFFER_SIZE - 8)) { + if (src_bit) { + handler->_framebuffer[dst_byte_idx] |= (1 << (7 - dst_bit_idx)); + } else { + handler->_framebuffer[dst_byte_idx] &= ~(1 << (7 - dst_bit_idx)); + } + } + } + } + } + } + + // Queue refresh request (non-blocking) + if (handler->_refresh_queue != nullptr) { + if (xQueueSend(handler->_refresh_queue, &perform_full_refresh, 0) != pdPASS) { + ESP_LOGW(TAG, "Refresh queue full, skipping refresh"); + } else { + ESP_LOGI(TAG, "Queued %s refresh", perform_full_refresh ? "full" : "partial"); + } + } + + lv_display_flush_ready(disp); +} + +void EInkDisplayHandler::_lvgl_touch_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { + EInkDisplayHandler* handler = static_cast(lv_indev_get_user_data(indev)); + + // Disable touch input during display refresh (BUSY) + if (handler->is_busy()) { + data->state = LV_INDEV_STATE_RELEASED; + data->continue_reading = false; + return; + } + + esp_lcd_touch_handle_t tp_handle = handler->get_touch_handle(); + if (tp_handle == nullptr) { + data->state = LV_INDEV_STATE_RELEASED; + return; + } + + + // Read touch data from GT911 + esp_err_t ret = esp_lcd_touch_read_data(tp_handle); + if (ret == ESP_OK) { + uint8_t touch_cnt = 0; + // Get touch data using new API + esp_lcd_touch_point_data_t point_data[1]; + esp_lcd_touch_get_data(tp_handle, point_data, &touch_cnt, 1); + + if (touch_cnt > 0) { + ESP_LOGI(TAG, "Touch data read successfully: x=%d, y=%d", point_data[0].x, point_data[0].y); + data->point.x = point_data[0].x; + data->point.y = point_data[0].y; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + + data->continue_reading = false; +} + +void EInkDisplayHandler::_perform_full_refresh(const uint8_t* framebuffer) { + ESP_LOGI(TAG, "Starting full refresh (3 seconds)..."); + + _wait_for_busy(); + + // Step 1: Write old data (0x10) - Arduino uses 0xFF (all white) for base map + epd_write_cmd(0x10); + + if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) { + ESP_LOGE(TAG, "SPI mutex timeout in full refresh step 1"); + return; + } + gpio_set_level(PIN_DC, 1); // Data mode + + ESP_LOGI(TAG, "Starting SPI data transmission for old data (0x10)..."); + + // Send 0xFF (white) for all old data, matching Arduino EPD_SetRAMValue_BaseMap + // Use DMA transfers in chunks for better performance + static uint8_t white_buffer[4096]; // 4KB chunk buffer + memset(white_buffer, 0xFF, sizeof(white_buffer)); + + const size_t CHUNK_SIZE = sizeof(white_buffer); + size_t remaining = DISPLAY_BUFFER_SIZE - 8; // Exclude palette from transmission + size_t offset = 0; + + while (remaining > 0) { + size_t transfer_size = (remaining < CHUNK_SIZE) ? remaining : CHUNK_SIZE; + + spi_transaction_t t = {}; + t.length = transfer_size * 8; // Length in bits + t.tx_buffer = white_buffer; + + esp_err_t ret = spi_device_polling_transmit(_spi, &t); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to send SPI chunk at offset %zu: %s", offset, esp_err_to_name(ret)); + break; + } + + remaining -= transfer_size; + offset += transfer_size; + + // Yield every 16KB to prevent watchdog timeout + if (offset % (16 * 1024) == 0) { + ESP_LOGI(TAG, "Old data progress: %zu/%zu bytes (%.1f%%)", offset, remaining, + (float)offset * 100.0f / (float)remaining); + vTaskDelay(pdMS_TO_TICKS(1)); + } + } + ESP_LOGI(TAG, "Completed SPI data transmission for old data"); + xSemaphoreGive(_spi_mutex); + + // Step 2: Write new data (0x13) + epd_write_cmd(0x13); + + if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) { + ESP_LOGE(TAG, "SPI mutex timeout in full refresh step 2"); + return; + } + gpio_set_level(PIN_DC, 1); // Data mode + + ESP_LOGI(TAG, "Starting SPI data transmission for new data (0x13)..."); + + // Send actual framebuffer data in chunks using DMA for better performance + offset = 0; + remaining = DISPLAY_BUFFER_SIZE - 8; // Reset remaining for step 2 + + while (remaining > 0) { + size_t transfer_size = (remaining < CHUNK_SIZE) ? remaining : CHUNK_SIZE; + + spi_transaction_t t = {}; + t.length = transfer_size * 8; // Length in bits + t.tx_buffer = framebuffer + offset; + + esp_err_t ret = spi_device_polling_transmit(_spi, &t); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to send SPI chunk at offset %zu: %s", offset, esp_err_to_name(ret)); + break; + } + + remaining -= transfer_size; + offset += transfer_size; + + // Yield every 16KB to prevent watchdog timeout + if (offset % (16 * 1024) == 0) { + ESP_LOGI(TAG, "New data progress: %zu/%zu bytes (%.1f%%)", offset, remaining, + (float)offset * 100.0f / (float)remaining); + vTaskDelay(pdMS_TO_TICKS(1)); + } + } + + ESP_LOGI(TAG, "Completed SPI data transmission for new data"); + xSemaphoreGive(_spi_mutex); + + // Step 3: Trigger display refresh (DRF) + epd_write_cmd(0x12); + // Critical delay - sample code says "!!!The delay here is necessary, 200uS at least!!!" + vTaskDelay(pdMS_TO_TICKS(10)); + ESP_LOGI(TAG, "Display refresh triggered, BUSY pin: %d", gpio_get_level(PIN_BUSY)); + + // Wait for refresh to complete + _wait_for_busy(); + + ESP_LOGI(TAG, "Full refresh complete"); +} + +void EInkDisplayHandler::_perform_partial_refresh(const uint8_t* framebuffer) { + ESP_LOGI(TAG, "Starting partial refresh (0.3 seconds)..."); + + _wait_for_busy(); + + // Step 1: Configure VCOM for partial refresh + const uint8_t vcom_data[] = { 0xA9, 0x07 }; + epd_write_cmd_with_data(0x50, vcom_data, 2); + + // Step 2: Enter partial refresh mode + epd_write_cmd(0x91); + + // Step 3: Define partial window (full screen for now) + // Format: 0x90 + 9 bytes (x_start_H, x_start_L, x_end_H, x_end_L, y_start_H, y_start_L, y_end_H, y_end_L, 0x01) + // For full screen: x=0 to 799 (0x031F), y=0 to 479 (0x01DF) + const uint8_t window_data[] = { + 0x00, 0x00, // x_start = 0 + 0x03, 0x1F, // x_end = 799 (0x31F) + 0x00, 0x00, // y_start = 0 + 0x01, 0xDF, // y_end = 479 (0x1DF) + 0x01 // PT_SCAN + }; + epd_write_cmd_with_data(0x90, window_data, 9); + + // Step 4: Write new data (0x13 command) + epd_write_cmd(0x13); + + if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) { + ESP_LOGE(TAG, "SPI mutex timeout in partial refresh"); + return; + } + gpio_set_level(PIN_DC, 1); // Data mode + + ESP_LOGI(TAG, "Starting SPI data transmission for partial refresh..."); + + // Send framebuffer data in chunks using DMA for better performance + const size_t CHUNK_SIZE = 4096; // 4KB chunks + size_t remaining = DISPLAY_BUFFER_SIZE - 8; // Exclude palette from transmission + size_t offset = 0; + + while (remaining > 0) { + size_t transfer_size = (remaining < CHUNK_SIZE) ? remaining : CHUNK_SIZE; + + spi_transaction_t t = {}; + t.length = transfer_size * 8; // Length in bits + t.tx_buffer = framebuffer + offset; + + esp_err_t ret = spi_device_polling_transmit(_spi, &t); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to send SPI chunk at offset %zu: %s", offset, esp_err_to_name(ret)); + break; + } + + remaining -= transfer_size; + offset += transfer_size; + + // Yield every 16KB to prevent watchdog timeout + if (offset % (16 * 1024) == 0) { + ESP_LOGI(TAG, "Partial refresh progress: %zu/%zu bytes (%.1f%%)", offset, remaining, + (float)offset * 100.0f / (float)remaining); + vTaskDelay(pdMS_TO_TICKS(1)); + } + } + ESP_LOGI(TAG, "Completed SPI data transmission for partial refresh"); + xSemaphoreGive(_spi_mutex); + + // Step 5: Trigger partial display refresh (DRF) + epd_write_cmd(0x12); + // Critical delay - sample code says "!!!The delay here is necessary, 200uS at least!!!" + vTaskDelay(pdMS_TO_TICKS(10)); + ESP_LOGI(TAG, "Partial refresh triggered, BUSY pin: %d", gpio_get_level(PIN_BUSY)); + + // Wait for refresh to complete + _wait_for_busy(); + + // Step 6: Exit partial refresh mode + epd_write_cmd(0x92); + + ESP_LOGI(TAG, "Partial refresh complete"); +} + +void EInkDisplayHandler::_refresh_task(void* param) { + EInkDisplayHandler* handler = static_cast(param); + bool perform_full_refresh = false; + + ESP_LOGI(TAG, "Refresh task started"); + + while (true) { + // Wait for refresh request + if (xQueueReceive(handler->_refresh_queue, &perform_full_refresh, portMAX_DELAY) == pdTRUE) { + // Perform the requested refresh type + if (perform_full_refresh) { + ESP_LOGI(TAG, "Refresh task: Performing full refresh..."); + handler->_perform_full_refresh(handler->_framebuffer); + } else { + ESP_LOGI(TAG, "Refresh task: Performing partial refresh..."); + handler->_perform_partial_refresh(handler->_framebuffer); + } + } + } +} + +void EInkDisplayHandler::_wait_for_busy() { + ESP_LOGI(TAG, "Waiting for display ready (BUSY pin)..."); + int initial_level = gpio_get_level(PIN_BUSY); + ESP_LOGI(TAG, "Initial BUSY pin level: %d (0=BUSY, 1=FREE)", initial_level); + + // If already free, no need to wait + if (initial_level == BUSY_INACTIVE_LEVEL) { + ESP_LOGI(TAG, "Display already ready (BUSY pin = 1)"); + return; + } + + int timeout = 0; + while (gpio_get_level(PIN_BUSY) == BUSY_ACTIVE_LEVEL) { // 0=BUSY, 1=FREE + vTaskDelay(pdMS_TO_TICKS(100)); + timeout++; + if (timeout > 100) { // 10 second timeout + ESP_LOGE(TAG, "Display BUSY timeout! Pin level: %d", gpio_get_level(PIN_BUSY)); + ESP_LOGW(TAG, "Attempting hardware reset..."); + + // Hardware reset sequence + gpio_set_level(PIN_RST, 0); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(PIN_RST, 1); + vTaskDelay(pdMS_TO_TICKS(100)); + + // Re-initialize display + ESP_LOGI(TAG, "Re-initializing display after reset..."); + _epd_init(); + + // Check if reset worked + int reset_timeout = 0; + while (gpio_get_level(PIN_BUSY) == BUSY_ACTIVE_LEVEL) { + vTaskDelay(pdMS_TO_TICKS(100)); + reset_timeout++; + if (reset_timeout > 50) { // 5 second timeout after reset + ESP_LOGE(TAG, "Display reset failed! Still busy after reset."); + break; + } + } + + if (gpio_get_level(PIN_BUSY) != BUSY_ACTIVE_LEVEL) { + ESP_LOGI(TAG, "Display reset successful after %d tenths of a second", reset_timeout); + } + break; + } + + // Log every 2 seconds to track progress + if (timeout % 20 == 0) { + ESP_LOGW(TAG, "Still waiting for BUSY pin, timeout: %d/100, level: %d", + timeout, gpio_get_level(PIN_BUSY)); + } + } + ESP_LOGI(TAG, "Display ready after %d tenths of a second", timeout); +} + +void EInkDisplayHandler::_convert_buffer_to_epaper(const uint8_t* lvgl_buf, uint8_t* epd_buf, size_t size) { + // LVGL 1-bit format is already compatible with e-paper + // Just copy directly + memcpy(epd_buf, lvgl_buf, size); +} diff --git a/main/display/eink_display_handler.h b/main/display/eink_display_handler.h new file mode 100644 index 0000000..ea26c3f --- /dev/null +++ b/main/display/eink_display_handler.h @@ -0,0 +1,126 @@ +#pragma once +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_lcd_touch_gt911.h" +#include "common/semaphore_guard.h" +#include +#include + +// Refresh mode configuration +#define PARTIAL_REFRESH_THRESHOLD 10 // Full refresh every N partial refreshes +#define DISPLAY_WIDTH 800 +#define DISPLAY_HEIGHT 480 + +// forward declarations +class EInkDisplayHandler; + +struct RefreshArea { +public: + RefreshArea(int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end) + : x1(x_start), y1(y_start), x2(x_end), y2(y_end) { } + int32_t x1; + int32_t y1; + int32_t x2; + int32_t y2; + // reset to empty area + void reset() { + x1 = y1 = x2 = y2 = 0; + } + // expand area to include another area + void expand_to_include(const RefreshArea& other) { + expand_to_include(other.x1, other.y1, other.x2, other.y2); + } + void expand_to_include(int32_t x1, int32_t y1, int32_t x2, int32_t y2) { + const bool force_update = is_empty(); + if (x1 < this->x1 || force_update) this->x1 = x1; + if (y1 < this->y1 || force_update) this->y1 = y1; + if (x2 > this->x2 || force_update) this->x2 = x2; + if (y2 > this->y2 || force_update) this->y2 = y2; + } + bool is_empty() const { + return (x1 == 0 && y1 == 0 && x2 == 0 && y2 == 0); + } + uint32_t area() const { + if (is_empty()) return 0; + return (x2 - x1 + 1) * (y2 - y1 + 1); + } +}; + +class EInkDisplayHandler { +public: + EInkDisplayHandler(); + virtual ~EInkDisplayHandler(); + + esp_err_t init_devices(EventGroupHandle_t system_event_group = nullptr); + + + esp_err_t refresh_display(void); + esp_err_t full_write(const uint8_t* framebuffer, const bool white_basemap = true); + esp_err_t partial_refresh(const uint8_t* framebuffer, const RefreshArea& area); + esp_err_t clear_display(void); + esp_err_t deep_sleep_display(void); + // Request a full refresh on next flush + void request_full_refresh(void); + + // Check if display is busy (refreshing) + bool is_busy(void) const; + void wait_for_idle(void) const; + + esp_lcd_touch_handle_t get_touch_handle() const { return tp_handle_; } + +protected: + esp_err_t epd_write_cmd(const uint8_t cmd, uint32_t transaction_id); + esp_err_t epd_write_data(const uint8_t data, uint32_t transaction_id); + esp_err_t epd_write_cmd_with_data(const uint8_t cmd, std::vector& data, uint32_t transaction_id); + esp_err_t transfer_spi_data(const uint8_t* data, const size_t& length, uint32_t transaction_id); + +private: + + esp_err_t init_display_pins_(void); + esp_err_t epd_init_(void); // full fast refresh init + esp_err_t epd_init_partial_(void); // partial refresh init (standalone) + esp_err_t epd_init_partial_internal_(uint32_t transaction_id); // partial refresh init (within existing transaction) + esp_err_t init_touch_(void); + esp_err_t dangerous_epd_write_cmd_without_lock_(const uint8_t cmd); + esp_err_t dangerous_epd_write_data_without_lock_(const uint8_t data); + + esp_err_t begin_transaction_(TickType_t timeout, uint32_t& out_id); + esp_err_t end_transaction_(void); + // given a transaction ID, wait for current transaction to complete. The transaction ID will determine if the wait is needed. + esp_err_t wait_for_transaction_end_(TickType_t timeout, uint32_t awaiting_transaction_id, SemaphoreGuard& out_transaction_guard); + + friend class TransactionGuard; + + uint32_t partial_refresh_count_ = 0; + bool force_full_refresh_ = false; + std::atomic is_deep_sleep_ { false }; + + SemaphoreHandle_t spi_mutex_ = nullptr; + SemaphoreHandle_t spi_transaction_mutex_ = nullptr; + SemaphoreHandle_t refresh_mutex_ = nullptr; + uint32_t spi_transaction_id = 0; // For tracking SPI transactions + spi_device_handle_t spi_ = nullptr; + esp_lcd_panel_io_handle_t tp_io_handle_ = nullptr; + esp_lcd_touch_handle_t tp_handle_ = nullptr; +}; + + +class TransactionGuard { +public: + TransactionGuard(EInkDisplayHandler& handler, TickType_t timeout = portMAX_DELAY) + : handler_(handler) { } + ~TransactionGuard() { if (transaction_id_) handler_.end_transaction_(); } + + esp_err_t begin(TickType_t timeout = portMAX_DELAY) { + esp_err_t err = handler_.begin_transaction_(timeout, transaction_id_); + return err; + } + uint32_t transaction_id() const { return transaction_id_; } + bool is_active() const { return transaction_id_ != 0; } +private: + // delete copy constructor and assignment operator + TransactionGuard(const TransactionGuard&) = delete; + TransactionGuard& operator=(const TransactionGuard&) = delete; + EInkDisplayHandler& handler_; + uint32_t transaction_id_ = 0; +}; diff --git a/main/display/eink_display_handler.h.old b/main/display/eink_display_handler.h.old new file mode 100644 index 0000000..8497e10 --- /dev/null +++ b/main/display/eink_display_handler.h.old @@ -0,0 +1,66 @@ +#pragma once +#include "display/display.h" +#include "lvgl.h" +#include "esp_lvgl_port.h" +#include "freertos/semphr.h" + +// Refresh mode configuration +#define PARTIAL_REFRESH_THRESHOLD 10 // Full refresh every N partial refreshes +#define DISPLAY_WIDTH 800 +#define DISPLAY_HEIGHT 480 +#define DISPLAY_BUFFER_SIZE (((DISPLAY_WIDTH * DISPLAY_HEIGHT) / 8) + 8) // 1-bit per pixel + 8-byte palette + +class EInkDisplayHandler : public DisplayHandler { +public: + EInkDisplayHandler(EventGroupHandle_t system_event_group); + virtual ~EInkDisplayHandler(); + + void init(); + void start_touch_task(); + + // Request a full refresh on next flush + void request_full_refresh(); + + // Check if display is busy (refreshing) + bool is_busy() const; + +private: + // LVGL display and input device handles + lv_display_t* _lvgl_display = nullptr; + lv_indev_t* _lvgl_touch_indev = nullptr; + lv_draw_buf_t* _lvgl_draw_buf = nullptr; + + // Framebuffer + uint8_t* _framebuffer = nullptr; + bool _framebuffer_in_psram = false; + + // Refresh tracking + uint32_t _partial_refresh_count = 0; + bool _force_full_refresh = false; + SemaphoreHandle_t _refresh_mutex = nullptr; + + // Touch task + TaskHandle_t _touch_task_handle = nullptr; + + // Refresh task and queue + TaskHandle_t _refresh_task_handle = nullptr; + QueueHandle_t _refresh_queue = nullptr; + + // LVGL callbacks + static void _lvgl_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map); + static void _lvgl_touch_read_cb(lv_indev_t* indev, lv_indev_data_t* data); + + // Display operations + void _perform_full_refresh(const uint8_t* framebuffer); + void _perform_partial_refresh(const uint8_t* framebuffer); + void _wait_for_busy(); + + // Touch task + static void _touch_task(void* param); + + // Refresh task + static void _refresh_task(void* param); + + // Helper to convert LVGL 1-bit buffer to e-paper format + void _convert_buffer_to_epaper(const uint8_t* lvgl_buf, uint8_t* epd_buf, size_t size); +}; diff --git a/main/display/lvgl_handler.cpp b/main/display/lvgl_handler.cpp new file mode 100644 index 0000000..26bcdb1 --- /dev/null +++ b/main/display/lvgl_handler.cpp @@ -0,0 +1,390 @@ +#include "display/lvgl_handler.h" +#include "esp_log.h" +#include "common/semaphore_guard.h" +#include "common/constants.h" +#include + +#define DISPLAY_BUFFER_SIZE (DISPLAY_WIDTH * DISPLAY_HEIGHT) / 8 // 1 bit per pixels +#define LVGL_BUFFER_SIZE (DISPLAY_BUFFER_SIZE + 8) // 1 bit per pixels + 8 bytes for palette +#define LV_DISPLAY_RENDER_MODE LV_DISPLAY_RENDER_MODE_PARTIAL +#define TAG "LVGLHandler" + +LVGLHandler::LVGLHandler( + std::unique_ptr display_handler_in +) : display_handler_(std::move(display_handler_in)) { + lvgl_mutex_ = xSemaphoreCreateMutex(); + if (lvgl_mutex_ == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL mutex"); + } +} + +LVGLHandler::~LVGLHandler() { + if (lvgl_display_ != nullptr) { + lv_display_delete(lvgl_display_); + lvgl_display_ = nullptr; + } + if (lvgl_touch_indev_ != nullptr) { + lvgl_port_remove_touch(lvgl_touch_indev_); + lvgl_touch_indev_ = nullptr; + } + if (lvgl_draw_buf_ != nullptr) { + lv_draw_buf_destroy(lvgl_draw_buf_); + lvgl_draw_buf_ = nullptr; + } + if (framebuffer_ != nullptr) { + heap_caps_free(framebuffer_); + framebuffer_ = nullptr; + } + if (lvgl_mutex_ != nullptr) { + vSemaphoreDelete(lvgl_mutex_); + lvgl_mutex_ = nullptr; + } +} + +esp_err_t LVGLHandler::initLVGL(EventGroupHandle_t system_event_group) { + esp_err_t err = initLVGLPort_(); + if (err != ESP_OK) { + return err; + } + err = initLVGLDisplay_(); + if (err != ESP_OK) { + return err; + } + err = registerLVGLTouch_(); + if (err != ESP_OK) { + return err; + } + + + auto lvgl_tick_timer_callback = [](TimerHandle_t xTimer) { + lv_tick_inc(5); + }; + TickType_t lvgl_tick_period = pdMS_TO_TICKS(5); + if (lvgl_tick_period == 0) { + lvgl_tick_period = 1; // ensure at least 1 tick to avoid FreeRTOS assert + } + ESP_LOGV(TAG, "Creating LVGL tick timer with period %u ticks...\n", (unsigned)lvgl_tick_period); + TimerHandle_t lvgl_tick_timer = xTimerCreate( + "lvgl_tick_timer", + lvgl_tick_period, + pdTRUE, + NULL, + lvgl_tick_timer_callback + ); + if (lvgl_tick_timer == NULL) { + ESP_LOGE("Main", "Failed to create LVGL tick timer"); + vTaskDelay(5000 / portTICK_PERIOD_MS); + return ESP_ERR_NO_MEM; + } + ESP_LOGV(TAG, "Starting LVGL tick timer...\n"); + xTimerStart(lvgl_tick_timer, 0); + + if (system_event_group != nullptr) { + xEventGroupSetBits(system_event_group, DISPLAY_READY_BIT | TOUCH_CALIBRATED_BIT); + } + + return ESP_OK; +} + +// +// Private methods +// + +void LVGLHandler::rounder_cb_(lv_display_t* disp, lv_area_t* area) { + // align x to byte boundary + area->x1 = (area->x1 & ~0x7); + area->x2 = (area->x2 | 0x7); +} + +void LVGLHandler::flush_cb_(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) { + if (disp == nullptr || area == nullptr || px_map == nullptr) { + ESP_LOGE(TAG, "Null parameters in flush callback"); + if (disp != nullptr) lv_display_flush_ready(disp); + return; + } + LVGLHandler* handler = static_cast(lv_display_get_user_data(disp)); + if (handler == nullptr || handler->display_handler_ == nullptr || handler->framebuffer_ == nullptr) { + ESP_LOGE(TAG, "Invalid handler or framebuffer in flush callback"); + lv_display_flush_ready(disp); + return; + } + uint8_t* pixel_data = px_map + 8; // Skip palette + // + ESP_LOGI(TAG, "Flush callback: x1=%d, y1=%d, x2=%d, y2=%d", area->x1, area->y1, area->x2, area->y2); + // take mutex + SemaphoreGuard guard(handler->lvgl_mutex_); + if (!guard.take(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "LVGL mutex timeout in flush callback"); + lv_display_flush_ready(disp); + return; + } + + // copy data to framebuffer + int32_t area_w = lv_area_get_width(area); + int32_t area_h = lv_area_get_height(area); + if (area->x1 == 0 && area->y1 == 0 && area_w == DISPLAY_WIDTH && area_h == DISPLAY_HEIGHT) { + // Check if content actually changed before triggering expensive e-ink refresh + if (memcmp(handler->framebuffer_, pixel_data, DISPLAY_BUFFER_SIZE) == 0) { + ESP_LOGD(TAG, "Full screen flush with no changes - skipping e-ink refresh"); + lv_display_flush_ready(disp); + return; + } + ESP_LOGI(TAG, "Full screen update"); + memcpy(handler->framebuffer_, pixel_data, DISPLAY_BUFFER_SIZE); + // invert the framebuffer for e-ink display + for (size_t i = 0; i < DISPLAY_BUFFER_SIZE; ++i) { + handler->framebuffer_[i] = ~handler->framebuffer_[i]; + } + // request full refresh + esp_err_t err = handler->display_handler_->full_write(handler->framebuffer_, true); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Full refresh request failed: %s", esp_err_to_name(err)); + } + } else { + // partial update + ESP_LOGI(TAG, "Partial update: x1=%d, y1=%d, w=%d, h=%d", area->x1, area->y1, area_w, area_h); + // update the framebuffer with the partial data + for (int32_t row = 0; row < area_h; ++row) { + int32_t fb_y = area->y1 + row; + int32_t fb_x_byte_start = area->x1 / 8; + int32_t fb_x_byte_end = area->x2 / 8; + uint8_t* fb_ptr = &handler->framebuffer_[fb_y * (DISPLAY_WIDTH / 8) + fb_x_byte_start]; + const uint8_t* src_ptr = &pixel_data[row * (area_w / 8)]; + // invert the partial framebuffer data for e-ink display + for (int32_t i = 0; i < (fb_x_byte_end - fb_x_byte_start + 1); ++i) { + fb_ptr[i] = ~src_ptr[i]; + } + } + // update the refresh area + handler->refresh_area_.expand_to_include(area->x1, area->y1, area->x2, area->y2); + // + + if (lv_display_flush_is_last(disp) && !handler->refresh_area_.is_empty()) { + ESP_LOGI(TAG, "Last flush in batch - performing partial refresh"); + ESP_LOGI(TAG, "Refresh area: x1=%d, y1=%d, x2=%d, y2=%d", + handler->refresh_area_.x1, handler->refresh_area_.y1, + handler->refresh_area_.x2, handler->refresh_area_.y2); + // copy the area to refresh + uint8_t* partial_buffer = new uint8_t[handler->refresh_area_.area() / 8]; + if (partial_buffer == nullptr) { + ESP_LOGE(TAG, "Failed to allocate partial buffer for refresh"); + lv_display_flush_ready(disp); + return; + } + // loop the refresh area and copy data + uint32_t x1 = handler->refresh_area_.x1; + uint32_t x2 = handler->refresh_area_.x2; + uint32_t y1 = handler->refresh_area_.y1; + uint32_t y2 = handler->refresh_area_.y2; + uint32_t height = y2 - y1 + 1; + uint32_t width = x2 - x1 + 1; + + for (uint32_t row = 0; row < height; ++row) { + uint32_t fb_y = y1 + row; + uint32_t fb_x_byte_start = x1 / 8; + uint32_t fb_x_byte_end = x2 / 8; + uint8_t* fb_ptr = &handler->framebuffer_[fb_y * (DISPLAY_WIDTH / 8) + fb_x_byte_start]; + uint8_t* dest_ptr = &partial_buffer[row * (width / 8)]; + for (uint32_t i = 0; i < (fb_x_byte_end - fb_x_byte_start + 1); ++i) { + dest_ptr[i] = ~fb_ptr[i]; + } + } + + esp_err_t err = handler->display_handler_->partial_refresh(partial_buffer, + handler->refresh_area_); + delete[] partial_buffer; + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Partial refresh request failed: %s", esp_err_to_name(err)); + } + handler->refresh_area_.reset(); + } + } + // + lv_display_flush_ready(disp); +} + +void LVGLHandler::touch_read_cb_(lv_indev_t* indev, lv_indev_data_t* data) { + LVGLHandler* handler = static_cast(lv_indev_get_user_data(indev)); + if (handler == nullptr || handler->display_handler_ == nullptr) { + data->state = LV_INDEV_STATE_RELEASED; + ESP_LOGE(TAG, "Invalid handler in touch read callback"); + return; + } + + // Disable touch input during display refresh (BUSY) + if (handler->display_handler_->is_busy()) { + data->state = LV_INDEV_STATE_RELEASED; + data->continue_reading = false; + return; + } + + esp_lcd_touch_handle_t tp_handle = handler->display_handler_->get_touch_handle(); + if (tp_handle == nullptr) { + data->state = LV_INDEV_STATE_RELEASED; + return; + } + + // Read touch data from GT911 + esp_err_t ret = esp_lcd_touch_read_data(tp_handle); + if (ret == ESP_OK) { + uint8_t touch_cnt = 0; + // Get touch data using new API + esp_lcd_touch_point_data_t point_data[1]; + esp_lcd_touch_get_data(tp_handle, point_data, &touch_cnt, 1); + + if (touch_cnt > 0) { + ESP_LOGI(TAG, "Touch data read successfully: x=%d, y=%d", point_data[0].x, point_data[0].y); + data->point.x = point_data[0].x; + data->point.y = point_data[0].y; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + + data->continue_reading = false; +} + +esp_err_t LVGLHandler::initLVGLDisplay_() { + if (display_handler_ == nullptr) { + return ESP_ERR_INVALID_STATE; + } + esp_err_t err = ESP_OK; + + // Lock LVGL to prevent the timer task from accessing partially initialized display + if (!lvgl_port_lock(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "Failed to lock LVGL port for display initialization"); + return ESP_ERR_TIMEOUT; + } + + // Create LVGL display + lvgl_display_ = lv_display_create(DISPLAY_WIDTH, DISPLAY_HEIGHT); + if (lvgl_display_ == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL display"); + lvgl_port_unlock(); + return ESP_FAIL; + } + + // set framebuffer + framebuffer_ = (uint8_t*)heap_caps_malloc(LVGL_BUFFER_SIZE, MALLOC_CAP_SPIRAM); + if (framebuffer_ != nullptr) { + framebuffer_in_psram_ = true; + ESP_LOGI(TAG, "Framebuffer allocated in PSRAM (%zu bytes)", LVGL_BUFFER_SIZE); + } else { + ESP_LOGW(TAG, "PSRAM not available, allocating framebuffer in internal RAM"); + framebuffer_ = (uint8_t*)heap_caps_malloc(LVGL_BUFFER_SIZE, MALLOC_CAP_INTERNAL); + framebuffer_in_psram_ = false; + if (framebuffer_ == nullptr) { + ESP_LOGE(TAG, "Failed to allocate framebuffer"); + lv_display_delete(lvgl_display_); + lvgl_display_ = nullptr; + lvgl_port_unlock(); + return ESP_FAIL; + } + ESP_LOGI(TAG, "Framebuffer allocated in internal RAM (%zu bytes)", LVGL_BUFFER_SIZE); + } + memset(framebuffer_, 0xFF, LVGL_BUFFER_SIZE); // Initialize to white + // Create a draw buffer covering the entire display + lvgl_draw_buf_ = lv_draw_buf_create(DISPLAY_WIDTH, DISPLAY_HEIGHT, LV_COLOR_FORMAT_I1, LV_STRIDE_AUTO); + if (lvgl_draw_buf_ == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL draw buffer"); + heap_caps_free(framebuffer_); + framebuffer_ = nullptr; + lv_display_delete(lvgl_display_); + lvgl_display_ = nullptr; + lvgl_port_unlock(); + return ESP_FAIL; + } + lv_display_set_draw_buffers(lvgl_display_, lvgl_draw_buf_, nullptr); + lv_display_set_render_mode(lvgl_display_, LV_DISPLAY_RENDER_MODE); + // + // Configure LVGL display + lv_display_set_color_format(lvgl_display_, LV_COLOR_FORMAT_I1); + lv_display_set_user_data(lvgl_display_, this); + + lv_display_add_event_cb(lvgl_display_, [](lv_event_t* e) { + LVGLHandler* handler = static_cast(lv_display_get_user_data(static_cast(lv_event_get_target(e)))); + if (handler != nullptr) { + handler->rounder_cb_(static_cast(lv_event_get_target(e)), + static_cast(lv_event_get_param(e))); + } else { + ESP_LOGE(TAG, "Invalid handler in rounder callback"); + } + }, LV_EVENT_INVALIDATE_AREA, lvgl_display_); + + lv_display_set_flush_cb(lvgl_display_, [](lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) { + LVGLHandler* handler = static_cast(lv_display_get_user_data(disp)); + if (handler != nullptr) { + handler->flush_cb_(disp, area, px_map); + } else { + lv_display_flush_ready(disp); + } + }); + + // Unlock LVGL now that display is fully initialized + + ESP_LOGI(TAG, "Performing initial display write..."); + // err = display_handler_->full_write(framebuffer_, false); + err = display_handler_->clear_display(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Initial display write failed: %d", err); + } else { + ESP_LOGI(TAG, "Initial display write complete"); + } + + lvgl_port_unlock(); + + ESP_LOGI(TAG, "LVGL display registered"); + return err; +} + +esp_err_t LVGLHandler::registerLVGLTouch_() { + if (display_handler_ == nullptr) { + return ESP_ERR_INVALID_STATE; + } + esp_lcd_touch_handle_t tp_handle = display_handler_->get_touch_handle(); + if (tp_handle == nullptr) { + ESP_LOGE(TAG, "Touch handle is NULL — touch initialization failed; skipping LVGL touch registration"); + return ESP_FAIL; + } + + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lvgl_display_, + .handle = tp_handle, + .scale = {}, // Default scaling + }; + + lvgl_touch_indev_ = lvgl_port_add_touch(&touch_cfg); + if (lvgl_touch_indev_ == nullptr) { + ESP_LOGE(TAG, "Failed to register LVGL touch input"); + return ESP_FAIL; + } + + lv_indev_set_user_data(lvgl_touch_indev_, this); + lv_indev_set_read_cb(lvgl_touch_indev_, [](lv_indev_t* indev, lv_indev_data_t* data) { + LVGLHandler* handler = static_cast(lv_indev_get_user_data(indev)); + if (handler != nullptr) { + handler->touch_read_cb_(indev, data); + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + }); + + ESP_LOGI(TAG, "LVGL touch input registered"); + return ESP_OK; +} + +esp_err_t LVGLHandler::initLVGLPort_() { + const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + esp_err_t err = lvgl_port_init(&lvgl_cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "LVGL port initialization failed: %s", esp_err_to_name(err)); + vTaskDelay(5000 / portTICK_PERIOD_MS); + return ESP_ERR_INVALID_STATE; + } + ESP_LOGI(TAG, "LVGL port initialized successfully.\n"); + return ESP_OK; +} diff --git a/main/display/lvgl_handler.h b/main/display/lvgl_handler.h new file mode 100644 index 0000000..e80aa2d --- /dev/null +++ b/main/display/lvgl_handler.h @@ -0,0 +1,40 @@ +#pragma once +#include "lvgl.h" +#include "esp_lvgl_port.h" +#include "display/eink_display_handler.h" +#include "freertos/semphr.h" +#include "freertos/event_groups.h" +#include "esp_err.h" +#include + +class LVGLHandler { +public: + LVGLHandler( + // an owning pointer to the display handler + // The display handler must outlive the LVGLHandler + // The display handler must be fully initialized before calling initLVGLDisplay + std::unique_ptr display_handler_in + ); + ~LVGLHandler(); + esp_err_t initLVGL(EventGroupHandle_t system_event_group = nullptr); + +private: + void rounder_cb_(lv_display_t* disp, lv_area_t* area); + void flush_cb_(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map); + void touch_read_cb_(lv_indev_t* indev, lv_indev_data_t* data); + esp_err_t initLVGLDisplay_(); + esp_err_t registerLVGLTouch_(); + esp_err_t initLVGLPort_(); + + std::unique_ptr display_handler_ = nullptr; + + lv_display_t* lvgl_display_ = nullptr; + lv_indev_t* lvgl_touch_indev_ = nullptr; + lv_draw_buf_t* lvgl_draw_buf_ = nullptr; + uint8_t* framebuffer_ = nullptr; + bool framebuffer_in_psram_ = false; + RefreshArea refresh_area_ = { 0, 0, 0, 0 }; + + + SemaphoreHandle_t lvgl_mutex_ = nullptr; +}; diff --git a/main/external/mtr/arrival.cpp b/main/external/mtr/arrival.cpp new file mode 100644 index 0000000..0fe398d --- /dev/null +++ b/main/external/mtr/arrival.cpp @@ -0,0 +1,98 @@ +#include "external/mtr/arrival.h" +#include "cJSON.h" +#include "esp_log.h" +#include + +static const char* TAG = "StationArrivalInfo"; + +StationArrivalInfo::StationArrivalInfo( + cJSON* mtr_line_station_json, + cJSON* arrival_json, + const std::string& train_line_code, + const std::string& train_station_code +) : _status(UNKNOWN_STATUS) +, _train_line(train_line_code) +, _train_station(train_station_code) { + + if (!arrival_json) { + ESP_LOGE(TAG, "arrival_json is null"); + _status = FAILED_WITH_MESSAGE; + _message = "No arrival data received"; + return; + } + + // Parse status + cJSON* status_json = cJSON_GetObjectItem(arrival_json, "status"); + if (status_json && cJSON_IsNumber(status_json)) { + int status_value = status_json->valueint; + if (status_value >= 0 && status_value <= 3) { + _status = static_cast(status_value); + } + } + + // TODO: verify the arrival json parsing + + // Parse message (if present) + cJSON* message_json = cJSON_GetObjectItem(arrival_json, "message"); + if (message_json && cJSON_IsString(message_json)) { + _message = message_json->valuestring; + } + + // Parse UP direction arrivals + cJSON* up_json = cJSON_GetObjectItem(arrival_json, "UP"); + if (up_json && cJSON_IsArray(up_json)) { + int up_count = cJSON_GetArraySize(up_json); + for (int i = 0; i < up_count; i++) { + cJSON* arrival_item = cJSON_GetArrayItem(up_json, i); + if (arrival_item) { + std::string time_str = ""; + std::string dest_str = ""; + + cJSON* time_json = cJSON_GetObjectItem(arrival_item, "time"); + if (time_json && cJSON_IsString(time_json)) { + time_str = time_json->valuestring; + } + + cJSON* dest_json = cJSON_GetObjectItem(arrival_item, "dest"); + if (dest_json && cJSON_IsString(dest_json)) { + dest_str = dest_json->valuestring; + } + + if (!time_str.empty()) { + _up_arrivals.emplace_back(time_str, dest_str); + } + } + } + } + + // Parse DOWN direction arrivals + cJSON* down_json = cJSON_GetObjectItem(arrival_json, "DOWN"); + if (down_json && cJSON_IsArray(down_json)) { + int down_count = cJSON_GetArraySize(down_json); + for (int i = 0; i < down_count; i++) { + cJSON* arrival_item = cJSON_GetArrayItem(down_json, i); + if (arrival_item) { + std::string time_str = ""; + std::string dest_str = ""; + + cJSON* time_json = cJSON_GetObjectItem(arrival_item, "time"); + if (time_json && cJSON_IsString(time_json)) { + time_str = time_json->valuestring; + } + + cJSON* dest_json = cJSON_GetObjectItem(arrival_item, "dest"); + if (dest_json && cJSON_IsString(dest_json)) { + dest_str = dest_json->valuestring; + } + + if (!time_str.empty()) { + _down_arrivals.emplace_back(time_str, dest_str); + } + } + } + } + + ESP_LOGI(TAG, "Parsed arrival info for %s/%s: %zu UP, %zu DOWN trains", + train_line_code.c_str(), train_station_code.c_str(), + _up_arrivals.size(), _down_arrivals.size()); +} diff --git a/main/external/mtr/arrival.h b/main/external/mtr/arrival.h new file mode 100644 index 0000000..a17abe1 --- /dev/null +++ b/main/external/mtr/arrival.h @@ -0,0 +1,67 @@ +#pragma once +#include "external/mtr/arrival.h" +#include "cJSON.h" +#include "external/mtr/mtr.h" +#include +#include + +// Forward declaration +class MTRNextTrainHandler; + +struct ArrivalInfo { +public: + // Caller transfers ownership of arrival_time to ArrivalInfo + ArrivalInfo( + const std::string& arrival_time, + const std::string& destination_name + ) : _arrival_time(arrival_time) + , _destination_name(destination_name) { } + + const char* arrival_time() const { + return _arrival_time.c_str(); + } + + const char* destination() const { + return _destination_name.c_str(); + } + +private: + const std::string _arrival_time; + const std::string _destination_name; // not the code of the station +}; + + +enum StatusEnum { + SUCCESSFUL_WITHOUT_DELAY = 0, + SUCCESSFUL_WITH_DELAY = 1, + FAILED_WITH_MESSAGE = 2, + UNKNOWN_STATUS = 3 +}; + +struct StationArrivalInfo { +public: + friend class MTRNextTrainHandler; + + // Public accessors + StatusEnum status() const { return _status; } + const char* message() const { return _message.c_str(); } + const char* train_line() const { return _train_line.c_str(); } + const char* train_station() const { return _train_station.c_str(); } + const std::vector* up_arrivals() const { return &_up_arrivals; } + const std::vector* down_arrivals() const { return &_down_arrivals; } + +private: + StationArrivalInfo( + cJSON* mtr_line_station_json, + cJSON* arrival_json, + const std::string& train_line_code, + const std::string& train_station_code + ); + + StatusEnum _status; + std::string _message; // only valid if status == FAILED_WITH_MESSAGE + std::string _train_line; + std::string _train_station; + std::vector _up_arrivals; + std::vector _down_arrivals; +}; diff --git a/main/external/mtr/line_info.cpp b/main/external/mtr/line_info.cpp new file mode 100644 index 0000000..153789b --- /dev/null +++ b/main/external/mtr/line_info.cpp @@ -0,0 +1,45 @@ +#include "external/mtr/line_info.h" +#include "external/mtr/station_info.h" +#include "cJSON.h" +#include "esp_log.h" + +LineInfo::LineInfo(cJSON* line_json) { + if (!line_json) { + ESP_LOGE(LINE_INFO_TAG, "line_json is null"); + return; + } + + // Parse line code + cJSON* code_json = cJSON_GetObjectItem(line_json, "code"); + if (code_json && cJSON_IsString(code_json)) { + _code = code_json->valuestring; + } else { + ESP_LOGW(LINE_INFO_TAG, "Missing or invalid 'code' field"); + } + + // Parse line color (note: field is 'line_color' in JSON, not 'color') + cJSON* color_json = cJSON_GetObjectItem(line_json, "line_color"); + if (color_json && cJSON_IsString(color_json)) { + _color = color_json->valuestring; + } else { + ESP_LOGW(LINE_INFO_TAG, "Missing or invalid 'line_color' field"); + } + + // Parse stations array + cJSON* stations_json = cJSON_GetObjectItem(line_json, "stations"); + if (stations_json && cJSON_IsArray(stations_json)) { + int station_count = cJSON_GetArraySize(stations_json); + _stations.reserve(station_count); + + for (int i = 0; i < station_count; i++) { + cJSON* station_json = cJSON_GetArrayItem(stations_json, i); + if (station_json) { + _stations.emplace_back(station_json); + } + } + + ESP_LOGI(LINE_INFO_TAG, "Created LineInfo: %s with %d stations", _code.c_str(), station_count); + } else { + ESP_LOGW(LINE_INFO_TAG, "Missing or invalid 'stations' array"); + } +} diff --git a/main/external/mtr/line_info.h b/main/external/mtr/line_info.h new file mode 100644 index 0000000..a72cad9 --- /dev/null +++ b/main/external/mtr/line_info.h @@ -0,0 +1,46 @@ +#pragma once +#include "cJSON.h" +#include "esp_log.h" +#include "external/mtr/station_info.h" +#include "external/mtr/mtr.h" +#include +#include + +#define LINE_INFO_TAG "LineInfo" + +// Forward declaration +class MTRNextTrainHandler; +struct StationInfo; + +struct LineInfo { +public: + + // caller does not own the returned char pointers + const char* code() const { + return _code.c_str(); + } + // caller does not own the returned char pointers + const char* color() const { + return _color.c_str(); + } + size_t station_count() const { + return _stations.size(); + } + // caller does not own the returned array or StationInfo pointers + const std::vector* stations() const { + return &_stations; + } + + friend class MTRNextTrainHandler; + +private: + // Caller transfers ownership of stations array and its contents to LineInfo + LineInfo( + cJSON* line_json + ); + + std::string _code; + std::string _color; + std::vector _stations; +}; + diff --git a/main/external/mtr/mtr.cpp b/main/external/mtr/mtr.cpp new file mode 100644 index 0000000..8a15f65 --- /dev/null +++ b/main/external/mtr/mtr.cpp @@ -0,0 +1,167 @@ +#include "external/mtr/mtr.h" +#include "external/mtr/line_info.h" +#include "external/mtr/station_info.h" +#include "external/mtr/arrival.h" +#include "assets/MTR_LINE_STATION.h" +#include "network/network.h" +#include "network/http_handler.h" +#include "cJSON.h" +#include "esp_log.h" +#include +#include + +static const char* TAG = "MTRNextTrainHandler"; + +// MTR Next Train API endpoint +// Note: This is a placeholder - replace with actual MTR API endpoint +static const char* MTR_API_BASE = "https://rt.data.gov.hk/v1/transport/mtr/getSchedule.php"; + +MTRNextTrainHandler::MTRNextTrainHandler() { + ESP_LOGI(TAG, "Initializing MTR Next Train Handler"); + mtr_data = cJSON_Parse(MTR_LINE_STATION_JSON); + if (!mtr_data) { + ESP_LOGE(TAG, "Failed to parse MTR line station JSON"); + } else { + ESP_LOGI(TAG, "Successfully parsed MTR line station JSON"); + } +} + +MTRNextTrainHandler::~MTRNextTrainHandler() { + if (mtr_data) { + cJSON_Delete(mtr_data); + mtr_data = nullptr; + } + ESP_LOGI(TAG, "MTR Next Train Handler destroyed"); +} + +std::vector MTRNextTrainHandler::get_lines() { + std::vector lines; + + if (!mtr_data) { + ESP_LOGE(TAG, "MTR data not initialized"); + return lines; + } + + // Iterate through all line objects in the JSON + cJSON* line_json = mtr_data->child; + while (line_json) { + if (cJSON_IsObject(line_json)) { + lines.push_back(LineInfo(line_json)); + } + line_json = line_json->next; + } + + ESP_LOGI(TAG, "Retrieved %zu MTR lines", lines.size()); + return lines; +} + +MtrArrivalErrorCode MTRNextTrainHandler::get_next_arrival_info( + NetworkHandler* network_handler, + std::string& line_code, + std::string& station_code, + StationArrivalInfo*& out_info, + Language lang +) { + if (!network_handler) { + ESP_LOGE(TAG, "NetworkHandler is null"); + return MtrArrivalErrorCode::UNKNOWN; + } + + if (!mtr_data) { + ESP_LOGE(TAG, "MTR data not initialized"); + return MtrArrivalErrorCode::UNKNOWN; + } + + // Verify line exists + cJSON* line_json = cJSON_GetObjectItem(mtr_data, line_code.c_str()); + if (!line_json) { + ESP_LOGW(TAG, "Line not found: %s", line_code.c_str()); + return MtrArrivalErrorCode::LINE_NOT_FOUND; + } + + // Verify station exists in line + bool station_found = false; + cJSON* stations_json = cJSON_GetObjectItem(line_json, "stations"); + if (stations_json && cJSON_IsArray(stations_json)) { + int station_count = cJSON_GetArraySize(stations_json); + for (int i = 0; i < station_count; i++) { + cJSON* station = cJSON_GetArrayItem(stations_json, i); + cJSON* code_json = cJSON_GetObjectItem(station, "code"); + if (code_json && cJSON_IsString(code_json)) { + if (station_code == code_json->valuestring) { + station_found = true; + break; + } + } + } + } + + if (!station_found) { + ESP_LOGW(TAG, "Station not found: %s in line %s", station_code.c_str(), line_code.c_str()); + return MtrArrivalErrorCode::STATION_NOT_FOUND; + } + + // Build API URL + std::ostringstream url; + url << MTR_API_BASE << "?line=" << line_code << "&sta=" << station_code; + if (lang == Language::EN) { + url << "&lang=en"; + } + + std::string url_str = url.str(); + ESP_LOGI(TAG, "Fetching arrival info from: %s", url_str.c_str()); + + // Create HTTP client configuration + esp_http_client_config_t http_config = {}; + http_config.url = url_str.c_str(); + http_config.timeout_ms = 10000; + http_config.transport_type = HTTP_TRANSPORT_OVER_SSL; + http_config.use_global_ca_store = true; + http_config.skip_cert_common_name_check = false; + + // Get HTTP handler and perform request + auto http_handler = network_handler->get_http_handler(std::move(http_config)); + if (!http_handler) { + ESP_LOGE(TAG, "Failed to create HTTP handler"); + return MtrArrivalErrorCode::UNKNOWN; + } + + esp_err_t err = http_handler->perform_request(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err)); + return MtrArrivalErrorCode::NO_ARRIVAL_INFO; + } + + // Get response body + char* buffer = nullptr; + int total_len = 0; + http_handler->get_body(buffer, total_len); + + if (!buffer || total_len <= 0) { + ESP_LOGE(TAG, "Empty response from MTR API"); + if (buffer) { + free(buffer); + } + return MtrArrivalErrorCode::NO_ARRIVAL_INFO; + } + + ESP_LOGI(TAG, "Received %d bytes from MTR API", total_len); + ESP_LOGD(TAG, "Response: %s", buffer); + + // Parse JSON response + cJSON* arrival_json = cJSON_Parse(buffer); + free(buffer); + + if (!arrival_json) { + ESP_LOGE(TAG, "Failed to parse MTR API response"); + return MtrArrivalErrorCode::NO_ARRIVAL_INFO; + } + + // Create StationArrivalInfo object + out_info = new StationArrivalInfo(mtr_data, arrival_json, line_code, station_code); + + cJSON_Delete(arrival_json); + + ESP_LOGI(TAG, "Successfully retrieved arrival info for %s/%s", line_code.c_str(), station_code.c_str()); + return MtrArrivalErrorCode::NONE; +} diff --git a/main/external/mtr/mtr.h b/main/external/mtr/mtr.h new file mode 100644 index 0000000..0cc09b7 --- /dev/null +++ b/main/external/mtr/mtr.h @@ -0,0 +1,58 @@ +#pragma once + +#include "assets/MTR_LINE_STATION.h" +#include "cJSON.h" +#include +#include "esp_log.h" +#include "external/mtr/line_info.h" +#include +#include "network/network.h" + +// Forward declaration +struct StationArrivalInfo; +struct LineInfo; + +enum class MtrArrivalErrorCode { + NONE = 0, + LINE_NOT_FOUND = 1, + STATION_NOT_FOUND = 2, + NO_ARRIVAL_INFO = 3, + UNKNOWN = 99, +}; + +enum class Language { + EN, + TC, +}; + +class MTRNextTrainHandler { +public: + + /** + * @brief Construct a new MTR Next Train Handler object + * @param json Pointer to cJSON object containing MTR Next Train data + * + * > Caller transfers ownership of the cJSON object to MTRNextTrainHandler + * + * cJSON structure for MTR Next Train data + * This structure is used to parse and store the MTR Next Train JSON data. + * Record + */ + MTRNextTrainHandler(); + ~MTRNextTrainHandler(); + + std::vector get_lines(); + + MtrArrivalErrorCode get_next_arrival_info( + NetworkHandler* network_handler, + std::string& line_code, + std::string& station_code, + StationArrivalInfo*& out_info, + Language lang = Language::TC + ); + +private: + cJSON* mtr_data; +}; + + diff --git a/main/external/mtr/station_info.cpp b/main/external/mtr/station_info.cpp new file mode 100644 index 0000000..b9155ed --- /dev/null +++ b/main/external/mtr/station_info.cpp @@ -0,0 +1,28 @@ +#include "external/mtr/station_info.h" +#include "cJSON.h" +#include "esp_log.h" + +StationInfo::StationInfo(cJSON* station_json) { + if (!station_json) { + ESP_LOGE(STATION_INFO_TAG, "station_json is null"); + return; + } + + // Parse station code + cJSON* code_json = cJSON_GetObjectItem(station_json, "code"); + if (code_json && cJSON_IsString(code_json)) { + _code = code_json->valuestring; + } else { + ESP_LOGW(STATION_INFO_TAG, "Missing or invalid 'code' field"); + } + + // Parse station name + cJSON* name_json = cJSON_GetObjectItem(station_json, "name"); + if (name_json && cJSON_IsString(name_json)) { + _name = name_json->valuestring; + } else { + ESP_LOGW(STATION_INFO_TAG, "Missing or invalid 'name' field"); + } + + ESP_LOGD(STATION_INFO_TAG, "Created StationInfo: %s (%s)", _name.c_str(), _code.c_str()); +} diff --git a/main/external/mtr/station_info.h b/main/external/mtr/station_info.h new file mode 100644 index 0000000..914e3b1 --- /dev/null +++ b/main/external/mtr/station_info.h @@ -0,0 +1,27 @@ +#pragma once +#include "esp_log.h" +#include "external/mtr/line_info.h" +#include + +#define STATION_INFO_TAG "StationInfo" + +// Forward declaration +struct LineInfo; + +struct StationInfo { +public: + StationInfo(cJSON* station_json); + + const char* name() const { return _name.c_str(); } + const char* code() const { return _code.c_str(); } + + friend class LineInfo; + +private: + // Caller transfers ownership of station_name and station_code to StationInfo + StationInfo(std::string& station_name, std::string& station_code) + : _name(station_name), _code(station_code) { } + + std::string _name; + std::string _code; +}; diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..01296f1 --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,20 @@ +## IDF Component Manager Manifest File +dependencies: + ## Required IDF version + idf: + version: '>=4.1.0' + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true + lvgl/lvgl: ^9.4.0 + espressif/esp_lcd_touch_gt911: ^1.2.0~1 + espressif/esp_lvgl_port: ^2.7.0 + espressif/cjson: ^1.7.19 diff --git a/main/info/info.cpp b/main/info/info.cpp new file mode 100644 index 0000000..421ae2e --- /dev/null +++ b/main/info/info.cpp @@ -0,0 +1,43 @@ +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_chip_info.h" +#include "esp_flash.h" +#include "esp_system.h" +#include "esp_psram.h" +#include "info.h" + +void display_chip_info() { + + /* Print chip information */ + esp_chip_info_t chip_info; + uint32_t flash_size; + esp_chip_info(&chip_info); + printf("This is %s chip with %d CPU core(s), %s%s%s%s%s, ", + CONFIG_IDF_TARGET, + chip_info.cores, + (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "", + (chip_info.features & CHIP_FEATURE_BT) ? "BT" : "", + (chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "", + (chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread), " : "", + // psram + (chip_info.features & CHIP_FEATURE_EMB_PSRAM) ? "with embedded PSRAM, " : ""); + + unsigned major_rev = chip_info.revision / 100; + unsigned minor_rev = chip_info.revision % 100; + printf("silicon revision v%d.%d, ", major_rev, minor_rev); + if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) { + printf("Get flash size failed"); + return; + } + + printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024), + (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); + + printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size()); + // psram + printf("PSRAM size: %u bytes\n", esp_psram_get_size()); + +} \ No newline at end of file diff --git a/main/info/info.h b/main/info/info.h new file mode 100644 index 0000000..2b324f4 --- /dev/null +++ b/main/info/info.h @@ -0,0 +1 @@ +void display_chip_info(); diff --git a/main/io/io.h b/main/io/io.h new file mode 100644 index 0000000..4f46722 --- /dev/null +++ b/main/io/io.h @@ -0,0 +1,26 @@ +#pragma once +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include + +typedef bool(*FilterFunc)(const std::string& key); +typedef void (*KeyValueProcessor)(void* arg, const std::string& key, const std::string& value); + +class KVStorageHandler { +public: + virtual ~KVStorageHandler() = default; + + virtual void init(const EventGroupHandle_t& system_event_group) = 0; + + // Store a key-value pair + virtual void put(const std::string& key, const std::string& value) = 0; + + // Retrieve a value by key, returns empty string if key not found + virtual std::string get(const std::string& key) const = 0; + virtual esp_err_t process_all(KeyValueProcessor processor, void* arg) const = 0; + virtual esp_err_t process_filtered(const std::string& key_prefix, KeyValueProcessor processor, void* arg) const = 0; + virtual esp_err_t process_filtered(FilterFunc filter_func, KeyValueProcessor processor, void* arg) const = 0; + + // Delete a key-value pair + virtual void remove(const std::string& key) = 0; +}; \ No newline at end of file diff --git a/main/io/nvs_handler.cpp b/main/io/nvs_handler.cpp new file mode 100644 index 0000000..cc83d66 --- /dev/null +++ b/main/io/nvs_handler.cpp @@ -0,0 +1,191 @@ +#include "common/constants.h" +#include "io/nvs_handler.h" +#include "nvs_flash.h" +#include "string.h" +#include "esp_log.h" + +#define TAG "NVSStorageHandler" + +NVSStorageHandler::NVSStorageHandler( + const char* name_space +) : name_space(name_space) { } + +NVSStorageHandler::~NVSStorageHandler() { + if (this->nvsHandle != 0) { + nvs_close(this->nvsHandle); + this->nvsHandle = 0; + } +} + +void NVSStorageHandler::init(const EventGroupHandle_t& system_event_group) { + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + nvs_flash_erase(); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); + + err = nvs_open(this->name_space, NVS_READWRITE, &this->nvsHandle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) opening NVS handle!", esp_err_to_name(err)); + } else { + if (system_event_group != nullptr) { + xEventGroupSetBits(system_event_group, STORAGE_READY_BIT); + } + ESP_LOGI(TAG, "NVS Storage initialized."); + } +} + +void NVSStorageHandler::put(const std::string& key, const std::string& value) { + if (this->nvsHandle == 0) { + ESP_LOGE(TAG, "NVS handle is not initialized."); + return; + } + + esp_err_t err = nvs_set_str(this->nvsHandle, key.c_str(), value.c_str()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) setting key-value pair in NVS!", esp_err_to_name(err)); + } else { + nvs_commit(this->nvsHandle); + // ESP_LOGI(TAG, "Key-value pair (%s, %s) stored in NVS.", key.c_str(), value.c_str()); + } +} + +std::string NVSStorageHandler::get(const std::string& key) const { + if (this->nvsHandle == 0) { + ESP_LOGE(TAG, "NVS handle is not initialized."); + return ""; + } + + size_t required_size = 0; + esp_err_t err = nvs_get_str(this->nvsHandle, key.c_str(), nullptr, &required_size); + if (err == ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGW(TAG, "Key %s not found in NVS.", key.c_str()); + return ""; + } else if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) getting size for key %s from NVS!", esp_err_to_name(err), key.c_str()); + return ""; + } + + // Allocate string buffer with correct size (includes null terminator) + std::string value(required_size - 1, '\0'); + err = nvs_get_str(this->nvsHandle, key.c_str(), &value[0], &required_size); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) getting value for key %s from NVS!", esp_err_to_name(err), key.c_str()); + return ""; + } + + return value; +} + +NVSIteratorGuard NVSStorageHandler::create_iterator() const { + nvs_iterator_t it = nullptr; + esp_err_t err = nvs_entry_find(NVS_DEFAULT_PART_NAME, this->name_space, NVS_TYPE_ANY, &it); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) creating NVS iterator!", esp_err_to_name(err)); + return NVSIteratorGuard(nullptr, err); + } + + return NVSIteratorGuard(it, ESP_OK); +} + +esp_err_t NVSStorageHandler::process_all(KeyValueProcessor processor, void* arg) const { + NVSIteratorGuard iterator_guard = this->create_iterator(); + if (!iterator_guard.is_valid()) { + return iterator_guard.get_error(); + } + const nvs_iterator_t& it = iterator_guard.get_iterator(); + + for (; it != NULL; iterator_guard.advance_iter()) { + nvs_entry_info_t info; + esp_err_t err = nvs_entry_info(it, &info); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) getting NVS entry info!", esp_err_to_name(err)); + return err; + } + nvs_handle_t temp_handle; + err = nvs_open(this->name_space, NVS_READONLY, &temp_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) opening NVS handle for reading!", esp_err_to_name(err)); + return err; + } + + // call the processor with the key and value + std::string key_str = info.key; + processor(arg, key_str, this->get(key_str)); + } + return ESP_OK; +} +esp_err_t NVSStorageHandler::process_filtered(const std::string& key_prefix, KeyValueProcessor processor, void* arg) const { + NVSIteratorGuard iterator_guard = this->create_iterator(); + if (!iterator_guard.is_valid()) { + return iterator_guard.get_error(); + } + const nvs_iterator_t& it = iterator_guard.get_iterator(); + + for (; it != NULL; iterator_guard.advance_iter()) { + nvs_entry_info_t info; + esp_err_t err = nvs_entry_info(it, &info); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) getting NVS entry info!", esp_err_to_name(err)); + return err; + } + // check if the key matches the prefix + if (strncmp(info.key, key_prefix.c_str(), key_prefix.length()) == 0) { + nvs_handle_t temp_handle; + err = nvs_open(this->name_space, NVS_READONLY, &temp_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) opening NVS handle for reading!", esp_err_to_name(err)); + return err; + } + // call the processor with the key and value + processor(arg, std::string(info.key), this->get(std::string(info.key))); + } + } + return ESP_OK; +} + +esp_err_t NVSStorageHandler::process_filtered(FilterFunc filter_func, KeyValueProcessor processor, void* arg) const { + NVSIteratorGuard iterator_guard = this->create_iterator(); + if (!iterator_guard.is_valid()) { + return iterator_guard.get_error(); + } + const nvs_iterator_t& it = iterator_guard.get_iterator(); + + for (; it != NULL; iterator_guard.advance_iter()) { + nvs_entry_info_t info; + esp_err_t err = nvs_entry_info(it, &info); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) getting NVS entry info!", esp_err_to_name(err)); + return err; + } + // check if the key matches the filter function + std::string key_str(info.key); + if (filter_func(key_str)) { + nvs_handle_t temp_handle; + err = nvs_open(this->name_space, NVS_READONLY, &temp_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) opening NVS handle for reading!", esp_err_to_name(err)); + return err; + } + // call the processor with the key and value + processor(arg, key_str, this->get(key_str)); + } + } + return ESP_OK; +} + +void NVSStorageHandler::remove(const std::string& key) { + if (this->nvsHandle == 0) { + ESP_LOGE(TAG, "NVS handle is not initialized."); + return; + } + + esp_err_t err = nvs_erase_key(this->nvsHandle, key.c_str()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) deleting key %s from NVS!", esp_err_to_name(err), key.c_str()); + } else { + nvs_commit(this->nvsHandle); + ESP_LOGI(TAG, "Key %s deleted from NVS.", key.c_str()); + } +} diff --git a/main/io/nvs_handler.h b/main/io/nvs_handler.h new file mode 100644 index 0000000..3b20e39 --- /dev/null +++ b/main/io/nvs_handler.h @@ -0,0 +1,70 @@ +#pragma once +#include "io/io.h" +#include "freertos/FreeRTOS.h" +#include "nvs.h" + +struct NVSIteratorGuard { +public: + ~NVSIteratorGuard() { + if (iterator) { + nvs_release_iterator(iterator); + } + } + + // accessors to the iterator, the internal state should not be modified directly + // The iterator is advanced using advance_iter(), and is changed to nullptr on error or end + // Caller MUST NOT release the iterator manually nor call get_iterator after advance_iter + const nvs_iterator_t& get_iterator() const { + return iterator; + } + + void advance_iter() { + if (iterator) { + // advance the iterator and update the internal state + esp_err_t err = nvs_entry_next(&iterator); + if (err != ESP_OK) { + error = err; + iterator = nullptr; + } + } + } + + esp_err_t get_error() const { + return error; + } + bool is_valid() const { + return iterator != nullptr && error == ESP_OK; + } + friend class NVSStorageHandler; +private: + NVSIteratorGuard(nvs_iterator_t it + , esp_err_t err + ) : iterator(it), error(err) { } + nvs_iterator_t iterator; + esp_err_t error; +}; + +class NVSStorageHandler : public KVStorageHandler { +public: + NVSStorageHandler( + const char* name_space + ); + ~NVSStorageHandler() override; + + void init(const EventGroupHandle_t& system_event_group) override; + + void put(const std::string& key, const std::string& value) override; + + std::string get(const std::string& key) const override; + esp_err_t process_all(KeyValueProcessor processor, void* arg) const override; + esp_err_t process_filtered(const std::string& key_prefix, KeyValueProcessor processor, void* arg) const override; + esp_err_t process_filtered(FilterFunc filter_func, KeyValueProcessor processor, void* arg) const override; + + void remove(const std::string& key) override; + +private: + NVSIteratorGuard create_iterator() const; + + nvs_handle_t nvsHandle = 0; + const char* name_space; +}; diff --git a/main/lv_conf.h b/main/lv_conf.h new file mode 100644 index 0000000..f6c72aa --- /dev/null +++ b/main/lv_conf.h @@ -0,0 +1 @@ +// placeholder file to avoid build errors, the actual lv_conf.h will be injected by esp-idf during build \ No newline at end of file diff --git a/main/main.cpp b/main/main.cpp new file mode 100644 index 0000000..b6c8566 --- /dev/null +++ b/main/main.cpp @@ -0,0 +1,430 @@ +#include +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_chip_info.h" +#include "esp_flash.h" +#include "esp_system.h" +#include "esp_log.h" + +// +#include "common/constants.h" +#include "common/queue_defs.h" +#include "io/nvs_handler.h" +#include "info/info.h" +#include "display/eink_display_handler.h" +#include "display/lvgl_handler.h" +#include "ui/ui_handler.h" +#include "ui/app_registry.h" +#include "ui/apps/shutdown_app.h" +#include "ui/apps/discord_app.h" +#include "ui/apps/mtr_app.h" +#include +#include "esp_lvgl_port.h" +#include "lvgl.h" +#include "network.h" +#include + +#include "lvgl.h" + +// nvs storage namespaces, 15 characters max +#define DEFAULT_STORAGE_NAMESPACE "storage" +#define WIFI_CREDENTIALS_STORAGE_NAMESPACE "wifi_cred" +#define TAG "Main" + +extern "C" void app_main(void); + +void init_queues( + QueueHandle_t& touch_queue, + EventGroupHandle_t& system_event_group, + EventGroupHandle_t& system_lifecycle_event_group +); + + + +void EInk_Checkerboard( + EInkDisplayHandler* display_handler +) { + struct CheckerboardTaskParams { + EInkDisplayHandler* display_handler; + }; + auto checkerboard_task_fn = [](void* pvParameters) { + CheckerboardTaskParams* params = static_cast(pvParameters); + if (params != nullptr && params->display_handler != nullptr) { + // Add this task to the watchdog timer + esp_err_t wdt_err = esp_task_wdt_add(NULL); + if (wdt_err != ESP_OK) { + ESP_LOGW(TAG, "Failed to add checkerboard task to watchdog: %s", esp_err_to_name(wdt_err)); + } + + EInkDisplayHandler* display_handler = params->display_handler; + const size_t DISPLAY_BUFFER_SIZE = DISPLAY_WIDTH * DISPLAY_HEIGHT / 8; + uint8_t* framebuffer = new uint8_t[DISPLAY_BUFFER_SIZE]; + if (framebuffer == nullptr) { + ESP_LOGE(TAG, "Failed to allocate framebuffer for checkerboard task"); + if (wdt_err == ESP_OK) { + esp_task_wdt_delete(NULL); + } + vTaskDelete(NULL); + return; + } + // Create checkerboard pattern + for (size_t y = 0; y < DISPLAY_HEIGHT; y++) { + for (size_t x = 0; x < DISPLAY_WIDTH; x++) { + size_t byte_index = (y * DISPLAY_WIDTH + x) / 8; + size_t bit_index = 7 - (x % 8); + bool is_white = ((x / 20) % 2) == ((y / 20) % 2); + if (is_white) { + framebuffer[byte_index] |= (1 << bit_index); // Set bit to 1 for white + } else { + framebuffer[byte_index] &= ~(1 << bit_index); // Clear bit to 0 for black + } + } + // Yield and reset watchdog periodically + const size_t YIELD_INTERVAL = 16; + if (y % YIELD_INTERVAL == 0) { + if (wdt_err == ESP_OK) { + esp_task_wdt_reset(); + } + vTaskDelay(1 / portTICK_PERIOD_MS); + // partial refresh to show progress + int32_t y_start = static_cast((y >= YIELD_INTERVAL - 1) ? (y - (YIELD_INTERVAL - 1)) : 0); + int32_t y_end = static_cast(y); + // get the partial framebuffer for this area + uint8_t* partial_framebuffer = &framebuffer[y_start * (DISPLAY_WIDTH / 8)]; + esp_err_t err = display_handler->partial_refresh(partial_framebuffer, RefreshArea { 0, y_start, DISPLAY_WIDTH - 1, y_end }); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Partial refresh failed at y=%d: %s", y, esp_err_to_name(err)); + } + // wait for 4 seconds to prevent spamming the display + // vTaskDelay(2000 / portTICK_PERIOD_MS); + } + } + // Perform full write to display + // esp_err_t err = display_handler->full_write(framebuffer); + // if (err != ESP_OK) { + // ESP_LOGE(TAG, "Checkerboard full write failed: %s", esp_err_to_name(err)); + // } else { + // ESP_LOGI(TAG, "Checkerboard pattern displayed successfully."); + // } + delete[] framebuffer; + + // Remove task from watchdog before deletion + if (wdt_err == ESP_OK) { + esp_task_wdt_delete(NULL); + } + } else { + ESP_LOGE(TAG, "Invalid parameters for checkerboard task"); + } + vTaskDelete(NULL); + }; + CheckerboardTaskParams* checker_params = new CheckerboardTaskParams(); + checker_params->display_handler = display_handler; + BaseType_t res = xTaskCreate( + checkerboard_task_fn, + "checkerboard_task", + 8192, + static_cast(checker_params), + tskIDLE_PRIORITY + 1, + NULL + ); + + if (res != pdPASS) { + ESP_LOGE(TAG, "Failed to create checkerboard task"); + delete checker_params; + } +} + +void LVGL_Checkerboard( + LVGLHandler* lvgl_handler +) { + struct CheckerboardTaskParams { + LVGLHandler* lvgl_handler; + }; + auto checkerboard_task_fn = [](void* pvParameters) { + CheckerboardTaskParams* params = static_cast(pvParameters); + if (params == nullptr || params->lvgl_handler == nullptr) { + ESP_LOGE(TAG, "Invalid parameters for LVGL checkerboard task"); + delete params; + vTaskDelete(NULL); + return; + } + auto* handler = static_cast(params->lvgl_handler); + + // Add safety checks + if (!handler) { + ESP_LOGE("LVGL", "Handler is null!"); + delete params; + vTaskDelete(NULL); + return; + } + + ESP_LOGI("HEAP", "Free: %d", esp_get_free_heap_size()); + + // Wait for LVGL system to fully initialize + vTaskDelay(pdMS_TO_TICKS(200)); + + // Acquire LVGL lock with proper timeout + if (!lvgl_port_lock(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "Failed to acquire LVGL lock for checkerboard"); + delete params; + vTaskDelete(NULL); + return; + } + + // Verify LVGL is properly initialized + if (lv_display_get_default() == nullptr) { + ESP_LOGE(TAG, "LVGL default display not available"); + lvgl_port_unlock(); + delete params; + vTaskDelete(NULL); + return; + } + + // Create LVGL objects for checkerboard + lv_obj_t* scr = lv_scr_act(); + if (scr == nullptr) { + ESP_LOGE(TAG, "Failed to get active LVGL screen"); + lvgl_port_unlock(); + delete params; + vTaskDelete(NULL); + return; + } + lv_obj_t* checkerboard = lv_obj_create(scr); + if (checkerboard == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL checkerboard object"); + lvgl_port_unlock(); + delete params; + vTaskDelete(NULL); + return; + } + lv_obj_set_size(checkerboard, DISPLAY_WIDTH, DISPLAY_HEIGHT); + // remove border and padding + lv_obj_set_style_pad_all(checkerboard, 0, 0); + lv_obj_set_style_border_width(checkerboard, 0, 0); + const int CELL_SIZE = 40; + lvgl_port_unlock(); + // Create checkerboard pattern using LVGL + for (int y = 0; y < DISPLAY_HEIGHT; y += CELL_SIZE) { + lvgl_port_lock(pdMS_TO_TICKS(1000)); + for (int x = 0; x < DISPLAY_WIDTH; x += CELL_SIZE) { + lv_color_t color = (((x / CELL_SIZE) % 2) == ((y / CELL_SIZE) % 2)) ? lv_color_hex(0xFFFFFF) : lv_color_hex(0x000000); + lv_obj_t* cell = lv_obj_create(checkerboard); + if (cell == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL checkerboard cell"); + lvgl_port_unlock(); + continue; + } + lv_obj_set_size(cell, CELL_SIZE, CELL_SIZE); + lv_obj_set_style_bg_color(cell, color, 0); + lv_obj_set_pos(cell, x, y); + // remove border and padding + lv_obj_set_style_pad_all(cell, 0, 0); + lv_obj_set_style_border_width(cell, 0, 0); + lv_obj_t* label = lv_label_create(cell); + if (label != nullptr) { + lv_label_set_text_fmt(label, "(%d,%d)", x, y); + lv_obj_center(label); + } + } + lvgl_port_unlock(); + // Yield to allow LVGL to process rendering + vTaskDelay(5000 / portTICK_PERIOD_MS); + } + + + + ESP_LOGI(TAG, "LVGL Checkerboard pattern displayed successfully."); + delete params; + vTaskDelete(NULL); + }; + CheckerboardTaskParams* checker_params = new CheckerboardTaskParams(); + checker_params->lvgl_handler = lvgl_handler; + BaseType_t res = xTaskCreate( + checkerboard_task_fn, + "lvgl_checkerboard_task", + 8192, + static_cast(checker_params), + tskIDLE_PRIORITY + 1, + NULL + ); + if (res != pdPASS) { + ESP_LOGE(TAG, "Failed to create LVGL checkerboard task"); + delete checker_params; + } +} + + +void app_main(void) { + display_chip_info(); + + QueueHandle_t touch_event_queue = NULL; + EventGroupHandle_t system_event_group = NULL, system_lifecycle_event_group = NULL; + + init_queues(touch_event_queue, system_event_group, system_lifecycle_event_group); + if (touch_event_queue == NULL || system_event_group == NULL || system_lifecycle_event_group == NULL) { + ESP_LOGE("Main", "Failed to create one or more queues/event groups"); + vTaskDelay(5000 / portTICK_PERIOD_MS); + return esp_restart(); + } + ESP_LOGI(TAG, "Queues initialized.\n"); + + // + // KVStorageHandler* kv_storage_handler = new NVSStorageHandler( + // DEFAULT_STORAGE_NAMESPACE + // ); + + // auto wifi_handler = std::make_unique( + // std::unique_ptr(new NVSStorageHandler(WIFI_CREDENTIALS_STORAGE_NAMESPACE)) + // ); + // NetworkHandler* network_handler = new NetworkHandler(std::move(wifi_handler)); + EInkDisplayHandler* display_handler = new EInkDisplayHandler(); + // Initialize display and touch + // display_handler->init_devices(system_event_group); + display_handler->init_devices(); + ESP_LOGI(TAG, "E-Ink display handler initialized.\n"); + // LVGL Handler + std::unique_ptr display_uptr(display_handler); + LVGLHandler lvgl_handler(std::move(display_uptr)); + esp_err_t err = lvgl_handler.initLVGL(system_event_group); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize LVGL handler: %s", esp_err_to_name(err)); + vTaskDelay(5000 / portTICK_PERIOD_MS); + return esp_restart(); + } + + // + // kv_storage_handler->init(system_event_group); + // network_handler->init(system_event_group); + + // + ESP_LOGI(TAG, "Waiting for system to be ready...\n"); + xEventGroupWaitBits( + system_event_group, + // DISPLAY_READY_BIT | STORAGE_READY_BIT | NETWORK_READY_BIT, + DISPLAY_READY_BIT, + // do not clear on exit, require explicit reset + pdFALSE, + pdTRUE, + portMAX_DELAY + ); + ESP_LOGI(TAG, "System is ready. Starting main application...\n"); + + // Allow LVGL system to stabilize before creating objects + vTaskDelay(pdMS_TO_TICKS(100)); + + // Show checkerboard pattern on display for testing + // EInk_Checkerboard(display_handler); + LVGL_Checkerboard(&lvgl_handler); + + // Register apps with AppRegistry by creating their descriptors + // Each descriptor will create and register the app instance + // DemoAppDescriptor* demo_descriptor = new DemoAppDescriptor(); + // ShutdownAppDescriptor* shutdown_descriptor = new ShutdownAppDescriptor(); + // DiscordAppDescriptor::instance(); // Use singleton pattern for Discord app + // MtrAppDescriptor* mtr_descriptor = new MtrAppDescriptor(); + + // Pass network handler to MtrApp so it can fetch arrival data + // MtrApp* mtr_app = dynamic_cast(mtr_descriptor->get_app_instance()); + // if (mtr_app) { + // mtr_app->set_network_handler(network_handler); + // } + + // ESP_LOGI(TAG, "Apps registered with AppRegistry\n"); + + // Initialize UI Handler (will render app icons from registry) + // UIHandler ui_handler; + // if (ui_handler.init() != ESP_OK) { + // ESP_LOGE(TAG, "Failed to initialize UI handler"); + // vTaskDelay(5000 / portTICK_PERIOD_MS); + // return esp_restart(); + // } + // ESP_LOGI(TAG, "UI handler initialized successfully\n"); + // ESP_LOGI(TAG, "Main screen displayed with app icons. Tap an icon to launch an app.\n"); + + + + // wait for shutdown signal + ESP_LOGI(TAG, "Waiting for shutdown signal...\n"); + EventBits_t bits = xEventGroupWaitBits( + system_lifecycle_event_group, + SYSTEM_SHUTDOWN_BIT | SYSTEM_RESTART_BIT, + // do not clear on exit, require explicit reset + pdFALSE, + pdFALSE, + portMAX_DELAY + ); + ESP_LOGI(TAG, "Shutdown signal received. Cleaning up...\n"); + + // Show shutdown screen using the shutdown descriptor's app instance + // ShutdownApp* shutdown_app = dynamic_cast(shutdown_descriptor->get_app_instance()); + // if (shutdown_app) { + // ui_handler.switch_app(shutdown_app); + // } + vTaskDelay(1000 / portTICK_PERIOD_MS); // Display shutdown message briefly + + // Cleanup + // ui_handler.deinit(); + // delete demo_descriptor; + // delete shutdown_descriptor; + // delete mtr_descriptor; + + vEventGroupDelete(system_event_group); + vQueueDelete(touch_event_queue); + ESP_LOGI(TAG, "Cleanup complete.\n"); + + // handle shutdown or restart + if (bits & SYSTEM_SHUTDOWN_BIT) { + // if (shutdown_display_handler != nullptr) { + // ESP_LOGI(TAG, "Calling display shutdown handler...\n"); + // shutdown_display_handler(); + // } else { + // ESP_LOGI(TAG, "No display shutdown handler to call.\n"); + // } + ESP_LOGI(TAG, "System is shutting down.\n"); + fflush(stdout); + // wait for start bit to be set again if future restart is desired, else expect manual power cycle + EventBits_t bits = xEventGroupWaitBits( + system_lifecycle_event_group, + SYSTEM_START_BIT, + pdFALSE, + pdFALSE, + portMAX_DELAY + ); + if (bits & SYSTEM_START_BIT) { + ESP_LOGI(TAG, "SYSTEM_START_BIT received, restarting system.\n"); + } else { + ESP_LOGW(TAG, "No restart signal received, waiting for manual power cycle.\n"); + while (true) { + vTaskDelay(portMAX_DELAY); + } + } + } else if (bits & SYSTEM_RESTART_BIT) { + // if (restart_display_handler != nullptr) { + // ESP_LOGI(TAG, "Calling display restart handler...\n"); + // restart_display_handler(); + // } else { + // ESP_LOGI(TAG, "No display restart handler to call.\n"); + // } + ESP_LOGI(TAG, "System is restarting.\n"); + fflush(stdout); + } else { + ESP_LOGW(TAG, "Unknown shutdown signal received. Restarting by default.\n"); + fflush(stdout); + } + + return esp_restart(); +} + +void init_queues( + QueueHandle_t& touch_queue, + EventGroupHandle_t& system_event_group, + EventGroupHandle_t& system_lifecycle_event_group +) { + // Implementation of queue initialization + touch_queue = xQueueCreate(10, sizeof(touch_event_t)); + system_event_group = xEventGroupCreate(); + system_lifecycle_event_group = xEventGroupCreate(); +} diff --git a/main/network/http_handler.cpp b/main/network/http_handler.cpp new file mode 100644 index 0000000..00fd7e8 --- /dev/null +++ b/main/network/http_handler.cpp @@ -0,0 +1,51 @@ +#include "network/http_handler.h" +#include "esp_http_client.h" +#include "esp_log.h" +#include "string.h" + +HttpHandler::HttpHandler(const esp_http_client_config_t&& config, WifiHandler* wifiHandler) + : wifiHandler(wifiHandler) { + this->client = esp_http_client_init(&config); +} + +HttpHandler::~HttpHandler() { + if (this->client) { + esp_http_client_cleanup(this->client); + } +} + +esp_err_t HttpHandler::set_method(esp_http_client_method_t method) { + return esp_http_client_set_method(this->client, method); +} + +esp_err_t HttpHandler::set_header(const char* header, const char* value) { + return esp_http_client_set_header(this->client, header, value); +} + +esp_err_t HttpHandler::set_post_field(const char* field, size_t len) { + return esp_http_client_set_post_field(this->client, field, len); +} + +esp_err_t HttpHandler::perform_request() { + return esp_http_client_perform(this->client); +} + +void HttpHandler::get_body( + char*& buffer, + int& total_len +) { + total_len = esp_http_client_get_content_length(this->client); + buffer = new char[total_len + 1]; // +1 for null-terminator + if (buffer) { + int read_len = esp_http_client_read(this->client, buffer, total_len); + if (read_len >= 0) { + buffer[read_len] = '\0'; // null-terminate + } else { + delete[] buffer; + buffer = nullptr; + total_len = 0; + } + } else { + total_len = 0; + } +} diff --git a/main/network/http_handler.h b/main/network/http_handler.h new file mode 100644 index 0000000..1be7670 --- /dev/null +++ b/main/network/http_handler.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include "esp_http_client.h" +#include "network/wifi_handler.h" + +// forward declare NetworkHandler to avoid circular include with network.h +class NetworkHandler; + +// default config values for esp_http_client_config_t +// disable Wmissing-field-initializers warning for these structs +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +static const inline esp_http_client_config_t DEFAULT_HTTP_CLIENT_CONFIG = { + .timeout_ms = 10000, +}; + +static const inline esp_http_client_config_t DEFAULT_HTTP_CLIENT_CONFIG_HTTPS = { + .transport_type = HTTP_TRANSPORT_OVER_SSL, + // + .use_global_ca_store = true, + .skip_cert_common_name_check = false, +}; +#pragma GCC diagnostic pop + +// esp http client wrapper with automatic initialization and cleanup +class HttpHandler { +public: + ~HttpHandler(); + + esp_err_t set_method(esp_http_client_method_t method); + esp_err_t set_header(const char* header, const char* value); + esp_err_t set_post_field(const char* field, size_t len); + // + esp_err_t perform_request(); + // buffer is allocated inside the method, caller must free it + void get_body( + char*& buffer, + int& total_len + ); + + + // only NetworkHandler can create HttpHandler instances + friend class NetworkHandler; + // disable copy constructor and assignment operator + HttpHandler(const HttpHandler&) = delete; + HttpHandler& operator=(const HttpHandler&) = delete; + +private: + // private constructor, only NetworkHandler can create instances + HttpHandler(const esp_http_client_config_t&& config, WifiHandler* wifiHandler); + + esp_http_client_handle_t client; + // backreference to WifiHandler to ensure WiFi is connected, DO NOT DELETE + WifiHandler* wifiHandler; +}; diff --git a/main/network/network.cpp b/main/network/network.cpp new file mode 100644 index 0000000..d02a717 --- /dev/null +++ b/main/network/network.cpp @@ -0,0 +1,32 @@ +#include "esp_log.h" +#include "network/network.h" +#include "network/http_handler.h" +#include "common/constants.h" + +NetworkHandler::NetworkHandler( + std::unique_ptr wifiHandler +) : wifiHandler(std::move(wifiHandler)) { } + +NetworkHandler::~NetworkHandler() { } + +void NetworkHandler::init(EventGroupHandle_t system_event_group) { + if (this->initialized) { + ESP_LOGW("NetworkHandler", "Already initialized, skipping"); + return; + } + this->wifiHandler->init(); + this->initialized = true; + xEventGroupSetBits( + system_event_group, + NETWORK_READY_BIT + ); +} + +WifiHandler& NetworkHandler::get_wifi_handler() { + return *this->wifiHandler; +} + +std::unique_ptr NetworkHandler::get_http_handler(const esp_http_client_config_t&& config) { + return std::unique_ptr(new HttpHandler(std::move(config), this->wifiHandler.get())); +} + diff --git a/main/network/network.h b/main/network/network.h new file mode 100644 index 0000000..edcc46c --- /dev/null +++ b/main/network/network.h @@ -0,0 +1,27 @@ +#pragma once +#include +#include "freertos/FreeRTOS.h" +#include "esp_system.h" +#include "network/wifi_handler.h" +#include "esp_http_client.h" + +// forward declare HttpHandler to avoid circular include with http_handler.h +class HttpHandler; + +class NetworkHandler { +public: + NetworkHandler( + std::unique_ptr wifiHandler + ); + ~NetworkHandler(); + + void init(EventGroupHandle_t system_event_group); + WifiHandler& get_wifi_handler(); + // factory method to create HttpHandler instances + std::unique_ptr get_http_handler(const esp_http_client_config_t&& config); + + +private: + std::unique_ptr wifiHandler; + bool initialized = false; +}; diff --git a/main/network/udp_client.cpp b/main/network/udp_client.cpp new file mode 100644 index 0000000..7929913 --- /dev/null +++ b/main/network/udp_client.cpp @@ -0,0 +1,172 @@ +#include "udp_client.h" +#include +#include +#include +#include +#include "esp_log.h" + +static const char* TAG = "UDPClient"; + +UDPClient::UDPClient() + : sock_fd_(-1) + , remote_port_(0) + , configured_(false) + , initialized_(false) { + memset(&remote_addr_, 0, sizeof(remote_addr_)); +} + +UDPClient::~UDPClient() { + close(); +} + +esp_err_t UDPClient::init() { + if (initialized_) { + ESP_LOGW(TAG, "Already initialized"); + return ESP_OK; + } + + sock_fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock_fd_ < 0) { + ESP_LOGE(TAG, "Failed to create socket: errno %d", errno); + return ESP_FAIL; + } + + // Set socket to non-blocking mode + esp_err_t err = set_nonblocking(); + if (err != ESP_OK) { + ::close(sock_fd_); + sock_fd_ = -1; + return err; + } + + initialized_ = true; + ESP_LOGI(TAG, "UDP client initialized (fd=%d)", sock_fd_); + return ESP_OK; +} + +esp_err_t UDPClient::set_nonblocking() { + int flags = fcntl(sock_fd_, F_GETFL, 0); + if (flags < 0) { + ESP_LOGE(TAG, "Failed to get socket flags: errno %d", errno); + return ESP_FAIL; + } + + if (fcntl(sock_fd_, F_SETFL, flags | O_NONBLOCK) < 0) { + ESP_LOGE(TAG, "Failed to set non-blocking mode: errno %d", errno); + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t UDPClient::configure(const std::string& ip, uint16_t port) { + if (ip.empty() || port == 0) { + ESP_LOGE(TAG, "Invalid IP or port"); + return ESP_ERR_INVALID_ARG; + } + + struct in_addr addr; + if (inet_pton(AF_INET, ip.c_str(), &addr) != 1) { + ESP_LOGE(TAG, "Invalid IP address format: %s", ip.c_str()); + return ESP_ERR_INVALID_ARG; + } + + remote_addr_.sin_family = AF_INET; + remote_addr_.sin_port = htons(port); + remote_addr_.sin_addr = addr; + + remote_ip_ = ip; + remote_port_ = port; + configured_ = true; + + ESP_LOGI(TAG, "Configured endpoint: %s:%u", ip.c_str(), port); + return ESP_OK; +} + +esp_err_t UDPClient::send_command(const std::string& command) { + if (!initialized_) { + ESP_LOGE(TAG, "Not initialized"); + return ESP_FAIL; + } + + if (!configured_) { + ESP_LOGE(TAG, "Endpoint not configured"); + return ESP_FAIL; + } + + ssize_t sent = sendto(sock_fd_, command.c_str(), command.length(), 0, + (struct sockaddr*)&remote_addr_, sizeof(remote_addr_)); + + if (sent < 0) { + ESP_LOGE(TAG, "Failed to send command '%s': errno %d", command.c_str(), errno); + return ESP_FAIL; + } + + ESP_LOGD(TAG, "Sent command: %s (%d bytes)", command.c_str(), (int)sent); + return ESP_OK; +} + +esp_err_t UDPClient::receive_response(std::string& response, int timeout_ms) { + if (!initialized_) { + ESP_LOGE(TAG, "Not initialized"); + return ESP_FAIL; + } + + // Setup select() for timeout + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(sock_fd_, &read_fds); + + struct timeval timeout; + struct timeval* timeout_ptr = nullptr; + + if (timeout_ms >= 0) { + timeout.tv_sec = timeout_ms / 1000; + timeout.tv_usec = (timeout_ms % 1000) * 1000; + timeout_ptr = &timeout; + } + + int ret = select(sock_fd_ + 1, &read_fds, nullptr, nullptr, timeout_ptr); + + if (ret < 0) { + ESP_LOGE(TAG, "select() failed: errno %d", errno); + return ESP_FAIL; + } + + if (ret == 0) { + ESP_LOGD(TAG, "Receive timeout (%d ms)", timeout_ms); + return ESP_ERR_TIMEOUT; + } + + // Data is available + char buffer[512]; + struct sockaddr_in from_addr; + socklen_t from_len = sizeof(from_addr); + + ssize_t received = recvfrom(sock_fd_, buffer, sizeof(buffer) - 1, 0, + (struct sockaddr*)&from_addr, &from_len); + + if (received < 0) { + ESP_LOGE(TAG, "recvfrom() failed: errno %d", errno); + return ESP_FAIL; + } + + buffer[received] = '\0'; + response = std::string(buffer, received); + + ESP_LOGD(TAG, "Received response: %s (%d bytes)", response.c_str(), (int)received); + return ESP_OK; +} + +void UDPClient::close() { + if (sock_fd_ >= 0) { + ::close(sock_fd_); + ESP_LOGI(TAG, "Socket closed"); + sock_fd_ = -1; + } + + initialized_ = false; + configured_ = false; + remote_ip_.clear(); + remote_port_ = 0; +} diff --git a/main/network/udp_client.h b/main/network/udp_client.h new file mode 100644 index 0000000..6e9bf49 --- /dev/null +++ b/main/network/udp_client.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include +#include "esp_err.h" + +/** + * @brief UDP client for sending commands and receiving responses + * + * Implements non-blocking UDP communication with configurable timeouts. + * Socket remains open for the lifetime of the instance. + */ +class UDPClient { +public: + UDPClient(); + ~UDPClient(); + + /** + * @brief Initialize UDP socket + * @return ESP_OK on success, error code otherwise + */ + esp_err_t init(); + + /** + * @brief Configure remote endpoint + * @param ip Remote IP address (e.g., "192.168.50.201") + * @param port Remote port number (e.g., 4211) + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if IP is invalid + */ + esp_err_t configure(const std::string& ip, uint16_t port); + + /** + * @brief Send command to remote endpoint + * @param command Command string to send (e.g., "TOGGLE", "STATUS", "MUTE", "UNMUTE") + * @return ESP_OK on success, ESP_FAIL if not configured or send failed + */ + esp_err_t send_command(const std::string& command); + + /** + * @brief Receive response from remote endpoint (non-blocking) + * @param response Output string for received data + * @param timeout_ms Timeout in milliseconds (0 = no wait, -1 = wait forever) + * @return ESP_OK on success, ESP_ERR_TIMEOUT on timeout, ESP_FAIL on error + */ + esp_err_t receive_response(std::string& response, int timeout_ms = 1000); + + /** + * @brief Check if client is configured with valid endpoint + * @return true if IP and port are configured + */ + bool is_configured() const { return configured_; } + + /** + * @brief Get current remote IP + */ + std::string get_ip() const { return remote_ip_; } + + /** + * @brief Get current remote port + */ + uint16_t get_port() const { return remote_port_; } + + /** + * @brief Close socket and reset configuration + */ + void close(); + +private: + int sock_fd_; // Socket file descriptor + struct sockaddr_in remote_addr_; // Remote endpoint address + std::string remote_ip_; // Remote IP address + uint16_t remote_port_; // Remote port number + bool configured_; // Whether endpoint is configured + bool initialized_; // Whether socket is initialized + + /** + * @brief Set socket to non-blocking mode + * @return ESP_OK on success + */ + esp_err_t set_nonblocking(); +}; diff --git a/main/network/wifi_handler.cpp b/main/network/wifi_handler.cpp new file mode 100644 index 0000000..6d9f9e6 --- /dev/null +++ b/main/network/wifi_handler.cpp @@ -0,0 +1,430 @@ +#include "wifi_handler.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "freertos/event_groups.h" +#include "esp_log.h" +#include "freertos/semphr.h" +#include "common/semaphore_guard.h" +#include "cJSON.h" + +static const char* TAG = "WifiHandler"; +static const char* WIFI_SSID_KEY = "ssid"; +static const char* WIFI_PASSWORD_STORE_KEY = "psw"; + +WifiHandler::WifiHandler( + // this handler is used to store/retrieve WiFi credentials + // should have a unique namespace for WiFi credentials + // it will be owned by WifiHandler and deleted in its destructor + std::unique_ptr kvs +) : kvs(std::move(kvs)) { + this->s_wifi_event_group = xEventGroupCreate(); + if (!this->s_wifi_event_group) { + ESP_LOGE(TAG, "Failed to create WiFi event group"); + } + this->scan_mutex = xSemaphoreCreateMutex(); + if (!this->scan_mutex) { + ESP_LOGE(TAG, "Failed to create scan mutex"); + } + this->connection_mutex = xSemaphoreCreateMutex(); + if (!this->connection_mutex) { + ESP_LOGE(TAG, "Failed to create connection mutex"); + } + this->credential_mutex = xSemaphoreCreateMutex(); + if (!this->credential_mutex) { + ESP_LOGE(TAG, "Failed to create credential mutex"); + } + if (this->kvs == nullptr) { + ESP_LOGW(TAG, "KVStorageHandler is null, WiFi credentials will not be stored"); + } else { + this->kvs->init(nullptr); + } +} + +WifiHandler::~WifiHandler() { + if (this->initialized) { + esp_wifi_stop(); + // Check if it should be called + esp_wifi_deinit(); + vEventGroupDelete(this->s_wifi_event_group); + if (!this->current_ssid.empty()) { + this->current_ssid.clear(); + } + vSemaphoreDelete(this->scan_mutex); + vSemaphoreDelete(this->connection_mutex); + esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &WifiHandler::wifi_event_handler); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &WifiHandler::wifi_event_handler); + this->initialized = false; + // unique_ptr will automatically delete the object + this->kvs = nullptr; + } +} + +esp_err_t WifiHandler::init() { + if (this->initialized) { + ESP_LOGW(TAG, "Already initialized, skipping"); + return ESP_OK; + } + esp_err_t err; + + // initialize TCP/IP stack and default event loop + err = esp_netif_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_netif_init failed: %s", esp_err_to_name(err)); + return err; + } + + err = esp_event_loop_create_default(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "esp_event_loop_create_default failed: %s", esp_err_to_name(err)); + return err; + } + + // create default WiFi station + esp_netif_create_default_wifi_sta(); + + // init WiFi driver + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + err = esp_wifi_init(&cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_init failed: %s", esp_err_to_name(err)); + return err; + } + + // register event handlers for WiFi and IP events + err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &WifiHandler::wifi_event_handler, this); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_event_handler_register failed: %s", esp_err_to_name(err)); + return err; + } + err = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &WifiHandler::wifi_event_handler, this); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_event_handler_register failed: %s", esp_err_to_name(err)); + return err; + } + + err = esp_wifi_set_mode(WIFI_MODE_STA); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_set_mode failed: %s", esp_err_to_name(err)); + return err; + } + + err = esp_wifi_start(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_start failed: %s", esp_err_to_name(err)); + return err; + } + + // get WiFi credentials from KV storage if available + std::string ssid; + std::string password; + this->get_wifi_credentials(ssid, password); + + // If KV storage didn't provide credentials, allow build-time injected values + // via compile-time defines BUILD_WIFI_SSID and BUILD_WIFI_PASSWORD. +#if defined(BUILD_WIFI_SSID) and defined(BUILD_WIFI_PASSWORD) + if (ssid.empty()) { + ssid = std::string(BUILD_WIFI_SSID); + ESP_LOGI(TAG, "Using build-time injected WiFi SSID"); + } + if (password.empty()) { + password = std::string(BUILD_WIFI_PASSWORD); + ESP_LOGI(TAG, "Using build-time injected WiFi password"); + } +#endif + + if (!ssid.empty() && !password.empty()) { + ESP_LOGI(TAG, "Found stored WiFi credentials, connecting to SSID: %s", ssid.c_str()); + err = this->connect(ssid, password); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to connect to stored WiFi credentials: %s", esp_err_to_name(err)); + } + } else { + ESP_LOGI(TAG, "No stored WiFi credentials found, not connecting"); + } + + initialized = true; + return ESP_OK; +} + +esp_err_t WifiHandler::connect(const std::string& ssid, const std::string& password) { + SemaphoreGuard guard(this->connection_mutex); + // wait up to 5 seconds to take the mutex + if (!guard.take(5000 / portTICK_PERIOD_MS)) { + ESP_LOGE(TAG, "Failed to take connection mutex"); + return ESP_FAIL; + } + + expect_disconnected = false; + if (!this->current_ssid.empty()) { + this->current_ssid.clear(); + } + this->current_ssid = ssid; + + // + wifi_config_t wifi_config = {}; + strncpy((char*)wifi_config.sta.ssid, this->current_ssid.c_str(), sizeof(wifi_config.sta.ssid)); + wifi_config.sta.ssid[sizeof(wifi_config.sta.ssid) - 1] = '\0'; + strncpy((char*)wifi_config.sta.password, password.c_str(), sizeof(wifi_config.sta.password)); + wifi_config.sta.password[sizeof(wifi_config.sta.password) - 1] = '\0'; + // set auth mode to WPA2_PSK minimum + wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; + + ESP_LOGI(TAG, "Connecting to SSID: %s", this->current_ssid.c_str()); + esp_err_t err = esp_wifi_set_config(wifi_interface_t::WIFI_IF_STA, &wifi_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi config: %s", esp_err_to_name(err)); + return err; + } + err = esp_wifi_connect(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initiate WiFi connection: %s", esp_err_to_name(err)); + return err; + } + + // store credentials after successful connection attempt + this->store_wifi_credentials(this->current_ssid, password); + + return ESP_OK; +} + +esp_err_t WifiHandler::connect(const std::string& ssid) { + std::string stored_ssid; + std::string stored_password; + this->get_wifi_credentials(stored_ssid, stored_password); + if (stored_ssid.empty() || stored_ssid != ssid) { + ESP_LOGE(TAG, "No stored credentials for SSID: %s", ssid.c_str()); + return ESP_FAIL; + } + esp_err_t err = this->connect(stored_ssid, stored_password); + return err; +} + +esp_err_t WifiHandler::reconnect() { + if (this->current_ssid.empty()) { + ESP_LOGE(TAG, "No current SSID set, cannot reconnect"); + return ESP_FAIL; + } + return this->connect(this->current_ssid); +} + +void WifiHandler::disconnect() { + SemaphoreGuard guard(this->connection_mutex); + // wait up to 5 seconds to take the mutex + if (!guard.take(5000 / portTICK_PERIOD_MS)) { + ESP_LOGE(TAG, "Failed to take connection mutex"); + return; + } + + expect_disconnected = true; + esp_wifi_disconnect(); + xEventGroupClearBits( + this->s_wifi_event_group, + WIFI_CONNECTED_BIT + ); +} + +esp_err_t WifiHandler::scan_networks( + wifi_ap_record_t*& ap_records, + uint16_t& ap_count +) { + SemaphoreGuard guard(this->scan_mutex); + // wait up to 5 seconds to take the mutex + if (!guard.take(5000 / portTICK_PERIOD_MS)) { + ESP_LOGE(TAG, "Failed to take scan mutex"); + return ESP_FAIL; + } + ap_records = nullptr; + ap_count = 0; + // start scan + + esp_err_t err = esp_wifi_scan_start(nullptr, true); // block until done + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start WiFi scan: %s", esp_err_to_name(err)); + return err; + } + + // get number of APs found + uint16_t ap_count_local = 0; + err = esp_wifi_scan_get_ap_num(&ap_count_local); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to get number of APs found: %s", esp_err_to_name(err)); + return err; + } + + wifi_ap_record_t* ap_records_local = new wifi_ap_record_t[ap_count_local]; + err = esp_wifi_scan_get_ap_records(&ap_count_local, ap_records_local); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to get AP records: %s", esp_err_to_name(err)); + delete[] ap_records_local; + return err; + } + + ap_records = ap_records_local; + ap_count = ap_count_local; + return ESP_OK; +} + +void WifiHandler::wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { + WifiHandler* self = static_cast(arg); + if (self == nullptr) { + ESP_LOGE(TAG, "wifi_event_handler received null WifiHandler pointer"); + return; + } + + switch (event_id) { + case WIFI_EVENT_STA_START: + // When the station starts, attempt to connect + ESP_LOGI(TAG, "WIFI_EVENT_STA_START"); + if (!self->expect_disconnected && !self->current_ssid.empty()) { + ESP_LOGI(TAG, "Station started, attempting to connect to SSID: %s", self->current_ssid.c_str()); + self->reconnect(); + } + // set the event bit to indicate started + xEventGroupSetBits( + self->s_wifi_event_group, + WIFI_STARTED_BIT + ); + break; + case WIFI_EVENT_STA_DISCONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED"); + if (!self->expect_disconnected) { + ESP_LOGI(TAG, "Unexpected disconnection, attempting to reconnect"); + self->reconnect(); + } + xEventGroupClearBits( + self->s_wifi_event_group, + WIFI_CONNECTED_BIT + ); + break; + case IP_EVENT_STA_GOT_IP: + { + ip_event_got_ip_t* event = static_cast(event_data); + ESP_LOGI(TAG, "WIFI_EVENT_STA_GOT_IP: " IPSTR, IP2STR(&event->ip_info.ip)); + xEventGroupSetBits( + self->s_wifi_event_group, + WIFI_CONNECTED_BIT + ); + break; + } + default: + ESP_LOGW(TAG, "Unhandled WiFi event: %d", event_id); + break; + } +} + +// +// private methods +// + +void WifiHandler::store_wifi_credentials(const std::string& ssid, const std::string& password) { + if (!kvs) { + ESP_LOGW(TAG, "KVStorageHandler not set, cannot store WiFi credentials"); + return; + } + SemaphoreGuard guard(this->credential_mutex); + // wait up to 5 seconds to take the mutex + if (!guard.take(5000 / portTICK_PERIOD_MS)) { + ESP_LOGE(TAG, "Failed to take credential mutex"); + return; + } + // store the password according to the JSON structure + std::string password_key_store = kvs->get(WIFI_PASSWORD_STORE_KEY); + cJSON* json = nullptr; + if (password_key_store.empty()) { + // create new JSON object + json = cJSON_CreateObject(); + } else { + // parse existing JSON + json = cJSON_Parse(password_key_store.c_str()); + if (json == nullptr) { + ESP_LOGE(TAG, "Failed to parse existing WiFi password JSON, creating new"); + json = cJSON_CreateObject(); + } + } + cJSON* credentials = cJSON_GetObjectItem(json, "credentials"); + if (credentials == nullptr || !cJSON_IsObject(credentials)) { + // create credentials object if it doesn't exist + credentials = cJSON_CreateObject(); + cJSON_AddItemToObject(json, "credentials", credentials); + } + // create SSID object + cJSON* ssid_item = cJSON_CreateObject(); + // add password field + cJSON_AddStringToObject(ssid_item, "password", password.c_str()); + // add SSID object to credentials + cJSON_AddItemToObject(credentials, ssid.c_str(), ssid_item); + // store updated JSON string + char* updated_json_str = cJSON_PrintUnformatted(json); + if (updated_json_str) { + kvs->put(WIFI_PASSWORD_STORE_KEY, std::string(updated_json_str)); + cJSON_free(updated_json_str); + } + cJSON_Delete(json); +} + +void WifiHandler::get_wifi_credentials(std::string& out_ssid, std::string& out_password) { + if (!kvs) { + ESP_LOGW(TAG, "KVStorageHandler not set, cannot get WiFi credentials"); + return; + } + SemaphoreGuard guard(this->credential_mutex); + // wait up to 5 seconds to take the mutex + if (!guard.take(5000 / portTICK_PERIOD_MS)) { + ESP_LOGE(TAG, "Failed to take credential mutex"); + return; + } + out_ssid = kvs->get(WIFI_SSID_KEY); + if (out_ssid.empty()) { + out_ssid = ""; + out_password = ""; + return; + } + // password is from KV storage, may be nullptr + std::string password_key_store = kvs->get(WIFI_PASSWORD_STORE_KEY); + if (password_key_store.empty()) { + out_password = ""; + return; + } + // parse from json + cJSON* json = cJSON_Parse(password_key_store.c_str()); + if (json == nullptr) { + ESP_LOGE(TAG, "Failed to parse WiFi password JSON"); + out_password = ""; + return; + } + cJSON* credentials = cJSON_GetObjectItem(json, "credentials"); + if (credentials == nullptr || !cJSON_IsObject(credentials)) { + ESP_LOGE(TAG, "WiFi password JSON does not contain valid 'credentials' object"); + out_password = ""; + cJSON_Delete(json); + return; + } + // get the ssid value + cJSON* ssid_item = cJSON_GetObjectItem(credentials, out_ssid.c_str()); + if (ssid_item == nullptr || !cJSON_IsObject(ssid_item)) { + ESP_LOGE(TAG, "WiFi password JSON does not contain valid SSID field for SSID: %s", out_ssid.c_str()); + out_password = ""; + cJSON_Delete(json); + return; + } + cJSON* password = cJSON_GetObjectItem(ssid_item, "password"); + if (password == nullptr || !cJSON_IsString(password)) { + ESP_LOGE(TAG, "WiFi password JSON does not contain valid 'password' field for SSID: %s", out_ssid.c_str()); + out_password = ""; + cJSON_Delete(json); + return; + } + out_password = password->valuestring; + cJSON_Delete(json); +} + +EventBits_t WifiHandler::wait_for_connection(TickType_t ticks_to_wait) { + return xEventGroupWaitBits( + s_wifi_event_group, + WIFI_CONNECTED_BIT, + pdFALSE, + pdTRUE, + ticks_to_wait + ); +} diff --git a/main/network/wifi_handler.h b/main/network/wifi_handler.h new file mode 100644 index 0000000..31a0f17 --- /dev/null +++ b/main/network/wifi_handler.h @@ -0,0 +1,57 @@ +#pragma once +#include "io/io.h" +#include "esp_wifi.h" +#include "freertos/event_groups.h" + +#define WIFI_STARTED_BIT (1 << 0) +#define WIFI_CONNECTED_BIT (1 << 1) + + +class WifiHandler { +public: + WifiHandler( + // this handler is used to store/retrieve WiFi credentials + // should have a unique namespace for WiFi credentials + // it will be owned by WifiHandler and deleted in its destructor + std::unique_ptr kvs + ); + ~WifiHandler(); + + esp_err_t init(); + esp_err_t connect(const std::string& ssid, const std::string& password); + esp_err_t connect(const std::string& ssid); // connect using stored password + esp_err_t reconnect(); // reconnect to current SSID + void disconnect(); + EventBits_t wait_for_connection(TickType_t ticks_to_wait); + // returns list of available networks, caller is responsible for freeing the returned memory + // returns nullptr if scan failed + esp_err_t scan_networks( + wifi_ap_record_t*& ap_records, + uint16_t& ap_count + ); + + static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data); + +private: + // prevent copying + WifiHandler(const WifiHandler&) = delete; + WifiHandler& operator=(const WifiHandler&) = delete; + // prevent moving + WifiHandler(WifiHandler&& other) = delete; + WifiHandler& operator=(WifiHandler&& other) = delete; + + void store_wifi_credentials(const std::string& ssid, const std::string& password); + void get_wifi_credentials(std::string& out_ssid, std::string& out_password); + + bool initialized = false; + std::unique_ptr kvs = nullptr; + EventGroupHandle_t s_wifi_event_group = 0; + SemaphoreHandle_t scan_mutex = nullptr; + SemaphoreHandle_t connection_mutex = nullptr; + SemaphoreHandle_t credential_mutex = nullptr; + // current connected / preferred SSID + std::string current_ssid; + // prevent auto-reconnect on expected disconnection, e.g. when user calls disconnect() + // should be reset to false after connect() + bool expect_disconnected = false; +}; diff --git a/main/ui/app_registry.h b/main/ui/app_registry.h new file mode 100644 index 0000000..1b3b463 --- /dev/null +++ b/main/ui/app_registry.h @@ -0,0 +1,39 @@ +#pragma once +#include "ui/ui_app.h" +#include + +/** + * @brief Registry for all available apps + * + * This singleton class maintains a list of all registered + * AppDescriptor instances, allowing the UIHandler or other + * components to query available apps. + */ +class AppRegistry { +public: + static AppRegistry& instance() { + static AppRegistry registry; + return registry; + } + + AppRegistry(const AppRegistry&) = delete; + void operator=(const AppRegistry&) = delete; + AppRegistry(AppRegistry&&) = delete; + void operator=(AppRegistry&&) = delete; + + + // Register a new app descriptor + // The registry takes ownership of the descriptor pointer. + void register_app(AppDescriptor* app_descriptor) { + _app_descriptors.push_back(app_descriptor); + } + + const std::vector& get_app_descriptors() const { + return _app_descriptors; + } + +private: + AppRegistry() = default; + ~AppRegistry() = default; + std::vector _app_descriptors = {}; +}; \ No newline at end of file diff --git a/main/ui/apps/discord_app.cpp b/main/ui/apps/discord_app.cpp new file mode 100644 index 0000000..954aba6 --- /dev/null +++ b/main/ui/apps/discord_app.cpp @@ -0,0 +1,652 @@ +#include "discord_app.h" +#include "esp_log.h" +#include "network/network.h" +#include + +static const char* TAG = "DiscordApp"; + +// ============================================================================ +// DiscordApp Implementation +// ============================================================================ + +DiscordApp::DiscordApp() + : page_stack_(nullptr) + , status_icon_label_(nullptr) + , status_text_label_(nullptr) + , mute_button_(nullptr) + , error_notification_(nullptr) + , ip_textarea_(nullptr) + , port_textarea_(nullptr) + , test_result_label_(nullptr) + , remote_port_(0) + , settings_configured_(false) + , current_state_(VoiceState::UNKNOWN) + , state_mutex_(nullptr) + , poll_task_handle_(nullptr) + , stop_polling_(false) + , consecutive_failures_(0) + , storage_(nullptr) { + + // Create mutex for thread-safe state access + state_mutex_ = xSemaphoreCreateMutex(); + + // Initialize storage + storage_ = new NVSStorageHandler(NVS_NAMESPACE); +} + +DiscordApp::~DiscordApp() { + stop_polling_task(); + + if (state_mutex_) { + vSemaphoreDelete(state_mutex_); + } + + if (storage_) { + delete storage_; + } +} + +esp_err_t DiscordApp::init(lv_obj_t* container) { + ESP_LOGI(TAG, "Initializing Discord app"); + + _container = container; + + // Initialize storage + storage_->init(nullptr); + + // Load saved settings + load_settings(); + + // Initialize UDP client + udp_client_.init(); + + // Configure UDP if settings are available + if (settings_configured_) { + udp_client_.configure(remote_ip_, remote_port_); + } + + // Create page stack + page_stack_ = new PageStack(container); + + // Build main page + page_stack_->push([this](lv_obj_t* page) { + build_main_page(page); + }); + + // Start polling task + start_polling_task(); + + return ESP_OK; +} + +esp_err_t DiscordApp::deinit() { + ESP_LOGI(TAG, "Deinitializing Discord app"); + + // Stop polling + stop_polling_task(); + + // Clean up page stack + if (page_stack_) { + delete page_stack_; + page_stack_ = nullptr; + } + + // Close UDP client + udp_client_.close(); + + // Reset widget pointers + status_icon_label_ = nullptr; + status_text_label_ = nullptr; + mute_button_ = nullptr; + error_notification_ = nullptr; + ip_textarea_ = nullptr; + port_textarea_ = nullptr; + test_result_label_ = nullptr; + + return ESP_OK; +} + +void DiscordApp::handle_event(uint32_t event_type, void* event_data) { + // Handle system events if needed +} + +bool DiscordApp::on_back_button_pressed() { + // If on settings page, go back to main page + if (page_stack_ && page_stack_->depth() > 1) { + page_stack_->pop(); + return true; + } + + // Let system handle back (return to app icons) + return false; +} + +// ============================================================================ +// Main Page UI +// ============================================================================ + +void DiscordApp::build_main_page(lv_obj_t* page) { + // Set up main page with flex column layout + lv_obj_set_flex_flow(page, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(page, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_all(page, 10, 0); + + // === Top Section: Error Notification === + error_notification_ = lv_obj_create(page); + lv_obj_set_width(error_notification_, LV_PCT(90)); + lv_obj_set_height(error_notification_, LV_SIZE_CONTENT); + lv_obj_set_style_bg_color(error_notification_, lv_color_hex(0xFF0000), 0); + lv_obj_set_style_bg_opa(error_notification_, LV_OPA_70, 0); + lv_obj_set_style_pad_all(error_notification_, 10, 0); + lv_obj_set_style_radius(error_notification_, 8, 0); + lv_obj_add_flag(error_notification_, LV_OBJ_FLAG_HIDDEN); + lv_obj_set_flex_flow(error_notification_, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(error_notification_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + lv_obj_t* error_label = lv_label_create(error_notification_); + lv_label_set_text(error_label, LV_SYMBOL_WARNING " Connection Lost"); + lv_obj_set_style_text_color(error_label, lv_color_white(), 0); + + // === Center Section: Main Content === + lv_obj_t* center_container = lv_obj_create(page); + lv_obj_set_size(center_container, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_bg_opa(center_container, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(center_container, 0, 0); + lv_obj_set_style_pad_all(center_container, 0, 0); + lv_obj_set_flex_flow(center_container, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(center_container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_row(center_container, 15, 0); + lv_obj_set_flex_grow(center_container, 1); + + // Status icon (large, centered) + status_icon_label_ = lv_label_create(center_container); + lv_label_set_text(status_icon_label_, LV_SYMBOL_MUTE); + + // Status text + status_text_label_ = lv_label_create(center_container); + lv_label_set_text(status_text_label_, "Unknown Status"); + + // Mute button + mute_button_ = lv_btn_create(center_container); + lv_obj_set_size(mute_button_, 200, 60); + lv_obj_add_event_cb(mute_button_, on_mute_button_clicked, LV_EVENT_CLICKED, this); + + lv_obj_t* mute_label = lv_label_create(mute_button_); + lv_label_set_text(mute_label, "MUTE"); + lv_obj_center(mute_label); + + // === Bottom Section: Settings and Config Prompt === + lv_obj_t* bottom_container = lv_obj_create(page); + lv_obj_set_size(bottom_container, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_bg_opa(bottom_container, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(bottom_container, 0, 0); + lv_obj_set_style_pad_all(bottom_container, 0, 0); + lv_obj_set_flex_flow(bottom_container, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(bottom_container, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + // Config prompt (left side) + if (!settings_configured_) { + lv_obj_t* config_prompt = lv_label_create(bottom_container); + lv_label_set_text(config_prompt, "Tap " LV_SYMBOL_SETTINGS " to configure"); + lv_obj_set_style_text_color(config_prompt, lv_color_hex(0x888888), 0); + } else { + // Empty spacer if configured + lv_obj_t* spacer = lv_obj_create(bottom_container); + lv_obj_set_size(spacer, 0, 0); + lv_obj_set_style_bg_opa(spacer, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(spacer, 0, 0); + } + + // Settings button (right side) + lv_obj_t* settings_btn = lv_btn_create(bottom_container); + lv_obj_set_size(settings_btn, 60, 60); + lv_obj_add_event_cb(settings_btn, on_settings_button_clicked, LV_EVENT_CLICKED, this); + + lv_obj_t* settings_icon = lv_label_create(settings_btn); + lv_label_set_text(settings_icon, LV_SYMBOL_SETTINGS); + lv_obj_center(settings_icon); + + // Update display with current state + update_status_display(); +} + +// ============================================================================ +// Settings Page UI +// ============================================================================ + +void DiscordApp::build_settings_page(lv_obj_t* page) { + // Title + lv_obj_t* title = lv_label_create(page); + lv_label_set_text(title, "Discord Bridge Settings"); + // Using default font + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20); + + // IP address label + lv_obj_t* ip_label = lv_label_create(page); + lv_label_set_text(ip_label, "Bridge IP Address:"); + lv_obj_align(ip_label, LV_ALIGN_TOP_LEFT, 20, 70); + + // IP address textarea + ip_textarea_ = lv_textarea_create(page); + lv_obj_set_size(ip_textarea_, 300, 50); + lv_obj_align(ip_textarea_, LV_ALIGN_TOP_LEFT, 20, 100); + lv_textarea_set_one_line(ip_textarea_, true); + lv_textarea_set_placeholder_text(ip_textarea_, "e.g., 192.168.1.100"); + + if (!remote_ip_.empty()) { + lv_textarea_set_text(ip_textarea_, remote_ip_.c_str()); + } + + // Port label + lv_obj_t* port_label = lv_label_create(page); + lv_label_set_text(port_label, "Bridge Port:"); + lv_obj_align(port_label, LV_ALIGN_TOP_LEFT, 20, 170); + + // Port textarea + port_textarea_ = lv_textarea_create(page); + lv_obj_set_size(port_textarea_, 150, 50); + lv_obj_align(port_textarea_, LV_ALIGN_TOP_LEFT, 20, 200); + lv_textarea_set_one_line(port_textarea_, true); + lv_textarea_set_placeholder_text(port_textarea_, "e.g., 4211"); + lv_textarea_set_accepted_chars(port_textarea_, "0123456789"); + lv_textarea_set_max_length(port_textarea_, 5); + + if (remote_port_ > 0) { + char port_str[8]; + snprintf(port_str, sizeof(port_str), "%u", remote_port_); + lv_textarea_set_text(port_textarea_, port_str); + } + + // Test connection button + lv_obj_t* test_btn = lv_btn_create(page); + lv_obj_set_size(test_btn, 200, 50); + lv_obj_align(test_btn, LV_ALIGN_TOP_MID, 0, 270); + lv_obj_add_event_cb(test_btn, on_test_connection_clicked, LV_EVENT_CLICKED, this); + + lv_obj_t* test_label = lv_label_create(test_btn); + lv_label_set_text(test_label, "Test Connection"); + lv_obj_center(test_label); + + // Test result label + test_result_label_ = lv_label_create(page); + lv_label_set_text(test_result_label_, ""); + lv_obj_align(test_result_label_, LV_ALIGN_TOP_MID, 0, 330); + + // Save button + lv_obj_t* save_btn = lv_btn_create(page); + lv_obj_set_size(save_btn, 150, 50); + lv_obj_align(save_btn, LV_ALIGN_BOTTOM_MID, 0, -20); + lv_obj_add_event_cb(save_btn, on_save_settings_clicked, LV_EVENT_CLICKED, this); + lv_obj_set_style_bg_color(save_btn, lv_color_hex(0x00AA00), 0); + + lv_obj_t* save_label = lv_label_create(save_btn); + lv_label_set_text(save_label, LV_SYMBOL_SAVE " Save"); + lv_obj_set_style_text_color(save_label, lv_color_white(), 0); + lv_obj_center(save_label); +} + +void DiscordApp::show_settings_page() { + page_stack_->push([this](lv_obj_t* page) { + build_settings_page(page); + }); +} + +// ============================================================================ +// Event Callbacks +// ============================================================================ + +void DiscordApp::on_mute_button_clicked(lv_event_t* e) { + DiscordApp* app = static_cast(lv_event_get_user_data(e)); + if (app) { + app->send_mute_command(); + } +} + +void DiscordApp::on_settings_button_clicked(lv_event_t* e) { + DiscordApp* app = static_cast(lv_event_get_user_data(e)); + if (app) { + app->show_settings_page(); + } +} + +void DiscordApp::on_save_settings_clicked(lv_event_t* e) { + DiscordApp* app = static_cast(lv_event_get_user_data(e)); + if (app) { + app->save_settings(); + + // Go back to main page + if (app->page_stack_->depth() > 1) { + app->page_stack_->pop(); + } + } +} + +void DiscordApp::on_test_connection_clicked(lv_event_t* e) { + DiscordApp* app = static_cast(lv_event_get_user_data(e)); + if (!app || !app->test_result_label_) return; + + // Get values from textareas + const char* ip = lv_textarea_get_text(app->ip_textarea_); + const char* port_str = lv_textarea_get_text(app->port_textarea_); + + if (strlen(ip) == 0 || strlen(port_str) == 0) { + lv_label_set_text(app->test_result_label_, LV_SYMBOL_CLOSE " Please fill all fields"); + lv_obj_set_style_text_color(app->test_result_label_, lv_color_hex(0xFF0000), 0); + return; + } + + uint16_t port = atoi(port_str); + if (port == 0) { + lv_label_set_text(app->test_result_label_, LV_SYMBOL_CLOSE " Invalid port"); + lv_obj_set_style_text_color(app->test_result_label_, lv_color_hex(0xFF0000), 0); + return; + } + + // Configure UDP temporarily + UDPClient test_client; + test_client.init(); + esp_err_t err = test_client.configure(ip, port); + + if (err != ESP_OK) { + lv_label_set_text(app->test_result_label_, LV_SYMBOL_CLOSE " Invalid IP address"); + lv_obj_set_style_text_color(app->test_result_label_, lv_color_hex(0xFF0000), 0); + return; + } + + lv_label_set_text(app->test_result_label_, "Testing..."); + lv_obj_set_style_text_color(app->test_result_label_, lv_color_hex(0x0000FF), 0); + + // Send STATUS command + err = test_client.send_command("STATUS"); + if (err != ESP_OK) { + lv_label_set_text(app->test_result_label_, LV_SYMBOL_CLOSE " Failed to send"); + lv_obj_set_style_text_color(app->test_result_label_, lv_color_hex(0xFF0000), 0); + return; + } + + // Wait for response + std::string response; + err = test_client.receive_response(response, 3000); + + if (err == ESP_OK && (response == "MUTED" || response == "UNMUTED")) { + lv_label_set_text(app->test_result_label_, LV_SYMBOL_OK " Connection successful!"); + lv_obj_set_style_text_color(app->test_result_label_, lv_color_hex(0x00AA00), 0); + } else { + lv_label_set_text(app->test_result_label_, LV_SYMBOL_CLOSE " No response from bridge"); + lv_obj_set_style_text_color(app->test_result_label_, lv_color_hex(0xFF0000), 0); + } +} + +// ============================================================================ +// UDP Communication +// ============================================================================ + +void DiscordApp::send_mute_command() { + if (!settings_configured_) { + ESP_LOGW(TAG, "Cannot send command: not configured"); + return; + } + + esp_err_t err = udp_client_.send_command("MUTE"); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send MUTE command"); + } +} + +bool DiscordApp::test_connection() { + if (!settings_configured_) { + return false; + } + + esp_err_t err = udp_client_.send_command("STATUS"); + if (err != ESP_OK) { + return false; + } + + std::string response; + err = udp_client_.receive_response(response, RESPONSE_TIMEOUT_MS); + + return (err == ESP_OK && (response == "MUTED" || response == "UNMUTED")); +} + +void DiscordApp::update_status_display() { + if (!status_icon_label_ || !status_text_label_) { + return; + } + + // Thread-safe state access + VoiceState state; + if (xSemaphoreTake(state_mutex_, pdMS_TO_TICKS(100)) == pdTRUE) { + state = current_state_; + xSemaphoreGive(state_mutex_); + } else { + return; + } + + switch (state) { + case VoiceState::MUTED: + lv_label_set_text(status_icon_label_, LV_SYMBOL_MUTE); + lv_label_set_text(status_text_label_, "Muted"); + lv_obj_set_style_text_color(status_icon_label_, lv_color_hex(0xFF0000), 0); + break; + + case VoiceState::UNMUTED: + lv_label_set_text(status_icon_label_, LV_SYMBOL_VOLUME_MAX); + lv_label_set_text(status_text_label_, "Unmuted"); + lv_obj_set_style_text_color(status_icon_label_, lv_color_hex(0x00AA00), 0); + break; + + case VoiceState::ERROR: + lv_label_set_text(status_icon_label_, LV_SYMBOL_WARNING); + lv_label_set_text(status_text_label_, "Connection Error"); + lv_obj_set_style_text_color(status_icon_label_, lv_color_hex(0xFF8800), 0); + break; + + case VoiceState::UNKNOWN: + default: + lv_label_set_text(status_icon_label_, LV_SYMBOL_BLUETOOTH); + lv_label_set_text(status_text_label_, "Unknown Status"); + lv_obj_set_style_text_color(status_icon_label_, lv_color_hex(0x888888), 0); + break; + } +} + +void DiscordApp::show_error_notification(bool show) { + if (error_notification_) { + if (show) { + lv_obj_clear_flag(error_notification_, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(error_notification_, LV_OBJ_FLAG_HIDDEN); + } + } +} + +// ============================================================================ +// Settings Management +// ============================================================================ + +void DiscordApp::load_settings() { + remote_ip_ = storage_->get(NVS_KEY_IP); + std::string port_str = storage_->get(NVS_KEY_PORT); + + if (!remote_ip_.empty() && !port_str.empty()) { + remote_port_ = atoi(port_str.c_str()); + settings_configured_ = (remote_port_ > 0); + ESP_LOGI(TAG, "Loaded settings: %s:%u", remote_ip_.c_str(), remote_port_); + } else { + settings_configured_ = false; + ESP_LOGI(TAG, "No settings found, user setup required"); + } +} + +void DiscordApp::save_settings() { + if (!ip_textarea_ || !port_textarea_) { + return; + } + + const char* ip = lv_textarea_get_text(ip_textarea_); + const char* port_str = lv_textarea_get_text(port_textarea_); + + if (strlen(ip) == 0 || strlen(port_str) == 0) { + ESP_LOGW(TAG, "Cannot save: empty fields"); + return; + } + + uint16_t port = atoi(port_str); + if (port == 0) { + ESP_LOGW(TAG, "Cannot save: invalid port"); + return; + } + + // Save to NVS + storage_->put(NVS_KEY_IP, ip); + storage_->put(NVS_KEY_PORT, port_str); + + // Update local config + remote_ip_ = ip; + remote_port_ = port; + settings_configured_ = true; + + // Reconfigure UDP client + udp_client_.configure(remote_ip_, remote_port_); + + // Reset failure counter + consecutive_failures_ = 0; + + ESP_LOGI(TAG, "Settings saved: %s:%u", remote_ip_.c_str(), remote_port_); +} + +// ============================================================================ +// Polling Task +// ============================================================================ + +void DiscordApp::poll_task(void* param) { + DiscordApp* app = static_cast(param); + + ESP_LOGI(TAG, "Polling task started"); + + while (!app->stop_polling_) { + app->poll_status(); + + // Use longer interval if in error state + int interval = (app->consecutive_failures_ >= MAX_FAILURES_BEFORE_ERROR) + ? ERROR_POLL_INTERVAL_MS + : POLL_INTERVAL_MS; + + vTaskDelay(pdMS_TO_TICKS(interval)); + } + + ESP_LOGI(TAG, "Polling task stopped"); + app->poll_task_handle_ = nullptr; + vTaskDelete(nullptr); +} + +void DiscordApp::start_polling_task() { + if (poll_task_handle_) { + ESP_LOGW(TAG, "Polling task already running"); + return; + } + + stop_polling_ = false; + xTaskCreate(poll_task, "discord_poll", 4096, this, 5, &poll_task_handle_); +} + +void DiscordApp::stop_polling_task() { + if (!poll_task_handle_) { + return; + } + + ESP_LOGI(TAG, "Stopping polling task"); + stop_polling_ = true; + + // Wait for task to finish (max 2 seconds) + int wait_count = 0; + while (poll_task_handle_ && wait_count < 20) { + vTaskDelay(pdMS_TO_TICKS(100)); + wait_count++; + } + + if (poll_task_handle_) { + ESP_LOGW(TAG, "Force deleting polling task"); + vTaskDelete(poll_task_handle_); + poll_task_handle_ = nullptr; + } +} + +void DiscordApp::poll_status() { + if (!settings_configured_) { + // Don't poll if not configured + return; + } + + // Send STATUS command + esp_err_t err = udp_client_.send_command("STATUS"); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Failed to send STATUS command"); + consecutive_failures_++; + + if (consecutive_failures_ >= MAX_FAILURES_BEFORE_ERROR) { + if (xSemaphoreTake(state_mutex_, pdMS_TO_TICKS(100)) == pdTRUE) { + current_state_ = VoiceState::ERROR; + xSemaphoreGive(state_mutex_); + } + show_error_notification(true); + } + return; + } + + // Wait for response + std::string response; + err = udp_client_.receive_response(response, RESPONSE_TIMEOUT_MS); + + if (err == ESP_OK) { + // Success - reset failure counter + consecutive_failures_ = 0; + show_error_notification(false); + + // Update state + VoiceState new_state = VoiceState::UNKNOWN; + if (response == "MUTED") { + new_state = VoiceState::MUTED; + } else if (response == "UNMUTED") { + new_state = VoiceState::UNMUTED; + } + + if (xSemaphoreTake(state_mutex_, pdMS_TO_TICKS(100)) == pdTRUE) { + current_state_ = new_state; + xSemaphoreGive(state_mutex_); + } + + update_status_display(); + + } else { + // Timeout or error + consecutive_failures_++; + ESP_LOGW(TAG, "No response (failures: %d)", consecutive_failures_); + + if (consecutive_failures_ >= MAX_FAILURES_BEFORE_ERROR) { + if (xSemaphoreTake(state_mutex_, pdMS_TO_TICKS(100)) == pdTRUE) { + current_state_ = VoiceState::ERROR; + xSemaphoreGive(state_mutex_); + } + update_status_display(); + show_error_notification(true); + } + } +} + +// ============================================================================ +// DiscordAppDescriptor Implementation +// ============================================================================ + +DiscordAppDescriptor::DiscordAppDescriptor() + : AppDescriptor("Discord", new DiscordApp()) { + // Auto-register on construction + AppRegistry::instance().register_app(this); +} + +void DiscordAppDescriptor::draw_icon(lv_obj_t* parent) { + lv_obj_t* icon = lv_label_create(parent); + lv_label_set_text(icon, LV_SYMBOL_CALL); + lv_obj_center(icon); +} diff --git a/main/ui/apps/discord_app.h b/main/ui/apps/discord_app.h new file mode 100644 index 0000000..d1e5f6e --- /dev/null +++ b/main/ui/apps/discord_app.h @@ -0,0 +1,123 @@ +#pragma once + +#include "ui/ui_app.h" +#include "ui/page_stack.h" +#include "ui/app_registry.h" +#include "network/udp_client.h" +#include "io/nvs_handler.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include + +/** + * @brief Discord voice control app + * + * Allows control of Discord voice settings (mute/unmute) via UDP communication + * with the IotDis Node.js bridge. Features: + * - Main page: Status icon + mute button + * - Settings page: IP/port configuration with connection test + * - Periodic status polling with automatic retry + * - Error notification when remote is unreachable + */ +class DiscordApp : public UIApp { +public: + DiscordApp(); + ~DiscordApp() override; + + // UIApp interface + esp_err_t init(lv_obj_t* container) override; + esp_err_t deinit() override; + std::string get_name() const override { return "Discord"; } + void handle_event(uint32_t event_type, void* event_data = nullptr) override; + bool on_back_button_pressed() override; + +private: + // Voice state enum + enum class VoiceState { + UNKNOWN, + MUTED, + UNMUTED, + ERROR + }; + + // Page management + PageStack* page_stack_; + void build_main_page(lv_obj_t* page); + void build_settings_page(lv_obj_t* page); + void show_settings_page(); + + // Main page widgets + lv_obj_t* status_icon_label_; + lv_obj_t* status_text_label_; + lv_obj_t* mute_button_; + lv_obj_t* error_notification_; + + // Settings page widgets + lv_obj_t* ip_textarea_; + lv_obj_t* port_textarea_; + lv_obj_t* test_result_label_; + + // UDP client and configuration + UDPClient udp_client_; + std::string remote_ip_; + uint16_t remote_port_; + bool settings_configured_; + + // Voice state + VoiceState current_state_; + SemaphoreHandle_t state_mutex_; + + // Polling task + TaskHandle_t poll_task_handle_; + bool stop_polling_; + int consecutive_failures_; + static constexpr int MAX_FAILURES_BEFORE_ERROR = 3; + static constexpr int POLL_INTERVAL_MS = 5000; + static constexpr int ERROR_POLL_INTERVAL_MS = 15000; + static constexpr int RESPONSE_TIMEOUT_MS = 2000; + + // NVS storage + NVSStorageHandler* storage_; + static constexpr const char* NVS_NAMESPACE = "discord"; + static constexpr const char* NVS_KEY_IP = "remote_ip"; + static constexpr const char* NVS_KEY_PORT = "remote_port"; + + // Event callbacks + static void on_mute_button_clicked(lv_event_t* e); + static void on_settings_button_clicked(lv_event_t* e); + static void on_save_settings_clicked(lv_event_t* e); + static void on_test_connection_clicked(lv_event_t* e); + + // UDP communication + void send_mute_command(); + bool test_connection(); + void update_status_display(); + void show_error_notification(bool show); + + // Settings management + void load_settings(); + void save_settings(); + + // Polling task + static void poll_task(void* param); + void start_polling_task(); + void stop_polling_task(); + void poll_status(); +}; + +/** + * @brief Discord app descriptor for registration + */ +class DiscordAppDescriptor : public AppDescriptor { +public: + static DiscordAppDescriptor& instance() { + static DiscordAppDescriptor instance; + return instance; + } + + void draw_icon(lv_obj_t* parent) override; + +private: + DiscordAppDescriptor(); +}; diff --git a/main/ui/apps/mtr_app.cpp b/main/ui/apps/mtr_app.cpp new file mode 100644 index 0000000..7108a73 --- /dev/null +++ b/main/ui/apps/mtr_app.cpp @@ -0,0 +1,399 @@ +#include "apps/mtr_app.h" +#include "external/mtr/arrival.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define TAG "MtrApp" + +// Event type for network ready +#define EVENT_NETWORK_READY 1 + +MtrApp::MtrApp() { + _mtr_handler = std::make_unique(); +} + +esp_err_t MtrApp::init(lv_obj_t* container) { + if (!container) { + ESP_LOGE(TAG, "Container is null"); + return ESP_ERR_INVALID_ARG; + } + + _container = container; + ESP_LOGI(TAG, "Initializing MTR app..."); + + // Create page stack + _page_stack = std::make_unique(container); + + // Load all lines + _all_lines = _mtr_handler->get_lines(); + ESP_LOGI(TAG, "Loaded %zu MTR lines", _all_lines.size()); + + // Build initial line selection page + _page_stack->push([this](lv_obj_t* page) { + this->build_line_selection_page(page); + }); + + ESP_LOGI(TAG, "MTR app initialized successfully"); + return ESP_OK; +} + +esp_err_t MtrApp::deinit(void) { + ESP_LOGI(TAG, "Deinitializing MTR app"); + + // Clear page stack + if (_page_stack) { + _page_stack->clear(); + _page_stack.reset(); + } + + // Clear state + _selected_line_code.clear(); + _selected_station_code.clear(); + _selected_line_info = nullptr; + _all_lines.clear(); + + return ESP_OK; +} + +std::string MtrApp::get_name(void) const { + return "MTR"; +} + +bool MtrApp::on_back_button_pressed(void) { + if (_page_stack && _page_stack->depth() > 1) { + _page_stack->pop(); + return true; // Handled + } + return false; // Not handled, go back to main menu +} + +void MtrApp::handle_event(uint32_t event_type, void* event_data) { + if (event_type == EVENT_NETWORK_READY) { + ESP_LOGI(TAG, "Network ready event received"); + } +} + +void MtrApp::build_line_selection_page(lv_obj_t* page_container) { + ESP_LOGI(TAG, "Building line selection page"); + + // Title + lv_obj_t* title = lv_label_create(page_container); + lv_label_set_text(title, "選擇路綫 Select Line"); + lv_obj_set_style_text_color(title, lv_color_black(), 0); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); + + // Scrollable container for line buttons + lv_obj_t* scroll_container = lv_obj_create(page_container); + lv_obj_set_size(scroll_container, lv_pct(95), lv_pct(85)); + lv_obj_align(scroll_container, LV_ALIGN_TOP_MID, 0, 40); + lv_obj_set_flex_flow(scroll_container, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(scroll_container, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_all(scroll_container, 5, 0); + lv_obj_set_style_pad_row(scroll_container, 8, 0); + + // Create button for each line + for (size_t i = 0; i < _all_lines.size(); i++) { + LineInfo* line = &_all_lines[i]; + + lv_obj_t* btn = lv_btn_create(scroll_container); + lv_obj_set_size(btn, lv_pct(95), 60); + + // Set button color based on line color + uint32_t color = parse_color_hex(line->color()); + lv_obj_set_style_bg_color(btn, lv_color_hex(color), 0); + + // Button label + lv_obj_t* label = lv_label_create(btn); + lv_label_set_text_fmt(label, "%s", line->code()); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_center(label); + + // Store line pointer in user data + lv_obj_add_event_cb(btn, line_button_event_cb, LV_EVENT_CLICKED, this); + lv_obj_set_user_data(btn, (void*)line); + } + + ESP_LOGI(TAG, "Created %zu line buttons", _all_lines.size()); +} + +void MtrApp::build_station_selection_page(lv_obj_t* page_container) { + ESP_LOGI(TAG, "Building station selection page for line: %s", _selected_line_code.c_str()); + + if (!_selected_line_info) { + ESP_LOGE(TAG, "No line info selected"); + return; + } + + // Title with line code + lv_obj_t* title = lv_label_create(page_container); + lv_label_set_text_fmt(title, "%s 路綫車站", _selected_line_code.c_str()); + lv_obj_set_style_text_color(title, lv_color_black(), 0); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); + + // Scrollable container for station buttons + lv_obj_t* scroll_container = lv_obj_create(page_container); + lv_obj_set_size(scroll_container, lv_pct(95), lv_pct(85)); + lv_obj_align(scroll_container, LV_ALIGN_TOP_MID, 0, 40); + lv_obj_set_flex_flow(scroll_container, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(scroll_container, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_all(scroll_container, 5, 0); + lv_obj_set_style_pad_row(scroll_container, 6, 0); + + // Create button for each station + const std::vector* stations = _selected_line_info->stations(); + for (size_t i = 0; i < stations->size(); i++) { + const StationInfo* station = &(*stations)[i]; + + lv_obj_t* btn = lv_btn_create(scroll_container); + lv_obj_set_size(btn, lv_pct(95), 50); + lv_obj_set_style_bg_color(btn, lv_color_hex(0x4CAF50), 0); + + // Button label with station name and code + lv_obj_t* label = lv_label_create(btn); + lv_label_set_text_fmt(label, "%s (%s)", station->name(), station->code()); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_center(label); + + // Store station pointer in user data + lv_obj_add_event_cb(btn, station_button_event_cb, LV_EVENT_CLICKED, this); + lv_obj_set_user_data(btn, (void*)station); + } + + ESP_LOGI(TAG, "Created %zu station buttons", stations->size()); +} + +void MtrApp::build_arrival_page(lv_obj_t* page_container) { + ESP_LOGI(TAG, "Building arrival page"); + + // Title + lv_obj_t* title = lv_label_create(page_container); + lv_label_set_text_fmt(title, "%s - %s", _selected_line_code.c_str(), _selected_station_code.c_str()); + lv_obj_set_style_text_color(title, lv_color_black(), 0); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); + + // Loading message + lv_obj_t* loading_label = lv_label_create(page_container); + lv_label_set_text(loading_label, "載入中... Loading..."); + lv_obj_set_style_text_color(loading_label, lv_color_black(), 0); + lv_obj_center(loading_label); + + // Refresh button + lv_obj_t* refresh_btn = lv_btn_create(page_container); + lv_obj_set_size(refresh_btn, 120, 50); + lv_obj_align(refresh_btn, LV_ALIGN_BOTTOM_MID, 0, -10); + lv_obj_add_event_cb(refresh_btn, refresh_button_event_cb, LV_EVENT_CLICKED, this); + + lv_obj_t* refresh_label = lv_label_create(refresh_btn); + lv_label_set_text(refresh_label, LV_SYMBOL_REFRESH " 重新整理"); + lv_obj_set_style_text_color(refresh_label, lv_color_white(), 0); + lv_obj_center(refresh_label); + + // Load arrival data asynchronously + load_arrival_data(page_container); +} + +void MtrApp::load_arrival_data(lv_obj_t* page_container) { + if (!_network_handler) { + ESP_LOGW(TAG, "Network handler not set, cannot fetch arrival data"); + // Update UI to show error + lv_obj_t* error_label = lv_label_create(page_container); + lv_label_set_text(error_label, "網絡未就緒\nNetwork not ready"); + lv_obj_set_style_text_color(error_label, lv_color_black(), 0); + lv_obj_align(error_label, LV_ALIGN_CENTER, 0, -30); + return; + } + + ESP_LOGI(TAG, "Fetching arrival data for %s/%s", _selected_line_code.c_str(), _selected_station_code.c_str()); + + StationArrivalInfo* arrival_info = nullptr; + MtrArrivalErrorCode error_code = _mtr_handler->get_next_arrival_info( + _network_handler, + _selected_line_code, + _selected_station_code, + arrival_info, + Language::TC + ); + + // Clear loading message + lv_obj_clean(page_container); + + // Recreate title + lv_obj_t* title = lv_label_create(page_container); + lv_label_set_text_fmt(title, "%s - %s", _selected_line_code.c_str(), _selected_station_code.c_str()); + lv_obj_set_style_text_color(title, lv_color_black(), 0); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); + + if (error_code != MtrArrivalErrorCode::NONE || !arrival_info) { + ESP_LOGE(TAG, "Failed to fetch arrival info, error code: %d", (int)error_code); + + lv_obj_t* error_label = lv_label_create(page_container); + lv_label_set_text(error_label, "無法取得班次資料\nFailed to fetch arrival data"); + lv_obj_set_style_text_color(error_label, lv_color_black(), 0); + lv_obj_center(error_label); + return; + } + + // Create scrollable container for arrivals + lv_obj_t* scroll_container = lv_obj_create(page_container); + lv_obj_set_size(scroll_container, lv_pct(95), lv_pct(75)); + lv_obj_align(scroll_container, LV_ALIGN_TOP_MID, 0, 45); + lv_obj_set_style_pad_all(scroll_container, 10, 0); + + int y_offset = 0; + + // Display UP direction trains + lv_obj_t* up_header = lv_label_create(scroll_container); + lv_label_set_text(up_header, "上行 UP:"); + lv_obj_set_style_text_color(up_header, lv_color_black(), 0); + lv_obj_set_pos(up_header, 0, y_offset); + y_offset += 30; + + const std::vector* up_arrivals = arrival_info->up_arrivals(); + if (up_arrivals->empty()) { + lv_obj_t* no_train = lv_label_create(scroll_container); + lv_label_set_text(no_train, " 暫無班次 No trains"); + lv_obj_set_style_text_color(no_train, lv_color_hex(0x666666), 0); + lv_obj_set_pos(no_train, 10, y_offset); + y_offset += 25; + } else { + for (const auto& arrival : *up_arrivals) { + lv_obj_t* arrival_label = lv_label_create(scroll_container); + lv_label_set_text_fmt(arrival_label, " %s → %s", arrival.arrival_time(), arrival.destination()); + lv_obj_set_style_text_color(arrival_label, lv_color_black(), 0); + lv_obj_set_pos(arrival_label, 10, y_offset); + y_offset += 25; + } + } + + y_offset += 10; + + // Display DOWN direction trains + lv_obj_t* down_header = lv_label_create(scroll_container); + lv_label_set_text(down_header, "下行 DOWN:"); + lv_obj_set_style_text_color(down_header, lv_color_black(), 0); + lv_obj_set_pos(down_header, 0, y_offset); + y_offset += 30; + + const std::vector* down_arrivals = arrival_info->down_arrivals(); + if (down_arrivals->empty()) { + lv_obj_t* no_train = lv_label_create(scroll_container); + lv_label_set_text(no_train, " 暫無班次 No trains"); + lv_obj_set_style_text_color(no_train, lv_color_hex(0x666666), 0); + lv_obj_set_pos(no_train, 10, y_offset); + y_offset += 25; + } else { + for (const auto& arrival : *down_arrivals) { + lv_obj_t* arrival_label = lv_label_create(scroll_container); + lv_label_set_text_fmt(arrival_label, " %s → %s", arrival.arrival_time(), arrival.destination()); + lv_obj_set_style_text_color(arrival_label, lv_color_black(), 0); + lv_obj_set_pos(arrival_label, 10, y_offset); + y_offset += 25; + } + } + + // Clean up + if (arrival_info != nullptr) { + delete arrival_info; + } + + // Refresh button + lv_obj_t* refresh_btn = lv_btn_create(page_container); + lv_obj_set_size(refresh_btn, 120, 50); + lv_obj_align(refresh_btn, LV_ALIGN_BOTTOM_MID, 0, -10); + lv_obj_add_event_cb(refresh_btn, refresh_button_event_cb, LV_EVENT_CLICKED, this); + + lv_obj_t* refresh_label = lv_label_create(refresh_btn); + lv_label_set_text(refresh_label, LV_SYMBOL_REFRESH " 重新整理"); + lv_obj_set_style_text_color(refresh_label, lv_color_white(), 0); + lv_obj_center(refresh_label); + + ESP_LOGI(TAG, "Arrival data displayed successfully"); +} + +uint32_t MtrApp::parse_color_hex(const char* hex_str) { + if (!hex_str || hex_str[0] != '#') { + return 0x808080; // Default gray + } + + // Skip the '#' character + hex_str++; + + uint32_t color = 0; + sscanf(hex_str, "%" SCNx32, &color); + return color; +} + +void MtrApp::line_button_event_cb(lv_event_t* e) { + lv_event_code_t code = lv_event_get_code(e); + if (code == LV_EVENT_CLICKED) { + MtrApp* app = (MtrApp*)lv_event_get_user_data(e); + lv_obj_t* btn = (lv_obj_t*)lv_event_get_target(e); + LineInfo* line = (LineInfo*)lv_obj_get_user_data(btn); + + if (app && line) { + ESP_LOGI(TAG, "Line selected: %s", line->code()); + app->_selected_line_code = line->code(); + app->_selected_line_info = line; + + // Push station selection page + app->_page_stack->push([app](lv_obj_t* page) { + app->build_station_selection_page(page); + }); + } + } +} + +void MtrApp::station_button_event_cb(lv_event_t* e) { + lv_event_code_t code = lv_event_get_code(e); + if (code == LV_EVENT_CLICKED) { + MtrApp* app = (MtrApp*)lv_event_get_user_data(e); + lv_obj_t* btn = (lv_obj_t*)lv_event_get_target(e); + const StationInfo* station = (const StationInfo*)lv_obj_get_user_data(btn); + + if (app && station) { + ESP_LOGI(TAG, "Station selected: %s (%s)", station->name(), station->code()); + app->_selected_station_code = station->code(); + + // Push arrival page + app->_page_stack->push([app](lv_obj_t* page) { + app->build_arrival_page(page); + }); + } + } +} + +void MtrApp::refresh_button_event_cb(lv_event_t* e) { + lv_event_code_t code = lv_event_get_code(e); + if (code == LV_EVENT_CLICKED) { + MtrApp* app = (MtrApp*)lv_event_get_user_data(e); + if (app && app->_page_stack && app->_page_stack->current_page()) { + ESP_LOGI(TAG, "Refresh button clicked"); + app->load_arrival_data(app->_page_stack->current_page()); + } + } +} + +// MtrAppDescriptor implementation +MtrApp* MtrAppDescriptor::_app_instance = nullptr; + +MtrAppDescriptor::MtrAppDescriptor() + : AppDescriptor("MTR", []() -> UIApp* { + if (!MtrAppDescriptor::_app_instance) { + MtrAppDescriptor::_app_instance = new MtrApp(); + } + return MtrAppDescriptor::_app_instance; + }()) { + // Register with AppRegistry + AppRegistry::instance().register_app(this); + ESP_LOGI(TAG, "MtrApp registered with AppRegistry"); +} + +void MtrAppDescriptor::draw_icon(lv_obj_t* parent) { + // Create MTR icon with train symbol + lv_obj_t* icon_label = lv_label_create(parent); + lv_label_set_text(icon_label, LV_SYMBOL_GPS "\nMTR"); + lv_obj_set_style_text_color(icon_label, lv_color_white(), 0); + lv_obj_set_style_text_align(icon_label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_center(icon_label); +} diff --git a/main/ui/apps/mtr_app.h b/main/ui/apps/mtr_app.h new file mode 100644 index 0000000..dd87dd6 --- /dev/null +++ b/main/ui/apps/mtr_app.h @@ -0,0 +1,71 @@ +#pragma once + +#include "ui/ui_app.h" +#include "ui/app_registry.h" +#include "ui/page_stack.h" +#include "external/mtr/mtr.h" +#include "external/mtr/line_info.h" +#include "external/mtr/station_info.h" +#include "network/network.h" +#include +#include + +/** + * @brief MTR Next Train application + * + * Provides multi-page navigation for: + * 1. Line selection - choose MTR line + * 2. Station selection - choose station within selected line + * 3. Arrival display - show real-time train arrival information + */ +class MtrApp : public UIApp { +public: + MtrApp(); + virtual ~MtrApp() = default; + + esp_err_t init(lv_obj_t* container) override; + esp_err_t deinit(void) override; + std::string get_name(void) const override; + bool on_back_button_pressed(void) override; + void handle_event(uint32_t event_type, void* event_data) override; + + // Set network handler (must be called before using app) + void set_network_handler(NetworkHandler* handler) { _network_handler = handler; } + +private: + std::unique_ptr _mtr_handler; + std::unique_ptr _page_stack; + NetworkHandler* _network_handler = nullptr; + + // Current selection state + std::string _selected_line_code; + std::string _selected_station_code; + LineInfo* _selected_line_info = nullptr; + std::vector _all_lines; + + // Page builders + void build_line_selection_page(lv_obj_t* page_container); + void build_station_selection_page(lv_obj_t* page_container); + void build_arrival_page(lv_obj_t* page_container); + + // Event handlers + static void line_button_event_cb(lv_event_t* e); + static void station_button_event_cb(lv_event_t* e); + static void refresh_button_event_cb(lv_event_t* e); + + // Helper functions + void load_arrival_data(lv_obj_t* page_container); + uint32_t parse_color_hex(const char* hex_str); +}; + +/** + * @brief AppDescriptor for MtrApp + */ +class MtrAppDescriptor : public AppDescriptor { +public: + MtrAppDescriptor(); + void draw_icon(lv_obj_t* parent) override; + +private: + static MtrApp* _app_instance; +}; diff --git a/main/ui/apps/shutdown_app.cpp b/main/ui/apps/shutdown_app.cpp new file mode 100644 index 0000000..19593cd --- /dev/null +++ b/main/ui/apps/shutdown_app.cpp @@ -0,0 +1,64 @@ +#include "apps/shutdown_app.h" +#include "esp_log.h" + +#define TAG "ShutdownApp" + +ShutdownApp::ShutdownApp(std::string message) + : _message(message.empty() ? "System Shutting Down..." : message) { } + +esp_err_t ShutdownApp::init(lv_obj_t* container) { + if (!container) { + ESP_LOGE(TAG, "Container is null"); + return ESP_ERR_INVALID_ARG; + } + + _container = container; + ESP_LOGI(TAG, "Initializing shutdown app with message: %s", _message.c_str()); + + // Main message label + _label_message = lv_label_create(_container); + lv_label_set_text(_label_message, _message.c_str()); + lv_obj_set_style_text_color(_label_message, lv_color_white(), 0); + lv_obj_align(_label_message, LV_ALIGN_CENTER, 0, 0); + + // Optional: Add spinner animation + lv_obj_t* spinner = lv_spinner_create(_container); + lv_obj_set_size(spinner, 80, 80); + lv_obj_align(spinner, LV_ALIGN_CENTER, 0, 80); + lv_obj_set_style_arc_color(spinner, lv_color_white(), LV_PART_INDICATOR); + + ESP_LOGI(TAG, "Shutdown app initialized successfully"); + return ESP_OK; +} + +esp_err_t ShutdownApp::deinit(void) { + ESP_LOGI(TAG, "Deinitializing shutdown app"); + _label_message = nullptr; + return ESP_OK; +} + +std::string ShutdownApp::get_name(void) const { + return "Shutdown"; +} + +// ShutdownAppDescriptor implementation +ShutdownApp* ShutdownAppDescriptor::_app_instance = nullptr; + +ShutdownAppDescriptor::ShutdownAppDescriptor() + : AppDescriptor("Shutdown", nullptr) { + // Create singleton app instance with default message + if (!_app_instance) { + _app_instance = new ShutdownApp(); + } + + // it's only used during system shutdown, not as a user-launchable app +} + +void ShutdownAppDescriptor::draw_icon(lv_obj_t* parent) { + // Create a simple icon (not normally shown in nav bar) + lv_obj_t* icon_label = lv_label_create(parent); + lv_label_set_text(icon_label, LV_SYMBOL_POWER "\nShutdown"); + lv_obj_set_style_text_color(icon_label, lv_color_white(), 0); + lv_obj_set_style_text_align(icon_label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_center(icon_label); +} diff --git a/main/ui/apps/shutdown_app.h b/main/ui/apps/shutdown_app.h new file mode 100644 index 0000000..87c72d4 --- /dev/null +++ b/main/ui/apps/shutdown_app.h @@ -0,0 +1,39 @@ +#pragma once + +#include "ui/ui_app.h" +#include "ui/app_registry.h" + +/** + * @brief Shutdown application - displays shutdown message + * + * Shown when the system is about to enter deep sleep or power off. + * Displays a message and optionally a spinner animation. + */ +class ShutdownApp : public UIApp { +public: + ShutdownApp(std::string message = ""); + virtual ~ShutdownApp() = default; + + esp_err_t init(lv_obj_t* container) override; + esp_err_t deinit(void) override; + std::string get_name(void) const override; + +private: + std::string _message; + lv_obj_t* _label_message = nullptr; +}; + +/** + * @brief AppDescriptor for ShutdownApp + * + * Note: Shutdown app is typically not shown in the navigation bar + * as it's only used during system shutdown. + */ +class ShutdownAppDescriptor : public AppDescriptor { +public: + ShutdownAppDescriptor(); + void draw_icon(lv_obj_t* parent) override; + +private: + static ShutdownApp* _app_instance; +}; diff --git a/main/ui/page_stack.cpp b/main/ui/page_stack.cpp new file mode 100644 index 0000000..3b65173 --- /dev/null +++ b/main/ui/page_stack.cpp @@ -0,0 +1,115 @@ +#include "page_stack.h" +#include "esp_log.h" + +static const char* TAG = "PageStack"; + +PageStack::PageStack(lv_obj_t* parent_container) + : parent_container_(parent_container) { + if (!parent_container_) { + ESP_LOGE(TAG, "Parent container is null"); + } +} + +PageStack::~PageStack() { + clear(); +} + +lv_obj_t* PageStack::create_page_container() { + lv_obj_t* page = lv_obj_create(parent_container_); + + // Fill parent container + lv_obj_set_size(page, LV_PCT(100), LV_PCT(100)); + lv_obj_set_pos(page, 0, 0); + + // Remove padding and scrollbars + lv_obj_set_style_pad_all(page, 0, 0); + lv_obj_set_scrollbar_mode(page, LV_SCROLLBAR_MODE_OFF); + + // White background + lv_obj_set_style_bg_color(page, lv_color_white(), 0); + lv_obj_set_style_bg_opa(page, LV_OPA_COVER, 0); + + // Remove border + lv_obj_set_style_border_width(page, 0, 0); + + return page; +} + +lv_obj_t* PageStack::push(PageBuilder builder, PageCleanup cleanup) { + if (!parent_container_) { + ESP_LOGE(TAG, "Cannot push page: parent container is null"); + return nullptr; + } + + if (!builder) { + ESP_LOGE(TAG, "Cannot push page: builder is null"); + return nullptr; + } + + // Hide current page if any + if (!pages_.empty()) { + lv_obj_add_flag(pages_.back().container, LV_OBJ_FLAG_HIDDEN); + } + + // Create new page container + lv_obj_t* page = create_page_container(); + + // Build page content + builder(page); + + // Add to stack + pages_.push_back({page, cleanup}); + + ESP_LOGD(TAG, "Pushed page (depth: %d)", pages_.size()); + return page; +} + +bool PageStack::pop() { + if (pages_.empty()) { + ESP_LOGW(TAG, "Cannot pop: stack is empty"); + return false; + } + + // Get and remove current page + Page current = pages_.back(); + pages_.pop_back(); + + // Call cleanup callback if provided + if (current.cleanup) { + current.cleanup(current.container); + } + + // Delete page container + lv_obj_del(current.container); + + // Show previous page if any + if (!pages_.empty()) { + lv_obj_clear_flag(pages_.back().container, LV_OBJ_FLAG_HIDDEN); + } + + ESP_LOGD(TAG, "Popped page (depth: %d)", pages_.size()); + return true; +} + +void PageStack::clear() { + ESP_LOGD(TAG, "Clearing all pages (depth: %d)", pages_.size()); + + // Pop all pages (calls cleanup callbacks) + while (!pages_.empty()) { + Page current = pages_.back(); + pages_.pop_back(); + + if (current.cleanup) { + current.cleanup(current.container); + } + + lv_obj_del(current.container); + } +} + +lv_obj_t* PageStack::current_page() const { + if (pages_.empty()) { + return nullptr; + } + return pages_.back().container; +} diff --git a/main/ui/page_stack.h b/main/ui/page_stack.h new file mode 100644 index 0000000..0a3633a --- /dev/null +++ b/main/ui/page_stack.h @@ -0,0 +1,86 @@ +#pragma once + +#include "lvgl.h" +#include +#include + +/** + * @brief Reusable page stack for multi-page navigation within LVGL apps + * + * Manages a stack of LVGL containers, allowing apps to push/pop pages + * and implement hierarchical navigation. Thread-safe for use with LVGL. + */ +class PageStack { +public: + /** + * @brief Page builder callback + * @param page_container The LVGL container to build the page in + */ + using PageBuilder = std::function; + + /** + * @brief Page cleanup callback + * @param page_container The LVGL container being destroyed + */ + using PageCleanup = std::function; + + /** + * @brief Construct page stack with parent container + * @param parent_container Parent LVGL container for pages + */ + explicit PageStack(lv_obj_t* parent_container); + + /** + * @brief Destructor - clears all pages + */ + ~PageStack(); + + /** + * @brief Push a new page onto the stack + * @param builder Function to build page content + * @param cleanup Optional cleanup function called when page is popped + * @return The created page container + */ + lv_obj_t* push(PageBuilder builder, PageCleanup cleanup = nullptr); + + /** + * @brief Pop the current page and return to previous + * @return true if page was popped, false if stack is empty + */ + bool pop(); + + /** + * @brief Clear all pages from the stack + */ + void clear(); + + /** + * @brief Get the current (top) page container + * @return Current page or nullptr if stack is empty + */ + lv_obj_t* current_page() const; + + /** + * @brief Get the number of pages in the stack + */ + size_t depth() const { return pages_.size(); } + + /** + * @brief Check if stack is empty + */ + bool empty() const { return pages_.empty(); } + +private: + struct Page { + lv_obj_t* container; + PageCleanup cleanup; + }; + + lv_obj_t* parent_container_; + std::vector pages_; + + /** + * @brief Create a page container + */ + lv_obj_t* create_page_container(); +}; diff --git a/main/ui/root_layout.cpp b/main/ui/root_layout.cpp new file mode 100644 index 0000000..1e7c0a8 --- /dev/null +++ b/main/ui/root_layout.cpp @@ -0,0 +1,264 @@ +#include "ui/root_layout.h" +#include "ui/ui_handler.h" +#include "ui/app_registry.h" +#include "esp_log.h" + +#define TAG "RootLayout" + +// Display dimensions +#define DISPLAY_WIDTH 800 +#define DISPLAY_HEIGHT 480 + +// Layout dimensions +#define HEADER_HEIGHT 40 +#define NAV_BAR_HEIGHT 50 +#define APP_CONTAINER_HEIGHT (DISPLAY_HEIGHT - HEADER_HEIGHT - NAV_BAR_HEIGHT) + +// forward-declare local event callback +static void on_home_button_clicked(lv_event_t* event); + +RootLayout::RootLayout(UIHandler* ui_handler) + : _ui_handler(ui_handler) { } + +esp_err_t RootLayout::init(lv_obj_t* parent) { + if (!parent) { + ESP_LOGE(TAG, "Parent object is null"); + return ESP_ERR_INVALID_ARG; + } + + ESP_LOGI(TAG, "Initializing RootLayout"); + + if (create_layout(parent) != ESP_OK) { + ESP_LOGE(TAG, "Failed to create layout"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "RootLayout initialized successfully"); + return ESP_OK; +} + +esp_err_t RootLayout::deinit(void) { + ESP_LOGI(TAG, "Deinitializing RootLayout"); + + // LVGL will handle cleanup when parent is destroyed + _header = nullptr; + _header_label = nullptr; + _app_container = nullptr; + _nav_bar = nullptr; + _back_button = nullptr; + + return ESP_OK; +} + +esp_err_t RootLayout::create_layout(lv_obj_t* parent) { + // Configure parent as flexbox column layout + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(parent, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_set_style_pad_all(parent, 0, 0); + lv_obj_set_style_pad_gap(parent, 0, 0); + + // Create header (top, fixed height) + _header = lv_obj_create(parent); + lv_obj_set_width(_header, lv_pct(100)); + lv_obj_set_height(_header, HEADER_HEIGHT); + lv_obj_set_style_bg_color(_header, lv_color_hex(0xFFFFFF), 0); + lv_obj_set_style_border_width(_header, 0, 0); + lv_obj_set_style_border_color(_header, lv_color_hex(0x000000), 0); + lv_obj_set_style_border_width(_header, 1, LV_BORDER_SIDE_BOTTOM); + lv_obj_set_style_pad_all(_header, 0, 0); + lv_obj_set_style_radius(_header, 0, 0); + + _header_label = lv_label_create(_header); + lv_label_set_text(_header_label, "App"); + lv_obj_set_style_text_color(_header_label, lv_color_black(), 0); + lv_obj_align(_header_label, LV_ALIGN_LEFT_MID, 10, 0); + + // Create app container (middle, flexible - grows to fill available space) + _app_container = lv_obj_create(parent); + lv_obj_set_width(_app_container, lv_pct(100)); + lv_obj_set_flex_grow(_app_container, 1); + lv_obj_set_style_bg_color(_app_container, lv_color_white(), 0); + lv_obj_set_style_border_width(_app_container, 0, 0); + lv_obj_set_style_pad_all(_app_container, 0, 0); + lv_obj_set_style_radius(_app_container, 0, 0); + + // Create navigation bar (bottom, fixed height) + _nav_bar = lv_obj_create(parent); + lv_obj_set_width(_nav_bar, lv_pct(100)); + lv_obj_set_height(_nav_bar, NAV_BAR_HEIGHT); + lv_obj_set_style_bg_color(_nav_bar, lv_color_hex(0xFFFFFF), 0); + lv_obj_set_style_border_color(_nav_bar, lv_color_hex(0x000000), 0); + lv_obj_set_style_border_width(_nav_bar, 1, LV_BORDER_SIDE_TOP); + lv_obj_set_style_pad_all(_nav_bar, 5, 0); + lv_obj_set_style_radius(_nav_bar, 0, 0); + + // Configure nav bar as flexbox row layout with space-between + lv_obj_set_flex_flow(_nav_bar, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(_nav_bar, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + // Create back button (aligned to start by flex layout) + _back_button = lv_btn_create(_nav_bar); + lv_obj_set_size(_back_button, 60, NAV_BAR_HEIGHT - 10); + lv_obj_set_style_bg_color(_back_button, lv_color_hex(0x555555), 0); + lv_obj_add_event_cb(_back_button, on_back_button_clicked, LV_EVENT_CLICKED, _ui_handler); + lv_obj_add_flag(_back_button, LV_OBJ_FLAG_HIDDEN); + + // Add back arrow label + lv_obj_t* back_label = lv_label_create(_back_button); + lv_label_set_text(back_label, LV_SYMBOL_LEFT); + lv_obj_set_style_text_color(back_label, lv_color_black(), 0); + lv_obj_align(back_label, LV_ALIGN_CENTER, 0, 0); + + // Create home button (aligned to end by flex layout) + lv_obj_t* home_button = lv_btn_create(_nav_bar); + lv_obj_set_size(home_button, 60, NAV_BAR_HEIGHT - 10); + lv_obj_set_style_bg_color(home_button, lv_color_hex(0x555555), 0); + lv_obj_t* home_label = lv_label_create(home_button); + lv_label_set_text(home_label, LV_SYMBOL_HOME); + lv_obj_set_style_text_color(home_label, lv_color_white(), 0); + lv_obj_align(home_label, LV_ALIGN_CENTER, 0, 0); + lv_obj_add_event_cb(home_button, on_home_button_clicked, LV_EVENT_CLICKED, _ui_handler); + + ESP_LOGI(TAG, "Layout created with flexible design: Header=%d, NavBar=%d", + HEADER_HEIGHT, NAV_BAR_HEIGHT); + + return ESP_OK; +} + +void RootLayout::update_header(std::string app_name) { + if (!_header_label) { + return; + } + + if (app_name.empty() == false) { + lv_label_set_text(_header_label, app_name.c_str()); + } else { + lv_label_set_text(_header_label, "App"); + } +} + +esp_err_t RootLayout::render_app_icons(void) { + if (!_nav_bar) { + ESP_LOGE(TAG, "Navigation bar not initialized"); + return ESP_FAIL; + } + + // Clear existing app container content (icons are rendered in the app area) + if (!_app_container) { + ESP_LOGE(TAG, "App container not initialized"); + return ESP_FAIL; + } + lv_obj_clean(_app_container); + + // Get all registered apps from registry + const auto& app_descriptors = AppRegistry::instance().get_app_descriptors(); + + if (app_descriptors.empty()) { + ESP_LOGW(TAG, "No apps registered in AppRegistry"); + lv_obj_t* nav_label = lv_label_create(_nav_bar); + lv_label_set_text(nav_label, "No apps available"); + lv_obj_set_style_text_color(nav_label, lv_color_white(), 0); + lv_obj_align(nav_label, LV_ALIGN_CENTER, 0, 0); + return ESP_OK; + } + + ESP_LOGI(TAG, "Rendering %d app icons", (int)app_descriptors.size()); + + // Calculate icon spacing inside the app container + int icon_count = app_descriptors.size(); + int icon_width = 96; + int icon_height = 96; + int icon_spacing = DISPLAY_WIDTH / (icon_count + 1); + int x_offset = icon_spacing; + int y_offset = (APP_CONTAINER_HEIGHT - icon_height) / 2; + + // Render each app icon into the app container + for (size_t i = 0; i < app_descriptors.size(); i++) { + AppDescriptor* descriptor = app_descriptors[i]; + + lv_obj_t* icon_container = lv_obj_create(_app_container); + lv_obj_set_size(icon_container, icon_width, icon_height); + lv_obj_set_pos(icon_container, x_offset - icon_width / 2, y_offset); + lv_obj_set_style_bg_opa(icon_container, LV_OPA_TRANSP, 0); + lv_obj_set_style_pad_all(icon_container, 0, 0); + // add a border for debugging + lv_obj_set_style_border_color(icon_container, lv_color_hex(0x000000), 0); + lv_obj_set_style_border_width(icon_container, 1, 0); + + lv_obj_set_user_data(icon_container, descriptor); + + descriptor->draw_icon(icon_container); + + lv_obj_add_flag(icon_container, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_event_cb(icon_container, on_app_icon_clicked, LV_EVENT_CLICKED, _ui_handler); + + x_offset += icon_spacing; + } + + return ESP_OK; +} + +void RootLayout::show_back_button(void) { + if (_back_button) { + lv_obj_clear_flag(_back_button, LV_OBJ_FLAG_HIDDEN); + } +} + +void RootLayout::hide_back_button(void) { + if (_back_button) { + lv_obj_add_flag(_back_button, LV_OBJ_FLAG_HIDDEN); + } +} + +void RootLayout::on_app_icon_clicked(lv_event_t* event) { + // Use the current target (the object the callback was attached to) + // instead of the event target, which may be a child (like a label). + lv_obj_t* icon_container = static_cast(lv_event_get_current_target(event)); + UIHandler* handler = static_cast(lv_event_get_user_data(event)); + AppDescriptor* descriptor = static_cast(lv_obj_get_user_data(icon_container)); + + if (!handler || !descriptor) { + ESP_LOGE(TAG, "Invalid event data in app icon click"); + return; + } + + ESP_LOGI(TAG, "App icon clicked: %s", descriptor->get_name().c_str()); + handler->switch_app(descriptor); +} + +void RootLayout::on_back_button_clicked(lv_event_t* event) { + UIHandler* handler = static_cast(lv_event_get_user_data(event)); + + if (!handler) { + ESP_LOGE(TAG, "Invalid handler in back button click"); + return; + } + + // Get the active app + UIApp* active_app = handler->get_active_app(); + if (!active_app) { + ESP_LOGW(TAG, "Back button pressed but no active app"); + return; + } + + // Let the app handle the back button press + bool handled = active_app->on_back_button_pressed(); + + if (handled) { + ESP_LOGI(TAG, "Back button handled by app: %s", active_app->get_name()); + } else { + ESP_LOGI(TAG, "Back button not handled by app, returning to main screen"); + handler->return_to_main_screen(); + } +} + +static void on_home_button_clicked(lv_event_t* event) { + UIHandler* handler = static_cast(lv_event_get_user_data(event)); + + if (!handler) { + ESP_LOGE(TAG, "Invalid handler in home button click"); + return; + } + + handler->return_to_main_screen(); +} diff --git a/main/ui/root_layout.h b/main/ui/root_layout.h new file mode 100644 index 0000000..c4a4f28 --- /dev/null +++ b/main/ui/root_layout.h @@ -0,0 +1,138 @@ +#pragma once + +#include "lvgl.h" +#include "esp_err.h" +#include + +// Forward declaration +class UIHandler; + +/** + * @brief Root Layout Manager - manages the main screen layout + * + * The RootLayout class is responsible for: + * - Creating and managing the main screen structure (header, app container, nav bar) + * - Rendering app icons from the AppRegistry + * - Managing the back button + * - Updating header content + */ +class RootLayout { +public: + /** + * @brief Construct a new RootLayout object + * + * @param ui_handler Pointer to the UIHandler (for callbacks) + */ + RootLayout(UIHandler* ui_handler); + + /** + * @brief Initialize the layout + * + * Creates the main screen with header, app container, and navigation bar. + * + * @param parent Parent LVGL object to attach layout to + * @return ESP_OK on success, error code otherwise + */ + esp_err_t init(lv_obj_t* parent); + + /** + * @brief Deinitialize the layout + * + * Cleans up all layout widgets. + * + * @return ESP_OK on success, error code otherwise + */ + esp_err_t deinit(void); + + /** + * @brief Render app icons in the navigation bar + * + * Queries the AppRegistry for all registered apps and + * renders their icons in the navigation bar. Also creates + * the back button. + * + * @return ESP_OK on success, error code otherwise + */ + esp_err_t render_app_icons(void); + + /** + * @brief Update header with app name + * + * @param app_name Name to display in header (nullptr for default) + */ + void update_header(std::string app_name); + + /** + * @brief Show the back button + */ + void show_back_button(void); + + /** + * @brief Hide the back button + */ + void hide_back_button(void); + + /** + * @brief Get the header object + * + * @return lv_obj_t* pointer to the header container + */ + lv_obj_t* get_header(void) const { + return _header; + } + + /** + * @brief Get the app container (where apps render) + * + * @return lv_obj_t* pointer to the app container + */ + lv_obj_t* get_app_container(void) const { + return _app_container; + } + + /** + * @brief Get the navigation bar object + * + * @return lv_obj_t* pointer to the navigation bar container + */ + lv_obj_t* get_nav_bar(void) const { + return _nav_bar; + } + +private: + UIHandler* _ui_handler = nullptr; ///< Reference to UIHandler for callbacks + lv_obj_t* _header = nullptr; ///< Header area (top) + lv_obj_t* _header_label = nullptr; ///< Header text label + lv_obj_t* _app_container = nullptr; ///< Container for app widgets (middle) + lv_obj_t* _nav_bar = nullptr; ///< Navigation bar (bottom) + lv_obj_t* _back_button = nullptr; ///< Back button in navigation bar + + /** + * @brief Create the layout structure + * + * Sets up header, app container, and navigation bar with + * appropriate dimensions and positioning. + * + * @param parent Parent object to attach layout to + * @return ESP_OK on success, error code otherwise + */ + esp_err_t create_layout(lv_obj_t* parent); + + /** + * @brief Handle app icon click event + * + * Static callback for LVGL event handling. + * + * @param event LVGL event object + */ + static void on_app_icon_clicked(lv_event_t* event); + + /** + * @brief Handle back button click event + * + * Static callback for LVGL event handling. + * + * @param event LVGL event object + */ + static void on_back_button_clicked(lv_event_t* event); +}; diff --git a/main/ui/ui_app.h b/main/ui/ui_app.h new file mode 100644 index 0000000..42b6bac --- /dev/null +++ b/main/ui/ui_app.h @@ -0,0 +1,98 @@ +#pragma once + +#include "lvgl.h" +#include "esp_err.h" +#include + +/** + * @brief Base class for all UI applications + * + * All UI applications (apps) must inherit from this class. + * Each app is responsible for managing its own widgets within + * the provided LVGL container. The UIHandler will manage the + * lifecycle of apps and event routing. + */ +class UIApp { +public: + virtual ~UIApp() = default; + + /** + * @brief Initialize the app with the given container + * + * The app should create all its widgets as children of the + * provided container. The container is already positioned + * between the header and navigation bar. + * + * @param container LVGL container object for this app + * @return ESP_OK on success, error code otherwise + */ + virtual esp_err_t init(lv_obj_t* container) = 0; + + /** + * @brief Deinitialize and clean up app resources + * + * The app should delete all widgets and release any resources. + * The container itself will be handled by UIHandler. + * + * @return ESP_OK on success, error code otherwise + */ + virtual esp_err_t deinit(void) = 0; + + /** + * @brief Get the display name of this app + * + * Used for logging and potentially showing in navigation. + * + * @return std::string app name + */ + virtual std::string get_name(void) const = 0; + + /** + * @brief Handle system events passed from UIHandler + * + * System events include network status changes, storage ready, + * display refresh, and other system-level events. + * + * @param event_type Type/ID of the event + * @param event_data Optional event data payload + */ + virtual void handle_event(uint32_t event_type, void* event_data = nullptr) { } + + virtual bool on_back_button_pressed(void) { + return false; // default: not handled + } + + /** + * @brief Get the app's root container + * + * @return lv_obj_t* pointer to the app's container + */ + lv_obj_t* get_container(void) const { + return _container; + } + +protected: + lv_obj_t* _container = nullptr; ///< LVGL container provided by UIHandler +}; + + +class AppDescriptor { +public: + virtual ~AppDescriptor() = default; + virtual void draw_icon(lv_obj_t* parent) = 0; + + std::string get_name() const { + return _name; + } + + UIApp* get_app_instance() const { + return _app_instance; + } + +protected: + AppDescriptor(std::string name, UIApp* app_instance) + : _name(name), _app_instance(app_instance) { } + + std::string _name; + UIApp* _app_instance; +}; \ No newline at end of file diff --git a/main/ui/ui_handler.cpp b/main/ui/ui_handler.cpp new file mode 100644 index 0000000..d962dbe --- /dev/null +++ b/main/ui/ui_handler.cpp @@ -0,0 +1,208 @@ +#include "ui/ui_handler.h" +#include "ui/root_layout.h" +#include "ui/app_registry.h" +#include "esp_log.h" +#include "lvgl.h" + +#define TAG "UIHandler" + +// Display dimensions from constants.h +#define DISPLAY_WIDTH 800 +#define DISPLAY_HEIGHT 480 + +// Layout dimensions +#define HEADER_HEIGHT 40 +#define NAV_BAR_HEIGHT 50 +#define _APP_CONTAINERHEIGHT (DISPLAY_HEIGHT - HEADER_HEIGHT - NAV_BAR_HEIGHT) + +esp_err_t UIHandler::init(void) { + ESP_LOGI(TAG, "Initializing UIHandler"); + + // Create main screen + _main_screen = lv_obj_create(NULL); + if (!_main_screen) { + ESP_LOGE(TAG, "Failed to create main screen"); + return ESP_FAIL; + } + lv_obj_set_style_bg_color(_main_screen, lv_color_black(), 0); + lv_obj_set_size(_main_screen, DISPLAY_WIDTH, DISPLAY_HEIGHT); + + // Create root layout + _root_layout = new RootLayout(this); + if (!_root_layout) { + ESP_LOGE(TAG, "Failed to allocate RootLayout"); + return ESP_FAIL; + } + + if (_root_layout->init(_main_screen) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize root layout"); + delete _root_layout; + _root_layout = nullptr; + return ESP_FAIL; + } + + // Render app icons from registry + if (_root_layout->render_app_icons() != ESP_OK) { + ESP_LOGW(TAG, "Failed to render app icons"); + } + + // Defer screen loading to prevent blocking during initialization + // Use LVGL timer to load screen after allowing watchdog reset + lv_timer_create([](lv_timer_t* timer) { + lv_obj_t* screen = static_cast(lv_timer_get_user_data(timer)); + ESP_LOGI("UIHandler", "Loading main screen via timer"); + lv_screen_load(screen); + lv_timer_del(timer); + }, 100, _main_screen); // 100ms delay to allow watchdog reset + + ESP_LOGI(TAG, "UIHandler initialized successfully"); + return ESP_OK; +} + +esp_err_t UIHandler::deinit(void) { + ESP_LOGI(TAG, "Deinitializing UIHandler"); + + // Deinit current app + if (_active_app) { + if (_active_app->deinit() != ESP_OK) { + ESP_LOGW(TAG, "Error deinitializing active app: %s", _active_app->get_name()); + } + _active_app = nullptr; + } + + // Delete shutdown app if cached + if (_shutdown_app) { + delete _shutdown_app; + _shutdown_app = nullptr; + } + + // Clean up root layout + if (_root_layout) { + _root_layout->deinit(); + delete _root_layout; + _root_layout = nullptr; + } + + // Main screen will be cleaned up by LVGL + _main_screen = nullptr; + + return ESP_OK; +} + +esp_err_t UIHandler::switch_app(UIApp* app) { + if (!app) { + ESP_LOGE(TAG, "Cannot switch to null app"); + return ESP_ERR_INVALID_ARG; + } + + lv_obj_t* app_container = get_app_container(); + if (!app_container) { + ESP_LOGE(TAG, "App container not initialized"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Switching to app: %s", app->get_name()); + + // Deinit current app + if (_active_app) { + if (_active_app->deinit() != ESP_OK) { + ESP_LOGW(TAG, "Error deinitializing app: %s", _active_app->get_name()); + } + } + + // Clear the app container + lv_obj_clean(app_container); + + // Initialize new app + if (app->init(app_container) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize app: %s", app->get_name()); + _active_app = nullptr; + return ESP_FAIL; + } + + _active_app = app; + + // Update header through RootLayout + if (_root_layout) { + _root_layout->update_header(_active_app->get_name()); + _root_layout->show_back_button(); + } + + return ESP_OK; +} + +esp_err_t UIHandler::switch_app(AppDescriptor* app_descriptor) { + if (!app_descriptor) { + ESP_LOGE(TAG, "Cannot switch to null app descriptor"); + return ESP_ERR_INVALID_ARG; + } + + UIApp* app = app_descriptor->get_app_instance(); + if (!app) { + ESP_LOGE(TAG, "App descriptor has null app instance"); + return ESP_ERR_INVALID_ARG; + } + + return switch_app(app); +} + +void UIHandler::route_event(uint32_t event_type, void* event_data) { + if (_active_app) { + _active_app->handle_event(event_type, event_data); + } +} + +esp_err_t UIHandler::show_shutdown_screen(std::string message) { + ESP_LOGI(TAG, "Showing shutdown screen"); + + lv_obj_t* app_container = get_app_container(); + if (!app_container) { + ESP_LOGE(TAG, "App container not initialized"); + return ESP_FAIL; + } + + // Clear current app reference + _active_app = nullptr; + + // Clear the app container + lv_obj_clean(app_container); + + // Create shutdown message + lv_obj_t* shutdown_label = lv_label_create(app_container); + lv_label_set_text(shutdown_label, message.empty() ? "Shutting down..." : message.c_str()); + lv_obj_set_style_text_color(shutdown_label, lv_color_white(), 0); + lv_obj_align(shutdown_label, LV_ALIGN_CENTER, 0, 0); + + // Update header through RootLayout + if (_root_layout) { + _root_layout->update_header("System Shutdown"); + } + + return ESP_OK; +} + +esp_err_t UIHandler::return_to_main_screen(void) { + ESP_LOGI(TAG, "Returning to main screen"); + + // Deinit current app + if (_active_app) { + if (_active_app->deinit() != ESP_OK) { + ESP_LOGW(TAG, "Error deinitializing app: %s", _active_app->get_name()); + } + _active_app = nullptr; + } + + // Clear the app container + lv_obj_t* app_container = get_app_container(); + if (app_container) { + lv_obj_clean(app_container); + } + + // Update header and hide back button through RootLayout + if (_root_layout) { + _root_layout->update_header(""); + _root_layout->hide_back_button(); + } + + return ESP_OK; +} diff --git a/main/ui/ui_handler.h b/main/ui/ui_handler.h new file mode 100644 index 0000000..4ec419a --- /dev/null +++ b/main/ui/ui_handler.h @@ -0,0 +1,147 @@ +#pragma once + +#include "ui_app.h" +#include "app_registry.h" +#include "root_layout.h" +#include "esp_err.h" + +// Forward declaration +class RootLayout; + +/** + * @brief UI Handler - manages app lifecycle and rendering + * + * The UIHandler manages: + * - Creation and destruction of UI apps + * - Switching between apps + * - Main screen layout (header, app container, navigation bar) + * - System event routing to active app + * - Displaying special screens (shutdown, etc.) + */ +class UIHandler { +public: + /** + * @brief Initialize the UI system with default layout + * + * Creates the main screen with: + * - Header area (top) + * - App container (middle) + * - Navigation bar (bottom) + * + * @return ESP_OK on success, error code otherwise + */ + esp_err_t init(void); + + /** + * @brief Deinitialize the UI system + * + * Cleans up the current app and destroys the main screen. + * + * @return ESP_OK on success, error code otherwise + */ + esp_err_t deinit(void); + + /** + * @brief Switch to a new app + * + * Deinitializes the current app (if any), initializes the new app, + * and updates the display. + * + * @param app Pointer to the new app to switch to + * @return ESP_OK on success, error code otherwise + */ + esp_err_t switch_app(UIApp* app); + + /** + * @brief Switch to an app by its descriptor + * + * Convenience method that extracts the UIApp from the descriptor + * and calls switch_app(). + * + * @param app_descriptor Pointer to the app descriptor + * @return ESP_OK on success, error code otherwise + */ + esp_err_t switch_app(AppDescriptor* app_descriptor); + + /** + * @brief Get the currently active app + * + * @return Pointer to the active UIApp, or nullptr if none + */ + UIApp* get_active_app(void) const { + return _active_app; + } + + /** + * @brief Route a system event to the active app + * + * If an app is active, this forwards the event to it. + * + * @param event_type Type/ID of the event + * @param event_data Optional event data payload + */ + void route_event(uint32_t event_type, void* event_data = nullptr); + + /** + * @brief Display shutdown screen + * + * Shows a shutdown screen with a message. Typically called + * before the system enters deep sleep or powers off. + * + * @param message Optional message to display (e.g., "Shutting down...") + * @return ESP_OK on success, error code otherwise + */ + esp_err_t show_shutdown_screen(std::string message = ""); + + /** + * @brief Get the main screen object + * + * @return lv_obj_t* pointer to the main screen + */ + lv_obj_t* get_main_screen(void) const { + return _main_screen; + } + + /** + * @brief Get the app container (where apps render) + * + * @return lv_obj_t* pointer to the app container + */ + lv_obj_t* get_app_container(void) const { + return _root_layout ? _root_layout->get_app_container() : nullptr; + } + + /** + * @brief Get the header object + * + * @return lv_obj_t* pointer to the header container + */ + lv_obj_t* get_header(void) const { + return _root_layout ? _root_layout->get_header() : nullptr; + } + + /** + * @brief Get the navigation bar object + * + * @return lv_obj_t* pointer to the navigation bar container + */ + lv_obj_t* get_nav_bar(void) const { + return _root_layout ? _root_layout->get_nav_bar() : nullptr; + } + + /** + * @brief Return to main screen (deinit app and show app icons) + * + * Deinitializes the active app and displays the app icons + * in the navigation bar, returning to the home screen. + * + * @return ESP_OK on success, error code otherwise + */ + esp_err_t return_to_main_screen(void); + +private: + lv_obj_t* _main_screen = nullptr; ///< Root screen + RootLayout* _root_layout = nullptr; ///< Root layout manager + UIApp* _active_app = nullptr; ///< Currently active app + UIApp* _shutdown_app = nullptr; ///< Cached shutdown app +}; diff --git a/pytest_hello_world.py b/pytest_hello_world.py new file mode 100644 index 0000000..eb02bd7 --- /dev/null +++ b/pytest_hello_world.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import hashlib +import logging +from typing import Callable + +import pytest +from pytest_embedded_idf.dut import IdfDut +from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_qemu.app import QemuApp +from pytest_embedded_qemu.dut import QemuDut + + +@pytest.mark.generic +@idf_parametrize('target', ['supported_targets', 'preview_targets'], indirect=['target']) +def test_hello_world(dut: IdfDut, log_minimum_free_heap_size: Callable[..., None]) -> None: + dut.expect('Hello world!') + log_minimum_free_heap_size() + + +@pytest.mark.host_test +@idf_parametrize('target', ['linux'], indirect=['target']) +def test_hello_world_linux(dut: IdfDut) -> None: + dut.expect('Hello world!') + + +@pytest.mark.host_test +@pytest.mark.macos_shell +@idf_parametrize('target', ['linux'], indirect=['target']) +def test_hello_world_macos(dut: IdfDut) -> None: + dut.expect('Hello world!') + + +def verify_elf_sha256_embedding(app: QemuApp, sha256_reported: str) -> None: + sha256 = hashlib.sha256() + with open(app.elf_file, 'rb') as f: + sha256.update(f.read()) + sha256_expected = sha256.hexdigest() + + logging.info(f'ELF file SHA256: {sha256_expected}') + logging.info(f'ELF file SHA256 (reported by the app): {sha256_reported}') + + # the app reports only the first several hex characters of the SHA256, check that they match + if not sha256_expected.startswith(sha256_reported): + raise ValueError('ELF file SHA256 mismatch') + + +@pytest.mark.host_test +@pytest.mark.qemu +@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) +def test_hello_world_host(app: QemuApp, dut: QemuDut) -> None: + sha256_reported = dut.expect(r'ELF file SHA256:\s+([a-f0-9]+)').group(1).decode('utf-8') + verify_elf_sha256_embedding(app, sha256_reported) + + dut.expect('Hello world!') diff --git a/sdkconfig.ci b/sdkconfig.ci new file mode 100644 index 0000000..e69de29 diff --git a/sdkconfig.default b/sdkconfig.default new file mode 100644 index 0000000..282070f --- /dev/null +++ b/sdkconfig.default @@ -0,0 +1,2591 @@ +# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) 5.5.2 Project Configuration +# +CONFIG_SOC_ADC_SUPPORTED=y +CONFIG_SOC_UART_SUPPORTED=y +CONFIG_SOC_PCNT_SUPPORTED=y +CONFIG_SOC_PHY_SUPPORTED=y +CONFIG_SOC_WIFI_SUPPORTED=y +CONFIG_SOC_TWAI_SUPPORTED=y +CONFIG_SOC_GDMA_SUPPORTED=y +CONFIG_SOC_UHCI_SUPPORTED=y +CONFIG_SOC_AHB_GDMA_SUPPORTED=y +CONFIG_SOC_GPTIMER_SUPPORTED=y +CONFIG_SOC_LCDCAM_SUPPORTED=y +CONFIG_SOC_LCDCAM_CAM_SUPPORTED=y +CONFIG_SOC_LCDCAM_I80_LCD_SUPPORTED=y +CONFIG_SOC_LCDCAM_RGB_LCD_SUPPORTED=y +CONFIG_SOC_MCPWM_SUPPORTED=y +CONFIG_SOC_DEDICATED_GPIO_SUPPORTED=y +CONFIG_SOC_CACHE_SUPPORT_WRAP=y +CONFIG_SOC_ULP_SUPPORTED=y +CONFIG_SOC_ULP_FSM_SUPPORTED=y +CONFIG_SOC_RISCV_COPROC_SUPPORTED=y +CONFIG_SOC_BT_SUPPORTED=y +CONFIG_SOC_USB_OTG_SUPPORTED=y +CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED=y +CONFIG_SOC_CCOMP_TIMER_SUPPORTED=y +CONFIG_SOC_ASYNC_MEMCPY_SUPPORTED=y +CONFIG_SOC_SUPPORTS_SECURE_DL_MODE=y +CONFIG_SOC_EFUSE_KEY_PURPOSE_FIELD=y +CONFIG_SOC_EFUSE_SUPPORTED=y +CONFIG_SOC_SDMMC_HOST_SUPPORTED=y +CONFIG_SOC_RTC_FAST_MEM_SUPPORTED=y +CONFIG_SOC_RTC_SLOW_MEM_SUPPORTED=y +CONFIG_SOC_RTC_MEM_SUPPORTED=y +CONFIG_SOC_PSRAM_DMA_CAPABLE=y +CONFIG_SOC_XT_WDT_SUPPORTED=y +CONFIG_SOC_I2S_SUPPORTED=y +CONFIG_SOC_RMT_SUPPORTED=y +CONFIG_SOC_SDM_SUPPORTED=y +CONFIG_SOC_GPSPI_SUPPORTED=y +CONFIG_SOC_LEDC_SUPPORTED=y +CONFIG_SOC_I2C_SUPPORTED=y +CONFIG_SOC_SYSTIMER_SUPPORTED=y +CONFIG_SOC_SUPPORT_COEXISTENCE=y +CONFIG_SOC_TEMP_SENSOR_SUPPORTED=y +CONFIG_SOC_AES_SUPPORTED=y +CONFIG_SOC_MPI_SUPPORTED=y +CONFIG_SOC_SHA_SUPPORTED=y +CONFIG_SOC_HMAC_SUPPORTED=y +CONFIG_SOC_DIG_SIGN_SUPPORTED=y +CONFIG_SOC_FLASH_ENC_SUPPORTED=y +CONFIG_SOC_SECURE_BOOT_SUPPORTED=y +CONFIG_SOC_MEMPROT_SUPPORTED=y +CONFIG_SOC_TOUCH_SENSOR_SUPPORTED=y +CONFIG_SOC_BOD_SUPPORTED=y +CONFIG_SOC_CLK_TREE_SUPPORTED=y +CONFIG_SOC_MPU_SUPPORTED=y +CONFIG_SOC_WDT_SUPPORTED=y +CONFIG_SOC_SPI_FLASH_SUPPORTED=y +CONFIG_SOC_RNG_SUPPORTED=y +CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y +CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y +CONFIG_SOC_LP_PERIPH_SHARE_INTERRUPT=y +CONFIG_SOC_PM_SUPPORTED=y +CONFIG_SOC_SIMD_INSTRUCTION_SUPPORTED=y +CONFIG_SOC_XTAL_SUPPORT_40M=y +CONFIG_SOC_APPCPU_HAS_CLOCK_GATING_BUG=y +CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y +CONFIG_SOC_ADC_DIG_CTRL_SUPPORTED=y +CONFIG_SOC_ADC_ARBITER_SUPPORTED=y +CONFIG_SOC_ADC_DIG_IIR_FILTER_SUPPORTED=y +CONFIG_SOC_ADC_MONITOR_SUPPORTED=y +CONFIG_SOC_ADC_DMA_SUPPORTED=y +CONFIG_SOC_ADC_PERIPH_NUM=2 +CONFIG_SOC_ADC_MAX_CHANNEL_NUM=10 +CONFIG_SOC_ADC_ATTEN_NUM=4 +CONFIG_SOC_ADC_DIGI_CONTROLLER_NUM=2 +CONFIG_SOC_ADC_PATT_LEN_MAX=24 +CONFIG_SOC_ADC_DIGI_MIN_BITWIDTH=12 +CONFIG_SOC_ADC_DIGI_MAX_BITWIDTH=12 +CONFIG_SOC_ADC_DIGI_RESULT_BYTES=4 +CONFIG_SOC_ADC_DIGI_DATA_BYTES_PER_CONV=4 +CONFIG_SOC_ADC_DIGI_IIR_FILTER_NUM=2 +CONFIG_SOC_ADC_DIGI_MONITOR_NUM=2 +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH=83333 +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW=611 +CONFIG_SOC_ADC_RTC_MIN_BITWIDTH=12 +CONFIG_SOC_ADC_RTC_MAX_BITWIDTH=12 +CONFIG_SOC_ADC_CALIBRATION_V1_SUPPORTED=y +CONFIG_SOC_ADC_SELF_HW_CALI_SUPPORTED=y +CONFIG_SOC_ADC_SHARED_POWER=y +CONFIG_SOC_APB_BACKUP_DMA=y +CONFIG_SOC_BROWNOUT_RESET_SUPPORTED=y +CONFIG_SOC_CACHE_WRITEBACK_SUPPORTED=y +CONFIG_SOC_CACHE_FREEZE_SUPPORTED=y +CONFIG_SOC_CACHE_ACS_INVALID_STATE_ON_PANIC=y +CONFIG_SOC_CPU_CORES_NUM=2 +CONFIG_SOC_CPU_INTR_NUM=32 +CONFIG_SOC_CPU_HAS_FPU=y +CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y +CONFIG_SOC_CPU_BREAKPOINTS_NUM=2 +CONFIG_SOC_CPU_WATCHPOINTS_NUM=2 +CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=0x40 +CONFIG_SOC_SIMD_PREFERRED_DATA_ALIGNMENT=16 +CONFIG_SOC_DS_SIGNATURE_MAX_BIT_LEN=4096 +CONFIG_SOC_DS_KEY_PARAM_MD_IV_LENGTH=16 +CONFIG_SOC_DS_KEY_CHECK_MAX_WAIT_US=1100 +CONFIG_SOC_AHB_GDMA_VERSION=1 +CONFIG_SOC_GDMA_NUM_GROUPS_MAX=1 +CONFIG_SOC_GDMA_PAIRS_PER_GROUP=5 +CONFIG_SOC_GDMA_PAIRS_PER_GROUP_MAX=5 +CONFIG_SOC_AHB_GDMA_SUPPORT_PSRAM=y +CONFIG_SOC_GPIO_PORT=1 +CONFIG_SOC_GPIO_PIN_COUNT=49 +CONFIG_SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER=y +CONFIG_SOC_GPIO_FILTER_CLK_SUPPORT_APB=y +CONFIG_SOC_GPIO_SUPPORT_RTC_INDEPENDENT=y +CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y +CONFIG_SOC_GPIO_VALID_GPIO_MASK=0x1FFFFFFFFFFFF +CONFIG_SOC_GPIO_IN_RANGE_MAX=48 +CONFIG_SOC_GPIO_OUT_RANGE_MAX=48 +CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0x0001FFFFFC000000 +CONFIG_SOC_GPIO_CLOCKOUT_BY_IO_MUX=y +CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=3 +CONFIG_SOC_DEDIC_GPIO_OUT_CHANNELS_NUM=8 +CONFIG_SOC_DEDIC_GPIO_IN_CHANNELS_NUM=8 +CONFIG_SOC_DEDIC_GPIO_OUT_AUTO_ENABLE=y +CONFIG_SOC_I2C_NUM=2 +CONFIG_SOC_HP_I2C_NUM=2 +CONFIG_SOC_I2C_FIFO_LEN=32 +CONFIG_SOC_I2C_CMD_REG_NUM=8 +CONFIG_SOC_I2C_SUPPORT_SLAVE=y +CONFIG_SOC_I2C_SUPPORT_HW_CLR_BUS=y +CONFIG_SOC_I2C_SUPPORT_XTAL=y +CONFIG_SOC_I2C_SUPPORT_RTC=y +CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y +CONFIG_SOC_I2C_SLAVE_SUPPORT_BROADCAST=y +CONFIG_SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS=y +CONFIG_SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE=y +CONFIG_SOC_I2S_NUM=2 +CONFIG_SOC_I2S_HW_VERSION_2=y +CONFIG_SOC_I2S_SUPPORTS_XTAL=y +CONFIG_SOC_I2S_SUPPORTS_PLL_F160M=y +CONFIG_SOC_I2S_SUPPORTS_PCM=y +CONFIG_SOC_I2S_SUPPORTS_PDM=y +CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y +CONFIG_SOC_I2S_SUPPORTS_PCM2PDM=y +CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y +CONFIG_SOC_I2S_SUPPORTS_PDM2PCM=y +CONFIG_SOC_I2S_PDM_MAX_TX_LINES=2 +CONFIG_SOC_I2S_PDM_MAX_RX_LINES=4 +CONFIG_SOC_I2S_SUPPORTS_TDM=y +CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y +CONFIG_SOC_LEDC_SUPPORT_XTAL_CLOCK=y +CONFIG_SOC_LEDC_TIMER_NUM=4 +CONFIG_SOC_LEDC_CHANNEL_NUM=8 +CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=14 +CONFIG_SOC_LEDC_SUPPORT_FADE_STOP=y +CONFIG_SOC_MCPWM_GROUPS=2 +CONFIG_SOC_MCPWM_TIMERS_PER_GROUP=3 +CONFIG_SOC_MCPWM_OPERATORS_PER_GROUP=3 +CONFIG_SOC_MCPWM_COMPARATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_GENERATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_TRIGGERS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_GPIO_FAULTS_PER_GROUP=3 +CONFIG_SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP=y +CONFIG_SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER=3 +CONFIG_SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP=3 +CONFIG_SOC_MCPWM_SWSYNC_CAN_PROPAGATE=y +CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=1 +CONFIG_SOC_MMU_PERIPH_NUM=1 +CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 +CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 +CONFIG_SOC_PCNT_GROUPS=1 +CONFIG_SOC_PCNT_UNITS_PER_GROUP=4 +CONFIG_SOC_PCNT_CHANNELS_PER_UNIT=2 +CONFIG_SOC_PCNT_THRES_POINT_PER_UNIT=2 +CONFIG_SOC_RMT_GROUPS=1 +CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP=4 +CONFIG_SOC_RMT_RX_CANDIDATES_PER_GROUP=4 +CONFIG_SOC_RMT_CHANNELS_PER_GROUP=8 +CONFIG_SOC_RMT_MEM_WORDS_PER_CHANNEL=48 +CONFIG_SOC_RMT_SUPPORT_RX_PINGPONG=y +CONFIG_SOC_RMT_SUPPORT_RX_DEMODULATION=y +CONFIG_SOC_RMT_SUPPORT_ASYNC_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_COUNT=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_SYNCHRO=y +CONFIG_SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY=y +CONFIG_SOC_RMT_SUPPORT_XTAL=y +CONFIG_SOC_RMT_SUPPORT_RC_FAST=y +CONFIG_SOC_RMT_SUPPORT_APB=y +CONFIG_SOC_RMT_SUPPORT_DMA=y +CONFIG_SOC_LCD_I80_SUPPORTED=y +CONFIG_SOC_LCD_RGB_SUPPORTED=y +CONFIG_SOC_LCD_I80_BUSES=1 +CONFIG_SOC_LCD_RGB_PANELS=1 +CONFIG_SOC_LCD_I80_BUS_WIDTH=16 +CONFIG_SOC_LCD_RGB_DATA_WIDTH=16 +CONFIG_SOC_LCD_SUPPORT_RGB_YUV_CONV=y +CONFIG_SOC_LCDCAM_I80_NUM_BUSES=1 +CONFIG_SOC_LCDCAM_I80_BUS_WIDTH=16 +CONFIG_SOC_LCDCAM_RGB_NUM_PANELS=1 +CONFIG_SOC_LCDCAM_RGB_DATA_WIDTH=16 +CONFIG_SOC_RTC_CNTL_CPU_PD_DMA_BUS_WIDTH=128 +CONFIG_SOC_RTC_CNTL_CPU_PD_REG_FILE_NUM=549 +CONFIG_SOC_RTC_CNTL_TAGMEM_PD_DMA_BUS_WIDTH=128 +CONFIG_SOC_RTCIO_PIN_COUNT=22 +CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y +CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y +CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y +CONFIG_SOC_LP_IO_CLOCK_IS_INDEPENDENT=y +CONFIG_SOC_SDM_GROUPS=1 +CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8 +CONFIG_SOC_SDM_CLK_SUPPORT_APB=y +CONFIG_SOC_SPI_PERIPH_NUM=3 +CONFIG_SOC_SPI_MAX_CS_NUM=6 +CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64 +CONFIG_SOC_SPI_SUPPORT_DDRCLK=y +CONFIG_SOC_SPI_SLAVE_SUPPORT_SEG_TRANS=y +CONFIG_SOC_SPI_SUPPORT_CD_SIG=y +CONFIG_SOC_SPI_SUPPORT_CONTINUOUS_TRANS=y +CONFIG_SOC_SPI_SUPPORT_SLAVE_HD_VER2=y +CONFIG_SOC_SPI_SUPPORT_CLK_APB=y +CONFIG_SOC_SPI_SUPPORT_CLK_XTAL=y +CONFIG_SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUT=y +CONFIG_SOC_MEMSPI_IS_INDEPENDENT=y +CONFIG_SOC_SPI_MAX_PRE_DIVIDER=16 +CONFIG_SOC_SPI_SUPPORT_OCT=y +CONFIG_SOC_SPI_SCT_SUPPORTED=y +CONFIG_SOC_SPI_SCT_REG_NUM=14 +CONFIG_SOC_SPI_SCT_BUFFER_NUM_MAX=y +CONFIG_SOC_SPI_SCT_CONF_BITLEN_MAX=0x3FFFA +CONFIG_SOC_MEMSPI_SRC_FREQ_120M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_40M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_20M_SUPPORTED=y +CONFIG_SOC_SPIRAM_SUPPORTED=y +CONFIG_SOC_SPIRAM_XIP_SUPPORTED=y +CONFIG_SOC_SYSTIMER_COUNTER_NUM=2 +CONFIG_SOC_SYSTIMER_ALARM_NUM=3 +CONFIG_SOC_SYSTIMER_BIT_WIDTH_LO=32 +CONFIG_SOC_SYSTIMER_BIT_WIDTH_HI=20 +CONFIG_SOC_SYSTIMER_FIXED_DIVIDER=y +CONFIG_SOC_SYSTIMER_INT_LEVEL=y +CONFIG_SOC_SYSTIMER_ALARM_MISS_COMPENSATE=y +CONFIG_SOC_TIMER_GROUPS=2 +CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2 +CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=54 +CONFIG_SOC_TIMER_GROUP_SUPPORT_XTAL=y +CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y +CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 +CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 +CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 +CONFIG_SOC_TOUCH_SENSOR_VERSION=2 +CONFIG_SOC_TOUCH_SENSOR_NUM=15 +CONFIG_SOC_TOUCH_MIN_CHAN_ID=1 +CONFIG_SOC_TOUCH_MAX_CHAN_ID=14 +CONFIG_SOC_TOUCH_SUPPORT_BENCHMARK=y +CONFIG_SOC_TOUCH_SUPPORT_SLEEP_WAKEUP=y +CONFIG_SOC_TOUCH_SUPPORT_WATERPROOF=y +CONFIG_SOC_TOUCH_SUPPORT_PROX_SENSING=y +CONFIG_SOC_TOUCH_SUPPORT_DENOISE_CHAN=y +CONFIG_SOC_TOUCH_PROXIMITY_CHANNEL_NUM=3 +CONFIG_SOC_TOUCH_PROXIMITY_MEAS_DONE_SUPPORTED=y +CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=1 +CONFIG_SOC_TWAI_CONTROLLER_NUM=1 +CONFIG_SOC_TWAI_MASK_FILTER_NUM=1 +CONFIG_SOC_TWAI_CLK_SUPPORT_APB=y +CONFIG_SOC_TWAI_BRP_MIN=2 +CONFIG_SOC_TWAI_BRP_MAX=16384 +CONFIG_SOC_TWAI_SUPPORTS_RX_STATUS=y +CONFIG_SOC_UART_NUM=3 +CONFIG_SOC_UART_HP_NUM=3 +CONFIG_SOC_UART_FIFO_LEN=128 +CONFIG_SOC_UART_BITRATE_MAX=5000000 +CONFIG_SOC_UART_SUPPORT_FSM_TX_WAIT_SEND=y +CONFIG_SOC_UART_SUPPORT_WAKEUP_INT=y +CONFIG_SOC_UART_SUPPORT_APB_CLK=y +CONFIG_SOC_UART_SUPPORT_RTC_CLK=y +CONFIG_SOC_UART_SUPPORT_XTAL_CLK=y +CONFIG_SOC_UART_WAKEUP_SUPPORT_ACTIVE_THRESH_MODE=y +CONFIG_SOC_UHCI_NUM=1 +CONFIG_SOC_USB_OTG_PERIPH_NUM=1 +CONFIG_SOC_SHA_DMA_MAX_BUFFER_SIZE=3968 +CONFIG_SOC_SHA_SUPPORT_DMA=y +CONFIG_SOC_SHA_SUPPORT_RESUME=y +CONFIG_SOC_SHA_GDMA=y +CONFIG_SOC_SHA_SUPPORT_SHA1=y +CONFIG_SOC_SHA_SUPPORT_SHA224=y +CONFIG_SOC_SHA_SUPPORT_SHA256=y +CONFIG_SOC_SHA_SUPPORT_SHA384=y +CONFIG_SOC_SHA_SUPPORT_SHA512=y +CONFIG_SOC_SHA_SUPPORT_SHA512_224=y +CONFIG_SOC_SHA_SUPPORT_SHA512_256=y +CONFIG_SOC_SHA_SUPPORT_SHA512_T=y +CONFIG_SOC_MPI_MEM_BLOCKS_NUM=4 +CONFIG_SOC_MPI_OPERATIONS_NUM=3 +CONFIG_SOC_RSA_MAX_BIT_LEN=4096 +CONFIG_SOC_AES_SUPPORT_DMA=y +CONFIG_SOC_AES_GDMA=y +CONFIG_SOC_AES_SUPPORT_AES_128=y +CONFIG_SOC_AES_SUPPORT_AES_256=y +CONFIG_SOC_PM_SUPPORT_EXT0_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_EXT_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_WIFI_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_BT_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_CPU_PD=y +CONFIG_SOC_PM_SUPPORT_TAGMEM_PD=y +CONFIG_SOC_PM_SUPPORT_RTC_PERIPH_PD=y +CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y +CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y +CONFIG_SOC_PM_SUPPORT_MAC_BB_PD=y +CONFIG_SOC_PM_SUPPORT_MODEM_PD=y +CONFIG_SOC_CONFIGURABLE_VDDSDIO_SUPPORTED=y +CONFIG_SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_ONLY=y +CONFIG_SOC_PM_CPU_RETENTION_BY_RTCCNTL=y +CONFIG_SOC_PM_MODEM_RETENTION_BY_BACKUPDMA=y +CONFIG_SOC_PM_MODEM_PD_BY_SW=y +CONFIG_SOC_CLK_RC_FAST_D256_SUPPORTED=y +CONFIG_SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256=y +CONFIG_SOC_CLK_RC_FAST_SUPPORT_CALIBRATION=y +CONFIG_SOC_CLK_XTAL32K_SUPPORTED=y +CONFIG_SOC_CLK_LP_FAST_SUPPORT_XTAL_D2=y +CONFIG_SOC_EFUSE_DIS_DOWNLOAD_ICACHE=y +CONFIG_SOC_EFUSE_DIS_DOWNLOAD_DCACHE=y +CONFIG_SOC_EFUSE_HARD_DIS_JTAG=y +CONFIG_SOC_EFUSE_DIS_USB_JTAG=y +CONFIG_SOC_EFUSE_SOFT_DIS_JTAG=y +CONFIG_SOC_EFUSE_DIS_DIRECT_BOOT=y +CONFIG_SOC_EFUSE_DIS_ICACHE=y +CONFIG_SOC_EFUSE_BLOCK9_KEY_PURPOSE_QUIRK=y +CONFIG_SOC_SECURE_BOOT_V2_RSA=y +CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=3 +CONFIG_SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS=y +CONFIG_SOC_SUPPORT_SECURE_BOOT_REVOKE_KEY=y +CONFIG_SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX=64 +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_OPTIONS=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_128=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_256=y +CONFIG_SOC_MEMPROT_CPU_PREFETCH_PAD_SIZE=16 +CONFIG_SOC_MEMPROT_MEM_ALIGN_SIZE=256 +CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21 +CONFIG_SOC_MAC_BB_PD_MEM_SIZE=192 +CONFIG_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_WAIT_IDLE=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_RESUME=y +CONFIG_SOC_SPI_MEM_SUPPORT_SW_SUSPEND=y +CONFIG_SOC_SPI_MEM_SUPPORT_FLASH_OPI_MODE=y +CONFIG_SOC_SPI_MEM_SUPPORT_TIMING_TUNING=y +CONFIG_SOC_SPI_MEM_SUPPORT_CONFIG_GPIO_BY_EFUSE=y +CONFIG_SOC_SPI_MEM_SUPPORT_WRAP=y +CONFIG_SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY=y +CONFIG_SOC_MEMSPI_CORE_CLK_SHARED_WITH_PSRAM=y +CONFIG_SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP=y +CONFIG_SOC_COEX_HW_PTI=y +CONFIG_SOC_EXTERNAL_COEX_LEADER_TX_LINE=y +CONFIG_SOC_SDMMC_USE_GPIO_MATRIX=y +CONFIG_SOC_SDMMC_NUM_SLOTS=2 +CONFIG_SOC_SDMMC_SUPPORT_XTAL_CLOCK=y +CONFIG_SOC_SDMMC_DELAY_PHASE_NUM=4 +CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_FAST_RC=y +CONFIG_SOC_WIFI_HW_TSF=y +CONFIG_SOC_WIFI_FTM_SUPPORT=y +CONFIG_SOC_WIFI_GCMP_SUPPORT=y +CONFIG_SOC_WIFI_WAPI_SUPPORT=y +CONFIG_SOC_WIFI_CSI_SUPPORT=y +CONFIG_SOC_WIFI_MESH_SUPPORT=y +CONFIG_SOC_WIFI_SUPPORT_VARIABLE_BEACON_WINDOW=y +CONFIG_SOC_WIFI_PHY_NEEDS_USB_WORKAROUND=y +CONFIG_SOC_BLE_SUPPORTED=y +CONFIG_SOC_BLE_MESH_SUPPORTED=y +CONFIG_SOC_BLE_50_SUPPORTED=y +CONFIG_SOC_BLE_DEVICE_PRIVACY_SUPPORTED=y +CONFIG_SOC_BLUFI_SUPPORTED=y +CONFIG_SOC_ULP_HAS_ADC=y +CONFIG_SOC_PHY_COMBO_MODULE=y +CONFIG_SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV=y +CONFIG_SOC_LCDCAM_CAM_PERIPH_NUM=1 +CONFIG_SOC_LCDCAM_CAM_DATA_WIDTH_MAX=16 +CONFIG_IDF_CMAKE=y +CONFIG_IDF_TOOLCHAIN="gcc" +CONFIG_IDF_TOOLCHAIN_GCC=y +CONFIG_IDF_TARGET_ARCH_XTENSA=y +CONFIG_IDF_TARGET_ARCH="xtensa" +CONFIG_IDF_TARGET="esp32s3" +CONFIG_IDF_INIT_VERSION="$IDF_INIT_VERSION" +CONFIG_IDF_TARGET_ESP32S3=y +CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009 + +# +# Build type +# +CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y +# CONFIG_APP_BUILD_TYPE_RAM is not set +CONFIG_APP_BUILD_GENERATE_BINARIES=y +CONFIG_APP_BUILD_BOOTLOADER=y +CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y +# CONFIG_APP_REPRODUCIBLE_BUILD is not set +# CONFIG_APP_NO_BLOBS is not set +# end of Build type + +# +# Bootloader config +# + +# +# Bootloader manager +# +CONFIG_BOOTLOADER_COMPILE_TIME_DATE=y +CONFIG_BOOTLOADER_PROJECT_VER=1 +# end of Bootloader manager + +# +# Application Rollback +# +CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y +# CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK is not set +# end of Application Rollback + +# +# Recovery Bootloader and Rollback +# +# end of Recovery Bootloader and Rollback + +CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x0 +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set + +# +# Log +# +CONFIG_BOOTLOADER_LOG_VERSION_1=y +CONFIG_BOOTLOADER_LOG_VERSION=1 +# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set +CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y +# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set +CONFIG_BOOTLOADER_LOG_LEVEL=3 + +# +# Format +# +CONFIG_BOOTLOADER_LOG_COLORS=y +CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y +# end of Format + +# +# Settings +# +CONFIG_BOOTLOADER_LOG_MODE_TEXT_EN=y +CONFIG_BOOTLOADER_LOG_MODE_TEXT=y +# end of Settings +# end of Log + +# +# Serial Flash Configurations +# +# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set +CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y +CONFIG_BOOTLOADER_FLASH_32BIT_ADDR=y +CONFIG_BOOTLOADER_FLASH_NEEDS_32BIT_FEAT=y +CONFIG_BOOTLOADER_FLASH_NEEDS_32BIT_ADDR_QUAD_FLASH=y +# end of Serial Flash Configurations + +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y +# CONFIG_BOOTLOADER_FACTORY_RESET is not set +# CONFIG_BOOTLOADER_APP_TEST is not set +CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y +CONFIG_BOOTLOADER_WDT_ENABLE=y +# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set +CONFIG_BOOTLOADER_WDT_TIME_MS=9000 +# CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set +CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0 +# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set +# end of Bootloader config + +# +# Security features +# +CONFIG_SECURE_BOOT_V2_RSA_SUPPORTED=y +CONFIG_SECURE_BOOT_V2_PREFERRED=y +# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set +# CONFIG_SECURE_BOOT is not set +# CONFIG_SECURE_FLASH_ENC_ENABLED is not set +CONFIG_SECURE_ROM_DL_MODE_ENABLED=y +# end of Security features + +# +# Application manager +# +CONFIG_APP_COMPILE_TIME_DATE=y +# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set +# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set +# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set +CONFIG_APP_RETRIEVE_LEN_ELF_SHA=9 +# end of Application manager + +CONFIG_ESP_ROM_HAS_CRC_LE=y +CONFIG_ESP_ROM_HAS_CRC_BE=y +CONFIG_ESP_ROM_HAS_MZ_CRC32=y +CONFIG_ESP_ROM_HAS_JPEG_DECODE=y +CONFIG_ESP_ROM_UART_CLK_IS_XTAL=y +CONFIG_ESP_ROM_HAS_RETARGETABLE_LOCKING=y +CONFIG_ESP_ROM_USB_OTG_NUM=3 +CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=4 +CONFIG_ESP_ROM_HAS_ERASE_0_REGION_BUG=y +CONFIG_ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV=y +CONFIG_ESP_ROM_GET_CLK_FREQ=y +CONFIG_ESP_ROM_HAS_HAL_WDT=y +CONFIG_ESP_ROM_NEEDS_SWSETUP_WORKAROUND=y +CONFIG_ESP_ROM_HAS_LAYOUT_TABLE=y +CONFIG_ESP_ROM_HAS_SPI_FLASH=y +CONFIG_ESP_ROM_HAS_SPI_FLASH_MMAP=y +CONFIG_ESP_ROM_HAS_ETS_PRINTF_BUG=y +CONFIG_ESP_ROM_HAS_NEWLIB=y +CONFIG_ESP_ROM_HAS_NEWLIB_NANO_FORMAT=y +CONFIG_ESP_ROM_HAS_NEWLIB_32BIT_TIME=y +CONFIG_ESP_ROM_NEEDS_SET_CACHE_MMU_SIZE=y +CONFIG_ESP_ROM_RAM_APP_NEEDS_MMU_INIT=y +CONFIG_ESP_ROM_HAS_FLASH_COUNT_PAGES_BUG=y +CONFIG_ESP_ROM_HAS_CACHE_SUSPEND_WAITI_BUG=y +CONFIG_ESP_ROM_HAS_CACHE_WRITEBACK_BUG=y +CONFIG_ESP_ROM_HAS_SW_FLOAT=y +CONFIG_ESP_ROM_HAS_VERSION=y +CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB=y +CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y +CONFIG_ESP_ROM_CONSOLE_OUTPUT_SECONDARY=y + +# +# Boot ROM Behavior +# +CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y +# CONFIG_BOOT_ROM_LOG_ALWAYS_OFF is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_HIGH is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_LOW is not set +# end of Boot ROM Behavior + +# +# Serial flasher config +# +# CONFIG_ESPTOOLPY_NO_STUB is not set +# CONFIG_ESPTOOLPY_OCT_FLASH is not set +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 +CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +# CONFIG_ESPTOOLPY_FLASHFREQ_120M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +# CONFIG_ESPTOOLPY_FLASHFREQ_40M is not set +# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set +CONFIG_ESPTOOLPY_FLASHFREQ="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 is not set +CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE="32MB" +# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set +CONFIG_ESPTOOLPY_BEFORE_RESET=y +# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +# CONFIG_ESPTOOLPY_AFTER_NORESET is not set +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +# end of Serial flasher config + +# +# Partition Table +# +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +CONFIG_PARTITION_TABLE_TWO_OTA_LARGE=y +# CONFIG_PARTITION_TABLE_CUSTOM is not set +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_two_ota_large.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# ink-board Configuration +# +CONFIG_PARTIAL_REFRESH_LIMIT=20 +# end of ink-board Configuration + +# +# Compiler options +# +# CONFIG_COMPILER_OPTIMIZATION_DEBUG is not set +# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set +CONFIG_COMPILER_OPTIMIZATION_PERF=y +# CONFIG_COMPILER_OPTIMIZATION_NONE is not set +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set +CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE=y +CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set +CONFIG_COMPILER_HIDE_PATHS_MACROS=y +# CONFIG_COMPILER_CXX_EXCEPTIONS is not set +CONFIG_COMPILER_CXX_RTTI=y +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set +# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set +# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set +CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y +# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set +# CONFIG_COMPILER_DUMP_RTL_FILES is not set +CONFIG_COMPILER_RT_LIB_GCCLIB=y +CONFIG_COMPILER_RT_LIB_NAME="gcc" +CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y +# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set +# CONFIG_COMPILER_STATIC_ANALYZER is not set +# end of Compiler options + +# +# Component config +# + +# +# Driver Configurations +# + +# +# Legacy TWAI Driver Configurations +# +# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set +CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y +# end of Legacy TWAI Driver Configurations + +# +# Legacy ADC Driver Configuration +# +# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set + +# +# Legacy ADC Calibration Configuration +# +# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy ADC Calibration Configuration +# end of Legacy ADC Driver Configuration + +# +# Legacy MCPWM Driver Configurations +# +# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy MCPWM Driver Configurations + +# +# Legacy Timer Group Driver Configurations +# +# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy Timer Group Driver Configurations + +# +# Legacy RMT Driver Configurations +# +# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy RMT Driver Configurations + +# +# Legacy I2S Driver Configurations +# +# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy I2S Driver Configurations + +# +# Legacy I2C Driver Configurations +# +# CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy I2C Driver Configurations + +# +# Legacy PCNT Driver Configurations +# +# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy PCNT Driver Configurations + +# +# Legacy SDM Driver Configurations +# +# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy SDM Driver Configurations + +# +# Legacy Temperature Sensor Driver Configurations +# +# CONFIG_TEMP_SENSOR_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_TEMP_SENSOR_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy Temperature Sensor Driver Configurations + +# +# Legacy Touch Sensor Driver Configurations +# +# CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_TOUCH_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy Touch Sensor Driver Configurations +# end of Driver Configurations + +# +# eFuse Bit Manager +# +# CONFIG_EFUSE_CUSTOM_TABLE is not set +# CONFIG_EFUSE_VIRTUAL is not set +CONFIG_EFUSE_MAX_BLK_LEN=256 +# end of eFuse Bit Manager + +# +# ESP-TLS +# +CONFIG_ESP_TLS_USING_MBEDTLS=y +# CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set +# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set +# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set +# CONFIG_ESP_TLS_PSK_VERIFICATION is not set +# CONFIG_ESP_TLS_INSECURE is not set +CONFIG_ESP_TLS_DYN_BUF_STRATEGY_SUPPORTED=y +# end of ESP-TLS + +# +# Wireless Coexistence +# +CONFIG_ESP_COEX_ENABLED=y +# CONFIG_ESP_COEX_EXTERNAL_COEXIST_ENABLE is not set +# CONFIG_ESP_COEX_GPIO_DEBUG is not set +# end of Wireless Coexistence + +# +# Common ESP-related +# +CONFIG_ESP_ERR_TO_NAME_LOOKUP=y +# end of Common ESP-related + +# +# ESP-Driver:GPIO Configurations +# +# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:GPIO Configurations + +# +# ESP-Driver:GPTimer Configurations +# +CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y +# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set +# CONFIG_GPTIMER_ISR_CACHE_SAFE is not set +CONFIG_GPTIMER_OBJ_CACHE_SAFE=y +# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:GPTimer Configurations + +# +# ESP-Driver:I2C Configurations +# +# CONFIG_I2C_ISR_IRAM_SAFE is not set +# CONFIG_I2C_ENABLE_DEBUG_LOG is not set +# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set +CONFIG_I2C_MASTER_ISR_HANDLER_IN_IRAM=y +# end of ESP-Driver:I2C Configurations + +# +# ESP-Driver:I2S Configurations +# +# CONFIG_I2S_ISR_IRAM_SAFE is not set +# CONFIG_I2S_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:I2S Configurations + +# +# ESP-Driver:LEDC Configurations +# +# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:LEDC Configurations + +# +# ESP-Driver:MCPWM Configurations +# +CONFIG_MCPWM_ISR_HANDLER_IN_IRAM=y +# CONFIG_MCPWM_ISR_CACHE_SAFE is not set +# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set +CONFIG_MCPWM_OBJ_CACHE_SAFE=y +# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:MCPWM Configurations + +# +# ESP-Driver:PCNT Configurations +# +# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set +# CONFIG_PCNT_ISR_IRAM_SAFE is not set +# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:PCNT Configurations + +# +# ESP-Driver:RMT Configurations +# +CONFIG_RMT_ENCODER_FUNC_IN_IRAM=y +CONFIG_RMT_TX_ISR_HANDLER_IN_IRAM=y +CONFIG_RMT_RX_ISR_HANDLER_IN_IRAM=y +# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set +# CONFIG_RMT_TX_ISR_CACHE_SAFE is not set +# CONFIG_RMT_RX_ISR_CACHE_SAFE is not set +CONFIG_RMT_OBJ_CACHE_SAFE=y +# CONFIG_RMT_ENABLE_DEBUG_LOG is not set +# CONFIG_RMT_ISR_IRAM_SAFE is not set +# end of ESP-Driver:RMT Configurations + +# +# ESP-Driver:Sigma Delta Modulator Configurations +# +# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set +# CONFIG_SDM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Sigma Delta Modulator Configurations + +# +# ESP-Driver:SPI Configurations +# +# CONFIG_SPI_MASTER_IN_IRAM is not set +CONFIG_SPI_MASTER_ISR_IN_IRAM=y +# CONFIG_SPI_SLAVE_IN_IRAM is not set +CONFIG_SPI_SLAVE_ISR_IN_IRAM=y +# end of ESP-Driver:SPI Configurations + +# +# ESP-Driver:Temperature Sensor Configurations +# +# CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Temperature Sensor Configurations + +# +# ESP-Driver:TWAI Configurations +# +# CONFIG_TWAI_ISR_IN_IRAM is not set +# CONFIG_TWAI_IO_FUNC_IN_IRAM is not set +# CONFIG_TWAI_ISR_CACHE_SAFE is not set +# CONFIG_TWAI_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:TWAI Configurations + +# +# ESP-Driver:UART Configurations +# +# CONFIG_UART_ISR_IN_IRAM is not set +# end of ESP-Driver:UART Configurations + +# +# ESP-Driver:UHCI Configurations +# +# CONFIG_UHCI_ISR_HANDLER_IN_IRAM is not set +# CONFIG_UHCI_ISR_CACHE_SAFE is not set +# CONFIG_UHCI_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:UHCI Configurations + +# +# ESP-Driver:USB Serial/JTAG Configuration +# +CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y +# end of ESP-Driver:USB Serial/JTAG Configuration + +# +# Event Loop Library +# +# CONFIG_ESP_EVENT_LOOP_PROFILING is not set +CONFIG_ESP_EVENT_POST_FROM_ISR=y +CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y +# end of Event Loop Library + +# +# ESP HTTP client +# +CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y +# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set +CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000 +# end of ESP HTTP client + +# +# Hardware Settings +# + +# +# Chip revision +# +CONFIG_ESP32S3_REV_MIN_0=y +# CONFIG_ESP32S3_REV_MIN_1 is not set +# CONFIG_ESP32S3_REV_MIN_2 is not set +CONFIG_ESP32S3_REV_MIN_FULL=0 +CONFIG_ESP_REV_MIN_FULL=0 + +# +# Maximum Supported ESP32-S3 Revision (Rev v0.99) +# +CONFIG_ESP32S3_REV_MAX_FULL=99 +CONFIG_ESP_REV_MAX_FULL=99 +CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0 +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 +# +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES=4 +# CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_TWO is not set +CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4 +# CONFIG_ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC is not set +# end of MAC Config + +# +# Sleep Config +# +CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y +CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y +CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y +CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y +CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y +CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=2000 +# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set +# CONFIG_ESP_SLEEP_DEBUG is not set +CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y +# end of Sleep Config + +# +# RTC Clock Config +# +CONFIG_RTC_CLK_SRC_INT_RC=y +# CONFIG_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_RTC_CLK_CAL_CYCLES=1024 +# end of RTC Clock Config + +# +# Peripheral Control +# +CONFIG_ESP_PERIPH_CTRL_FUNC_IN_IRAM=y +CONFIG_ESP_REGI2C_CTRL_FUNC_IN_IRAM=y +# end of Peripheral Control + +# +# GDMA Configurations +# +CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y +CONFIG_GDMA_ISR_HANDLER_IN_IRAM=y +CONFIG_GDMA_OBJ_DRAM_SAFE=y +# CONFIG_GDMA_ENABLE_DEBUG_LOG is not set +# CONFIG_GDMA_ISR_IRAM_SAFE is not set +# end of GDMA Configurations + +# +# Main XTAL Config +# +CONFIG_XTAL_FREQ_40=y +CONFIG_XTAL_FREQ=40 +# end of Main XTAL Config + +# +# Power Supplier +# + +# +# Brownout Detector +# +CONFIG_ESP_BROWNOUT_DET=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_1 is not set +CONFIG_ESP_BROWNOUT_DET_LVL=7 +CONFIG_ESP_BROWNOUT_USE_INTR=y +# end of Brownout Detector +# end of Power Supplier + +CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y +CONFIG_ESP_INTR_IN_IRAM=y +# end of Hardware Settings + +# +# ESP-Driver:LCD Controller Configurations +# +# CONFIG_LCD_RGB_ISR_IRAM_SAFE is not set +# CONFIG_LCD_RGB_RESTART_IN_VSYNC is not set +# CONFIG_LCD_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:LCD Controller Configurations + +# +# ESP-MM: Memory Management Configurations +# +# CONFIG_ESP_MM_CACHE_MSYNC_C2M_CHUNKED_OPS is not set +# end of ESP-MM: Memory Management Configurations + +# +# ESP NETIF Adapter +# +CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 +# CONFIG_ESP_NETIF_PROVIDE_CUSTOM_IMPLEMENTATION is not set +CONFIG_ESP_NETIF_TCPIP_LWIP=y +# CONFIG_ESP_NETIF_LOOPBACK is not set +CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y +CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y +# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set +# CONFIG_ESP_NETIF_L2_TAP is not set +# CONFIG_ESP_NETIF_BRIDGE_EN is not set +# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set +# end of ESP NETIF Adapter + +# +# Partition API Configuration +# +# end of Partition API Configuration + +# +# PHY +# +CONFIG_ESP_PHY_ENABLED=y +CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y +# CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set +CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP_PHY_MAX_TX_POWER=20 +# CONFIG_ESP_PHY_REDUCE_TX_POWER is not set +CONFIG_ESP_PHY_ENABLE_USB=y +# CONFIG_ESP_PHY_ENABLE_CERT_TEST is not set +CONFIG_ESP_PHY_RF_CAL_PARTIAL=y +# CONFIG_ESP_PHY_RF_CAL_NONE is not set +# CONFIG_ESP_PHY_RF_CAL_FULL is not set +CONFIG_ESP_PHY_CALIBRATION_MODE=0 +CONFIG_ESP_PHY_PLL_TRACK_PERIOD_MS=1000 +# CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set +# CONFIG_ESP_PHY_RECORD_USED_TIME is not set +CONFIG_ESP_PHY_IRAM_OPT=y +# CONFIG_ESP_PHY_DEBUG is not set +# end of PHY + +# +# Power Management +# +CONFIG_PM_SLEEP_FUNC_IN_IRAM=y +# CONFIG_PM_ENABLE is not set +CONFIG_PM_SLP_IRAM_OPT=y +CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y +CONFIG_PM_RESTORE_CACHE_TAGMEM_AFTER_LIGHT_SLEEP=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 +CONFIG_SPIRAM_TYPE_AUTO=y +# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set +CONFIG_SPIRAM_CLK_IO=30 +CONFIG_SPIRAM_CS_IO=26 +# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set +# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set +# CONFIG_SPIRAM_RODATA is not set +CONFIG_SPIRAM_SPEED_80M=y +# CONFIG_SPIRAM_SPEED_40M is not set +CONFIG_SPIRAM_SPEED=80 +# CONFIG_SPIRAM_ECC_ENABLE is not set +CONFIG_SPIRAM_BOOT_HW_INIT=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y +CONFIG_SPIRAM_IGNORE_NOTFOUND=y +# CONFIG_SPIRAM_USE_MEMMAP is not set +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +CONFIG_SPIRAM_USE_MALLOC=y +# CONFIG_SPIRAM_MEMTEST is not set +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 +# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set +# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set +# end of SPI RAM config +# end of ESP PSRAM + +# +# ESP Ringbuf +# +# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set +# end of ESP Ringbuf + +# +# ESP-ROM +# +CONFIG_ESP_ROM_PRINT_IN_IRAM=y +# end of ESP-ROM + +# +# ESP Security Specific +# +# end of ESP Security Specific + +# +# ESP System Settings +# +# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y +# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 is not set +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160 + +# +# Cache config +# +CONFIG_ESP32S3_INSTRUCTION_CACHE_16KB=y +# CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE=0x4000 +# CONFIG_ESP32S3_INSTRUCTION_CACHE_4WAYS is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_8WAYS=y +CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS=8 +# CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_16B is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_32B=y +CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE=32 +# CONFIG_ESP32S3_DATA_CACHE_16KB is not set +CONFIG_ESP32S3_DATA_CACHE_32KB=y +# CONFIG_ESP32S3_DATA_CACHE_64KB is not set +CONFIG_ESP32S3_DATA_CACHE_SIZE=0x8000 +# CONFIG_ESP32S3_DATA_CACHE_4WAYS is not set +CONFIG_ESP32S3_DATA_CACHE_8WAYS=y +CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS=8 +# CONFIG_ESP32S3_DATA_CACHE_LINE_16B is not set +CONFIG_ESP32S3_DATA_CACHE_LINE_32B=y +# CONFIG_ESP32S3_DATA_CACHE_LINE_64B is not set +CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE=32 +# end of Cache config + +# +# Memory +# +# CONFIG_ESP32S3_RTCDATA_IN_FAST_MEM is not set +# CONFIG_ESP32S3_USE_FIXED_STATIC_RAM_SIZE is not set +# end of Memory + +# +# Trace memory +# +# CONFIG_ESP32S3_TRAX is not set +CONFIG_ESP32S3_TRACEMEM_RESERVE_DRAM=0x0 +# end of Trace memory + +CONFIG_ESP_SYSTEM_IN_IRAM=y +# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set +CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 +CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y +CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y + +# +# Memory protection +# +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=y +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y +# end of Memory protection + +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584 +CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y +# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set +# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set +CONFIG_ESP_MAIN_TASK_AFFINITY=0x0 +CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +# CONFIG_ESP_CONSOLE_USB_CDC is not set +# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set +# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set +# CONFIG_ESP_CONSOLE_NONE is not set +# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set +CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y +CONFIG_ESP_CONSOLE_UART=y +CONFIG_ESP_CONSOLE_UART_NUM=0 +CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0 +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_INT_WDT_CHECK_CPU1=y +CONFIG_ESP_TASK_WDT_EN=y +CONFIG_ESP_TASK_WDT_INIT=y +# CONFIG_ESP_TASK_WDT_PANIC is not set +CONFIG_ESP_TASK_WDT_TIMEOUT_S=10 +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP_PANIC_HANDLER_IRAM is not set +# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP_DEBUG_OCDAWARE=y +CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y +CONFIG_ESP_SYSTEM_BBPLL_RECALIB=y +# end of ESP System Settings + +# +# IPC (Inter-Processor Call) +# +CONFIG_ESP_IPC_ENABLE=y +CONFIG_ESP_IPC_TASK_STACK_SIZE=1280 +CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y +CONFIG_ESP_IPC_ISR_ENABLE=y +# end of IPC (Inter-Processor Call) + +# +# ESP Timer (High Resolution Timer) +# +CONFIG_ESP_TIMER_IN_IRAM=y +# CONFIG_ESP_TIMER_PROFILING is not set +CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y +CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1 +# CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL is not set +CONFIG_ESP_TIMER_TASK_AFFINITY=0x0 +CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y +CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y +# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set +CONFIG_ESP_TIMER_IMPL_SYSTIMER=y +# end of ESP Timer (High Resolution Timer) + +# +# Wi-Fi +# +CONFIG_ESP_WIFI_ENABLED=y +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_STATIC_TX_BUFFER=y +# CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER is not set +CONFIG_ESP_WIFI_TX_BUFFER_TYPE=0 +CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=16 +CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER=y +# CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER is not set +CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0 +CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5 +# CONFIG_ESP_WIFI_CSI_ENABLED is not set +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP_WIFI_TX_BA_WIN=6 +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP_WIFI_RX_BA_WIN=6 +CONFIG_ESP_WIFI_NVS_ENABLED=y +# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0 is not set +CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1=y +CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP_WIFI_IRAM_OPT=y +# CONFIG_ESP_WIFI_EXTRA_IRAM_OPT is not set +CONFIG_ESP_WIFI_RX_IRAM_OPT=y +CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP_WIFI_ENABLE_SAE_PK=y +CONFIG_ESP_WIFI_ENABLE_SAE_H2E=y +CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y +CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=y +# CONFIG_ESP_WIFI_SLP_IRAM_OPT is not set +CONFIG_ESP_WIFI_SLP_DEFAULT_MIN_ACTIVE_TIME=50 +# CONFIG_ESP_WIFI_BSS_MAX_IDLE_SUPPORT is not set +CONFIG_ESP_WIFI_SLP_DEFAULT_MAX_ACTIVE_TIME=10 +CONFIG_ESP_WIFI_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 +# CONFIG_ESP_WIFI_FTM_ENABLE is not set +CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y +# CONFIG_ESP_WIFI_GCMP_SUPPORT is not set +CONFIG_ESP_WIFI_GMAC_SUPPORT=y +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y +# CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set +CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7 +CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y +CONFIG_ESP_WIFI_MBEDTLS_TLS_CLIENT=y +# CONFIG_ESP_WIFI_WAPI_PSK is not set +# CONFIG_ESP_WIFI_SUITE_B_192 is not set +# CONFIG_ESP_WIFI_11KV_SUPPORT is not set +# CONFIG_ESP_WIFI_MBO_SUPPORT is not set +# CONFIG_ESP_WIFI_DPP_SUPPORT is not set +# CONFIG_ESP_WIFI_11R_SUPPORT is not set +# CONFIG_ESP_WIFI_WPS_SOFTAP_REGISTRAR is not set + +# +# WPS Configuration Options +# +# CONFIG_ESP_WIFI_WPS_STRICT is not set +# CONFIG_ESP_WIFI_WPS_PASSPHRASE is not set +# CONFIG_ESP_WIFI_WPS_RECONNECT_ON_FAIL is not set +# end of WPS Configuration Options + +# CONFIG_ESP_WIFI_DEBUG_PRINT is not set +CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=y +# CONFIG_ESP_WIFI_ENT_FREE_DYNAMIC_BUFFER is not set +# end of Wi-Fi + +# +# FreeRTOS +# + +# +# Kernel +# +# CONFIG_FREERTOS_SMP is not set +# CONFIG_FREERTOS_UNICORE is not set +CONFIG_FREERTOS_HZ=100 +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 +# CONFIG_FREERTOS_USE_IDLE_HOOK is not set +# CONFIG_FREERTOS_USE_TICK_HOOK is not set +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set +CONFIG_FREERTOS_USE_TIMERS=y +CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc" +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set +CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY=y +CONFIG_FREERTOS_TIMER_SERVICE_TASK_CORE_AFFINITY=0x7FFFFFFF +CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 +# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set +# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set +# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set +# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set +# end of Kernel + +# +# Port +# +# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set +CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y +# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set +# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set +CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y +CONFIG_FREERTOS_ISR_STACKSIZE=1536 +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y +# CONFIG_FREERTOS_FPU_IN_ISR is not set +CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y +CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y +# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set +CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y +# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set +# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set +# end of Port + +# +# Extra +# +CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y +# end of Extra + +CONFIG_FREERTOS_PORT=y +CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y +CONFIG_FREERTOS_DEBUG_OCDAWARE=y +CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y +CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y +CONFIG_FREERTOS_NUMBER_OF_CORES=2 +CONFIG_FREERTOS_IN_IRAM=y +# end of FreeRTOS + +# +# Hardware Abstraction Layer (HAL) and Low Level (LL) +# +CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y +# CONFIG_HAL_ASSERTION_DISABLE is not set +# CONFIG_HAL_ASSERTION_SILENT is not set +# CONFIG_HAL_ASSERTION_ENABLE is not set +CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 +CONFIG_HAL_WDT_USE_ROM_IMPL=y +# end of Hardware Abstraction Layer (HAL) and Low Level (LL) + +# +# Heap memory debugging +# +CONFIG_HEAP_POISONING_DISABLED=y +# CONFIG_HEAP_POISONING_LIGHT is not set +# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set +CONFIG_HEAP_TRACING_OFF=y +# CONFIG_HEAP_TRACING_STANDALONE is not set +# CONFIG_HEAP_TRACING_TOHOST is not set +# CONFIG_HEAP_USE_HOOKS is not set +# CONFIG_HEAP_TASK_TRACKING is not set +# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set +# CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set +# end of Heap memory debugging + +# +# Log +# +CONFIG_LOG_VERSION_1=y +# CONFIG_LOG_VERSION_2 is not set +CONFIG_LOG_VERSION=1 + +# +# Log Level +# +# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set +# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set +# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y +# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set +# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set +CONFIG_LOG_MAXIMUM_LEVEL=3 + +# +# Level Settings +# +# CONFIG_LOG_MASTER_LEVEL is not set +CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y +# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set +# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y +# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set +CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31 +# end of Level Settings +# end of Log Level + +# +# Format +# +CONFIG_LOG_COLORS=y +CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y +# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set +# end of Format + +# +# Settings +# +CONFIG_LOG_MODE_TEXT_EN=y +CONFIG_LOG_MODE_TEXT=y +# end of Settings + +CONFIG_LOG_IN_IRAM=y +# end of Log + +# +# LWIP +# +CONFIG_LWIP_ENABLE=y +CONFIG_LWIP_LOCAL_HOSTNAME="espressif" +CONFIG_LWIP_TCPIP_TASK_PRIO=18 +# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set +# CONFIG_LWIP_CHECK_THREAD_SAFETY is not set +CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y +# CONFIG_LWIP_L2_TO_L3_COPY is not set +# CONFIG_LWIP_IRAM_OPTIMIZATION is not set +# CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION is not set +CONFIG_LWIP_TIMERS_ONDEMAND=y +CONFIG_LWIP_ND6=y +# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set +CONFIG_LWIP_MAX_SOCKETS=10 +# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set +# CONFIG_LWIP_SO_LINGER is not set +CONFIG_LWIP_SO_REUSE=y +CONFIG_LWIP_SO_REUSE_RXTOALL=y +# CONFIG_LWIP_SO_RCVBUF is not set +# CONFIG_LWIP_NETBUF_RECVINFO is not set +CONFIG_LWIP_IP_DEFAULT_TTL=64 +CONFIG_LWIP_IP4_FRAG=y +CONFIG_LWIP_IP6_FRAG=y +# CONFIG_LWIP_IP4_REASSEMBLY is not set +# CONFIG_LWIP_IP6_REASSEMBLY is not set +CONFIG_LWIP_IP_REASS_MAX_PBUFS=10 +# CONFIG_LWIP_IP_FORWARD is not set +# CONFIG_LWIP_STATS is not set +CONFIG_LWIP_ESP_GRATUITOUS_ARP=y +CONFIG_LWIP_GARP_TMR_INTERVAL=60 +CONFIG_LWIP_ESP_MLDV6_REPORT=y +CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set +# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set +# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set +CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y +# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set +CONFIG_LWIP_DHCP_OPTIONS_LEN=69 +CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0 +CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 + +# +# DHCP server +# +CONFIG_LWIP_DHCPS=y +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y +CONFIG_LWIP_DHCPS_ADD_DNS=y +# end of DHCP server + +# CONFIG_LWIP_AUTOIP is not set +CONFIG_LWIP_IPV4=y +CONFIG_LWIP_IPV6=y +# CONFIG_LWIP_IPV6_AUTOCONFIG is not set +CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 +# CONFIG_LWIP_IPV6_FORWARD is not set +# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +CONFIG_LWIP_MAX_LISTENING_TCP=16 +CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y +CONFIG_LWIP_TCP_MAXRTX=12 +CONFIG_LWIP_TCP_SYNMAXRTX=12 +CONFIG_LWIP_TCP_MSS=1440 +CONFIG_LWIP_TCP_TMR_INTERVAL=250 +CONFIG_LWIP_TCP_MSL=60000 +CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT=20000 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760 +CONFIG_LWIP_TCP_WND_DEFAULT=5760 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_ACCEPTMBOX_SIZE=6 +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +CONFIG_LWIP_TCP_OOSEQ_TIMEOUT=6 +CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=4 +# CONFIG_LWIP_TCP_SACK_OUT is not set +CONFIG_LWIP_TCP_OVERSIZE_MSS=y +# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set +CONFIG_LWIP_TCP_RTO_TIME=1500 +# end of TCP + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +# end of UDP + +# +# Checksums +# +# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set +# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set +CONFIG_LWIP_CHECKSUM_CHECK_ICMP=y +# end of Checksums + +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF +CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 +CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 +CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5 +CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3 +CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10 +# CONFIG_LWIP_IPV6_ND6_ROUTE_INFO_OPTION_SUPPORT is not set +# CONFIG_LWIP_PPP_SUPPORT is not set +# CONFIG_LWIP_SLIP_SUPPORT is not set + +# +# ICMP +# +CONFIG_LWIP_ICMP=y +# CONFIG_LWIP_MULTICAST_PING is not set +# CONFIG_LWIP_BROADCAST_PING is not set +# end of ICMP + +# +# LWIP RAW API +# +CONFIG_LWIP_MAX_RAW_PCBS=16 +# end of LWIP RAW API + +# +# SNTP +# +CONFIG_LWIP_SNTP_MAX_SERVERS=1 +# CONFIG_LWIP_DHCP_GET_NTP_SRV is not set +CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000 +CONFIG_LWIP_SNTP_STARTUP_DELAY=y +CONFIG_LWIP_SNTP_MAXIMUM_STARTUP_DELAY=5000 +# end of SNTP + +# +# DNS +# +CONFIG_LWIP_DNS_MAX_HOST_IP=1 +CONFIG_LWIP_DNS_MAX_SERVERS=3 +# CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set +# CONFIG_LWIP_DNS_SETSERVER_WITH_NETIF is not set +# CONFIG_LWIP_USE_ESP_GETADDRINFO is not set +# end of DNS + +CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7 +CONFIG_LWIP_ESP_LWIP_ASSERT=y + +# +# Hooks +# +# CONFIG_LWIP_HOOK_TCP_ISN_NONE is not set +CONFIG_LWIP_HOOK_TCP_ISN_DEFAULT=y +# CONFIG_LWIP_HOOK_TCP_ISN_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_ROUTE_NONE=y +# CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_ROUTE_CUSTOM is not set +CONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y +# CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT is not set +# CONFIG_LWIP_HOOK_ND6_GET_GW_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_NONE=y +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM is not set +CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_NONE=y +# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_DEFAULT is not set +# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_CUSTOM is not set +CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set +CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_NONE=y +# CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_CUSTOM is not set +# CONFIG_LWIP_HOOK_IP6_INPUT_NONE is not set +CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y +# CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set +# end of Hooks + +# CONFIG_LWIP_DEBUG is not set +# end of LWIP + +# +# mbedTLS +# +CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y +# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set +# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set +# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set +CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 +# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set +# CONFIG_MBEDTLS_DEBUG is not set + +# +# mbedTLS v3.x related +# +# CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 is not set +# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set +# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set +# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set +CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y +# CONFIG_MBEDTLS_SSL_KEYING_MATERIAL_EXPORT is not set +CONFIG_MBEDTLS_PKCS7_C=y +# end of mbedTLS v3.x related + +# +# Certificate Bundle +# +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set +# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST is not set +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 +# end of Certificate Bundle + +# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set +CONFIG_MBEDTLS_CMAC_C=y +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_AES_USE_INTERRUPT=y +CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 +CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y +CONFIG_MBEDTLS_HARDWARE_MPI=y +# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set +CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y +CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 +CONFIG_MBEDTLS_HARDWARE_SHA=y +CONFIG_MBEDTLS_ROM_MD5=y +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set +CONFIG_MBEDTLS_HAVE_TIME=y +# CONFIG_MBEDTLS_PLATFORM_TIME_ALT is not set +# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set +CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y +CONFIG_MBEDTLS_SHA1_C=y +CONFIG_MBEDTLS_SHA512_C=y +# CONFIG_MBEDTLS_SHA3_C is not set +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set +# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set +# CONFIG_MBEDTLS_TLS_DISABLED is not set +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +# CONFIG_MBEDTLS_PSK_MODES is not set +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +# end of TLS Key Exchange Methods + +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set +# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y +CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +# CONFIG_MBEDTLS_CAMELLIA_C is not set +# CONFIG_MBEDTLS_DES_C is not set +# CONFIG_MBEDTLS_BLOWFISH_C is not set +# CONFIG_MBEDTLS_XTEA_C is not set +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +# CONFIG_MBEDTLS_NIST_KW_C is not set +# end of Symmetric Ciphers + +# CONFIG_MBEDTLS_RIPEMD160_C is not set + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +# end of Certificates + +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y +CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y +# CONFIG_MBEDTLS_DHM_C is not set +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +# CONFIG_MBEDTLS_ECJPAKE_C is not set +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y +# CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM is not set +# CONFIG_MBEDTLS_POLY1305_C is not set +# CONFIG_MBEDTLS_CHACHA20_C is not set +# CONFIG_MBEDTLS_HKDF_C is not set +# CONFIG_MBEDTLS_THREADING_C is not set +CONFIG_MBEDTLS_ERROR_STRINGS=y +CONFIG_MBEDTLS_FS_IO=y +# CONFIG_MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION is not set +# end of mbedTLS + +# +# LibC +# +CONFIG_LIBC_NEWLIB=y +CONFIG_LIBC_MISC_IN_IRAM=y +CONFIG_LIBC_LOCKS_PLACE_IN_IRAM=y +CONFIG_LIBC_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_LIBC_STDOUT_LINE_ENDING_LF is not set +# CONFIG_LIBC_STDOUT_LINE_ENDING_CR is not set +# CONFIG_LIBC_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_LIBC_STDIN_LINE_ENDING_LF is not set +CONFIG_LIBC_STDIN_LINE_ENDING_CR=y +# CONFIG_LIBC_NEWLIB_NANO_FORMAT is not set +CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y +# CONFIG_LIBC_TIME_SYSCALL_USE_RTC is not set +# CONFIG_LIBC_TIME_SYSCALL_USE_HRT is not set +# CONFIG_LIBC_TIME_SYSCALL_USE_NONE is not set +# end of LibC + +CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y + +# +# NVS +# +# CONFIG_NVS_ENCRYPTION is not set +# CONFIG_NVS_ASSERT_ERROR_CHECK is not set +# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set +# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set +# end of NVS + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_PTHREAD_STACK_MIN=768 +CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y +# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set +# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set +CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" +# end of PThreads + +# +# MMU Config +# +CONFIG_MMU_PAGE_SIZE_64KB=y +CONFIG_MMU_PAGE_MODE="64KB" +CONFIG_MMU_PAGE_SIZE=0x10000 +# end of MMU Config + +# +# Main Flash configuration +# + +# +# SPI Flash behavior when brownout +# +CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y +CONFIG_SPI_FLASH_BROWNOUT_RESET=y +# end of SPI Flash behavior when brownout + +# +# Optional and Experimental Features (READ DOCS FIRST) +# + +# +# Features here require specific hardware (READ DOCS FIRST!) +# +# CONFIG_SPI_FLASH_HPM_ENA is not set +CONFIG_SPI_FLASH_HPM_AUTO=y +# CONFIG_SPI_FLASH_HPM_DIS is not set +CONFIG_SPI_FLASH_HPM_ON=y +CONFIG_SPI_FLASH_HPM_DC_AUTO=y +# CONFIG_SPI_FLASH_HPM_DC_DISABLE is not set +# CONFIG_SPI_FLASH_AUTO_SUSPEND is not set +CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 +# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set +# CONFIG_SPI_FLASH_FORCE_ENABLE_C6_H2_SUSPEND is not set +CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM=y +# end of Optional and Experimental Features (READ DOCS FIRST) +# end of Main Flash configuration + +# +# SPI Flash driver +# +# CONFIG_SPI_FLASH_VERIFY_WRITE is not set +# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +# CONFIG_SPI_FLASH_ROM_IMPL is not set +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set +# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set +CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y +CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 +CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 +CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 +# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set +# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set +# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set + +# +# Auto-detect flash chips +# +CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_GD_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_ISSI_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_MXIC_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_WINBOND_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_BOYA_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_VENDOR_TH_SUPPORT_ENABLED=y +CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_TH_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_MXIC_OPI_CHIP=y +# end of Auto-detect flash chips + +CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y +# end of SPI Flash driver + +# +# TCP Transport +# + +# +# Websocket +# +CONFIG_WS_TRANSPORT=y +CONFIG_WS_BUFFER_SIZE=1024 +# CONFIG_WS_DYNAMIC_BUFFER is not set +# end of Websocket +# end of TCP Transport + +# +# Virtual file system +# +CONFIG_VFS_SUPPORT_IO=y +CONFIG_VFS_SUPPORT_DIR=y +CONFIG_VFS_SUPPORT_SELECT=y +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +# CONFIG_VFS_SELECT_IN_RAM is not set +CONFIG_VFS_SUPPORT_TERMIOS=y +CONFIG_VFS_MAX_COUNT=8 + +# +# Host File System I/O (Semihosting) +# +CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# end of Host File System I/O (Semihosting) + +CONFIG_VFS_INITIALIZE_DEV_NULL=y +# end of Virtual file system + +# +# ESP LCD TOUCH +# +CONFIG_ESP_LCD_TOUCH_MAX_POINTS=5 +CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS=1 +# end of ESP LCD TOUCH + +# +# ESP LVGL PORT +# +# end of ESP LVGL PORT + +# +# LVGL configuration +# +CONFIG_LV_CONF_SKIP=y +# CONFIG_LV_CONF_MINIMAL is not set + +# +# Color Settings +# +# CONFIG_LV_COLOR_DEPTH_32 is not set +# CONFIG_LV_COLOR_DEPTH_24 is not set +CONFIG_LV_COLOR_DEPTH_16=y +# CONFIG_LV_COLOR_DEPTH_8 is not set +# CONFIG_LV_COLOR_DEPTH_1 is not set +CONFIG_LV_COLOR_DEPTH=16 +# end of Color Settings + +# +# Memory Settings +# +# CONFIG_LV_USE_BUILTIN_MALLOC is not set +CONFIG_LV_USE_CLIB_MALLOC=y +# CONFIG_LV_USE_MICROPYTHON_MALLOC is not set +# CONFIG_LV_USE_RTTHREAD_MALLOC is not set +# CONFIG_LV_USE_CUSTOM_MALLOC is not set +CONFIG_LV_USE_BUILTIN_STRING=y +# CONFIG_LV_USE_CLIB_STRING is not set +# CONFIG_LV_USE_CUSTOM_STRING is not set +CONFIG_LV_USE_BUILTIN_SPRINTF=y +# CONFIG_LV_USE_CLIB_SPRINTF is not set +# CONFIG_LV_USE_CUSTOM_SPRINTF is not set +# end of Memory Settings + +# +# HAL Settings +# +CONFIG_LV_DEF_REFR_PERIOD=33 +CONFIG_LV_DPI_DEF=124 +# end of HAL Settings + +# +# Operating System (OS) +# +# CONFIG_LV_OS_NONE is not set +# CONFIG_LV_OS_PTHREAD is not set +CONFIG_LV_OS_FREERTOS=y +# CONFIG_LV_OS_CMSIS_RTOS2 is not set +# CONFIG_LV_OS_RTTHREAD is not set +# CONFIG_LV_OS_WINDOWS is not set +# CONFIG_LV_OS_MQX is not set +# CONFIG_LV_OS_SDL2 is not set +# CONFIG_LV_OS_CUSTOM is not set +CONFIG_LV_USE_FREERTOS_TASK_NOTIFY=y +# end of Operating System (OS) + +# +# Rendering Configuration +# +CONFIG_LV_DRAW_BUF_STRIDE_ALIGN=1 +CONFIG_LV_DRAW_BUF_ALIGN=4 +CONFIG_LV_DRAW_LAYER_SIMPLE_BUF_SIZE=24576 +CONFIG_LV_DRAW_LAYER_MAX_MEMORY=0 +CONFIG_LV_DRAW_THREAD_STACK_SIZE=8192 +CONFIG_LV_DRAW_THREAD_PRIO=3 +CONFIG_LV_USE_DRAW_SW=y +CONFIG_LV_DRAW_SW_SUPPORT_RGB565=y +CONFIG_LV_DRAW_SW_SUPPORT_RGB565A8=y +CONFIG_LV_DRAW_SW_SUPPORT_RGB888=y +CONFIG_LV_DRAW_SW_SUPPORT_XRGB8888=y +CONFIG_LV_DRAW_SW_SUPPORT_ARGB8888=y +CONFIG_LV_DRAW_SW_SUPPORT_ARGB8888_PREMULTIPLIED=y +CONFIG_LV_DRAW_SW_SUPPORT_L8=y +CONFIG_LV_DRAW_SW_SUPPORT_AL88=y +CONFIG_LV_DRAW_SW_SUPPORT_A8=y +CONFIG_LV_DRAW_SW_SUPPORT_I1=y +CONFIG_LV_DRAW_SW_I1_LUM_THRESHOLD=127 +CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=1 +# CONFIG_LV_USE_DRAW_ARM2D_SYNC is not set +# CONFIG_LV_USE_NATIVE_HELIUM_ASM is not set +CONFIG_LV_DRAW_SW_COMPLEX=y +# CONFIG_LV_USE_DRAW_SW_COMPLEX_GRADIENTS is not set +CONFIG_LV_DRAW_SW_SHADOW_CACHE_SIZE=0 +CONFIG_LV_DRAW_SW_CIRCLE_CACHE_SIZE=4 +CONFIG_LV_DRAW_SW_ASM_NONE=y +# CONFIG_LV_DRAW_SW_ASM_NEON is not set +# CONFIG_LV_DRAW_SW_ASM_HELIUM is not set +# CONFIG_LV_DRAW_SW_ASM_CUSTOM is not set +CONFIG_LV_USE_DRAW_SW_ASM=0 +# CONFIG_LV_USE_PXP is not set +# CONFIG_LV_USE_G2D is not set +# CONFIG_LV_USE_DRAW_DAVE2D is not set +# CONFIG_LV_USE_DRAW_SDL is not set +# CONFIG_LV_USE_DRAW_VG_LITE is not set +# CONFIG_LV_USE_VECTOR_GRAPHIC is not set +# CONFIG_LV_USE_DRAW_DMA2D is not set +# CONFIG_LV_USE_PPA is not set +# CONFIG_LV_USE_DRAW_EVE is not set +# end of Rendering Configuration + +# +# Feature Configuration +# + +# +# Logging +# +# CONFIG_LV_USE_LOG is not set +# end of Logging + +# +# Asserts +# +CONFIG_LV_USE_ASSERT_NULL=y +CONFIG_LV_USE_ASSERT_MALLOC=y +CONFIG_LV_USE_ASSERT_STYLE=y +# CONFIG_LV_USE_ASSERT_MEM_INTEGRITY is not set +# CONFIG_LV_USE_ASSERT_OBJ is not set +CONFIG_LV_ASSERT_HANDLER_INCLUDE="assert.h" +# end of Asserts + +# +# Debug +# +# CONFIG_LV_USE_REFR_DEBUG is not set +# CONFIG_LV_USE_LAYER_DEBUG is not set +# CONFIG_LV_USE_PARALLEL_DRAW_DEBUG is not set +# end of Debug + +# +# Others +# +# CONFIG_LV_ENABLE_GLOBAL_CUSTOM is not set +CONFIG_LV_CACHE_DEF_SIZE=0 +CONFIG_LV_IMAGE_HEADER_CACHE_DEF_CNT=0 +CONFIG_LV_GRADIENT_MAX_STOPS=2 +CONFIG_LV_COLOR_MIX_ROUND_OFS=128 +# CONFIG_LV_OBJ_STYLE_CACHE is not set +# CONFIG_LV_USE_OBJ_ID is not set +# CONFIG_LV_USE_OBJ_NAME is not set +# CONFIG_LV_USE_OBJ_PROPERTY is not set +# end of Others +# end of Feature Configuration + +# +# Compiler Settings +# +# CONFIG_LV_BIG_ENDIAN_SYSTEM is not set +CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1 +# CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM is not set +# CONFIG_LV_USE_FLOAT is not set +# CONFIG_LV_USE_MATRIX is not set +# CONFIG_LV_USE_PRIVATE_API is not set +# end of Compiler Settings + +# +# Font Usage +# + +# +# Enable built-in fonts +# +# CONFIG_LV_FONT_MONTSERRAT_8 is not set +# CONFIG_LV_FONT_MONTSERRAT_10 is not set +# CONFIG_LV_FONT_MONTSERRAT_12 is not set +CONFIG_LV_FONT_MONTSERRAT_14=y +# CONFIG_LV_FONT_MONTSERRAT_16 is not set +# CONFIG_LV_FONT_MONTSERRAT_18 is not set +# CONFIG_LV_FONT_MONTSERRAT_20 is not set +CONFIG_LV_FONT_MONTSERRAT_22=y +# CONFIG_LV_FONT_MONTSERRAT_24 is not set +# CONFIG_LV_FONT_MONTSERRAT_26 is not set +# CONFIG_LV_FONT_MONTSERRAT_28 is not set +# CONFIG_LV_FONT_MONTSERRAT_30 is not set +# CONFIG_LV_FONT_MONTSERRAT_32 is not set +# CONFIG_LV_FONT_MONTSERRAT_34 is not set +# CONFIG_LV_FONT_MONTSERRAT_36 is not set +# CONFIG_LV_FONT_MONTSERRAT_38 is not set +CONFIG_LV_FONT_MONTSERRAT_40=y +# CONFIG_LV_FONT_MONTSERRAT_42 is not set +# CONFIG_LV_FONT_MONTSERRAT_44 is not set +# CONFIG_LV_FONT_MONTSERRAT_46 is not set +# CONFIG_LV_FONT_MONTSERRAT_48 is not set +# CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set +# CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set +# CONFIG_LV_FONT_SOURCE_HAN_SANS_SC_14_CJK is not set +# CONFIG_LV_FONT_SOURCE_HAN_SANS_SC_16_CJK is not set +# CONFIG_LV_FONT_UNSCII_8 is not set +# CONFIG_LV_FONT_UNSCII_16 is not set +# end of Enable built-in fonts + +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_10 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set +CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_22 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_24 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_26 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_30 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_32 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_34 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_36 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_38 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_40 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_42 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_44 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_46 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_48 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set +# CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set +# CONFIG_LV_FONT_DEFAULT_SOURCE_HAN_SANS_SC_14_CJK is not set +# CONFIG_LV_FONT_DEFAULT_SOURCE_HAN_SANS_SC_16_CJK is not set +# CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set +# CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set +# CONFIG_LV_FONT_FMT_TXT_LARGE is not set +# CONFIG_LV_USE_FONT_COMPRESSED is not set +CONFIG_LV_USE_FONT_PLACEHOLDER=y + +# +# Enable static fonts +# +# end of Enable static fonts +# end of Font Usage + +# +# Text Settings +# +CONFIG_LV_TXT_ENC_UTF8=y +# CONFIG_LV_TXT_ENC_ASCII is not set +CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_)}" +CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0 +CONFIG_LV_TXT_COLOR_CMD="#" +# CONFIG_LV_USE_BIDI is not set +# CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set +# end of Text Settings + +# +# Widget Usage +# +CONFIG_LV_WIDGETS_HAS_DEFAULT_VALUE=y +CONFIG_LV_USE_ANIMIMG=y +CONFIG_LV_USE_ARC=y +CONFIG_LV_USE_ARCLABEL=y +CONFIG_LV_USE_BAR=y +CONFIG_LV_USE_BUTTON=y +CONFIG_LV_USE_BUTTONMATRIX=y +CONFIG_LV_USE_CALENDAR=y +# CONFIG_LV_CALENDAR_WEEK_STARTS_MONDAY is not set + +# +# Days name configuration +# +CONFIG_LV_MONDAY_STR="Mo" +CONFIG_LV_TUESDAY_STR="Tu" +CONFIG_LV_WEDNESDAY_STR="We" +CONFIG_LV_THURSDAY_STR="Th" +CONFIG_LV_FRIDAY_STR="Fr" +CONFIG_LV_SATURDAY_STR="Sa" +CONFIG_LV_SUNDAY_STR="Su" +# end of Days name configuration + +CONFIG_LV_USE_CALENDAR_HEADER_ARROW=y +CONFIG_LV_USE_CALENDAR_HEADER_DROPDOWN=y +# CONFIG_LV_USE_CALENDAR_CHINESE is not set +CONFIG_LV_USE_CANVAS=y +CONFIG_LV_USE_CHART=y +CONFIG_LV_USE_CHECKBOX=y +CONFIG_LV_USE_DROPDOWN=y +CONFIG_LV_USE_IMAGE=y +CONFIG_LV_USE_IMAGEBUTTON=y +CONFIG_LV_USE_KEYBOARD=y +CONFIG_LV_USE_LABEL=y +CONFIG_LV_LABEL_TEXT_SELECTION=y +CONFIG_LV_LABEL_LONG_TXT_HINT=y +CONFIG_LV_LABEL_WAIT_CHAR_COUNT=3 +CONFIG_LV_USE_LED=y +CONFIG_LV_USE_LINE=y +CONFIG_LV_USE_LIST=y +CONFIG_LV_USE_MENU=y +CONFIG_LV_USE_MSGBOX=y +CONFIG_LV_USE_ROLLER=y +CONFIG_LV_USE_SCALE=y +CONFIG_LV_USE_SLIDER=y +CONFIG_LV_USE_SPAN=y +CONFIG_LV_SPAN_SNIPPET_STACK_SIZE=64 +CONFIG_LV_USE_SPINBOX=y +CONFIG_LV_USE_SPINNER=y +CONFIG_LV_USE_SWITCH=y +CONFIG_LV_USE_TEXTAREA=y +CONFIG_LV_TEXTAREA_DEF_PWD_SHOW_TIME=1500 +CONFIG_LV_USE_TABLE=y +CONFIG_LV_USE_TABVIEW=y +CONFIG_LV_USE_TILEVIEW=y +CONFIG_LV_USE_WIN=y +# end of Widget Usage + +# +# Themes +# +CONFIG_LV_USE_THEME_DEFAULT=y +# CONFIG_LV_THEME_DEFAULT_DARK is not set +CONFIG_LV_THEME_DEFAULT_GROW=y +CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=80 +CONFIG_LV_USE_THEME_SIMPLE=y +# CONFIG_LV_USE_THEME_MONO is not set +# end of Themes + +# +# Layouts +# +CONFIG_LV_USE_FLEX=y +CONFIG_LV_USE_GRID=y +# end of Layouts + +# +# 3rd Party Libraries +# +CONFIG_LV_FS_DEFAULT_DRIVER_LETTER=0 +# CONFIG_LV_USE_FS_STDIO is not set +# CONFIG_LV_USE_FS_POSIX is not set +# CONFIG_LV_USE_FS_WIN32 is not set +# CONFIG_LV_USE_FS_FATFS is not set +# CONFIG_LV_USE_FS_MEMFS is not set +# CONFIG_LV_USE_FS_LITTLEFS is not set +# CONFIG_LV_USE_FS_ARDUINO_ESP_LITTLEFS is not set +# CONFIG_LV_USE_FS_ARDUINO_SD is not set +# CONFIG_LV_USE_FS_UEFI is not set +# CONFIG_LV_USE_FS_FROGFS is not set +# CONFIG_LV_USE_LODEPNG is not set +# CONFIG_LV_USE_LIBPNG is not set +# CONFIG_LV_USE_BMP is not set +# CONFIG_LV_USE_TJPGD is not set +# CONFIG_LV_USE_LIBJPEG_TURBO is not set +# CONFIG_LV_USE_GIF is not set +# CONFIG_LV_BIN_DECODER_RAM_LOAD is not set +# CONFIG_LV_USE_RLE is not set +# CONFIG_LV_USE_QRCODE is not set +# CONFIG_LV_USE_BARCODE is not set +# CONFIG_LV_USE_FREETYPE is not set +# CONFIG_LV_USE_TINY_TTF is not set +# CONFIG_LV_USE_RLOTTIE is not set +# CONFIG_LV_USE_THORVG is not set +# CONFIG_LV_USE_LZ4 is not set +# CONFIG_LV_USE_FFMPEG is not set +# end of 3rd Party Libraries + +# +# Others +# +# CONFIG_LV_USE_SNAPSHOT is not set +# CONFIG_LV_USE_SYSMON is not set +# CONFIG_LV_USE_PROFILER is not set +# CONFIG_LV_USE_MONKEY is not set +# CONFIG_LV_USE_GRIDNAV is not set +# CONFIG_LV_USE_FRAGMENT is not set +# CONFIG_LV_USE_IMGFONT is not set +CONFIG_LV_USE_OBSERVER=y +# CONFIG_LV_USE_IME_PINYIN is not set +# CONFIG_LV_USE_FILE_EXPLORER is not set +# CONFIG_LV_USE_FONT_MANAGER is not set +# CONFIG_LV_USE_TEST is not set +# CONFIG_LV_USE_TRANSLATION is not set +# CONFIG_LV_USE_XML is not set +# CONFIG_LV_USE_COLOR_FILTER is not set +CONFIG_LVGL_VERSION_MAJOR=9 +CONFIG_LVGL_VERSION_MINOR=4 +CONFIG_LVGL_VERSION_PATCH=0 +# end of Others + +# +# Devices +# +# CONFIG_LV_USE_SDL is not set +# CONFIG_LV_USE_X11 is not set +# CONFIG_LV_USE_WAYLAND is not set +# CONFIG_LV_USE_LINUX_FBDEV is not set +# CONFIG_LV_USE_NUTTX is not set +# CONFIG_LV_USE_LINUX_DRM is not set +# CONFIG_LV_USE_TFT_ESPI is not set +# CONFIG_LV_USE_LOVYAN_GFX is not set +# CONFIG_LV_USE_EVDEV is not set +# CONFIG_LV_USE_LIBINPUT is not set +# CONFIG_LV_USE_ST7735 is not set +# CONFIG_LV_USE_ST7789 is not set +# CONFIG_LV_USE_ST7796 is not set +# CONFIG_LV_USE_ILI9341 is not set +# CONFIG_LV_USE_GENERIC_MIPI is not set +# CONFIG_LV_USE_NXP_ELCDIF is not set +# CONFIG_LV_USE_RENESAS_GLCDC is not set +# CONFIG_LV_USE_ST_LTDC is not set +# CONFIG_LV_USE_FT81X is not set +# CONFIG_LV_USE_UEFI is not set +# CONFIG_LV_USE_OPENGLES is not set +# CONFIG_LV_USE_QNX is not set +# end of Devices + +# +# Examples +# +# CONFIG_LV_BUILD_EXAMPLES is not set +# end of Examples + +# +# Demos +# +# CONFIG_LV_BUILD_DEMOS is not set +# end of Demos +# end of LVGL configuration +# end of Component config + +# 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=y +# CONFIG_APP_ANTI_ROLLBACK 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 is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set +# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set +# 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_EXTERNAL_COEX_ENABLE is not set +# CONFIG_ESP_WIFI_EXTERNAL_COEXIST_ENABLE 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_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_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_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_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_MAIN_TASK_STACK_SIZE=3584 +CONFIG_CONSOLE_UART_DEFAULT=y +# CONFIG_CONSOLE_UART_CUSTOM is not set +# CONFIG_CONSOLE_UART_NONE is not set +# CONFIG_ESP_CONSOLE_UART_NONE is not set +CONFIG_CONSOLE_UART=y +CONFIG_CONSOLE_UART_NUM=0 +CONFIG_CONSOLE_UART_BAUDRATE=115200 +CONFIG_INT_WDT=y +CONFIG_INT_WDT_TIMEOUT_MS=300 +CONFIG_INT_WDT_CHECK_CPU1=y +CONFIG_TASK_WDT=y +CONFIG_ESP_TASK_WDT=y +# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_TIMEOUT_S=10 +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP32S3_DEBUG_OCDAWARE=y +CONFIG_IPC_TASK_STACK_SIZE=1280 +CONFIG_TIMER_TASK_STACK_SIZE=3584 +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=y +# CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER is not set +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16 +# 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 is not set +CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1=y +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_WPA_MBEDTLS_CRYPTO=y +CONFIG_WPA_MBEDTLS_TLS_CLIENT=y +# CONFIG_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_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_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +# CONFIG_NEWLIB_NANO_FORMAT is not set +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y +CONFIG_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_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_ESP32_PTHREAD_STACK_MIN=768 +CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set +CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set +CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_SUPPORT_TERMIOS=y +CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# End of deprecated options diff --git a/wokwi.toml b/wokwi.toml new file mode 100644 index 0000000..2ef6510 --- /dev/null +++ b/wokwi.toml @@ -0,0 +1,4 @@ +[wokwi] +version = 1 +firmware = 'build/flasher_args.json' +elf = "build/ink-board.elf" \ No newline at end of file