diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..48c7c23 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +language: "en-US" +reviews: + profile: "chill" + high_level_summary: true + poem: false + collapse_walkthrough: true + sequence_diagrams: false + auto_review: + enabled: true + drafts: true + in_progress_fortune: false + tools: + github-checks: + enabled: false + cppcheck: + enabled: true + pylint: + enabled: true + yamllint: + enabled: false + markdownlint: + enabled: true + finishing_touches: + docstrings: + enabled: false + unit_tests: + enabled: false + pre_merge_checks: + docstrings: + mode: "off" + title: + mode: "off" +chat: + art: false diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..97f18d6 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [Kampi] diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..7ce660c --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,1184 @@ +# GitHub Copilot Instructions for PyroVision Firmware + +## Project Overview + +PyroVision is an ESP32-S3 based thermal imaging camera firmware using the ESP-IDF framework. The project manages a Lepton thermal camera with WiFi connectivity, web interface, VISA server, and comprehensive settings management. + +**Key Technologies:** +- Platform: ESP32-S3 (ESP-IDF framework) +- Build System: PlatformIO +- RTOS: FreeRTOS +- GUI: LVGL +- Storage: NVS, LittleFS, SD Card +- Networking: WiFi (STA/AP), HTTP Server, WebSockets, VISA/SCPI + +--- + +## Code Style and Formatting + +### General Formatting Rules + +The project uses **Artistic Style (AStyle)** with a K&R-based configuration (`scripts/astyle.cfg`): + +- **Style**: K&R (Kernighan & Ritchie) +- **Indentation**: 4 spaces (no tabs) +- **Line Length**: Maximum 120 characters +- **Braces**: K&R style (opening brace on same line for functions, control structures) +- **Operators**: Space padding around operators: `a = bar((b - c) * a, d--);` +- **Headers**: Space between header and bracket: `if (condition) {` +- **Pointers/References**: Stick to name: `char *pThing`, `char &thing` +- **Conditionals**: Always use braces, even for single-line blocks +- **Boolean Negation**: Always use explicit comparison with `== false` or `== NULL` instead of `!` operator +- **Switch**: Indent case statements + +**Example:** +```cpp +esp_err_t MyFunction(uint8_t *p_Buffer, size_t Size) +{ + if (p_Buffer == NULL) { + return ESP_ERR_INVALID_ARG; + } + + for (size_t i = 0; i < Size; i++) { + p_Buffer[i] = ProcessByte(p_Buffer[i]); + } + + return ESP_OK; +} +``` + +**Boolean Negation Examples:** +```cpp +// ✅ CORRECT: Explicit comparison +if (isInitialized == false) { + return ESP_ERR_INVALID_STATE; +} + +if (p_Buffer == NULL) { + return ESP_ERR_INVALID_ARG; +} + +// ❌ INCORRECT: Negation operator +if (!isInitialized) { + return ESP_ERR_INVALID_STATE; +} + +if (!p_Buffer) { + return ESP_ERR_INVALID_ARG; +} +``` + +### Naming Conventions + +#### Functions +- **Public API**: `ModuleName_FunctionName()` using PascalCase + - Examples: `SettingsManager_Init()`, `NetworkManager_Connect()`, `TimeManager_SetTimezone()` +- **Private/Static**: `snake_case` with descriptive names + - Examples: `on_WiFi_Event()`, `run_astyle()` + +#### Variables +- **Local variables**: `PascalCase` for important variables, `lowercase` for simple counters + - Examples: `RetryCount`, `EventGroup`, `Error`, `i`, `x` +- **Pointers**: Prefix with `p` (e.g., `p_Settings`, `p_Buffer`, `p_Data`) +- **Global/Static module state**: Prefix with underscore: `_State`, `_Network_Manager_State`, `_App_Context` + +#### Constants and Macros +- **All UPPERCASE** with underscores: `WIFI_CONNECTED_BIT`, `NVS_NAMESPACE`, `SETTINGS_VERSION` +- Module-specific macros should include module name: `SETTINGS_NVS_NAMESPACE`, `VISA_MAX_CLIENTS` + +#### Types +- **Structs/Enums**: `ModuleName_Description_t` with `_t` suffix + - Examples: `Settings_t`, `Network_State_t`, `Settings_WiFi_t` +- **Enums**: Use descriptive prefix for values + - Example: `SETTINGS_EVENT_LOADED`, `NETWORK_EVENT_WIFI_CONNECTED` + +#### File Names +- Header files: `moduleName.h` +- Implementation files: `moduleName.cpp` +- Types/definitions: `moduleTypes.h` +- Private implementations: `Private/internalModule.cpp` + +### Code Organization + +#### Directory Structure +``` +main/ +├── main.cpp # Application entry point +├── Application/ +│ ├── application.h # Application-wide types and events +│ ├── Manager/ # All manager modules +│ │ ├── managers.h # Manager includes +│ │ ├── Settings/ # Settings management +│ │ ├── Network/ # Network management +│ │ ├── Devices/ # Device drivers +│ │ ├── Time/ # Time management +│ │ └── SD/ # SD card management +│ └── Tasks/ # FreeRTOS tasks +│ ├── tasks.h # Task declarations +│ ├── GUI/ # GUI task +│ ├── Lepton/ # Camera task +│ └── Network/ # Network task +``` + +#### Private Implementations +Use `Private/` subdirectories for internal module implementations that should not be exposed: +``` +Manager/Settings/ +├── settingsManager.h # Public API +├── settingsManager.cpp # Implementation +├── settingsTypes.h # Public types +└── Private/ + ├── settingsLoader.h # Internal interface + ├── settingsJSONLoader.cpp + └── settingsDefaultLoader.cpp +``` + +### Type Casting + +**CRITICAL**: Always use C++ style casts. Never use C-style casts `(Type)value`. + +#### Cast Types and Usage + +##### static_cast() +**Use for**: Safe, checked type conversions at compile time +- Numeric conversions: `static_cast(value)` +- Pointer upcasting in inheritance hierarchies +- Explicit conversions between compatible types +- Void pointer to typed pointer (when type is known) + +**Examples:** +```cpp +// Numeric conversions +uint32_t Value = static_cast(floatValue); +size_t Size = static_cast(intValue); + +// Pointer conversions (type-safe) +uint8_t *p_Buffer = static_cast(heap_caps_malloc(Size, MALLOC_CAP_SPIRAM)); +void *p_Data = GetData(); +Settings_t *p_Settings = static_cast(p_Data); + +// Enum conversions +USB_Mode_t Mode = static_cast(intValue); +``` + +##### reinterpret_cast() +**Use for**: Low-level pointer/reference reinterpretation (use sparingly) +- Converting between unrelated pointer types +- Pointer to integer conversions +- Hardware register access + +**Examples:** +```cpp +// Pointer to integer (for hardware address manipulation) +uintptr_t Address = reinterpret_cast(p_Register); + +// Hardware register access +volatile uint32_t *p_Register = reinterpret_cast(0x40000000); + +// Reinterpreting data (when absolutely necessary) +uint32_t *p_IntData = reinterpret_cast(p_ByteArray); +``` + +##### const_cast() +**Use for**: Removing or adding const/volatile qualifiers (discouraged, use only when interfacing with legacy APIs) +- Passing const data to non-const APIs (ESP-IDF legacy functions) +- Should be avoided in new code + +**Examples:** +```cpp +// Interfacing with legacy API (use sparingly) +void LegacyFunction(char *p_Buffer); // Should be const but isn't + +const char *p_ConstStr = "Hello"; +LegacyFunction(const_cast(p_ConstStr)); // Only if absolutely necessary + +// Better approach: avoid const_cast by redesigning API +``` + +##### dynamic_cast() +**Use for**: Safe downcasting in polymorphic class hierarchies with runtime checking +- Requires RTTI (Runtime Type Information) +- Returns nullptr for pointers or throws for references if cast fails +- Typically not used in embedded systems due to RTTI overhead + +**Examples:** +```cpp +// Polymorphic type checking (rarely used in embedded) +Base *p_Base = GetObject(); +Derived *p_Derived = dynamic_cast(p_Base); +if (p_Derived != nullptr) { + p_Derived->DerivedMethod(); +} +``` + +#### Cast Guidelines + +**DO:** +- Use `static_cast()` for most conversions +- Use `reinterpret_cast()` only for hardware access or truly low-level operations +- Document why `reinterpret_cast()` or `const_cast()` is necessary +- Prefer redesigning APIs over using `const_cast()` + +**DON'T:** +- Never use C-style casts: `(uint8_t *)ptr` ❌ +- Avoid `const_cast()` unless interfacing with legacy code +- Don't use `dynamic_cast()` without RTTI enabled +- Don't use `reinterpret_cast()` for conversions that `static_cast()` can handle + +**Common Patterns:** + +```cpp +// ✅ CORRECT: C++ style casts +uint8_t *p_Data = static_cast(malloc(Size)); +size_t Length = static_cast(strlen(p_String)); +uint32_t Value = static_cast(floatValue); + +// ❌ INCORRECT: C-style casts +uint8_t *p_Data = (uint8_t *)malloc(Size); +size_t Length = (size_t)strlen(p_String); +uint32_t Value = (uint32_t)floatValue; +``` + +--- + +## License and Copyright + +### File Headers + +**Every source file** (`.cpp`, `.h`, `.py`) must include the following header: + +```cpp +/* + * filename.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Brief description of the file's purpose. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ +``` + +For Python files: +```python +""" +filename.py + +Copyright (C) Daniel Kampert, 2026 +Website: www.kampis-elektroecke.de +File info: Brief description of the file's purpose. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +``` + +**License**: GNU General Public License v3.0 +**Copyright Holder**: Daniel Kampert +**Contact**: DanielKampert@kampis-elektroecke.de +**Website**: www.kampis-elektroecke.de + +--- + +## Documentation Standards + +### Function Documentation Requirements + +**CRITICAL**: Every function declaration in header files MUST include complete Doxygen documentation. + +#### Doxygen-Style Documentation for ALL Functions + +Use Doxygen comments for **ALL** public API functions with complete documentation: + +```cpp +/** @brief Initialize the settings manager and load configuration from NVS. + * This function initializes the settings subsystem, opens the NVS namespace, + * and attempts to load stored settings. If no settings exist, default values + * are loaded from JSON or hardcoded defaults. + * @note Must be called after NVS flash initialization. + * This function must be called before any other SettingsManager API calls. + * @warning Not thread-safe during initialization. Call once from main task. + * @return ESP_OK on success + * ESP_ERR_NVS_NOT_FOUND if settings namespace doesn't exist + * ESP_ERR_NO_MEM if memory allocation fails + * ESP_ERR_INVALID_STATE if already initialized + */ +esp_err_t SettingsManager_Init(void); +``` + +**Mandatory documentation elements and order:** +1. `@brief` - Short description on same line or next line, followed by detailed explanation (no blank line between) +2. `@note` - Important usage notes in a single consolidated block +3. `@warning` - Critical warnings in a single consolidated block (if applicable) +4. `@param` - Description for EACH parameter (include direction: in/out/inout if relevant) +5. `@return` - Document ALL possible return values in a single consolidated block + +**CRITICAL formatting rules:** +- **NO blank lines** between @brief and description text +- **NO blank lines** between description and @note +- **NO blank lines** between @note and @warning +- **NO blank lines** between @warning and @param +- **NO blank lines** between @param and @return +- All @return values in ONE block with continuation indentation +- All @note statements in ONE block with continuation indentation +- All @warning statements in ONE block with continuation indentation +- Continuation lines aligned with 20 spaces of indentation + +#### Incomplete Documentation is NOT Acceptable + +❌ **Insufficient:** +```cpp +/** @brief Brief description. + * @param p_Param Description + * @return ESP_OK on success + */ +``` + +❌ **Wrong order (return before note):** +```cpp +/** @brief Set WiFi credentials. + * @param p_SSID SSID string + * @param p_Pass Password string + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if parameters invalid + * @note Changes not persisted until Save() called. + */ +``` + +❌ **Blank lines between blocks:** +```cpp +/** @brief Set WiFi credentials. + * + * @param p_SSID SSID string + * + * @return ESP_OK on success + * + * @note Changes not persisted. + */ +``` + +✅ **Complete and correctly formatted:** +```cpp +/** @brief Set WiFi credentials and update configuration. + * Updates the WiFi SSID and password in RAM and posts a SETTINGS_EVENT_WIFI_CHANGED + * event. Changes are not persisted until SettingsManager_Save() is called. + * @note Call SettingsManager_Save() to persist changes to NVS. + * This function is thread-safe. + * @warning Password is stored in plain text in NVS. + * @param p_SSID Pointer to null-terminated SSID string (max 32 chars) + * @param p_Pass Pointer to null-terminated password string (max 64 chars) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_SSID or p_Pass is NULL + * ESP_ERR_INVALID_ARG if strings exceed maximum length + * ESP_ERR_INVALID_STATE if SettingsManager not initialized + */ +esp_err_t SettingsManager_SetWiFi(const char *p_SSID, const char *p_Pass); +``` + +### Code Comments + +#### Inline Comments +- Use `//` for single-line comments +- Use `/* */` for block comments +- Add explanatory comments for complex logic +- Comment "why", not "what" (the code shows what) + +**Example:** +```cpp +/* Reset config_valid flag to allow reloading default config */ +Error = nvs_set_u8(_State.NVS_Handle, "config_valid", false); +``` + +### Structure Documentation + +Document struct members inline: + +```cpp +typedef struct { + uint16_t Port; /**< HTTP server port. */ + uint16_t WSPingIntervalSec; /**< WebSocket ping interval in seconds. */ + uint8_t MaxClients; /**< Maximum number of simultaneous clients. */ +} Settings_HTTP_Server_t; +``` + +### Enum Documentation + +```cpp +/** @brief Settings event identifiers. + */ +enum { + SETTINGS_EVENT_LOADED, /**< Settings loaded from NVS. */ + SETTINGS_EVENT_SAVED, /**< Settings saved to NVS. */ + SETTINGS_EVENT_WIFI_CHANGED,/**< WiFi settings changed. + Data contains Settings_WiFi_t. */ +}; +``` + +### External Documentation + +For complex modules, create documentation in `docs/` directory: +- Use **AsciiDoc** (`.adoc`) for technical documentation +- Use **Markdown** (`.md`) for README-style documentation +- Include architecture diagrams, API references, usage examples, and troubleshooting + +--- + +## ESP-IDF Specific Conventions + +### Error Handling + +- **Always check return values** from ESP-IDF functions +- Use `ESP_ERROR_CHECK()` for critical initialization that should abort on failure +- Use manual error handling for recoverable errors: + +```cpp +esp_err_t Error = nvs_open(NAMESPACE, NVS_READWRITE, &Handle); +if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to open NVS: %d", Error); + return Error; +} +``` + +### Logging + +Use ESP-IDF logging macros with appropriate levels: +- `ESP_LOGE(TAG, ...)` - Errors +- `ESP_LOGW(TAG, ...)` - Warnings +- `ESP_LOGI(TAG, ...)` - Information +- `ESP_LOGD(TAG, ...)` - Debug + +Define TAG at the top of each file: +```cpp +static const char *TAG = "module_name"; +``` + +### Event System + +- Use ESP Event system for module communication +- Define event bases: `ESP_EVENT_DEFINE_BASE(MODULE_EVENTS);` +- Declare in headers: `ESP_EVENT_DECLARE_BASE(MODULE_EVENTS);` +- Use descriptive event IDs in enums +- Always include documentation about event data payload + +### FreeRTOS + +- Task names should be descriptive: `"DevicesTask"`, `"NetworkTask"` +- Use appropriate priorities (defined in task headers) +- Always check if queue/semaphore creation succeeded +- Use `portMAX_DELAY` for blocking operations unless timeout is critical +- Prefer queues for inter-task communication +- **Always use `pdMS_TO_TICKS()` for time conversions** instead of manual division by `portTICK_PERIOD_MS` + - For ticks-to-seconds conversion use `xTaskGetTickCount() / configTICK_RATE_HZ` + +**Time Conversion Examples:** +```cpp +// ✅ CORRECT: pdMS_TO_TICKS macro +vTaskDelay(pdMS_TO_TICKS(100)); +xSemaphoreTake(Mutex, pdMS_TO_TICKS(500)); +xQueueReceive(Queue, &Data, pdMS_TO_TICKS(200)); + +// ✅ CORRECT: Ticks to seconds +uint32_t UptimeSeconds = xTaskGetTickCount() / configTICK_RATE_HZ; + +// ❌ INCORRECT: Manual division by portTICK_PERIOD_MS +vTaskDelay(100 / portTICK_PERIOD_MS); +xSemaphoreTake(Mutex, 500 / portTICK_PERIOD_MS); +uint32_t Seconds = xTaskGetTickCount() * portTICK_PERIOD_MS / 1000; +``` + +--- + +## Module Design Patterns + +### Manager Pattern + +Managers are stateful modules that provide a cohesive API for a subsystem: + +```cpp +// Public API pattern +esp_err_t ModuleName_Init(void); +esp_err_t ModuleName_Deinit(void); +esp_err_t ModuleName_GetConfig(Module_Config_t *p_Config); +esp_err_t ModuleName_UpdateConfig(Module_Config_t *p_Config); +esp_err_t ModuleName_Save(void); + +// Internal state (static in .cpp) +static Module_State_t _State; +``` + +### Thread-Safe Access Pattern + +For shared resources: + +```cpp +typedef struct { + SemaphoreHandle_t Mutex; + // ... data fields +} Module_State_t; + +esp_err_t Module_GetData(Data_t *p_Data) +{ + if (p_Data == NULL) { + return ESP_ERR_INVALID_ARG; + } + + xSemaphoreTake(_State.Mutex, portMAX_DELAY); + memcpy(p_Data, &_State.Data, sizeof(Data_t)); + xSemaphoreGive(_State.Mutex); + + return ESP_OK; +} +``` + +### Event-Driven Updates + +When updating module state: +1. Validate parameters +2. Acquire mutex +3. Update state +4. Release mutex +5. Post event to notify listeners + +```cpp +esp_err_t Module_Update(Config_t *p_Config) +{ + if (_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } + + xSemaphoreTake(_State.Mutex, portMAX_DELAY); + memcpy(&_State.Config, p_Config, sizeof(Config_t)); + xSemaphoreGive(_State.Mutex); + + esp_event_post(MODULE_EVENTS, MODULE_EVENT_CONFIG_CHANGED, + p_Config, sizeof(Config_t), portMAX_DELAY); + + return ESP_OK; +} +``` + +--- + +## Settings Management + +### Settings Structure + +- Settings are organized into categories (WiFi, Display, System, etc.) +- Each category has a dedicated struct type: `App_Settings_CategoryName_t` +- Use `__attribute__((packed))` for settings structures stored in NVS +- Include reserved fields for future expansion + +### Settings API Pattern + +Each settings category follows this pattern: + +```cpp +esp_err_t SettingsManager_GetCategory(App_Settings_Category_t* p_Settings); +esp_err_t SettingsManager_UpdateCategory(App_Settings_Category_t* p_Settings); +``` + +**Important**: `Update` functions modify RAM only. Call `SettingsManager_Save()` to persist changes! + +### Factory Defaults + +Two-tier default system: +1. **JSON Config** (`data/default_settings.json`) - Preferred, loaded on first boot +2. **Hardcoded Defaults** - Fallback in code + +--- + +## Network and Communication + +### WiFi Management + +- Support both STA (Station) and AP (Access Point) modes +- Implement retry logic with configurable max attempts +- Use ESP Event system for connection state changes +- Store credentials securely in NVS + +### HTTP/WebSocket Server + +- Maximum clients defined by `WS_MAX_CLIENTS` +- Implement proper client tracking and cleanup +- Use WebSocket for real-time data streaming +- Regular ping intervals to detect disconnections + +### VISA/SCPI Server + +- Standard port: 5025 +- Implement SCPI command parsing +- Return standard SCPI error codes +- Support concurrent clients (up to `VISA_MAX_CLIENTS`) + +--- + +## Device Integration + +### I2C/SPI Devices + +- Initialize buses in device manager +- Create device handles for each peripheral +- Implement proper error handling and recovery +- Use appropriate clock speeds and configurations + +### Lepton Camera + +- Interface through custom ESP32-Lepton component +- Handle frame buffers efficiently (DMA, PSRAM) +- Implement ROI (Region of Interest) calculations +- Support multiple ROI types (Spotmeter, Scene, AGC, Video Focus) + +--- + +## Build and Tooling + +### PlatformIO Configuration + +- Default environment: `debug` +- Board: `esp32-s3-devkitc-1` +- Flash size: 8MB +- PSRAM: OPI mode +- Filesystem: LittleFS + +### Pre/Post Build Scripts + +- `scripts/clean.py` - Clean build artifacts (pre-build) +- `scripts/format.py` - Format code with AStyle (post-build) + +### Formatting + +Run formatting manually: +```bash +astyle --options=scripts/astyle.cfg "main/**/*.cpp" "main/**/*.h" +``` + +--- + +## Documentation Maintenance + +### AsciiDoc Documentation in `docs/` + +The project maintains comprehensive AsciiDoc documentation in the `docs/` directory. **When modifying code, always update the corresponding documentation.** + +#### Documentation Structure + +``` +docs/ +├── index.adoc # Overview and getting started +├── SettingsManager.adoc # Settings management system +├── NetworkManager.adoc # Network and communication +├── DeviceManager.adoc # Device drivers and peripherals +├── TimeManager.adoc # Time management and RTC +├── SDManager.adoc # SD card management +├── LeptonTask.adoc # Lepton camera task +├── GUITask.adoc # GUI task and LVGL +├── NetworkTask.adoc # Network task +└── VISAServer.adoc # VISA/SCPI server +``` + +#### When to Update Documentation + +**Always update the corresponding `.adoc` file when:** +- Adding new public API functions +- Modifying function signatures or parameters +- Changing behavior or semantics of existing functions +- Adding new modules or managers +- Modifying settings structure or event types +- Changing initialization requirements or dependencies +- Adding new features or capabilities +- Fixing bugs that affect documented behavior + +#### Documentation Update Pattern + +**MANDATORY WORKFLOW**: When making ANY code changes that affect public APIs or module behavior, you MUST update the documentation simultaneously. + +1. **Identify Affected Documentation**: Use the Module-to-Documentation Mapping table above to find the corresponding `.adoc` file(s) +2. **Update Code First**: Make your code changes in the source files +3. **Update Documentation Immediately**: In the SAME session/commit, update the `.adoc` file(s): + - Update function signatures if changed + - Update parameter descriptions + - Update return value documentation + - Update code examples to match new behavior + - Add new sections for new features + - Update diagrams if architecture changed +4. **Verify Consistency**: Ensure documentation exactly matches the code +5. **Test Examples**: Verify that code examples in documentation still compile and work + +**CRITICAL**: Do NOT defer documentation updates to a later time. Documentation MUST be updated in the same editing session as the code change. + +#### Automatic Documentation Update Rules + +When you modify code, you MUST automatically update the corresponding AsciiDoc documentation according to these rules: + +**1. Function Signature Changes:** +```cpp +// If you change this in the header: +esp_err_t MyModule_DoSomething(uint8_t *p_Data, size_t Size, bool NewParam); +``` + +**You MUST update the corresponding `.adoc` file:** +```asciidoc +=== MyModule_DoSomething() + +[source,c] +---- +esp_err_t MyModule_DoSomething(uint8_t *p_Data, size_t Size, bool NewParam); +---- + +**Parameters:** + +* `p_Data` - Pointer to data buffer (must not be NULL) +* `Size` - Size of data buffer in bytes +* `NewParam` - [ADD DESCRIPTION OF NEW PARAMETER] + +[Rest of documentation...] +---- +``` + +**2. Adding New Functions:** + +When adding a new public API function, you MUST add a complete documentation section to the appropriate `.adoc` file: + +```asciidoc +=== NewModule_NewFunction() + +[source,c] +---- +esp_err_t NewModule_NewFunction(void); +---- + +[Brief description of what the function does] + +**Return Values:** + +* `ESP_OK` - Success +* [List all possible return values] + +**Thread Safety:** [Describe thread-safety characteristics] + +**Example:** +[source,c] +---- +[Provide working code example] +---- +``` + +**3. Behavior Changes:** + +If you change how a function behaves (even without changing the signature), update the description and notes in the `.adoc` file. + +**4. New Events or Types:** + +When adding new event types, data structures, or enums, document them in the appropriate sections of the `.adoc` file. + +#### Documentation Update Checklist + +After every code change that affects public APIs, verify: + +``` +☐ Identified corresponding .adoc file(s) using Module-to-Documentation Mapping +☐ Updated function signatures in documentation to match code +☐ Updated or added parameter descriptions +☐ Updated or added return value documentation +☐ Updated code examples to reflect changes +☐ Added new sections for new functions/features +☐ Updated architecture diagrams if structure changed +☐ Verified consistency between code and documentation +☐ Checked that examples compile and run correctly +``` + +#### Common Documentation Scenarios + +**Scenario 1: Adding a new parameter to an existing function** + +1. Update header file with new parameter + Doxygen documentation +2. Open corresponding `.adoc` file +3. Find the function's documentation section +4. Update the function signature in the `[source,c]` block +5. Add the new parameter to the "Parameters" list +6. Update any example code to include the new parameter + +**Scenario 2: Adding a completely new module** + +1. Create the new module source files +2. Create a new `.adoc` file in `docs/` (e.g., `docs/NewModule.adoc`) +3. Use existing module documentation as a template (copy structure from `SettingsManager.adoc` or similar) +4. Document all public APIs, data structures, and usage examples +5. Add a link to the new documentation in `docs/index.adoc` +6. Update the Module-to-Documentation Mapping in this file + +**Scenario 3: Changing function behavior without signature change** + +1. Modify the function implementation +2. Open corresponding `.adoc` file +3. Update the function's description to reflect new behavior +4. Update notes, warnings, or examples as needed +5. Add version information if significant change ("*Changed in v1.1.0:* ...") + +**Scenario 4: Removing or deprecating a function** + +1. Mark function as deprecated in header (if deprecating) or remove (if deleting) +2. Update `.adoc` file: + - If deprecating: Add a "**DEPRECATED**" notice and suggest alternative + - If removing: Delete the function's documentation section entirely +3. Update examples that used the removed function + +#### Module-to-Documentation Mapping + +| Code Location | Documentation File | +|--------------|-------------------| +| `Manager/Settings/` | `SettingsManager.adoc` | +| `Manager/Network/` | `NetworkManager.adoc` | +| `Manager/Network/Server/HTTP/` | `HTTPServer.adoc` | +| `Manager/Network/Server/VISA/` | `VISAServer.adoc` | +| `Manager/Network/Server/WebSocket/` | `HTTPServer.adoc` (WebSocket section) | +| `Manager/Devices/` | `DeviceManager.adoc` | +| `Manager/Time/` | `TimeManager.adoc` | +| `Manager/Memory/` | `MemoryManager.adoc` | +| `Manager/USB/` | `USBManager.adoc` | +| `Tasks/Lepton/` | `LeptonTask.adoc` | +| `Tasks/GUI/` | `GUITask.adoc` | +| `Tasks/Network/` | `NetworkTask.adoc` | +| `Tasks/Devices/` | `DevicesTask.adoc` | +| `main.cpp` (main application) | `index.adoc` (overview section) | +| Any file with `heap_caps_malloc` / `malloc` / task stack | `MemoryMap.adoc` | + +#### Documentation Style Guidelines + +- Use AsciiDoc syntax consistently +- Include code examples for new API functions +- Document all parameters, return values, and error codes +- Add diagrams for complex workflows (using PlantUML or similar) +- Keep examples realistic and tested +- Document thread-safety and RTOS considerations +- Include usage warnings and common pitfalls + +**Example Documentation Entry:** +```asciidoc +=== MyModule_DoSomething() + +[source,c] +---- +esp_err_t MyModule_DoSomething(uint8_t *p_Data, size_t Size); +---- + +Performs an important operation on the provided data buffer. + +**Parameters:** + +* `p_Data` - Pointer to data buffer (must not be NULL) +* `Size` - Size of data buffer in bytes + +**Return Values:** + +* `ESP_OK` - Operation successful +* `ESP_ERR_INVALID_ARG` - NULL pointer or invalid size +* `ESP_ERR_INVALID_STATE` - Module not initialized + +**Thread Safety:** This function is thread-safe and can be called from multiple tasks. + +**Example:** +[source,c] +---- +uint8_t Buffer[128]; +esp_err_t Error = MyModule_DoSomething(Buffer, sizeof(Buffer)); +if (Error != ESP_OK) { + ESP_LOGE(TAG, "Operation failed: %d", Error); +} +---- +``` + +#### Automated Documentation Build + +Documentation is automatically built and deployed via GitHub Actions workflow (`.github/workflows/documentation.yml`). The CI/CD pipeline: +- Builds all `.adoc` files to HTML and PDF +- Deploys to GitHub Pages: https://kampi.github.io/PyroVision/ +- Creates release artifacts with PDF documentation + +**Do not commit generated HTML/PDF files** - these are built automatically by the CI/CD pipeline. + +--- + +## Memory Management Documentation + +### Overview + +The file `docs/MemoryMap.adoc` is the **single source of truth** for all memory +allocations, task stacks, and DMA-relevant buffers in the firmware. +It must be kept in sync with the code at all times. + +### ESP32-S3 Memory Regions (Quick Reference) + +| Region | DMA-capable? | Notes | +|--------|-------------|-------| +| Internal SRAM (`0x3FC88000`–`0x3FD00000`) | **YES** | Use for SPI buffers, task stacks | +| PSRAM (`0x3C000000`+) | **NO** | Requires SPI bounce buffers for DMA | + +**SPI Bounce Buffer Rule:** Every PSRAM buffer used as SPI TX source causes the SPI +driver to allocate an internal DMA bounce buffer of `CONFIG_SPI_TRANSFER_SIZE` (= 4,096 bytes) +per queued transaction. With `trans_queue_depth=3` this is 12,288 bytes of internal DMA RAM. +Do **NOT** increase `trans_queue_depth` above 6 without recalculating DMA RAM usage. + +### When to Update `docs/MemoryMap.adoc` + +**MANDATORY**: Update `docs/MemoryMap.adoc` in the **same session** as the code change +whenever any of the following happens: + +- Adding a new `heap_caps_malloc()`, `malloc()`, or `calloc()` call +- Removing or resizing an existing heap allocation +- Adding a new FreeRTOS task (`xTaskCreate` / `xTaskCreatePinnedToCore`) +- Changing an existing task's stack size (`CONFIG_*_TASK_STACKSIZE`) +- Adding a new `xQueueCreate`, `xSemaphoreCreateMutex`, or `xEventGroupCreate` +- Adding buffers that indirectly use SPI DMA (i.e., PSRAM buffers passed to `spi_device_queue_trans`) + +### What to Document + +For every new allocation, add a row to the appropriate section of `docs/MemoryMap.adoc`: + +**Heap allocations:** +``` +| Buffer description + what it holds | Size in bytes (formula preferred) | PSRAM or Internal | file:line | +``` + +**Task stacks:** +``` +| Task name | Stack bytes | Priority | Core | file:line | +``` + +**Always:** +- State the **exact size formula** (e.g. `160 * 120 * 3 = 57,600 bytes`), not just a number +- State the **heap type**: PSRAM (`MALLOC_CAP_SPIRAM`), Internal (`MALLOC_CAP_INTERNAL`), + DMA-capable internal (both `MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL`) +- State whether the allocation is **permanent** (init-time) or **temporary** (freed after use) +- For temporary allocations, state the **maximum possible size** and **when it is freed** +- After adding the entry, **recalculate the section subtotal and the summary table** + +### Memory Documentation Checklist + +After every code change that adds or modifies memory usage, verify: + +``` +☐ New/modified allocation documented in docs/MemoryMap.adoc +☐ Section subtotal recalculated (PSRAM permanent / task stacks / etc.) +☐ Summary table updated (MemoryMap.adoc bottom section) +☐ SPI DMA headroom rechecked: trans_queue_depth × 4096 < (free DMA RAM after all inits) +☐ Conditional allocations (e.g. UVC, SD) clearly marked in documentation +``` + +### Automatic Memory Comment in Code + +Every `heap_caps_malloc` call **MUST** have an inline comment stating the allocation +size with its formula: + +```cpp +// ✅ CORRECT +// RGB888 frame buffer: 160 × 120 × 3 = 57,600 bytes (PSRAM) +_State.p_Buffer = static_cast(heap_caps_malloc(160 * 120 * 3, MALLOC_CAP_SPIRAM)); + +// ❌ INCORRECT — no size documentation +_State.p_Buffer = static_cast(heap_caps_malloc(BufferSize, MALLOC_CAP_SPIRAM)); +``` + +If the size comes from a variable/config, document the **maximum possible value**: + +```cpp +// JPEG output buffer: max = Width × Height × 3 = 160 × 120 × 3 = 57,600 bytes (PSRAM) +p_JpegBuffer = static_cast(heap_caps_malloc(JpegBufferSize, MALLOC_CAP_SPIRAM)); +``` + +--- + +## Code Quality and Validation + +### Mandatory Checks After Code Changes + +**CRITICAL**: After making ANY code changes, you MUST perform the following validation steps: + +#### 1. Syntax and Spelling Validation +- Verify code compiles without errors using `pio run` or `idf.py build` +- Check for correct bracket matching, semicolons, and C++ syntax +- Validate all include statements and dependencies +- Ensure no missing header files or forward declarations +- **Check for spelling errors** in: + - Variable names, function names, and type names + - Comments and documentation strings + - String literals and user-facing messages + - Log messages (TAG names, error messages) +- Use consistent spelling and terminology across the codebase + +#### 2. Error and Warning Analysis +- **Zero tolerance for compiler warnings** - all warnings must be addressed +- Run static analysis if available +- Check for: + - Unused variables + - Type mismatches or implicit conversions + - Potential null pointer dereferences + - Memory leaks in error paths + - Missing return statements + +#### 3. Error Handling Validation +- Verify ALL ESP-IDF function return codes are checked +- Ensure proper error propagation (don't silently ignore errors) +- Validate error cleanup paths (free resources on failure) +- Check mutex/semaphore release in all paths (including errors) + +#### 4. Documentation Synchronization +- **MANDATORY**: Update corresponding `.adoc` documentation file when changing any public API +- Use Module-to-Documentation Mapping table to identify which `.adoc` file to update +- Update function signatures if changed +- Update parameter descriptions if behavior changed +- Update return value documentation +- Verify code examples in documentation still compile +- Add new documentation sections for new functions +- Update architecture diagrams if module structure changed +- **Do NOT skip documentation updates** - they must be done in the same session as code changes + +**Example validation checklist for each change:** +``` +☐ Code compiles without errors (pio run -e debug) +☐ No compiler warnings introduced +☐ No spelling errors in code, comments, or documentation +☐ All new/modified functions have complete Doxygen documentation +☐ All return values and parameters documented +☐ Error handling implemented for all ESP-IDF calls +☐ Mutex/semaphore properly released in all code paths +☐ Related documentation (.adoc files) updated using Module-to-Documentation Mapping +☐ New functions have complete documentation sections in .adoc files +☐ Function signatures in .adoc files match code exactly +☐ Code examples in documentation updated and verified +☐ Code formatted with AStyle (scripts/format.py) +☐ Run static analysis if available (pio check) +``` + +### Automated Validation + +Use provided scripts for validation: + +```bash +# Format code +python scripts/format.py + +# Build and check for errors/warnings +pio run -e debug + +# Run static analysis (if configured) +pio check +``` + +--- + +## Testing and Debugging + +### Debugging + +- Use `ESP_LOGD` for debug output (disabled in release builds) +- Enable debug build type for verbose logging: `build_type = debug` +- Use ESP-IDF monitor with exception decoder: `monitor_filters = esp32_exception_decoder` + +### Error Reporting + +- Log errors with context: function name, error code, relevant parameters +- Include error strings when available +- Use descriptive error messages + +--- + +## Best Practices + +### Memory Management + +- Check malloc/calloc/queue/semaphore creation success +- Free resources in deinit functions +- Use PSRAM for large buffers (frame buffers, JSON parsing) +- Be mindful of stack sizes for tasks + +### Initialization Order + +1. Event loop +2. NVS Flash +3. Settings Manager (loads from NVS) +4. Device Manager (I2C, SPI, peripherals) +5. Time Manager (requires RTC from Device Manager) +6. Network Manager +7. Tasks (GUI, Lepton, Network, etc.) + +### Configuration + +- All configurable parameters should go through Settings Manager +- Avoid hardcoded values that users might want to change +- Provide sensible defaults +- Document valid ranges and constraints + +### Maintainability + +- Keep functions focused and small +- Extract complex logic into separate functions +- Use descriptive variable names +- Document assumptions and constraints +- Write self-documenting code where possible + +--- + +## Common Pitfalls to Avoid + +❌ **Don't:** +- Mix tabs and spaces +- Exceed 120 character line length +- Forget error checking on ESP-IDF calls +- Omit or provide incomplete function documentation +- Skip syntax and error validation after code changes +- Ignore compiler warnings (treat warnings as errors) +- Use blocking operations in ISRs +- Forget to call `SettingsManager_Save()` after updates +- Access shared state without mutex protection +- Use `portTICK_PERIOD_MS` for time conversions (use `pdMS_TO_TICKS()` instead) + +✅ **Do:** +- Use consistent naming conventions +- **Document ALL public functions with complete Doxygen comments** +- Include license headers in all files +- **Validate syntax, check for errors and warnings after every code change** +- Test error paths and edge cases +- Use appropriate log levels +- Clean up resources on failure +- Follow the established module patterns +- **Update corresponding AsciiDoc documentation when changing code** +- Address all compiler warnings before committing +- **Use `pdMS_TO_TICKS()` for all millisecond-to-tick conversions** + +--- + +## Additional Resources + +- **ESP-IDF Documentation**: https://docs.espressif.com/projects/esp-idf/ +- **FreeRTOS Documentation**: https://www.freertos.org/ +- **LVGL Documentation**: https://docs.lvgl.io/ +- **Project Repository**: (Add if applicable) + +--- + +**Last Updated**: February 26, 2026 +**Maintainer**: Daniel Kampert (DanielKampert@kampis-elektroecke.de) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..f6115ef --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,231 @@ +name: Documentation + +on: + push: + branches: + - main + - master + paths: + - docs/** + - .github/workflows/documentation.yml + pull_request: + paths: + - docs/** + - .github/workflows/documentation.yml + workflow_dispatch: + +permissions: + contents: write + pages: write + +jobs: + + asciidoctor: + runs-on: ubuntu-latest + name: Documentation (AsciiDoc) + + steps: + + - name: Repository Checkout + uses: actions/checkout@v4 + + - name: Install AsciiDoctor + run: | + sudo apt-get update + sudo apt-get install -y asciidoctor ruby-asciidoctor-pdf + + - name: Build AsciiDoc Documentation + run: | + cd docs + mkdir -p public/pdf + + # Find all .adoc files and process them + echo "Processing AsciiDoc files..." + for adoc_file in *.adoc; do + if [ -f "$adoc_file" ]; then + base_name="${adoc_file%.adoc}" + echo " - Building $base_name..." + + # Generate HTML + asciidoctor "$adoc_file" -o "public/${base_name}.html" + + # Generate PDF + asciidoctor-pdf "$adoc_file" -o "public/pdf/${base_name}.pdf" + fi + done + + # Copy any markdown files as supplementary documentation + if ls *.md 1> /dev/null 2>&1; then + cp *.md public/ + fi + + # Create index page listing all documentation + cat > public/index.html << EOF + + + + + + PyroVision Firmware Documentation + + + +

📚 PyroVision Firmware Documentation

+

Complete documentation for the PyroVision ESP32-S3 thermal imaging camera firmware.

+ +
+

📖 Available Documentation

+ EOF + + # Add links for each documentation file + for html_file in public/*.html; do + if [ -f "$html_file" ]; then + html_name=$(basename "$html_file") + if [ "$html_name" != "index.html" ]; then + base_name="${html_name%.html}" + # Create a more readable title + title=$(echo "$base_name" | sed 's/_/ /g' | sed 's/\b\(.\)/\u\1/g') + + cat >> public/index.html << EOF +
+

$title

+ +
+ EOF + fi + fi + done + + cat >> public/index.html << EOF +
+ + + + + EOF + + - name: Upload Artifact - Documentation + uses: actions/upload-artifact@v4 + with: + name: PyroVision-Documentation + path: docs/public + + + deploy: + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || contains(github.ref, 'refs/tags/')) + needs: + - asciidoctor + runs-on: ubuntu-latest + name: Deploy to Releases and Pages + + steps: + + - name: Repository Checkout + uses: actions/checkout@v4 + + - name: Download Artifacts + uses: actions/download-artifact@v4 + + - name: Organize public subdir and create a tarball + run: | + mv PyroVision-Documentation public + mv public/pdf ./ + tar zvcf PyroVision-Docs-nightly.tar.gz -C public . + # Rename all PDFs with nightly suffix + cd pdf + for pdf_file in *.pdf; do + if [ -f "$pdf_file" ]; then + base_name="${pdf_file%.pdf}" + mv "$pdf_file" "${base_name}-nightly.pdf" + fi + done + + # Tagged: create a pre-release or a release (semver) + # Untagged: update the assets of pre-release nightly + - name: Deploy to GitHub-Releases + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + # Create nightly release if it doesnt exist + gh release view nightly 2>/dev/null || gh release create nightly --title "Nightly Build" --notes "Automatically generated documentation" --prerelease + # Upload tarball and all PDFs + gh release upload nightly PyroVision-Docs-nightly.tar.gz pdf/*.pdf --clobber + + - name: Deploy to GitHub-Pages + run: | + cd public + git init + # use authenticated remote (GITHUB_TOKEN is available as a secret) + git remote add origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" + touch .nojekyll + git add . + git config --local user.email "push@gha" + git config --local user.name "GHA" + git commit -am "update documentation ${{ github.sha }}" + git push -u origin +HEAD:gh-pages --force diff --git a/.github/workflows/firmware.yml b/.github/workflows/firmware.yml new file mode 100644 index 0000000..ea7486d --- /dev/null +++ b/.github/workflows/firmware.yml @@ -0,0 +1,146 @@ +name: Firmware + +on: [push] + +jobs: + release: + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + ref: ${{ github.ref }} + + - name: ZIP artifact + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y zip + zip -r ${GITHUB_REF_NAME}.zip . + -x "*.git*" \ + -x "*node_modules*" \ + -x "*.pio*" \ + -x "*__pycache__*" \ + -x "*.vscode*" \ + -x "*build*" \ + -x "*.platformio*" + + - name: Release + uses: docker://antonyurchenko/git-release:v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CHANGELOG_FILE: CHANGELOG.md + with: + args: | + ${GITHUB_REF_NAME}.zip + + build: + runs-on: ubuntu-22.04 + + strategy: + matrix: + build_type: [debug, release] + + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + + - uses: actions/cache@v4 + with: + path: | + ~/.platformio + key: ${{ runner.os }}-pio + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y astyle + + - name: Install PlatformIO Core + run: | + pip install --upgrade platformio + + - name: Format source code + run: | + pio run -t format + + - name: Get release version + run: | + # Try to extract version from commit message (format: Major.Minor.Revision-Dev or Major.Minor-Dev) + COMMIT_MSG=$(git log -1 --pretty=%B) + VERSION=$(echo "$COMMIT_MSG" | grep -oP '(?<=^|\s)([0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9]+)?)(?=\s|$)' | head -n1) + + # If not found in commit message, try branch name + if [ -z "$VERSION" ]; then + BRANCH_NAME="${GITHUB_REF_NAME}" + echo "No version in commit message, trying branch name: ${BRANCH_NAME}" + VERSION=$(echo "$BRANCH_NAME" | grep -oP '([0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9]+)?)' | head -n1) + fi + + # If still not found, use git describe as fallback + if [ -z "$VERSION" ]; then + echo "No version in branch name, using git describe" + VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "0.0.0-$(git rev-parse --short HEAD)") + fi + + echo "Detected version: ${VERSION}" + + # Parse version components (handle both Major.Minor and Major.Minor.Revision) + if [[ "$VERSION" =~ ^([0-9]+)\.([0-9]+)(\.([0-9]+))?(-(.+))? ]]; then + MAJOR="${BASH_REMATCH[1]}" + MINOR="${BASH_REMATCH[2]}" + BUILD="${BASH_REMATCH[4]:-0}" # Default to 0 if revision not specified + SUFFIX="${BASH_REMATCH[6]}" + + # Reconstruct version string + if [ -n "$SUFFIX" ]; then + BASE_VERSION="${MAJOR}.${MINOR}.${BUILD}-${SUFFIX}" + else + BASE_VERSION="${MAJOR}.${MINOR}.${BUILD}" + fi + else + echo "Warning: Could not parse version, using defaults" + MAJOR="0" + MINOR="0" + BUILD="0" + BASE_VERSION="0.0.0-unknown" + fi + + echo "MAJOR=${MAJOR}" >> ${GITHUB_ENV} + echo "MINOR=${MINOR}" >> ${GITHUB_ENV} + echo "BUILD=${BUILD}" >> ${GITHUB_ENV} + echo "VERSION=${BASE_VERSION}" >> ${GITHUB_ENV} + + - name: Update version numbers + shell: bash + run: | + echo "Using version: ${{ env.VERSION }}" + sed -i -E "s/(PYROVISION_VERSION_MAJOR=)[0-9]+/\1${{ env.MAJOR }}/" CMakeLists.txt + sed -i -E "s/(PYROVISION_VERSION_MINOR=)[0-9]+/\1${{ env.MINOR }}/" CMakeLists.txt + sed -i -E "s/(PYROVISION_VERSION_BUILD=)[0-9]+/\1${{ env.BUILD }}/" CMakeLists.txt + sed -i "s/^## \[Unreleased\]/## [${{ env.VERSION }}] - $(date +'%Y-%m-%d')/" CHANGELOG.md + + - name: Build PlatformIO Project + run: | + pio run -e ${{ matrix.build_type }} + + - name: Prepare upload + run: | + mkdir -p ${{ matrix.build_type }} + cp .pio/build/${{ matrix.build_type }}/bootloader.bin ${{ matrix.build_type }}/bootloader.bin + cp .pio/build/${{ matrix.build_type }}/partitions.bin ${{ matrix.build_type }}/partitions.bin + cp .pio/build/${{ matrix.build_type }}/ota_data_initial.bin ${{ matrix.build_type }}/ota_data_initial.bin + cp .pio/build/${{ matrix.build_type }}/firmware.bin ${{ matrix.build_type }}/firmware.bin + cp .pio/build/${{ matrix.build_type }}/firmware.elf ${{ matrix.build_type }}/firmware.elf + + - name: Upload Firmware + uses: actions/upload-artifact@v4.3.3 + with: + name: ${{ matrix.build_type }} + path: | + ${{ matrix.build_type }} + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index d379b21..bdf53fc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ .vscode/ipch *.old *.lock - managed_components/ */backup/ -*/Font-Awesome/* \ No newline at end of file +*/Font-Awesome/* +data/settings.json \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b38dadc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "ui/assets/Font-Awesome"] + path = ui/assets/Font-Awesome + url = https://github.com/FortAwesome/Font-Awesome.git +[submodule "components/ESP32-Lepton"] + path = components/ESP32-Lepton + url = https://github.com/Kampi/ESP32-Lepton.git diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c08ba23 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# CHANGELOG + +## [1.0.0] - + +**Added:** + +**Changed:** + +**Removed:** diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ecfa115 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.16.0) + +# Get version from git describe +execute_process( + COMMAND git describe --always --tags --dirty + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE PROJECT_VER + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET +) + +# Fallback if git not available +if(NOT PROJECT_VER) + set(PROJECT_VER "X.Y.Z-dev") +endif() + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +project(PyroVision) diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3ee333 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# PyroVision - Firmware + +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg?logo=opensourceinitiative)](https://www.gnu.org/licenses/gpl-3.0) +[![ESP-IDF](https://img.shields.io/badge/ESP--IDF-v5.1+-blue.svg)](https://github.com/espressif/esp-idf) +[![Documentation](https://img.shields.io/badge/User%20Guide-PDF-007ec6?longCache=true&style=flat&logo=asciidoctor&colorA=555555)](https://github.com/PyroVision-ThermalCam/Firmware) + +## Table of Contents + +- [PyroVision - Firmware](#pyrovision---firmware) + - [Table of Contents](#table-of-contents) + - [License](#license) + - [Color palette](#color-palette) + - [VISA and HTTP / WebSocket interface](#visa-and-http--websocket-interface) + - [Maintainer](#maintainer) + +## License + +This project is licensed under the **GNU General Public License v3.0**. + +See [LICENSE](LICENSE) for full text. + +## Color palette + +| Name | Hex | Description | +| ------------------ | ------- | ---------------------------- | +| Violet | #7B3FF0 | Primary color, iconic accents | +| Blue | #2196F3 | Cool heatmap colors | +| Red | #FF3B3B | Heat/hotspots | +| Orange | #FF9500 | Medium heat | +| Yellow | #FFD500 | Maximum hot areas | +| Light Purple | #B998FF | Accent color | +| Light Red/Orange | #FF6F3C | Accent color | +| Dark Gray/Black | #1E1E1E | Background color | + +## VISA and HTTP / WebSocket interface + +The camera can use a VISA and / or HTTP / Websocket interface for communication. Please read the [documentation](Remote-Control.md) for additional informations about it. + +## Maintainer + +**Daniel Kampert** +📧 [DanielKampert@kampis-elektroecke.de](mailto:DanielKampert@kampis-elektroecke.de) +🌐 [www.kampis-elektroecke.de](https://www.kampis-elektroecke.de) + +--- + +**Contributions Welcome!** Please open issues or pull requests on GitHub. diff --git a/Remote-Control.md b/Remote-Control.md new file mode 100644 index 0000000..99b5d03 --- /dev/null +++ b/Remote-Control.md @@ -0,0 +1,174 @@ +# Lepton - Remote-Control + +## Table of Contents + +- [Lepton - Remote-Control](#lepton---remote-control) + - [Table of Contents](#table-of-contents) + - [Available parameters](#available-parameters) + - [VISA SCPI Commands](#visa-scpi-commands) + - [System Commands](#system-commands) + - [Sensor Commands](#sensor-commands) + - [Display Commands](#display-commands) + - [Memory Commands](#memory-commands) + - [WebSocket Commands](#websocket-commands) + - [Request Format](#request-format) + - [Response Format](#response-format) + - [Available Commands](#available-commands) + - [Example WebSocket Session](#example-websocket-session) + - [Maintainer](#maintainer) + +## Available parameters + +| Parameter | Description | Methods | +|-------------------------|------------------------------------------|----------| +| Temperature Sensor Value| Temperature from the TMP117 sensor | GET | +| Time | Current system time | GET, SET | +| Battery Voltage | Voltage of the battery | GET | +| State-Of-Charge | Battery state of charge | GET | +| OV5640 Image | Image from OV5640 camera | GET | +| Lepton Image | Image from Lepton camera | GET | +| Lepton Emissivity | Emissivity of Lepton camera | GET, SET | +| Lepton Scene Statistics | Scene statistics from Lepton | GET | +| Lepton ROI | Region of Interest for Lepton | GET, SET | +| Lepton Spotmeter | Spotmeter data from Lepton | GET | +| Flash Power | Power of the flash | GET, SET | +| Flash On / Off | Flash state | GET, SET | +| Image Format | File format of the output images (PNG, Raw, JPEG) | GET, SET | +| Status LED | Set the Status LED (Red, Green, Blue) | SET | +| State SD card | Checks if an SD card is available | GET | +| Format | Format the active memory (internal flash or SD card) | SET | +| Messagebox Display | Display a message box with a given text | SET | +| Lock | Lock the input buttons and the joystick | GET, SET | + +### VISA SCPI Commands + +The VISA server (port 5025) supports the following SCPI commands: + +#### System Commands + +| Command | Description | Example | +|-------------------|----------------------------------|----------------------------------| +| `*IDN?` | Get device identification | `*IDN?` | +| `*RST` | Reset device | `*RST` | +| `SYST:TIME?` | Get current system time | `SYST:TIME?` | +| `SYST:TIME` | Set system time (ISO 8601) | `SYST:TIME 2026-01-15T12:30:00` | +| `SYST:LOCK?` | Get input lock state | `SYST:LOCK?` | +| `SYST:LOCK` | Set input lock state | `SYST:LOCK LOCKED` | + +#### Sensor Commands + +| Command | Description | Example | +|--------------------------|----------------------------------|----------------------------------| +| `SENS:TEMP?` | Get temperature sensor value | `SENS:TEMP?` | +| `SENS:BATT:VOLT?` | Get battery voltage | `SENS:BATT:VOLT?` | +| `SENS:BATT:SOC?` | Get battery state of charge | `SENS:BATT:SOC?` | +| `SENS:IMG:FORM?` | Get image format | `SENS:IMG:FORM?` | +| `SENS:IMG:FORM` | Set image format | `SENS:IMG:FORM JPEG` | +| `SENS:IMG:LEP:EMIS?` | Get Lepton emissivity | `SENS:IMG:LEP:EMIS?` | +| `SENS:IMG:LEP:EMIS` | Set Lepton emissivity (0-100) | `SENS:IMG:LEP:EMIS 98` | +| `SENS:IMG:LEP:STAT?` | Get Lepton scene statistics | `SENS:IMG:LEP:STAT?` | +| `SENS:IMG:LEP:ROI?` | Get Lepton ROI | `SENS:IMG:LEP:ROI?` | +| `SENS:IMG:LEP:ROI` | Set Lepton ROI (JSON) | `SENS:IMG:LEP:ROI {"x":40,...}` | +| `SENS:IMG:LEP:SPOT?` | Get Lepton spotmeter data | `SENS:IMG:LEP:SPOT?` | + +#### Display Commands + +| Command | Description | Example | +|--------------------------|----------------------------------|----------------------------------| +| `DISP:LED:STAT` | Set status LED | `DISP:LED:STAT RED 255` | +| `DISP:FLASH:POW?` | Get flash power | `DISP:FLASH:POW?` | +| `DISP:FLASH:POW` | Set flash power (0-100) | `DISP:FLASH:POW 50` | +| `DISP:FLASH:STAT?` | Get flash state | `DISP:FLASH:STAT?` | +| `DISP:FLASH:STAT` | Set flash state | `DISP:FLASH:STAT ON` | +| `DISP:MBOX` | Display message box | `DISP:MBOX "Hello World"` | + +#### Memory Commands + +| Command | Description | Example | +|-------------------|----------------------------------|----------------------------------| +| `MEM:SD:STAT?` | Check SD card availability | `MEM:SD:STAT?` | +| `MEM:FORM` | Format active memory | `MEM:FORM` | + +### WebSocket Commands + +The WebSocket endpoint (`ws://device-ip/ws`) supports JSON-formatted commands: + +#### Request Format + +```json +{ + "cmd": "command_name", + "data": { + // command-specific data + } +} +``` + +#### Response Format + +```json +{ + "cmd": "command_name", + "status": "ok|error", + "data": { + // response data + }, + "error": "error message (if status=error)" +} +``` + +#### Available Commands + +| Command | Description | Request Data | +|----------------------------|----------------------------------|----------------------------------| +| `get_temperature` | Get temperature sensor value | - | +| `get_time` | Get current system time | - | +| `set_time` | Set system time | `{"time": "ISO8601"}` | +| `get_battery` | Get battery voltage and SOC | - | +| `get_lepton_emissivity` | Get Lepton emissivity | - | +| `set_lepton_emissivity` | Set Lepton emissivity | `{"emissivity": 98}` | +| `get_lepton_stats` | Get Lepton scene statistics | - | +| `get_lepton_roi` | Get Lepton ROI | - | +| `set_lepton_roi` | Set Lepton ROI | `{"x":40,"y":30,"width":80,...}` | +| `get_lepton_spotmeter` | Get Lepton spotmeter data | - | +| `get_flash` | Get flash state and power | - | +| `set_flash` | Set flash state/power | `{"enabled":true,"power":50}` | +| `get_image_format` | Get current image format | - | +| `set_image_format` | Set image format | `{"format":"JPEG"}` | +| `set_status_led` | Set status LED | `{"color":"RED","brightness":255}`| +| `get_sd_state` | Check SD card availability | - | +| `format_memory` | Format active memory | - | +| `display_message` | Display message box | `{"message":"Hello World"}` | +| `get_lock` | Get input lock state | - | +| `set_lock` | Set input lock state | `{"locked":true}` | + +#### Example WebSocket Session + +```javascript +// Connect to WebSocket +const ws = new WebSocket('ws://192.168.1.100/ws'); + +// Get temperature +ws.send(JSON.stringify({ + cmd: "get_temperature", + data: {} +})); + +// Response: +// {"cmd":"temperature","status":"ok","data":{"temperature":25.3}} + +// Set Lepton emissivity +ws.send(JSON.stringify({ + cmd: "set_lepton_emissivity", + data: {"emissivity": 98} +})); + +// Response: +// {"cmd":"set_lepton_emissivity","status":"ok"} +``` + +## Maintainer + +**Daniel Kampert** +📧 [DanielKampert@kampis-elektroecke.de](mailto:DanielKampert@kampis-elektroecke.de) +🌐 [www.kampis-elektroecke.de](https://www.kampis-elektroecke.de) \ No newline at end of file diff --git a/components/ESP32-Lepton b/components/ESP32-Lepton new file mode 160000 index 0000000..3d8993c --- /dev/null +++ b/components/ESP32-Lepton @@ -0,0 +1 @@ +Subproject commit 3d8993c9cfdf7db7a2f0498404af68459011694f diff --git a/components/esp_tinyusb/CHANGELOG.md b/components/esp_tinyusb/CHANGELOG.md new file mode 100644 index 0000000..d100aec --- /dev/null +++ b/components/esp_tinyusb/CHANGELOG.md @@ -0,0 +1,143 @@ +## 2.1.0 + +- Added configurable Suspend/Resume device event support using TinyUSB callbacks `tud_suspend_cb` and `tud_resume_cb` + +## 2.0.1~1 + +- esp_tinyusb: Claim forward compatibility with TinyUSB 0.19 +- CDC: Added support for new VFS API (for esp-idf v5.4 and higher) + +## 2.0.1 + +- esp_tinyusb: Added ESP32H4 support +- esp_tinyusb: Fixed an assertion failure on the GetOtherSpeedDescriptor() request for ESP32P4 when the OTG1.1 port is used +- MSC: Added dynamic member and storage operation multitask protection +- MSC: Used `esp_vfs_fat_register_cfg` function prototype for esp-idf v5.3 and higher + +## 2.0.0 + +- esp_tinyusb: Added USB Compliance Verification results +- CDC-ACM: Added a configurable parameter for the endpoint DMA buffer + +### Breaking changes + +- esp_tinyusb: External PHY is no longer initialized automatically. If an external PHY is required, it must be explicitly initialized by the user with configuration parameter `phy.skip_setup = true` +- esp_tinyusb: Added run-time configuration for peripheral port selection, task settings, and descriptors. For more details, refer to the [Espressif's Addition to TinyUSB Mirgation guide v2](../../docs/device/migration-guides/v2/tinyusb.md) +- esp_tinyusb: Added USB Device event callback to handle different USB Device events. For the list of supported USB Device events, refer to to [Espressif's Addition to TinyUSB - README](../esp_tinyusb/README.md) +- esp_tinyusb: Removed configuration option to handle TinyUSB events outside of this driver +- NCM: Added possibility to deinit the driver +- NCM: Updated public API; refer to the [NCM Class Migration guide v2](../../docs/device/migration-guides/v2/tinyusb_ncm.md) +- MSC: Removed dedicated callbacks; introduced a single callback with an event ID for each storage +- MSC: Added storage format support +- MSC: Added dual storage support (SPI/Flash and SD/MMC) +- MSC: Updated public API; refer to the [MSC Class Migration guide v2](../../docs/device/migration-guides/v2/tinyusb_msc.md) +- Console: Updated public API; refer to the [Console Class Migration guide v2](../../docs/device/migration-guides/v2/tinyusb_console.md) +- CDC-ACM: Updated public API; refer to the [CDC-ACM Class Migration guide v2](../../docs/device/migration-guides/v2/tinyusb_cdc_acm.md) + +## 1.7.6~1 + +- esp_tinyusb: Added documentation to README.md + +## 1.7.6 + +- MSC: Fixed the possibility to use SD/MMC storage with large capacity (more than 4 GB) + +## 1.7.5 + +- esp_tinyusb: Provide forward compatibility with IDF 6.0 + +## 1.7.4~1 + +- esp_tinyusb: Claim forward compatibility with IDF 6.0 + +## 1.7.4 + +- MSC: WL Sector runtime check during spiflash init (fix for build time error check) + +## 1.7.3 [yanked] + +- MSC: Improved transfer speed to SD cards and SPI flash + +## 1.7.2 + +- esp_tinyusb: Fixed crash on logging from ISR +- PHY: Fixed crash with external_phy=true configuration + +## 1.7.1 + +- NCM: Changed default NTB config to decrease DRAM memory usage (fix for DRAM overflow on ESP32S2) + +## 1.7.0 [yanked] + +- NCM: Added possibility to configure NCM Transfer Blocks (NTB) via menuconfig +- esp_tinyusb: Added option to select TinyUSB peripheral on esp32p4 via menuconfig (USB_PHY_SUPPORTS_P4_OTG11 in esp-idf is required) +- esp_tinyusb: Fixed uninstall tinyusb driver with not default task configuration + +## 1.6.0 + +- CDC-ACM: Fixed memory leak on deinit +- esp_tinyusb: Added Teardown + +## 1.5.0 + +- esp_tinyusb: Added DMA mode option to tinyusb DCD DWC2 configuration +- esp_tinyusb: Changed the default affinity mask of the task to CPU1 + +## 1.4.5 + +- CDC-ACM: Fixed memory leak at VFS unregister +- Vendor specific: Provided default configuration + +## 1.4.4 + +- esp_tinyusb: Added HighSpeed and Qualifier device descriptors in tinyusb configuration +- CDC-ACM: Removed MIN() definition if already defined +- MSC: Fixed EP size selecting in default configuration descriptor + +## 1.4.3 + +- esp_tinyusb: Added ESP32P4 support (HS only) + +## 1.4.2 + +- MSC: Fixed maximum files open +- Added uninstall function + +## 1.4.0 + +- MSC: Fixed integer overflows +- CDC-ACM: Removed intermediate RX ringbuffer +- CDC-ACM: Increased default FIFO size to 512 bytes +- CDC-ACM: Fixed Virtual File System binding + +## 1.3.0 + +- Added NCM extension + +## 1.2.1 - 1.2.2 + +- Minor bugfixes + +## 1.2.0 + +- Added MSC extension for accessing SPI Flash on memory card https://github.com/espressif/idf-extra-components/commit/a8c00d7707ba4ceeb0970c023d702c7768dba3dc + +## 1.1.0 + +- Added support for NCM, ECM/RNDIS, DFU and Bluetooth TinyUSB drivers https://github.com/espressif/idf-extra-components/commit/79f35c9b047b583080f93a63310e2ee7d82ef17b + +## 1.0.4 + +- Cleaned up string descriptors handling https://github.com/espressif/idf-extra-components/commit/046cc4b02f524d5c7e3e56480a473cfe844dc3d6 + +## 1.0.2 - 1.0.3 + +- Minor bugfixes + +## 1.0.1 + +- CDC-ACM: Return ESP_OK if there is nothing to flush https://github.com/espressif/idf-extra-components/commit/388ff32eb09aa572d98c54cb355f1912ce42707c + +## 1.0.0 + +- Initial version based on [esp-idf v4.4.3](https://github.com/espressif/esp-idf/tree/v4.4.3/components/tinyusb) diff --git a/components/esp_tinyusb/CMakeLists.txt b/components/esp_tinyusb/CMakeLists.txt new file mode 100644 index 0000000..f0c8b07 --- /dev/null +++ b/components/esp_tinyusb/CMakeLists.txt @@ -0,0 +1,69 @@ +set(srcs + "descriptors_control.c" + "tinyusb.c" + "usb_descriptors.c" + "tinyusb_task.c" + ) + +set(priv_req "") + +if(${IDF_VERSION_MAJOR} LESS 6) + list(APPEND priv_req "usb") +endif() + +if(CONFIG_TINYUSB_CDC_ENABLED) + list(APPEND srcs + "cdc.c" + "tinyusb_cdc_acm.c" + ) + if(CONFIG_VFS_SUPPORT_IO) + list(APPEND srcs + "tinyusb_console.c" + "vfs_tinyusb.c" + ) + endif() # CONFIG_VFS_SUPPORT_IO +endif() # CONFIG_TINYUSB_CDC_ENABLED + +if(CONFIG_TINYUSB_MSC_ENABLED) + list(APPEND srcs + "tinyusb_msc.c" + "storage_spiflash.c" + ) + if(CONFIG_SOC_SDMMC_HOST_SUPPORTED) + list(APPEND srcs + "storage_sdmmc.c" + ) + endif() # CONFIG_SOC_SDMMC_HOST_SUPPORTED +endif() # CONFIG_TINYUSB_MSC_ENABLED + + +if(CONFIG_TINYUSB_NET_MODE_NCM) + list(APPEND srcs + "tinyusb_net.c" + ) +endif() # CONFIG_TINYUSB_NET_MODE_NCM + +if(CONFIG_TINYUSB_UVC_ENABLED) + list(APPEND srcs + "tinyusb_uvc.c" + ) +endif() # CONFIG_TINYUSB_UVC_ENABLED + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "include_private" + PRIV_REQUIRES ${priv_req} + REQUIRES fatfs vfs esp_timer tinyusb + ) + +# Determine whether tinyusb is fetched from component registry or from local path +idf_build_get_property(build_components BUILD_COMPONENTS) +if(tinyusb IN_LIST build_components) + set(tinyusb_name tinyusb) # Local component +else() + set(tinyusb_name espressif__tinyusb) # Managed component +endif() + +# Pass tusb_config.h from this component to TinyUSB +idf_component_get_property(tusb_lib ${tinyusb_name} COMPONENT_LIB) +target_include_directories(${tusb_lib} PRIVATE "include") diff --git a/components/esp_tinyusb/Kconfig.projbuild b/components/esp_tinyusb/Kconfig.projbuild new file mode 100644 index 0000000..113cfc7 --- /dev/null +++ b/components/esp_tinyusb/Kconfig.projbuild @@ -0,0 +1,553 @@ +menu "TinyUSB Stack" + config TINYUSB_DEBUG_LEVEL + int "TinyUSB log level (0-3)" + default 1 + range 0 3 + help + Specify verbosity of TinyUSB log output. + + menu "TinyUSB DCD" + choice TINYUSB_MODE + prompt "DCD Mode" + default TINYUSB_MODE_DMA + help + TinyUSB DCD DWC2 Driver supports two modes: Slave mode (based on IRQ) and Buffer DMA mode. + + config TINYUSB_MODE_SLAVE + bool "Slave/IRQ" + config TINYUSB_MODE_DMA + bool "Buffer DMA" + endchoice + endmenu # "TinyUSB DCD" + + menu "TinyUSB callbacks" + config TINYUSB_SUSPEND_CALLBACK + bool "Register suspend callback" + default n + help + Register TinyUSB's suspend callback (tud_suspend_cb()) in esp_tinyusb. + + When enabled, esp_tinyusb provides a strong implementation of + tud_suspend_cb() and dispatches TINYUSB_EVENT_SUSPENDED via the + esp_tinyusb device event callback. + + When disabled, tinyusb provides weak implementation of the tud_suspend_cb(), + and user can provide it's own strong implementation of the tud_suspend_cb(). + + NOTE: When this option is enabled, user applications MUST NOT + define tud_suspend_cb() themselves. Defining tud_suspend_cb() + in the application while this option is enabled will result in + a linker error due to multiple definitions. + + config TINYUSB_RESUME_CALLBACK + bool "Register resume callback" + default n + help + Register TinyUSB's resume callback (tud_resume_cb()) in esp_tinyusb. + + When enabled, esp_tinyusb provides a strong implementation of + tud_resume_cb() and dispatches TINYUSB_EVENT_RESUMED via the + esp_tinyusb device event callback. + + When disabled, tinyusb provides weak implementation of the tud_resume_cb(), + and user can provide it's own strong implementation of the tud_resume_cb(). + + NOTE: When this option is enabled, user applications MUST NOT + define tud_resume_cb() themselves. Defining tud_resume_cb() + in the application while this option is enabled will result in + a linker error due to multiple definitions. + endmenu # "TinyUSB callbacks" + + menu "Descriptor configuration" + comment "You can provide your custom descriptors via tinyusb_driver_install()" + config TINYUSB_DESC_USE_ESPRESSIF_VID + bool "VID: Use Espressif's vendor ID" + default y + help + Enable this option, USB device will use Espressif's vendor ID as its VID. + This is helpful at product develop stage. + + config TINYUSB_DESC_CUSTOM_VID + hex "VID: Custom vendor ID" + default 0x1234 + depends on !TINYUSB_DESC_USE_ESPRESSIF_VID + help + Custom Vendor ID. + + config TINYUSB_DESC_USE_DEFAULT_PID + bool "PID: Use a default PID assigned to TinyUSB" + default y + help + Default TinyUSB PID assigning uses values 0x4000...0x4007. + + config TINYUSB_DESC_CUSTOM_PID + hex "PID: Custom product ID" + default 0x5678 + depends on !TINYUSB_DESC_USE_DEFAULT_PID + help + Custom Product ID. + + config TINYUSB_DESC_BCD_DEVICE + hex "bcdDevice" + default 0x0100 + help + Version of the firmware of the USB device. + + config TINYUSB_DESC_MANUFACTURER_STRING + string "Manufacturer name" + default "Espressif Systems" + help + Name of the manufacturer of the USB device. + + config TINYUSB_DESC_PRODUCT_STRING + string "Product name" + default "Espressif Device" + help + Name of the USB device. + + config TINYUSB_DESC_SERIAL_STRING + string "Serial string" + default "123456" + help + Serial number of the USB device. + + config TINYUSB_DESC_CDC_STRING + depends on TINYUSB_CDC_ENABLED + string "CDC Device String" + default "Espressif CDC Device" + help + Name of the CDC device. + + config TINYUSB_DESC_MSC_STRING + depends on TINYUSB_MSC_ENABLED + string "MSC Device String" + default "Espressif MSC Device" + help + Name of the MSC device. + endmenu # "Descriptor configuration" + + menu "Mass Storage Class (MSC)" + config TINYUSB_MSC_ENABLED + bool "Enable TinyUSB MSC feature" + default n + help + Enable TinyUSB MSC feature. + + config TINYUSB_MSC_BUFSIZE + depends on TINYUSB_MSC_ENABLED + int "MSC FIFO size" + default 512 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32H4 + default 8192 if IDF_TARGET_ESP32P4 + range 64 8192 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32H4 + range 64 32768 if IDF_TARGET_ESP32P4 + help + MSC FIFO size, in bytes. + + config TINYUSB_MSC_MOUNT_PATH + depends on TINYUSB_MSC_ENABLED + string "Mount Path" + default "/data" + help + MSC Mount Path of storage. + endmenu # "Mass Storage Class" + + menu "Communication Device Class (CDC)" + config TINYUSB_CDC_ENABLED + bool "Enable TinyUSB CDC feature" + default n + help + Enable TinyUSB CDC feature. + + config TINYUSB_CDC_COUNT + int "CDC Channel Count" + default 1 + range 1 2 + depends on TINYUSB_CDC_ENABLED + help + Number of independent serial ports. + + config TINYUSB_CDC_RX_BUFSIZE + depends on TINYUSB_CDC_ENABLED + int "CDC FIFO size of RX channel" + default 512 + range 64 10000 + help + This buffer size defines maximum data length in bytes that you can receive at once. + Must be greater or equal to TINYUSB_CDC_EP_BUFSIZE for correct receiving. + + config TINYUSB_CDC_TX_BUFSIZE + depends on TINYUSB_CDC_ENABLED + int "CDC FIFO size of TX channel" + default 512 + help + This buffer size defines maximum data length in bytes that you can transmit at once. + + config TINYUSB_CDC_EP_BUFSIZE + depends on TINYUSB_CDC_ENABLED + int "CDC Endpoint buffer size" + default 512 + help + This low layer buffer has the most significant impact on performance. Set to 8192 for best performance. + Sizes above 8192 bytes bring only little performance improvement. + endmenu # "Communication Device Class" + + menu "Musical Instrument Digital Interface (MIDI)" + config TINYUSB_MIDI_COUNT + int "TinyUSB MIDI interfaces count" + default 0 + range 0 2 + help + Setting value greater than 0 will enable TinyUSB MIDI feature. + endmenu # "Musical Instrument Digital Interface (MIDI)" + + menu "Human Interface Device Class (HID)" + config TINYUSB_HID_COUNT + int "TinyUSB HID interfaces count" + default 0 + range 0 4 + help + Setting value greater than 0 will enable TinyUSB HID feature. + endmenu # "HID Device Class (HID)" + + menu "Device Firmware Upgrade (DFU)" + choice TINYUSB_DFU_MODE + prompt "DFU mode" + default TINYUSB_DFU_MODE_NONE + help + Select which DFU driver you want to use. + + config TINYUSB_DFU_MODE_DFU + bool "DFU" + + config TINYUSB_DFU_MODE_DFU_RUNTIME + bool "DFU Runtime" + + config TINYUSB_DFU_MODE_NONE + bool "None" + endchoice + config TINYUSB_DFU_BUFSIZE + depends on TINYUSB_DFU_MODE_DFU + int "DFU XFER BUFFSIZE" + default 512 + help + DFU XFER BUFFSIZE. + endmenu # Device Firmware Upgrade (DFU) + + menu "Bluetooth Host Class (BTH)" + config TINYUSB_BTH_ENABLED + bool "Enable TinyUSB BTH feature" + default n + help + Enable TinyUSB BTH feature. + + config TINYUSB_BTH_ISO_ALT_COUNT + depends on TINYUSB_BTH_ENABLED + int "BTH ISO ALT COUNT" + default 0 + help + BTH ISO ALT COUNT. + endmenu # "Bluetooth Host Device Class" + + menu "USB Video Class (UVC)" + config TINYUSB_UVC_ENABLED + bool "Enable TinyUSB UVC feature" + default n + help + Enable TinyUSB UVC (USB Video Class) feature for webcam devices. + + config TINYUSB_DESC_UVC_STRING + depends on TINYUSB_UVC_ENABLED + string "UVC Device String" + default "Espressif UVC Camera" + help + Name of the UVC device. + + config TINYUSB_UVC_SUPPORT_TWO_CAM + depends on TINYUSB_UVC_ENABLED + bool "Support two cameras" + default n + help + Enable support for two independent UVC camera interfaces. + + config TINYUSB_UVC_CAM1_FRAMERATE + depends on TINYUSB_UVC_ENABLED + int "Camera 1 Frame Rate (fps)" + default 15 + range 1 60 + help + Frame rate for camera 1 in frames per second. + + config TINYUSB_UVC_CAM1_FRAMESIZE_WIDTH + depends on TINYUSB_UVC_ENABLED + int "Camera 1 Frame Width" + default 640 + help + Frame width for camera 1 in pixels. + + config TINYUSB_UVC_CAM1_FRAMESIZE_HEIGHT + depends on TINYUSB_UVC_ENABLED + int "Camera 1 Frame Height" + default 480 + help + Frame height for camera 1 in pixels. + + choice TINYUSB_UVC_CAM1_FORMAT + depends on TINYUSB_UVC_ENABLED + prompt "Camera 1 Video Format" + default TINYUSB_UVC_FORMAT_MJPEG_CAM1 + help + Select video format for camera 1. + + config TINYUSB_UVC_FORMAT_MJPEG_CAM1 + bool "MJPEG" + config TINYUSB_UVC_FORMAT_H264_CAM1 + bool "H264" + endchoice + + config TINYUSB_UVC_CAM1_MULTI_FRAMESIZE + depends on TINYUSB_UVC_ENABLED + bool "Enable multiple frame sizes for Camera 1" + default n + help + Enable support for multiple frame sizes (VGA, HVGA, etc.). + + config TINYUSB_UVC_CAM1_BULK_MODE + depends on TINYUSB_UVC_ENABLED + bool "Use Bulk mode for Camera 1" + default n + help + Use Bulk transfer mode instead of Isochronous for Camera 1. + + config TINYUSB_UVC_CAM2_FRAMERATE + depends on TINYUSB_UVC_ENABLED && TINYUSB_UVC_SUPPORT_TWO_CAM + int "Camera 2 Frame Rate (fps)" + default 15 + range 1 60 + help + Frame rate for camera 2 in frames per second. + + config TINYUSB_UVC_CAM2_FRAMESIZE_WIDTH + depends on TINYUSB_UVC_ENABLED && TINYUSB_UVC_SUPPORT_TWO_CAM + int "Camera 2 Frame Width" + default 640 + help + Frame width for camera 2 in pixels. + + config TINYUSB_UVC_CAM2_FRAMESIZE_HEIGHT + depends on TINYUSB_UVC_ENABLED && TINYUSB_UVC_SUPPORT_TWO_CAM + int "Camera 2 Frame Height" + default 480 + help + Frame height for camera 2 in pixels. + + choice TINYUSB_UVC_CAM2_FORMAT + depends on TINYUSB_UVC_ENABLED && TINYUSB_UVC_SUPPORT_TWO_CAM + prompt "Camera 2 Video Format" + default TINYUSB_UVC_FORMAT_MJPEG_CAM2 + help + Select video format for camera 2. + + config TINYUSB_UVC_FORMAT_MJPEG_CAM2 + bool "MJPEG" + config TINYUSB_UVC_FORMAT_H264_CAM2 + bool "H264" + endchoice + + config TINYUSB_UVC_CAM2_MULTI_FRAMESIZE + depends on TINYUSB_UVC_ENABLED && TINYUSB_UVC_SUPPORT_TWO_CAM + bool "Enable multiple frame sizes for Camera 2" + default n + help + Enable support for multiple frame sizes (VGA, HVGA, etc.). + + config TINYUSB_UVC_CAM2_BULK_MODE + depends on TINYUSB_UVC_ENABLED && TINYUSB_UVC_SUPPORT_TWO_CAM + bool "Use Bulk mode for Camera 2" + default n + help + Use Bulk transfer mode instead of Isochronous for Camera 2. + + config TINYUSB_UVC_MULTI_FRAME_WIDTH_1 + depends on TINYUSB_UVC_ENABLED + int "Multi-frame Width 1" + default 640 + help + Additional frame size width 1. + + config TINYUSB_UVC_MULTI_FRAME_HEIGHT_1 + depends on TINYUSB_UVC_ENABLED + int "Multi-frame Height 1" + default 480 + help + Additional frame size height 1. + + config TINYUSB_UVC_MULTI_FRAME_FPS_1 + depends on TINYUSB_UVC_ENABLED + int "Multi-frame FPS 1" + default 15 + help + Additional frame rate 1. + + config TINYUSB_UVC_MULTI_FRAME_WIDTH_2 + depends on TINYUSB_UVC_ENABLED + int "Multi-frame Width 2" + default 320 + help + Additional frame size width 2. + + config TINYUSB_UVC_MULTI_FRAME_HEIGHT_2 + depends on TINYUSB_UVC_ENABLED + int "Multi-frame Height 2" + default 240 + help + Additional frame size height 2. + + config TINYUSB_UVC_MULTI_FRAME_FPS_2 + depends on TINYUSB_UVC_ENABLED + int "Multi-frame FPS 2" + default 15 + help + Additional frame rate 2. + + config TINYUSB_UVC_MULTI_FRAME_WIDTH_3 + depends on TINYUSB_UVC_ENABLED + int "Multi-frame Width 3" + default 160 + help + Additional frame size width 3. + + config TINYUSB_UVC_MULTI_FRAME_HEIGHT_3 + depends on TINYUSB_UVC_ENABLED + int "Multi-frame Height 3" + default 120 + help + Additional frame size height 3. + + config TINYUSB_UVC_MULTI_FRAME_FPS_3 + depends on TINYUSB_UVC_ENABLED + int "Multi-frame FPS 3" + default 15 + help + Additional frame rate 3. + + config TINYUSB_UVC_CAM1_TASK_PRIORITY + depends on TINYUSB_UVC_ENABLED + int "Camera 1 Task Priority" + default 5 + range 1 25 + help + Priority of the Camera 1 task. + + config TINYUSB_UVC_CAM1_TASK_CORE + depends on TINYUSB_UVC_ENABLED + int "Camera 1 Task Core (-1 for any)" + default -1 + range -1 1 + help + Core affinity for Camera 1 task. -1 for no affinity. + + config TINYUSB_UVC_CAM2_TASK_PRIORITY + depends on TINYUSB_UVC_ENABLED && TINYUSB_UVC_SUPPORT_TWO_CAM + int "Camera 2 Task Priority" + default 5 + range 1 25 + help + Priority of the Camera 2 task. + + config TINYUSB_UVC_CAM2_TASK_CORE + depends on TINYUSB_UVC_ENABLED && TINYUSB_UVC_SUPPORT_TWO_CAM + int "Camera 2 Task Core (-1 for any)" + default -1 + range -1 1 + help + Core affinity for Camera 2 task. -1 for no affinity. + + config TINYUSB_UVC_TINYUSB_TASK_PRIORITY + depends on TINYUSB_UVC_ENABLED + int "TinyUSB Task Priority" + default 5 + range 1 25 + help + Priority of the TinyUSB task for UVC. + + config TINYUSB_UVC_TINYUSB_TASK_CORE + depends on TINYUSB_UVC_ENABLED + int "TinyUSB Task Core (-1 for any)" + default -1 + range -1 1 + help + Core affinity for TinyUSB task. -1 for no affinity. + endmenu # "USB Video Class (UVC)" + + menu "Network driver (ECM/NCM/RNDIS)" + choice TINYUSB_NET_MODE + prompt "Network mode" + default TINYUSB_NET_MODE_NONE + help + Select network driver you want to use. + + config TINYUSB_NET_MODE_ECM_RNDIS + bool "ECM/RNDIS" + + config TINYUSB_NET_MODE_NCM + bool "NCM" + + config TINYUSB_NET_MODE_NONE + bool "None" + endchoice + + config TINYUSB_NCM_OUT_NTB_BUFFS_COUNT + int "Number of NCM NTB buffers for reception side" + depends on TINYUSB_NET_MODE_NCM + default 3 + range 1 6 + help + Number of NTB buffers for reception side. + Can be increased to improve performance and stability with the cost of additional RAM requirements. + Helps to mitigate "tud_network_can_xmit: request blocked" warning message when running NCM device. + + config TINYUSB_NCM_IN_NTB_BUFFS_COUNT + int "Number of NCM NTB buffers for transmission side" + depends on TINYUSB_NET_MODE_NCM + default 3 + range 1 6 + help + Number of NTB buffers for transmission side. + Can be increased to improve performance and stability with the cost of additional RAM requirements. + Helps to mitigate "tud_network_can_xmit: request blocked" warning message when running NCM device. + + config TINYUSB_NCM_OUT_NTB_BUFF_MAX_SIZE + int "NCM NTB Buffer size for reception size" + depends on TINYUSB_NET_MODE_NCM + default 3200 + range 1600 10240 + help + Size of NTB buffers on the reception side. The minimum size used by Linux is 2048 bytes. + NTB buffer size must be significantly larger than the MTU (Maximum Transmission Unit). + The typical default MTU size for Ethernet is 1500 bytes, plus an additional packet overhead. + To improve performance, the NTB buffer size should be large enough to fit multiple MTU-sized + frames in a single NTB buffer and it's length should be multiple of 4. + + config TINYUSB_NCM_IN_NTB_BUFF_MAX_SIZE + int "NCM NTB Buffer size for transmission size" + depends on TINYUSB_NET_MODE_NCM + default 3200 + range 1600 10240 + help + Size of NTB buffers on the transmission side. The minimum size used by Linux is 2048 bytes. + NTB buffer size must be significantly larger than the MTU (Maximum Transmission Unit). + The typical default MTU size for Ethernet is 1500 bytes, plus an additional packet overhead. + To improve performance, the NTB buffer size should be large enough to fit multiple MTU-sized + frames in a single NTB buffer and it's length should be multiple of 4. + + endmenu # "Network driver (ECM/NCM/RNDIS)" + + menu "Vendor Specific Interface" + config TINYUSB_VENDOR_COUNT + int "TinyUSB Vendor specific interfaces count" + default 0 + range 0 2 + help + Setting value greater than 0 will enable TinyUSB Vendor specific feature. + endmenu # "Vendor Specific Interface" +endmenu # "TinyUSB Stack" diff --git a/components/esp_tinyusb/LICENSE b/components/esp_tinyusb/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/components/esp_tinyusb/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/esp_tinyusb/README.md b/components/esp_tinyusb/README.md new file mode 100644 index 0000000..dec2ac1 --- /dev/null +++ b/components/esp_tinyusb/README.md @@ -0,0 +1,511 @@ +# Espressif's Additions to TinyUSB + +[![Component Registry](https://components.espressif.com/components/espressif/esp_tinyusb/badge.svg)](https://components.espressif.com/components/espressif/esp_tinyusb) + +This component extends TinyUSB with features that simplify integration into ESP-IDF applications. + +It provides both default and customizable configurations for TinyUSB, enabling USB device functionality on ESP chips with USB-OTG support. + +### Run-time configuration + +During configuration, the following parameters can be set when installing the driver: + +- Descriptors +- Peripheral port +- Task parameters (size, priority, and CPU affinity) +- USB PHY parameters + +### Default configuration + +Run-time default configuration for Device Stack is managed internally via the `TINYUSB_DEFAULT_CONFIG()` macros. + +### Custom configuration + +Manual configuration for Device Stack: descriptors, peripheral port, task, and USB PHY parameters can be set as needed. + +### Build-Time configuration + +Configure the Device Stack using `menuconfig`: + +- TinyUSB log verbosity +- Default device/string descriptor used by the default configuration macros +- Class-specific options (CDC-ACM, MSC, MIDI, HID, DFU, BTH, ECM/NCM/RNDIS, Vendor etc.) + +### Supported classes + +- USB Serial Device (CDC-ACM) with optional Virtual File System support. +- Input and output streams through the USB Serial Device (available only when Virtual File System support is enabled). +- Mass Storage Device Class (MSC): create USB flash drives using SPI Flash or SD/MMC as storage media. +- Support for other USB classes (MIDI, HID, etc.) directly via TinyUSB. + +## How to use? + +This component is distributed via [IDF component manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). Just add `idf_component.yml` file to your main component with the following content: + +```yaml +## IDF Component Manager Manifest File +dependencies: + esp_tinyusb: "~2.0.0" +``` + +Or simply run: + +``` +idf.py add-dependency esp_tinyusb~2.0.0 +``` + +## Breaking changes migration guides + +- [v2.0.0](../../docs/device/migration-guides/v2/) + +## USB Device Stack: usage, installation & configuration + +### Structure overview + +The Device Stack is built on top of TinyUSB and provides: + +- Custom USB descriptor support +- Serial device (CDC-ACM) support +- Standard stream redirection through the serial device +- Storage media support (SPI Flash and SD Card) for the USB MSC class +- A dedicated task for TinyUSB servicing + +### Installation + +Install the Device Stack by calling `tinyusb_driver_install` with a `tinyusb_config_t` structure. + +A default configuration is available using the `TINYUSB_DEFAULT_CONFIG()` macro. + +The default installation automatically configures the port (High-speed if supported by the hardware, otherwise Full-speed), task (with default parameters), USB PHY, Device Event callback and descriptors. + +Default descriptors are provided for the following USB classes: CDC, MSC, and NCM. + +> **⚠️ Important:** For demonstration purposes, all error handling logic has been removed from the code examples. Do not ignore proper error handling in actual development. + +```c + #include "tinyusb_default_config.h" + + void main(void) { + const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + tinyusb_driver_install(&tusb_cfg); + } +``` + +### Device Event callback + +USB Device Event callback allows to get the following events during USB Device lifecycle: + +- `TINYUSB_EVENT_ATTACHED`: Device attached to the USB Host +- `TINYUSB_EVENT_DETACHED`: Device detached from the USB Host +- `TINYUSB_EVENT_SUSPENDED`: Device entered suspended state +- `TINYUSB_EVENT_RESUMED`: Device was resumed from suspended state + +To configure the USB Device Event Callback, provide the callback to the `TINYUSB_DEFAULT_CONFIG()` macros: + +```c + #include "tinyusb_default_config.h" + + static void device_event_handler(tinyusb_event_t *event, void *arg) + { + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + case TINYUSB_EVENT_DETACHED: + case TINYUSB_EVENT_SUSPENDED: + case TINYUSB_EVENT_RESUMED: + default: + break; + } + } + + void main(void) { + const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(device_event_handler); + tinyusb_driver_install(&tusb_cfg); + } +``` + +User Argument could be passed to the USB Device Event callback as a second argument (optional): + +```c + #include "tinyusb_default_config.h" + + static context_t *context; + + static void device_event_handler(tinyusb_event_t *event, void *arg) + { + context_t *context = (context_t*) arg; + + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + case TINYUSB_EVENT_DETACHED: + case TINYUSB_EVENT_SUSPENDED: + case TINYUSB_EVENT_RESUMED: + default: + break; + } + } + + void main(void) { + const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(device_event_handler, (void*) context); + tinyusb_driver_install(&tusb_cfg); + } +``` + +### Suspend / Resume Device Events + +Suspend and resume device events are **optional** and are disabled by default. +Users can choose one of the following approaches: + +#### Option 1 — Use esp_tinyusb device events (recommended for integration) + +Enable the following Kconfig options: + +- `CONFIG_TINYUSB_SUSPEND_CALLBACK` → enables `TINYUSB_EVENT_SUSPENDED` +- `CONFIG_TINYUSB_RESUME_CALLBACK` → enables `TINYUSB_EVENT_RESUMED` + +When enabled: + +- esp_tinyusb provides strong implementations of: + - `tud_suspend_cb()` + - `tud_resume_cb()` +- esp_tinyusb dispatches suspend/resume events via the device event callback. + +⚠️ **Important:** +When these options are enabled, user applications **MUST NOT** define +`tud_suspend_cb()` or `tud_resume_cb()` themselves. Doing so will result +in a linker error due to multiple definitions. + +#### Option 2 — Use TinyUSB callbacks directly (default behavior) + +If the Kconfig options are **disabled** (default): + +- esp_tinyusb does NOT handle suspend/resume events +- Users may implement TinyUSB callbacks directly in their application: + +```c +void tud_suspend_cb(bool remote_wakeup_en) +{ + // User suspend handling +} + +void tud_resume_cb(void) +{ + // User resume handling +} +``` + +### Peripheral port + +When several peripheral ports are available by the hardware, the specific port could be configured manually: + +```c + #include "tinyusb_default_config.h" + + void main(void) { + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.port = TINYUSB_PORT_HIGH_SPEED_0; +#else + tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; +#endif + tinyusb_driver_install(&tusb_cfg); + } +``` + +### Task configuration + +When the default parameters of the internal task should be changed: + +```c + #include "tinyusb_default_config.h" + + void main(void) { + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + tusb_cfg.task = TINYUSB_TASK_CUSTOM(4096 /*size */, 4 /* priority */, 0 /* affinity: 0 - CPU0, 1 - CPU1 ... */); + tinyusb_driver_install(&tusb_cfg); + } +``` + +### USB Descriptors configuration + +Configure USB descriptors using the `tinyusb_config_t` structure: + +- `descriptor.device` +- `descriptor.string` +- `descriptor.full_speed_config` +- `descriptor.high_speed_config` +- `descriptor.qualifier` + +If any descriptor field is set to `NULL`, default descriptor will be assigned during installation. Values of default descriptors could be configured via `menuconfig`. + +```c + #include "tinyusb_default_config.h" + + void main(void) { + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + + tusb_cfg.descriptor.device = &custom_device_descriptor; + tusb_cfg.descriptor.full_speed_config = custom_full_speed_configuration; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.high_speed_config = custom_high_speed_configuration; +#endif // TUD_OPT_HIGH_SPEED + + tinyusb_driver_install(&tusb_cfg); + } +``` + +### USB PHY configuration & Self-Powered Device + +For self-powered devices, monitoring the VBUS voltage is required. To do this: + +- Configure a GPIO pin as an input, using an external voltage divider or comparator to detect the VBUS state. +- Set `self_powered = true` and assign the VBUS monitor GPIO in the `tinyusb_config_t` structure. + +```c + #include "tinyusb_default_config.h" + + void main(void) + { + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + + tusb_cfg.phy.self_powered = true; + tusb_cfg.phy.vbus_monitor_io = GPIO_NUM_0; + + tinyusb_driver_install(&tusb_cfg); + } +``` + +If external PHY is used: + +```c + #include "tinyusb_default_config.h" + #include "esp_private/usb_phy.h" + + void main(void) + { + // Initialize the USB PHY externally + usb_new_phy(...); + + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + + tusb_cfg.phy.skip_setup = true; + + tinyusb_driver_install(&tusb_cfg); + } +``` + +## USB Device Classes: usage, installation & configuration + +### USB Serial Device (CDC-ACM) + +To enable USB Serial Device: + +- select the option from `menuconfig`. +- initialize the USB Serial Device with `tusb_cdc_acm_init` and a `tinyusb_config_cdcacm_t` structure + +```c +const tinyusb_config_cdcacm_t acm_cfg = { + .cdc_port = TINYUSB_CDC_ACM_0, + .rx_unread_buf_sz = 64, + .callback_rx = NULL, + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = NULL, + .callback_line_coding_changed = NULL +}; +tusb_cdc_acm_init(&acm_cfg); +``` + +Redirect standard I/O streams to USB with `esp_tusb_init_console` and revert with `esp_tusb_deinit_console`. + +### USB Mass Storage Device (MSC) + +To enable Mass Storage Device: + +- select the option from `menuconfig` +- configure storage for MSC Device class: SPI Flash or SD/MMC (when supported by the hardware). + +**SPI-Flash Storage** + +```c +static wl_handle_t storage_init_spiflash(void) +{ + wl_handle_t wl; + // Find partition + // Mount Wear Levelling + return wl; +} + +void main(void) +{ + wl_handle_t wl_handle = storage_init_spiflash(); + + tinyusb_msc_storage_handle_t storage_hdl; + const tinyusb_msc_storage_config_t cfg = { + .medium.wl_handle = wl_handle, + }; + tinyusb_msc_new_storage_spiflash(&cfg, &storage_hdl); + + // After installing TinyUSB driver, MSC Class will have one LUN, mapped to SPI/Flash storage +} +``` + +**SD-Card Storage** + +```c +static sdmmc_card_t *storage_init_sdmmc(void) +{ + sdmmc_card_t *card; + // Config sdmmc + // Init sdmmc_host + // Init sdmmc slot + // Init sdmmc card + return card; +} + +void main(void) +{ + sdmmc_card_t *card = storage_init_sdmmc(); + + tinyusb_msc_storage_handle_t storage_hdl; + const tinyusb_msc_storage_config_t cfg = { + .medium.card = card, + }; + tinyusb_msc_new_storage_sdmmc(&cfg, &storage_hdl); + + // After installing TinyUSB driver, MSC Class will have one LUN, mapped to SD/MMC storage +} +``` + +**Dual Storage** + +```c +static wl_handle_t storage_init_spiflash(void) +{ + wl_handle_t wl; + // Find partition + // Mount Wear Levelling + return wl; +} + +static sdmmc_card_t *storage_init_sdmmc(void) +{ + sdmmc_card_t *card; + // Config sdmmc + // Init sdmmc_host + // Init sdmmc slot + // Init sdmmc card + return card; +} + +void main(void) +{ + tinyusb_msc_storage_handle_t storage1_hdl; + tinyusb_msc_storage_handle_t storage2_hdl; + tinyusb_msc_storage_config_t cfg; + + sdmmc_card_t *card = storage_init_sdmmc(); + wl_handle_t wl_handle = storage_init_spiflash(); + + // Create SPI/Flash storage + cfg.medium.wl_handle = wl_handle; + tinyusb_msc_new_storage_spiflash(&cfg, &storage_hdl); + + // Create SD/MMC storage + cfg.medium.card = card; + tinyusb_msc_new_storage_sdmmc(&cfg, &storage_hdl); + + // After installing TinyUSB driver, MSC Class will have two LUNs, mapped to SPI/Flash and SD/MMC storages accordingly +} +``` + +**Storage callback** + +Storage event callback is called, when one of the following events occurred: + +- `TINYUSB_MSC_EVENT_MOUNT_START`: Start mount from APP to USB or from USB to APP +- `TINYUSB_MSC_EVENT_MOUNT_COMPLETE`: Complete mount from USB to APP or from APP to USB +- `TINYUSB_MSC_EVENT_FORMAT_REQUIRED`: Filesystem not found, format needed +- `TINYUSB_MSC_EVENT_MOUNT_FAILED`: Error occurred during mounting filesystem + +To use or enable storage callback there are two options available. + +Set the callback with specific call after creating the storage: + +```c +static void storage_event_handle(tinyusb_msc_storage_handle_t handle, tinyusb_msc_event_t *event, void *arg) +{ + switch (event->id) { + case TINYUSB_MSC_EVENT_MOUNT_START: + case TINYUSB_MSC_EVENT_MOUNT_COMPLETE: + case TINYUSB_MSC_EVENT_MOUNT_FAILED: + case TINYUSB_MSC_EVENT_FORMAT_REQUIRED: + default: + break; + } +} + +void main(void) +{ + sdmmc_card_t *card = storage_init_sdmmc(); + + tinyusb_msc_storage_handle_t storage_hdl; + const tinyusb_msc_storage_config_t cfg = { + .medium.card = card, + }; + tinyusb_msc_new_storage_sdmmc(&cfg, &storage_hdl); + tinyusb_msc_set_storage_callback(storage_event_handle, NULL /* user argument for the event callback */); +} +``` + +Install the TinyUSB MSC Storage driver explicitly and provide the storage event via configuration: + +```c +void main(void) +{ + sdmmc_card_t *card = storage_init_sdmmc(); + + const tinyusb_msc_driver_config_t driver_cfg = { + .storage_event_cb = storage_event_handle, + .storage_event_arg = NULL /* user argument for the storage event callback */, + }; + tinyusb_msc_install_driver(&driver_cfg); + + tinyusb_msc_storage_handle_t storage_hdl; + const tinyusb_msc_storage_config_t cfg = { + .medium.card = card, + }; + tinyusb_msc_new_storage_sdmmc(&cfg, &storage_hdl); +} +``` + +### MSC Performance Optimization + +- **Single-buffer approach:** Buffer size is set via `CONFIG_TINYUSB_MSC_BUFSIZE`. +- **Performance:** SD cards offer higher throughput than internal SPI flash due to architectural constraints. + +**Performance Table (ESP32-S3):** + +| FIFO Size | Read Speed | Write Speed | +| --------- | ---------- | ----------- | +| 512 B | 0.566 MB/s | 0.236 MB/s | +| 8192 B | 0.925 MB/s | 0.928 MB/s | + +**Performance Table (ESP32-P4):** + +| FIFO Size | Read Speed | Write Speed | +| --------- | ---------- | ----------- | +| 512 B | 1.174 MB/s | 0.238 MB/s | +| 8192 B | 4.744 MB/s | 2.157 MB/s | +| 32768 B | 5.998 MB/s | 4.485 MB/s | + +**Performance Table (ESP32-S2, SPI Flash):** + +| FIFO Size | Write Speed | +| --------- | ----------- | +| 512 B | 5.59 KB/s | +| 8192 B | 21.54 KB/s | + +**Note:** Internal SPI flash is for demonstration only; use SD cards or external flash for higher performance. + +## Examples + +You can find examples in [ESP-IDF on GitHub](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/device). diff --git a/components/esp_tinyusb/cdc.c b/components/esp_tinyusb/cdc.c new file mode 100644 index 0000000..a3fe293 --- /dev/null +++ b/components/esp_tinyusb/cdc.c @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "tusb.h" +#include "cdc.h" + +#define CDC_INTF_NUM CFG_TUD_CDC // number of cdc blocks +static esp_tusb_cdc_t *cdc_obj[CDC_INTF_NUM] = {}; +static const char *TAG = "tusb_cdc"; + +esp_tusb_cdc_t *tinyusb_cdc_get_intf(int itf_num) +{ + if (itf_num >= CDC_INTF_NUM || itf_num < 0) { + return NULL; + } + return cdc_obj[itf_num]; +} + +static esp_err_t cdc_obj_check(int itf, bool expected_inited, tusb_class_code_t expected_type) +{ + esp_tusb_cdc_t *this_itf = tinyusb_cdc_get_intf(itf); + + bool inited = (this_itf != NULL); + ESP_RETURN_ON_FALSE(expected_inited == inited, ESP_ERR_INVALID_STATE, TAG, "Wrong state of the interface. Expected state: %s", expected_inited ? "initialized" : "not initialized"); + ESP_RETURN_ON_FALSE(!(inited && (expected_type != -1) && !(this_itf->type == expected_type)), ESP_ERR_INVALID_STATE, TAG, "Wrong type of the interface. Should be : 0x%x (tusb_class_code_t)", expected_type); + return ESP_OK; +} + +static esp_err_t tusb_cdc_comm_init(int itf) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, -1), TAG, "cdc_obj_check failed"); + cdc_obj[itf] = calloc(1, sizeof(esp_tusb_cdc_t)); + if (cdc_obj[itf] != NULL) { + cdc_obj[itf]->type = TUSB_CLASS_CDC; + ESP_LOGD(TAG, "CDC Comm class initialized"); + return ESP_OK; + } else { + ESP_LOGE(TAG, "CDC Comm initialization error"); + return ESP_FAIL; + } +} + +static esp_err_t tusb_cdc_deinit_comm(int itf) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, TUSB_CLASS_CDC), TAG, "cdc_obj_check failed"); + free(cdc_obj[itf]); + cdc_obj[itf] = NULL; + return ESP_OK; +} + +static esp_err_t tusb_cdc_data_init(int itf) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, TUSB_CLASS_CDC_DATA), TAG, "cdc_obj_check failed"); + cdc_obj[itf] = calloc(1, sizeof(esp_tusb_cdc_t)); + if (cdc_obj[itf] != NULL) { + cdc_obj[itf]->type = TUSB_CLASS_CDC_DATA; + ESP_LOGD(TAG, "CDC Data class initialized"); + return ESP_OK; + } else { + ESP_LOGE(TAG, "CDC Data initialization error"); + return ESP_FAIL; + } +} + +static esp_err_t tusb_cdc_deinit_data(int itf) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, TUSB_CLASS_CDC_DATA), TAG, "cdc_obj_check failed"); + free(cdc_obj[itf]); + cdc_obj[itf] = NULL; + return ESP_OK; +} + +esp_err_t tinyusb_cdc_init(int itf, const tinyusb_config_cdc_t *cfg) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, -1), TAG, "cdc_obj_check failed"); + + ESP_LOGD(TAG, "Init CDC %d", itf); + if (cfg->cdc_class == TUSB_CLASS_CDC) { + ESP_RETURN_ON_ERROR(tusb_cdc_comm_init(itf), TAG, "tusb_cdc_comm_init failed"); + cdc_obj[itf]->cdc_subclass.comm_subclass = cfg->cdc_subclass.comm_subclass; + } else { + ESP_RETURN_ON_ERROR(tusb_cdc_data_init(itf), TAG, "tusb_cdc_data_init failed"); + cdc_obj[itf]->cdc_subclass.data_subclass = cfg->cdc_subclass.data_subclass; + } + return ESP_OK; +} + +esp_err_t tinyusb_cdc_deinit(int itf) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, -1), TAG, "cdc_obj_check failed"); + + ESP_LOGD(TAG, "Deinit CDC %d", itf); + if (cdc_obj[itf]->type == TUSB_CLASS_CDC) { + ESP_RETURN_ON_ERROR(tusb_cdc_deinit_comm(itf), TAG, "tusb_cdc_deinit_comm failed"); + } else if (cdc_obj[itf]->type == TUSB_CLASS_CDC_DATA) { + ESP_RETURN_ON_ERROR(tusb_cdc_deinit_data(itf), TAG, "tusb_cdc_deinit_data failed"); + } else { + return ESP_ERR_INVALID_ARG; + } + return ESP_OK; +} diff --git a/components/esp_tinyusb/descriptors_control.c b/components/esp_tinyusb/descriptors_control.c new file mode 100644 index 0000000..0e10eb4 --- /dev/null +++ b/components/esp_tinyusb/descriptors_control.c @@ -0,0 +1,309 @@ +/* + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" +#include "esp_check.h" +#include "esp_err.h" +#include "descriptors_control.h" +#include "usb_descriptors.h" + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#define MAX_DESC_BUF_SIZE 32 // Max length of string descriptor (can be extended, USB supports lengths up to 255 bytes) + +static const char *TAG = "tusb_desc"; + +// ============================================================================= +// STRUCTS +// ============================================================================= + +/** + * @brief Descriptor pointers for tinyusb descriptor requests callbacks + * + */ +typedef struct { + const tusb_desc_device_t *dev; /*!< Pointer to device descriptor. */ + const uint8_t *fs_cfg; /*!< Pointer to Full-speed configuration descriptor, always present. */ + const uint8_t *hs_cfg; /*!< Pointer to High-speed configuration descriptor, NULL when device Full-speed only. */ +#if (TUD_OPT_HIGH_SPEED) + const tusb_desc_device_qualifier_t *qualifier; /*!< Pointer to Qualifier descriptor. */ + uint8_t *other_speed; /*!< Pointer for other speed configuration descriptor. */ +#endif // TUD_OPT_HIGH_SPEED + const char *str[USB_STRING_DESCRIPTOR_ARRAY_SIZE]; /*!< Pointer to array of UTF-8 strings. */ + int str_count; /*!< Number of descriptors in str array. */ +} tinyusb_descriptors_map_t; + +static tinyusb_descriptors_map_t s_desc_cfg; + +// ============================================================================= +// CALLBACKS +// ============================================================================= + +/** + * @brief Invoked when received GET DEVICE DESCRIPTOR. + * Descriptor contents must exist long enough for transfer to complete + * + * @return Pointer to device descriptor + */ +uint8_t const *tud_descriptor_device_cb(void) +{ + assert(s_desc_cfg.dev); + return (uint8_t const *)s_desc_cfg.dev; +} + +/** + * @brief Invoked when received GET CONFIGURATION DESCRIPTOR. + * Descriptor contents must exist long enough for transfer to complete + * + * @param[in] index Index of required configuration + * @return Pointer to configuration descriptor + */ +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) +{ + (void)index; // Unused, this driver supports only 1 configuration + // Return configuration descriptor based on Host speed + return (TUSB_SPEED_HIGH == tud_speed_get()) + ? s_desc_cfg.hs_cfg + : s_desc_cfg.fs_cfg; +} + +#if (TUD_OPT_HIGH_SPEED) +/** + * @brief Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request + * Descriptor contents must exist long enough for transfer to complete + * If not highspeed capable stall this request + */ +uint8_t const *tud_descriptor_device_qualifier_cb(void) +{ + return (uint8_t const *)s_desc_cfg.qualifier; +} + +/** + * @brief Invoked when received GET OTHER SPEED CONFIGURATION DESCRIPTOR request + * Descriptor contents must exist long enough for transfer to complete + * Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa + */ +uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) +{ + if (s_desc_cfg.other_speed == NULL) { + // Other speed configuration descriptor is not supported + // or the buffer wasn't created + // return NULL to STALL the request + return NULL; + } + + const uint8_t *other_speed = (TUSB_SPEED_HIGH == tud_speed_get()) + ? s_desc_cfg.fs_cfg + : s_desc_cfg.hs_cfg; + + memcpy(s_desc_cfg.other_speed, + other_speed, + ((tusb_desc_configuration_t *)other_speed)->wTotalLength); + + ((tusb_desc_configuration_t *)s_desc_cfg.other_speed)->bDescriptorType = TUSB_DESC_OTHER_SPEED_CONFIG; + return s_desc_cfg.other_speed; +} +#endif // TUD_OPT_HIGH_SPEED + +/** + * @brief Invoked when received GET STRING DESCRIPTOR request + * + * @param[in] index Index of required descriptor + * @param[in] langid Language of the descriptor + * @return Pointer to UTF-16 string descriptor + */ +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + (void) langid; // Unused, this driver supports only one language in string descriptors + assert(s_desc_cfg.str); + uint8_t chr_count; + static uint16_t _desc_str[MAX_DESC_BUF_SIZE]; + + if (index == 0) { + memcpy(&_desc_str[1], s_desc_cfg.str[0], 2); + chr_count = 1; + } else { + if (index >= USB_STRING_DESCRIPTOR_ARRAY_SIZE) { + ESP_LOGW(TAG, "String index (%u) is out of bounds, check your string descriptor", index); + return NULL; + } + + if (s_desc_cfg.str[index] == NULL) { + ESP_LOGW(TAG, "String index (%u) points to NULL, check your string descriptor", index); + return NULL; + } + + const char *str = s_desc_cfg.str[index]; + chr_count = strnlen(str, MAX_DESC_BUF_SIZE - 1); // Buffer len - header + + // Convert ASCII string into UTF-16 + for (uint8_t i = 0; i < chr_count; i++) { + _desc_str[1 + i] = str[i]; + } + } + + // First byte is length in bytes (including header), second byte is descriptor type (TUSB_DESC_STRING) + _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2 * chr_count + 2); + + return _desc_str; +} + +// ============================================================================= +// Driver functions +// ============================================================================= + +esp_err_t tinyusb_descriptors_check(tinyusb_port_t port, const tinyusb_desc_config_t *config) +{ + ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Descriptors config can't be NULL"); + ESP_RETURN_ON_FALSE(config->string_count <= USB_STRING_DESCRIPTOR_ARRAY_SIZE, ESP_ERR_NOT_SUPPORTED, TAG, "String descriptors exceed limit"); + +#if (SOC_USB_OTG_PERIPH_NUM > 1) + if (port == TINYUSB_PORT_HIGH_SPEED_0) { +#if !TUD_OPT_HIGH_SPEED + ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "Device has only Full-speed port"); +#endif + } +#endif + + return ESP_OK; +} + +esp_err_t tinyusb_descriptors_set(tinyusb_port_t port, const tinyusb_desc_config_t *config) +{ + esp_err_t ret; + const char **pstr_desc; + // Flush descriptors control struct + memset(&s_desc_cfg, 0x00, sizeof(tinyusb_descriptors_map_t)); + + // Device Descriptor + if (config->device == NULL) { + ESP_LOGW(TAG, "No Device descriptor provided, using default."); + s_desc_cfg.dev = &descriptor_dev_default; + } else { + s_desc_cfg.dev = config->device; + } + + // Full-speed configuration descriptor + if (config->full_speed_config == NULL) { +#if (CFG_TUD_CDC > 0 || CFG_TUD_MSC > 0 || CFG_TUD_NCM > 0 || CFG_TUD_VIDEO > 0) + // We provide default config descriptors only for CDC, MSC, NCM and VIDEO classes + ESP_LOGW(TAG, "No Full-speed configuration descriptor provided, using default."); + s_desc_cfg.fs_cfg = descriptor_fs_cfg_default; +#else + // Default configuration descriptor must be provided via config structure + ESP_GOTO_ON_FALSE(config->full_speed_config, ESP_ERR_INVALID_ARG, fail, TAG, "Full-speed configuration descriptor must be provided for this device"); +#endif + } else { + s_desc_cfg.fs_cfg = config->full_speed_config; + } + +#if (SOC_USB_OTG_PERIPH_NUM > 1) + // High-speed configuration descriptor + if (port == TINYUSB_PORT_HIGH_SPEED_0) { +#if (TUD_OPT_HIGH_SPEED) + if (config->high_speed_config == NULL) { +#if (CFG_TUD_CDC > 0 || CFG_TUD_MSC > 0 || CFG_TUD_NCM > 0 || CFG_TUD_VIDEO > 0) + // We provide default config descriptors only for CDC, MSC, NCM and VIDEO classes + ESP_LOGW(TAG, "No High-speed configuration descriptor provided, using default."); + s_desc_cfg.hs_cfg = descriptor_hs_cfg_default; +#else + // High-speed configuration descriptor must be provided via config structure + ESP_GOTO_ON_FALSE(config->high_speed_config, ESP_ERR_INVALID_ARG, fail, TAG, "High-speed configuration descriptor must be provided for this device"); +#endif + } else { + s_desc_cfg.hs_cfg = config->high_speed_config; + } + + // Device Qualifier Descriptor + if (config->qualifier == NULL) { + // Get default qualifier if device descriptor is default + ESP_LOGW(TAG, "No Qualifier descriptor provided, using default."); + s_desc_cfg.qualifier = &descriptor_qualifier_default; + } else { + s_desc_cfg.qualifier = config->qualifier; + } + // Other Speed Descriptor buffer allocation, will be used for other speed configuration descriptor request + uint16_t other_speed_buf_size = MAX(((tusb_desc_configuration_t *)s_desc_cfg.fs_cfg)->wTotalLength, + ((tusb_desc_configuration_t *)s_desc_cfg.hs_cfg)->wTotalLength); + s_desc_cfg.other_speed = calloc(1, other_speed_buf_size); + ESP_GOTO_ON_FALSE(s_desc_cfg.other_speed, ESP_ERR_NO_MEM, fail, TAG, "Other speed memory allocation error"); +#endif // TUD_OPT_HIGH_SPEED + } else { + s_desc_cfg.hs_cfg = NULL; + } +#endif // (SOC_USB_OTG_PERIPH_NUM > 1) + + // Select String Descriptors and count them + if (config->string == NULL) { + ESP_LOGW(TAG, "No String descriptors provided, using default."); + pstr_desc = descriptor_str_default; + while (descriptor_str_default[++s_desc_cfg.str_count] != NULL); + } else { + pstr_desc = config->string; + s_desc_cfg.str_count = config->string_count; + } + + ESP_GOTO_ON_FALSE(s_desc_cfg.str_count <= USB_STRING_DESCRIPTOR_ARRAY_SIZE, ESP_ERR_NOT_SUPPORTED, fail, TAG, "String descriptors exceed limit"); + memcpy(s_desc_cfg.str, pstr_desc, s_desc_cfg.str_count * sizeof(pstr_desc[0])); + + ESP_LOGI(TAG, "\n" + "┌─────────────────────────────────┐\n" + "│ USB Device Descriptor Summary │\n" + "├───────────────────┬─────────────┤\n" + "│bDeviceClass │ %-4u │\n" + "├───────────────────┼─────────────┤\n" + "│bDeviceSubClass │ %-4u │\n" + "├───────────────────┼─────────────┤\n" + "│bDeviceProtocol │ %-4u │\n" + "├───────────────────┼─────────────┤\n" + "│bMaxPacketSize0 │ %-4u │\n" + "├───────────────────┼─────────────┤\n" + "│idVendor │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│idProduct │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│bcdDevice │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│iManufacturer │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│iProduct │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│iSerialNumber │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│bNumConfigurations │ %-#10x │\n" + "└───────────────────┴─────────────┘", + s_desc_cfg.dev->bDeviceClass, s_desc_cfg.dev->bDeviceSubClass, + s_desc_cfg.dev->bDeviceProtocol, s_desc_cfg.dev->bMaxPacketSize0, + s_desc_cfg.dev->idVendor, s_desc_cfg.dev->idProduct, s_desc_cfg.dev->bcdDevice, + s_desc_cfg.dev->iManufacturer, s_desc_cfg.dev->iProduct, s_desc_cfg.dev->iSerialNumber, + s_desc_cfg.dev->bNumConfigurations); + + return ESP_OK; + +fail: +#if (TUD_OPT_HIGH_SPEED) + free(s_desc_cfg.other_speed); +#endif // TUD_OPT_HIGH_SPEED + return ret; +} + +void tinyusb_descriptors_set_string(const char *str, int str_idx) +{ + assert(str_idx < USB_STRING_DESCRIPTOR_ARRAY_SIZE); + s_desc_cfg.str[str_idx] = str; +} + +void tinyusb_descriptors_free(void) +{ +#if (TUD_OPT_HIGH_SPEED) + if (s_desc_cfg.other_speed) { + free(s_desc_cfg.other_speed); + } +#endif // TUD_OPT_HIGH_SPEED +} diff --git a/components/esp_tinyusb/idf_component.yml b/components/esp_tinyusb/idf_component.yml new file mode 100644 index 0000000..fc47dc9 --- /dev/null +++ b/components/esp_tinyusb/idf_component.yml @@ -0,0 +1,15 @@ +## IDF Component Manager Manifest File +description: Espressif's additions to TinyUSB +documentation: "https://docs.espressif.com/projects/esp-usb/en/latest/esp32p4/usb_device.html" +version: "2.1.0" +url: https://github.com/espressif/esp-usb/tree/master/device/esp_tinyusb +targets: + - esp32s2 + - esp32s3 + - esp32p4 + - esp32h4 +dependencies: + idf: '>=5.0' # IDF 4.x contains TinyUSB as submodule + tinyusb: + version: '>=0.17.0~2' # 0.17.0~2 is the first version that supports deinit + public: true diff --git a/components/esp_tinyusb/include/deprecated/tusb_cdc_acm.h b/components/esp_tinyusb/include/deprecated/tusb_cdc_acm.h new file mode 100644 index 0000000..94ef016 --- /dev/null +++ b/components/esp_tinyusb/include/deprecated/tusb_cdc_acm.h @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" +#include "tinyusb_cdc_acm.h" + +/** + * @brief Initialize CDC ACM. Initialization will be finished with + * the `tud_cdc_line_state_cb` callback + * + * @deprecated Deprecated and may be removed in future releases. + * + * @param[in] cfg Configuration structure + * @return esp_err_t + */ +#define tusb_cdc_acm_init(cfg) tinyusb_cdcacm_init((cfg)) + +/** + * @brief De-initialize CDC ACM. + * + * @deprecated Deprecated and may be removed in future releases. + * + * @param[in] itf Index of CDC interface + * @return esp_err_t + */ +#define tusb_cdc_acm_deinit(itf) tinyusb_cdcacm_deinit((itf)) + +/** + * @brief Check if the CDC interface is initialized + * + * @deprecated Deprecated and may be removed in future releases. + * + * @param[in] itf Index of CDC interface + * @return - true Initialized + * - false Not Initialized + */ +#define tusb_cdc_acm_initialized(itf) tinyusb_cdcacm_initialized((itf)) + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include/deprecated/tusb_console.h b/components/esp_tinyusb/include/deprecated/tusb_console.h new file mode 100644 index 0000000..7bd4c46 --- /dev/null +++ b/components/esp_tinyusb/include/deprecated/tusb_console.h @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" +#include "tinyusb_console.h" + +/** + * @brief Redirect output to the USB serial + * + * @deprecated Deprecated and may be removed in future releases. + * + * @param cdc_intf - interface number of TinyUSB's CDC + * + * @return esp_err_t - ESP_OK, ESP_FAIL or an error code + */ +#define esp_tusb_init_console(cdc_intf) tinyusb_console_init((cdc_intf)) + +/** + * @brief Switch log to the default output + * + * @deprecated Deprecated and may be removed in future releases. + * + * @param cdc_intf - interface number of TinyUSB's CDC + * + * @return esp_err_t + */ +#define esp_tusb_deinit_console(cdc_intf) tinyusb_console_deinit((cdc_intf)) + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include/deprecated/tusb_msc_storage.h b/components/esp_tinyusb/include/deprecated/tusb_msc_storage.h new file mode 100644 index 0000000..c2f6787 --- /dev/null +++ b/components/esp_tinyusb/include/deprecated/tusb_msc_storage.h @@ -0,0 +1,259 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "tinyusb_msc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Types of MSC events + * @deprecated Deprecated and may be removed in future releases. + */ +typedef enum { + TINYUSB_MSC_EVENT_MOUNT_CHANGED, /*!< Event type AFTER mount/unmount operation is successfully finished */ + TINYUSB_MSC_EVENT_PREMOUNT_CHANGED /*!< Event type BEFORE mount/unmount operation is started */ +} tinyusb_msc_event_type_t; + +/** + * @brief Configuration structure for spiflash initialization + * + * @deprecated Deprecated and may be removed in future releases. + * + * User configurable parameters that are used while + * initializing the SPI Flash media. + */ +typedef struct { + wl_handle_t wl_handle; /*!< Pointer to spiflash wear-levelling handle */ + tusb_msc_callback_t callback_mount_changed; /*!< Pointer to the function callback that will be delivered AFTER storage mount/unmount operation is successfully finished */ + tusb_msc_callback_t callback_premount_changed; /*!< Pointer to the function callback that will be delivered BEFORE storage mount/unmount operation is started */ + tusb_msc_callback_t callback_device_mount_changed; /*!< Pointer to the function callback that will be delivered when a device is unmounted, from tud_umount_cb() */ + const esp_vfs_fat_mount_config_t mount_config; /*!< FATFS mount config */ +} tinyusb_msc_spiflash_config_t; + +/** + * @brief Register storage type SPI Flash with tinyusb driver + * + * @deprecated Deprecated and may be removed in future releases. + * + * + * @param config pointer to the SPI Flash configuration + * @return + * - ESP_OK: SPI Flash storage initialized successfully + * - ESP_ERR_NO_MEM: There was no memory to allocate storage components; + */ +esp_err_t tinyusb_msc_storage_init_spiflash(const tinyusb_msc_spiflash_config_t *config) +{ + return tinyusb_msc_new_storage_spiflash(&(tinyusb_msc_storage_config_t) { + .medium.wl_handle = config->wl_handle, + .fat_fs = { + .base_path = NULL, + .do_not_format = false, + .config = config->mount_config, + }, + }, NULL); +} + +#if SOC_SDMMC_HOST_SUPPORTED +/** + * @brief Configuration structure for sdmmc initialization + * + * @deprecated Deprecated and may be removed in future releases. + * + * User configurable parameters that are used while + * initializing the sdmmc media. + */ +typedef struct { + sdmmc_card_t *card; /*!< Pointer to sdmmc card configuration structure */ + tusb_msc_callback_t callback_mount_changed; /*!< Pointer to the function callback that will be delivered AFTER storage mount/unmount operation is successfully finished */ + tusb_msc_callback_t callback_premount_changed; /*!< Pointer to the function callback that will be delivered BEFORE storage mount/unmount operation is started */ + tusb_msc_callback_t callback_device_mount_changed; /*!< Pointer to the function callback that will be delivered when a device mounted/unmounted */ + const esp_vfs_fat_mount_config_t mount_config; /*!< FATFS mount config */ +} tinyusb_msc_sdmmc_config_t; + +/** + * @brief Register storage type sd-card with tinyusb driver + * + * @deprecated Deprecated and may be removed in future releases. + * + * @param config pointer to the sd card configuration + * @return + * - ESP_OK: SDMMC storage initialized successfully + * - ESP_ERR_NO_MEM: There was no memory to allocate storage components; + */ +esp_err_t tinyusb_msc_storage_init_sdmmc(const tinyusb_msc_sdmmc_config_t *config) +{ + return tinyusb_msc_new_storage_sdmmc(&(tinyusb_msc_storage_config_t) { + .medium.card = config->card, + .fat_fs = { + .base_path = NULL, + .do_not_format = false, + .config = config->mount_config, + }, + }, NULL); +} +#endif + +/** + * @brief Deinitialize TinyUSB MSC storage + * + * This function deinitializes the TinyUSB MSC storage interface. + * It releases any resources allocated during initialization. + * + * @deprecated Deprecated and may be removed in future releases. + * Please use the recommended alternative tinyusb_msc_storage_delete(). + */ +void tinyusb_msc_storage_deinit(void) +{ + tinyusb_msc_delete_storage(NULL); +} + +/** + * @brief Register a callback invoking on MSC event. If the callback had been + * already registered, it will be overwritten + * + * @deprecated Deprecated and may be removed in future releases. + * Please, update the callback function and use the recommended alternative tinyusb_msc_set_storage_callback(). + * + * @param event_type - type of registered event for a callback + * @param callback - callback function + * @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG + */ +esp_err_t tinyusb_msc_register_callback(tinyusb_msc_event_type_t event_type, + tusb_msc_callback_t callback) +{ + return ESP_OK; // This function is deprecated and does nothing +} + +/** + * @brief Unregister a callback invoking on MSC event. + * + * @deprecated Deprecated and may be removed in future releases. + * + * @param event_type - type of registered event for a callback + * @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG + */ +esp_err_t tinyusb_msc_unregister_callback(tinyusb_msc_event_type_t event_type) +{ + return ESP_OK; // This function is deprecated and does nothing +} + +/** + * @brief Mount the storage partition locally on the firmware application. + * + * Get the available drive number. Register spi flash partition. + * Connect POSIX and C standard library IO function with FATFS. + * Mounts the partition. + * This API is used by the firmware application. If the storage partition is + * mounted by this API, host (PC) can't access the storage via MSC. + * When this function is called from the tinyusb callback functions, care must be taken + * so as to make sure that user callbacks must be completed within a + * specific time. Otherwise, MSC device may re-appear again on Host. + * + * @deprecated Deprecated and may be removed in future releases. + * Please use the recommended alternative tinyusb_msc_set_storage_mount_point(). + * + * @param base_path path prefix where FATFS should be registered + * @return esp_err_t + * - ESP_OK, if success; + * - ESP_ERR_NOT_FOUND if the maximum count of volumes is already mounted + * - ESP_ERR_NO_MEM if not enough memory or too many VFSes already registered; + */ +esp_err_t tinyusb_msc_storage_mount(const char *base_path) +{ + tinyusb_msc_config_storage_fat_fs(NULL, &(tinyusb_msc_fatfs_config_t) { + .base_path = (char *)base_path, + .config = { + .max_files = 2, // Default max files + .format_if_mount_failed = false, // Do not format if mount fails + }, + .do_not_format = false, // Allow formatting + .format_flags = FM_ANY, // Auto-select FAT type based on volume size + }); + return tinyusb_msc_set_storage_mount_point(NULL, TINYUSB_MSC_STORAGE_MOUNT_APP); +} + +/** + * @brief Get number of sectors in storage media + * + * @deprecated Deprecated and may be removed in future releases. + * Please use the recommended alternative tinyusb_msc_get_storage_capacity(). + * + * @return usable size, in bytes + */ +uint32_t tinyusb_msc_storage_get_sector_count(void) +{ + uint32_t capacity = 0; + tinyusb_msc_get_storage_capacity(NULL, &capacity); + return capacity; +} + +/** + * @brief Get sector size of storage media + * + * @deprecated Deprecated and may be removed in future releases. + * Please use the recommended alternative tinyusb_msc_storage_get_sector_size(). + * + * @return sector count + */ +uint32_t tinyusb_msc_storage_get_sector_size(void) +{ + uint32_t sector_size = 0; + tinyusb_msc_get_storage_sector_size(NULL, §or_size); + return sector_size; +} + +/** + * @brief Unmount the storage partition from the firmware application. + * + * Unmount the partition. Unregister diskio driver. + * Unregister the SPI flash partition. + * Finally, Un-register FATFS from VFS. + * After this function is called, storage device can be seen (recognized) by host (PC). + * When this function is called from the tinyusb callback functions, care must be taken + * so as to make sure that user callbacks must be completed within a specific time. + * Otherwise, MSC device may not appear on Host. + * + * @deprecated Deprecated and may be removed in future releases. + * Please use the recommended alternative tinyusb_msc_set_storage_mount_point(). + * + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if FATFS is not registered in VFS + */ +esp_err_t tinyusb_msc_storage_unmount(void) +{ + return tinyusb_msc_set_storage_mount_point(NULL, TINYUSB_MSC_STORAGE_MOUNT_USB); +} + +/** + * @brief Get status if storage media is exposed over USB to Host + * + * This function checks if the storage media is currently mounted on the application or exposed to the Host. + * + * @deprecated Deprecated and may be removed in future releases. + * Please use the recommended alternative tinyusb_msc_storage_get_mount_point(). + * + * @return bool + * - true, if the storage media is exposed to Host + * - false, if the stoarge media is mounted on application (not exposed to Host) + */ +bool tinyusb_msc_storage_in_use_by_usb_host(void) +{ + bool exposed_to_host = false; + tinyusb_msc_mount_point_t mp; + if (tinyusb_msc_get_storage_mount_point(NULL, &mp) == ESP_OK) { + exposed_to_host = (mp == TINYUSB_MSC_STORAGE_MOUNT_USB); + } + return exposed_to_host; +} + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include/tinyusb.h b/components/esp_tinyusb/include/tinyusb.h new file mode 100644 index 0000000..adc3f90 --- /dev/null +++ b/components/esp_tinyusb/include/tinyusb.h @@ -0,0 +1,196 @@ +/* + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "soc/soc_caps.h" +#include "tusb.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief TinyUSB Vendor ID + * + * @note This is the Vendor ID which will be used in Default Device Descriptor. + * + */ +#define TINYUSB_ESPRESSIF_VID 0x303A + +/** + * @brief TinyUSB peripheral port number + */ +typedef enum { + TINYUSB_PORT_FULL_SPEED_0 = 0, /*!< USB OTG 1.1 peripheral */ +#if (SOC_USB_OTG_PERIPH_NUM > 1) + TINYUSB_PORT_HIGH_SPEED_0, /*!< USB OTG 2.0 peripheral */ +#endif // (SOC_USB_OTG_PERIPH_NUM > 1) + TINYUSB_PORT_MAX, +} tinyusb_port_t; + +/** + * @brief TinyUSB PHY configuration + * + * @note This structure is used to configure the USB PHY. The user can set the parameters + * according to their requirements. + */ +typedef struct { + bool skip_setup; /*!< If set, the esp_tinyusb will not configure the USB PHY thus allowing + the user to manually configure the USB PHY before calling tinyusb_driver_install(). + Users should set this if they want to use an external USB PHY. Otherwise, + the esp_tinyusb will automatically configure the internal USB PHY */ + // Relevant only when skip_setup is false + bool self_powered; /*!< USB specification mandates self-powered devices to monitor USB VBUS to detect connection/disconnection events. + To use this feature, connect VBUS to any free GPIO through a voltage divider or voltage comparator. + The voltage divider output should be (0.75 * Vdd) if VBUS is 4.4V (lowest valid voltage at device port). + The comparator thresholds should be set with hysteresis: 4.35V (falling edge) and 4.75V (raising edge). */ + int vbus_monitor_io; /*!< GPIO for VBUS monitoring, 3.3 V tolerant (use a comparator or a resistior divider to detect the VBUS valid condition). Ignored if not self_powered. */ +} tinyusb_phy_config_t; + +/** + * @brief TinyUSB Task configuration + */ +typedef struct { + size_t size; /*!< USB Device Task size. */ + uint8_t priority; /*!< USB Device Task priority. */ + int xCoreID; /*!< USB Device Task core affinity. */ +} tinyusb_task_config_t; + +/** + * @brief USB device descriptor configuration structure + * + * This structure holds pointers to the USB descriptors required to initialize + * a TinyUSB device stack. These descriptors define how the USB device is identified + * and communicates with the USB host. + * + * This configuration is typically passed during TinyUSB driver installation. + * If not provided, default descriptor values can be defined and used via Kconfig. + * + * @note All pointers must remain valid throughout the lifetime of the TinyUSB stack. + */ +typedef struct { + const tusb_desc_device_t *device; /*!< Pointer to the USB device descriptor. + Defines general device information such as + USB version, vendor ID, product ID, etc. + Must not be NULL. */ + + const tusb_desc_device_qualifier_t *qualifier; /*!< Pointer to the device qualifier descriptor. + Required only for High-Speed capable devices. + Can be NULL for Full-Speed only devices. */ + + const char **string; /*!< Pointer to an array of USB string descriptors. + Strings describe elements like manufacturer name, + product name, and serial number. + Can be NULL if no strings are used. */ + + int string_count; /*!< Number of elements in the string descriptor array. + Should match the actual number of strings provided. */ + + const uint8_t *full_speed_config; /*!< Pointer to the Full-Speed configuration descriptor. + If NULL, the default descriptor from Kconfig is used, if available for the desired class. */ + + const uint8_t *high_speed_config; /*!< Pointer to the High-Speed configuration descriptor. + If NULL, the default descriptor from Kconfig is used, if available for the desired class and the device supports High-Speed. */ +} tinyusb_desc_config_t; + +/** + * @brief TinyUSB event type + */ +typedef enum { + TINYUSB_EVENT_ATTACHED = 0, /*!< USB device attached to the Host */ + TINYUSB_EVENT_DETACHED = 1, /*!< USB device detached from the Host */ +#ifdef CONFIG_TINYUSB_SUSPEND_CALLBACK + TINYUSB_EVENT_SUSPENDED = 2, /*!< USB device suspended */ +#endif // CONFIG_TINYUSB_SUSPEND_CALLBACK +#ifdef CONFIG_TINYUSB_RESUME_CALLBACK + TINYUSB_EVENT_RESUMED = 3, /*!< USB device resumed */ +#endif // CONFIG_TINYUSB_RESUME_CALLBACK +} tinyusb_event_id_t; + +/** + * @brief TinyUSB event structure + * + * This structure is used to pass the event information to the callback function. + */ +typedef struct { + tinyusb_event_id_t id; /*!< Event ID */ + uint8_t rhport; /*!< USB Peripheral hardware port number. Available when hardware has several available peripherals. */ + union { + struct { + bool remote_wakeup; /*!< Remote wakeup enabled flag */ + } suspended; /*!< Specific event id data */ + }; +} tinyusb_event_t; + +/** + * @brief Callback used to indicate TinyUSB events + * + * @param event Pointer to event data structure + * @param arg Pointer to the argument passed to the callback + */ +typedef void (*tinyusb_event_cb_t)(tinyusb_event_t *event, void *arg); + +/** + * @brief Configuration structure of the TinyUSB driver + */ +typedef struct { + tinyusb_port_t port; /*!< USB Peripheral hardware port number. Available when hardware has several available peripherals. */ + tinyusb_phy_config_t phy; /*!< USB PHY configuration. */ + tinyusb_task_config_t task; /*!< USB Device Task configuration. */ + tinyusb_desc_config_t descriptor; /*!< Pointer to a descriptor configuration. If set to NULL, the TinyUSB device will use a default descriptor configuration whose values are set in Kconfig */ + tinyusb_event_cb_t event_cb; /*!< Callback function that will be called when USB events occur. */ + void *event_arg; /*!< Pointer to the argument passed to the callback */ +} tinyusb_config_t; + +/** + * @brief This is an all-in-one helper function, including: + * 1. USB device driver initialization + * 2. Descriptors preparation + * 3. TinyUSB stack initialization + * 4. Creates and start a task to handle usb events + * + * @note Don't change Custom descriptor, but if it has to be done, + * Suggest to define as follows in order to match the Interface Association Descriptor (IAD): + * bDeviceClass = TUSB_CLASS_MISC, + * bDeviceSubClass = MISC_SUBCLASS_COMMON, + * + * @param config tinyusb stack specific configuration + * @retval ESP_ERR_INVALID_ARG Install driver and tinyusb stack failed because of invalid argument + * @retval ESP_FAIL Install driver and tinyusb stack failed because of internal error + * @retval ESP_OK Install driver and tinyusb stack successfully + */ +esp_err_t tinyusb_driver_install(const tinyusb_config_t *config); + +/** + * @brief This is an all-in-one helper function, including: + * 1. Stops the task to handle usb events + * 2. TinyUSB stack tearing down + * 2. Freeing resources after descriptors preparation + * 3. Deletes USB PHY + * + * @retval ESP_FAIL Uninstall driver or tinyusb stack failed because of internal error + * @retval ESP_OK Uninstall driver, tinyusb stack and USB PHY successfully + */ +esp_err_t tinyusb_driver_uninstall(void); + +/** + * @brief Send a remote wakeup signal to the USB host + * + * @note This function can be called only when the device is suspended and the host has enabled remote wakeup. + * + * @retval ESP_ERR_INVALID_STATE Remote wakeup is not enabled by the host + * @retval ESP_FAIL Remote wakeup request failed + * @retval ESP_OK Remote wakeup request sent successfully + */ +esp_err_t tinyusb_remote_wakeup(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include/tinyusb_cdc_acm.h b/components/esp_tinyusb/include/tinyusb_cdc_acm.h new file mode 100644 index 0000000..b2afe8d --- /dev/null +++ b/components/esp_tinyusb/include/tinyusb_cdc_acm.h @@ -0,0 +1,198 @@ +/* + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "sdkconfig.h" +#include "esp_err.h" +#include "class/cdc/cdc.h" + +#if (CONFIG_TINYUSB_CDC_ENABLED != 1) +#error "TinyUSB CDC driver must be enabled in menuconfig" +#endif + + +/** + * @brief CDC ports available to setup + */ +typedef enum { + TINYUSB_CDC_ACM_0 = 0x0, + TINYUSB_CDC_ACM_1, + TINYUSB_CDC_ACM_MAX +} tinyusb_cdcacm_itf_t; + +/*************************************************************************/ +/* Callbacks and events */ +/*************************************************************************/ + +/** + * @brief Data provided to the input of the `callback_rx_wanted_char` callback + */ +typedef struct { + char wanted_char; /*!< Wanted character */ +} cdcacm_event_rx_wanted_char_data_t; + +/** + * @brief Data provided to the input of the `callback_line_state_changed` callback + */ +typedef struct { + bool dtr; /*!< Data Terminal Ready (DTR) line state */ + bool rts; /*!< Request To Send (RTS) line state */ +} cdcacm_event_line_state_changed_data_t; + +/** + * @brief Data provided to the input of the `line_coding_changed` callback + */ +typedef struct { + cdc_line_coding_t const *p_line_coding; /*!< New line coding value */ +} cdcacm_event_line_coding_changed_data_t; + +/** + * @brief Types of CDC ACM events + */ +typedef enum { + CDC_EVENT_RX, + CDC_EVENT_RX_WANTED_CHAR, + CDC_EVENT_LINE_STATE_CHANGED, + CDC_EVENT_LINE_CODING_CHANGED +} cdcacm_event_type_t; + +/** + * @brief Describes an event passing to the input of a callbacks + */ +typedef struct { + cdcacm_event_type_t type; /*!< Event type */ + union { + cdcacm_event_rx_wanted_char_data_t rx_wanted_char_data; /*!< Data input of the `callback_rx_wanted_char` callback */ + cdcacm_event_line_state_changed_data_t line_state_changed_data; /*!< Data input of the `callback_line_state_changed` callback */ + cdcacm_event_line_coding_changed_data_t line_coding_changed_data; /*!< Data input of the `line_coding_changed` callback */ + }; +} cdcacm_event_t; + +/** + * @brief CDC-ACM callback type + */ +typedef void(*tusb_cdcacm_callback_t)(int itf, cdcacm_event_t *event); + +/** + * @brief Configuration structure for CDC-ACM + */ +typedef struct { + tinyusb_cdcacm_itf_t cdc_port; /*!< CDC port */ + tusb_cdcacm_callback_t callback_rx; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */ + tusb_cdcacm_callback_t callback_rx_wanted_char; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */ + tusb_cdcacm_callback_t callback_line_state_changed; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */ + tusb_cdcacm_callback_t callback_line_coding_changed; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */ +} tinyusb_config_cdcacm_t; + +/************************************************************************/ +/* Public functions */ +/************************************************************************/ + +/** + * @brief Initialize CDC ACM. Initialization will be finished with + * the `tud_cdc_line_state_cb` callback + * + * @param[in] cfg Configuration structure + * @return esp_err_t + */ +esp_err_t tinyusb_cdcacm_init(const tinyusb_config_cdcacm_t *cfg); + +/** + * @brief De-initialize CDC ACM. + * + * @param[in] itf Index of CDC interface + * @return esp_err_t + */ +esp_err_t tinyusb_cdcacm_deinit(int itf); + +/** + * @brief Register a callback invoking on CDC event. If the callback had been + * already registered, it will be overwritten + * + * @param[in] itf Index of CDC interface + * @param[in] event_type Type of registered event for a callback + * @param[in] callback Callback function + * @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG + */ +esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf, + cdcacm_event_type_t event_type, + tusb_cdcacm_callback_t callback); + +/** + * @brief Unregister a callback invoking on CDC event + * + * @param[in] itf Index of CDC interface + * @param[in] event_type Type of registered event for a callback + * @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG + */ +esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf, cdcacm_event_type_t event_type); + +/** + * @brief Sent one character to a write buffer + * + * @param[in] itf Index of CDC interface + * @param[in] ch Character to send + * @return size_t - amount of queued bytes + */ +size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch); + +/** + * @brief Write data to write buffer + * + * @param[in] itf Index of CDC interface + * @param[in] in_buf Data + * @param[in] in_size Data size in bytes + * @return size_t - amount of queued bytes + */ +size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, const uint8_t *in_buf, size_t in_size); + +/** + * @brief Flush data in write buffer of CDC interface + * + * Use `tinyusb_cdcacm_write_queue` to add data to the buffer + * + * WARNING! TinyUSB can block output Endpoint for several RX callbacks, after will do additional flush + * after the each transfer. That can leads to the situation when you requested a flush, but it will fail until + * one of the next callbacks ends. + * SO USING OF THE FLUSH WITH TIMEOUTS IN CALLBACKS IS NOT RECOMMENDED - YOU CAN GET A LOCK FOR THE TIMEOUT + * + * @param[in] itf Index of CDC interface + * @param[in] timeout_ticks Transfer timeout. Set to zero for non-blocking mode + * @return - ESP_OK All data flushed + * - ESP_ERR_TIMEOUT Time out occurred in blocking mode + * - ESP_NOT_FINISHED The transfer is still in progress in non-blocking mode + */ +esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks); + +/** + * @brief Receive data from CDC interface + * + * @param[in] itf Index of CDC interface + * @param[out] out_buf Data buffer + * @param[in] out_buf_sz Data buffer size in bytes + * @param[out] rx_data_size Number of bytes written to out_buf + * @return esp_err_t ESP_OK, ESP_FAIL or ESP_ERR_INVALID_STATE + */ +esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size); + +/** + * @brief Check if the CDC interface is initialized + * + * @param[in] itf Index of CDC interface + * @return - true Initialized + * - false Not Initialized + */ +bool tinyusb_cdcacm_initialized(tinyusb_cdcacm_itf_t itf); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include/tinyusb_console.h b/components/esp_tinyusb/include/tinyusb_console.h new file mode 100644 index 0000000..8782b61 --- /dev/null +++ b/components/esp_tinyusb/include/tinyusb_console.h @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" + +/** + * @brief Redirect output to the USB serial + * @param cdc_intf - interface number of TinyUSB's CDC + * + * @return esp_err_t - ESP_OK, ESP_FAIL or an error code + */ +esp_err_t tinyusb_console_init(int cdc_intf); + +/** + * @brief Switch log to the default output + * @param cdc_intf - interface number of TinyUSB's CDC + * + * @return esp_err_t + */ +esp_err_t tinyusb_console_deinit(int cdc_intf); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include/tinyusb_default_config.h b/components/esp_tinyusb/include/tinyusb_default_config.h new file mode 100644 index 0000000..a1cb971 --- /dev/null +++ b/components/esp_tinyusb/include/tinyusb_default_config.h @@ -0,0 +1,134 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "tinyusb.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define GET_CONFIG_MACRO(dummy, arg1, arg2, arg3, name, ...) name + +/** + * @brief Default TinyUSB Driver configuration structure initializer + * + * Default port: + * - ESP32P4: USB OTG 2.0 (High-speed) + * - ESP32S2/S3: USB OTG 1.1 (Full-speed) + * + * Default size: + * - 4096 bytes + * Default priority: + * - 5 + * + * Default task affinity: + * - Multicore: CPU1 + * - Unicore: CPU0 + * + */ + +#define TINYUSB_DEFAULT_CONFIG(...) GET_CONFIG_MACRO(, ##__VA_ARGS__, \ + TINYUSB_CONFIG_INVALID, \ + TINYUSB_CONFIG_EVENT_ARG, \ + TINYUSB_CONFIG_EVENT, \ + TINYUSB_CONFIG_NO_ARG \ + )(__VA_ARGS__) + +#define TINYUSB_CONFIG_INVALID(...) static_assert(false, "Too many arguments for TINYUSB_DEFAULT_CONFIG") + +#if CONFIG_IDF_TARGET_ESP32P4 +#define TINYUSB_CONFIG_NO_ARG() TINYUSB_CONFIG_HIGH_SPEED(NULL, NULL) +#define TINYUSB_CONFIG_EVENT(event_hdl) TINYUSB_CONFIG_HIGH_SPEED(event_hdl, NULL) +#define TINYUSB_CONFIG_EVENT_ARG(event_hdl, arg) TINYUSB_CONFIG_HIGH_SPEED(event_hdl, arg) +#else +#define TINYUSB_CONFIG_NO_ARG() TINYUSB_CONFIG_FULL_SPEED(NULL, NULL) +#define TINYUSB_CONFIG_EVENT(event_hdl) TINYUSB_CONFIG_FULL_SPEED(event_hdl, NULL) +#define TINYUSB_CONFIG_EVENT_ARG(event_hdl, arg) TINYUSB_CONFIG_FULL_SPEED(event_hdl, arg) +#endif + +#if CONFIG_FREERTOS_UNICORE +#define TINYUSB_DEFAULT_TASK_AFFINITY (0U) +#else +#define TINYUSB_DEFAULT_TASK_AFFINITY (1U) +#endif // CONFIG_FREERTOS_UNICORE + +// Default size for task stack used in TinyUSB task creation +#define TINYUSB_DEFAULT_TASK_SIZE 4096 +// Default priority for task used in TinyUSB task creation +#define TINYUSB_DEFAULT_TASK_PRIO 5 + +#define TINYUSB_CONFIG_FULL_SPEED(event_hdl, arg) \ + (tinyusb_config_t) { \ + .port = TINYUSB_PORT_FULL_SPEED_0, \ + .phy = { \ + .skip_setup = false, \ + .self_powered = false, \ + .vbus_monitor_io = -1, \ + }, \ + .task = TINYUSB_TASK_DEFAULT(), \ + .descriptor = { \ + .device = NULL, \ + .qualifier = NULL, \ + .string = NULL, \ + .string_count = 0, \ + .full_speed_config = NULL, \ + .high_speed_config = NULL, \ + }, \ + .event_cb = (event_hdl), \ + .event_arg = (arg), \ + } + +#define TINYUSB_CONFIG_HIGH_SPEED(event_hdl, arg) \ + (tinyusb_config_t) { \ + .port = TINYUSB_PORT_HIGH_SPEED_0, \ + .phy = { \ + .skip_setup = false, \ + .self_powered = false, \ + .vbus_monitor_io = -1, \ + }, \ + .task = TINYUSB_TASK_DEFAULT(), \ + .descriptor = { \ + .device = NULL, \ + .qualifier = NULL, \ + .string = NULL, \ + .string_count = 0, \ + .full_speed_config = NULL, \ + .high_speed_config = NULL, \ + }, \ + .event_cb = (event_hdl), \ + .event_arg = (arg), \ + } + +#define TINYUSB_TASK_DEFAULT() \ + (tinyusb_task_config_t) { \ + .size = TINYUSB_DEFAULT_TASK_SIZE, \ + .priority = TINYUSB_DEFAULT_TASK_PRIO, \ + .xCoreID = TINYUSB_DEFAULT_TASK_AFFINITY, \ + } + +/** + * @brief TinyUSB Task configuration structure initializer + * + * This macro is used to create a custom TinyUSB Task configuration structure. + * + * @param s Stack size of the task + * @param p Task priority + * @param a Task affinity (CPU core) + */ +#define TINYUSB_TASK_CUSTOM(s, p, a) \ + (tinyusb_task_config_t) { \ + .size = (s), \ + .priority = (p), \ + .xCoreID = (a), \ + } + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include/tinyusb_msc.h b/components/esp_tinyusb/include/tinyusb_msc.h new file mode 100644 index 0000000..cf23fb3 --- /dev/null +++ b/components/esp_tinyusb/include/tinyusb_msc.h @@ -0,0 +1,326 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "soc/soc_caps.h" +#include "esp_err.h" +#include "wear_levelling.h" +#include "esp_vfs_fat.h" +#if (SOC_SDMMC_HOST_SUPPORTED) +#include "driver/sdmmc_host.h" +#endif // SOC_SDMMC_HOST_SUPPORTED + +/** + * @brief Handle for TinyUSB MSC storage + */ +typedef struct tinyusb_msc_storage_s *tinyusb_msc_storage_handle_t; + +/** + * @brief Storage mount point types + */ +typedef enum { + TINYUSB_MSC_STORAGE_MOUNT_USB = 0, /*!< Storage is exclusively used by USB host */ + TINYUSB_MSC_STORAGE_MOUNT_APP, /*!< Storage is used by the application */ +} tinyusb_msc_mount_point_t; + +/** + * @brief MSC event IDs for mount and unmount operations + * + * These events are used to notify the application about the status of the storage mount or unmount operations. + */ +typedef enum { + TINYUSB_MSC_EVENT_MOUNT_START, /*!< Called BEFORE mounting or unmounting the filesystem */ + TINYUSB_MSC_EVENT_MOUNT_COMPLETE, /*!< Called AFTER the mount or unmount operation is complete */ + TINYUSB_MSC_EVENT_MOUNT_FAILED, /*!< Called if the mount operation failed */ + TINYUSB_MSC_EVENT_FORMAT_REQUIRED, /*!< Called when the storage needs to be formatted */ + TINYUSB_MSC_EVENT_FORMAT_FAILED, /*!< Called if the format operation failed */ +} tinyusb_msc_event_id_t; + +/** + * @brief Describes an event passing to the input of a callbacks + */ +typedef struct { + tinyusb_msc_event_id_t id; /*!< Event id */ + tinyusb_msc_mount_point_t mount_point; /*!< Mount point type */ + union { + struct { + + } event_data; /*!< Placeholder for future event data, currently unused */ + // Deprecated in v2.0.0, could be removed in future releases + struct { + bool is_mounted; /*!< Flag indicating if the storage is mounted or not */ + } mount_changed_data __attribute__((deprecated)); /*!< Data for mount changed events */ + } ; +} tinyusb_msc_event_t; + + +typedef struct { + char *base_path; /*!< Filesystem mount path. + * - If NULL, a default mount path from the Kconfig is used. + */ + esp_vfs_fat_mount_config_t config; /*!< FAT filesystem mount configuration. + * Controls auto-formatting, max open files, allocation unit size, etc. + */ + bool do_not_format; /*!< If true, do not format the drive if filesystem is not present. + * - Default is false, meaning the drive will be formatted if no filesystem is found. + */ + BYTE format_flags; /*!< Flags for FAT formatting. + * - A bitwise combination of: + * - FM_FAT (FAT12/16) + * - FM_FAT32 + * - FM_EXFAT (ignored if exFAT not enabled) + * - FM_ANY (default; auto-select based on volume size) + * - FM_SFD (Single FAT partition) + * - Set to 0 to use the default (FM_ANY). + */ +} tinyusb_msc_fatfs_config_t; + +/** + * @brief MSC event callback function type + * + * This callback is invoked when a storage mount or unmount operation is initiated or completed. + */ +typedef void(*tusb_msc_callback_t)(tinyusb_msc_storage_handle_t handle, tinyusb_msc_event_t *event, void *arg); + +/** + * @brief Configuration structure for TinyUSB MSC (Mass Storage Class). + */ +typedef struct { + union { + wl_handle_t wl_handle; /*!< Wear leveling handle for SPI Flash storage. */ +#if (SOC_SDMMC_HOST_SUPPORTED) + sdmmc_card_t *card; /*!< Pointer to the SD/MMC card structure. */ +#endif // SOC_SDMMC_HOST_SUPPORTED + } medium; /*!< Storage medium configuration. + * - For SPI Flash, this is a wear leveling handle. + * - For SD/MMC, this is a pointer to the sdmmc_card_t structure. + */ + tinyusb_msc_fatfs_config_t fat_fs; /*!< FAT filesystem configuration. */ + tinyusb_msc_mount_point_t mount_point; /*!< Specifies who initially owns access to the storage: + + * - TINYUSB_MSC_STORAGE_MOUNT_USB: USB host initially owns the storage (MSC mode). + * - TINYUSB_MSC_STORAGE_MOUNT_APP: Application code initially owns and accesses the storage. + * + * This affects whether the filesystem is mounted for local use or exposed over USB on startup. + * Default value is TINYUSB_MSC_STORAGE_MOUNT_USB. + */ +} tinyusb_msc_storage_config_t; + +typedef struct { + union { + struct { + uint16_t auto_mount_off: 1; + uint16_t reserved15: 15; + }; + uint16_t val; + } user_flags; /*!< Configuration flags for the MSC driver. + * - auto_mount_off: If true, filesystem will not be automatically re-mounted when device connects to or disconnects from USB Host. + * This allows manual control over when the storage is exposed to the USB host. + */ + tusb_msc_callback_t callback; /*!< Callback function invoked on storage events. + * - Called before and after switching access to the storage between USB and App. + * - Called when a mount or unmount operation has not been completed. + */ + + void *callback_arg; /*!< Argument passed to the user callback. + * - Can be used to pass user context or additional data. + */ +} tinyusb_msc_driver_config_t; + +// ----------------------------- Driver API --------------------------------- + +/** + * @brief Install TinyUSB MSC driver + * + * This function initializes the TinyUSB MSC driver with the provided configuration. + * + * @param[in] config Pointer to the configuration structure for TinyUSB MSC driver + * @return + * - ESP_OK: Installation successful + * - ESP_ERR_INVALID_STATE: Driver is already installed + * - ESP_ERR_INVALID_ARG: Invalid input argument + * - ESP_ERR_NO_MEM: Not enough memory to install the driver + */ +esp_err_t tinyusb_msc_install_driver(const tinyusb_msc_driver_config_t *config); + +/** + * @brief Uninstall TinyUSB MSC driver + * + * This function deinitializes the TinyUSB MSC driver and releases any resources allocated during initialization. + * + * @return + * - ESP_OK: Uninstallation successful + * - ESP_ERR_NOT_SUPPORTED: Driver is not installed + * - ESP_ERR_INVALID_STATE: Driver is in incorrect state: storage is still mounted + */ +esp_err_t tinyusb_msc_uninstall_driver(void); + +/** + * @brief Initialize TinyUSB MSC SPI Flash storage + * + * This function initializes the TinyUSB MSC storage interface with SPI Flash as a storage medium. + * + * @param[in] config Pointer to the configuration structure for TinyUSB MSC storage + * @param[out] handle Pointer to the storage handle + * + * @return + * - ESP_OK: Initialization successful + * - ESP_ERR_INVALID_ARG: Invalid input argument + * - ESP_ERR_NOT_SUPPORTED: TinyUSB buffer size is less than the Wear Levelling sector size + * - ESP_ERR_NO_MEM: Not enough memory to initialize storage + * - ESP_FAIL: Failed to map storage to LUN or mount storage + */ +esp_err_t tinyusb_msc_new_storage_spiflash(const tinyusb_msc_storage_config_t *config, tinyusb_msc_storage_handle_t *handle); + +#if (SOC_SDMMC_HOST_SUPPORTED) +/** + * @brief Initialize TinyUSB MSC with SD/MMC storage + * + * This function initializes the TinyUSB MSC storage interface with SD/MMC as the storage medium. + * + * @param[in] config Pointer to the configuration structure for TinyUSB MSC storage with SD/MMC + * @param[out] handle Pointer to the storage handle + * + * @return + * - ESP_OK: Initialization successful + * - ESP_ERR_INVALID_ARG: Invalid input argument + * - ESP_ERR_NO_MEM: Not enough memory to initialize storage + * - ESP_FAIL: Failed to map storage to LUN or mount storage + */ +esp_err_t tinyusb_msc_new_storage_sdmmc(const tinyusb_msc_storage_config_t *config, tinyusb_msc_storage_handle_t *handle); +#endif // SOC_SDMMC_HOST_SUPPORTED + +/** + * @brief Delete TinyUSB MSC Storage + * + * This function deinitializes the TinyUSB MSC storage interface. + * It releases any resources allocated during initialization. + * + * @param[in] handle Storage handle, obtained during storage creation. + * + * @return + * - ESP_OK: Deletion successful + * - ESP_ERR_INVALID_STATE: Driver is not installed, no storage was created, or there are pending deferred writes + * - ESP_ERR_INVALID_ARG: Invalid input argument, handle is NULL + * - ESP_ERR_NOT_FOUND: Storage not found in any LUN + */ +esp_err_t tinyusb_msc_delete_storage(tinyusb_msc_storage_handle_t handle); + +/** + * @brief Set a callback function for MSC storage events + * + * This function allows the user to set a callback that will be invoked + * when a storage event occurs, such as a mount or unmount operation. + * + * @param[in] callback Pointer to the callback function to be invoked on storage events + * @param[in] arg Pointer to an argument that will be passed to the callback function + * + * @return + * - ESP_OK: Callback set successfully + * - ESP_ERR_INVALID_STATE: Driver is not installed + * - ESP_ERR_INVALID_ARG: Invalid input argument, callback is NULL + */ +esp_err_t tinyusb_msc_set_storage_callback(tusb_msc_callback_t callback, void *arg); + +/** + * @brief Format the storage + * + * This function formats the storage media with a FAT filesystem. + * + * @note This function should be called with caution, as it will erase all data on the storage media. + * @note Could be called when only when the storage media is mounted to the application. + * + * @param[in] handle Storage handle, obtained during storage creation. + * + * @return + * - ESP_OK: Storage formatted successfully + * - ESP_ERR_INVALID_STATE: MSC driver is not initialized or storage is not initialized + * - ESP_ERR_INVALID_ARG: Invalid input argument, handle is NULL + * - ESP_ERR_NOT_FOUND: Unexpected filesystem found on the drive + */ +esp_err_t tinyusb_msc_format_storage(tinyusb_msc_storage_handle_t handle); + +/** + * @brief Configure FAT filesystem parameters for the storage media + * + * This function sets the FAT filesystem parameters for the storage media which will be used while mounting and formatting. + * + * @param[in] handle Storage handle, obtained from creating the storage. + * @param[in] fatfs_config Pointer to the FAT filesystem configuration structure. + * + * @return + * - ESP_OK: Base path set successfully + * - ESP_ERR_INVALID_ARG: Invalid input argument + * - ESP_ERR_INVALID_STATE: MSC driver is not initialized or storage is not initialized + */ +esp_err_t tinyusb_msc_config_storage_fat_fs(tinyusb_msc_storage_handle_t handle, tinyusb_msc_fatfs_config_t *fatfs_config); + +/** + * @brief Set the mount point for the storage media + * + * This function sets the mount point for the storage media, which determines whether the storage is exposed to the USB host or used by the application. + * + * @param[in] handle Storage handle, obtained during storage creation. + * @param[in] mount_point The mount point to set, either TINYUSB_MSC_STORAGE_MOUNT_USB or TINYUSB_MSC_STORAGE_MOUNT_APP. + * + * @return + * - ESP_OK: Mount point set successfully + * - ESP_ERR_INVALID_STATE: Driver is not installed or storage wasn't created + */ +esp_err_t tinyusb_msc_set_storage_mount_point(tinyusb_msc_storage_handle_t handle, + tinyusb_msc_mount_point_t mount_point); + +// ------------------------------------ Getters ------------------------------------ + +/** + * @brief Get storage capacity in sectors + * + * @param[in] handle Storage handle, obtained during storage creation. + * @param[out] sector_count Pointer to store the number of sectors in the storage media. + * + * @return + * - ESP_OK: Sector count retrieved successfully + * - ESP_ERR_INVALID_ARG: Invalid input argument, sector_count pointer is NULL + * - ESP_ERR_INVALID_STATE: MSC driver is not initialized or storage is not initialized + */ +esp_err_t tinyusb_msc_get_storage_capacity(tinyusb_msc_storage_handle_t handle, uint32_t *sector_count); + +/** + * @brief Get sector size of storage + * + * @param[in] handle Storage handle, obtained during storage creation. + * @param[out] sector_size Pointer to store the size of the sector in the storage media. + * + * @return + * - ESP_OK: Sector size retrieved successfully + * - ESP_ERR_INVALID_ARG: Invalid input argument, sector_size pointer is NULL + * - ESP_ERR_INVALID_STATE: MSC driver is not initialized or storage is not initialized + */ +esp_err_t tinyusb_msc_get_storage_sector_size(tinyusb_msc_storage_handle_t handle, uint32_t *sector_size); + +/** + * @brief Get status if storage media is exposed over USB to USB Host + * + * @param[in] handle Storage handle, obtained during storage creation. + * @param[out] mount_point Pointer to store the current mount point of the storage media. + * + * @return + * - ESP_OK: Mount point retrieved successfully + * - ESP_ERR_INVALID_ARG: Invalid input argument, mount_point pointer is NULL + * - ESP_ERR_INVALID_STATE: MSC driver is not installed or storage is not initialized + */ +esp_err_t tinyusb_msc_get_storage_mount_point(tinyusb_msc_storage_handle_t handle, + tinyusb_msc_mount_point_t *mount_point); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include/tinyusb_net.h b/components/esp_tinyusb/include/tinyusb_net.h new file mode 100644 index 0000000..76e2ee9 --- /dev/null +++ b/components/esp_tinyusb/include/tinyusb_net.h @@ -0,0 +1,102 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "sdkconfig.h" + +#if (CONFIG_TINYUSB_NET_MODE_NONE != 1) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief On receive callback type + */ +typedef esp_err_t (*tusb_net_rx_cb_t)(void *buffer, uint16_t len, void *ctx); + +/** + * @brief Free Tx buffer callback type + */ +typedef void (*tusb_net_free_tx_cb_t)(void *buffer, void *ctx); + +/** + * @brief On init callback type + */ +typedef void (*tusb_net_init_cb_t)(void *ctx); + +/** + * @brief ESP TinyUSB NCM driver configuration structure + */ +typedef struct { + uint8_t mac_addr[6]; /*!< MAC address. Must be 6 bytes long. */ + tusb_net_rx_cb_t on_recv_callback; /*!< TinyUSB receive data callbeck */ + tusb_net_free_tx_cb_t free_tx_buffer; /*!< User function for freeing the Tx buffer. + * - could be NULL, if user app is responsible for freeing the buffer + * - must be used in asynchronous send mode + * - is only called if the used tinyusb_net_send...() function returns ESP_OK + * - in sync mode means that the packet was accepted by TinyUSB + * - in async mode means that the packet was queued to be processed in TinyUSB task + */ + tusb_net_init_cb_t on_init_callback; /*!< TinyUSB init network callback */ + void *user_context; /*!< User context to be passed to any of the callback */ +} tinyusb_net_config_t; + +/** + * @brief Initialize TinyUSB NET driver + * + * @param[in] cfg Configuration of the driver + * @return esp_err_t + */ +esp_err_t tinyusb_net_init(const tinyusb_net_config_t *cfg); + +/** + * @brief Deinitialize TinyUSB NET driver + */ +void tinyusb_net_deinit(void); + +/** + * @brief TinyUSB NET driver send data synchronously + * + * @note It is possible to use sync and async send interchangeably. + * This function needs some synchronization primitives, so using sync mode (even once) uses more heap + * + * @param[in] buffer USB send data + * @param[in] len Send data len + * @param[in] buff_free_arg Pointer to be passed to the free_tx_buffer() callback + * @param[in] timeout Send data len + * @return ESP_OK on success == packet has been consumed by tusb and would be eventually freed + * by free_tx_buffer() callback (if non null) + * ESP_ERR_TIMEOUT on timeout + * ESP_ERR_INVALID_STATE if tusb not initialized, ESP_ERR_NO_MEM on alloc failure + */ +esp_err_t tinyusb_net_send_sync(void *buffer, uint16_t len, void *buff_free_arg, TickType_t timeout); + +/** + * @brief TinyUSB NET driver send data asynchronously + * + * @note If using asynchronous sends, you must free the buffer using free_tx_buffer() callback. + * @note It is possible to use sync and async send interchangeably. + * @note Async flavor of the send is useful when the USB stack runs faster than the caller, + * since we have no control over the transmitted packets, if they get accepted or discarded. + * + * @param[in] buffer USB send data + * @param[in] len Send data len + * @param[in] buff_free_arg Pointer to be passed to the free_tx_buffer() callback + * @return ESP_OK on success == packet has been consumed by tusb and will be freed + * by free_tx_buffer() callback (if non null) + * ESP_ERR_INVALID_STATE if tusb not initialized + */ +esp_err_t tinyusb_net_send_async(void *buffer, uint16_t len, void *buff_free_arg); + +#endif // (CONFIG_TINYUSB_NET_MODE_NONE != 1) + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include/tinyusb_uvc.h b/components/esp_tinyusb/include/tinyusb_uvc.h new file mode 100644 index 0000000..dd7e90d --- /dev/null +++ b/components/esp_tinyusb/include/tinyusb_uvc.h @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD + * 2026 Daniel Kampert (DanielKampert@Kampis-Elektroecke.de) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include + +#include + +#if (CONFIG_TINYUSB_UVC_ENABLED != 1) +#error "TinyUSB UVC driver must be enabled in menuconfig" +#endif + +/** @brief UVC camera interfaces available. + */ +typedef enum { + TINYUSB_UVC_ITF_0 = 0, +#if CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM + TINYUSB_UVC_ITF_1, +#endif + TINYUSB_UVC_ITF_MAX +} tinyusb_uvc_itf_t; + +/** @brief Types of UVC events. + */ +typedef enum { + UVC_EVENT_FRAME_START, /*!< Frame transmission started */ + UVC_EVENT_FRAME_END, /*!< Frame transmission ended */ + UVC_EVENT_STREAMING_START, /*!< Streaming started by host */ + UVC_EVENT_STREAMING_STOP, /*!< Streaming stopped by host */ +} uvc_event_type_t; + +/** @brief Describes an event passing to the input of a callback + */ +typedef struct { + uvc_event_type_t type; /*!< Event type */ +} uvc_event_t; + +/** @brief UVC callback type + */ +typedef void(*tusb_uvc_callback_t)(int itf, uvc_event_t *event); + +/** @brief Callback for frame buffer request. + * @param itf Interface number + * @param buffer Pointer to store the frame buffer address + * @param buffer_size Pointer to store the frame buffer size + * @return true if buffer is available, false otherwise + */ +typedef bool(*tusb_uvc_fb_request_cb_t)(int itf, uint8_t **buffer, size_t *buffer_size); + +/** @brief Callback for frame buffer return. + * @param itf Interface number + * @param buffer Frame buffer address + */ +typedef void(*tusb_uvc_fb_return_cb_t)(int itf, uint8_t *buffer); + +/** @brief Callback for stop notification. + * @param itf Interface number + */ +typedef void(*tusb_uvc_stop_cb_t)(int itf); + +/** @brief Configuration structure for UVC. + */ +typedef struct { + tinyusb_uvc_itf_t uvc_port; /*!< UVC interface number */ + tusb_uvc_callback_t callback_streaming_start; /*!< Streaming start callback */ + tusb_uvc_callback_t callback_streaming_stop; /*!< Streaming stop callback */ + tusb_uvc_fb_request_cb_t fb_request_cb; /*!< Frame buffer request callback */ + tusb_uvc_fb_return_cb_t fb_return_cb; /*!< Frame buffer return callback */ + tusb_uvc_stop_cb_t stop_cb; /*!< Stop callback */ + const uint8_t *uvc_buffer; /*!< UVC working buffer (read-only) */ + size_t uvc_buffer_size; /*!< UVC buffer size */ +} tinyusb_config_uvc_t; + +/** @brief Initialize UVC interface. + * @param cfg Configuration structure + * @return ESP_OK on success + */ +esp_err_t tinyusb_uvc_init(const tinyusb_config_uvc_t *cfg); + +/** @brief Deinitialize UVC interface. + * @param itf Interface number + * @return ESP_OK on success + */ +esp_err_t tinyusb_uvc_deinit(tinyusb_uvc_itf_t itf); + +/** @brief Check if UVC streaming is active. + * @param itf Interface number + * @return true if streaming is active + */ +bool tinyusb_uvc_streaming_active(tinyusb_uvc_itf_t itf); + +/** @brief Transmit a video frame. + * @param itf Interface number + * @param frame Frame buffer + * @param len Frame length + * @return ESP_OK on success + */ +esp_err_t tinyusb_uvc_transmit_frame(tinyusb_uvc_itf_t itf, uint8_t *frame, size_t len); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include/tusb_cdc_acm.h b/components/esp_tinyusb/include/tusb_cdc_acm.h new file mode 100644 index 0000000..d2d9a12 --- /dev/null +++ b/components/esp_tinyusb/include/tusb_cdc_acm.h @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#pragma message("This header file is deprecated since v2.0.0. Use 'tinyusb_cdc_acm.h' for the new development.") + +#include "deprecated/tusb_cdc_acm.h" diff --git a/components/esp_tinyusb/include/tusb_config.h b/components/esp_tinyusb/include/tusb_config.h new file mode 100644 index 0000000..ee9a5ff --- /dev/null +++ b/components/esp_tinyusb/include/tusb_config.h @@ -0,0 +1,202 @@ +/* + * SPDX-FileCopyrightText: 2019 Ha Thach (tinyusb.org), + * SPDX-FileContributor: 2020-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: MIT + * + * Copyright (c) 2019 Ha Thach (tinyusb.org), + * Additions Copyright (c) 2020, Espressif Systems (Shanghai) PTE LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#pragma once + +#include "tusb_option.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef CONFIG_TINYUSB_CDC_ENABLED +# define CONFIG_TINYUSB_CDC_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_CDC_COUNT +# define CONFIG_TINYUSB_CDC_COUNT 0 +#endif + +#ifndef CONFIG_TINYUSB_MSC_ENABLED +# define CONFIG_TINYUSB_MSC_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_HID_COUNT +# define CONFIG_TINYUSB_HID_COUNT 0 +#endif + +#ifndef CONFIG_TINYUSB_MIDI_COUNT +# define CONFIG_TINYUSB_MIDI_COUNT 0 +#endif + +#ifndef CONFIG_TINYUSB_VENDOR_COUNT +# define CONFIG_TINYUSB_VENDOR_COUNT 0 +#endif + +#ifndef CONFIG_TINYUSB_NET_MODE_ECM_RNDIS +# define CONFIG_TINYUSB_NET_MODE_ECM_RNDIS 0 +#endif + +#ifndef CONFIG_TINYUSB_NET_MODE_NCM +# define CONFIG_TINYUSB_NET_MODE_NCM 0 +#endif + +#ifndef CONFIG_TINYUSB_DFU_MODE_DFU +# define CONFIG_TINYUSB_DFU_MODE_DFU 0 +#endif + +#ifndef CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME +# define CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME 0 +#endif + +#ifndef CONFIG_TINYUSB_BTH_ENABLED +# define CONFIG_TINYUSB_BTH_ENABLED 0 +# define CONFIG_TINYUSB_BTH_ISO_ALT_COUNT 0 +#endif + +#ifndef CONFIG_TINYUSB_DEBUG_LEVEL +# define CONFIG_TINYUSB_DEBUG_LEVEL 0 +#endif + +#define CFG_TUD_ENABLED 1 // TinyUSB Device enabled + +#if (CONFIG_IDF_TARGET_ESP32P4) +#define CFG_TUD_MAX_SPEED OPT_MODE_HIGH_SPEED +#else +#define CFG_TUD_MAX_SPEED OPT_MODE_FULL_SPEED +#endif + +// ------------------------------------------------------------------------ +// DCD DWC2 Mode +// ------------------------------------------------------------------------ +#define CFG_TUD_DWC2_SLAVE_ENABLE 1 // Enable Slave/IRQ by default + +// ------------------------------------------------------------------------ +// DMA & Cache +// ------------------------------------------------------------------------ +#ifdef CONFIG_TINYUSB_MODE_DMA +// DMA Mode has a priority over Slave/IRQ mode and will be used if hardware supports it +#define CFG_TUD_DWC2_DMA_ENABLE 1 // Enable DMA + +#if CONFIG_CACHE_L1_CACHE_LINE_SIZE +// To enable the dcd_dcache clean/invalidate/clean_invalidate calls +# define CFG_TUD_MEM_DCACHE_ENABLE 1 +#define CFG_TUD_MEM_DCACHE_LINE_SIZE CONFIG_CACHE_L1_CACHE_LINE_SIZE +// NOTE: starting with esp-idf v5.3 there is specific attribute present: DRAM_DMA_ALIGNED_ATTR +# define CFG_TUSB_MEM_SECTION __attribute__((aligned(CONFIG_CACHE_L1_CACHE_LINE_SIZE))) DRAM_ATTR +#else +# define CFG_TUD_MEM_CACHE_ENABLE 0 +# define CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) DRAM_ATTR +#endif // CONFIG_CACHE_L1_CACHE_LINE_SIZE +#endif // CONFIG_TINYUSB_MODE_DMA + +#define CFG_TUSB_OS OPT_OS_FREERTOS + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +# define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +# define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) +#endif + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +// Debug Level +#define CFG_TUSB_DEBUG CONFIG_TINYUSB_DEBUG_LEVEL +#define CFG_TUSB_DEBUG_PRINTF esp_rom_printf // TinyUSB can print logs from ISR, so we must use esp_rom_printf() + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE CONFIG_TINYUSB_CDC_RX_BUFSIZE +#define CFG_TUD_CDC_TX_BUFSIZE CONFIG_TINYUSB_CDC_TX_BUFSIZE +#define CFG_TUD_CDC_EP_BUFSIZE CONFIG_TINYUSB_CDC_EP_BUFSIZE + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_BUFSIZE CONFIG_TINYUSB_MSC_BUFSIZE + +// MIDI macros +#define CFG_TUD_MIDI_EP_BUFSIZE 64 +#define CFG_TUD_MIDI_EPSIZE CFG_TUD_MIDI_EP_BUFSIZE +#define CFG_TUD_MIDI_RX_BUFSIZE 64 +#define CFG_TUD_MIDI_TX_BUFSIZE 64 + +// Vendor FIFO size of TX and RX +#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// DFU macros +#define CFG_TUD_DFU_XFER_BUFSIZE CONFIG_TINYUSB_DFU_BUFSIZE + +// Number of BTH ISO alternatives +#define CFG_TUD_BTH_ISO_ALT_COUNT CONFIG_TINYUSB_BTH_ISO_ALT_COUNT + +// UVC (Video Class) configuration +#if CONFIG_TINYUSB_UVC_ENABLED + #if CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM + #define CFG_TUD_VIDEO 2 + #else + #define CFG_TUD_VIDEO 1 + #endif + #define CFG_TUD_VIDEO_STREAMING CFG_TUD_VIDEO + #define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 1023 : 512) +#else + #define CFG_TUD_VIDEO 0 + #define CFG_TUD_VIDEO_STREAMING 0 +#endif + +// Enabled device class driver +#define CFG_TUD_CDC CONFIG_TINYUSB_CDC_COUNT +#define CFG_TUD_MSC CONFIG_TINYUSB_MSC_ENABLED +#define CFG_TUD_HID CONFIG_TINYUSB_HID_COUNT +#define CFG_TUD_MIDI CONFIG_TINYUSB_MIDI_COUNT +#define CFG_TUD_VENDOR CONFIG_TINYUSB_VENDOR_COUNT +#define CFG_TUD_ECM_RNDIS CONFIG_TINYUSB_NET_MODE_ECM_RNDIS +#define CFG_TUD_NCM CONFIG_TINYUSB_NET_MODE_NCM +#define CFG_TUD_DFU CONFIG_TINYUSB_DFU_MODE_DFU +#define CFG_TUD_DFU_RUNTIME CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME +#define CFG_TUD_BTH CONFIG_TINYUSB_BTH_ENABLED + +// NCM NET Mode NTB buffers configuration +#define CFG_TUD_NCM_OUT_NTB_N CONFIG_TINYUSB_NCM_OUT_NTB_BUFFS_COUNT +#define CFG_TUD_NCM_IN_NTB_N CONFIG_TINYUSB_NCM_IN_NTB_BUFFS_COUNT +#define CFG_TUD_NCM_OUT_NTB_MAX_SIZE CONFIG_TINYUSB_NCM_OUT_NTB_BUFF_MAX_SIZE +#define CFG_TUD_NCM_IN_NTB_MAX_SIZE CONFIG_TINYUSB_NCM_IN_NTB_BUFF_MAX_SIZE + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include/tusb_console.h b/components/esp_tinyusb/include/tusb_console.h new file mode 100644 index 0000000..4f7f387 --- /dev/null +++ b/components/esp_tinyusb/include/tusb_console.h @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#pragma message("This header file is deprecated since v2.0.0. Use 'tinyusb_console.h' for the new development.") + +#include "deprecated/tusb_console.h" diff --git a/components/esp_tinyusb/include/tusb_msc_storage.h b/components/esp_tinyusb/include/tusb_msc_storage.h new file mode 100644 index 0000000..ac4e06c --- /dev/null +++ b/components/esp_tinyusb/include/tusb_msc_storage.h @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#warning "This header file is deprecated since v2.0.0. Callback prototype has changed. Use 'tinyusb_msc.h' for the new development and refer to the migration guide for more details." + +#include "deprecated/tusb_msc_storage.h" diff --git a/components/esp_tinyusb/include/vfs_tinyusb.h b/components/esp_tinyusb/include/vfs_tinyusb.h new file mode 100644 index 0000000..9c9ba91 --- /dev/null +++ b/components/esp_tinyusb/include/vfs_tinyusb.h @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "esp_vfs_common.h" // For esp_line_endings_t definitions + +#ifdef __cplusplus +extern "C" { +#endif + +#define VFS_TUSB_MAX_PATH 16 +#define VFS_TUSB_PATH_DEFAULT "/dev/tusb_cdc" + +/** + * @brief Register TinyUSB CDC at VFS with path + * + * Know limitation: + * In case there are multiple CDC interfaces in the system, only one of them can be registered to VFS. + * + * @param[in] cdc_intf Interface number of TinyUSB's CDC + * @param[in] path Path where the CDC will be registered, `/dev/tusb_cdc` will be used if left NULL. + * @return esp_err_t ESP_OK or ESP_FAIL + */ +esp_err_t esp_vfs_tusb_cdc_register(int cdc_intf, char const *path); + +/** + * @brief Unregister TinyUSB CDC from VFS + * + * @param[in] path Path where the CDC will be unregistered if NULL will be used `/dev/tusb_cdc` + * @return esp_err_t ESP_OK or ESP_FAIL + */ +esp_err_t esp_vfs_tusb_cdc_unregister(char const *path); + +/** + * @brief Set the line endings to sent + * + * This specifies the conversion between newlines ('\n', LF) on stdout and line + * endings sent: + * + * - ESP_LINE_ENDINGS_CRLF: convert LF to CRLF + * - ESP_LINE_ENDINGS_CR: convert LF to CR + * - ESP_LINE_ENDINGS_LF: no modification + * + * @param[in] mode line endings to send + */ +void esp_vfs_tusb_cdc_set_tx_line_endings(esp_line_endings_t mode); + +/** + * @brief Set the line endings expected to be received + * + * This specifies the conversion between line endings received and + * newlines ('\n', LF) passed into stdin: + * + * - ESP_LINE_ENDINGS_CRLF: convert CRLF to LF + * - ESP_LINE_ENDINGS_CR: convert CR to LF + * - ESP_LINE_ENDINGS_LF: no modification + * + * @param[in] mode line endings expected + */ +void esp_vfs_tusb_cdc_set_rx_line_endings(esp_line_endings_t mode); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include_private/cdc.h b/components/esp_tinyusb/include_private/cdc.h new file mode 100644 index 0000000..d9d870d --- /dev/null +++ b/components/esp_tinyusb/include_private/cdc.h @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/timers.h" +#include "tusb.h" + +/* CDC classification + ********************************************************************* */ +typedef enum { + TINYUSB_CDC_DATA = 0x00, +} cdc_data_sublcass_type_t; // CDC120 specification + +/* Note:other classification is represented in the file components\tinyusb\tinyusb\src\class\cdc\cdc.h */ + +/*********************************************************************** CDC classification*/ +/* Structs + ********************************************************************* */ +typedef struct { + tusb_class_code_t cdc_class; /*!< CDC device class : Communications or Data device */ + union { + cdc_comm_sublcass_type_t comm_subclass; /*!< Communications device subclasses: ACM, ECM, etc. */ + cdc_data_sublcass_type_t data_subclass; /*!< Data device has only one subclass.*/ + } cdc_subclass; /*!< CDC device subclass according to Class Definitions for Communications Devices the CDC v.1.20 */ +} tinyusb_config_cdc_t; /*!< Main configuration structure of a CDC device */ + +typedef struct { + tusb_class_code_t type; + union { + cdc_comm_sublcass_type_t comm_subclass; /*!< Communications device subclasses: ACM, ECM, etc. */ + cdc_data_sublcass_type_t data_subclass; /*!< Data device has only one subclass.*/ + } cdc_subclass; /*!< CDC device subclass according to Class Definitions for Communications Devices the CDC v.1.20 */ + void *subclass_obj; /*!< Dynamically allocated subclass specific object */ +} esp_tusb_cdc_t; +/*********************************************************************** Structs*/ +/* Functions + ********************************************************************* */ +/** + * @brief Initializing CDC basic object + * @param itf - number of a CDC object + * @param cfg - CDC configuration structure + * + * @return esp_err_t ESP_OK or ESP_FAIL + */ +esp_err_t tinyusb_cdc_init(int itf, const tinyusb_config_cdc_t *cfg); + + +/** + * @brief De-initializing CDC. Clean its objects + * @param itf - number of a CDC object + * @return esp_err_t ESP_OK, ESP_ERR_INVALID_ARG, ESP_ERR_INVALID_STATE + * + */ +esp_err_t tinyusb_cdc_deinit(int itf); + + +/** + * @brief Return interface of a CDC device + * + * @param itf_num + * @return esp_tusb_cdc_t* pointer to the interface or (NULL) on error + */ +esp_tusb_cdc_t *tinyusb_cdc_get_intf(int itf_num); +/*********************************************************************** Functions*/ + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include_private/descriptors_control.h b/components/esp_tinyusb/include_private/descriptors_control.h new file mode 100644 index 0000000..ec7592c --- /dev/null +++ b/components/esp_tinyusb/include_private/descriptors_control.h @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "tinyusb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define USB_STRING_DESCRIPTOR_ARRAY_SIZE 8 // Max 8 string descriptors for a device. LANGID, Manufacturer, Product, Serial number + 4 user defined + +/** + * @brief Check the TinyUSB configuration + * + * @param[in] port TinyUSB port number + * @param[in] config TinyUSB Descriptor configuration + * @retval ESP_ERR_INVALID_ARG if config is NULL or port is invalid + * @retval ESP_ERR_NOT_SUPPORTED if string_count is greater than USB_STRING_DESCRIPTOR_ARRAY_SIZE + * @retval ESP_OK if config is valid + */ +esp_err_t tinyusb_descriptors_check(tinyusb_port_t port, const tinyusb_desc_config_t *config); + +/** + * @brief Parse tinyusb configuration and prepare the device configuration pointer list to configure tinyusb driver + * + * @attention All descriptors passed to this function must exist for the duration of USB device lifetime + * + * @param[in] port TinyUSB port number + * @param[in] config Tinyusb Descriptor configuration. + * @retval ESP_ERR_INVALID_ARG Default configuration descriptor is provided only for CDC, MSC and NCM classes + * @retval ESP_ERR_NO_MEM Memory allocation error + * @retval ESP_OK Descriptors configured without error + */ +esp_err_t tinyusb_descriptors_set(tinyusb_port_t port, const tinyusb_desc_config_t *config); + +/** + * @brief Set specific string descriptor + * + * @attention The descriptor passed to this function must exist for the duration of USB device lifetime + * + * @param[in] str UTF-8 string + * @param[in] str_idx String descriptor index + */ +void tinyusb_descriptors_set_string(const char *str, int str_idx); + +/** + * @brief Free memory allocated during tinyusb_descriptors_set + */ +void tinyusb_descriptors_free(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include_private/msc_storage.h b/components/esp_tinyusb/include_private/msc_storage.h new file mode 100644 index 0000000..cdd1211 --- /dev/null +++ b/components/esp_tinyusb/include_private/msc_storage.h @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "stdint.h" +#include "esp_err.h" +#include "device/usbd_pvt.h" +#include "diskio_impl.h" +#include "vfs_fat_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enumeration for Storage types. + * + * This enumeration defines the possible storage types for the TinyUSB MSC storage. + */ +typedef enum { + STORAGE_MEDIUM_TYPE_SPIFLASH = 0, /*!< Storage type is SPI flash with wear leveling. */ + STORAGE_MEDIUM_TYPE_SDMMC, /*!< Storage type is SDMMC card. */ +} storage_medium_type_t; + +/** + * @brief Storage information structure + * + * This structure contains information about the storage medium, including the total number of sectors + * and the size of each sector. + */ +typedef struct { + uint32_t total_sectors; /*!< Total number of sectors in the storage medium. */ + uint32_t sector_size; /*!< Size of a single sector in bytes. */ +} storage_info_t; + +/** + * @brief Storage medium structure + * + * This structure defines the function pointers for mounting, unmounting, reading, writing, + * and getting information about the storage medium. + */ +typedef struct { + const storage_medium_type_t type; /*!< Type of the storage medium (SPI flash, SDMMC, etc.). */ + esp_err_t (*mount)(BYTE pdrv); /*!< Storage mount function pointer. */ + esp_err_t (*unmount)(void); /*!< Storage unmount function pointer. */ + esp_err_t (*read)(uint32_t lba, uint32_t offset, size_t size, void *dest); /*!< Storage read function pointer. */ + esp_err_t (*write)(uint32_t lba, uint32_t offset, size_t size, const void *src); /*!< Storage write function pointer. */ + esp_err_t (*get_info)(storage_info_t *info); /*!< Storage get information function pointer */ + void (*close)(void); /*!< Storage close function pointer. */ +} storage_medium_t; + +/** + * @brief Mount the storage to the application + * + * This function mounts the storage to the application, allowing it to access the storage medium. + * + * @note This function is for the tud_mount_cb() callback and should not be called directly. + */ +void msc_storage_mount_to_app(void); + +/** + * @brief Unmount the storage from the application + * + * This function unmounts the storage from the application, preventing further access to the storage medium. + * + * @note This function is for the tud_umount_cb() callback and should not be called directly. + */ +void msc_storage_mount_to_usb(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include_private/storage_sdmmc.h b/components/esp_tinyusb/include_private/storage_sdmmc.h new file mode 100644 index 0000000..27a9350 --- /dev/null +++ b/components/esp_tinyusb/include_private/storage_sdmmc.h @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "stdint.h" +#include "esp_err.h" +#include "msc_storage.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Open the storage medium for SDMMC + * + * This function returns a storage API that can be used to interact with the SDMMC storage. + * + * @note Only one SDMMC card can be opened at a time. + * To open a new SDMMC card, the previous one must be closed first. + * + * @param[in] card Pointer to `sdmmc_card_t` structure. + * @param[out] medium Pointer to the storage medium. + * + * @return + * - ESP_OK: Storage API returned successfully. + * - ESP_ERR_INVALID_ARG: Invalid argument, ctx or storage_api is NULL. + */ +esp_err_t storage_sdmmc_open_medium(sdmmc_card_t *card, const storage_medium_t **medium); + + + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include_private/storage_spiflash.h b/components/esp_tinyusb/include_private/storage_spiflash.h new file mode 100644 index 0000000..c35cd75 --- /dev/null +++ b/components/esp_tinyusb/include_private/storage_spiflash.h @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "stdint.h" +#include "esp_err.h" +#include "msc_storage.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Open the storage medium for SPI Flash + * + * This function returns a storage API that can be used to interact with the SPI Flash storage. + * + * @param[in] wl_handle Wear-leveling handle: `wl_handle_t` + * @param[out] medium Pointer to the storage API. + * + * @return + * - ESP_OK: Storage API returned successfully. + * - ESP_ERR_INVALID_ARG: Invalid argument, ctx or storage_api is NULL. + */ +esp_err_t storage_spiflash_open_medium(wl_handle_t wl_handle, const storage_medium_t **medium); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include_private/tinyusb_task.h b/components/esp_tinyusb/include_private/tinyusb_task.h new file mode 100644 index 0000000..f030263 --- /dev/null +++ b/components/esp_tinyusb/include_private/tinyusb_task.h @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "tinyusb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Check the TinyUSB Task configuration + * + * This function checks the TinyUSB Task configuration parameters. + * + * @param task_cfg TinyUSB Task configuration + * @retval + * - ESP_ERR_INVALID_ARG if task_cfg is NULL, size is 0, priority is 0 or affinity is invalid + * - ESP_OK if task_cfg is valid + */ +esp_err_t tinyusb_task_check_config(const tinyusb_task_config_t *task_cfg); + +/** + * @brief Starts TinyUSB Task + * + * Prepares TinyUSB Task for running TinyUSB stack. + * + * @param port USB Peripheral hardware port number. Available when hardware has several available peripherals. + * @param task_cfg TinyUSB Task configuration. + * @param desc_cfg USB device descriptor configuration. + * + * @retval + * - ESP_ERR_INVALID_STATE if TinyUSB Task already started + * - ESP_ERR_INVALID_ARG if task_cfg is NULL or init_params is NULL when init_in_task is true + * - ESP_ERR_TIMEOUT if task was not able to start TinyUSB stack + * - ESP_ERR_NOT_FINISHED if TinyUSB task creation failed + * - ESP_ERR_NO_MEM if memory allocation failed + * - ESP_OK if TinyUSB Task initialized successfully + */ +esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *task_cfg, const tinyusb_desc_config_t *desc_cfg); + +/** + * @brief Stops TinyUSB Task + * + * @note function should be called only when TinyUSB task was initialized via tinyusb_task_start() + * + * @retval + * - ESP_ERR_INVALID_STATE if TinyUSB Task not initialized + * - ESP_OK if TinyUSB Task deinitialized successfully + */ +esp_err_t tinyusb_task_stop(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include_private/usb_descriptors.h b/components/esp_tinyusb/include_private/usb_descriptors.h new file mode 100644 index 0000000..be1979a --- /dev/null +++ b/components/esp_tinyusb/include_private/usb_descriptors.h @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "tusb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Device descriptor generated from Kconfig + * + * This descriptor is used by default. + * The user can provide their own device descriptor via tinyusb_driver_install() call + */ +extern const tusb_desc_device_t descriptor_dev_default; + +#if (TUD_OPT_HIGH_SPEED) +/** + * @brief Qualifier Device descriptor generated from Kconfig + * + * This descriptor is used by default. + * The user can provide their own descriptor via tinyusb_driver_install() call + */ +extern const tusb_desc_device_qualifier_t descriptor_qualifier_default; +#endif // TUD_OPT_HIGH_SPEED + +/** + * @brief Array of string descriptors generated from Kconfig + * + * This descriptor is used by default. + * The user can provide their own descriptor via tinyusb_driver_install() call + */ +extern const char *descriptor_str_default[]; + +/** + * @brief FullSpeed configuration descriptor generated from Kconfig + * This descriptor is used by default. + * The user can provide their own FullSpeed configuration descriptor via tinyusb_driver_install() call + */ +extern const uint8_t descriptor_fs_cfg_default[]; + +#if (TUD_OPT_HIGH_SPEED) +/** + * @brief HighSpeed Configuration descriptor generated from Kconfig + * + * This descriptor is used by default. + * The user can provide their own HighSpeed configuration descriptor via tinyusb_driver_install() call + */ +extern const uint8_t descriptor_hs_cfg_default[]; +#endif // TUD_OPT_HIGH_SPEED + +uint8_t tusb_get_mac_string_id(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include_private/uvc.h b/components/esp_tinyusb/include_private/uvc.h new file mode 100644 index 0000000..f3d374b --- /dev/null +++ b/components/esp_tinyusb/include_private/uvc.h @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD + * 2026 Daniel Kampert (DanielKampert@Kampis-Elektroecke.de) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "tusb.h" +#include "tinyusb_uvc.h" + +/** + * @brief UVC device state structure + */ +typedef struct { + bool initialized; /*!< Initialization flag */ + bool streaming; /*!< Streaming active flag */ + tusb_uvc_callback_t callback_streaming_start; /*!< Streaming start callback */ + tusb_uvc_callback_t callback_streaming_stop; /*!< Streaming stop callback */ + tusb_uvc_fb_request_cb_t fb_request_cb; /*!< Frame buffer request callback */ + tusb_uvc_fb_return_cb_t fb_return_cb; /*!< Frame buffer return callback */ + tusb_uvc_stop_cb_t stop_cb; /*!< Stop callback */ + uint8_t *uvc_buffer; /*!< UVC working buffer */ + size_t uvc_buffer_size; /*!< UVC buffer size */ + uint32_t interval_ms; /*!< Frame interval in ms */ + TaskHandle_t uvc_task_hdl; /*!< UVC task handle */ + EventGroupHandle_t event_group; /*!< Event group for synchronization */ + uint8_t *current_frame_buffer; /*!< Currently transmitting frame buffer */ +} esp_tusb_uvc_t; + +/** + * @brief Initialize UVC basic object + * + * @param itf Interface number + * @return esp_err_t ESP_OK on success + */ +esp_err_t tinyusb_uvc_init_itf(tinyusb_uvc_itf_t itf); + +/** + * @brief Deinitialize UVC + * + * @param itf Interface number + * @return esp_err_t ESP_OK on success + */ +esp_err_t tinyusb_uvc_deinit_itf(tinyusb_uvc_itf_t itf); + +/** + * @brief Get UVC interface instance + * + * @param itf Interface number + * @return esp_tusb_uvc_t* Pointer to UVC instance or NULL + */ +esp_tusb_uvc_t *tinyusb_uvc_get_intf(tinyusb_uvc_itf_t itf); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_tinyusb/include_private/uvc_descriptors.h b/components/esp_tinyusb/include_private/uvc_descriptors.h new file mode 100644 index 0000000..7fb54d5 --- /dev/null +++ b/components/esp_tinyusb/include_private/uvc_descriptors.h @@ -0,0 +1,670 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Jerzy Kasenbreg + * Copyright (c) 2021 Koji KITAYAMA + * Copyright (c) 2023 Espressif + * Copyright (c) 2026 Daniel Kampert (DanielKampert@Kampis-Elektroecke.de) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _USB_DESCRIPTORS_H_ +#define _USB_DESCRIPTORS_H_ + +#include "uvc_frame_config.h" + +/* Time stamp base clock. It is a deprecated parameter. */ +#define UVC_CLOCK_FREQUENCY 27000000 + +/* video capture path */ +#define UVC_ENTITY_CAP_INPUT_TERMINAL 0x01 +#define UVC_ENTITY_CAP_OUTPUT_TERMINAL 0x02 + +/* Interface numbers will be defined in usb_descriptors.c */ +#if (CFG_TUD_VIDEO) +#define TUD_VIDEO_CAPTURE_DESC_UNCOMPR_LEN (\ + TUD_VIDEO_DESC_IAD_LEN\ + /* control */\ + + TUD_VIDEO_DESC_STD_VC_LEN\ + + (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\ + + TUD_VIDEO_DESC_CAMERA_TERM_LEN\ + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN\ + /* Interface 1, Alternate 0 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\ + + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\ + /* Interface 1, Alternate 1 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + 7/* Endpoint */\ + ) + +#define TUD_VIDEO_CAPTURE_DESC_MJPEG_LEN (\ + TUD_VIDEO_DESC_IAD_LEN\ + /* control */\ + + TUD_VIDEO_DESC_STD_VC_LEN\ + + (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\ + + TUD_VIDEO_DESC_CAMERA_TERM_LEN\ + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN\ + /* Interface 1, Alternate 0 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\ + + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\ + /* Interface 1, Alternate 1 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + 7/* Endpoint */\ + ) + +#define TUD_VIDEO_CAPTURE_DESC_MULTI_MJPEG_LEN(n) (\ + TUD_VIDEO_DESC_IAD_LEN\ + /* control */\ + + TUD_VIDEO_DESC_STD_VC_LEN\ + + (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\ + + TUD_VIDEO_DESC_CAMERA_TERM_LEN\ + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN\ + /* Interface 1, Alternate 0 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\ + + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN\ + + (n*TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN)\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\ + /* Interface 1, Alternate 1 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + 7/* Endpoint */\ + ) + +#define TUD_VIDEO_CAPTURE_DESC_UNCOMPR_BULK_LEN (\ + TUD_VIDEO_DESC_IAD_LEN\ + /* control */\ + + TUD_VIDEO_DESC_STD_VC_LEN\ + + (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\ + + TUD_VIDEO_DESC_CAMERA_TERM_LEN\ + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN\ + /* Interface 1, Alternate 0 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\ + + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\ + + 7/* Endpoint */\ + ) + +#define TUD_VIDEO_CAPTURE_DESC_MJPEG_BULK_LEN (\ + TUD_VIDEO_DESC_IAD_LEN\ + /* control */\ + + TUD_VIDEO_DESC_STD_VC_LEN\ + + (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\ + + TUD_VIDEO_DESC_CAMERA_TERM_LEN\ + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN\ + /* Interface 1, Alternate 0 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\ + + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\ + + 7/* Endpoint */\ + ) + +#define TUD_VIDEO_CAPTURE_DESC_MULTI_MJPEG_BULK_LEN(n) (\ + TUD_VIDEO_DESC_IAD_LEN\ + /* control */\ + + TUD_VIDEO_DESC_STD_VC_LEN\ + + (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\ + + TUD_VIDEO_DESC_CAMERA_TERM_LEN\ + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN\ + /* Interface 1, Alternate 0 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\ + + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN\ + + (n*TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN)\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\ + + 7/* Endpoint */\ + ) + +#define TUD_VIDEO_CAPTURE_DESC_MULTI_FRAME_BASED_BULK_LEN(n) (\ + TUD_VIDEO_DESC_IAD_LEN\ + /* control */\ + + TUD_VIDEO_DESC_STD_VC_LEN\ + + (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\ + + TUD_VIDEO_DESC_CAMERA_TERM_LEN\ + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN\ + /* Interface 1, Alternate 0 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\ + + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN\ + + (n*TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN)\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\ + + 7/* Endpoint */\ + ) + +#define TUD_VIDEO_CAPTURE_DESC_FRAME_BASED_BULK_LEN (\ + TUD_VIDEO_DESC_IAD_LEN\ + /* control */\ + + TUD_VIDEO_DESC_STD_VC_LEN\ + + (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\ + + TUD_VIDEO_DESC_CAMERA_TERM_LEN\ + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN\ + /* Interface 1, Alternate 0 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\ + + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\ + + 7/* Endpoint */\ + ) + +#define TUD_VIDEO_CAPTURE_DESC_FRAME_BASED_LEN (\ + TUD_VIDEO_DESC_IAD_LEN\ + /* control */\ + + TUD_VIDEO_DESC_STD_VC_LEN\ + + (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\ + + TUD_VIDEO_DESC_CAMERA_TERM_LEN\ + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN\ + /* Interface 1, Alternate 0 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\ + + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\ + /* Interface 1, Alternate 1 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + 7/* Endpoint */\ + ) + +#define TUD_VIDEO_CAPTURE_DESC_MULTI_FRAME_BASED_LEN(n) (\ + TUD_VIDEO_DESC_IAD_LEN\ + /* control */\ + + TUD_VIDEO_DESC_STD_VC_LEN\ + + (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\ + + TUD_VIDEO_DESC_CAMERA_TERM_LEN\ + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN\ + /* Interface 1, Alternate 0 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\ + + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN\ + + (n*TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN)\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\ + /* Interface 1, Alternate 1 */\ + + TUD_VIDEO_DESC_STD_VS_LEN\ + + 7/* Endpoint */\ + ) + +/* Windows support YUY2 and NV12 + * https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/usb-video-class-driver-overview */ + +#define TUD_VIDEO_DESC_CS_VS_FMT_YUY2(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \ + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_YUY2, 16, _frmidx, _asrx, _asry, _interlace, _cp) +#define TUD_VIDEO_DESC_CS_VS_FMT_NV12(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \ + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_NV12, 12, _frmidx, _asrx, _asry, _interlace, _cp) +#define TUD_VIDEO_DESC_CS_VS_FMT_M420(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \ + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_M420, 12, _frmidx, _asrx, _asry, _interlace, _cp) +#define TUD_VIDEO_DESC_CS_VS_FMT_I420(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \ + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_I420, 12, _frmidx, _asrx, _asry, _interlace, _cp) + +#define TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_TEMPLATE(bCamIndex ,bFrameIndex) \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT(bFrameIndex, 0, UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].width, UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].height, \ + UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].width * UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].height * 16, UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].width * UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].height * 16 * UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].rate, \ + UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].width * UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].height * 16 / 8, \ + (10000000/UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].rate), (10000000/UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].rate), (10000000/UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].rate), (10000000/UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].rate)) + +#define TUD_VIDEO_DESC_CS_VS_FRM_H264_CONT_TEMPLATE(bCamIndex ,bFrameIndex) \ + TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT(bFrameIndex, 0, UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].width, UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].height, \ + UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].width * UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].height * 16, UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].width * UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].height * 16 * UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].rate, \ + (10000000/UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].rate), /*bytesPreLine*/ 0, (10000000/UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].rate), (10000000/UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].rate), (10000000/UVC_FRAMES_INFO[bCamIndex][bFrameIndex-1].rate)) + +#define TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(_stridx, _itf,_epin, _width, _height, _fps, _epsize) \ + TUD_VIDEO_DESC_IAD(_itf, /* 2 Interfaces */ 0x02, _stridx), \ + /* Video control 0 */ \ + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ + TUD_VIDEO_DESC_CS_VC( /* UVC 1.5*/ 0x0150, \ + /* wTotalLength - bLength */ \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \ + UVC_CLOCK_FREQUENCY, _itf + 1), \ + TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0,\ + /*wObjectiveFocalLengthMin*/0, /*wObjectiveFocalLengthMax*/0,\ + /*wObjectiveFocalLength*/0, /*bmControls*/0), \ + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \ + /* Video stream alt. 0 */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 0, 0, _stridx), \ + /* Video stream header for without still image capture */ \ + TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \ + /*wTotalLength - bLength */\ + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\ + _epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \ + /*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \ + /*bmaControls(1)*/0), \ + /* Video stream format */ \ + TUD_VIDEO_DESC_CS_VS_FMT_YUY2(/*bFormatIndex*/1, /*bNumFrameDescriptors*/1, \ + /*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0), \ + /* Video stream frame format */ \ + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT(/*bFrameIndex */1, 0, _width, _height, \ + _width * _height * 16, _width * _height * 16 * _fps, \ + _width * _height * 16, \ + (10000000/_fps), (10000000/_fps), (10000000/_fps)*_fps, (10000000/_fps)), \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \ + /* VS alt 1 */\ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 1, 1, _stridx), \ + /* EP */ \ + TUD_VIDEO_DESC_EP_ISO(_epin, _epsize, 1) + +#define TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG(_stridx, _itf, _epin, _width, _height, _fps, _epsize) \ + TUD_VIDEO_DESC_IAD(_itf, /* 2 Interfaces */ 0x02, _stridx), \ + /* Video control 0 */ \ + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ + TUD_VIDEO_DESC_CS_VC( /* UVC 1.5*/ 0x0150, \ + /* wTotalLength - bLength */ \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \ + UVC_CLOCK_FREQUENCY, _itf + 1), \ + TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0,\ + /*wObjectiveFocalLengthMin*/0, /*wObjectiveFocalLengthMax*/0,\ + /*wObjectiveFocalLength*/0, /*bmControls*/0), \ + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \ + /* Video stream alt. 0 */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 0, 0, _stridx), \ + /* Video stream header for without still image capture */ \ + TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \ + /*wTotalLength - bLength */\ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\ + _epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \ + /*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \ + /*bmaControls(1)*/0), \ + /* Video stream format */ \ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG(/*bFormatIndex*/1, /*bNumFrameDescriptors*/1, \ + /*bmFlags*/0, /*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0), \ + /* Video stream frame format */ \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT(/*bFrameIndex */1, 0, _width, _height, \ + _width * _height * 16, _width * _height * 16 * _fps, \ + _width * _height * 16 / 8, \ + (10000000/_fps), (10000000/_fps), (10000000/_fps)*_fps, (10000000/_fps)), \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \ + /* VS alt 1 */\ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 1, 1, _stridx), \ + /* EP */ \ + TUD_VIDEO_DESC_EP_ISO(_epin, _epsize, 1) + +#define TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG(_stridx, _itf, _epin, _epsize) \ + TUD_VIDEO_DESC_IAD(_itf, /* 2 Interfaces */ 0x02, _stridx), \ + /* Video control 0 */ \ + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ + TUD_VIDEO_DESC_CS_VC( /* UVC 1.5*/ 0x0150, \ + /* wTotalLength - bLength */ \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \ + UVC_CLOCK_FREQUENCY, _itf + 1), \ + TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0,\ + /*wObjectiveFocalLengthMin*/0, /*wObjectiveFocalLengthMax*/0,\ + /*wObjectiveFocalLength*/0, /*bmControls*/0), \ + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \ + /* Video stream alt. 0 */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 0, 0, _stridx), \ + /* Video stream header for without still image capture */ \ + TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \ + /*wTotalLength - bLength */\ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN\ + + (UVC_FRAME_NUM*TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN)\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\ + _epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \ + /*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \ + /*bmaControls(1)*/0), \ + /* Video stream format */ \ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG(/*bFormatIndex*/1, /*bNumFrameDescriptors*/UVC_FRAME_NUM, \ + /*bmFlags*/0, /*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0), \ + /* Video stream frame format */ \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_TEMPLATE(_itf/2, 1), \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_TEMPLATE(_itf/2, 2), \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_TEMPLATE(_itf/2, 3), \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_TEMPLATE(_itf/2, 4), \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \ + /* VS alt 1 */\ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 1, 1, _stridx), \ + /* EP */ \ + TUD_VIDEO_DESC_EP_ISO(_epin, _epsize, 1) + +#define TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(_stridx, _itf, _epin, _width, _height, _fps, _epsize) \ + TUD_VIDEO_DESC_IAD(_itf, /* 2 Interfaces */ 0x02, _stridx), \ + /* Video control 0 */ \ + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ + TUD_VIDEO_DESC_CS_VC( /* UVC 1.5*/ 0x0150, \ + /* wTotalLength - bLength */ \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \ + UVC_CLOCK_FREQUENCY, _itf + 1), \ + TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0,\ + /*wObjectiveFocalLengthMin*/0, /*wObjectiveFocalLengthMax*/0,\ + /*wObjectiveFocalLength*/0, /*bmControls*/0), \ + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \ + /* Video stream alt. 0 */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 0, 1, _stridx), \ + /* Video stream header for without still image capture */ \ + TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \ + /*wTotalLength - bLength */\ + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\ + _epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \ + /*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \ + /*bmaControls(1)*/0), \ + /* Video stream format */ \ + TUD_VIDEO_DESC_CS_VS_FMT_YUY2(/*bFormatIndex*/1, /*bNumFrameDescriptors*/1, \ + /*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0), \ + /* Video stream frame format */ \ + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT(/*bFrameIndex */1, 0, _width, _height, \ + _width * _height * 16, _width * _height * 16 * _fps, \ + _width * _height * 16, \ + (10000000/_fps), (10000000/_fps), (10000000/_fps)*_fps, (10000000/_fps)), \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \ + TUD_VIDEO_DESC_EP_BULK(_epin, _epsize, 1) + +#define TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG_BULK(_stridx, _itf, _epin, _width, _height, _fps, _epsize) \ + TUD_VIDEO_DESC_IAD(_itf, /* 2 Interfaces */ 0x02, _stridx), \ + /* Video control 0 */ \ + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ + TUD_VIDEO_DESC_CS_VC( /* UVC 1.5*/ 0x0150, \ + /* wTotalLength - bLength */ \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \ + UVC_CLOCK_FREQUENCY, _itf + 1), \ + TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0,\ + /*wObjectiveFocalLengthMin*/0, /*wObjectiveFocalLengthMax*/0,\ + /*wObjectiveFocalLength*/0, /*bmControls*/0), \ + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \ + /* Video stream alt. 0 */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 0, 1, _stridx), \ + /* Video stream header for without still image capture */ \ + TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \ + /*wTotalLength - bLength */\ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\ + _epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \ + /*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \ + /*bmaControls(1)*/0), \ + /* Video stream format */ \ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG(/*bFormatIndex*/1, /*bNumFrameDescriptors*/1, \ + /*bmFlags*/0, /*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0), \ + /* Video stream frame format */ \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT(/*bFrameIndex */1, 0, _width, _height, \ + _width * _height * 16, _width * _height * 16 * _fps, \ + _width * _height * 16 / 8, \ + (10000000/_fps), (10000000/_fps), (10000000/_fps), (10000000/_fps)), \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \ + /* EP */ \ + TUD_VIDEO_DESC_EP_BULK(_epin, _epsize, 1) + +#define TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG_BULK(_stridx, _itf, _epin, _epsize) \ + TUD_VIDEO_DESC_IAD(_itf, /* 2 Interfaces */ 0x02, _stridx), \ + /* Video control 0 */ \ + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ + TUD_VIDEO_DESC_CS_VC( /* UVC 1.5*/ 0x0150, \ + /* wTotalLength - bLength */ \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \ + UVC_CLOCK_FREQUENCY, _itf + 1), \ + TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0,\ + /*wObjectiveFocalLengthMin*/0, /*wObjectiveFocalLengthMax*/0,\ + /*wObjectiveFocalLength*/0, /*bmControls*/0), \ + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \ + /* Video stream alt. 0 */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 0, 1, _stridx), \ + /* Video stream header for without still image capture */ \ + TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \ + /*wTotalLength - bLength */\ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN\ + + (UVC_FRAME_NUM*TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN)\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\ + _epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \ + /*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \ + /*bmaControls(1)*/0), \ + /* Video stream format */ \ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG(/*bFormatIndex*/1, /*bNumFrameDescriptors*/UVC_FRAME_NUM, \ + /*bmFlags*/0, /*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0), \ + /* Video stream frame format */ \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_TEMPLATE(_itf/2, 1), \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_TEMPLATE(_itf/2, 2), \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_TEMPLATE(_itf/2, 3), \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_TEMPLATE(_itf/2, 4), \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \ + /* EP */ \ + TUD_VIDEO_DESC_EP_BULK(_epin, _epsize, 1) + +#define TUD_VIDEO_CAPTURE_DESCRIPTOR_H264(_stridx, _itf, _epin, _width, _height, _fps, _epsize) \ + TUD_VIDEO_DESC_IAD(_itf, /* 2 Interfaces */ 0x02, _stridx), \ + /* Video control 0 */ \ + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ + TUD_VIDEO_DESC_CS_VC( /* UVC 1.5*/ 0x0150, \ + /* wTotalLength - bLength */ \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \ + UVC_CLOCK_FREQUENCY, _itf + 1), \ + TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0,\ + /*wObjectiveFocalLengthMin*/0, /*wObjectiveFocalLengthMax*/0,\ + /*wObjectiveFocalLength*/0, /*bmControls*/0), \ + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \ + /* Video stream alt. 0 */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 0, 0, _stridx), \ + /* Video stream header for without still image capture */ \ + TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \ + /*wTotalLength - bLength */\ + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\ + _epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \ + /*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \ + /*bmaControls(1)*/0), \ + /* Video stream format */ \ + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED(/*bFormatIndex*/1, /*bNumFrameDescriptors*/1, \ + /*guid*/TUD_VIDEO_GUID_H264, /*bitsPerPix*/16, /*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0, /*variableSize*/1), \ + /* Video stream frame format */ \ + TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT(/*bFrameIndex */1, 0, _width, _height, \ + _width * _height * 16, _width * _height * 16 * _fps, \ + (10000000/_fps), /*bytesPreLine*/ 0, \ + (10000000/_fps), (10000000/_fps)*_fps, (10000000/_fps)), \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \ + /* VS alt 1 */\ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 1, 1, _stridx), \ + /* EP */ \ + TUD_VIDEO_DESC_EP_ISO(_epin, _epsize, 1) + +#define TUD_VIDEO_CAPTURE_DESCRIPTOR_H264_BULK(_stridx, _itf, _epin, _width, _height, _fps, _epsize) \ + TUD_VIDEO_DESC_IAD(_itf, /* 2 Interfaces */ 0x02, _stridx), \ + /* Video control 0 */ \ + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ + TUD_VIDEO_DESC_CS_VC( /* UVC 1.5*/ 0x0150, \ + /* wTotalLength - bLength */ \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \ + UVC_CLOCK_FREQUENCY, _itf + 1), \ + TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0,\ + /*wObjectiveFocalLengthMin*/0, /*wObjectiveFocalLengthMax*/0,\ + /*wObjectiveFocalLength*/0, /*bmControls*/0), \ + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \ + /* Video stream alt. 0 */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 0, 1, _stridx), \ + /* Video stream header for without still image capture */ \ + TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \ + /*wTotalLength - bLength */\ + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN\ + + TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\ + _epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \ + /*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \ + /*bmaControls(1)*/0), \ + /* Video stream format */ \ + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED(/*bFormatIndex*/1, /*bNumFrameDescriptors*/1, \ + /*guid*/TUD_VIDEO_GUID_H264, /*bitsPerPix*/16, /*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0, /*variableSize*/1), \ + /* Video stream frame format */ \ + TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT(/*bFrameIndex */1, 0, _width, _height, \ + _width * _height * 16, _width * _height * 16 * _fps, \ + (10000000/_fps), /*bytesPreLine*/ 0, \ + (10000000/_fps), (10000000/_fps), (10000000/_fps)), \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \ + /* EP */ \ + TUD_VIDEO_DESC_EP_BULK(_epin, _epsize, 1) + +#define TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264(_stridx, _itf, _epin, _epsize) \ + TUD_VIDEO_DESC_IAD(_itf, /* 2 Interfaces */ 0x02, _stridx), \ + /* Video control 0 */ \ + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ + TUD_VIDEO_DESC_CS_VC( /* UVC 1.5*/ 0x0150, \ + /* wTotalLength - bLength */ \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \ + UVC_CLOCK_FREQUENCY, _itf + 1), \ + TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0,\ + /*wObjectiveFocalLengthMin*/0, /*wObjectiveFocalLengthMax*/0,\ + /*wObjectiveFocalLength*/0, /*bmControls*/0), \ + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \ + /* Video stream alt. 0 */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 0, 0, _stridx), \ + /* Video stream header for without still image capture */ \ + TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \ + /*wTotalLength - bLength */\ + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN\ + + (UVC_FRAME_NUM*TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN)\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\ + _epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \ + /*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \ + /*bmaControls(1)*/0), \ + /* Video stream format */ \ + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED(/*bFormatIndex*/1, /*bNumFrameDescriptors*/UVC_FRAME_NUM, \ + /*guid*/TUD_VIDEO_GUID_H264, /*bitsPerPix*/16, /*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0, /*variableSize*/1), \ + /* Video stream frame format */ \ + TUD_VIDEO_DESC_CS_VS_FRM_H264_CONT_TEMPLATE(_itf/2, 1), \ + TUD_VIDEO_DESC_CS_VS_FRM_H264_CONT_TEMPLATE(_itf/2, 2), \ + TUD_VIDEO_DESC_CS_VS_FRM_H264_CONT_TEMPLATE(_itf/2, 3), \ + TUD_VIDEO_DESC_CS_VS_FRM_H264_CONT_TEMPLATE(_itf/2, 4), \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \ + /* VS alt 1 */\ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 1, 1, _stridx), \ + /* EP */ \ + TUD_VIDEO_DESC_EP_ISO(_epin, _epsize, 1) + +#define TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264_BULK(_stridx, _itf, _epin, _epsize) \ + TUD_VIDEO_DESC_IAD(_itf, /* 2 Interfaces */ 0x02, _stridx), \ + /* Video control 0 */ \ + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ + TUD_VIDEO_DESC_CS_VC( /* UVC 1.5*/ 0x0150, \ + /* wTotalLength - bLength */ \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \ + UVC_CLOCK_FREQUENCY, _itf + 1), \ + TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0,\ + /*wObjectiveFocalLengthMin*/0, /*wObjectiveFocalLengthMax*/0,\ + /*wObjectiveFocalLength*/0, /*bmControls*/0), \ + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \ + /* Video stream alt. 0 */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 0, 1, _stridx), \ + /* Video stream header for without still image capture */ \ + TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \ + /*wTotalLength - bLength */\ + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN\ + + (UVC_FRAME_NUM*TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN)\ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\ + _epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \ + /*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \ + /*bmaControls(1)*/0), \ + /* Video stream format */ \ + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED(/*bFormatIndex*/1, /*bNumFrameDescriptors*/UVC_FRAME_NUM, \ + /*guid*/TUD_VIDEO_GUID_H264, /*bitsPerPix*/16, /*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0, /*variableSize*/1), \ + /* Video stream frame format */ \ + TUD_VIDEO_DESC_CS_VS_FRM_H264_CONT_TEMPLATE(_itf/2, 1), \ + TUD_VIDEO_DESC_CS_VS_FRM_H264_CONT_TEMPLATE(_itf/2, 2), \ + TUD_VIDEO_DESC_CS_VS_FRM_H264_CONT_TEMPLATE(_itf/2, 3), \ + TUD_VIDEO_DESC_CS_VS_FRM_H264_CONT_TEMPLATE(_itf/2, 4), \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \ + /* EP */ \ + TUD_VIDEO_DESC_EP_BULK(_epin, _epsize, 1) + +/* Camera 1 descriptor length based on configuration */ +#if CONFIG_TINYUSB_UVC_CAM1_BULK_MODE + #if CONFIG_TINYUSB_UVC_CAM1_MULTI_FRAMESIZE + #if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + #define TUD_CAM1_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MULTI_MJPEG_BULK_LEN(4) + #elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + #define TUD_CAM1_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MULTI_FRAME_BASED_BULK_LEN(4) + #endif + #else + #if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + #define TUD_CAM1_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MJPEG_BULK_LEN + #elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + #define TUD_CAM1_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_FRAME_BASED_BULK_LEN + #else + #define TUD_CAM1_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_UNCOMPR_BULK_LEN + #endif + #endif +#else + #if CONFIG_TINYUSB_UVC_CAM1_MULTI_FRAMESIZE + #if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + #define TUD_CAM1_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MULTI_MJPEG_LEN(4) + #elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + #define TUD_CAM1_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MULTI_FRAME_BASED_LEN(4) + #endif + #else + #if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + #define TUD_CAM1_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MJPEG_LEN + #elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + #define TUD_CAM1_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_FRAME_BASED_LEN + #else + #define TUD_CAM1_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_UNCOMPR_LEN + #endif + #endif +#endif + +/* Camera 2 descriptor length based on configuration */ +#if CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM +#if CONFIG_TINYUSB_UVC_CAM2_BULK_MODE + #if CONFIG_TINYUSB_UVC_CAM2_MULTI_FRAMESIZE + #if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + #define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MULTI_MJPEG_BULK_LEN(4) + #elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + #define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MULTI_FRAME_BASED_BULK_LEN(4) + #endif + #else + #if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + #define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MJPEG_BULK_LEN + #elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + #define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_FRAME_BASED_BULK_LEN + #else + #define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_UNCOMPR_BULK_LEN + #endif + #endif +#else + #if CONFIG_TINYUSB_UVC_CAM2_MULTI_FRAMESIZE + #if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + #define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MULTI_MJPEG_LEN(4) + #elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + #define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MULTI_FRAME_BASED_LEN(4) + #endif + #else + #if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + #define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_MJPEG_LEN + #elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + #define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_FRAME_BASED_LEN + #else + #define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN TUD_VIDEO_CAPTURE_DESC_UNCOMPR_LEN + #endif + #endif +#endif +#else + #define TUD_CAM2_VIDEO_CAPTURE_DESC_LEN 0 +#endif + +#endif //CFG_TUD_VIDEO +#endif diff --git a/components/esp_tinyusb/include_private/uvc_frame_config.h b/components/esp_tinyusb/include_private/uvc_frame_config.h new file mode 100644 index 0000000..69e5370 --- /dev/null +++ b/components/esp_tinyusb/include_private/uvc_frame_config.h @@ -0,0 +1,83 @@ +/* + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD + * 2026 Daniel Kampert (DanielKampert@Kampis-Elektroecke.de) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#ifdef CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 +#define FORMAT_MJPEG_CAM1 1 +#endif + +#ifdef CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 +#define FORMAT_H264_CAM1 1 +#endif + +#ifdef CONFIG_TINYUSB_UVC_CAM1_MULTI_FRAMESIZE +#define UVC_CAM1_FRAME_MULTI 1 +#endif + +#define UVC_CAM1_FRAME_WIDTH CONFIG_TINYUSB_UVC_CAM1_FRAMESIZE_WIDTH +#define UVC_CAM1_FRAME_HEIGHT CONFIG_TINYUSB_UVC_CAM1_FRAMESIZE_HEIGHT +#define UVC_CAM1_FRAME_RATE CONFIG_TINYUSB_UVC_CAM1_FRAMERATE + +#ifdef CONFIG_TINYUSB_UVC_MODE_BULK_CAM1 +#define UVC_CAM1_BULK_MODE 1 +#endif + +#if CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM +#ifdef CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 +#define FORMAT_MJPEG_CAM2 1 +#endif + +#ifdef CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 +#define FORMAT_H264_CAM2 1 +#endif + +#ifdef CONFIG_TINYUSB_UVC_CAM2_MULTI_FRAMESIZE +#define UVC_CAM2_FRAME_MULTI 1 +#endif + +#define UVC_CAM2_FRAME_WIDTH CONFIG_TINYUSB_UVC_CAM2_FRAMESIZE_WIDTH +#define UVC_CAM2_FRAME_HEIGHT CONFIG_TINYUSB_UVC_CAM2_FRAMESIZE_HEIGHT +#define UVC_CAM2_FRAME_RATE CONFIG_TINYUSB_UVC_CAM2_FRAMERATE + +#ifdef CONFIG_TINYUSB_UVC_MODE_BULK_CAM2 +#define UVC_CAM2_BULK_MODE 1 +#endif +#endif + +#ifndef UVC_CAM2_FRAME_WIDTH +#define UVC_CAM2_FRAME_WIDTH UVC_CAM1_FRAME_WIDTH +#endif + +#ifndef UVC_CAM2_FRAME_HEIGHT +#define UVC_CAM2_FRAME_HEIGHT UVC_CAM1_FRAME_HEIGHT +#endif + +#ifndef UVC_CAM2_FRAME_RATE +#define UVC_CAM2_FRAME_RATE UVC_CAM1_FRAME_RATE +#endif + +static const struct { + int width; + int height; + int rate; +} UVC_FRAMES_INFO[][4] = {{ + {UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE}, + {CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_1, CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_1, CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_1}, + {CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_2, CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_2, CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_2}, + {CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_3, CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_3, CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_3}, + }, { + {UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE}, + {CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_1, CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_1, CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_1}, + {CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_2, CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_2, CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_2}, + {CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_3, CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_3, CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_3}, + } +}; + +#define UVC_FRAME_NUM (sizeof(UVC_FRAMES_INFO[0]) / sizeof(UVC_FRAMES_INFO[0][0])) diff --git a/components/esp_tinyusb/sbom.yml b/components/esp_tinyusb/sbom.yml new file mode 100644 index 0000000..f1e805b --- /dev/null +++ b/components/esp_tinyusb/sbom.yml @@ -0,0 +1,2 @@ +supplier: 'Organization: Espressif Systems (Shanghai) CO LTD' +originator: 'Organization: Espressif Systems (Shanghai) CO LTD' diff --git a/components/esp_tinyusb/storage_sdmmc.c b/components/esp_tinyusb/storage_sdmmc.c new file mode 100644 index 0000000..8eca7b6 --- /dev/null +++ b/components/esp_tinyusb/storage_sdmmc.c @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "soc/soc_caps.h" +#include "msc_storage.h" + + +#if SOC_SDMMC_HOST_SUPPORTED +#include "diskio_sdmmc.h" + +static const char *TAG = "storage_sdmmc"; + +static sdmmc_card_t *_scard = NULL; + +static esp_err_t storage_sdmmc_mount(BYTE pdrv) +{ + assert(_scard != NULL); + ff_diskio_register_sdmmc(pdrv, _scard); + ff_sdmmc_set_disk_status_check(pdrv, false); + return ESP_OK; +} + +static esp_err_t storage_sdmmc_unmount(void) +{ + BYTE pdrv; + pdrv = ff_diskio_get_pdrv_card(_scard); + if (pdrv == 0xff) { + ESP_LOGE(TAG, "Invalid state"); + return ESP_ERR_INVALID_STATE; + } + + char drv[3] = {(char)('0' + pdrv), ':', 0}; + f_mount(0, drv, 0); + ff_diskio_unregister(pdrv); + + return ESP_OK; +} + +static inline size_t storage_sdmmc_get_sector_count(void) +{ + assert(_scard); + return (size_t)_scard->csd.capacity; +} + +static inline size_t storage_sdmmc_get_sector_size(void) +{ + assert(_scard); + assert(_scard->csd.sector_size != 0); // Sector size must be non-zero if SDMMC card was initialized correctly + return (size_t)_scard->csd.sector_size; +} + +static esp_err_t storage_sdmmc_sector_read(uint32_t lba, uint32_t offset, size_t size, void *dest) +{ + assert(_scard); + uint32_t sector_size = storage_sdmmc_get_sector_size(); + return sdmmc_read_sectors(_scard, dest, lba, size / sector_size); +} + +static esp_err_t storage_sdmmc_sector_write(uint32_t lba, uint32_t offset, size_t size, const void *src) +{ + assert(_scard); + uint32_t sector_size = storage_sdmmc_get_sector_size(); + return sdmmc_write_sectors(_scard, src, lba, size / sector_size); +} + +static esp_err_t storage_sdmmc_get_info(storage_info_t *info) +{ + ESP_RETURN_ON_FALSE(info, ESP_ERR_INVALID_ARG, TAG, "Storage info pointer can't be NULL"); + + info->total_sectors = (uint32_t) storage_sdmmc_get_sector_count(); + info->sector_size = (uint32_t) storage_sdmmc_get_sector_size(); + return ESP_OK; +} + +static void storage_sdmmc_close(void) +{ + _scard = NULL; +} + +// Constant struct of function pointers +const storage_medium_t sdmmc_storage_medium = { + .type = STORAGE_MEDIUM_TYPE_SDMMC, + .mount = &storage_sdmmc_mount, + .unmount = &storage_sdmmc_unmount, + .read = &storage_sdmmc_sector_read, + .write = &storage_sdmmc_sector_write, + .get_info = &storage_sdmmc_get_info, + .close = &storage_sdmmc_close, +}; + +esp_err_t storage_sdmmc_open_medium(sdmmc_card_t *card, const storage_medium_t **medium) +{ + ESP_RETURN_ON_FALSE(medium != NULL, ESP_ERR_INVALID_ARG, TAG, "Storage API pointer can't be NULL"); + ESP_RETURN_ON_FALSE(card, ESP_ERR_INVALID_ARG, TAG, "SDMMC card handle can't be NULL"); + + _scard = card; + *medium = &sdmmc_storage_medium; + return ESP_OK; +} +#endif // SOC_SDMMC_HOST_SUPPORTED diff --git a/components/esp_tinyusb/storage_spiflash.c b/components/esp_tinyusb/storage_spiflash.c new file mode 100644 index 0000000..d7d1676 --- /dev/null +++ b/components/esp_tinyusb/storage_spiflash.c @@ -0,0 +1,127 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "sdkconfig.h" +#include "wear_levelling.h" +#include "diskio_wl.h" +#include "msc_storage.h" + +static const char *TAG = "storage_spiflash"; + +static wl_handle_t _wl_handle = WL_INVALID_HANDLE; // Global variable to hold the wear-levelling handle + +static esp_err_t storage_spiflash_mount(BYTE pdrv) +{ + assert(_wl_handle != WL_INVALID_HANDLE); + return ff_diskio_register_wl_partition(pdrv, _wl_handle); +} + +static esp_err_t storage_spiflash_unmount(void) +{ + assert(_wl_handle != WL_INVALID_HANDLE); + BYTE pdrv; + pdrv = ff_diskio_get_pdrv_wl(_wl_handle); + if (pdrv == 0xff) { + return ESP_ERR_INVALID_STATE; + } + ff_diskio_clear_pdrv_wl(_wl_handle); + + char drv[3] = {(char)('0' + pdrv), ':', 0}; + f_mount(0, drv, 0); + ff_diskio_unregister(pdrv); + + return ESP_OK; +} + +static size_t storage_spiflash_get_sector_count(void) +{ + assert(_wl_handle != WL_INVALID_HANDLE); + size_t result = 0; + size_t size = wl_sector_size(_wl_handle); + if (size == 0) { + result = 0; + } else { + result = (size_t)(wl_size(_wl_handle) / size); + } + return result; +} + +static size_t storage_spiflash_get_sector_size(void) +{ + assert(_wl_handle != WL_INVALID_HANDLE); + return (size_t)wl_sector_size(_wl_handle); +} + +static esp_err_t storage_spiflash_sector_read(uint32_t lba, uint32_t offset, size_t size, void *dest) +{ + assert(_wl_handle != WL_INVALID_HANDLE); + size_t temp = 0; + size_t addr = 0; // Address of the data to be read, relative to the beginning of the partition. + size_t sector_size = storage_spiflash_get_sector_size(); + + ESP_RETURN_ON_FALSE(!__builtin_umul_overflow(lba, sector_size, &temp), ESP_ERR_INVALID_SIZE, TAG, "overflow lba %lu sector_size %u", lba, sector_size); + ESP_RETURN_ON_FALSE(!__builtin_uadd_overflow(temp, offset, &addr), ESP_ERR_INVALID_SIZE, TAG, "overflow addr %u offset %lu", temp, offset); + + return wl_read(_wl_handle, addr, dest, size); +} + +static esp_err_t storage_spiflash_sector_write(uint32_t lba, uint32_t offset, size_t size, const void *src) +{ + assert(_wl_handle != WL_INVALID_HANDLE); + (void) lba; // lba is not used in this implementation + (void) offset; // offset is not used in this implementation + + size_t temp = 0; + size_t addr = 0; // Address of the data to be read, relative to the beginning of the partition. + size_t sector_size = storage_spiflash_get_sector_size(); + + ESP_RETURN_ON_FALSE(!__builtin_umul_overflow(lba, sector_size, &temp), ESP_ERR_INVALID_SIZE, TAG, "overflow lba %lu sector_size %u", lba, sector_size); + ESP_RETURN_ON_FALSE(!__builtin_uadd_overflow(temp, offset, &addr), ESP_ERR_INVALID_SIZE, TAG, "overflow addr %u offset %lu", temp, offset); + ESP_RETURN_ON_ERROR(wl_erase_range(_wl_handle, addr, size), TAG, "Failed to erase"); + + return wl_write(_wl_handle, addr, src, size); +} + +static esp_err_t storage_spiflash_get_info(storage_info_t *info) +{ + ESP_RETURN_ON_FALSE(info, ESP_ERR_INVALID_ARG, TAG, "Storage info pointer can't be NULL"); + + info->total_sectors = (uint32_t) storage_spiflash_get_sector_count(); + info->sector_size = (uint32_t) storage_spiflash_get_sector_size(); + + return ESP_OK; +} + +static void storage_spiflash_close(void) +{ + _wl_handle = WL_INVALID_HANDLE; // Reset the global wear-levelling handle +} + +// Constant struct of function pointers +const storage_medium_t spiflash_medium = { + .type = STORAGE_MEDIUM_TYPE_SPIFLASH, + .mount = &storage_spiflash_mount, + .unmount = &storage_spiflash_unmount, + .read = &storage_spiflash_sector_read, + .write = &storage_spiflash_sector_write, + .get_info = &storage_spiflash_get_info, + .close = &storage_spiflash_close, +}; + +esp_err_t storage_spiflash_open_medium(wl_handle_t wl_handle, const storage_medium_t **medium) +{ + ESP_RETURN_ON_FALSE(wl_handle != WL_INVALID_HANDLE, ESP_ERR_INVALID_ARG, TAG, "Invalid wear-levelling handle"); + ESP_RETURN_ON_FALSE(medium != NULL, ESP_ERR_INVALID_ARG, TAG, "Storage API pointer can't be NULL"); + + _wl_handle = wl_handle; + *medium = &spiflash_medium; + + return ESP_OK; +} diff --git a/components/esp_tinyusb/test_apps/README.md b/components/esp_tinyusb/test_apps/README.md new file mode 100644 index 0000000..c18ce50 --- /dev/null +++ b/components/esp_tinyusb/test_apps/README.md @@ -0,0 +1,183 @@ +# CI target runner setup + +To allow a Docker container, running on a CI target runner, to access USB devices connected to the CI target runner, some modifications must be made. In our case, it's an `RPI` target runner. + +The main idea about accessing USB Devices from a running docker container comes from this response on [stackoverflow](https://stackoverflow.com/a/66427245/19840830). The same approach is also recommended in the official Docker [documentation](https://docs.docker.com/reference/cli/docker/container/run/#device-cgroup-rule) + +### Following changes shall be made on a CI target runner + +- [`UDEV rules`](#udev-rules) +- [`Docker tty script`](#docker-tty-script) +- [`Logging`](#logging) +- [`Running a docker container`](#running-a-docker-container) +- [`GitHub CI target runner setup`](#github-ci-target-runner-setup) +- [`GitLab CI target runner setup`](#gitlab-ci-target-runner-setup) + +## UDEV rules + +### Propagate USB device connection/disconnection to running docker + +- This UDEV rule will trigger a `docker_tty.sh` script every time a USB device is connected, disconnected, or enumerated by the host machine (CI target runner) +- Location: `/etc/udev/rules.d/99-docker-tty.rules` +- `99-docker-tty.rules` file content: + +```sh +ACTION=="add", SUBSYSTEM=="tty", RUN+="/usr/local/bin/docker_tty.sh 'added' '%E{DEVNAME}' '%M' '%m'" +ACTION=="remove", SUBSYSTEM=="tty", RUN+="/usr/local/bin/docker_tty.sh 'removed' '%E{DEVNAME}' '%M' '%m'" +``` + +### Set power management of the USB device + +- This UDEV rule sets power control flag of a selected USB device to `auto` to allow automatic suspend after a set period of inactivity (2 seconds by default) + +- The power control attribute is `auto` by default on linux host PCs, on RPI it is set to `on` (no auto suspend) by default + +- Location: `/etc/udev/rules.d/99-usb-pm.rules` + +- `99-usb-pm.rules` file content: + +```sh +ACTION=="add|bind", SUBSYSTEM=="usb", ATTR{idVendor}=="303a", ATTR{idProduct}=="4002", TEST=="power/control", ATTR{power/control}="auto", ATTR{power/autosuspend}="2", ATTR{power/autosuspend_delay_ms}="2000" +``` + +The rule uses `{idVendor}` and `{idProduct}` attributes set to match TinyUSB cdc-acm device used in tests + +### Set root permissions for low level access to USB devices + +- Since we can't run pytest as root, we can allow root access to selected devices even without being root + +- This is useful when using python frontend `pyusb` to control USB devices (sending transfers, reading descriptors) + +- Following the recommendations from `pyusb` [docs](https://github.com/pyusb/pyusb/blob/master/docs/faq.rst#how-to-practically-deal-with-permission-issues-on-linux) + +- Location: `/etc/udev/rules.d/99-usb-access.rules` + +- `99-usb-access.rules` file content: + +```sh +SUBSYSTEM=="usb", ATTR{idVendor}=="303a", ATTR{idProduct}=="4002", MODE="0666" +``` + +## Docker tty script + +- This `.sh` script, triggered by the UDEV rule which propagates USB devices to a running Docker container. +- Location: `/usr/local/bin/docker_tty.sh` +- `docker_tty.sh` file content: + +```sh +#!/usr/bin/env bash + +# Log the USB event with parameters +echo "USB event: $1 $2 $3 $4" >> /tmp/docker_tty.log + +# Find a running Docker container (using the first one found) +docker_name=$(docker ps --format "{{.Names}}" | head -n 1) + +# Check if a container was found +if [ ! -z "$docker_name" ]; then + if [ "$1" == "added" ]; then + docker exec -u 0 "$docker_name" mknod $2 c $3 $4 + docker exec -u 0 "$docker_name" chmod -R 777 $2 + echo "Adding $2 to Docker container $docker_name" >> /tmp/docker_tty.log + else + docker exec -u 0 "$docker_name" rm $2 + echo "Removing $2 from Docker container $docker_name" >> /tmp/docker_tty.log + fi +else + echo "No running Docker containers found." >> /tmp/docker_tty.log +fi +``` + +### Making the script executable + +Don't forget to make the created script executable: + +```sh +root@~$ chmod +x /usr/local/bin/docker_tty.sh +``` + +## Logging + +- The `docker_tty.sh` script logs information about the USB devices it processes. +- Location: `/tmp/docker_tty.log` +- Example of a log from the `docker_tty.log` file, showing a flow of the `pytest_usb_device.py` test + +``` +USB event: added /dev/ttyACM0 166 0 +USB event: added /dev/ttyACM1 166 1 +Adding /dev/ttyACM0 to Docker container d5e5c774174b435b8befea864f8fcb7f_python311bookworm_6a975d +Adding /dev/ttyACM1 to Docker container d5e5c774174b435b8befea864f8fcb7f_python311bookworm_6a975d +USB event: removed /dev/ttyACM0 166 0 +USB event: removed /dev/ttyACM1 166 1 +``` + +## Running a docker container + +### Check Major and Minor numbers of connected devices + +Check the Major and Minor numbers assigned by the Linux kernel to devices that you want the Docker container to access. In our case, we want to access `/dev/ttyUSB0`, `/dev/ttyACM0` and `/dev/ttyACM1` + +`/dev/ttyUSB0`: Major 188, Minor 0 + +```sh +peter@BrnoRPIG007:~ $ ls -l /dev/ttyUSB0 +crw-rw-rw- 1 root dialout 188, 0 Nov 12 11:08 /dev/ttyUSB0 +``` + +`/dev/ttyACM0` and `/dev/ttyACM1`: Major 166, Minor 0 (1) + +```sh +peter@BrnoRPIG007:~ $ ls -l /dev/ttyACM0 +crw-rw---- 1 root dialout 166, 0 Nov 13 10:26 /dev/ttyACM0 +peter@BrnoRPIG007:~ $ ls -l /dev/ttyACM1 +crw-rw---- 1 root dialout 166, 1 Nov 13 10:26 /dev/ttyACM1 +``` + +### Check VID and PID of the DUT device + +Check `VID (idVendor)` and `PID (idProduct)` of the device used during testing, to correctly set UDEV rules `{idVendor}` and `{idProduct}` attributes: + +`Espressif Systems Espressif Device`: VID 303a, PID 4002 + +```sh +peter@peter ➜ ~ lsusb +... +Bus 005 Device 100: ID 303a:4002 Espressif Systems Espressif Device +... +``` + +### Run a docker container + +Run a Docker container with the following extra options: + +```sh +docker run --device-cgroup-rule='c 188:* rmw' --device-cgroup-rule='c 166:* rmw' --privileged --volumes /dev/bus/usb:/dev/bus/usb .. +``` + +- `--device-cgroup-rule='c 188:* rmw'`: allow access to `ttyUSBx` (Major 188, all Minors) +- `--device-cgroup-rule='c 166:* rmw'`: allow access to `ttyACMx` (Major 166, all Minors) +- `--volumes /dev/bus/usb:/dev/bus/usb`: mount volumes: Share low level access to usb devices with the docker container, otherwise the container creates it's own, internal device tree. + +## GitHub CI target runner setup + +To apply these changes to a GitHub target runner a `.yml` file used to run a Docker container for pytest must be modified. The Docker container is then run with the following options: + +```yaml +container: + image: python:3.11-bookworm + options: --privileged --device-cgroup-rule="c 188:* rmw" --device-cgroup-rule="c 166:* rmw" + volumes: + - /dev/bus/usb:/dev/bus/usb +``` + +## GitLab CI target runner setup + +To apply these changes to a GitLab runner the `config.toml` file located at `/etc/gitlab-runner/config.toml` on each GitLab target runner must be modified. + +According to GitLab's [documentation](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnersdocker-section) the `[runners.docker]` section of the `config.toml` file should include the `device_cgroup_rules` parameter: + +```toml +[runners.docker] + ... + device_cgroup_rules = ["c 188:* rmw", "c 166:* rmw"] +``` diff --git a/components/esp_tinyusb/test_apps/cdc/CMakeLists.txt b/components/esp_tinyusb/test_apps/cdc/CMakeLists.txt new file mode 100644 index 0000000..619c320 --- /dev/null +++ b/components/esp_tinyusb/test_apps/cdc/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +project(test_app_cdc) diff --git a/components/esp_tinyusb/test_apps/cdc/main/CMakeLists.txt b/components/esp_tinyusb/test_apps/cdc/main/CMakeLists.txt new file mode 100644 index 0000000..e81e627 --- /dev/null +++ b/components/esp_tinyusb/test_apps/cdc/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES unity + WHOLE_ARCHIVE) diff --git a/components/esp_tinyusb/test_apps/cdc/main/idf_component.yml b/components/esp_tinyusb/test_apps/cdc/main/idf_component.yml new file mode 100644 index 0000000..b1cb5b5 --- /dev/null +++ b/components/esp_tinyusb/test_apps/cdc/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "*" + override_path: "../../../" diff --git a/components/esp_tinyusb/test_apps/cdc/main/test_app_main.c b/components/esp_tinyusb/test_apps/cdc/main/test_app_main.c new file mode 100644 index 0000000..b1321ed --- /dev/null +++ b/components/esp_tinyusb/test_apps/cdc/main/test_app_main.c @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" + +void app_main(void) +{ + /* + _ _ _ + | | (_) | | + ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ + / _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \ + | __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) | + \___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/ + | |______ __/ | + |_|______| |___/ + _____ _____ _____ _____ + |_ _| ___/ ___|_ _| + | | | |__ \ `--. | | + | | | __| `--. \ | | + | | | |___/\__/ / | | + \_/ \____/\____/ \_/ + */ + + printf(" _ _ _ \n"); + printf(" | | (_) | | \n"); + printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n"); + printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n"); + printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n"); + printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n"); + printf(" | |______ __/ | \n"); + printf(" |_|______| |___/ \n"); + printf(" _____ _____ _____ _____ \n"); + printf("|_ _| ___/ ___|_ _| \n"); + printf(" | | | |__ \\ `--. | | \n"); + printf(" | | | __| `--. \\ | | \n"); + printf(" | | | |___/\\__/ / | | \n"); + printf(" \\_/ \\____/\\____/ \\_/ \n"); + + unity_utils_setup_heap_record(80); + unity_utils_set_leak_level(128); + unity_run_menu(); +} + +/* setUp runs before every test */ +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + unity_utils_evaluate_leaks(); +} diff --git a/components/esp_tinyusb/test_apps/cdc/main/test_cdc.c b/components/esp_tinyusb/test_apps/cdc/main/test_cdc.c new file mode 100644 index 0000000..e173775 --- /dev/null +++ b/components/esp_tinyusb/test_apps/cdc/main/test_cdc.c @@ -0,0 +1,203 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" +#if SOC_USB_OTG_SUPPORTED + +#include +#include +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_err.h" + +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "tinyusb_cdc_acm.h" +#include "vfs_tinyusb.h" +#include "tusb_config.h" + +#define VFS_PATH "/dev/usb-cdc1" + +static const tusb_desc_device_t cdc_device_descriptor = { + .bLength = sizeof(cdc_device_descriptor), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = TINYUSB_ESPRESSIF_VID, + .idProduct = 0x4002, + .bcdDevice = 0x0100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01 +}; + +#if (TUD_OPT_HIGH_SPEED) +static const tusb_desc_device_qualifier_t device_qualifier = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0 +}; +#endif // TUD_OPT_HIGH_SPEED + +static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) +{ +} + +/** + * @brief TinyUSB CDC testcase + * + * This is not a 'standard' testcase, as it never exits. The testcase runs in a loop where it echoes back all the data received. + * + * - Init TinyUSB with standard CDC device and configuration descriptors + * - Init 2 CDC-ACM interfaces + * - Map CDC1 to Virtual File System + * - In a loop: Read data from CDC0 and CDC1. Echo received data back + * + * Note: CDC0 appends 'novfs' to echoed data, so the host (test runner) can easily determine which port is which. + */ +TEST_CASE("tinyusb_cdc", "[esp_tinyusb][cdc]") +{ + static const uint16_t cdc_desc_config_len = TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN; + static const uint8_t cdc_desc_configuration[] = { + TUD_CONFIG_DESCRIPTOR(1, 4, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, (TUD_OPT_HIGH_SPEED ? 512 : 64)), + TUD_CDC_DESCRIPTOR(2, 4, 0x83, 8, 0x04, 0x84, (TUD_OPT_HIGH_SPEED ? 512 : 64)), + }; + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + tusb_cfg.descriptor.device = &cdc_device_descriptor; + tusb_cfg.descriptor.full_speed_config = cdc_desc_configuration; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.qualifier = &device_qualifier; + tusb_cfg.descriptor.high_speed_config = cdc_desc_configuration; +#endif // TUD_OPT_HIGH_SPEED + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + + tinyusb_config_cdcacm_t acm_cfg = { + .cdc_port = TINYUSB_CDC_ACM_0, + .callback_rx = &tinyusb_cdc_rx_callback, + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = NULL, + .callback_line_coding_changed = NULL + }; + + // Init CDC 0 + TEST_ASSERT_FALSE(tinyusb_cdcacm_initialized(TINYUSB_CDC_ACM_0)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_init(&acm_cfg)); + TEST_ASSERT_TRUE(tinyusb_cdcacm_initialized(TINYUSB_CDC_ACM_0)); + + // Init CDC 1 + acm_cfg.cdc_port = TINYUSB_CDC_ACM_1; + acm_cfg.callback_rx = NULL; + TEST_ASSERT_FALSE(tinyusb_cdcacm_initialized(TINYUSB_CDC_ACM_1)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_init(&acm_cfg)); + TEST_ASSERT_TRUE(tinyusb_cdcacm_initialized(TINYUSB_CDC_ACM_1)); + + // Install VFS to CDC 1 + TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_tusb_cdc_register(TINYUSB_CDC_ACM_1, VFS_PATH)); + esp_vfs_tusb_cdc_set_rx_line_endings(ESP_LINE_ENDINGS_CRLF); + esp_vfs_tusb_cdc_set_tx_line_endings(ESP_LINE_ENDINGS_LF); + FILE *cdc = fopen(VFS_PATH, "r+"); + TEST_ASSERT_NOT_NULL(cdc); + + uint8_t buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1]; + while (true) { + size_t b = fread(buf, 1, sizeof(buf), cdc); + if (b > 0) { + printf("Intf VFS, RX %d bytes\n", b); + //ESP_LOG_BUFFER_HEXDUMP("test", buf, b, ESP_LOG_INFO); + fwrite(buf, 1, b, cdc); + } + vTaskDelay(1); + + size_t rx_size = 0; + int itf = 0; + ESP_ERROR_CHECK(tinyusb_cdcacm_read(itf, buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size)); + if (rx_size > 0) { + printf("Intf %d, RX %d bytes\n", itf, rx_size); + + // Add 'novfs' to reply so the host can identify the port + strcpy((char *)&buf[rx_size - 2], "novfs\r\n"); + tinyusb_cdcacm_write_queue(itf, buf, rx_size + sizeof("novfs") - 1); + + tinyusb_cdcacm_write_flush(itf, 0); + } + } +} + +#define CDC_THROUGHPUT_TEST_BUFFER_SIZE (32 * 1024) +#define CDC_THROUGHPUT_TEST_DURATION_MS 11000*10 +TEST_CASE("tinyusb_cdc_throughput", "[esp_tinyusb][cdc_throughput]") +{ + static const uint16_t cdc_desc_config_len = TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN; + static const uint8_t cdc_desc_configuration[] = { + TUD_CONFIG_DESCRIPTOR(1, 2, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, (TUD_OPT_HIGH_SPEED ? 512 : 64)), + }; + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + tusb_cfg.descriptor.device = &cdc_device_descriptor; + tusb_cfg.descriptor.full_speed_config = cdc_desc_configuration; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.qualifier = &device_qualifier; + tusb_cfg.descriptor.high_speed_config = cdc_desc_configuration; +#endif // TUD_OPT_HIGH_SPEED + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + + tinyusb_config_cdcacm_t acm_cfg = { + .cdc_port = TINYUSB_CDC_ACM_0, + .callback_rx = NULL, // TX only in this test + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = NULL, + .callback_line_coding_changed = NULL + }; + printf("TinyUSB CDC config:\n\tEP_BUFSIZE = %d\n\tTX_BUFSIZE = %d\n\tTEST_BUFSIZE = %d\n", CFG_TUD_CDC_EP_BUFSIZE, CFG_TUD_CDC_TX_BUFSIZE, CDC_THROUGHPUT_TEST_BUFFER_SIZE); + + // Init CDC 0 + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_init(&acm_cfg)); + + // Wait for host to open this cdc channel + while (!tud_cdc_n_connected(0)) { + vTaskDelay(pdMS_TO_TICKS(100)); + } + + size_t bytes_written = 0; + TimeOut_t to; + TickType_t remaining = pdMS_TO_TICKS(CDC_THROUGHPUT_TEST_DURATION_MS); + vTaskSetTimeOutState(&to); + + uint8_t *tx_buf = malloc(CDC_THROUGHPUT_TEST_BUFFER_SIZE); + TEST_ASSERT_NOT_NULL(tx_buf); + + while (tud_cdc_n_connected(0) && xTaskCheckForTimeOut(&to, &remaining) == pdFALSE) { + bytes_written += tud_cdc_n_write(0, tx_buf, CDC_THROUGHPUT_TEST_BUFFER_SIZE); + } + + free(tx_buf); + + // Cleanup + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_deinit(0)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} + +#endif diff --git a/components/esp_tinyusb/test_apps/cdc/pytest_cdc.py b/components/esp_tinyusb/test_apps/cdc/pytest_cdc.py new file mode 100644 index 0000000..c25fbe4 --- /dev/null +++ b/components/esp_tinyusb/test_apps/cdc/pytest_cdc.py @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded_idf.dut import IdfDut +from time import sleep +from serial import Serial, SerialException +from serial.tools.list_ports import comports +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.usb_device +@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +def test_usb_device_cdc(dut: IdfDut) -> None: + ''' + Running the test locally: + 1. Build the test_app for your DUT (ESP32-S2/S3/P4) + 2. Connect you DUT to your test runner (local machine) with USB port and flashing port + 3. Run `pytest --target esp32s3` + + Test procedure: + 1. Run the test on the DUT + 2. Expect 2 Virtual COM Ports in the system + 3. Open both comports and send some data. Expect echoed data + ''' + dut.expect_exact('Press ENTER to see the list of tests.') + dut.write('[cdc]') + dut.expect_exact('TinyUSB: TinyUSB Driver installed') + sleep(2) # Some time for the OS to enumerate our USB device + + # Find devices with Espressif TinyUSB VID/PID + s = [] + ports = comports() + + for port, _, hwid in ports: + if '303A:4002' in hwid: + s.append(port) + + if len(s) != 2: + raise Exception('TinyUSB COM port not found') + + try: + with Serial(s[0]) as cdc0: + with Serial(s[1]) as cdc1: + # Write dummy string and check for echo + cdc0.write('text\r\n'.encode()) + res = cdc0.readline() + assert b'text' in res + if b'novfs' in res: + novfs_cdc = cdc0 + vfs_cdc = cdc1 + + cdc1.write('text\r\n'.encode()) + res = cdc1.readline() + assert b'text' in res + if b'novfs' in res: + novfs_cdc = cdc1 + vfs_cdc = cdc0 + + # Write more than MPS, check that the transfer is not divided + novfs_cdc.write(bytes(100)) + dut.expect_exact("Intf 0, RX 100 bytes") + + # Write more than RX buffer, check correct reception + novfs_cdc.write(bytes(550)) + transfer_len1 = int(dut.expect(r'Intf 0, RX (\d+) bytes')[1].decode()) + transfer_len2 = int(dut.expect(r'Intf 0, RX (\d+) bytes')[1].decode()) + assert transfer_len1 + transfer_len2 == 550 + + # The VFS is setup for CRLF RX and LF TX + vfs_cdc.write('text\r\n'.encode()) + res = vfs_cdc.readline() + assert b'text\n' in res + + return + + except SerialException as e: + print(f"SerialException occurred: {e}") + raise + + except Exception as e: + print(f"An unexpected error occurred: {e}") + raise diff --git a/components/esp_tinyusb/test_apps/cdc/pytest_cdc_throughput.py b/components/esp_tinyusb/test_apps/cdc/pytest_cdc_throughput.py new file mode 100644 index 0000000..d3f6453 --- /dev/null +++ b/components/esp_tinyusb/test_apps/cdc/pytest_cdc_throughput.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +""" +USB CDC (COM Port) Throughput Tester +------------------------------------ + +This script measures throughput for USB CDC-ACM (virtual COM port) devices +connected to a Windows (or Linux/macOS) host. It can test: + + --tx Host → Device (send continuously from host) + --rx Device → Host (read continuously on host) + --rxtx Full-duplex (simultaneous send & receive) + +It prints instantaneous and average bit rates once per second. + +Usage Examples: +--------------- +List available COM ports: + python cdc_throughput.py + +TX test (host → device, 30 seconds, 16 KiB write blocks): + python cdc_throughput.py --port COM7 --baud 12000000 --tx --size 16384 --seconds 30 + +RX test (device → host, run until Ctrl+C): + python cdc_throughput.py --port COM7 --rx --size 32768 + +Full-duplex test for 20 seconds: + python cdc_throughput.py --port COM7 --rxtx --size 65536 --seconds 20 + +Select device by VID/PID (instead of COM port): + python cdc_throughput.py --vid 0x303A --pid 0x4002 --rx --size 32768 + +Parameters: +----------- +--port COM port name (e.g., COM7 on Windows, /dev/ttyACM0 on Linux) +--vid USB Vendor ID (hex or decimal) to search for device +--pid USB Product ID (hex or decimal) to search for device +--baud Baud rate to set on host side. Ignored by CDC but still required by OS. +--size I/O block size in bytes (try 16–64 KiB for high throughput) +--seconds Duration in seconds (0 = run until interrupted) +--rtscts Enable RTS/CTS hardware flow control +--xonxoff Enable XON/XOFF software flow control +--dsrdtr Enable DSR/DTR flow control + +Notes: +------ +- For RX test, the device must continuously send data as fast as possible. +- For TX test, the device should read & discard incoming data to avoid backpressure. +- Full-duplex mode works best if the device echoes or transmits in parallel. +- CDC ignores the baud rate, but some OS drivers require a nonzero setting. +- Large write sizes can improve throughput by reducing system call overhead. +""" + +import argparse +import threading +import time +import sys +try: + import serial + from serial.tools import list_ports +except ImportError: + print("pyserial is required. Install with: pip install pyserial") + sys.exit(1) + + +def human_bps(bps: float) -> str: + if bps >= 1e9: + return f"{bps/1e9:.2f} Gbit/s" + if bps >= 1e6: + return f"{bps/1e6:.2f} Mbit/s" + if bps >= 1e3: + return f"{bps/1e3:.2f} kbit/s" + return f"{bps:.0f} bit/s" + + +def human_bytes(b: float) -> str: + units = ["B", "KiB", "MiB", "GiB"] + v = float(b) + for u in units: + if v < 1024.0 or u == units[-1]: + return f"{v:.2f} {u}" + v /= 1024.0 + return f"{b:.0f} B" + + +def list_com_ports() -> None: + print("Available ports:") + for p in list_ports.comports(): + print(f" {p.device:10s} {p.description}") + + +class Counter: + def __init__(self) -> None: + self._lock = threading.Lock() + self._bytes = 0 + + def add(self, n: int) -> None: + with self._lock: + self._bytes += n + + def snapshot(self) -> int: + with self._lock: + return self._bytes + + +def find_port_by_vid_pid(vid: int, pid: int) -> str: + for p in list_ports.comports(): + if p.vid is not None and p.pid is not None: + if p.vid == vid and p.pid == pid: + return p.device + raise RuntimeError(f"No serial port found with VID=0x{vid:04X} PID=0x{pid:04X}") + + +def open_serial(args) -> serial.Serial: + port = args.port + # If port not given, but VID/PID are, search for port + if not port and args.vid and args.pid: + port = find_port_by_vid_pid(args.vid, args.pid) + print(f"Using port {port} for VID=0x{args.vid:04X} PID=0x{args.pid:04X}") + if not port: + raise RuntimeError("No port specified and no VID/PID match found.") + s = serial.Serial() + s.port = port + s.baudrate = args.baud + s.bytesize = serial.EIGHTBITS + s.parity = serial.PARITY_NONE + s.stopbits = serial.STOPBITS_ONE + s.timeout = 0 + s.write_timeout = 1 # Set a write timeout to avoid overfilling OS buffers + s.rtscts = args.rtscts + s.xonxoff = args.xonxoff + s.dsrdtr = args.dsrdtr + s.open() + return s + + +def writer_thread(s: serial.Serial, block: bytes, total: Counter, stop_evt: threading.Event): + # Throttle writes: if OS buffer is full, SerialTimeoutException will be raised and we sleep + view = memoryview(block) + while not stop_evt.is_set(): + remaining = len(block) + offset = 0 + while remaining and not stop_evt.is_set(): + try: + n = s.write(view[offset:offset+remaining]) + if n is None: + n = 0 + except serial.SerialTimeoutException: + # OS buffer is full, wait a bit longer before retrying + time.sleep(0.0005) + n = 0 + except Exception as e: + print(f"\n[writer] Exception: {e}") + stop_evt.set() + return + if n > 0: + total.add(n) + offset += n + remaining -= n + else: + time.sleep(0.0005) + # Optionally flush once at the end if needed + # try: + # s.flush() + # except Exception: + # pass + + +def reader_thread(s: serial.Serial, bufsize: int, total: Counter, stop_evt: threading.Event): + buf = bytearray(bufsize) + mv = memoryview(buf) + while not stop_evt.is_set(): + try: + n = s.readinto(mv) + except Exception as e: + print(f"\n[reader] Exception: {e}") + stop_evt.set() + return + if n: + total.add(n) + else: + time.sleep(0.0005) + + +def run_tx(args) -> None: + s = open_serial(args) + block = (bytes([0xAA, 0x55]) * (args.size // 2 + 1))[:args.size] + total = Counter() + stop_evt = threading.Event() + t = threading.Thread(target=writer_thread, args=(s, block, total, stop_evt), daemon=True) + t.start() + + start = last_t = time.perf_counter() + last_bytes = 0 + try: + while True: + time.sleep(1.0) + now = time.perf_counter() + cur = total.snapshot() + delta_b = cur - last_bytes + delta_t = now - last_t + inst_bps = (delta_b * 8) / delta_t if delta_t > 0 else 0.0 + elapsed = now - start + avg_bps = (cur * 8) / elapsed if elapsed > 0 else 0.0 + print(f"[TX] +{human_bytes(delta_b)}/s inst={human_bps(inst_bps):>10s} avg={human_bps(avg_bps):>10s} total={human_bytes(cur)}") + last_t = now + last_bytes = cur + if args.seconds and elapsed >= args.seconds: + break + finally: + stop_evt.set() + t.join(timeout=1.0) + s.close() + + +def run_rx(args) -> None: + s = open_serial(args) + total = Counter() + stop_evt = threading.Event() + t = threading.Thread(target=reader_thread, args=(s, args.size, total, stop_evt), daemon=True) + t.start() + + start = last_t = time.perf_counter() + last_bytes = 0 + try: + while True: + time.sleep(1.0) + now = time.perf_counter() + cur = total.snapshot() + delta_b = cur - last_bytes + delta_t = now - last_t + inst_bps = (delta_b * 8) / delta_t if delta_t > 0 else 0.0 + elapsed = now - start + avg_bps = (cur * 8) / elapsed if elapsed > 0 else 0.0 + print(f"[RX] +{human_bytes(delta_b)}/s inst={human_bps(inst_bps):>10s} avg={human_bps(avg_bps):>10s} total={human_bytes(cur)}") + last_t = now + last_bytes = cur + if args.seconds and elapsed >= args.seconds: + break + finally: + stop_evt.set() + t.join(timeout=1.0) + s.close() + + +def run_rxtx(args) -> None: + s = open_serial(args) + tx_block = (bytes([0xA5]) * args.size) + rx_total = Counter() + tx_total = Counter() + stop_evt = threading.Event() + + rt = threading.Thread(target=reader_thread, args=(s, args.size, rx_total, stop_evt), daemon=True) + wt = threading.Thread(target=writer_thread, args=(s, tx_block, tx_total, stop_evt), daemon=True) + rt.start() + wt.start() + + start = last_t = time.perf_counter() + last_rx = 0 + last_tx = 0 + try: + while True: + time.sleep(1.0) + now = time.perf_counter() + rx = rx_total.snapshot() + tx = tx_total.snapshot() + + d_rx = rx - last_rx + d_tx = tx - last_tx + dt = now - last_t + + rx_inst = (d_rx * 8) / dt if dt > 0 else 0.0 + tx_inst = (d_tx * 8) / dt if dt > 0 else 0.0 + elapsed = now - start + rx_avg = (rx * 8) / elapsed if elapsed > 0 else 0.0 + tx_avg = (tx * 8) / elapsed if elapsed > 0 else 0.0 + + print(f"[RX/TX] rx:+{human_bytes(d_rx)}/s ({human_bps(rx_inst)}) tx:+{human_bytes(d_tx)}/s ({human_bps(tx_inst)})" + f" avg rx={human_bps(rx_avg)} tx={human_bps(tx_avg)} totals rx={human_bytes(rx)} tx={human_bytes(tx)}") + + last_t = now + last_rx = rx + last_tx = tx + if args.seconds and elapsed >= args.seconds: + break + finally: + stop_evt.set() + rt.join(timeout=1.0) + wt.join(timeout=1.0) + s.close() + + +def main(): + parser = argparse.ArgumentParser(description="USB CDC (COM port) throughput tester") + parser.add_argument("--port", help="COM port (e.g., COM7). If omitted, prints available ports.", type=str) + parser.add_argument("--vid", help="USB VID (hex or dec) to search for device.", type=lambda x: int(x, 0)) + parser.add_argument("--pid", help="USB PID (hex or dec) to search for device.", type=lambda x: int(x, 0)) + parser.add_argument("--baud", help="Baud rate to set (may be ignored by CDC).", type=int, default=12_000_000) + parser.add_argument("--size", help="I/O block size in bytes.", type=int, default=16384) + parser.add_argument("--seconds", help="Stop after this many seconds (default: run until Ctrl+C).", type=int, default=0) + + mode = parser.add_mutually_exclusive_group() + mode.add_argument("--tx", action="store_true", help="Host -> Device throughput test") + mode.add_argument("--rx", action="store_true", help="Device -> Host throughput test") + mode.add_argument("--rxtx", action="store_true", help="Full-duplex test") + + parser.add_argument("--rtscts", action="store_true", help="Enable RTS/CTS hardware flow control") + parser.add_argument("--xonxoff", action="store_true", help="Enable XON/XOFF software flow control") + parser.add_argument("--dsrdtr", action="store_true", help="Enable DSR/DTR flow control") + + args = parser.parse_args() + + if not args.port and not (args.vid and args.pid): + list_com_ports() + print("\nRe-run with --port COMx or --vid VID --pid PID and a mode (--tx/--rx/--rxtx).") + return + + try: + if args.tx: + run_tx(args) + elif args.rx: + run_rx(args) + elif args.rxtx: + run_rxtx(args) + else: + print("Pick a mode: --tx, --rx, or --rxtx.") + except KeyboardInterrupt: + print("\nStopped by user.") + except Exception as e: + print(f"Error: {e}") + sys.exit(2) + + +if __name__ == "__main__": + main() + + +try: + # Running this test from pytest slows down the performance to 50% + # Compared to running this test from clean CLI + # That is why we do not export throughput results from here + import pytest + from pytest_embedded_idf.dut import IdfDut + from pytest_embedded_idf.utils import idf_parametrize + @pytest.mark.usb_device + @idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) + def test_tusb_cdc_throughput(dut: IdfDut) -> None: + dut.expect_exact('Press ENTER to see the list of tests.') + dut.write('[cdc_throughput]') + dut.expect_exact('TinyUSB: TinyUSB Driver installed') + time.sleep(2) # Some time for the OS to enumerate our USB device + class Args: + port = None # Use VID/PID instead + vid = 0x303A + pid = 0x4002 + baud = 480000000 + size = 32768 + seconds = 10 + tx = False + rx = True + rxtx = False + rtscts = False + xonxoff = False + dsrdtr = False + + run_rx(Args) + dut.close() +except ImportError: + print("pytest is missing. No test exported.") diff --git a/components/esp_tinyusb/test_apps/cdc/sdkconfig.defaults b/components/esp_tinyusb/test_apps/cdc/sdkconfig.defaults new file mode 100644 index 0000000..c4e8cd0 --- /dev/null +++ b/components/esp_tinyusb/test_apps/cdc/sdkconfig.defaults @@ -0,0 +1,17 @@ +# Configure TinyUSB, it will be used to mock USB devices +CONFIG_TINYUSB_MSC_ENABLED=n +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_COUNT=2 +CONFIG_TINYUSB_HID_COUNT=0 + +# Disable watchdogs, they'd get triggered during unity interactive menu +# CONFIG_ESP_TASK_WDT_INIT is not set + +# Run-time checks of Heap and Stack +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y + +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/components/esp_tinyusb/test_apps/cdc/sdkconfig.defaults.esp32p4 b/components/esp_tinyusb/test_apps/cdc/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000..2dbcf83 --- /dev/null +++ b/components/esp_tinyusb/test_apps/cdc/sdkconfig.defaults.esp32p4 @@ -0,0 +1 @@ +CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y diff --git a/components/esp_tinyusb/test_apps/dconn_detection/CMakeLists.txt b/components/esp_tinyusb/test_apps/dconn_detection/CMakeLists.txt new file mode 100644 index 0000000..ff14e20 --- /dev/null +++ b/components/esp_tinyusb/test_apps/dconn_detection/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +project(test_app_dconn_detection) diff --git a/components/esp_tinyusb/test_apps/dconn_detection/main/CMakeLists.txt b/components/esp_tinyusb/test_apps/dconn_detection/main/CMakeLists.txt new file mode 100644 index 0000000..e81e627 --- /dev/null +++ b/components/esp_tinyusb/test_apps/dconn_detection/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES unity + WHOLE_ARCHIVE) diff --git a/components/esp_tinyusb/test_apps/dconn_detection/main/idf_component.yml b/components/esp_tinyusb/test_apps/dconn_detection/main/idf_component.yml new file mode 100644 index 0000000..b1cb5b5 --- /dev/null +++ b/components/esp_tinyusb/test_apps/dconn_detection/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "*" + override_path: "../../../" diff --git a/components/esp_tinyusb/test_apps/dconn_detection/main/test_app_main.c b/components/esp_tinyusb/test_apps/dconn_detection/main/test_app_main.c new file mode 100644 index 0000000..b1321ed --- /dev/null +++ b/components/esp_tinyusb/test_apps/dconn_detection/main/test_app_main.c @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" + +void app_main(void) +{ + /* + _ _ _ + | | (_) | | + ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ + / _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \ + | __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) | + \___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/ + | |______ __/ | + |_|______| |___/ + _____ _____ _____ _____ + |_ _| ___/ ___|_ _| + | | | |__ \ `--. | | + | | | __| `--. \ | | + | | | |___/\__/ / | | + \_/ \____/\____/ \_/ + */ + + printf(" _ _ _ \n"); + printf(" | | (_) | | \n"); + printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n"); + printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n"); + printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n"); + printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n"); + printf(" | |______ __/ | \n"); + printf(" |_|______| |___/ \n"); + printf(" _____ _____ _____ _____ \n"); + printf("|_ _| ___/ ___|_ _| \n"); + printf(" | | | |__ \\ `--. | | \n"); + printf(" | | | __| `--. \\ | | \n"); + printf(" | | | |___/\\__/ / | | \n"); + printf(" \\_/ \\____/\\____/ \\_/ \n"); + + unity_utils_setup_heap_record(80); + unity_utils_set_leak_level(128); + unity_run_menu(); +} + +/* setUp runs before every test */ +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + unity_utils_evaluate_leaks(); +} diff --git a/components/esp_tinyusb/test_apps/dconn_detection/main/test_dconn_detection.c b/components/esp_tinyusb/test_apps/dconn_detection/main/test_dconn_detection.c new file mode 100644 index 0000000..27da67d --- /dev/null +++ b/components/esp_tinyusb/test_apps/dconn_detection/main/test_dconn_detection.c @@ -0,0 +1,151 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED + +#include +#include +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_err.h" +#include "driver/gpio.h" +#include "esp_rom_gpio.h" +#include "soc/gpio_sig_map.h" +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" + +#define DEVICE_DETACH_TEST_ROUNDS 10 +#define DEVICE_DETACH_ROUND_DELAY_MS 1000 + +#if (CONFIG_IDF_TARGET_ESP32P4) +#define USB_SRP_BVALID_IN_IDX USB_SRP_BVALID_PAD_IN_IDX +#endif // CONFIG_IDF_TARGET_ESP32P4 + +/* TinyUSB descriptors + ********************************************************************* */ +#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN) + +static unsigned int dev_mounted = 0; +static unsigned int dev_umounted = 0; + +static uint8_t const test_configuration_descriptor[] = { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), +}; + +static const tusb_desc_device_t test_device_descriptor = { + .bLength = sizeof(test_device_descriptor), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers + .idProduct = 0x4002, + .bcdDevice = 0x100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01 +}; + +#if (TUD_OPT_HIGH_SPEED) +static const tusb_desc_device_qualifier_t device_qualifier = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0 +}; +#endif // TUD_OPT_HIGH_SPEED + +/** + * @brief TinyUSB callback for device event + * + * @note + * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. + * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. + */ +void test_dconn_event_handler(tinyusb_event_t *event, void *arg) +{ + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + printf("%s\n", __FUNCTION__); + dev_mounted++; + break; + case TINYUSB_EVENT_DETACHED: + printf("%s\n", __FUNCTION__); + dev_umounted++; + break; + default: + break; + } +} + +/** + * @brief TinyUSB Disconnect Detection test case + * + * This is specific artificial test for verifying the disconnection detection event. + * Normally, this event comes as a result of detaching USB device from the port and disappearing the VBUS voltage. + * In this test case, we use GPIO matrix and connect the signal to the ZERO or ONE constant inputs. + * Connection to constant ONE input emulates the attachment to the USB Host port (appearing VBUS). + * Connection to constant ZERO input emulates the detachment from the USB Host port (removing VBUS). + * + * Test logic: + * - Install TinyUSB Device stack without any class + * - In cycle: + * - Emulate the detachment, get the tud_umount_cb(), increase the dev_umounted value + * - Emulate the attachment, get the tud_mount_cb(), increase the dev_mounted value + * - Verify that dev_umounted == dev_mounted + * - Verify that dev_mounted == DEVICE_DETACH_TEST_ROUNDS, where DEVICE_DETACH_TEST_ROUNDS - amount of rounds + * - Uninstall TinyUSB Device stack + * + */ +TEST_CASE("dconn_detection", "[esp_tinyusb][dconn]") +{ + unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS; + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_dconn_event_handler); + tusb_cfg.descriptor.device = &test_device_descriptor; + tusb_cfg.descriptor.full_speed_config = test_configuration_descriptor; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.qualifier = &device_qualifier; + tusb_cfg.descriptor.high_speed_config = test_configuration_descriptor; +#endif // TUD_OPT_HIGH_SPEED + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + + dev_mounted = 0; + dev_umounted = 0; + + while (rounds--) { + // LOW to emulate disconnect USB device + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_SRP_BVALID_IN_IDX, false); + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + // HIGH to emulate connect USB device + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_SRP_BVALID_IN_IDX, false); + vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS)); + } + + // Verify + TEST_ASSERT_EQUAL(dev_umounted, dev_mounted); + TEST_ASSERT_EQUAL(DEVICE_DETACH_TEST_ROUNDS, dev_mounted); + + // Cleanup + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/dconn_detection/pytest_dconn_detection.py b/components/esp_tinyusb/test_apps/dconn_detection/pytest_dconn_detection.py new file mode 100644 index 0000000..ad6a8a0 --- /dev/null +++ b/components/esp_tinyusb/test_apps/dconn_detection/pytest_dconn_detection.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded_idf.dut import IdfDut +from pytest_embedded_idf.utils import idf_parametrize + +#@pytest.mark.usb_device Disable in CI: unavailable teardown for P4 +@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +def test_usb_device_dconn_detection(dut: IdfDut) -> None: + dut.run_all_single_board_cases(group='dconn') diff --git a/components/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults b/components/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults new file mode 100644 index 0000000..0a6271f --- /dev/null +++ b/components/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults @@ -0,0 +1,17 @@ +# Configure TinyUSB, it will be used to mock USB devices +CONFIG_TINYUSB_MSC_ENABLED=n +CONFIG_TINYUSB_CDC_ENABLED=n +CONFIG_TINYUSB_CDC_COUNT=0 +CONFIG_TINYUSB_HID_COUNT=0 + +# Disable watchdogs, they'd get triggered during unity interactive menu +# CONFIG_ESP_TASK_WDT_INIT is not set + +# Run-time checks of Heap and Stack +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y + +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/components/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults.esp32p4 b/components/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000..2dbcf83 --- /dev/null +++ b/components/esp_tinyusb/test_apps/dconn_detection/sdkconfig.defaults.esp32p4 @@ -0,0 +1 @@ +CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y diff --git a/components/esp_tinyusb/test_apps/msc_storage/CMakeLists.txt b/components/esp_tinyusb/test_apps/msc_storage/CMakeLists.txt new file mode 100644 index 0000000..e4d25e1 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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) +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(test_app_msc_storage) diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/CMakeLists.txt b/components/esp_tinyusb/test_apps/msc_storage/main/CMakeLists.txt new file mode 100644 index 0000000..76f407e --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES unity + PRIV_REQUIRES fatfs wear_levelling esp_partition + WHOLE_ARCHIVE) diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/Kconfig.projbuild b/components/esp_tinyusb/test_apps/msc_storage/main/Kconfig.projbuild new file mode 100644 index 0000000..4f8a289 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/Kconfig.projbuild @@ -0,0 +1,45 @@ +menu "TinyUSB MSC Test Configuration" + + orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps" + + menu "SDMMC GPIO Configuration" + depends on SOC_SDMMC_USE_GPIO_MATRIX && SOC_SDMMC_HOST_SUPPORTED + + config TEST_SDMMC_PIN_CMD + int "CMD GPIO number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX + default 35 if IDF_TARGET_ESP32S3 + default 44 if IDF_TARGET_ESP32P4 + + config TEST_SDMMC_PIN_CLK + int "CLK GPIO number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX + default 36 if IDF_TARGET_ESP32S3 + default 43 if IDF_TARGET_ESP32P4 + + config TEST_SDMMC_PIN_D0 + int "D0 GPIO number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX + default 37 if IDF_TARGET_ESP32S3 + default 39 if IDF_TARGET_ESP32P4 + + config TEST_SDMMC_PIN_D1 + int "D1 GPIO number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX + default 38 if IDF_TARGET_ESP32S3 + default 40 if IDF_TARGET_ESP32P4 + + config TEST_SDMMC_PIN_D2 + int "D2 GPIO number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX + default 33 if IDF_TARGET_ESP32S3 + default 41 if IDF_TARGET_ESP32P4 + + config TEST_SDMMC_PIN_D3 + int "D3 GPIO number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX + default 34 if IDF_TARGET_ESP32S3 + default 42 if IDF_TARGET_ESP32P4 + + endmenu # SDMMC GPIO Configuration +endmenu # TinyUSB MSC Test Configuration diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/device_common.c b/components/esp_tinyusb/test_apps/msc_storage/main/device_common.c new file mode 100644 index 0000000..791c3a6 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/device_common.c @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "tinyusb.h" + +static SemaphoreHandle_t wait_mount = NULL; + +#define TUSB_DEVICE_DELAY_MS 5000 + +void test_device_setup(void) +{ + wait_mount = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(wait_mount); +} + +void test_device_release(void) +{ + TEST_ASSERT_NOT_NULL(wait_mount); + vSemaphoreDelete(wait_mount); +} + +void test_device_wait(void) +{ + // Wait for tud_mount_cb() to be called (first timeout) + if (xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TUSB_DEVICE_DELAY_MS)) != pdTRUE) { + ESP_LOGW("device timeout!", "Device did not appear in first %d ms, waiting again...", TUSB_DEVICE_DELAY_MS); + // Wait for the second timeout + TEST_ASSERT_EQUAL_MESSAGE(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TUSB_DEVICE_DELAY_MS)), "No tusb_mount_cb() after second timeout"); + } + // Delay to allow finish the enumeration + // Disable this delay could lead to potential race conditions when the tud_task() is pinned to another CPU + vTaskDelay(pdMS_TO_TICKS(250)); +} + +/** + * @brief TinyUSB callback for device mount. + * + * @note + * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. + * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. + */ +void test_device_event_handler(tinyusb_event_t *event, void *arg) +{ + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + xSemaphoreGive(wait_mount); + break; + case TINYUSB_EVENT_DETACHED: + break; + default: + break; + } +} + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/device_common.h b/components/esp_tinyusb/test_apps/msc_storage/main/device_common.h new file mode 100644 index 0000000..b286f96 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/device_common.h @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "tinyusb.h" + +// +// ========================== Test Configuration Parameters ===================================== +// + +#define TEST_DEVICE_PRESENCE_TIMEOUT_MS 5000 // Timeout for checking device presence + +/** + * @brief Test device setup + */ +void test_device_setup(void); + +/** + * @brief Test device release + */ +void test_device_release(void); + +/** + * @brief Test device wait + * + * Waits the tusb_mount_cb() which indicates the device connected to the Host and enumerated. + */ +void test_device_wait(void); + +/** + * @brief TinyUSB callback for device mount. + * + * @note + * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. + * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. + */ +void test_device_event_handler(tinyusb_event_t *event, void *arg); diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/idf_component.yml b/components/esp_tinyusb/test_apps/msc_storage/main/idf_component.yml new file mode 100644 index 0000000..b1cb5b5 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "*" + override_path: "../../../" diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/storage_common.c b/components/esp_tinyusb/test_apps/msc_storage/main/storage_common.c new file mode 100644 index 0000000..e74f47d --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/storage_common.c @@ -0,0 +1,146 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "esp_idf_version.h" +#include "sdkconfig.h" +#include "storage_common.h" + +#if (SOC_SDMMC_HOST_SUPPORTED) +#include "driver/sdmmc_host.h" +#endif // SOC_SDMMC_HOST_SUPPORTED + +// SDMMC GPIO configuration +#define TEST_SDMMC_PIN_CMD CONFIG_TEST_SDMMC_PIN_CMD +#define TEST_SDMMC_PIN_CLK CONFIG_TEST_SDMMC_PIN_CLK +#define TEST_SDMMC_PIN_D0 CONFIG_TEST_SDMMC_PIN_D0 +#define TEST_SDMMC_PIN_D1 CONFIG_TEST_SDMMC_PIN_D1 +#define TEST_SDMMC_PIN_D2 CONFIG_TEST_SDMMC_PIN_D2 +#define TEST_SDMMC_PIN_D3 CONFIG_TEST_SDMMC_PIN_D3 + +// IDF VERSION +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#if (SOC_SDMMC_IO_POWER_EXTERNAL) +// Some boards required internal LDO to be enabled for SDMMC initialization +// To understand if your board requires this, please refer to the board's documentation +#define TEST_SDMMC_INIT_INTERNAL_LDO 1 // Enable internal LDO for SDMMC initialization +#define TEST_SDMMC_LDO_CHAN_ID 4 +#endif // (SOC_SDMMC_IO_POWER_EXTERNAL) +#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) + +#if (TEST_SDMMC_INIT_INTERNAL_LDO) +#include "sd_pwr_ctrl_by_on_chip_ldo.h" + +static sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL; +#endif // TEST_SDMMC_INIT_INTERNAL_LDO + +void storage_init_spiflash(wl_handle_t *wl_handle) +{ + wl_handle_t wl; + const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_FAT, "storage"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, wl_mount(data_partition, &wl), "Failed to mount wear levelling on storage partition"); + + printf("SPIFLASH Wear Levelling initialized successfully\n"); + printf("\tSectors: %u\n", wl_size(wl) / wl_sector_size(wl)); + printf("\tSector Size: %u\n", wl_sector_size(wl)); + *wl_handle = wl; +} + +void storage_erase_spiflash(void) +{ + const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_FAT, "storage"); + TEST_ASSERT_NOT_NULL_MESSAGE(data_partition, "Storage partition not found"); + // Erase the data partition + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, esp_partition_erase_range(data_partition, 0, data_partition->size), + "Failed to erase storage partition"); + printf("Storage partition erased successfully (size in bytes: %ld)\n", data_partition->size); +} + +void storage_deinit_spiflash(wl_handle_t wl_handle) +{ + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, wl_unmount(wl_handle), "Failed to unmount wear levelling on data partition"); +} + +#if (SOC_SDMMC_HOST_SUPPORTED) +void storage_init_sdmmc(sdmmc_card_t **card) +{ + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); +#if TEST_SDMMC_INIT_INTERNAL_LDO + sd_pwr_ctrl_ldo_config_t ldo_config = { + .ldo_chan_id = TEST_SDMMC_LDO_CHAN_ID, + }; + + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle), + "Failed to create a new on-chip LDO power control driver"); + host.pwr_ctrl_handle = pwr_ctrl_handle; +#endif // TEST_SDMMC_INIT_INTERNAL_LDO + + // This initializes the slot without card detect (CD) and write protect (WP) signals. + // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals. + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + // Set the slot configuration parameters + slot_config.width = 4; +#if (SOC_SDMMC_USE_GPIO_MATRIX) + slot_config.cmd = TEST_SDMMC_PIN_CMD; + slot_config.clk = TEST_SDMMC_PIN_CLK; + slot_config.d0 = TEST_SDMMC_PIN_D0; + slot_config.d1 = TEST_SDMMC_PIN_D1; + slot_config.d2 = TEST_SDMMC_PIN_D2; + slot_config.d3 = TEST_SDMMC_PIN_D3; +#endif // SOC_SDMMC_USE_GPIO_MATRIX + + // Enable internal pullups on enabled pins. The internal pullups + // are insufficient however, please make sure 10k external pullups are + // connected on the bus. This is for debug / example purpose only. + slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; + + // not using ff_memalloc here, as allocation in internal RAM is preferred + sdmmc_card_t *sd_card = (sdmmc_card_t *)malloc(sizeof(sdmmc_card_t)); + TEST_ASSERT_NOT_NULL_MESSAGE(sd_card, "Failed to allocate memory for sdmmc_card_t"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, sdmmc_host_init(), "SDMMC Host Config Init fail"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, sdmmc_host_init_slot(host.slot, (const sdmmc_slot_config_t *) &slot_config), "Host init slot fail"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, sdmmc_card_init(&host, sd_card), "SDMMC Card init fail"); + + *card = sd_card; + + printf("SDMMC Card initialized successfully\n"); + printf("\tSectors: %u\n", sd_card->csd.capacity); + printf("\tSector Size: %u\n", sd_card->csd.sector_size); +} + +void storage_erase_sdmmc(sdmmc_card_t *card) +{ + // Erase the SDMMC card + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, sdmmc_full_erase(card), "Failed to erase SDMMC card"); + printf("SDMMC Card erased successfully\n"); +} + +void storage_deinit_sdmmc(sdmmc_card_t *card) +{ + // Deinit the host + sdmmc_host_deinit(); + // Delete the sd_card pointer + free(card); +#if TEST_SDMMC_INIT_INTERNAL_LDO + sd_pwr_ctrl_del_on_chip_ldo(pwr_ctrl_handle); +#endif // TEST_SDMMC_INIT_INTERNAL_LDO +} +#endif // SOC_SDMMC_HOST_SUPPORTED + + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/storage_common.h b/components/esp_tinyusb/test_apps/msc_storage/main/storage_common.h new file mode 100644 index 0000000..412fbbe --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/storage_common.h @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "wear_levelling.h" + +#if (SOC_SDMMC_HOST_SUPPORTED) +#include "sdmmc_cmd.h" +#endif // SOC_SDMMC_HOST_SUPPORTED + +/** + * @brief Initialize the SPIFLASH storage with wear levelling + * + * @param[out] wl_handle Pointer to the wear levelling handle that will be initialized + */ +void storage_init_spiflash(wl_handle_t *wl_handle); + +/** + * @brief Erase the SPIFLASH storage partition + * + * This function erases the entire SPIFLASH storage partition. + * + * @note There is no protection against accidental data loss and collision with wear levelling, use with caution. + */ +void storage_erase_spiflash(void); + +/** + * @brief Deinitialize the SPIFLASH storage and unmount wear levelling + * + * @param[in] wl_handle Wear levelling handle, obtained from storage_init_spiflash + */ +void storage_deinit_spiflash(wl_handle_t wl_handle); + +#if (SOC_SDMMC_HOST_SUPPORTED) +/** + * @brief Initialize the SDMMC storage + * + * This function initializes the SDMMC host and card, setting up the necessary configurations. + * + * @param[out] card Pointer to a pointer that will hold the initialized sdmmc_card_t structure + */ +void storage_init_sdmmc(sdmmc_card_t **card); + +/** + * @brief Erase the SDMMC storage + * + * This function erases the entire SDMMC card. + * + * @param[in] card Pointer to the sdmmc_card_t structure that was initialized in storage_init_sdmmc + */ +void storage_erase_sdmmc(sdmmc_card_t *card); + +/** + * @brief Deinitialize the SDMMC storage + * + * @param[in] card Pointer to the sdmmc_card_t structure that was initialized in storage_init_sdmmc + */ +void storage_deinit_sdmmc(sdmmc_card_t *card); +#endif // SOC_SDMMC_HOST_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/test_app_main.c b/components/esp_tinyusb/test_apps/msc_storage/main/test_app_main.c new file mode 100644 index 0000000..c182710 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/test_app_main.c @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" +#include "device_common.h" +#include "test_msc_common.h" + +/* setUp runs before every test */ +void setUp(void) +{ + unity_utils_record_free_mem(); + test_device_setup(); + test_storage_event_queue_setup(); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + // Short delay to allow task to be cleaned up + vTaskDelay(10); + test_device_release(); + test_storage_event_queue_teardown(); + unity_utils_evaluate_leaks(); +} + + +void app_main(void) +{ + /* + _ _ _ + | | (_) | | + ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ + / _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \ + | __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) | + \___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/ + | |______ __/ | + |_|______| |___/ + _____ _____ _____ _____ + |_ _| ___/ ___|_ _| + | | | |__ \ `--. | | + | | | __| `--. \ | | + | | | |___/\__/ / | | + \_/ \____/\____/ \_/ + */ + + printf(" _ _ _ \n"); + printf(" | | (_) | | \n"); + printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n"); + printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n"); + printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n"); + printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n"); + printf(" | |______ __/ | \n"); + printf(" |_|______| |___/ \n"); + printf(" _____ _____ _____ _____ \n"); + printf("|_ _| ___/ ___|_ _| \n"); + printf(" | | | |__ \\ `--. | | \n"); + printf(" | | | __| `--. \\ | | \n"); + printf(" | | | |___/\\__/ / | | \n"); + printf(" \\_/ \\____/\\____/ \\_/ \n"); + + unity_utils_setup_heap_record(80); + /* The leakage level does depend on esp-idf release */ + /* TODO: Increased from 1048 to 1600 as a workaround to unblock the CI */ + unity_utils_set_leak_level(1600); // 128 (default) + 820 (wl_mount) + 100 (in case of driver format) + unity_run_menu(); +} diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_common.c b/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_common.c new file mode 100644 index 0000000..b2f34c0 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_common.c @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED + +#include "unity.h" +#include "test_msc_common.h" + + +#define TEST_QUEUE_LEN 6 // Length of the queue for storage events +#define TEST_STORAGE_EVENT_TIMEOUT_MS 5000 // Timeout for waiting storage events + +typedef struct { + tinyusb_msc_event_id_t event_id; /*!< Event ID */ +} test_storage_event_t; + +const char *storage_event_str[] = { + "Mount Start", + "Mount Complete", + "Mount Failed", + "Format Required", +}; + +static QueueHandle_t _test_storage_event_queue = NULL; + +void test_storage_event_queue_setup(void) +{ + _test_storage_event_queue = xQueueCreate(TEST_QUEUE_LEN, sizeof(test_storage_event_t)); + TEST_ASSERT_NOT_NULL(_test_storage_event_queue); +} + +void test_storage_event_queue_teardown(void) +{ + if (_test_storage_event_queue) { + vQueueDelete(_test_storage_event_queue); + _test_storage_event_queue = NULL; + } +} + +void test_storage_event_cb(tinyusb_msc_storage_handle_t handle, tinyusb_msc_event_t *event, void *arg) +{ + printf("Storage event\n"); + + switch (event->id) { + case TINYUSB_MSC_EVENT_MOUNT_START: + case TINYUSB_MSC_EVENT_MOUNT_COMPLETE: + printf("\t-> %s, mounted to %s\n", storage_event_str[event->id], (event->mount_point == TINYUSB_MSC_STORAGE_MOUNT_USB) ? "USB" : "APP"); + break; + case TINYUSB_MSC_EVENT_MOUNT_FAILED: + case TINYUSB_MSC_EVENT_FORMAT_REQUIRED: + printf("\t-> %s\n", storage_event_str[event->id]); + break; + default: + printf("Unknown storage event: %d\n", event->id); + TEST_ASSERT_MESSAGE(0, "Unknown storage event received"); + break; + } + + test_storage_event_t msg = { + .event_id = event->id, + }; + xQueueSend(_test_storage_event_queue, &msg, portMAX_DELAY); +} + +void test_storage_event_wait_callback(tinyusb_msc_event_id_t event_id) +{ + TEST_ASSERT_NOT_NULL(_test_storage_event_queue); + // Wait for port callback to send an event message + test_storage_event_t msg; + BaseType_t ret = xQueueReceive(_test_storage_event_queue, &msg, pdMS_TO_TICKS(TEST_STORAGE_EVENT_TIMEOUT_MS)); + TEST_ASSERT_EQUAL_MESSAGE(pdPASS, ret, "MSC storage event not generated on time"); + // Check the contents of that event message + printf("\tMSC storage event: %s\n", storage_event_str[msg.event_id]); + TEST_ASSERT_EQUAL_MESSAGE(event_id, msg.event_id, "Unexpected MSC storage event type received"); +} + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_common.h b/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_common.h new file mode 100644 index 0000000..edf9a70 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_common.h @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tinyusb_msc.h" + + +void test_storage_event_queue_setup(void); + +void test_storage_event_queue_teardown(void); + +/** + * @brief Callback to handle MSC Storage mount changed events + * + * This callback is triggered when the storage mount state changes. + * + * @param event Pointer to the event data structure containing mount state information. + */ +void test_storage_event_cb(tinyusb_msc_storage_handle_t handle, tinyusb_msc_event_t *event, void *arg); + +/** + * @brief Wait for a specific storage event to be generated + * + * @param event_id The expected event ID to wait for + */ +void test_storage_event_wait_callback(tinyusb_msc_event_id_t expected_event_id); diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_filesystem.c b/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_filesystem.c new file mode 100644 index 0000000..5576fae --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_filesystem.c @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "tinyusb_msc.h" +// +#include "device_common.h" +#include "storage_common.h" +#include "test_msc_common.h" + + +/** + * @brief Test case for verifying filesystem access when storage is mounted to both APP and USB + * + * Goal is to verify that filesystem available: + * - via filesystem API in APP + * - via USB MSC class to the Host + */ +TEST_CASE("MSC: Verify filesystem access APP and USB", "[storage][fs]") +{ + tinyusb_msc_driver_config_t driver_cfg = { + .callback = test_storage_event_cb, // Test callback + .callback_arg = NULL, // No additional argument for the callback + }; + + // Install the MSC driver + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_install_driver(&driver_cfg), "Failed to install TinyUSB MSC driver"); + + // Create a storage on SPI Flash + wl_handle_t wl_handle = WL_INVALID_HANDLE; + storage_init_spiflash(&wl_handle); + TEST_ASSERT_NOT_EQUAL_MESSAGE(WL_INVALID_HANDLE, wl_handle, "Wear leveling handle is invalid, check the partition configuration"); + + tinyusb_msc_storage_handle_t storage_hdl; + tinyusb_msc_storage_config_t storage_cfg = { + .medium = { + .wl_handle = wl_handle, + }, + .fat_fs = { + .config = { + .max_files = 4, + }, + .do_not_format = false, + .format_flags = FM_ANY, + .base_path = "/msc_test", // Custom mount path + }, + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_APP, // Initially mount to APP + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_new_storage_spiflash(&storage_cfg, &storage_hdl), "Failed to create new MSC storage on SPI Flash"); + // Wait for the storage to be mounted + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + // As the filesystem on storage is mounted to APP, we can access it via VFS + const char *file_path = "/msc_test/hello.txt"; + FILE *f = fopen(file_path, "w"); + TEST_ASSERT_NOT_NULL_MESSAGE(f, "Failed to open file for writing on MSC storage"); + const char *text = "Hello, TinyUSB MSC!"; + size_t written = fwrite(text, 1, strlen(text), f); + TEST_ASSERT_EQUAL_MESSAGE(strlen(text), written, "Failed to write the complete text to the file"); + fclose(f); + + // Reopen the file for reading + f = fopen(file_path, "r"); + TEST_ASSERT_NOT_NULL_MESSAGE(f, "Failed to open file for reading on MSC storage"); + char read_buf[32] = {0}; + size_t read = fread(read_buf, 1, sizeof(read_buf) - 1, f); + TEST_ASSERT_EQUAL_MESSAGE(written, read, "Failed to read the complete text from the file"); + fclose(f); + + // // Verify the read content + TEST_ASSERT_EQUAL_STRING_MESSAGE(text, read_buf, "Read content does not match written content"); + + // Mount the storage to USB + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_set_storage_mount_point(storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB), "Failed to set storage mount point to USB"); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + // As the filesystem on storage is mounted to USB, we cannot access it via VFS + f = fopen(file_path, "r"); + TEST_ASSERT_NULL_MESSAGE(f, "File should not be accessible when storage is mounted to USB"); + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + + test_device_wait(); + + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); + + // Uninstall TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + // Cleanup + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_delete_storage(storage_hdl), "Failed to delete TinyUSB MSC storage"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_uninstall_driver(), "Failed to uninstall TinyUSB MSC driver"); + storage_deinit_spiflash(wl_handle); +} + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_multitask.c b/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_multitask.c new file mode 100644 index 0000000..ffdf16e --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_multitask.c @@ -0,0 +1,133 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/event_groups.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_msc.h" + +// +// ========================== Test Configuration Parameters ===================================== +// + +#define MULTIPLE_THREADS_TASKS_NUM 5 + +#define BIT_0 ( 1 << 0 ) + +EventGroupHandle_t xCreatedEventGroup; + +static int nb_of_success; +static SemaphoreHandle_t nb_of_success_mutex = NULL; + +void init_success_counter(void) +{ + nb_of_success = 0; + nb_of_success_mutex = xSemaphoreCreateMutex(); + TEST_ASSERT_NOT_NULL_MESSAGE(nb_of_success_mutex, "Failed to create mutex"); +} + +static inline void increase_nb_of_success(void) +{ + xSemaphoreTake(nb_of_success_mutex, portMAX_DELAY); + nb_of_success++; + xSemaphoreGive(nb_of_success_mutex); +} + +static inline int get_nb_of_success(void) +{ + int value; + xSemaphoreTake(nb_of_success_mutex, portMAX_DELAY); + value = nb_of_success; + xSemaphoreGive(nb_of_success_mutex); + return value; +} + +void delete_success_counter(void) +{ + TEST_ASSERT_NOT_NULL_MESSAGE(nb_of_success_mutex, "Mutex is NULL"); + vSemaphoreDelete(nb_of_success_mutex); +} + +static void test_task_install(void *arg) +{ + TaskHandle_t parent_task_handle = (TaskHandle_t)arg; + + tinyusb_msc_driver_config_t driver_cfg = { + .callback = NULL, // No callback for mount changed events + .callback_arg = NULL, // No additional argument for the callback + }; + + EventBits_t uxBits = xEventGroupWaitBits( + xCreatedEventGroup, /* The event group being tested. */ + BIT_0, /* The bits within the event group to wait for. */ + pdTRUE, /* BIT_0 should be cleared before returning. */ + pdFALSE, /* Don't wait for both bits, either bit will do. */ + pdMS_TO_TICKS(1000)); /* Wait a maximum of 1s for either bit to be set. */ + + if (uxBits) { + if (tinyusb_msc_install_driver(&driver_cfg) == ESP_OK) { + vTaskDelay(10); // Let the other tasks to run + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_uninstall_driver(), "Failed to uninstall TinyUSB MSC driver"); + increase_nb_of_success(); + } + // Notify the parent task that the task completed the job + xTaskNotifyGive(parent_task_handle); + } + + // Delete task + vTaskDelete(NULL); +} + + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Trying to install MSC driver from several tasks + */ +TEST_CASE("MSC: driver multitask access", "[ci][driver]") +{ + init_success_counter(); + + xCreatedEventGroup = xEventGroupCreate(); + TEST_ASSERT_NOT_NULL_MESSAGE(xCreatedEventGroup, "Failed to create event group"); + + // Create tasks that will start the driver + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(test_task_install, + "InstallTask", + 4096, + (void *) xTaskGetCurrentTaskHandle(), + 4 + i, + NULL)); + } + // Set the event group bits to start the tasks + xEventGroupSetBits(xCreatedEventGroup, BIT_0); + // Wait until all tasks are finished + vTaskDelay(pdMS_TO_TICKS(2000)); + // Check if all tasks finished, we should get all notification from the tasks + TEST_ASSERT_EQUAL_MESSAGE(MULTIPLE_THREADS_TASKS_NUM, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000)), "Not all tasks finished"); + // There should be only one task that was able to install the driver + TEST_ASSERT_EQUAL_MESSAGE(1, get_nb_of_success(), "Only one task should be able to install the driver"); + vEventGroupDelete(xCreatedEventGroup); + delete_success_counter(); +} + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_storage.c b/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_storage.c new file mode 100644 index 0000000..1a29c57 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/main/test_msc_storage.c @@ -0,0 +1,684 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "device_common.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "tinyusb_msc.h" +#include "storage_common.h" +// +#include "test_msc_common.h" + +// +// ========================== TinyUSB MSC Storage Initialization Tests ============================= +// + +/** + * @brief Test case for checking the consistency of the public API functions + * + * This test case verifies that all public API functions are defined and have the expected signatures. + */ +TEST_CASE("MSC: Public API consistency", "[ci][driver]") +{ + // Check that the public API functions are consistent with the expected signatures + TEST_ASSERT_NOT_NULL(tinyusb_msc_install_driver); + TEST_ASSERT_NOT_NULL(tinyusb_msc_uninstall_driver); + TEST_ASSERT_NOT_NULL(tinyusb_msc_new_storage_spiflash); +#if (SOC_SDMMC_HOST_SUPPORTED) + TEST_ASSERT_NOT_NULL(tinyusb_msc_new_storage_sdmmc); +#endif // SOC_SDMMC_HOST_SUPPORTED + TEST_ASSERT_NOT_NULL(tinyusb_msc_delete_storage); + TEST_ASSERT_NOT_NULL(tinyusb_msc_set_storage_callback); + TEST_ASSERT_NOT_NULL(tinyusb_msc_set_storage_mount_point); + TEST_ASSERT_NOT_NULL(tinyusb_msc_config_storage_fat_fs); + TEST_ASSERT_NOT_NULL(tinyusb_msc_format_storage); + TEST_ASSERT_NOT_NULL(tinyusb_msc_get_storage_capacity); + TEST_ASSERT_NOT_NULL(tinyusb_msc_get_storage_sector_size); + TEST_ASSERT_NOT_NULL(tinyusb_msc_get_storage_mount_point); + + // Functions signatures should match the expected ones and do not fall during compilation + // Driver + tinyusb_msc_driver_config_t config = { 0 }; + tinyusb_msc_install_driver(&config); + tinyusb_msc_uninstall_driver(); + + // Storage + tinyusb_msc_storage_handle_t storage_hdl = NULL; + tinyusb_msc_storage_config_t storage_config = { 0 }; + tinyusb_msc_new_storage_spiflash(&storage_config, &storage_hdl); +#if (SOC_SDMMC_HOST_SUPPORTED) + tinyusb_msc_new_storage_sdmmc(&storage_config, &storage_hdl); +#endif // SOC_SDMMC_HOST_SUPPORTED + tinyusb_msc_delete_storage(storage_hdl); + + // Setters, Getters & Config + tinyusb_msc_set_storage_callback(test_storage_event_cb, NULL); + tinyusb_msc_set_storage_mount_point(storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB); + tinyusb_msc_config_storage_fat_fs(storage_hdl, NULL); + tinyusb_msc_format_storage(storage_hdl); + uint32_t sector_count = 0; + tinyusb_msc_get_storage_capacity(storage_hdl, §or_count); + uint32_t sector_size = 0; + tinyusb_msc_get_storage_sector_size(storage_hdl, §or_size); + tinyusb_msc_mount_point_t mount_point = TINYUSB_MSC_STORAGE_MOUNT_USB; + tinyusb_msc_get_storage_mount_point(storage_hdl, &mount_point); +} + +/** + * @brief Test case for installing TinyUSB MSC driver without storage + * + * Scenario: + * 1. Install TinyUSB MSC driver without storage. + * 2. Install TinyUSB driver with the MSC configuration. + * 3. Wait for the device to be recognized. + * 4. Uninstall TinyUSB driver and deinitialize the MSC driver. + */ +TEST_CASE("MSC: driver install & uninstall without storage", "[ci][driver]") +{ + tinyusb_msc_driver_config_t driver_cfg = { + .callback = NULL, // Register the callback for mount changed events + .callback_arg = NULL, // No additional argument for the callback + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_install_driver(&driver_cfg), "Failed to install TinyUSB MSC driver"); + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + + // No storage events without storage + + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); // Allow some time for the device to be recognized + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_uninstall_driver(), "Failed to uninstall TinyUSB MSC driver"); +} + +/** + * @brief Test case for installing TinyUSB MSC storage without installing the driver + * + * Scenario: + * 1. Init SPI Flash storage to obtain the wear leveling handle. + * 2. Create TinyUSB MSC Storage with SPI Flash. + * 3. Wait for the device to be recognized. + * 4. Uninstall TinyUSB driver, delete storage and cleanup test. + */ +TEST_CASE("MSC: enable storage without driver install", "[ci][driver]") +{ + wl_handle_t wl_handle = WL_INVALID_HANDLE; + storage_init_spiflash(&wl_handle); + TEST_ASSERT_NOT_EQUAL_MESSAGE(WL_INVALID_HANDLE, wl_handle, "Wear leveling handle is invalid, check the partition configuration"); + + tinyusb_msc_storage_config_t config = { + .medium.wl_handle = wl_handle, // Set the context to the wear leveling handle + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_USB, // Initial mount point to USB + .fat_fs = { + .base_path = NULL, // Use default base path + .config.max_files = 5, // Maximum number of files that can be opened simultaneously + .format_flags = 0, // No special format flags + }, + }; + + // Initialize TinyUSB MSC storage + tinyusb_msc_storage_handle_t storage_hdl = NULL; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_new_storage_spiflash(&config, &storage_hdl), "Failed to initialize TinyUSB MSC storage with SPIFLASH"); + // Configure the callback for mount changed events + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_set_storage_callback(test_storage_event_cb, NULL), "Failed to set storage event callback"); + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); // Allow some time for the device to be recognized + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_delete_storage(storage_hdl), "Failed to delete TinyUSB MSC storage"); + storage_deinit_spiflash(wl_handle); +} + +/** + * @brief Test case for initializing TinyUSB MSC storage with SPIFLASH + * + * Scenario: + * 1. Create a queue for storage events. + * 2. Initialize SPIFLASH storage with wear levelling. + * 3. Configure TinyUSB MSC with the SPIFLASH storage and mounting to APP. + * 4. Install TinyUSB driver with the MSC configuration. + * 5. Wait for the storage to be mounted, verify that re-mount callbacks are received. + * 7. Uninstall TinyUSB driver and deinitialize SPIFLASH storage. + * 8. Delete the storage event queue. + */ +TEST_CASE("MSC: storage SPI Flash", "[ci][storage][spiflash]") +{ + wl_handle_t wl_handle = WL_INVALID_HANDLE; + storage_init_spiflash(&wl_handle); + TEST_ASSERT_NOT_EQUAL_MESSAGE(WL_INVALID_HANDLE, wl_handle, "Wear leveling handle is invalid, check the partition configuration"); + + tinyusb_msc_driver_config_t driver_cfg = { + .callback = test_storage_event_cb, // Register the callback for mount changed events + .callback_arg = NULL, // No additional argument for the callback + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_install_driver(&driver_cfg), "Failed to install TinyUSB MSC driver"); + + tinyusb_msc_storage_config_t config = { + .medium.wl_handle = wl_handle, // Set the context to the wear leveling handle + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_APP, // Initial mount point to APP + .fat_fs = { + .base_path = NULL, // Use default base path + .config.max_files = 5, // Maximum number of files that can be opened simultaneously + .format_flags = 0, // No special format flags + }, + }; + + // Initialize TinyUSB MSC storage + tinyusb_msc_storage_handle_t storage_hdl = NULL; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_new_storage_spiflash(&config, &storage_hdl), "Failed to initialize TinyUSB MSC storage with SPIFLASH"); + // Wait for the storage to be mounted + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); // Allow some time for the device to be recognized + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_delete_storage(storage_hdl), "Failed to delete TinyUSB MSC storage"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_uninstall_driver(), "Failed to uninstall TinyUSB MSC driver"); + storage_deinit_spiflash(wl_handle); +} + +#if (SOC_SDMMC_HOST_SUPPORTED) +/** + * @brief Test case for initializing TinyUSB MSC storage with SDMMC + * + * Scenario: + * 1. Create a queue for storage events. + * 2. Initialize SDMMC storage. + * 3. Configure TinyUSB MSC with the SDMMC storage mounted to APP. + * 4. Verify that mount callbacks are registered correctly. + * 5. Install TinyUSB driver with the MSC configuration. + * 6. Wait for the storage to be mounted, verify that re-mount callbacks are received. + * 7. Uninstall TinyUSB driver and deinitialize SDMMC storage. + * 8. Delete the storage event queue. + */ +TEST_CASE("MSC: storage SD/MMC", "[storage][sdmmc]") +{ + sdmmc_card_t *card = NULL; + storage_init_sdmmc(&card); + TEST_ASSERT_NOT_NULL_MESSAGE(card, "SD/MMC card handle is NULL, check the SDMMC configuration"); + + tinyusb_msc_driver_config_t driver_cfg = { + .callback = test_storage_event_cb, // Register the callback for mount changed events + .callback_arg = NULL, // No additional argument for the callback + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_install_driver(&driver_cfg), "Failed to install TinyUSB MSC driver"); + + tinyusb_msc_storage_config_t config = { + .medium.card = card, // Set the context to the SDMMC card handle + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_APP, // Initial mount point to APP + .fat_fs = { + .base_path = NULL, // Use default base path + .config.max_files = 5, // Maximum number of files that can be opened simultaneously + .format_flags = 0, // No special format flags + }, + }; + + // Initialize TinyUSB MSC storage + tinyusb_msc_storage_handle_t storage_hdl = NULL; + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_new_storage_sdmmc(&config, &storage_hdl)); + // Wait for the storage to be mounted + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + // Verify card capacity and sector size + uint32_t capacity = 0; + uint32_t sector_size = 0; + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_get_storage_capacity(storage_hdl, &capacity)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_get_storage_sector_size(storage_hdl, §or_size)); + + TEST_ASSERT_EQUAL_MESSAGE(card->csd.capacity, capacity, "SDMMC card capacity does not match TinyUSB MSC storage sector count"); + TEST_ASSERT_EQUAL_MESSAGE(card->csd.sector_size, sector_size, "SDMMC card sector size does not match TinyUSB MSC storage sector size"); + + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); // Allow some time for the device to be recognized + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_delete_storage(storage_hdl), "Failed to delete TinyUSB MSC storage"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_uninstall_driver(), "Failed to uninstall TinyUSB MSC driver"); + storage_deinit_sdmmc(card); +} + +/** + * @brief Test case for initializing TinyUSB MSC storage with SPIFLASH + * + * Scenario: + * 1. Create a queue for storage events. + * 2. Initialize SPIFLASH storage1 with wear levelling. + * 3. Initialize SDMMC storage2 with SD/MMC. + * 3. Configure TinyUSB MSC with the SPIFLASH storage and mounting to USB. + * 4. Install TinyUSB driver with the MSC configuration. + * 5. Wait for the storages to be mounted. + * 7. Uninstall TinyUSB driver and deinitialize storages. + * 8. Delete the storage event queue. + */ +TEST_CASE("MSC: dual storage SPIFLASH + SDMMC", "[storage][spiflash][sdmmc]") +{ + wl_handle_t wl_handle = WL_INVALID_HANDLE; + storage_init_spiflash(&wl_handle); + TEST_ASSERT_NOT_EQUAL_MESSAGE(WL_INVALID_HANDLE, wl_handle, "Wear leveling handle1 is invalid, check the partition configuration"); + + sdmmc_card_t *card = NULL; + storage_init_sdmmc(&card); + TEST_ASSERT_NOT_NULL_MESSAGE(card, "SDMMC card handle is NULL, check the SDMMC configuration"); + + tinyusb_msc_driver_config_t driver_cfg = { + .callback = test_storage_event_cb, // Register the callback for mount changed events + .callback_arg = NULL, // No additional argument for the callback + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_install_driver(&driver_cfg), "Failed to install TinyUSB MSC driver"); + + tinyusb_msc_storage_config_t config = { + .medium.wl_handle = wl_handle, // Set the context to the wear leveling handle + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_APP, // Initial mount point to APP + .fat_fs = { + .base_path = "/custom1", // Use default base path + .config.max_files = 5, // Maximum number of files that can be opened simultaneously + .format_flags = 0, // No special format flags + }, + }; + + // Initialize TinyUSB MSC storage1 + tinyusb_msc_storage_handle_t storage1_hdl = NULL; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_new_storage_spiflash(&config, &storage1_hdl), "Failed to initialize TinyUSB MSC storage with SPIFLASH"); + // Wait for the storage to be mounted + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + + // Initialize TinyUSB MSC storage2 + tinyusb_msc_storage_handle_t storage2_hdl = NULL; + config.medium.card = card; // Change the medium to the SDMMC card handle + config.fat_fs.base_path = "/custom2"; // Use a different base path for the second storage + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_new_storage_sdmmc(&config, &storage2_hdl), "Failed to initialize second TinyUSB MSC storage with SDMMC"); + // Wait for the storage to be mounted + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + + test_device_wait(); + + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); // Allow some time for the device to be recognized + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_delete_storage(storage2_hdl), "Failed to delete TinyUSB MSC storage2"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_delete_storage(storage1_hdl), "Failed to delete TinyUSB MSC storage1"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_uninstall_driver(), "Failed to uninstall TinyUSB MSC driver"); + storage_deinit_spiflash(wl_handle); + storage_deinit_sdmmc(card); +} +#endif // SOC_SDMMC_HOST_SUPPORTED + +/** + * @brief Test case for initializing TinyUSB MSC storage with a specific base path + * + * Scenario: + * 1. Create a queue for storage events. + * 2. Initialize SPIFLASH storage with wear levelling. + * 3. Configure TinyUSB MSC with the SPIFLASH storage and mounting to APP with a custom base path. + * 4. Install TinyUSB driver with the MSC configuration. + * 5. Wait for the storage to be mounted, verify that re-mount callbacks are received. + * 6. Uninstall TinyUSB driver and deinitialize SPIFLASH storage. + * 7. Delete the storage event queue. + */ +TEST_CASE("MSC: storage specific base path", "[ci][storage][spiflash]") +{ + wl_handle_t wl_handle = WL_INVALID_HANDLE; + storage_init_spiflash(&wl_handle); + TEST_ASSERT_NOT_EQUAL_MESSAGE(WL_INVALID_HANDLE, wl_handle, "Wear leveling handle is invalid, check the partition configuration"); + + tinyusb_msc_driver_config_t driver_cfg = { + .callback = test_storage_event_cb, // Register the callback for mount changed events + .callback_arg = NULL, // No additional argument for the callback + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_install_driver(&driver_cfg), "Failed to install TinyUSB MSC driver"); + + tinyusb_msc_storage_config_t config = { + .medium.wl_handle = wl_handle, // Set the context to the wear leveling handle + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_APP, // Initial mount point to APP + .fat_fs = { + .base_path = "/custom1", // Use specific base path + .config.max_files = 5, // Maximum number of files that can be opened simultaneously + .format_flags = 0, // No special format flags + }, + }; + + // Initialize TinyUSB MSC storage + tinyusb_msc_storage_handle_t storage_hdl = NULL; + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_new_storage_spiflash(&config, &storage_hdl)); + // Wait for the storage to be mounted + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); // Allow some time for the device to be recognized + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_delete_storage(storage_hdl), "Failed to delete TinyUSB MSC storage"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_uninstall_driver(), "Failed to uninstall TinyUSB MSC driver"); + storage_deinit_spiflash(wl_handle); +} + +/** + * @brief Test case for initializing TinyUSB MSC storage with empty SPIFLASH + * + * Scenario: + * 1. Create a queue for storage events. + * 2. Erase the SPIFLASH storage. + * 3. Initialize SPIFLASH storage with wear levelling. + * 4. Configure TinyUSB MSC with the SPIFLASH storage and mounting to APP. + * 5. Verify callback presence for mount START and COMPLETE events. + * 6. Uninstall TinyUSB MSC driver and deinitialize SPIFLASH storage. + * 7. Delete the storage event queue. + */ +TEST_CASE("MSC: auto format storage SPI Flash", "[ci][storage][spiflash]") +{ + storage_erase_spiflash(); + + wl_handle_t wl_handle = WL_INVALID_HANDLE; + storage_init_spiflash(&wl_handle); + TEST_ASSERT_NOT_EQUAL_MESSAGE(WL_INVALID_HANDLE, wl_handle, "Wear leveling handle is invalid, check the partition configuration"); + + + tinyusb_msc_driver_config_t driver_cfg = { + .callback = test_storage_event_cb, // Register the callback for mount changed events + .callback_arg = NULL, // No additional argument for the callback + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_install_driver(&driver_cfg), "Failed to install TinyUSB MSC driver"); + + tinyusb_msc_storage_config_t config = { + .medium.wl_handle = wl_handle, // Set the context to the wear leveling handle + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_APP, // Initial mount point to APP + .fat_fs = { + .base_path = NULL, // Use default base path + .config.max_files = 5, // Maximum number of files that can be opened simultaneously + .format_flags = 0, // No special format flags + }, + }; + + // Initialize TinyUSB MSC storage + tinyusb_msc_storage_handle_t storage_hdl = NULL; + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_new_storage_spiflash(&config, &storage_hdl)); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); // Allow some time for the device to be recognized + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_delete_storage(storage_hdl), "Failed to delete TinyUSB MSC storage"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_uninstall_driver(), "Failed to uninstall TinyUSB MSC driver"); + storage_deinit_spiflash(wl_handle); +} + +/** + * @brief Test case for initializing TinyUSB MSC storage with SPIFLASH and do not format option when initial mount point is APP + * + * Scenario: + * 1. Create a queue for storage events. + * 2. Erase the SPIFLASH storage. + * 3. Initialize SPIFLASH storage with wear levelling. + * 4. Configure TinyUSB MSC with the SPIFLASH storage and mounting to APP with do not format option. + * 5. Verify callback presence for mount START and FORMAT_REQUIRED events. + * 6. Check that the storage is still mounted to APP. + * 7. Format the storage. + * 8. Install TinyUSB driver. + * 9. Wait for the storage to be mounted, verify that re-mount callbacks are received. + * 10. Uninstall TinyUSB MSC driver and deinitialize SPIFLASH storage. + * 11. Delete the storage event queue. + */ +TEST_CASE("MSC: format storage SPI Flash, mounted to APP", "[ci][storage][spiflash]") +{ + storage_erase_spiflash(); + + wl_handle_t wl_handle = WL_INVALID_HANDLE; + storage_init_spiflash(&wl_handle); + TEST_ASSERT_NOT_EQUAL_MESSAGE(WL_INVALID_HANDLE, wl_handle, "Wear leveling handle is invalid, check the partition configuration"); + + + tinyusb_msc_driver_config_t driver_cfg = { + .callback = test_storage_event_cb, // Register the callback for mount changed events + .callback_arg = NULL, // No additional argument for the callback + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_install_driver(&driver_cfg), "Failed to install TinyUSB MSC driver"); + + tinyusb_msc_storage_config_t config = { + .medium.wl_handle = wl_handle, // Set the context to the wear leveling handle + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_APP, // Initial mount point to APP + .fat_fs = { + .base_path = NULL, // Use default base path + .do_not_format = true, // Do not format the drive if filesystem is not present + .config.max_files = 5, // Maximum number of files that can be opened simultaneously + .format_flags = 0, // No special format flags + }, + }; + + // Initialize TinyUSB MSC storage + tinyusb_msc_storage_handle_t storage_hdl = NULL; + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_new_storage_spiflash(&config, &storage_hdl)); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_FORMAT_REQUIRED); + // Mount wasn't completed, so the storage mount point is APP + tinyusb_msc_mount_point_t mount_point; + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_get_storage_mount_point(storage_hdl, &mount_point)); + TEST_ASSERT_EQUAL(TINYUSB_MSC_STORAGE_MOUNT_APP, mount_point); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_format_storage(storage_hdl)); + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); // Allow some time for the device to be recognized + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_delete_storage(storage_hdl), "Failed to delete TinyUSB MSC storage"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_uninstall_driver(), "Failed to uninstall TinyUSB MSC driver"); + storage_deinit_spiflash(wl_handle); +} + +/** + * @brief Test case for initializing TinyUSB MSC storage with SPIFLASH and do not format option when initial mount point is USB + * + * Scenario: + * 1. Create a queue for storage events. + * 2. Erase the SPIFLASH storage. + * 3. Initialize SPIFLASH storage with wear levelling. + * 4. Configure TinyUSB MSC with the SPIFLASH storage and mounting to USB with do not format option. + * 6. Check that the storage is still mounted to USB. + * 7. Install TinyUSB driver. + * 8. Change the mount point to APP. + * 9. Verify callback presence for mount START and FORMAT_REQUIRED events. + * 10. Check that the storage is still mounted to USB. + * 11. Uninstall TinyUSB MSC driver and deinitialize SPIFLASH storage. + * 12. Delete the storage event queue. + */ +TEST_CASE("MSC: format storage SPI Flash, mounted to USB", "[ci][storage][spiflash]") +{ + storage_erase_spiflash(); + + wl_handle_t wl_handle = WL_INVALID_HANDLE; + storage_init_spiflash(&wl_handle); + TEST_ASSERT_NOT_EQUAL_MESSAGE(WL_INVALID_HANDLE, wl_handle, "Wear leveling handle is invalid, check the partition configuration"); + + + tinyusb_msc_driver_config_t driver_cfg = { + .callback = test_storage_event_cb, // Register the callback for mount changed events + .callback_arg = NULL, // No additional argument for the callback + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_install_driver(&driver_cfg), "Failed to install TinyUSB MSC driver"); + + tinyusb_msc_storage_config_t config = { + .medium.wl_handle = wl_handle, // Set the context to the wear leveling handle + .fat_fs = { + .base_path = NULL, // Use default base path + .do_not_format = true, // Do not format the drive if filesystem is not present + .config.max_files = 5, // Maximum number of files that can be opened simultaneously + .format_flags = 0, // No special format flags + }, + }; + + // Initialize TinyUSB MSC storage + tinyusb_msc_storage_handle_t storage_hdl = NULL; + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_new_storage_spiflash(&config, &storage_hdl)); + // We don't get the mount start event, since the storage is not mounted to the APP + // The storage should be mounted to USB + tinyusb_msc_mount_point_t mount_point; + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_get_storage_mount_point(storage_hdl, &mount_point)); + TEST_ASSERT_EQUAL(TINYUSB_MSC_STORAGE_MOUNT_USB, mount_point); + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + // We don't expect to receive the mount start event again, since the storage was exposed to the USB + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); // Allow some time for the device to be recognized + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + // Remount the storage to APP + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_set_storage_mount_point(storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_APP)); + // Wait for the storage to be mounted + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_FORMAT_REQUIRED); + // Mount was completed with ESP_OK, so the storage mount point should be APP now + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_get_storage_mount_point(storage_hdl, &mount_point)); + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_MSC_STORAGE_MOUNT_APP, mount_point, "Storage mount point is not APP after the format"); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_format_storage(storage_hdl)); + // Remount the storage to USB once again + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_set_storage_mount_point(storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB)); + // Wait for the storage to be mounted + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + // Check the mount point again + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_get_storage_mount_point(storage_hdl, &mount_point)); + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_MSC_STORAGE_MOUNT_USB, mount_point, "Storage mount point is not USB"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_delete_storage(storage_hdl), "Failed to delete TinyUSB MSC storage"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_uninstall_driver(), "Failed to uninstall TinyUSB MSC driver"); + storage_deinit_spiflash(wl_handle); +} + +#if (SOC_SDMMC_HOST_SUPPORTED) +/** + * @brief Test case for initializing TinyUSB MSC storage with SD/MMC and do not format option when initial mount point is APP + * + * Scenario: + * 1. Create a queue for storage events. + * 2. Initialize SD/MMC storage. + * 3. Erase the SD/MMC storage. + * 4. Configure TinyUSB MSC with the SD/MMC storage and mounting to APP with do not format option. + * 5. Verify callback presence for mount START and FORMAT_REQUIRED events. + * 6. Uninstall TinyUSB MSC driver and deinitialize SD/MMC storage. + * 7. Delete the storage event queue. + */ +TEST_CASE("MSC: format storage SD/MMC, mounted to APP", "[storage][sdmmc]") +{ + sdmmc_card_t *card = NULL; + storage_init_sdmmc(&card); + TEST_ASSERT_NOT_NULL_MESSAGE(card, "SD/MMC card handle is NULL, check the SDMMC configuration"); + storage_erase_sdmmc(card); + + tinyusb_msc_driver_config_t driver_cfg = { + .callback = test_storage_event_cb, // Register the callback for mount changed events + .callback_arg = NULL, // No additional argument for the callback + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_install_driver(&driver_cfg), "Failed to install TinyUSB MSC driver"); + + tinyusb_msc_storage_config_t config = { + .medium.card = card, // Set the context to the SDMMC card handle + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_APP, // Initial mount point to APP + .fat_fs = { + .base_path = NULL, // Use default base path + .config.max_files = 5, // Maximum number of files that can be opened simultaneously + .format_flags = 0, // No special format flags + .do_not_format = true, // Do not format the drive if filesystem is not present + }, + }; + + // Initialize TinyUSB MSC storage + tinyusb_msc_storage_handle_t storage_hdl = NULL; + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_new_storage_sdmmc(&config, &storage_hdl)); + // Wait for the storage to be mounted + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_FORMAT_REQUIRED); + // Mount was completed with ESP_OK, so the storage mount point should be APP now + tinyusb_msc_mount_point_t mount_point; + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_get_storage_mount_point(storage_hdl, &mount_point)); + TEST_ASSERT_EQUAL(TINYUSB_MSC_STORAGE_MOUNT_APP, mount_point); + // Format the storage + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_msc_format_storage(storage_hdl)); + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + // Wait for the storage to be mounted + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_START); + test_storage_event_wait_callback(TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); // Allow some time for the device to be recognized + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_delete_storage(storage_hdl), "Failed to delete TinyUSB MSC storage"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_uninstall_driver(), "Failed to uninstall TinyUSB MSC driver"); + storage_deinit_sdmmc(card); +} +#endif // SOC_SDMMC_HOST_SUPPORTED + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/msc_storage/partitions.csv b/components/esp_tinyusb/test_apps/msc_storage/partitions.csv new file mode 100644 index 0000000..1c79321 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, fat, , 1M, diff --git a/components/esp_tinyusb/test_apps/msc_storage/pytest_msc_storage.py b/components/esp_tinyusb/test_apps/msc_storage/pytest_msc_storage.py new file mode 100644 index 0000000..db19010 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/pytest_msc_storage.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded_idf.dut import IdfDut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.usb_device +@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +def test_usb_device_msc_storage(dut: IdfDut) -> None: + dut.run_all_single_board_cases(group=['ci']) # Run all test cases in the 'ci' group diff --git a/components/esp_tinyusb/test_apps/msc_storage/sdkconfig.defaults b/components/esp_tinyusb/test_apps/msc_storage/sdkconfig.defaults new file mode 100644 index 0000000..e29bd0c --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/sdkconfig.defaults @@ -0,0 +1,26 @@ +# Configure TinyUSB, it will be used to mock USB devices +CONFIG_TINYUSB_MSC_ENABLED=y + +# Partitions configuration, used by spiflash storage +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_MODE_PERF=y + +# On chips with USB Serial JTAG, disable secondary console which does not make sense when using console component +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y + +# Disable watchdogs, they'd get triggered during unity interactive menu +CONFIG_ESP_INT_WDT=n +CONFIG_ESP_TASK_WDT=n + +# Run-time checks of Heap and Stack +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y + +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/components/esp_tinyusb/test_apps/msc_storage/sdkconfig.defaults.esp32p4 b/components/esp_tinyusb/test_apps/msc_storage/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000..2dbcf83 --- /dev/null +++ b/components/esp_tinyusb/test_apps/msc_storage/sdkconfig.defaults.esp32p4 @@ -0,0 +1 @@ +CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y diff --git a/components/esp_tinyusb/test_apps/ncm/CMakeLists.txt b/components/esp_tinyusb/test_apps/ncm/CMakeLists.txt new file mode 100644 index 0000000..fa1d3d1 --- /dev/null +++ b/components/esp_tinyusb/test_apps/ncm/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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) +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(test_app_ncm) diff --git a/components/esp_tinyusb/test_apps/ncm/main/CMakeLists.txt b/components/esp_tinyusb/test_apps/ncm/main/CMakeLists.txt new file mode 100644 index 0000000..e81e627 --- /dev/null +++ b/components/esp_tinyusb/test_apps/ncm/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES unity + WHOLE_ARCHIVE) diff --git a/components/esp_tinyusb/test_apps/ncm/main/device_common.c b/components/esp_tinyusb/test_apps/ncm/main/device_common.c new file mode 100644 index 0000000..030d93e --- /dev/null +++ b/components/esp_tinyusb/test_apps/ncm/main/device_common.c @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "tinyusb.h" + +static SemaphoreHandle_t wait_mount = NULL; + +#define TUSB_DEVICE_DELAY_MS 5000 + +void test_device_setup(void) +{ + wait_mount = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(wait_mount); +} + +void test_device_release(void) +{ + TEST_ASSERT_NOT_NULL(wait_mount); + vSemaphoreDelete(wait_mount); +} + +void test_device_wait(void) +{ + // Wait for tud_mount_cb() to be called + TEST_ASSERT_EQUAL_MESSAGE(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TUSB_DEVICE_DELAY_MS)), "No tusb_mount_cb() in time"); + // Delay to allow finish the enumeration + // Disable this delay could lead to potential race conditions when the tud_task() is pinned to another CPU + vTaskDelay(pdMS_TO_TICKS(250)); +} + +/** + * @brief TinyUSB callback for device mount. + * + * @note + * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. + * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. + */ +void test_device_event_handler(tinyusb_event_t *event, void *arg) +{ + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + xSemaphoreGive(wait_mount); + break; + case TINYUSB_EVENT_DETACHED: + break; + default: + break; + } +} + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/ncm/main/device_common.h b/components/esp_tinyusb/test_apps/ncm/main/device_common.h new file mode 100644 index 0000000..851d0d3 --- /dev/null +++ b/components/esp_tinyusb/test_apps/ncm/main/device_common.h @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "tinyusb.h" + +/** + * @brief Test device setup + */ +void test_device_setup(void); + +/** + * @brief Test device release + */ +void test_device_release(void); + +/** + * @brief Test device wait + * + * Waits the tusb_mount_cb() which indicates the device connected to the Host and enumerated. + */ +void test_device_wait(void); + +/** + * @brief TinyUSB callback for device mount. + * + * @note + * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. + * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. + */ +void test_device_event_handler(tinyusb_event_t *event, void *arg); diff --git a/components/esp_tinyusb/test_apps/ncm/main/idf_component.yml b/components/esp_tinyusb/test_apps/ncm/main/idf_component.yml new file mode 100644 index 0000000..b1cb5b5 --- /dev/null +++ b/components/esp_tinyusb/test_apps/ncm/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "*" + override_path: "../../../" diff --git a/components/esp_tinyusb/test_apps/ncm/main/test_app_main.c b/components/esp_tinyusb/test_apps/ncm/main/test_app_main.c new file mode 100644 index 0000000..20728fa --- /dev/null +++ b/components/esp_tinyusb/test_apps/ncm/main/test_app_main.c @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" +#include "device_common.h" + +/* setUp runs before every test */ +void setUp(void) +{ + unity_utils_record_free_mem(); + test_device_setup(); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + // Short delay to allow task to be cleaned up + vTaskDelay(10); + test_device_release(); + unity_utils_evaluate_leaks(); +} + + +void app_main(void) +{ + /* + _ _ _ + | | (_) | | + ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ + / _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \ + | __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) | + \___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/ + | |______ __/ | + |_|______| |___/ + _____ _____ _____ _____ + |_ _| ___/ ___|_ _| + | | | |__ \ `--. | | + | | | __| `--. \ | | + | | | |___/\__/ / | | + \_/ \____/\____/ \_/ + */ + + printf(" _ _ _ \n"); + printf(" | | (_) | | \n"); + printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n"); + printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n"); + printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n"); + printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n"); + printf(" | |______ __/ | \n"); + printf(" |_|______| |___/ \n"); + printf(" _____ _____ _____ _____ \n"); + printf("|_ _| ___/ ___|_ _| \n"); + printf(" | | | |__ \\ `--. | | \n"); + printf(" | | | __| `--. \\ | | \n"); + printf(" | | | |___/\\__/ / | | \n"); + printf(" \\_/ \\____/\\____/ \\_/ \n"); + + unity_utils_setup_heap_record(80); + unity_utils_set_leak_level(128); + unity_run_menu(); +} diff --git a/components/esp_tinyusb/test_apps/ncm/main/test_ncm.c b/components/esp_tinyusb/test_apps/ncm/main/test_ncm.c new file mode 100644 index 0000000..7b6b030 --- /dev/null +++ b/components/esp_tinyusb/test_apps/ncm/main/test_ncm.c @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "device_common.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "tinyusb_net.h" + +// +// ========================== Test Configuration Parameters ===================================== +// + +#define TEST_DEVICE_PRESENCE_TIMEOUT_MS 5000 // Timeout for checking device presence + +// +// ========================== TinyUSB General Device Descriptors =============================== +// + + +// +// ============================= TinyUSB NCM Initialization Tests ============================= +// + +static esp_err_t usb_recv_callback(void *buffer, uint16_t len, void *ctx) +{ + return ESP_OK; +} + +static void wifi_pkt_free(void *eb, void *ctx) +{ + +} + +/** + * @brief Test case for installing TinyUSB NCM driver + * + * Scenario: + * 1. Install TinyUSB NCM. + * 3. Wait for the device to be recognized. + * 4. Uninstall TinyUSB driver and deinitialize the NCM driver. + */ +TEST_CASE("NCM: driver install & uninstall", "[ci][driver]") +{ + tinyusb_net_config_t net_config = { + .on_recv_callback = usb_recv_callback, + .free_tx_buffer = wifi_pkt_free, + .user_context = NULL, + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_net_init(&net_config), "Failed to initialize TinyUSB NCM driver"); + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + // Allow some time for the device to be recognized + vTaskDelay(pdMS_TO_TICKS(TEST_DEVICE_PRESENCE_TIMEOUT_MS)); + + tinyusb_net_deinit(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/ncm/sdkconfig.defaults b/components/esp_tinyusb/test_apps/ncm/sdkconfig.defaults new file mode 100644 index 0000000..c6e0c35 --- /dev/null +++ b/components/esp_tinyusb/test_apps/ncm/sdkconfig.defaults @@ -0,0 +1,15 @@ +# Configure TinyUSB, it will be used to mock USB devices +CONFIG_TINYUSB_NET_MODE_NCM=y + +# Disable watchdogs, they'd get triggered during unity interactive menu +CONFIG_ESP_INT_WDT=n +CONFIG_ESP_TASK_WDT=n + +# Run-time checks of Heap and Stack +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y + +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/components/esp_tinyusb/test_apps/ncm/sdkconfig.defaults.esp32p4 b/components/esp_tinyusb/test_apps/ncm/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000..2dbcf83 --- /dev/null +++ b/components/esp_tinyusb/test_apps/ncm/sdkconfig.defaults.esp32p4 @@ -0,0 +1 @@ +CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y diff --git a/components/esp_tinyusb/test_apps/power_management/CMakeLists.txt b/components/esp_tinyusb/test_apps/power_management/CMakeLists.txt new file mode 100644 index 0000000..4e7a7bc --- /dev/null +++ b/components/esp_tinyusb/test_apps/power_management/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +project(test_app_pm) diff --git a/components/esp_tinyusb/test_apps/power_management/README.md b/components/esp_tinyusb/test_apps/power_management/README.md new file mode 100644 index 0000000..5f928a5 --- /dev/null +++ b/components/esp_tinyusb/test_apps/power_management/README.md @@ -0,0 +1,16 @@ +| Supported Targets | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | -------- | + +# Espressif's Additions to TinyUSB - Power management Test Application + +This directory contains Unity tests that validate Espressif-specific integration of TinyUSB. + +The tests focus on: + +- Power management of the USB Device +- Testing tinyusb suspend/resume callbacks delivery +- Testing remote wakeup signalizing by the device + +## Running the test locally on Linux host PC: + +- User needs to [set permissions](../README.md#set-root-permissions-for-low-level-access-to-usb-devices) to the USB device, to successfully run test app on Linux host PC diff --git a/components/esp_tinyusb/test_apps/power_management/main/CMakeLists.txt b/components/esp_tinyusb/test_apps/power_management/main/CMakeLists.txt new file mode 100644 index 0000000..e81e627 --- /dev/null +++ b/components/esp_tinyusb/test_apps/power_management/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES unity + WHOLE_ARCHIVE) diff --git a/components/esp_tinyusb/test_apps/power_management/main/idf_component.yml b/components/esp_tinyusb/test_apps/power_management/main/idf_component.yml new file mode 100644 index 0000000..b1cb5b5 --- /dev/null +++ b/components/esp_tinyusb/test_apps/power_management/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "*" + override_path: "../../../" diff --git a/components/esp_tinyusb/test_apps/power_management/main/test_app_main.c b/components/esp_tinyusb/test_apps/power_management/main/test_app_main.c new file mode 100644 index 0000000..98bd279 --- /dev/null +++ b/components/esp_tinyusb/test_apps/power_management/main/test_app_main.c @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" +#include "esp_log.h" + +#include "tinyusb.h" +#include "tinyusb_cdc_acm.h" +const static char *TAG = "PM_Device_main_app"; + +void app_main(void) +{ + /* + _ _ _ + | | (_) | | + ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ + / _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \ + | __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) | + \___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/ + | |______ __/ | + |_|______| |___/ + _____ _____ _____ _____ + |_ _| ___/ ___|_ _| + | | | |__ \ `--. | | + | | | __| `--. \ | | + | | | |___/\__/ / | | + \_/ \____/\____/ \_/ + */ + + printf(" _ _ _ \n"); + printf(" | | (_) | | \n"); + printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n"); + printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n"); + printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n"); + printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n"); + printf(" | |______ __/ | \n"); + printf(" |_|______| |___/ \n"); + printf(" _____ _____ _____ _____ \n"); + printf("|_ _| ___/ ___|_ _| \n"); + printf(" | | | |__ \\ `--. | | \n"); + printf(" | | | __| `--. \\ | | \n"); + printf(" | | | |___/\\__/ / | | \n"); + printf(" \\_/ \\____/\\____/ \\_/ \n"); + + unity_utils_setup_heap_record(80); + unity_utils_set_leak_level(128); + unity_run_menu(); +} + +/* setUp runs before every test */ +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + ESP_LOGI(TAG, "Cleanup"); + tinyusb_cdcacm_deinit(TINYUSB_CDC_ACM_0); + tinyusb_driver_uninstall(); + unity_utils_evaluate_leaks(); +} diff --git a/components/esp_tinyusb/test_apps/power_management/main/test_device_pm.c b/components/esp_tinyusb/test_apps/power_management/main/test_device_pm.c new file mode 100644 index 0000000..cbf8560 --- /dev/null +++ b/components/esp_tinyusb/test_apps/power_management/main/test_device_pm.c @@ -0,0 +1,303 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" +#if SOC_USB_OTG_SUPPORTED + +#include +#include +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_err.h" + +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "tinyusb_cdc_acm.h" +#include "tusb_config.h" +#include "sdkconfig.h" + +#define TINYUSB_CDC_RX_BUFSIZE CONFIG_TINYUSB_CDC_RX_BUFSIZE +#define SUSPEND_RESUME_TEST_ITERATIONS 5 +#define DEVICE_EVENT_WAIT_MS 5000 + +#define EVENT_BITS_ATTACHED (1U << 0) /**< Device attached event */ +#define EVENT_BITS_SUSPENDED_REMOTE_WAKE_EN (1U << 1) /**< Device suspended with remote wakeup enabled event */ +#define EVENT_BITS_SUSPENDED_REMOTE_WAKE_DIS (1U << 2) /**< Device suspended with remote wakeup disabled event */ +#define EVENT_BITS_RESUMED (1U << 3) /**< Device resumed event */ + +static char err_msg_buf[128]; +const static char *TAG = "PM_Device"; +static TaskHandle_t main_task_hdl = NULL; + +static const tusb_desc_device_t cdc_device_descriptor = { + .bLength = sizeof(cdc_device_descriptor), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = TINYUSB_ESPRESSIF_VID, + .idProduct = 0x4002, + .bcdDevice = 0x0100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01 +}; + +#if (TUD_OPT_HIGH_SPEED) +static const tusb_desc_device_qualifier_t device_qualifier = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0 +}; +#endif // TUD_OPT_HIGH_SPEED + +static tinyusb_config_cdcacm_t acm_cfg = { + .cdc_port = TINYUSB_CDC_ACM_0, + .callback_rx = NULL, + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = NULL, + .callback_line_coding_changed = NULL +}; + +static const uint16_t cdc_desc_config_len = TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN; +static const uint8_t cdc_desc_configuration_remote_wakeup[] = { + TUD_CONFIG_DESCRIPTOR(1, 2, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, (TUD_OPT_HIGH_SPEED ? 512 : 64)), +}; + +/** + * @brief CDC Device RX callback for tinyusb_suspend_resume_events test case + */ +static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) +{ + if (main_task_hdl != NULL) { + ESP_LOGI(TAG, "RX data cb"); + xTaskNotifyGive(main_task_hdl); + } +} + +/** + * @brief Device event handler for tinyusb_suspend_resume_events test case + */ +static void test_suspend_resume_event_handler(tinyusb_event_t *event, void *arg) +{ + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + printf("TINYUSB_EVENT_ATTACHED\n"); + break; + case TINYUSB_EVENT_DETACHED: + printf("TINYUSB_EVENT_DETACHED\n"); + break; + case TINYUSB_EVENT_SUSPENDED: + printf("TINYUSB_EVENT_SUSPENDED\n"); + break; + case TINYUSB_EVENT_RESUMED: + printf("TINYUSB_EVENT_RESUMED\n"); + break; + default: + break; + } +} + +/** + * @brief Tinyusb power management suspend/resume events + * + * Tests TINYUSB_EVENT_SUSPENDED and TINYUSB_EVENT_RESUMED esp_tinyusb events + * + * Pytest expects TINYUSB_EVENT_SUSPENDED event - because of auto suspend + * Pytest sends data to device to resume it + * Device resumes, receives and validates the data, sends a response and goes to suspended state (auto suspend) + * Pytest expect TINYUSB_EVENT_SUSPENDED ... + */ +TEST_CASE("tinyusb_suspend_resume_events", "[esp_tinyusb][device_pm_suspend_resume]") +{ + // Get current task handle for task notification from the RX callback + main_task_hdl = xTaskGetCurrentTaskHandle(); + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_suspend_resume_event_handler); + tusb_cfg.descriptor.device = &cdc_device_descriptor; + tusb_cfg.descriptor.full_speed_config = cdc_desc_configuration_remote_wakeup; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.qualifier = &device_qualifier; + tusb_cfg.descriptor.high_speed_config = cdc_desc_configuration_remote_wakeup; +#endif // TUD_OPT_HIGH_SPEED + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + acm_cfg.callback_rx = &tinyusb_cdc_rx_callback; + + // Init CDC device + TEST_ASSERT_FALSE(tinyusb_cdcacm_initialized(TINYUSB_CDC_ACM_0)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_init(&acm_cfg)); + TEST_ASSERT_TRUE(tinyusb_cdcacm_initialized(TINYUSB_CDC_ACM_0)); + + uint8_t buf[TINYUSB_CDC_RX_BUFSIZE + 1]; + const char expect_reply[] = "Time to resume\r\n"; + const char send_message[] = "Time to suspend\r\n"; + + int test_iterations = 0; + do { + // Wait for new data from the host (Sent by pytest) + if (pdTRUE == ulTaskNotifyTake(true, pdMS_TO_TICKS(10000))) { + + size_t rx_size = 0; + ESP_ERROR_CHECK(tinyusb_cdcacm_read(TINYUSB_CDC_ACM_0, buf, TINYUSB_CDC_RX_BUFSIZE, &rx_size)); + if (rx_size > 0) { + ESP_LOGI(TAG, "Intf %d, RX %d bytes", TINYUSB_CDC_ACM_0, rx_size); + // Check if received string is equal to expect_reply string + TEST_ASSERT_EQUAL_UINT8_ARRAY(expect_reply, buf, sizeof(expect_reply) - 1); + + // Reply to the host with send_message string + strncpy((char *)buf, send_message, sizeof(send_message) - 1); + tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, buf, sizeof(send_message) - 1); + tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0, 0); + test_iterations++; + } + } else { + TEST_FAIL_MESSAGE("RX Data CB not received on time"); + } + } while (test_iterations <= SUSPEND_RESUME_TEST_ITERATIONS); +} + +/** + * @brief Dummy CDC Device RX callback for tinyusb_remote_wakeup_reporting test case + */ +static void tinyusb_cdc_rx_callback_dmy(int itf, cdcacm_event_t *event) +{ +} + +/** + * @brief Device event handler for tinyusb_remote_wakeup_reporting test case + */ +static void test_remote_wake_event_handler(tinyusb_event_t *event, void *arg) +{ + uint32_t event_bits = UINT32_MAX; + + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + printf("TINYUSB_EVENT_ATTACHED\n"); + event_bits = EVENT_BITS_ATTACHED; + break; + case TINYUSB_EVENT_DETACHED: + printf("TINYUSB_EVENT_DETACHED\n"); + return; + case TINYUSB_EVENT_SUSPENDED: + if (event->suspended.remote_wakeup) { + printf("TINYUSB_EVENT_SUSPENDED_REMOTE_WAKE_EN\n"); + event_bits = EVENT_BITS_SUSPENDED_REMOTE_WAKE_EN; + } else { + printf("TINYUSB_EVENT_SUSPENDED_REMOTE_WAKE_DIS\n"); + event_bits = EVENT_BITS_SUSPENDED_REMOTE_WAKE_DIS; + } + break; + case TINYUSB_EVENT_RESUMED: + printf("TINYUSB_EVENT_RESUMED\n"); + event_bits = EVENT_BITS_RESUMED; + break; + default: + return; + } + + if (main_task_hdl) { + xTaskNotify(main_task_hdl, event_bits, eSetBits); + } +} + +/** + * @brief Expect device event + * + * @param[in] expected_event Expected device event + * @param[in] ticks time to expect the event + * @param[in] file file from which the function was called + * @param[in] line line from which the function was called + */ +static void expect_device_event_impl(const uint32_t expected_event, TickType_t ticks, const char *file, int line) +{ + uint32_t notify_bits = 0; + if (pdTRUE == xTaskNotifyWait(0, UINT32_MAX, ¬ify_bits, ticks)) { + if (expected_event != notify_bits) { + snprintf(err_msg_buf, sizeof(err_msg_buf), + "Unexpected event at %s:%d\n %ld expected, %ld delivered\n", + file, line, expected_event, notify_bits); + TEST_FAIL_MESSAGE(err_msg_buf); + } + } else { + snprintf(err_msg_buf, sizeof(err_msg_buf), + "Event %ld at %s:%d\n was not delivered on time", + expected_event, file, line); + TEST_FAIL_MESSAGE(err_msg_buf); + } +} +#define expect_device_event(expected_event, ticks) expect_device_event_impl((expected_event), (ticks), __FILE__, __LINE__) + + +/** + * @brief Tinyusb power management remote wakeup + * + * Tests device reporting remote wakeup capability + * + * - Install device with remote wakeup allowed in it's configuration descriptor, but disabled (by default after reset) + * - Expect auto suspend device event with remote wakeup disabled + * - Pytest enables the remote wakeup feature by a ctrl transfer + * - Expect device resume event (because of ctrl transfer) + * - Expect auto suspend device event with remote wakeup enabled + * - Signalize remote wakeup and expect resume event + */ +TEST_CASE("tinyusb_remote_wakeup_reporting", "[esp_tinyusb][device_pm_remote_wake]") +{ + // Get current tak handle for the device event handler + main_task_hdl = xTaskGetCurrentTaskHandle(); + + // Install TinyUSB driver, device with remote wakeup enabled + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_remote_wake_event_handler); + tusb_cfg.descriptor.device = &cdc_device_descriptor; + tusb_cfg.descriptor.full_speed_config = cdc_desc_configuration_remote_wakeup; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.qualifier = &device_qualifier; + tusb_cfg.descriptor.high_speed_config = cdc_desc_configuration_remote_wakeup; +#endif // TUD_OPT_HIGH_SPEED + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + acm_cfg.callback_rx = &tinyusb_cdc_rx_callback_dmy; + + // Init CDC device + TEST_ASSERT_FALSE(tinyusb_cdcacm_initialized(TINYUSB_CDC_ACM_0)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_init(&acm_cfg)); + TEST_ASSERT_TRUE(tinyusb_cdcacm_initialized(TINYUSB_CDC_ACM_0)); + + // Expect attach event and auto suspend event with remote wakeup disabled by default + expect_device_event(EVENT_BITS_ATTACHED, pdMS_TO_TICKS(DEVICE_EVENT_WAIT_MS)); + expect_device_event(EVENT_BITS_SUSPENDED_REMOTE_WAKE_DIS, pdMS_TO_TICKS(DEVICE_EVENT_WAIT_MS)); + + // Try to signalize remote wakeup, when the host did not enable it + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, tinyusb_remote_wakeup()); + + // Pytest enables remote wakeup on the device by sending a ctrl transfer to the the device + // Expect the device to: + // - resumed (because of the ctrl transfer) + // - auto suspended with remote wakeup enabled + + expect_device_event(EVENT_BITS_RESUMED, pdMS_TO_TICKS(DEVICE_EVENT_WAIT_MS)); + expect_device_event(EVENT_BITS_SUSPENDED_REMOTE_WAKE_EN, pdMS_TO_TICKS(DEVICE_EVENT_WAIT_MS)); + + // Signalize remote wakeup and expect resume event + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_remote_wakeup()); + expect_device_event(EVENT_BITS_RESUMED, pdMS_TO_TICKS(DEVICE_EVENT_WAIT_MS)); +} + +#endif diff --git a/components/esp_tinyusb/test_apps/power_management/pytest_usb_pm.py b/components/esp_tinyusb/test_apps/power_management/pytest_usb_pm.py new file mode 100644 index 0000000..3db6aa6 --- /dev/null +++ b/components/esp_tinyusb/test_apps/power_management/pytest_usb_pm.py @@ -0,0 +1,213 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +from time import sleep +import pytest +from serial import Serial, SerialException +from serial.tools.list_ports import comports +from pytest_embedded_idf.dut import IdfDut +from pytest_embedded_idf.utils import idf_parametrize + +# Mainly for a local run, as there is no error when pyusb is not installed and the pytest silently fails +try: + import usb.core + import usb.util +except ImportError as e: + raise RuntimeError("pyusb is not installed. Install it with: pip install pyusb") from e + +# Standard USB requests (USB 2.0 spec, Table 9-4) +USB_B_REQUEST_SET_FEATURE = 0x03 + +# Standard feature selectors (USB 2.0 spec, Table 9-6) +USB_FEAT_DEVICE_REMOTE_WAKEUP = 0x01 + +# Bit mask belonging to the bmAttributes field of a configuration descriptor +USB_BM_ATTRIBUTES_WAKEUP = 0x20 + +# Device Under Test VID:PID +DUT_VID = 0x303A +DUT_PID = 0x4002 + +# Tinyusb device events from device event handler +TINYUSB_EVENTS = { + "attached": "TINYUSB_EVENT_ATTACHED", + "suspended": "TINYUSB_EVENT_SUSPENDED", + "resumed": "TINYUSB_EVENT_RESUMED", + "suspended_remote_wake_dis": "TINYUSB_EVENT_SUSPENDED_REMOTE_WAKE_DIS", + "suspended_remote_wake_en": "TINYUSB_EVENT_SUSPENDED_REMOTE_WAKE_EN", +} + +@pytest.mark.usb_device +@pytest.mark.flaky(reruns=1, reruns_delay=10) +@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +def test_usb_device_suspend_resume(dut: IdfDut) -> None: + ''' + Running the test locally: + 1. Build the test_app for your DUT (ESP32-S2/S3/P4) + 2. Connect you DUT to your test runner (local machine) with USB port and flashing port + 3. Run `pytest --target esp32s3` + + Test procedure: + 1. Run the test on the DUT + 2. Expect one COM Port in the system + 3. Open it and and test power management of the USB device (Suspend/Resume) + 4. Suspend: Device enters suspended state after some time of inactivity + 5. Resume: Device is resumed by accessing it (sending some data to it) + ''' + sleep(5) # When rerunning flaky test, to re-initialize the device + dut.expect_exact('Press ENTER to see the list of tests.') + dut.write('[device_pm_suspend_resume]') + dut.expect_exact('TinyUSB: TinyUSB Driver installed') + sleep(2) # Some time for the OS to enumerate our USB device + + # Find device with Espressif TinyUSB VID/PID + ports = [] + for p in comports(): + if (p.vid == DUT_VID and p.pid == DUT_PID): + ports.append(p.device) + + if len(ports) == 0: + raise Exception('TinyUSB COM port not found') + + try: + with Serial(ports[0], timeout=2) as cdc: + dut.expect_exact(TINYUSB_EVENTS['attached']) + + # Wait for auto suspend (set to 3 seconds) + # This expect_exact is ignored by pytest when running second rerun of flaky test (unknown reason), + # making the test fail in further steps, adding explicit sleep(3) to wait for the suspend events + sleep(3) + dut.expect_exact(TINYUSB_EVENTS['suspended']) + + for i in range(0, 5): + print(f"Power cycle iteration {i}.") + + # Resume the device by accessing it + cdc.write(b'Time to resume\r\n') + res = cdc.readline() + assert b'Time to suspend\r\n' in res + + dut.expect_exact(TINYUSB_EVENTS['resumed']) + + # Wait for auto suspend (set to 3 seconds) + dut.expect_exact(TINYUSB_EVENTS['suspended']) + + # Stay suspended for a while + sleep(2) + + except SerialException as e: + raise RuntimeError(f"Failed to open CDC device on {ports[0]}") from e + +def set_remote_wake_on_device(VID: int, PID: int) -> None: + ''' + Resume the device by opening it + Set remote wakeup on device by sending SET_FEATURE ctrl transfer to the device + + :param VID: VID of the device + :param PID: PID of the device + ''' + + # Device is currently suspended, we must open it to resume it and send the ctrl transfer + dev = usb.core.find(idVendor=VID, idProduct=PID) + if dev is None: + raise ValueError("Device not found") + + bmRequestType = usb.util.build_request_type( + usb.util.CTRL_OUT, + usb.util.CTRL_TYPE_STANDARD, + usb.util.CTRL_RECIPIENT_DEVICE) + + try: + dev.ctrl_transfer( + bmRequestType=bmRequestType, + bRequest=USB_B_REQUEST_SET_FEATURE, + wValue=USB_FEAT_DEVICE_REMOTE_WAKEUP, + wIndex=0, + ) + except usb.core.USBError as e: + raise RuntimeError("Control transfer not sent") from e + + print("CTRL transfer sent") + + try: + usb.util.dispose_resources(dev) + except usb.core.USBError as e: + raise RuntimeError("Device resources not released") from e + + +def check_remote_wake_feature(VID: int, PID: int, has_remote_wake: bool) -> None: + ''' + Check if the device reports remote wakeup feature from it's configuration descriptor + + :param VID: VID of the device + :param PID: PID of the device + :param has_remote_wake: Expect the device to does/does not feature with remote wakeup + ''' + + sleep(2) # Some time for the OS to enumerate our USB device + dev = usb.core.find(idVendor=VID, idProduct=PID) + if dev is None: + raise ValueError("Device not found") + + cfg = dev.get_active_configuration() + remote_wake_supported = bool(cfg.bmAttributes & USB_BM_ATTRIBUTES_WAKEUP) + + if remote_wake_supported: + print("Device advertises remote wakeup feature in it's descriptor") + else: + print("Device does not advertise remote wakeup feature in it's descriptor") + + # Assertion to fail on mismatch + assert remote_wake_supported == has_remote_wake, ( + f"Remote wakeup capability mismatch: " + f"expected {has_remote_wake}, " + f"device reports {remote_wake_supported}" + ) + + try: + usb.util.dispose_resources(dev) + except usb.core.USBError as e: + raise RuntimeError("Device resources not released") from e + +@pytest.mark.usb_device +@pytest.mark.flaky(reruns=1, reruns_delay=10) +@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +def test_usb_device_remote_wakeup_en(dut: IdfDut) -> None: + ''' + Running the test locally: + 1. Build the test_app for your DUT (ESP32-S2/S3/P4) + 2. Connect you DUT to your test runner (local machine) with USB port and flashing port + 3. Run `pytest --target esp32s3` + + Test procedure: + 1. Run the test on the DUT + 2. Expect one COM Port in the system + 3. Check the device's configuration descriptor, if it reports remote wakeup functionality + 4. Enable the remote wakeup by sending a ctrl transfer + ''' + sleep(5) + dut.expect_exact('Press ENTER to see the list of tests.') + dut.write('[device_pm_remote_wake]') + dut.expect_exact('TinyUSB: TinyUSB Driver installed') + sleep(2) + + # Wait for device attach event + dut.expect_exact(TINYUSB_EVENTS['attached']) + # Check if the device reports remote wakeup feature + check_remote_wake_feature(DUT_VID, DUT_PID, has_remote_wake=True) + + # Expect device suspend event (auto suspend) with remote wakeup disabled + dut.expect_exact(TINYUSB_EVENTS['suspended_remote_wake_dis']) + + # Enable remote wakeup on the device + set_remote_wake_on_device(DUT_VID, DUT_PID) + + # Expect device to resume (ctrl transfer sent) + dut.expect_exact(TINYUSB_EVENTS['resumed']) + # Expect device suspend event (auto suspend) with remote wakeup enabled + dut.expect_exact(TINYUSB_EVENTS['suspended_remote_wake_en']) + + # Device called remote wakeup + + # Expect device to resume (remote wakeup) + dut.expect_exact(TINYUSB_EVENTS['resumed']) diff --git a/components/esp_tinyusb/test_apps/power_management/sdkconfig.defaults b/components/esp_tinyusb/test_apps/power_management/sdkconfig.defaults new file mode 100644 index 0000000..cd6d10f --- /dev/null +++ b/components/esp_tinyusb/test_apps/power_management/sdkconfig.defaults @@ -0,0 +1,15 @@ +# Configure TinyUSB, it will be used to mock USB devices +CONFIG_TINYUSB_MSC_ENABLED=n +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_COUNT=1 +CONFIG_TINYUSB_HID_COUNT=0 + +# Register tinyusb suspend/resume callbacks in esp_tinyusb +CONFIG_TINYUSB_SUSPEND_CALLBACK=y +CONFIG_TINYUSB_RESUME_CALLBACK=y + +# Disable watchdogs, they'd get triggered during unity interactive menu +# CONFIG_ESP_TASK_WDT_INIT is not set + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/components/esp_tinyusb/test_apps/power_management/sdkconfig.defaults.esp32p4 b/components/esp_tinyusb/test_apps/power_management/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000..2dbcf83 --- /dev/null +++ b/components/esp_tinyusb/test_apps/power_management/sdkconfig.defaults.esp32p4 @@ -0,0 +1 @@ +CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y diff --git a/components/esp_tinyusb/test_apps/runtime_config/CMakeLists.txt b/components/esp_tinyusb/test_apps/runtime_config/CMakeLists.txt new file mode 100644 index 0000000..632c671 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +project(test_app_runtime_config) diff --git a/components/esp_tinyusb/test_apps/runtime_config/README.md b/components/esp_tinyusb/test_apps/runtime_config/README.md new file mode 100644 index 0000000..3cd1095 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/README.md @@ -0,0 +1,42 @@ +| Supported Targets | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | -------- | + +# Espressif's Additions to TinyUSB - Runtime Configuration Test Application + +This directory contains Unity tests that validate Espressif-specific integration of TinyUSB. + +The tests focus on: + +- TinyUSB configuration helpers (default macros, per-port config). +- USB Device descriptors (FS/HS, string descriptors, edge cases). +- USB peripheral / PHY configuration for full-speed and high-speed. +- TinyUSB task configuration (CPU pinning, invalid parameters). +- Multitask access to the TinyUSB driver (concurrent installs). + +The test prints a numbered menu, for example: + +``` +(1) "Config: Default macros arguments" [runtime_config][default] +(2) "Config: Full-speed (High-speed)" [runtime_config][full_speed] +... +``` + +You can run all tests by running `pytest` or select individual ones by name and number. + +## Tags + +Each test is tagged with categories and modes: + +### Categories + +- [runtime_config] – Tests focusing on `tinyusb_config_t` and runtime configuration. +- [periph] – Tests that directly exercise the USB peripheral (USB OTG 1.1 or USB OTG 2.0). +- [task] – Tests related to the dedicated TinyUSB task configuration. + +### Speed / Mode + +- [default] – Generic, target-agnostic. +- [full_speed] – Tests specific to USB OTG 1.1 / Full-speed port. +- [high_speed] – Tests specific to USB OTG 2.0 / High-speed port. + +These tags can be used by test runners / CI to select or filter tests. diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/CMakeLists.txt b/components/esp_tinyusb/test_apps/runtime_config/main/CMakeLists.txt new file mode 100644 index 0000000..0d87f25 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/CMakeLists.txt @@ -0,0 +1,12 @@ +set(priv_req "") + +if(${IDF_VERSION_MAJOR} LESS 6) + list(APPEND priv_req "usb") +endif() + +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + PRIV_INCLUDE_DIRS "../../../include_private" + PRIV_REQUIRES ${priv_req} + REQUIRES unity + WHOLE_ARCHIVE) diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/device_handling.c b/components/esp_tinyusb/test_apps/runtime_config/main/device_handling.c new file mode 100644 index 0000000..fa63c8f --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/device_handling.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "tinyusb.h" + +static SemaphoreHandle_t wait_mount = NULL; + +#define TUSB_DEVICE_DELAY_MS 1000 + +void test_device_setup(void) +{ + wait_mount = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(wait_mount); +} + +void test_device_release(void) +{ + TEST_ASSERT_NOT_NULL(wait_mount); + vSemaphoreDelete(wait_mount); +} + +void test_device_wait(void) +{ + // Wait for tud_mount_cb() to be called + TEST_ASSERT_EQUAL_MESSAGE(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TUSB_DEVICE_DELAY_MS)), "No tusb_mount_cb() in time"); + // Delay to allow finish the enumeration + // Disable this delay could lead to potential race conditions when the tud_task() is pinned to another CPU + vTaskDelay(pdMS_TO_TICKS(250)); +} + +void test_device_event_handler(tinyusb_event_t *event, void *arg) +{ + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + xSemaphoreGive(wait_mount); + break; + case TINYUSB_EVENT_DETACHED: + break; + default: + break; + } +} + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/device_handling.h b/components/esp_tinyusb/test_apps/runtime_config/main/device_handling.h new file mode 100644 index 0000000..851d0d3 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/device_handling.h @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "tinyusb.h" + +/** + * @brief Test device setup + */ +void test_device_setup(void); + +/** + * @brief Test device release + */ +void test_device_release(void); + +/** + * @brief Test device wait + * + * Waits the tusb_mount_cb() which indicates the device connected to the Host and enumerated. + */ +void test_device_wait(void); + +/** + * @brief TinyUSB callback for device mount. + * + * @note + * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. + * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. + */ +void test_device_event_handler(tinyusb_event_t *event, void *arg); diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/idf_component.yml b/components/esp_tinyusb/test_apps/runtime_config/main/idf_component.yml new file mode 100644 index 0000000..b1cb5b5 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "*" + override_path: "../../../" diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/test_app_main.c b/components/esp_tinyusb/test_apps/runtime_config/main/test_app_main.c new file mode 100644 index 0000000..87e499a --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/test_app_main.c @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" +#include "device_handling.h" + +/* setUp runs before every test */ +void setUp(void) +{ + unity_utils_record_free_mem(); + test_device_setup(); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + // Short delay to allow task to be cleaned up + vTaskDelay(10); + test_device_release(); + unity_utils_evaluate_leaks(); +} + + +void app_main(void) +{ + /* + _ _ _ + | | (_) | | + ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ + / _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \ + | __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) | + \___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/ + | |______ __/ | + |_|______| |___/ + _____ _____ _____ _____ + |_ _| ___/ ___|_ _| + | | | |__ \ `--. | | + | | | __| `--. \ | | + | | | |___/\__/ / | | + \_/ \____/\____/ \_/ + */ + + printf(" _ _ _ \n"); + printf(" | | (_) | | \n"); + printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n"); + printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n"); + printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n"); + printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n"); + printf(" | |______ __/ | \n"); + printf(" |_|______| |___/ \n"); + printf(" _____ _____ _____ _____ \n"); + printf("|_ _| ___/ ___|_ _| \n"); + printf(" | | | |__ \\ `--. | | \n"); + printf(" | | | __| `--. \\ | | \n"); + printf(" | | | |___/\\__/ / | | \n"); + printf(" \\_/ \\____/\\____/ \\_/ \n"); + + unity_utils_setup_heap_record(80); + unity_utils_set_leak_level(128); + unity_run_menu(); +} diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/test_config.c b/components/esp_tinyusb/test_apps/runtime_config/main/test_config.c new file mode 100644 index 0000000..562c1a0 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/test_config.c @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED + +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" + + +// Enable to verify static assert during the build +#define RUNTIME_CONFIG_CHECK_STATIC_ASSERTS 0 + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Verifies the default macros arguments + * Awaiting: + * - Default macros should configure to NULL when no arguments are provided + * - Default macros should configure to the provided arguments when one or two arguments are provided + */ +TEST_CASE("Config: Default macros arguments", "[runtime_config][default]") +{ + void *dummy_event_hdl = (void *) 0xDEADBEEF; + void *dummy_event_arg = (void *) 0xBEEFDEAD; + + const tinyusb_config_t tusb_cfg_arg0 = TINYUSB_DEFAULT_CONFIG(); + const tinyusb_config_t tusb_cfg_arg1 = TINYUSB_DEFAULT_CONFIG(dummy_event_hdl); + const tinyusb_config_t tusb_cfg_arg2 = TINYUSB_DEFAULT_CONFIG(dummy_event_hdl, dummy_event_arg); +#if (RUNTIME_CONFIG_CHECK_STATIC_ASSERTS) + const tinyusb_config_t tusb_cfg_arg3 = TINYUSB_DEFAULT_CONFIG(dummy_event_hdl, dummy_event_arg, NULL); +#endif // RUNTIME_CONFIG_CHECK_STATIC_ASSERTS + + TEST_ASSERT_EQUAL_MESSAGE(NULL, tusb_cfg_arg0.event_cb, "Event callback should be NULL when no arguments provided"); + TEST_ASSERT_EQUAL_MESSAGE(NULL, tusb_cfg_arg0.event_arg, "Event argument should be NULL when no arguments provided"); + + TEST_ASSERT_EQUAL_MESSAGE(dummy_event_hdl, tusb_cfg_arg1.event_cb, "Event callback was not set correctly"); + TEST_ASSERT_EQUAL_MESSAGE(NULL, tusb_cfg_arg1.event_arg, "Event argument should be NULL when one argument is provided"); + + TEST_ASSERT_EQUAL_MESSAGE(dummy_event_hdl, tusb_cfg_arg2.event_cb, "Event callback was not set correctly"); + TEST_ASSERT_EQUAL_MESSAGE(dummy_event_arg, tusb_cfg_arg2.event_arg, "Event argument was not set correctly"); +} + +#if (SOC_USB_OTG_PERIPH_NUM == 1) +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Verifies default configuration values (Default config for Full-speed only target) + * Awaiting: Install returns ESP_OK, default descriptors are being used, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Config: Full-speed default (Full-speed)", "[runtime_config][full_speed]") +{ + const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_PORT_FULL_SPEED_0, tusb_cfg.port, "Wrong default port number"); + TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.skip_setup, "Wrong default skip_setup value"); + TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.self_powered, "Wrong default self-powered flag"); + TEST_ASSERT_EQUAL_MESSAGE(-1, tusb_cfg.phy.vbus_monitor_io, "Wrong default VBUS monitor IO"); + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_SIZE, tusb_cfg.task.size, "Wrong default task size"); + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_PRIO, tusb_cfg.task.priority, "Wrong default task priority"); +#if CONFIG_FREERTOS_UNICORE + TEST_ASSERT_EQUAL_MESSAGE(0, tusb_cfg.task.xCoreID, "Wrong default task affinity, should be 0 on unicore"); +#else + TEST_ASSERT_EQUAL_MESSAGE(1, tusb_cfg.task.xCoreID, "Wrong default task affinity, should be 1 on multicore"); +#endif // CONFIG_FREERTOS_UNICORE +} + +#else +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Verifies Full-speed configuration values (Full-speed config for High-speed target) + * Awaiting: Install returns ESP_OK, default descriptors are being used, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Config: Full-speed (High-speed)", "[runtime_config][full_speed]") +{ + const tinyusb_config_t tusb_cfg = TINYUSB_CONFIG_FULL_SPEED(NULL, NULL); + + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_PORT_FULL_SPEED_0, tusb_cfg.port, "Wrong default port number"); + TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.skip_setup, "Wrong default skip_setup value"); + TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.self_powered, "Wrong default self-powered flag"); + TEST_ASSERT_EQUAL_MESSAGE(-1, tusb_cfg.phy.vbus_monitor_io, "Wrong default VBUS monitor IO"); + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_SIZE, tusb_cfg.task.size, "Wrong default task size"); + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_PRIO, tusb_cfg.task.priority, "Wrong default task priority"); + TEST_ASSERT_EQUAL_MESSAGE(1, tusb_cfg.task.xCoreID, "Wrong default task affinity, should be 1 on multicore"); +} + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Verifies High-speed configuration values (High-speed config for High-speed target) + * Awaiting: Install returns ESP_OK, default descriptors are being used, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Config: High-speed default (High-speed)", "[runtime_config][high_speed]") +{ + const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_PORT_HIGH_SPEED_0, tusb_cfg.port, "Wrong default port number"); + TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.skip_setup, "Wrong default skip_setup value"); + TEST_ASSERT_EQUAL_MESSAGE(false, tusb_cfg.phy.self_powered, "Wrong default self-powered flag"); + TEST_ASSERT_EQUAL_MESSAGE(-1, tusb_cfg.phy.vbus_monitor_io, "Wrong default VBUS monitor IO"); + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_SIZE, tusb_cfg.task.size, "Wrong default task size"); + TEST_ASSERT_EQUAL_MESSAGE(TINYUSB_DEFAULT_TASK_PRIO, tusb_cfg.task.priority, "Wrong default task priority"); + TEST_ASSERT_EQUAL_MESSAGE(1, tusb_cfg.task.xCoreID, "Wrong default task affinity, should be 1 on multicore"); +} +#endif // SOC_USB_OTG_PERIPH_NUM > 1 + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/test_cpu_load.c b/components/esp_tinyusb/test_apps/runtime_config/main/test_cpu_load.c new file mode 100644 index 0000000..d0bb351 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/test_cpu_load.c @@ -0,0 +1,181 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_err.h" +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "device_handling.h" + +// Enable this, if you need to see the stat for all tasks +#define STAT_CONFIG_ALL_TASKS 0 +// Increase this if test_cpu_load_init or test_cpu_load_measure fail due to insufficient array size +#define STAT_CONFIG_ARRAY_SIZE_OFFSET 5 + +// TinyUSB task related variables +const static char *TINYUSB_TASK_NAME = "TinyUSB"; +static uint32_t _tinyusb_run_time = 0; +static uint32_t _tinyusb_cpu_load = 0; + +// Arrays and variables for CPU load measurement +static TaskStatus_t *start_array = NULL; +static TaskStatus_t *end_array = NULL; +static UBaseType_t start_array_size; +static UBaseType_t end_array_size; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) +static configRUN_TIME_COUNTER_TYPE start_run_time; +static configRUN_TIME_COUNTER_TYPE end_run_time; +#else +static uint32_t start_run_time; +static uint32_t end_run_time; +#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#define CPU_AMOUNT_OF_CORES CONFIG_FREERTOS_NUMBER_OF_CORES +#else +#define CPU_AMOUNT_OF_CORES portNUM_PROCESSORS +#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + +/** + * @brief Initialize CPU load measurement + */ +static void test_cpu_load_init(void) +{ + printf("Starting TinyUSB load measurement test...\n"); + + // Allocate array to store current task states + start_array_size = uxTaskGetNumberOfTasks() + STAT_CONFIG_ARRAY_SIZE_OFFSET; + start_array = malloc(sizeof(TaskStatus_t) * start_array_size); + TEST_ASSERT_NOT_NULL_MESSAGE(start_array, "Insufficient memory to allocate internal arrays"); + + // Get current task states + start_array_size = uxTaskGetSystemState(start_array, start_array_size, &start_run_time); + TEST_ASSERT_MESSAGE(start_array_size != 0, "Insufficient array size for uxTaskGetSystemState. Try increasing STAT_CONFIG_ARRAY_SIZE_OFFSET"); +} + +/** + * @brief Measure CPU load since initialization + * + * Note: test_cpu_load_init() must be called before this function + */ +static void test_cpu_load_measure(void) +{ + end_array_size = uxTaskGetNumberOfTasks() + STAT_CONFIG_ARRAY_SIZE_OFFSET; + end_array = malloc(sizeof(TaskStatus_t) * end_array_size); + TEST_ASSERT_NOT_NULL_MESSAGE(end_array, "Insufficient memory to allocate internal arrays"); + + // Get post delay task states + end_array_size = uxTaskGetSystemState(end_array, end_array_size, &end_run_time); + TEST_ASSERT_MESSAGE(end_array_size != 0, "Insufficient array size for uxTaskGetSystemState. Try increasing STAT_CONFIG_ARRAY_SIZE_OFFSET"); + + // Calculate total_elapsed_time in units of run time stats clock period. + uint32_t total_elapsed_time = (end_run_time - start_run_time); + TEST_ASSERT_MESSAGE(total_elapsed_time != 0, "Delay duration too short"); + +#if (STAT_CONFIG_ALL_TASKS) + printf("\n%-20s %10s %8s\n", "Name", "Run time", "CPU load"); +#endif // STAT_CONFIG_ALL_TASKS + // Print TinyUSB statistics only + for (int i = 0; i < start_array_size; i++) { + int k = -1; + for (int j = 0; j < end_array_size; j++) { + if (start_array[i].xHandle == end_array[j].xHandle) { + k = j; + // Mark that task have been matched by overwriting their handles + start_array[i].xHandle = NULL; + end_array[j].xHandle = NULL; + break; + } + } + // Check if matching task found + if (k >= 0) { + uint32_t task_elapsed_time = end_array[k].ulRunTimeCounter - start_array[i].ulRunTimeCounter; + uint32_t percentage_time = (task_elapsed_time * 100UL) / (total_elapsed_time * CPU_AMOUNT_OF_CORES); +#if (STAT_CONFIG_ALL_TASKS) + printf("%-20.20s %10" PRIu32 " %7" PRIu32 "%%\n", + start_array[i].pcTaskName, // left-aligned, max 20 chars + task_elapsed_time, // right-aligned, width 10 + percentage_time); // right-aligned, width 7 + '%' char +#endif // STAT_CONFIG_ALL_TASKS + // Save the TinyUSB task stats for test validation + if (strcmp(start_array[i].pcTaskName, TINYUSB_TASK_NAME) == 0) { + _tinyusb_run_time = task_elapsed_time; + _tinyusb_cpu_load = percentage_time; + } + } + } +#if (STAT_CONFIG_ALL_TASKS) + // Print unmatched tasks + for (int i = 0; i < start_array_size; i++) { + if (start_array[i].xHandle != NULL) { + printf("%-20.20s %10s\n", + start_array[i].pcTaskName, // left-aligned, max 20 chars + "Deleted"); // right-aligned, width 10 + } + } + for (int i = 0; i < end_array_size; i++) { + if (end_array[i].xHandle != NULL) { + printf("%-20.20s %10s\n", + end_array[i].pcTaskName, // left-aligned, max 20 chars + "Created"); // right-aligned, width 10 + } + } +#endif // STAT_CONFIG_ALL_TASKS + free(start_array); + free(end_array); + printf("CPU load measurement test completed.\n"); +} + +/** + * @brief Test TinyUSB CPU load measurement + * + * Scenario: + * - Install TinyUSB driver with default configuration + * - wait for device connection + * - measure CPU load + * - uninstall driver + * - show results + */ +TEST_CASE("[CPU load] Install & Uninstall, default configuration", "[cpu_load]") +{ +#if (!CONFIG_FREERTOS_UNICORE) + // Allow other core to finish initialization + vTaskDelay(pdMS_TO_TICKS(100)); +#endif // (!CONFIG_FREERTOS_UNICORE) + + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + + // Initialize CPU load measurement + test_cpu_load_init(); + + // Wait for the device to be mounted and enumerated by the Host + test_device_wait(); + printf("\t -> Device connected\n"); + + // Measure CPU load + test_cpu_load_measure(); + + // Uninstall TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + + // Show results + printf("TinyUSB Run time: %" PRIu32 " ticks\n", _tinyusb_run_time); + printf("TinyUSB CPU load: %" PRIu32 " %%\n", _tinyusb_cpu_load); +} + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/test_device.c b/components/esp_tinyusb/test_apps/runtime_config/main/test_device.c new file mode 100644 index 0000000..87e01e9 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/test_device.c @@ -0,0 +1,264 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +// TinyUSB Public +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "test_task.h" +// TinyUSB Private +#include "descriptors_control.h" +// Test common +#include "device_handling.h" + + +// ========================== TinyUSB General Device Descriptors =============================== + +#define TUSB_CFG_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN) + +static const uint8_t test_fs_configuration_descriptor[] = { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_CDC * 2, 0, TUSB_CFG_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, 64), +#if CFG_TUD_CDC > 1 + TUD_CDC_DESCRIPTOR(2, 4, 0x83, 8, 0x04, 0x84, 64), +#endif // CFG_TUD_CDC > 1 +}; + +#if (TUD_OPT_HIGH_SPEED) + +static const uint8_t test_hs_configuration_descriptor[] = { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_CDC * 2, 0, TUSB_CFG_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, 512), +#if CFG_TUD_CDC > 1 + TUD_CDC_DESCRIPTOR(2, 4, 0x83, 8, 0x04, 0x84, 512), +#endif // CFG_TUD_CDC > 1 +}; + +static const tusb_desc_device_qualifier_t device_qualifier = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0 +}; +#endif // TUD_OPT_HIGH_SPEED + +static const tusb_desc_device_t test_device_descriptor = { + .bLength = sizeof(test_device_descriptor), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers + .idProduct = 0x4002, + .bcdDevice = 0x100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01 +}; + +/** + * @brief String descriptor + */ +const char *test_string_descriptor[USB_STRING_DESCRIPTOR_ARRAY_SIZE + 1] = { + // array of pointer to string descriptors + (char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) + "TinyUSB", // 1: Manufacturer + "TinyUSB Device", // 2: Product + "123456", // 3: Serials, should use chip ID + "TinyUSB CDC", // 4: CDC String descriptor + "String 5", // 5: Test string #6 + "String 6", // 6: Test string #7 + "String 7", // 7: Test string #8 + "String 8", // 8: Test string #9 +}; + + +// ========================== Callbacks ======================================== + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Provide more string descriptors than is supported + * Awaiting: Install returns ESP_ERR_NOT_SUPPORTED + */ +TEST_CASE("Device: String Descriptors overflow", "[runtime_config][default]") +{ + // TinyUSB driver default configuration + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + tusb_cfg.descriptor.string = test_string_descriptor; + tusb_cfg.descriptor.string_count = USB_STRING_DESCRIPTOR_ARRAY_SIZE + 1; + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, tinyusb_driver_install(&tusb_cfg)); +} + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Provide maximum supported string descriptors + * Awaiting: Install returns ESP_OK, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Device: String Descriptors maximum value", "[runtime_config][default]") +{ + // TinyUSB driver default configuration + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + tusb_cfg.descriptor.string = test_string_descriptor; + tusb_cfg.descriptor.string_count = USB_STRING_DESCRIPTOR_ARRAY_SIZE; + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Provide Device & Configuration descriptors + * Awaiting: Install returns ESP_OK, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Device: Device & Configuration", "[runtime_config][default]") +{ + // TinyUSB driver configuration + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + // Set descriptors + tusb_cfg.descriptor.device = &test_device_descriptor; + tusb_cfg.descriptor.string = test_string_descriptor; + tusb_cfg.descriptor.string_count = 5; // 5 string descriptors as we report string index 4 for CDC in the configuration descriptor + tusb_cfg.descriptor.full_speed_config = test_fs_configuration_descriptor; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.qualifier = &device_qualifier; + tusb_cfg.descriptor.high_speed_config = test_hs_configuration_descriptor; +#endif // TUD_OPT_HIGH_SPEED + + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Provide no descriptors (All default for Full-speed) + * Awaiting: Install returns ESP_OK, default descriptors are being used, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Device: Full-speed default", "[runtime_config][full_speed]") +{ + const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Provide only device descriptor + * Awaiting: Install returns ESP_OK, default descriptor is being used, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Device: Device Descriptor only", "[runtime_config][default]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + + tusb_cfg.descriptor.device = &test_device_descriptor; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.qualifier = &device_qualifier; +#endif // TUD_OPT_HIGH_SPEED + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Provide only device & FS configuration descriptor + * Awaiting: Install returns ESP_OK, default FS configuration descriptor is being used, device is enumerated, tusb_mount_cb() is called + * + * Note: HS configuration descriptor is not provided by user (legacy compatibility) and default configuration descriptor for HS is used. + */ +TEST_CASE("Device: Device & Full-speed config only", "[runtime_config][default]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + + tusb_cfg.descriptor.device = &test_device_descriptor; + tusb_cfg.descriptor.full_speed_config = test_fs_configuration_descriptor; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.qualifier = &device_qualifier; + // tusb_cfg.descriptor.high_speed_config = NULL; +#endif // TUD_OPT_HIGH_SPEED + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} + +#if (SOC_USB_OTG_PERIPH_NUM > 1) +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Provide no descriptors (All default for High-speed) + * Awaiting: Install returns ESP_OK, default descriptors are being used, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Device: High-speed default", "[runtime_config][high_speed]") +{ + const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Provide only device & HS configuration descriptor + * Awaiting: Install returns ESP_OK, default FS configuration descriptor is being used, device is enumerated, tusb_mount_cb() is called + * + * Note: FS configuration descriptor is not provided by user (legacy compatibility) and default configuration descriptor for FS is used. + */ +TEST_CASE("Device: Device and High-speed config only", "[runtime_config][high_speed]") +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + + tusb_cfg.descriptor.device = &test_device_descriptor; + tusb_cfg.descriptor.qualifier = &device_qualifier; + tusb_cfg.descriptor.high_speed_config = test_hs_configuration_descriptor; + + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} +#endif // SOC_USB_OTG_PERIPH_NUM > 1 + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/test_multitask_access.c b/components/esp_tinyusb/test_apps/runtime_config/main/test_multitask_access.c new file mode 100644 index 0000000..9e34e1e --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/test_multitask_access.c @@ -0,0 +1,189 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +#include "esp_private/usb_phy.h" +// +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "test_task.h" +#include "sdkconfig.h" +#include "device_handling.h" + +#define MULTIPLE_THREADS_TASKS_NUM 5 + +static SemaphoreHandle_t sem_done = NULL; +TaskHandle_t test_task_handles[MULTIPLE_THREADS_TASKS_NUM]; + +// Unlocked spinlock, ready to use +static portMUX_TYPE _spinlock = portMUX_INITIALIZER_UNLOCKED; +static volatile int nb_of_success = 0; + +static void test_task_install(void *arg) +{ + (void) arg; + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + tusb_cfg.phy.skip_setup = true; // Skip phy setup to allow multiple tasks to install the driver + + // Wait to be started by main thread + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + if (tinyusb_driver_install(&tusb_cfg) == ESP_OK) { + test_device_wait(); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_uninstall(), "Unable to uninstall driver after install in worker"); + taskENTER_CRITICAL(&_spinlock); + nb_of_success++; + taskEXIT_CRITICAL(&_spinlock); + } + + // Notify the parent task that the task completed the job + xSemaphoreGive(sem_done); + // Delete task + vTaskDelete(NULL); +} + + +static void test_task_uninstall(void *arg) +{ + (void) arg; + // Wait to be started by main thread + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + if (tinyusb_driver_uninstall() == ESP_OK) { + taskENTER_CRITICAL(&_spinlock); + nb_of_success++; + taskEXIT_CRITICAL(&_spinlock); + } + + // Notify the parent task that the task completed the job + xSemaphoreGive(sem_done); + // Delete task + vTaskDelete(NULL); +} + +// USB PHY + +static usb_phy_handle_t test_init_phy(void) +{ + usb_phy_handle_t phy_hdl = NULL; + usb_phy_config_t phy_conf = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_DEVICE, + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, usb_new_phy(&phy_conf, &phy_hdl), "Unable to install USB PHY "); + return phy_hdl; +} + +static void test_deinit_phy(usb_phy_handle_t phy_hdl) +{ + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, usb_del_phy(phy_hdl), "Unable to delete USB PHY "); +} + +// ============================= Tests ========================================= + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Trying to install driver from several tasks + * Note: when phy.skip_setup = false, the task access will be determined by the first task install the phy + */ +TEST_CASE("Multitask: Install", "[runtime_config][default]") +{ + usb_phy_handle_t phy_hdl = test_init_phy(); + + // Create counting semaphore to wait for all tasks to complete + sem_done = xSemaphoreCreateCounting(MULTIPLE_THREADS_TASKS_NUM, 0); + TEST_ASSERT_NOT_NULL(sem_done); + + // No task are running yet + nb_of_success = 0; + + // Create tasks that will start the driver + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(test_task_install, + "InstallTask", + 4096, + NULL, + 4 + i, + &test_task_handles[i])); + } + + // Start all tasks + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + xTaskNotifyGive(test_task_handles[i]); + } + + // Wait for all tasks to complete + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL_MESSAGE(pdTRUE, xSemaphoreTake(sem_done, pdMS_TO_TICKS(5000)), "Not all tasks completed in time"); + } + + // There should be only one task that was able to install the driver + TEST_ASSERT_EQUAL_MESSAGE(1, nb_of_success, "Only one task should be able to install the driver"); + // Clean-up + test_deinit_phy(phy_hdl); + vSemaphoreDelete(sem_done); +} + +TEST_CASE("Multitask: Uninstall", "[runtime_config][default]") +{ + usb_phy_handle_t phy_hdl = test_init_phy(); + // Create counting semaphore to wait for all tasks to complete + sem_done = xSemaphoreCreateCounting(MULTIPLE_THREADS_TASKS_NUM, 0); + TEST_ASSERT_NOT_NULL(sem_done); + + // No task are running yet + nb_of_success = 0; + + // Install the driver once + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + tusb_cfg.phy.skip_setup = true; // Skip phy setup to allow multiple tasks + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_driver_install(&tusb_cfg), "Unable to install TinyUSB driver "); + // Create tasks that will uninstall the driver + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(test_task_uninstall, + "UninstallTask", + 4096, + NULL, + 4 + i, + &test_task_handles[i])); + } + + // Start all tasks + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + xTaskNotifyGive(test_task_handles[i]); + } + // Wait for all tasks to complete + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL_MESSAGE(pdTRUE, xSemaphoreTake(sem_done, pdMS_TO_TICKS(5000)), "Not all tasks completed in time"); + } + + // There should be only one task that was able to uninstall the driver + TEST_ASSERT_EQUAL_MESSAGE(1, nb_of_success, "Only one task should be able to uninstall the driver"); + + // Clean-up + test_deinit_phy(phy_hdl); + vSemaphoreDelete(sem_done); +} + + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/test_periph.c b/components/esp_tinyusb/test_apps/runtime_config/main/test_periph.c new file mode 100644 index 0000000..69156ae --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/test_periph.c @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "test_task.h" +#include "sdkconfig.h" +#include "device_handling.h" + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Initialise TinyUSB on Full-speed peripheral (USB OTG 1.1) + * Awaiting: Install returns ESP_OK, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Periph: Full-speed", "[periph][full_speed]") +{ + // TinyUSB driver default configuration + const tinyusb_config_t tusb_cfg = TINYUSB_CONFIG_FULL_SPEED(test_device_event_handler, NULL); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} + +#if (SOC_USB_OTG_PERIPH_NUM > 1) +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Initialise TinyUSB on High-speed peripheral (USB OTG 2.0) + * Awaiting: Install returns ESP_OK, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Periph: High-speed", "[periph][high_speed]") +{ + // TinyUSB driver default configuration + const tinyusb_config_t tusb_cfg = TINYUSB_CONFIG_HIGH_SPEED(test_device_event_handler, NULL); + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} +#endif // SOC_USB_OTG_PERIPH_NUM > 1 + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/test_task.c b/components/esp_tinyusb/test_apps/runtime_config/main/test_task.c new file mode 100644 index 0000000..3a79985 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/test_task.c @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "test_task.h" +#include "sdkconfig.h" +#include "device_handling.h" + +// ============================= Tests ========================================= + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Invalid configuration + * Awaiting: Install returns ESP_ERR_INVALID_ARG + */ +TEST_CASE("Task: Invalid params", "[runtime_config][default]") +{ + // TinyUSB driver default configuration + tinyusb_config_t tusb_cfg = { 0 }; + // Install TinyUSB driver - Invalid Task size + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, tinyusb_driver_install(&tusb_cfg)); + tusb_cfg.task.size = 4096; + // Install TinyUSB driver - Invalid Task priority + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, tinyusb_driver_install(&tusb_cfg)); + tusb_cfg.task.priority = 5; + // Install TinyUSB driver - Invalid Task affinity + tusb_cfg.task.xCoreID = 0xff; + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, tinyusb_driver_install(&tusb_cfg)); +} + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Initialise with Internal task + * Awaiting: Install returns ESP_OK, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Task: Default configuration", "[task][default]") +{ + // TinyUSB driver default configuration + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} + +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Initialise with Internal task and CPU0 affinity + * Awaiting: Install returns ESP_OK, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Task: Default configuration, pin to CPU0", "[task][default]") +{ + // TinyUSB driver default configuration + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + tusb_cfg.task.xCoreID = 0; + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} + +#if !CONFIG_FREERTOS_UNICORE +/** + * @brief TinyUSB Task specific testcase + * + * Scenario: Initialise with Internal task and CPU1 affinity + * Awaiting: Install returns ESP_OK, device is enumerated, tusb_mount_cb() is called + */ +TEST_CASE("Task: Default configuration, pin to CPU1", "[task][default]") +{ + // TinyUSB driver default configuration + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + tusb_cfg.task.xCoreID = 1; + // Install TinyUSB driver + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + test_device_wait(); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); +} +#endif // CONFIG_FREERTOS_UNICORE + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/runtime_config/main/test_task.h b/components/esp_tinyusb/test_apps/runtime_config/main/test_task.h new file mode 100644 index 0000000..739af13 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/main/test_task.h @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "sdkconfig.h" + +/** + * Test Task Configuration setup + */ + +// Default size for task stack used in TinyUSB task creation +#define TUSB_TASK_SIZE 4096 +// Default priority for task used in TinyUSB task creation +#define TUSB_TASK_PRIO 5 +// Affinity for task used in TinyUSB task creation +#define TUSB_TASK_AFFINITY_NO 0x7FFFFFFF /* FREERTOS_NO_AFFINITY */ +#define TUSB_TASK_AFFINITY_CPU0 0x00 +#if (!CONFIG_FREERTOS_UNICORE) +#define TUSB_TASK_AFFINITY_CPU1 0x01 +#endif // !CONFIG_FREERTOS_UNICORE diff --git a/components/esp_tinyusb/test_apps/runtime_config/pytest_runtime_config.py b/components/esp_tinyusb/test_apps/runtime_config/pytest_runtime_config.py new file mode 100644 index 0000000..ce6a6ff --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/pytest_runtime_config.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded_idf.dut import IdfDut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.usb_device +@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +def test_usb_device_runtime_config(dut: IdfDut) -> None: + peripherals = [ + 'default', + 'high_speed', + # 'full_speed', TODO: Enable this after the P4 USB OTG 1.1 periph is connected + ] if dut.target == 'esp32p4' else [ + 'default', + 'full_speed', + ] + + for periph in peripherals: + dut.run_all_single_board_cases(group=periph) + +# The threshold values for TinyUSB Task Run time (in cycles) for different targets +TASK_RUN_TIME_LIMITS = { + 'esp32s2': 7000, + 'esp32s3': 3000, + 'esp32p4': 1800, +} + +def _get_run_time_th(target: str) -> int: + assert target in TASK_RUN_TIME_LIMITS + return TASK_RUN_TIME_LIMITS.get(target) + +@pytest.mark.usb_device +@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +def test_cpu_load_task_stat_print(dut: IdfDut) -> None: + ''' + Test to verify that Run time and CPU load measurement for TinyUSB task is working. + This test runs only on runtime_config test app. + + Test procedure: + 1. Run the test on the DUT + 2. Expect to see TinyUSB task CPU load printed in the output + 3. Expect TinyUSB task CPU load to be not greater than 0% + ''' + dut.expect_exact('Press ENTER to see the list of tests.') + dut.write('[cpu_load]') + dut.expect_exact('Starting TinyUSB load measurement test...') + dut.expect_exact('CPU load measurement test completed.') + + line = dut.expect(r'TinyUSB Run time: (\d+) ticks') + run_time = int(line.group(1)) + run_time_max = _get_run_time_th(dut.target) + + assert 0 < run_time < run_time_max, f'Unexpected TinyUSB Run time: {run_time}' diff --git a/components/esp_tinyusb/test_apps/runtime_config/sdkconfig.defaults b/components/esp_tinyusb/test_apps/runtime_config/sdkconfig.defaults new file mode 100644 index 0000000..d4d55b3 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/sdkconfig.defaults @@ -0,0 +1,19 @@ +# Configure TinyUSB, it will be used to mock USB devices +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_COUNT=2 + +# Disable watchdogs, they'd get triggered during unity interactive menu +# CONFIG_ESP_TASK_WDT_INIT is not set + +# For CPU load measurement +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y + +# Run-time checks of Heap and Stack +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y + +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/components/esp_tinyusb/test_apps/runtime_config/sdkconfig.defaults.esp32p4 b/components/esp_tinyusb/test_apps/runtime_config/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000..2dbcf83 --- /dev/null +++ b/components/esp_tinyusb/test_apps/runtime_config/sdkconfig.defaults.esp32p4 @@ -0,0 +1 @@ +CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y diff --git a/components/esp_tinyusb/test_apps/teardown_device/CMakeLists.txt b/components/esp_tinyusb/test_apps/teardown_device/CMakeLists.txt new file mode 100644 index 0000000..d63d14e --- /dev/null +++ b/components/esp_tinyusb/test_apps/teardown_device/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +project(test_app_teardown_device) diff --git a/components/esp_tinyusb/test_apps/teardown_device/main/CMakeLists.txt b/components/esp_tinyusb/test_apps/teardown_device/main/CMakeLists.txt new file mode 100644 index 0000000..e81e627 --- /dev/null +++ b/components/esp_tinyusb/test_apps/teardown_device/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES unity + WHOLE_ARCHIVE) diff --git a/components/esp_tinyusb/test_apps/teardown_device/main/idf_component.yml b/components/esp_tinyusb/test_apps/teardown_device/main/idf_component.yml new file mode 100644 index 0000000..b1cb5b5 --- /dev/null +++ b/components/esp_tinyusb/test_apps/teardown_device/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "*" + override_path: "../../../" diff --git a/components/esp_tinyusb/test_apps/teardown_device/main/test_app_main.c b/components/esp_tinyusb/test_apps/teardown_device/main/test_app_main.c new file mode 100644 index 0000000..b1321ed --- /dev/null +++ b/components/esp_tinyusb/test_apps/teardown_device/main/test_app_main.c @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" + +void app_main(void) +{ + /* + _ _ _ + | | (_) | | + ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ + / _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \ + | __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) | + \___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/ + | |______ __/ | + |_|______| |___/ + _____ _____ _____ _____ + |_ _| ___/ ___|_ _| + | | | |__ \ `--. | | + | | | __| `--. \ | | + | | | |___/\__/ / | | + \_/ \____/\____/ \_/ + */ + + printf(" _ _ _ \n"); + printf(" | | (_) | | \n"); + printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n"); + printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n"); + printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n"); + printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n"); + printf(" | |______ __/ | \n"); + printf(" |_|______| |___/ \n"); + printf(" _____ _____ _____ _____ \n"); + printf("|_ _| ___/ ___|_ _| \n"); + printf(" | | | |__ \\ `--. | | \n"); + printf(" | | | __| `--. \\ | | \n"); + printf(" | | | |___/\\__/ / | | \n"); + printf(" \\_/ \\____/\\____/ \\_/ \n"); + + unity_utils_setup_heap_record(80); + unity_utils_set_leak_level(128); + unity_run_menu(); +} + +/* setUp runs before every test */ +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + unity_utils_evaluate_leaks(); +} diff --git a/components/esp_tinyusb/test_apps/teardown_device/main/test_teardown.c b/components/esp_tinyusb/test_apps/teardown_device/main/test_teardown.c new file mode 100644 index 0000000..95de8fc --- /dev/null +++ b/components/esp_tinyusb/test_apps/teardown_device/main/test_teardown.c @@ -0,0 +1,148 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" +#if SOC_USB_OTG_SUPPORTED + +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" + +static const char *TAG = "teardown"; + +SemaphoreHandle_t wait_mount = NULL; + +#define TEARDOWN_DEVICE_INIT_DELAY_MS 1000 +#define TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS 1000 +#define TEARDOWN_DEVICE_DETACH_DELAY_MS 1000 + +#define TEARDOWN_AMOUNT 10 + +#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN) +static uint8_t const test_configuration_descriptor[] = { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), +}; + +static const tusb_desc_device_t test_device_descriptor = { + .bLength = sizeof(test_device_descriptor), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers + .idProduct = 0x4002, + .bcdDevice = 0x100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01 +}; + +#if (TUD_OPT_HIGH_SPEED) +static const tusb_desc_device_qualifier_t device_qualifier = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0 +}; +#endif // TUD_OPT_HIGH_SPEED + +/** + * @brief TinyUSB callback for device event + * + * @note + * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. + * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. + */ +void test_teardown_event_handler(tinyusb_event_t *event, void *arg) +{ + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + xSemaphoreGive(wait_mount); + break; + default: + break; + } +} + +/** + * @brief TinyUSB Teardown specific testcase + * + * Scenario: + * 1. Install TinyUSB device without any class + * 2. Wait SetConfiguration() (tud_mount_cb) + * 3. If attempts == 0 goto step 8 + * 4. Wait TEARDOWN_DEVICE_DETACH_DELAY_MS + * 5. Uninstall TinyUSB device + * 6. Wait TEARDOWN_DEVICE_INIT_DELAY_MS + * 7. Decrease attempts by 1, goto step 3 + * 8. Wait TEARDOWN_DEVICE_DETACH_DELAY_MS + * 9. Uninstall TinyUSB device + */ +TEST_CASE("tinyusb_teardown", "[esp_tinyusb][teardown]") +{ + wait_mount = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_EQUAL(NULL, wait_mount); + + // TinyUSB driver configuration + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_teardown_event_handler); + tusb_cfg.descriptor.device = &test_device_descriptor; + tusb_cfg.descriptor.full_speed_config = test_configuration_descriptor; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.qualifier = &device_qualifier; + tusb_cfg.descriptor.high_speed_config = test_configuration_descriptor; +#endif // TUD_OPT_HIGH_SPEED + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + // Wait for the usb event + ESP_LOGD(TAG, "wait mount..."); + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS))); + ESP_LOGD(TAG, "mounted"); + + // Teardown routine + int attempts = TEARDOWN_AMOUNT; + while (attempts--) { + // Keep device attached + vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + // Teardown + vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_INIT_DELAY_MS)); + // Reconnect + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + // Wait for the usb event + ESP_LOGD(TAG, "wait mount..."); + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS))); + ESP_LOGD(TAG, "mounted"); + } + + // Teardown + vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + // Remove primitives + vSemaphoreDelete(wait_mount); +} + +#endif diff --git a/components/esp_tinyusb/test_apps/teardown_device/pytest_teardown_device.py b/components/esp_tinyusb/test_apps/teardown_device/pytest_teardown_device.py new file mode 100644 index 0000000..382265f --- /dev/null +++ b/components/esp_tinyusb/test_apps/teardown_device/pytest_teardown_device.py @@ -0,0 +1,75 @@ +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded_idf.dut import IdfDut +import subprocess +from time import sleep, time +from pytest_embedded_idf.utils import idf_parametrize + + +class DeviceNotFoundError(Exception): + """Custom exception for device not found within the timeout period.""" + pass + +def tusb_dev_in_list(vid, pid): + try: + output = subprocess.check_output(["lsusb"], text=True) + search_string = f"{vid}:{pid}" + return search_string in output + except Exception as e: + print(f"Error while executing lsusb: {e}") + raise + +def wait_tusb_dev_appeared(vid, pid, timeout): + start_time = time() + while True: + if tusb_dev_in_list(vid, pid): + return True + if time() - start_time > timeout: + raise DeviceNotFoundError(f"Device with VID: 0x{vid:04x}, PID: 0x{pid:04x} not found within {timeout} seconds.") + sleep(0.5) + +def wait_tusb_dev_removed(vid, pid, timeout): + start_time = time() + while True: + if not tusb_dev_in_list(vid, pid): + return True + if time() - start_time > timeout: + raise DeviceNotFoundError(f"Device with VID: 0x{vid:04x}, PID: 0x{pid:04x} wasn't removed within {timeout} seconds.") + sleep(0.5) + +def tusb_device_teardown(iterations, timeout): + TUSB_VID = "303a" # Espressif TinyUSB VID + TUSB_PID = "4002" # Espressif TinyUSB VID + + for i in range(iterations): + # Wait until the device is present + print(f"Waiting for device ...") + wait_tusb_dev_appeared(TUSB_VID, TUSB_PID, timeout) + print("Device detected.") + + # Wait until the device is removed + print("Waiting for the device to be removed...") + wait_tusb_dev_removed(TUSB_VID, TUSB_PID, timeout) + print("Device removed.") + print("Monitoring completed.") + +@pytest.mark.usb_device +@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +def test_usb_teardown_device(dut: IdfDut) -> None: + dut.expect_exact('Press ENTER to see the list of tests.') + dut.write('[teardown]') + dut.expect_exact('TinyUSB: TinyUSB Driver installed') + sleep(2) # Some time for the OS to enumerate our USB device + + try: + tusb_device_teardown(10, 10) # Teardown tusb device: amount, timeout + + except DeviceNotFoundError as e: + print(f"Error: {e}") + raise + + except Exception as e: + print(f"An unexpected error occurred: {e}") + raise diff --git a/components/esp_tinyusb/test_apps/teardown_device/sdkconfig.defaults b/components/esp_tinyusb/test_apps/teardown_device/sdkconfig.defaults new file mode 100644 index 0000000..21bf3a7 --- /dev/null +++ b/components/esp_tinyusb/test_apps/teardown_device/sdkconfig.defaults @@ -0,0 +1,15 @@ +# Configure TinyUSB, it will be used to mock USB devices +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_COUNT=1 + +# Disable watchdogs, they'd get triggered during unity interactive menu +# CONFIG_ESP_TASK_WDT_INIT is not set + +# Run-time checks of Heap and Stack +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y + +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/components/esp_tinyusb/test_apps/teardown_device/sdkconfig.defaults.esp32p4 b/components/esp_tinyusb/test_apps/teardown_device/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000..2dbcf83 --- /dev/null +++ b/components/esp_tinyusb/test_apps/teardown_device/sdkconfig.defaults.esp32p4 @@ -0,0 +1 @@ +CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y diff --git a/components/esp_tinyusb/test_apps/usb_cv/CMakeLists.txt b/components/esp_tinyusb/test_apps/usb_cv/CMakeLists.txt new file mode 100644 index 0000000..64c1766 --- /dev/null +++ b/components/esp_tinyusb/test_apps/usb_cv/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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) +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(test_app_usbcv) diff --git a/components/esp_tinyusb/test_apps/usb_cv/main/CMakeLists.txt b/components/esp_tinyusb/test_apps/usb_cv/main/CMakeLists.txt new file mode 100644 index 0000000..42cee62 --- /dev/null +++ b/components/esp_tinyusb/test_apps/usb_cv/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES unity + PRIV_REQUIRES fatfs wear_levelling esp_partition esp_timer + WHOLE_ARCHIVE) diff --git a/components/esp_tinyusb/test_apps/usb_cv/main/device_common.c b/components/esp_tinyusb/test_apps/usb_cv/main/device_common.c new file mode 100644 index 0000000..c3565d6 --- /dev/null +++ b/components/esp_tinyusb/test_apps/usb_cv/main/device_common.c @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "tinyusb.h" + +static SemaphoreHandle_t wait_mount = NULL; + +#define TUSB_DEVICE_DELAY_MS 1000 + +void test_device_setup(void) +{ + wait_mount = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(wait_mount); +} + +void test_device_release(void) +{ + TEST_ASSERT_NOT_NULL(wait_mount); + vSemaphoreDelete(wait_mount); +} + +void test_device_wait(void) +{ + // Wait for tud_mount_cb() to be called + TEST_ASSERT_EQUAL_MESSAGE(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TUSB_DEVICE_DELAY_MS)), "No tusb_mount_cb() in time"); + // Delay to allow finish the enumeration + // Disable this delay could lead to potential race conditions when the tud_task() is pinned to another CPU + vTaskDelay(pdMS_TO_TICKS(250)); +} + +/** + * @brief TinyUSB callback for device mount. + * + * @note + * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. + * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. + */ +void test_device_event_handler(tinyusb_event_t *event, void *arg) +{ + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + xSemaphoreGive(wait_mount); + break; + case TINYUSB_EVENT_DETACHED: + break; + default: + break; + } +} + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/usb_cv/main/device_common.h b/components/esp_tinyusb/test_apps/usb_cv/main/device_common.h new file mode 100644 index 0000000..851d0d3 --- /dev/null +++ b/components/esp_tinyusb/test_apps/usb_cv/main/device_common.h @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "tinyusb.h" + +/** + * @brief Test device setup + */ +void test_device_setup(void); + +/** + * @brief Test device release + */ +void test_device_release(void); + +/** + * @brief Test device wait + * + * Waits the tusb_mount_cb() which indicates the device connected to the Host and enumerated. + */ +void test_device_wait(void); + +/** + * @brief TinyUSB callback for device mount. + * + * @note + * For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver. + * For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor. + */ +void test_device_event_handler(tinyusb_event_t *event, void *arg); diff --git a/components/esp_tinyusb/test_apps/usb_cv/main/idf_component.yml b/components/esp_tinyusb/test_apps/usb_cv/main/idf_component.yml new file mode 100644 index 0000000..b1cb5b5 --- /dev/null +++ b/components/esp_tinyusb/test_apps/usb_cv/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "*" + override_path: "../../../" diff --git a/components/esp_tinyusb/test_apps/usb_cv/main/storage_common.c b/components/esp_tinyusb/test_apps/usb_cv/main/storage_common.c new file mode 100644 index 0000000..7b91b40 --- /dev/null +++ b/components/esp_tinyusb/test_apps/usb_cv/main/storage_common.c @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "esp_idf_version.h" +#include "sdkconfig.h" +#include "storage_common.h" + +void storage_init_spiflash(wl_handle_t *wl_handle) +{ + wl_handle_t wl; + const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_FAT, "storage"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, wl_mount(data_partition, &wl), "Failed to mount wear levelling on storage partition"); + + printf("SPIFLASH Wear Levelling initialized successfully\n"); + printf("\tSectors: %u\n", wl_size(wl) / wl_sector_size(wl)); + printf("\tSector Size: %u\n", wl_sector_size(wl)); + *wl_handle = wl; +} + +void storage_erase_spiflash(void) +{ + const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_FAT, "storage"); + TEST_ASSERT_NOT_NULL_MESSAGE(data_partition, "Storage partition not found"); + // Erase the data partition + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, esp_partition_erase_range(data_partition, 0, data_partition->size), + "Failed to erase storage partition"); + printf("Storage partition erased successfully (size in bytes: %ld)\n", data_partition->size); +} + +void storage_deinit_spiflash(wl_handle_t wl_handle) +{ + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, wl_unmount(wl_handle), "Failed to unmount wear levelling on data partition"); +} + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/usb_cv/main/storage_common.h b/components/esp_tinyusb/test_apps/usb_cv/main/storage_common.h new file mode 100644 index 0000000..b6bbb7d --- /dev/null +++ b/components/esp_tinyusb/test_apps/usb_cv/main/storage_common.h @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "wear_levelling.h" + +#if (SOC_SDMMC_HOST_SUPPORTED) +#include "sdmmc_cmd.h" +#endif // SOC_SDMMC_HOST_SUPPORTED + +/** + * @brief Initialize the SPIFLASH storage with wear levelling + * + * @param[out] wl_handle Pointer to the wear levelling handle that will be initialized + */ +void storage_init_spiflash(wl_handle_t *wl_handle); + +/** + * @brief Erase the SPIFLASH storage partition + * + * This function erases the entire SPIFLASH storage partition. + * + * @note There is no protection against accidental data loss and collision with wear levelling, use with caution. + */ +void storage_erase_spiflash(void); + +/** + * @brief Deinitialize the SPIFLASH storage and unmount wear levelling + * + * @param[in] wl_handle Wear levelling handle, obtained from storage_init_spiflash + */ +void storage_deinit_spiflash(wl_handle_t wl_handle); diff --git a/components/esp_tinyusb/test_apps/usb_cv/main/test_app_main.c b/components/esp_tinyusb/test_apps/usb_cv/main/test_app_main.c new file mode 100644 index 0000000..bfbecb6 --- /dev/null +++ b/components/esp_tinyusb/test_apps/usb_cv/main/test_app_main.c @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" +#include "device_common.h" + +/* setUp runs before every test */ +void setUp(void) +{ + unity_utils_record_free_mem(); + test_device_setup(); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + // Short delay to allow task to be cleaned up + vTaskDelay(10); + test_device_release(); + unity_utils_evaluate_leaks(); +} + + +void app_main(void) +{ + /* + _ _ _ + | | (_) | | + ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ + / _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \ + | __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) | + \___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/ + | |______ __/ | + |_|______| |___/ + _____ _____ _____ _____ + |_ _| ___/ ___|_ _| + | | | |__ \ `--. | | + | | | __| `--. \ | | + | | | |___/\__/ / | | + \_/ \____/\____/ \_/ + */ + + printf(" _ _ _ \n"); + printf(" | | (_) | | \n"); + printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n"); + printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n"); + printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n"); + printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n"); + printf(" | |______ __/ | \n"); + printf(" |_|______| |___/ \n"); + printf(" _____ _____ _____ _____ \n"); + printf("|_ _| ___/ ___|_ _| \n"); + printf(" | | | |__ \\ `--. | | \n"); + printf(" | | | __| `--. \\ | | \n"); + printf(" | | | |___/\\__/ / | | \n"); + printf(" \\_/ \\____/\\____/ \\_/ \n"); + + unity_utils_setup_heap_record(80); + unity_utils_set_leak_level(1048); // 128 (default) + 820 (wl_mount) + 100 (in case of driver format) + unity_run_menu(); +} diff --git a/components/esp_tinyusb/test_apps/usb_cv/main/test_usbcv.c b/components/esp_tinyusb/test_apps/usb_cv/main/test_usbcv.c new file mode 100644 index 0000000..e119d9a --- /dev/null +++ b/components/esp_tinyusb/test_apps/usb_cv/main/test_usbcv.c @@ -0,0 +1,234 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_USB_OTG_SUPPORTED +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +// +#include "esp_system.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "class/hid/hid_device.h" +// +#include "unity.h" +#include "device_common.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "tinyusb_msc.h" +#include "storage_common.h" + + +// +// ========================== Test Configuration Parameters ===================================== +// + +#define TEST_USBCV_TEST_TIMEOUT_MS 150000 // Timeout for USBCV compliance test +#define TEST_USBCV_REMOTE_WAKEUP_DELAY_MS 1000 // Delay for remote wakeup + +static esp_timer_handle_t test_remote_wakeup_timer = NULL; + +static void test_remote_wakeup_timer_callback(void *arg) +{ + printf("Remote wakeup...\n"); + tud_remote_wakeup(); +} + +static void test_remote_wakeup_timer_init(void) +{ + esp_timer_create_args_t timer_args = { + .callback = &test_remote_wakeup_timer_callback, + .name = "test_remote_wakeup_timer", + }; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, esp_timer_create(&timer_args, &test_remote_wakeup_timer), "Failed to create remote wakeup timer"); +} + +static void test_remote_wakeup_timer_deinit(void) +{ + TEST_ASSERT_NOT_NULL_MESSAGE(test_remote_wakeup_timer, "Remote wakeup timer is not initialized"); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, esp_timer_delete(test_remote_wakeup_timer), "Failed to delete remote wakeup timer"); + test_remote_wakeup_timer = NULL; +} + +// +// ========================== TinyUSB HID Device Descriptors =============================== +// + +#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_HID * TUD_HID_DESC_LEN) + +/** + * @brief HID report descriptor + * + * In this example we implement Keyboard + Mouse HID device, + * so we must define both report descriptors + */ +const uint8_t hid_report_descriptor[] = { + TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD)), + TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE)) +}; + +/** + * @brief Configuration descriptor + * + * This is a simple configuration descriptor that defines 1 configuration and 1 HID interface + */ +static const uint8_t hid_configuration_descriptor[] = { + // Configuration number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + // Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval + TUD_HID_DESCRIPTOR(0, 4, false, sizeof(hid_report_descriptor), 0x81, 16, 10), +}; + +// +// ========================== TinyUSB MSC Storage Event Handling ================================= +// + +// +// ========================== TinyUSB MSC Storage Initialization Tests ============================= +// + +// +// =================================== TinyUSB callbacks =========================================== +// + +// Invoked when received GET HID REPORT DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) +{ + // We use only one interface and one HID report descriptor, so we can ignore parameter 'instance' + return hid_report_descriptor; +} + +// Invoked when received GET_REPORT control request +// Application must fill buffer report's content and return its length. +// Return zero will cause the stack to STALL request +uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) +{ + (void) instance; + (void) report_id; + (void) report_type; + (void) buffer; + (void) reqlen; + + return 0; +} + +// Invoked when received SET_REPORT control request or +// received data on OUT endpoint ( Report ID = 0, Type = 0 ) +void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) +{ + (void) instance; + (void) report_id; + (void) report_type; + (void) buffer; + (void) bufsize; + +} + + + +// Invoked when the device is suspended +void tud_suspend_cb(bool remote_wakeup_en) +{ + printf("Device suspended, remote wakeup enabled: %s\n", remote_wakeup_en ? "true" : "false"); + if (remote_wakeup_en) { + // If remote wakeup is enabled, we can wake up the host by sending a resume signal + // tinyusb_msc_storage_resume(); + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, esp_timer_start_once(test_remote_wakeup_timer, TEST_USBCV_REMOTE_WAKEUP_DELAY_MS * 1000), "Failed to start remote wakeup timer"); + } + +} + +// Invoked when the device resumes from suspend +void tud_resume_cb(void) +{ + printf("Device resumed from suspend\n"); +} + +TEST_CASE("USBCV: HID Device", "[hid]") +{ + // Initialize the remote wakeup timer + test_remote_wakeup_timer_init(); + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + + tusb_cfg.descriptor.full_speed_config = hid_configuration_descriptor; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.high_speed_config = hid_configuration_descriptor; +#endif // TUD_OPT_HIGH_SPEED + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + + + printf("Device is configured, launch the USBCV compliance test..\n"); + vTaskDelay(pdMS_TO_TICKS(TEST_USBCV_TEST_TIMEOUT_MS)); + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + test_remote_wakeup_timer_deinit(); +} + +TEST_CASE("USBCV: MSC Device", "[msc]") +{ + // Initialize the remote wakeup timer + test_remote_wakeup_timer_init(); + + wl_handle_t wl_handle = WL_INVALID_HANDLE; + storage_init_spiflash(&wl_handle); + TEST_ASSERT_NOT_EQUAL_MESSAGE(WL_INVALID_HANDLE, wl_handle, "Wear leveling handle is invalid, check the partition configuration"); + + tinyusb_msc_storage_config_t config = { + .medium.wl_handle = wl_handle, // Set the context to the wear leveling handle + }; + + // Initialize TinyUSB MSC storage + tinyusb_msc_storage_handle_t storage_hdl = NULL; + TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, tinyusb_msc_new_storage_spiflash(&config, &storage_hdl), "Failed to initialize TinyUSB MSC storage with SPIFLASH"); + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + + printf("Device is configured, launch the USBCV compliance test..\n"); + vTaskDelay(pdMS_TO_TICKS(TEST_USBCV_TEST_TIMEOUT_MS)); + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + storage_deinit_spiflash(wl_handle); + tinyusb_msc_delete_storage(storage_hdl); + test_remote_wakeup_timer_deinit(); +} + +#if (SOC_USB_OTG_PERIPH_NUM > 1) +// ESP32-P4 has both Full-speed and High-speed USB OTG ports, so we can test both + +TEST_CASE("USBCV: HID Device on Full-speed port", "[hid][full_speed]") +{ + // Initialize the remote wakeup timer + test_remote_wakeup_timer_init(); + // Install TinyUSB driver on Full-speed port + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(test_device_event_handler); + + tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; + tusb_cfg.descriptor.full_speed_config = hid_configuration_descriptor; + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + + printf("Device is configured, launch the USBCV compliance test..\n"); + vTaskDelay(pdMS_TO_TICKS(TEST_USBCV_TEST_TIMEOUT_MS)); + + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + test_remote_wakeup_timer_deinit(); +} +#endif // (SOC_USB_OTG_PERIPH_NUM > 1) + +#endif // SOC_USB_OTG_SUPPORTED diff --git a/components/esp_tinyusb/test_apps/usb_cv/partitions.csv b/components/esp_tinyusb/test_apps/usb_cv/partitions.csv new file mode 100644 index 0000000..1c79321 --- /dev/null +++ b/components/esp_tinyusb/test_apps/usb_cv/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, fat, , 1M, diff --git a/components/esp_tinyusb/test_apps/usb_cv/sdkconfig.defaults b/components/esp_tinyusb/test_apps/usb_cv/sdkconfig.defaults new file mode 100644 index 0000000..47baef3 --- /dev/null +++ b/components/esp_tinyusb/test_apps/usb_cv/sdkconfig.defaults @@ -0,0 +1,27 @@ +# Configure TinyUSB, it will be used to mock USB devices +CONFIG_TINYUSB_MSC_ENABLED=y +CONFIG_TINYUSB_HID_COUNT=1 + +# Partitions configuration, used by spiflash storage +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_MODE_PERF=y + +# On chips with USB Serial JTAG, disable secondary console which does not make sense when using console component +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y + +# Disable watchdogs, they'd get triggered during unity interactive menu +CONFIG_ESP_INT_WDT=n +CONFIG_ESP_TASK_WDT=n + +# Run-time checks of Heap and Stack +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y + +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/components/esp_tinyusb/test_apps/uvc/CMakeLists.txt b/components/esp_tinyusb/test_apps/uvc/CMakeLists.txt new file mode 100644 index 0000000..6d6e2cf --- /dev/null +++ b/components/esp_tinyusb/test_apps/uvc/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.16.0) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(USB-UVC) diff --git a/components/esp_tinyusb/test_apps/uvc/files/Image1.jpg b/components/esp_tinyusb/test_apps/uvc/files/Image1.jpg new file mode 100644 index 0000000..ef05e44 Binary files /dev/null and b/components/esp_tinyusb/test_apps/uvc/files/Image1.jpg differ diff --git a/components/esp_tinyusb/test_apps/uvc/files/Image2.jpg b/components/esp_tinyusb/test_apps/uvc/files/Image2.jpg new file mode 100644 index 0000000..3a15323 Binary files /dev/null and b/components/esp_tinyusb/test_apps/uvc/files/Image2.jpg differ diff --git a/components/esp_tinyusb/test_apps/uvc/main/CMakeLists.txt b/components/esp_tinyusb/test_apps/uvc/main/CMakeLists.txt new file mode 100644 index 0000000..5bedfd6 --- /dev/null +++ b/components/esp_tinyusb/test_apps/uvc/main/CMakeLists.txt @@ -0,0 +1,5 @@ +FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/main/*.*) + +idf_component_register(SRCS ${app_sources} + EMBED_FILES "../files/Image1.jpg" + EMBED_FILES "../files/Image2.jpg") diff --git a/components/esp_tinyusb/test_apps/uvc/main/main.cpp b/components/esp_tinyusb/test_apps/uvc/main/main.cpp new file mode 100644 index 0000000..c24310a --- /dev/null +++ b/components/esp_tinyusb/test_apps/uvc/main/main.cpp @@ -0,0 +1,136 @@ +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_timer.h" + +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "tinyusb_uvc.h" + +extern const uint8_t image1_start[] asm("_binary_Image1_jpg_start"); +extern const size_t image1_len asm("Image1_jpg_length"); +extern const uint8_t image2_start[] asm("_binary_Image2_jpg_start"); +extern const size_t image2_len asm("Image2_jpg_length"); + +typedef struct { + uint8_t *buffer_a; + uint8_t *buffer_b; + size_t size_a; + size_t size_b; + bool use_a; + bool a_in_use; + bool b_in_use; + SemaphoreHandle_t mutex; +} ping_pong_t; + +static ping_pong_t ping_pong; + +static const char *TAG = "UVC"; + +bool fb_request_cb(int itf, uint8_t **buffer, size_t *buffer_size) { + if (xSemaphoreTake(ping_pong.mutex, pdMS_TO_TICKS(5)) == pdTRUE) { + if ((ping_pong.use_a == true) && (ping_pong.a_in_use == false) && (ping_pong.size_a > 0)) { + *buffer = ping_pong.buffer_a; + *buffer_size = ping_pong.size_a; + ping_pong.a_in_use = true; + + ESP_LOGD(TAG, "Requesting Buffer A (size=%d), B_in_use=%d", ping_pong.size_a, ping_pong.b_in_use); + + xSemaphoreGive(ping_pong.mutex); + + return true; + } + else if ((ping_pong.use_a == false) && (ping_pong.b_in_use == false) && (ping_pong.size_b > 0)) { + *buffer = ping_pong.buffer_b; + *buffer_size = ping_pong.size_b; + ping_pong.b_in_use = true; + + ESP_LOGD(TAG, "Requesting Buffer B (size=%d), A_in_use=%d", ping_pong.size_b, ping_pong.a_in_use); + + xSemaphoreGive(ping_pong.mutex); + + return true; + } + + ESP_LOGD(TAG, "No buffer available! A_in_use=%d, B_in_use=%d, use_a=%d", + ping_pong.a_in_use, ping_pong.b_in_use, ping_pong.use_a); + xSemaphoreGive(ping_pong.mutex); + } + + return false; +} + +void fb_return_cb(int itf, uint8_t *buffer) { + if (xSemaphoreTake(ping_pong.mutex, portMAX_DELAY) == pdTRUE) { + if (buffer == ping_pong.buffer_a) { + ESP_LOGD(TAG, "Returning Buffer A"); + ping_pong.a_in_use = false; + // Wechsle zum anderen Buffer für den nächsten Request + ping_pong.use_a = false; + } else if (buffer == ping_pong.buffer_b) { + ESP_LOGD(TAG, "Returning Buffer B"); + ping_pong.b_in_use = false; + // Wechsle zum anderen Buffer für den nächsten Request + ping_pong.use_a = true; + } else { + ESP_LOGE(TAG, "Unknown buffer returned: %p", buffer); + } + + xSemaphoreGive(ping_pong.mutex); + } +} + +void streaming_start_cb(int itf, uvc_event_t *event) { + ESP_LOGI(TAG, "Streaming started"); +} + +extern "C" void app_main(void) { + esp_err_t ret; + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + + ping_pong.buffer_a = (uint8_t*)image1_start; + ping_pong.buffer_b = (uint8_t*)image2_start; + ping_pong.size_a = image1_len; + ping_pong.size_b = image2_len; + ping_pong.use_a = true; + ping_pong.a_in_use = false; + ping_pong.b_in_use = false; + ping_pong.mutex = xSemaphoreCreateMutex(); + + ESP_LOGI(TAG, "Initializing USB..."); + + ret = tinyusb_driver_install(&tusb_cfg); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "TinyUSB driver install failed: %d", ret); + return; + } + + ESP_LOGI(TAG, "TinyUSB driver installed successfully"); + + tinyusb_config_uvc_t uvc_cfg = { + .uvc_port = TINYUSB_UVC_ITF_0, + .callback_streaming_start = streaming_start_cb, + .callback_streaming_stop = NULL, + .fb_request_cb = fb_request_cb, + .fb_return_cb = fb_return_cb, + .stop_cb = NULL, + .uvc_buffer = (uint8_t*)image1_start, + .uvc_buffer_size = image1_len, + }; + + ret = tinyusb_uvc_init(&uvc_cfg); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "UVC init failed: %d", ret); + return; + } + ESP_LOGI(TAG, "UVC initialized successfully"); + + while(1) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + } +} \ No newline at end of file diff --git a/components/esp_tinyusb/test_apps/uvc/partitions.csv b/components/esp_tinyusb/test_apps/uvc/partitions.csv new file mode 100644 index 0000000..7dcdaba --- /dev/null +++ b/components/esp_tinyusb/test_apps/uvc/partitions.csv @@ -0,0 +1,8 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +nvs_keys, data, nvs_keys, 0xD000, 0x1000, +otadata, data, ota, 0xE000, 0x2000, +app0, app, ota_0, 0x10000, 0x2F0000, +app1, app, ota_1, 0x300000, 0x2F0000, +storage, data, spiffs, 0x5F0000, 0x160000, +settings, data, nvs, 0x750000, 0x10000, \ No newline at end of file diff --git a/components/esp_tinyusb/test_apps/uvc/sdkconfig.defaults b/components/esp_tinyusb/test_apps/uvc/sdkconfig.defaults new file mode 100644 index 0000000..c57e9a1 --- /dev/null +++ b/components/esp_tinyusb/test_apps/uvc/sdkconfig.defaults @@ -0,0 +1,51 @@ +# +# USB Video Class (UVC) +# +CONFIG_TINYUSB_UVC_ENABLED=y +CONFIG_TINYUSB_DESC_UVC_STRING="Espressif UVC Camera" +# CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM is not set +CONFIG_TINYUSB_UVC_CAM1_FRAMERATE=15 +CONFIG_TINYUSB_UVC_CAM1_FRAMESIZE_WIDTH=640 +CONFIG_TINYUSB_UVC_CAM1_FRAMESIZE_HEIGHT=480 +CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1=y +# CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 is not set +# CONFIG_TINYUSB_UVC_CAM1_MULTI_FRAMESIZE is not set +# CONFIG_TINYUSB_UVC_CAM1_BULK_MODE is not set +CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_1=640 +CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_1=480 +CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_1=15 +CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_2=320 +CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_2=240 +CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_2=15 +CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_3=160 +CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_3=120 +CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_3=15 +CONFIG_TINYUSB_UVC_CAM1_TASK_PRIORITY=5 +CONFIG_TINYUSB_UVC_CAM1_TASK_CORE=-1 +CONFIG_TINYUSB_UVC_TINYUSB_TASK_PRIORITY=5 +CONFIG_TINYUSB_UVC_TINYUSB_TASK_CORE=-1 +# end of USB Video Class (UVC) + +# Partitions configuration, used by spiflash storage +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_MODE_PERF=y + +# On chips with USB Serial JTAG, disable secondary console which does not make sense when using console component +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y + +# Disable watchdogs, they'd get triggered during unity interactive menu +CONFIG_ESP_INT_WDT=n +CONFIG_ESP_TASK_WDT=n + +# Run-time checks of Heap and Stack +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y + +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/components/esp_tinyusb/test_apps/vendor/CMakeLists.txt b/components/esp_tinyusb/test_apps/vendor/CMakeLists.txt new file mode 100644 index 0000000..a600c6f --- /dev/null +++ b/components/esp_tinyusb/test_apps/vendor/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +project(test_app_vendor_specific) diff --git a/components/esp_tinyusb/test_apps/vendor/README.md b/components/esp_tinyusb/test_apps/vendor/README.md new file mode 100644 index 0000000..102c13c --- /dev/null +++ b/components/esp_tinyusb/test_apps/vendor/README.md @@ -0,0 +1,14 @@ +| Supported Targets | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | -------- | + +# Espressif's Additions to TinyUSB - Vendor specific device Test Application + +This directory contains Unity tests that validate Espressif-specific integration of TinyUSB. + +The tests focus on: + +- Creating a vendor specific device using esp_tinyusb and validating communication with the device using pyusb + +## Running the test locally on Linux host PC: + +- User needs to [set permissions](../README.md#set-root-permissions-for-low-level-access-to-usb-devices) to the USB device, to successfully run test app on Linux host PC diff --git a/components/esp_tinyusb/test_apps/vendor/main/CMakeLists.txt b/components/esp_tinyusb/test_apps/vendor/main/CMakeLists.txt new file mode 100644 index 0000000..e81e627 --- /dev/null +++ b/components/esp_tinyusb/test_apps/vendor/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES unity + WHOLE_ARCHIVE) diff --git a/components/esp_tinyusb/test_apps/vendor/main/idf_component.yml b/components/esp_tinyusb/test_apps/vendor/main/idf_component.yml new file mode 100644 index 0000000..b1cb5b5 --- /dev/null +++ b/components/esp_tinyusb/test_apps/vendor/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "*" + override_path: "../../../" diff --git a/components/esp_tinyusb/test_apps/vendor/main/test_app_main.c b/components/esp_tinyusb/test_apps/vendor/main/test_app_main.c new file mode 100644 index 0000000..cce83db --- /dev/null +++ b/components/esp_tinyusb/test_apps/vendor/main/test_app_main.c @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "unity_test_runner.h" + +void app_main(void) +{ + /* + _ _ _ + | | (_) | | + ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ + / _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \ + | __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) | + \___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/ + | |______ __/ | + |_|______| |___/ + _____ _____ _____ _____ + |_ _| ___/ ___|_ _| + | | | |__ \ `--. | | + | | | __| `--. \ | | + | | | |___/\__/ / | | + \_/ \____/\____/ \_/ + */ + + printf(" _ _ _ \n"); + printf(" | | (_) | | \n"); + printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n"); + printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n"); + printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n"); + printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n"); + printf(" | |______ __/ | \n"); + printf(" |_|______| |___/ \n"); + printf(" _____ _____ _____ _____ \n"); + printf("|_ _| ___/ ___|_ _| \n"); + printf(" | | | |__ \\ `--. | | \n"); + printf(" | | | __| `--. \\ | | \n"); + printf(" | | | |___/\\__/ / | | \n"); + printf(" \\_/ \\____/\\____/ \\_/ \n"); + + // We don't check memory leaks here because we cannot uninstall TinyUSB yet + unity_run_menu(); +} diff --git a/components/esp_tinyusb/test_apps/vendor/main/test_vendor.c b/components/esp_tinyusb/test_apps/vendor/main/test_vendor.c new file mode 100644 index 0000000..8c59e38 --- /dev/null +++ b/components/esp_tinyusb/test_apps/vendor/main/test_vendor.c @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" +#if SOC_USB_OTG_SUPPORTED + +#include +#include +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_err.h" + +#include "unity.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" + +static const char *TAG = "vendor_test"; + +#define TUSB_CFG_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_VENDOR * TUD_VENDOR_DESC_LEN) + +static const uint8_t test_fs_configuration_descriptor[] = { + TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_VENDOR, 0, TUSB_CFG_DESC_TOTAL_LEN, 0x00, 100), + TUD_VENDOR_DESCRIPTOR(0, 4, 0x02, 0x82, 64), +#if CFG_TUD_VENDOR > 1 + TUD_VENDOR_DESCRIPTOR(1, 4, 0x04, 0x84, 64), +#endif // CFG_TUD_VENDOR > 1 +}; + +#if (TUD_OPT_HIGH_SPEED) + +static const uint8_t test_hs_configuration_descriptor[] = { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_VENDOR, 0, TUSB_CFG_DESC_TOTAL_LEN, 0x00, 100), + TUD_VENDOR_DESCRIPTOR(0, 4, 0x02, 0x82, 512), +#if CFG_TUD_VENDOR > 1 + TUD_VENDOR_DESCRIPTOR(1, 4, 0x04, 0x84, 512), +#endif // CFG_TUD_VENDOR > 1 +}; + +static const tusb_desc_device_qualifier_t device_qualifier = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_VENDOR_SPECIFIC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0 +}; +#endif // TUD_OPT_HIGH_SPEED + +static const tusb_desc_device_t test_device_descriptor = { + .bLength = sizeof(test_device_descriptor), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_VENDOR_SPECIFIC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers + .idProduct = 0x4040, + .bcdDevice = 0x100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01 +}; + +char buffer_in[64]; +#if (TUSB_VERSION_MINOR >= 17) +void tud_vendor_rx_cb(uint8_t itf, uint8_t const *buffer, uint16_t bufsize) +#else +void tud_vendor_rx_cb(uint8_t itf) +#endif // TUSB_VERSION_MINOR +{ + ESP_LOGI(TAG, "tud_vendor_rx_cb(itf=%d)", itf); + int available = tud_vendor_n_available(itf); + int read = tud_vendor_n_read(itf, buffer_in, available); + ESP_LOGI(TAG, "actual read: %d. buffer message: %s", read, buffer_in); +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) +{ + // nothing to with DATA & ACK stage + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + // stall unknown request + return false; +} + +/** + * @brief TinyUSB Vendor specific testcase + */ +TEST_CASE("tinyusb_vendor", "[esp_tinyusb][vendor]") +{ + // Install TinyUSB driver + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + // Set descriptors + tusb_cfg.descriptor.device = &test_device_descriptor; + tusb_cfg.descriptor.full_speed_config = test_fs_configuration_descriptor; +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.qualifier = &device_qualifier; + tusb_cfg.descriptor.high_speed_config = test_hs_configuration_descriptor; +#endif // TUD_OPT_HIGH_SPEED + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); +} + +#endif diff --git a/components/esp_tinyusb/test_apps/vendor/pytest_vendor.py b/components/esp_tinyusb/test_apps/vendor/pytest_vendor.py new file mode 100644 index 0000000..e5aca80 --- /dev/null +++ b/components/esp_tinyusb/test_apps/vendor/pytest_vendor.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded_idf.dut import IdfDut +from pytest_embedded_idf.utils import idf_parametrize +from time import sleep + +# Mainly for a local run, as there is no error when pyusb is not installed and the pytest silently fails +try: + import usb.core + import usb.util +except ImportError as e: + raise RuntimeError("pyusb is not installed. Install it with: pip install pyusb") from e + + +def find_interface_by_index(device, interface_index: int) -> int: + ''' + Function to find the interface by index + ''' + for cfg in device: + for intf in cfg: + if intf.bInterfaceNumber == interface_index: + return intf + return None + + +def send_data_to_intf(VID: int, PID: int, interface_index: int) -> None: + ''' + Find a device, its interface and dual BULK endpoints + Send some data to it + ''' + # Find the USB device by VID and PID + dev = usb.core.find(idVendor=VID, idProduct=PID) + if dev is None: + raise ValueError("Device not found") + + # Find the interface by index + intf = find_interface_by_index(dev, interface_index) + if intf is None: + raise ValueError(f"Interface with index {interface_index} not found") + + try: + def ep_read(len): + try: + return ep_in.read(len, 100) + except usb.core.USBError: + return None + def ep_write(buf): + try: + ep_out.write(buf, 100) + except usb.core.USBError: + pass + + ep_in = usb.util.find_descriptor(intf, custom_match = \ + lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN) + + ep_out = usb.util.find_descriptor(intf, custom_match = \ + lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT) + + #print(ep_in) + #print(ep_out) + maximum_packet_size = ep_in.wMaxPacketSize + buf = "IF{}\n".format(interface_index).encode('utf-8') + ep_write(bytes(buf)) + + ep_read(maximum_packet_size) + + finally: + try: + usb.util.dispose_resources(dev) + except usb.core.USBError: + pass + +#@pytest.mark.usb_device Disable in CI, for now, not stable when running this test in Docker container +@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +def test_usb_device_vendor(dut: IdfDut) -> None: + ''' + Running the test locally: + 1. Build the test app for your DUT + 2. Connect you DUT to your test runner (local machine) with USB port and flashing port + 3. Run `pytest --target esp32s3` + + Important note: On Windows you must manually assign a driver the device, otherwise it will never be configured. + On Linux this is automatic + + Test procedure: + 1. Run the test on the DUT + 2. Expect 2 Vendor specific interfaces in the system + 3. Send some data to it, check log output + ''' + dut.run_all_single_board_cases(group='vendor') + + sleep(2) # Wait until the device is enumerated + + VID = 0x303A # Replace with your device's Vendor ID + PID = 0x4040 # Replace with your device's Product ID + + send_data_to_intf(VID, PID, 0) + dut.expect_exact('vendor_test: actual read: 4. buffer message: IF0') + send_data_to_intf(VID, PID, 1) + dut.expect_exact('vendor_test: actual read: 4. buffer message: IF1') diff --git a/components/esp_tinyusb/test_apps/vendor/sdkconfig.defaults b/components/esp_tinyusb/test_apps/vendor/sdkconfig.defaults new file mode 100644 index 0000000..2a4cccc --- /dev/null +++ b/components/esp_tinyusb/test_apps/vendor/sdkconfig.defaults @@ -0,0 +1,14 @@ +# Configure TinyUSB, it will be used to mock USB devices +CONFIG_TINYUSB_VENDOR_COUNT=2 + +# Disable watchdogs, they'd get triggered during unity interactive menu +# CONFIG_ESP_TASK_WDT_INIT is not set + +# Run-time checks of Heap and Stack +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y + +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/components/esp_tinyusb/test_apps/vendor/sdkconfig.defaults.esp32p4 b/components/esp_tinyusb/test_apps/vendor/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000..2dbcf83 --- /dev/null +++ b/components/esp_tinyusb/test_apps/vendor/sdkconfig.defaults.esp32p4 @@ -0,0 +1 @@ +CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y diff --git a/components/esp_tinyusb/tinyusb.c b/components/esp_tinyusb/tinyusb.c new file mode 100644 index 0000000..6c25731 --- /dev/null +++ b/components/esp_tinyusb/tinyusb.c @@ -0,0 +1,232 @@ +/* + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_private/usb_phy.h" +#include "tinyusb.h" +#include "tinyusb_task.h" +#include "tusb.h" + +#if (CONFIG_TINYUSB_MSC_ENABLED) +#include "tinyusb_msc.h" +#include "msc_storage.h" +#endif // CONFIG_TINYUSB_MSC_ENABLED + +const static char *TAG = "TinyUSB"; + +/** + * @brief TinyUSB context + */ +typedef struct { + tinyusb_port_t port; /*!< USB Peripheral hardware port number. Available when hardware has several available peripherals. */ + usb_phy_handle_t phy_hdl; /*!< USB PHY handle */ + tinyusb_event_cb_t event_cb; /*!< Callback function that will be called when USB events occur. */ + void *event_arg; /*!< Pointer to the argument passed to the callback */ + bool remote_wakeup_en; /*!< Remote wakeup enabled flag */ +} tinyusb_ctx_t; + +static tinyusb_ctx_t s_ctx; // TinyUSB context + +// ================================================================================== +// ============================= TinyUSB Callbacks ================================== +// ================================================================================== + +/** + * @brief Callback function invoked when device is mounted (configured) + * + * This function is called by TinyUSB stack when: + * + * - SetConfiguration(n) is called by the host, where n is the configuration number and not zero. + * + * @note + * For Win-based Hosts: SetConfiguration(n) request is present only with available Class in Device Descriptor. + */ +void tud_mount_cb(void) +{ +#if (CONFIG_TINYUSB_MSC_ENABLED) + msc_storage_mount_to_usb(); +#endif // CONFIG_TINYUSB_MSC_ENABLED + tinyusb_event_t event = { + .id = TINYUSB_EVENT_ATTACHED, + .rhport = s_ctx.port, + }; + + if (s_ctx.event_cb) { + s_ctx.event_cb(&event, s_ctx.event_arg); + } +} + +/** + * @brief Callback function invoked when device is unmounted + * + * This function is called by TinyUSB stack when: + * + * - SetConfiguration(0) is called by the host. + * - Device is disconnected (DCD_EVENT_UNPLUGGED) from the host. + */ +void tud_umount_cb(void) +{ +#if (CONFIG_TINYUSB_MSC_ENABLED) + msc_storage_mount_to_app(); +#endif // CONFIG_TINYUSB_MSC_ENABLED + tinyusb_event_t event = { + .id = TINYUSB_EVENT_DETACHED, + .rhport = s_ctx.port, + }; + + if (s_ctx.event_cb) { + s_ctx.event_cb(&event, s_ctx.event_arg); + } +} + +#ifdef CONFIG_TINYUSB_SUSPEND_CALLBACK +/** + * @brief Callback function invoked when device is suspended + * + * This function is called by TinyUSB stack when: + * + * - Host suspends the root port + * + * @param[in] remote_wakeup_en Remote wakeup is currently enabled/disabled on the device + */ +void tud_suspend_cb(bool remote_wakeup_en) +{ + tinyusb_event_t event = { + .id = TINYUSB_EVENT_SUSPENDED, + .rhport = s_ctx.port, + .suspended = { + .remote_wakeup = remote_wakeup_en, + }, + }; + + // Save the remote wakeup enabled flag + s_ctx.remote_wakeup_en = remote_wakeup_en; + + if (s_ctx.event_cb) { + s_ctx.event_cb(&event, s_ctx.event_arg); + } +} +#endif // CONFIG_TINYUSB_SUSPEND_CALLBACK + +#ifdef CONFIG_TINYUSB_RESUME_CALLBACK +/** + * @brief Callback function invoked when device is resumed + * + * This function is called by TinyUSB stack when: + * + * - Host resumes the the root port + */ +void tud_resume_cb(void) +{ + tinyusb_event_t event = { + .id = TINYUSB_EVENT_RESUMED, + .rhport = s_ctx.port, + }; + + if (s_ctx.event_cb) { + s_ctx.event_cb(&event, s_ctx.event_arg); + } +} +#endif // CONFIG_TINYUSB_RESUME_CALLBACK + +// ================================================================================== +// ============================= ESP TinyUSB Driver ================================= +// ================================================================================== + +/** + * @brief Check the TinyUSB configuration + */ +static esp_err_t tinyusb_check_config(const tinyusb_config_t *config) +{ + ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Config can't be NULL"); + ESP_RETURN_ON_FALSE(config->port < TINYUSB_PORT_MAX, ESP_ERR_INVALID_ARG, TAG, "Port number should be supported by the hardware"); +#if (CONFIG_IDF_TARGET_ESP32P4) +#ifndef USB_PHY_SUPPORTS_P4_OTG11 + ESP_RETURN_ON_FALSE(config->port != TINYUSB_PORT_0, ESP_ERR_INVALID_ARG, TAG, "USB PHY support for OTG1.1 has not been implemented, please update your esp-idf"); +#endif // ESP-IDF supports OTG1.1 peripheral +#endif // CONFIG_IDF_TARGET_ESP32P4 + return ESP_OK; +} + +esp_err_t tinyusb_driver_install(const tinyusb_config_t *config) +{ + ESP_RETURN_ON_ERROR(tinyusb_check_config(config), TAG, "TinyUSB configuration check failed"); + ESP_RETURN_ON_ERROR(tinyusb_task_check_config(&config->task), TAG, "TinyUSB task configuration check failed"); + + esp_err_t ret; + usb_phy_handle_t phy_hdl = NULL; + if (!config->phy.skip_setup) { + // Configure USB PHY + usb_phy_config_t phy_conf = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_DEVICE, + .otg_speed = USB_PHY_SPEED_FULL, + }; + +#if (SOC_USB_OTG_PERIPH_NUM > 1) + if (config->port == TINYUSB_PORT_HIGH_SPEED_0) { + // Default PHY for OTG2.0 is UTMI + phy_conf.target = USB_PHY_TARGET_UTMI; + phy_conf.otg_speed = USB_PHY_SPEED_HIGH; + } +#endif // (SOC_USB_OTG_PERIPH_NUM > 1) + + // OTG IOs config + const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->phy.vbus_monitor_io); + if (config->phy.self_powered) { + phy_conf.otg_io_conf = &otg_io_conf; + } + ESP_RETURN_ON_ERROR(usb_new_phy(&phy_conf, &phy_hdl), TAG, "Install USB PHY failed"); + } + // Init TinyUSB stack in task + ESP_GOTO_ON_ERROR(tinyusb_task_start(config->port, &config->task, &config->descriptor), del_phy, TAG, "Init TinyUSB task failed"); + + s_ctx.port = config->port; // Save the port number + s_ctx.phy_hdl = phy_hdl; // Save the PHY handle for uninstallation + s_ctx.event_cb = config->event_cb; // Save the event callback + s_ctx.event_arg = config->event_arg; // Save the event callback argument + s_ctx.remote_wakeup_en = false; // Remote wakeup is disabled by default + + ESP_LOGI(TAG, "TinyUSB Driver installed on port %d", config->port); + return ESP_OK; + +del_phy: + if (!config->phy.skip_setup) { + usb_del_phy(phy_hdl); + } + return ret; +} + +esp_err_t tinyusb_driver_uninstall(void) +{ + ESP_RETURN_ON_ERROR(tinyusb_task_stop(), TAG, "Deinit TinyUSB task failed"); + if (s_ctx.phy_hdl) { + ESP_RETURN_ON_ERROR(usb_del_phy(s_ctx.phy_hdl), TAG, "Unable to delete PHY"); + s_ctx.phy_hdl = NULL; + } + return ESP_OK; +} + +esp_err_t tinyusb_remote_wakeup(void) +{ + // Check if the remote wakeup flag was set by the esp_tinyusb's suspend callback + // In case of user-defined suspend callback, user manages remote wakeup capability on it's own +#ifdef CONFIG_TINYUSB_SUSPEND_CALLBACK + ESP_RETURN_ON_FALSE(s_ctx.remote_wakeup_en, ESP_ERR_INVALID_STATE, TAG, "Remote wakeup is not enabled by the host"); +#endif // CONFIG_TINYUSB_SUSPEND_CALLBACK + + ESP_RETURN_ON_FALSE(tud_remote_wakeup(), ESP_FAIL, TAG, "Remote wakeup request failed"); + +#ifdef CONFIG_TINYUSB_SUSPEND_CALLBACK + s_ctx.remote_wakeup_en = false; // Remote wakeup can be used only once, disable it until next suspend +#endif // CONFIG_TINYUSB_SUSPEND_CALLBACK + + return ESP_OK; +} diff --git a/components/esp_tinyusb/tinyusb_cdc_acm.c b/components/esp_tinyusb/tinyusb_cdc_acm.c new file mode 100644 index 0000000..5d93034 --- /dev/null +++ b/components/esp_tinyusb/tinyusb_cdc_acm.c @@ -0,0 +1,362 @@ +/* + * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "tusb.h" +#include "tinyusb_cdc_acm.h" +#include "cdc.h" +#include "sdkconfig.h" + +#ifndef MIN +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#endif + +#if (CFG_TUD_CDC_EP_BUFSIZE > CFG_TUD_CDC_RX_BUFSIZE) +#warning "CDC EP buffer size is larger than RX buffer size. Receive path will not work correctly. Use this configuration only for transmit-only devices." +#endif + +// CDC-ACM spinlock +static portMUX_TYPE cdc_acm_lock = portMUX_INITIALIZER_UNLOCKED; +#define CDC_ACM_ENTER_CRITICAL() portENTER_CRITICAL(&cdc_acm_lock) +#define CDC_ACM_EXIT_CRITICAL() portEXIT_CRITICAL(&cdc_acm_lock) + +typedef struct { + tusb_cdcacm_callback_t callback_rx; + tusb_cdcacm_callback_t callback_rx_wanted_char; + tusb_cdcacm_callback_t callback_line_state_changed; + tusb_cdcacm_callback_t callback_line_coding_changed; +} esp_tusb_cdcacm_t; /*!< CDC_ACM object */ + +static const char *TAG = "tusb_cdc_acm"; + +static inline esp_tusb_cdcacm_t *get_acm(tinyusb_cdcacm_itf_t itf) +{ + esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf); + if (cdc_inst == NULL) { + return (esp_tusb_cdcacm_t *)NULL; + } + return (esp_tusb_cdcacm_t *)(cdc_inst->subclass_obj); +} + + +/* TinyUSB callbacks + ********************************************************************* */ + +/* Invoked by cdc interface when line state changed e.g connected/disconnected */ +void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (dtr && rts) { // connected + if (acm != NULL) { + ESP_LOGV(TAG, "Host connected to CDC no.%d.", itf); + } else { + ESP_LOGW(TAG, "Host is connected to CDC no.%d, but it is not initialized. Initialize it using `tinyusb_cdc_init`.", itf); + return; + } + } else { // disconnected + if (acm != NULL) { + ESP_LOGV(TAG, "Serial device is ready to connect to CDC no.%d", itf); + } else { + return; + } + } + if (acm) { + CDC_ACM_ENTER_CRITICAL(); + tusb_cdcacm_callback_t cb = acm->callback_line_state_changed; + CDC_ACM_EXIT_CRITICAL(); + if (cb) { + cdcacm_event_t event = { + .type = CDC_EVENT_LINE_STATE_CHANGED, + .line_state_changed_data = { + .dtr = dtr, + .rts = rts + } + }; + cb(itf, &event); + } + } +} + +/* Invoked when CDC interface received data from host */ +void tud_cdc_rx_cb(uint8_t itf) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (acm) { + CDC_ACM_ENTER_CRITICAL(); + tusb_cdcacm_callback_t cb = acm->callback_rx; + CDC_ACM_EXIT_CRITICAL(); + if (cb) { + cdcacm_event_t event = { + .type = CDC_EVENT_RX + }; + cb(itf, &event); + } + } +} + +// Invoked when line coding is change via SET_LINE_CODING +void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (acm) { + CDC_ACM_ENTER_CRITICAL(); + tusb_cdcacm_callback_t cb = acm->callback_line_coding_changed; + CDC_ACM_EXIT_CRITICAL(); + if (cb) { + cdcacm_event_t event = { + .type = CDC_EVENT_LINE_CODING_CHANGED, + .line_coding_changed_data = { + .p_line_coding = p_line_coding, + } + }; + cb(itf, &event); + } + } +} + +// Invoked when received `wanted_char` +void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (acm) { + CDC_ACM_ENTER_CRITICAL(); + tusb_cdcacm_callback_t cb = acm->callback_rx_wanted_char; + CDC_ACM_EXIT_CRITICAL(); + if (cb) { + cdcacm_event_t event = { + .type = CDC_EVENT_RX_WANTED_CHAR, + .rx_wanted_char_data = { + .wanted_char = wanted_char, + } + }; + cb(itf, &event); + } + } +} + +esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf, + cdcacm_event_type_t event_type, + tusb_cdcacm_callback_t callback) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (acm) { + switch (event_type) { + case CDC_EVENT_RX: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_rx = callback; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_RX_WANTED_CHAR: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_rx_wanted_char = callback; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_LINE_STATE_CHANGED: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_line_state_changed = callback; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_LINE_CODING_CHANGED: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_line_coding_changed = callback; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + default: + ESP_LOGE(TAG, "Wrong event type"); + return ESP_ERR_INVALID_ARG; + } + } else { + ESP_LOGE(TAG, "CDC-ACM is not initialized"); + return ESP_ERR_INVALID_STATE; + } +} + +esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf, + cdcacm_event_type_t event_type) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (!acm) { + ESP_LOGE(TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization"); + return ESP_ERR_INVALID_STATE; + } + switch (event_type) { + case CDC_EVENT_RX: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_rx = NULL; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_RX_WANTED_CHAR: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_rx_wanted_char = NULL; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_LINE_STATE_CHANGED: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_line_state_changed = NULL; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_LINE_CODING_CHANGED: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_line_coding_changed = NULL; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + default: + ESP_LOGE(TAG, "Wrong event type"); + return ESP_ERR_INVALID_ARG; + } +} + +/*********************************************************************** TinyUSB callbacks*/ +/* CDC-ACM + ********************************************************************* */ + +esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + ESP_RETURN_ON_FALSE(acm, ESP_ERR_INVALID_STATE, TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization"); + + if (tud_cdc_n_available(itf) == 0) { + *rx_data_size = 0; + } else { + *rx_data_size = tud_cdc_n_read(itf, out_buf, out_buf_sz); + } + return ESP_OK; +} + +size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch) +{ + if (!get_acm(itf)) { // non-initialized + return 0; + } + return tud_cdc_n_write_char(itf, ch); +} + +size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, const uint8_t *in_buf, size_t in_size) +{ + if (!get_acm(itf)) { // non-initialized + return 0; + } + const uint32_t size_available = tud_cdc_n_write_available(itf); + return tud_cdc_n_write(itf, in_buf, MIN(in_size, size_available)); +} + +static uint32_t tud_cdc_n_write_occupied(tinyusb_cdcacm_itf_t itf) +{ + return CFG_TUD_CDC_TX_BUFSIZE - tud_cdc_n_write_available(itf); +} + +esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks) +{ + if (!get_acm(itf)) { // non-initialized + return ESP_FAIL; + } + + if (!timeout_ticks) { // if no timeout - nonblocking mode + // It might take some time until TinyUSB flushes the endpoint + // Since this call is non-blocking, we don't wait for flush finished, + // We only inform the user by returning ESP_ERR_NOT_FINISHED + tud_cdc_n_write_flush(itf); + if (tud_cdc_n_write_occupied(itf)) { + return ESP_ERR_NOT_FINISHED; + } + } else { // trying during the timeout + uint32_t ticks_start = xTaskGetTickCount(); + uint32_t ticks_now = ticks_start; + while (1) { // loop until success or until the time runs out + ticks_now = xTaskGetTickCount(); + tud_cdc_n_write_flush(itf); + if (tud_cdc_n_write_occupied(itf) == 0) { + break; // All data flushed + } + if ( (ticks_now - ticks_start) > timeout_ticks ) { // Time is up + ESP_LOGW(TAG, "Flush failed"); + return ESP_ERR_TIMEOUT; + } + vTaskDelay(1); + } + } + return ESP_OK; +} + +static esp_err_t alloc_obj(tinyusb_cdcacm_itf_t itf) +{ + esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf); + if (cdc_inst == NULL) { + return ESP_FAIL; + } + cdc_inst->subclass_obj = calloc(1, sizeof(esp_tusb_cdcacm_t)); + if (cdc_inst->subclass_obj == NULL) { + return ESP_FAIL; + } + return ESP_OK; +} + +static esp_err_t obj_free(tinyusb_cdcacm_itf_t itf) +{ + esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf); + if (cdc_inst == NULL || cdc_inst->subclass_obj == NULL) { + return ESP_FAIL; + } + free(cdc_inst->subclass_obj); + return ESP_OK; +} + +esp_err_t tinyusb_cdcacm_init(const tinyusb_config_cdcacm_t *cfg) +{ + esp_err_t ret = ESP_OK; + int itf = (int)cfg->cdc_port; + /* Creating a CDC object */ + const tinyusb_config_cdc_t cdc_cfg = { + .cdc_class = TUSB_CLASS_CDC, + .cdc_subclass.comm_subclass = CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL + }; + + ESP_RETURN_ON_ERROR(tinyusb_cdc_init(itf, &cdc_cfg), TAG, "tinyusb_cdc_init failed"); + ESP_GOTO_ON_ERROR(alloc_obj(itf), fail, TAG, "alloc_obj failed"); + + /* Callbacks setting up*/ + if (cfg->callback_rx) { + tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX, cfg->callback_rx); + } + if (cfg->callback_rx_wanted_char) { + tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX_WANTED_CHAR, cfg->callback_rx_wanted_char); + } + if (cfg->callback_line_state_changed) { + tinyusb_cdcacm_register_callback(itf, CDC_EVENT_LINE_STATE_CHANGED, cfg->callback_line_state_changed); + } + if (cfg->callback_line_coding_changed) { + tinyusb_cdcacm_register_callback( itf, CDC_EVENT_LINE_CODING_CHANGED, cfg->callback_line_coding_changed); + } + + return ESP_OK; +fail: + tinyusb_cdc_deinit(itf); + return ret; +} + +esp_err_t tinyusb_cdcacm_deinit(int itf) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_ERROR(obj_free(itf), TAG, "obj_free failed"); + ESP_RETURN_ON_ERROR(tinyusb_cdc_deinit(itf), TAG, "tinyusb_cdc_deinit failed"); + return ret; +} + +bool tinyusb_cdcacm_initialized(tinyusb_cdcacm_itf_t itf) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (acm) { + return true; + } else { + return false; + } +} +/*********************************************************************** CDC-ACM*/ diff --git a/components/esp_tinyusb/tinyusb_console.c b/components/esp_tinyusb/tinyusb_console.c new file mode 100644 index 0000000..e764d43 --- /dev/null +++ b/components/esp_tinyusb/tinyusb_console.c @@ -0,0 +1,115 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include +#include +#include "esp_log.h" +#include "cdc.h" +#include "tinyusb_console.h" +#include "tinyusb.h" +#include "vfs_tinyusb.h" +#include "esp_check.h" + +#define STRINGIFY(s) STRINGIFY2(s) +#define STRINGIFY2(s) #s + +static const char *TAG = "tusb_console"; + +typedef struct { + FILE *in; + FILE *out; + FILE *err; +} console_handle_t; + +static console_handle_t con; + + +/** + * @brief Reopen standard streams using a new path + * + * @param f_in - pointer to a pointer holding a file for in or NULL to don't change stdin + * @param f_out - pointer to a pointer holding a file for out or NULL to don't change stdout + * @param f_err - pointer to a pointer holding a file for err or NULL to don't change stderr + * @param path - mount point + * @return esp_err_t ESP_FAIL or ESP_OK + */ +static esp_err_t redirect_std_streams_to(FILE **f_in, FILE **f_out, FILE **f_err, const char *path) +{ + if (f_in) { + *f_in = freopen(path, "r", stdin); + if (*f_in == NULL) { + ESP_LOGE(TAG, "Failed to reopen in!"); + return ESP_FAIL; + } + } + if (f_out) { + *f_out = freopen(path, "w", stdout); + if (*f_out == NULL) { + ESP_LOGE(TAG, "Failed to reopen out!"); + return ESP_FAIL; + } + } + if (f_err) { + *f_err = freopen(path, "w", stderr); + if (*f_err == NULL) { + ESP_LOGE(TAG, "Failed to reopen err!"); + return ESP_FAIL; + } + } + + return ESP_OK; +} + +/** + * @brief Restore output to default + * + * @param f_in - pointer to a pointer of an in file updated with `redirect_std_streams_to` or NULL to don't change stdin + * @param f_out - pointer to a pointer of an out file updated with `redirect_std_streams_to` or NULL to don't change stdout + * @param f_err - pointer to a pointer of an err file updated with `redirect_std_streams_to` or NULL to don't change stderr + * @return esp_err_t ESP_FAIL or ESP_OK + */ +static esp_err_t restore_std_streams(FILE **f_in, FILE **f_out, FILE **f_err) +{ + const char *default_uart_dev = "/dev/uart/" STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM); + if (f_in) { + stdin = freopen(default_uart_dev, "r", *f_in); + if (stdin == NULL) { + ESP_LOGE(TAG, "Failed to reopen stdin!"); + return ESP_FAIL; + } + } + if (f_out) { + stdout = freopen(default_uart_dev, "w", *f_out); + if (stdout == NULL) { + ESP_LOGE(TAG, "Failed to reopen stdout!"); + return ESP_FAIL; + } + } + if (f_err) { + stderr = freopen(default_uart_dev, "w", *f_err); + if (stderr == NULL) { + ESP_LOGE(TAG, "Failed to reopen stderr!"); + return ESP_FAIL; + } + } + return ESP_OK; +} + +esp_err_t tinyusb_console_init(int cdc_intf) +{ + /* Registering TUSB at VFS */ + ESP_RETURN_ON_ERROR(esp_vfs_tusb_cdc_register(cdc_intf, NULL), TAG, ""); + ESP_RETURN_ON_ERROR(redirect_std_streams_to(&con.in, &con.out, &con.err, VFS_TUSB_PATH_DEFAULT), TAG, "Failed to redirect STD streams"); + return ESP_OK; +} + +esp_err_t tinyusb_console_deinit(int cdc_intf) +{ + ESP_RETURN_ON_ERROR(restore_std_streams(&con.in, &con.out, &con.err), TAG, "Failed to restore STD streams"); + esp_vfs_tusb_cdc_unregister(NULL); + return ESP_OK; +} diff --git a/components/esp_tinyusb/tinyusb_msc.c b/components/esp_tinyusb/tinyusb_msc.c new file mode 100644 index 0000000..5f9c3a4 --- /dev/null +++ b/components/esp_tinyusb/tinyusb_msc.c @@ -0,0 +1,1327 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_vfs_fat.h" +#include "esp_partition.h" +#include "esp_memory_utils.h" +#include "soc/soc_caps.h" +#include "sdkconfig.h" +#include "vfs_fat_internal.h" +#include "tinyusb.h" +#include "device/usbd_pvt.h" +#include "class/msc/msc_device.h" + +#include "storage_spiflash.h" +#include "msc_storage.h" +#include "tinyusb_msc.h" + +#if (SOC_SDMMC_HOST_SUPPORTED) +#include "storage_sdmmc.h" +#include "diskio_sdmmc.h" +#endif // SOC_SDMMC_HOST_SUPPORTED + +static const char *TAG = "tinyusb_msc_storage"; + +#define MSC_STORAGE_MEM_ALIGN 4 +#define MSC_STORAGE_BUFFER_SIZE CONFIG_TINYUSB_MSC_BUFSIZE /*!< Size of the buffer, configured via menuconfig (MSC FIFO size) */ + +#if ((MSC_STORAGE_BUFFER_SIZE) % MSC_STORAGE_MEM_ALIGN != 0) +#error "CONFIG_TINYUSB_MSC_BUFSIZE must be divisible by MSC_STORAGE_MEM_ALIGN. Adjust your configuration (MSC FIFO size) in menuconfig." +#endif + +#define TINYUSB_MSC_STORAGE_MAX_LUNS 2 /*!< Maximum number of LUNs supported by TinyUSB MSC storage. Dafult value is 2 */ +#define TINYUSB_DEFAULT_BASE_PATH CONFIG_TINYUSB_MSC_MOUNT_PATH /*!< Default base path for the filesystem, configured via menuconfig */ + +/** + * @brief Structure representing a single write buffer for MSC operations. + */ +typedef struct { + uint8_t data_buffer[MSC_STORAGE_BUFFER_SIZE]; /*!< Buffer to store write data. The size is defined by MSC_STORAGE_BUFFER_SIZE. */ + uint8_t lun; /*!< Logical Unit Number (LUN) for the current write operation. */ + uint32_t lba; /*!< Logical Block Address for the current WRITE10 operation. */ + uint32_t offset; /*!< Offset within the specified LBA for the current write operation. */ + uint32_t bufsize; /*!< Number of bytes to be written in this operation. */ +} msc_storage_buffer_t; + +/** + * @brief Handle for TinyUSB MSC storage interface. + * + * This structure holds metadata and function pointers required to + * manage the underlying storage medium (SPI flash, SDMMC). + */ +typedef struct { + // Storage related + const storage_medium_t *medium; /*!< Pointer to the storage medium. */ + tinyusb_msc_mount_point_t mount_point; /*!< Current mount point type (application or USB host). */ + // Optimisation purpose + uint32_t sector_count; /*!< Total number of sectors in the storage medium. */ + uint32_t sector_size; /*!< Size of a single sector in bytes. */ + // FS related + struct { + const char *base_path; /*!< Base path where the filesystem is mounted. */ + int max_files; /*!< Maximum number of files that can be open simultaneously. */ + bool do_not_format; /*!< If true, do not format the drive if filesystem is not present. */ + BYTE format_flags; /*!< Flags for formatting the filesystem, can be 0 to use default settings. */ + } fat_fs; + // Buffer for storage operations + msc_storage_buffer_t storage_buffer; /*!< Buffer for storing data during write operations. */ + uint32_t deffered_writes; /*!< Number of deferred writes pending in the buffer. */ + SemaphoreHandle_t mux_lock; /**< Mutex for storage operations */ +} tinyusb_msc_storage_s; + +typedef tinyusb_msc_storage_s msc_storage_obj_t; + +typedef struct { + struct { + msc_storage_obj_t *storage[TINYUSB_MSC_STORAGE_MAX_LUNS]; /*!< Storage objects */ + uint8_t lun_count; /*!< Number of logical units (LUNs) supported by the storage. */ + tusb_msc_callback_t event_cb; /*!< Callback for mount changed events. */ + void *event_arg; /*!< Argument to pass to the event callback. */ + } dynamic; + + struct { + union { + struct { + // User config - 16 bits + uint32_t auto_mount_off: 1; /**< If true, turn off automatically mount on USB host connection */ + uint32_t user_reserved15: 15; /**< Reserved for future use */ + // Internal config - 16 bits + uint32_t internal_reserved15: 15; /**< Reserved for intenral use */ + uint32_t internally_installed: 1; /**< Driver was internally installed. Uninstall driver on last storage removal */ + }; + uint32_t val; /**< MSC Driver configuration flag value */ + } flags; + } constant; +} tinyusb_msc_driver_t; + +static tinyusb_msc_driver_t *p_msc_driver; + +static portMUX_TYPE msc_lock = portMUX_INITIALIZER_UNLOCKED; +#define MSC_ENTER_CRITICAL() portENTER_CRITICAL(&msc_lock) +#define MSC_EXIT_CRITICAL() portEXIT_CRITICAL(&msc_lock) + +#define MSC_GOTO_ON_FALSE_CRITICAL(cond, err) \ + do { \ + if(!(cond)) { \ + MSC_EXIT_CRITICAL(); \ + ret = err; \ + goto fail; \ + } \ + } while(0) + +#define MSC_CHECK_ON_CRITICAL(cond, err) \ + do { \ + if(!(cond)) { \ + MSC_EXIT_CRITICAL(); \ + return err; \ + } \ + } while(0) + +// +// ========================== TinyUSB MSC Storage Event Handling ================================= +// + +static inline void tinyusb_event_cb(msc_storage_obj_t *storage, tinyusb_msc_event_id_t event_id) +{ + assert(p_msc_driver != NULL); + assert(storage != NULL); + + MSC_ENTER_CRITICAL(); + tusb_msc_callback_t cb = p_msc_driver->dynamic.event_cb; + void *cb_arg = p_msc_driver->dynamic.event_arg; + MSC_EXIT_CRITICAL(); + + tinyusb_msc_event_t event = { + .id = event_id, + .mount_point = storage->mount_point, + }; + cb((tinyusb_msc_storage_handle_t)storage, &event, cb_arg); +} + +// +// ========================== TinyUSB MSC Storage Operations ================================= +// + +/** + * @brief Get the storage object by LUN + * This function retrieves the storage object associated with the specified LUN. + * + * @note This function must be called from a critical section. + * + * @param[in] lun The logical unit number (LUN) to retrieve the storage for. + * @param[out] storage Pointer to the storage object pointer that will be set. + * @return + * - true if storage object is found for the specified LUN and not NULL, false otherwise. + */ +static inline bool _msc_storage_get_by_lun(uint8_t lun, msc_storage_obj_t **storage) +{ + if (p_msc_driver == NULL) { + return false; + } + + if ((lun < TINYUSB_MSC_STORAGE_MAX_LUNS) && + (p_msc_driver->dynamic.storage[lun] != NULL)) { + *storage = p_msc_driver->dynamic.storage[lun]; + return true; + } + return false; +} + +/** + * @brief Map a storage object to a specific LUN + * This function associates a storage object with a logical unit number (LUN). + * + * @note This function must be called from a critical section. + * + * Storage should have the medium initialized before calling this function. + * + * @param[in] storage Pointer to the storage object to be mapped. + * @return + * - true if storage is successfully mapped to a LUN, false otherwise. + */ +static inline bool _msc_storage_map_to_lun(msc_storage_obj_t *storage) +{ + if (storage == NULL || storage->medium == NULL) { + return false; + } + + if (p_msc_driver->dynamic.lun_count >= TINYUSB_MSC_STORAGE_MAX_LUNS) { + return false; + } + + p_msc_driver->dynamic.storage[p_msc_driver->dynamic.lun_count] = storage; + p_msc_driver->dynamic.lun_count++; + return true; +} + +/** + * @brief Unmap a storage object from a specific LUN + * This function disassociates a storage object from a logical unit number (LUN). + * + * @note This function must be called from a critical section. + * + * @param[in] lun The logical unit number (LUN) to unmap the storage from. + * @return + * - true if storage is successfully unmapped from a LUN, false otherwise. + */ +static inline bool _msc_storage_unmap_from_lun(msc_storage_obj_t *storage) +{ + if (storage == NULL) { + return false; + } + + for (uint8_t i = 0; i < TINYUSB_MSC_STORAGE_MAX_LUNS; i++) { + if (p_msc_driver->dynamic.storage[i] == storage) { + p_msc_driver->dynamic.storage[i] = NULL; + p_msc_driver->dynamic.lun_count--; + return true; + } + } + return false; +} + +/** + * @brief Read a sector from the storage medium + * + * This function reads a sector from the storage medium associated with the specified LUN. + * + * @param[in] lun The logical unit number (LUN) to read from. + * @param[in] lba Logical Block Address of the sector to read. + * @param[in] offset Offset within the sector to read from. + * @param[in] size Number of bytes to read. + * @param[out] dest Pointer to the destination buffer where the read data will be stored. + * + * @return + * - ESP_OK: Read operation successful + * - ESP_ERR_NOT_FOUND: Storage not found for the specified LUN + */ +static inline esp_err_t msc_storage_read_sector(uint8_t lun, uint32_t lba, uint32_t offset, size_t size, void *dest) +{ + msc_storage_obj_t *storage = NULL; + esp_err_t ret; + + MSC_ENTER_CRITICAL(); + bool found = _msc_storage_get_by_lun(lun, &storage); + MSC_EXIT_CRITICAL(); + + if (!found || storage == NULL) { + ESP_LOGE(TAG, "Storage not found for LUN %d", lun); + return ESP_ERR_NOT_FOUND; + } + // Otherwise, take the lock and proceed with the read + xSemaphoreTake(storage->mux_lock, portMAX_DELAY); + ret = storage->medium->read(lba, offset, size, dest); + xSemaphoreGive(storage->mux_lock); + return ret; +} + +/** + * @brief Write a sector to the storage medium + * + * This function writes a sector to the storage medium associated with the specified LUN. + * + * @param[in] lun The logical unit number (LUN) to write to. + * @param[in] lba Logical Block Address of the sector to write to. + * @param[in] offset Offset within the sector to write to. + * @param[in] size Number of bytes to write. + * @param[in] src Pointer to the source buffer containing the data to write. + * + * @return + * - ESP_OK: Write operation successful + * - ESP_ERR_NOT_FOUND: Storage not found for the specified LUN + */ +static inline esp_err_t msc_storage_write_sector(uint8_t lun, uint32_t lba, uint32_t offset, size_t size, const void *src) +{ + msc_storage_obj_t *storage = NULL; + esp_err_t ret; + + MSC_ENTER_CRITICAL(); + bool found = _msc_storage_get_by_lun(lun, &storage); + MSC_EXIT_CRITICAL(); + + if (!found || storage == NULL) { + ESP_LOGE(TAG, "Storage not found for LUN %d", lun); + return ESP_ERR_NOT_FOUND; + } + // Otherwise, take the lock and proceed with the write + xSemaphoreTake(storage->mux_lock, portMAX_DELAY); + ret = storage->medium->write(lba, offset, size, src); + xSemaphoreGive(storage->mux_lock); + return ret; +} + +/** + * @brief Handles deferred USB MSC write operations. + * + * This function is invoked via TinyUSB's deferred execution mechanism to perform + * write operations to the underlying storage. It writes data from the + * `storage_buffer` stored within the `s_storage_handle`. + * + * @param param Pointer to the storage object containing the write parameters. + */ +static void tusb_write_func(void *param) +{ + assert(param); // Ensure storage is not NULL + msc_storage_obj_t *storage = (msc_storage_obj_t *)param; + + esp_err_t err = msc_storage_write_sector( + storage->storage_buffer.lun, + storage->storage_buffer.lba, + storage->storage_buffer.offset, + storage->storage_buffer.bufsize, + (const void *)storage->storage_buffer.data_buffer + ); + + // Decrement the deferred writes counter + MSC_ENTER_CRITICAL(); + assert(storage->deffered_writes > 0); // Ensure there are deferred writes pending + storage->deffered_writes--; + MSC_EXIT_CRITICAL(); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Write failed, error=0x%x", err); + } +} + +/** + * @brief Write a sector to the storage medium using deferred execution. + * + * This function copies the data to be written into an internal buffer and + * defers the actual write operation to be executed in the TinyUSB task context. + * + * @param[in] lun The logical unit number (LUN) to write to. + * @param[in] lba Logical Block Address of the sector to write to. + * @param[in] offset Offset within the sector to write to. + * @param[in] size Number of bytes to write. + * @param[in] src Pointer to the source buffer containing the data to write. + * + * @return + * - ESP_OK: Write operation successfully deferred + * - ESP_ERR_NOT_FOUND: Storage not found for the specified LUN + * - ESP_ERR_INVALID_SIZE: Address calculation overflow for SPI Flash storage medium + */ +static inline esp_err_t msc_storage_write_sector_deferred(uint8_t lun, uint32_t lba, uint32_t offset, size_t size, const void *src) +{ + msc_storage_obj_t *storage = NULL; + + MSC_ENTER_CRITICAL(); + bool found = _msc_storage_get_by_lun(lun, &storage); + MSC_EXIT_CRITICAL(); + + if (!found || storage == NULL) { + ESP_LOGE(TAG, "LUN %d is not mapped to any storage", lun); + return ESP_ERR_NOT_FOUND; + } + + // As we defer the write operation to the TinyUSB task, we need to ensure that + // the address does not overflow for SPI Flash storage medium + if (storage->medium->type == STORAGE_MEDIUM_TYPE_SPIFLASH) { + size_t addr = 0; // Address of the data to be read, relative to the beginning of the partition. + size_t temp = 0; + size_t sector_size = storage->sector_size; + ESP_RETURN_ON_FALSE(!__builtin_umul_overflow(lba, sector_size, &temp), ESP_ERR_INVALID_SIZE, TAG, "overflow lba %lu sector_size %u", lba, sector_size); + ESP_RETURN_ON_FALSE(!__builtin_uadd_overflow(temp, offset, &addr), ESP_ERR_INVALID_SIZE, TAG, "overflow addr %u offset %lu", temp, offset); + } + + // Copy data to the buffer + memcpy((void *)storage->storage_buffer.data_buffer, src, size); + storage->storage_buffer.lun = lun; + storage->storage_buffer.lba = lba; + storage->storage_buffer.offset = offset; + storage->storage_buffer.bufsize = size; + + // Increment the deferred writes counter + MSC_ENTER_CRITICAL(); + storage->deffered_writes++; + MSC_EXIT_CRITICAL(); + + // Defer execution of the write to the TinyUSB task + usbd_defer_func(tusb_write_func, (void *)storage, false); + + return ESP_OK; +} + +static esp_err_t vfs_fat_format(BYTE format_flags) +{ + esp_err_t ret; + FRESULT fresult; + // Drive does not have a filesystem, try to format it + const size_t workbuf_size = 4096; + void *workbuf = ff_memalloc(workbuf_size); + if (workbuf == NULL) { + return ESP_ERR_NO_MEM; + } + + size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(CONFIG_WL_SECTOR_SIZE, workbuf_size); + + ESP_LOGD(TAG, "Format drive, allocation unit size=%d", alloc_unit_size); + + const MKFS_PARM opt = {format_flags, 0, 0, 0, alloc_unit_size}; + fresult = f_mkfs("", &opt, workbuf, workbuf_size); // Use default volume + if (fresult != FR_OK) { + ret = ESP_FAIL; + ESP_LOGE(TAG, "Unable to create default volume, (%d)", fresult); + goto fail; + } + ff_memfree(workbuf); + workbuf = NULL; + return ESP_OK; +fail: + if (workbuf) { + ff_memfree(workbuf); + } + return ret; +} + +static esp_err_t vfs_fat_mount(char *drv, FATFS *fs, bool force) +{ + esp_err_t ret; + // Try to mount the drive + FRESULT fresult = f_mount(fs, drv, force ? 1 : 0); + switch (fresult) { + case FR_OK: + ESP_LOGD(TAG, "Mounted drive %s successfully", drv); + ret = ESP_OK; + break; + case FR_NO_FILESYSTEM: + case FR_INT_ERR: + // These are recoverable errors, which can be fixed by formatting the drive + ESP_LOGD(TAG, "Drive %s does not have a filesystem, need to format", drv); + ret = ESP_ERR_NOT_FOUND; // No filesystem or internal error, need to format + break; + default: + ESP_LOGE(TAG, "Failed to mount drive %s (%d)", drv, fresult); + ret = ESP_FAIL; // Other errors + break; + } + return ret; +} + +/** + * @brief Mount the storage medium + * + * Registers the FAT filesystem on the storage medium under the base_path via ESP VFS. + * + * @param[in] storage Pointer to the storage object, with filesystem on medium to be mounted. + * + * @return + * - ESP_OK: Storage mounted successfully + * - ESP_ERR_INVALID_STATE: Unable to register the FATFS object to VFS + * - ESP_ERR_NOT_FOUND: Filesystem not found on the mounted drive + */ +static esp_err_t msc_storage_mount(msc_storage_obj_t *storage) +{ + esp_err_t ret; + FATFS *fs = NULL; + const char *base_path = storage->fat_fs.base_path; + int max_files = storage->fat_fs.max_files; + + if (storage->mount_point == TINYUSB_MSC_STORAGE_MOUNT_APP) { + // If the storage is already mounted to APP, no need to unmount + return ESP_OK; + } + + tinyusb_event_cb(storage, TINYUSB_MSC_EVENT_MOUNT_START); + + // Get the vacant driver number + BYTE pdrv = 0xFF; + ESP_RETURN_ON_ERROR(ff_diskio_get_drive(&pdrv), TAG, "The maximum count of volumes is already mounted"); + + // Lock the storage + xSemaphoreTake(storage->mux_lock, portMAX_DELAY); + + // Register the partition under the drive number + ret = storage->medium->mount(pdrv); + if (ret != ESP_OK) { + xSemaphoreGive(storage->mux_lock); + ESP_LOGE(TAG, "Failed to mount the storage medium, pdrv=%d, err=0x%x", pdrv, ret); + goto exit; + } + + // Register FATFS object to VFS + char drv[3] = {(char)('0' + pdrv), ':', 0}; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + esp_vfs_fat_conf_t conf = { + .base_path = base_path, + .fat_drive = drv, + .max_files = max_files, + }; + ret = esp_vfs_fat_register_cfg(&conf, &fs); +#else + ret = esp_vfs_fat_register(base_path, drv, max_files, &fs); +#endif + if (ret == ESP_ERR_INVALID_STATE) { + ESP_LOGD(TAG, "VFS FAT already registered"); + } else if (ret != ESP_OK) { + xSemaphoreGive(storage->mux_lock); + ESP_LOGE(TAG, "VFS FAT register failed, %s", esp_err_to_name(ret)); + tinyusb_event_cb(storage, TINYUSB_MSC_EVENT_MOUNT_FAILED); + goto exit; + } + + // Registering the FATFS object was done successfully; change the mount point. + // All subsequent errors depend on the filesystem. + storage->mount_point = TINYUSB_MSC_STORAGE_MOUNT_APP; + + ret = vfs_fat_mount(drv, fs, true); + if (ret == ESP_ERR_NOT_FOUND) { + // If mount failed, try to format the drive + if (storage->fat_fs.do_not_format) { + xSemaphoreGive(storage->mux_lock); + ESP_LOGE(TAG, "Mount failed and do not format is set"); + tinyusb_event_cb(storage, TINYUSB_MSC_EVENT_FORMAT_REQUIRED); + ret = ESP_OK; + goto exit; + } + ESP_LOGW(TAG, "Mount failed, trying to format the drive"); + BYTE format_flags = storage->fat_fs.format_flags; + ESP_GOTO_ON_ERROR(vfs_fat_format(format_flags), fail, TAG, "Failed to format the drive"); + ESP_GOTO_ON_ERROR(vfs_fat_mount(drv, fs, false), fail, TAG, "Failed to mount FAT filesystem"); + ESP_LOGD(TAG, "Format completed, FAT mounted successfully"); + } else if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount drive, %s", esp_err_to_name(ret)); + goto fail; + } + + xSemaphoreGive(storage->mux_lock); + tinyusb_event_cb(storage, TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + return ESP_OK; +fail: + xSemaphoreGive(storage->mux_lock); + tinyusb_event_cb(storage, TINYUSB_MSC_EVENT_FORMAT_FAILED); +exit: + storage->medium->unmount(); + if (fs) { + esp_vfs_fat_unregister_path(base_path); + } + return ret; +} + +/** + * @brief Unmount the storage medium + * + * Unregisters the FAT filesystem on the storage medium and unregister the base_path via ESP VFS. + * After, the FS is not available under base_path and can be exposed to USB Host. + * + * @param[in] storage Pointer to the storage object, with filesystem on medium to be unmounted. + * + * @return + * ESP_OK if storage unmounted successfully, otherwise an error code indicating the failure reason. + */ +static esp_err_t msc_storage_unmount(msc_storage_obj_t *storage) +{ + if (storage->mount_point == TINYUSB_MSC_STORAGE_MOUNT_USB) { + // If the storage is already mounted to USB, no need to unmount + return ESP_OK; + } + + tinyusb_event_cb(storage, TINYUSB_MSC_EVENT_MOUNT_START); + + xSemaphoreTake(storage->mux_lock, portMAX_DELAY); + + // Unregister the partition + esp_err_t ret = storage->medium->unmount(); + if (ret != ESP_OK) { + xSemaphoreGive(storage->mux_lock); + ESP_LOGE(TAG, "Failed to unmount the storage medium"); + return ret; + } + // Unregister FATFS object from VFS + ret = esp_vfs_fat_unregister_path(storage->fat_fs.base_path); + if (ret != ESP_OK) { + xSemaphoreGive(storage->mux_lock); + ESP_LOGE(TAG, "Failed to unregister VFS FAT"); + return ret; + } + + storage->mount_point = TINYUSB_MSC_STORAGE_MOUNT_USB; + xSemaphoreGive(storage->mux_lock); + + tinyusb_event_cb(storage, TINYUSB_MSC_EVENT_MOUNT_COMPLETE); + return ESP_OK; +} + +static void msc_storage_event_default_cb(tinyusb_msc_storage_handle_t handle, tinyusb_msc_event_t *event, void *arg) +{ + (void) handle; + (void) event; + (void) arg; + + // Default callback does nothing + // This is used when no user-defined callback is provided + ESP_LOGW(TAG, "Default MSC event callback called, event ID: %d, mount point: %d", event->id, event->mount_point); +} + +/** + * @brief Create a new MSC storage object + * + * This function allocates and initializes a new MSC storage object based on the provided configuration + * and storage medium. + * + * @param[in] config Pointer to the MSC storage configuration structure. + * @param[in] medium Pointer to the storage medium interface. + * @param[out] storage_hdl Pointer to the location where the created storage object handle will be stored. + * + * @return + * - ESP_OK: Storage object created successfully + * - ESP_ERR_NO_MEM: Memory allocation failed + */ +static esp_err_t msc_storage_new(const tinyusb_msc_storage_config_t *config, + const storage_medium_t *medium, + msc_storage_obj_t **storage_hdl) +{ + esp_err_t ret; + + // Create mutex for storage operations + SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex(); + ESP_RETURN_ON_FALSE(mux_lock != NULL, ESP_ERR_NO_MEM, TAG, "Failed to create mutex for storage operations"); + // Create storage object + msc_storage_obj_t *storage_obj = (msc_storage_obj_t *)heap_caps_aligned_calloc(MSC_STORAGE_MEM_ALIGN, 1, sizeof(msc_storage_obj_t), MALLOC_CAP_DMA); + if (storage_obj == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for MSC storage"); + ret = ESP_ERR_NO_MEM; + goto fail; + } + + // Configure the storage object + storage_obj->mux_lock = mux_lock; + storage_obj->medium = medium; + storage_obj->mount_point = TINYUSB_MSC_STORAGE_MOUNT_USB; // Default mount point is USB host + storage_obj->deffered_writes = 0; + // In case the user does not set mount_config.max_files + // and for backward compatibility with versions <1.4.2 + // max_files is set to 2 + const int max_files = config->fat_fs.config.max_files; + storage_obj->fat_fs.max_files = max_files > 0 ? max_files : 2; + storage_obj->fat_fs.do_not_format = config->fat_fs.do_not_format; + storage_obj->fat_fs.format_flags = config->fat_fs.format_flags; + if (storage_obj->fat_fs.format_flags == 0) { + // Use default format flags if not provided + storage_obj->fat_fs.format_flags = FM_ANY; // Auto-select FAT type based on volume size + } + if (config->fat_fs.base_path == NULL) { + // Use default base path if not provided + storage_obj->fat_fs.base_path = TINYUSB_DEFAULT_BASE_PATH; + } else { + // Use the provided base path + storage_obj->fat_fs.base_path = config->fat_fs.base_path; + } + + // Set sector count and size + storage_info_t storage_info; + ret = storage_obj->medium->get_info(&storage_info); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to get storage info"); + goto fail; + } + + storage_obj->sector_count = storage_info.total_sectors; + storage_obj->sector_size = storage_info.sector_size; + + ESP_LOGD(TAG, "Storage type: , sectors count: %"PRIu32", sector size: %"PRIu32"", + storage_obj->sector_count, + storage_obj->sector_size); + + *storage_hdl = storage_obj; + return ESP_OK; +fail: + if (storage_obj) { + heap_caps_free(storage_obj); + } + if (mux_lock) { + vSemaphoreDelete(mux_lock); + } + return ret; +} + +/** + * @brief Delete an MSC storage object + * + * This function frees the resources associated with an MSC storage object. + * + * @param[in] storage Pointer to the storage object to be deleted. + */ +static void msc_storage_delete(msc_storage_obj_t *storage) +{ + storage->medium = NULL; + + if (storage->mux_lock) { + vSemaphoreDelete(storage->mux_lock); + } + heap_caps_free(storage); +} + +/** + * @brief Install the MSC driver + * + * This function initializes the MSC driver with the provided configuration. + * + * @param[in] config Pointer to the MSC driver configuration structure. + * @param[in] internally_installed Boolean flag indicating if the driver is installed internally during the first storage creation. + * + */ +static esp_err_t msc_driver_install(const tinyusb_msc_driver_config_t *config, bool internally_installed) +{ + ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "Config can't be NULL"); + + MSC_ENTER_CRITICAL(); + MSC_CHECK_ON_CRITICAL(p_msc_driver == NULL, ESP_ERR_INVALID_STATE); + MSC_EXIT_CRITICAL(); + + esp_err_t ret; + tinyusb_msc_driver_t *msc_driver = NULL; + msc_driver = (tinyusb_msc_driver_t *)heap_caps_calloc(1, sizeof(tinyusb_msc_driver_t), MALLOC_CAP_DEFAULT); + ESP_RETURN_ON_FALSE(msc_driver != NULL, ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for MSC driver"); + + // Default callback + if (config->callback == NULL) { + msc_driver->dynamic.event_cb = msc_storage_event_default_cb; + msc_driver->dynamic.event_arg = NULL; + } else { + msc_driver->dynamic.event_cb = config->callback; + msc_driver->dynamic.event_arg = config->callback_arg; + } + + msc_driver->dynamic.lun_count = 0; // LUN will be added with storage initialization + msc_driver->constant.flags.val = (uint16_t) config->user_flags.val; // Config flags for the MSC driver + msc_driver->constant.flags.internally_installed = internally_installed; + + MSC_ENTER_CRITICAL(); + MSC_GOTO_ON_FALSE_CRITICAL(p_msc_driver == NULL, ESP_ERR_INVALID_STATE); + p_msc_driver = msc_driver; + MSC_EXIT_CRITICAL(); + + return ESP_OK; +fail: + heap_caps_free(msc_driver); + return ret; +} + +// +// ============================ TinyUSB MSC Storage Private Functions ========================== +// + +void msc_storage_mount_to_app(void) +{ + if (p_msc_driver == NULL) { + return; + } + + for (uint8_t i = 0; i < TINYUSB_MSC_STORAGE_MAX_LUNS; i++) { + if (p_msc_driver->dynamic.storage[i] != NULL && !p_msc_driver->constant.flags.auto_mount_off) { + if (msc_storage_mount(p_msc_driver->dynamic.storage[i]) != ESP_OK) { + ESP_LOGW(TAG, "Unable to mount storage to app"); + tinyusb_event_cb(p_msc_driver->dynamic.storage[i], TINYUSB_MSC_EVENT_MOUNT_FAILED); + } + } + } +} + +void msc_storage_mount_to_usb(void) +{ + if (p_msc_driver == NULL) { + return; + } + + for (uint8_t i = 0; i < TINYUSB_MSC_STORAGE_MAX_LUNS; i++) { + if (p_msc_driver->dynamic.storage[i] != NULL && !p_msc_driver->constant.flags.auto_mount_off) { + if (msc_storage_unmount(p_msc_driver->dynamic.storage[i]) != ESP_OK) { + ESP_LOGW(TAG, "Unable to mount storage to usb"); + tinyusb_event_cb(p_msc_driver->dynamic.storage[i], TINYUSB_MSC_EVENT_MOUNT_FAILED); + } + } + } +} + +// +// ========================== TinyUSB MSC Public API Functions ================================= +// + +esp_err_t tinyusb_msc_set_storage_callback(tusb_msc_callback_t callback, void *arg) +{ + ESP_RETURN_ON_FALSE(p_msc_driver != NULL, ESP_ERR_INVALID_STATE, TAG, "Storage handle is not initialized"); + ESP_RETURN_ON_FALSE(callback != NULL, ESP_ERR_INVALID_ARG, TAG, "Callback can't be NULL"); + + MSC_ENTER_CRITICAL(); + p_msc_driver->dynamic.event_cb = callback; + p_msc_driver->dynamic.event_arg = arg; + MSC_EXIT_CRITICAL(); + return ESP_OK; +} + +esp_err_t tinyusb_msc_install_driver(const tinyusb_msc_driver_config_t *config) +{ + return msc_driver_install(config, false); +} + +esp_err_t tinyusb_msc_uninstall_driver(void) +{ + MSC_ENTER_CRITICAL(); + MSC_CHECK_ON_CRITICAL(p_msc_driver != NULL, ESP_ERR_NOT_SUPPORTED); + MSC_CHECK_ON_CRITICAL(p_msc_driver->dynamic.lun_count == 0, ESP_ERR_INVALID_STATE); + tinyusb_msc_driver_t *msc_driver = p_msc_driver; + p_msc_driver = NULL; + MSC_EXIT_CRITICAL(); + + // Free the driver memory + heap_caps_free(msc_driver); + return ESP_OK; +} + +esp_err_t tinyusb_msc_new_storage_spiflash(const tinyusb_msc_storage_config_t *config, + tinyusb_msc_storage_handle_t *handle) +{ + ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "Config can't be NULL"); + ESP_RETURN_ON_FALSE(config->medium.wl_handle != WL_INVALID_HANDLE, ESP_ERR_INVALID_ARG, TAG, "Wear levelling handle should be valid"); + + ESP_RETURN_ON_FALSE(CONFIG_TINYUSB_MSC_BUFSIZE >= CONFIG_WL_SECTOR_SIZE, + ESP_ERR_NOT_SUPPORTED, + TAG, + "TinyUSB buffer size (%d) must be at least the size of Wear Levelling sector size (%d), please reconfigure the project.", + (int)(CONFIG_TINYUSB_MSC_BUFSIZE), (int)(CONFIG_WL_SECTOR_SIZE)); + + bool need_to_install_driver = false; + const storage_medium_t *medium = NULL; + msc_storage_obj_t *storage = NULL; + esp_err_t ret; + + MSC_ENTER_CRITICAL(); + if (p_msc_driver == NULL) { + need_to_install_driver = true; + } + MSC_EXIT_CRITICAL(); + + // Driver was not installed, install it now + if (need_to_install_driver) { + tinyusb_msc_driver_config_t default_cfg = { + .callback = msc_storage_event_default_cb, + }; + ret = msc_driver_install(&default_cfg, true); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to install MSC driver"); + goto driver_err; + } + } + + // Create a medium for storage + ret = storage_spiflash_open_medium(config->medium.wl_handle, &medium); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to open SPI Flash medium"); + goto medium_err; + } + // Create a storage object + ret = msc_storage_new(config, medium, &storage); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to create MSC storage object"); + goto storage_err; + } + // Map the storage object to the MSC Lun + MSC_ENTER_CRITICAL(); + if (!_msc_storage_map_to_lun(storage)) { + MSC_EXIT_CRITICAL(); + ESP_LOGE(TAG, "Failed to map storage to LUN"); + ret = ESP_FAIL; + goto map_err; + } + MSC_EXIT_CRITICAL(); + + // Mount the storage if it is configured to be mounted to application + if (config->mount_point == TINYUSB_MSC_STORAGE_MOUNT_APP) { + ret = msc_storage_mount(storage); + if (ret != ESP_OK) { + // Unrecoverable error + ESP_LOGE(TAG, "Failed to mount storage to application"); + goto map_err; + } + } + + // Return the handle to the storage + if (handle != NULL) { + *handle = (tinyusb_msc_storage_handle_t)storage; + } + return ESP_OK; + +map_err: + msc_storage_delete(storage); +storage_err: + medium->close(); +medium_err: + if (need_to_install_driver) { + tinyusb_msc_uninstall_driver(); + } +driver_err: + return ret; +} + +#if (SOC_SDMMC_HOST_SUPPORTED) +esp_err_t tinyusb_msc_new_storage_sdmmc(const tinyusb_msc_storage_config_t *config, + tinyusb_msc_storage_handle_t *handle) +{ + ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "Config can't be NULL"); + ESP_RETURN_ON_FALSE(config->medium.card != NULL, ESP_ERR_INVALID_ARG, TAG, "Card handle should be valid"); + + bool need_to_install_driver = false; + const storage_medium_t *medium = NULL; + msc_storage_obj_t *storage = NULL; + esp_err_t ret; + + MSC_ENTER_CRITICAL(); + if (p_msc_driver == NULL) { + need_to_install_driver = true; + } + MSC_EXIT_CRITICAL(); + + // Driver was not installed, install it now + if (need_to_install_driver) { + tinyusb_msc_driver_config_t default_cfg = { + .callback = msc_storage_event_default_cb, + }; + ret = msc_driver_install(&default_cfg, true); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to install MSC driver"); + goto driver_err; + } + } + + // Create a medium for storage + ret = storage_sdmmc_open_medium(config->medium.card, &medium); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to open SD/MMC medium"); + goto medium_err; + } + // Create a storage object + ret = msc_storage_new(config, medium, &storage); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to create MSC storage object"); + goto storage_err; + } + // Map the storage object to the MSC Lun + MSC_ENTER_CRITICAL(); + if (!_msc_storage_map_to_lun(storage)) { + MSC_EXIT_CRITICAL(); + ESP_LOGE(TAG, "Failed to map storage to LUN"); + ret = ESP_FAIL; + goto map_err; + } + MSC_EXIT_CRITICAL(); + + // Mount the storage if it is configured to be mounted to application + if (config->mount_point == TINYUSB_MSC_STORAGE_MOUNT_APP) { + ret = msc_storage_mount(storage); + if (ret != ESP_OK) { + // Unrecoverable error + ESP_LOGE(TAG, "Failed to mount storage to application"); + goto map_err; + } + } + + // Return the handle to the storage + if (handle != NULL) { + *handle = (tinyusb_msc_storage_handle_t)storage; + } + return ESP_OK; + +map_err: + msc_storage_delete(storage); +storage_err: + medium->close(); +medium_err: + if (need_to_install_driver) { + tinyusb_msc_uninstall_driver(); + } +driver_err: + return ret; +} +#endif // SOC_SDMMC_HOST_SUPPORTED + +esp_err_t tinyusb_msc_delete_storage(tinyusb_msc_storage_handle_t handle) +{ + ESP_RETURN_ON_FALSE(handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Storage handle can't be NULL"); + msc_storage_obj_t *storage = (msc_storage_obj_t *)handle; + bool no_more_luns = false; + + MSC_ENTER_CRITICAL(); + MSC_CHECK_ON_CRITICAL(p_msc_driver != NULL, ESP_ERR_INVALID_STATE); + MSC_CHECK_ON_CRITICAL(p_msc_driver->dynamic.lun_count > 0, ESP_ERR_INVALID_STATE); + MSC_CHECK_ON_CRITICAL(storage->deffered_writes == 0, ESP_ERR_INVALID_STATE); + MSC_EXIT_CRITICAL(); + + if (storage->mount_point == TINYUSB_MSC_STORAGE_MOUNT_APP) { + // Unmount the storage if it is mounted to application + ESP_ERROR_CHECK(msc_storage_unmount(storage)); + } + + // Unmap the storage from the MSC Lun + MSC_ENTER_CRITICAL(); + if (!_msc_storage_unmap_from_lun(storage)) { + MSC_EXIT_CRITICAL(); + ESP_LOGE(TAG, "Storage not found in any LUN"); + return ESP_ERR_NOT_FOUND; + }; + no_more_luns = (p_msc_driver->dynamic.lun_count == 0); + MSC_EXIT_CRITICAL(); + + // Close the storage medium + storage->medium->close(); + + // If no LUNs left and driver was installed internally, uninstall the driver + if (no_more_luns && p_msc_driver->constant.flags.internally_installed) { + ESP_ERROR_CHECK(tinyusb_msc_uninstall_driver()); // Should never fail + } + // Free the storage object and related resources + msc_storage_delete(storage); + return ESP_OK; +} + +esp_err_t tinyusb_msc_get_storage_capacity(tinyusb_msc_storage_handle_t handle, uint32_t *sector_count) +{ + ESP_RETURN_ON_FALSE(handle != NULL, ESP_ERR_INVALID_STATE, TAG, "MSC storage is not initialized"); + ESP_RETURN_ON_FALSE(sector_count != NULL, ESP_ERR_INVALID_ARG, TAG, "Sector count pointer can't be NULL"); + + MSC_ENTER_CRITICAL(); + MSC_CHECK_ON_CRITICAL(p_msc_driver != NULL, ESP_ERR_INVALID_STATE); + MSC_EXIT_CRITICAL(); + + msc_storage_obj_t *storage = (msc_storage_obj_t *) handle; + *sector_count = storage->sector_count; + + return ESP_OK; +} + +esp_err_t tinyusb_msc_get_storage_sector_size(tinyusb_msc_storage_handle_t handle, uint32_t *sector_size) +{ + ESP_RETURN_ON_FALSE(p_msc_driver != NULL, ESP_ERR_INVALID_STATE, TAG, "MSC driver is not initialized"); + ESP_RETURN_ON_FALSE(handle != NULL, ESP_ERR_INVALID_STATE, TAG, "MSC storage is not initialized"); + ESP_RETURN_ON_FALSE(sector_size != NULL, ESP_ERR_INVALID_ARG, TAG, "Sector size pointer can't be NULL"); + + msc_storage_obj_t *storage = (msc_storage_obj_t *) handle; + *sector_size = storage->sector_size; + + return ESP_OK; +} + +esp_err_t tinyusb_msc_set_storage_mount_point(tinyusb_msc_storage_handle_t handle, + tinyusb_msc_mount_point_t mount_point) +{ + ESP_RETURN_ON_FALSE(p_msc_driver != NULL, ESP_ERR_INVALID_STATE, TAG, "MSC driver is not initialized"); + + msc_storage_obj_t *storage = (msc_storage_obj_t *)handle; + + if (storage->mount_point == mount_point) { + // If the storage is already mounted to the requested mount point, do nothing + return ESP_OK; + } + + if (mount_point == TINYUSB_MSC_STORAGE_MOUNT_APP) { + // If the storage is mounted to application, mount it + msc_storage_mount(storage); + } else { + // If the storage is mounted to USB host, unmount it + msc_storage_unmount(storage); + } + storage->mount_point = mount_point; + + return ESP_OK; +} + +esp_err_t tinyusb_msc_config_storage_fat_fs(tinyusb_msc_storage_handle_t handle, + tinyusb_msc_fatfs_config_t *fatfs_config) +{ + ESP_RETURN_ON_FALSE(p_msc_driver != NULL, ESP_ERR_INVALID_STATE, TAG, "MSC driver is not initialized"); + ESP_RETURN_ON_FALSE(fatfs_config != NULL, ESP_ERR_INVALID_ARG, TAG, "FatFS config pointer can't be NULL"); + + msc_storage_obj_t *storage = (msc_storage_obj_t *) handle; + ESP_RETURN_ON_FALSE(storage != NULL, ESP_ERR_INVALID_STATE, TAG, "MSC storage is not initialized"); + // In case the user does not set mount_config.max_files + // and for backward compatibility with versions <1.4.2 + // max_files is set to 2 + const int max_files = fatfs_config->config.max_files; + storage->fat_fs.max_files = max_files > 0 ? max_files : 2; + storage->fat_fs.do_not_format = fatfs_config->do_not_format; + storage->fat_fs.format_flags = fatfs_config->format_flags; + if (storage->fat_fs.format_flags == 0) { + // Use default format flags if not provided + storage->fat_fs.format_flags = FM_ANY; // Auto-select FAT type based on volume size + } + if (fatfs_config->base_path == NULL) { + // Use default base path if not provided + storage->fat_fs.base_path = TINYUSB_DEFAULT_BASE_PATH; + } else { + // Use the provided base path + storage->fat_fs.base_path = fatfs_config->base_path; + } + + return ESP_OK; +} + +esp_err_t tinyusb_msc_get_storage_mount_point(tinyusb_msc_storage_handle_t handle, + tinyusb_msc_mount_point_t *mount_point) +{ + ESP_RETURN_ON_FALSE(p_msc_driver != NULL, ESP_ERR_INVALID_STATE, TAG, "MSC driver is not initialized"); + ESP_RETURN_ON_FALSE(handle != NULL, ESP_ERR_INVALID_STATE, TAG, "MSC storage is not initialized"); + ESP_RETURN_ON_FALSE(mount_point != NULL, ESP_ERR_INVALID_ARG, TAG, "Mount point pointer can't be NULL"); + + MSC_ENTER_CRITICAL(); + msc_storage_obj_t *storage = (msc_storage_obj_t *) handle; + *mount_point = storage->mount_point; + MSC_EXIT_CRITICAL(); + + return ESP_OK; +} + +esp_err_t tinyusb_msc_format_storage(tinyusb_msc_storage_handle_t handle) +{ + ESP_RETURN_ON_FALSE(p_msc_driver != NULL, ESP_ERR_INVALID_STATE, TAG, "MSC driver is not initialized"); + ESP_RETURN_ON_FALSE(handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Storage handle can't be NULL"); + msc_storage_obj_t *storage = (msc_storage_obj_t *) handle; + + esp_err_t ret; + FATFS *fs = NULL; + BYTE pdrv = 0xFF; + const char *base_path = storage->fat_fs.base_path; + int max_files = storage->fat_fs.max_files; + + ESP_RETURN_ON_FALSE(storage->mount_point == TINYUSB_MSC_STORAGE_MOUNT_APP, ESP_ERR_INVALID_ARG, TAG, "Storage must be mounted to APP to format it"); + // Register the diskio driver on the storage medium + ESP_RETURN_ON_ERROR(ff_diskio_get_drive(&pdrv), TAG, "The maximum count of volumes is already mounted"); + ESP_RETURN_ON_ERROR(storage->medium->mount(pdrv), TAG, "Failed pdrv=%d", pdrv); + + // Register FAT FS with VFS component + char drv[3] = {(char)('0' + pdrv), ':', 0}; // FATFS drive specificator; if only one drive is used, can be an empty string +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + esp_vfs_fat_conf_t conf = { + .base_path = base_path, + .fat_drive = drv, + .max_files = max_files, + }; + ESP_RETURN_ON_ERROR(esp_vfs_fat_register_cfg(&conf, &fs), TAG, "VFS FAT register failed"); +#else + ESP_RETURN_ON_ERROR(esp_vfs_fat_register(base_path, drv, max_files, &fs), TAG, "VFS FAT register failed"); +#endif + // to make format, we need to mount the fs + // Mount the FAT FS + ret = vfs_fat_mount(drv, fs, true); + ESP_RETURN_ON_FALSE(ret == ESP_ERR_NOT_FOUND, ESP_ERR_NOT_FOUND, TAG, "Unexpected filesystem found on the drive"); + ESP_RETURN_ON_ERROR(vfs_fat_format(storage->fat_fs.format_flags), TAG, "Failed to format the drive"); + ESP_RETURN_ON_ERROR(vfs_fat_mount(drv, fs, false), TAG, "Failed to mount FAT filesystem"); + + ESP_LOGD(TAG, "Storage formatted successfully"); + return ESP_OK; +} + +/* TinyUSB MSC callbacks + ********************************************************************* */ + +/** SCSI ASC/ASCQ codes. **/ +/** User can add and use more codes as per the need of the application **/ +#define SCSI_CODE_ASC_MEDIUM_NOT_PRESENT 0x3A /** SCSI ASC code for 'MEDIUM NOT PRESENT' **/ +#define SCSI_CODE_ASC_INVALID_COMMAND_OPERATION_CODE 0x20 /** SCSI ASC code for 'INVALID COMMAND OPERATION CODE' **/ +#define SCSI_CODE_ASCQ 0x00 + +// Invoked when received GET_MAX_LUN request, required for multiple LUNs implementation +uint8_t tud_msc_get_maxlun_cb(void) +{ + uint8_t msc_lun = 1; // Default 1 LUN, even when the storage is not initialized, report 1 LUN, but without a media + + MSC_ENTER_CRITICAL(); + if (p_msc_driver != NULL && p_msc_driver->dynamic.lun_count) { + msc_lun = p_msc_driver->dynamic.lun_count; + } + MSC_EXIT_CRITICAL(); + + return msc_lun; +} + +// Invoked when received SCSI_CMD_INQUIRY +// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) +{ + (void) lun; + const char vid[] = "TinyUSB"; + const char pid[] = "TEST MSC Storage"; + const char rev[] = "0.1"; + + memcpy(vendor_id, vid, strlen(vid)); + memcpy(product_id, pid, strlen(pid)); + memcpy(product_rev, rev, strlen(rev)); +} + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool tud_msc_test_unit_ready_cb(uint8_t lun) +{ + msc_storage_obj_t *storage = NULL; + + MSC_ENTER_CRITICAL(); + bool found = _msc_storage_get_by_lun(lun, &storage); + MSC_EXIT_CRITICAL(); + + if (found && (storage != NULL) && (storage->mount_point == TINYUSB_MSC_STORAGE_MOUNT_USB)) { + // Storage media is ready for access by USB host + return true; + } + // Storage media is not ready for access by USB host + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, SCSI_CODE_ASC_MEDIUM_NOT_PRESENT, SCSI_CODE_ASCQ); + return false; +} + +// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size +// Application update block count and block size +void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) +{ + msc_storage_obj_t *storage = NULL; + + uint32_t capacity = 0; + uint32_t sec_size = 0; + + MSC_ENTER_CRITICAL(); + bool found = _msc_storage_get_by_lun(lun, &storage); + MSC_EXIT_CRITICAL(); + + if (found && storage != NULL) { + // Do not verify the error, if the storage is not initialized + tinyusb_msc_get_storage_capacity((tinyusb_msc_storage_handle_t) storage, &capacity); + tinyusb_msc_get_storage_sector_size((tinyusb_msc_storage_handle_t) storage, &sec_size); + } + + *block_count = capacity; + *block_size = (uint16_t)sec_size; +} + +// Invoked when received Start Stop Unit command +// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage +// - Start = 1 : active mode, if load_eject = 1 : load disk storage +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) +{ + (void) lun; + (void) power_condition; + + if (load_eject && !start) { + // Eject media from the storage + msc_storage_mount_to_app(); + } + return true; +} + +// Invoked when received SCSI READ10 command +// - Address = lba * BLOCK_SIZE + offset +// - Application fill the buffer (up to bufsize) with address contents and return number of read byte. +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) +{ + esp_err_t err = msc_storage_read_sector(lun, lba, offset, bufsize, buffer); + if (err != ESP_OK) { + ESP_LOGE(TAG, "READ(10) command failed, %s", esp_err_to_name(err)); + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_CODE_ASC_INVALID_COMMAND_OPERATION_CODE, SCSI_CODE_ASCQ); + return -1; // Indicate an error occurred + } + return bufsize; +} + +// Invoked when received SCSI WRITE10 command +// - Address = lba * BLOCK_SIZE + offset +// - Application write data from buffer to address contents (up to bufsize) and return number of written byte. +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) +{ + // There is no way to return the error from the deferred function, so we need to check everything here + if (bufsize > MSC_STORAGE_BUFFER_SIZE) { + ESP_LOGE(TAG, "Buffer size %"PRIu32" exceeds maximum allowed size %d", bufsize, MSC_STORAGE_BUFFER_SIZE); + goto error; + } + esp_err_t err = msc_storage_write_sector_deferred(lun, lba, offset, bufsize, buffer); + if (err != ESP_OK) { + ESP_LOGE(TAG, "WRITE(10) command failed, %s", esp_err_to_name(err)); + goto error; + } + // Return the number of bytes accepted + return bufsize; + +error: + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_CODE_ASC_INVALID_COMMAND_OPERATION_CODE, SCSI_CODE_ASCQ); + return -1; // Indicate an error occurred +} + +/** + * Invoked when received an SCSI command not in built-in list below. + * - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, TEST_UNIT_READY, START_STOP_UNIT, MODE_SENSE6, REQUEST_SENSE + * - READ10 and WRITE10 has their own callbacks + * + * \param[in] lun Logical unit number + * \param[in] scsi_cmd SCSI command contents which application must examine to response accordingly + * \param[out] buffer Buffer for SCSI Data Stage. + * - For INPUT: application must fill this with response. + * - For OUTPUT it holds the Data from host + * \param[in] bufsize Buffer's length. + * + * \return Actual bytes processed, can be zero for no-data command. + * \retval negative Indicate error e.g unsupported command, tinyusb will \b STALL the corresponding + * endpoint and return failed status in command status wrapper phase. + */ +int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize) +{ + int32_t ret; + + switch (scsi_cmd[0]) { + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + /* SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL is the Prevent/Allow Medium Removal + command (1Eh) that requests the library to enable or disable user access to + the storage media/partition. */ + ret = 0; + break; + default: + ESP_LOGW(TAG, "tud_msc_scsi_cb() invoked: %d", scsi_cmd[0]); + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_CODE_ASC_INVALID_COMMAND_OPERATION_CODE, SCSI_CODE_ASCQ); + ret = -1; + break; + } + return ret; +} + +/*********************************************************************** TinyUSB MSC callbacks*/ diff --git a/components/esp_tinyusb/tinyusb_net.c b/components/esp_tinyusb/tinyusb_net.c new file mode 100644 index 0000000..4ef978c --- /dev/null +++ b/components/esp_tinyusb/tinyusb_net.c @@ -0,0 +1,191 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "tinyusb_net.h" +#include "descriptors_control.h" +#include "usb_descriptors.h" +#include "device/usbd_pvt.h" +#include "esp_check.h" + +#define MAC_ADDR_LEN 6 + +typedef struct packet { + void *buffer; + void *buff_free_arg; + uint16_t len; + esp_err_t result; +} packet_t; + +struct tinyusb_net_handle { + bool initialized; + SemaphoreHandle_t buffer_sema; + EventGroupHandle_t tx_flags; + tusb_net_rx_cb_t rx_cb; + tusb_net_free_tx_cb_t tx_buff_free_cb; + tusb_net_init_cb_t init_cb; + char mac_str[2 * MAC_ADDR_LEN + 1]; + void *ctx; + packet_t *packet_to_send; +}; + +const static int TX_FINISHED_BIT = BIT0; +static struct tinyusb_net_handle s_net_obj = { }; +static const char *TAG = "tusb_net"; + +static void do_send_sync(void *ctx) +{ + (void) ctx; + if (xSemaphoreTake(s_net_obj.buffer_sema, 0) != pdTRUE || s_net_obj.packet_to_send == NULL) { + return; + } + + packet_t *packet = s_net_obj.packet_to_send; + if (tud_network_can_xmit(packet->len)) { + tud_network_xmit(packet, packet->len); + packet->result = ESP_OK; + } else { + packet->result = ESP_FAIL; + } + xSemaphoreGive(s_net_obj.buffer_sema); + xEventGroupSetBits(s_net_obj.tx_flags, TX_FINISHED_BIT); +} + +static void do_send_async(void *ctx) +{ + packet_t *packet = ctx; + if (tud_network_can_xmit(packet->len)) { + tud_network_xmit(packet, packet->len); + } else if (s_net_obj.tx_buff_free_cb) { + ESP_LOGW(TAG, "Packet cannot be accepted on USB interface, dropping"); + s_net_obj.tx_buff_free_cb(packet->buff_free_arg, s_net_obj.ctx); + } + free(packet); +} + +esp_err_t tinyusb_net_send_async(void *buffer, uint16_t len, void *buff_free_arg) +{ + if (!tud_mounted()) { + return ESP_ERR_INVALID_STATE; + } + + packet_t *packet = calloc(1, sizeof(packet_t)); + packet->len = len; + packet->buffer = buffer; + packet->buff_free_arg = buff_free_arg; + ESP_RETURN_ON_FALSE(packet, ESP_ERR_NO_MEM, TAG, "Failed to allocate packet to send"); + usbd_defer_func(do_send_async, packet, false); + return ESP_OK; +} + +esp_err_t tinyusb_net_send_sync(void *buffer, uint16_t len, void *buff_free_arg, TickType_t timeout) +{ + if (!tud_mounted()) { + return ESP_ERR_INVALID_STATE; + } + + // Lazy init the flags and semaphores, as they might not be needed (if async approach is used) + if (!s_net_obj.tx_flags) { + s_net_obj.tx_flags = xEventGroupCreate(); + ESP_RETURN_ON_FALSE(s_net_obj.tx_flags, ESP_ERR_NO_MEM, TAG, "Failed to allocate event flags"); + } + if (!s_net_obj.buffer_sema) { + s_net_obj.buffer_sema = xSemaphoreCreateBinary(); + ESP_RETURN_ON_FALSE(s_net_obj.buffer_sema, ESP_ERR_NO_MEM, TAG, "Failed to allocate buffer semaphore"); + } + + packet_t packet = { + .buffer = buffer, + .len = len, + .buff_free_arg = buff_free_arg + }; + s_net_obj.packet_to_send = &packet; + xSemaphoreGive(s_net_obj.buffer_sema); // now the packet is ready, let's mark it available to tusb send + + // to execute the send function in tinyUSB task context + usbd_defer_func(do_send_sync, NULL, false); // arg=NULL -> sync send, we keep the packet inside the object + + // wait wor completion with defined timeout + EventBits_t bits = xEventGroupWaitBits(s_net_obj.tx_flags, TX_FINISHED_BIT, pdTRUE, pdTRUE, timeout); + xSemaphoreTake(s_net_obj.buffer_sema, portMAX_DELAY); // if tusb sending already started, we have wait before ditching the packet + s_net_obj.packet_to_send = NULL; // invalidate the argument + if (bits & TX_FINISHED_BIT) { // If transaction finished, return error code + return packet.result; + } + return ESP_ERR_TIMEOUT; +} + +esp_err_t tinyusb_net_init(const tinyusb_net_config_t *cfg) +{ + ESP_RETURN_ON_FALSE(s_net_obj.initialized == false, ESP_ERR_INVALID_STATE, TAG, "TinyUSB Net class is already initialized"); + + // the semaphore and event flags are initialized only if needed + s_net_obj.rx_cb = cfg->on_recv_callback; + s_net_obj.init_cb = cfg->on_init_callback; + s_net_obj.tx_buff_free_cb = cfg->free_tx_buffer; + s_net_obj.ctx = cfg->user_context; + + const uint8_t *mac = &cfg->mac_addr[0]; + snprintf(s_net_obj.mac_str, sizeof(s_net_obj.mac_str), "%02X%02X%02X%02X%02X%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + uint8_t mac_id = tusb_get_mac_string_id(); + // Pass it to Descriptor control module + tinyusb_descriptors_set_string(s_net_obj.mac_str, mac_id); + + s_net_obj.initialized = true; + + return ESP_OK; +} + +void tinyusb_net_deinit(void) +{ + if (s_net_obj.buffer_sema) { + vSemaphoreDelete(s_net_obj.buffer_sema); + s_net_obj.buffer_sema = NULL; + } + if (s_net_obj.tx_flags) { + vEventGroupDelete(s_net_obj.tx_flags); + s_net_obj.tx_flags = NULL; + } + s_net_obj.initialized = false; + s_net_obj.rx_cb = NULL; + s_net_obj.init_cb = NULL; + s_net_obj.tx_buff_free_cb = NULL; + s_net_obj.ctx = NULL; + s_net_obj.packet_to_send = NULL; + memset(s_net_obj.mac_str, 0, sizeof(s_net_obj.mac_str)); +} + +//--------------------------------------------------------------------+ +// tinyusb callbacks +//--------------------------------------------------------------------+ +bool tud_network_recv_cb(const uint8_t *src, uint16_t size) +{ + if (s_net_obj.rx_cb) { + s_net_obj.rx_cb((void *)src, size, s_net_obj.ctx); + } + tud_network_recv_renew(); + return true; +} + +uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg) +{ + packet_t *packet = ref; + uint16_t len = arg; + + memcpy(dst, packet->buffer, packet->len); + if (s_net_obj.tx_buff_free_cb) { + s_net_obj.tx_buff_free_cb(packet->buff_free_arg, s_net_obj.ctx); + } + return len; +} + +void tud_network_init_cb(void) +{ + if (s_net_obj.init_cb) { + s_net_obj.init_cb(s_net_obj.ctx); + } +} diff --git a/components/esp_tinyusb/tinyusb_task.c b/components/esp_tinyusb/tinyusb_task.c new file mode 100644 index 0000000..79d4aca --- /dev/null +++ b/components/esp_tinyusb/tinyusb_task.c @@ -0,0 +1,187 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "soc/soc_caps.h" +#include "esp_log.h" +#include "esp_check.h" +#include "tinyusb.h" +#include "sdkconfig.h" +#include "descriptors_control.h" + +#if TUSB_VERSION_NUMBER < 1900 // < 0.19.0 +#define tusb_deinit(x) tusb_teardown(x) // For compatibility with tinyusb component versions from 0.17.0~2 to 0.18.0~5 +#endif + +const static char *TAG = "tinyusb_task"; + +static portMUX_TYPE tusb_task_lock = portMUX_INITIALIZER_UNLOCKED; +#define TINYUSB_TASK_ENTER_CRITICAL() portENTER_CRITICAL(&tusb_task_lock) +#define TINYUSB_TASK_EXIT_CRITICAL() portEXIT_CRITICAL(&tusb_task_lock) + +#define TINYUSB_TASK_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) + +#define TINYUSB_TASK_CHECK_FROM_CRIT(cond, ret_val) ({ \ + if (!(cond)) { \ + TINYUSB_TASK_EXIT_CRITICAL(); \ + return ret_val; \ +} \ +}) + +// TinyUSB task context +typedef struct { + // TinyUSB stack configuration + uint8_t rhport; /*!< USB Peripheral hardware port number. Available when hardware has several available peripherals. */ + tusb_rhport_init_t rhport_init; /*!< USB Device RH port initialization configuration pointer */ + const tinyusb_desc_config_t *desc_cfg; /*!< USB Device descriptors configuration pointer */ + // Task related + TaskHandle_t handle; /*!< Task handle */ + volatile TaskHandle_t awaiting_handle; /*!< Task handle, waiting to be notified after successful start of TinyUSB stack */ +} tinyusb_task_ctx_t; + +static bool _task_is_running = false; // Locking flag for the task, access only from the critical section +static tinyusb_task_ctx_t *p_tusb_task_ctx = NULL; // TinyUSB task context + +/** + * @brief This top level thread processes all usb events and invokes callbacks + */ +static void tinyusb_device_task(void *arg) +{ + tinyusb_task_ctx_t *task_ctx = (tinyusb_task_ctx_t *)arg; + + // Sanity check + assert(task_ctx != NULL); + assert(task_ctx->awaiting_handle != NULL); + + ESP_LOGD(TAG, "TinyUSB task started"); + + if (tud_inited()) { + ESP_LOGE(TAG, "TinyUSB stack is already initialized"); + goto del; + } + if (tinyusb_descriptors_set(task_ctx->rhport, task_ctx->desc_cfg) != ESP_OK) { + ESP_LOGE(TAG, "TinyUSB descriptors set failed"); + goto del; + } + if (!tusb_rhport_init(task_ctx->rhport, &task_ctx->rhport_init)) { + ESP_LOGE(TAG, "Init TinyUSB stack failed"); + goto desc_free; + } + + TINYUSB_TASK_ENTER_CRITICAL(); + task_ctx->handle = xTaskGetCurrentTaskHandle(); // Save task handle + p_tusb_task_ctx = task_ctx; // Save global task context pointer + TINYUSB_TASK_EXIT_CRITICAL(); + + xTaskNotifyGive(task_ctx->awaiting_handle); // Notify parent task that TinyUSB stack was started successfully + + while (1) { // RTOS forever loop + tud_task(); + } + +desc_free: + tinyusb_descriptors_free(); +del: + TINYUSB_TASK_ENTER_CRITICAL(); + _task_is_running = false; // Task is not running anymore + TINYUSB_TASK_EXIT_CRITICAL(); + vTaskDelete(NULL); + // No return needed here: vTaskDelete(NULL) does not return +} + +esp_err_t tinyusb_task_check_config(const tinyusb_task_config_t *config) +{ + ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Task configuration can't be NULL"); + ESP_RETURN_ON_FALSE(config->size != 0, ESP_ERR_INVALID_ARG, TAG, "Task size can't be 0"); + ESP_RETURN_ON_FALSE(config->priority != 0, ESP_ERR_INVALID_ARG, TAG, "Task priority can't be 0"); +#if CONFIG_FREERTOS_UNICORE + ESP_RETURN_ON_FALSE(config->xCoreID == 0, ESP_ERR_INVALID_ARG, TAG, "Task affinity must be 0 only in uniprocessor mode"); +#else + ESP_RETURN_ON_FALSE(config->xCoreID <= SOC_CPU_CORES_NUM, ESP_ERR_INVALID_ARG, TAG, "Task affinity should be less or equal to CPU amount"); +#endif // + return ESP_OK; +} + +esp_err_t tinyusb_task_start(tinyusb_port_t port, const tinyusb_task_config_t *config, const tinyusb_desc_config_t *desc_cfg) +{ + ESP_RETURN_ON_ERROR(tinyusb_descriptors_check(port, desc_cfg), TAG, "TinyUSB descriptors check failed"); + + TINYUSB_TASK_ENTER_CRITICAL(); + TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx == NULL, ESP_ERR_INVALID_STATE); // Task shouldn't started + TINYUSB_TASK_CHECK_FROM_CRIT(!_task_is_running, ESP_ERR_INVALID_STATE); // Task shouldn't be running + _task_is_running = true; // Task is running flag, will be cleared in task in case of the error + TINYUSB_TASK_EXIT_CRITICAL(); + + esp_err_t ret; + tinyusb_task_ctx_t *task_ctx = heap_caps_calloc(1, sizeof(tinyusb_task_ctx_t), MALLOC_CAP_DEFAULT); + if (task_ctx == NULL) { + return ESP_ERR_NO_MEM; + } + + task_ctx->awaiting_handle = xTaskGetCurrentTaskHandle(); // Save parent task handle + task_ctx->handle = NULL; // TinyUSB task is not started + task_ctx->rhport = port; // Peripheral port number + task_ctx->rhport_init.role = TUSB_ROLE_DEVICE; // Role selection: esp_tinyusb is always a device + task_ctx->rhport_init.speed = (port == TINYUSB_PORT_FULL_SPEED_0) ? TUSB_SPEED_FULL : TUSB_SPEED_HIGH; // Speed selection + task_ctx->desc_cfg = desc_cfg; + + TaskHandle_t task_hdl = NULL; + ESP_LOGD(TAG, "Creating TinyUSB main task on CPU%d", config->xCoreID); + // Create a task for tinyusb device stack + xTaskCreatePinnedToCore(tinyusb_device_task, + "TinyUSB", + config->size, + (void *) task_ctx, + config->priority, + &task_hdl, + config->xCoreID); + if (task_hdl == NULL) { + ESP_LOGE(TAG, "Create TinyUSB main task failed"); + ret = ESP_ERR_NOT_FINISHED; + goto err; + } + + // Wait until the Task notify that port is active, 5 sec is more than enough + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000)) == 0) { + ESP_LOGE(TAG, "Task wasn't able to start TinyUSB stack"); + ret = ESP_ERR_TIMEOUT; + goto err; + } + + return ESP_OK; + +err: + heap_caps_free(task_ctx); + return ret; +} + +esp_err_t tinyusb_task_stop(void) +{ + TINYUSB_TASK_ENTER_CRITICAL(); + TINYUSB_TASK_CHECK_FROM_CRIT(p_tusb_task_ctx != NULL, ESP_ERR_INVALID_STATE); + tinyusb_task_ctx_t *task_ctx = p_tusb_task_ctx; + p_tusb_task_ctx = NULL; + _task_is_running = false; + TINYUSB_TASK_EXIT_CRITICAL(); + + if (task_ctx->handle != NULL) { + vTaskDelete(task_ctx->handle); + task_ctx->handle = NULL; + } + // Free descriptors + tinyusb_descriptors_free(); + // Stop TinyUSB stack + ESP_RETURN_ON_FALSE(tusb_deinit(task_ctx->rhport), ESP_ERR_NOT_FINISHED, TAG, "Unable to teardown TinyUSB stack"); + // Cleanup + heap_caps_free(task_ctx); + return ESP_OK; +} diff --git a/components/esp_tinyusb/tinyusb_uvc.c b/components/esp_tinyusb/tinyusb_uvc.c new file mode 100644 index 0000000..c7fe512 --- /dev/null +++ b/components/esp_tinyusb/tinyusb_uvc.c @@ -0,0 +1,336 @@ +/* + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD + * 2026 Daniel Kampert (DanielKampert@Kampis-Elektroecke.de) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +static const char *TAG = "tusb_uvc"; + +#define UVC_EVENT_EXIT (1<<0) +#define UVC_EVENT_EXIT_DONE (1<<1) + +static esp_tusb_uvc_t s_uvc_obj[TINYUSB_UVC_ITF_MAX]; + + +#if (CFG_TUD_VIDEO) +void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) +{ + esp_tusb_uvc_t *uvc = tinyusb_uvc_get_intf(ctl_idx); + + if ((uvc == NULL) || (uvc->initialized == false)) { + return; + } + + ESP_LOGD(TAG, "Frame transfer complete callback, buffer=%p", uvc->current_frame_buffer); + + // Frame transfer completed - return buffer to user + if ((uvc->current_frame_buffer != NULL) && uvc->fb_return_cb) { + uvc->fb_return_cb(ctl_idx, uvc->current_frame_buffer); + uvc->current_frame_buffer = NULL; + } + + if (uvc->callback_streaming_start) { + uvc_event_t event = { + .type = UVC_EVENT_FRAME_END, + }; + + uvc->callback_streaming_start(ctl_idx, &event); + } +} + +int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, + video_probe_and_commit_control_t const *parameters) +{ + esp_tusb_uvc_t *uvc = tinyusb_uvc_get_intf(ctl_idx); + + if ((uvc == NULL) || (uvc->initialized == false)) { + return VIDEO_ERROR_UNKNOWN; + } + + ESP_LOGD(TAG, "UVC streaming committed on interface %d", ctl_idx); + + if (uvc->callback_streaming_start) { + uvc_event_t event = { + .type = UVC_EVENT_STREAMING_START, + }; + uvc->callback_streaming_start(ctl_idx, &event); + } + + uvc->streaming = true; + + return VIDEO_ERROR_NONE; +} +#endif /* CFG_TUD_VIDEO */ + +esp_tusb_uvc_t *tinyusb_uvc_get_intf(tinyusb_uvc_itf_t itf) +{ + if (itf >= TINYUSB_UVC_ITF_MAX) { + return NULL; + } + return &s_uvc_obj[itf]; +} + +static void video_task(void *arg) +{ + tinyusb_uvc_itf_t itf = (tinyusb_uvc_itf_t)(uintptr_t)arg; + esp_tusb_uvc_t *uvc = tinyusb_uvc_get_intf(itf); + + if (uvc == NULL) { + vTaskDelete(NULL); + return; + } + + uint32_t start_ms = 0; + uint32_t frame_num = 0; + bool already_start = false; + + ESP_LOGD(TAG, "UVC task started for interface %d", itf); + + esp_err_t wdt_err = esp_task_wdt_add(NULL); + if (wdt_err != ESP_OK) { + ESP_LOGW(TAG, "Failed to add UVC task to watchdog: %d!", wdt_err); + } + + while (1) { + esp_task_wdt_reset(); + + EventBits_t uxBits = xEventGroupGetBits(uvc->event_group); + if (uxBits & UVC_EVENT_EXIT) { + ESP_LOGD(TAG, "UVC task exit for interface %d", itf); + break; + } + + if (tud_video_n_streaming(itf, 0) == false) { + already_start = false; + frame_num = 0; + + if (uvc->streaming) { + uvc->streaming = false; + if (uvc->callback_streaming_stop) { + uvc_event_t event = { + .type = UVC_EVENT_STREAMING_STOP, + }; + uvc->callback_streaming_stop(itf, &event); + } + } + + vTaskDelay(pdMS_TO_TICKS(10)); + + continue; + } + + if (already_start == false) { + already_start = true; + start_ms = (uint32_t)(esp_timer_get_time() / 1000); + ESP_LOGD(TAG, "UVC streaming started on interface %d", itf); + } + + uint32_t cur = (uint32_t)(esp_timer_get_time() / 1000); + if ((cur - start_ms) < (uvc->interval_ms * frame_num)) { + vTaskDelay(pdMS_TO_TICKS(5)); + continue; + } + + if ((cur - start_ms) > ((uvc->interval_ms * frame_num) + 100)) { + start_ms = cur; + frame_num = 0; + } + + uint8_t *frame_buffer = NULL; + size_t frame_size = 0; + + // Request frame buffer from user + if (uvc->fb_request_cb && uvc->fb_request_cb(itf, &frame_buffer, &frame_size)) { + if (frame_buffer && (frame_size > 0)) { + // Check if we can transmit + if (tud_video_n_frame_xfer(itf, 0, (void*)frame_buffer, frame_size)) { + ESP_LOGD(TAG, "Frame transfer started, buffer=%p, size=%d", frame_buffer, frame_size); + // Store current buffer - it will be returned in the complete callback + uvc->current_frame_buffer = frame_buffer; + frame_num++; + } else { + ESP_LOGW(TAG, "Failed to transmit frame, current_buffer=%p", uvc->current_frame_buffer); + // Return buffer immediately if transmission failed + if (uvc->fb_return_cb) { + uvc->fb_return_cb(itf, frame_buffer); + } + } + } + } + + vTaskDelay(pdMS_TO_TICKS(10)); + } + + esp_task_wdt_delete(NULL); + + xEventGroupSetBits(uvc->event_group, UVC_EVENT_EXIT_DONE); + vTaskDelete(NULL); +} + +esp_err_t tinyusb_uvc_init_itf(tinyusb_uvc_itf_t itf) +{ + ESP_RETURN_ON_FALSE(itf < TINYUSB_UVC_ITF_MAX, ESP_ERR_INVALID_ARG, TAG, "Invalid interface"); + + esp_tusb_uvc_t *uvc = tinyusb_uvc_get_intf(itf); + ESP_RETURN_ON_FALSE(uvc->initialized == false, ESP_ERR_INVALID_STATE, TAG, "Already initialized"); + + memset(uvc, 0, sizeof(esp_tusb_uvc_t)); + + return ESP_OK; +} + +esp_err_t tinyusb_uvc_init(const tinyusb_config_uvc_t *cfg) +{ + ESP_RETURN_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, TAG, "Config is NULL"); + ESP_RETURN_ON_FALSE(cfg->uvc_port < TINYUSB_UVC_ITF_MAX, ESP_ERR_INVALID_ARG, TAG, "Invalid port"); + + tinyusb_uvc_itf_t itf = cfg->uvc_port; + esp_tusb_uvc_t *uvc = tinyusb_uvc_get_intf(itf); + + ESP_RETURN_ON_ERROR(tinyusb_uvc_init_itf(itf), TAG, "Init interface failed"); + + uvc->callback_streaming_start = cfg->callback_streaming_start; + uvc->callback_streaming_stop = cfg->callback_streaming_stop; + uvc->fb_request_cb = cfg->fb_request_cb; + uvc->fb_return_cb = cfg->fb_return_cb; + uvc->stop_cb = cfg->stop_cb; + uvc->uvc_buffer = (uint8_t*)cfg->uvc_buffer; + uvc->uvc_buffer_size = cfg->uvc_buffer_size; + +#if CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM + if (itf == TINYUSB_UVC_ITF_0) { + uvc->interval_ms = 1000 / CONFIG_TINYUSB_UVC_CAM1_FRAMERATE; + } else { + uvc->interval_ms = 1000 / CONFIG_TINYUSB_UVC_CAM2_FRAMERATE; + } +#else + uvc->interval_ms = 1000 / CONFIG_TINYUSB_UVC_CAM1_FRAMERATE; +#endif + + uvc->event_group = xEventGroupCreate(); + ESP_RETURN_ON_FALSE(uvc->event_group, ESP_ERR_NO_MEM, TAG, "Failed to create event group"); + +#if CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM + BaseType_t core_id = (itf == TINYUSB_UVC_ITF_0) + ? ((CONFIG_TINYUSB_UVC_CAM1_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_TINYUSB_UVC_CAM1_TASK_CORE) + : ((CONFIG_TINYUSB_UVC_CAM2_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_TINYUSB_UVC_CAM2_TASK_CORE); + int priority = (itf == TINYUSB_UVC_ITF_0) + ? CONFIG_TINYUSB_UVC_CAM1_TASK_PRIORITY + : CONFIG_TINYUSB_UVC_CAM2_TASK_PRIORITY; +#else + BaseType_t core_id = (CONFIG_TINYUSB_UVC_CAM1_TASK_CORE < 0) ? tskNO_AFFINITY : CONFIG_TINYUSB_UVC_CAM1_TASK_CORE; + int priority = CONFIG_TINYUSB_UVC_CAM1_TASK_PRIORITY; +#endif + + char task_name[16]; + snprintf(task_name, sizeof(task_name), "UVC%d", itf); + + BaseType_t result = xTaskCreatePinnedToCore( + video_task, + task_name, + 4096, + (void*)(uintptr_t)itf, + priority, + &uvc->uvc_task_hdl, + core_id + ); + + if (result != pdPASS) { + vEventGroupDelete(uvc->event_group); + uvc->event_group = NULL; + return ESP_ERR_NO_MEM; + } + + uvc->initialized = true; + ESP_LOGD(TAG, "UVC interface %d initialized", itf); + + return ESP_OK; +} + +esp_err_t tinyusb_uvc_deinit_itf(tinyusb_uvc_itf_t itf) +{ + ESP_RETURN_ON_FALSE(itf < TINYUSB_UVC_ITF_MAX, ESP_ERR_INVALID_ARG, TAG, "Invalid interface"); + + esp_tusb_uvc_t *uvc = tinyusb_uvc_get_intf(itf); + ESP_RETURN_ON_FALSE(uvc->initialized, ESP_ERR_INVALID_STATE, TAG, "Not initialized"); + + if (uvc->event_group) { + xEventGroupSetBits(uvc->event_group, UVC_EVENT_EXIT); + + EventBits_t bits = xEventGroupWaitBits( + uvc->event_group, + UVC_EVENT_EXIT_DONE, + pdTRUE, + pdFALSE, + pdMS_TO_TICKS(1000) + ); + + if ((bits & UVC_EVENT_EXIT_DONE) == false) { + ESP_LOGW(TAG, "UVC task did not exit gracefully"); + } + + vEventGroupDelete(uvc->event_group); + uvc->event_group = NULL; + } + + memset(uvc, 0, sizeof(esp_tusb_uvc_t)); + + ESP_LOGD(TAG, "UVC interface %d deinitialized", itf); + + return ESP_OK; +} + +esp_err_t tinyusb_uvc_deinit(tinyusb_uvc_itf_t itf) +{ + return tinyusb_uvc_deinit_itf(itf); +} + +bool tinyusb_uvc_streaming_active(tinyusb_uvc_itf_t itf) +{ + if (itf >= TINYUSB_UVC_ITF_MAX) { + return false; + } + + esp_tusb_uvc_t *uvc = tinyusb_uvc_get_intf(itf); + if ((uvc == NULL) || (uvc->initialized == false)) { + return false; + } + + return uvc->streaming && tud_video_n_streaming(itf, 0); +} + +esp_err_t tinyusb_uvc_transmit_frame(tinyusb_uvc_itf_t itf, uint8_t *frame, size_t len) +{ + ESP_RETURN_ON_FALSE(itf < TINYUSB_UVC_ITF_MAX, ESP_ERR_INVALID_ARG, TAG, "Invalid interface"); + ESP_RETURN_ON_FALSE(frame, ESP_ERR_INVALID_ARG, TAG, "Frame is NULL"); + ESP_RETURN_ON_FALSE(len > 0, ESP_ERR_INVALID_ARG, TAG, "Invalid length"); + + esp_tusb_uvc_t *uvc = tinyusb_uvc_get_intf(itf); + ESP_RETURN_ON_FALSE(uvc->initialized, ESP_ERR_INVALID_STATE, TAG, "Not initialized"); + ESP_RETURN_ON_FALSE(uvc->streaming, ESP_ERR_INVALID_STATE, TAG, "Not streaming"); + + if (tud_video_n_frame_xfer(itf, 0, (void*)frame, len) == false) { + return ESP_FAIL; + } + + return ESP_OK; +} diff --git a/components/esp_tinyusb/usb_descriptors.c b/components/esp_tinyusb/usb_descriptors.c new file mode 100644 index 0000000..56a86c3 --- /dev/null +++ b/components/esp_tinyusb/usb_descriptors.c @@ -0,0 +1,477 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * 2026 Daniel Kampert (DanielKampert@Kampis-Elektroecke.de) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "usb_descriptors.h" +#include "sdkconfig.h" +#include "tinyusb.h" + +#if CONFIG_TINYUSB_UVC_ENABLED +#include "uvc_descriptors.h" +#include "uvc_frame_config.h" +#endif + +/* + * A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n)) +#define USB_TUSB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) | _PID_MAP(VIDEO, 6)) + +/**** Kconfig driven Descriptor ****/ + +//------------- Device Descriptor -------------// +const tusb_desc_device_t descriptor_dev_default = { + .bLength = sizeof(descriptor_dev_default), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + +#if CFG_TUD_CDC || CFG_TUD_VIDEO + // Use Interface Association Descriptor (IAD) for CDC or VIDEO + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, +#else + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, +#endif + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + +#if CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID + .idVendor = TINYUSB_ESPRESSIF_VID, +#else + .idVendor = CONFIG_TINYUSB_DESC_CUSTOM_VID, +#endif + +#if CONFIG_TINYUSB_DESC_USE_DEFAULT_PID + .idProduct = USB_TUSB_PID, +#else + .idProduct = CONFIG_TINYUSB_DESC_CUSTOM_PID, +#endif + + .bcdDevice = CONFIG_TINYUSB_DESC_BCD_DEVICE, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +#if (TUD_OPT_HIGH_SPEED) +const tusb_desc_device_qualifier_t descriptor_qualifier_default = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x0200, + +#if CFG_TUD_CDC || CFG_TUD_VIDEO + // Use Interface Association Descriptor (IAD) for CDC or VIDEO + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, +#else + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, +#endif + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0 +}; +#endif // TUD_OPT_HIGH_SPEED + +//------------- Array of String Descriptors -------------// +const char *descriptor_str_default[] = { + // array of pointer to string descriptors + (char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) + CONFIG_TINYUSB_DESC_MANUFACTURER_STRING, // 1: Manufacturer + CONFIG_TINYUSB_DESC_PRODUCT_STRING, // 2: Product + CONFIG_TINYUSB_DESC_SERIAL_STRING, // 3: Serials, should use chip ID + +#if CONFIG_TINYUSB_CDC_ENABLED + CONFIG_TINYUSB_DESC_CDC_STRING, // 4: CDC Interface +#endif + +#if CONFIG_TINYUSB_MSC_ENABLED + CONFIG_TINYUSB_DESC_MSC_STRING, // 5: MSC Interface +#endif + +#if CONFIG_TINYUSB_NET_MODE_ECM_RNDIS || CONFIG_TINYUSB_NET_MODE_NCM + "USB net", // 6. NET Interface + "", // 7. MAC +#endif + +#if CFG_TUD_VENDOR + "Vendor specific", // 8. Vendor specific +#endif + +#if CONFIG_TINYUSB_UVC_ENABLED + CONFIG_TINYUSB_DESC_UVC_STRING, // 9. UVC Interface +#endif + + NULL // NULL: Must be last. Indicates end of array +}; + +//------------- Interfaces enumeration -------------// +enum { +#if CFG_TUD_CDC + ITF_NUM_CDC = 0, + ITF_NUM_CDC_DATA, +#endif + +#if CFG_TUD_CDC > 1 + ITF_NUM_CDC1, + ITF_NUM_CDC1_DATA, +#endif + +#if CFG_TUD_MSC + ITF_NUM_MSC, +#endif + +#if CFG_TUD_NCM + ITF_NUM_NET, + ITF_NUM_NET_DATA, +#endif + +#if CFG_TUD_VENDOR + ITF_VENDOR, +#endif + +#if CFG_TUD_VENDOR > 1 + ITF_VENDOR1, +#endif + +#if CFG_TUD_VIDEO + ITF_NUM_VIDEO_CONTROL, + ITF_NUM_VIDEO_STREAMING, +#if CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM + ITF_NUM_VIDEO_CONTROL_2, + ITF_NUM_VIDEO_STREAMING_2, +#endif +#endif + + ITF_NUM_TOTAL +}; + +enum { + TUSB_DESC_TOTAL_LEN = TUD_CONFIG_DESC_LEN + + CFG_TUD_CDC * TUD_CDC_DESC_LEN + + CFG_TUD_MSC * TUD_MSC_DESC_LEN + + CFG_TUD_NCM * TUD_CDC_NCM_DESC_LEN + + CFG_TUD_VENDOR * TUD_VENDOR_DESC_LEN + +#if CONFIG_TINYUSB_UVC_ENABLED + TUD_CAM1_VIDEO_CAPTURE_DESC_LEN + +#if CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM + TUD_CAM2_VIDEO_CAPTURE_DESC_LEN + +#endif +#endif + 0 +}; + +//------------- USB Endpoint numbers -------------// +enum { + // Available USB Endpoints: 5 IN/OUT EPs and 1 IN EP + EP_EMPTY = 0, +#if CFG_TUD_CDC + EPNUM_0_CDC_NOTIF, + EPNUM_0_CDC, +#endif + +#if CFG_TUD_CDC > 1 + EPNUM_1_CDC_NOTIF, + EPNUM_1_CDC, +#endif + +#if CFG_TUD_MSC + EPNUM_MSC, +#endif + +#if CFG_TUD_NCM + EPNUM_NET_NOTIF, + EPNUM_NET_DATA, +#endif + +#if CFG_TUD_VENDOR + EPNUM_0_VENDOR, +#endif + +#if CFG_TUD_VENDOR > 1 + EPNUM_1_VENDOR, +#endif + +#if CFG_TUD_VIDEO + EPNUM_CAM1_VIDEO_IN +#if CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM + , EPNUM_CAM2_VIDEO_IN +#endif +#endif +}; + +//------------- STRID -------------// +enum { + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, +#if CFG_TUD_CDC + STRID_CDC_INTERFACE, +#endif + +#if CFG_TUD_MSC + STRID_MSC_INTERFACE, +#endif + +#if CFG_TUD_NCM + STRID_NET_INTERFACE, + STRID_MAC, +#endif + +#if CFG_TUD_VENDOR + STRID_VENDOR_INTERFACE, +#endif + +#if CONFIG_TINYUSB_UVC_ENABLED + STRID_UVC_INTERFACE, +#endif +}; + +//------------- Configuration Descriptor -------------// +uint8_t const descriptor_fs_cfg_default[] = { + // Configuration number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + +#if CFG_TUD_CDC + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, STRID_CDC_INTERFACE, 0x80 | EPNUM_0_CDC_NOTIF, 8, EPNUM_0_CDC, 0x80 | EPNUM_0_CDC, 64), +#endif + +#if CFG_TUD_CDC > 1 + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC1, STRID_CDC_INTERFACE, 0x80 | EPNUM_1_CDC_NOTIF, 8, EPNUM_1_CDC, 0x80 | EPNUM_1_CDC, 64), +#endif + +#if CFG_TUD_MSC + // Interface number, string index, EP Out & EP In address, EP size + TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, STRID_MSC_INTERFACE, EPNUM_MSC, 0x80 | EPNUM_MSC, 64), +#endif + +#if CFG_TUD_NCM + // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. + TUD_CDC_NCM_DESCRIPTOR(ITF_NUM_NET, STRID_NET_INTERFACE, STRID_MAC, (0x80 | EPNUM_NET_NOTIF), 64, EPNUM_NET_DATA, (0x80 | EPNUM_NET_DATA), 64, CFG_TUD_NET_MTU), +#endif + +#if CFG_TUD_VENDOR + // Interface number, string index, EP Out & IN address, EP size + TUD_VENDOR_DESCRIPTOR(ITF_VENDOR, STRID_VENDOR_INTERFACE, EPNUM_0_VENDOR, 0x80 | EPNUM_0_VENDOR, 64), +#endif + +#if CFG_TUD_VENDOR > 1 + // Interface number, string index, EP Out & IN address, EP size + TUD_VENDOR_DESCRIPTOR(ITF_VENDOR1, STRID_VENDOR_INTERFACE, EPNUM_1_VENDOR, 0x80 | EPNUM_1_VENDOR, 64), +#endif + +#if CONFIG_TINYUSB_UVC_ENABLED + // UVC Camera 1 +#if CONFIG_TINYUSB_UVC_CAM1_BULK_MODE +#if CONFIG_TINYUSB_UVC_CAM1_MULTI_FRAMESIZE +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#else // Single framesize +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_H264_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#else + TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#endif // Multi vs single framesize +#else // Isochronous mode +#if CONFIG_TINYUSB_UVC_CAM1_MULTI_FRAMESIZE +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#else // Single framesize +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_H264(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#else + TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#endif // Multi vs single framesize +#endif // Bulk vs Iso + +#if CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM + // UVC Camera 2 +#if CONFIG_TINYUSB_UVC_CAM2_BULK_MODE +#if CONFIG_TINYUSB_UVC_CAM2_MULTI_FRAMESIZE +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#else // Single framesize +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_H264_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#else + TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#endif // Multi vs single framesize +#else // Isochronous mode +#if CONFIG_TINYUSB_UVC_CAM2_MULTI_FRAMESIZE +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#else // Single framesize +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_H264(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#else + TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#endif // Multi vs single framesize +#endif // Bulk vs Iso +#endif // TWO_CAM +#endif // UVC_ENABLED +}; + +#if (TUD_OPT_HIGH_SPEED) +uint8_t const descriptor_hs_cfg_default[] = { + // Configuration number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + +#if CFG_TUD_CDC + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, STRID_CDC_INTERFACE, 0x80 | EPNUM_0_CDC_NOTIF, 8, EPNUM_0_CDC, 0x80 | EPNUM_0_CDC, 512), +#endif + +#if CFG_TUD_CDC > 1 + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC1, STRID_CDC_INTERFACE, 0x80 | EPNUM_1_CDC_NOTIF, 8, EPNUM_1_CDC, 0x80 | EPNUM_1_CDC, 512), +#endif + +#if CFG_TUD_MSC + // Interface number, string index, EP Out & EP In address, EP size + TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, STRID_MSC_INTERFACE, EPNUM_MSC, 0x80 | EPNUM_MSC, 512), +#endif + +#if CFG_TUD_NCM + // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. + TUD_CDC_NCM_DESCRIPTOR(ITF_NUM_NET, STRID_NET_INTERFACE, STRID_MAC, (0x80 | EPNUM_NET_NOTIF), 64, EPNUM_NET_DATA, (0x80 | EPNUM_NET_DATA), 512, CFG_TUD_NET_MTU), +#endif + +#if CFG_TUD_VENDOR + // Interface number, string index, EP Out & IN address, EP size + TUD_VENDOR_DESCRIPTOR(ITF_VENDOR, STRID_VENDOR_INTERFACE, EPNUM_0_VENDOR, 0x80 | EPNUM_0_VENDOR, 512), +#endif + +#if CFG_TUD_VENDOR > 1 + // Interface number, string index, EP Out & IN address, EP size + TUD_VENDOR_DESCRIPTOR(ITF_VENDOR1, STRID_VENDOR_INTERFACE, EPNUM_1_VENDOR, 0x80 | EPNUM_1_VENDOR, 512), +#endif + +#if CONFIG_TINYUSB_UVC_ENABLED + // UVC Camera 1 +#if CONFIG_TINYUSB_UVC_CAM1_BULK_MODE +#if CONFIG_TINYUSB_UVC_CAM1_MULTI_FRAMESIZE +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#else // Single framesize +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_H264_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#else + TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#endif // Multi vs single framesize +#else // Isochronous mode +#if CONFIG_TINYUSB_UVC_CAM1_MULTI_FRAMESIZE +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#else // Single framesize +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 + TUD_VIDEO_CAPTURE_DESCRIPTOR_H264(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#else + TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL, 0x80 | EPNUM_CAM1_VIDEO_IN, UVC_CAM1_FRAME_WIDTH, UVC_CAM1_FRAME_HEIGHT, UVC_CAM1_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#endif // Multi vs single framesize +#endif // Bulk vs Iso + +#if CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM + // UVC Camera 2 +#if CONFIG_TINYUSB_UVC_CAM2_BULK_MODE +#if CONFIG_TINYUSB_UVC_CAM2_MULTI_FRAMESIZE +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#else // Single framesize +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_H264_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#else + TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#endif // Multi vs single framesize +#else // Isochronous mode +#if CONFIG_TINYUSB_UVC_CAM2_MULTI_FRAMESIZE +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_MJPEG(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MULTI_H264(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#else // Single framesize +#if CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#elif CONFIG_TINYUSB_UVC_FORMAT_H264_CAM2 + TUD_VIDEO_CAPTURE_DESCRIPTOR_H264(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#else + TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(STRID_UVC_INTERFACE, ITF_NUM_VIDEO_CONTROL_2, 0x80 | EPNUM_CAM2_VIDEO_IN, UVC_CAM2_FRAME_WIDTH, UVC_CAM2_FRAME_HEIGHT, UVC_CAM2_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), +#endif +#endif // Multi vs single framesize +#endif // Bulk vs Iso +#endif // TWO_CAM +#endif // UVC_ENABLED +}; +#endif // TUD_OPT_HIGH_SPEED + +#if CFG_TUD_NCM +uint8_t tusb_get_mac_string_id(void) +{ + return STRID_MAC; +} +#endif +/* End of Kconfig driven Descriptor */ diff --git a/components/esp_tinyusb/vfs_tinyusb.c b/components/esp_tinyusb/vfs_tinyusb.c new file mode 100644 index 0000000..a011664 --- /dev/null +++ b/components/esp_tinyusb/vfs_tinyusb.c @@ -0,0 +1,320 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "esp_attr.h" +#include "esp_log.h" +#include "esp_vfs.h" +#include "esp_vfs_dev.h" +#include "tinyusb.h" +#include "tinyusb_cdc_acm.h" +#include "vfs_tinyusb.h" +#include "esp_idf_version.h" +#include "sdkconfig.h" + +const static char *TAG = "tusb_vfs"; + +// Token signifying that no character is available +#define NONE -1 + +#define FD_CHECK(fd, ret_val) do { \ + if ((fd) != 0) { \ + errno = EBADF; \ + return (ret_val); \ + } \ + } while (0) + + + +#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF +# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_CRLF +#elif CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR +# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_CR +#else +# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_LF +#endif + +#if CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF +# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_CRLF +#elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR +# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_CR +#else +# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_LF +#endif + +typedef struct { + _lock_t write_lock; + _lock_t read_lock; + esp_line_endings_t tx_mode; // Newline conversion mode when transmitting + esp_line_endings_t rx_mode; // Newline conversion mode when receiving + uint32_t flags; + char vfs_path[VFS_TUSB_MAX_PATH]; + int cdc_intf; +} vfs_tinyusb_t; + +static vfs_tinyusb_t s_vfstusb; + + +static esp_err_t apply_path(char const *path) +{ + if (path == NULL) { + path = VFS_TUSB_PATH_DEFAULT; + } + + size_t path_len = strlen(path) + 1; + if (path_len > VFS_TUSB_MAX_PATH) { + ESP_LOGE(TAG, "The path is too long; maximum is %d characters", VFS_TUSB_MAX_PATH); + return ESP_ERR_INVALID_ARG; + } + strncpy(s_vfstusb.vfs_path, path, (VFS_TUSB_MAX_PATH - 1)); + ESP_LOGV(TAG, "Path is set to `%s`", path); + return ESP_OK; +} + +/** + * @brief Fill s_vfstusb + * + * @param cdc_intf - interface of tusb for registration + * @param path - a path where the CDC will be registered + * @return esp_err_t ESP_OK or ESP_ERR_INVALID_ARG + */ +static esp_err_t vfstusb_init(int cdc_intf, char const *path) +{ + s_vfstusb.cdc_intf = cdc_intf; + s_vfstusb.tx_mode = DEFAULT_TX_MODE; + s_vfstusb.rx_mode = DEFAULT_RX_MODE; + + return apply_path(path); +} + +/** + * @brief Clear s_vfstusb to default values + */ +static void vfstusb_deinit(void) +{ + _lock_close(&(s_vfstusb.write_lock)); + _lock_close(&(s_vfstusb.read_lock)); + memset(&s_vfstusb, 0, sizeof(s_vfstusb)); +} + +static int tusb_open(const char *path, int flags, int mode) +{ + (void) mode; + (void) path; + s_vfstusb.flags = flags | O_NONBLOCK; // for now only non-blocking mode is implemented + return 0; +} + +static ssize_t tusb_write(int fd, const void *data, size_t size) +{ + FD_CHECK(fd, -1); + size_t written_sz = 0; + const char *data_c = (const char *)data; + _lock_acquire(&(s_vfstusb.write_lock)); + for (size_t i = 0; i < size; i++) { + int c = data_c[i]; + if (c != '\n') { + if (!tinyusb_cdcacm_write_queue_char(s_vfstusb.cdc_intf, c)) { + break; // can't write anymore + } + } else { + if (s_vfstusb.tx_mode == ESP_LINE_ENDINGS_CRLF || s_vfstusb.tx_mode == ESP_LINE_ENDINGS_CR) { + char cr = '\r'; + if (!tinyusb_cdcacm_write_queue_char(s_vfstusb.cdc_intf, cr)) { + break; // can't write anymore + } + } + if (s_vfstusb.tx_mode == ESP_LINE_ENDINGS_CRLF || s_vfstusb.tx_mode == ESP_LINE_ENDINGS_LF) { + char lf = '\n'; + if (!tinyusb_cdcacm_write_queue_char(s_vfstusb.cdc_intf, lf)) { + break; // can't write anymore + } + } + } + written_sz++; + } + tud_cdc_n_write_flush(s_vfstusb.cdc_intf); + _lock_release(&(s_vfstusb.write_lock)); + return written_sz; +} + +static int tusb_close(int fd) +{ + FD_CHECK(fd, -1); + return 0; +} + +static ssize_t tusb_read(int fd, void *data, size_t size) +{ + FD_CHECK(fd, -1); + char *data_c = (char *) data; + size_t received = 0; + _lock_acquire(&(s_vfstusb.read_lock)); + + if (tud_cdc_n_available(s_vfstusb.cdc_intf) == 0) { + goto finish; + } + while (received < size) { + int c = tud_cdc_n_read_char(s_vfstusb.cdc_intf); + if ( c == NONE) { // if data ends + break; + } + + // Handle line endings. From configured mode -> LF mode + if (s_vfstusb.rx_mode == ESP_LINE_ENDINGS_CR) { + // Change CRs to newlines + if (c == '\r') { + c = '\n'; + } + } else if (s_vfstusb.rx_mode == ESP_LINE_ENDINGS_CRLF) { + if (c == '\r') { + uint8_t next_char = NONE; + // Check if next char is newline. If yes, we got CRLF sequence + tud_cdc_n_peek(s_vfstusb.cdc_intf, &next_char); + if (next_char == '\n') { + c = tud_cdc_n_read_char(s_vfstusb.cdc_intf); // Remove '\n' from the fifo + } + } + } + + data_c[received] = (char) c; + ++received; + if (c == '\n') { + break; + } + } +finish: + _lock_release(&(s_vfstusb.read_lock)); + if (received > 0) { + return received; + } + errno = EWOULDBLOCK; + return -1; +} + +static int tusb_fstat(int fd, struct stat *st) +{ + FD_CHECK(fd, -1); + memset(st, 0, sizeof(*st)); + st->st_mode = S_IFCHR; + return 0; +} + +static int tusb_fcntl(int fd, int cmd, int arg) +{ + FD_CHECK(fd, -1); + int result = 0; + switch (cmd) { + case F_GETFL: + result = s_vfstusb.flags; + break; + case F_SETFL: + s_vfstusb.flags = arg; + break; + default: + result = -1; + errno = ENOSYS; + break; + } + return result; +} + +esp_err_t esp_vfs_tusb_cdc_unregister(char const *path) +{ + ESP_LOGD(TAG, "Unregistering CDC-VFS driver"); + int res; + + if (path == NULL) { // NULL means using the default path for unregistering: VFS_TUSB_PATH_DEFAULT + path = VFS_TUSB_PATH_DEFAULT; + } + res = strcmp(s_vfstusb.vfs_path, path); + + if (res) { + res = ESP_ERR_INVALID_ARG; + ESP_LOGE(TAG, "There is no CDC-VFS driver registered to path '%s' (err: 0x%x)", path, res); + return res; + } + + res = esp_vfs_unregister(s_vfstusb.vfs_path); + if (res != ESP_OK) { + ESP_LOGE(TAG, "Can't unregister CDC-VFS driver from '%s' (err: 0x%x)", s_vfstusb.vfs_path, res); + } else { + ESP_LOGD(TAG, "Unregistered CDC-VFS driver"); + vfstusb_deinit(); + } + return res; +} + +esp_err_t esp_vfs_tusb_cdc_register(int cdc_intf, char const *path) +{ + ESP_LOGD(TAG, "Registering CDC-VFS driver"); + int res; + if (!tinyusb_cdcacm_initialized(cdc_intf)) { + ESP_LOGE(TAG, "TinyUSB CDC#%d is not initialized", cdc_intf); + return ESP_ERR_INVALID_STATE; + } + + res = vfstusb_init(cdc_intf, path); + if (res != ESP_OK) { + return res; + } + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) + static const esp_vfs_fs_ops_t fs_ops = { + .close = &tusb_close, + .fcntl = &tusb_fcntl, + .fstat = &tusb_fstat, + .open = &tusb_open, + .read = &tusb_read, + .write = &tusb_write, + }; + + res = esp_vfs_register_fs(s_vfstusb.vfs_path, &fs_ops, ESP_VFS_FLAG_STATIC, NULL); + +#else + esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_DEFAULT, + .close = &tusb_close, + .fcntl = &tusb_fcntl, + .fstat = &tusb_fstat, + .open = &tusb_open, + .read = &tusb_read, + .write = &tusb_write, + }; + + res = esp_vfs_register(s_vfstusb.vfs_path, &vfs, NULL); + +#endif + + if (res != ESP_OK) { + ESP_LOGE(TAG, "Can't register CDC-VFS driver (err: %x)", res); + } else { + ESP_LOGD(TAG, "CDC-VFS registered (%s)", s_vfstusb.vfs_path); + } + return res; +} + +void esp_vfs_tusb_cdc_set_rx_line_endings(esp_line_endings_t mode) +{ + _lock_acquire(&(s_vfstusb.read_lock)); + s_vfstusb.rx_mode = mode; + _lock_release(&(s_vfstusb.read_lock)); +} + +void esp_vfs_tusb_cdc_set_tx_line_endings(esp_line_endings_t mode) +{ + _lock_acquire(&(s_vfstusb.write_lock)); + s_vfstusb.tx_mode = mode; + _lock_release(&(s_vfstusb.write_lock)); +} diff --git a/data/settings_default.json b/data/settings_default.json new file mode 100644 index 0000000..6dbb299 --- /dev/null +++ b/data/settings_default.json @@ -0,0 +1,93 @@ +{ + "version" : 1, + "led-flash" : { + "enabled" : true, + "power" : 100 + }, + "lepton" : { + "emissivity" : [ + { + "name" : "Concrete", + "value" : 0.92 + }, + { + "name" : "Human Skin", + "value" : 0.98 + }, + { + "name" : "Water", + "value" : 0.96 + }, + { + "name" : "Asphalt", + "value" : 0.95 + } + ], + "roi" : [ + { + "name" : "spotmeter", + "x" : 60, + "y" : 60, + "width" : 40, + "height" : 40 + }, + { + "name" : "scene", + "x" : 0, + "y" : 0, + "width" : 160, + "height" : 120 + }, + { + "name" : "agc", + "x" : 0, + "y" : 0, + "width" : 160, + "height" : 120 + }, + { + "name" : "video-focus", + "x" : 1, + "y" : 1, + "width" : 157, + "height" : 117 + } + ] + }, + "wifi" : { + "ssid" : "", + "password" : "", + "maxRetries" : 5, + "retryInterval" : 2000, + "autoConnect" : true + }, + "provisioning" : { + "name" : "PyroVision-PROV", + "timeout" : 300 + }, + "display" : { + "brightness" : 80, + "timeout" : 60 + }, + "system" : { + "timezone" : "CET-1CEST,M3.5.0,M10.5.0/3", + "ntp-server" : "pool.ntp.org", + "imageFormat" : "JPEG", + "jpegQuality" : 80 + }, + "http-server" : { + "port" : 80, + "ws-ping-interval" : 30, + "max-clients" : 4, + "enable-cors" : true, + "api-key" : "" + }, + "visa-server": { + "port" : 5025, + "timeout" : 5000 + }, + "usb": { + "msc-enabled" : false, + "uvc-enabled" : false + } +} diff --git a/docs/DeviceManager.adoc b/docs/DeviceManager.adoc new file mode 100644 index 0000000..fcb3e31 --- /dev/null +++ b/docs/DeviceManager.adoc @@ -0,0 +1,855 @@ += PyroVision Device Manager Documentation +Daniel Kampert +v1.0, 2026-01-14 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The Device Manager is the central hardware abstraction component for the PyroVision firmware. It initializes and manages all peripheral hardware interfaces including I2C, SPI, ADC, RTC (Real-Time Clock), and port expander. The Device Manager provides a unified API for accessing hardware resources throughout the application. + +=== Key Features + +* *Bus Management*: Centralized I2C and SPI bus initialization +* *Hardware Abstraction*: Unified interface for all peripherals +* *Battery Monitoring*: ADC-based battery voltage and percentage measurement +* *RTC Integration*: Real-time clock access for Time Manager +* *Port Expander*: GPIO expansion via I2C for additional pins +* *Power Management*: Controlled battery voltage measurement +* *Resource Sharing*: Safe sharing of I2C/SPI buses across components + +--- + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Manager/Devices/ +├── devicesManager.h # Public API +├── devicesManager.cpp # Implementation +├── devices.h # Device definitions +├── I2C/ # I2C master driver +│ ├── i2c.h +│ └── i2c.cpp +├── SPI/ # SPI master driver +│ ├── spi.h +│ └── spi.cpp +├── ADC/ # ADC driver for battery +│ ├── adc.h +│ └── adc.cpp +├── RTC/ # RTC driver (DS3231/DS1307) +│ ├── rtc.h +│ └── rtc.cpp +└── PortExpander/ # I2C port expander + ├── portexpander.h + └── portexpander.cpp +---- + +=== Hardware Buses + +==== I2C Bus + +* *Port*: Configured via `CONFIG_DEVICES_I2C_HOST` +* *SDA Pin*: Configured via `CONFIG_DEVICES_I2C_SDA` +* *SCL Pin*: Configured via `CONFIG_DEVICES_I2C_SCL` +* *Clock Source*: Default (typically APB) +* *Pull-ups*: External (internal pull-ups disabled) +* *Glitch Filter*: 7 cycles + +*Connected Devices*: + +* RTC (DS3231 or DS1307) +* Port Expander (PCF8574/PCF8575 or similar) +* Other I2C peripherals + +==== SPI Bus (SPI3) + +* *Host*: SPI3_HOST +* *MOSI Pin*: `CONFIG_SPI_MOSI` +* *MISO Pin*: `CONFIG_SPI_MISO` +* *SCLK Pin*: `CONFIG_SPI_SCLK` +* *Max Transfer Size*: `CONFIG_SPI_TRANSFER_SIZE` +* *DMA*: Auto-selected channel + +*Connected Devices*: + +* ILI9341 LCD Display +* XPT2046 Touch Controller +* SD Card (optional) + +==== ADC + +* *Purpose*: Battery voltage measurement +* *Channel*: Configured for battery input +* *Resolution*: 12-bit (configurable) +* *Attenuation*: Configured for voltage range + +--- + +== Internal State + +=== State Structure + +[source,cpp] +---- +typedef struct { + bool initialized; // Manager initialization status + i2c_master_dev_handle_t RTC_Handle; // RTC device handle + i2c_master_bus_handle_t I2C_Bus_Handle; // I2C bus handle +} Devices_Manager_State_t; +---- + +The state is maintained privately within the module and accessed only through the public API. + +--- + +== API Reference + +=== Initialization and Lifecycle + +==== `DevicesManager_Init()` + +[source,cpp] +---- +esp_err_t DevicesManager_Init(void); +---- + +*Description*: Initializes all peripheral hardware interfaces. + +*Initialization Sequence*: + +1. Initialize I2C master bus +2. Initialize SPI3 master bus +3. Initialize ADC for battery measurement +4. Initialize port expander via I2C +5. Wait 2 seconds for camera power stabilization +6. Initialize RTC via I2C +7. Disable battery voltage measurement (default off) + +*Return Value*: + +* `ESP_OK` on success +* `ESP_FAIL` if any peripheral initialization fails + +*Important Notes*: + +* Must be called before any other device operations +* Automatically waits for Lepton camera stabilization (~2 seconds) +* Battery measurement is disabled by default to save power +* All buses are configured from menuconfig settings + +*Example*: + +[source,cpp] +---- +void app_main(void) +{ + ESP_LOGI(TAG, "Initializing Device Manager..."); + + if (DevicesManager_Init() != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize devices!"); + return; + } + + ESP_LOGI(TAG, "All devices initialized successfully"); + + // Continue with application initialization... +} +---- + +==== `DevicesManager_Deinit()` + +[source,cpp] +---- +esp_err_t DevicesManager_Deinit(void); +---- + +*Description*: Deinitializes all peripheral hardware and releases resources. + +*Deinitialization Sequence*: + +1. Disable battery voltage measurement +2. Deinitialize ADC +3. Deinitialize RTC +4. Deinitialize port expander +5. Deinitialize I2C bus +6. Deinitialize SPI3 bus + +*Return Value*: + +* `ESP_OK` on success +* Error code from bus deinitialization (if any) + +*Example*: + +[source,cpp] +---- +void shutdown_system(void) +{ + ESP_LOGI(TAG, "Shutting down device manager..."); + DevicesManager_Deinit(); + + // Continue with full system shutdown... +} +---- + +--- + +=== Bus Access + +==== `DevicesManager_GetI2CBusHandle()` + +[source,cpp] +---- +i2c_master_bus_handle_t DevicesManager_GetI2CBusHandle(void); +---- + +*Description*: Gets the I2C bus handle for adding additional I2C devices. + +*Return Value*: + +* I2C bus handle on success +* `NULL` if Device Manager not initialized + +*Use Case*: When additional I2C devices need to be added to the bus dynamically. + +*Example*: + +[source,cpp] +---- +// Add a custom I2C sensor +i2c_master_bus_handle_t i2c_bus = DevicesManager_GetI2CBusHandle(); +if (i2c_bus != NULL) { + i2c_device_config_t sensor_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = 0x48, // Custom sensor address + .scl_speed_hz = 100000, + }; + + i2c_master_dev_handle_t sensor_handle; + ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus, &sensor_cfg, &sensor_handle)); + + // Use sensor_handle for I2C transactions... +} +---- + +--- + +=== Hardware Access + +==== `DevicesManager_GetBatteryVoltage()` + +[source,cpp] +---- +esp_err_t DevicesManager_GetBatteryVoltage(int *p_Voltage, int *p_Percentage); +---- + +*Description*: Measures battery voltage and calculates remaining percentage. + +*Process*: + +1. Enables battery voltage measurement circuit (via port expander) +2. Reads ADC value from battery measurement pin +3. Calculates actual voltage using voltage divider compensation +4. Calculates percentage based on LiPo voltage curve +5. Disables battery voltage measurement circuit + +*Parameters*: + +* `p_Voltage`: Pointer to store voltage in millivolts (mV) +* `p_Percentage`: Pointer to store percentage (0-100%) + +*Voltage Calculation*: + +[source] +---- +Actual Voltage = ADC Reading × (R1 + R2) / R2 + +R1 = 10kΩ (upper resistor) +R2 = 3.3kΩ (lower resistor) +---- + +*Percentage Calculation*: + +[source] +---- +Battery Range: 3.3V (0%) to 4.2V (100%) + +Percentage = (Voltage - 3300) × 100 / (4200 - 3300) + = (Voltage - 3300) × 100 / 900 + +Clamped to [0, 100] range +---- + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_STATE` if Device Manager not initialized +* `ESP_ERR_INVALID_ARG` if parameters are NULL +* Error code from ADC read operation + +*Example*: + +[source,cpp] +---- +int voltage_mv, percentage; + +if (DevicesManager_GetBatteryVoltage(&voltage_mv, &percentage) == ESP_OK) { + ESP_LOGI(TAG, "Battery: %d mV (%d%%)", voltage_mv, percentage); + + if (percentage < 10) { + ESP_LOGW(TAG, "Low battery warning!"); + display_low_battery_icon(); + } +} else { + ESP_LOGE(TAG, "Failed to read battery voltage"); +} +---- + +==== `DevicesManager_GetRTCHandle()` + +[source,cpp] +---- +esp_err_t DevicesManager_GetRTCHandle(i2c_master_dev_handle_t *p_Handle); +---- + +*Description*: Gets the RTC device handle for Time Manager integration. + +*Parameters*: + +* `p_Handle`: Pointer to store the RTC device handle + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_STATE` if Device Manager not initialized +* `ESP_ERR_INVALID_ARG` if `p_Handle` is NULL + +*Use Case*: Time Manager requires the RTC handle for time synchronization and backup. + +*Example*: + +[source,cpp] +---- +i2c_master_dev_handle_t rtc_handle; + +if (DevicesManager_GetRTCHandle(&rtc_handle) == ESP_OK) { + // Pass RTC handle to Time Manager + TimeManager_Init(&rtc_handle); +} else { + ESP_LOGW(TAG, "RTC not available, Time Manager will use SNTP only"); + TimeManager_Init(NULL); +} +---- + +--- + +== Usage + +=== Basic Initialization Workflow + +[source,cpp] +---- +#include "devicesManager.h" +#include "timeManager.h" + +void app_main(void) +{ + // Initialize NVS (required for WiFi) + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + // Initialize event loop + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + // Initialize Settings Manager + ESP_ERROR_CHECK(SettingsManager_Init()); + + // Initialize Device Manager (MUST be before Time Manager) + ESP_ERROR_CHECK(DevicesManager_Init()); + + // Get RTC handle for Time Manager + i2c_master_dev_handle_t rtc_handle; + ESP_ERROR_CHECK(DevicesManager_GetRTCHandle(&rtc_handle)); + + // Initialize Time Manager with RTC support + ESP_ERROR_CHECK(TimeManager_Init(&rtc_handle)); + + // Continue with other initializations... +} +---- + +--- + +=== Battery Monitoring Task + +[source,cpp] +---- +void battery_monitor_task(void *pvParameters) +{ + int voltage_mv, percentage; + int last_percentage = -1; + + while (1) { + if (DevicesManager_GetBatteryVoltage(&voltage_mv, &percentage) == ESP_OK) { + // Only update display if percentage changed + if (percentage != last_percentage) { + ESP_LOGI(TAG, "Battery: %d mV (%d%%)", voltage_mv, percentage); + update_battery_indicator(percentage); + last_percentage = percentage; + } + + // Check critical battery level + if (percentage <= 5) { + ESP_LOGE(TAG, "Critical battery level! Shutting down..."); + initiate_safe_shutdown(); + break; + } + + // Low battery warning + if (percentage <= 15) { + ESP_LOGW(TAG, "Low battery warning"); + enable_power_saving_mode(); + } + } + + // Check battery every 30 seconds + vTaskDelay(pdMS_TO_TICKS(30000)); + } + + vTaskDelete(NULL); +} + +// Create task +xTaskCreate(battery_monitor_task, "BatteryMonitor", 2048, NULL, 5, NULL); +---- + +--- + +=== Adding Custom I2C Devices + +[source,cpp] +---- +typedef struct { + i2c_master_dev_handle_t handle; + uint8_t address; +} CustomSensor_t; + +esp_err_t CustomSensor_Init(CustomSensor_t *p_Sensor, uint8_t address) +{ + i2c_master_bus_handle_t i2c_bus = DevicesManager_GetI2CBusHandle(); + if (i2c_bus == NULL) { + ESP_LOGE(TAG, "I2C bus not available!"); + return ESP_ERR_INVALID_STATE; + } + + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = address, + .scl_speed_hz = 100000, + }; + + esp_err_t ret = i2c_master_bus_add_device(i2c_bus, &dev_cfg, &p_Sensor->handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to add I2C device: %d", ret); + return ret; + } + + p_Sensor->address = address; + ESP_LOGI(TAG, "Custom sensor initialized at address 0x%02X", address); + + return ESP_OK; +} + +esp_err_t CustomSensor_Read(CustomSensor_t *p_Sensor, uint8_t reg, uint8_t *p_Data, size_t len) +{ + return i2c_master_transmit_receive(p_Sensor->handle, + ®, 1, + p_Data, len, + 1000); +} +---- + +--- + +=== Battery Voltage Calculation Example + +For understanding the voltage measurement: + +[source,cpp] +---- +/* + * Hardware Setup: + * + * VBAT (4.2V max) + * | + * R1 (10kΩ) + * | + * +---- ADC Input (measures this point) + * | + * R2 (3.3kΩ) + * | + * GND + * + * Voltage at ADC = VBAT × R2 / (R1 + R2) + * = VBAT × 3300 / 13300 + * = VBAT × 0.248 + * + * So for 4.2V battery: ADC sees ~1.04V (safe for ESP32 ADC) + */ + +// Internal calculation in DevicesManager_GetBatteryVoltage(): +int adc_voltage_mv = 1040; // Example: ADC reads 1.04V + +// Compensate for voltage divider +int actual_voltage_mv = adc_voltage_mv * (10000 + 3300) / 3300; +// actual_voltage_mv = 1040 × 13300 / 3300 = 4193 mV ≈ 4.2V + +// Calculate percentage (LiPo: 3.3V = empty, 4.2V = full) +int percentage = (actual_voltage_mv - 3300) * 100 / (4200 - 3300); +// percentage = (4193 - 3300) × 100 / 900 = 99% +---- + +--- + +== Configuration + +=== Menuconfig Settings + +Device Manager uses several menuconfig settings (defined in `Kconfig.projbuild` or component configs): + +==== I2C Configuration + +[source] +---- +CONFIG_DEVICES_I2C_HOST # I2C port number (0 or 1) +CONFIG_DEVICES_I2C_SDA # SDA GPIO pin +CONFIG_DEVICES_I2C_SCL # SCL GPIO pin +---- + +*Example*: +[source] +---- +CONFIG_DEVICES_I2C_HOST=1 +CONFIG_DEVICES_I2C_SDA=21 +CONFIG_DEVICES_I2C_SCL=22 +---- + +==== SPI Configuration + +[source] +---- +CONFIG_SPI_MOSI # MOSI GPIO pin +CONFIG_SPI_MISO # MISO GPIO pin +CONFIG_SPI_SCLK # Clock GPIO pin +CONFIG_SPI_TRANSFER_SIZE # Max transfer size (bytes) +---- + +*Example*: +[source] +---- +CONFIG_SPI_MOSI=23 +CONFIG_SPI_MISO=19 +CONFIG_SPI_SCLK=18 +CONFIG_SPI_TRANSFER_SIZE=32768 +---- + +==== Battery Voltage Divider + +If you use different resistor values, update in `devicesManager.cpp`: + +[source,cpp] +---- +#define BATTERY_R1 10000 // Upper resistor (Ω) +#define BATTERY_R2 3300 // Lower resistor (Ω) +#define BATTERY_MIN_VOLTAGE 3300 // Empty voltage (mV) +#define BATTERY_MAX_VOLTAGE 4200 // Full voltage (mV) +---- + +--- + +== Hardware Dependencies + +=== Required Components + +[cols="2,3,2"] +|=== +|Component |Purpose |Interface + +|ESP32-S3 +|Main controller +|N/A + +|DS3231/DS1307 +|Real-time clock with battery backup +|I2C + +|PCF8574/PCF8575 +|GPIO port expander +|I2C + +|ILI9341 +|LCD display +|SPI + +|XPT2046 +|Touch controller +|SPI + +|Voltage Divider +|Battery voltage measurement (10kΩ + 3.3kΩ) +|ADC + +|Lepton Camera +|Thermal imaging sensor +|SPI (via separate component) +|=== + +=== Pin Assignments + +Typical pin configuration (adjust via menuconfig): + +[cols="2,2,3"] +|=== +|Function |Pin |Notes + +|I2C SDA +|GPIO 21 +|External 4.7kΩ pull-up recommended + +|I2C SCL +|GPIO 22 +|External 4.7kΩ pull-up recommended + +|SPI MOSI +|GPIO 23 +|Shared by LCD, Touch, SD + +|SPI MISO +|GPIO 19 +|Shared by LCD, Touch, SD + +|SPI CLK +|GPIO 18 +|Shared by LCD, Touch, SD + +|Battery ADC +|GPIO 34-39 +|ADC1 channel (varies by design) +|=== + +--- + +== Best Practices + +=== DO's + +* ✓ Initialize Device Manager early (after NVS, before Time Manager) +* ✓ Check return values from all API calls +* ✓ Use `DevicesManager_GetI2CBusHandle()` for additional I2C devices +* ✓ Monitor battery periodically (not continuously to save power) +* ✓ Implement low battery warnings and automatic shutdown +* ✓ Use external pull-ups on I2C bus for reliability + +=== DON'Ts + +* ✗ Don't directly access hardware buses without Device Manager +* ✗ Don't call device APIs before `DevicesManager_Init()` +* ✗ Don't continuously poll battery voltage (wastes power) +* ✗ Don't exceed I2C bus speed for slowest device +* ✗ Don't forget 2-second camera stabilization is automatic + +--- + +== Troubleshooting + +=== I2C Communication Failures + +*Symptom*: Device Manager initialization fails with I2C errors + +*Possible Causes*: + +* Missing external pull-ups (4.7kΩ recommended) +* Wrong GPIO pins configured +* Device address conflict +* Faulty hardware or loose connections + +*Solution*: + +[source,cpp] +---- +// Enable I2C debugging +esp_log_level_set("i2c", ESP_LOG_DEBUG); + +// Check I2C bus with scanner +void i2c_scanner(void) +{ + i2c_master_bus_handle_t bus = DevicesManager_GetI2CBusHandle(); + ESP_LOGI(TAG, "Scanning I2C bus..."); + + for (uint8_t addr = 1; addr < 127; addr++) { + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = addr, + .scl_speed_hz = 100000, + }; + + i2c_master_dev_handle_t temp_handle; + if (i2c_master_bus_add_device(bus, &dev_cfg, &temp_handle) == ESP_OK) { + uint8_t dummy; + if (i2c_master_receive(temp_handle, &dummy, 1, 100) == ESP_OK) { + ESP_LOGI(TAG, "Found device at 0x%02X", addr); + } + i2c_master_bus_rm_device(temp_handle); + } + } +} +---- + +--- + +=== RTC Not Found + +*Symptom*: Warning "Failed to initialize RTC" + +*Possible Causes*: + +* RTC not connected +* Wrong I2C address +* Faulty RTC module +* Insufficient power + +*Solution*: + +* Verify RTC is DS3231 or DS1307 compatible +* Check I2C address (typically 0x68) +* Verify VCC (3.3V or 5V) and GND connections +* Check battery in RTC module + +Time Manager will still work using SNTP only if RTC fails. + +--- + +=== Battery Voltage Reads Zero + +*Symptom*: Battery voltage always returns 0 or very low values + +*Possible Causes*: + +* Port expander not enabling measurement circuit +* Wrong ADC channel configured +* Voltage divider resistors incorrect +* ADC pin not connected + +*Solution*: + +[source,cpp] +---- +// Test ADC directly +int voltage, percentage; +for (int i = 0; i < 10; i++) { + DevicesManager_GetBatteryVoltage(&voltage, &percentage); + ESP_LOGI(TAG, "Test %d: %d mV (%d%%)", i, voltage, percentage); + vTaskDelay(pdMS_TO_TICKS(500)); +} + +// Check if port expander is working +PortExpander_EnableBatteryVoltage(true); +vTaskDelay(pdMS_TO_TICKS(100)); +// Manually read ADC here +PortExpander_EnableBatteryVoltage(false); +---- + +--- + +=== SPI Display Issues + +*Symptom*: Display not working or garbled output + +*Possible Causes*: + +* Wrong SPI pins +* Insufficient DMA buffer size +* CS/DC/RST pins misconfigured (not managed by Device Manager) + +*Solution*: + +* Verify `CONFIG_SPI_TRANSFER_SIZE` is large enough (≥32768) +* Check SPI3_HOST is not used by other peripherals +* Increase `CONFIG_SPI_TRANSFER_SIZE` if needed for large displays + +--- + +== Technical Details + +=== I2C Bus Sharing + +The I2C bus is shared among multiple devices: + +* RTC (typically 0x68) +* Port Expander (typically 0x20-0x27) +* Additional sensors (if added) + +All devices use the same bus handle, and the ESP-IDF I2C master driver handles arbitration and locking automatically. + +=== Power Sequencing + +Device Manager ensures proper power-up sequence: + +1. Initialize I2C/SPI buses (fast, <10ms) +2. Initialize ADC and Port Expander +3. **Wait 2 seconds** for camera power stabilization +4. Initialize RTC (time-critical component last) + +This sequence ensures the Lepton camera has adequate boot time (~1.5 seconds required). + +=== Battery Percentage Accuracy + +The percentage calculation is linear and optimized for LiPo batteries: + +* Uses 3.3V as "empty" (safe cutoff for LiPo) +* Uses 4.2V as "full" (fully charged LiPo) +* Provides reasonable accuracy for most applications +* Does not account for battery aging or temperature + +For improved accuracy, implement a lookup table based on actual discharge curve. + +--- + +== Dependencies + +Device Manager depends on: + +* ESP-IDF I2C Master Driver (`driver/i2c_master.h`) +* ESP-IDF SPI Master Driver (`driver/spi_master.h`) +* ESP-IDF ADC Driver (`driver/adc.h`) +* FreeRTOS (`freertos/FreeRTOS.h`, `freertos/task.h`) + +Device Manager is required by: + +* Time Manager (needs RTC handle) +* GUI Task (needs SPI for display) +* Application tasks (needs battery monitoring) + +--- + +== License + +Copyright (C) Daniel Kampert, 2026 + +This software is licensed under the GNU General Public License v3.0. +Website: www.kampis-elektroecke.de + +--- + +== Contact + +Please report bugs and suggestions to: DanielKampert@kampis-elektroecke.de diff --git a/docs/DevicesTask.adoc b/docs/DevicesTask.adoc new file mode 100644 index 0000000..d3c5a56 --- /dev/null +++ b/docs/DevicesTask.adoc @@ -0,0 +1,217 @@ += PyroVision Devices Task Documentation +Daniel Kampert +v1.0, 2026-02-09 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The Devices Task monitors peripheral hardware including battery voltage (via ADC), RTC (Real-Time Clock), port expander, and other sensors. It performs periodic polling and posts events when significant changes occur. + +=== Key Features + +* *Battery Monitoring*: ADC-based voltage measurement +* *RTC Management*: Real-Time Clock synchronization +* *Port Expander*: GPIO expansion and monitoring +* *Sensor Polling*: Periodic updates of device states +* *Event Generation*: Notifications for battery low, RTC sync, etc. + +--- + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Tasks/Devices/ +├── devicesTask.h # Public API +└── devicesTask.cpp # Implementation +---- + +=== Task Architecture + +* *Task Name*: `"DevicesTask"` +* *Priority*: Low (typically `tskIDLE_PRIORITY + 1`) +* *Stack Size*: 4 KB +* *Core Affinity*: Any (typically Core 0) +* *Polling Interval*: 1 second (configurable) + +--- + +== Public API + +=== DevicesTask_Init() + +[source,c] +---- +esp_err_t DevicesTask_Init(void); +---- + +Initializes the devices task. + +**Return Values:** + +* `ESP_OK` - Initialization successful +* `ESP_ERR_NO_MEM` - Memory allocation failed +* `ESP_ERR_INVALID_STATE` - Already initialized + +--- + +=== DevicesTask_Deinit() + +[source,c] +---- +void DevicesTask_Deinit(void); +---- + +Deinitializes the devices task. + +--- + +=== DevicesTask_Start() + +[source,c] +---- +esp_err_t DevicesTask_Start(App_Context_t *p_AppContext); +---- + +Starts the devices monitoring task. + +**Parameters:** + +* `p_AppContext` - Pointer to application context + +**Return Values:** + +* `ESP_OK` - Task started +* `ESP_ERR_INVALID_ARG` - NULL pointer +* `ESP_ERR_INVALID_STATE` - Not initialized or already running + +--- + +=== DevicesTask_Stop() + +[source,c] +---- +esp_err_t DevicesTask_Stop(void); +---- + +Stops the devices task. + +--- + +=== DevicesTask_isRunning() + +[source,c] +---- +bool DevicesTask_isRunning(void); +---- + +Checks if the task is running. + +--- + +## Monitored Devices + +=== 1. Battery Monitor (ADC) + +**Functionality:** + +* Measures battery voltage via ADC +* Calculates battery percentage +* Detects low battery condition + +**Events:** + +* `DEVICE_EVENT_BATTERY_LOW` - Battery below threshold (e.g., 10%) +* `DEVICE_EVENT_BATTERY_CRITICAL` - Battery critically low (e.g., 5%) + +=== 2. RTC (Real-Time Clock) + +**Functionality:** + +* Monitors RTC synchronization status +* Checks RTC battery +* Validates time accuracy + +**Events:** + +* `DEVICE_EVENT_RTC_SYNC` - RTC synchronized with NTP +* `DEVICE_EVENT_RTC_BATTERY_LOW` - RTC backup battery low + +=== 3. Port Expander + +**Functionality:** + +* Reads GPIO states from I2C port expander +* Detects button presses +* Controls external LEDs + +--- + +== Task Operation + +=== Polling Loop + +[source,c] +---- +while (running) { + // 1. Read battery voltage + float BatteryVoltage = ADC_ReadVoltage(ADC_CHANNEL_BATTERY); + uint8_t BatteryPercent = CalculateBatteryPercent(BatteryVoltage); + + if (BatteryPercent < LOW_BATTERY_THRESHOLD) { + esp_event_post(DEVICE_EVENTS, DEVICE_EVENT_BATTERY_LOW, + &BatteryPercent, sizeof(BatteryPercent), 0); + } + + // 2. Check RTC status + if (RTC_IsTimeValid()) { + // Update system time if necessary + } + + // 3. Read port expander inputs + uint8_t GPIO_States = PortExpander_ReadAll(); + ProcessGPIOChanges(GPIO_States); + + vTaskDelay(pdMS_TO_TICKS(1000)); // 1 second interval +} +---- + +--- + +== Dependencies + +=== Required Managers + +* *Device Manager*: ADC, RTC, I2C port expander initialization +* *Settings Manager*: Device configuration (thresholds, intervals) + +--- + +## Configuration + +=== Battery Thresholds + +* Low Battery: 10% (configurable) +* Critical Battery: 5% (configurable) +* Voltage Range: 3.0V - 4.2V (Li-Po) + +=== Polling Interval + +* Default: 1000 ms +* Range: 100 ms - 10000 ms + +--- + +== Related Documentation + +* link:DeviceManager.adoc[Device Manager Documentation] + +--- + +**Last Updated**: February 9, 2026 + +**Maintainer**: Daniel Kampert (DanielKampert@kampis-elektroecke.de) diff --git a/docs/GUITask.adoc b/docs/GUITask.adoc new file mode 100644 index 0000000..a92b4a8 --- /dev/null +++ b/docs/GUITask.adoc @@ -0,0 +1,580 @@ += PyroVision GUI Task Documentation +Daniel Kampert +v1.0, 2026-02-09 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The GUI Task is responsible for rendering the user interface using LVGL (Light and Versatile Graphics Library) and managing user interactions with the touch display. It displays thermal camera frames, settings menus, system information, and handles touch input for ROI (Region of Interest) editing and navigation. + +=== Key Features + +* *LVGL Integration*: Hardware-accelerated graphics rendering +* *Thermal Image Display*: Real-time display of processed Lepton camera frames +* *Touch Interface*: Interactive settings and configuration screens +* *ROI Editor*: Visual overlay for adjusting spotmeter regions +* *Image Capture*: Save thermal images as BMP files to SD card +* *Multi-Screen Layout*: Main view, menu, settings, and info screens +* *Event-Driven UI*: Responsive to settings changes and system events + +--- + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Tasks/GUI/ +├── guiTask.h # Public API +├── guiTask.cpp # Main implementation +├── Export/ # Auto-generated from SquareLine Studio +│ ├── ui.h +│ ├── ui_events.h +│ ├── ui_events.cpp +│ ├── ui_helpers.h +│ └── screens/ +│ ├── ui_Info.h # Info screen +│ ├── ui_Main.h # Main thermal view +│ ├── ui_Menu.h # Settings menu +│ └── ui_Splash.h # Boot splash screen +├── UI/ # Custom UI components +│ ├── ui_Settings.h +│ ├── ui_Settings.cpp +│ └── Private/ +│ ├── ui_Settings_Events.h +│ └── ui_Settings_Events.cpp +└── Private/ + ├── guiHelper.h + └── guiHelper.cpp # Internal helper functions +---- + +=== Task Architecture + +The GUI Task runs as a FreeRTOS task with the following characteristics: + +* *Task Name*: `"GUITask"` +* *Priority*: Configurable (typically `tskIDLE_PRIORITY + 2`) +* *Stack Size*: 16 KB (to accommodate LVGL's stack requirements) +* *Core Affinity*: Typically pinned to Core 1 (APP CPU) + +=== LVGL Integration + +The task integrates LVGL with the ESP32-S3's display controller: + +* *Display Driver*: ILI9341 SPI LCD (320x240 pixels) +* *Touch Driver*: GT911 capacitive touch controller +* *Refresh Rate*: 30 FPS nominal +* *Double Buffering*: Used for smooth rendering +* *Tick Handler*: LVGL tick driven by FreeRTOS timer + +--- + +== Public API + +=== GUI_Task_Init() + +[source,c] +---- +esp_err_t GUI_Task_Init(void); +---- + +Initializes the GUI subsystem including LVGL library, display driver, touch driver, and UI screens. + +**Initialization Steps:** + +1. Initialize LVGL library +2. Configure display driver (ILI9341) +3. Initialize touch input driver (GT911) +4. Create LVGL display and input device objects +5. Load UI screens from SquareLine Studio export +6. Register event handlers for system events + +**Return Values:** + +* `ESP_OK` - GUI subsystem initialized successfully +* `ESP_ERR_NO_MEM` - Memory allocation failed +* `ESP_FAIL` - Display or touch driver initialization failed +* `ESP_ERR_INVALID_STATE` - Already initialized + +**Thread Safety:** Not thread-safe. Must be called once during system initialization. + +**Example:** +[source,c] +---- +esp_err_t Error = GUI_Task_Init(); +if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize GUI: %d", Error); + return Error; +} +---- + +--- + +=== GUI_Task_Deinit() + +[source,c] +---- +void GUI_Task_Deinit(void); +---- + +Deinitializes the GUI subsystem, freeing all allocated resources. + +**Actions:** + +* Stops GUI task if running +* Frees LVGL objects and screens +* Deinitializes display and touch drivers +* Releases allocated memory + +**Thread Safety:** Not thread-safe. Call only during shutdown. + +--- + +=== GUI_Task_Start() + +[source,c] +---- +esp_err_t GUI_Task_Start(App_Context_t *p_AppContext); +---- + +Starts the GUI FreeRTOS task. + +**Parameters:** + +* `p_AppContext` - Pointer to application context structure (must not be NULL) + +**Return Values:** + +* `ESP_OK` - Task started successfully +* `ESP_ERR_INVALID_ARG` - NULL pointer passed +* `ESP_ERR_INVALID_STATE` - GUI not initialized or already running +* `ESP_ERR_NO_MEM` - Task creation failed + +**Thread Safety:** Thread-safe. Can be called from any task. + +**Example:** +[source,c] +---- +App_Context_t AppContext; +esp_err_t Error = GUI_Task_Start(&AppContext); +if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to start GUI task: %d", Error); +} +---- + +--- + +=== GUI_Task_Stop() + +[source,c] +---- +esp_err_t GUI_Task_Stop(void); +---- + +Stops the GUI task. + +**Return Values:** + +* `ESP_OK` - Task stopped successfully +* `ESP_ERR_INVALID_STATE` - Task not running + +**Thread Safety:** Thread-safe. Can be called from any task. + +**Note:** This function blocks until the task has fully stopped. + +--- + +=== GUI_Task_isRunning() + +[source,c] +---- +bool GUI_Task_isRunning(void); +---- + +Checks if the GUI task is currently active. + +**Return Values:** + +* `true` - Task is running +* `false` - Task is not running + +**Thread Safety:** Thread-safe. + +--- + +=== GUI_Toggle_ROI_EditMode() + +[source,c] +---- +void GUI_Toggle_ROI_EditMode(void); +---- + +Toggles the ROI (Region of Interest) edit mode on the main thermal display. + +**Behavior:** + +* When enabled: Displays a draggable rectangle overlay representing the spotmeter region +* User can touch and drag to reposition the ROI +* Updates are applied to camera spotmeter settings in real-time +* Visual feedback with colored rectangle + +**Thread Safety:** Should be called from GUI context or via event system. + +**Example:** +[source,c] +---- +// Called from button press event +GUI_Toggle_ROI_EditMode(); +---- + +--- + +=== GUI_SaveThermalImage() + +[source,c] +---- +esp_err_t GUI_SaveThermalImage(void); +---- + +Captures the current thermal image and saves it to the SD card as a BMP file. + +**File Naming:** + +* Format: `THERMAL_YYYYMMDD_HHMMSS.BMP` +* Example: `THERMAL_20260209_143022.BMP` + +**Return Values:** + +* `ESP_OK` - Image saved successfully +* `ESP_ERR_INVALID_STATE` - Filesystem locked (USB MSC active) +* `ESP_ERR_NO_MEM` - No frame available or memory allocation failed +* `ESP_FAIL` - File write operation failed + +**Thread Safety:** Thread-safe. Can be called from any task. + +**Storage Location:** `/sdcard/` (SD card root directory) + +**Note:** Function checks if filesystem is locked by USB Mass Storage Class before attempting write. + +**Example:** +[source,c] +---- +esp_err_t Error = GUI_SaveThermalImage(); +if (Error == ESP_ERR_INVALID_STATE) { + ESP_LOGW(TAG, "Cannot save: USB MSC active"); +} else if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to save image: %d", Error); +} +---- + +--- + +== Event System + +=== GUI Events + +The GUI Task posts events to the system event loop: + +[source,c] +---- +ESP_EVENT_DECLARE_BASE(GUI_EVENTS); +---- + +**Event Types:** + +Currently, the GUI Task primarily *consumes* events rather than posting them: + +* `SETTINGS_EVENTS` - Updates UI when settings change +* `NETWORK_EVENTS` - Updates WiFi status indicators +* `LEPTON_EVENTS` - Receives frame updates for display + +--- + +== UI Screens + +=== 1. Splash Screen (`ui_Splash`) + +* Displayed during boot +* Shows PyroVision logo +* Duration: 2-3 seconds +* Transitions to Main screen + +=== 2. Main Screen (`ui_Main`) + +The primary thermal imaging view: + +* *Thermal Image Canvas*: Full-color rendered thermal frame +* *Spotmeter Temperature*: Large numeric display of spotmeter temp +* *Min/Max Temperatures*: Scene statistics +* *ROI Overlay*: Visual spotmeter position indicator +* *Status Bar*: WiFi, USB, SD card status icons +* *Menu Button*: Access to settings and configuration + +=== 3. Menu Screen (`ui_Menu`) + +Navigation to various settings: + +* WiFi Configuration +* Camera Settings +* Display Settings +* System Information +* Return to Main + +=== 4. Info Screen (`ui_Info`) + +System information display: + +* Firmware version +* Hardware info +* Memory usage +* Uptime +* Network status + +=== 5. Settings Screens (`ui_Settings_*`) + +Dynamic settings panels (custom implementation): + +* WiFi SSID/Password entry +* Camera ROI configuration +* AGC settings +* Emissivity presets +* Display brightness + +--- + +== Thermal Image Rendering + +=== Frame Processing Pipeline + +[source] +---- +┌──────────────────┐ +│ Lepton Task │ +│ (Raw 14-bit) │ +└────────┬─────────┘ + │ + │ Post frame via queue + ↓ +┌──────────────────┐ +│ GUI Task │ +│ Receive Frame │ +└────────┬─────────┘ + │ + │ Apply colormap + ↓ +┌──────────────────┐ +│ RGB565 Conversion│ +│ (320x240 scaled) │ +└────────┬─────────┘ + │ + │ LVGL Canvas Update + ↓ +┌──────────────────┐ +│ Display (ILI9341)│ +│ SPI DMA Transfer │ +└──────────────────┘ +---- + +**Frame Format:** + +* Input: 14-bit RAW thermal data (80x60 or 160x120 depending on Lepton model) +* Processing: Apply colormap (Iron, Rainbow, etc.) +* Output: RGB565 (16-bit color) +* Scaling: Bilinear interpolation to fit display + +**Performance:** + +* Target: 8.6 Hz frame rate (Lepton 3.5 native rate) +* Actual: ~8-10 FPS depending on UI complexity +* Latency: <100ms from capture to display + +--- + +== ROI (Region of Interest) Editor + +=== Interactive ROI Adjustment + +The GUI allows visual adjustment of the camera's spotmeter ROI: + +**Interaction:** + +1. User presses "Edit ROI" button +2. Colored rectangle overlay appears on thermal image +3. User touches and drags rectangle to reposition +4. Release updates camera spotmeter settings +5. Press "Done" to exit edit mode + +**Implementation:** + +* LVGL drag event handler +* Real-time coordinate translation (display → camera pixels) +* Boundary checking to keep ROI within frame +* Visual feedback during dragging + +**Constraints:** + +* Minimum ROI size: 4x4 pixels (camera limitation) +* ROI must be fully within frame boundaries +* Only one ROI editable at a time (typically spotmeter) + +--- + +== Image Capture (BMP Export) + +=== BMP File Format + +Saved thermal images use the following format: + +* *Format*: BMP (Windows Bitmap) +* *Color Depth*: 24-bit RGB +* *Resolution*: 320x240 pixels (native display resolution) +* *Compression*: None (uncompressed) +* *Color Space*: sRGB + +**File Structure:** + +[source] +---- +BMP Header (14 bytes) +DIB Header (40 bytes) +Pixel Data (320 x 240 x 3 bytes = 230,400 bytes) +Total: ~230 KB per image +---- + +**Memory Considerations:** + +* Temporary buffer allocated from PSRAM (if available) +* Freed immediately after file write +* SD card must have sufficient space + +--- + +== Dependencies + +=== Required Managers + +* *Device Manager*: Display and touch controller initialization +* *Settings Manager*: UI configuration (brightness, theme, etc.) +* *Memory Manager*: Buffer allocation for frame processing +* *Time Manager*: Timestamp for saved images + +=== Required Tasks + +* *Lepton Task*: Provides thermal frames for display + +=== Libraries + +* *LVGL* (v8.3+): Graphics library +* *ESP-IDF LCD/Touch*: Display and touch drivers +* *ESP-IDF VFS/FatFS*: File system for BMP export + +--- + +== Configuration + +=== Kconfig Options + +The GUI can be configured via menuconfig: + +* `GUI_TASK_PRIORITY` - Task priority +* `GUI_TASK_STACK_SIZE` - Stack size (default: 16384) +* `GUI_TASK_CORE` - CPU core affinity (0 or 1) +* `GUI_LVGL_BUFFER_SIZE` - LVGL draw buffer size + +=== Compile-Time Configuration + +Display and touch driver parameters are configured in Device Manager. + +--- + +== Threading and Synchronization + +=== Thread Safety + +* **Task-Internal**: GUI operations are thread-safe when called from GUI task context +* **External Calls**: API functions (`GUI_Task_Start()`, `GUI_SaveThermalImage()`) use mutexes for thread safety +* **Event Handlers**: GUI event handlers run in event loop context + +=== Critical Sections + +* Frame buffer access (protected by semaphore) +* Settings updates (coordinated via event system) +* File system access (checked for USB lock) + +--- + +== Troubleshooting + +=== Common Issues + +**Display not updating:** + +* Check LVGL tick handler is running +* Verify SPI bus configuration +* Ensure display backlight is enabled + +**Touch not responding:** + +* Verify GT911 I2C address (0x5D or 0x14) +* Check touch interrupt GPIO configuration +* Confirm touch calibration + +**Frame rate too low:** + +* Reduce LVGL animation complexity +* Enable SPI DMA for display +* Allocate draw buffers in PSRAM + +**Image save fails:** + +* Check SD card is mounted +* Verify filesystem not locked (USB MSC inactive) +* Ensure sufficient free space on SD card + +--- + +== Performance Optimization + +=== Tips for Smooth UI + +* Use double buffering for LVGL +* Enable SPI DMA transfers +* Minimize LVGL object count on screen +* Use cached images for static content +* Pin GUI task to dedicated core + +=== Memory Optimization + +* Allocate large buffers (frame buffers) in PSRAM +* Use LVGL built-in image cache +* Release unused screens from memory +* Monitor heap fragmentation + +--- + +== Future Enhancements + +* Multiple colormap options (Iron, Rainbow, Grayscale) +* Histogram display +* Temperature profile graphs +* Multi-ROI visual editing +* Touch gestures (pinch-to-zoom) +* Screen rotation support + +--- + +== Related Documentation + +* link:LeptonTask.adoc[Lepton Task Documentation] +* link:DeviceManager.adoc[Device Manager Documentation] +* link:SettingsManager.adoc[Settings Manager Documentation] +* link:MemoryManager.adoc[Memory Manager Documentation] + +--- + +**Last Updated**: February 9, 2026 + +**Maintainer**: Daniel Kampert (DanielKampert@kampis-elektroecke.de) diff --git a/docs/HTTPServer.adoc b/docs/HTTPServer.adoc new file mode 100644 index 0000000..7cd36a0 --- /dev/null +++ b/docs/HTTPServer.adoc @@ -0,0 +1,330 @@ += PyroVision HTTP Server Documentation +Daniel Kampert +v1.0, 2026-02-09 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The HTTP Server provides a web-based interface for viewing thermal imagery, configuring the device, and streaming live video via WebSocket. It serves static provisioning pages for WiFi setup and exposes a REST API for configuration and telemetry. + +=== Key Features + +* *Web Interface*: HTML provisioning and configuration pages +* *REST API*: JSON-based configuration and telemetry endpoints +* *WebSocket*: Real-time thermal image streaming +* *CORS Support*: Cross-origin resource sharing for web apps +* *Captive Portal*: WiFi provisioning without app +* *OTA Updates*: Firmware upload via HTTP POST + +--- + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Manager/Network/Server/HTTP/ +├── http_server.h # Public API +└── http_server.cpp # Implementation + +main/Application/Manager/Network/Server/WebSocket/ +├── websocket.h # WebSocket API +└── websocket.cpp # WebSocket implementation + +main/Application/Manager/Network/Server/ImageEncoder/ +├── imageEncoder.h # Image encoder +└── imageEncoder.cpp # JPEG compression +---- + +--- + +== Public API + +=== HTTP_Server_Init() + +[source,c] +---- +esp_err_t HTTP_Server_Init(const Network_HTTP_Server_Config_t *p_Config); +---- + +Initializes the HTTP server with configuration. + +**Configuration:** + +[source,c] +---- +typedef struct { + uint16_t Port; // Port number (default: 80) + uint16_t WSPingIntervalSec; // WebSocket ping interval (default: 30) + uint8_t MaxClients; // Max concurrent clients (default: 5) +} Network_HTTP_Server_Config_t; +---- + +--- + +=== HTTP_Server_Start() + +[source,c] +---- +esp_err_t HTTP_Server_Start(void); +---- + +Starts the HTTP server. + +--- + +=== HTTP_Server_Stop() + +[source,c] +---- +esp_err_t HTTP_Server_Stop(void); +---- + +Stops the HTTP server and closes all connections. + +--- + +=== HTTP_Server_SetThermalFrame() + +[source,c] +---- +void HTTP_Server_SetThermalFrame(Network_Thermal_Frame_t *p_Frame); +---- + +Updates the thermal frame available for `/api/image` endpoint. + +--- + +== REST API Endpoints + +=== GET `/api/image` + +Returns the current thermal frame as JPEG image. + +**Response:** +- Content-Type: `image/jpeg` +- Body: JPEG-compressed thermal image + +**Example:** +[source,bash] +---- +curl http://192.168.1.100/api/image > thermal.jpg +---- + +--- + +=== GET `/api/telemetry` + +Returns telemetry data in JSON format. + +**Response:** +[source,json] +---- +{ + "spotmeter": 25.3, + "min": 20.1, + "max": 30.5, + "avg": 25.0, + "frame_count": 12345 +} +---- + +--- + +=== POST `/api/time` + +Sets the system time. + +**Request Body:** +[source,json] +---- +{ + "datetime": "2026-02-09T14:30:00" +} +---- + +**Response:** +[source,json] +---- +{ + "status": "ok" +} +---- + +--- + +=== POST `/api/update` + +Uploads firmware for OTA update. + +**Request:** +- Content-Type: `application/octet-stream` +- Body: Binary firmware file + +**Response:** +[source,json] +---- +{ + "status": "ok", + "message": "Update successful, rebooting..." +} +---- + +--- + +=== OPTIONS `/api/*` + +Handles CORS preflight requests. + +**Response Headers:** +[source] +---- +Access-Control-Allow-Origin: * +Access-Control-Allow-Methods: GET, POST, OPTIONS +Access-Control-Allow-Headers: Content-Type +---- + +--- + +== WebSocket API + +=== Connection + +**Endpoint:** `ws://192.168.1.100/ws` + +**Protocol:** Binary frames (JPEG-compressed thermal images) + +--- + +=== Frame Format + +Each WebSocket message contains a JPEG-compressed thermal frame: + +[source] +---- +Message Type: Binary +Frame Rate: ~8-9 Hz +Size: 5-15 KB per frame +---- + +--- + +=== JavaScript Example + +[source,javascript] +---- +const ws = new WebSocket('ws://192.168.1.100/ws'); + +ws.binaryType = 'arraybuffer'; + +ws.onmessage = (event) => { + const blob = new Blob([event.data], {type: 'image/jpeg'}); + const url = URL.createObjectURL(blob); + + const img = document.getElementById('thermal-image'); + img.src = url; +}; + +ws.onopen = () => { + console.log('WebSocket connected'); +}; + +ws.onerror = (error) => { + console.error('WebSocket error:', error); +}; + +ws.onclose = () => { + console.log('WebSocket closed'); +}; +---- + +--- + +== Provisioning Pages + +=== GET `/` + +Serves the WiFi provisioning page. + +**Features:** +- WiFi SSID scanner +- Password entry +- Connection status +- Access to device configuration + +--- + +=== GET `/logo.png` + +Serves the PyroVision logo for provisioning page. + +--- + +=== GET `/api/provision/scan` + +Scans for available WiFi networks. + +**Response:** +[source,json] +---- +{ + "networks": [ + {"ssid": "MyWiFi", "rssi": -45, "auth": "WPA2"}, + {"ssid": "GuestNet", "rssi": -67, "auth": "Open"} + ] +} +---- + +--- + +=== POST `/api/provision` + +Submits WiFi credentials. + +**Request Body:** +[source,json] +---- +{ + "ssid": "MyWiFi", + "password": "MyPassword" +} +---- + +--- + +== Captive Portal + +When in AP mode, the device implements a captive portal that redirects all HTTP requests to the provisioning page. + +**Endpoints:** +- `/generate_204` (Android) +- `/generate204` (Alternative) + +**Behavior:** +- Redirects to provisioning page (`/`) +- Allows WiFi configuration without app installation + +--- + +## Performance + +* **Max Clients**: 5 (HTTP) + 5 (WebSocket) +* **Request Latency**: <50 ms (typical) +* **WebSocket Throughput**: ~80 KB/s per client +* **Memory Usage**: ~50 KB per active connection + +--- + +== Related Documentation + +* link:NetworkTask.adoc[Network Task Documentation] +* link:NetworkManager.adoc[Network Manager Documentation] + +--- + +**Last Updated**: February 9, 2026 + +**Maintainer**: Daniel Kampert (DanielKampert@kampis-elektroecke.de) diff --git a/docs/LeptonTask.adoc b/docs/LeptonTask.adoc new file mode 100644 index 0000000..bd4cc70 --- /dev/null +++ b/docs/LeptonTask.adoc @@ -0,0 +1,653 @@ += PyroVision Lepton Task Documentation +Daniel Kampert +v1.0, 2026-02-09 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The Lepton Task is responsible for interfacing with the FLIR Lepton thermal camera, acquiring thermal frames, processing temperature data, calculating ROI (Region of Interest) statistics, and distributing frame data to consumers (GUI, Network, etc.). It runs as a high-priority FreeRTOS task to ensure consistent frame acquisition. + +=== Key Features + +* *Lepton Camera Interface*: SPI communication with FLIR Lepton 3.5 +* *Frame Acquisition*: Continuous capture at camera native rate (~8.6 Hz) +* *Temperature Calculation*: Conversion from RAW values to Celsius +* *ROI Processing*: Calculate min/max/average temperatures for multiple ROIs +* *Frame Distribution*: Queue-based delivery to GUI and Network tasks +* *Telemetry Generation*: Camera status and temperature statistics +* *Error Recovery*: Automatic resync on frame corruption +* *Power Management*: Camera standby and wake-up control + +--- + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Tasks/Lepton/ +├── leptonTask.h # Public API +└── leptonTask.cpp # Implementation +---- + +**Driver Integration:** + +The task uses the ESP32-Lepton component located in `components/ESP32-Lepton/`. + +=== Task Architecture + +* *Task Name*: `"LeptonTask"` +* *Priority*: High (typically `tskIDLE_PRIORITY + 4`) +* *Stack Size*: 8 KB +* *Core Affinity*: Typically Core 1 (APP CPU) +* *Frame Rate*: 8.6 Hz (Lepton 3.5 native) + +=== Data Flow + +[source] +---- +┌────────────────┐ +│ Lepton Camera │ (160x120 RAW 14-bit thermal data) +└────────┬───────┘ + │ SPI Transfer + ↓ +┌────────────────┐ +│ Lepton Task │ +│ - Frame Sync │ +│ - RAW to Temp │ +│ - ROI Calc │ +└───────┬────────┘ + │ + ├─→ Queue → GUI Task (Display) + │ + ├─→ Queue → Network Task (WebSocket streaming) + │ + └─→ Telemetry → HTTP/VISA Server +---- + +--- + +== Public API + +=== Lepton_Task_Init() + +[source,c] +---- +esp_err_t Lepton_Task_Init(void); +---- + +Initializes the Lepton camera driver and allocates frame buffers. + +**Initialization Steps:** + +1. Initialize Lepton SPI communication +2. Configure camera settings (AGC, ROI, FFC mode) +3. Allocate frame buffers in PSRAM +4. Create frame queues for distribution +5. Verify camera connection and firmware version + +**Return Values:** + +* `ESP_OK` - Initialization successful +* `ESP_ERR_NO_MEM` - Memory allocation failed +* `ESP_FAIL` - Camera communication failed +* `ESP_ERR_INVALID_STATE` - Already initialized + +**Thread Safety:** Not thread-safe. Must be called once during system initialization. + +**Example:** +[source,c] +---- +esp_err_t Error = Lepton_Task_Init(); +if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize Lepton: %d", Error); + return Error; +} +---- + +--- + +=== Lepton_Task_Deinit() + +[source,c] +---- +void Lepton_Task_Deinit(void); +---- + +Deinitializes the Lepton camera and frees allocated resources. + +**Actions:** + +* Stops task if running +* Puts camera in standby mode +* Frees frame buffers +* Deletes frame queues +* Deinitializes SPI interface + +**Thread Safety:** Not thread-safe. Call only during shutdown. + +--- + +=== Lepton_Task_Start() + +[source,c] +---- +esp_err_t Lepton_Task_Start(App_Context_t *p_AppContext); +---- + +Starts the Lepton acquisition task. + +**Parameters:** + +* `p_AppContext` - Pointer to application context (must not be NULL) + +**Return Values:** + +* `ESP_OK` - Task started successfully +* `ESP_ERR_INVALID_ARG` - NULL pointer passed +* `ESP_ERR_INVALID_STATE` - Not initialized or already running +* `ESP_ERR_NO_MEM` - Task creation failed + +**Thread Safety:** Thread-safe. + +**Example:** +[source,c] +---- +App_Context_t AppContext; +esp_err_t Error = Lepton_Task_Start(&AppContext); +---- + +--- + +=== Lepton_Task_Stop() + +[source,c] +---- +esp_err_t Lepton_Task_Stop(void); +---- + +Stops the Lepton task and puts camera in standby mode. + +**Return Values:** + +* `ESP_OK` - Task stopped successfully +* `ESP_ERR_INVALID_STATE` - Task not running + +**Thread Safety:** Thread-safe. Blocks until task has stopped. + +--- + +=== Lepton_Task_isRunning() + +[source,c] +---- +bool Lepton_Task_isRunning(void); +---- + +Checks if the Lepton task is currently active. + +**Return Values:** + +* `true` - Task is running +* `false` - Task is not running + +**Thread Safety:** Thread-safe. + +--- + +== Frame Processing + +=== Frame Acquisition Loop + +[source,c] +---- +while (running) { + // 1. Read frame from camera (SPI) + Lepton_GetFrame(&RawFrame); + + // 2. Validate frame sync + if (!Lepton_ValidateFrame(&RawFrame)) { + Lepton_Resync(); + continue; + } + + // 3. Convert RAW to temperature (Celsius) + Lepton_RawToTemp(&RawFrame, &TempFrame); + + // 4. Calculate ROI statistics + CalculateROI(&TempFrame, &ROIData); + + // 5. Distribute to consumers + SendToGUI(&TempFrame); + SendToNetwork(&TempFrame); + + // 6. Update telemetry + UpdateTelemetry(&ROIData); +} +---- + +=== RAW to Temperature Conversion + +The Lepton camera outputs 14-bit RAW values that must be converted to temperature: + +**Formula:** + +[source] +---- +Temperature (Kelvin) = RAW_Value × 0.01 K + +Temperature (Celsius) = (RAW_Value × 0.01) - 273.15 +---- + +**Considerations:** + +* RAW value 0 is invalid (indicates bad pixel or sync error) +* Lepton 3.5 range: -10°C to +140°C (typical) +* Accuracy: ±5°C (typical), ±1.5°C (with calibration) + +--- + +== ROI (Region of Interest) Processing + +=== ROI Types + +The Lepton Task calculates statistics for up to 4 ROIs: + +1. **Spotmeter ROI** - User-defined region for temperature measurement +2. **Scene ROI** - Entire frame or custom scene region +3. **AGC ROI** - Region used for Automatic Gain Control +4. **Video Focus ROI** - Region for focus optimization (future) + +=== ROI Statistics + +For each ROI, the task calculates: + +* **Minimum Temperature**: Coldest pixel in ROI +* **Maximum Temperature**: Hottest pixel in ROI +* **Average Temperature**: Mean of all pixels in ROI +* **Pixel Coordinates**: Location of min/max pixels + +**Data Structure:** + +[source,c] +---- +typedef struct { + float MinTemp; // Minimum temperature (°C) + float MaxTemp; // Maximum temperature (°C) + float AvgTemp; // Average temperature (°C) + uint16_t MinX, MinY; // Coordinates of coldest pixel + uint16_t MaxX, MaxY; // Coordinates of hottest pixel +} ROI_Stats_t; +---- + +=== ROI Configuration + +ROIs are configured via Settings Manager: + +[source,c] +---- +Settings_ROI_t ROI = { + .X = 70, // X position (pixels) + .Y = 50, // Y position (pixels) + .Width = 20, // Width (pixels) + .Height = 20 // Height (pixels) +}; +SettingsManager_UpdateROI(ROI_SPOTMETER, &ROI); +---- + +**Constraints:** + +* ROI must be within frame boundaries (0-159 for X, 0-119 for Y on Lepton 3.5) +* Minimum ROI size: 4x4 pixels +* ROI coordinates must be even numbers (hardware requirement) + +--- + +== Telemetry + +=== Telemetry Structure + +The task generates telemetry data for monitoring and remote access: + +[source,c] +---- +typedef struct { + float SpotmeterTemp; // Spotmeter center temperature + float MinTemp; // Scene minimum temperature + float MaxTemp; // Scene maximum temperature + float AvgTemp; // Scene average temperature + uint32_t FrameCount; // Total frames captured + uint32_t ErrorCount; // Frame sync errors + uint16_t CameraTemp; // Camera housing temperature (°C) + bool FFC_InProgress; // Flat Field Correction active +} Lepton_Telemetry_t; +---- + +=== Telemetry Access + +Telemetry is accessible via: + +* HTTP REST API: `GET /api/telemetry` +* VISA/SCPI: `MEAS:TEMP?` +* WebSocket: Real-time streaming +* GUI: Displayed on main screen + +--- + +== Flat Field Correction (FFC) + +=== What is FFC? + +Flat Field Correction is a camera calibration process that compensates for sensor non-uniformities. The Lepton camera performs FFC automatically or on-demand. + +**FFC Modes:** + +1. **Automatic FFC**: Camera decides when to perform FFC (every ~3 minutes or on temperature change) +2. **Manual FFC**: Triggered by user command +3. **External Shutter FFC**: Uses external shutter (not implemented) + +**FFC Duration**: ~1-2 seconds (camera pauses during FFC) + +### FFC Event Handling + +The task monitors FFC status: + +[source,c] +---- +if (Lepton_FFC_InProgress()) { + ESP_LOGI(TAG, "FFC in progress..."); + // Notify GUI (display "Calibrating..." message) + // Pause frame distribution +} +---- + +--- + +== Error Handling and Recovery + +=== Frame Sync Errors + +The Lepton camera transmits frames as SPI packets. Sync errors can occur due to: + +* SPI timing issues +* Electromagnetic interference +* Camera power fluctuations + +**Recovery Strategy:** + +1. Detect invalid frame (invalid ID or checksum) +2. Discard current frame +3. Perform resynchronization (discard 3 frames) +4. Resume normal operation +5. Log error for diagnostics + +**Resync Algorithm:** + +[source,c] +---- +static void Lepton_Resync(void) +{ + ESP_LOGW(TAG, "Frame sync lost, resyncing..."); + + for (int i = 0; i < 3; i++) { + Lepton_ReadPacket(NULL); // Discard packet + } + + _ErrorCount++; +} +---- + +=== Camera Reboot Detection + +If the camera reboots (power loss, overheat protection), the task detects it and reinitializes: + +[source,c] +---- +if (Lepton_DetectReboot()) { + ESP_LOGE(TAG, "Camera rebooted, reinitializing..."); + Lepton_Task_Deinit(); + vTaskDelay(pdMS_TO_TICKS(1000)); + Lepton_Task_Init(); +} +---- + +--- + +== Camera Configuration + +=== AGC (Automatic Gain Control) + +AGC adjusts image contrast for optimal visualization: + +**AGC Modes:** + +* **Linear** - Direct mapping (no contrast enhancement) +* **Histogram Equalization (HEQ)** - Adaptive contrast (recommended) + +**Configuration:** + +[source,c] +---- +Lepton_SetAGCMode(LEPTON_AGC_HEQ); +Lepton_SetAGCROI(70, 50, 20, 20); // ROI for AGC calculation +---- + +=== Emissivity Correction + +Emissivity affects temperature accuracy for different materials: + +[source,c] +---- +// Set emissivity (0.0 - 1.0, typically 0.95 for most surfaces) +Lepton_SetEmissivity(0.95f); +---- + +**Common Emissivities:** + +* Skin: 0.98 +* Concrete: 0.92 +* Aluminum (oxidized): 0.30 +* Water: 0.95 + +--- + +== Frame Distribution + +=== Frame Queues + +Frames are distributed to consumers via FreeRTOS queues: + +**GUI Queue:** + +* Queue depth: 1 (only latest frame) +* Overwrite mode: Drop old frames if GUI is slow + +**Network Queue:** + +* Queue depth: 3 (small buffer for network jitter) +* Block if full (backpressure to prevent memory exhaustion) + +**Implementation:** + +[source,c] +---- +// Send to GUI (non-blocking, drop if full) +xQueueOverwrite(GUI_FrameQueue, &Frame); + +// Send to Network (blocking with timeout) +xQueueSend(Network_FrameQueue, &Frame, pdMS_TO_TICKS(100)); +---- + +--- + +## Dependencies + +=== Required Managers + +* *Device Manager*: SPI bus initialization for camera +* *Settings Manager*: ROI configuration, AGC settings, emissivity +* *Memory Manager*: Frame buffer allocation in PSRAM + +=== Required Components + +* *ESP32-Lepton*: Lepton camera driver (in `components/`) + +--- + +== Configuration + +=== Kconfig Options + +* `LEPTON_TASK_PRIORITY` - Task priority +* `LEPTON_TASK_STACK_SIZE` - Stack size (default: 8192) +* `LEPTON_TASK_CORE` - CPU core affinity +* `LEPTON_SPI_HOST` - SPI host (HSPI or VSPI) +* `LEPTON_SPI_FREQ` - SPI clock frequency (default: 20 MHz) + +--- + +== Performance Characteristics + +=== Frame Rate + +* **Lepton 3.5 Native Rate**: 8.6 Hz +* **Typical Achieved Rate**: 8-9 Hz +* **Frame Jitter**: ±10ms + +=== CPU Usage + +* **Typical**: 15-20% of one core +* **During FFC**: Minimal (camera paused) +* **Peak**: 25% (with complex ROI calculations) + +=== Memory Usage + +* **Frame Buffer (160x120x2)**: 38.4 KB per frame +* **Total Buffers (x3)**: ~115 KB (PSRAM) +* **Stack**: 8 KB +* **Heap**: ~5 KB (misc allocations) + +--- + +== Troubleshooting + +=== Common Issues + +**No frames acquired:** + +* Check SPI wiring and configuration +* Verify camera power supply (3.3V, sufficient current) +* Ensure CS, MOSI, MISO, CLK pins correct +* Check camera is not in standby mode + +**Frame sync errors:** + +* Reduce SPI clock frequency (try 16 MHz instead of 20 MHz) +* Check for electrical noise on SPI bus +* Verify ground connection +* Add pull-up resistors to CS line + +**Incorrect temperatures:** + +* Verify RAW to temperature conversion formula +* Check emissivity setting +* Perform FFC (Flat Field Correction) +* Allow camera to warm up (30 seconds) + +**Low frame rate:** + +* Check if task priority is sufficient +* Verify other tasks are not blocking +* Monitor CPU usage +* Ensure frame queues are not full + +--- + +== Event Handling + +The Lepton Task subscribes to events from other system components to receive configuration updates and requests. + +=== Subscribed Events + +==== Settings Events (`SETTINGS_EVENTS`) + +**`SETTINGS_EVENT_LEPTON_CHANGED`:** + +Received when Lepton camera settings have been modified (e.g., emissivity). + +* **Event Data**: `Settings_Manager_Setting_t *` or NULL +* **Handler**: `on_Settings_Event_Handler()` +* **Behavior**: + - If event data is NULL, indicates a bulk settings update without specific change information + - If event data is provided, contains `ID` and `Value` of the specific setting changed + - Handler checks `ID` field to determine which setting changed (e.g., `SETTINGS_ID_LEPTON_EMISSIVITY`) + +*IMPORTANT*: Event handlers MUST check for NULL event data before dereferencing pointers, as bulk setting updates may not include specific change details. + +[source,c] +---- +static void on_Settings_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, + int32_t ID, void *p_Data) +{ + switch (ID) { + case SETTINGS_EVENT_LEPTON_CHANGED: { + if (p_Data != NULL) { + Settings_Manager_Setting_t *p_Setting = + (Settings_Manager_Setting_t *)p_Data; + + if (p_Setting->ID == SETTINGS_ID_LEPTON_EMISSIVITY) { + // Apply emissivity change + } + } else { + // Bulk update, reload all settings + } + break; + } + } +} +---- + +==== GUI Events (`GUI_EVENTS`) + +**`GUI_EVENT_REQUEST_ROI`:** + +Requests ROI statistics calculation for a specific region. + +* **Event Data**: `Settings_ROI_t *` (MUST NOT be NULL) +* **Response**: Posts `LEPTON_EVENT_ROI_DATA` with calculated statistics + +**`GUI_EVENT_REQUEST_PIXEL_TEMPERATURE`:** + +Requests temperature at a specific screen position. + +* **Event Data**: `App_GUI_Screenposition_t *` (MUST NOT be NULL) +* **Response**: Posts `LEPTON_EVENT_PIXEL_TEMPERATURE` with temperature value + +--- + +== Future Enhancements + +* Multiple colormap support (Iron, Rainbow, Grayscale) +* Advanced image processing (edge detection, filtering) +* Temperature trend recording +* Multi-point temperature tracking +* Thermal anomaly detection +* Camera firmware update via OTA + +--- + +== Related Documentation + +* link:GUITask.adoc[GUI Task Documentation] +* link:NetworkTask.adoc[Network Task Documentation] +* link:DeviceManager.adoc[Device Manager Documentation] +* link:SettingsManager.adoc[Settings Manager Documentation] + +--- + +**Last Updated**: February 9, 2026 + +**Maintainer**: Daniel Kampert (DanielKampert@kampis-elektroecke.de) diff --git a/docs/MemoryManager.adoc b/docs/MemoryManager.adoc new file mode 100644 index 0000000..9aaff02 --- /dev/null +++ b/docs/MemoryManager.adoc @@ -0,0 +1,1243 @@ += PyroVision Memory Manager Documentation +Daniel Kampert +v1.0, 2026-02-08 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The Memory Manager is a unified storage abstraction component for the PyroVision firmware that provides seamless access to either internal flash storage or external SD card storage. It automatically detects the available storage medium and provides a consistent API for filesystem operations, usage monitoring, and USB Mass Storage integration. + +=== Key Features + +* *Automatic Storage Detection*: Detects SD card presence and selects storage location automatically +* *Unified Interface*: Consistent API regardless of storage medium (internal flash or SD card) +* *Dual Storage Support*: Internal FAT32 with wear leveling or external SD card (SDMMC) +* *Usage Monitoring*: Real-time filesystem usage statistics +* *USB Integration*: Filesystem locking mechanism for safe USB Mass Storage Device operation +* *Partition Management*: Access to storage and coredump partition information +* *Thread-safe Operations*: Protected access to shared resources +* *Coredump Support*: Separate coredump partition management + +--- + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Manager/Memory/ +├── memoryManager.h # Public API +└── memoryManager.cpp # Implementation +---- + +=== Storage Architecture + +The Memory Manager supports two distinct storage backends: + +==== 1. Internal Flash Storage + +* *Technology*: SPI Flash with FAT32 filesystem +* *Wear Leveling*: ESP-IDF wear leveling layer for flash longevity +* *Partition*: Dedicated `storage` partition in flash +* *Mount Point*: `/storage` +* *Capacity*: Configurable via partition table (typically 1-4 MB) +* *Advantages*: Always available, no external hardware required +* *Disadvantages*: Limited capacity, wear concerns + +==== 2. SD Card Storage + +* *Technology*: SDMMC interface with FAT32 filesystem +* *Mount Point*: `/sdcard` +* *Capacity*: Up to 32 GB (FAT32 limit) +* *Advantages*: High capacity, removable, replaceable +* *Disadvantages*: Requires external SD card slot, card must be inserted +* *Detection*: Automatic on initialization + +=== Storage Selection Logic + +The Memory Manager follows this priority: + +1. *SD Card Detection*: On initialization, check if SD card is present and mounted +2. *SD Card Priority*: If SD card available, use it as primary storage +3. *Flash Fallback*: If no SD card, use internal flash storage +4. *Static Selection*: Selection persists until reinitialization + +[source] +---- ++-----------------------------+ +| MemoryManager_Init() | ++-------------+---------------+ + | + v + +--------------+ + | SD Card | YES +------------------+ + | Present? +------>| Use /sdcard | + +------+-------+ | (SD Card) | + | NO +------------------+ + v + +------------------+ + | Use /storage | + | (Internal Flash) | + +------------------+ +---- + +=== Internal State Management + +The manager maintains its state in the `MemoryManager_State_t` structure: + +[source,cpp] +---- +typedef struct { + bool isInitialized; // Initialization status + bool hasSDCard; // SD card detected + bool filesystemLocked; // Filesystem locked for USB access + MemoryManager_Location_t location; // Active storage location + wl_handle_t wl_handle; // Wear leveling handle (internal flash) + sdmmc_card_t *p_SDCard; // SD card handle (SD card storage) +} MemoryManager_State_t; +---- + +=== Filesystem Locking Mechanism + +The Memory Manager provides a locking mechanism to prevent concurrent access conflicts when USB Mass Storage Device (MSC) mode is active. This is critical because: + +* *USB Host Control*: When USB MSC is active, the PC has direct block-level access to the storage +* *Data Corruption Risk*: Simultaneous access from firmware and PC can corrupt filesystem structures +* *FAT32 Limitation*: FAT32 has no built-in locking or journaling +* *Safe USB Mode*: Locking prevents firmware writes during USB MSC operation + +==== Lock/Unlock Flow + +[source] +---- +USB Activation: + 1. Application calls MemoryManager_LockFilesystem() + 2. Memory Manager sets filesystemLocked = true + 3. USB Manager exposes storage to PC + 4. Firmware must not write to filesystem + +USB Deactivation: + 1. USB Manager safely disconnects from PC + 2. Application calls MemoryManager_UnlockFilesystem() + 3. Memory Manager sets filesystemLocked = false + 4. Firmware can safely write to filesystem again +---- + +--- + +== Data Structures + +=== Storage Location Enum + +[source,cpp] +---- +typedef enum { + MEMORY_LOCATION_INTERNAL, // Internal flash storage partition + MEMORY_LOCATION_SD_CARD, // External SD card +} MemoryManager_Location_t; +---- + +=== Memory Usage Structure + +[source,cpp] +---- +typedef struct { + size_t TotalBytes; // Total partition/storage size in bytes + size_t UsedBytes; // Used space in bytes + size_t FreeBytes; // Free space in bytes + uint8_t UsedPercent; // Used space percentage (0-100) +} MemoryManager_Usage_t; +---- + +* *TotalBytes*: Total capacity of storage or partition +* *UsedBytes*: Space occupied by files and filesystem metadata +* *FreeBytes*: Available space for new files +* *UsedPercent*: Calculated usage percentage for UI display + +--- + +== API Reference + +=== Initialization and Lifecycle + +==== MemoryManager_Init() + +[source,cpp] +---- +esp_err_t MemoryManager_Init(void); +---- + +Initialize the Memory Manager and mount the storage filesystem. + +**Behavior:** + +1. Detects SD card presence via SDMMC interface +2. If SD card present and mountable, selects SD card storage +3. Otherwise, selects internal flash storage with wear leveling +4. Mounts the filesystem at appropriate mount point +5. Posts MEMORY_EVENT_INITIALIZED event + +**Return Values:** + +* `ESP_OK` - Storage successfully initialized and mounted +* `ESP_ERR_INVALID_STATE` - Already initialized +* `ESP_FAIL` - Failed to mount storage + +**Thread Safety:** Not thread-safe during initialization. Call once from main task. + +**Example:** + +[source,cpp] +---- +esp_err_t Error = MemoryManager_Init(); +if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize Memory Manager: %d", Error); + return Error; +} + +// Check which storage is active +if (MemoryManager_HasSDCard()) { + ESP_LOGI(TAG, "Using SD card storage"); +} else { + ESP_LOGI(TAG, "Using internal flash storage"); +} +---- + +==== MemoryManager_Deinit() + +[source,cpp] +---- +esp_err_t MemoryManager_Deinit(void); +---- + +Deinitialize the Memory Manager and unmount the storage filesystem. + +**Behavior:** + +1. Unmounts the active filesystem +2. Releases wear leveling handle (if internal flash) +3. Releases SD card handle (if SD card) +4. Resets internal state + +**Return Values:** + +* `ESP_OK` - Successfully deinitialized +* `ESP_ERR_INVALID_STATE` - Not initialized + +**Warning:** Ensure no files are open and filesystem is not locked before calling this function. + +--- + +=== Storage Information + +==== MemoryManager_HasSDCard() + +[source,cpp] +---- +bool MemoryManager_HasSDCard(void); +---- + +Check if SD card is present and successfully mounted. + +**Return Values:** + +* `true` - SD card is available and active +* `false` - Using internal flash storage + +**Example:** + +[source,cpp] +---- +if (MemoryManager_HasSDCard()) { + ESP_LOGI(TAG, "SD card detected: %s", MemoryManager_GetStoragePath()); + + // Display SD card icon in UI + lv_obj_clear_flag(ui_Icon_SDCard, LV_OBJ_FLAG_HIDDEN); +} else { + ESP_LOGI(TAG, "Using internal storage: %s", MemoryManager_GetStoragePath()); + + // Hide SD card icon + lv_obj_add_flag(ui_Icon_SDCard, LV_OBJ_FLAG_HIDDEN); +} +---- + +==== MemoryManager_GetStorageLocation() + +[source,cpp] +---- +MemoryManager_Location_t MemoryManager_GetStorageLocation(void); +---- + +Get the currently active storage location type. + +**Return Values:** + +* `MEMORY_LOCATION_INTERNAL` - Internal flash storage +* `MEMORY_LOCATION_SD_CARD` - SD card storage + +**Example:** + +[source,cpp] +---- +MemoryManager_Location_t Location = MemoryManager_GetStorageLocation(); + +switch (Location) { + case MEMORY_LOCATION_INTERNAL: + ESP_LOGI(TAG, "Storage: Internal Flash (FAT32 with Wear Leveling)"); + break; + + case MEMORY_LOCATION_SD_CARD: + ESP_LOGI(TAG, "Storage: SD Card (SDMMC)"); + break; +} +---- + +==== MemoryManager_GetStoragePath() + +[source,cpp] +---- +const char *MemoryManager_GetStoragePath(void); +---- + +Get the base mount point path for the current storage location. + +**Return Values:** + +* `"/storage"` - Internal flash storage mount point +* `"/sdcard"` - SD card storage mount point +* `NULL` - Not initialized + +**Thread Safety:** Thread-safe (returns pointer to static string). + +**Example:** + +[source,cpp] +---- +const char *p_Path = MemoryManager_GetStoragePath(); +if (p_Path == NULL) { + ESP_LOGE(TAG, "Memory Manager not initialized!"); + return ESP_ERR_INVALID_STATE; +} + +// Construct full file path +char FilePath[256]; +snprintf(FilePath, sizeof(FilePath), "%s/thermal_image_%d.jpg", p_Path, ImageCounter); + +// Open file +FILE *fp = fopen(FilePath, "wb"); +if (fp != NULL) { + // Write image data + fwrite(p_ImageData, 1, ImageSize, fp); + fclose(fp); + + ESP_LOGI(TAG, "Image saved: %s", FilePath); +} +---- + +--- + +=== Usage Monitoring + +==== MemoryManager_GetStorageUsage() + +[source,cpp] +---- +esp_err_t MemoryManager_GetStorageUsage(MemoryManager_Usage_t *p_Usage); +---- + +Get filesystem usage statistics for the current storage location. + +**Parameters:** + +* `p_Usage` - Pointer to usage structure to populate + +**Return Values:** + +* `ESP_OK` - Usage information retrieved successfully +* `ESP_ERR_INVALID_ARG` - p_Usage is NULL +* `ESP_ERR_NOT_FOUND` - Storage not found +* `ESP_FAIL` - Filesystem not mounted + +**Note:** Storage must be mounted to get usage information. + +**Example:** + +[source,cpp] +---- +MemoryManager_Usage_t Usage; +esp_err_t Error = MemoryManager_GetStorageUsage(&Usage); +if (Error == ESP_OK) { + ESP_LOGI(TAG, "Storage Usage:"); + ESP_LOGI(TAG, " Total: %zu bytes (%.2f MB)", + Usage.TotalBytes, Usage.TotalBytes / (1024.0 * 1024.0)); + ESP_LOGI(TAG, " Used: %zu bytes (%.2f MB)", + Usage.UsedBytes, Usage.UsedBytes / (1024.0 * 1024.0)); + ESP_LOGI(TAG, " Free: %zu bytes (%.2f MB)", + Usage.FreeBytes, Usage.FreeBytes / (1024.0 * 1024.0)); + ESP_LOGI(TAG, " Usage: %u%%", Usage.UsedPercent); + + // Update UI progress bar + lv_bar_set_value(ui_Bar_StorageUsage, Usage.UsedPercent, LV_ANIM_ON); + lv_label_set_text_fmt(ui_Label_StorageUsage, "%u%% Used", Usage.UsedPercent); +} else { + ESP_LOGE(TAG, "Failed to get storage usage: %d", Error); +} +---- + +==== MemoryManager_GetCoredumpUsage() + +[source,cpp] +---- +esp_err_t MemoryManager_GetCoredumpUsage(MemoryManager_Usage_t *p_Usage); +---- + +Get usage information for the coredump partition. + +**Parameters:** + +* `p_Usage` - Pointer to usage structure to populate + +**Return Values:** + +* `ESP_OK` - Coredump usage retrieved successfully +* `ESP_ERR_INVALID_ARG` - p_Usage is NULL +* `ESP_ERR_NOT_FOUND` - Coredump partition not found + +**Note:** Returns size of stored coredump data. If no crash has occurred, UsedBytes will be 0. + +**Example:** + +[source,cpp] +---- +MemoryManager_Usage_t CoredumpUsage; +esp_err_t Error = MemoryManager_GetCoredumpUsage(&CoredumpUsage); +if (Error == ESP_OK) { + if (CoredumpUsage.UsedBytes > 0) { + ESP_LOGW(TAG, "Coredump found: %zu bytes", CoredumpUsage.UsedBytes); + ESP_LOGW(TAG, "A previous crash was detected!"); + + // Show warning in UI + lv_obj_clear_flag(ui_Icon_CoredumpWarning, LV_OBJ_FLAG_HIDDEN); + } else { + ESP_LOGI(TAG, "No coredump stored"); + } +} else { + ESP_LOGE(TAG, "Failed to get coredump usage: %d", Error); +} +---- + +--- + +=== USB Integration + +==== MemoryManager_LockFilesystem() + +[source,cpp] +---- +esp_err_t MemoryManager_LockFilesystem(void); +---- + +Lock the filesystem for USB Mass Storage access. Prevents the application from writing to the filesystem while USB MSC is active. + +**Return Values:** + +* `ESP_OK` - Filesystem successfully locked +* `ESP_ERR_INVALID_STATE` - Already locked + +**Warning:** Application must not write to filesystem after locking. Data corruption will occur if both USB host and firmware access the filesystem simultaneously. + +**Thread Safety:** Thread-safe. + +**Example:** + +[source,cpp] +---- +// Before activating USB Mass Storage +esp_err_t Error = MemoryManager_LockFilesystem(); +if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to lock filesystem: %d", Error); + return Error; +} + +ESP_LOGW(TAG, "Filesystem locked - Application CANNOT write!"); + +// Now safe to activate USB MSC +Error = USBManager_Init(&USB_Config); +if (Error != ESP_OK) { + // Unlock on failure + MemoryManager_UnlockFilesystem(); + return Error; +} +---- + +==== MemoryManager_UnlockFilesystem() + +[source,cpp] +---- +esp_err_t MemoryManager_UnlockFilesystem(void); +---- + +Unlock the filesystem for application access. Re-enables filesystem write operations after USB Mass Storage is deactivated. + +**Return Values:** + +* `ESP_OK` - Filesystem successfully unlocked +* `ESP_ERR_INVALID_STATE` - Not locked + +**Note:** Call this function only after USB MSC has been safely deactivated and the host has ejected the device. + +**Thread Safety:** Thread-safe. + +**Example:** + +[source,cpp] +---- +// After USB Mass Storage deactivated +esp_err_t Error = MemoryManager_UnlockFilesystem(); +if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to unlock filesystem: %d", Error); + return Error; +} + +ESP_LOGI(TAG, "Filesystem unlocked - Application can write again!"); + +// Safe to write files now +FILE *fp = fopen("/storage/test.txt", "w"); +if (fp != NULL) { + fprintf(fp, "USB MSC deactivated, filesystem accessible again\n"); + fclose(fp); +} +---- + +==== MemoryManager_IsFilesystemLocked() + +[source,cpp] +---- +bool MemoryManager_IsFilesystemLocked(void); +---- + +Check if the filesystem is currently locked for USB access. + +**Return Values:** + +* `true` - Filesystem locked (USB active, application must not write) +* `false` - Filesystem unlocked (application can write safely) + +**Thread Safety:** Thread-safe. + +**Example:** + +[source,cpp] +---- +void SaveImage(uint8_t *p_ImageData, size_t ImageSize) +{ + // Check if filesystem is accessible + if (MemoryManager_IsFilesystemLocked()) { + ESP_LOGW(TAG, "Cannot save image - USB mode is active!"); + + // Show error message in UI + lv_obj_t *msgbox = lv_msgbox_create(NULL); + lv_msgbox_add_title(msgbox, "USB Active"); + lv_msgbox_add_text(msgbox, "Cannot save - USB mode is active!\nDisable USB first."); + + return; + } + + // Safe to write + const char *p_Path = MemoryManager_GetStoragePath(); + char FilePath[256]; + snprintf(FilePath, sizeof(FilePath), "%s/image_%d.jpg", p_Path, ImageCounter++); + + FILE *fp = fopen(FilePath, "wb"); + if (fp != NULL) { + fwrite(p_ImageData, 1, ImageSize, fp); + fclose(fp); + ESP_LOGI(TAG, "Image saved: %s", FilePath); + } +} +---- + +==== MemoryManager_SoftUnmountStorage() + +[source,cpp] +---- +esp_err_t MemoryManager_SoftUnmountStorage(void); +---- + +Soft-unmount the active storage VFS filesystem. Unmounts the FAT filesystem and unregisters the VFS path, but preserves the underlying storage handles (wear leveling handle or SD card handle). This allows USB Mass Storage to access the raw storage blocks while the VFS path is no longer accessible by the application. + +**Return Values:** + +* `ESP_OK` - Storage soft-unmounted successfully +* `ESP_ERR_INVALID_STATE` - Not initialized or storage not mounted +* `ESP_ERR_INVALID_ARG` - Unknown storage location + +**Thread Safety:** Not thread-safe. Call from the USB Manager task context only. + +**Example:** + +[source,cpp] +---- +// Prepare storage for USB MSC access +MemoryManager_LockFilesystem(); + +esp_err_t Error = MemoryManager_SoftUnmountStorage(); +if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to unmount storage for USB: %d", Error); + MemoryManager_UnlockFilesystem(); + + return Error; +} + +// VFS path is now inaccessible, raw storage available for USB MSC +---- + +==== MemoryManager_SoftRemountStorage() + +[source,cpp] +---- +esp_err_t MemoryManager_SoftRemountStorage(void); +---- + +Soft-remount the active storage VFS filesystem. Re-registers the storage with diskio, registers the VFS path, and mounts the FAT filesystem using the preserved storage handles from a previous `MemoryManager_SoftUnmountStorage()` call. + +**Return Values:** + +* `ESP_OK` - Storage remounted successfully +* `ESP_ERR_INVALID_STATE` - Not initialized or handles are invalid +* `ESP_ERR_INVALID_ARG` - Unknown storage location +* `ESP_FAIL` - FAT filesystem mount failed + +**Thread Safety:** Not thread-safe. Call from the USB Manager task context only. + +**Example:** + +[source,cpp] +---- +// Restore VFS access after USB MSC is done +esp_err_t Error = MemoryManager_SoftRemountStorage(); +if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to remount storage: %d", Error); +} + +MemoryManager_UnlockFilesystem(); + +// Application can now write files again +---- + +--- + +=== Partition Management + +==== MemoryManager_EraseStorage() + +[source,cpp] +---- +esp_err_t MemoryManager_EraseStorage(void); +---- + +Erase the current storage location completely and reformat it. + +**Warning:** This will delete ALL files in the active storage filesystem. Operation is irreversible. + +**Behavior:** + +1. Unmounts the filesystem +2. Erases the partition (internal flash) or reformats SD card +3. Remounts the filesystem (clean/empty) + +**Return Values:** + +* `ESP_OK` - Storage successfully erased and reformatted +* `ESP_ERR_NOT_FOUND` - Storage not found +* `ESP_FAIL` - Erase or mount operation failed + +**Note:** Filesystem will be automatically reformatted after erase. + +**Example:** + +[source,cpp] +---- +void FactoryReset_Storage(void) +{ + ESP_LOGW(TAG, "Erasing storage partition..."); + + esp_err_t Error = MemoryManager_EraseStorage(); + if (Error == ESP_OK) { + ESP_LOGI(TAG, "Storage partition erased and reformatted"); + + // Show success message + lv_obj_t *msgbox = lv_msgbox_create(NULL); + lv_msgbox_add_title(msgbox, "Storage Erased"); + lv_msgbox_add_text(msgbox, "All files have been deleted."); + } else { + ESP_LOGE(TAG, "Failed to erase storage: %d", Error); + } +} +---- + +==== MemoryManager_EraseCoredump() + +[source,cpp] +---- +esp_err_t MemoryManager_EraseCoredump(void); +---- + +Erase the coredump partition completely. + +**Warning:** This will delete any stored crash dumps. Use cautiously if debugging. + +**Behavior:** + +1. Finds the coredump partition +2. Erases the entire partition +3. Partition is ready for new coredumps + +**Return Values:** + +* `ESP_OK` - Coredump partition successfully erased +* `ESP_ERR_NOT_FOUND` - Coredump partition not found +* `ESP_FAIL` - Erase operation failed + +**Example:** + +[source,cpp] +---- +void ClearCoredump(void) +{ + ESP_LOGW(TAG, "Erasing coredump partition..."); + + esp_err_t Error = MemoryManager_EraseCoredump(); + if (Error == ESP_OK) { + ESP_LOGI(TAG, "Coredump partition cleared"); + + // Hide coredump warning icon + lv_obj_add_flag(ui_Icon_CoredumpWarning, LV_OBJ_FLAG_HIDDEN); + } else { + ESP_LOGE(TAG, "Failed to erase coredump: %d", Error); + } +} +---- + +--- + +=== Advanced Access + +==== MemoryManager_GetWearLevelingHandle() + +[source,cpp] +---- +esp_err_t MemoryManager_GetWearLevelingHandle(wl_handle_t *p_Handle); +---- + +Get the wear leveling handle for internal flash storage. + +**Parameters:** + +* `p_Handle` - Pointer to receive the wear leveling handle + +**Return Values:** + +* `ESP_OK` - Handle retrieved successfully +* `ESP_ERR_INVALID_ARG` - p_Handle is NULL +* `ESP_ERR_INVALID_STATE` - Not using internal storage or not mounted + +**Note:** Only valid for internal flash storage (not SD card). Required for USB MSC TinyUSB integration. + +**Example:** + +[source,cpp] +---- +wl_handle_t WL_Handle; +esp_err_t Error = MemoryManager_GetWearLevelingHandle(&WL_Handle); +if (Error == ESP_OK) { + ESP_LOGD(TAG, "Wear leveling handle: %d", WL_Handle); + + // Use with TinyUSB MSC + tinyusb_msc_storage_config_t msc_cfg; + memset(&msc_cfg, 0, sizeof(tinyusb_msc_storage_config_t)); + msc_cfg.medium.wl_handle = WL_Handle; + + Error = tinyusb_msc_new_storage_spiflash(&msc_cfg, &Storage); +} else { + ESP_LOGE(TAG, "Not using internal storage or not mounted"); +} +---- + +==== MemoryManager_GetSDCardHandle() + +[source,cpp] +---- +esp_err_t MemoryManager_GetSDCardHandle(sdmmc_card_t **pp_Card); +---- + +Get the SD card handle for direct SDMMC access. + +**Parameters:** + +* `pp_Card` - Pointer to receive the SD card handle pointer + +**Return Values:** + +* `ESP_OK` - Handle retrieved successfully +* `ESP_ERR_INVALID_ARG` - pp_Card is NULL +* `ESP_ERR_INVALID_STATE` - SD card not mounted + +**Note:** Only valid when SD card storage is active. Required for USB MSC TinyUSB integration. + +**Example:** + +[source,cpp] +---- +sdmmc_card_t *p_Card = NULL; +esp_err_t Error = MemoryManager_GetSDCardHandle(&p_Card); +if (Error == ESP_OK) { + ESP_LOGD(TAG, "SD card name: %s", p_Card->cid.name); + ESP_LOGD(TAG, "SD card sectors: %llu", p_Card->csd.capacity); + + // Use with TinyUSB MSC + tinyusb_msc_storage_config_t msc_cfg; + memset(&msc_cfg, 0, sizeof(tinyusb_msc_storage_config_t)); + msc_cfg.medium.card = p_Card; + + Error = tinyusb_msc_new_storage_sdmmc(&msc_cfg, &Storage); +} else { + ESP_LOGE(TAG, "SD card not mounted or not in use"); +} +---- + +--- + +== Common Use Cases + +=== Use Case 1: Application Startup + +[source,cpp] +---- +void app_main(void) +{ + // Initialize NVS + esp_err_t Error = nvs_flash_init(); + ESP_ERROR_CHECK(Error); + + // Initialize Memory Manager + Error = MemoryManager_Init(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize storage: %d", Error); + esp_restart(); + } + + // Log storage information + if (MemoryManager_HasSDCard()) { + ESP_LOGI(TAG, "Storage: SD Card at %s", MemoryManager_GetStoragePath()); + } else { + ESP_LOGI(TAG, "Storage: Internal Flash at %s", MemoryManager_GetStoragePath()); + } + + // Check available space + MemoryManager_Usage_t Usage; + if (MemoryManager_GetStorageUsage(&Usage) == ESP_OK) { + ESP_LOGI(TAG, "Free space: %zu bytes (%.2f MB)", + Usage.FreeBytes, Usage.FreeBytes / (1024.0 * 1024.0)); + } + + // Continue with other initialization... +} +---- + +=== Use Case 2: Saving Files + +[source,cpp] +---- +esp_err_t SaveThermalImage(uint8_t *p_ImageData, size_t ImageSize) +{ + // Check if filesystem is accessible + if (MemoryManager_IsFilesystemLocked()) { + ESP_LOGW(TAG, "Cannot save - USB mode active!"); + return ESP_ERR_INVALID_STATE; + } + + // Check available space + MemoryManager_Usage_t Usage; + if (MemoryManager_GetStorageUsage(&Usage) == ESP_OK) { + if (Usage.FreeBytes < ImageSize) { + ESP_LOGE(TAG, "Insufficient space: need %zu, have %zu", ImageSize, Usage.FreeBytes); + return ESP_ERR_NO_MEM; + } + } + + // Build file path + const char *p_BasePath = MemoryManager_GetStoragePath(); + char FilePath[256]; + time_t now; + time(&now); + struct tm timeinfo; + localtime_r(&now, &timeinfo); + + snprintf(FilePath, sizeof(FilePath), "%s/thermal_%04d%02d%02d_%02d%02d%02d.jpg", + p_BasePath, + timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + + // Save file + FILE *fp = fopen(FilePath, "wb"); + if (fp == NULL) { + ESP_LOGE(TAG, "Failed to open file: %s", FilePath); + return ESP_FAIL; + } + + size_t Written = fwrite(p_ImageData, 1, ImageSize, fp); + fclose(fp); + + if (Written != ImageSize) { + ESP_LOGE(TAG, "Write error: expected %zu, wrote %zu", ImageSize, Written); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Image saved: %s (%zu bytes)", FilePath, ImageSize); + return ESP_OK; +} +---- + +=== Use Case 3: USB Mass Storage Activation + +The USB MSC workflow uses soft-unmount/remount to give the USB host exclusive block-level access to the storage while preserving the underlying storage handles for later remount. + +[source,cpp] +---- +esp_err_t EnableUSB_MassStorage(void) +{ + // Lock filesystem for USB access + esp_err_t Error = MemoryManager_LockFilesystem(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to lock filesystem: %d", Error); + return Error; + } + + // Soft-unmount VFS so USB host gets exclusive raw block access + Error = MemoryManager_SoftUnmountStorage(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to soft-unmount storage: %d", Error); + MemoryManager_UnlockFilesystem(); + return Error; + } + + // Create TinyUSB MSC storage with TINYUSB_MSC_STORAGE_MOUNT_USB + // (actual implementation in USBMSC_Init) + + return ESP_OK; +} + +esp_err_t DisableUSB_MassStorage(void) +{ + // Delete TinyUSB MSC storage + // (actual implementation in USBMSC_Deinit) + + // Soft-remount VFS to restore application file I/O + esp_err_t Error = MemoryManager_SoftRemountStorage(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to soft-remount storage: %d", Error); + } + + Error = MemoryManager_UnlockFilesystem(); + if (Error == ESP_OK) { + ESP_LOGI(TAG, "Filesystem unlocked - Application can write again!"); + } + + return Error; +} +---- + +=== Use Case 4: Storage Diagnostics + +[source,cpp] +---- +void PrintStorageDiagnostics(void) +{ + ESP_LOGI(TAG, "=== Storage Diagnostics ==="); + + // Storage location + if (MemoryManager_HasSDCard()) { + ESP_LOGI(TAG, "Location: SD Card (External)"); + } else { + ESP_LOGI(TAG, "Location: Internal Flash (SPI)"); + } + + // Mount point + ESP_LOGI(TAG, "Path: %s", MemoryManager_GetStoragePath()); + + // Storage usage + MemoryManager_Usage_t StorageUsage; + if (MemoryManager_GetStorageUsage(&StorageUsage) == ESP_OK) { + ESP_LOGI(TAG, "Storage Usage:"); + ESP_LOGI(TAG, " Total: %.2f MB", StorageUsage.TotalBytes / (1024.0 * 1024.0)); + ESP_LOGI(TAG, " Used: %.2f MB (%u%%)", + StorageUsage.UsedBytes / (1024.0 * 1024.0), StorageUsage.UsedPercent); + ESP_LOGI(TAG, " Free: %.2f MB", StorageUsage.FreeBytes / (1024.0 * 1024.0)); + } + + // Coredump status + MemoryManager_Usage_t CoredumpUsage; + if (MemoryManager_GetCoredumpUsage(&CoredumpUsage) == ESP_OK) { + if (CoredumpUsage.UsedBytes > 0) { + ESP_LOGW(TAG, "Coredump: %zu bytes (crash detected!)", CoredumpUsage.UsedBytes); + } else { + ESP_LOGI(TAG, "Coredump: Empty (no crashes)"); + } + } + + // Filesystem lock status + if (MemoryManager_IsFilesystemLocked()) { + ESP_LOGW(TAG, "Filesystem: LOCKED (USB active)"); + } else { + ESP_LOGI(TAG, "Filesystem: Unlocked (app can write)"); + } + + ESP_LOGI(TAG, "========================="); +} +---- + +--- + +== Best Practices + +=== Storage Selection + +* *Automatic Detection*: Let Memory Manager auto-detect storage - do not hardcode assumptions +* *Check Availability*: Always check `MemoryManager_HasSDCard()` if behavior depends on storage type +* *Path Usage*: Always use `MemoryManager_GetStoragePath()` for base path - never hardcode "/storage" or "/sdcard" + +=== File Operations + +* *Check Lock State*: Always call `MemoryManager_IsFilesystemLocked()` before writing files +* *Error Handling*: Always check return values from file operations +* *Space Checks*: Check available space before writing large files with `MemoryManager_GetStorageUsage()` +* *Sync After Write*: Call `fsync()` or `fclose()` to ensure data is written to storage + +=== USB Integration + +* *Lock Before USB*: Always lock filesystem with `MemoryManager_LockFilesystem()` before activating USB MSC +* *Soft-Unmount for MSC*: Call `MemoryManager_SoftUnmountStorage()` after locking to give USB host exclusive block access +* *Soft-Remount After MSC*: Call `MemoryManager_SoftRemountStorage()` after MSC is done to restore VFS file I/O +* *Unlock After USB*: Always unlock filesystem with `MemoryManager_UnlockFilesystem()` after remounting +* *Safe Ejection*: Ensure PC has ejected/unmounted the USB drive before deactivating USB MSC +* *No Writes During USB*: Never write to filesystem while USB MSC is active + +=== SD Card Handling + +* *Removable Media*: SD card can be removed at runtime - implement error handling for file operations +* *Power Cycling*: SD card mount may fail after deep sleep - add retry logic +* *Format Requirements*: SD card must be formatted as FAT32 before use + +=== Partition Management + +* *Coredump Preservation*: Do not erase coredump partition unless you've extracted the crash logs +* *Factory Reset*: Use `MemoryManager_EraseStorage()` for complete data wipe in factory reset scenarios +* *User Confirmation*: Always require user confirmation before erasing storage + +--- + +== Troubleshooting + +=== SD Card Not Detected + +**Symptoms:** +* `MemoryManager_HasSDCard()` returns false +* Using internal flash despite SD card being inserted + +**Possible Causes:** +* SD card not inserted or not properly seated +* SD card not formatted or corrupted filesystem +* SD card not FAT32 formatted +* SDMMC interface initialization failed + +**Solutions:** +* Check SD card is properly inserted +* Format SD card as FAT32 on PC +* Check ESP32 GPIO connections for SDMMC +* Enable debug logging: `idf.py menuconfig → Component config → Log output` + +=== Filesystem Lock Issues + +**Symptoms:** +* Cannot save files even after USB MSC deactivation +* `MemoryManager_IsFilesystemLocked()` returns true unexpectedly + +**Possible Causes:** +* `MemoryManager_UnlockFilesystem()` not called after USB deactivation +* USB Manager crashed during deinitialization + +**Solutions:** +* Ensure `MemoryManager_UnlockFilesystem()` is called in USB_EVENT_UNINITIALIZED handler +* Restart device to reset filesystem lock state + +=== Out of Space Errors + +**Symptoms:** +* File writes fail with no error message +* `fopen()` returns NULL +* `MemoryManager_GetStorageUsage()` shows 100% usage + +**Possible Causes:** +* Storage partition full +* Many small files (FAT32 cluster overhead) +* Corrupted filesystem + +**Solutions:** +* Delete old files to free space +* Erase storage partition: `MemoryManager_EraseStorage()` +* Use SD card for larger capacity +* Implement automatic old file cleanup + +=== Wear Leveling Warnings + +**Symptoms:** +* ESP_LOGW messages about wear leveling +* Reduced write performance on internal flash + +**Possible Causes:** +* High write frequency to internal flash +* Flash nearing end of life + +**Solutions:** +* Use SD card for high-frequency writes +* Reduce write frequency (batch writes, use RAM buffer) +* Monitor flash health with wear leveling statistics + +--- + +== Configuration + +=== PlatformIO / ESP-IDF + +Memory Manager requires the following components: + +[source,ini] +---- +[env] +platform = espressif32 +framework = espidf +board = esp32-s3-devkitc-1 + +lib_deps = + espressif/esp_tinyusb # For USB MSC integration + joltwallet/littlefs # If using LittleFS (optional) +---- + +=== Partition Table + +Define storage and coredump partitions in `partitions.csv`: + +[source] +---- +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x300000, +storage, data, fat, 0x310000,0x100000, # 1 MB internal storage +coredump, data, coredump, 0x410000,0x10000, # 64 KB coredump +---- + +Adjust `storage` partition size based on your requirements. + +=== SDMMC Configuration + +Configure SDMMC pins in `sdkconfig`: + +[source] +---- +CONFIG_SDMMC_HOST_MAX_FREQ_HIGHSPEED=40000000 +CONFIG_SDMMC_HOST_SLOT_AUTO_DETECT_EN=y +---- + +GPIO pin assignment is typically done in `DeviceManager` (I2C bus, SDMMC pins, etc.). + +--- + +== Advanced Topics + +=== Custom Mount Points + +While Memory Manager uses `/storage` and `/sdcard`, you can mount additional partitions: + +[source,cpp] +---- +// Mount additional partition +const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 5, + .format_if_mount_failed = true, +}; + +wl_handle_t wl_handle; +esp_err_t Error = esp_vfs_fat_spiflash_mount("/data", "data_partition", + &mount_config, &wl_handle); +if (Error == ESP_OK) { + ESP_LOGI(TAG, "Custom partition mounted at /data"); +} +---- + +=== LittleFS Integration + +Memory Manager currently uses FAT32, but LittleFS can be integrated: + +[source,cpp] +---- +const esp_vfs_littlefs_conf_t lfs_conf = { + .base_path = "/lfs", + .partition_label = "littlefs", + .format_if_mount_failed = true, +}; + +esp_err_t Error = esp_vfs_littlefs_register(&lfs_conf); +if (Error == ESP_OK) { + ESP_LOGI(TAG, "LittleFS mounted at /lfs"); +} +---- + +Note: LittleFS is not supported by USB MSC (requires FAT filesystem). + +=== SPIFFS Alternative + +SPIFFS is deprecated but still available: + +[source,cpp] +---- +esp_vfs_spiffs_conf_t spiffs_conf = { + .base_path = "/spiffs", + .partition_label = "spiffs", + .max_files = 5, + .format_if_mount_failed = true, +}; + +esp_err_t Error = esp_vfs_spiffs_register(&spiffs_conf); +if (Error == ESP_OK) { + ESP_LOGI(TAG, "SPIFFS mounted at /spiffs"); +} +---- + +Note: SPIFFS does not support wear leveling and is not recommended for new projects. + +--- + +== Related Documentation + +* link:USBManager.adoc[USB Manager Documentation] - USB Mass Storage Device integration +* link:SettingsManager.adoc[Settings Manager Documentation] - Persistent settings storage +* link:DeviceManager.adoc[Device Manager Documentation] - Hardware initialization (I2C, SDMMC) + +== References + +* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/wear-levelling.html[ESP-IDF Wear Levelling] +* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/fatfs.html[ESP-IDF FAT Filesystem] +* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/sdmmc_host.html[ESP-IDF SDMMC Host Driver] +* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html[ESP-IDF Partition Tables] + +--- + +**Last Updated:** February 8, 2026 + +**Maintainer:** Daniel Kampert (DanielKampert@kampis-elektroecke.de) + +**License:** GNU General Public License v3.0 diff --git a/docs/MemoryMap.adoc b/docs/MemoryMap.adoc new file mode 100644 index 0000000..96bbf83 --- /dev/null +++ b/docs/MemoryMap.adoc @@ -0,0 +1,555 @@ += PyroVision Firmware – Memory Map +Daniel Kampert +:doctype: article +:toc: left +:toclevels: 3 +:icons: font +:source-highlighter: rouge +:revdate: 2026-02-26 + +== Overview + +This document provides a complete inventory of all heap allocations, task stacks, and +DMA-relevant memory regions in the PyroVision firmware. + +The ESP32-S3 memory architecture distinguishes three relevant regions: + +[cols="1,2,2,2"] +|=== +| Region | Address Range | Size | DMA-capable? + +| Internal SRAM (DMA) +| `0x3FC88000` – `0x3FD00000` +| ~480 KB +| *YES* (`esp_ptr_dma_capable()` == true) + +| General Internal SRAM +| `0x3FC80000` – `0x3FD20000` +| ~640 KB total +| Partially + +| PSRAM (external OPI) +| `0x3C000000` – `0x3FFFFFFF` +| 8 MB (device: 8 MB fitted) +| *NO* → SPI driver allocates internal bounce buffers + +|=== + +WARNING: The SPI master driver allocates an internal DMA bounce buffer for *every* +queued transaction when the source buffer is in PSRAM. With `CONFIG_SPI_TRANSFER_SIZE=4096`, +each bounce buffer is 4,096 bytes. The number of simultaneous bounce buffers equals +`trans_queue_depth`. See <>. + +--- + +== Static / Permanent Allocations + +These are allocated once at module initialization and released only on `Deinit()`. + +=== GUI Task (`guiTask.cpp` / `guiHelper.cpp`) + +[cols="3,1,1,2"] +|=== +| Buffer | Size (Bytes) | Heap | Source + +| LVGL Draw Buffer 1 + + `(2 × 320 × 240 × 2) / 10` → 1/10 of full RGB565 frame +| 30,720 +| PSRAM +| `guiHelper.cpp` – `GUI_DRAW_BUFFER_SIZE` + +| LVGL Draw Buffer 2 (second partial buffer for DMA ping-pong) +| 30,720 +| PSRAM +| `guiHelper.cpp` – `GUI_DRAW_BUFFER_SIZE` + +| Thermal Canvas Buffer + + Scaled 240×180 RGB565 image for LVGL display widget +| 86,400 +| PSRAM +| `guiTask.cpp:1138` – `240 * 180 * 2` + +| Gradient Canvas Buffer + + Temperature scale palette strip (20×180 RGB565) +| 7,200 +| PSRAM +| `guiTask.cpp:1139` – `20 * 180 * 2` + +| Network RGB Buffer + + 240×180 RGB888 image for HTTP/WebSocket streaming +| 129,600 +| PSRAM +| `guiTask.cpp:1140` – `240 * 180 * 3` + +|=== + +*GUI Task PSRAM subtotal (permanent): 284,640 bytes (≈ 278 KB)* + +=== Lepton Task (`leptonTask.cpp`) + +[cols="3,1,1,2"] +|=== +| Buffer | Size (Bytes) | Heap | Source + +| RGB Buffer[0] + + Ping-pong buffer A for 160×120 RGB888 Lepton frames +| 57,600 +| PSRAM +| `leptonTask.cpp:676` – `160 * 120 * 3` + +| RGB Buffer[1] + + Ping-pong buffer B for 160×120 RGB888 Lepton frames +| 57,600 +| PSRAM +| `leptonTask.cpp:677` – `160 * 120 * 3` + +| JPEG Compression Buffer + + Output buffer for JPEG encoder (used for UVC transmission). + + Allocated only when `USBUVC_Init()` path is active. +| 57,600 +| PSRAM +| `leptonTask.cpp:719` – `160 * 120 * 3` + +|=== + +*Lepton Task PSRAM subtotal (permanent): 172,800 bytes (≈ 169 KB)* + +=== USB UVC Module (`usbUVC.cpp`) + +Only allocated when UVC is enabled via `USBUVC_Init()`. + +[cols="3,1,1,2"] +|=== +| Buffer | Size (Bytes) | Heap | Source + +| UVC Frame Buffer[0] + + Ping-pong MJPEG output buffer A (`UVC_MAX_FRAME_SIZE = 160×120×3`) +| 57,600 +| PSRAM +| `usbUVC.cpp:190` – `UVC_MAX_FRAME_SIZE` + +| UVC Frame Buffer[1] + + Ping-pong MJPEG output buffer B +| 57,600 +| PSRAM +| `usbUVC.cpp:190` – `UVC_MAX_FRAME_SIZE` + +|=== + +*UVC PSRAM subtotal (when enabled): 115,200 bytes (≈ 112 KB)* + +=== PSRAM Grand Total + +[cols="2,1"] +|=== +| Condition | Total PSRAM (permanent) + +| Normal operation (GUI + Lepton, no UVC) +| 457,440 bytes ≈ *446 KB* + +| With UVC enabled +| 572,640 bytes ≈ *559 KB* + +|=== + +--- + +== Dynamic / Temporary Allocations + +These are allocated on-demand and freed after use. + +[cols="3,2,1,2"] +|=== +| Allocation | Max Size | Heap | Source + +| Settings JSON loader + + Reads `settings_default.json` into PSRAM for `cJSON` parsing. + Freed after parsing completes. +| `FileSize + 1` bytes + + (settings_default.json ≈ 2–5 KB) +| PSRAM +| `settingsJSONLoader.cpp:445` + +| HTTP request body buffer + + Allocates the full POST/PUT body for REST handlers. + Freed after handler returns. +| `content_len + 1` bytes + + (vary, typically < 4 KB) +| PSRAM +| `http_server.cpp:153` + +| HTTP response scratch buffer + + Used for JSON response construction. + Freed after send. +| 1,024 bytes +| PSRAM +| `http_server.cpp:394` + +| Provisioning scan buffer + + Wi-Fi AP list from `esp_wifi_scan_get_results()`. + Internal heap (no PSRAM flag). +| `sizeof(wifi_ap_record_t) × AP_count` + + ≈ `88 × N` bytes +| Internal +| `provisionHandlers.cpp:176` + +| Provisioning JSON response buffer + + Serialized scan results for browser. + Freed after HTTP send. +| `ContentLen + 1` bytes +| PSRAM +| `provisionHandlers.cpp:222` + +| WebSocket inbound frame payload + + One allocation per received WS message. + Freed after processing. +| `Frame.len + 1` bytes + + (typically < 512 bytes) +| PSRAM +| `websocket.cpp:437` + +| VISA binary data buffer + + Scratch for binary SCPI responses. + Freed after send. +| 1,024 bytes +| PSRAM +| `visaCommands.cpp:301` + +| Image encoder RGB buffer + + Full-frame RGB888 intermediate for PNG/JPEG encoding. + Freed after encode. +| `Width × Height × 3` bytes + + (240×180×3 = 129,600 bytes) +| PSRAM +| `imageEncoder.cpp:119` + +| JPEG encoder output buffer + + MJPEG output for snapshot endpoint. + Freed after HTTP send. +| `Width × Height × 3` bytes + + (worst-case = source size, typically 10×smaller) +| PSRAM +| `jpegEncoder.cpp:70` + +| PNG encoder output buffer + + PNG encoded snapshot. + Freed after HTTP send. +| varies (png_size, typically < 50 KB) +| PSRAM +| `pngEncoder.cpp:58` + +|=== + +--- + +[[spi_dma_constraint]] +== SPI DMA Bounce Buffers (Critical Constraint) + +PSRAM is *not* in the DMA-capable address range (`SOC_DMA_LOW=0x3FC88000` – +`SOC_DMA_HIGH=0x3FD00000`). The SPI master driver (`spi_master.c:setup_priv_desc`) +therefore allocates an internal DMA bounce buffer for **every** queued LCD transaction +when the source buffer is in PSRAM. + +[source,c] +---- +// spi_master.c – setup_priv_desc() +if (!esp_ptr_dma_capable(send_ptr) || tx_unaligned) { + uint32_t *temp = heap_caps_aligned_alloc(alignment, tx_byte_len, MALLOC_CAP_DMA); + // tx_byte_len = min(transfer, CONFIG_SPI_TRANSFER_SIZE) = 4,096 bytes +} +---- + +[cols="2,1"] +|=== +| Parameter | Value + +| `CONFIG_SPI_TRANSFER_SIZE` +| 4,096 bytes + +| LCD SPI `trans_queue_depth` +| 3 (set in `guiHelper.cpp`) + +| Max simultaneous bounce buffers +| 3 + +| Peak internal DMA RAM consumed (LCD) +| `3 × 4,096 = 12,288 bytes` + +|=== + +WARNING: Before this was fixed, `trans_queue_depth=10` caused up to `8 × 4,096 = 32,768 bytes` +of simultaneous bounce buffer allocations. After UVC init, only ~28 KB DMA RAM was free +→ allocation failure → `ESP_ERR_NO_MEM` from `spi_device_queue_trans` → display freeze. +*Do not increase `trans_queue_depth` above 6* (= 24,576 bytes, leaves margin with UVC active). + +--- + +== FreeRTOS Task Stacks (Internal RAM) + +All task stacks are allocated from internal SRAM by the FreeRTOS heap. + +[cols="2,1,1,1,1"] +|=== +| Task Name | Stack (Bytes) | Priority | Core | Source + +| `Task_GUI` +| 16,384 +| 2 +| Core 0 +| `guiTask.cpp`, `CONFIG_GUI_TASK_STACKSIZE` + +| `Task_Network` +| 8,192 +| 16 +| Core 1 +| `networkTask.cpp`, `CONFIG_NETWORK_TASK_STACKSIZE` + +| `Task_Lepton` +| 8,192 +| 16 +| Core 1 +| `leptonTask.cpp`, `CONFIG_LEPTON_TASK_STACKSIZE` + +| `Task_Camera` +| 8,192 +| 12 +| Core 1 +| `cameraTask.cpp`, `CONFIG_CAMERA_TASK_STACKSIZE` + +| `Task_Devices` +| 4,096 +| 16 +| Core 1 +| `devicesTask.cpp`, `CONFIG_DEVICES_TASK_STACKSIZE` + +| `Task_ImgSave` +| 8,192 +| 1 (GUI−1) +| Core 1 +| `guiTask.cpp:1177` + +| `USBMonTask` +| 4,096 +| 5 +| Any +| `usbManager.cpp:269` + +| `DNS_Server` +| 4,096 +| 3 +| Core 1 +| `dnsServer.cpp:194` + +| `WS_Broadcast` +| 4,096 +| 5 +| Core 1 +| `websocket.cpp:828` + +| `visa_server` +| 4,096 +| 5 +| Any +| `visaServer.cpp:326` + +| `UVC0` (`video_task`, when UVC active) +| 4,096 +| 5 +| Core 1 +| `tinyusb_uvc.c` – hardcoded + +| TinyUSB `tud_task` (component) +| see `CONFIG_TINYUSB_TASK_STACK_SIZE` +| 5 +| Core 1 +| `tinyusb.c` + +|=== + +*Task stack subtotal (without TinyUSB): 73,728 bytes (≈ 72 KB)* + +--- + +== FreeRTOS Objects (Internal RAM) + +Each object below occupies a small amount of internal SRAM for its control block. +Approximate per-object overhead: `SemaphoreHandle_t` ≈ 88 bytes, +`QueueHandle_t` ≈ 76 bytes + item storage, `EventGroupHandle_t` ≈ 52 bytes. + +[cols="2,1,1,2"] +|=== +| Object | Type | Count | Source + +| `SettingsManager` mutex +| Mutex +| 1 +| `settingsManager.cpp:125` + +| `SPI[n]` mutex (per bus, max 2) +| Mutex +| 2 +| `spi.cpp:66` + +| `I2C` mutex +| Mutex +| 1 +| `i2c.cpp:51` + +| `VISA Server` mutex +| Mutex +| 1 +| `visaServer.cpp:260` + +| `WebSocket` clients mutex +| Mutex +| 1 +| `websocket.cpp:548` + +| `RemoteControl` mutex +| Mutex +| 1 +| `remoteControl.cpp:265` + +| `UVC` buffer mutex +| Mutex +| 1 +| `usbUVC.cpp:180` + +| `GUI` network frame mutex +| Mutex +| 1 +| `guiTask.cpp:1237` + +| `Lepton` buffer mutex +| Mutex +| 1 +| `leptonTask.cpp:657` + +| `USB_Manager` command queue (8 items × `sizeof(USB_Manager_Cmd_t)`) +| Queue +| 1 +| `usbManager.cpp:259` + +| `App_Context` Lepton frame queue (1 item × `sizeof(App_Lepton_FrameReady_t)`) +| Queue +| 1 +| `main.cpp:46` + +| `GUI` image save queue (1 item × `sizeof(App_Lepton_FrameReady_t)`) +| Queue +| 1 +| `guiTask.cpp:1166` + +| `Lepton` raw frame queue (1 item × `sizeof(Lepton_FrameBuffer_t)`) +| Queue +| 1 +| `leptonTask.cpp:700` + +| `WebSocket` frame-ready queue (1 item × `sizeof(uint8_t)`) +| Queue +| 1 +| `websocket.cpp:556` + +| `Network Task` event group +| EventGroup +| 1 +| `networkTask.cpp:450` + +| `Network Manager` event group +| EventGroup +| 1 +| `networkManager.cpp:308` + +| `Lepton Task` event group +| EventGroup +| 1 +| `leptonTask.cpp:649` + +| `Devices Task` event group +| EventGroup +| 1 +| `devicesTask.cpp:138` + +| `Camera Task` event group +| EventGroup +| 1 +| `cameraTask.cpp:90` + +| `GUI Helper` event group +| EventGroup +| 1 +| `guiHelper.cpp:291` + +|=== + +--- + +== Static Data (BSS / Data Segment) + +[cols="3,1,2"] +|=== +| Object | Size | Source + +| VISA error queue `int[10]` +| 40 bytes +| `visaCommands.cpp:43` – `CONFIG_NETWORK_VISA_ERROR_QUEUE_LLENGTH=10` + +| USB LangID string `char[2]` +| 2 bytes +| `usbManager.cpp:76` + +|=== + +--- + +== Memory Budget Summary + +[cols="2,1,1"] +|=== +| Category | Bytes | Notes + +| PSRAM – permanent allocations (no UVC) +| 457,440 +| ≈ 446 KB + +| PSRAM – additional when UVC active +| 115,200 +| ≈ 112 KB + +| PSRAM – peak dynamic (snapshot encode) +| ~130,000 +| RGB + JPEG/PNG buffers, temporary + +| Internal RAM – task stacks +| ~80,000 +| Including TinyUSB task + +| Internal DMA RAM – LCD bounce buffers (peak) +| 12,288 +| 3 × 4,096 bytes + +| Internal RAM – FreeRTOS objects +| ~3,000 +| Approx. + +|=== + +*Available PSRAM*: 8 MB total. +*Available internal SRAM*: ~400 KB total (shared with ESP-IDF system stacks, Wi-Fi, TCP/IP). + +--- + +== Memory Documentation Rules + +When adding or modifying memory allocations: + +. **Always document** new `heap_caps_malloc` / `malloc` calls in this file, in the + appropriate section, within the **same commit/session** as the code change. +. **Recalculate** the section subtotal and the summary table when a size changes. +. **Mark conditional allocations** (e.g. UVC, SD card) clearly with a note. +. **Specify the heap type**: PSRAM, Internal, DMA-capable internal. +. **For task stacks**: add a row to the task stack table; update the subtotal. +. **For temporary allocations**: note the maximum possible size and when it is freed. +. **DMA bounce buffer budget**: verify that `trans_queue_depth × CONFIG_SPI_TRANSFER_SIZE` + still fits within the available DMA RAM *after* adding any new permanent internal + allocations. Current safe headroom: ~16 KB after UVC init. diff --git a/docs/NetworkManager.adoc b/docs/NetworkManager.adoc new file mode 100644 index 0000000..2243d47 --- /dev/null +++ b/docs/NetworkManager.adoc @@ -0,0 +1,1213 @@ += PyroVision Network Manager Documentation +Daniel Kampert +v1.0, 2026-01-14 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The Network Manager is the central networking component for the PyroVision firmware. It manages WiFi connectivity (both Station and Access Point modes), HTTP/WebSocket servers, VISA/SCPI server, WiFi provisioning, and network events. The Network Manager provides a unified API for all network operations with automatic reconnection, retry logic, and comprehensive state management. + +=== Key Features + +* *WiFi Management*: Station (STA) and Access Point (AP) modes +* *Automatic Reconnection*: Configurable retry logic with intelligent backoff +* *HTTP/WebSocket Server*: Real-time thermal imaging data streaming +* *VISA/SCPI Server*: Standard instrumentation interface (port 5025) +* *WiFi Provisioning*: BLE and SoftAP provisioning support +* *Event-Driven Architecture*: Comprehensive event system for state changes +* *Network Monitoring*: RSSI, IP info, connection statistics +* *Credentials Management*: Secure storage in NVS +* *State Machine*: Well-defined connection states + +--- + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Manager/Network/ +├── networkManager.h # Public API +├── networkManager.cpp # Implementation +├── networkTypes.h # Type definitions and events +├── Server/ # HTTP/WebSocket server +│ ├── server.h +│ └── server.cpp +├── VISA/ # VISA/SCPI server +│ ├── visa.h +│ └── visa.cpp +├── SNTP/ # SNTP time sync (used by Time Manager) +│ ├── sntp.h +│ └── sntp.cpp +└── Provisioning/ # WiFi provisioning + ├── provisioning.h + └── provisioning.cpp +---- + +=== Network State Machine + +The Network Manager uses a state machine to track connection status: + +[source] +---- +┌──────────────┐ +│ IDLE │ ← Initial state, WiFi not started +└──────┬───────┘ + │ NetworkManager_StartSTA() + ↓ +┌──────────────┐ +│ CONNECTING │ ← Attempting to connect to AP +└──┬───────────┘ + │ + ├─→ [Success] → CONNECTED + │ + ├─→ [Retry] → CONNECTING (up to MaxRetries) + │ + └─→ [Max retries] → ERROR + +┌──────────────┐ +│ CONNECTED │ ← Connected, has IP address +└──┬───────────┘ + │ + ├─→ [Disconnect] → DISCONNECTED + │ + └─→ [Lost IP] → CONNECTING (auto-retry) + +┌──────────────┐ +│ DISCONNECTED │ ← WiFi disconnected (network lost, etc.) +└──────────────┘ + +┌──────────────┐ +│ AP_STARTED │ ← Access Point mode active +└──────────────┘ + +┌──────────────┐ +│ PROVISIONING │ ← Provisioning mode (BLE or SoftAP) +└──────────────┘ + +┌──────────────┐ +│ ERROR │ ← Max retries exceeded, manual intervention needed +└──────────────┘ +---- + +--- + +== Data Structures + +=== Network State Enum + +[source,cpp] +---- +typedef enum { + NETWORK_STATE_IDLE = 0, // WiFi not started + NETWORK_STATE_CONNECTING, // Attempting connection + NETWORK_STATE_CONNECTED, // Connected with IP + NETWORK_STATE_DISCONNECTED, // Disconnected from AP + NETWORK_STATE_PROVISIONING, // Provisioning mode active + NETWORK_STATE_AP_STARTED, // Access Point started + NETWORK_STATE_ERROR, // Error state (max retries) +} Network_State_t; +---- + +=== WiFi Configuration Structures + +==== WiFi Credentials + +[source,cpp] +---- +typedef struct { + char SSID[33]; // WiFi SSID (max 32 chars + null) + char Password[65]; // WiFi password (max 64 chars + null) +} Network_WiFi_Credentials_t; +---- + +==== Station Mode Configuration + +[source,cpp] +---- +typedef struct { + Network_WiFi_Credentials_t Credentials; // SSID and password + uint8_t MaxRetries; // Max connection attempts + uint16_t RetryInterval; // Delay between retries (ms) +} Network_WiFi_STA_Config_t; +---- + +=== Server Configuration + +[source,cpp] +---- +typedef struct { + uint16_t HTTP_Port; // HTTP server port (default 80) + uint8_t MaxClients; // Max simultaneous clients + uint16_t WSPingIntervalSec; // WebSocket ping interval (seconds) + bool EnableCORS; // Enable CORS headers + const char *API_Key; // Optional API key for authentication +} Network_Server_Config_t; +---- + +=== Event System + +==== Event Base + +[source,cpp] +---- +ESP_EVENT_DECLARE_BASE(NETWORK_EVENTS); +---- + +==== Event Types + +[cols="2,3,2"] +|=== +|Event |Description |Data + +|`NETWORK_EVENT_WIFI_CONNECTED` +|WiFi connected to AP +|None + +|`NETWORK_EVENT_WIFI_DISCONNECTED` +|WiFi disconnected from AP +|`wifi_event_sta_disconnected_t` + +|`NETWORK_EVENT_WIFI_GOT_IP` +|IP address obtained via DHCP +|`Network_IP_Info_t` + +|`NETWORK_EVENT_CREDENTIALS_UPDATED` +|WiFi credentials changed +|`Network_WiFi_Credentials_t` + +|`NETWORK_EVENT_AP_STARTED` +|Access Point started +|None + +|`NETWORK_EVENT_AP_STOPPED` +|Access Point stopped +|None + +|`NETWORK_EVENT_AP_STA_CONNECTED` +|Station connected to our AP +|`Network_Event_STA_Info_t` + +|`NETWORK_EVENT_AP_STA_DISCONNECTED` +|Station disconnected from our AP +|`Network_Event_STA_Info_t` + +|`NETWORK_EVENT_PROV_STARTED` +|Provisioning started +|None + +|`NETWORK_EVENT_PROV_STOPPED` +|Provisioning stopped +|None + +|`NETWORK_EVENT_PROV_CRED_RECV` +|Provisioning credentials received +|None + +|`NETWORK_EVENT_PROV_SUCCESS` +|Provisioning successful +|None + +|`NETWORK_EVENT_PROV_FAILED` +|Provisioning failed +|None + +|`NETWORK_EVENT_SERVER_STARTED` +|HTTP/WebSocket server started +|None + +|`NETWORK_EVENT_SERVER_STOPPED` +|HTTP/WebSocket server stopped +|None +|=== + +==== Event Data Structures + +[source,cpp] +---- +/** @brief IP info event data. */ +typedef struct { + uint32_t IP; // IP address + uint32_t Netmask; // Subnet mask + uint32_t Gateway; // Gateway address +} Network_IP_Info_t; + +/** @brief Station info event data. */ +typedef struct { + uint8_t MAC[6]; // MAC address +} Network_Event_STA_Info_t; +---- + +--- + +== API Reference + +=== Initialization and Lifecycle + +==== `NetworkManager_Init()` + +[source,cpp] +---- +esp_err_t NetworkManager_Init(Network_WiFi_STA_Config_t *p_Config); +---- + +*Description*: Initializes the Network Manager with WiFi configuration. + +*Process*: + +1. Initializes TCP/IP stack (`esp_netif_init()`) +2. Creates FreeRTOS event group for synchronization +3. Creates WiFi station and AP network interfaces +4. Initializes WiFi with default configuration +5. Registers WiFi and IP event handlers +6. Sets WiFi storage mode to RAM + +*Parameters*: + +* `p_Config`: Pointer to WiFi station configuration (credentials, retries, interval) + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_ARG` if `p_Config` is NULL +* `ESP_ERR_NO_MEM` if event group creation fails +* `ESP_FAIL` if netif or WiFi initialization fails + +*Example*: + +[source,cpp] +---- +Network_WiFi_STA_Config_t wifi_config = { + .Credentials = { + .SSID = "MyWiFi", + .Password = "MyPassword123", + }, + .MaxRetries = 5, + .RetryInterval = 5000, // 5 seconds +}; + +if (NetworkManager_Init(&wifi_config) == ESP_OK) { + ESP_LOGI(TAG, "Network Manager initialized"); +} else { + ESP_LOGE(TAG, "Failed to initialize Network Manager"); +} +---- + +==== `NetworkManager_Deinit()` + +[source,cpp] +---- +void NetworkManager_Deinit(void); +---- + +*Description*: Deinitializes the Network Manager and releases all resources. + +*Process*: + +1. Stops WiFi and SNTP +2. Deinitializes WiFi driver +3. Destroys network interfaces (STA and AP) +4. Deletes event group +5. Resets internal state + +*Example*: + +[source,cpp] +---- +void shutdown_network(void) +{ + ESP_LOGI(TAG, "Shutting down network..."); + NetworkManager_Deinit(); +} +---- + +--- + +=== WiFi Connection Management + +==== `NetworkManager_StartSTA()` + +[source,cpp] +---- +esp_err_t NetworkManager_StartSTA(void); +---- + +*Description*: Starts WiFi in station mode and connects to configured AP. + +*Process*: + +1. Resets retry counter +2. Clears event group bits +3. Configures WiFi with stored credentials +4. Sets authentication mode (WPA2-PSK minimum) +5. Enables Protected Management Frames (PMF) +6. Starts WiFi and initiates connection + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_STATE` if not initialized + +*Authentication*: + +* Minimum: WPA2-PSK +* PMF capable: Yes +* PMF required: No (for compatibility) + +*Example*: + +[source,cpp] +---- +// Start WiFi connection +if (NetworkManager_StartSTA() == ESP_OK) { + ESP_LOGI(TAG, "WiFi STA started, connecting..."); + + // Wait for connection or failure + while (NetworkManager_GetState() == NETWORK_STATE_CONNECTING) { + vTaskDelay(pdMS_TO_TICKS(100)); + } + + if (NetworkManager_isConnected()) { + ESP_LOGI(TAG, "Connected successfully!"); + } else { + ESP_LOGE(TAG, "Connection failed"); + } +} +---- + +==== `NetworkManager_Stop()` + +[source,cpp] +---- +esp_err_t NetworkManager_Stop(void); +---- + +*Description*: Stops WiFi and SNTP, returns to IDLE state. + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_STATE` if not initialized + +*Example*: + +[source,cpp] +---- +// Temporarily disable WiFi to save power +NetworkManager_Stop(); +ESP_LOGI(TAG, "WiFi stopped"); + +// Later, restart connection +NetworkManager_StartSTA(); +---- + +==== `NetworkManager_ConnectWiFi()` + +[source,cpp] +---- +esp_err_t NetworkManager_ConnectWiFi(const char *p_SSID, const char *p_Password); +---- + +*Description*: Connects to WiFi with new credentials (updates configuration). + +*Parameters*: + +* `p_SSID`: New SSID (NULL to keep current) +* `p_Password`: New password (NULL to keep current) + +*Process*: + +1. Updates credentials if provided +2. Stops current WiFi connection +3. Restarts with new credentials +4. Waits up to 30 seconds for connection + +*Return Value*: + +* `ESP_OK` if connected successfully +* `ESP_ERR_INVALID_STATE` if not initialized +* `ESP_FAIL` if connection failed +* `ESP_ERR_TIMEOUT` if connection timed out (30 seconds) + +*Example*: + +[source,cpp] +---- +// Connect to new WiFi network +esp_err_t ret = NetworkManager_ConnectWiFi("NewNetwork", "NewPassword123"); + +if (ret == ESP_OK) { + ESP_LOGI(TAG, "Connected to new network!"); +} else if (ret == ESP_ERR_TIMEOUT) { + ESP_LOGW(TAG, "Connection timeout - check credentials and signal strength"); +} else { + ESP_LOGE(TAG, "Connection failed: %d", ret); +} +---- + +==== `NetworkManager_DisconnectWiFi()` + +[source,cpp] +---- +esp_err_t NetworkManager_DisconnectWiFi(void); +---- + +*Description*: Disconnects from current WiFi network. + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_STATE` if not initialized + +*Example*: + +[source,cpp] +---- +NetworkManager_DisconnectWiFi(); +ESP_LOGI(TAG, "Disconnected from WiFi"); +---- + +--- + +=== Status and Information + +==== `NetworkManager_isConnected()` + +[source,cpp] +---- +bool NetworkManager_isConnected(void); +---- + +*Description*: Checks if WiFi is connected and has IP address. + +*Return Value*: + +* `true` if state is `NETWORK_STATE_CONNECTED` +* `false` otherwise + +*Example*: + +[source,cpp] +---- +if (NetworkManager_isConnected()) { + // Safe to start servers, sync time, etc. + start_http_server(); +} else { + ESP_LOGW(TAG, "Not connected to WiFi"); +} +---- + +==== `NetworkManager_GetState()` + +[source,cpp] +---- +Network_State_t NetworkManager_GetState(void); +---- + +*Description*: Gets the current network connection state. + +*Return Value*: Current `Network_State_t` value + +*Example*: + +[source,cpp] +---- +Network_State_t state = NetworkManager_GetState(); + +switch (state) { + case NETWORK_STATE_IDLE: + ESP_LOGI(TAG, "WiFi not started"); + break; + case NETWORK_STATE_CONNECTING: + ESP_LOGI(TAG, "Connecting..."); + break; + case NETWORK_STATE_CONNECTED: + ESP_LOGI(TAG, "Connected!"); + break; + case NETWORK_STATE_ERROR: + ESP_LOGE(TAG, "Error state - max retries exceeded"); + break; + default: + break; +} +---- + +==== `NetworkManager_GetIP()` + +[source,cpp] +---- +esp_err_t NetworkManager_GetIP(esp_netif_ip_info_t *p_IP); +---- + +*Description*: Gets current IP, netmask, and gateway information. + +*Parameters*: + +* `p_IP`: Pointer to store IP information + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_ARG` if `p_IP` is NULL + +*Example*: + +[source,cpp] +---- +esp_netif_ip_info_t ip_info; + +if (NetworkManager_GetIP(&ip_info) == ESP_OK) { + ESP_LOGI(TAG, "IP: " IPSTR, IP2STR(&ip_info.ip)); + ESP_LOGI(TAG, "Netmask: " IPSTR, IP2STR(&ip_info.netmask)); + ESP_LOGI(TAG, "Gateway: " IPSTR, IP2STR(&ip_info.gw)); +} +---- + +==== `NetworkManager_GetRSSI()` + +[source,cpp] +---- +int8_t NetworkManager_GetRSSI(void); +---- + +*Description*: Gets WiFi signal strength (RSSI) in dBm. + +*Return Value*: + +* RSSI in dBm (typically -30 to -90) +* `0` if not connected + +*RSSI Interpretation*: + +* -30 dBm: Excellent signal +* -50 dBm: Very good signal +* -60 dBm: Good signal +* -70 dBm: Fair signal +* -80 dBm: Weak signal +* -90 dBm: Very weak signal + +*Example*: + +[source,cpp] +---- +int8_t rssi = NetworkManager_GetRSSI(); + +if (rssi != 0) { + const char *quality; + if (rssi >= -50) quality = "Excellent"; + else if (rssi >= -60) quality = "Very Good"; + else if (rssi >= -70) quality = "Good"; + else if (rssi >= -80) quality = "Fair"; + else quality = "Weak"; + + ESP_LOGI(TAG, "Signal: %d dBm (%s)", rssi, quality); +} +---- + +==== `NetworkManager_GetMAC()` + +[source,cpp] +---- +esp_err_t NetworkManager_GetMAC(uint8_t *p_MAC); +---- + +*Description*: Gets the MAC address of the WiFi station interface. + +*Parameters*: + +* `p_MAC`: Buffer to store MAC address (6 bytes) + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_ARG` if `p_MAC` is NULL + +*Example*: + +[source,cpp] +---- +uint8_t mac[6]; + +if (NetworkManager_GetMAC(mac) == ESP_OK) { + ESP_LOGI(TAG, "MAC: %02X:%02X:%02X:%02X:%02X:%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} +---- + +--- + +=== Credentials Management + +==== `NetworkManager_SetCredentials()` + +[source,cpp] +---- +esp_err_t NetworkManager_SetCredentials(Network_WiFi_Credentials_t *p_Credentials); +---- + +*Description*: Updates WiFi credentials without reconnecting. + +*Parameters*: + +* `p_Credentials`: Pointer to new credentials + +*Process*: + +1. Updates internal credentials +2. Posts `NETWORK_EVENT_CREDENTIALS_UPDATED` event +3. Does NOT reconnect (call `NetworkManager_ConnectWiFi()` to reconnect) + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_ARG` if `p_Credentials` is NULL + +*Example*: + +[source,cpp] +---- +Network_WiFi_Credentials_t creds = { + .SSID = "NewNetwork", + .Password = "NewPass123", +}; + +// Update credentials (saved in RAM) +NetworkManager_SetCredentials(&creds); + +// Save to NVS via Settings Manager +Settings_WiFi_t wifi_settings; +SettingsManager_GetWiFi(&wifi_settings); +memcpy(&wifi_settings.Credentials, &creds, sizeof(creds)); +SettingsManager_UpdateWiFi(&wifi_settings); +SettingsManager_Save(); + +// Reconnect with new credentials +NetworkManager_ConnectWiFi(NULL, NULL); +---- + +--- + +=== Server Management + +==== `NetworkManager_StartServer()` + +[source,cpp] +---- +esp_err_t NetworkManager_StartServer(Server_Config_t *p_Config); +---- + +*Description*: Starts the HTTP/WebSocket server. + +*Parameters*: + +* `p_Config`: Pointer to server configuration + +*Return Value*: + +* `ESP_OK` on success +* Error code from server initialization + +*Example*: + +[source,cpp] +---- +Network_Server_Config_t server_cfg = { + .HTTP_Port = 80, + .MaxClients = 4, + .WSPingIntervalSec = 30, + .EnableCORS = true, + .API_Key = NULL, // No API key +}; + +if (NetworkManager_isConnected()) { + NetworkManager_StartServer(&server_cfg); + ESP_LOGI(TAG, "HTTP/WebSocket server started on port 80"); +} +---- + +==== `NetworkManager_GetConnectedStations()` + +[source,cpp] +---- +uint8_t NetworkManager_GetConnectedStations(void); +---- + +*Description*: Gets the number of stations connected to our AP (AP mode only). + +*Return Value*: Number of connected stations (0 if not in AP mode) + +*Example*: + +[source,cpp] +---- +uint8_t num_stations = NetworkManager_GetConnectedStations(); +ESP_LOGI(TAG, "Connected stations: %d", num_stations); +---- + +--- + +== Usage + +=== Basic Connection Flow + +[source,cpp] +---- +#include "networkManager.h" + +void init_network(void) +{ + // Load WiFi settings from Settings Manager + Settings_WiFi_t wifi_settings; + SettingsManager_GetWiFi(&wifi_settings); + + // Prepare network configuration + Network_WiFi_STA_Config_t network_cfg = { + .Credentials = wifi_settings.Credentials, + .MaxRetries = 5, + .RetryInterval = 5000, + }; + + // Initialize Network Manager + ESP_ERROR_CHECK(NetworkManager_Init(&network_cfg)); + + // Start WiFi connection + ESP_ERROR_CHECK(NetworkManager_StartSTA()); + + ESP_LOGI(TAG, "WiFi connection initiated"); +} +---- + +--- + +=== Event Handling + +[source,cpp] +---- +static void network_event_handler(void* arg, esp_event_base_t base, + int32_t id, void* event_data) +{ + switch (id) { + case NETWORK_EVENT_WIFI_CONNECTED: + ESP_LOGI(TAG, "WiFi connected to AP"); + break; + + case NETWORK_EVENT_WIFI_DISCONNECTED: { + wifi_event_sta_disconnected_t *evt = (wifi_event_sta_disconnected_t*)event_data; + ESP_LOGW(TAG, "WiFi disconnected, reason: %d", evt->reason); + break; + } + + case NETWORK_EVENT_WIFI_GOT_IP: { + Network_IP_Info_t *ip_info = (Network_IP_Info_t*)event_data; + esp_ip4_addr_t ip = { .addr = ip_info->IP }; + ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&ip)); + + // Safe to start servers now + start_application_servers(); + + // Notify Time Manager + TimeManager_OnNetworkConnected(); + break; + } + + case NETWORK_EVENT_SERVER_STARTED: + ESP_LOGI(TAG, "HTTP/WebSocket server started"); + break; + + default: + break; + } +} + +void register_network_events(void) +{ + ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_EVENTS, + ESP_EVENT_ANY_ID, + network_event_handler, + NULL)); +} +---- + +--- + +=== Connection Status Monitoring + +[source,cpp] +---- +void network_monitor_task(void *pvParameters) +{ + while (1) { + if (NetworkManager_isConnected()) { + // Get connection details + int8_t rssi = NetworkManager_GetRSSI(); + esp_netif_ip_info_t ip_info; + NetworkManager_GetIP(&ip_info); + + ESP_LOGI(TAG, "=== Network Status ==="); + ESP_LOGI(TAG, "State: CONNECTED"); + ESP_LOGI(TAG, "IP: " IPSTR, IP2STR(&ip_info.ip)); + ESP_LOGI(TAG, "RSSI: %d dBm", rssi); + + // Check signal quality + if (rssi < -80) { + ESP_LOGW(TAG, "Weak signal detected!"); + } + } else { + Network_State_t state = NetworkManager_GetState(); + ESP_LOGI(TAG, "Network state: %d (not connected)", state); + + if (state == NETWORK_STATE_ERROR) { + ESP_LOGE(TAG, "Network in error state - manual intervention needed"); + } + } + + vTaskDelay(pdMS_TO_TICKS(10000)); // Check every 10 seconds + } +} +---- + +--- + +=== Retry Logic Implementation + +The Network Manager implements intelligent retry logic: + +[source,cpp] +---- +/* + * Retry behavior: + * 1. On disconnect, check if retries remain + * 2. Wait RetryInterval milliseconds + * 3. Attempt reconnection + * 4. Repeat up to MaxRetries times + * 5. If max retries exceeded, enter ERROR state + */ + +// Example: Configure aggressive retries +Network_WiFi_STA_Config_t config = { + .Credentials = { .SSID = "MyNetwork", .Password = "Pass123" }, + .MaxRetries = 10, // Try 10 times + .RetryInterval = 3000, // 3 seconds between attempts +}; + +// Total timeout = 10 × 3s = 30 seconds of retry attempts +---- + +--- + +=== HTTP Server Integration + +[source,cpp] +---- +void start_web_interface(void) +{ + // Wait for network connection + while (!NetworkManager_isConnected()) { + ESP_LOGI(TAG, "Waiting for network connection..."); + vTaskDelay(pdMS_TO_TICKS(1000)); + } + + // Load server settings + Settings_HTTP_Server_t http_settings; + SettingsManager_GetHTTPServer(&http_settings); + + // Configure server + Network_Server_Config_t server_cfg = { + .HTTP_Port = http_settings.Port, + .MaxClients = http_settings.MaxClients, + .WSPingIntervalSec = http_settings.WSPingIntervalSec, + .EnableCORS = true, + .API_Key = NULL, + }; + + // Start server + esp_err_t ret = NetworkManager_StartServer(&server_cfg); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Web interface available at http://" IPSTR, + IP2STR(¤t_ip)); + } else { + ESP_LOGE(TAG, "Failed to start server: %d", ret); + } +} +---- + +--- + +== Configuration + +=== WiFi Configuration + +==== Station Mode Settings + +Configure via Settings Manager or directly: + +[source,cpp] +---- +Network_WiFi_STA_Config_t wifi_config = { + .Credentials = { + .SSID = "YourNetwork", // Max 32 characters + .Password = "YourPassword", // Max 64 characters (WPA2) + }, + .MaxRetries = 5, // 0-255 retries + .RetryInterval = 5000, // Milliseconds between retries +}; +---- + +==== Server Settings + +[source,cpp] +---- +Network_Server_Config_t server_config = { + .HTTP_Port = 80, // HTTP port (80 or 8080 typical) + .MaxClients = 4, // Max simultaneous clients + .WSPingIntervalSec = 30, // WebSocket keepalive (seconds) + .EnableCORS = true, // Allow cross-origin requests + .API_Key = "secret123", // Optional API key (or NULL) +}; +---- + +--- + +== WiFi Disconnect Reasons + +The Network Manager provides detailed disconnect reason logging: + +[cols="1,2,3"] +|=== +|Code |Reason |Description + +|1 +|Unspecified +|Generic disconnect + +|2 +|Auth expired +|Authentication timeout + +|4 +|Assoc expired +|Association timeout + +|6 +|Not authed +|Not authenticated yet + +|8 +|Assoc leave +|Device chose to disconnect + +|15 +|4-way handshake timeout +|WPA handshake failed + +|23 +|Beacon timeout +|Lost connection (AP disappeared) + +|201 +|No AP found +|SSID not found (check spelling, 2.4GHz) + +|202 +|Auth failed +|Wrong password + +|205 +|Connection failed +|Generic connection failure +|=== + +--- + +== Best Practices + +=== DO's + +* ✓ Initialize Network Manager after NVS and before starting tasks +* ✓ Register event handlers before starting WiFi +* ✓ Check `NetworkManager_isConnected()` before starting servers +* ✓ Handle disconnection gracefully with automatic retry +* ✓ Monitor RSSI for signal quality issues +* ✓ Use Settings Manager to persist WiFi credentials +* ✓ Implement timeout logic for critical network operations + +=== DON'Ts + +* ✗ Don't call `NetworkManager_StartSTA()` multiple times without stopping +* ✗ Don't start servers before network is connected +* ✗ Don't ignore disconnect events (handle gracefully) +* ✗ Don't hardcode credentials (use Settings Manager) +* ✗ Don't forget to call `NetworkManager_Deinit()` on shutdown +* ✗ Don't exceed ESP32 WiFi client limits (typically 4-8) + +--- + +== Troubleshooting + +=== Connection Fails Immediately + +*Symptom*: `NETWORK_EVENT_WIFI_DISCONNECTED` with reason 201 ("No AP found") + +*Possible Causes*: + +* SSID typo or incorrect case +* AP is on 5GHz band (ESP32 only supports 2.4GHz) +* AP is out of range +* AP is hidden (ESP32 can connect to hidden SSIDs but requires exact match) + +*Solution*: + +[source,cpp] +---- +ESP_LOGI(TAG, "Scanning for networks..."); +wifi_scan_config_t scan_config = { .ssid = NULL }; +ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, true)); + +uint16_t ap_count = 0; +esp_wifi_scan_get_ap_num(&ap_count); +wifi_ap_record_t *ap_list = malloc(sizeof(wifi_ap_record_t) * ap_count); +esp_wifi_scan_get_ap_records(&ap_count, ap_list); + +for (int i = 0; i < ap_count; i++) { + ESP_LOGI(TAG, "Found: %s (RSSI: %d, Channel: %d)", + ap_list[i].ssid, ap_list[i].rssi, ap_list[i].primary); +} +free(ap_list); +---- + +--- + +=== Authentication Failures + +*Symptom*: Disconnect reason 202 ("Auth failed") + +*Cause*: Incorrect password or incompatible encryption + +*Solution*: + +* Verify password is correct (case-sensitive) +* Ensure AP uses WPA2-PSK (WPA3 may require additional configuration) +* Check if AP requires additional settings (e.g., MAC filtering) + +--- + +=== Connection Drops Frequently + +*Symptom*: Repeated connect/disconnect cycles + +*Possible Causes*: + +* Weak signal (RSSI < -80 dBm) +* Interference from other 2.4GHz devices +* AP overload (too many clients) +* Power saving mode issues + +*Solution*: + +[source,cpp] +---- +// Disable WiFi power saving for stability +esp_wifi_set_ps(WIFI_PS_NONE); + +// Monitor RSSI +int8_t rssi = NetworkManager_GetRSSI(); +if (rssi < -75) { + ESP_LOGW(TAG, "Signal too weak: %d dBm", rssi); + // Consider reconnecting to different AP or warning user +} +---- + +--- + +=== Server Not Accessible + +*Symptom*: Cannot access HTTP server from browser + +*Possible Causes*: + +* Network not connected +* Firewall on client device +* Wrong IP address +* Server not started + +*Solution*: + +[source,cpp] +---- +if (!NetworkManager_isConnected()) { + ESP_LOGE(TAG, "Not connected!"); + return; +} + +esp_netif_ip_info_t ip_info; +NetworkManager_GetIP(&ip_info); +ESP_LOGI(TAG, "Server should be at: http://" IPSTR, IP2STR(&ip_info.ip)); + +// Test with curl or browser: +// curl http:/// +---- + +--- + +== Technical Details + +=== WiFi Mode + +Network Manager supports: + +* **Station (STA)**: Client mode, connects to existing AP +* **Access Point (AP)**: Creates own network (for provisioning) +* **APSTA**: Both modes simultaneously (not commonly used) + +Current implementation focuses on STA mode with AP support for provisioning. + +=== Security + +* **Minimum Authentication**: WPA2-PSK +* **PMF (Protected Management Frames)**: Capable (not required for compatibility) +* **Credentials Storage**: RAM only (use Settings Manager + NVS for persistence) + +=== Performance + +* **WiFi Standard**: 802.11 b/g/n (2.4GHz only) +* **Max Throughput**: ~20 Mbps (practical) +* **Concurrent Clients**: Configurable (typically 4-8) +* **WebSocket Frame Rate**: ~10-30 fps (depends on resolution and network) + +--- + +== Dependencies + +Network Manager depends on: + +* ESP-IDF WiFi Driver (`esp_wifi.h`) +* ESP-IDF TCP/IP Stack (`esp_netif.h`) +* ESP-IDF Event Loop (`esp_event.h`) +* FreeRTOS (`freertos/FreeRTOS.h`, `freertos/event_groups.h`) +* NVS (for credential storage via Settings Manager) + +Network Manager provides services to: + +* Time Manager (SNTP synchronization) +* HTTP/WebSocket Server (thermal image streaming) +* VISA Server (instrument control) +* Application tasks (network connectivity) + +--- + +== License + +Copyright (C) Daniel Kampert, 2026 + +This software is licensed under the GNU General Public License v3.0. +Website: www.kampis-elektroecke.de + +--- + +== Contact + +Please report bugs and suggestions to: DanielKampert@kampis-elektroecke.de diff --git a/docs/NetworkTask.adoc b/docs/NetworkTask.adoc new file mode 100644 index 0000000..e207c16 --- /dev/null +++ b/docs/NetworkTask.adoc @@ -0,0 +1,347 @@ += PyroVision Network Task Documentation +Daniel Kampert +v1.0, 2026-02-09 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The Network Task manages all network-related operations including WiFi connectivity, HTTP/WebSocket server management, VISA/SCPI server, and network event handling. It acts as the coordination layer between the Network Manager and other system components. + +=== Key Features + +* *WiFi Connection Management*: Monitors and maintains WiFi connectivity +* *HTTP/WebSocket Server*: Serves web interface and streams thermal data +* *VISA/SCPI Server*: Provides instrumentation protocol (port 5025) +* *Network Event Handling*: Responds to connection state changes +* *Automatic Reconnection*: Retry logic for lost connections +* *WebSocket Frame Streaming*: Real-time thermal image distribution + +--- + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Tasks/Network/ +├── networkTask.h # Public API +└── networkTask.cpp # Implementation +---- + +=== Task Architecture + +* *Task Name*: `"NetworkTask"` +* *Priority*: Medium (typically `tskIDLE_PRIORITY + 2`) +* *Stack Size*: 6 KB +* *Core Affinity*: Typically Core 0 (PRO CPU) + +--- + +== Public API + +=== Network_Task_Init() + +[source,c] +---- +esp_err_t Network_Task_Init(App_Context_t *p_AppContext); +---- + +Initializes the network task and sets up event handlers. + +**Parameters:** + +* `p_AppContext` - Pointer to application context (must not be NULL) + +**Return Values:** + +* `ESP_OK` - Initialization successful +* `ESP_ERR_INVALID_ARG` - NULL pointer passed +* `ESP_ERR_NO_MEM` - Memory allocation failed +* `ESP_ERR_INVALID_STATE` - Already initialized + +**Thread Safety:** Not thread-safe. Call once during system initialization. + +--- + +=== Network_Task_Deinit() + +[source,c] +---- +void Network_Task_Deinit(void); +---- + +Deinitializes the network task and cleans up resources. + +--- + +=== Network_Task_Start() + +[source,c] +---- +esp_err_t Network_Task_Start(void); +---- + +Starts the network task. + +**Return Values:** + +* `ESP_OK` - Task started successfully +* `ESP_ERR_INVALID_STATE` - Not initialized or already running +* `ESP_ERR_NO_MEM` - Task creation failed + +--- + +=== Network_Task_Stop() + +[source,c] +---- +esp_err_t Network_Task_Stop(void); +---- + +Stops the network task. + +**Return Values:** + +* `ESP_OK` - Task stopped +* `ESP_ERR_INVALID_STATE` - Not running + +--- + +=== Network_Task_isRunning() + +[source,c] +---- +bool Network_Task_isRunning(void); +---- + +Checks if the task is running. + +**Return Values:** + +* `true` - Task is running +* `false` - Task is stopped + +--- + +== Task Operation + +=== Main Event Loop + +[source,c] +---- +while (running) { + // 1. Monitor WiFi connection state + if (NetworkManager_GetState() == NETWORK_STATE_CONNECTED) { + // Ensure servers are running + if (!HTTP_Server_isRunning()) { + HTTP_Server_Start(); + } + if (!VISA_Server_isRunning()) { + VISA_Server_Start(); + } + } + + // 2. Handle WebSocket frame streaming + if (HasWebSocketClients()) { + Frame_t Frame; + if (xQueueReceive(FrameQueue, &Frame, 0) == pdTRUE) { + WebSocket_BroadcastFrame(&Frame); + } + } + + // 3. Process network events + ProcessNetworkEvents(); + + vTaskDelay(pdMS_TO_TICKS(100)); +} +---- + +--- + +== WebSocket Frame Streaming + +=== Frame Distribution + +The task receives thermal frames from Lepton Task and distributes them to WebSocket clients: + +**Flow:** + +[source] +---- +Lepton Task → Queue → Network Task → WebSocket → Browser Clients +---- + +**Frame Format:** + +* Binary format: JPEG-compressed thermal image +* Frame rate: ~8-9 Hz (camera native rate) +* Size: 5-15 KB per frame (depending on compression) + +**Implementation:** + +[source,c] +---- +// Receive frame from Lepton Task +Frame_t Frame; +if (xQueueReceive(Network_FrameQueue, &Frame, 0) == pdTRUE) { + // Compress to JPEG + uint8_t *JpegBuffer; + size_t JpegSize; + ImageEncoder_EncodeJPEG(&Frame, &JpegBuffer, &JpegSize); + + // Broadcast to all connected WebSocket clients + WebSocket_BroadcastBinary(JpegBuffer, JpegSize); + + free(JpegBuffer); +} +---- + +--- + +== Server Management + +=== HTTP/WebSocket Server + +The task ensures the HTTP server (which handles WebSocket connections) is running when WiFi is connected: + +**Startup:** + +1. Wait for WiFi connection (NETWORK_STATE_CONNECTED) +2. Start HTTP server +3. Register WebSocket upgrade handler +4. Begin streaming frames + +**Shutdown:** + +* Gracefully closes all client connections +* Stops HTTP server +* Frees resources + +=== VISA/SCPI Server + +The VISA server runs independently on port 5025: + +**Management:** + +* Started when WiFi connects +* Handles concurrent clients (max 5) +* Provides SCPI command interface +* Automatic client timeout (30 seconds idle) + +--- + +== Event Handling + +=== Network Events + +The task listens to Network Manager events: + +**Event Types:** + +* `NETWORK_EVENT_WIFI_CONNECTED` - WiFi connection established +* `NETWORK_EVENT_WIFI_DISCONNECTED` - WiFi connection lost +* `NETWORK_EVENT_IP_ACQUIRED` - IP address obtained +* `NETWORK_EVENT_AP_STARTED` - Access Point mode active + +**Event Handlers:** + +[source,c] +---- +static void on_WiFi_Connected(void *p_Arg, esp_event_base_t EventBase, + int32_t EventID, void *p_EventData) +{ + ESP_LOGI(TAG, "WiFi connected, starting servers..."); + + HTTP_Server_Start(&HTTPConfig); + VISA_Server_Start(&VISAConfig); + + // Notify GUI (update WiFi icon) + esp_event_post(GUI_EVENTS, GUI_EVENT_WIFI_CONNECTED, NULL, 0, 0); +} +---- + +--- + +== Dependencies + +=== Required Managers + +* *Network Manager*: WiFi and connection management +* *Settings Manager*: Server configuration (ports, credentials) + +=== Required Tasks + +* *Lepton Task*: Provides thermal frames for streaming + +=== Server Components + +* *HTTP Server*: Web interface and REST API +* *WebSocket*: Real-time frame streaming +* *VISA Server*: SCPI instrumentation protocol +* *DNS Server*: Captive portal for provisioning + +--- + +== Configuration + +=== Server Ports + +* HTTP: 80 (default) +* WebSocket: 80 (HTTP upgrade) +* VISA/SCPI: 5025 (standard) + +--- + +## Performance + +=== CPU Usage + +* **Typical**: 10-15% (during streaming) +* **Idle**: <5% (no clients connected) +* **Peak**: 20% (multiple WebSocket clients) + +=== Network Bandwidth + +* **Per WebSocket Client**: ~80-120 KB/s +* **Max Clients**: 5 simultaneous +* **Total Bandwidth**: ~600 KB/s (all clients) + +--- + +== Troubleshooting + +**WebSocket disconnects frequently:** + +* Check WiFi signal strength +* Verify ping interval (30 seconds recommended) +* Monitor memory usage (may be low) + +**VISA server not responding:** + +* Verify port 5025 is not blocked +* Check firewall settings +* Ensure WiFi is connected + +**High CPU usage:** + +* Reduce number of WebSocket clients +* Lower frame compression quality +* Increase task delay interval + +--- + +== Related Documentation + +* link:NetworkManager.adoc[Network Manager Documentation] +* link:LeptonTask.adoc[Lepton Task Documentation] +* link:VISAServer.adoc[VISA Server Documentation] +* link:HTTPServer.adoc[HTTP Server Documentation] + +--- + +**Last Updated**: February 9, 2026 + +**Maintainer**: Daniel Kampert (DanielKampert@kampis-elektroecke.de) diff --git a/docs/SettingsManager.adoc b/docs/SettingsManager.adoc new file mode 100644 index 0000000..5fa3494 --- /dev/null +++ b/docs/SettingsManager.adoc @@ -0,0 +1,801 @@ += PyroVision Settings Manager Documentation +Daniel Kampert +v1.0, 2026-01-14 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The Settings Manager is a central component for persistent management of application settings in the PyroVision firmware. It provides thread-safe access to various settings categories and uses ESP32 NVS (Non-Volatile Storage) for permanent storage. + +=== Key Features + +* *Thread-safe Operations*: All accesses are protected by a mutex +* *Persistent Storage*: Settings are stored in NVS flash memory +* *Event-based Architecture*: Notification of changes via ESP Event System +* *Categorized Settings*: Modular structure for different subsystems +* *Factory Defaults*: Support for default settings from JSON or hardcoded +* *Versioning*: Support for future migrations + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Manager/Settings/ +├── settingsManager.h # Public API +├── settingsManager.cpp # Implementation +├── settingsTypes.h # Data types and Events +└── Private/ + ├── settingsLoader.h # Internal loader interface + ├── settingsJSONLoader.cpp # JSON Factory Defaults Loader + └── settingsDefaultLoader.cpp # Hardcoded Defaults +---- + +=== Internal State Management + +The manager maintains its state in the `SettingsManager_State_t` structure: + +[source,cpp] +---- +typedef struct { + bool isInitialized; // Initialization status + bool PendingChanges; // Unsaved changes + nvs_handle_t NVS_Handle; // NVS Handle + Settings_t Settings; // Current settings in RAM + SemaphoreHandle_t Mutex; // Thread safety +} SettingsManager_State_t; +---- + +=== NVS Storage Layout + +* *Namespace*: `"pyrovision"` +* *Blob Key*: `"settings"` - Contains the complete `Settings_t` structure +* *Version Key*: Reserved for future migrations +* *Config valid Flag*: `"config_valid"` - Indicates a valid configuration in the NVS + +== Data Structures + +=== Settings Categories + +The settings are divided into the following categories: + +==== 1. Lepton Camera Settings (`Settings_Lepton_t`) + +[source,cpp] +---- +typedef struct { + Settings_ROI_t ROI[4]; // Camera ROIs + Settings_Emissivity_t EmissivityPresets[128]; // Emissivity presets + size_t EmissivityPresetsCount; // Number of presets +} Settings_Lepton_t; +---- + +* *ROIs*: Up to 4 Regions of Interest (Spotmeter, Scene, AGC, Video Focus) +* *Emissivity Presets*: Predefined material emissivities (e.g. Concrete: 0.92, Skin: 0.98) + +==== 2. WiFi Settings (`Settings_WiFi_t`) + +[source,cpp] +---- +typedef struct { + char SSID[33]; // WiFi SSID (max 32 characters + \0) + char Password[65]; // WiFi password (max 64 characters + \0) + bool AutoConnect; // Automatic connection + uint8_t MaxRetries; // Maximum connection attempts + uint16_t RetryInterval; // Wait time between attempts (ms) +} Settings_WiFi_t; +---- + +==== 3. Provisioning Settings (`Settings_Provisioning_t`) + +[source,cpp] +---- +typedef struct { + char DeviceName[32]; // Device name for provisioning + char PoP[32]; // Proof of Possession + uint32_t Timeout; // Provisioning timeout (seconds) +} Settings_Provisioning_t; +---- + +==== 4. Display Settings (`Settings_Display_t`) + +[source,cpp] +---- +typedef struct { + uint8_t Brightness; // Brightness (0-100%) + uint16_t ScreenTimeout; // Screen timeout (seconds, 0=off) +} Settings_Display_t; +---- + +==== 5. HTTP Server Settings (`Settings_HTTP_Server_t`) + +[source,cpp] +---- +typedef struct { + uint16_t Port; // HTTP Server Port + uint16_t WSPingIntervalSec; // WebSocket ping interval (seconds) + uint8_t MaxClients; // Max. simultaneous clients +} Settings_HTTP_Server_t; +---- + +==== 6. VISA Server Settings (`Settings_VISA_Server_t`) + +[source,cpp] +---- +typedef struct { + uint16_t Port; // VISA Server Port (default: 5025) +} Settings_VISA_Server_t; +---- + +==== 7. System Settings (`Settings_System_t`) + +[source,cpp] +---- +typedef struct { + char DeviceName[32]; // Device name + bool SDCard_AutoMount; // Automatic SD card mounting + bool Bluetooth_Enabled; // Bluetooth enabled + char Timezone[32]; // Timezone string (POSIX format) + uint8_t Reserved[100]; // Reserved for future extensions +} Settings_System_t; +---- + +=== Event System + +==== Event Base + +[source,cpp] +---- +ESP_EVENT_DECLARE_BASE(SETTINGS_EVENTS); +---- + +==== Event IDs + +[cols="2,3,2"] +|=== +|Event |Description |Data + +|`SETTINGS_EVENT_LOADED` +|Settings loaded from NVS +|`Settings_t` + +|`SETTINGS_EVENT_SAVED` +|Settings saved to NVS +|NULL + +|`SETTINGS_EVENT_LEPTON_CHANGED` +|Lepton settings changed +|`Settings_Manager_Setting_t` or NULL (see note below) + +|`SETTINGS_EVENT_WIFI_CHANGED` +|WiFi settings changed +|`Settings_WiFi_t` + +|`SETTINGS_EVENT_PROVISIONING_CHANGED` +|Provisioning settings changed +|`Settings_Provisioning_t` + +|`SETTINGS_EVENT_DISPLAY_CHANGED` +|Display settings changed +|`Settings_Display_t` + +|`SETTINGS_EVENT_HTTP_SERVER_CHANGED` +|HTTP server settings changed +|`Settings_HTTP_Server_t` + +|`SETTINGS_EVENT_VISA_SERVER_CHANGED` +|VISA server settings changed +|`Settings_VISA_Server_t` + +|`SETTINGS_EVENT_SYSTEM_CHANGED` +|System settings changed +|`Settings_System_t` + +|`SETTINGS_EVENT_REQUEST_GET` +|Request to retrieve current settings +|NULL + +|`SETTINGS_EVENT_REQUEST_SAVE` +|Request to save settings +|NULL + +|`SETTINGS_EVENT_REQUEST_RESET` +|Request to reset to factory defaults +|NULL +|=== + +*NOTE*: For `SETTINGS_EVENT_LEPTON_CHANGED` events, the event data may be NULL if the update was called without specifying which particular setting changed (i.e., when `SettingsManager_UpdateLepton()` is called without the optional `p_ChangedSetting` parameter). Event handlers MUST check for NULL before accessing event data. + +== API Reference + +=== Initialization and Lifecycle + +==== `SettingsManager_Init()` + +[source,cpp] +---- +esp_err_t SettingsManager_Init(void); +---- + +*Description*: Initializes the Settings Manager and loads all settings from NVS. + +*Process*: + +1. Creates a mutex for thread safety +2. Opens the NVS namespace `"pyrovision"` +3. Attempts to load settings from NVS +4. If no settings exist: + * Loads JSON factory defaults from `data/default_settings.json` + * If JSON not available: Uses hardcoded defaults + * Saves the defaults to NVS +5. Posts `SETTINGS_EVENT_LOADED` event + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_NO_MEM` if mutex could not be created +* Other `ESP_ERR_*` on NVS errors + +*Example*: + +[source,cpp] +---- +if (SettingsManager_Init() == ESP_OK) { + ESP_LOGI(TAG, "Settings Manager successfully initialized"); +} +---- + +==== `SettingsManager_Deinit()` + +[source,cpp] +---- +esp_err_t SettingsManager_Deinit(void); +---- + +*Description*: Deinitializes the Settings Manager and releases resources. + +*Process*: + +1. Closes the NVS handle +2. Deletes the mutex +3. Resets initialization status + +*Return Value*: `ESP_OK` + +=== Loading and Saving Settings + +==== `SettingsManager_Load()` + +[source,cpp] +---- +esp_err_t SettingsManager_Load(Settings_t *p_Settings); +---- + +*Description*: Loads all settings from NVS into RAM and the provided structure. *Warning*: Overwrites unsaved RAM changes! + +*Parameters*: + +* `p_Settings`: Pointer to settings structure to be populated + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_STATE` if manager is not initialized +* `ESP_ERR_INVALID_ARG` if `p_Settings` is NULL +* `ESP_ERR_NVS_NOT_FOUND` if no settings exist in NVS +* `ESP_ERR_INVALID_SIZE` on size mismatch (e.g., after firmware update) + +==== `SettingsManager_Save()` + +[source,cpp] +---- +esp_err_t SettingsManager_Save(void); +---- + +*Description*: Saves all RAM settings persistently to NVS. + +*Process*: + +1. Writes complete `Settings_t` structure as blob to NVS +2. Commits the changes +3. Posts `SETTINGS_EVENT_SAVED` event + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_STATE` if manager is not initialized +* Other `ESP_ERR_*` on NVS errors + +*Important*: Changes via `Update*()` functions are only made in RAM. `Save()` must be called explicitly! + +=== Get/Update Functions + +For each settings category, there exists a pair of `Get` and `Update` functions: + +==== Pattern: Get Function + +[source,cpp] +---- +esp_err_t SettingsManager_Get(App_Settings__t* p_Settings); +---- + +*Description*: Reads the current settings from RAM (thread-safe). + +*Parameters*: Pointer to the structure to be populated + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_ARG` on NULL pointer + +*Example*: + +[source,cpp] +---- +Settings_WiFi_t wifi_settings; +if (SettingsManager_GetWiFi(&wifi_settings) == ESP_OK) { + ESP_LOGI(TAG, "WiFi SSID: %s", wifi_settings.SSID); +} +---- + +==== Pattern: Update Function + +[source,cpp] +---- +esp_err_t SettingsManager_Update(App_Settings__t* p_Settings); +---- + +*Description*: Updates settings in RAM (not persistent!) and posts a change event. + +*Parameters*: Pointer to the new settings structure + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_STATE` if manager is not initialized +* `ESP_ERR_INVALID_ARG` on NULL pointer + +*Process*: + +1. Copies new values to RAM structure (thread-safe) +2. Posts corresponding `SETTINGS_EVENT_*_CHANGED` event with the new data + +*Example*: + +[source,cpp] +---- +Settings_Display_t display_settings; +SettingsManager_GetDisplay(&display_settings); + +display_settings.Brightness = 90; +display_settings.ScreenTimeout = 120; + +SettingsManager_UpdateDisplay(&display_settings); +SettingsManager_Save(); // Make persistent! +---- + +==== Available Get/Update Pairs + +* `SettingsManager_GetLepton()` / `SettingsManager_UpdateLepton()` +* `SettingsManager_GetWiFi()` / `SettingsManager_UpdateWiFi()` +* `SettingsManager_GetProvisioning()` / `SettingsManager_UpdateProvisioning()` +* `SettingsManager_GetDisplay()` / `SettingsManager_UpdateDisplay()` +* `SettingsManager_GetHTTPServer()` / `SettingsManager_UpdateHTTPServer()` +* `SettingsManager_GetVISAServer()` / `SettingsManager_UpdateVISAServer()` +* `SettingsManager_GetSystem()` / `SettingsManager_UpdateSystem()` + +=== Factory Reset + +==== `SettingsManager_ResetToDefaults()` + +[source,cpp] +---- +esp_err_t SettingsManager_ResetToDefaults(void); +---- + +*Description*: Resets all settings to factory defaults and restarts the device. + +*Process*: + +1. Deletes the `"settings"` key from NVS +2. Resets `"config_valid"` flag (allows reloading JSON defaults) +3. Executes `esp_restart()` + +*Return Value*: + +* `ESP_OK` on success (never reached due to restart) +* `ESP_ERR_INVALID_STATE` if manager is not initialized +* Other `ESP_ERR_*` on NVS errors + +*Warning*: This function does not return as a restart is performed! + +== Usage + +=== Basic Workflow + +==== 1. Initialization at Startup + +[source,cpp] +---- +#include "settingsManager.h" + +void app_main(void) +{ + // Initialize Settings Manager + ESP_ERROR_CHECK(SettingsManager_Init()); + + // Further initializations... +} +---- + +==== 2. Reading Settings + +[source,cpp] +---- +Settings_System_t system_settings; + +if (SettingsManager_GetSystem(&system_settings) == ESP_OK) { + ESP_LOGI(TAG, "Device Name: %s", system_settings.DeviceName); + ESP_LOGI(TAG, "Timezone: %s", system_settings.Timezone); +} +---- + +==== 3. Changing Settings + +[source,cpp] +---- +Settings_Display_t display; + +// Read current settings +SettingsManager_GetDisplay(&display); + +// Change values +display.Brightness = 75; +display.ScreenTimeout = 300; + +// Update in RAM (triggers event) +SettingsManager_UpdateDisplay(&display); + +// IMPORTANT: Save persistently! +SettingsManager_Save(); +---- + +=== Event Handling + +==== Registering Event Handlers + +[source,cpp] +---- +#include +#include "settingsTypes.h" + +static void settings_event_handler(void* handler_args, esp_event_base_t base, + int32_t id, void* event_data) +{ + switch (id) { + case SETTINGS_EVENT_LOADED: + ESP_LOGI(TAG, "Settings have been loaded"); + break; + + case SETTINGS_EVENT_SAVED: + ESP_LOGI(TAG, "Settings have been saved"); + break; + + case SETTINGS_EVENT_WIFI_CHANGED: + Settings_WiFi_t* wifi = (Settings_WiFi_t*)event_data; + ESP_LOGI(TAG, "WiFi settings changed: SSID=%s", wifi->SSID); + // Reconfigure WiFi... + break; + + case SETTINGS_EVENT_DISPLAY_CHANGED: + Settings_Display_t* display = (Settings_Display_t*)event_data; + ESP_LOGI(TAG, "Display Brightness: %d%%", display->Brightness); + // Adjust display brightness... + break; + + // More events... + } +} + +void init_event_handlers(void) +{ + esp_event_handler_register(SETTINGS_EVENTS, + ESP_EVENT_ANY_ID, + settings_event_handler, + NULL); +} +---- + +=== Advanced Patterns + +==== Atomic Settings Update with Rollback + +[source,cpp] +---- +esp_err_t update_wifi_with_rollback(const char* ssid, const char* password) +{ + Settings_WiFi_t old_settings; + Settings_WiFi_t new_settings; + + // Backup old settings + SettingsManager_GetWiFi(&old_settings); + + // Prepare new settings + new_settings = old_settings; + strncpy(new_settings.SSID, ssid, sizeof(new_settings.SSID) - 1); + strncpy(new_settings.Password, password, sizeof(new_settings.Password) - 1); + + // Update + SettingsManager_UpdateWiFi(&new_settings); + SettingsManager_Save(); + + // Test WiFi connection... + if (wifi_connect_test() != ESP_OK) { + // On error: Rollback + SettingsManager_UpdateWiFi(&old_settings); + SettingsManager_Save(); + return ESP_FAIL; + } + + return ESP_OK; +} +---- + +==== Batch Updates + +[source,cpp] +---- +void configure_device_initial(void) +{ + Settings_System_t system; + Settings_Display_t display; + Settings_WiFi_t wifi; + + // Change multiple settings + SettingsManager_GetSystem(&system); + strncpy(system.DeviceName, "PyroVision-001", sizeof(system.DeviceName)); + system.SDCard_AutoMount = true; + SettingsManager_UpdateSystem(&system); + + SettingsManager_GetDisplay(&display); + display.Brightness = 80; + display.ScreenTimeout = 60; + SettingsManager_UpdateDisplay(&display); + + SettingsManager_GetWiFi(&wifi); + wifi.AutoConnect = true; + wifi.MaxRetries = 5; + SettingsManager_UpdateWiFi(&wifi); + + // Save ONCE for all changes + SettingsManager_Save(); +} +---- + +== Factory Defaults System + +=== Hierarchy of Default Sources + +1. *JSON Config* (`data/default_settings.json`) + * Loaded on first boot + * Flag `"config_valid"` prevents reloading of the configuration into the Flash memory + * Flexible and easy to customize without recompilation + +2. *Hardcoded Defaults* (in `settingsDefaultLoader.cpp`) + * Fallback if JSON is not available + * Guaranteed base configuration + * Defined in code + +=== JSON Format + +[source,json] +---- +{ + "lepton": { + "emissivity": [ + { + "name": "Concrete", + "value": 0.92 + }, + { + "name": "Human Skin", + "value": 0.98 + } + ], + "roi": { + "spotmeter": { + "x": 60, + "y": 60, + "width": 40, + "height": 40 + } + } + }, + "wifi": { + "ssid": "", + "password": "", + "maxRetries": 5, + "retryInterval": 2000, + "autoConnect": true + }, + "display": { + "brightness": 80, + "timeout": 60 + } +} +---- + +=== Customizing Defaults + +==== Option 1: Modify JSON + +1. Edit `data/default_settings.json` +2. Flash firmware +3. Perform factory reset or manually reset `config_valid` flag + +==== Option 2: Change Hardcoded Defaults + +Functions in `settingsDefaultLoader.cpp`: + +* `SettingsManager_InitDefaultLepton()` +* `SettingsManager_InitDefaultWiFi()` +* `SettingsManager_InitDefaultDisplay()` +* etc. + +== Best Practices + +=== DO's + +* ✓ Always call `SettingsManager_Save()` after updates if persistence is desired +* ✓ Register event handlers for dynamic response to setting changes +* ✓ Use Get/Update functions for category-specific access +* ✓ Configure factory defaults in JSON for easy customization +* ✓ Perform error handling on all API calls + +=== DON'Ts + +* ✗ Do not directly access internal `_State` structure +* ✗ Do not access settings without mutex (API already uses mutex) +* ✗ Do not forget to save after updates +* ✗ Do not use `ResetToDefaults()` in production code without user confirmation +* ✗ Do not save too frequently (consider flash wear) + +=== Performance Tips + +* *Batch Updates*: Make multiple changes, then save once +* *Event-based*: Use events instead of polling +* *Category-specific*: Only load/update relevant settings category + +== Troubleshooting + +=== Common Problems + +==== Settings are not saved + +*Symptom*: Changes are lost after restart + +*Cause*: `SettingsManager_Save()` was not called + +*Solution*: +[source,cpp] +---- +SettingsManager_UpdateWiFi(&wifi); +SettingsManager_Save(); // <-- Don't forget! +---- + +==== ESP_ERR_INVALID_SIZE on loading + +*Symptom*: Settings cannot be loaded after firmware update + +*Cause*: Structure size has changed + +*Solution*: Perform factory reset or implement migration + +==== Defaults are not loaded + +*Symptom*: JSON defaults are ignored + +*Cause*: `config_valid` flag is set + +*Solution*: +[source,cpp] +---- +// Manually reset flag +nvs_handle_t handle; +nvs_open("pyrovision", NVS_READWRITE, &handle); +nvs_set_u8(handle, "config_valid", 0); +nvs_commit(handle); +nvs_close(handle); +esp_restart(); +---- + +==== Events are not received + +*Symptom*: Event handler is not called + +*Cause*: Handler not registered or wrong event base + +*Solution*: +[source,cpp] +---- +esp_event_loop_create_default(); // Initialize event loop +esp_event_handler_register(SETTINGS_EVENTS, // Correct base! + SETTINGS_EVENT_WIFI_CHANGED, + handler, + NULL); +---- + +== Extensions + +=== Adding a New Settings Category + +1. *Define structure in `settingsTypes.h`*: +[source,cpp] +---- +typedef struct { + uint16_t NewParameter; + char NewString[32]; +} App_Settings_NewCategory_t; +---- + +2. *Insert into `Settings_t`*: +[source,cpp] +---- +typedef struct { + // ... existing categories + App_Settings_NewCategory_t NewCategory; +} Settings_t; +---- + +3. *Add event ID* in `settingsTypes.h`: +[source,cpp] +---- +enum { + // ... existing events + SETTINGS_EVENT_NEWCATEGORY_CHANGED, +}; +---- + +4. *Implement Get/Update functions* in `settingsManager.cpp`: +[source,cpp] +---- +esp_err_t SettingsManager_GetNewCategory(App_Settings_NewCategory_t* p_Settings) +{ + if (p_Settings == NULL) { + return ESP_ERR_INVALID_ARG; + } + + xSemaphoreTake(_State.Mutex, portMAX_DELAY); + memcpy(p_Settings, &_State.Settings.NewCategory, sizeof(App_Settings_NewCategory_t)); + xSemaphoreGive(_State.Mutex); + + return ESP_OK; +} + +esp_err_t SettingsManager_UpdateNewCategory(App_Settings_NewCategory_t* p_Settings) +{ + return SettingsManager_Update(p_Settings, + &_State.Settings.NewCategory, + sizeof(App_Settings_NewCategory_t), + SETTINGS_EVENT_NEWCATEGORY_CHANGED); +} +---- + +5. *Add default initialization* + +6. *Extend JSON schema* (optional) + +== License + +Copyright (C) Daniel Kampert, 2026 + +This software is licensed under the GNU General Public License v3.0. +Website: www.kampis-elektroecke.de + +== Contact + +Please report bugs and suggestions to: DanielKampert@kampis-elektroecke.de diff --git a/docs/TimeManager.adoc b/docs/TimeManager.adoc new file mode 100644 index 0000000..c71d95e --- /dev/null +++ b/docs/TimeManager.adoc @@ -0,0 +1,998 @@ += PyroVision Time Manager Documentation +Daniel Kampert +v1.0, 2026-01-14 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The Time Manager is a unified time management component for the PyroVision firmware that provides reliable timekeeping through multiple time sources. It automatically manages time synchronization via SNTP (network time) and RTC (Real-Time Clock) backup, ensuring accurate timestamps even without network connectivity. + +=== Key Features + +* *Dual Time Sources*: Automatic switching between SNTP (network) and RTC (hardware backup) +* *Automatic Synchronization*: Periodic SNTP sync with configurable intervals +* *RTC Backup*: Automatic backup of synchronized time to hardware RTC +* *Network Awareness*: Seamless handling of network connect/disconnect events +* *Event-based Architecture*: Notification of time synchronization and source changes +* *Timezone Support*: Configurable timezone with POSIX format +* *Failover Management*: Graceful fallback to RTC when network unavailable +* *Status Monitoring*: Comprehensive status information for debugging + +--- + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Manager/Time/ +├── timeManager.h # Public API +├── timeManager.cpp # Implementation +└── time_types.h # Event types and definitions +---- + +=== Time Source Hierarchy + +The Time Manager uses a prioritized hierarchy of time sources: + +1. *SNTP (Network Time Protocol)* + * Highest priority when network is available + * Provides internet-synchronized time (accurate to milliseconds) + * Automatically syncs every hour + * Requires WiFi/network connection + +2. *RTC (Real-Time Clock)* + * Hardware backup time source + * Used when network is unavailable + * Maintains time during power cycles (with battery) + * Updated periodically from SNTP (every 5 minutes) + +3. *System Time* + * Fallback when neither SNTP nor RTC available + * Will drift over time without external synchronization + * Loses accuracy during deep sleep or reset + +=== Internal State Management + +The manager maintains its state in the `TimeManager_State_t` structure: + +[source,cpp] +---- +typedef struct { + bool isInitialized; // Initialization status + bool hasRTC; // RTC hardware available + bool hasNetwork; // Network connected + bool timeSynchronized; // Time has been synchronized + i2c_master_dev_handle_t RTC_Handle; // RTC device handle + TimeManager_Source_t activeSource; // Current active time source + time_t lastSNTP_Sync; // Last SNTP sync timestamp + time_t lastRTC_Sync; // Last RTC read timestamp + time_t lastRTC_Backup; // Last RTC backup timestamp + uint32_t sntpSyncCount; // SNTP sync counter + uint32_t rtcSyncCount; // RTC read counter + esp_timer_handle_t syncTimer; // Periodic sync timer +} TimeManager_State_t; +---- + +=== Synchronization Strategy + +==== SNTP Synchronization + +* *Interval*: Every 3600 seconds (1 hour) +* *Server*: pool.ntp.org (configurable) +* *Mode*: Polling mode +* *Callback*: Notifies on successful sync +* *Auto-restart*: Restarts on network reconnection + +==== RTC Backup + +* *Interval*: Every 300 seconds (5 minutes) +* *Condition*: Only when time is synchronized from SNTP +* *Purpose*: Preserve accurate time during network outages +* *Validation*: Checks year >= 2025 before accepting RTC time + +--- + +== Data Structures + +=== Time Source Enum + +[source,cpp] +---- +typedef enum { + TIME_SOURCE_NONE = 0, // No time source available + TIME_SOURCE_RTC, // Time from RTC + TIME_SOURCE_SNTP, // Time from SNTP + TIME_SOURCE_SYSTEM, // Time from system (not synchronized) +} TimeManager_Source_t; +---- + +=== Time Synchronization Status + +[source,cpp] +---- +typedef struct { + TimeManager_Source_t ActiveSource; // Currently active time source + bool SNTP_Available; // SNTP available (network connected) + bool RTC_Available; // RTC available + time_t LastSync_SNTP; // Timestamp of last SNTP sync + time_t LastSync_RTC; // Timestamp of last RTC sync + uint32_t SNTP_SyncCount; // Number of successful SNTP syncs + uint32_t RTC_SyncCount; // Number of RTC reads +} TimeManager_Status_t; +---- + +=== Event System + +==== Event Base + +[source,cpp] +---- +ESP_EVENT_DECLARE_BASE(TIME_EVENTS); +---- + +==== Event IDs + +[cols="2,3,2"] +|=== +|Event |Description |Data + +|`TIME_EVENT_SYNCHRONIZED` +|Time synchronized from SNTP +|`struct tm` + +|`TIME_EVENT_SOURCE_CHANGED` +|Time source changed (SNTP/RTC/System) +|`TimeManager_Source_t` +|=== + +--- + +== API Reference + +=== Initialization and Lifecycle + +==== `TimeManager_Init()` + +[source,cpp] +---- +esp_err_t TimeManager_Init(void *p_RTC_Handle); +---- + +*Description*: Initializes the Time Manager and sets up time sources. + +*Process*: + +1. Validates and stores RTC handle (if provided) +2. Attempts to load time from RTC (if available) +3. Validates RTC time (year >= 2025) +4. Sets system time from RTC if valid +5. Sets default timezone (UTC) +6. Creates periodic synchronization timer +7. Starts timer with 60-second period + +*Parameters*: + +* `p_RTC_Handle`: Pointer to RTC device handle (NULL if RTC not available) + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_*` on timer creation/start failure + +*Example*: + +[source,cpp] +---- +i2c_master_dev_handle_t rtc_handle; +// ... initialize RTC ... + +if (TimeManager_Init(&rtc_handle) == ESP_OK) { + ESP_LOGI(TAG, "Time Manager initialized successfully"); +} +---- + +==== `TimeManager_Deinit()` + +[source,cpp] +---- +esp_err_t TimeManager_Deinit(void); +---- + +*Description*: Deinitializes the Time Manager and releases resources. + +*Process*: + +1. Stops periodic sync timer +2. Deletes timer +3. Stops SNTP if enabled +4. Resets initialization status + +*Return Value*: `ESP_OK` + +--- + +=== Network Event Handlers + +==== `TimeManager_OnNetworkConnected()` + +[source,cpp] +---- +esp_err_t TimeManager_OnNetworkConnected(void); +---- + +*Description*: Called when network connection is established. Starts SNTP synchronization. + +*Process*: + +1. Sets network available flag +2. Initializes SNTP if first connection +3. Restarts SNTP if already initialized (triggers immediate sync) +4. Configures SNTP server (pool.ntp.org) +5. Sets sync notification callback + +*Return Value*: `ESP_OK` + +*Example*: + +[source,cpp] +---- +static void wifi_event_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + if (event_id == WIFI_EVENT_STA_CONNECTED) { + TimeManager_OnNetworkConnected(); + } +} +---- + +==== `TimeManager_OnNetworkDisconnected()` + +[source,cpp] +---- +esp_err_t TimeManager_OnNetworkDisconnected(void); +---- + +*Description*: Called when network connection is lost. Switches to RTC as time source. + +*Process*: + +1. Clears network available flag +2. Switches active source to RTC (if available) +3. Posts `TIME_EVENT_SOURCE_CHANGED` event if source changed +4. Falls back to system time if no RTC + +*Return Value*: `ESP_OK` + +--- + +=== Time Retrieval + +==== `TimeManager_GetTime()` + +[source,cpp] +---- +esp_err_t TimeManager_GetTime(struct tm *p_Time, TimeManager_Source_t *p_Source); +---- + +*Description*: Gets the current time from the best available source. + +*Parameters*: + +* `p_Time`: Pointer to store the time structure +* `p_Source`: Optional pointer to store the time source (can be NULL) + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_ARG` if `p_Time` is NULL + +*Example*: + +[source,cpp] +---- +struct tm timeinfo; +TimeManager_Source_t source; + +if (TimeManager_GetTime(&timeinfo, &source) == ESP_OK) { + ESP_LOGI(TAG, "Current time: %04d-%02d-%02d %02d:%02d:%02d (Source: %d)", + timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, source); +} +---- + +==== `TimeManager_GetTimestamp()` + +[source,cpp] +---- +esp_err_t TimeManager_GetTimestamp(time_t *p_Time, TimeManager_Source_t *p_Source); +---- + +*Description*: Gets the current time as UNIX timestamp (seconds since epoch). + +*Parameters*: + +* `p_Time`: Pointer to store the timestamp +* `p_Source`: Optional pointer to store the time source (can be NULL) + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_ARG` if `p_Time` is NULL + +*Example*: + +[source,cpp] +---- +time_t timestamp; + +if (TimeManager_GetTimestamp(×tamp, NULL) == ESP_OK) { + ESP_LOGI(TAG, "Current timestamp: %ld", timestamp); +} +---- + +==== `TimeManager_GetTimeString()` + +[source,cpp] +---- +esp_err_t TimeManager_GetTimeString(char *p_Buffer, size_t Size, const char *Format); +---- + +*Description*: Formats the current time as a string using strftime format. + +*Parameters*: + +* `p_Buffer`: Buffer to store the formatted time +* `Size`: Buffer size +* `Format`: Time format string (strftime format) + +*Common Format Specifiers*: + +* `%Y` - Year (4 digits) +* `%m` - Month (01-12) +* `%d` - Day of month (01-31) +* `%H` - Hour (00-23) +* `%M` - Minute (00-59) +* `%S` - Second (00-59) +* `%A` - Weekday name (e.g., "Monday") +* `%B` - Month name (e.g., "January") + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_ARG` if parameters are NULL or Size is 0 +* `ESP_ERR_NO_MEM` if buffer is too small + +*Example*: + +[source,cpp] +---- +char time_str[64]; + +// ISO 8601 format +TimeManager_GetTimeString(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S"); +ESP_LOGI(TAG, "Time: %s", time_str); + +// Human-readable format +TimeManager_GetTimeString(time_str, sizeof(time_str), "%A, %B %d, %Y at %I:%M %p"); +ESP_LOGI(TAG, "Time: %s", time_str); +---- + +--- + +=== Status and Control + +==== `TimeManager_GetStatus()` + +[source,cpp] +---- +esp_err_t TimeManager_GetStatus(TimeManager_Status_t *p_Status); +---- + +*Description*: Gets comprehensive status information about time synchronization. + +*Parameters*: + +* `p_Status`: Pointer to store the status structure + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_ARG` if `p_Status` is NULL + +*Example*: + +[source,cpp] +---- +TimeManager_Status_t status; + +if (TimeManager_GetStatus(&status) == ESP_OK) { + ESP_LOGI(TAG, "Active source: %d", status.ActiveSource); + ESP_LOGI(TAG, "SNTP available: %s", status.SNTP_Available ? "yes" : "no"); + ESP_LOGI(TAG, "RTC available: %s", status.RTC_Available ? "yes" : "no"); + ESP_LOGI(TAG, "SNTP syncs: %lu", status.SNTP_SyncCount); + ESP_LOGI(TAG, "Last SNTP sync: %ld", status.LastSync_SNTP); +} +---- + +==== `TimeManager_ForceSync()` + +[source,cpp] +---- +esp_err_t TimeManager_ForceSync(void); +---- + +*Description*: Forces immediate SNTP synchronization (if network available). + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_STATE` if network is not connected + +*Example*: + +[source,cpp] +---- +if (TimeManager_ForceSync() == ESP_OK) { + ESP_LOGI(TAG, "Forced SNTP sync initiated"); +} +---- + +==== `TimeManager_IsTimeSynchronized()` + +[source,cpp] +---- +bool TimeManager_IsTimeSynchronized(void); +---- + +*Description*: Checks if time has been synchronized from SNTP or RTC. + +*Return Value*: `true` if time is synchronized and reliable, `false` otherwise + +*Example*: + +[source,cpp] +---- +if (TimeManager_IsTimeSynchronized()) { + // Safe to use timestamps for logging, etc. + save_data_with_timestamp(); +} else { + ESP_LOGW(TAG, "Time not synchronized, timestamps may be inaccurate"); +} +---- + +--- + +=== Timezone Configuration + +==== `TimeManager_SetTimezone()` + +[source,cpp] +---- +esp_err_t TimeManager_SetTimezone(const char *p_Timezone); +---- + +*Description*: Sets the timezone for time display. + +*Parameters*: + +* `p_Timezone`: Timezone string in POSIX format + +*POSIX Timezone Format*: + +[source] +---- +std offset [dst [offset] [,start[/time],end[/time]]] + +std - Standard timezone abbreviation (e.g., CET, EST) +offset - Hours offset from UTC (e.g., -1, +5) +dst - Daylight saving time abbreviation (e.g., CEST, EDT) +start - DST start rule (e.g., M3.5.0 = last Sunday in March) +end - DST end rule (e.g., M10.5.0 = last Sunday in October) +---- + +*Common Timezone Examples*: + +[cols="2,3,2"] +|=== +|Timezone |String |Description + +|UTC +|`UTC-0` +|Coordinated Universal Time + +|CET/CEST +|`CET-1CEST,M3.5.0,M10.5.0/3` +|Central European Time + +|EST/EDT +|`EST5EDT,M3.2.0,M11.1.0` +|US Eastern Time + +|PST/PDT +|`PST8PDT,M3.2.0,M11.1.0` +|US Pacific Time + +|JST +|`JST-9` +|Japan Standard Time (no DST) + +|AEST/AEDT +|`AEST-10AEDT,M10.1.0,M4.1.0/3` +|Australian Eastern Time +|=== + +*Return Value*: + +* `ESP_OK` on success +* `ESP_ERR_INVALID_ARG` if `p_Timezone` is NULL + +*Example*: + +[source,cpp] +---- +// Set Central European Time +TimeManager_SetTimezone("CET-1CEST,M3.5.0,M10.5.0/3"); + +// Now all time functions return local time +char time_str[64]; +TimeManager_GetTimeString(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S %Z"); +ESP_LOGI(TAG, "Local time: %s", time_str); +---- + +--- + +== Usage + +=== Basic Workflow + +==== 1. Initialization at Startup + +[source,cpp] +---- +#include "timeManager.h" + +void app_main(void) +{ + i2c_master_dev_handle_t rtc_handle; + + // Initialize I2C and RTC first + DevicesManager_GetRTCHandle(&rtc_handle); + + // Initialize Time Manager + ESP_ERROR_CHECK(TimeManager_Init(&rtc_handle)); + + // Set desired timezone + Settings_System_t system_settings; + SettingsManager_GetSystem(&system_settings); + TimeManager_SetTimezone(system_settings.Timezone); + + // Further initializations... +} +---- + +==== 2. Network Event Integration + +[source,cpp] +---- +static void network_event_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + switch (event_id) { + case NETWORK_EVENT_WIFI_CONNECTED: + TimeManager_OnNetworkConnected(); + break; + + case NETWORK_EVENT_WIFI_DISCONNECTED: + TimeManager_OnNetworkDisconnected(); + break; + } +} + +void init_network(void) +{ + esp_event_handler_register(NETWORK_EVENTS, + ESP_EVENT_ANY_ID, + network_event_handler, + NULL); +} +---- + +==== 3. Using Time in Application + +[source,cpp] +---- +// Get current time +struct tm timeinfo; +if (TimeManager_GetTime(&timeinfo, NULL) == ESP_OK) { + char filename[64]; + snprintf(filename, sizeof(filename), + "capture_%04d%02d%02d_%02d%02d%02d.jpg", + timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + save_image(filename); +} + +// Check if time is reliable +if (TimeManager_IsTimeSynchronized()) { + // Safe to use for logging + log_event_with_timestamp(); +} else { + ESP_LOGW(TAG, "Time not synchronized, waiting..."); +} +---- + +--- + +=== Event Handling + +==== Registering Event Handlers + +[source,cpp] +---- +#include +#include "time_types.h" + +static void time_event_handler(void* handler_args, esp_event_base_t base, + int32_t id, void* event_data) +{ + switch (id) { + case TIME_EVENT_SYNCHRONIZED: { + struct tm *timeinfo = (struct tm*)event_data; + ESP_LOGI(TAG, "Time synchronized: %04d-%02d-%02d %02d:%02d:%02d", + timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, + timeinfo->tm_mday, timeinfo->tm_hour, + timeinfo->tm_min, timeinfo->tm_sec); + + // Update RTC display + update_clock_display(); + break; + } + + case TIME_EVENT_SOURCE_CHANGED: { + TimeManager_Source_t *source = (TimeManager_Source_t*)event_data; + const char *source_name = (*source == TIME_SOURCE_SNTP) ? "SNTP" : + (*source == TIME_SOURCE_RTC) ? "RTC" : "System"; + ESP_LOGI(TAG, "Time source changed to: %s", source_name); + break; + } + } +} + +void init_time_events(void) +{ + esp_event_handler_register(TIME_EVENTS, + ESP_EVENT_ANY_ID, + time_event_handler, + NULL); +} +---- + +--- + +=== Advanced Patterns + +==== Status Monitoring Task + +[source,cpp] +---- +void time_status_task(void *pvParameters) +{ + TimeManager_Status_t status; + char time_str[64]; + + while (1) { + if (TimeManager_GetStatus(&status) == ESP_OK) { + TimeManager_GetTimeString(time_str, sizeof(time_str), + "%Y-%m-%d %H:%M:%S"); + + ESP_LOGI(TAG, "=== Time Manager Status ==="); + ESP_LOGI(TAG, "Current time: %s", time_str); + ESP_LOGI(TAG, "Active source: %s", + status.ActiveSource == TIME_SOURCE_SNTP ? "SNTP" : + status.ActiveSource == TIME_SOURCE_RTC ? "RTC" : "System"); + ESP_LOGI(TAG, "SNTP syncs: %lu (last: %ld sec ago)", + status.SNTP_SyncCount, + status.LastSync_SNTP > 0 ? time(NULL) - status.LastSync_SNTP : 0); + ESP_LOGI(TAG, "RTC reads: %lu", status.RTC_SyncCount); + } + + vTaskDelay(pdMS_TO_TICKS(30000)); // Every 30 seconds + } +} +---- + +==== Timezone Selection UI + +[source,cpp] +---- +typedef struct { + const char *name; + const char *posix_tz; +} Timezone_Entry_t; + +const Timezone_Entry_t timezones[] = { + {"UTC", "UTC-0"}, + {"London (UK)", "GMT0BST,M3.5.0/1,M10.5.0"}, + {"Berlin (Germany)", "CET-1CEST,M3.5.0,M10.5.0/3"}, + {"New York (US)", "EST5EDT,M3.2.0,M11.1.0"}, + {"Los Angeles (US)", "PST8PDT,M3.2.0,M11.1.0"}, + {"Tokyo (Japan)", "JST-9"}, + {"Sydney (Australia)", "AEST-10AEDT,M10.1.0,M4.1.0/3"}, +}; + +void set_timezone_by_index(size_t index) +{ + if (index < sizeof(timezones) / sizeof(timezones[0])) { + ESP_LOGI(TAG, "Setting timezone to: %s", timezones[index].name); + TimeManager_SetTimezone(timezones[index].posix_tz); + + // Save to settings + Settings_System_t system; + SettingsManager_GetSystem(&system); + strncpy(system.Timezone, timezones[index].posix_tz, sizeof(system.Timezone) - 1); + SettingsManager_UpdateSystem(&system); + SettingsManager_Save(); + } +} +---- + +==== Periodic Time Display + +[source,cpp] +---- +void display_time_task(void *pvParameters) +{ + char time_str[64]; + TimeManager_Source_t source; + + while (1) { + struct tm timeinfo; + if (TimeManager_GetTime(&timeinfo, &source) == ESP_OK) { + // Format time + strftime(time_str, sizeof(time_str), "%H:%M:%S", &timeinfo); + + // Update display with source indicator + const char *source_icon = (source == TIME_SOURCE_SNTP) ? "🌐" : + (source == TIME_SOURCE_RTC) ? "⏰" : "⚠️"; + + display_text(time_str, source_icon); + } + + vTaskDelay(pdMS_TO_TICKS(1000)); // Update every second + } +} +---- + +--- + +== Configuration + +=== Compile-Time Constants + +The following constants can be adjusted in `timeManager.cpp`: + +[cols="2,1,3"] +|=== +|Constant |Default |Description + +|`TIME_MANAGER_SYNC_INTERVAL_SEC` +|3600 +|SNTP sync interval (1 hour) + +|`TIME_MANAGER_RTC_BACKUP_INTERVAL_SEC` +|300 +|RTC backup interval (5 minutes) + +|`TIME_MANAGER_VALID_YEAR_MIN` +|2025 +|Minimum valid year for RTC validation +|=== + +=== SNTP Server Configuration + +By default, the Time Manager uses `pool.ntp.org`. To use a custom NTP server: + +[source,cpp] +---- +// In timeManager.cpp, modify: +esp_sntp_setservername(0, "time.google.com"); // Google NTP +// or +esp_sntp_setservername(0, "time.nist.gov"); // NIST NTP +// or +esp_sntp_setservername(0, "192.168.1.1"); // Local NTP server +---- + +--- + +== Best Practices + +=== DO's + +* ✓ Always initialize Time Manager after Device Manager (requires RTC handle) +* ✓ Call `TimeManager_OnNetworkConnected()` when WiFi connects +* ✓ Call `TimeManager_OnNetworkDisconnected()` when WiFi disconnects +* ✓ Check `TimeManager_IsTimeSynchronized()` before using time for critical operations +* ✓ Set timezone according to user settings +* ✓ Register event handlers to monitor time sync status +* ✓ Use RTC with battery backup for best reliability + +=== DON'Ts + +* ✗ Don't assume time is accurate immediately after boot +* ✗ Don't use system time for security-critical operations without sync check +* ✗ Don't manually set system time (use Time Manager functions) +* ✗ Don't forget to handle network events +* ✗ Don't rely on time accuracy without RTC during extended offline periods + +=== Performance Tips + +* *Battery Backup*: Use RTC with battery for uninterrupted timekeeping +* *Fast Boot*: RTC provides immediate valid time on startup +* *Network Sync*: SNTP provides high accuracy when online +* *Periodic Backup*: RTC backup ensures time survives network outages + +--- + +== Troubleshooting + +=== Common Problems + +==== Time not synchronized after boot + +*Symptom*: `TimeManager_IsTimeSynchronized()` returns `false` + +*Possible Causes*: +* Network not connected yet +* RTC not available or has invalid time +* System just booted and SNTP hasn't synced yet + +*Solution*: +[source,cpp] +---- +// Wait for time sync or proceed with degraded functionality +int retries = 0; +while (!TimeManager_IsTimeSynchronized() && retries < 30) { + ESP_LOGI(TAG, "Waiting for time sync... (%d/30)", retries + 1); + vTaskDelay(pdMS_TO_TICKS(1000)); + retries++; +} + +if (TimeManager_IsTimeSynchronized()) { + ESP_LOGI(TAG, "Time synchronized!"); +} else { + ESP_LOGW(TAG, "Proceeding without time sync"); +} +---- + +==== SNTP sync fails + +*Symptom*: Time source stays on RTC/System, SNTP_SyncCount doesn't increase + +*Possible Causes*: +* Network connection issues +* Firewall blocking NTP traffic (port 123 UDP) +* DNS resolution failure for pool.ntp.org +* Incorrect NTP server address + +*Solution*: +[source,cpp] +---- +// Check network connectivity +ESP_LOGI(TAG, "Testing network..."); +// Try force sync +TimeManager_ForceSync(); + +// Check status +TimeManager_Status_t status; +TimeManager_GetStatus(&status); +ESP_LOGI(TAG, "Network available: %s", status.SNTP_Available ? "yes" : "no"); +ESP_LOGI(TAG, "SNTP syncs: %lu", status.SNTP_SyncCount); +---- + +==== RTC time invalid after battery replacement + +*Symptom*: RTC year shows < 2025, time not loaded from RTC + +*Cause*: RTC was reset or battery was dead + +*Solution*: +[source,cpp] +---- +// RTC will be automatically updated once SNTP syncs +// Wait for network and SNTP sync, or manually set RTC: +struct tm manual_time = { + .tm_year = 2026 - 1900, // Years since 1900 + .tm_mon = 0, // January (0-11) + .tm_mday = 14, // 14th + .tm_hour = 12, + .tm_min = 0, + .tm_sec = 0, +}; +RTC_SetTime(&manual_time); +---- + +==== Timezone not working correctly + +*Symptom*: Displayed time is wrong by several hours + +*Cause*: Incorrect timezone string or DST rules + +*Solution*: +[source,cpp] +---- +// Verify timezone string format +const char *tz = "CET-1CEST,M3.5.0,M10.5.0/3"; // Correct format +TimeManager_SetTimezone(tz); + +// Test with known date/time +struct tm test_time; +TimeManager_GetTime(&test_time, NULL); +char time_str[64]; +strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S %Z", &test_time); +ESP_LOGI(TAG, "Current time: %s", time_str); +---- + +==== Time drifts during long offline periods + +*Symptom*: Time becomes increasingly inaccurate without network + +*Cause*: System clock drift, no RTC, or RTC crystal inaccuracy + +*Solution*: + +* Install RTC module with accurate crystal oscillator +* Use RTC with temperature compensation (DS3231) +* Ensure RTC has backup battery +* Implement periodic RTC calibration when online + +--- + +== Technical Details + +=== SNTP Operation + +The Time Manager uses ESP-IDF's built-in SNTP client: + +* *Protocol*: SNTPv4 (Simple Network Time Protocol) +* *Transport*: UDP port 123 +* *Mode*: Client polling mode +* *Callback*: Async notification on successful sync +* *Retry*: Automatic retry on failure + +=== RTC Integration + +Compatible with I2C RTC modules: + +* DS1307, DS3231, PCF8523, etc. +* Requires I2C master device handle +* Uses standard `struct tm` format +* Validates year before accepting RTC time + +=== Timezone Implementation + +Uses POSIX TZ environment variable: + +* Configured via `setenv("TZ", ...)` and `tzset()` +* Supports DST (Daylight Saving Time) transitions +* Automatic DST adjustment based on rules +* Affects all time functions (`localtime()`, `strftime()`, etc.) + +--- + +== License + +Copyright (C) Daniel Kampert, 2026 + +This software is licensed under the GNU General Public License v3.0. +Website: www.kampis-elektroecke.de + +--- + +== Contact + +Please report bugs and suggestions to: DanielKampert@kampis-elektroecke.de diff --git a/docs/USBManager.adoc b/docs/USBManager.adoc new file mode 100644 index 0000000..16d1326 --- /dev/null +++ b/docs/USBManager.adoc @@ -0,0 +1,1229 @@ += PyroVision USB Manager Documentation +Daniel Kampert +v1.0, 2026-02-08 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The USB Manager implements USB functionality for the PyroVision firmware, supporting both USB Mass Storage Device (MSC) and USB Video Class (UVC) modes. This allows the device to function as: + +* *USB Mass Storage Device*: Expose internal storage (SD card or flash) as a USB drive +* *USB Webcam (UVC)*: Stream the thermal camera image to a PC as a webcam device + +=== Key Features + +* *Dual Mode Support*: USB Mass Storage Class (MSC) and USB Video Class (UVC) +* *USB Mass Storage Class (MSC)*: Standard USB drive functionality compatible with all operating systems +* *USB Video Class (UVC)*: Webcam functionality for streaming thermal images +* *Automatic Storage Detection*: Works with both internal flash and SD card storage (MSC mode) +* *Upscaled Thermal Streaming*: High-resolution thermal image streaming (UVC mode) +* *TinyUSB Integration*: Based on Espressif's esp_tinyusb component +* *Asynchronous Deinitialization*: Non-blocking USB disconnect with background task +* *Event-driven Architecture*: Notification of USB state changes via ESP Event System +* *Windows Compatibility*: Proper USB enumeration and ejection support +* *Re-initialization Support*: Reliable activate → deactivate → activate cycles +* *Filesystem Locking*: Integrates with Memory Manager to prevent data corruption (MSC mode) + +--- + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Manager/USB/ +├── usbManager.h # Public API +├── usbManager.cpp # Implementation +└── usbTypes.h # Event types and configuration +---- + +=== TinyUSB Stack Integration + +The USB Manager is built on top of the TinyUSB stack provided by Espressif: + +[source] +---- +┌─────────────────────────────────────────┐ +│ Application Layer │ +│ (GUI, Settings, File Operations) │ +└──────────────┬──────────────────────────┘ + │ +┌──────────────▼──────────────────────────┐ +│ USB Manager │ +│ - Init/Deinit Lifecycle │ +│ - Storage Configuration │ +│ - Event Posting │ +└──────────────┬──────────────────────────┘ + │ +┌──────────────▼──────────────────────────┐ +│ TinyUSB MSC Layer │ +│ - tinyusb_msc_new_storage_sdmmc() │ +│ - tinyusb_msc_new_storage_spiflash() │ +│ - tinyusb_msc_delete_storage() │ +└──────────────┬──────────────────────────┘ + │ +┌──────────────▼──────────────────────────┐ +│ TinyUSB Core │ +│ - USB Stack Implementation │ +│ - tud_connect() / tud_disconnect() │ +│ - tud_mounted() Status │ +└──────────────┬──────────────────────────┘ + │ +┌──────────────▼──────────────────────────┐ +│ USB Hardware (ESP32-S3) │ +│ - Native USB OTG Interface │ +└─────────────────────────────────────────┘ +---- + +=== Storage Backend Selection + +USB Manager automatically detects and configures the appropriate storage backend. +When MSC is enabled, the VFS filesystem is soft-unmounted to give the USB host exclusive +block-level access. When MSC is disabled, the VFS is soft-remounted so the application +can resume file operations. + +[source] +---- +MSC Enable: + MemoryManager_LockFilesystem() + MemoryManager_SoftUnmountStorage() ← VFS unmounted, raw handles preserved + MemoryManager_GetStorageLocation() + │ + ├─► SD Card Detected + │ └─► tinyusb_msc_new_storage_sdmmc(MOUNT_USB) + │ └─► Uses sdmmc_card_t handle (block-level access) + │ + └─► Internal Flash + └─► tinyusb_msc_new_storage_spiflash(MOUNT_USB) + └─► Uses wl_handle_t (block-level access) + +MSC Disable: + tinyusb_msc_delete_storage() ← Release USB block access + MemoryManager_SoftRemountStorage() ← VFS remounted for app + MemoryManager_UnlockFilesystem() +---- + +=== USB Lifecycle State Machine + +[source] +---- +┌─────────────┐ +│ NOT_INIT │ Initial state +└──────┬──────┘ + │ USBManager_Init() + ▼ +┌─────────────┐ +│ INITIALIZED │ USB MSC Active, Device Visible +│ │ Posts: USB_EVENT_INITIALIZED +└──────┬──────┘ + │ USBManager_Deinit() + ▼ +┌─────────────┐ +│DEINITIALIZING│ Background Task Running +│ │ Waiting for USB Disconnect +└──────┬──────┘ + │ tud_mounted() == false + │ Timeout or Disconnect Confirmed + ▼ +┌─────────────┐ +│ NOT_INIT │ Posts: USB_EVENT_UNINITIALIZED +│ │ Ready for Re-initialization +└─────────────┘ +---- + +=== Internal State Management + +The manager maintains its state in the `USBManager_State_t` structure: + +[source,cpp] +---- +typedef struct { + bool isInitialized; // USB MSC active + bool isDeinitializing; // Deinitialization in progress + bool isUSBMounted; // USB host has mounted device + USB_Manager_Config_t Config; // Current configuration + tinyusb_msc_storage_handle_t Storage; // Storage handle + TaskHandle_t DeinitTask; // Deinit task handle + SemaphoreHandle_t DisconnectSemaphore; // Disconnect detection +} USBManager_State_t; +---- + +--- + +== Data Structures + +=== USB Manager Configuration + +[source,cpp] +---- +typedef enum { + USB_MODE_MSC = 0, // USB Mass Storage Class mode + USB_MODE_UVC = 1, // USB Video Class (Webcam) mode +} USB_Mode_t; + +typedef struct { + USB_Mode_t Mode; // USB mode (MSC or UVC) + const char *MountPoint; // Mount point (MSC mode only) + char *Manufacturer; // USB manufacturer string + char *Product; // USB product name string + char *SerialNumber; // USB serial number string + uint16_t FrameWidth; // Frame width (UVC mode only) + uint16_t FrameHeight; // Frame height (UVC mode only) + uint8_t FrameInterval; // Frame interval (UVC mode only) +} USB_Manager_Config_t; +---- + +* *Mode*: USB operating mode (MSC or UVC) +* *MountPoint*: Must match MemoryManager mount point (MSC mode only, `/storage` or `/sdcard`) +* *Manufacturer*: Manufacturer name shown on PC (e.g., "Kampi's Elektroecke") +* *Product*: Product name shown on PC (e.g., "PyroVision Thermal Camera") +* *SerialNumber*: Serial number string +* *FrameWidth*: Video frame width in pixels (UVC mode only, e.g., 160) +* *FrameHeight*: Video frame height in pixels (UVC mode only, e.g., 120) +* *FrameInterval*: Frame interval in 100ns units (UVC mode only, FPS = 10000000 / FrameInterval) + +**Example (MSC Mode):** + +[source,cpp] +---- +const USB_Manager_Config_t USB_MSC_Config = { + .Mode = USB_MODE_MSC, + .MountPoint = MemoryManager_GetStoragePath(), + .Manufacturer = "Kampi's Elektroecke", + .Product = "PyroVision", + .SerialNumber = "PV-12345678", + .FrameWidth = 0, // Not used in MSC mode + .FrameHeight = 0, // Not used in MSC mode + .FrameInterval = 0, // Not used in MSC mode +}; +---- + +**Example (UVC Mode):** + +[source,cpp] +---- +const USB_Manager_Config_t USB_UVC_Config = { + .Mode = USB_MODE_UVC, + .MountPoint = NULL, // Not used in UVC mode + .Manufacturer = "Kampi's Elektroecke", + .Product = "PyroVision Thermal Webcam", + .SerialNumber = "PV-12345678", + .FrameWidth = 160, // Lepton thermal camera resolution + .FrameHeight = 120, + .FrameInterval = 1111111, // 9 FPS (10000000 / 9) +}; +---- + +=== Event System + +==== Event Base + +[source,cpp] +---- +ESP_EVENT_DECLARE_BASE(USB_EVENTS); +---- + +==== Event IDs + +[cols="2,3,2"] +|=== +|Event |Description |Data Payload + +|`USB_EVENT_INITIALIZED` +|USB MSC initialized and ready + +Device is visible to PC +|NULL + +|`USB_EVENT_UNINITIALIZED` +|USB MSC deinitialized + +Filesystem unlocked, app can write +|NULL +|=== + +**Event Handler Example:** + +[source,cpp] +---- +void on_USB_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, + int32_t ID, void *p_Data) +{ + switch (ID) { + case USB_EVENT_INITIALIZED: + ESP_LOGI(TAG, "USB MSC Active - Device visible to PC"); + + // Update UI: Show USB icon + lv_obj_clear_flag(ui_Icon_USB, LV_OBJ_FLAG_HIDDEN); + + // Disable save buttons (filesystem locked) + lv_obj_add_state(ui_Button_Save, LV_STATE_DISABLED); + break; + + case USB_EVENT_UNINITIALIZED: + ESP_LOGI(TAG, "USB MSC Deactivated - Filesystem accessible"); + + // Update UI: Hide USB icon + lv_obj_add_flag(ui_Icon_USB, LV_OBJ_FLAG_HIDDEN); + + // Enable save buttons again + lv_obj_clear_state(ui_Button_Save, LV_STATE_DISABLED); + break; + } +} +---- + +**Registration:** + +[source,cpp] +---- +esp_event_handler_register(USB_EVENTS, ESP_EVENT_ANY_ID, + on_USB_Event_Handler, NULL); +---- + +--- + +== API Reference + +=== Initialization and Lifecycle + +==== USBManager_Init() + +[source,cpp] +---- +esp_err_t USBManager_Init(const USB_Manager_Config_t *p_Config); +---- + +Initialize the USB Manager in either Mass Storage (MSC) or Video Class (UVC) mode. + +**Behavior (MSC Mode):** + +1. Validates configuration parameters +2. Waits for any pending deinitialization to complete (max 10 seconds) +3. Locks filesystem via `MemoryManager_LockFilesystem()` +4. Detects storage type from Memory Manager (SD card or internal flash) +5. Cleans up old storage handles from previous sessions +6. Installs TinyUSB driver (reuses if already installed) +7. Creates MSC storage backend (SDMMC or SPI flash) +8. Connects to USB bus via `tud_connect()` to trigger PC enumeration +9. Posts `USB_EVENT_INITIALIZED` event + +**Behavior (UVC Mode):** + +1. Validates configuration parameters (FrameWidth, FrameHeight) +2. Waits for any pending deinitialization to complete +3. Creates frame buffer mutex for thread-safe frame updates +4. Allocates initial frame buffer in SPIRAM +5. Installs TinyUSB driver +6. NOTE: Full UVC implementation requires ESP-IOT-Solution UVC component +7. Posts `USB_EVENT_INITIALIZED` event + +**Parameters:** + +* `p_Config` - Pointer to USB manager configuration structure + +**Return Values:** + +* `ESP_OK` - USB successfully initialized and active +* `ESP_ERR_INVALID_ARG` - p_Config is NULL or invalid parameters +* `ESP_ERR_INVALID_STATE` - Already initialized or deinitialization timeout +* `ESP_ERR_NO_MEM` - Failed to allocate memory (increase DMA pool or SPIRAM) +* `ESP_FAIL` - USB initialization failed + +**Thread Safety:** Not thread-safe during initialization. Call once from main task. + +**Critical Requirements (MSC Mode):** + +* Memory Manager must be initialized first +* DMA memory pool must be adequate (CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL ≥ 160 KB) +* Filesystem must not be accessed by application during USB MSC operation + +**Critical Requirements (UVC Mode):** + +* Frame buffer memory must be available in SPIRAM +* For full functionality, requires ESP-IOT-Solution UVC component integration +* Call `USBManager_SendFrame()` periodically to update webcam stream + +**Example (MSC Mode):** + +[source,cpp] +---- +void EnableUSB_MSC_Mode(void) +{ + Settings_Info_t InfoSettings; + SettingsManager_GetInfo(&InfoSettings); + + // Configure USB Mass Storage + const USB_Manager_Config_t USB_Config = { + .Mode = USB_MODE_MSC, + .MountPoint = MemoryManager_GetStoragePath(), + .Manufacturer = InfoSettings.Manufacturer, + .Product = InfoSettings.Name, + .SerialNumber = InfoSettings.Serial, + .FrameWidth = 0, + .FrameHeight = 0, + .FrameInterval = 0, + }; + + // Initialize USB MSC + esp_err_t Error = USBManager_Init(&USB_Config); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize USB MSC: %d", Error); + return; + } + + ESP_LOGI(TAG, "USB Mass Storage Device active"); +} +---- + +**Example (UVC Mode):** + +[source,cpp] +---- +void EnableUSB_UVC_Mode(void) +{ + Settings_Info_t InfoSettings; + SettingsManager_GetInfo(&InfoSettings); + + // Configure USB Webcam + const USB_Manager_Config_t USB_Config = { + .Mode = USB_MODE_UVC, + .MountPoint = NULL, + .Manufacturer = InfoSettings.Manufacturer, + .Product = InfoSettings.Name, + .SerialNumber = InfoSettings.Serial, + .FrameWidth = 160, + .FrameHeight = 120, + .FrameInterval = 1111111, // 9 FPS + }; + + // Initialize USB UVC + esp_err_t Error = USBManager_Init(&USB_Config); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize USB UVC: %d", Error); + return; + } + + ESP_LOGI(TAG, "USB Webcam (UVC) active"); + ESP_LOGW(TAG, "Note: Full UVC requires ESP-IOT-Solution component"); +} +---- + } + + // Wait for USB_EVENT_INITIALIZED event... +} +---- + +==== USBManager_Deinit() + +[source,cpp] +---- +esp_err_t USBManager_Deinit(void); +---- + +Deinitialize the USB Manager and stop USB Mass Storage Device asynchronously. + +**Behavior:** + +1. Validates USB Manager is initialized +2. Validates not already deinitializing +3. Sets `isInitialized = false` to prevent new operations +4. Sets `isDeinitializing = true` to signal deinitialization in progress +5. Creates background task `Task_USB_Deinit` (priority 1) to handle the lengthy disconnect process +6. Returns immediately (non-blocking) + +**Background Task Operations:** + +1. Calls `tud_disconnect()` to actively disconnect from USB bus +2. Waits 500 ms for Windows to process disconnect +3. Polls `tud_mounted()` every 100 ms until false (max 5 seconds) +4. Unlocks filesystem via `MemoryManager_UnlockFilesystem()` +5. Posts `USB_EVENT_UNINITIALIZED` event +6. Self-deletes task via `vTaskDelete(NULL)` + +**Return Values:** + +* `ESP_OK` - Deinitialization started successfully (async operation) +* `ESP_ERR_INVALID_STATE` - Not initialized or already deinitializing + +**Thread Safety:** Thread-safe. Can be called from any task. + +**Important Notes:** + +* Function returns immediately; actual deinitialization happens in background +* Storage handle is NOT deleted to prevent TinyUSB callback crashes +* Old handle is cleaned up on next `USBManager_Init()` call +* Application should wait for `USB_EVENT_UNINITIALIZED` before writing to filesystem + +**Example:** + +[source,cpp] +---- +void DisableUSB_Mode(void) +{ + if (!USBManager_IsInitialized()) { + ESP_LOGW(TAG, "USB not active!"); + return; + } + + ESP_LOGI(TAG, "Disabling USB Mass Storage..."); + + // Start asynchronous deinitialization + esp_err_t Error = USBManager_Deinit(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to deinitialize USB: %d", Error); + return; + } + + ESP_LOGI(TAG, "USB deinitialization started - waiting for completion..."); + + // Show loading message box + lv_obj_t *msgbox = lv_msgbox_create(NULL); + lv_msgbox_add_title(msgbox, "USB Deinitialization"); + lv_msgbox_add_text(msgbox, "Waiting for USB device to disconnect...\nPlease wait."); + lv_obj_center(msgbox); + + // Message box will be closed by USB_EVENT_UNINITIALIZED handler +} +---- + +==== USBManager_IsInitialized() + +[source,cpp] +---- +bool USBManager_IsInitialized(void); +---- + +Check if USB Manager is initialized and USB MSC is active. + +**Return Values:** + +* `true` - USB MSC is active, device is exposed to PC +* `false` - USB MSC not initialized + +**Thread Safety:** Thread-safe. + +**Example:** + +[source,cpp] +---- +void SaveImage(uint8_t *p_Data, size_t Size) +{ + // Check if USB is active + if (USBManager_IsInitialized()) { + ESP_LOGW(TAG, "Cannot save image - USB mode is active!"); + + // Show error message + lv_obj_t *msgbox = lv_msgbox_create(NULL); + lv_msgbox_add_title(msgbox, "USB Active"); + lv_msgbox_add_text(msgbox, "Cannot save - USB mode is active!\nDisable USB first."); + + return; + } + + // Safe to write file + const char *p_Path = MemoryManager_GetStoragePath(); + char FilePath[256]; + snprintf(FilePath, sizeof(FilePath), "%s/image_%d.jpg", p_Path, ImageCounter++); + + FILE *fp = fopen(FilePath, "wb"); + if (fp != NULL) { + fwrite(p_Data, 1, Size, fp); + fclose(fp); + ESP_LOGI(TAG, "Image saved: %s", FilePath); + } +} +---- + +==== USBManager_SendFrame() + +[source,cpp] +---- +esp_err_t USBManager_SendFrame(const uint8_t *p_FrameBuffer, size_t BufferSize); +---- + +Send a new frame to the USB Video Class device (UVC mode only). + +**Behavior:** + +1. Validates that USB Manager is initialized in UVC mode +2. Validates frame buffer pointer and size +3. Acquires frame buffer mutex +4. Reallocates internal frame buffer if size changed +5. Copies frame data to internal buffer +6. Releases mutex +7. TODO: Signals TinyUSB UVC that new frame is ready + +**Parameters:** + +* `p_FrameBuffer` - Pointer to MJPEG frame buffer +* `BufferSize` - Size of the frame buffer in bytes + +**Return Values:** + +* `ESP_OK` - Frame copied successfully +* `ESP_ERR_INVALID_STATE` - USB not initialized or not in UVC mode +* `ESP_ERR_INVALID_ARG` - Invalid frame buffer pointer or size +* `ESP_ERR_NO_MEM` - Failed to allocate frame buffer + +**Thread Safety:** Thread-safe due to mutex protection. + +**Important Notes:** + +* Only valid in UVC mode; returns error if called in MSC mode +* Frame buffer is copied internally, so p_FrameBuffer can be reused after return +* Frame format must be MJPEG (Motion JPEG) +* Full UVC streaming requires ESP-IOT-Solution UVC component integration +* Current implementation provides API structure but streaming not yet functional + +**Example:** + +[source,cpp] +---- +void StreamThermalFrame(void) +{ + uint8_t *p_JPEGBuffer = NULL; + size_t JPEGSize = 0; + + // Capture and encode thermal frame as MJPEG + esp_err_t Error = CaptureThermalImage(&p_JPEGBuffer, &JPEGSize, IMAGE_FORMAT_JPEG); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to capture thermal image: %d", Error); + return; + } + + // Send frame to USB UVC + Error = USBManager_SendFrame(p_JPEGBuffer, JPEGSize); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to send frame: %d", Error); + } + + // Free buffer + free(p_JPEGBuffer); +} + +// Called periodically (e.g., 30 FPS) +void USB_UVC_StreamTask(void *p_Param) +{ + while (true) { + if (USBManager_IsInitialized()) { + StreamThermalFrame(); + } + + vTaskDelay(pdMS_TO_TICKS(33)); // ~30 FPS + } +} +---- + + +--- + +== Common Use Cases + +=== Use Case 1: Basic USB Activation + +[source,cpp] +---- +void ActivateUSB_Button_Callback(lv_event_t *e) +{ + ESP_LOGI(TAG, "USB activation requested by user"); + + // Check if already active + if (USBManager_IsInitialized()) { + ESP_LOGW(TAG, "USB already active!"); + return; + } + + // Lock filesystem + esp_err_t Error = MemoryManager_LockFilesystem(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to lock filesystem!"); + return; + } + + // Configure USB MSC + const USB_Manager_Config_t USB_Config = { + .p_MountPoint = MemoryManager_GetStoragePath(), + .p_VendorID = "PyroVis", + .p_ProductID = "ThermalCam", + .p_ProductRevision = "1.0", + }; + + // Initialize USB + Error = USBManager_Init(&USB_Config); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "USB initialization failed: %d", Error); + MemoryManager_UnlockFilesystem(); + + // Show error message + lv_obj_t *msgbox = lv_msgbox_create(NULL); + lv_msgbox_add_title(msgbox, "USB Error"); + lv_msgbox_add_text(msgbox, "Failed to activate USB mode!"); + return; + } +} +---- + +=== Use Case 2: Event-Driven UI Updates + +[source,cpp] +---- +void on_USB_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, + int32_t ID, void *p_Data) +{ + switch (ID) { + case USB_EVENT_INITIALIZED: { + ESP_LOGI(TAG, "USB initialized - updating UI"); + + // Set switch to ON state + lv_obj_add_state(ui_Switch_USB, LV_STATE_CHECKED); + + // Show USB icon in status bar + lv_obj_clear_flag(ui_Icon_USB_Active, LV_OBJ_FLAG_HIDDEN); + + // Hide save buttons (filesystem locked) + lv_obj_add_flag(ui_Button_Save, LV_OBJ_FLAG_HIDDEN); + + // Update status label + lv_label_set_text(ui_Label_Status, "USB Mode Active"); + + // Show success message + lv_obj_t *msgbox = lv_msgbox_create(NULL); + lv_msgbox_add_title(msgbox, "USB Active"); + lv_msgbox_add_text(msgbox, "Device connected to PC\nDo not disconnect during file transfer!"); + + break; + } + + case USB_EVENT_UNINITIALIZED: { + ESP_LOGI(TAG, "USB deinitialized - updating UI"); + + // Set switch to OFF state + lv_obj_clear_state(ui_Switch_USB, LV_STATE_CHECKED); + + // Hide USB icon + lv_obj_add_flag(ui_Icon_USB_Active, LV_OBJ_FLAG_HIDDEN); + + // Show save buttons again + lv_obj_clear_flag(ui_Button_Save, LV_OBJ_FLAG_HIDDEN); + + // Update status label + lv_label_set_text(ui_Label_Status, "USB Mode Disabled"); + + // Close loading message box if visible + if (usb_msgbox != NULL) { + lv_obj_del(usb_msgbox); + usb_msgbox = NULL; + } + + // Show completion message + lv_obj_t *msgbox = lv_msgbox_create(NULL); + lv_msgbox_add_title(msgbox, "USB Deactivated"); + lv_msgbox_add_text(msgbox, "USB safely disconnected\nYou can save files again."); + + break; + } + } +} + +// Register event handler in initialization +void app_main(void) +{ + // ... other initialization ... + + esp_event_handler_register(USB_EVENTS, ESP_EVENT_ANY_ID, + on_USB_Event_Handler, NULL); +} +---- + +=== Use Case 3: Toggle Switch with Animation Fix + +[source,cpp] +---- +void on_USB_Mode_Switch_Callback(lv_event_t *e) +{ + lv_obj_t *switch_obj = lv_event_get_target(e); + + if (lv_obj_has_state(switch_obj, LV_STATE_CHECKED)) { + // Switch turned ON - Activate USB + ESP_LOGI(TAG, "Enabling USB Mass Storage..."); + + if (USBManager_IsInitialized()) { + ESP_LOGW(TAG, "USB already active!"); + return; + } + + // Configure and initialize + const USB_Manager_Config_t USB_Config = { + .p_MountPoint = MemoryManager_GetStoragePath(), + .p_VendorID = "PyroVis", + .p_ProductID = "ThermalCam", + .p_ProductRevision = "1.0", + }; + + esp_err_t Error = USBManager_Init(&USB_Config); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize USB: %d", Error); + + // Revert switch on error + lv_obj_clear_state(switch_obj, LV_STATE_CHECKED); + } + } else { + // Switch turned OFF - Deactivate USB + ESP_LOGI(TAG, "Disabling USB Mass Storage..."); + + if (!USBManager_IsInitialized()) { + ESP_LOGW(TAG, "USB not active!"); + return; + } + + // Start asynchronous deinitialization + esp_err_t Error = USBManager_Deinit(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to deinitialize USB: %d", Error); + return; + } + + // Show waiting message + lv_obj_t *msgbox = lv_msgbox_create(NULL); + lv_msgbox_add_title(msgbox, "USB Deinitialization"); + lv_msgbox_add_text(msgbox, "Waiting for USB device to disconnect...\nPlease wait."); + lv_obj_center(msgbox); + + // Switch will be updated to OFF by USB_EVENT_UNINITIALIZED handler + } +} +---- + +=== Use Case 4: Re-initialization Cycle + +[source,cpp] +---- +void TestUSB_Reinitialization(void) +{ + ESP_LOGI(TAG, "=== USB Re-initialization Test ==="); + + // First activation + ESP_LOGI(TAG, "Activation #1"); + const USB_Manager_Config_t USB_Config = { + .p_MountPoint = MemoryManager_GetStoragePath(), + .p_VendorID = "PyroVis", + .p_ProductID = "Test", + .p_ProductRevision = "1.0", + }; + + esp_err_t Error = USBManager_Init(&USB_Config); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Init #1 failed: %d", Error); + return; + } + + ESP_LOGI(TAG, "Init #1 successful, waiting 5 seconds..."); + vTaskDelay(pdMS_TO_TICKS(5000)); + + // First deactivation + ESP_LOGI(TAG, "Deactivation #1"); + Error = USBManager_Deinit(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Deinit #1 failed: %d", Error); + return; + } + + ESP_LOGI(TAG, "Deinit #1 started, waiting for completion..."); + + // Wait for USB_EVENT_UNINITIALIZED (in real code, use event handler) + uint32_t WaitCount = 0; + while (USBManager_IsInitialized() && WaitCount < 100) { + vTaskDelay(pdMS_TO_TICKS(100)); + WaitCount++; + } + + if (WaitCount >= 100) { + ESP_LOGE(TAG, "Deinit #1 timeout!"); + return; + } + + ESP_LOGI(TAG, "Deinit #1 completed after %u ms", WaitCount * 100); + + // Additional delay to ensure cleanup + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Second activation (re-initialization) + ESP_LOGI(TAG, "Activation #2 (Re-initialization)"); + Error = USBManager_Init(&USB_Config); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Init #2 failed: %d", Error); + return; + } + + ESP_LOGI(TAG, "Init #2 successful!"); + ESP_LOGI(TAG, "=== Re-initialization Test PASSED ==="); +} +---- + +--- + +== Best Practices + +=== Initialization + +* *Order Matters*: Initialize Memory Manager before USB Manager +* *Lock Filesystem*: Always lock filesystem before activating USB MSC +* *Soft-Unmount for MSC*: Call `MemoryManager_SoftUnmountStorage()` after locking so the USB host gets exclusive block-level access +* *Event Registration*: Register USB event handlers before initialization +* *Configuration Validation*: Validate mount point matches Memory Manager + +=== Deinitialization + +* *Delete MSC Storage*: Always call `tinyusb_msc_delete_storage()` to release the LUN before remounting +* *Soft-Remount After MSC*: Call `MemoryManager_SoftRemountStorage()` to restore VFS file I/O +* *Unlock Filesystem*: Call `MemoryManager_UnlockFilesystem()` after remounting +* *Safe Ejection*: Ensure PC has ejected/unmounted drive before deactivating +* *No Forced Disconnect*: Never unplug cable without proper deactivation + +=== Filesystem Access + +* *Check Lock State*: Always check `MemoryManager_IsFilesystemLocked()` before writing +* *No Concurrent Access*: Never write to filesystem while USB MSC is active +* *UI Feedback*: Disable file save buttons during USB mode +* *Error Handling*: Show clear error messages if user tries to save during USB mode + +=== Error Recovery + +* *Unlock on Failure*: Always unlock filesystem if USB initialization fails +* *Timeout Handling*: Implement timeouts for deinitialization (max 10 seconds recommended) +* *State Validation*: Check `USBManager_IsInitialized()` before operations +* *Restart as Last Resort*: If USB state is corrupted, restart device + +=== UI/UX Considerations + +* *Visual Feedback*: Show USB icon in status bar when active +* *Loading Indicators*: Show progress during async deinitialization +* *Clear Warnings*: Warn users not to disconnect during file transfer +* *Disable Controls*: Disable conflicting operations during USB mode + +--- + +== Troubleshooting + +=== USB Device Not Visible on PC + +**Symptoms:** +* Device plugged in, but no drive letter appears +* Device manager shows unknown device or error code + +**Possible Causes:** +* `tud_connect()` not called or failed +* DMA memory insufficient (heap_caps_aligned_calloc failure) +* TinyUSB callbacks conflicting with library +* USB cable is charge-only (no data lines) + +**Solutions:** +* Ensure CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL ≥ 160 KB +* Use proper USB data cable, not charge-only cable +* Check ESP32-S3 USB port (not UART USB port) +* Enable debug logging: `ESP_LOG_LEVEL(TAG, ESP_LOG_DEBUG)` + +=== USB Deinitialization Timeout + +**Symptoms:** +* `USBManager_Deinit()` never posts `USB_EVENT_UNINITIALIZED` +* `tud_mounted()` remains true after deinitialization +* PC still shows device after deactivation + +**Possible Causes:** +* PC has not ejected the drive +* Background deinit task crashed +* `tud_disconnect()` not called + +**Solutions:** +* Eject drive from PC before calling `USBManager_Deinit()` +* Add explicit `tud_disconnect()` call in deinit task +* Increase polling timeout (currently 5 seconds) +* Restart device if timeout occurs + +=== Re-initialization Failure + +**Symptoms:** +* Cannot activate USB after deactivating it +* `USBManager_Init()` returns ESP_ERR_INVALID_STATE +* Error: "Previous deinit still running" + +**Possible Causes:** +* `USBManager_Init()` called too soon after `USBManager_Deinit()` +* Background deinit task still running +* Storage handle not cleaned up + +**Solutions:** +* Wait for `USB_EVENT_UNINITIALIZED` before re-initialization +* Implement wait loop for `isDeinitializing` flag (max 10 seconds) +* Add delay (500-1000 ms) after deinitialization completes +* Check for pending deinitialization in `USBManager_Init()` + +=== Display Artifacts During USB Operations + +**Symptoms:** +* Screen shows stripes or corruption when activating/deactivating USB +* Display freezes momentarily + +**Possible Causes:** +* High-priority USB deinit task starving display task +* SPI bus contention (display and flash on same bus) +* Display refresh interrupted + +**Solutions:** +* Lower USB deinit task priority to 1 (below display task) +* Avoid display invalidation pause (not necessary for internal flash) +* Ensure display task priority is higher (e.g., 5) +* Use Core 1 for USB operations, Core 0 for display + +=== Windows Drive Remains After Deactivation + +**Symptoms:** +* Windows shows drive letter even after USB deactivation +* "Device not responding" error when accessing drive + +**Possible Causes:** +* `tud_disconnect()` not called explicitly +* Windows cached driver state +* PC has files open on the drive + +**Solutions:** +* Add explicit `tud_disconnect()` in deinit task +* Ensure all files closed before deactivation +* Manually eject drive in Windows before deactivation +* Wait 500 ms after `tud_disconnect()` for OS processing + +=== Memory Allocation Failures + +**Symptoms:** +* `USBManager_Init()` returns ESP_ERR_NO_MEM +* Log: "Failed to allocate memory for MSC storage" + +**Possible Causes:** +* Insufficient DMA memory pool +* Memory fragmentation +* Large allocations by other components + +**Solutions:** +* Increase CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL to 160 KB or higher +* Initialize USB Manager early in boot sequence +* Free unused memory before USB initialization +* Check memory usage: `heap_caps_get_free_size(MALLOC_CAP_DMA)` + +--- + +== Configuration + +=== PlatformIO / ESP-IDF + +USB Manager requires the following components: + +[source,ini] +---- +[env] +platform = espressif32 +framework = espidf +board = esp32-s3-devkitc-1 + +lib_deps = + espressif/esp_tinyusb@^2.1.0 +---- + +=== sdkconfig Settings + +==== DMA Memory Pool (Critical) + +[source] +---- +# Increase DMA-capable memory pool for TinyUSB MSC +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=163840 # 160 KB +---- + +**Why:** TinyUSB MSC storage allocation requires substantial DMA memory via `heap_caps_aligned_calloc()`. Default 32 KB is insufficient. + +**Calculation:** +- TinyUSB MSC buffer: ~64 KB +- WiFi/Bluetooth: ~32 KB +- Lepton VoSPI: ~32 KB +- Safety margin: ~32 KB +- Total: 160 KB + +==== USB OTG Configuration + +[source] +---- +CONFIG_TINYUSB_ENABLED=y +CONFIG_TINYUSB_MSC_ENABLED=y +CONFIG_TINYUSB_UVC_ENABLED=y +---- + +NOTE: Both MSC and UVC must be enabled in Kconfig. The active mode is selected at runtime via the UI switches. MSC and UVC are mutually exclusive — only one mode can be active at a time. + +==== PSRAM Configuration + +[source] +---- +CONFIG_SPIRAM_MODE_OCT=y # Octal SPI for 8 MB PSRAM +CONFIG_SPIRAM_SIZE=8388608 # 8 MB +---- + +=== Partition Table + +No special partition requirements. USB Manager uses existing storage partition defined in [MemoryManager](MemoryManager.adoc). + +--- + +== Advanced Topics + +=== Custom USB Descriptors + +The project uses custom USB descriptors defined in `main/Application/Manager/USB/Descriptors/descriptors.cpp`. +The composite device includes UVC (Video), CDC (Serial), and MSC (Mass Storage) interfaces. + +**Descriptor layout:** + +* Interface 0: Video Control +* Interface 1: Video Streaming (Endpoint 0x84 IN, Bulk) +* Interface 2: CDC Control (Endpoint 0x82 NOTIFICATION) +* Interface 3: CDC Data (Endpoint 0x02 OUT, 0x83 IN) +* Interface 4: MSC (Endpoint 0x01 OUT, 0x81 IN) + +**UVC Configuration:** + +* Transfer mode: Bulk (CONFIG_TINYUSB_UVC_CAM1_BULK_MODE=y) +* Resolution: 160x120 pixels (matching Lepton thermal camera) +* Frame rate: 9 FPS +* Format: MJPEG + +IMPORTANT: The resolution and frame rate in the USB descriptor must match the values used +by `USBUVC_Init()` and the actual frame data submitted via `USBUVC_SubmitFrame()`. + +=== TinyUSB Callbacks + +USB Manager does NOT override TinyUSB callbacks to avoid linker conflicts: + +[source,cpp] +---- +// Do NOT implement these in your code if using USB Manager: +void tud_mount_cb(void); // Already defined in esp_tinyusb +void tud_umount_cb(void); // Already defined in esp_tinyusb +---- + +**Polling Instead:** USB Manager uses `tud_mounted()` polling for disconnect detection. + +=== Storage Handle Lifetime + +**Critical Implementation Detail:** + +Storage handle is NOT deleted during deinitialization to prevent crashes: + +[source,cpp] +---- +// In Task_USB_Deinit(): +// DO NOT: tinyusb_msc_delete_storage(_State.Storage); +// Reason: TinyUSB callbacks may still access storage for up to 5 seconds + +// Instead: Keep storage alive, clean up on next USBManager_Init() +---- + +**Why:** TinyUSB MSC callbacks (`tud_msc_read10_cb`, `tud_msc_write10_cb`) may still be active for several seconds after disconnect, even after `tud_mounted()` returns false. + +**Cleanup Strategy:** Old storage handle is deleted in next `USBManager_Init()` call, when we know no callbacks are active. + +=== Multi-Device Support + +Current implementation supports single USB MSC device. For multiple drives: + +[source,cpp] +---- +// Not currently supported, would require: +// 1. Multiple tinyusb_msc_storage_handle_t instances +// 2. Separate LUN (Logical Unit Number) configuration +// 3. Modified TinyUSB descriptors + +// Future enhancement possibility +---- + +--- + +== Performance Considerations + +=== USB Transfer Speed + +* *SD Card*: ~10-20 MB/s read, ~5-10 MB/s write +* *Internal Flash*: ~2-5 MB/s read/write (limited by wear leveling) + +**Optimization:** +* Use SD card for large file transfers +* Format as FAT32 with 4 KB cluster size +* Use high-speed SD card (UHS-I or better) + +=== Deinitialization Time + +* *Typical*: 0.5-2 seconds +* *Maximum*: 5 seconds (timeout) + +**Factors:** +* PC operating system (Windows slower than Linux) +* Open file handles on PC +* USB bus congestion +* Number of files in filesystem + +=== Task Priority Impact + +USB deinit task priority affects GUI responsiveness: + +* *Priority 1*: Minimal GUI impact, slower deinitialization +* *Priority 3*: Moderate GUI impact, faster deinitialization +* *Priority 5+*: Significant GUI impact (not recommended) + +**Current Setting:** Priority 1 (lowest impact) + +--- + +== Security Considerations + +=== Data Exposure + +* USB MSC exposes ALL files in storage partition +* No file-level access control +* PC has full read/write access + +**Mitigation:** +* Store sensitive data in separate partition +* Encrypt sensitive files before storage +* Implement USB authorization (future enhancement) + +=== Filesystem Corruption Risk + +* Concurrent access can corrupt FAT32 filesystem +* No journaling or crash recovery in FAT32 + +**Mitigation:** +* Strict filesystem locking via Memory Manager +* Always check lock state before writing +* Regular filesystem backups (if critical data) + +--- + +== Related Documentation + +* link:MemoryManager.adoc[Memory Manager Documentation] - Storage abstraction and filesystem locking +* link:DeviceManager.adoc[Device Manager Documentation] - Hardware initialization and I2C/SPI configuration +* link:SettingsManager.adoc[Settings Manager Documentation] - Persistent settings storage + +== References + +* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/usb_device.html[ESP-IDF USB Device Driver] +* https://github.com/hathach/tinyusb[TinyUSB GitHub Repository] +* https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/usb_otg.html[ESP32-S3 USB OTG Documentation] +* https://www.usb.org/documents[USB Mass Storage Class Specification] + +--- + +**Last Updated:** February 8, 2026 + +**Maintainer:** Daniel Kampert (DanielKampert@kampis-elektroecke.de) + +**License:** GNU General Public License v3.0 diff --git a/docs/VISAServer.adoc b/docs/VISAServer.adoc new file mode 100644 index 0000000..0429b47 --- /dev/null +++ b/docs/VISAServer.adoc @@ -0,0 +1,564 @@ += PyroVision VISA/SCPI Server Documentation +Daniel Kampert +v1.0, 2026-02-09 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +The VISA Server provides a standard SCPI (Standard Commands for Programmable Instruments) interface over TCP/IP on port 5025. This allows remote control and data acquisition using standard instrumentation software like MATLAB, LabVIEW, or Python's PyVISA. + +=== Key Features + +* *Standard Port*: 5025 (VISA/SCPI standard) +* *TCP/IP Socket*: Multi-client support (up to 4 clients) +* *SCPI Commands*: Standard command syntax +* *Remote Control*: Configure camera, acquire data, save images +* *Error Reporting*: Standard SCPI error queue +* *Timeout Handling*: Automatic client disconnection on inactivity + +--- + +== Architecture + +=== Component Structure + +[source] +---- +main/Application/Manager/Network/Server/VISA/ +├── visaServer.h # Public API +├── visaServer.cpp # Server implementation +└── Private/ + ├── visaCommands.h # Command parser + └── visaCommands.cpp # Command handlers +---- + +--- + +== Public API + +=== VISAServer_Init() + +[source,c] +---- +esp_err_t VISAServer_Init(const Network_VISA_Server_Config_t *p_Config); +---- + +Initializes the VISA server with configuration. + +**Configuration:** + +[source,c] +---- +typedef struct { + uint16_t Port; // Port number (default: 5025) + uint8_t MaxClients; // Max concurrent clients (default: 4) + uint32_t TimeoutMS; // Client timeout (default: 30000 ms) +} Network_VISA_Server_Config_t; +---- + +**Return Values:** + +* `ESP_OK` - Initialization successful +* `ESP_ERR_INVALID_ARG` - Invalid configuration +* `ESP_ERR_NO_MEM` - Memory allocation failed +* `ESP_ERR_INVALID_STATE` - Already initialized + +--- + +=== VISAServer_Start() + +[source,c] +---- +esp_err_t VISAServer_Start(void); +---- + +Starts the VISA server and begins listening for client connections. + +**Return Values:** + +* `ESP_OK` - Server started successfully +* `ESP_FAIL` - Socket creation or binding failed +* `ESP_ERR_INVALID_STATE` - Not initialized + +--- + +=== VISAServer_Stop() + +[source,c] +---- +esp_err_t VISAServer_Stop(void); +---- + +Stops the VISA server and closes all client connections. + +--- + +=== VISAServer_isRunning() + +[source,c] +---- +bool VISAServer_isRunning(void); +---- + +Checks if the VISA server is running. + +--- + +== SCPI Command Reference + +=== IEEE 488.2 Common Commands + +==== `*IDN?` - Identification Query + +Returns device identification string. + +**Syntax:** +[source] +---- +*IDN? +---- + +**Response:** +[source] +---- +PyroVision,Thermal Camera,, +---- + +**Example:** +[source] +---- +VISA>> *IDN? +PyroVision,Thermal Camera,000001,1.0.0 +---- + +--- + +==== `*RST` - Reset + +Resets the device to default settings. + +**Syntax:** +[source] +---- +*RST +---- + +--- + +==== `*CLS` - Clear Status + +Clears the error queue and status registers. + +**Syntax:** +[source] +---- +*CLS +---- + +--- + +==== `*OPC?` - Operation Complete Query + +Returns `1` when all pending operations are complete. + +**Syntax:** +[source] +---- +*OPC? +---- + +**Response:** +[source] +---- +1 +---- + +--- + +=== Measurement Commands + +==== `MEASure:TEMPerature?` - Read Spotmeter Temperature + +Reads the current spotmeter temperature. + +**Syntax:** +[source] +---- +MEAS:TEMP? +---- + +**Response:** +[source] +---- + +---- + +**Example:** +[source] +---- +VISA>> MEAS:TEMP? +25.3 +---- + +--- + +==== `MEASure:TEMPerature:MINimum?` - Scene Minimum + +Reads the minimum temperature in the scene. + +**Syntax:** +[source] +---- +MEAS:TEMP:MIN? +---- + +**Response:** +[source] +---- + +---- + +--- + +==== `MEASure:TEMPerature:MAXimum?` - Scene Maximum + +Reads the maximum temperature in the scene. + +**Syntax:** +[source] +---- +MEAS:TEMP:MAX? +---- + +--- + +==== `MEASure:TEMPerature:AVERage?` - Scene Average + +Reads the average temperature in the scene. + +**Syntax:** +[source] +---- +MEAS:TEMP:AVER? +---- + +--- + +=== Configuration Commands + +==== `CONFigure:ROI` - Set ROI Position + +Sets the position and size of a Region of Interest. + +**Syntax:** +[source] +---- +CONF:ROI ,,,, +---- + +**Parameters:** + +* `` - ROI identifier (0=Spotmeter, 1=Scene, 2=AGC, 3=Video) +* ``, `` - Top-left coordinates (pixels) +* ``, `` - ROI dimensions (pixels) + +**Example:** +[source] +---- +VISA>> CONF:ROI 0,70,50,20,20 +OK +---- + +--- + +==== `CONFigure:EMISSivity` - Set Emissivity + +Sets the emissivity correction factor. + +**Syntax:** +[source] +---- +CONF:EMIS +---- + +**Parameters:** + +* `` - Emissivity (0.1 - 1.0) + +**Example:** +[source] +---- +VISA>> CONF:EMIS 0.95 +OK +---- + +--- + +==== `CONFigure:AGC` - AGC Mode + +Sets the Automatic Gain Control mode. + +**Syntax:** +[source] +---- +CONF:AGC +---- + +**Parameters:** + +* `` - AGC mode: `0` = Linear, `1` = HEQ (Histogram Equalization) + +**Example:** +[source] +---- +VISA>> CONF:AGC 1 +OK +---- + +--- + +=== Data Acquisition Commands + +==== `DATA:IMAGe?` - Get Thermal Image + +Requests the current thermal frame as binary data. + +**Syntax:** +[source] +---- +DATA:IMAG? +---- + +**Response:** + +Binary data block in IEEE 488.2 format: + +[source] +---- +# +---- + +**Example:** +[source] +---- +VISA>> DATA:IMAG? +#512345 +---- + +--- + +==== `DATA:TELEMetry?` - Get Telemetry + +Returns comprehensive telemetry data in JSON format. + +**Syntax:** +[source] +---- +DATA:TELE? +---- + +**Response:** +[source,json] +---- +{ + "spotmeter": 25.3, + "min": 20.1, + "max": 30.5, + "avg": 25.0, + "frame_count": 12345, + "error_count": 2 +} +---- + +--- + +=== System Commands + +==== `SYSTem:SAVE` - Save Settings + +Saves current settings to NVS. + +**Syntax:** +[source] +---- +SYST:SAVE +---- + +--- + +==== `SYSTem:RESTore` - Restore Factory Defaults + +Restores factory default settings. + +**Syntax:** +[source] +---- +SYST:REST +---- + +--- + +==== `SYSTem:TIME` - Set System Time + +Sets the system date and time. + +**Syntax:** +[source] +---- +SYST:TIME , +---- + +**Example:** +[source] +---- +VISA>> SYST:TIME 2026-02-09,14:30:00 +OK +---- + +--- + +==== `SYSTem:ERRor?` - Query Error Queue + +Retrieves the oldest error from the error queue. + +**Syntax:** +[source] +---- +SYST:ERR? +---- + +**Response:** +[source] +---- +,"" +---- + +**Example:** +[source] +---- +VISA>> SYST:ERR? +-200,"Execution error" +---- + +--- + +== Error Codes + +Standard SCPI error codes are returned: + +* `0` - No error +* `-100` - Command error (syntax error) +* `-200` - Execution error (cannot execute) +* `-300` - Device-specific error +* `-400` - Query error (data not available) +* `-410` - Query interrupted +* `-420` - Query unterminated + +--- + +== Usage Examples + +=== Python (PyVISA) + +[source,python] +---- +import pyvisa + +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP::192.168.1.100::5025::SOCKET') +inst.read_termination = '\n' +inst.write_termination = '\n' + +# Query identification +print(inst.query('*IDN?')) + +# Read temperature +temp = float(inst.query('MEAS:TEMP?')) +print(f"Temperature: {temp} °C") + +# Set ROI +inst.write('CONF:ROI 0,70,50,20,20') + +# Get telemetry +telemetry = inst.query('DATA:TELE?') +print(telemetry) + +inst.close() +---- + +--- + +=== MATLAB + +[source,matlab] +---- +% Create VISA-TCPIP object +t = tcpip('192.168.1.100', 5025); +fopen(t); + +% Query identification +fprintf(t, '*IDN?'); +idn = fscanf(t); +disp(idn); + +% Read temperature +fprintf(t, 'MEAS:TEMP?'); +temp = fscanf(t, '%f'); +fprintf('Temperature: %.2f °C\n', temp); + +% Close connection +fclose(t); +delete(t); +---- + +--- + +== Protocol Details + +=== Connection Flow + +[source] +---- +Client Server + | | + |-- TCP Connect (5025) -->| + |<-- Accept Connection ---| + | | + |-- Send Command -------->| + | | (Parse & Execute) + |<-- Send Response -------| + | | + |-- Close Connection ---->| +---- + +=== Command Termination + +Commands must be terminated with newline (`\n` or `\r\n`). + +=== Response Termination + +Responses are terminated with newline (`\n`). + +--- + +== Performance + +* **Max Clients**: 4 concurrent connections +* **Command Processing**: <10 ms (typical) +* **Data Transfer**: ~500 KB/s (image data) +* **Timeout**: 30 seconds idle disconnect + +--- + +== Related Documentation + +* link:NetworkTask.adoc[Network Task Documentation] +* link:NetworkManager.adoc[Network Manager Documentation] + +--- + +**Last Updated**: February 9, 2026 + +**Maintainer**: Daniel Kampert (DanielKampert@kampis-elektroecke.de) diff --git a/docs/index.adoc b/docs/index.adoc new file mode 100644 index 0000000..5a83853 --- /dev/null +++ b/docs/index.adoc @@ -0,0 +1,347 @@ += PyroVision Firmware Documentation +Daniel Kampert +v1.0, 2026-02-09 +:toc: left +:toclevels: 3 +:sectnums: +:source-highlighter: coderay + +== Overview + +PyroVision is an advanced ESP32-S3 based thermal imaging camera system featuring a FLIR Lepton 3.5 thermal sensor, touch display interface, WiFi connectivity, and comprehensive remote control capabilities. This documentation provides a complete reference for the firmware architecture, APIs, and usage. + +=== Key Features + +* *Thermal Imaging*: FLIR Lepton 3.5 (160x120, 14-bit, 8.6 Hz) +* *Display*: 320x240 ILI9341 TFT with GT911 capacitive touch +* *WiFi*: Station and Access Point modes with provisioning +* *Web Interface*: Live thermal streaming via WebSocket +* *SCPI/VISA*: Standard instrumentation protocol (port 5025) +* *Storage*: SD card for image capture and logging +* *USB*: Mass Storage Class for SD card access +* *RTOS*: FreeRTOS-based multi-tasking architecture + +--- + +== System Architecture + +=== High-Level Overview + +[source] +---- +┌─────────────────────────────────────────────────────────┐ +│ Application Layer │ +├────────────┬───────────────┬─────────────┬──────────────┤ +│ GUI Task │ Lepton Task │ Network Task│ Devices Task │ +├────────────┴───────────────┴─────────────┴──────────────┤ +│ Manager Layer │ +├──────────┬────────────┬────────────┬───────────┬────────┤ +│ Settings │ Network │ Device │ Time │ Memory │ +│ Manager │ Manager │ Manager │ Manager │ Manager│ +├──────────┴────────────┴────────────┴───────────┴────────┤ +│ Hardware Abstraction Layer │ +├──────────┬────────────┬────────────┬───────────┬────────┤ +│ SPI/I2C │ Display │ Camera │ RTC │ SD Card│ +├──────────┴────────────┴────────────┴───────────┴────────┤ +│ ESP-IDF / FreeRTOS │ +└─────────────────────────────────────────────────────────┘ +---- + +== Documentation Index + +=== Managers + +Managers provide stateful APIs for major subsystems: + +* link:SettingsManager.adoc[*Settings Manager*] - Configuration and NVS storage +* link:NetworkManager.adoc[*Network Manager*] - WiFi, servers, and connectivity +* link:DeviceManager.adoc[*Device Manager*] - Peripheral initialization (I2C, SPI, display, etc.) +* link:TimeManager.adoc[*Time Manager*] - RTC and NTP time synchronization +* link:MemoryManager.adoc[*Memory Manager*] - Heap and PSRAM management +* link:USBManager.adoc[*USB Manager*] - USB device (MSC) functionality + +=== Tasks + +FreeRTOS tasks implement the main application logic: + +* link:GUITask.adoc[*GUI Task*] - LVGL-based touch interface +* link:LeptonTask.adoc[*Lepton Task*] - Thermal camera acquisition and processing +* link:NetworkTask.adoc[*Network Task*] - Network connectivity and streaming +* link:DevicesTask.adoc[*Devices Task*] - Peripheral monitoring (battery, sensors) + +=== Network Servers + +Server implementations for remote access: + +* link:HTTPServer.adoc[*HTTP Server*] - Web interface and REST API +* link:VISAServer.adoc[*VISA Server*] - SCPI instrumentation protocol + +== Getting Started + +=== Building the Firmware + +**Prerequisites:** + +* PlatformIO (recommended) or ESP-IDF 5.5+ +* Python 3.8+ +* AStyle (for code formatting) + +**Build Commands:** + +[source,bash] +---- +# Debug build +pio run -e debug + +# Release build +pio run -e release + +# Upload to device +pio run -t upload + +# Monitor serial output +pio device monitor +---- + +== First Boot + +**Initial Configuration:** + +1. Device boots into Access Point mode (`PyroVision-XXXXXX`) +2. Connect to the AP with your phone/computer +3. Captive portal automatically opens (or navigate to `http://192.168.4.1`) +4. Scan for WiFi networks and enter credentials +5. Device connects to your WiFi network +6. Access web interface at device's IP address + +== Configuration + +=== Kconfig Options + +Key configuration options (via `menuconfig`): + +* **WiFi Settings**: Default SSID, password, max retries +* **Display Settings**: Orientation, backlight, color depth +* **Lepton Settings**: SPI frequency, AGC mode, emissivity +* **Task Priorities**: Configure FreeRTOS task priorities +* **Memory Allocation**: PSRAM usage, heap sizes + +=== Settings Structure + +Settings are organized hierarchically: + +[source] +---- +Settings_t +├── WiFi (SSID, password, auto-connect) +├── Display (brightness, orientation, timeout) +├── Lepton (ROIs, emissivity presets, AGC) +├── Network (HTTP port, VISA port, WebSocket config) +├── System (timezone, hostname, log level) +└── USB (MSC mode, vendor/product strings) +---- + +**Persistent Storage:** All settings stored in NVS (Non-Volatile Storage). + +== API Quick Reference + +=== Common Patterns + +**Manager Initialization:** + +[source,c] +---- +// Standard manager initialization pattern +esp_err_t Error = ManagerName_Init(); +if (Error != ESP_OK) { + ESP_LOGE(TAG, "Init failed: %d", Error); + return Error; +} +---- + +**Getting Settings:** + +[source,c] +---- +Settings_WiFi_t WiFiSettings; +esp_err_t Error = SettingsManager_GetWiFi(&WiFiSettings); +---- + +**Updating Settings:** + +[source,c] +---- +Settings_WiFi_t WiFiSettings = { + .SSID = "MyNetwork", + .Password = "MyPassword", + .AutoConnect = true +}; +SettingsManager_UpdateWiFi(&WiFiSettings); +SettingsManager_Save(); // Persist to NVS +---- + +**Event Subscription:** + +[source,c] +---- +esp_event_handler_register(SETTINGS_EVENTS, + SETTINGS_EVENT_WIFI_CHANGED, + &on_WiFi_Changed, NULL); +---- + +== Development Guidelines + +=== Code Style + +* **Style**: K&R with 4-space indentation +* **Line Length**: Maximum 120 characters +* **Naming**: PascalCase for functions, lowercase/PascalCase for variables +* **Documentation**: Complete Doxygen comments for all public functions +* **License**: GNU GPL v3.0 header in all files + +**Format Code:** + +[source,bash] +---- +pio run -t format +---- + +== Troubleshooting + +**Checklist:** + +1. Create module in appropriate directory (Manager/Task) +2. Define public API in header file with complete Doxygen documentation +3. Implement functionality in `.cpp` file +4. Add GPL v3 license header to all files +5. Register events if module posts events +6. Update relevant AsciiDoc documentation +7. Add configuration options to Settings if needed +8. Format code with AStyle +9. Test thoroughly (debug and release builds) + +== Troubleshooting + +=== Build Issues + +**"WebSocket support not enabled":** + +* Enable `CONFIG_HTTPD_WS_SUPPORT` in sdkconfig +* Ensure both debug and release sdkconfig have WS support + +**"Guru Meditation Error":** + +* Check stack sizes (increase if needed) +* Verify all pointers are valid before dereferencing +* Enable debug build for stack traces + +=== Runtime Issues + +**WiFi won't connect:** + +* Verify SSID and password in settings +* Check signal strength (RSSI) +* Ensure router supports 2.4 GHz (ESP32 doesn't support 5 GHz) +* Check max retry count and retry interval + +**Display not updating:** + +* Verify LVGL tick handler is running +* Check SPI bus configuration +* Ensure display backlight GPIO is correct + +**Thermal image frozen:** + +* Check Lepton Task is running +* Verify SPI communication with camera +* Perform camera FFC (Flat Field Correction) + +**WebSocket disconnects frequently:** + +* Increase ping interval +* Check WiFi stability +* Monitor memory usage (may be low) + +== Performance Optimization + +=== Memory Management + +* **PSRAM Usage**: Allocate large buffers (frame buffers, JSON parsing) in PSRAM +* **Heap Monitoring**: Use `heap_caps_get_free_size()` to monitor available memory +* **Stack Sizes**: Tune task stack sizes (too large wastes RAM, too small causes crashes) + +=== Frame Rate Optimization + +* **Camera**: Lepton 3.5 native rate is 8.6 Hz (cannot be increased) +* **Display**: Reduce LVGL animation complexity, enable DMA +* **Network**: Adjust JPEG compression quality vs. size tradeoff + +== Hardware Pinout + +=== ESP32-S3 Pin Assignment + +**Lepton Camera (SPI):** + +* CS: GPIO 10 +* MOSI: GPIO 11 +* MISO: GPIO 13 +* CLK: GPIO 12 + +**Display (SPI):** + +* CS: GPIO 15 +* DC: GPIO 21 +* RST: GPIO 48 +* Backlight: GPIO 45 + +**Touch (I2C):** + +* SDA: GPIO 18 +* SCL: GPIO 17 +* INT: GPIO 16 + +**SD Card (SPI):** + +* CS: GPIO 9 +* MOSI: GPIO 35 +* MISO: GPIO 37 +* CLK: GPIO 36 + +**RTC (I2C):** + +* SDA: GPIO 18 (shared with touch) +* SCL: GPIO 17 (shared with touch) + +== License and Copyright + +**License:** GNU General Public License v3.0 + +**Copyright:** © 2026 Daniel Kampert + +**Website:** www.kampis-elektroecke.de + +**Contact:** DanielKampert@kampis-elektroecke.de + +== Contributing + +Contributions are welcome! Please: + +1. Follow the code style guidelines +2. Add complete documentation for new APIs +3. Update AsciiDoc documentation when changing functionality +4. Test both debug and release builds +5. Format code with AStyle before committing + +== Version History + +* **v1.0.0** (2026-02-09) - Initial release + - Lepton 3.5 support + - Touch GUI with LVGL + - WiFi provisioning + - HTTP/WebSocket server + - VISA/SCPI server + - USB MSC support + - SD card image storage + +**Last Updated**: February 9, 2026 + +**Maintainer**: Daniel Kampert (DanielKampert@kampis-elektroecke.de) diff --git a/main/Application/Manager/Devices/ADC/adc.cpp b/main/Application/Manager/Devices/ADC/adc.cpp new file mode 100644 index 0000000..e0def88 --- /dev/null +++ b/main/Application/Manager/Devices/ADC/adc.cpp @@ -0,0 +1,305 @@ +/* + * adc.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: ADC driver implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "adc.h" + +#include + +#define ADC_EMA_ALPHA (CONFIG_BATTERY_ADC_EMA_ALPHA / 100.0f) + +#ifdef CONFIG_BATTERY_ADC_1 + #define ADC_UNIT ADC_UNIT_1 +#elif CONFIG_BATTERY_ADC_2 + #define ADC_UNIT ADC_UNIT_2 +#else + #error "No ADC channel configured for battery measurement" +#endif + +#ifdef CONFIG_BATTERY_ADC_ATT_0 + #define ADC_ATTEN ADC_ATTEN_DB_0 +#elif CONFIG_BATTERY_ADC_ATT_2_5 + #define ADC_ATTEN ADC_ATTEN_DB_2_5 +#elif CONFIG_BATTERY_ADC_ATT_6 + #define ADC_ATTEN ADC_ATTEN_DB_6 +#elif CONFIG_BATTERY_ADC_ATT_12 + #define ADC_ATTEN ADC_ATTEN_DB_12 +#else + #error "No ADC attenuation configured for battery measurement" +#endif + +static bool _ADC_Calib_Done = false; +static bool _ADC_Initialized = false; +static int _ADC_Last_Voltage = -1; + +static adc_channel_t _ADC_Channel = static_cast(CONFIG_BATTERY_ADC_CHANNEL); +static adc_cali_handle_t _ADC_Calib_Handle; +static adc_oneshot_unit_handle_t _ADC_Handle; +static adc_oneshot_unit_init_cfg_t _ADC_Init_Config = { + .unit_id = ADC_UNIT, + .clk_src = ADC_RTC_CLK_SRC_RC_FAST, + .ulp_mode = ADC_ULP_MODE_DISABLE, +}; + +static adc_oneshot_chan_cfg_t ADC_Config = { + .atten = static_cast(ADC_ATTEN), + .bitwidth = ADC_BITWIDTH_DEFAULT, +}; + +const char *TAG = "ADC"; + +esp_err_t ADC_Init(void) +{ + esp_err_t Error; + + if (_ADC_Initialized) { + return ESP_OK; + } + + ESP_ERROR_CHECK(adc_oneshot_new_unit(&_ADC_Init_Config, &_ADC_Handle)); + ESP_ERROR_CHECK(adc_oneshot_config_channel(_ADC_Handle, _ADC_Channel, &ADC_Config)); + + Error = ESP_OK; + _ADC_Calib_Done = false; + + if (_ADC_Calib_Done == false) { + ESP_LOGD(TAG, "calibration scheme version is %s", "Curve Fitting"); + adc_cali_curve_fitting_config_t CaliConfig = { + .unit_id = _ADC_Init_Config.unit_id, + .chan = _ADC_Channel, + .atten = ADC_Config.atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + + Error = adc_cali_create_scheme_curve_fitting(&CaliConfig, &_ADC_Calib_Handle); + if (Error == ESP_OK) { + _ADC_Calib_Done = true; + } + } + + if (Error == ESP_OK) { + ESP_LOGD(TAG, "Calibration Success"); + } else if ((Error == ESP_ERR_NOT_SUPPORTED) || (_ADC_Calib_Done == false)) { + ESP_LOGW(TAG, "eFuse not burnt, skip software calibration"); + } else { + ESP_LOGE(TAG, "Invalid arg or no memory!"); + + return ESP_FAIL; + } + + _ADC_Initialized = true; + ESP_LOGD(TAG, "ADC Initialized"); + + return ESP_OK; +} + +esp_err_t ADC_Deinit(void) +{ + if (_ADC_Initialized == false) { + return ESP_OK; + } + + ESP_ERROR_CHECK(adc_oneshot_del_unit(_ADC_Handle)); + + if (_ADC_Calib_Done) { + adc_cali_delete_scheme_curve_fitting(_ADC_Calib_Handle); + } + + _ADC_Initialized = false; + _ADC_Last_Voltage = -1; + + return ESP_OK; +} + +/** @brief Calculate median of sorted array + * @param p_Array Sorted array of integers + * @param Size Size of array + * @return Median value + */ +static int ADC_CalculateMedian(int *p_Array, size_t Size) +{ + if (Size % 2 == 0) { + return (p_Array[Size / 2 - 1] + p_Array[Size / 2]) / 2; + } else { + return p_Array[Size / 2]; + } +} + +/** @brief Compare function for qsort + */ +static int ADC_CompareInt(const void *a, const void *b) +{ + return (*static_cast(a) - *static_cast(b)); +} + +/** @brief Calculate battery percentage using improved LiPo discharge curve + * @param Voltage Battery voltage in mV + * @return Battery percentage (0-100%) + * @note Uses piecewise linear approximation of real LiPo discharge curve + */ +static uint8_t ADC_CalculateBatteryPercentage(int Voltage) +{ + /* LiPo discharge curve points (voltage, percentage) */ + /* These values provide better accuracy than linear interpolation */ + const struct { + int Voltage; + uint8_t Percentage; + } Curve[] = { + {CONFIG_BATTERY_ADC_MIN_MV, 0}, /* 3.0V = 0% (cutoff) */ + {CONFIG_BATTERY_ADC_MAX_MV - 700, 5}, /* 3.5V = 5% */ + {CONFIG_BATTERY_ADC_MAX_MV - 600, 10}, /* 3.6V = 10% */ + {CONFIG_BATTERY_ADC_MAX_MV - 500, 25}, /* 3.7V = 25% */ + {CONFIG_BATTERY_ADC_MAX_MV - 400, 50}, /* 3.8V = 50% */ + {CONFIG_BATTERY_ADC_MAX_MV - 300, 70}, /* 3.9V = 70% */ + {CONFIG_BATTERY_ADC_MAX_MV - 200, 85}, /* 4.0V = 85% */ + {CONFIG_BATTERY_ADC_MAX_MV - 100, 95}, /* 4.1V = 95% */ + {CONFIG_BATTERY_ADC_MAX_MV, 100} /* 4.2V = 100% (full) */ + }; + + /* Clamp to min/max */ + if (Voltage <= Curve[0].Voltage) { + return 0; + }else if (Voltage >= Curve[(sizeof(Curve) / sizeof(Curve[0])) - 1].Voltage) { + return 100; + } + + /* Find the two points to interpolate between */ + for (size_t i = 0; i < (sizeof(Curve) / sizeof(Curve[0])) - 1; i++) { + if ((Voltage >= Curve[i].Voltage) && (Voltage <= Curve[i + 1].Voltage)) { + /* Linear interpolation between two points */ + return Curve[i].Percentage + ((Curve[i + 1].Percentage - Curve[i].Percentage) * (Voltage - Curve[i].Voltage) / (Curve[i + 1].Voltage - Curve[i].Voltage)); + } + } + + return 0; +} + +esp_err_t ADC_ReadBattery(int *p_Voltage, uint8_t *p_Percentage) +{ + int Sum = 0; + int Count = 0; + int Raw; + int Voltages[CONFIG_BATTERY_ADC_SAMPLES]; + int ValidSamples = 0; + esp_err_t Error; + + if ((p_Voltage == NULL) || (p_Percentage == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* Initialize ADC if not already done */ + Error = ADC_Init(); + if (Error != ESP_OK) { + return Error; + } + + /* Take multiple samples for better accuracy */ + for (uint32_t i = 0; i < CONFIG_BATTERY_ADC_SAMPLES; i++) { + Error = adc_oneshot_read(_ADC_Handle, _ADC_Channel, &Raw); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "ADC read failed: %d", Error); + + continue; + } + + if (_ADC_Calib_Done) { + int Voltage; + + Error = adc_cali_raw_to_voltage(_ADC_Calib_Handle, Raw, &Voltage); + if (Error != ESP_OK) { + continue; + } + + /* Apply voltage divider correction */ + Voltages[ValidSamples] = Voltage * (CONFIG_BATTERY_ADC_R1 + CONFIG_BATTERY_ADC_R2) / CONFIG_BATTERY_ADC_R2; + } else { + Voltages[ValidSamples] = Raw; + } + + ValidSamples++; + + /* Small delay between samples */ + if (i < (CONFIG_BATTERY_ADC_SAMPLES - 1)) { + vTaskDelay(pdMS_TO_TICKS(CONFIG_BATTERY_ADC_SAMPLE_DELAY_MS)); + } + } + + if (ValidSamples == 0) { + ESP_LOGE(TAG, "No valid ADC samples obtained!"); + + return ESP_FAIL; + } + + /* Sort samples for median calculation and outlier detection */ + qsort(Voltages, ValidSamples, sizeof(int), ADC_CompareInt); + + /* Calculate median */ + int MedianVoltage = ADC_CalculateMedian(Voltages, ValidSamples); + + /* Filter outliers and calculate average of remaining samples */ + for (uint32_t i = 0; i < ValidSamples; i++) { + int Deviation; + + Deviation = Voltages[i] - MedianVoltage; + if (Deviation < 0) { + Deviation = -Deviation; + } + + /* Only include samples within threshold */ + if (Deviation <= CONFIG_BATTERY_ADC_OUTLIER_THRESHOLD) { + Sum += Voltages[i]; + Count++; + } + } + + if (Count == 0) { + /* If all samples were outliers, use median */ + *p_Voltage = MedianVoltage; + } else { + /* Use average of non-outlier samples */ + *p_Voltage = Sum / Count; + } + + /* Apply exponential moving average for smoother readings */ + if (_ADC_Last_Voltage >= 0) { + *p_Voltage = static_cast(ADC_EMA_ALPHA * (*p_Voltage) + (1.0f - ADC_EMA_ALPHA) * _ADC_Last_Voltage); + } + _ADC_Last_Voltage = *p_Voltage; + + /* Calculate battery percentage using improved discharge curve */ + *p_Percentage = ADC_CalculateBatteryPercentage(*p_Voltage); + + ESP_LOGD(TAG, "ADC%d Channel%d: %d samples, median=%d mV, filtered=%d mV (%d%%)", + ADC_UNIT, _ADC_Channel, ValidSamples, MedianVoltage, *p_Voltage, *p_Percentage); + + return ESP_OK; +} \ No newline at end of file diff --git a/main/Application/Manager/Devices/ADC/adc.h b/main/Application/Manager/Devices/ADC/adc.h new file mode 100644 index 0000000..b640f27 --- /dev/null +++ b/main/Application/Manager/Devices/ADC/adc.h @@ -0,0 +1,67 @@ +/* + * adc.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: ADC driver interface for battery voltage monitoring. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef ADC_H_ +#define ADC_H_ + +#include +#include + +#include + +/** @brief Initializes the ADC driver for battery monitoring. + * Configures ADC channel for battery voltage measurement with + * calibration from eFuse. Uses internal voltage divider. + * @note ADC is calibrated using factory eFuse values. + * Battery voltage is measured through voltage divider. + * Call this after GPIO initialization. + * @return ESP_OK on success + * ESP_ERR_NO_MEM if memory allocation fails + * ESP_FAIL if ADC initialization fails + */ +esp_err_t ADC_Init(void); + +/** @brief Deinitializes the ADC driver. + * Frees ADC resources and disables battery voltage monitoring. + * @note ADC readings become unavailable after this. + * @return ESP_OK on success + * ESP_FAIL if ADC cleanup fails + */ +esp_err_t ADC_Deinit(void); + +/** @brief Read battery voltage and calculate percentage. + * Performs ADC reading, applies calibration, and calculates remaining + * battery percentage based on voltage curve for Li-Ion batteries. + * @note Voltage range: typically 3000mV (0%) to 4200mV (100%). + * Percentage uses standard Li-Ion discharge curve. + * Multiple samples are averaged for stability. + * @param p_Voltage Pointer to store battery voltage in millivolts (mV) + * @param p_Percentage Pointer to store battery percentage (0-100%) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + * ESP_ERR_INVALID_STATE if ADC not initialized + * ESP_FAIL if ADC read fails + */ +esp_err_t ADC_ReadBattery(int *p_Voltage, uint8_t *p_Percentage); + +#endif /* ADC_H_ */ \ No newline at end of file diff --git a/main/Application/Manager/Devices/I2C/i2c.cpp b/main/Application/Manager/Devices/I2C/i2c.cpp new file mode 100644 index 0000000..3839b79 --- /dev/null +++ b/main/Application/Manager/Devices/I2C/i2c.cpp @@ -0,0 +1,213 @@ +/* + * i2c.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: I2C bus manager implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include +#include +#include +#include + +#include "i2c.h" + +#include + +#define I2C_READ_ADDR(Addr) ((Addr << 0x01) | I2C_MASTER_READ) +#define I2C_WRITE_ADDR(Addr) ((Addr << 0x01) | I2C_MASTER_WRITE) +#define I2C_WAIT pdMS_TO_TICKS(100) +#define I2C_MUTEX_TIMEOUT pdMS_TO_TICKS(2000) + +static SemaphoreHandle_t _I2C_Mutex; + +static const char *TAG = "I2C"; + +/** @brief Initialize I2C master bus. + * @param p_Config Pointer to I2C bus configuration + * @param p_Bus_Handle Pointer to store bus handle + * @return ESP_OK on success, error code otherwise + */ +int32_t I2CM_Init(i2c_master_bus_config_t *p_Config, i2c_master_bus_handle_t *p_Bus_Handle) +{ + _I2C_Mutex = xSemaphoreCreateMutex(); + if (_I2C_Mutex == NULL) { + ESP_LOGE(TAG, "Failed to create I2C mutex!"); + + return ESP_ERR_NO_MEM; + } + + return i2c_new_master_bus(p_Config, p_Bus_Handle); +} + +/** @brief Deinitialize I2C master bus. + * @param Bus_Handle I2C bus handle + * @return ESP_OK on success, error code otherwise + */ +int32_t I2CM_Deinit(i2c_master_bus_handle_t Bus_Handle) +{ + vSemaphoreDelete(_I2C_Mutex); + + return i2c_del_master_bus(Bus_Handle); +} + +/** @brief Write data to I2C device. + * @param p_Dev_Handle Pointer to device handle + * @param p_Data Pointer to data to write + * @param Length Number of bytes to write + * @return ESP_OK on success, error code otherwise + */ +int32_t I2CM_Write(i2c_master_dev_handle_t *p_Dev_Handle, const uint8_t *p_Data, uint32_t Length) +{ + esp_err_t Error; + + if ((p_Dev_Handle == NULL) || (*p_Dev_Handle == NULL) || (p_Data == NULL)) { + ESP_LOGE(TAG, "I2C Write: Invalid handle or data pointer!"); + + return ESP_ERR_INVALID_ARG; + } else if (Length == 0) { + return ESP_OK; + } + + ESP_LOGD(TAG, "Write %u bytes:", static_cast(Length)); + for (uint32_t i = 0; i < Length; i++) { + ESP_LOGD(TAG, " Byte %u: 0x%02X", static_cast(i), *(p_Data + i)); + } + + Error = ESP_FAIL; + + if (xSemaphoreTake(_I2C_Mutex, I2C_MUTEX_TIMEOUT) == pdTRUE) { + Error = i2c_master_transmit(*p_Dev_Handle, p_Data, Length, I2C_WAIT); + + xSemaphoreGive(_I2C_Mutex); + + if (Error != ESP_OK) { + ESP_LOGW(TAG, "I2C transmit failed: %d", Error); + } + } else { + ESP_LOGE(TAG, "I2C Write: Mutex timeout!"); + + return ESP_ERR_TIMEOUT; + } + + return Error; +} + +/** @brief Read data from I2C device. + * @param p_Dev_Handle Pointer to device handle + * @param p_Data Pointer to buffer for received data + * @param Length Number of bytes to read + * @return ESP_OK on success, error code otherwise + */ +int32_t I2CM_Read(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t *p_Data, uint32_t Length) +{ + esp_err_t Error; + + if ((p_Dev_Handle == NULL) || (*p_Dev_Handle == NULL) || (p_Data == NULL)) { + ESP_LOGE(TAG, "I2C Read: Invalid handle or data pointer!"); + + return ESP_ERR_INVALID_ARG; + } else if (Length == 0) { + return ESP_OK; + } + + ESP_LOGD(TAG, "Read %u bytes:", static_cast(Length)); + + for (uint32_t i = 0; i < Length; i++) { + ESP_LOGD(TAG, " Byte %u: 0x%02X", static_cast(i), *(p_Data + i)); + } + + Error = ESP_FAIL; + + if (xSemaphoreTake(_I2C_Mutex, I2C_MUTEX_TIMEOUT) == pdTRUE) { + Error = i2c_master_receive(*p_Dev_Handle, p_Data, Length, I2C_WAIT); + + xSemaphoreGive(_I2C_Mutex); + + if (Error != ESP_OK) { + ESP_LOGW(TAG, "I2C receive failed: %d!", Error); + } + } else { + ESP_LOGE(TAG, "I2C Read: Mutex timeout!"); + + return ESP_ERR_TIMEOUT; + } + + return Error; +} + +int32_t I2CM_ModifyRegister(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t Register, uint8_t Mask, uint8_t Value) +{ + esp_err_t Error; + uint8_t RegAddr = Register; + uint8_t RegValue = 0xFF; + + if ((p_Dev_Handle == NULL) || (*p_Dev_Handle == NULL)) { + ESP_LOGE(TAG, "I2C ModifyRegister: Invalid handle!"); + + return ESP_ERR_INVALID_ARG; + } + + /* Acquire mutex once for the entire read-modify-write sequence */ + if (xSemaphoreTake(_I2C_Mutex, I2C_MUTEX_TIMEOUT) == pdFALSE) { + ESP_LOGE(TAG, "I2C ModifyRegister: Mutex timeout!"); + + return ESP_ERR_TIMEOUT; + } + + /* Write register address */ + Error = i2c_master_transmit(*p_Dev_Handle, &RegAddr, 1, I2C_WAIT); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "I2C ModifyRegister: transmit failed: %d", Error); + + xSemaphoreGive(_I2C_Mutex); + + return Error; + } + + /* Read current register value */ + Error = i2c_master_receive(*p_Dev_Handle, &RegValue, 1, I2C_WAIT); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "I2C ModifyRegister: receive failed: %d", Error); + + xSemaphoreGive(_I2C_Mutex); + + return Error; + } + + /* Modify and write back */ + RegValue &= ~Mask; + RegValue |= Value; + + uint8_t Data[2] = {Register, RegValue}; + + ESP_LOGD(TAG, "Modify Register 0x%02X with mask 0x%02X: 0x%02X", Register, Mask, RegValue); + + Error = i2c_master_transmit(*p_Dev_Handle, Data, sizeof(Data), I2C_WAIT); + + xSemaphoreGive(_I2C_Mutex); + + if (Error != ESP_OK) { + ESP_LOGW(TAG, "I2C ModifyRegister: write-back failed: %d", Error); + } + + return Error; +} \ No newline at end of file diff --git a/main/Application/Manager/Devices/I2C/i2c.h b/main/Application/Manager/Devices/I2C/i2c.h new file mode 100644 index 0000000..6812076 --- /dev/null +++ b/main/Application/Manager/Devices/I2C/i2c.h @@ -0,0 +1,96 @@ +/* + * i2c.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: I2C master driver interface. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef I2C_H_ +#define I2C_H_ + +#include +#include + +#include + +#include + +/** @brief Initialize the I2C master bus. + * Creates an I2C master bus with specified configuration (GPIO pins, + * clock speed, pullups, etc.). Bus can be shared by multiple devices. + * @note Configure GPIO pullups in p_Config if not hardware-pulled. + * Typical clock speeds: 100kHz (standard), 400kHz (fast). + * @param p_Config Pointer to i2c_master_bus_config_t configuration + * @param p_Bus_Handle Pointer to store the created bus handle + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL or config invalid + * ESP_ERR_NO_MEM if bus handle allocation fails + * ESP_FAIL if bus initialization fails + */ +int32_t I2CM_Init(i2c_master_bus_config_t *p_Config, i2c_master_bus_handle_t *p_Bus_Handle); + +/** @brief Deinitialize the I2C master bus. + * Removes the I2C bus and frees all associated resources. All device + * handles on this bus must be removed first. + * @note Remove all devices with i2c_master_bus_rm_device() first. + * @warning Bus handle becomes invalid after this call. + * @param Bus_Handle I2C bus handle to deinitialize + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if devices still attached + * ESP_FAIL if bus removal fails + */ +int32_t I2CM_Deinit(i2c_master_bus_handle_t Bus_Handle); + +/** @brief Transmit data over the I2C interface. + * Sends data to an I2C device. Uses blocking transmission with timeout. + * @note Default timeout: 1000ms. + * Device must be added to bus before calling this. + * @param p_Dev_Handle Pointer to I2C device handle + * @param p_Data Pointer to data buffer to transmit + * @param Length Number of bytes to transmit + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Dev_Handle or p_Data is NULL + * ESP_ERR_INVALID_STATE if I2C bus not initialized + * ESP_ERR_TIMEOUT if transaction times out + * ESP_FAIL if transmission fails (NACK, bus error) + */ +int32_t I2CM_Write(i2c_master_dev_handle_t *p_Dev_Handle, const uint8_t *p_Data, uint32_t Length); + +/** @brief Receive data from the I2C interface. + * @param p_Dev_Handle Pointer to I2C device handle + * @param p_Data Pointer to data + * @param Length Length of data in bytes + * @return ESP_OK when successful + * ESP_ERR_INVALID_ARG when an invalid argument is passed into the function + * ESP_ERR_INVALID_STATE when the I2C interface isn´t initialized + */ +int32_t I2CM_Read(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t *p_Data, uint32_t Length); + +/** @brief Modify the content of a register. + * @param p_Dev_Handle Pointer to I2C device handle + * @param Register Register address + * @param Mask Bit mask + * @param Value Bit level + * @return ESP_OK when successful + * ESP_ERR_INVALID_ARG when an invalid argument is passed into the function + * ESP_ERR_INVALID_STATE when the I2C interface isn´t initialized + */ +int32_t I2CM_ModifyRegister(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t Register, uint8_t Mask, uint8_t Value); + +#endif /* I2C_H_ */ \ No newline at end of file diff --git a/main/Application/Manager/Devices/PCA9633DP1/pca9633dp1.cpp b/main/Application/Manager/Devices/PCA9633DP1/pca9633dp1.cpp new file mode 100644 index 0000000..0f37c93 --- /dev/null +++ b/main/Application/Manager/Devices/PCA9633DP1/pca9633dp1.cpp @@ -0,0 +1,467 @@ +/* + * pca9633dp1.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: PCA9633DP1 LED Driver implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include +#include + +#include "pca9633dp1.h" + +#include + +static const char *TAG = "PCA9633DP1"; + +/** @brief PCA9633 I2C address (ALLCALLADR pin configuration). + */ +#define PCA9633_I2C_ADDR 0x62 + +/** @brief PCA9633 register addresses. + */ +#define PCA9633_REG_MODE1 0x00 +#define PCA9633_REG_MODE2 0x01 +#define PCA9633_REG_PWM0 0x02 +#define PCA9633_REG_PWM1 0x03 +#define PCA9633_REG_PWM2 0x04 +#define PCA9633_REG_PWM3 0x05 +#define PCA9633_REG_GRPPWM 0x06 +#define PCA9633_REG_GRPFREQ 0x07 +#define PCA9633_REG_LEDOUT 0x08 +#define PCA9633_REG_SUBADR1 0x09 +#define PCA9633_REG_SUBADR2 0x0A +#define PCA9633_REG_SUBADR3 0x0B +#define PCA9633_REG_ALLCALLADR 0x0C + +/** @brief MODE1 register bit masks. + */ +#define PCA9633_MODE1_AI2 0x80 /**< Auto-Increment: All registers. */ +#define PCA9633_MODE1_AI1 0x40 /**< Auto-Increment: Individual brightness only. */ +#define PCA9633_MODE1_AI0 0x20 /**< Auto-Increment: Global control only. */ +#define PCA9633_MODE1_SLEEP 0x10 /**< Low power mode (oscillator off). */ +#define PCA9633_MODE1_SUB1 0x08 /**< Respond to subaddress 1. */ +#define PCA9633_MODE1_SUB2 0x04 /**< Respond to subaddress 2. */ +#define PCA9633_MODE1_SUB3 0x02 /**< Respond to subaddress 3. */ +#define PCA9633_MODE1_ALLCALL 0x01 /**< Respond to All Call I2C-bus address. */ + +/** @brief MODE2 register bit masks. + */ +#define PCA9633_MODE2_DMBLNK 0x20 /**< Group control: blinking (1) or dimming (0). */ +#define PCA9633_MODE2_INVRT 0x10 /**< Output logic state inverted. */ +#define PCA9633_MODE2_OCH 0x08 /**< Outputs change on ACK (0) or STOP (1). */ +#define PCA9633_MODE2_OUTDRV 0x04 /**< Output driver: totem pole (1) or open-drain (0). */ +#define PCA9633_MODE2_OUTNE1 0x02 /**< Output state when OE=1 (bit 1). */ +#define PCA9633_MODE2_OUTNE0 0x01 /**< Output state when OE=1 (bit 0). */ + +/** @brief LEDOUT register - LED driver output state. + */ +#define PCA9633_LEDOUT_OFF 0x00 /**< LED driver off. */ +#define PCA9633_LEDOUT_ON 0x01 /**< LED driver fully on (not PWM controlled). */ +#define PCA9633_LEDOUT_PWM 0x02 /**< LED driver individual PWM control. */ +#define PCA9633_LEDOUT_GRPPWM 0x03 /**< LED driver group PWM control. */ + +/** @brief Auto-Increment flag for register access. + */ +#define PCA9633_AUTO_INCREMENT 0x80 + +static i2c_device_config_t _Device_I2C_Config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = PCA9633_I2C_ADDR, + .scl_speed_hz = 400000, + .scl_wait_us = 0, + .flags = { + .disable_ack_check = 0, + }, +}; + +/** @brief Write a single register to PCA9633. + * @param p_Dev_Handle Device handle + * @param RegAddr Register address + * @param Value 8-bit value to write + * @return ESP_OK on success + */ +static esp_err_t pca9633_write_register(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t RegAddr, uint8_t Value) +{ + uint8_t Buffer[2]; + + Buffer[0] = RegAddr; + Buffer[1] = Value; + + return I2CM_Write(p_Dev_Handle, Buffer, sizeof(Buffer)); +} + +/** @brief Read a single register from PCA9633. + * @param p_Dev_Handle Device handle + * @param RegAddr Register address + * @param p_Value Pointer to store the read value + * @return ESP_OK on success + */ +static esp_err_t pca9633_read_register(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t RegAddr, uint8_t *p_Value) +{ + esp_err_t Error; + + Error = I2CM_Write(p_Dev_Handle, &RegAddr, 1); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to write register address 0x%02X: %d!", RegAddr, Error); + + return Error; + } + + Error = I2CM_Read(p_Dev_Handle, p_Value, 1); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read register 0x%02X: %d!", RegAddr, Error); + + return Error; + } + + return ESP_OK; +} + +/** @brief Write multiple registers to PCA9633 using auto-increment. + * @param p_Dev_Handle Device handle + * @param RegAddr Starting register address + * @param p_Data Pointer to data buffer + * @param Length Number of bytes to write + * @return ESP_OK on success + */ +static esp_err_t pca9633_write_registers(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t RegAddr, const uint8_t *p_Data, uint8_t Length) +{ + uint8_t Buffer[16]; + + if (Length > 15) { + ESP_LOGE(TAG, "Write length exceeds buffer size!"); + + return ESP_ERR_INVALID_ARG; + } + + /* Set auto-increment flag */ + Buffer[0] = RegAddr | PCA9633_AUTO_INCREMENT; + for (uint8_t i = 0; i < Length; i++) { + Buffer[i + 1] = p_Data[i]; + } + + return I2CM_Write(p_Dev_Handle, Buffer, Length + 1); +} + +esp_err_t PCA9633DP1_Init(i2c_master_bus_handle_t *p_Bus_Handle, i2c_master_dev_handle_t *p_Dev_Handle) +{ + esp_err_t Error; + uint8_t PWM_Values[4] = {0, 0, 0, 0}; + + if ((p_Bus_Handle == NULL) || (p_Dev_Handle == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* Add device to bus */ + Error = i2c_master_bus_add_device(*p_Bus_Handle, &_Device_I2C_Config, p_Dev_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to add I2C device: %d!", Error); + + return Error; + } + + /* Configure MODE1: Normal mode, enable All Call address */ + Error = pca9633_write_register(p_Dev_Handle, PCA9633_REG_MODE1, PCA9633_MODE1_ALLCALL); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure MODE1: %d!", Error); + + i2c_master_bus_rm_device(*p_Dev_Handle); + + return Error; + } + + /* Wait for oscillator to start (500μs typical) */ + vTaskDelay(pdMS_TO_TICKS(1)); + + /* Configure MODE2: Totem pole outputs, change on STOP command */ + Error = pca9633_write_register(p_Dev_Handle, PCA9633_REG_MODE2, PCA9633_MODE2_OUTDRV); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure MODE2: %d!", Error); + + i2c_master_bus_rm_device(*p_Dev_Handle); + + return Error; + } + + /* Set all LEDs to off state */ + Error = pca9633_write_register(p_Dev_Handle, PCA9633_REG_LEDOUT, 0x00); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to set LEDOUT: %d!", Error); + + i2c_master_bus_rm_device(*p_Dev_Handle); + + return Error; + } + + /* Set all PWM registers to 0 (LEDs off) */ + Error = pca9633_write_registers(p_Dev_Handle, PCA9633_REG_PWM0, PWM_Values, 4); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize PWM values: %d!", Error); + + i2c_master_bus_rm_device(*p_Dev_Handle); + + return Error; + } + + ESP_LOGI(TAG, "PCA9633DP1 initialized successfully"); + + return ESP_OK; +} + +esp_err_t PCA9633DP1_Deinit(i2c_master_dev_handle_t *p_Dev_Handle) +{ + esp_err_t Error; + + if (p_Dev_Handle == NULL) { + ESP_LOGE(TAG, "Invalid device handle!"); + + return ESP_ERR_INVALID_ARG; + } + + /* Turn off all LEDs before deinit */ + Error = pca9633_write_register(p_Dev_Handle, PCA9633_REG_LEDOUT, 0x00); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to turn off LEDs: %d", Error); + } + + /* Remove device from bus */ + Error = i2c_master_bus_rm_device(*p_Dev_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to remove I2C device: %d!", Error); + + return Error; + } + + ESP_LOGI(TAG, "PCA9633DP1 deinitialized"); + + return ESP_OK; +} + +esp_err_t PCA9633DP1_SetLED(i2c_master_dev_handle_t *p_Dev_Handle, PCA9633_LED_t LED, uint8_t Brightness) +{ + uint8_t RegAddr; + uint8_t LEDOUT_Value; + esp_err_t Error; + + if (p_Dev_Handle == NULL) { + ESP_LOGE(TAG, "Invalid device handle!"); + + return ESP_ERR_INVALID_ARG; + } else if (LED > PCA9633_LED3) { + ESP_LOGE(TAG, "Invalid LED index!"); + + return ESP_ERR_INVALID_ARG; + } + + /* Determine PWM register address */ + RegAddr = PCA9633_REG_PWM0 + static_cast(LED); + + /* Write brightness value (0-255) */ + Error = pca9633_write_register(p_Dev_Handle, RegAddr, Brightness); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to set LED%d brightness: %d!", LED, Error); + + return Error; + } + + /* Read current LEDOUT configuration */ + Error = pca9633_read_register(p_Dev_Handle, PCA9633_REG_LEDOUT, &LEDOUT_Value); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read LEDOUT: %d!", Error); + + return Error; + } + + /* Set LED to PWM control mode */ + LEDOUT_Value &= ~(0x03 << (LED * 2)); /* Clear bits for this LED */ + LEDOUT_Value |= (PCA9633_LEDOUT_PWM << (LED * 2)); /* Set to PWM mode */ + + Error = pca9633_write_register(p_Dev_Handle, PCA9633_REG_LEDOUT, LEDOUT_Value); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to update LEDOUT: %d!", Error); + + return Error; + } + + return ESP_OK; +} + +esp_err_t PCA9633DP1_SetAllLEDs(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t LED0, uint8_t LED1, uint8_t LED2, uint8_t LED3) +{ + uint8_t PWM_Values[4]; + uint8_t LEDOUT_Value; + esp_err_t Error; + + if (p_Dev_Handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* Prepare PWM values */ + PWM_Values[0] = LED0; + PWM_Values[1] = LED1; + PWM_Values[2] = LED2; + PWM_Values[3] = LED3; + + /* Write all PWM values using auto-increment */ + Error = pca9633_write_registers(p_Dev_Handle, PCA9633_REG_PWM0, PWM_Values, 4); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to set all LED brightness: %d!", Error); + + return Error; + } + + /* Set all LEDs to PWM control mode */ + LEDOUT_Value = (PCA9633_LEDOUT_PWM << 0) | /* LED0 */ + (PCA9633_LEDOUT_PWM << 2) | /* LED1 */ + (PCA9633_LEDOUT_PWM << 4) | /* LED2 */ + (PCA9633_LEDOUT_PWM << 6); /* LED3 */ + + Error = pca9633_write_register(p_Dev_Handle, PCA9633_REG_LEDOUT, LEDOUT_Value); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to update LEDOUT: %d!", Error); + + return Error; + } + + return ESP_OK; +} + +esp_err_t PCA9633DP1_SetLEDState(i2c_master_dev_handle_t *p_Dev_Handle, PCA9633_LED_t LED, PCA9633_LED_State_t State) +{ + uint8_t LEDOUT_Value; + esp_err_t Error; + + if (p_Dev_Handle == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (LED > PCA9633_LED3) { + return ESP_ERR_INVALID_ARG; + } + + /* Read current LEDOUT configuration */ + Error = pca9633_read_register(p_Dev_Handle, PCA9633_REG_LEDOUT, &LEDOUT_Value); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read LEDOUT: %d!", Error); + + return Error; + } + + /* Update LED state */ + LEDOUT_Value &= ~(0x03 << (LED * 2)); /* Clear bits for this LED */ + LEDOUT_Value |= (State << (LED * 2)); /* Set new state */ + + Error = pca9633_write_register(p_Dev_Handle, PCA9633_REG_LEDOUT, LEDOUT_Value); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to update LEDOUT: %d!", Error); + + return Error; + } + + return ESP_OK; +} + +esp_err_t PCA9633DP1_SetGroupControl(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t GroupPWM, uint8_t GroupFreq, bool Blinking) +{ + esp_err_t Error; + uint8_t MODE2_Value; + + if (p_Dev_Handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* Set group PWM value (0-255 for dimming, or duty cycle for blinking) */ + Error = pca9633_write_register(p_Dev_Handle, PCA9633_REG_GRPPWM, GroupPWM); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to set GRPPWM: %d!", Error); + + return Error; + } + + /* Set group frequency (only used in blinking mode) */ + Error = pca9633_write_register(p_Dev_Handle, PCA9633_REG_GRPFREQ, GroupFreq); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to set GRPFREQ: %d!", Error); + + return Error; + } + + /* Configure MODE2 for blinking or dimming */ + Error = pca9633_read_register(p_Dev_Handle, PCA9633_REG_MODE2, &MODE2_Value); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read MODE2: %d!", Error); + + return Error; + } + + if (Blinking) { + MODE2_Value |= PCA9633_MODE2_DMBLNK; + } else { + MODE2_Value &= ~PCA9633_MODE2_DMBLNK; + } + + Error = pca9633_write_register(p_Dev_Handle, PCA9633_REG_MODE2, MODE2_Value); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to update MODE2: %d!", Error); + + return Error; + } + + return ESP_OK; +} + +esp_err_t PCA9633DP1_SetSleepMode(i2c_master_dev_handle_t *p_Dev_Handle, bool Sleep) +{ + uint8_t MODE1_Value; + esp_err_t Error; + + if (p_Dev_Handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* Read current MODE1 value */ + Error = pca9633_read_register(p_Dev_Handle, PCA9633_REG_MODE1, &MODE1_Value); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read MODE1: %d!", Error); + + return Error; + } + + /* Update sleep bit */ + if (Sleep) { + MODE1_Value |= PCA9633_MODE1_SLEEP; + } else { + MODE1_Value &= ~PCA9633_MODE1_SLEEP; + } + + Error = pca9633_write_register(p_Dev_Handle, PCA9633_REG_MODE1, MODE1_Value); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to update MODE1: %d!", Error); + + return Error; + } + + /* Wait for oscillator to stabilize when waking up */ + if (Sleep == false) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + + return ESP_OK; +} \ No newline at end of file diff --git a/main/Application/Manager/Devices/PCA9633DP1/pca9633dp1.h b/main/Application/Manager/Devices/PCA9633DP1/pca9633dp1.h new file mode 100644 index 0000000..3373902 --- /dev/null +++ b/main/Application/Manager/Devices/PCA9633DP1/pca9633dp1.h @@ -0,0 +1,148 @@ +/* + * pca9633dp1.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: PCA9633DP1 LED Driver definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef PCA9633DP1_H_ +#define PCA9633DP1_H_ + +#include + +#include "../I2C/i2c.h" + +/** @brief LED channel identifier. + */ +typedef enum { + PCA9633_LED0 = 0, /**< LED channel 0. */ + PCA9633_LED1 = 1, /**< LED channel 1. */ + PCA9633_LED2 = 2, /**< LED channel 2. */ + PCA9633_LED3 = 3, /**< LED channel 3. */ +} PCA9633_LED_t; + +/** @brief LED output state. + */ +typedef enum { + PCA9633_LED_OFF = 0, /**< LED driver off (default power-up state). */ + PCA9633_LED_FULLY_ON = 1, /**< LED driver fully on (not PWM controlled). */ + PCA9633_LED_PWM = 2, /**< LED driver individual PWM + group PWM/dimming. */ + PCA9633_LED_PWM_GRPPWM = 3, /**< LED driver individual PWM + group blinking. */ +} PCA9633_LED_State_t; + +/** @brief Initializes the PCA9633DP1 LED driver. + * Creates I2C device handle and configures the PCA9633 for PWM LED control. + * Default configuration sets all LEDs to off state. + * @note I2C address: 0x62 (default for PCA9633). + * Supports 4 independent PWM channels. + * @param p_Bus_Handle Pointer to I2C bus handle + * @param p_Dev_Handle Pointer to store the created device handle + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + * ESP_ERR_NO_MEM if device handle allocation fails + * ESP_FAIL if I2C communication fails + */ +esp_err_t PCA9633DP1_Init(i2c_master_bus_handle_t *p_Bus_Handle, i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Deinitializes the PCA9633DP1 LED driver. + * Removes I2C device handle and frees resources. LEDs are left in + * their current state. + * @note Device handle becomes invalid after this call. + * @param p_Dev_Handle Pointer to device handle + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Dev_Handle is NULL + * ESP_FAIL if I2C device removal fails + */ +esp_err_t PCA9633DP1_Deinit(i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Set brightness of a single LED channel. + * Sets the PWM duty cycle for the specified LED channel and enables + * PWM control mode. + * @note LED is automatically enabled in PWM control mode. + * Brightness range: 0 (off) to 255 (full brightness). + * @param p_Dev_Handle Pointer to I2C device handle + * @param LED LED channel (PCA9633_LED0 to PCA9633_LED3) + * @param Brightness PWM duty cycle (0-255) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if device handle NULL or invalid LED + * ESP_FAIL if I2C communication fails + */ +esp_err_t PCA9633DP1_SetLED(i2c_master_dev_handle_t *p_Dev_Handle, PCA9633_LED_t LED, uint8_t Brightness); + +/** @brief Set brightness of all LED channels simultaneously. + * Sets PWM duty cycle for all 4 LED channels using auto-increment + * and enables PWM control mode for all channels. + * @note More efficient than calling PCA9633DP1_SetLED() four times. + * All LEDs are automatically enabled in PWM control mode. + * @param p_Dev_Handle Pointer to I2C device handle + * @param LED0 Brightness for LED channel 0 (0-255) + * @param LED1 Brightness for LED channel 1 (0-255) + * @param LED2 Brightness for LED channel 2 (0-255) + * @param LED3 Brightness for LED channel 3 (0-255) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if device handle is NULL + * ESP_FAIL if I2C communication fails + */ +esp_err_t PCA9633DP1_SetAllLEDs(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t LED0, uint8_t LED1, uint8_t LED2, uint8_t LED3); + +/** @brief Set output state of an LED channel. + * Controls LED driver output state: off, fully on, PWM controlled, + * or PWM with group control. + * @note Use this to turn LED fully on/off without changing PWM value. + * PWM value is preserved when switching states. + * @param p_Dev_Handle Pointer to I2C device handle + * @param LED LED channel (PCA9633_LED0 to PCA9633_LED3) + * @param State Output state (see PCA9633_LED_State_t) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if device handle NULL or invalid LED + * ESP_FAIL if I2C communication fails + */ +esp_err_t PCA9633DP1_SetLEDState(i2c_master_dev_handle_t *p_Dev_Handle, PCA9633_LED_t LED, PCA9633_LED_State_t State); + +/** @brief Configure group PWM and blinking control. + * Sets up group control for global dimming or synchronized blinking + * of all LEDs set to group control mode. + * @note Only affects LEDs set to PCA9633_LED_PWM_GRPPWM state. + * Blinking frequency = GroupFreq / 24 Hz (e.g., 255 = 0.17 Hz). + * GroupPWM defines duty cycle in dimming mode or on-time in blink mode. + * @param p_Dev_Handle Pointer to I2C device handle + * @param GroupPWM Group PWM duty cycle (0-255) + * @param GroupFreq Group frequency for blinking (0-255), only used when Blinking=true + * @param Blinking true for blinking mode, false for dimming mode + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if device handle is NULL + * ESP_FAIL if I2C communication fails + */ +esp_err_t PCA9633DP1_SetGroupControl(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t GroupPWM, uint8_t GroupFreq, bool Blinking); + +/** @brief Enable or disable sleep mode (low power mode). + * In sleep mode, the internal oscillator is turned off and PWM + * generation stops. All register contents are preserved. + * @note Sleep mode reduces power consumption significantly. + * Wake-up time: ~500μs for oscillator to stabilize. + * LED outputs are held at their last state during sleep. + * @param p_Dev_Handle Pointer to I2C device handle + * @param Sleep true to enter sleep mode, false to wake up + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if device handle is NULL + * ESP_FAIL if I2C communication fails + */ +esp_err_t PCA9633DP1_SetSleepMode(i2c_master_dev_handle_t *p_Dev_Handle, bool Sleep); + +#endif /* PCA9633DP1_H_ */ \ No newline at end of file diff --git a/main/Application/Manager/Devices/PCAL6416AHF/pcal6416ahf.cpp b/main/Application/Manager/Devices/PCAL6416AHF/pcal6416ahf.cpp new file mode 100644 index 0000000..6d9b5f5 --- /dev/null +++ b/main/Application/Manager/Devices/PCAL6416AHF/pcal6416ahf.cpp @@ -0,0 +1,261 @@ +/* + * portexpander.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: PCAL6416AHF Port Expander driver implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include +#include +#include + +#include "pcal6416ahf.h" + +#include + +#define ADDR_PCAL6416AHF 0x20 + +#define PORT_EXPANDER_REG_INPUT0 0x00 +#define PORT_EXPANDER_REG_INPUT1 0x01 +#define PORT_EXPANDER_REG_OUTPUT0 0x02 +#define PORT_EXPANDER_REG_OUTPUT1 0x03 +#define PORT_EXPANDER_REG_POL0 0x04 +#define PORT_EXPANDER_REG_POL1 0x05 +#define PORT_EXPANDER_REG_CONF0 0x06 +#define PORT_EXPANDER_REG_CONF1 0x07 +#define PORT_EXPANDER_REG_STRENGTH0_0 0x40 +#define PORT_EXPANDER_REG_STRENGTH0_1 0x41 +#define PORT_EXPANDER_REG_STRENGTH1_0 0x42 +#define PORT_EXPANDER_REG_STRENGTH1_1 0x43 +#define PORT_EXPANDER_REG_LATCH0 0x44 +#define PORT_EXPANDER_REG_LATCH1 0x45 +#define PORT_EXPANDER_REG_PULL_EN0 0x46 +#define PORT_EXPANDER_REG_PULL_EN1 0x47 +#define PORT_EXPANDER_REG_PULL_SEL0 0x48 +#define PORT_EXPANDER_REG_PULL_SEL1 0x49 +#define PORT_EXPANDER_REG_INT_MASK0 0x4A +#define PORT_EXPANDER_REG_INT_MASK1 0x4B +#define PORT_EXPANDER_REG_INT_STATUS0 0x4C +#define PORT_EXPANDER_REG_INT_STATUS1 0x4D +#define PORT_EXPANDER_REG_OUT_CONFIG 0x4F + +#define PORT_EXPANDER_INPUT 0x01 +#define PORT_EXPANDER_OUTPUT 0x00 + +/** @brief Battery voltage enable pin connected to port 0, pin 0. + */ +#define PIN_BATTERY_VOLTAGE_ENABLE 0 + +/** @brief The LDO are connected with port 0, pin 1. + */ +#define PIN_CAMERA 1 + +/** @brief The LED is connected with port 0, pin 2. + */ +#define PIN_LED 2 + +/** @brief Port expander port number declaration. + */ +typedef enum { + PORT_0 = 0x00, + PORT_1 = 0x01, +} PortDefinition_t; + +/** @brief Port expander pull option declaration. + */ +typedef enum { + PULL_PULLDOWN = 0x00, + PULL_PULLUP = 0x01, +} PullDefinition_t; + +/** @brief Port expander polarity configuration declaration. + */ +typedef enum { + POL_NORMAL = 0x00, + POL_INVERT = 0x01, +} PolDefinition_t; + +/** @brief Default input polarity inversion configuration for the Port Expander. + */ +static uint8_t DefaultPolarityConfig[] = { + // Byte 0 + PORT_EXPANDER_REG_POL0, + + // Byte 1 (Polarity 0) + 0x00, + + // Byte 2 (Polarity 1) + 0x00, +}; + +/** @brief Default input latch configuration for the Port Expander. + */ +static uint8_t DefaultLatchConfig[] = { + // Byte 0 + PORT_EXPANDER_REG_LATCH0, + + // Byte 1 (Latch 0) + 0x00, + + // Byte 2 (Latch 1) + 0x00, +}; + +/** @brief Default pull-up / pull-down configuration for the Port Expander. + */ +static uint8_t DefaultPullConfig[] = { + // Byte 0 + PORT_EXPANDER_REG_PULL_EN0, + + // Byte 1 (Enable 0) + 0x00, + + // Byte 2 (Enable 1) + 0x00, + + // Byte 3 (Select 0) + 0x00, + + // Byte 4 (Select 1) + 0x00, +}; + +/** @brief Default output pin configuration for the Port Expander. + */ +static uint8_t DefaultPortConfiguration[] = { + // Byte 0 + PORT_EXPANDER_REG_CONF0, + + // Byte 1 (Config 0) + (PORT_EXPANDER_OUTPUT << PIN_BATTERY_VOLTAGE_ENABLE) | (PORT_EXPANDER_OUTPUT << PIN_CAMERA) | + (PORT_EXPANDER_OUTPUT << PIN_LED), + + // Byte 2 (Config 1) + 0x00, +}; + +/** @brief Default output pin level for the Port Expander. + */ +static uint8_t DefaultPinConfiguration[] = { + // Byte 0 + PORT_EXPANDER_REG_OUTPUT0, + + // Byte 1 (Output 0) + // Enable camera power by default (active high for PIN_CAMERA) + // Battery voltage measurement and LED are off + (0x01 << PIN_CAMERA), + + // Byte 2 (Output 1) + 0x00, +}; + +static i2c_device_config_t _Device_I2C_Config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = ADDR_PCAL6416AHF, + .scl_speed_hz = 400000, + .scl_wait_us = 0, + .flags = { + .disable_ack_check = 0, + }, +}; + +static const char *TAG = "PortExpander"; + +/** @brief Set the pin level of the pins of a given port. + * @param p_Dev_Handle Pointer to device handle + * @param Port Target port + * @param Mask Pin mask + * @param Level Pin level + * @return ESP_OK when successful + * ESP_ERR_INVALID_ARG when an invalid argument is passed into the function + * ESP_ERR_INVALID_STATE when the I2C interface isn´t initialized + */ +static esp_err_t PortExpander_SetPinLevel(i2c_master_dev_handle_t *p_Dev_Handle, PortDefinition_t Port, uint8_t Mask, + uint8_t Level) +{ + return I2CM_ModifyRegister(p_Dev_Handle, PORT_EXPANDER_REG_OUTPUT0 + static_cast(Port), Mask, Level); +} + +/** @brief Enable the interrupts for given pins. + * @param p_Dev_Handle Pointer to device handle + * @param Port Target port + * @param Mask Pin mask + * @param EnableMask Interrupt enable mask + * @return ESP_OK when successful + * ESP_ERR_INVALID_ARG when an invalid argument is passed into the function + * ESP_ERR_INVALID_STATE when the I2C interface isn´t initialized + */ +static esp_err_t PortExpander_SetInterruptMask(i2c_master_dev_handle_t *p_Dev_Handle, PortDefinition_t Port, + uint8_t Mask, uint8_t EnableMask) +{ + return I2CM_ModifyRegister(p_Dev_Handle, PORT_EXPANDER_REG_INT_MASK0 + static_cast(Port), Mask, + ~EnableMask); +} + +esp_err_t PortExpander_Init(i2c_master_bus_handle_t *p_Bus_Handle, i2c_master_dev_handle_t *p_Dev_Handle) +{ + esp_err_t Error; + + Error = i2c_master_bus_add_device(*p_Bus_Handle, &_Device_I2C_Config, p_Dev_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to add I2C device: %d!", Error); + + return Error; + } + + ESP_LOGI(TAG, "Configure Port Expander..."); + + return I2CM_Write(p_Dev_Handle, DefaultPortConfiguration, sizeof(DefaultPortConfiguration)) || + I2CM_Write(p_Dev_Handle, DefaultPinConfiguration, sizeof(DefaultPinConfiguration)) || + I2CM_Write(p_Dev_Handle, DefaultPullConfig, sizeof(DefaultPullConfig)) || + I2CM_Write(p_Dev_Handle, DefaultLatchConfig, sizeof(DefaultLatchConfig)) || + I2CM_Write(p_Dev_Handle, DefaultPolarityConfig, sizeof(DefaultPolarityConfig)); +} + +esp_err_t PortExpander_Deinit(i2c_master_dev_handle_t *p_Dev_Handle) +{ + if (*p_Dev_Handle != NULL) { + esp_err_t Error = i2c_master_bus_rm_device(*p_Dev_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to remove I2C device: %d!", Error); + + return Error; + } + + *p_Dev_Handle = NULL; + } + + return ESP_OK; +} + +esp_err_t PortExpander_EnableCamera(i2c_master_dev_handle_t *p_Dev_Handle, bool Enable) +{ + return PortExpander_SetPinLevel(p_Dev_Handle, PORT_0, (0x01 << PIN_CAMERA), ((Enable == false ? 1 : 0) << PIN_CAMERA)); +} + +esp_err_t PortExpander_EnableLED(i2c_master_dev_handle_t *p_Dev_Handle, bool Enable) +{ + return PortExpander_SetPinLevel(p_Dev_Handle, PORT_0, (0x01 << PIN_LED), (Enable << PIN_LED)); +} + +esp_err_t PortExpander_EnableBatteryVoltage(i2c_master_dev_handle_t *p_Dev_Handle, bool Enable) +{ + return PortExpander_SetPinLevel(p_Dev_Handle, PORT_0, (0x01 << PIN_BATTERY_VOLTAGE_ENABLE), + ((Enable == false ? 1 : 0) << PIN_BATTERY_VOLTAGE_ENABLE)); +} diff --git a/main/Application/Manager/Devices/PCAL6416AHF/pcal6416ahf.h b/main/Application/Manager/Devices/PCAL6416AHF/pcal6416ahf.h new file mode 100644 index 0000000..1fba4f2 --- /dev/null +++ b/main/Application/Manager/Devices/PCAL6416AHF/pcal6416ahf.h @@ -0,0 +1,58 @@ +/* + * pcal6416ahf.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: PCAL6416AHF Port Expander driver definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef PCAL6416AHF_H_ +#define PCAL6416AHF_H_ + +#include + +#include "../I2C/i2c.h" + +/** @brief Initializes the port expander driver. + * @param p_Bus_Handle Pointer to I2C bus handle + * @param p_Dev_Handle Pointer to store the created device handle + * @return ESP_OK on success, error code otherwise + */ +esp_err_t PortExpander_Init(i2c_master_bus_handle_t *p_Bus_Handle, i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Deinitializes the port expander driver. + * @param p_Dev_Handle Pointer to device handle + * @return ESP_OK on success, error code otherwise + */ +esp_err_t PortExpander_Deinit(i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Enables or disables the LED (active high). + * @param p_Dev_Handle Pointer to device handle + * @param Enable true to enable the LED, false to disable it + * @return ESP_OK on success, error code otherwise + */ +esp_err_t PortExpander_EnableLED(i2c_master_dev_handle_t *p_Dev_Handle, bool Enable); + +/** @brief Enables or disables the battery voltage measurement (active low). + * @param p_Dev_Handle Pointer to device handle + * @param Enable true to enable battery voltage measurement, false to disable it + * @return ESP_OK on success, error code otherwise + */ +esp_err_t PortExpander_EnableBatteryVoltage(i2c_master_dev_handle_t *p_Dev_Handle, bool Enable); + +#endif /* PCAL6416AHF_H_ */ \ No newline at end of file diff --git a/main/Application/Manager/Devices/RV8263-C8/rv8263c8.cpp b/main/Application/Manager/Devices/RV8263-C8/rv8263c8.cpp new file mode 100644 index 0000000..0765c9a --- /dev/null +++ b/main/Application/Manager/Devices/RV8263-C8/rv8263c8.cpp @@ -0,0 +1,448 @@ +/* + * rv8263c8.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: RV8263-C8 Real-Time Clock driver implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include +#include +#include + +#include "rv8263c8.h" + +#include + +/* RV8263-C8 I2C Address */ +#define ADDR_RV8263C8 0x51 + +/* RV8263-C8 Register Addresses */ +#define RV8263_REG_CONTROL1 0x00 +#define RV8263_REG_CONTROL2 0x01 +#define RV8263_REG_OFFSET 0x02 +#define RV8263_REG_RAM 0x03 +#define RV8263_REG_SECONDS 0x04 +#define RV8263_REG_MINUTES 0x05 +#define RV8263_REG_HOURS 0x06 +#define RV8263_REG_DATE 0x07 +#define RV8263_REG_WEEKDAY 0x08 +#define RV8263_REG_MONTH 0x09 +#define RV8263_REG_YEAR 0x0A +#define RV8263_REG_SECONDS_ALARM 0x0B +#define RV8263_REG_MINUTES_ALARM 0x0C +#define RV8263_REG_HOURS_ALARM 0x0D +#define RV8263_REG_DATE_ALARM 0x0E +#define RV8263_REG_WEEKDAY_ALARM 0x0F +#define RV8263_REG_TIMER_VALUE 0x10 +#define RV8263_REG_TIMER_MODE 0x11 + +/* Control1 Register Bits */ +#define RV8263_CTRL1_CAP_SEL (1 << 0) /* Oscillator capacitance selection */ +#define RV8263_CTRL1_STOP (1 << 5) /* Stop RTC clock */ +#define RV8263_CTRL1_SR (1 << 7) /* Software reset */ + +/* Control2 Register Bits */ +#define RV8263_CTRL2_CLKIE (1 << 0) /* Clock output interrupt enable */ +#define RV8263_CTRL2_TIE (1 << 1) /* Timer interrupt enable */ +#define RV8263_CTRL2_AIE (1 << 2) /* Alarm interrupt enable */ +#define RV8263_CTRL2_TF (1 << 3) /* Timer flag */ +#define RV8263_CTRL2_AF (1 << 4) /* Alarm flag */ +#define RV8263_CTRL2_CTAIE (1 << 5) /* Countdown timer A interrupt enable */ +#define RV8263_CTRL2_CTBIE (1 << 6) /* Countdown timer B interrupt enable */ +#define RV8263_CTRL2_CTAF (1 << 7) /* Countdown timer A flag */ + +/* Timer Mode Register Bits */ +#define RV8263_TIMER_TE (1 << 2) /* Timer enable */ +#define RV8263_TIMER_TIE (1 << 3) /* Timer interrupt enable */ +#define RV8263_TIMER_TI_TP (1 << 4) /* Timer interrupt mode (pulse/level) */ +#define RV8263_TIMER_TD_MASK 0x03 /* Timer clock frequency mask */ +#define RV8263_TIMER_TD_4096HZ 0x00 /* 4096 Hz */ +#define RV8263_TIMER_TD_64HZ 0x01 /* 64 Hz */ +#define RV8263_TIMER_TD_1HZ 0x02 /* 1 Hz */ +#define RV8263_TIMER_TD_1_60HZ 0x03 /* 1/60 Hz */ + +/* Alarm Register Bits */ +#define RV8263_ALARM_AE (1 << 7) /* Alarm enable (0 = enabled) */ + +/* Seconds Register */ +#define RV8263_SECONDS_OS (1 << 7) /* Oscillator stop flag */ + +/* Month Register */ +#define RV8263_MONTH_CENTURY (1 << 7) /* Century bit */ + +#define MAX_RTC_REGS 64 + +static i2c_device_config_t _Device_I2C_Config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = ADDR_RV8263C8, + .scl_speed_hz = 400000, + .scl_wait_us = 0, + .flags = { + .disable_ack_check = 0, + }, +}; + +static const char *TAG = "RTC"; + +/** @brief Convert BCD to binary. + * @param BCD BCD value + * @return Binary value + */ +static uint8_t RTC_BCD2Bin(uint8_t BCD) +{ + return ((BCD >> 4) * 10) + (BCD & 0x0F); +} + +/** @brief Convert binary to BCD. + * @param Bin Binary value + * @return BCD value + */ +static uint8_t RTC_Bin2BCD(uint8_t Bin) +{ + return ((Bin / 10) << 4) | (Bin % 10); +} + +/** @brief Read a single register from the RTC. + * @param Register Register address + * @param p_Data Pointer to store the read value + * @param p_Dev_Handle Pointer to store the created device handle + * @return ESP_OK when successful + */ +static esp_err_t RTC_ReadRegister(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t Register, uint8_t *p_Data) +{ + esp_err_t Error; + + Error = I2CM_Write(p_Dev_Handle, &Register, 1); + if (Error != ESP_OK) { + return Error; + } + + return I2CM_Read(p_Dev_Handle, p_Data, 1); +} + +/** @brief Write a single register to the RTC. + * @param Register Register address + * @param Data Data to write + * @param p_Dev_Handle Pointer to store the created device handle + * @return ESP_OK when successful + */ +static esp_err_t RTC_WriteRegister(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t Register, uint8_t Data) +{ + uint8_t Buffer[2] = {Register, Data}; + + return I2CM_Write(p_Dev_Handle, Buffer, sizeof(Buffer)); +} + +/** @brief Read multiple consecutive registers from the RTC. + * @param Register Starting register address + * @param p_Data Pointer to store the read values + * @param Length Number of bytes to read + * @param p_Dev_Handle Pointer to store the created device handle + * @return ESP_OK when successful + */ +static esp_err_t RTC_ReadRegisters(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t Register, uint8_t *p_Data, + uint8_t Length) +{ + esp_err_t Error; + + Error = I2CM_Write(p_Dev_Handle, &Register, 1); + if (Error != ESP_OK) { + return Error; + } + + return I2CM_Read(p_Dev_Handle, p_Data, Length); +} + +/** @brief Write multiple consecutive registers to the RTC. + * @param Register Starting register address + * @param p_Data Pointer to data to write + * @param Length Number of bytes to write + * @param p_Dev_Handle Pointer to store the created device handle + * @return ESP_OK when successful + */ +static esp_err_t RTC_WriteRegisters(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t Register, const uint8_t *p_Data, + uint8_t Length) +{ + uint8_t Buffer[MAX_RTC_REGS + 1]; + + if (Length > MAX_RTC_REGS) { + return ESP_ERR_INVALID_SIZE; + } + + Buffer[0] = Register; + memcpy(&Buffer[1], p_Data, Length); + + return I2CM_Write(p_Dev_Handle, Buffer, Length + 1); +} + +esp_err_t RTC_Init(i2c_master_bus_handle_t *p_Bus_Handle, i2c_master_dev_handle_t *p_Dev_Handle) +{ + esp_err_t Error; + uint8_t Control1; + uint8_t Seconds; + + Error = i2c_master_bus_add_device(*p_Bus_Handle, &_Device_I2C_Config, p_Dev_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to add I2C device: %d!", Error); + + return Error; + } + + ESP_LOGD(TAG, "Initialize RV8263-C8 RTC..."); + + /* Check oscillator stop flag */ + Error = RTC_ReadRegister(p_Dev_Handle, RV8263_REG_SECONDS, &Seconds); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read seconds register: %d!", Error); + + return Error; + } + + if (Seconds & RV8263_SECONDS_OS) { + ESP_LOGW(TAG, "Oscillator was stopped - time may be invalid!"); + + /* Clear OS flag by writing seconds register */ + Error = RTC_WriteRegister(p_Dev_Handle, RV8263_REG_SECONDS, Seconds & ~RV8263_SECONDS_OS); + if (Error != ESP_OK) { + return Error; + } + } + + /* Read Control1 register */ + Error = RTC_ReadRegister(p_Dev_Handle, RV8263_REG_CONTROL1, &Control1); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read control register: %d!", Error); + + return Error; + } + + /* Ensure oscillator is running */ + if (Control1 & RV8263_CTRL1_STOP) { + ESP_LOGD(TAG, "Starting RTC oscillator..."); + + Control1 &= ~RV8263_CTRL1_STOP; + + Error = RTC_WriteRegister(p_Dev_Handle, RV8263_REG_CONTROL1, Control1); + if (Error != ESP_OK) { + return Error; + } + } + + ESP_LOGD(TAG, "RV8263-C8 RTC initialized successfully"); + + return ESP_OK; +} + +esp_err_t RTC_Deinit(i2c_master_dev_handle_t *p_Dev_Handle) +{ + if (p_Dev_Handle != NULL) { + esp_err_t Error; + + Error = i2c_master_bus_rm_device(*p_Dev_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to remove I2C device: %d!", Error); + + return Error; + } + + p_Dev_Handle = NULL; + } + + return ESP_OK; +} + +esp_err_t RTC_GetTime(i2c_master_dev_handle_t *p_Dev_Handle, struct tm *p_Time) +{ + esp_err_t Error; + uint8_t Buffer[7]; + + if ((p_Time == NULL) || (p_Dev_Handle == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* Read time registers (Seconds to Year) */ + Error = RTC_ReadRegisters(p_Dev_Handle, RV8263_REG_SECONDS, Buffer, sizeof(Buffer)); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read time: %d!", Error); + + return Error; + } + + /* Convert BCD to binary and map to struct tm format */ + p_Time->tm_sec = RTC_BCD2Bin(Buffer[0] & 0x7F); /* Seconds (0-59) */ + p_Time->tm_min = RTC_BCD2Bin(Buffer[1] & 0x7F); /* Minutes (0-59) */ + p_Time->tm_hour = RTC_BCD2Bin(Buffer[2] & 0x3F); /* Hours (0-23) */ + p_Time->tm_mday = RTC_BCD2Bin(Buffer[3] & 0x3F); /* Day of month (1-31) */ + p_Time->tm_wday = Buffer[4] & 0x07; /* Day of week (0-6) */ + p_Time->tm_mon = RTC_BCD2Bin(Buffer[5] & 0x1F) - 1; /* Month (0-11, struct tm uses 0-based) */ + p_Time->tm_year = RTC_BCD2Bin(Buffer[6]) + 100; /* Years since 1900 (2000 = 100) */ + + /* Check century bit */ + if (Buffer[5] & RV8263_MONTH_CENTURY) { + p_Time->tm_year += 100; + } + + /* Calculate day of year */ + p_Time->tm_yday = 0; + + /* Unknown DST status */ + p_Time->tm_isdst = -1; + + return ESP_OK; +} + +esp_err_t RTC_SetTime(i2c_master_dev_handle_t *p_Dev_Handle, const struct tm *p_Time) +{ + uint8_t Buffer[7]; + int Year; + int Month; + + if ((p_Time == NULL) || (p_Dev_Handle == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* Convert struct tm format to RTC values */ + Year = p_Time->tm_year + 1900; /* tm_year is years since 1900 */ + Month = p_Time->tm_mon + 1; /* tm_mon is 0-11, RTC needs 1-12 */ + + /* Validate input */ + if ((p_Time->tm_sec > 59) || (p_Time->tm_min > 59) || (p_Time->tm_hour > 23) || + (p_Time->tm_mday < 1) || (p_Time->tm_mday > 31) || (Month < 1) || (Month > 12) || + (p_Time->tm_wday > 6)) { + ESP_LOGE(TAG, "Invalid time values!"); + + return ESP_ERR_INVALID_ARG; + } + + /* Convert binary to BCD */ + Buffer[0] = RTC_Bin2BCD(p_Time->tm_sec); + Buffer[1] = RTC_Bin2BCD(p_Time->tm_min); + Buffer[2] = RTC_Bin2BCD(p_Time->tm_hour); + Buffer[3] = RTC_Bin2BCD(p_Time->tm_mday); + Buffer[4] = p_Time->tm_wday; + Buffer[5] = RTC_Bin2BCD(Month); + Buffer[6] = RTC_Bin2BCD(Year % 100); + + /* Set century bit if year >= 2100 */ + if (Year >= 2100) { + Buffer[5] |= RV8263_MONTH_CENTURY; + } + + ESP_LOGD(TAG, "Setting time: %04d-%02d-%02d %02d:%02d:%02d", + Year, Month, p_Time->tm_mday, + p_Time->tm_hour, p_Time->tm_min, p_Time->tm_sec); + + return RTC_WriteRegisters(p_Dev_Handle, RV8263_REG_SECONDS, Buffer, sizeof(Buffer)); +} + +esp_err_t RTC_SetAlarm(i2c_master_dev_handle_t *p_Dev_Handle, const RTC_Alarm_t *p_Alarm) +{ + uint8_t Buffer[5]; + + if ((p_Alarm == NULL) || (p_Dev_Handle == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* Configure alarm registers - set AE bit to 1 to disable, 0 to enable */ + Buffer[0] = p_Alarm->EnableSeconds ? RTC_Bin2BCD(p_Alarm->Seconds) : RV8263_ALARM_AE; + Buffer[1] = p_Alarm->EnableMinutes ? RTC_Bin2BCD(p_Alarm->Minutes) : RV8263_ALARM_AE; + Buffer[2] = p_Alarm->EnableHours ? RTC_Bin2BCD(p_Alarm->Hours) : RV8263_ALARM_AE; + Buffer[3] = p_Alarm->EnableDay ? RTC_Bin2BCD(p_Alarm->Day) : RV8263_ALARM_AE; + Buffer[4] = p_Alarm->EnableWeekday ? p_Alarm->Weekday : RV8263_ALARM_AE; + + return RTC_WriteRegisters(p_Dev_Handle, RV8263_REG_SECONDS_ALARM, Buffer, sizeof(Buffer)); +} + +esp_err_t RTC_EnableAlarmInterrupt(i2c_master_dev_handle_t *p_Dev_Handle, bool Enable) +{ + return I2CM_ModifyRegister(p_Dev_Handle, RV8263_REG_CONTROL2, RV8263_CTRL2_AIE, + Enable ? RV8263_CTRL2_AIE : 0); +} + +esp_err_t RTC_ClearAlarmFlag(i2c_master_dev_handle_t *p_Dev_Handle) +{ + return I2CM_ModifyRegister(p_Dev_Handle, RV8263_REG_CONTROL2, RV8263_CTRL2_AF, 0); +} + +bool RTC_IsAlarmTriggered(i2c_master_dev_handle_t *p_Dev_Handle) +{ + uint8_t Control2; + + if (RTC_ReadRegister(p_Dev_Handle, RV8263_REG_CONTROL2, &Control2) != ESP_OK) { + ESP_LOGE(TAG, "Failed to read Control2 register for alarm status!"); + + return false; + } + + return (Control2 & RV8263_CTRL2_AF) != 0; +} + +esp_err_t RTC_SetTimer(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t Value, RTC_TimerFreq_t Frequency, + bool InterruptEnable) +{ + esp_err_t Error; + uint8_t TimerMode; + + if (p_Dev_Handle == NULL) { + return ESP_ERR_INVALID_STATE; + } + + /* Set timer value */ + Error = RTC_WriteRegister(p_Dev_Handle, RV8263_REG_TIMER_VALUE, Value); + if (Error != ESP_OK) { + return Error; + } + + /* Configure timer mode */ + TimerMode = RV8263_TIMER_TE | (static_cast(Frequency) & RV8263_TIMER_TD_MASK); + if (InterruptEnable) { + TimerMode |= RV8263_TIMER_TIE; + } + + return RTC_WriteRegister(p_Dev_Handle, RV8263_REG_TIMER_MODE, TimerMode); +} + +esp_err_t RTC_StopTimer(i2c_master_dev_handle_t *p_Dev_Handle) +{ + return RTC_WriteRegister(p_Dev_Handle, RV8263_REG_TIMER_MODE, 0); +} + +esp_err_t RTC_SoftwareReset(i2c_master_dev_handle_t *p_Dev_Handle) +{ + ESP_LOGD(TAG, "Performing software reset..."); + + return RTC_WriteRegister(p_Dev_Handle, RV8263_REG_CONTROL1, RV8263_CTRL1_SR); +} + +#ifdef DEBUG +void RTC_DumpRegisters(i2c_master_dev_handle_t *p_Dev_Handle) +{ + uint8_t Data; + + ESP_LOGI(TAG, "Register dump:"); + + for (uint8_t i = 0x00; i <= 0x11; i++) { + if (RTC_ReadRegister(p_Dev_Handle, i, &Data) == ESP_OK) { + ESP_LOGI(TAG, " Register 0x%02X: 0x%02X", i, Data); + } + } +} +#endif \ No newline at end of file diff --git a/main/Application/Manager/Devices/RV8263-C8/rv8263c8.h b/main/Application/Manager/Devices/RV8263-C8/rv8263c8.h new file mode 100644 index 0000000..f641737 --- /dev/null +++ b/main/Application/Manager/Devices/RV8263-C8/rv8263c8.h @@ -0,0 +1,141 @@ +/* + * rv8263c8.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: RV8263-C8 Real-Time Clock driver definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef RV8263C8_H_ +#define RV8263C8_H_ + +#include + +#include +#include +#include + +#include "../I2C/i2c.h" + +/** @brief Alarm configuration structure. + */ +typedef struct { + uint8_t Seconds; /**< Alarm seconds (0-59) */ + uint8_t Minutes; /**< Alarm minutes (0-59) */ + uint8_t Hours; /**< Alarm hours (0-23) */ + uint8_t Day; /**< Alarm day of month (1-31) */ + uint8_t Weekday; /**< Alarm weekday (0-6) */ + bool EnableSeconds; /**< Enable seconds matching */ + bool EnableMinutes; /**< Enable minutes matching */ + bool EnableHours; /**< Enable hours matching */ + bool EnableDay; /**< Enable day matching */ + bool EnableWeekday; /**< Enable weekday matching */ +} RTC_Alarm_t; + +/** @brief Timer frequency options. + */ +typedef enum { + RTC_TIMER_FREQ_4096HZ = 0, /**< 4096 Hz (244 µs resolution) */ + RTC_TIMER_FREQ_64HZ = 1, /**< 64 Hz (15.625 ms resolution) */ + RTC_TIMER_FREQ_1HZ = 2, /**< 1 Hz (1 second resolution) */ + RTC_TIMER_FREQ_1_60HZ = 3, /**< 1/60 Hz (1 minute resolution) */ +} RTC_TimerFreq_t; + +/** @brief Initialize the RV8263-C8 RTC. + * @param p_Bus_Handle Pointer to I2C bus handle + * @param p_Dev_Handle Pointer to store the created device handle + * @return ESP_OK when successful + */ +esp_err_t RTC_Init(i2c_master_bus_handle_t *p_Bus_Handle, i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Deinitialize the RTC driver. + * @param p_Dev_Handle Pointer to device handle + * @return ESP_OK when successful + */ +esp_err_t RTC_Deinit(i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Get the current time from the RTC. + * @param p_Dev_Handle Pointer to device handle + * @param p_Time Pointer to store the time + * @return ESP_OK when successful + */ +esp_err_t RTC_GetTime(i2c_master_dev_handle_t *p_Dev_Handle, struct tm *p_Time); + +/** @brief Set the time on the RTC. + * @param p_Dev_Handle Pointer to device handle + * @param p_Time Pointer to the time to set + * @return ESP_OK when successful + */ +esp_err_t RTC_SetTime(i2c_master_dev_handle_t *p_Dev_Handle, const struct tm *p_Time); + +/** @brief Configure the alarm. + * @param p_Dev_Handle Pointer to device handle + * @param p_Alarm Pointer to alarm configuration + * @return ESP_OK when successful + */ +esp_err_t RTC_SetAlarm(i2c_master_dev_handle_t *p_Dev_Handle, const RTC_Alarm_t *p_Alarm); + +/** @brief Enable or disable the alarm interrupt. + * @param p_Dev_Handle Pointer to device handle + * @param Enable true to enable, false to disable + * @return ESP_OK when successful + */ +esp_err_t RTC_EnableAlarmInterrupt(i2c_master_dev_handle_t *p_Dev_Handle, bool Enable); + +/** @brief Clear the alarm flag. + * @param p_Dev_Handle Pointer to device handle + * @return ESP_OK when successful + */ +esp_err_t RTC_ClearAlarmFlag(i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Check if the alarm has been triggered. + * @param p_Dev_Handle Pointer to device handle + * @return true if alarm is triggered, false otherwise + */ +bool RTC_IsAlarmTriggered(i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Configure and start the countdown timer. + * @param p_Dev_Handle Pointer to device handle + * @param Value Timer countdown value (0-255) + * @param Frequency Timer clock frequency + * @param InterruptEnable Enable timer interrupt + * @return ESP_OK when successful + */ +esp_err_t RTC_SetTimer(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t Value, RTC_TimerFreq_t Frequency, + bool InterruptEnable); + +/** @brief Stop the countdown timer. + * @param p_Dev_Handle Pointer to device handle + * @return ESP_OK when successful + */ +esp_err_t RTC_StopTimer(i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Perform a software reset of the RTC. + * @param p_Dev_Handle Pointer to device handle + * @return ESP_OK when successful + */ +esp_err_t RTC_SoftwareReset(i2c_master_dev_handle_t *p_Dev_Handle); + +#ifdef DEBUG +/** @brief Dump all RTC registers for debugging. + * @param p_Dev_Handle Pointer to device handle + */ +void RTC_DumpRegisters(i2c_master_dev_handle_t *p_Dev_Handle); +#endif + +#endif /* RV8263C8_H_ */ \ No newline at end of file diff --git a/main/Application/Manager/Devices/SPI/spi.cpp b/main/Application/Manager/Devices/SPI/spi.cpp new file mode 100644 index 0000000..d1cd441 --- /dev/null +++ b/main/Application/Manager/Devices/SPI/spi.cpp @@ -0,0 +1,231 @@ +/* + * spi.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Central SPI bus manager for shared SPI peripherals. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include +#include +#include + +#include "spi.h" + +#include + +typedef struct { + bool isInitialized; + SemaphoreHandle_t Mutex; + uint32_t DeviceCount; +} SPI_Bus_State_t; + +static SPI_Bus_State_t _SPI_State[SOC_SPI_PERIPH_NUM]; + +static const char *TAG = "spi-manager"; + +/** @brief Initialize SPI bus. + * @param p_Config Pointer to SPI bus configuration + * @param Host SPI host device + * @param DMA_Channel DMA channel to use + * @return ESP_OK on success, error code otherwise + */ +esp_err_t SPIM_Init(const spi_bus_config_t *p_Config, spi_host_device_t Host, int DMA_Channel) +{ + esp_err_t Error; + + if ((p_Config == NULL) || (Host >= SOC_SPI_PERIPH_NUM)) { + return ESP_ERR_INVALID_ARG; + } + + /* Check if already initialized */ + if (_SPI_State[Host].isInitialized) { + ESP_LOGW(TAG, "SPI%d already initialized", Host + 1); + + return ESP_ERR_INVALID_STATE; + } + + /* Create mutex for bus access */ + _SPI_State[Host].Mutex = xSemaphoreCreateMutex(); + if (_SPI_State[Host].Mutex == NULL) { + ESP_LOGE(TAG, "Failed to create SPI%d mutex!", Host + 1); + + return ESP_ERR_NO_MEM; + } + + /* Initialize the SPI bus */ + Error = spi_bus_initialize(Host, p_Config, DMA_Channel); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize SPI%d bus: %d", Host + 1, Error); + + vSemaphoreDelete(_SPI_State[Host].Mutex); + _SPI_State[Host].Mutex = NULL; + + return Error; + } + + _SPI_State[Host].isInitialized = true; + _SPI_State[Host].DeviceCount = 0; + + ESP_LOGD(TAG, "SPI%d bus initialized successfully", Host + 1); + + return ESP_OK; +} + +/** @brief Deinitialize SPI bus. + * @param Host SPI host device + * @return ESP_OK on success, error code otherwise + */ +esp_err_t SPIM_Deinit(spi_host_device_t Host) +{ + esp_err_t Error; + + if (Host >= SOC_SPI_PERIPH_NUM) { + return ESP_ERR_INVALID_ARG; + } else if (_SPI_State[Host].isInitialized == false) { + return ESP_OK; + } else if (_SPI_State[Host].DeviceCount > 0) { + ESP_LOGW(TAG, "SPI%d still has %d devices attached", Host + 1, _SPI_State[Host].DeviceCount); + } + + xSemaphoreTake(_SPI_State[Host].Mutex, portMAX_DELAY); + Error = spi_bus_free(Host); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to free SPI%d bus: %d!", Host + 1, Error); + xSemaphoreGive(_SPI_State[Host].Mutex); + return Error; + } + xSemaphoreGive(_SPI_State[Host].Mutex); + + if (_SPI_State[Host].Mutex != NULL) { + vSemaphoreDelete(_SPI_State[Host].Mutex); + _SPI_State[Host].Mutex = NULL; + } + + _SPI_State[Host].isInitialized = false; + _SPI_State[Host].DeviceCount = 0; + + ESP_LOGD(TAG, "SPI%d bus deinitialized", Host + 1); + + return ESP_OK; +} + +esp_err_t SPIM_AddDevice(spi_host_device_t Host, const spi_device_interface_config_t *p_Dev_Config, + spi_device_handle_t *p_Handle) +{ + esp_err_t Error; + + if ((p_Dev_Config == NULL) || (p_Handle == NULL) || (Host >= SOC_SPI_PERIPH_NUM)) { + return ESP_ERR_INVALID_ARG; + } else if (Host >= SOC_SPI_PERIPH_NUM) { + ESP_LOGE(TAG, "Invalid SPI host: %d", Host); + + return ESP_ERR_INVALID_ARG; + } else if (_SPI_State[Host].isInitialized == false) { + ESP_LOGE(TAG, "SPI%d bus not initialized!", Host + 1); + + return ESP_ERR_INVALID_STATE; + } + + xSemaphoreTake(_SPI_State[Host].Mutex, portMAX_DELAY); + Error = spi_bus_add_device(Host, p_Dev_Config, p_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to add device to SPI%d: %d!", Host + 1, Error); + + xSemaphoreGive(_SPI_State[Host].Mutex); + + return Error; + } + + _SPI_State[Host].DeviceCount++; + xSemaphoreGive(_SPI_State[Host].Mutex); + + ESP_LOGD(TAG, "Device added to SPI%d (total: %d devices)", Host + 1, _SPI_State[Host].DeviceCount); + + return ESP_OK; +} + +esp_err_t SPIM_RemoveDevice(spi_host_device_t Host, spi_device_handle_t Handle) +{ + esp_err_t Error; + + if ((Handle == NULL) || (Host >= SOC_SPI_PERIPH_NUM)) { + return ESP_ERR_INVALID_ARG; + } + + xSemaphoreTake(_SPI_State[Host].Mutex, portMAX_DELAY); + Error = spi_bus_remove_device(Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to remove SPI device: %d!", Error); + + xSemaphoreGive(_SPI_State[Host].Mutex); + + return Error; + } + + if (_SPI_State[Host].DeviceCount > 0) { + _SPI_State[Host].DeviceCount--; + } + + xSemaphoreGive(_SPI_State[Host].Mutex); + + ESP_LOGD(TAG, "SPI device removed"); + + return ESP_OK; +} + +bool SPIM_IsInitialized(spi_host_device_t Host) +{ + if (Host >= SOC_SPI_PERIPH_NUM) { + return false; + } + + return _SPI_State[Host].isInitialized; +} + +esp_err_t SPIM_Transmit(spi_host_device_t Host, spi_device_handle_t Handle, uint8_t *p_Tx_Data, uint8_t *p_Rx_Data, + size_t Length) +{ + esp_err_t Error; + spi_transaction_t trans; + + if ((Handle == NULL) || (Host >= SOC_SPI_PERIPH_NUM)) { + return ESP_ERR_INVALID_ARG; + } + + memset(&trans, 0, sizeof(trans)); + trans.tx_buffer = p_Tx_Data; + trans.rx_buffer = p_Rx_Data; + trans.length = Length * 8; + + xSemaphoreTake(_SPI_State[Host].Mutex, portMAX_DELAY); + Error = spi_device_polling_transmit(Handle, &trans); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "SPI transmit failed: %d!", Error); + + xSemaphoreGive(_SPI_State[Host].Mutex); + + return Error; + } + + xSemaphoreGive(_SPI_State[Host].Mutex); + + return ESP_OK; +} \ No newline at end of file diff --git a/main/Application/Manager/Devices/SPI/spi.h b/main/Application/Manager/Devices/SPI/spi.h new file mode 100644 index 0000000..f241b20 --- /dev/null +++ b/main/Application/Manager/Devices/SPI/spi.h @@ -0,0 +1,83 @@ +/* + * spi.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Central SPI bus manager for shared SPI peripherals. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef SPI_H_ +#define SPI_H_ + +#include +#include + +#include +#include + +#include + +/** @brief Initialize the SPI bus. + * @param p_Config Pointer to SPI bus configuration + * @param Host SPI host device (SPI2_HOST or SPI3_HOST) + * @param DMA_Channel DMA channel (SPI_DMA_CH_AUTO recommended) + * @return ESP_OK when successful + * ESP_ERR_INVALID_STATE if bus already initialized + */ +esp_err_t SPIM_Init(const spi_bus_config_t *p_Config, spi_host_device_t Host, int DMA_Channel); + +/** @brief Deinitialize the SPI bus. + * @param Host SPI host device to deinitialize + * @return ESP_OK when successful + */ +esp_err_t SPIM_Deinit(spi_host_device_t Host); + +/** @brief Add a device to the SPI bus. + * @param Host SPI host device + * @param p_Dev_Config Device configuration + * @param p_Handle Pointer to store device handle + * @return ESP_OK when successful + */ +esp_err_t SPIM_AddDevice(spi_host_device_t Host, const spi_device_interface_config_t *p_Dev_Config, + spi_device_handle_t *p_Handle); + +/** @brief Remove a device from the SPI bus. + * @param Host SPI host device + * @param Handle Device handle to remove + * @return ESP_OK when successful + */ +esp_err_t SPIM_RemoveDevice(spi_host_device_t Host, spi_device_handle_t Handle); + +/** @brief Check if SPI host is initialized. + * @param Host SPI host device + * @return true if bus is initialized, false otherwise + */ +bool SPIM_IsInitialized(spi_host_device_t Host); + +/** @brief Transmit data over SPI. + * @param Host SPI host device + * @param Handle Device handle + * @param p_Tx_Data Pointer to data to transmit + * @param p_Rx_Data Pointer to buffer for received data (or NULL if not needed) + * @param Length Length of data to transmit/receive in bytes + * @return ESP_OK when successful + */ +esp_err_t SPIM_Transmit(spi_host_device_t Host, spi_device_handle_t Handle, uint8_t *p_Tx_Data, uint8_t *p_Rx_Data, + size_t Length); + +#endif /* SPI_H_ */ diff --git a/main/Application/Manager/Devices/TMP117/tmp117.cpp b/main/Application/Manager/Devices/TMP117/tmp117.cpp new file mode 100644 index 0000000..f2b42aa --- /dev/null +++ b/main/Application/Manager/Devices/TMP117/tmp117.cpp @@ -0,0 +1,384 @@ +/* + * tmp117.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: TMP117 Temperature Sensor driver implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include +#include + +#include "tmp117.h" + +#include + +static const char *TAG = "TMP117"; + +/** @brief TMP117 I2C address (ADD0 pin connected to GND). + */ +#define TMP117_I2C_ADDR 0x48 + +/** @brief TMP117 register addresses. + */ +#define TMP117_REG_TEMP_RESULT 0x00 +#define TMP117_REG_CONFIGURATION 0x01 +#define TMP117_REG_T_HIGH_LIMIT 0x02 +#define TMP117_REG_T_LOW_LIMIT 0x03 +#define TMP117_REG_EEPROM_UL 0x04 +#define TMP117_REG_EEPROM1 0x05 +#define TMP117_REG_EEPROM2 0x06 +#define TMP117_REG_TEMP_OFFSET 0x07 +#define TMP117_REG_EEPROM3 0x08 +#define TMP117_REG_DEVICE_ID 0x0F + +/** @brief Configuration register bit masks. + */ +#define TMP117_CFG_MOD_MASK 0x0C00 +#define TMP117_CFG_MOD_SHIFT 10 +#define TMP117_CFG_CONV_MASK 0x01C0 +#define TMP117_CFG_CONV_SHIFT 7 +#define TMP117_CFG_AVG_MASK 0x0060 +#define TMP117_CFG_AVG_SHIFT 5 +#define TMP117_CFG_DATA_READY 0x2000 +#define TMP117_CFG_SOFT_RESET 0x0002 + +/** @brief TMP117 device ID expected value. + */ +#define TMP117_DEVICE_ID 0x0117 + +/** @brief Temperature resolution in °C per LSB. + */ +#define TMP117_RESOLUTION 0.0078125f + +static i2c_device_config_t _Device_I2C_Config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = TMP117_I2C_ADDR, + .scl_speed_hz = 400000, + .scl_wait_us = 0, + .flags = { + .disable_ack_check = 0, + }, +}; + +/** @brief Write a 16-bit register to TMP117. + * @param p_Dev_Handle Device handle + * @param RegAddr Register address + * @param Value 16-bit value to write + * @return ESP_OK on success + */ +static esp_err_t TMP117_Write_Register(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t RegAddr, uint16_t Value) +{ + uint8_t Buffer[3]; + + Buffer[0] = RegAddr; + Buffer[1] = static_cast((Value >> 8) & 0xFF); + Buffer[2] = static_cast(Value & 0xFF); + + return I2CM_Write(p_Dev_Handle, Buffer, sizeof(Buffer)); +} + +/** @brief Read a 16-bit register from TMP117. + * @param p_Dev_Handle Device handle + * @param RegAddr Register address + * @param p_Value Pointer to store the read value + * @return ESP_OK on success + */ +static esp_err_t TMP117_Read_Register(i2c_master_dev_handle_t *p_Dev_Handle, uint8_t RegAddr, uint16_t *p_Value) +{ + uint8_t Buffer[2]; + esp_err_t Error; + + Error = I2CM_Write(p_Dev_Handle, &RegAddr, 1); + if (Error != ESP_OK) { + return Error; + } + + Error = I2CM_Read(p_Dev_Handle, Buffer, sizeof(Buffer)); + if (Error != ESP_OK) { + return Error; + } + + *p_Value = (static_cast(Buffer[0]) << 8) | static_cast(Buffer[1]); + + return ESP_OK; +} + +esp_err_t TMP117_Init(i2c_master_bus_handle_t *p_Bus_Handle, i2c_master_dev_handle_t *p_Dev_Handle) +{ + esp_err_t Error; + uint16_t DeviceID; + + if ((p_Bus_Handle == NULL) || (p_Dev_Handle == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + Error = i2c_master_bus_add_device(*p_Bus_Handle, &_Device_I2C_Config, p_Dev_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to add I2C device: %d!", Error); + + return Error; + } + + /* Verify device ID */ + Error = TMP117_ReadDeviceID(p_Dev_Handle, &DeviceID); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read device ID: %d!", Error); + + i2c_master_bus_rm_device(*p_Dev_Handle); + + return Error; + } + + if (DeviceID != TMP117_DEVICE_ID) { + ESP_LOGE(TAG, "Invalid device ID: 0x%04X (expected 0x%04X)!", DeviceID, TMP117_DEVICE_ID); + + i2c_master_bus_rm_device(*p_Dev_Handle); + + return ESP_ERR_NOT_FOUND; + } + + ESP_LOGI(TAG, "TMP117 initialized successfully (ID: 0x%04X)", DeviceID); + + /* Configure default settings: Continuous mode, 1 second cycle, no averaging */ + TMP117_Config_t DefaultConfig = { + .Mode = TMP117_MODE_CONTINUOUS, + .Cycle = TMP117_CYCLE_1S, + .Averaging = TMP117_AVG_NONE, + }; + + Error = TMP117_Configure(p_Dev_Handle, &DefaultConfig); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure default settings: %d!", Error); + + i2c_master_bus_rm_device(*p_Dev_Handle); + + return Error; + } + + return ESP_OK; +} + +esp_err_t TMP117_Deinit(i2c_master_dev_handle_t *p_Dev_Handle) +{ + esp_err_t Error; + + if ((p_Dev_Handle == NULL) || (*p_Dev_Handle == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + Error = i2c_master_bus_rm_device(*p_Dev_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to remove I2C device: %d!", Error); + + return Error; + } + + ESP_LOGI(TAG, "TMP117 deinitialized"); + + return ESP_OK; +} + +esp_err_t TMP117_Configure(i2c_master_dev_handle_t *p_Dev_Handle, const TMP117_Config_t *p_Config) +{ + uint16_t ConfigReg; + esp_err_t Error; + + if ((p_Dev_Handle == NULL) || (p_Config == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* Read current configuration */ + Error = TMP117_Read_Register(p_Dev_Handle, TMP117_REG_CONFIGURATION, &ConfigReg); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read configuration register: %d!", Error); + + return Error; + } + + /* Clear mode, conversion cycle, and averaging bits */ + ConfigReg &= ~(TMP117_CFG_MOD_MASK | TMP117_CFG_CONV_MASK | TMP117_CFG_AVG_MASK); + + /* Set new configuration */ + ConfigReg |= ((static_cast(p_Config->Mode) << TMP117_CFG_MOD_SHIFT) & TMP117_CFG_MOD_MASK); + ConfigReg |= ((static_cast(p_Config->Cycle) << TMP117_CFG_CONV_SHIFT) & TMP117_CFG_CONV_MASK); + ConfigReg |= ((static_cast(p_Config->Averaging) << TMP117_CFG_AVG_SHIFT) & TMP117_CFG_AVG_MASK); + + /* Write configuration */ + Error = TMP117_Write_Register(p_Dev_Handle, TMP117_REG_CONFIGURATION, ConfigReg); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to write configuration register: %d!", Error); + + return Error; + } + + ESP_LOGD(TAG, "TMP117 configured: Mode=%d, Cycle=%d, Avg=%d", + static_cast(p_Config->Mode), static_cast(p_Config->Cycle), static_cast(p_Config->Averaging)); + + return ESP_OK; +} + +esp_err_t TMP117_ReadTemperature(i2c_master_dev_handle_t *p_Dev_Handle, float *p_Temp) +{ + uint16_t TempRaw; + int16_t TempSigned; + esp_err_t Error; + + if ((p_Dev_Handle == NULL) || (p_Temp == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* Read temperature register */ + Error = TMP117_Read_Register(p_Dev_Handle, TMP117_REG_TEMP_RESULT, &TempRaw); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read temperature register: %d!", Error); + + return Error; + } + + /* Convert to signed 16-bit value */ + TempSigned = static_cast(TempRaw); + + /* Apply resolution (0.0078125°C per LSB) */ + *p_Temp = static_cast(TempSigned) * TMP117_RESOLUTION; + + ESP_LOGD(TAG, "Temperature: %.4f°C (Raw: 0x%04X)", *p_Temp, TempRaw); + + return ESP_OK; +} + +esp_err_t TMP117_TriggerOneShot(i2c_master_dev_handle_t *p_Dev_Handle) +{ + uint16_t ConfigReg; + esp_err_t Error; + uint8_t CurrentMode; + + if (p_Dev_Handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* Read current configuration */ + Error = TMP117_Read_Register(p_Dev_Handle, TMP117_REG_CONFIGURATION, &ConfigReg); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read configuration register: %d!", Error); + + return Error; + } + + /* Check if in one-shot mode */ + CurrentMode = static_cast((ConfigReg & TMP117_CFG_MOD_MASK) >> TMP117_CFG_MOD_SHIFT); + if (CurrentMode != TMP117_MODE_ONE_SHOT) { + ESP_LOGE(TAG, "Device not in one-shot mode (current mode: %d)!", CurrentMode); + + return ESP_ERR_INVALID_STATE; + } + + /* Trigger one-shot conversion by setting mode bits back to one-shot */ + ConfigReg &= ~TMP117_CFG_MOD_MASK; + ConfigReg |= (static_cast(TMP117_MODE_ONE_SHOT) << TMP117_CFG_MOD_SHIFT) & TMP117_CFG_MOD_MASK; + + Error = TMP117_Write_Register(p_Dev_Handle, TMP117_REG_CONFIGURATION, ConfigReg); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to trigger one-shot conversion: %d!", Error); + + return Error; + } + + ESP_LOGD(TAG, "One-shot conversion triggered"); + + return ESP_OK; +} + +esp_err_t TMP117_IsDataReady(i2c_master_dev_handle_t *p_Dev_Handle, bool *p_Ready) +{ + uint16_t ConfigReg; + esp_err_t Error; + + if ((p_Dev_Handle == NULL) || (p_Ready == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* Read configuration register */ + Error = TMP117_Read_Register(p_Dev_Handle, TMP117_REG_CONFIGURATION, &ConfigReg); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read configuration register: %d!", Error); + + return Error; + } + + /* Check Data_Ready flag */ + *p_Ready = (ConfigReg & TMP117_CFG_DATA_READY) != 0; + + return ESP_OK; +} + +esp_err_t TMP117_ReadDeviceID(i2c_master_dev_handle_t *p_Dev_Handle, uint16_t *p_DeviceID) +{ + esp_err_t Error; + + if ((p_Dev_Handle == NULL) || (p_DeviceID == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* Read device ID register */ + Error = TMP117_Read_Register(p_Dev_Handle, TMP117_REG_DEVICE_ID, p_DeviceID); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read device ID register: %d!", Error); + + return Error; + } + + return ESP_OK; +} + +esp_err_t TMP117_SoftReset(i2c_master_dev_handle_t *p_Dev_Handle) +{ + uint16_t ConfigReg; + esp_err_t Error; + + if (p_Dev_Handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* Read current configuration */ + Error = TMP117_Read_Register(p_Dev_Handle, TMP117_REG_CONFIGURATION, &ConfigReg); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read configuration register: %d!", Error); + + return Error; + } + + /* Set soft reset bit */ + ConfigReg |= TMP117_CFG_SOFT_RESET; + + Error = TMP117_Write_Register(p_Dev_Handle, TMP117_REG_CONFIGURATION, ConfigReg); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to write soft reset: %d!", Error); + + return Error; + } + + /* Wait for reset to complete (minimum 2ms) */ + vTaskDelay(pdMS_TO_TICKS(5)); + + ESP_LOGI(TAG, "TMP117 soft reset completed"); + + return ESP_OK; +} \ No newline at end of file diff --git a/main/Application/Manager/Devices/TMP117/tmp117.h b/main/Application/Manager/Devices/TMP117/tmp117.h new file mode 100644 index 0000000..17f16e9 --- /dev/null +++ b/main/Application/Manager/Devices/TMP117/tmp117.h @@ -0,0 +1,170 @@ +/* + * tmp117.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: TMP117 Temperature Sensor driver definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef TMP117_H_ +#define TMP117_H_ + +#include + +#include +#include + +#include "../I2C/i2c.h" + +/** @brief TMP117 conversion mode options. + */ +typedef enum { + TMP117_MODE_CONTINUOUS = 0x00, /**< Continuous conversion mode */ + TMP117_MODE_SHUTDOWN = 0x01, /**< Shutdown mode (low power) */ + TMP117_MODE_ONE_SHOT = 0x03, /**< One-shot conversion mode */ +} TMP117_ConversionMode_t; + +/** @brief TMP117 conversion cycle time options. + */ +typedef enum { + TMP117_CYCLE_15_5MS = 0x00, /**< 15.5 ms conversion cycle */ + TMP117_CYCLE_125MS = 0x01, /**< 125 ms conversion cycle */ + TMP117_CYCLE_250MS = 0x02, /**< 250 ms conversion cycle */ + TMP117_CYCLE_500MS = 0x03, /**< 500 ms conversion cycle */ + TMP117_CYCLE_1S = 0x04, /**< 1 second conversion cycle */ + TMP117_CYCLE_4S = 0x05, /**< 4 seconds conversion cycle */ + TMP117_CYCLE_8S = 0x06, /**< 8 seconds conversion cycle */ + TMP117_CYCLE_16S = 0x07, /**< 16 seconds conversion cycle */ +} TMP117_ConversionCycle_t; + +/** @brief TMP117 averaging mode options. + */ +typedef enum { + TMP117_AVG_NONE = 0x00, /**< No averaging (1 conversion) */ + TMP117_AVG_8 = 0x01, /**< Average over 8 conversions */ + TMP117_AVG_32 = 0x02, /**< Average over 32 conversions */ + TMP117_AVG_64 = 0x03, /**< Average over 64 conversions */ +} TMP117_AveragingMode_t; + +/** @brief TMP117 configuration structure. + */ +typedef struct { + TMP117_ConversionMode_t Mode; /**< Conversion mode */ + TMP117_ConversionCycle_t Cycle; /**< Conversion cycle time */ + TMP117_AveragingMode_t Averaging; /**< Number of conversions to average */ +} TMP117_Config_t; + +/** @brief Initializes the TMP117 driver with default configuration. + * Default settings: Continuous mode, 1 second cycle, no averaging. + * @note The device is configured for continuous conversion at 1 second intervals. + * Call TMP117_ReadTemperature() to retrieve temperature readings. + * @param p_Bus_Handle Pointer to I2C bus handle + * @param p_Dev_Handle Pointer to store the created device handle + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + * ESP_ERR_NO_MEM if device handle allocation fails + * ESP_FAIL if I2C communication fails + */ +esp_err_t TMP117_Init(i2c_master_bus_handle_t *p_Bus_Handle, i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Deinitializes the TMP117 driver and frees resources. + * @note After calling this function, the device handle becomes invalid. + * @param p_Dev_Handle Pointer to device handle + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointer is NULL + */ +esp_err_t TMP117_Deinit(i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Configure the TMP117 sensor. + * Allows customization of conversion mode, cycle time, and averaging. + * @note Configuration is written to the device immediately. + * In One-Shot mode, call TMP117_TriggerOneShot() to start conversion. + * @param p_Dev_Handle Pointer to device handle + * @param p_Config Pointer to configuration structure + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + * ESP_FAIL if I2C communication fails + */ +esp_err_t TMP117_Configure(i2c_master_dev_handle_t *p_Dev_Handle, const TMP117_Config_t *p_Config); + +/** @brief Read temperature from TMP117 sensor. + * Reads the current temperature value from the sensor's result register. + * Resolution is 0.0078125°C (7.8125 m°C) per LSB. + * @note In continuous mode, returns the latest conversion result. + * In one-shot mode, returns the result of the last triggered conversion. + * Temperature range: -256°C to +256°C. + * @param p_Dev_Handle Pointer to device handle + * @param p_Temp Pointer to store temperature in degrees Celsius + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + * ESP_FAIL if I2C communication fails + */ +esp_err_t TMP117_ReadTemperature(i2c_master_dev_handle_t *p_Dev_Handle, float *p_Temp); + +/** @brief Trigger a one-shot temperature conversion. + * Only applicable in One-Shot mode. Initiates a single temperature conversion. + * @note Conversion time depends on averaging setting (15.5ms to ~1 second). + * Wait for conversion to complete before reading temperature. + * Use TMP117_IsDataReady() to check if conversion is complete. + * @param p_Dev_Handle Pointer to device handle + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointer is NULL + * ESP_ERR_INVALID_STATE if not in One-Shot mode + * ESP_FAIL if I2C communication fails + */ +esp_err_t TMP117_TriggerOneShot(i2c_master_dev_handle_t *p_Dev_Handle); + +/** @brief Check if new temperature data is ready. + * Reads the Data_Ready flag from the configuration register. + * @note In continuous mode, flag is set after each conversion completes. + * In one-shot mode, flag is set after the triggered conversion completes. + * Flag is cleared automatically when temperature register is read. + * @param p_Dev_Handle Pointer to device handle + * @param p_Ready Pointer to store ready status (true if data is ready) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + * ESP_FAIL if I2C communication fails + */ +esp_err_t TMP117_IsDataReady(i2c_master_dev_handle_t *p_Dev_Handle, bool *p_Ready); + +/** @brief Read the TMP117 device ID. + * Reads the device ID register to verify sensor identity. + * Expected value: 0x0117 + * @note Use this function to verify correct I2C communication. + * Device ID should always read 0x0117. + * @param p_Dev_Handle Pointer to device handle + * @param p_DeviceID Pointer to store device ID + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + * ESP_FAIL if I2C communication fails + */ +esp_err_t TMP117_ReadDeviceID(i2c_master_dev_handle_t *p_Dev_Handle, uint16_t *p_DeviceID); + +/** @brief Perform a soft reset of the TMP117. + * Resets the device to power-on default state. All configuration is lost. + * @note After reset, device returns to default configuration. + * Wait at least 2ms after reset before accessing the device. + * @warning All custom configuration will be lost after reset. + * @param p_Dev_Handle Pointer to device handle + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointer is NULL + * ESP_FAIL if I2C communication fails + */ +esp_err_t TMP117_SoftReset(i2c_master_dev_handle_t *p_Dev_Handle); + +#endif /* TMP117_H_ */ \ No newline at end of file diff --git a/main/Application/Manager/Devices/devicesManager.cpp b/main/Application/Manager/Devices/devicesManager.cpp new file mode 100644 index 0000000..83df00b --- /dev/null +++ b/main/Application/Manager/Devices/devicesManager.cpp @@ -0,0 +1,305 @@ +/* + * devicesManager.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Devices Manager implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include +#include + +#include + +#include "SPI/spi.h" +#include "ADC/adc.h" +#include "TMP117/tmp117.h" +#include "RV8263-C8/rv8263c8.h" +#include "PCAL6416AHF/pcal6416ahf.h" +#include "PCA9633DP1/pca9633dp1.h" +#include "devicesManager.h" + +#if defined(CONFIG_TOUCH_I2C0_HOST) +#define TOUCH_I2C_HOST I2C_NUM_0 +#elif defined(CONFIG_TOUCH_I2C1_HOST) +#define TOUCH_I2C_HOST I2C_NUM_1 +#else +#error "No I2C host defined for touch!" +#endif + +#if defined(CONFIG_DEVICES_I2C_I2C0_HOST) +#define DEVICES_I2C_HOST I2C_NUM_0 +#elif defined(CONFIG_DEVICES_I2C_I2C1_HOST) +#define DEVICES_I2C_HOST I2C_NUM_1 +#else +#error "No I2C host defined for devices!" +#endif + +ESP_EVENT_DEFINE_BASE(DEVICES_EVENTS); + +/** @brief + */ +enum { + DEVICES_EXPANDER_MAINBOARD = 0, + DEVICES_EXPANDER_DISPLAYBOARD, +}; + +/** @brief Default configuration for the I2C interface (shared by RTC, Port Expander, Lepton and Temperature Sensor). + */ +static i2c_master_bus_config_t _Devices_Manager_Devices_I2CM_Config = { + .i2c_port = DEVICES_I2C_HOST, + .sda_io_num = static_cast(CONFIG_DEVICES_I2C_SDA), + .scl_io_num = static_cast(CONFIG_DEVICES_I2C_SCL), + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = false, + .allow_pd = false, + }, +}; + +/** @brief Default configuration for the I2C interface (shared by Touch). + */ +static i2c_master_bus_config_t _Devices_Manager_Touch_I2CM_Config = { + .i2c_port = TOUCH_I2C_HOST, + .sda_io_num = static_cast(CONFIG_TOUCH_SDA), + .scl_io_num = static_cast(CONFIG_TOUCH_SCL), + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = true, + .allow_pd = false, + }, +}; + +/** @brief + */ +static const spi_host_device_t _Devices_Manager_Periph_SPI = SPI3_HOST; + +/** @brief Default configuration for the SPI3 bus (shared by LCD, Touch, SD card). + */ +static const spi_bus_config_t _Devices_Manager_Periph_SPI_Config = { + .mosi_io_num = CONFIG_SPI_MOSI, + .miso_io_num = CONFIG_SPI_MISO, + .sclk_io_num = CONFIG_SPI_SCLK, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .data4_io_num = -1, + .data5_io_num = -1, + .data6_io_num = -1, + .data7_io_num = -1, + .data_io_default_level = 0, + .max_transfer_sz = CONFIG_SPI_TRANSFER_SIZE, + .flags = SPICOMMON_BUSFLAG_MASTER, + .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, + .intr_flags = 0, +}; + +typedef struct { + bool initialized; + i2c_master_dev_handle_t RTC_Handle; + i2c_master_dev_handle_t Expander_Handle[2]; + i2c_master_bus_handle_t I2C_Bus_Handle; + i2c_master_bus_handle_t Touch_I2C_Bus_Handle; +} Devices_Manager_State_t; + +static Devices_Manager_State_t _Devices_Manager_State; + +static const char *TAG = "Devices-Manager"; + +esp_err_t DevicesManager_Init(void) +{ + if (_Devices_Manager_State.initialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + ESP_LOGD(TAG, "Initializing Devices Manager..."); + + memset(&_Devices_Manager_State, 0, sizeof(Devices_Manager_State_t)); + + if (I2CM_Init(&_Devices_Manager_Touch_I2CM_Config, &_Devices_Manager_State.Touch_I2C_Bus_Handle) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize Touch I2C!"); + + return ESP_FAIL; + } + + if (I2CM_Init(&_Devices_Manager_Devices_I2CM_Config, &_Devices_Manager_State.I2C_Bus_Handle) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize Peripheral I2C!"); + + return ESP_FAIL; + } + + if (SPIM_Init(&_Devices_Manager_Periph_SPI_Config, _Devices_Manager_Periph_SPI, SPI_DMA_CH_AUTO) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize SPI%u!", _Devices_Manager_Periph_SPI); + + return ESP_FAIL; + } + + if (PortExpander_Init(&_Devices_Manager_State.I2C_Bus_Handle, + &_Devices_Manager_State.Expander_Handle[DEVICES_EXPANDER_MAINBOARD]) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set default configuration for the mainboard port expander!"); + + return ESP_FAIL; + } + + if (RTC_Init(&_Devices_Manager_State.I2C_Bus_Handle, &_Devices_Manager_State.RTC_Handle) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize RTC!"); + + return ESP_FAIL; + } + + PortExpander_EnableBatteryVoltage(&_Devices_Manager_State.Expander_Handle[DEVICES_EXPANDER_MAINBOARD], false); + + _Devices_Manager_State.initialized = true; + + return ESP_OK; +} + +esp_err_t DevicesManager_Deinit(void) +{ + esp_err_t Error; + + if (_Devices_Manager_State.initialized == false) { + ESP_LOGE(TAG, "Devices Manager not initialized yet!"); + + return ESP_OK; + } + + ADC_Deinit(); + + PortExpander_EnableBatteryVoltage(&_Devices_Manager_State.Expander_Handle[DEVICES_EXPANDER_MAINBOARD], false); + + RTC_Deinit(&_Devices_Manager_State.RTC_Handle); + PortExpander_Deinit(&_Devices_Manager_State.Expander_Handle[DEVICES_EXPANDER_MAINBOARD]); + Error = I2CM_Deinit(_Devices_Manager_State.I2C_Bus_Handle); + + SPIM_Deinit(_Devices_Manager_Periph_SPI); + + _Devices_Manager_State.initialized = false; + + return Error; +} + +i2c_master_bus_handle_t DevicesManager_GetI2CBusHandle(void) +{ + if (_Devices_Manager_State.initialized == false) { + ESP_LOGE(TAG, "Devices Manager not initialized yet!"); + + return NULL; + } + + return _Devices_Manager_State.I2C_Bus_Handle; +} + +i2c_master_bus_handle_t DevicesManager_GetTouchI2CBusHandle(void) +{ + if (_Devices_Manager_State.initialized == false) { + ESP_LOGE(TAG, "Devices Manager not initialized yet!"); + + return NULL; + } + + return _Devices_Manager_State.Touch_I2C_Bus_Handle; +} + +spi_host_device_t DevicesManager_GetSPIHost(void) +{ + return _Devices_Manager_Periph_SPI; +} + +esp_err_t DevicesManager_GetBatteryVoltage(int *p_Voltage, uint8_t *p_Percentage) +{ + esp_err_t Error; + + if (_Devices_Manager_State.initialized == false) { + ESP_LOGE(TAG, "Devices Manager not initialized yet!"); + + return ESP_ERR_INVALID_STATE; + } else if ((p_Voltage == NULL) || (p_Percentage == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + PortExpander_EnableBatteryVoltage(&_Devices_Manager_State.Expander_Handle[DEVICES_EXPANDER_MAINBOARD], true); + + Error = ADC_ReadBattery(p_Voltage, p_Percentage); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read battery voltage: %d!", Error); + } + + PortExpander_EnableBatteryVoltage(&_Devices_Manager_State.Expander_Handle[DEVICES_EXPANDER_MAINBOARD], false); + + return Error; +} + +esp_err_t DevicesManager_GetStateOfCharge(uint8_t *p_Percentage) +{ + int Voltage; + esp_err_t Error; + + if (p_Percentage == NULL) { + return ESP_ERR_INVALID_ARG; + } + + Error = DevicesManager_GetBatteryVoltage(&Voltage, p_Percentage); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to get battery voltage for SOC calculation!"); + + return Error; + } + + return ESP_OK; +} + +esp_err_t DevicesManager_GetRTCHandle(i2c_master_dev_handle_t *p_Handle) +{ + if (_Devices_Manager_State.initialized == false) { + ESP_LOGE(TAG, "Devices Manager not initialized yet!"); + + return ESP_ERR_INVALID_STATE; + } else if (p_Handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + *p_Handle = _Devices_Manager_State.RTC_Handle; + + return ESP_OK; +} + +esp_err_t DevicesManager_GetTime(struct tm *p_Time) +{ + return RTC_GetTime(&_Devices_Manager_State.RTC_Handle, p_Time); +} + +esp_err_t DevicesManager_SetTime(const struct tm *p_Time) +{ + return RTC_SetTime(&_Devices_Manager_State.RTC_Handle, p_Time); +} + +esp_err_t DevicesManager_GetTemperature(float *p_Temperature) +{ + *p_Temperature = 0.0f; + return ESP_OK; +} \ No newline at end of file diff --git a/main/Application/Manager/Devices/devicesManager.h b/main/Application/Manager/Devices/devicesManager.h new file mode 100644 index 0000000..86bb28e --- /dev/null +++ b/main/Application/Manager/Devices/devicesManager.h @@ -0,0 +1,155 @@ +/* + * devicesManager.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Devices Manager definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef DEVICESMANAGER_H_ +#define DEVICESMANAGER_H_ + +#include +#include + +#include +#include + +#include "I2C/i2c.h" +#include "devicesTypes.h" + +/** @brief Initialize the Devices Manager. + * Initializes all hardware peripherals including I2C and SPI buses, + * port expander, RTC, ADC, and LED driver. Must be called before + * any other DevicesManager functions. + * @note This function must be called after NVS initialization. + * I2C bus speed: 400 kHz, SPI: up to 40 MHz. + * @warning Not thread-safe. Call once from main task during startup. + * @return ESP_OK on success + * ESP_ERR_NO_MEM if memory allocation fails + * ESP_FAIL if I2C/SPI initialization fails + */ +esp_err_t DevicesManager_Init(void); + +/** @brief Deinitialize the Devices Manager. + * Cleans up all device handles, removes I2C/SPI devices, and frees + * allocated resources. Should be called during shutdown. + * @note After calling this, DevicesManager_Init() must be called again. + * @warning All device handles become invalid after this call. + * @return ESP_OK on success + * ESP_FAIL if cleanup fails + */ +esp_err_t DevicesManager_Deinit(void); + +/** @brief Get the I2C bus handle for peripheral devices. + * Returns the I2C bus handle used by RTC, Port Expander, and other + * peripheral devices (not the touch controller). + * @note This is the main I2C bus (I2C_NUM_0). + * Bus speed: 400 kHz. + * @return I2C bus handle on success + * NULL if DevicesManager not initialized + */ +i2c_master_bus_handle_t DevicesManager_GetI2CBusHandle(void); + +/** @brief Get the I2C bus handle for touch controller. + * Returns the dedicated I2C bus handle used exclusively by the + * GT911 touch controller. + * @note This is a separate I2C bus (I2C_NUM_1). + * Dedicated bus prevents interference with other peripherals. + * @return I2C bus handle on success + * NULL if DevicesManager not initialized + */ +i2c_master_bus_handle_t DevicesManager_GetTouchI2CBusHandle(void); + +/** @brief Get the SPI host device identifier. + * Returns the SPI host device that is managed by the Devices Manager. + * This host is shared by LCD display, touch controller, and SD card. + * @note Shared SPI bus requires proper CS (Chip Select) management. + * Maximum clock speed depends on connected device (LCD: 40MHz). + * @return SPI host identifier (typically SPI2_HOST) + */ +spi_host_device_t DevicesManager_GetSPIHost(void); + +/** @brief Get the battery voltage and percentage. + * Reads the battery voltage via ADC and calculates the remaining + * charge percentage based on voltage curve. Voltage measurement is + * enabled via port expander GPIO. + * @note ADC is calibrated using eFuse values. + * Battery voltage range: typically 3.0V to 4.2V for Li-Ion. + * @warning Measurement enables battery voltage divider (increases power consumption). + * @param p_Voltage Pointer to store voltage in mV (millivolts) + * @param p_Percentage Pointer to store percentage (0-100) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + * ESP_FAIL if ADC read fails + */ +esp_err_t DevicesManager_GetBatteryVoltage(int *p_Voltage, uint8_t *p_Percentage); + +/** @brief Get the battery state of charge (SOC) percentage. + * Calculates the battery SOC percentage based on the voltage reading. + * @note ADC is calibrated using eFuse values. + * Battery voltage range: typically 3.0V to 4.2V for Li-Ion. + * @warning Measurement enables battery voltage divider (increases power consumption). + * @param p_Percentage Pointer to store percentage (0-100) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + * ESP_FAIL if ADC read fails + */ +esp_err_t DevicesManager_GetStateOfCharge(uint8_t *p_Percentage); + +/** @brief Get the RTC device handle (for Time Manager). + * Returns the I2C device handle for the RV8263-C8 Real-Time Clock. + * Used by TimeManager for time synchronization. + * @note RTC provides backup time when network unavailable. + * This function is typically called by TimeManager only. + * @param p_Handle Pointer to store the RTC I2C device handle + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Handle is NULL + * ESP_ERR_INVALID_STATE if DevicesManager not initialized + */ +esp_err_t DevicesManager_GetRTCHandle(i2c_master_dev_handle_t *p_Handle); + +/** @brief Get the current time from the RTC. + * Reads the current date and time from the RV8263-C8 Real-Time Clock + * and converts it to a tm structure. + * @note This is a convenience wrapper for TimeManager. + * Time is in local timezone (not UTC). + * @param p_Time Pointer to tm structure to store the time + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Time is NULL + * ESP_ERR_INVALID_STATE if RTC not initialized + * ESP_FAIL if I2C communication fails + */ +esp_err_t DevicesManager_GetTime(struct tm *p_Time); + +/** @brief Set the time on the RTC. + * @param p_Time Pointer to the time to set + * @return ESP_OK when successful + */ +esp_err_t DevicesManager_SetTime(const struct tm *p_Time); + +/** @brief Get the temperature from the TMP117 sensor. + * @param p_Temperature Pointer to store the temperature in Celsius + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Temperature is NULL + * ESP_ERR_INVALID_STATE if TMP117 not initialized + * ESP_FAIL if I2C communication fails + */ +esp_err_t DevicesManager_GetTemperature(float *p_Temperature); + +#endif /* DEVICESMANAGER_H_ */ \ No newline at end of file diff --git a/main/Application/Manager/Devices/devicesTypes.h b/main/Application/Manager/Devices/devicesTypes.h new file mode 100644 index 0000000..53cfb96 --- /dev/null +++ b/main/Application/Manager/Devices/devicesTypes.h @@ -0,0 +1,48 @@ +/* + * devicesTypes.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Common type definitions for the Devices Manager component. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef DEVICES_TYPES_H_ +#define DEVICES_TYPES_H_ + +#include +#include + +#include +#include +#include + +#include +#include +#include + +/** @brief Devices Manager events base. + */ +ESP_EVENT_DECLARE_BASE(DEVICES_EVENTS); + +/** @brief Devices Manager event identifiers. + */ +enum { + +}; + +#endif /* DEVICES_TYPES_H_ */ diff --git a/main/Application/Manager/Memory/memoryManager.cpp b/main/Application/Manager/Memory/memoryManager.cpp new file mode 100644 index 0000000..4f875f4 --- /dev/null +++ b/main/Application/Manager/Memory/memoryManager.cpp @@ -0,0 +1,813 @@ +/* + * memoryManager.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Memory Manager implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "../Devices/SPI/spi.h" +#include "../Devices/devicesManager.h" +#include "memoryManager.h" + +ESP_EVENT_DEFINE_BASE(MEMORY_EVENTS); + +typedef struct { + bool isInitialized; + bool hasSDCard; + bool isFilesystemLocked; + MemoryManager_Location_t StorageLocation; + sdmmc_card_t *SDCard; + wl_handle_t WL_Handle; +} Memory_Manager_State_t; + +static Memory_Manager_State_t _Memory_Manager_State; + +static const char *TAG = "Memory-Manager"; + +/** @brief Calculate directory size recursively. + * @param p_Path Path to directory + * @return Total size in bytes + */ +static size_t MemoryManager_Calc_Dir_Size(const char *p_Path) +{ + size_t TotalSize = 0; + struct dirent *Entry; + DIR *Dir = opendir(p_Path); + + if (Dir == NULL) { + return 0; + } + + while ((Entry = readdir(Dir)) != NULL) { + struct stat St; + char FullPath[256]; + + if ((strcmp(Entry->d_name, ".") == 0) || (strcmp(Entry->d_name, "..") == 0)) { + continue; + } + + snprintf(FullPath, sizeof(FullPath), "%s/%s", p_Path, Entry->d_name); + + if (stat(FullPath, &St) == 0) { + if (S_ISDIR(St.st_mode)) { + TotalSize += MemoryManager_Calc_Dir_Size(FullPath); + } else { + TotalSize += St.st_size; + } + } + } + + closedir(Dir); + + return TotalSize; +} + +/** @brief Mount internal flash storage as FAT32 with wear leveling. + * @return ESP_OK if mounted successfully + */ +static esp_err_t MemoryManager_Mount_Internal_Storage(void) +{ + esp_err_t Error; + const esp_vfs_fat_mount_config_t MountConfig = { + .format_if_mount_failed = true, + .max_files = 5, + .allocation_unit_size = 4096, + .disk_status_check_enable = false, + .use_one_fat = false + }; + const esp_partition_t *Partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_FAT, + "storage"); + + ESP_LOGD(TAG, "Mounting internal storage with FAT32 and wear leveling..."); + + if (Partition == NULL) { + ESP_LOGE(TAG, "Storage partition not found!"); + + return ESP_ERR_NOT_FOUND; + } + + ESP_LOGD(TAG, "Found storage partition: size=%lu bytes", Partition->size); + + Error = esp_vfs_fat_spiflash_mount_rw_wl("/storage", "storage", &MountConfig, &_Memory_Manager_State.WL_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount FAT filesystem: %d", Error); + + return Error; + } + + ESP_LOGD(TAG, "Internal storage mounted successfully at /storage"); + ESP_LOGD(TAG, "Wear leveling handle: %d", _Memory_Manager_State.WL_Handle); + + return ESP_OK; +} + +/** @brief Try to mount SD card via SPI. + * @return ESP_OK if SD card mounted successfully + */ +static esp_err_t MemoryManager_Mount_SD_Card(void) +{ + esp_err_t Error; + sdmmc_host_t Host = SDSPI_HOST_DEFAULT(); + sdspi_device_config_t SlotConfig = SDSPI_DEVICE_CONFIG_DEFAULT(); + spi_host_device_t SPI_Host = DevicesManager_GetSPIHost(); + const esp_vfs_fat_mount_config_t MountConfig = { + .format_if_mount_failed = false, + .max_files = 5, + .allocation_unit_size = 16 * 1024, + .disk_status_check_enable = false, + .use_one_fat = false + }; + + ESP_LOGD(TAG, "Attempting to mount SD card via SPI..."); + + if (SPIM_IsInitialized(SPI_Host) == false) { + ESP_LOGE(TAG, "SPI bus not initialized!"); + + return ESP_ERR_INVALID_STATE; + } + + SlotConfig.gpio_cs = static_cast(CONFIG_SD_CARD_PIN_CS); + SlotConfig.host_id = SPI_Host; + Host.slot = SPI_Host; + + Error = esp_vfs_fat_sdspi_mount("/sdcard", &Host, &SlotConfig, &MountConfig, &_Memory_Manager_State.SDCard); + if (Error != ESP_OK) { + if (Error == ESP_FAIL) { + ESP_LOGW(TAG, "Failed to mount SD card filesystem"); + } else { + ESP_LOGW(TAG, "Failed to initialize SD card: %d!", Error); + } + + return Error; + } + + ESP_LOGD(TAG, "SD card mounted successfully at /sdcard via SPI"); + + return ESP_OK; +} + +esp_err_t MemoryManager_Init(void) +{ + esp_err_t Error; + + if (_Memory_Manager_State.isInitialized) { + ESP_LOGW(TAG, "Memory Manager already initialized"); + + return ESP_OK; + } + + ESP_LOGD(TAG, "Initializing Memory Manager"); + + memset(&_Memory_Manager_State, 0, sizeof(Memory_Manager_State_t)); + + /* Try to mount SD card first */ + if (MemoryManager_Mount_SD_Card() == ESP_OK) { + ESP_LOGD(TAG, "Using SD card for storage"); + + _Memory_Manager_State.hasSDCard = true; + _Memory_Manager_State.StorageLocation = MEMORY_LOCATION_SD_CARD; + + esp_event_post(MEMORY_EVENTS, MEMORY_EVENT_SD_CARD_MOUNTED, NULL, 0, portMAX_DELAY); + } else { + ESP_LOGD(TAG, "SD card not available, using internal flash"); + + /* Mount internal flash with FAT32 */ + Error = MemoryManager_Mount_Internal_Storage(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount internal storage: %d!", Error); + + return Error; + } + + _Memory_Manager_State.hasSDCard = false; + _Memory_Manager_State.StorageLocation = MEMORY_LOCATION_INTERNAL; + esp_event_post(MEMORY_EVENTS, MEMORY_EVENT_FLASH_MOUNTED, NULL, 0, portMAX_DELAY); + } + + _Memory_Manager_State.isInitialized = true; + + return ESP_OK; +} + +esp_err_t MemoryManager_Deinit(void) +{ + if (_Memory_Manager_State.isInitialized == false) { + return ESP_OK; + } + + ESP_LOGD(TAG, "Deinitializing Memory Manager"); + + if (_Memory_Manager_State.hasSDCard) { + esp_vfs_fat_sdcard_unmount("/sdcard", _Memory_Manager_State.SDCard); + _Memory_Manager_State.SDCard = NULL; + _Memory_Manager_State.hasSDCard = false; + } else if (_Memory_Manager_State.WL_Handle != WL_INVALID_HANDLE) { + esp_vfs_fat_spiflash_unmount_rw_wl("/storage", _Memory_Manager_State.WL_Handle); + _Memory_Manager_State.WL_Handle = WL_INVALID_HANDLE; + } + + _Memory_Manager_State.isInitialized = false; + + return ESP_OK; +} + +bool MemoryManager_HasSDCard(void) +{ + return _Memory_Manager_State.hasSDCard; +} + +MemoryManager_Location_t MemoryManager_GetStorageLocation(void) +{ + return _Memory_Manager_State.StorageLocation; +} + +const char *MemoryManager_GetStoragePath(void) +{ + if (_Memory_Manager_State.StorageLocation == MEMORY_LOCATION_SD_CARD) { + return "/sdcard"; + } else { + return "/storage"; + } +} + +esp_err_t MemoryManager_GetStorageUsage(MemoryManager_Usage_t *p_Usage) +{ + FATFS *fs; + DWORD fre_clust; + BYTE Pdrv; + char Drive[3]; + + if (p_Usage == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (_Memory_Manager_State.StorageLocation == MEMORY_LOCATION_SD_CARD) { + if (_Memory_Manager_State.hasSDCard == false) { + ESP_LOGE(TAG, "SD card not mounted!"); + + return ESP_ERR_INVALID_STATE; + } + + Pdrv = ff_diskio_get_pdrv_card(_Memory_Manager_State.SDCard); + if (Pdrv == 0xFF) { + ESP_LOGE(TAG, "SD card diskio drive not found!"); + + return ESP_ERR_INVALID_STATE; + } + } else { + if (_Memory_Manager_State.WL_Handle == WL_INVALID_HANDLE) { + ESP_LOGE(TAG, "Internal storage not mounted!"); + + return ESP_ERR_INVALID_STATE; + } + + Pdrv = ff_diskio_get_pdrv_wl(_Memory_Manager_State.WL_Handle); + if (Pdrv == 0xFF) { + ESP_LOGE(TAG, "Internal storage diskio drive not found!"); + + return ESP_ERR_INVALID_STATE; + } + } + + Drive[0] = static_cast('0' + Pdrv); + Drive[1] = ':'; + Drive[2] = '\0'; + + if (f_getfree(Drive, &fre_clust, &fs) == FR_OK) { + uint64_t Total; + uint64_t Free; + + Total = (fs->n_fatent - 2) * fs->csize; + Free = fre_clust * fs->csize; + + p_Usage->TotalBytes = Total * fs->ssize; + p_Usage->FreeBytes = Free * fs->ssize; + p_Usage->UsedBytes = p_Usage->TotalBytes - p_Usage->FreeBytes; + + if (p_Usage->TotalBytes > 0) { + p_Usage->UsedPercent = static_cast((p_Usage->UsedBytes * 100) / p_Usage->TotalBytes); + } else { + p_Usage->UsedPercent = 0; + } + + return ESP_OK; + } else { + ESP_LOGE(TAG, "Failed to get %s filesystem info!", + (_Memory_Manager_State.StorageLocation == MEMORY_LOCATION_SD_CARD) ? "SD card" : "internal storage"); + + return ESP_FAIL; + } +} + +esp_err_t MemoryManager_GetCoredumpUsage(MemoryManager_Usage_t *p_Usage) +{ + if (p_Usage == NULL) { + return ESP_ERR_INVALID_ARG; + } + + const esp_partition_t *Partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_COREDUMP, + "coredump"); + if (Partition == NULL) { + ESP_LOGE(TAG, "Coredump partition not found!"); + + return ESP_ERR_NOT_FOUND; + } + + p_Usage->TotalBytes = Partition->size; + + /* Read first 4 bytes to check if coredump exists (magic number check) */ + uint32_t Magic = 0; + esp_err_t Error = esp_partition_read(Partition, 0, &Magic, sizeof(Magic)); + + if ((Error == ESP_OK) && (Magic != 0xFFFFFFFF) && (Magic != 0x00000000)) { + /* Coredump likely present - assume partition is used */ + p_Usage->UsedBytes = p_Usage->TotalBytes; + p_Usage->FreeBytes = 0; + p_Usage->UsedPercent = 100; + } else { + /* No coredump or empty partition */ + p_Usage->UsedBytes = 0; + p_Usage->FreeBytes = p_Usage->TotalBytes; + p_Usage->UsedPercent = 0; + } + + return ESP_OK; +} + +esp_err_t MemoryManager_EraseStorage(void) +{ + if (_Memory_Manager_State.StorageLocation == MEMORY_LOCATION_SD_CARD) { + DIR *Dir; + struct dirent *Entry; + + ESP_LOGD(TAG, "Erasing SD card storage..."); + + Dir = opendir("/sdcard"); + if (Dir == NULL) { + ESP_LOGE(TAG, "Failed to open SD card directory!"); + + return ESP_FAIL; + } + + while ((Entry = readdir(Dir)) != NULL) { + char FilePath[512]; + + if ((strcmp(Entry->d_name, ".") == 0) || (strcmp(Entry->d_name, "..") == 0)) { + continue; + } + + snprintf(FilePath, sizeof(FilePath), "/sdcard/%s", Entry->d_name); + + struct stat St; + if (stat(FilePath, &St) == 0) { + if (S_ISDIR(St.st_mode)) { + /* TODO: Implement recursive directory deletion */ + ESP_LOGW(TAG, "Skipping directory: %s", FilePath); + } else { + if (unlink(FilePath) != 0) { + ESP_LOGW(TAG, "Failed to delete: %s", FilePath); + } + } + } + } + + closedir(Dir); + + ESP_LOGD(TAG, "SD card storage erased successfully"); + + return ESP_OK; + + } else { + esp_err_t Error; + const esp_vfs_fat_mount_config_t MountConfig = { + .format_if_mount_failed = true, + .max_files = 5, + .allocation_unit_size = 4096, + .disk_status_check_enable = false, + .use_one_fat = false + }; + + /* Erase internal FAT storage by unmounting and reformatting */ + ESP_LOGD(TAG, "Erasing internal storage..."); + + /* Unmount current filesystem */ + if (_Memory_Manager_State.WL_Handle != WL_INVALID_HANDLE) { + esp_vfs_fat_spiflash_unmount_rw_wl("/storage", _Memory_Manager_State.WL_Handle); + _Memory_Manager_State.WL_Handle = WL_INVALID_HANDLE; + } + + Error = esp_vfs_fat_spiflash_mount_rw_wl("/storage", "storage", &MountConfig, &_Memory_Manager_State.WL_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to remount storage: %d!", Error); + + return ESP_FAIL; + } + + ESP_LOGD(TAG, "Internal storage erased and reformatted successfully"); + + return ESP_OK; + } +} + +esp_err_t MemoryManager_EraseCoredump(void) +{ + esp_err_t Error; + const esp_partition_t *Partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_COREDUMP, + "coredump"); + + if (Partition == NULL) { + ESP_LOGE(TAG, "Coredump partition not found!"); + + return ESP_ERR_NOT_FOUND; + } + + ESP_LOGD(TAG, "Erasing coredump partition (%d bytes)...", Partition->size); + + Error = esp_partition_erase_range(Partition, 0, Partition->size); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase coredump partition: %d!", Error); + + return ESP_FAIL; + } + + ESP_LOGD(TAG, "Coredump partition erased successfully"); + + return ESP_OK; +} + +esp_err_t MemoryManager_GetWearLevelingHandle(wl_handle_t *p_Handle) +{ + if (p_Handle == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (_Memory_Manager_State.StorageLocation != MEMORY_LOCATION_INTERNAL) { + ESP_LOGE(TAG, "Wear leveling only available for internal storage!"); + + return ESP_ERR_INVALID_STATE; + } else if (_Memory_Manager_State.WL_Handle == WL_INVALID_HANDLE) { + ESP_LOGE(TAG, "Internal storage not mounted!"); + + return ESP_ERR_INVALID_STATE; + } + + *p_Handle = _Memory_Manager_State.WL_Handle; + + return ESP_OK; +} + +esp_err_t MemoryManager_GetSDCardHandle(sdmmc_card_t **pp_Card) +{ + if (pp_Card == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (_Memory_Manager_State.StorageLocation != MEMORY_LOCATION_SD_CARD) { + ESP_LOGE(TAG, "SD card handle only available when SD card is mounted!"); + + return ESP_ERR_INVALID_STATE; + } else if (_Memory_Manager_State.SDCard == NULL) { + ESP_LOGE(TAG, "SD card not mounted!"); + + return ESP_ERR_INVALID_STATE; + } + + *pp_Card = _Memory_Manager_State.SDCard; + + return ESP_OK; +} + +esp_err_t MemoryManager_LockFilesystem(void) +{ + if (_Memory_Manager_State.isFilesystemLocked) { + ESP_LOGW(TAG, "Filesystem already locked!"); + + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGD(TAG, "Locking filesystem for USB access"); + ESP_LOGW(TAG, " Application MUST NOT write to %s while USB is active!", MemoryManager_GetStoragePath()); + + _Memory_Manager_State.isFilesystemLocked = true; + + return ESP_OK; +} + +esp_err_t MemoryManager_UnlockFilesystem(void) +{ + if (_Memory_Manager_State.isFilesystemLocked == false) { + ESP_LOGW(TAG, "Filesystem not locked!"); + + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGD(TAG, "Unlocking filesystem - application can write again"); + + _Memory_Manager_State.isFilesystemLocked = false; + + return ESP_OK; +} + +bool MemoryManager_IsFilesystemLocked(void) +{ + return _Memory_Manager_State.isFilesystemLocked; +} + +esp_err_t MemoryManager_SoftUnmountStorage(void) +{ + BYTE Pdrv; + char Drive[3]; + + if (_Memory_Manager_State.isInitialized == false) { + ESP_LOGE(TAG, "Memory Manager not initialized!"); + + return ESP_ERR_INVALID_STATE; + } + + if (_Memory_Manager_State.StorageLocation == MEMORY_LOCATION_INTERNAL) { + if (_Memory_Manager_State.WL_Handle == WL_INVALID_HANDLE) { + ESP_LOGE(TAG, "Internal storage not mounted!"); + + return ESP_ERR_INVALID_STATE; + } + + Pdrv = ff_diskio_get_pdrv_wl(_Memory_Manager_State.WL_Handle); + if (Pdrv == 0xFF) { + ESP_LOGE(TAG, "Failed to get diskio drive for WL handle!"); + + return ESP_ERR_INVALID_STATE; + } + + Drive[0] = static_cast('0' + Pdrv); + Drive[1] = ':'; + Drive[2] = '\0'; + + f_mount(0, Drive, 0); + ff_diskio_unregister(Pdrv); + esp_vfs_fat_unregister_path("/storage"); + + ESP_LOGD(TAG, "Internal storage VFS soft-unmounted (WL handle preserved)"); + } else if (_Memory_Manager_State.StorageLocation == MEMORY_LOCATION_SD_CARD) { + if (_Memory_Manager_State.SDCard == NULL) { + ESP_LOGE(TAG, "SD card not mounted!"); + + return ESP_ERR_INVALID_STATE; + } + + Pdrv = ff_diskio_get_pdrv_card(_Memory_Manager_State.SDCard); + if (Pdrv == 0xFF) { + ESP_LOGE(TAG, "Failed to get diskio drive for SD card!"); + + return ESP_ERR_INVALID_STATE; + } + + Drive[0] = static_cast('0' + Pdrv); + Drive[1] = ':'; + Drive[2] = '\0'; + + f_mount(0, Drive, 0); + ff_diskio_unregister(Pdrv); + esp_vfs_fat_unregister_path("/sdcard"); + + ESP_LOGD(TAG, "SD card VFS soft-unmounted (card handle preserved)"); + } else { + ESP_LOGE(TAG, "Unknown storage location!"); + + return ESP_ERR_INVALID_ARG; + } + + return ESP_OK; +} + +esp_err_t MemoryManager_SoftRemountStorage(void) +{ + esp_err_t Error; + BYTE Pdrv; + FATFS *p_FS = NULL; + + if (_Memory_Manager_State.isInitialized == false) { + ESP_LOGE(TAG, "Memory Manager not initialized!"); + + return ESP_ERR_INVALID_STATE; + } + + Error = ff_diskio_get_drive(&Pdrv); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "No free diskio drive available!"); + + return Error; + } + + char Drive[3] = {static_cast('0' + Pdrv), ':', '\0'}; + + if (_Memory_Manager_State.StorageLocation == MEMORY_LOCATION_INTERNAL) { + FRESULT FResult; + esp_vfs_fat_conf_t VFS_Conf; + + if (_Memory_Manager_State.WL_Handle == WL_INVALID_HANDLE) { + ESP_LOGE(TAG, "Internal storage WL handle invalid!"); + + return ESP_ERR_INVALID_STATE; + } + + Error = ff_diskio_register_wl_partition(Pdrv, _Memory_Manager_State.WL_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to register WL partition to diskio: %d!", Error); + + return Error; + } + + memset(&VFS_Conf, 0, sizeof(VFS_Conf)); + VFS_Conf.base_path = "/storage"; + VFS_Conf.fat_drive = Drive; + VFS_Conf.max_files = 5; + + Error = esp_vfs_fat_register_cfg(&VFS_Conf, &p_FS); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to register VFS FAT for internal storage: %d!", Error); + + ff_diskio_unregister(Pdrv); + + return Error; + } + + FResult = f_mount(p_FS, Drive, 1); + if (FResult != FR_OK) { + ESP_LOGE(TAG, "Failed to mount FAT filesystem on internal storage: %d!", FResult); + + esp_vfs_fat_unregister_path("/storage"); + ff_diskio_unregister(Pdrv); + + return ESP_FAIL; + } + + ESP_LOGD(TAG, "Internal storage VFS soft-remounted successfully"); + } else if (_Memory_Manager_State.StorageLocation == MEMORY_LOCATION_SD_CARD) { + FRESULT FResult; + esp_vfs_fat_conf_t VFS_Conf; + + if (_Memory_Manager_State.SDCard == NULL) { + ESP_LOGE(TAG, "SD card handle invalid!"); + + return ESP_ERR_INVALID_STATE; + } + + ff_diskio_register_sdmmc(Pdrv, _Memory_Manager_State.SDCard); + ff_sdmmc_set_disk_status_check(Pdrv, false); + + memset(&VFS_Conf, 0, sizeof(VFS_Conf)); + VFS_Conf.base_path = "/sdcard"; + VFS_Conf.fat_drive = Drive; + VFS_Conf.max_files = 5; + + Error = esp_vfs_fat_register_cfg(&VFS_Conf, &p_FS); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to register VFS FAT for SD card: %d!", Error); + + ff_diskio_unregister(Pdrv); + + return Error; + } + + FResult = f_mount(p_FS, Drive, 1); + if (FResult != FR_OK) { + ESP_LOGE(TAG, "Failed to mount FAT filesystem on SD card: %d!", FResult); + + esp_vfs_fat_unregister_path("/sdcard"); + ff_diskio_unregister(Pdrv); + + return ESP_FAIL; + } + + ESP_LOGD(TAG, "SD card VFS soft-remounted successfully"); + } else { + ESP_LOGE(TAG, "Unknown storage location!"); + + return ESP_ERR_INVALID_ARG; + } + + return ESP_OK; +} + +esp_err_t MemoryManager_FormatActiveStorage(void) +{ + esp_err_t Error; + + if (_Memory_Manager_State.isFilesystemLocked == false) { + ESP_LOGW(TAG, "Filesystem not locked! Lock filesystem before formatting."); + + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGI(TAG, "Formatting active storage location..."); + + if (_Memory_Manager_State.StorageLocation == MEMORY_LOCATION_SD_CARD) { + sdmmc_host_t Host = SDSPI_HOST_DEFAULT(); + sdspi_device_config_t SlotConfig = SDSPI_DEVICE_CONFIG_DEFAULT(); + spi_host_device_t SPI_Host = DevicesManager_GetSPIHost(); + const esp_vfs_fat_mount_config_t MountConfig = { + .format_if_mount_failed = true, + .max_files = 5, + .allocation_unit_size = 16 * 1024, + .disk_status_check_enable = false, + .use_one_fat = false + }; + + ESP_LOGD(TAG, "Formatting SD card..."); + + /* Unmount current SD card */ + if (_Memory_Manager_State.hasSDCard) { + esp_vfs_fat_sdcard_unmount("/sdcard", _Memory_Manager_State.SDCard); + _Memory_Manager_State.SDCard = NULL; + } + + /* Format by creating new filesystem */ + SlotConfig.gpio_cs = static_cast(CONFIG_SD_CARD_PIN_CS); + SlotConfig.host_id = SPI_Host; + Host.slot = SPI_Host; + + Error = esp_vfs_fat_sdspi_mount("/sdcard", &Host, &SlotConfig, &MountConfig, &_Memory_Manager_State.SDCard); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to format and remount SD card: %d!", Error); + _Memory_Manager_State.hasSDCard = false; + _Memory_Manager_State.SDCard = NULL; + + return Error; + } + + ESP_LOGI(TAG, "SD card formatted and remounted successfully"); + _Memory_Manager_State.hasSDCard = true; + + return ESP_OK; + + } else { + esp_err_t Error; + const esp_vfs_fat_mount_config_t MountConfig = { + .format_if_mount_failed = true, + .max_files = 5, + .allocation_unit_size = 4096, + .disk_status_check_enable = false, + .use_one_fat = false + }; + + ESP_LOGD(TAG, "Formatting internal storage..."); + + /* Unmount current filesystem */ + if (_Memory_Manager_State.WL_Handle != WL_INVALID_HANDLE) { + esp_vfs_fat_spiflash_unmount_rw_wl("/storage", _Memory_Manager_State.WL_Handle); + _Memory_Manager_State.WL_Handle = WL_INVALID_HANDLE; + } + + /* Format and remount */ + Error = esp_vfs_fat_spiflash_format_rw_wl("/storage", "storage"); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to format internal storage: %d!", Error); + + return Error; + } + + Error = esp_vfs_fat_spiflash_mount_rw_wl("/storage", "storage", &MountConfig, &_Memory_Manager_State.WL_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to remount internal storage after formatting: %d!", Error); + + return Error; + } + + ESP_LOGI(TAG, "Internal storage formatted and remounted successfully"); + + return ESP_OK; + } +} \ No newline at end of file diff --git a/main/Application/Manager/Memory/memoryManager.h b/main/Application/Manager/Memory/memoryManager.h new file mode 100644 index 0000000..aea5d29 --- /dev/null +++ b/main/Application/Manager/Memory/memoryManager.h @@ -0,0 +1,250 @@ +/* + * memoryManager.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Memory Manager definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef MEMORYMANAGER_H_ +#define MEMORYMANAGER_H_ + +#include +#include + +#include "memoryTypes.h" + +/** @brief Storage location types. + */ +typedef enum { + MEMORY_LOCATION_INTERNAL, /**< Internal flash storage partition. */ + MEMORY_LOCATION_SD_CARD, /**< External SD card. */ +} MemoryManager_Location_t; + +/** @brief Memory usage information. + */ +typedef struct { + size_t TotalBytes; /**< Total partition/storage size in bytes. */ + size_t UsedBytes; /**< Used space in bytes. */ + size_t FreeBytes; /**< Free space in bytes. */ + uint8_t UsedPercent; /**< Used space percentage (0-100). */ +} MemoryManager_Usage_t; + +/** @brief Initialize the Memory Manager. + * Detects SD card presence and determines storage location (internal flash + * or SD card). Mounts the filesystem and prepares it for use. + * @note SD card is preferred if detected, otherwise internal flash is used. + * Internal storage uses LittleFS with wear leveling. + * SD card uses FAT filesystem. + * @warning Must be called after SPI bus initialization (DevicesManager). + * @return ESP_OK on success + * ESP_ERR_NO_MEM if memory allocation fails + * ESP_FAIL if filesystem mount fails + */ +esp_err_t MemoryManager_Init(void); + +/** @brief Deinitialize the Memory Manager. + * Unmounts filesystem, releases SD card handle, and frees all + * allocated resources. + * @note All open file handles must be closed before calling this. + * @warning Storage path and handles become invalid after this call. + * @return ESP_OK on success + * ESP_FAIL if unmount fails + */ +esp_err_t MemoryManager_Deinit(void); + +/** @brief Check if SD card is present and mounted. + * Returns the SD card availability status. True means SD card is + * currently in use as active storage. + * @note SD card is hot-plug capable but requires re-initialization. + * This is thread-safe. + * @return true if SD card is detected and mounted + * false if using internal flash or SD not detected + */ +bool MemoryManager_HasSDCard(void); + +/** @brief Get current active storage location. + * Returns whether internal flash or SD card is currently used for + * file storage operations. + * @note Location is determined at initialization time. + * This is thread-safe. + * @return MEMORY_LOCATION_INTERNAL for internal flash + * MEMORY_LOCATION_SD_CARD for SD card + */ +MemoryManager_Location_t MemoryManager_GetStorageLocation(void); + +/** @brief Get base path for current storage location. + * Returns the mount point path for file operations. + * Use this path as base for all file access. + * @note Path is valid until MemoryManager_Deinit() is called. + * NULL if not initialized + * Path is constant and can be cached. + * Append filename with e.g. "/storage/image.png". + * Always prepend this path to filename for file operations. + * @return "/storage" for internal flash + * "/sdcard" for SD card + */ +const char *MemoryManager_GetStoragePath(void); + +/** @brief Get storage usage information for current location. + * Retrieves total, used, and free space in bytes, plus usage percentage + * for the active storage location. + * @note Storage must be mounted to get usage information. + * Values are approximate for wear-leveled storage. + * @param p_Usage Pointer to MemoryManager_Usage_t structure to populate + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Usage is NULL + * ESP_ERR_NOT_FOUND if storage not found + * ESP_FAIL if filesystem not mounted + */ +esp_err_t MemoryManager_GetStorageUsage(MemoryManager_Usage_t *p_Usage); + +/** @brief Get coredump partition usage information. + * Returns size of stored crash dump data if any exists in the + * dedicated coredump partition. + * @note Coredump partition is separate from application storage. + * Use MemoryManager_EraseCoredump() to clear crash dumps. + * @param p_Usage Pointer to MemoryManager_Usage_t structure to populate + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Usage is NULL + * ESP_ERR_NOT_FOUND if coredump partition not found + */ +esp_err_t MemoryManager_GetCoredumpUsage(MemoryManager_Usage_t *p_Usage); + +/** @brief Lock filesystem for USB Mass Storage access. + * Prevents application from accessing filesystem while USB MSC is active. + * Must be called before enabling USB Mass Storage to prevent corruption. + * @note All application file operations will fail when locked. + * GUI should show "USB Mode Active" warning. + * @warning Do NOT write to filesystem while locked - causes corruption! + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if already locked + */ +esp_err_t MemoryManager_LockFilesystem(void); + +/** @brief Unlock filesystem for application access. + * Allows application to access filesystem again after USB Mass Storage + * is deactivated. + * @note Call this after USB MSC is disconnected. + * Application can resume normal file operations. + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not locked + */ +esp_err_t MemoryManager_UnlockFilesystem(void); + +/** @brief Check if filesystem is locked for USB access. + * Returns current filesystem lock status. Use this to check before + * any file write operations. + * @note This is thread-safe and fast - check before every write. + * Image saving, logging, and config writes should check this. + * @return true if locked (USB active, app must not write) + * false if unlocked (app can write normally) + */ +bool MemoryManager_IsFilesystemLocked(void); + +/** @brief Soft-unmount the active storage VFS filesystem. + * Unmounts the FAT filesystem and unregisters the VFS path, but preserves + * the underlying storage handles (wear leveling handle or SD card handle). + * This allows USB Mass Storage to access the raw storage blocks while + * the VFS path is no longer accessible by the application. + * @note Must be called before USB MSC exposes the storage to the host. + * The storage handles remain valid and can be used by TinyUSB MSC. + * Call MemoryManager_SoftRemountStorage() to restore VFS access. + * @warning File I/O via VFS will fail after this call until remounted. + * Lock the filesystem before calling this to prevent concurrent writes. + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not initialized or storage not mounted + * ESP_ERR_INVALID_ARG if unknown storage location + */ +esp_err_t MemoryManager_SoftUnmountStorage(void); + +/** @brief Soft-remount the active storage VFS filesystem. + * Re-registers the storage with diskio, registers the VFS path, and mounts + * the FAT filesystem using the preserved storage handles from a previous + * MemoryManager_SoftUnmountStorage() call. + * @note Must be called after USB MSC releases the storage. + * Restores full VFS file I/O capability for the application. + * @warning Must only be called after a successful MemoryManager_SoftUnmountStorage(). + * Do not call while USB MSC is still accessing the storage. + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not initialized or handles are invalid + * ESP_ERR_INVALID_ARG if unknown storage location + * ESP_FAIL if FAT filesystem mount fails + */ +esp_err_t MemoryManager_SoftRemountStorage(void); + +/** @brief Erase current storage location completely. + * Deletes all files in the active storage filesystem (internal flash + * or SD card) and reformats it. + * @note Filesystem is automatically reformatted after erase. + * All data loss is permanent - no recovery possible. + * @warning This will delete ALL files including images and logs! + * @return ESP_OK on success + * ESP_ERR_NOT_FOUND if storage not found + * ESP_FAIL if erase or format fails + */ +esp_err_t MemoryManager_EraseStorage(void); + +/** @brief Erase coredump partition completely. + * Deletes any stored crash dumps from the dedicated coredump partition. + * Useful for clearing old crash data or freeing partition space. + * @note Partition will be cleared and ready for new coredumps. + * Does not affect application data or settings. + * @warning Crash dump data will be lost permanently. + * @return ESP_OK on success + * ESP_ERR_NOT_FOUND if coredump partition not found + * ESP_FAIL if erase fails + */ +esp_err_t MemoryManager_EraseCoredump(void); + +/** @brief Get wear leveling handle for internal storage. + * Returns the wear leveling layer handle for direct access to + * internal flash storage statistics and control. + * @note Only valid for internal flash storage (not SD card). + * Used for low-level wear leveling statistics. + * @param p_Handle Pointer to receive the wl_handle_t + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Handle is NULL + * ESP_ERR_INVALID_STATE if not using internal storage + * ESP_ERR_INVALID_STATE if storage not mounted + */ +esp_err_t MemoryManager_GetWearLevelingHandle(wl_handle_t *p_Handle); + +/** @brief Get SD card handle. + * Returns pointer to sdmmc_card_t structure for direct SD card + * access and card information queries. + * @note Only valid when SD card storage is active. + * Use for card info (capacity, speed, manufacturer). + * @param pp_Card Pointer to receive the SD card handle pointer + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pp_Card is NULL + * ESP_ERR_INVALID_STATE if SD card not mounted + */ +esp_err_t MemoryManager_GetSDCardHandle(sdmmc_card_t **pp_Card); + +/** @brief Format the active storage location. + * Reformats the currently active storage (internal flash or SD card). + * @note Use with caution - all data will be lost. Use MemoryManager_EraseStorage() instead for safer erase. + * @warning This will delete ALL files including images and logs! + * @return ESP_OK on success + * ESP_ERR_NOT_FOUND if storage not found + * ESP_FAIL if format fails + */ +esp_err_t MemoryManager_FormatActiveStorage(void); + +#endif /* MEMORYMANAGER_H_ */ diff --git a/main/Application/Manager/Memory/memoryTypes.h b/main/Application/Manager/Memory/memoryTypes.h new file mode 100644 index 0000000..92bcb5c --- /dev/null +++ b/main/Application/Manager/Memory/memoryTypes.h @@ -0,0 +1,49 @@ +/* + * memoryTypes.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Common type definitions for the Memory Manager component. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef MEMORY_TYPES_H +#define MEMORY_TYPES_H + +#include +#include + +#include +#include +#include + +#include +#include +#include + +/** @brief Memory Manager events base. + */ +ESP_EVENT_DECLARE_BASE(MEMORY_EVENTS); + +/** @brief Memory Manager event identifiers. + */ +enum { + MEMORY_EVENT_SD_CARD_MOUNTED, /**< SD card was successfully mounted and is ready for use. */ + MEMORY_EVENT_FLASH_MOUNTED /**< Internal flash storage was mounted and is ready for use. */ +}; + +#endif /* MEMORY_TYPES_H */ diff --git a/main/Application/Manager/Network/DNS/dnsServer.cpp b/main/Application/Manager/Network/DNS/dnsServer.cpp new file mode 100644 index 0000000..a20961a --- /dev/null +++ b/main/Application/Manager/Network/DNS/dnsServer.cpp @@ -0,0 +1,219 @@ +/* + * dnsServer.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Simple DNS server implementation for captive portal. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include "dnsServer.h" + +#define DNS_PORT 53 +#define DNS_BUFFER_SIZE 512 + +/** @brief DNS header structure. + */ +typedef struct { + uint16_t ID; /**< Identification */ + uint16_t Flags; /**< Flags */ + uint16_t QDCount; /**< Number of questions */ + uint16_t ANCount; /**< Number of answer RRs */ + uint16_t NSCount; /**< Number of authority RRs */ + uint16_t ARCount; /**< Number of additional RRs */ +} __attribute__((packed)) DNS_Header_t; + +typedef struct { + bool isRunning; + int Socket; + TaskHandle_t Task; +} DNS_Server_State_t; + +static DNS_Server_State_t _DNS_Server_State; + +static const char *TAG = "DNS-Server"; + +/** @brief DNS server task. + * @param p_Arg Task argument (not used) + */ +static void DNS_Server_Task(void *p_Arg) +{ + uint8_t Buffer[DNS_BUFFER_SIZE]; + struct sockaddr_in ClientAddr; + socklen_t ClientAddrLen = sizeof(ClientAddr); + esp_netif_ip_info_t IP_Info; + esp_netif_t *AP_NetIF; + + _DNS_Server_State.isRunning = true; + + /* Get AP IP address */ + AP_NetIF = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"); + if (AP_NetIF == NULL) { + ESP_LOGE(TAG, "Failed to get AP netif!"); + + vTaskDelete(NULL); + + return; + } + + esp_netif_get_ip_info(AP_NetIF, &IP_Info); + + esp_task_wdt_add(NULL); + + ESP_LOGD(TAG, "DNS server started, redirecting all queries to " IPSTR, IP2STR(&IP_Info.ip)); + + while (_DNS_Server_State.isRunning) { + DNS_Header_t *Header; + int Length; + uint16_t *AnswerPtr; + uint8_t *ResponsePtr; + + esp_task_wdt_reset(); + + Length = recvfrom(_DNS_Server_State.Socket, Buffer, sizeof(Buffer), 0, + (struct sockaddr *)&ClientAddr, &ClientAddrLen); + + /* Timeout or no data - continue loop to reset watchdog */ + if (Length < 0) { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { + continue; + } + + ESP_LOGW(TAG, "recvfrom error: %d", errno); + + continue; + } + + if (Length < sizeof(DNS_Header_t)) { + continue; + } + + Header = reinterpret_cast(Buffer); + Header->Flags = htons(0x8180); + Header->ANCount = Header->QDCount; + Header->NSCount = 0; + Header->ARCount = 0; + + ResponsePtr = Buffer + sizeof(DNS_Header_t); + while (ResponsePtr < (Buffer + Length) && (*ResponsePtr != 0)) { + ResponsePtr += *ResponsePtr + 1; + } + ResponsePtr += 5; + + AnswerPtr = reinterpret_cast(ResponsePtr); + *AnswerPtr++ = htons(0xC00C); /* Name pointer to question */ + *AnswerPtr++ = htons(0x0001); /* Type A */ + *AnswerPtr++ = htons(0x0001); /* Class IN */ + *AnswerPtr++ = htons(0x0000); /* TTL high */ + *AnswerPtr++ = htons(0x003C); /* TTL low (60 seconds) */ + *AnswerPtr++ = htons(0x0004); /* Data length */ + + memcpy(AnswerPtr, &IP_Info.ip.addr, sizeof(IP_Info.ip.addr)); + ResponsePtr = reinterpret_cast(AnswerPtr) + sizeof(IP_Info.ip.addr); + + sendto(_DNS_Server_State.Socket, Buffer, ResponsePtr - Buffer, 0, + (struct sockaddr *)&ClientAddr, ClientAddrLen); + + vTaskDelay(pdMS_TO_TICKS(10)); + } + + ESP_LOGD(TAG, "DNS server task exiting"); + + esp_task_wdt_delete(NULL); + + vTaskDelete(NULL); +} + +esp_err_t DNS_Server_Start(void) +{ + struct sockaddr_in ServerAddr; + + if (_DNS_Server_State.isRunning) { + ESP_LOGW(TAG, "DNS server already running"); + + return ESP_OK; + } + + _DNS_Server_State.Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (_DNS_Server_State.Socket < 0) { + ESP_LOGE(TAG, "Failed to create socket: %d!", errno); + + return ESP_FAIL; + } + + /* Set socket timeout to allow watchdog reset */ + struct timeval Timeout; + Timeout.tv_sec = 1; + Timeout.tv_usec = 0; + if (setsockopt(_DNS_Server_State.Socket, SOL_SOCKET, SO_RCVTIMEO, &Timeout, sizeof(Timeout)) < 0) { + ESP_LOGW(TAG, "Failed to set socket timeout: %d!", errno); + } + + memset(&ServerAddr, 0, sizeof(ServerAddr)); + ServerAddr.sin_family = AF_INET; + ServerAddr.sin_addr.s_addr = INADDR_ANY; + ServerAddr.sin_port = htons(DNS_PORT); + + if (bind(_DNS_Server_State.Socket, (struct sockaddr *)&ServerAddr, sizeof(ServerAddr)) < 0) { + ESP_LOGE(TAG, "Failed to bind socket: %d!", errno); + + close(_DNS_Server_State.Socket); + + return ESP_FAIL; + } + + if (xTaskCreatePinnedToCore(DNS_Server_Task, "DNS_Server", 4096, NULL, 3, &_DNS_Server_State.Task, 1) != pdPASS) { + ESP_LOGE(TAG, "Failed to create DNS server task!"); + + close(_DNS_Server_State.Socket); + _DNS_Server_State.isRunning = false; + + return ESP_ERR_NO_MEM; + } + + ESP_LOGD(TAG, "DNS server initialized"); + + return ESP_OK; +} + +void DNS_Server_Stop(void) +{ + if (_DNS_Server_State.isRunning == false) { + return; + } + + _DNS_Server_State.isRunning = false; + + if (_DNS_Server_State.Socket >= 0) { + close(_DNS_Server_State.Socket); + _DNS_Server_State.Socket = -1; + } + + ESP_LOGD(TAG, "DNS server stopped"); +} diff --git a/main/Application/Manager/Network/DNS/dnsServer.h b/main/Application/Manager/Network/DNS/dnsServer.h new file mode 100644 index 0000000..14f1a3f --- /dev/null +++ b/main/Application/Manager/Network/DNS/dnsServer.h @@ -0,0 +1,39 @@ +/* + * dnsServer.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Simple DNS server for captive portal. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef DNS_SERVER_H_ +#define DNS_SERVER_H_ + +#include + +/** @brief Start the DNS server for captive portal. + * Redirects all DNS queries to the ESP32's IP address. + * @return ESP_OK on success + */ +esp_err_t DNS_Server_Start(void); + +/** @brief Stop the DNS server. + */ +void DNS_Server_Stop(void); + +#endif /* DNS_SERVER_H_ */ diff --git a/main/Application/Manager/Network/Provisioning/provisionHandlers.cpp b/main/Application/Manager/Network/Provisioning/provisionHandlers.cpp new file mode 100644 index 0000000..3564a28 --- /dev/null +++ b/main/Application/Manager/Network/Provisioning/provisionHandlers.cpp @@ -0,0 +1,295 @@ +/* + * provisionHandlers.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: HTTP provisioning endpoint handlers. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "provisionHandlers.h" +#include "../networkTypes.h" +#include "../networkManager.h" +#include "../../Settings/settingsManager.h" + +static const char *TAG = "Provision-Handlers"; + +extern const uint8_t provision_html_start[] asm("_binary_provision_html_start"); +extern const uint8_t provision_html_end[] asm("_binary_provision_html_end"); +extern const uint8_t logo_png_start[] asm("_binary_logo_png_start"); +extern const uint8_t logo_png_end[] asm("_binary_logo_png_end"); + +/** @brief Send JSON response. + * @param p_Request HTTP request handle + * @param p_JSON JSON object to send + * @param StatusCode HTTP status code + * @return ESP_OK on success + */ +static esp_err_t Provision_Handler_Send_JSON_Response(httpd_req_t *p_Request, cJSON *p_JSON, int StatusCode) +{ + esp_err_t Error; + char *ResponseStr; + + ResponseStr = cJSON_PrintUnformatted(p_JSON); + if (ResponseStr == NULL) { + httpd_resp_send_500(p_Request); + return ESP_ERR_NO_MEM; + } + + httpd_resp_set_status(p_Request, StatusCode == 200 ? HTTPD_200 : HTTPD_400); + httpd_resp_set_type(p_Request, "application/json"); + httpd_resp_set_hdr(p_Request, "Access-Control-Allow-Origin", "*"); + + Error = httpd_resp_send(p_Request, ResponseStr, strlen(ResponseStr)); + + free(ResponseStr); + cJSON_Delete(p_JSON); + + return Error; +} + +esp_err_t Provision_Handler_Root(httpd_req_t *p_Request) +{ + httpd_resp_set_type(p_Request, "text/html"); + httpd_resp_set_hdr(p_Request, "Cache-Control", "no-cache"); + + return httpd_resp_send(p_Request, (const char *)provision_html_start, provision_html_end - provision_html_start); +} + +esp_err_t Provision_Handler_Logo(httpd_req_t *p_Request) +{ + httpd_resp_set_type(p_Request, "image/png+xml"); + httpd_resp_set_hdr(p_Request, "Cache-Control", "public, max-age=86400"); + + return httpd_resp_send(p_Request, reinterpret_cast(logo_png_start), logo_png_end - logo_png_start); +} + +esp_err_t Provision_Handler_CaptivePortal(httpd_req_t *p_Request) +{ + char address[26] = {0}; + esp_netif_t *ap_netif; + + ap_netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"); + if (ap_netif != NULL) { + esp_netif_ip_info_t ip_info; + + if (esp_netif_get_ip_info(ap_netif, &ip_info) == ESP_OK) { + snprintf(address, sizeof(address) - 1, "http://" IPSTR, IP2STR(&ip_info.ip)); + } else { + strncpy(address, "http://192.168.4.1", sizeof(address) - 1); + } + } else { + strncpy(address, "http://192.168.4.1", sizeof(address) - 1); + } + + ESP_LOGD(TAG, "Captive portal redirect to %s", address); + + /* Redirect captive portal detection to main page */ + httpd_resp_set_status(p_Request, "302 Found"); + httpd_resp_set_hdr(p_Request, "Location", address); + httpd_resp_send(p_Request, NULL, 0); + + return ESP_OK; +} + +esp_err_t Provision_Handler_Scan(httpd_req_t *p_Request) +{ + esp_err_t Error; + wifi_scan_config_t ScanConfig; + uint16_t APCount = 0; + uint16_t MaxAPCount = 50; + wifi_ap_record_t *APList; + + memset(&ScanConfig, 0, sizeof(ScanConfig)); + ScanConfig.show_hidden = true; /* Also scan for hidden networks */ + ScanConfig.scan_type = WIFI_SCAN_TYPE_ACTIVE; /* Active scan for better detection */ + ScanConfig.scan_time.active.min = 120; /* Minimum scan time per channel (ms) */ + ScanConfig.scan_time.active.max = 150; /* Maximum scan time per channel (ms) */ + + /* Start async scan */ + Error = esp_wifi_scan_start(&ScanConfig, false); + if (Error != ESP_OK) { + cJSON *Response; + + ESP_LOGW(TAG, "Scan start failed: %d", Error); + + Response = cJSON_CreateObject(); + cJSON_AddArrayToObject(Response, "networks"); + + return Provision_Handler_Send_JSON_Response(p_Request, Response, 200); + } + + /* Wait for scan to complete (max 15 seconds for thorough scan) */ + for (uint8_t i = 0; i < 150; i++) { + vTaskDelay(pdMS_TO_TICKS(100)); + if (esp_wifi_scan_get_ap_num(&APCount) == ESP_OK) { + break; + } + } + + if (APCount == 0) { + cJSON *Response; + + ESP_LOGI(TAG, "No networks found in scan"); + + Response = cJSON_CreateObject(); + cJSON_AddArrayToObject(Response, "networks"); + + return Provision_Handler_Send_JSON_Response(p_Request, Response, 200); + } + + ESP_LOGI(TAG, "Found %d networks", APCount); + + /* Limit to maximum to prevent memory issues */ + if (APCount > MaxAPCount) { + ESP_LOGW(TAG, "Found %d networks, limiting to %d", APCount, MaxAPCount); + + APCount = MaxAPCount; + } + + APList = static_cast(malloc(sizeof(wifi_ap_record_t) * APCount)); + if (APList == NULL) { + httpd_resp_send_500(p_Request); + + return ESP_ERR_NO_MEM; + } + + esp_wifi_scan_get_ap_records(&APCount, APList); + + cJSON *Response = cJSON_CreateObject(); + cJSON *Networks = cJSON_AddArrayToObject(Response, "networks"); + + for (uint16_t i = 0; i < APCount; i++) { + cJSON *Network = cJSON_CreateObject(); + cJSON_AddStringToObject(Network, "ssid", (const char *)APList[i].ssid); + cJSON_AddNumberToObject(Network, "rssi", APList[i].rssi); + cJSON_AddNumberToObject(Network, "channel", APList[i].primary); + cJSON_AddStringToObject(Network, "auth", + APList[i].authmode == WIFI_AUTH_OPEN ? "open" : "secured"); + + cJSON_AddItemToArray(Networks, Network); + } + + free(APList); + + return Provision_Handler_Send_JSON_Response(p_Request, Response, 200); +} + +esp_err_t Provision_Handler_Connect(httpd_req_t *p_Request) +{ + char *Buffer = NULL; + cJSON *JSON = NULL; + cJSON *SSID_JSON = NULL; + cJSON *Password_JSON = NULL; + cJSON *Response = NULL; + esp_err_t Error = ESP_OK; + uint32_t Caps; + int Received; + Settings_WiFi_t WiFiSettings; + + if (p_Request->content_len > 512) { + httpd_resp_send_err(p_Request, HTTPD_400_BAD_REQUEST, "Request too large"); + + return ESP_FAIL; + } + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + #else + Caps = MALLOC_CAP_8BIT; + #endif + + Buffer = static_cast(heap_caps_malloc(p_Request->content_len + 1, Caps)); + if (Buffer == NULL) { + httpd_resp_send_500(p_Request); + + return ESP_ERR_NO_MEM; + } + + Received = httpd_req_recv(p_Request, Buffer, p_Request->content_len); + if (Received <= 0) { + heap_caps_free(Buffer); + httpd_resp_send_err(p_Request, HTTPD_400_BAD_REQUEST, "Failed to read request"); + + return ESP_FAIL; + } + + Buffer[Received] = '\0'; + + JSON = cJSON_Parse(Buffer); + free(Buffer); + + if (JSON == NULL) { + httpd_resp_send_err(p_Request, HTTPD_400_BAD_REQUEST, "Invalid JSON"); + + return ESP_FAIL; + } + + SSID_JSON = cJSON_GetObjectItem(JSON, "ssid"); + Password_JSON = cJSON_GetObjectItem(JSON, "password"); + + if ((cJSON_IsString(SSID_JSON) == false) || (cJSON_IsString(Password_JSON) == false)) { + cJSON_Delete(JSON); + httpd_resp_send_err(p_Request, HTTPD_400_BAD_REQUEST, "Missing ssid or password"); + + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Provisioning request: SSID=%s", SSID_JSON->valuestring); + + Response = cJSON_CreateObject(); + + SettingsManager_GetWiFi(&WiFiSettings); + + strncpy(WiFiSettings.SSID, SSID_JSON->valuestring, sizeof(WiFiSettings.SSID) - 1); + strncpy(WiFiSettings.Password, Password_JSON->valuestring, sizeof(WiFiSettings.Password) - 1); + + SettingsManager_UpdateWiFi(&WiFiSettings); + + Error = SettingsManager_Save(); + if (Error == ESP_OK) { + ESP_LOGI(TAG, "WiFi credentials saved to NVS"); + + cJSON_AddBoolToObject(Response, "success", true); + cJSON_AddStringToObject(Response, "message", "Credentials saved, connecting to WiFi..."); + + Provision_Handler_Send_JSON_Response(p_Request, Response, 200); + + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_PROV_SUCCESS, NULL, 0, portMAX_DELAY); + } else { + ESP_LOGE(TAG, "Failed to save credentials to NVS: %d!", Error); + + cJSON_AddBoolToObject(Response, "success", false); + cJSON_AddStringToObject(Response, "error", "NVS storage full, please reset device"); + Provision_Handler_Send_JSON_Response(p_Request, Response, 500); + } + + cJSON_Delete(JSON); + + return Error; +} diff --git a/main/Application/Manager/Network/Provisioning/provisionHandlers.h b/main/Application/Manager/Network/Provisioning/provisionHandlers.h new file mode 100644 index 0000000..7d15d90 --- /dev/null +++ b/main/Application/Manager/Network/Provisioning/provisionHandlers.h @@ -0,0 +1,60 @@ +/* + * provisionHandlers.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: HTTP provisioning endpoint handlers. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef PROVISION_HANDLERS_H_ +#define PROVISION_HANDLERS_H_ + +#include + +/** @brief Serve provision HTML page. + * @param p_Request HTTP request handle + * @return ESP_OK on success + */ +esp_err_t Provision_Handler_Root(httpd_req_t *p_Request); + +/** @brief Serve PyroVision logo SVG. + * @param p_Request HTTP request handle + * @return ESP_OK on success + */ +esp_err_t Provision_Handler_Logo(httpd_req_t *p_Request); + +/** @brief Handle WiFi scan request. + * @param p_Request HTTP request handle + * @return ESP_OK on success + */ +esp_err_t Provision_Handler_Scan(httpd_req_t *p_Request); + +/** @brief Handle WiFi connect request. + * @param p_Request HTTP request handle + * @return ESP_OK on success + */ +esp_err_t Provision_Handler_Connect(httpd_req_t *p_Request); + +/** @brief Handle captive portal detection. + * Responds to Android/iOS captive portal checks. + * @param p_Request HTTP request handle + * @return ESP_OK on success + */ +esp_err_t Provision_Handler_CaptivePortal(httpd_req_t *p_Request); + +#endif /* PROVISION_HANDLERS_H_ */ diff --git a/main/Application/Manager/Network/Provisioning/provisioning.cpp b/main/Application/Manager/Network/Provisioning/provisioning.cpp new file mode 100644 index 0000000..a741d25 --- /dev/null +++ b/main/Application/Manager/Network/Provisioning/provisioning.cpp @@ -0,0 +1,380 @@ +/* + * provisioning.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: WiFi provisioning implementation using ESP-IDF unified provisioning. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include "../networkManager.h" +#include "../Server/server.h" +#include "../DNS/dnsServer.h" +#include "provisioning.h" +#include "Settings/settingsManager.h" + +typedef struct { + bool isInitialized; + bool isActive; + bool usePortal; + char Name[32]; + uint32_t TimeOut; + esp_timer_handle_t TimeoutTimer; + wifi_sta_config_t WiFi_STA_Config; + bool hasCredentials; +} Provisioning_State_t; + +static Provisioning_State_t _Provisioning_State; + +static const char *TAG = "Provisioning"; + +/** @brief Timeout timer callback. + * @param p_Arg Timer argument + */ +static void on_TimeoutTimer_Handler(void *p_Arg) +{ + ESP_LOGD(TAG, "Provisioning timeout reached"); + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_PROV_TIMEOUT, NULL, 0, pdMS_TO_TICKS(100)); +} + +/** @brief Provisioning event handler. + * @param p_Arg User argument + * @param event_base Event base + * @param event_id Event ID + * @param event_data Event data + */ +static void on_Prov_Event(void *p_Arg, esp_event_base_t EventBase, int32_t EventId, void *p_EventData) +{ + switch (EventId) { + case WIFI_PROV_START: { + ESP_LOGD(TAG, "Provisioning started"); + + break; + } + case WIFI_PROV_CRED_RECV: { + wifi_sta_config_t *wifi_cfg = static_cast(p_EventData); + + if (wifi_cfg != NULL) { + memcpy(&_Provisioning_State.WiFi_STA_Config.ssid, wifi_cfg->ssid, sizeof(wifi_cfg->ssid)); + memcpy(&_Provisioning_State.WiFi_STA_Config.password, wifi_cfg->password, sizeof(wifi_cfg->password)); + + _Provisioning_State.hasCredentials = true; + + ESP_LOGD(TAG, "Received WiFi credentials - SSID: %s", _Provisioning_State.WiFi_STA_Config.ssid); + } else { + ESP_LOGE(TAG, "WiFi config in CRED_RECV is NULL!"); + } + + break; + } + case WIFI_PROV_CRED_FAIL: { + wifi_prov_sta_fail_reason_t *reason = static_cast(p_EventData); + + ESP_LOGE(TAG, "Provisioning failed! Reason: %s", (*reason == WIFI_PROV_STA_AUTH_ERROR) ? "Auth Error" : "AP Not Found"); + + break; + } + case WIFI_PROV_CRED_SUCCESS: { + Settings_WiFi_t WiFiSettings; + + ESP_LOGD(TAG, "Provisioning successful"); + + if (_Provisioning_State.TimeoutTimer != NULL) { + esp_timer_stop(_Provisioning_State.TimeoutTimer); + } + + /* Copy SSID and password from saved configuration */ + if (_Provisioning_State.hasCredentials) { + SettingsManager_GetWiFi(&WiFiSettings); + + strncpy(WiFiSettings.SSID, (const char *)_Provisioning_State.WiFi_STA_Config.ssid, sizeof(WiFiSettings.SSID) - 1); + strncpy(WiFiSettings.Password, (const char *)_Provisioning_State.WiFi_STA_Config.password, + sizeof(WiFiSettings.Password) - 1); + + SettingsManager_UpdateWiFi(&WiFiSettings); + } else { + ESP_LOGE(TAG, "No WiFi credentials available!"); + } + + break; + } + case WIFI_PROV_END: { + ESP_LOGD(TAG, "Provisioning ended"); + + wifi_prov_mgr_deinit(); + + _Provisioning_State.isActive = false; + + break; + } + default: { + break; + } + } +} + +esp_err_t Provisioning_Init(void) +{ + Settings_Provisioning_t ProvisioningSettings; + + if (_Provisioning_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + SettingsManager_GetProvisioning(&ProvisioningSettings); + + ESP_LOGD(TAG, "Initializing Provisioning Manager"); + strncpy(_Provisioning_State.Name, ProvisioningSettings.Name, + sizeof(_Provisioning_State.Name) - 1); + _Provisioning_State.TimeOut = ProvisioningSettings.Timeout; + _Provisioning_State.usePortal = true; + + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &on_Prov_Event, NULL)); + + const esp_timer_create_args_t timer_args = { + .callback = on_TimeoutTimer_Handler, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "prov_timeout", + .skip_unhandled_events = false + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &_Provisioning_State.TimeoutTimer)); + + _Provisioning_State.isInitialized = true; + + ESP_LOGD(TAG, "Provisioning initialized"); + + return ESP_OK; +} + +void Provisioning_Deinit(void) +{ + if (_Provisioning_State.isInitialized == false) { + return; + } + + Provisioning_Stop(); + + vTaskDelay(pdMS_TO_TICKS(200)); + + wifi_prov_mgr_deinit(); + + if (_Provisioning_State.TimeoutTimer != NULL) { + esp_timer_delete(_Provisioning_State.TimeoutTimer); + _Provisioning_State.TimeoutTimer = NULL; + } + + esp_event_handler_unregister(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &on_Prov_Event); + + _Provisioning_State.isInitialized = false; +} + +esp_err_t Provisioning_Start(void) +{ + esp_err_t Error; + uint8_t MAC[6]; + wifi_config_t Config; + Network_HTTP_Server_Config_t ServerConfig; + esp_netif_t *NetIf; + + if (_Provisioning_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (_Provisioning_State.isActive) { + ESP_LOGW(TAG, "Provisioning already active"); + + return ESP_OK; + } + + ESP_LOGD(TAG, "Starting captive portal provisioning"); + + NetworkManager_GetMAC(MAC); + + /* Create unique SSID */ + snprintf(reinterpret_cast(Config.ap.ssid), sizeof(Config.ap.ssid), + "%.26s-%02X%02X", _Provisioning_State.Name, MAC[4], MAC[5]); + Config.ap.ssid_len = strlen(reinterpret_cast(Config.ap.ssid)); + Config.ap.channel = 1; + Config.ap.authmode = WIFI_AUTH_OPEN; + Config.ap.max_connection = 4; + + ESP_LOGD(TAG, "Starting SoftAP with SSID: %s", Config.ap.ssid); + + /* Set APSTA mode to allow WiFi scanning while AP is active */ + Error = esp_wifi_set_mode(WIFI_MODE_APSTA); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to set APSTA mode: %d!", Error); + + return Error; + } + + Error = esp_wifi_set_config(WIFI_IF_AP, &Config); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to set AP config: %d!", Error); + + return Error; + } + + Error = esp_wifi_start(); + if ((Error != ESP_OK) && (Error != ESP_ERR_WIFI_STATE)) { + ESP_LOGE(TAG, "Failed to start WiFi: %d!", Error); + + return Error; + } + + vTaskDelay(pdMS_TO_TICKS(100)); + + /* Start HTTP server with minimal configuration for provisioning */ + ServerConfig.Port = 80; + ServerConfig.MaxClients = 1; /* Only one client for provisioning */ + ServerConfig.EnableCORS = true; + memset(ServerConfig.API_Key, '\0', sizeof(ServerConfig.API_Key)); + + /* Initialize HTTP server first (without WebSocket) */ + Error = HTTP_Server_Init(&ServerConfig); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to init HTTP server: 0x%x!", Error); + + esp_wifi_stop(); + + return Error; + } + + Error = HTTP_Server_Start(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to start HTTP server: 0x%x!", Error); + + HTTP_Server_Deinit(); + esp_wifi_stop(); + + return Error; + } + + /* Start DNS server for captive portal */ + Error = DNS_Server_Start(); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to start DNS server: %d!", Error); + /* Continue anyway, DNS is not critical */ + } + + _Provisioning_State.isActive = true; + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_PROV_STARTED, NULL, 0, portMAX_DELAY); + + /* Start timeout timer */ + if (_Provisioning_State.TimeoutTimer != NULL) { + esp_timer_start_once(_Provisioning_State.TimeoutTimer, _Provisioning_State.TimeOut * 1000000ULL); + } + + NetIf = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"); + if (NetIf != NULL) { + esp_netif_ip_info_t IP; + + if (esp_netif_get_ip_info(NetIf, &IP) == ESP_OK) { + ESP_LOGD(TAG, "Captive portal started at http://" IPSTR, IP2STR(&IP.ip)); + } else { + ESP_LOGD(TAG, "Captive portal started at http://192.168.4.1"); + } + } else { + ESP_LOGD(TAG, "Captive portal started at http://192.168.4.1"); + } + + return ESP_OK; +} + +esp_err_t Provisioning_Stop(void) +{ + if (_Provisioning_State.isActive == false) { + return ESP_OK; + } + + ESP_LOGD(TAG, "Stopping Provisioning"); + + _Provisioning_State.isActive = false; + + if (_Provisioning_State.usePortal) { + ESP_LOGD(TAG, "Stopping DNS server"); + DNS_Server_Stop(); + + ESP_LOGD(TAG, "Stopping HTTP server"); + HTTP_Server_Stop(); + + ESP_LOGD(TAG, "Stopping WiFi"); + esp_wifi_stop(); + + /* Small delay for WiFi to fully stop */ + vTaskDelay(pdMS_TO_TICKS(50)); + + HTTP_Server_Deinit(); + + ESP_LOGD(TAG, "Provisioning stopped"); + } else { + if (_Provisioning_State.TimeoutTimer != NULL) { + esp_timer_stop(_Provisioning_State.TimeoutTimer); + } + + wifi_prov_mgr_stop_provisioning(); + } + + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_PROV_STOPPED, NULL, 0, portMAX_DELAY); + + return ESP_OK; +} + +bool Provisioning_isProvisioned(void) +{ + bool isProvisioned; + wifi_prov_mgr_config_t Config; + + memset(&Config, 0, sizeof(wifi_prov_mgr_config_t)); + + Config.scheme = wifi_prov_scheme_softap; + Config.scheme_event_handler = WIFI_PROV_EVENT_HANDLER_NONE; + + if (wifi_prov_mgr_init(Config) == ESP_OK) { + wifi_prov_mgr_is_provisioned(&isProvisioned); + wifi_prov_mgr_deinit(); + } + + ESP_LOGD(TAG, "Provisioned: %s", isProvisioned ? "true" : "false"); + + return isProvisioned; +} + +esp_err_t Provisioning_Reset(void) +{ + ESP_LOGD(TAG, "Resetting Provisioning"); + + wifi_prov_mgr_reset_provisioning(); + + return ESP_OK; +} + +bool Provisioning_IsActive(void) +{ + return _Provisioning_State.isActive; +} \ No newline at end of file diff --git a/main/Application/Manager/Network/Provisioning/provisioning.h b/main/Application/Manager/Network/Provisioning/provisioning.h new file mode 100644 index 0000000..211b8cb --- /dev/null +++ b/main/Application/Manager/Network/Provisioning/provisioning.h @@ -0,0 +1,66 @@ +/* + * provisioning.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: WiFi provisioning via BLE and SoftAP. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef PROVISIONING_H_ +#define PROVISIONING_H_ + +#include +#include + +#include "../networkTypes.h" + +/** @brief Initialize provisioning manager. + * @return ESP_OK on success + */ +esp_err_t Provisioning_Init(void); + +/** @brief Deinitialize provisioning manager. + */ +void Provisioning_Deinit(void); + +/** @brief Start provisioning. + * @return ESP_OK on success + */ +esp_err_t Provisioning_Start(void); + +/** @brief Stop provisioning. + * @return ESP_OK on success + */ +esp_err_t Provisioning_Stop(void); + +/** @brief Check if device is provisioned. + * @return true if provisioned + */ +bool Provisioning_isProvisioned(void); + +/** @brief Reset provisioning (clear stored credentials). + * @return ESP_OK on success + */ +esp_err_t Provisioning_Reset(void); + +/** @brief Check if provisioning is active. + * @return true if provisioning is running + */ +bool Provisioning_IsActive(void); + +#endif /* PROVISIONING_H_ */ diff --git a/main/Application/Manager/Network/SNTP/sntp.cpp b/main/Application/Manager/Network/SNTP/sntp.cpp new file mode 100644 index 0000000..e8300d0 --- /dev/null +++ b/main/Application/Manager/Network/SNTP/sntp.cpp @@ -0,0 +1,116 @@ +/* + * sntp.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: SNTP client implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include + +#include "sntp.h" +#include "Settings/settingsManager.h" + +ESP_EVENT_DEFINE_BASE(SNTP_EVENTS); + +static const char *TAG = "SNTP"; + +/** @brief SNTP time synchronization callback. + * @param p_tv Pointer to timeval structure with synchronized time + */ +static void on_SNTP_Time_Sync(struct timeval *p_tv) +{ + ESP_LOGD(TAG, "Time synchronized via SNTP"); + + esp_event_post(SNTP_EVENTS, SNTP_EVENT_SNTP_SYNCED, p_tv, sizeof(struct timeval), portMAX_DELAY); +} + +esp_err_t SNTP_Init(const char *p_Timezone, const char *p_Server, uint32_t SyncInterval) +{ + esp_sntp_setoperatingmode(SNTP_OPMODE_POLL); + esp_sntp_setservername(0, p_Server); + esp_sntp_set_time_sync_notification_cb(on_SNTP_Time_Sync); + esp_sntp_init(); + + SNTP_SetTimezone(p_Timezone); + + /* Setup automatic sync timer if interval is specified */ + esp_sntp_set_sync_interval(SyncInterval); + + return ESP_OK; +} + +esp_err_t SNTP_Deinit(void) +{ + esp_sntp_stop(); + + return ESP_OK; +} + +esp_err_t SNTP_GetTime(uint8_t Retries) +{ + time_t Now; + struct tm TimeInfo; + uint8_t Retry; + + ESP_LOGD(TAG, "Initializing SNTP"); + + memset(&TimeInfo, 0, sizeof(struct tm)); + + Retry = 0; + while ((TimeInfo.tm_year < (2016 - 1900)) && (++Retry < Retries)) { + ESP_LOGD(TAG, "Waiting for system time... (%d/%d)", Retry, Retries); + vTaskDelay(pdMS_TO_TICKS(2000)); + time(&Now); + localtime_r(&Now, &TimeInfo); + } + + if (Retry == Retries) { + ESP_LOGW(TAG, "Failed to synchronize time!"); + + return ESP_FAIL; + } + + ESP_LOGD(TAG, "Time synchronized successfully"); + + time(&Now); + localtime_r(&Now, &TimeInfo); + + return ESP_OK; +} + +void SNTP_SetTimezone(const char *p_Timezone) +{ + Settings_System_t SystemSettings; + SettingsManager_ChangeNotification_t Changed; + + setenv("TZ", p_Timezone, 1); + tzset(); + + SettingsManager_GetSystem(&SystemSettings); + + memset(SystemSettings.Timezone, '\0', sizeof(SystemSettings.Timezone)); + memcpy(&SystemSettings.Timezone, p_Timezone, strlen(p_Timezone) + 1); + + Changed.ID = SETTINGS_ID_SNTP_TIMEZONE; + Changed.Value = 0; + SettingsManager_UpdateSystem(&SystemSettings, &Changed); + SettingsManager_Save(); +} \ No newline at end of file diff --git a/main/Application/Manager/Network/SNTP/sntp.h b/main/Application/Manager/Network/SNTP/sntp.h new file mode 100644 index 0000000..6517d6a --- /dev/null +++ b/main/Application/Manager/Network/SNTP/sntp.h @@ -0,0 +1,68 @@ +/* + * sntp.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: SNTP client implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef SNTP_H_ +#define SNTP_H_ + +#include +#include + +#include + +/** @brief SNTP events base. + */ +ESP_EVENT_DECLARE_BASE(SNTP_EVENTS); + +/** @brief SNTP event types (used as event IDs in SNTP_EVENTS base). + */ +typedef enum { + SNTP_EVENT_SNTP_SYNCED, /**< SNTP time synchronization completed + Data is of type struct timeval */ +} SNTP_Event_t; + +/** @brief Initialize SNTP and event handlers. + * @param p_Timezone Timezone string (e.g. "CET-1CEST,M3.5.0,M10.5.0/3") + * @param p_Server NTP server address (e.g. "pool.ntp.org") + * @param SyncInterval SNTP sync interval in seconds + * @return ESP_OK on success + */ +esp_err_t SNTP_Init(const char *p_Timezone = "CET-1CEST,M3.5.0,M10.5.0/3", const char *p_Server = "pool.ntp.org", + uint32_t SyncInterval = 3600); + +/** @brief Deinitialize SNTP and event handlers. + * @return ESP_OK on success + */ +esp_err_t SNTP_Deinit(void); + +/** @brief Initialize SNTP and synchronize time. + * @param Retries Number of retries to get time + * @return ESP_OK on success + */ +esp_err_t SNTP_GetTime(uint8_t Retries); + +/** @brief Set SNTP timezone. + * @param p_Timezone Timezone string (e.g. "CET-1CEST,M3.5.0,M10.5.0/3") + */ +void SNTP_SetTimezone(const char *p_Timezone); + +#endif /* SNTP_H_ */ \ No newline at end of file diff --git a/main/Application/Manager/Network/Server/HTTP/http_server.cpp b/main/Application/Manager/Network/Server/HTTP/http_server.cpp new file mode 100644 index 0000000..53dc6c8 --- /dev/null +++ b/main/Application/Manager/Network/Server/HTTP/http_server.cpp @@ -0,0 +1,745 @@ +/* + * http_server.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: HTTP server implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "http_server.h" +#include "../ImageEncoder/imageEncoder.h" +#include "../../Provisioning/provisionHandlers.h" +#include "../../SNTP/sntp.h" + +#define HTTP_SERVER_API_BASE_PATH "/api/v1" +#define HTTP_SERVER_API_KEY_HEADER "X-API-Key" + +typedef struct { + bool isInitialized; + bool isRunning; + httpd_handle_t Handle; + Network_HTTP_Server_Config_t Config; + Network_Thermal_Frame_t *ThermalFrame; + uint32_t RequestCount; + uint32_t StartTime; +} HTTP_Server_State_t; + +static HTTP_Server_State_t _HTTP_Server_State; + +static const char *TAG = "HTTP-Server"; + +/** @brief Check API key authentication. + * @param p_Request HTTP request handle + * @return true if authenticated + */ +static bool HTTP_Server_CheckAuth(httpd_req_t *p_Request) +{ + std::string ApiKey(64, '\0'); + + if (_HTTP_Server_State.Config.API_Key[0] == '\0') { + return true; + } else if (httpd_req_get_hdr_value_str(p_Request, HTTP_SERVER_API_KEY_HEADER, &ApiKey[0], ApiKey.size()) != ESP_OK) { + return false; + } + + ApiKey.resize(strlen(ApiKey.c_str())); + + return (ApiKey == _HTTP_Server_State.Config.API_Key); +} + +/** @brief Send JSON response. + * @param p_Request HTTP request handle + * @param p_JSON JSON object to send + * @param StatusCode HTTP status code + * @return ESP_OK on success + */ +static esp_err_t HTTP_Server_SendJSON(httpd_req_t *p_Request, cJSON *p_JSON, int StatusCode) +{ + esp_err_t Error; + char *JSON; + + JSON = cJSON_PrintUnformatted(p_JSON); + if (JSON == NULL) { + httpd_resp_send_err(p_Request, HTTPD_500_INTERNAL_SERVER_ERROR, "JSON encoding failed"); + + return ESP_FAIL; + } + + if (StatusCode != 200) { + httpd_resp_set_status(p_Request, std::to_string(StatusCode).c_str()); + } + + httpd_resp_set_type(p_Request, "application/json"); + + if (_HTTP_Server_State.Config.EnableCORS) { + httpd_resp_set_hdr(p_Request, "Access-Control-Allow-Origin", "*"); + } + + Error = httpd_resp_sendstr(p_Request, JSON); + cJSON_free(JSON); + + return Error; +} + +/** @brief Send error response. + * @param p_Request HTTP request handle + * @param StatusCode HTTP status code + * @param p_Message Error message + * @return ESP_OK on success + */ +static esp_err_t HTTP_Server_SendError(httpd_req_t *p_Request, int StatusCode, const char *p_Message) +{ + esp_err_t Error; + cJSON *JSON; + + JSON = cJSON_CreateObject(); + if (JSON == NULL) { + httpd_resp_send_err(p_Request, HTTPD_500_INTERNAL_SERVER_ERROR, "JSON allocation failed"); + + return ESP_FAIL; + } + + cJSON_AddStringToObject(JSON, "error", p_Message); + cJSON_AddNumberToObject(JSON, "code", StatusCode); + + Error = HTTP_Server_SendJSON(p_Request, JSON, StatusCode); + cJSON_Delete(JSON); + + return Error; +} + +/** @brief Parse JSON from request body. + * @param p_Request HTTP request handle + * @return cJSON object or NULL on error + */ +static cJSON *HTTP_Server_ParseJSON(httpd_req_t *p_Request) +{ + char *Buffer; + cJSON *JSON; + uint32_t Caps; + + if ((p_Request->content_len <= 0) || (p_Request->content_len > 4096)) { + return NULL; + } + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + #else + Caps = MALLOC_CAP_8BIT; + #endif + + Buffer = static_cast(heap_caps_malloc(p_Request->content_len + 1, Caps)); + if (Buffer == NULL) { + return NULL; + } + + if (httpd_req_recv(p_Request, Buffer, p_Request->content_len) != p_Request->content_len) { + heap_caps_free(Buffer); + + return NULL; + } + + Buffer[p_Request->content_len] = '\0'; + JSON = cJSON_Parse(Buffer); + heap_caps_free(Buffer); + + return JSON; +} + +/** @brief Handler for POST /api/v1/time. + * @param p_Request HTTP request handle + * @return ESP_OK on success + */ +static esp_err_t HTTP_Handler_Time(httpd_req_t *p_Request) +{ + esp_err_t Error; + cJSON *JSON; + cJSON *Epoch; + cJSON *Timezone; + + _HTTP_Server_State.RequestCount++; + + if (HTTP_Server_CheckAuth(p_Request) == false) { + return HTTP_Server_SendError(p_Request, 401, "Unauthorized"); + } + + JSON = HTTP_Server_ParseJSON(p_Request); + if (JSON == NULL) { + return HTTP_Server_SendError(p_Request, 400, "Invalid JSON"); + } + + Epoch = cJSON_GetObjectItem(JSON, "epoch"); + Timezone = cJSON_GetObjectItem(JSON, "timezone"); + + if (cJSON_IsNumber(Epoch) == false) { + cJSON_Delete(JSON); + + return HTTP_Server_SendError(p_Request, 400, "Missing epoch field"); + } + + /* Set system time */ + struct timeval tv = { + .tv_sec = static_cast(Epoch->valuedouble), + .tv_usec = 0, + }; + settimeofday(&tv, NULL); + + /* Set timezone if provided */ + if (cJSON_IsString(Timezone) && (Timezone->valuestring != NULL)) { + SNTP_SetTimezone(Timezone->valuestring); + } + + ESP_LOGD(TAG, "Time set to epoch: %f", Epoch->valuedouble); + + cJSON_Delete(JSON); + + /* Send response */ + JSON = cJSON_CreateObject(); + cJSON_AddStringToObject(JSON, "status", "ok"); + Error = HTTP_Server_SendJSON(p_Request, JSON, 200); + cJSON_Delete(JSON); + + return Error; +} + +/** @brief Handler for GET /api/v1/image. + * @param p_Request HTTP request handle + * @return ESP_OK on success + */ +static esp_err_t HTTP_Handler_Image(httpd_req_t *p_Request) +{ + esp_err_t Error; + Network_Encoded_Image_t Encoded; + std::string Query(128, '\0'); + Settings_Image_Format_t Format = IMAGE_FORMAT_JPEG; + Server_Palette_t Palette = PALETTE_IRON; + + _HTTP_Server_State.RequestCount++; + + if (HTTP_Server_CheckAuth(p_Request) == false) { + return HTTP_Server_SendError(p_Request, 401, "Unauthorized"); + } else if (_HTTP_Server_State.ThermalFrame == NULL) { + return HTTP_Server_SendError(p_Request, 503, "No thermal data available"); + } + + if (httpd_req_get_url_query_str(p_Request, &Query[0], Query.size()) == ESP_OK) { + std::string Param(32, '\0'); + + Query.resize(strlen(Query.c_str())); + if (httpd_query_key_value(Query.c_str(), "format", &Param[0], Param.size()) == ESP_OK) { + Param.resize(strlen(Param.c_str())); + + if (Param == "png") { + Format = IMAGE_FORMAT_PNG; + } else if (Param == "raw") { + Format = IMAGE_FORMAT_RAW; + } + } + + if (httpd_query_key_value(Query.c_str(), "palette", &Param[0], Param.size()) == ESP_OK) { + Param.resize(strlen(Param.c_str())); + + if (Param == "gray") { + Palette = PALETTE_GRAY; + } else if (Param == "rainbow") { + Palette = PALETTE_RAINBOW; + } + } + } + + if (xSemaphoreTake(_HTTP_Server_State.ThermalFrame->Mutex, pdMS_TO_TICKS(100)) != pdTRUE) { + return HTTP_Server_SendError(p_Request, 503, "Frame busy"); + } + + Error = ImageEncoder_Encode(_HTTP_Server_State.ThermalFrame, Format, Palette, &Encoded); + + xSemaphoreGive(_HTTP_Server_State.ThermalFrame->Mutex); + + if (Error != ESP_OK) { + return HTTP_Server_SendError(p_Request, 500, "Image encoding failed"); + } + + switch (Format) { + case IMAGE_FORMAT_JPEG: { + httpd_resp_set_type(p_Request, "image/jpeg"); + + break; + } + case IMAGE_FORMAT_PNG: { + httpd_resp_set_type(p_Request, "image/png"); + + break; + } + case IMAGE_FORMAT_RAW: { + httpd_resp_set_type(p_Request, "application/octet-stream"); + + break; + } + case IMAGE_FORMAT_BITMAP: { + httpd_resp_set_type(p_Request, "image/bmp"); + + break; + } + } + + if (_HTTP_Server_State.Config.EnableCORS) { + httpd_resp_set_hdr(p_Request, "Access-Control-Allow-Origin", "*"); + } + + Error = httpd_resp_send(p_Request, reinterpret_cast(Encoded.Data), Encoded.Size); + + ImageEncoder_Free(&Encoded); + + return Error; +} + +/** @brief Handler for GET /api/v1/telemetry. + * @param p_Request HTTP request handle + * @return ESP_OK on success + */ +static esp_err_t HTTP_Handler_Telemetry(httpd_req_t *p_Request) +{ + esp_err_t Error; + cJSON *JSON; + cJSON *Card; + wifi_ap_record_t Info; + + _HTTP_Server_State.RequestCount++; + + if (HTTP_Server_CheckAuth(p_Request) == false) { + return HTTP_Server_SendError(p_Request, 401, "Unauthorized"); + } + + JSON = cJSON_CreateObject(); + + cJSON_AddNumberToObject(JSON, "uptime_s", esp_timer_get_time() / 1000000); + + /* Sensor temperature (from thermal frame if available) */ + if (_HTTP_Server_State.ThermalFrame != NULL) { + // TODO + //cJSON_AddNumberToObject(JSON, "sensor_temp_c", _HTTP_Server_State.ThermalFrame->temp_avg); + } + + cJSON_AddNumberToObject(JSON, "supply_voltage_v", 0); + + if (esp_wifi_sta_get_ap_info(&Info) == ESP_OK) { + cJSON_AddNumberToObject(JSON, "wifi_rssi_dbm", Info.rssi); + } else { + cJSON_AddNumberToObject(JSON, "wifi_rssi_dbm", 0); + } + + Card = cJSON_CreateObject(); + cJSON_AddBoolToObject(Card, "present", false); + cJSON_AddNumberToObject(Card, "free_mb", 0); + cJSON_AddItemToObject(JSON, "sdcard", Card); + + Error = HTTP_Server_SendJSON(p_Request, JSON, 200); + cJSON_Delete(JSON); + + return Error; +} + +/** @brief Handler for POST /api/v1/update (OTA). + * @param p_Request HTTP request handle + * @return ESP_OK on success + */ +static esp_err_t HTTP_Handler_Update(httpd_req_t *p_Request) +{ + uint32_t Caps; + int Received; + int Total_Received; + esp_err_t Error; + esp_ota_handle_t ota_handle; + char *Buffer; + cJSON *JSON; + + _HTTP_Server_State.RequestCount++; + + if (HTTP_Server_CheckAuth(p_Request) == false) { + return HTTP_Server_SendError(p_Request, 401, "Unauthorized"); + } + + const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL); + + if (update_partition == NULL) { + return HTTP_Server_SendError(p_Request, 500, "No OTA partition found"); + } + + ESP_LOGI(TAG, "OTA update starting, partition: %s", update_partition->label); + + Error = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &ota_handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_begin failed: %d!", Error); + + return HTTP_Server_SendError(p_Request, 500, "OTA begin failed"); + } + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + #else + Caps = MALLOC_CAP_8BIT; + #endif + + /* Receive firmware data */ + Buffer = static_cast(heap_caps_malloc(1024, Caps)); + if (Buffer == NULL) { + esp_ota_abort(ota_handle); + + return HTTP_Server_SendError(p_Request, 500, "Memory allocation failed"); + } + + Received = 0; + Total_Received = 0; + while (Total_Received < p_Request->content_len) { + Received = httpd_req_recv(p_Request, Buffer, 1024); + if (Received <= 0) { + if (Received == HTTPD_SOCK_ERR_TIMEOUT) { + continue; + } + + heap_caps_free(Buffer); + esp_ota_abort(ota_handle); + + return HTTP_Server_SendError(p_Request, 500, "Receive failed"); + } + + Error = esp_ota_write(ota_handle, Buffer, Received); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_write failed: %d!", Error); + + heap_caps_free(Buffer); + esp_ota_abort(ota_handle); + + return HTTP_Server_SendError(p_Request, 500, "OTA write failed"); + } + + Total_Received += Received; + ESP_LOGD(TAG, "OTA progress: %d/%d bytes", Total_Received, p_Request->content_len); + } + + heap_caps_free(Buffer); + + Error = esp_ota_end(ota_handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_end failed: %d", Error); + + return HTTP_Server_SendError(p_Request, 500, "OTA end failed"); + } + + Error = esp_ota_set_boot_partition(update_partition); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %d!", Error); + + return HTTP_Server_SendError(p_Request, 500, "Set boot partition failed"); + } + + ESP_LOGI(TAG, "OTA update successful, rebooting..."); + + /* Send response */ + JSON = cJSON_CreateObject(); + cJSON_AddStringToObject(JSON, "status", "updating"); + cJSON_AddStringToObject(JSON, "message", "Firmware upload successful. Device will reboot after update."); + Error = HTTP_Server_SendJSON(p_Request, JSON, 200); + cJSON_Delete(JSON); + + /* Reboot after response is sent */ + vTaskDelay(pdMS_TO_TICKS(1000)); + esp_restart(); + + return Error; +} + +/** @brief Handler for CORS preflight requests. + * @param p_Request HTTP request handle + * @return ESP_OK on success + */ +static esp_err_t HTTP_Handler_Options(httpd_req_t *p_Request) +{ + httpd_resp_set_hdr(p_Request, "Access-Control-Allow-Origin", "*"); + httpd_resp_set_hdr(p_Request, "Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + httpd_resp_set_hdr(p_Request, "Access-Control-Allow-Headers", "Content-Type, X-API-Key"); + httpd_resp_set_hdr(p_Request, "Access-Control-Max-Age", "86400"); + httpd_resp_send(p_Request, NULL, 0); + + return ESP_OK; +} + +static const httpd_uri_t _URI_Time = { + .uri = HTTP_SERVER_API_BASE_PATH "/time", + .method = HTTP_POST, + .handler = HTTP_Handler_Time, + .user_ctx = NULL, + .is_websocket = false, + .handle_ws_control_frames = false, + .supported_subprotocol = NULL, +}; + +static const httpd_uri_t _URI_Image = { + .uri = HTTP_SERVER_API_BASE_PATH "/image", + .method = HTTP_GET, + .handler = HTTP_Handler_Image, + .user_ctx = NULL, + .is_websocket = false, + .handle_ws_control_frames = false, + .supported_subprotocol = NULL, +}; + +static const httpd_uri_t _URI_Telemetry = { + .uri = HTTP_SERVER_API_BASE_PATH "/telemetry", + .method = HTTP_GET, + .handler = HTTP_Handler_Telemetry, + .user_ctx = NULL, + .is_websocket = false, + .handle_ws_control_frames = false, + .supported_subprotocol = NULL, +}; + +static const httpd_uri_t _URI_Update = { + .uri = HTTP_SERVER_API_BASE_PATH "/update", + .method = HTTP_POST, + .handler = HTTP_Handler_Update, + .user_ctx = NULL, + .is_websocket = false, + .handle_ws_control_frames = false, + .supported_subprotocol = NULL, +}; + +static const httpd_uri_t _URI_Options = { + .uri = HTTP_SERVER_API_BASE_PATH "/*", + .method = HTTP_OPTIONS, + .handler = HTTP_Handler_Options, + .user_ctx = NULL, + .is_websocket = false, + .handle_ws_control_frames = false, + .supported_subprotocol = NULL, +}; + +static const httpd_uri_t _URI_Provision_Root = { + .uri = "/", + .method = HTTP_GET, + .handler = Provision_Handler_Root, + .user_ctx = NULL, + .is_websocket = false, + .handle_ws_control_frames = false, + .supported_subprotocol = NULL, +}; + +static const httpd_uri_t _URI_Provision_Logo = { + .uri = "/logo.png", + .method = HTTP_GET, + .handler = Provision_Handler_Logo, + .user_ctx = NULL, + .is_websocket = false, + .handle_ws_control_frames = false, + .supported_subprotocol = NULL, +}; + +static const httpd_uri_t _URI_Provision_Scan = { + .uri = HTTP_SERVER_API_BASE_PATH "/provision/scan", + .method = HTTP_GET, + .handler = Provision_Handler_Scan, + .user_ctx = NULL, + .is_websocket = false, + .handle_ws_control_frames = false, + .supported_subprotocol = NULL, +}; + +static const httpd_uri_t _URI_Provision_Connect = { + .uri = HTTP_SERVER_API_BASE_PATH "/provision", + .method = HTTP_POST, + .handler = Provision_Handler_Connect, + .user_ctx = NULL, + .is_websocket = false, + .handle_ws_control_frames = false, + .supported_subprotocol = NULL, +}; + +static const httpd_uri_t _URI_CaptivePortal_Generate204 = { + .uri = "/generate_204", + .method = HTTP_GET, + .handler = Provision_Handler_CaptivePortal, + .user_ctx = NULL, + .is_websocket = false, + .handle_ws_control_frames = false, + .supported_subprotocol = NULL, +}; + +static const httpd_uri_t _URI_CaptivePortal_Generate204_NoUnderscore = { + .uri = "/generate204", + .method = HTTP_GET, + .handler = Provision_Handler_CaptivePortal, + .user_ctx = NULL, + .is_websocket = false, + .handle_ws_control_frames = false, + .supported_subprotocol = NULL, +}; + +esp_err_t HTTP_Server_Init(const Network_HTTP_Server_Config_t *p_Config) +{ + if (p_Config == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (_HTTP_Server_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized, updating config"); + + memcpy(&_HTTP_Server_State.Config, p_Config, sizeof(Network_HTTP_Server_Config_t)); + + return ESP_OK; + } + + ESP_LOGD(TAG, "Initializing HTTP server"); + + memcpy(&_HTTP_Server_State.Config, p_Config, sizeof(Network_HTTP_Server_Config_t)); + _HTTP_Server_State.Handle = NULL; + _HTTP_Server_State.ThermalFrame = NULL; + _HTTP_Server_State.RequestCount = 0; + _HTTP_Server_State.isInitialized = true; + + return ESP_OK; +} + +void HTTP_Server_Deinit(void) +{ + if (_HTTP_Server_State.isInitialized == false) { + return; + } + + HTTP_Server_Stop(); + _HTTP_Server_State.isInitialized = false; + + ESP_LOGD(TAG, "HTTP server deinitialized"); +} + +esp_err_t HTTP_Server_Start(void) +{ + esp_err_t Error; + + if (_HTTP_Server_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (_HTTP_Server_State.isRunning) { + ESP_LOGW(TAG, "Server already running"); + + return ESP_OK; + } + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = _HTTP_Server_State.Config.Port; + config.max_uri_handlers = 12; + + /* Use only 2 sockets for provisioning */ + config.max_open_sockets = 2; + config.lru_purge_enable = true; + config.uri_match_fn = httpd_uri_match_wildcard; + + /* Very short timeouts to allow quick shutdown */ + config.recv_wait_timeout = 1; + config.send_wait_timeout = 1; + config.keep_alive_enable = false; /* Disable keep-alive to reduce memory */ + + /* Increased stack and header size for Android captive portal requests */ + config.stack_size = 5120; + config.max_resp_headers = 8; + config.max_req_hdr_len = 1024; /* Android sends large headers */ + + /* Core affinity and priority - run on core 1 to avoid blocking IDLE0 */ + config.ctrl_port = 0; + config.core_id = 1; + config.task_priority = 3; + + ESP_LOGD(TAG, "Starting HTTP server on port %d", config.server_port); + + Error = httpd_start(&_HTTP_Server_State.Handle, &config); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to start HTTP server: %d!", Error); + + return Error; + } + + /* Register URI handlers */ + httpd_register_uri_handler(_HTTP_Server_State.Handle, &_URI_Provision_Root); + httpd_register_uri_handler(_HTTP_Server_State.Handle, &_URI_Provision_Logo); + httpd_register_uri_handler(_HTTP_Server_State.Handle, &_URI_Provision_Scan); + httpd_register_uri_handler(_HTTP_Server_State.Handle, &_URI_Provision_Connect); + httpd_register_uri_handler(_HTTP_Server_State.Handle, &_URI_CaptivePortal_Generate204); + httpd_register_uri_handler(_HTTP_Server_State.Handle, &_URI_CaptivePortal_Generate204_NoUnderscore); + httpd_register_uri_handler(_HTTP_Server_State.Handle, &_URI_Time); + httpd_register_uri_handler(_HTTP_Server_State.Handle, &_URI_Image); + httpd_register_uri_handler(_HTTP_Server_State.Handle, &_URI_Telemetry); + httpd_register_uri_handler(_HTTP_Server_State.Handle, &_URI_Update); + + if (_HTTP_Server_State.Config.EnableCORS) { + httpd_register_uri_handler(_HTTP_Server_State.Handle, &_URI_Options); + } + + _HTTP_Server_State.isRunning = true; + _HTTP_Server_State.StartTime = esp_timer_get_time() / 1000000; + + ESP_LOGD(TAG, "HTTP server started"); + + return ESP_OK; +} + +esp_err_t HTTP_Server_Stop(void) +{ + esp_err_t Error; + + if (_HTTP_Server_State.isRunning == false) { + return ESP_OK; + } + + ESP_LOGD(TAG, "Stopping HTTP server"); + + _HTTP_Server_State.isRunning = false; + + if (_HTTP_Server_State.Handle != NULL) { + Error = httpd_stop(_HTTP_Server_State.Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to stop HTTP server: %d", Error); + } + _HTTP_Server_State.Handle = NULL; + } + + return ESP_OK; +} + +bool HTTP_Server_IsRunning(void) +{ + return _HTTP_Server_State.isRunning; +} + +void HTTP_Server_SetThermalFrame(Network_Thermal_Frame_t *p_Frame) +{ + _HTTP_Server_State.ThermalFrame = p_Frame; +} + +httpd_handle_t HTTP_Server_GetHandle(void) +{ + return _HTTP_Server_State.Handle; +} diff --git a/main/Application/Manager/Network/Server/HTTP/http_server.h b/main/Application/Manager/Network/Server/HTTP/http_server.h new file mode 100644 index 0000000..236d4be --- /dev/null +++ b/main/Application/Manager/Network/Server/HTTP/http_server.h @@ -0,0 +1,67 @@ +/* + * http_server.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: HTTP server handler. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef HTTP_SERVER_H_ +#define HTTP_SERVER_H_ + +#include +#include + +#include "../../networkTypes.h" + +/** @brief Initialize the HTTP server. + * @param p_Config Pointer to server configuration. + * @return ESP_OK on success. + */ +esp_err_t HTTP_Server_Init(const Network_HTTP_Server_Config_t *p_Config); + +/** @brief Deinitialize the HTTP server. + */ +void HTTP_Server_Deinit(void); + +/** @brief Start the HTTP server. + * @return ESP_OK on success + */ +esp_err_t HTTP_Server_Start(void); + +/** @brief Stop the HTTP server. + * @return ESP_OK on success + */ +esp_err_t HTTP_Server_Stop(void); + +/** @brief Check if the HTTP server is running. + * @return true if running + */ +bool HTTP_Server_IsRunning(void); + +/** @brief Set thermal frame data for image endpoint. + * @param p_Frame Pointer to thermal frame data. + */ +void HTTP_Server_SetThermalFrame(Network_Thermal_Frame_t *p_Frame); + +/** @brief Get the HTTP server handle for WebSocket registration. + * @return HTTP server handle or NULL if not running + */ +httpd_handle_t HTTP_Server_GetHandle(void); + +#endif /* HTTP_SERVER_H_ */ diff --git a/main/Application/Manager/Network/Server/ImageEncoder/Bitmap/bitmapEncoder.cpp b/main/Application/Manager/Network/Server/ImageEncoder/Bitmap/bitmapEncoder.cpp new file mode 100644 index 0000000..87b7f93 --- /dev/null +++ b/main/Application/Manager/Network/Server/ImageEncoder/Bitmap/bitmapEncoder.cpp @@ -0,0 +1,149 @@ +/* + * bitmapEncoder.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Bitmap (BMP) encoder implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include + +#include + +#include "bitmapEncoder.h" + +/** @brief BMP file header structure (14 bytes). + */ +typedef struct { + uint16_t Type; /**< File type, must be 0x4D42 ('BM') */ + uint32_t Size; /**< File size in bytes */ + uint16_t Reserved1; /**< Reserved, must be 0 */ + uint16_t Reserved2; /**< Reserved, must be 0 */ + uint32_t OffBits; /**< Offset to pixel data from beginning of file */ +} __attribute__((packed)) BMP_FileHeader_t; + +/** @brief BMP info header structure (40 bytes, BITMAPINFOHEADER). + */ +typedef struct { + uint32_t Size; /**< Size of this header (40 bytes) */ + int32_t Width; /**< Image width in pixels */ + int32_t Height; /**< Image height in pixels (positive = bottom-up) */ + uint16_t Planes; /**< Number of color planes, must be 1 */ + uint16_t BitCount; /**< Bits per pixel (24 for RGB888) */ + uint32_t Compression; /**< Compression method (0 = uncompressed) */ + uint32_t SizeImage; /**< Size of raw bitmap data (can be 0 for uncompressed) */ + int32_t XPelsPerMeter; /**< Horizontal resolution (pixels/meter) */ + int32_t YPelsPerMeter; /**< Vertical resolution (pixels/meter) */ + uint32_t ClrUsed; /**< Number of colors in palette (0 = default) */ + uint32_t ClrImportant; /**< Number of important colors (0 = all) */ +} __attribute__((packed)) BMP_InfoHeader_t; + +static const char *TAG = "Bitmap-Encoder"; + +esp_err_t BitmapEncoder_Encode(const uint8_t *p_RGB, uint16_t Width, uint16_t Height, + uint8_t **p_Output, size_t *p_Size) +{ + BMP_FileHeader_t FileHeader; + BMP_InfoHeader_t InfoHeader; + uint32_t RowSize; + uint32_t PixelDataSize; + uint32_t TotalSize; + uint32_t Caps; + uint8_t *p_BMP; + uint8_t *p_Dest; + const uint8_t *p_Src; + + if ((p_RGB == NULL) || (p_Output == NULL) || (p_Size == NULL)) { + return ESP_ERR_INVALID_ARG; + } else if ((Width == 0) || (Height == 0)) { + return ESP_ERR_INVALID_ARG; + } + + /* Calculate row size (must be multiple of 4 bytes) */ + RowSize = ((Width * 3 + 3) / 4) * 4; + PixelDataSize = RowSize * Height; + TotalSize = sizeof(BMP_FileHeader_t) + sizeof(BMP_InfoHeader_t) + PixelDataSize; + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + #else + Caps = MALLOC_CAP_8BIT; + #endif + + /* Allocate output buffer */ + p_BMP = static_cast(heap_caps_malloc(TotalSize, Caps)); + if (p_BMP == NULL) { + ESP_LOGE(TAG, "Failed to allocate BMP output buffer (%lu bytes)!", TotalSize); + + return ESP_ERR_NO_MEM; + } + + /* Fill file header */ + memset(&FileHeader, 0, sizeof(BMP_FileHeader_t)); + FileHeader.Type = 0x4D42; /* 'BM' */ + FileHeader.Size = TotalSize; + FileHeader.OffBits = sizeof(BMP_FileHeader_t) + sizeof(BMP_InfoHeader_t); + + /* Fill info header */ + memset(&InfoHeader, 0, sizeof(BMP_InfoHeader_t)); + InfoHeader.Size = sizeof(BMP_InfoHeader_t); + InfoHeader.Width = Width; + InfoHeader.Height = Height; /* Positive = bottom-up */ + InfoHeader.Planes = 1; + InfoHeader.BitCount = 24; /* 24-bit RGB */ + InfoHeader.Compression = 0; /* Uncompressed */ + InfoHeader.SizeImage = PixelDataSize; + InfoHeader.XPelsPerMeter = 2835; /* 72 DPI */ + InfoHeader.YPelsPerMeter = 2835; + InfoHeader.ClrUsed = 0; + InfoHeader.ClrImportant = 0; + + /* Write headers to output buffer */ + memcpy(p_BMP, &FileHeader, sizeof(BMP_FileHeader_t)); + memcpy(p_BMP + sizeof(BMP_FileHeader_t), &InfoHeader, sizeof(BMP_InfoHeader_t)); + + /* Convert RGB to BGR and write pixel data (bottom-to-top) */ + p_Dest = p_BMP + sizeof(BMP_FileHeader_t) + sizeof(BMP_InfoHeader_t); + + for (int32_t y = Height - 1; y >= 0; y--) { + p_Src = p_RGB + (y * Width * 3); + + for (uint16_t x = 0; x < Width; x++) { + /* BMP uses BGR order instead of RGB */ + p_Dest[x * 3 + 0] = p_Src[x * 3 + 2]; /* Blue */ + p_Dest[x * 3 + 1] = p_Src[x * 3 + 1]; /* Green */ + p_Dest[x * 3 + 2] = p_Src[x * 3 + 0]; /* Red */ + } + + /* Add row padding (if needed) */ + uint32_t Padding = RowSize - (Width * 3); + if (Padding > 0) { + memset(p_Dest + (Width * 3), 0, Padding); + } + + p_Dest += RowSize; + } + + *p_Output = p_BMP; + *p_Size = TotalSize; + + ESP_LOGD(TAG, "Encoded %dx%d BMP image, size=%lu bytes", Width, Height, TotalSize); + + return ESP_OK; +} diff --git a/main/Application/Manager/Network/Server/ImageEncoder/Bitmap/bitmapEncoder.h b/main/Application/Manager/Network/Server/ImageEncoder/Bitmap/bitmapEncoder.h new file mode 100644 index 0000000..116d697 --- /dev/null +++ b/main/Application/Manager/Network/Server/ImageEncoder/Bitmap/bitmapEncoder.h @@ -0,0 +1,49 @@ +/* + * bitmapEncoder.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Bitmap (BMP) encoder for thermal images. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef BITMAP_ENCODER_H_ +#define BITMAP_ENCODER_H_ + +#include + +#include +#include + +/** @brief Encode RGB888 data to BMP (Windows Bitmap) format. + * Creates an uncompressed 24-bit RGB bitmap with standard headers. + * @note BMP format: 14-byte file header + 40-byte info header + pixel data. + * Pixel data is stored bottom-to-top, left-to-right, in BGR order. + * Rows are padded to 4-byte boundaries. + * @param p_RGB Pointer to RGB888 pixel data (width * height * 3 bytes) + * @param Width Image width in pixels + * @param Height Image height in pixels + * @param p_Output Pointer to store encoded BMP data pointer + * @param p_Size Pointer to store encoded data size + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if parameters are invalid + * ESP_ERR_NO_MEM if memory allocation fails + */ +esp_err_t BitmapEncoder_Encode(const uint8_t *p_RGB, uint16_t Width, uint16_t Height, + uint8_t **p_Output, size_t *p_Size); + +#endif /* BITMAP_ENCODER_H_ */ diff --git a/main/Application/Manager/Network/Server/ImageEncoder/JPEG/jpegEncoder.cpp b/main/Application/Manager/Network/Server/ImageEncoder/JPEG/jpegEncoder.cpp new file mode 100644 index 0000000..30c8f49 --- /dev/null +++ b/main/Application/Manager/Network/Server/ImageEncoder/JPEG/jpegEncoder.cpp @@ -0,0 +1,104 @@ +/* + * jpegEncoder.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: JPEG encoder implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include + +#include "jpegEncoder.h" + +static const char *TAG = "JPEG-Encoder"; + +esp_err_t JPEGEncoder_Encode(const uint8_t *p_RGB, uint16_t Width, uint16_t Height, + uint8_t Quality, uint8_t **p_Output, size_t *p_Size) +{ + int Size; + uint32_t Caps; + jpeg_error_t Error; + jpeg_enc_handle_t Encoder = NULL; + jpeg_enc_config_t EncoderConfig = { + .width = Width, + .height = Height, + .src_type = JPEG_PIXEL_FORMAT_RGB888, + .subsampling = JPEG_SUBSAMPLE_420, + .quality = Quality, + .rotate = JPEG_ROTATE_0D, + .task_enable = false, + .hfm_task_priority = 0, + .hfm_task_core = 0, + }; + + if ((p_RGB == NULL) || (p_Output == NULL) || (p_Size == NULL)) { + return ESP_ERR_INVALID_ARG; + } else if ((Width == 0) || (Height == 0)) { + return ESP_ERR_INVALID_ARG; + } + + /* Clamp quality to valid range */ + if (Quality < 1) { + Quality = 1; + } else if (Quality > 100) { + Quality = 100; + } + + Error = jpeg_enc_open(&EncoderConfig, &Encoder); + if (Error != JPEG_ERR_OK) { + ESP_LOGE(TAG, "Failed to open JPEG encoder: %d!", Error); + + return ESP_FAIL; + } + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + #else + Caps = MALLOC_CAP_8BIT; + #endif + + *p_Output = static_cast(heap_caps_malloc(Width * Height * 3, Caps)); + if (*p_Output == NULL) { + ESP_LOGE(TAG, "Failed to allocate JPEG output buffer!"); + + jpeg_enc_close(Encoder); + + return ESP_ERR_NO_MEM; + } + + Error = jpeg_enc_process(Encoder, p_RGB, Width * Height * 3, *p_Output, Width * Height * 3, &Size); + + jpeg_enc_close(Encoder); + + if (Error != JPEG_ERR_OK) { + ESP_LOGE(TAG, "JPEG encoding failed: %d!", Error); + + heap_caps_free(*p_Output); + *p_Output = NULL; + + return ESP_FAIL; + } + + *p_Size = Size; + + ESP_LOGD(TAG, "Encoded %dx%d JPEG image, quality=%d, size=%d bytes", Width, Height, Quality, Size); + + return ESP_OK; +} diff --git a/main/Application/Manager/Network/Server/ImageEncoder/JPEG/jpegEncoder.h b/main/Application/Manager/Network/Server/ImageEncoder/JPEG/jpegEncoder.h new file mode 100644 index 0000000..98b9dc7 --- /dev/null +++ b/main/Application/Manager/Network/Server/ImageEncoder/JPEG/jpegEncoder.h @@ -0,0 +1,47 @@ +/* + * jpegEncoder.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: JPEG encoder for thermal images. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef JPEG_ENCODER_H_ +#define JPEG_ENCODER_H_ + +#include + +#include +#include + +/** @brief Encode RGB888 data to JPEG format. + * @param p_RGB Pointer to RGB888 pixel data (width * height * 3 bytes) + * @param Width Image width in pixels + * @param Height Image height in pixels + * @param Quality JPEG quality (1-100, higher is better quality) + * @param p_Output Pointer to store encoded JPEG data pointer + * @param p_Size Pointer to store encoded data size + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if parameters are invalid + * ESP_ERR_NO_MEM if memory allocation fails + * ESP_FAIL if encoding fails + */ +esp_err_t JPEGEncoder_Encode(const uint8_t *p_RGB, uint16_t Width, uint16_t Height, + uint8_t Quality, uint8_t **p_Output, size_t *p_Size); + +#endif /* JPEG_ENCODER_H_ */ diff --git a/main/Application/Manager/Network/Server/ImageEncoder/PNG/pngEncoder.cpp b/main/Application/Manager/Network/Server/ImageEncoder/PNG/pngEncoder.cpp new file mode 100644 index 0000000..ab32a1d --- /dev/null +++ b/main/Application/Manager/Network/Server/ImageEncoder/PNG/pngEncoder.cpp @@ -0,0 +1,75 @@ +/* + * pngEncoder.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: PNG encoder implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include "pngEncoder.h" + +static const char *TAG = "PNG-Encoder"; + +esp_err_t PNGEncoder_Encode(const uint8_t *p_RGB, uint16_t Width, uint16_t Height, + uint8_t **p_Output, size_t *p_Size) +{ + if ((p_RGB == NULL) || (p_Output == NULL) || (p_Size == NULL)) { + return ESP_ERR_INVALID_ARG; + } else if ((Width == 0) || (Height == 0)) { + return ESP_ERR_INVALID_ARG; + } + + /* TODO: Implement PNG encoding using a library such as: + * - lodepng (https://github.com/lvandeve/lodepng) - Simple, single-file + * - libpng (http://www.libpng.org/) - Full-featured, standard library + * - stb_image_write (https://github.com/nothings/stb) - Single-header library + * + * Example integration with lodepng: + * + * #include "lodepng.h" + * + * unsigned char *png_data; + * size_t png_size; + * unsigned error = lodepng_encode24(&png_data, &png_size, p_RGB, Width, Height); + * + * if (error) { + * ESP_LOGE(TAG, "PNG encoding error %u: %s", error, lodepng_error_text(error)); + * return ESP_FAIL; + * } + * + * // Allocate output buffer with proper memory caps + * *p_Output = static_cast(heap_caps_malloc(png_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); + * if (*p_Output == NULL) { + * free(png_data); + * return ESP_ERR_NO_MEM; + * } + * + * memcpy(*p_Output, png_data, png_size); + * free(png_data); + * *p_Size = png_size; + * + * return ESP_OK; + */ + + ESP_LOGW(TAG, "PNG encoding is not yet implemented!"); + ESP_LOGW(TAG, "Please integrate a PNG library (lodepng, libpng, or stb_image_write)"); + + return ESP_ERR_NOT_SUPPORTED; +} diff --git a/main/Application/Manager/Network/Server/ImageEncoder/PNG/pngEncoder.h b/main/Application/Manager/Network/Server/ImageEncoder/PNG/pngEncoder.h new file mode 100644 index 0000000..59e092b --- /dev/null +++ b/main/Application/Manager/Network/Server/ImageEncoder/PNG/pngEncoder.h @@ -0,0 +1,50 @@ +/* + * pngEncoder.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: PNG encoder for thermal images. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef PNG_ENCODER_H_ +#define PNG_ENCODER_H_ + +#include + +#include +#include + +/** @brief Encode RGB888 data to PNG format. + * @note PNG encoding requires an external library (e.g., lodepng, libpng). + * This is currently a placeholder implementation. + * TODO: Integrate PNG encoding library for full PNG support. + * @param p_RGB Pointer to RGB888 pixel data (width * height * 3 bytes) + * @param Width Image width in pixels + * @param Height Image height in pixels + * @param p_Output Pointer to store encoded PNG data pointer + * @param p_Size Pointer to store encoded data size + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if parameters are invalid + * ESP_ERR_NO_MEM if memory allocation fails + * ESP_ERR_NOT_SUPPORTED if PNG encoding is not implemented + * ESP_FAIL if encoding fails + */ +esp_err_t PNGEncoder_Encode(const uint8_t *p_RGB, uint16_t Width, uint16_t Height, + uint8_t **p_Output, size_t *p_Size); + +#endif /* PNG_ENCODER_H_ */ diff --git a/main/Application/Manager/Network/Server/ImageEncoder/imageEncoder.cpp b/main/Application/Manager/Network/Server/ImageEncoder/imageEncoder.cpp new file mode 100644 index 0000000..2723152 --- /dev/null +++ b/main/Application/Manager/Network/Server/ImageEncoder/imageEncoder.cpp @@ -0,0 +1,239 @@ +/* + * image_encoder.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Image encoder implementation (dispatcher for format-specific encoders). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include + +#include + +#include "imageEncoder.h" +#include "JPEG/jpegEncoder.h" +#include "PNG/pngEncoder.h" +#include "Bitmap/bitmapEncoder.h" + +#include "lepton.h" + +typedef struct { + bool isInitialized; + uint8_t JpegQuality; +} Image_Image_Encoder_State_t; + +static Image_Image_Encoder_State_t _Image_Encoder_State; + +static const char *TAG = "Image-Encoder"; + +/** @brief Apply color palette to thermal frame. + * @param p_Frame Pointer to thermal frame (with RGB888 buffer) + * @param Palette Color palette (currently ignored, frame already has correct colors) + * @param p_Output Output RGB buffer (width * height * 3 bytes) + * @return ESP_OK on success + */ +static esp_err_t ImageEncoder_ApplyPalette(const Network_Thermal_Frame_t *p_Frame, + Server_Palette_t Palette, + uint8_t *p_Output) +{ + if ((p_Frame == NULL) || (p_Output == NULL) || (p_Frame->Buffer == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + memcpy(p_Output, p_Frame->Buffer, p_Frame->Width * p_Frame->Height * 3); + + return ESP_OK; +} + +esp_err_t ImageEncoder_Init(uint8_t Quality) +{ + if (_Image_Encoder_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + ESP_LOGD(TAG, "Initializing image encoder, quality=%d", Quality); + + _Image_Encoder_State.JpegQuality = Quality; + if (_Image_Encoder_State.JpegQuality < 1) { + _Image_Encoder_State.JpegQuality = 1; + } + + if (_Image_Encoder_State.JpegQuality > 100) { + _Image_Encoder_State.JpegQuality = 100; + } + + _Image_Encoder_State.isInitialized = true; + + return ESP_OK; +} + +void ImageEncoder_Deinit(void) +{ + if (_Image_Encoder_State.isInitialized == false) { + return; + } + + _Image_Encoder_State.isInitialized = false; + + ESP_LOGD(TAG, "Image encoder deinitialized"); +} + +esp_err_t ImageEncoder_Encode(const Network_Thermal_Frame_t *p_Frame, + Settings_Image_Format_t Format, + Server_Palette_t Palette, + Network_Encoded_Image_t *p_Encoded) +{ + esp_err_t Error; + size_t PixelCount; + size_t EncodedSize; + uint8_t *p_RGB; + uint8_t *p_EncodedData; + uint32_t Caps; + + if ((p_Frame == NULL) || (p_Encoded == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + memset(p_Encoded, 0, sizeof(Network_Encoded_Image_t)); + + PixelCount = p_Frame->Width * p_Frame->Height; + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + #else + Caps = MALLOC_CAP_8BIT; + #endif + + /* Allocate temporary RGB buffer for palette application */ + p_RGB = static_cast(heap_caps_malloc(PixelCount * 3, Caps)); + if (p_RGB == NULL) { + ESP_LOGE(TAG, "Failed to allocate RGB buffer!"); + + return ESP_ERR_NO_MEM; + } + + /* Apply palette to frame */ + Error = ImageEncoder_ApplyPalette(p_Frame, Palette, p_RGB); + if (Error != ESP_OK) { + heap_caps_free(p_RGB); + + return Error; + } + + /* Dispatch to appropriate encoder based on format */ + switch (Format) { + case IMAGE_FORMAT_JPEG: { + Error = JPEGEncoder_Encode(p_RGB, p_Frame->Width, p_Frame->Height, + _Image_Encoder_State.JpegQuality, + &p_EncodedData, &EncodedSize); + if (Error == ESP_OK) { + p_Encoded->Data = p_EncodedData; + p_Encoded->Size = EncodedSize; + p_Encoded->Format = IMAGE_FORMAT_JPEG; + p_Encoded->Width = p_Frame->Width; + p_Encoded->Height = p_Frame->Height; + } + + break; + } + case IMAGE_FORMAT_PNG: { + Error = PNGEncoder_Encode(p_RGB, p_Frame->Width, p_Frame->Height, + &p_EncodedData, &EncodedSize); + if (Error == ESP_OK) { + p_Encoded->Data = p_EncodedData; + p_Encoded->Size = EncodedSize; + p_Encoded->Format = IMAGE_FORMAT_PNG; + p_Encoded->Width = p_Frame->Width; + p_Encoded->Height = p_Frame->Height; + + break; + } else if (Error == ESP_ERR_NOT_SUPPORTED) { + ESP_LOGW(TAG, "PNG format not supported, falling back to RAW"); + /* Fallback to RAW format */ + Error = ESP_OK; + p_Encoded->Data = p_RGB; + p_Encoded->Size = PixelCount * 3; + p_Encoded->Format = IMAGE_FORMAT_RAW; + p_Encoded->Width = p_Frame->Width; + p_Encoded->Height = p_Frame->Height; + + return ESP_OK; /* Don't free p_RGB, it's now owned by p_Encoded */ + } else { + /* Other errors */ + break; + } + } + case IMAGE_FORMAT_BITMAP: { + Error = BitmapEncoder_Encode(p_RGB, p_Frame->Width, p_Frame->Height, + &p_EncodedData, &EncodedSize); + if (Error == ESP_OK) { + p_Encoded->Data = p_EncodedData; + p_Encoded->Size = EncodedSize; + p_Encoded->Format = IMAGE_FORMAT_BITMAP; + p_Encoded->Width = p_Frame->Width; + p_Encoded->Height = p_Frame->Height; + } + + break; + } + case IMAGE_FORMAT_RAW: + default: { + p_Encoded->Data = p_RGB; + p_Encoded->Size = PixelCount * 3; + p_Encoded->Format = IMAGE_FORMAT_RAW; + p_Encoded->Width = p_Frame->Width; + p_Encoded->Height = p_Frame->Height; + + return ESP_OK; /* Don't free p_RGB, it's now owned by p_Encoded */ + } + } + + /* Free temporary RGB buffer (unless it was transferred to output) */ + heap_caps_free(p_RGB); + + return Error; +} + +void ImageEncoder_Free(Network_Encoded_Image_t *p_Encoded) +{ + if (p_Encoded == NULL) { + return; + } else if (p_Encoded->Data != NULL) { + heap_caps_free(p_Encoded->Data); + p_Encoded->Data = NULL; + } + + p_Encoded->Size = 0; +} + +void ImageEncoder_SetQuality(uint8_t Quality) +{ + _Image_Encoder_State.JpegQuality = Quality; + if (_Image_Encoder_State.JpegQuality < 1) { + _Image_Encoder_State.JpegQuality = 1; + } + + if (_Image_Encoder_State.JpegQuality > 100) { + _Image_Encoder_State.JpegQuality = 100; + } + + ESP_LOGD(TAG, "JPEG quality set to %d", _Image_Encoder_State.JpegQuality); +} diff --git a/main/Application/Manager/Network/Server/ImageEncoder/imageEncoder.h b/main/Application/Manager/Network/Server/ImageEncoder/imageEncoder.h new file mode 100644 index 0000000..a9c59d5 --- /dev/null +++ b/main/Application/Manager/Network/Server/ImageEncoder/imageEncoder.h @@ -0,0 +1,64 @@ +/* + * image_encoder.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Image encoder for thermal frames. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef IMAGE_ENCODER_H_ +#define IMAGE_ENCODER_H_ + +#include + +#include "../../networkTypes.h" +#include "Settings/settingsTypes.h" + +/** @brief Initialize the image encoder. + * @param Quality JPEG quality (1-100) + * @return ESP_OK on success + */ +esp_err_t ImageEncoder_Init(uint8_t Quality); + +/** @brief Deinitialize the image encoder. + */ +void ImageEncoder_Deinit(void); + +/** @brief Encode a thermal frame to the specified format. + * @param p_Frame Pointer to thermal frame data + * @param Format Output image format + * @param Palette Color palette to use + * @param p_Encoded Pointer to store encoded image data + * @return ESP_OK on success + */ +esp_err_t ImageEncoder_Encode(const Network_Thermal_Frame_t *p_Frame, + Settings_Image_Format_t Format, + Server_Palette_t Palette, + Network_Encoded_Image_t *p_Encoded); + +/** @brief Free encoded image data. + * @param p_Encoded Pointer to encoded image structure + */ +void ImageEncoder_Free(Network_Encoded_Image_t *p_Encoded); + +/** @brief Set JPEG encoding quality. + * @param Quality Quality value (1-100) + */ +void ImageEncoder_SetQuality(uint8_t Quality); + +#endif /* IMAGE_ENCODER_H_ */ diff --git a/main/Application/Manager/Network/Server/RemoteControl/remoteControl.cpp b/main/Application/Manager/Network/Server/RemoteControl/remoteControl.cpp new file mode 100644 index 0000000..8cce743 --- /dev/null +++ b/main/Application/Manager/Network/Server/RemoteControl/remoteControl.cpp @@ -0,0 +1,471 @@ +/* + * remoteControl.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Common remote control interface implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include +#include +#include + +#include "remoteControl.h" +#include "managers.h" + +static const char *TAG = "RemoteControl"; + +typedef struct { + SemaphoreHandle_t Mutex; + bool isValid; + bool isLocked; +} RemoteControl_State_t; + +static RemoteControl_State_t _RemoteControl_State; + +esp_err_t RemoteControl_GetTemperature(float *p_Temperature) +{ + if (p_Temperature == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (DevicesManager_GetTemperature(p_Temperature) != ESP_OK) { + ESP_LOGE(TAG, "Failed to get temperature from DevicesManager!"); + + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t RemoteControl_GetTime(char *p_TimeStr, size_t MaxLen) +{ + time_t Now; + struct tm TimeInfo; + + if (p_TimeStr == NULL) { + return ESP_ERR_INVALID_ARG; + } + + time(&Now); + localtime_r(&Now, &TimeInfo); + + /* ISO 8601 format: YYYY-MM-DDTHH:MM:SS */ + strftime(p_TimeStr, MaxLen, "%Y-%m-%dT%H:%M:%S", &TimeInfo); + + return ESP_OK; +} + +esp_err_t RemoteControl_SetTime(const char *p_TimeStr) +{ + struct tm TimeInfo; + time_t NewTime; + + if (p_TimeStr == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* Parse ISO 8601 format: YYYY-MM-DDTHH:MM:SS */ + if (strptime(p_TimeStr, "%Y-%m-%dT%H:%M:%S", &TimeInfo) == NULL) { + ESP_LOGE(TAG, "Invalid time format. Expected: YYYY-MM-DDTHH:MM:SS!"); + + return ESP_ERR_INVALID_ARG; + } + + NewTime = mktime(&TimeInfo); + if (NewTime == -1) { + ESP_LOGE(TAG, "Failed to convert time!"); + + return ESP_ERR_INVALID_ARG; + } + + /* Set system time */ + struct timeval tv = { + .tv_sec = NewTime, + .tv_usec = 0, + }; + + settimeofday(&tv, NULL); + + ESP_LOGD(TAG, "System time set to: %s", p_TimeStr); + + return ESP_OK; +} + +esp_err_t RemoteControl_GetBatteryVoltage(int *p_Voltage) +{ + return DevicesManager_GetBatteryVoltage(p_Voltage, NULL); +} + +esp_err_t RemoteControl_GetStateOfCharge(uint8_t *p_SOC) +{ + return DevicesManager_GetStateOfCharge(p_SOC); +} + +esp_err_t RemoteControl_GetOV5640Image(uint8_t **pp_Buffer, size_t *p_Size, Settings_Image_Format_t Format) +{ + if ((pp_Buffer == NULL) || (p_Size == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* TODO: Get image from OV5640 camera */ + ESP_LOGW(TAG, "OV5640 image capture not implemented"); + + return ESP_ERR_NOT_FOUND; +} + +esp_err_t RemoteControl_GetLeptonImage(uint8_t **pp_Buffer, size_t *p_Size, Settings_Image_Format_t Format) +{ + if ((pp_Buffer == NULL) || (p_Size == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* TODO: Get image from Lepton camera via LeptonTask */ + ESP_LOGW(TAG, "Lepton image capture not implemented"); + + return ESP_ERR_NOT_FOUND; +} + +esp_err_t RemoteControl_GetLeptonEmissivity(uint8_t *p_Emissivity) +{ + esp_err_t Error; + Settings_Lepton_t Lepton; + + if (p_Emissivity == NULL) { + return ESP_ERR_INVALID_ARG; + } + + Error = SettingsManager_GetLepton(&Lepton); + if (Error != ESP_OK) { + return Error; + } + + *p_Emissivity = Lepton.CurrentEmissivity; + + return ESP_OK; +} + +esp_err_t RemoteControl_SetLeptonEmissivity(uint8_t Emissivity) +{ + esp_err_t Error; + Settings_Lepton_t Lepton; + SettingsManager_ChangeNotification_t Changed; + + if (Emissivity > 100) { + return ESP_ERR_INVALID_ARG; + } + + Error = SettingsManager_GetLepton(&Lepton); + if(Error != ESP_OK) { + return Error; + } + + Lepton.CurrentEmissivity = Emissivity; + Changed.ID = SETTINGS_ID_LEPTON_EMISSIVITY; + Changed.Value = Emissivity; + + ESP_LOGD(TAG, "Set Lepton emissivity to: %u%%", Emissivity); + + return SettingsManager_UpdateLepton(&Lepton, &Changed); +} + +esp_err_t RemoteControl_GetLeptonSceneStats(cJSON *p_JSON) +{ + if (p_JSON == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* TODO: Get scene statistics from Lepton camera */ + cJSON_AddNumberToObject(p_JSON, "min_temp", 15.5); + cJSON_AddNumberToObject(p_JSON, "max_temp", 35.2); + cJSON_AddNumberToObject(p_JSON, "avg_temp", 22.8); + + return ESP_OK; +} + +esp_err_t RemoteControl_GetLeptonROI(cJSON *p_JSON) +{ + if (p_JSON == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* TODO: Get ROI from Lepton camera */ + cJSON_AddNumberToObject(p_JSON, "x", 40); + cJSON_AddNumberToObject(p_JSON, "y", 30); + cJSON_AddNumberToObject(p_JSON, "width", 80); + cJSON_AddNumberToObject(p_JSON, "height", 60); + + return ESP_OK; +} + +esp_err_t RemoteControl_SetLeptonROI(const cJSON *p_JSON) +{ + if (p_JSON == NULL) { + return ESP_ERR_INVALID_ARG; + } + + cJSON *x = cJSON_GetObjectItem(p_JSON, "x"); + cJSON *y = cJSON_GetObjectItem(p_JSON, "y"); + cJSON *width = cJSON_GetObjectItem(p_JSON, "width"); + cJSON *height = cJSON_GetObjectItem(p_JSON, "height"); + + if ((cJSON_IsNumber(x) == false) || (cJSON_IsNumber(y) == false) || + (cJSON_IsNumber(width) == false) || (cJSON_IsNumber(height) == false)) { + return ESP_ERR_INVALID_ARG; + } + + /* TODO: Set ROI in Lepton camera */ + ESP_LOGI(TAG, "Set Lepton ROI: x=%d, y=%d, w=%d, h=%d", + x->valueint, y->valueint, width->valueint, height->valueint); + + return ESP_OK; +} + +esp_err_t RemoteControl_UpdateSpotmeter(float Min, float Max, float Mean) +{ + /* Initialize mutex on first call */ + if (_RemoteControl_State.Mutex == NULL) { + _RemoteControl_State.Mutex = xSemaphoreCreateMutex(); + if (_RemoteControl_State.Mutex == NULL) { + ESP_LOGE(TAG, "Failed to create mutex for spotmeter data!"); + + return ESP_ERR_NO_MEM; + } + } + + /* Thread-safe update of spotmeter data */ + if (xSemaphoreTake(_RemoteControl_State.Mutex, pdMS_TO_TICKS(100)) == pdTRUE) { + //_RemoteControl_State.Spotmeter.Min = Min; + //_RemoteControl_State.Spotmeter.Max = Max; + //_RemoteControl_State.Spotmeter.Mean = Mean; + //_RemoteControl_State.isValid = true; + xSemaphoreGive(_RemoteControl_State.Mutex); + + //ESP_LOGD(TAG, "Spotmeter data updated: Min=%.2f°C, Max=%.2f°C, Avg=%.2f°C", + // _RemoteControl_State.Spotmeter.Min, _RemoteControl_State.Spotmeter.Max, _RemoteControl_State.Spotmeter.Mean); + + return ESP_OK; + } else { + ESP_LOGE(TAG, "Failed to acquire mutex for spotmeter update!"); + + return ESP_ERR_TIMEOUT; + } +} + +esp_err_t RemoteControl_GetLeptonSpotmeter(cJSON *p_JSON) +{ + if (p_JSON == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (_RemoteControl_State.isValid == false) { + ESP_LOGW(TAG, "No spotmeter data available yet"); + + return ESP_ERR_NOT_FOUND; + } + + /* Thread-safe read of spotmeter data */ + if (xSemaphoreTake(_RemoteControl_State.Mutex, pdMS_TO_TICKS(100)) == pdTRUE) { + //cJSON_AddNumberToObject(p_JSON, "min", _RemoteControl_State.Spotmeter.Min); + //cJSON_AddNumberToObject(p_JSON, "max", _RemoteControl_State.Spotmeter.Max); + //cJSON_AddNumberToObject(p_JSON, "average", _RemoteControl_State.Spotmeter.Average); + //cJSON_AddNumberToObject(p_JSON, "mean", _RemoteControl_State.Spotmeter.Mean); + xSemaphoreGive(_RemoteControl_State.Mutex); + + return ESP_OK; + } else { + ESP_LOGE(TAG, "Failed to acquire mutex for spotmeter read!"); + + return ESP_ERR_TIMEOUT; + } +} + +esp_err_t RemoteControl_GetFlashConfig(bool *p_Enabled, uint8_t *p_Power) +{ + esp_err_t Error; + Settings_LED_Flash_t LEDFlash; + + if ((p_Enabled == NULL) || (p_Power == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + Error = SettingsManager_GetLEDFlash(&LEDFlash); + if (Error != ESP_OK) { + return Error; + } + + *p_Enabled = LEDFlash.Enable; + *p_Power = LEDFlash.Power; + + return ESP_OK; +} + +esp_err_t RemoteControl_SetFlashPower(uint8_t Power) +{ + esp_err_t Error; + Settings_LED_Flash_t LEDFlash; + SettingsManager_ChangeNotification_t Changed; + + Error = SettingsManager_GetLEDFlash(&LEDFlash); + if (Error != ESP_OK) { + return Error; + } else if (Power > 100) { + return ESP_ERR_INVALID_ARG; + } + + LEDFlash.Power = Power; + Changed.ID = SETTINGS_ID_LED_FLASH_POWER; + Changed.Value = Power; + + return SettingsManager_UpdateLEDFlash(&LEDFlash, &Changed); +} + +esp_err_t RemoteControl_SetFlashState(bool Enabled) +{ + esp_err_t Error; + Settings_LED_Flash_t LEDFlash; + SettingsManager_ChangeNotification_t Changed; + + Error = SettingsManager_GetLEDFlash(&LEDFlash); + if (Error != ESP_OK) { + return Error; + } + + LEDFlash.Enable = Enabled; + Changed.ID = SETTINGS_ID_LED_FLASH_ENABLE; + Changed.Value = Enabled; + + return SettingsManager_UpdateLEDFlash(&LEDFlash, &Changed); +} + +esp_err_t RemoteControl_GetImageFormat(Settings_Image_Format_t *p_Format) +{ + esp_err_t Error; + Settings_System_t System; + + if (p_Format == NULL) { + return ESP_ERR_INVALID_ARG; + } + + Error = SettingsManager_GetSystem(&System); + if (Error != ESP_OK) { + return Error; + } + + *p_Format = System.ImageFormat; + + return ESP_OK; +} + +esp_err_t RemoteControl_SetImageFormat(Settings_Image_Format_t Format) +{ + esp_err_t Error; + Settings_System_t System; + SettingsManager_ChangeNotification_t Changed; + + if ((Format != IMAGE_FORMAT_PNG) && + (Format != IMAGE_FORMAT_RAW) && + (Format != IMAGE_FORMAT_JPEG)) { + return ESP_ERR_INVALID_ARG; + } + + Error = SettingsManager_GetSystem(&System); + if (Error != ESP_OK) { + return Error; + } + + Changed.ID = SETTINGS_ID_IMAGE_FORMAT; + Changed.Value = Format; + System.ImageFormat = static_cast(Format); + + ESP_LOGI(TAG, "Set image format to: %d", System.ImageFormat); + + return SettingsManager_UpdateSystem(&System, &Changed); +} + +esp_err_t RemoteControl_SetStatusLED(Remote_LED_Color_t Color, uint8_t Brightness) +{ + if ((Color != REMOTE_LED_RED) && + (Color != REMOTE_LED_GREEN) && + (Color != REMOTE_LED_BLUE)) { + return ESP_ERR_INVALID_ARG; + } + + /* TODO: Set LED via PCA9633 driver */ + const char *color_str = (Color == REMOTE_LED_RED) ? "RED" : + (Color == REMOTE_LED_GREEN) ? "GREEN" : "BLUE"; + + ESP_LOGI(TAG, "Set status LED: %s at brightness %u", color_str, Brightness); + + return ESP_OK; +} + +esp_err_t RemoteControl_GetSDCardState(bool *p_Available) +{ + MemoryManager_Location_t Location; + + if (p_Available == NULL) { + return ESP_ERR_INVALID_ARG; + } + + Location = MemoryManager_GetStorageLocation(); + if(Location == MEMORY_LOCATION_SD_CARD) { + *p_Available = true; + } else { + *p_Available = false; + } + + return ESP_OK; +} + +esp_err_t RemoteControl_FormatMemory(void) +{ + return MemoryManager_FormatActiveStorage(); +} + +esp_err_t RemoteControl_DisplayMessageBox(const char *p_Message) +{ + Remote_Display_Message_t Message; + + if (p_Message == NULL) { + return ESP_ERR_INVALID_ARG; + } + + ESP_LOGI(TAG, "Display message: %s", p_Message); + + memcpy(Message.Message, p_Message, sizeof(Message.Message) - 1); + Message.Message[sizeof(Message.Message) - 1] = '\0'; + + return esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_REMOTE_DISPLAY_MESSAGE, &Message, sizeof(Message), portMAX_DELAY); +} + +esp_err_t RemoteControl_GetLockState(bool *p_Locked) +{ + if (p_Locked == NULL) { + return ESP_ERR_INVALID_ARG; + } + + *p_Locked = _RemoteControl_State.isLocked; + + return ESP_OK; +} + +esp_err_t RemoteControl_SetLockState(bool Locked) +{ + _RemoteControl_State.isLocked = Locked; + + return esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_REMOTE_LOCK_SET, &Locked, sizeof(Locked), portMAX_DELAY); +} diff --git a/main/Application/Manager/Network/Server/RemoteControl/remoteControl.h b/main/Application/Manager/Network/Server/RemoteControl/remoteControl.h new file mode 100644 index 0000000..39e4351 --- /dev/null +++ b/main/Application/Manager/Network/Server/RemoteControl/remoteControl.h @@ -0,0 +1,229 @@ +/* + * remoteControl.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Common remote control interface for VISA and HTTP/WebSocket servers. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef REMOTE_CONTROL_H_ +#define REMOTE_CONTROL_H_ + +#include + +#include +#include +#include + +#include "../../networkTypes.h" +#include "Settings/settingsTypes.h" + +/** @brief Get temperature from TMP117 sensor. + * @param p_Temperature Pointer to store temperature value in Celsius + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Temperature is NULL + * ESP_ERR_INVALID_STATE if sensor not initialized + */ +esp_err_t RemoteControl_GetTemperature(float *p_Temperature); + +/** @brief Get current system time. + * @param p_TimeStr Pointer to buffer for time string (ISO 8601 format) + * @param MaxLen Maximum length of buffer + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_TimeStr is NULL + * ESP_ERR_INVALID_STATE if time not synchronized + */ +esp_err_t RemoteControl_GetTime(char *p_TimeStr, size_t MaxLen); + +/** @brief Set current system time. + * @param p_TimeStr Time string in ISO 8601 format + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_TimeStr is NULL or invalid format + */ +esp_err_t RemoteControl_SetTime(const char *p_TimeStr); + +/** @brief Get battery voltage. + * @param p_Voltage Pointer to store voltage value in millivolts + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Voltage is NULL + * ESP_ERR_NOT_SUPPORTED if battery monitoring not available + */ +esp_err_t RemoteControl_GetBatteryVoltage(int *p_Voltage); + +/** @brief Get battery state of charge. + * @param p_SOC Pointer to store SOC value in percent (0-100) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_SOC is NULL + * ESP_ERR_NOT_SUPPORTED if battery monitoring not available + */ +esp_err_t RemoteControl_GetStateOfCharge(uint8_t *p_SOC); + +/** @brief Get OV5640 camera image. + * @param pp_Buffer Pointer to store image buffer pointer (caller must free) + * @param p_Size Pointer to store image size in bytes + * @param Format Desired image format + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + * ESP_ERR_NO_MEM if allocation fails + * ESP_ERR_NOT_FOUND if camera not available + */ +esp_err_t RemoteControl_GetOV5640Image(uint8_t **pp_Buffer, size_t *p_Size, Settings_Image_Format_t Format); + +/** @brief Get Lepton thermal camera image. + * @param pp_Buffer Pointer to store image buffer pointer (caller must free) + * @param p_Size Pointer to store image size in bytes + * @param Format Desired image format + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + * ESP_ERR_NO_MEM if allocation fails + * ESP_ERR_NOT_FOUND if camera not available + */ +esp_err_t RemoteControl_GetLeptonImage(uint8_t **pp_Buffer, size_t *p_Size, Settings_Image_Format_t Format); + +/** @brief Get Lepton camera emissivity. + * @param p_Emissivity Pointer to store emissivity value + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Emissivity is NULL + */ +esp_err_t RemoteControl_GetLeptonEmissivity(uint8_t *p_Emissivity); + +/** @brief Set Lepton camera emissivity. + * @param Emissivity Emissivity value (0-100) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if value out of range + */ +esp_err_t RemoteControl_SetLeptonEmissivity(uint8_t Emissivity); + +/** @brief Get Lepton scene statistics. + * @param p_JSON Pointer to cJSON object to populate (must be created) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_JSON is NULL + * ESP_ERR_NOT_FOUND if no data available + */ +esp_err_t RemoteControl_GetLeptonSceneStats(cJSON *p_JSON); + +/** @brief Get Lepton ROI (Region of Interest). + * @param p_JSON Pointer to cJSON object to populate with ROI data + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_JSON is NULL + */ +esp_err_t RemoteControl_GetLeptonROI(cJSON *p_JSON); + +/** @brief Set Lepton ROI (Region of Interest). + * @param p_JSON Pointer to cJSON object containing ROI data + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_JSON is NULL or invalid + */ +esp_err_t RemoteControl_SetLeptonROI(const cJSON *p_JSON); + +/** @brief Get Lepton spotmeter data. + * @param p_JSON Pointer to cJSON object to populate + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_JSON is NULL + */ +esp_err_t RemoteControl_GetLeptonSpotmeter(cJSON *p_JSON); + +/** @brief Update Lepton spotmeter data in server cache. + * This function stores the latest spotmeter ROI results for use by + * VISA and WebSocket/HTTP interfaces. + * @note This function is thread-safe and can be called from any task. + * @param Min Minimum temperature in Celsius + * @param Max Maximum temperature in Celsius + * @param Mean Mean temperature in Celsius + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_ROIResult is NULL + */ +esp_err_t RemoteControl_UpdateSpotmeter(float Min, float Max, float Mean); + +/** @brief Get flash configuration. + * @param p_Enabled Pointer to store flash enabled state + * @param p_Power Pointer to store flash power level (0-100) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if pointers are NULL + */ +esp_err_t RemoteControl_GetFlashConfig(bool *p_Enabled, uint8_t *p_Power); + +/** @brief Set flash power level. + * @param Power Power level (0-100) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if value out of range + */ +esp_err_t RemoteControl_SetFlashPower(uint8_t Power); + +/** @brief Set flash state. + * @param Enabled Flash enabled state + * @return ESP_OK on success + */ +esp_err_t RemoteControl_SetFlashState(bool Enabled); + +/** @brief Get image format. + * @param p_Format Pointer to store image format + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Format is NULL + */ +esp_err_t RemoteControl_GetImageFormat(Settings_Image_Format_t *p_Format); + +/** @brief Set image format. + * @param Format Image format + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if format invalid + */ +esp_err_t RemoteControl_SetImageFormat(Settings_Image_Format_t Format); + +/** @brief Set status LED color and brightness. + * @param Color LED color + * @param Brightness Brightness level (0-255) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if color invalid + */ +esp_err_t RemoteControl_SetStatusLED(Remote_LED_Color_t Color, uint8_t Brightness); + +/** @brief Check if SD card is available. + * @param p_Available Pointer to store availability state + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Available is NULL + */ +esp_err_t RemoteControl_GetSDCardState(bool *p_Available); + +/** @brief Format active memory (internal flash or SD card). + * @return ESP_OK on success + * ESP_FAIL if format operation failed + */ +esp_err_t RemoteControl_FormatMemory(void); + +/** @brief Display message box on screen. + * @param p_Message Message text to display + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Message is NULL + */ +esp_err_t RemoteControl_DisplayMessageBox(const char *p_Message); + +/** @brief Get lock state of input buttons and joystick. + * @param p_Locked Pointer to store lock state + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Locked is NULL + */ +esp_err_t RemoteControl_GetLockState(bool *p_Locked); + +/** @brief Set lock state of input buttons and joystick. + * @param Locked Lock state + * @return ESP_OK on success + */ +esp_err_t RemoteControl_SetLockState(bool Locked); + +#endif /* REMOTE_CONTROL_H_ */ diff --git a/main/Application/Manager/Network/Server/VISA/Private/visaCommands.cpp b/main/Application/Manager/Network/Server/VISA/Private/visaCommands.cpp new file mode 100644 index 0000000..d0309a7 --- /dev/null +++ b/main/Application/Manager/Network/Server/VISA/Private/visaCommands.cpp @@ -0,0 +1,613 @@ +/* + * visaCommands.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: VISA commands implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "visaCommands.h" +#include "visaRemoteCommands.h" +#include "Settings/settingsManager.h" + +#include "sdkconfig.h" + +static int VISA_ErrorQueue[CONFIG_NETWORK_VISA_ERROR_QUEUE_LLENGTH]; +static size_t VISA_ErrorCount = 0; + +/** @brief Operation complete flag */ +static bool VISA_OperationComplete = true; + +static const char *TAG = "VISA-Commands"; + +/** @brief Push error to queue + * @param Error Error code + */ +static void VISA_PushError(int Error) +{ + if (VISA_ErrorCount < CONFIG_NETWORK_VISA_ERROR_QUEUE_LLENGTH) { + VISA_ErrorQueue[VISA_ErrorCount++] = Error; + } +} + +/** @brief Case-insensitive string comparison + * @param s1 First string + * @param s2 Second string + * @return true if equal (case-insensitive) + */ +static bool string_iequals(const std::string& s1, const std::string& s2) +{ + return std::equal(s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char a, char b) { + return std::tolower(a) == std::tolower(b); + }); +} + +/** @brief Check if string is a query (ends with ?) + * @param Command Command string + * @return true if query, false otherwise + */ +static bool VISA_IsQuery(const std::string& Command) +{ + return (Command.empty() == false) && (Command.back() == '?'); +} + +/** @brief Parse command into tokens + * @param Command Command string + * @return Vector of token strings + */ +static std::vector VISA_ParseCommand(const std::string& Command) +{ + std::vector TokenList; + std::string Token; + + for (char c : Command) { + if ((c == ' ') || (c == '\t') || (c == ':')) { + if ((Token.empty() == false)) { + TokenList.push_back(Token); + Token.clear(); + } + } else { + Token += c; + } + } + + if (Token.empty() == false) { + TokenList.push_back(Token); + } + + return TokenList; +} + +/* ===== IEEE 488.2 Common Commands ===== */ + +/** @brief *IDN? - Identification query + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length + */ +static int VISA_CMD_IDN(char *p_Response, size_t MaxLen) +{ + size_t Length; + Settings_Info_t Info; + std::ostringstream oss; + std::string Result; + + SettingsManager_GetInfo(&Info); + + /* Build response using C++ string for safety */ + oss << (Info.Manufacturer[0] ? Info.Manufacturer : "PyroVision") << "," + << (Info.Name[0] ? Info.Name : "ThermalCam") << "," + << (Info.Serial[0] ? Info.Serial : "00000001") << "," + << (Info.FirmwareVersion[0] ? Info.FirmwareVersion : "1.0.0") << "\n"; + + Result = oss.str(); + + /* Copy to output buffer */ + Length = std::min(Result.length(), MaxLen - 1); + memcpy(p_Response, Result.c_str(), Length); + p_Response[Length] = '\0'; + + ESP_LOGI(TAG, "*IDN? response: %s", Result.c_str()); + + return static_cast(Length); +} + +/** @brief *RST - Reset device + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length + */ +static int VISA_CMD_RST(char *p_Response, size_t MaxLen) +{ + ESP_LOGI(TAG, "Device reset requested"); + + /* TODO: Implement actual reset logic */ + // Reset managers to default state + + VISA_OperationComplete = true; + + /* No response for command */ + return 0; +} + +/** @brief *CLS - Clear status + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length + */ +static int VISA_CMD_CLS(char *p_Response, size_t MaxLen) +{ + VISACommands_ClearErrors(); + VISA_OperationComplete = true; + + /* No response for command */ + return 0; +} + +/** @brief *OPC? - Operation complete query + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length + */ +static int VISA_CMD_OPC(char *p_Response, size_t MaxLen) +{ + size_t Length; + std::string Result; + + Result = std::to_string(VISA_OperationComplete ? 1 : 0) + "\n"; + Length = std::min(Result.length(), MaxLen - 1); + memcpy(p_Response, Result.c_str(), Length); + p_Response[Length] = '\0'; + + return static_cast(Length); +} + +/** @brief *TST? - Self-test query + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length + */ +static int VISA_CMD_TST(char *p_Response, size_t MaxLen) +{ + /* Perform basic self-test */ + /* 0 = pass, non-zero = fail */ + int result = 0; + size_t Length; + std::string Response; + + /* TODO: Implement actual self-test */ + // Check camera connection + // Check display + // Check memory + + Response = std::to_string(result) + "\n"; + Length = std::min(Response.length(), MaxLen - 1); + memcpy(p_Response, Response.c_str(), Length); + p_Response[Length] = '\0'; + + return static_cast(Length); +} + +/** @brief SYSTem:ERRor? - Get error from queue + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length + */ +static int VISA_CMD_SYST_ERR(char *p_Response, size_t MaxLen) +{ + size_t Length; + std::string Result; + std::ostringstream oss; + int Error; + + Error = VISACommands_GetError(); + if (Error == SCPI_ERROR_NO_ERROR) { + oss << "0,\"No error\"\n"; + } else { + oss << Error << ",\"Error " << Error << "\"\n"; + } + + Result = oss.str(); + Length = std::min(Result.length(), MaxLen - 1); + memcpy(p_Response, Result.c_str(), Length); + p_Response[Length] = '\0'; + + return static_cast(Length); +} + +/** @brief SYSTem:VERSion? - Get SCPI version + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length + */ +static int VISA_CMD_SYST_VERS(char *p_Response, size_t MaxLen) +{ + std::string result = "1999.0\n"; /* SCPI-99 */ + size_t Length; + + Length = std::min(result.length(), MaxLen - 1); + memcpy(p_Response, result.c_str(), Length); + p_Response[Length] = '\0'; + + return static_cast(Length); +} + +/* ===== Device-Specific Commands ===== */ + +/** @brief SENSe:IMAGE:CAPTure - Capture image + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length + */ +static int VISA_CMD_SENS_IMG_CAPT(char *p_Response, size_t MaxLen) +{ + /* TODO: Trigger image capture */ + ESP_LOGI(TAG, "Image capture triggered"); + + VISA_OperationComplete = false; + /* Capture happens asynchronously */ + /* Set _operation_complete = true when done */ + + return 0; /* No immediate response */ +} + +/** @brief SENSe:IMAGE:DATA? - Get captured image data + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative for binary data + */ +static int VISA_CMD_SENS_IMG_DATA(char *p_Response, size_t MaxLen) +{ + char Header[32]; + int HeaderSize; + int Digits; + size_t Size = 1024; + uint8_t *Data; + uint32_t Caps; + + /* TODO: Get actual image data */ + /* This should return binary data in IEEE 488.2 format */ + /* Format: # where n = digits in length */ + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + #else + Caps = MALLOC_CAP_8BIT; + #endif + + /* Example with dummy data */ + Data = static_cast(heap_caps_malloc(1024, Caps)); + if (Data == NULL) { + VISA_PushError(SCPI_ERROR_OUT_OF_MEMORY); + + return SCPI_ERROR_OUT_OF_MEMORY; + } + + memset(Data, 0xAA, Size); /* Dummy data */ + + /* Format binary block header */ + Digits = snprintf(Header, sizeof(Header), "%zu", Size); + HeaderSize = snprintf(p_Response, MaxLen, "#%d%zu", Digits, Size); + + /* Copy image data after header */ + if ((HeaderSize + Size) < MaxLen) { + memcpy(p_Response + HeaderSize, Data, Size); + free(Data); + + return HeaderSize + Size; + } + + free(Data); + VISA_PushError(SCPI_ERROR_OUT_OF_MEMORY); + + return SCPI_ERROR_OUT_OF_MEMORY; +} + +esp_err_t VISACommands_Init(void) +{ + VISACommands_ClearErrors(); + VISA_OperationComplete = true; + + ESP_LOGD(TAG, "VISA command handler initialized"); + + return ESP_OK; +} + +esp_err_t VISACommands_Deinit(void) +{ + VISACommands_ClearErrors(); + + ESP_LOGD(TAG, "VISA command handler deinitialized"); + + return ESP_OK; +} + +int VISACommands_Execute(const char *Command, char *Response, size_t MaxLen) +{ + std::vector Tokens; + bool isQuery; + + if ((Command == NULL) || (Response == NULL)) { + return SCPI_ERROR_COMMAND_ERROR; + } + + /* Convert to C++ string and parse */ + Tokens = VISA_ParseCommand(std::string(Command)); + if (Tokens.empty()) { + VISA_PushError(SCPI_ERROR_COMMAND_ERROR); + + return SCPI_ERROR_COMMAND_ERROR; + } + + /* Check for queries */ + isQuery = VISA_IsQuery(Tokens.back()); + + /* Remove ? from last token if query */ + if (isQuery && (Tokens.back().empty() == false)) { + Tokens.back().pop_back(); + } + + /* IEEE 488.2 Common Commands */ + if (Tokens[0] == "*IDN") { + if (isQuery) { + return VISA_CMD_IDN(Response, MaxLen); + } + } else if (Tokens[0] == "*RST") { + return VISA_CMD_RST(Response, MaxLen); + } else if (Tokens[0] == "*CLS") { + return VISA_CMD_CLS(Response, MaxLen); + } else if (Tokens[0] == "*OPC") { + if (isQuery) { + return VISA_CMD_OPC(Response, MaxLen); + } + } else if (Tokens[0] == "*TST") { + if (isQuery) { + return VISA_CMD_TST(Response, MaxLen); + } + } + /* SCPI System Commands */ + else if (string_iequals(Tokens[0], "SYST") || string_iequals(Tokens[0], "SYSTem")) { + if ((Tokens.size() >= 2) && (string_iequals(Tokens[1], "ERR") || string_iequals(Tokens[1], "ERRor"))) { + if (isQuery) { + return VISA_CMD_SYST_ERR(Response, MaxLen); + } + } else if ((Tokens.size() >= 2) && (string_iequals(Tokens[1], "VERS") || string_iequals(Tokens[1], "VERSion"))) { + if (isQuery) { + return VISA_CMD_SYST_VERS(Response, MaxLen); + } + } else if ((Tokens.size() >= 2) && string_iequals(Tokens[1], "TIME")) { + if (isQuery) { + return VISA_Cmd_GetTime(Response, MaxLen); + } else { + std::vector TokenList; + + for (auto& t : Tokens) { + TokenList.push_back(const_cast(t.c_str())); + } + + return VISA_Cmd_SetTime(TokenList.data(), static_cast(Tokens.size()), Response, MaxLen); + } + } else if ((Tokens.size() >= 2) && string_iequals(Tokens[1], "LOCK")) { + if (isQuery) { + return VISA_Cmd_GetLockState(Response, MaxLen); + } else { + std::vector TokenList; + + for (auto& t : Tokens) { + TokenList.push_back(const_cast(t.c_str())); + } + + return VISA_Cmd_SetLockState(TokenList.data(), static_cast(Tokens.size()), Response, MaxLen); + } + } + } + /* Device-Specific Commands - SENSe */ + else if (string_iequals(Tokens[0], "SENS") || string_iequals(Tokens[0], "SENSe")) { + if ((Tokens.size() >= 2) && (string_iequals(Tokens[1], "TEMP") || string_iequals(Tokens[1], "TEMPerature"))) { + if (isQuery) { + return VISA_Cmd_GetTemperature(Response, MaxLen); + } + } else if ((Tokens.size() >= 2) && (string_iequals(Tokens[1], "BATT") || string_iequals(Tokens[1], "BATTery"))) { + if ((Tokens.size() >= 3) && (string_iequals(Tokens[2], "VOLT") || string_iequals(Tokens[2], "VOLTage"))) { + if (isQuery) { + return VISA_Cmd_GetBatteryVoltage(Response, MaxLen); + } + } else if ((Tokens.size() >= 3) && string_iequals(Tokens[2], "SOC")) { + if (isQuery) { + return VISA_Cmd_GetStateOfCharge(Response, MaxLen); + } + } + } else if ((Tokens.size() >= 2) && (string_iequals(Tokens[1], "IMG") || string_iequals(Tokens[1], "IMAGE"))) { + if ((Tokens.size() >= 3) && (string_iequals(Tokens[2], "CAPT") || string_iequals(Tokens[2], "CAPTure"))) { + return VISA_CMD_SENS_IMG_CAPT(Response, MaxLen); + } else if ((Tokens.size() >= 3) && string_iequals(Tokens[2], "DATA")) { + if (isQuery) { + return VISA_CMD_SENS_IMG_DATA(Response, MaxLen); + } + } else if ((Tokens.size() >= 3) && (string_iequals(Tokens[2], "FORM") || string_iequals(Tokens[2], "FORMat"))) { + if (isQuery) { + return VISA_Cmd_GetImageFormat(Response, MaxLen); + } else { + std::vector TokenList; + + for (auto& t : Tokens) { + TokenList.push_back(const_cast(t.c_str())); + } + + return VISA_Cmd_SetImageFormat(TokenList.data(), static_cast(Tokens.size()), Response, MaxLen); + } + } else if ((Tokens.size() >= 3) && (string_iequals(Tokens[2], "PAL") || string_iequals(Tokens[2], "PALette"))) { + std::vector TokenList; + + for (auto& t : Tokens) { + TokenList.push_back(const_cast(t.c_str())); + } + + return VISA_Cmd_SetImagePalette(TokenList.data(), static_cast(Tokens.size()), Response, MaxLen); + } else if ((Tokens.size() >= 3) && (string_iequals(Tokens[2], "LEP") || string_iequals(Tokens[2], "LEPton"))) { + if ((Tokens.size() >= 4) && (string_iequals(Tokens[3], "EMIS") || string_iequals(Tokens[3], "EMISsivity"))) { + if (isQuery) { + return VISA_Cmd_GetLeptonEmissivity(Response, MaxLen); + } else { + std::vector TokenList; + + for (auto& t : Tokens) { + TokenList.push_back(const_cast(t.c_str())); + } + + return VISA_Cmd_SetLeptonEmissivity(TokenList.data(), static_cast(Tokens.size()), Response, MaxLen); + } + } else if ((Tokens.size() >= 4) && (string_iequals(Tokens[3], "STAT") || string_iequals(Tokens[3], "STATistics"))) { + if (isQuery) { + return VISA_Cmd_GetLeptonStats(Response, MaxLen); + } + } else if ((Tokens.size() >= 4) && string_iequals(Tokens[3], "ROI")) { + if (isQuery) { + return VISA_Cmd_GetLeptonROI(Response, MaxLen); + } else { + std::vector TokenList; + + for (auto& t : Tokens) { + TokenList.push_back(const_cast(t.c_str())); + } + + return VISA_Cmd_SetLeptonROI(TokenList.data(), static_cast(Tokens.size()), Response, MaxLen); + } + } else if ((Tokens.size() >= 4) && (string_iequals(Tokens[3], "SPOT") || string_iequals(Tokens[3], "SPOTmeter"))) { + if (isQuery) { + + return VISA_Cmd_GetLeptonSpotmeter(Response, MaxLen); + } + } + } + } + } + /* Device-Specific Commands - DISPlay */ + else if (string_iequals(Tokens[0], "DISP") || string_iequals(Tokens[0], "DISPlay")) { + if ((Tokens.size() >= 2) && string_iequals(Tokens[1], "LED")) { + if ((Tokens.size() >= 3) && (string_iequals(Tokens[2], "STAT") || string_iequals(Tokens[2], "STATe"))) { + std::vector TokenList; + + for (auto& t : Tokens) { + TokenList.push_back(const_cast(t.c_str())); + } + + return VISA_Cmd_SetStatusLED(TokenList.data(), static_cast(Tokens.size()), Response, MaxLen); + } else if ((Tokens.size() >= 3) && (string_iequals(Tokens[2], "BRIG") || string_iequals(Tokens[2], "BRIGhtness"))) { + std::vector TokenList; + + for (auto& t : Tokens) { + TokenList.push_back(const_cast(t.c_str())); + } + + return VISA_Cmd_SetLEDBrightness(TokenList.data(), static_cast(Tokens.size()), Response, MaxLen); + } + } else if ((Tokens.size() >= 2) && string_iequals(Tokens[1], "FLASH")) { + if ((Tokens.size() >= 3) && (string_iequals(Tokens[2], "POW") || string_iequals(Tokens[2], "POWer"))) { + if (isQuery) { + return VISA_Cmd_GetFlashPower(Response, MaxLen); + } else { + std::vector TokenList; + + for (auto& t : Tokens) { + TokenList.push_back(const_cast(t.c_str())); + } + + return VISA_Cmd_SetFlashPower(TokenList.data(), static_cast(Tokens.size()), Response, MaxLen); + } + } else if ((Tokens.size() >= 3) && (string_iequals(Tokens[2], "STAT") || string_iequals(Tokens[2], "STATe"))) { + if (isQuery) { + return VISA_Cmd_GetFlashState(Response, MaxLen); + } else { + std::vector TokenList; + + for (auto& t : Tokens) { + TokenList.push_back(const_cast(t.c_str())); + } + + return VISA_Cmd_SetFlashState(TokenList.data(), static_cast(Tokens.size()), Response, MaxLen); + } + } + } else if ((Tokens.size() >= 2) && (string_iequals(Tokens[1], "MBOX") || string_iequals(Tokens[1], "MessageBOX"))) { + std::vector TokenList; + + for (auto& t : Tokens) { + TokenList.push_back(const_cast(t.c_str())); + } + + return VISA_Cmd_DisplayMessageBox(TokenList.data(), static_cast(Tokens.size()), Response, MaxLen); + } + } + /* Device-Specific Commands - MEMory */ + else if (string_iequals(Tokens[0], "MEM") || string_iequals(Tokens[0], "MEMory")) { + if ((Tokens.size() >= 2) && string_iequals(Tokens[1], "SD")) { + if ((Tokens.size() >= 3) && (string_iequals(Tokens[2], "STAT") || string_iequals(Tokens[2], "STATe"))) { + if (isQuery) { + return VISA_Cmd_GetSDCardState(Response, MaxLen); + } + } + } else if ((Tokens.size() >= 2) && (string_iequals(Tokens[1], "FORM") || string_iequals(Tokens[1], "FORMat"))) { + return VISA_Cmd_FormatMemory(Response, MaxLen); + } + } + + /* Command not found */ + VISA_PushError(SCPI_ERROR_UNDEFINED_HEADER); + + return SCPI_ERROR_UNDEFINED_HEADER; +} + +int VISACommands_GetError(void) +{ + if (VISA_ErrorCount > 0) { + int Error; + + Error = VISA_ErrorQueue[0]; + + /* Shift queue */ + for (size_t i = 1; i < VISA_ErrorCount; i++) { + VISA_ErrorQueue[i - 1] = VISA_ErrorQueue[i]; + } + + VISA_ErrorCount--; + + return Error; + } + + return SCPI_ERROR_NO_ERROR; +} + +void VISACommands_ClearErrors(void) +{ + VISA_ErrorCount = 0; + memset(VISA_ErrorQueue, 0, sizeof(VISA_ErrorQueue)); +} diff --git a/main/Application/Manager/Network/Server/VISA/Private/visaCommands.h b/main/Application/Manager/Network/Server/VISA/Private/visaCommands.h new file mode 100644 index 0000000..16d45bc --- /dev/null +++ b/main/Application/Manager/Network/Server/VISA/Private/visaCommands.h @@ -0,0 +1,77 @@ +/* + * visaCommands.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: VISA commands implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef VISA_COMMANDS_H_ +#define VISA_COMMANDS_H_ + +#include + +#include + +/** @brief Standard SCPI error codes */ +#define SCPI_ERROR_NO_ERROR 0 /**< No error */ +#define SCPI_ERROR_COMMAND_ERROR -100 /**< Command error */ +#define SCPI_ERROR_INVALID_CHARACTER -101 /**< Invalid character */ +#define SCPI_ERROR_SYNTAX_ERROR -102 /**< Syntax error */ +#define SCPI_ERROR_INVALID_SEPARATOR -103 /**< Invalid separator */ +#define SCPI_ERROR_DATA_TYPE_ERROR -104 /**< Data type error */ +#define SCPI_ERROR_PARAMETER_NOT_ALLOWED -108 /**< Parameter not allowed */ +#define SCPI_ERROR_MISSING_PARAMETER -109 /**< Missing parameter */ +#define SCPI_ERROR_COMMAND_HEADER_ERROR -110 /**< Command header error */ +#define SCPI_ERROR_UNDEFINED_HEADER -113 /**< Undefined header */ +#define SCPI_ERROR_EXECUTION_ERROR -200 /**< Execution error */ +#define SCPI_ERROR_DATA_OUT_OF_RANGE -222 /**< Data out of range */ +#define SCPI_ERROR_HARDWARE_MISSING -241 /**< Hardware missing */ +#define SCPI_ERROR_HARDWARE_ERROR -240 /**< Hardware error */ +#define SCPI_ERROR_SYSTEM_ERROR -300 /**< System error */ +#define SCPI_ERROR_OUT_OF_MEMORY -350 /**< Out of memory */ +#define SCPI_ERROR_QUERY_ERROR -400 /**< Query error */ + +/** @brief Initialize command handler + * @return ESP_OK on success, error code otherwise + */ +esp_err_t VISACommands_Init(void); + +/** @brief Deinitialize command handler + * @return ESP_OK on success, error code otherwise + */ +esp_err_t VISACommands_Deinit(void); + +/** @brief Execute VISA/SCPI command + * @param Command Command string + * @param Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or error code + */ +int VISACommands_Execute(const char *Command, char *Response, size_t MaxLen); + +/** @brief Get last error from error queue + * @return Error code + */ +int VISACommands_GetError(void); + +/** @brief Clear error queue + */ +void VISACommands_ClearErrors(void); + +#endif /* VISA_COMMANDS_H_ */ diff --git a/main/Application/Manager/Network/Server/VISA/Private/visaRemoteCommands.cpp b/main/Application/Manager/Network/Server/VISA/Private/visaRemoteCommands.cpp new file mode 100644 index 0000000..ff918f2 --- /dev/null +++ b/main/Application/Manager/Network/Server/VISA/Private/visaRemoteCommands.cpp @@ -0,0 +1,588 @@ +/* + * visaRemoteCommands.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: VISA commands for remote control interface. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include +#include +#include + +#include "visaRemoteCommands.h" +#include "visaCommands.h" +#include "../../RemoteControl/remoteControl.h" + +int VISA_Cmd_GetTemperature(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + float Temperature; + std::string Response; + std::string TempStr; + + Error = RemoteControl_GetTemperature(&Temperature); + if (Error != ESP_OK) { + return SCPI_ERROR_HARDWARE_ERROR; + } + + TempStr = std::to_string(Temperature); + Response = TempStr.substr(0, TempStr.find('.') + 3) + "\n"; + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_GetTime(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + char TimeStr[32]; + std::string Response; + + Error = RemoteControl_GetTime(TimeStr, sizeof(TimeStr)); + if (Error != ESP_OK) { + return SCPI_ERROR_EXECUTION_ERROR; + } + + Response = std::string(TimeStr) + "\n"; + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_SetTime(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + + if (Count < 4) { + return SCPI_ERROR_MISSING_PARAMETER; + } + + Error = RemoteControl_SetTime(pp_Tokens[3]); + if (Error != ESP_OK) { + return SCPI_ERROR_DATA_OUT_OF_RANGE; + } + + return 0; +} + +int VISA_Cmd_GetBatteryVoltage(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + int Voltage; + std::string Response; + std::string VoltageStr; + + Error = RemoteControl_GetBatteryVoltage(&Voltage); + if (Error != ESP_OK) { + return SCPI_ERROR_HARDWARE_ERROR; + } + + VoltageStr = std::to_string(Voltage); + Response = VoltageStr.substr(0, VoltageStr.find('.') + 4) + "\n"; + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_GetStateOfCharge(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + uint8_t SOC; + std::string Response; + + Error = RemoteControl_GetStateOfCharge(&SOC); + if (Error != ESP_OK) { + return SCPI_ERROR_HARDWARE_ERROR; + } + + Response = std::to_string(SOC) + "\n"; + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_GetLeptonEmissivity(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + std::string Response; + uint8_t Emissivity; + + Error = RemoteControl_GetLeptonEmissivity(&Emissivity); + if (Error != ESP_OK) { + return SCPI_ERROR_HARDWARE_ERROR; + } + + Response = std::to_string(Emissivity) + "\n"; + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_SetLeptonEmissivity(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + int Emissivity; + + if (Count < 5) { + return SCPI_ERROR_MISSING_PARAMETER; + } + + Emissivity = atoi(pp_Tokens[4]); + if ((Emissivity < 0) || (Emissivity > 100)) { + return SCPI_ERROR_DATA_OUT_OF_RANGE; + } + + Error = RemoteControl_SetLeptonEmissivity(static_cast(Emissivity)); + if (Error != ESP_OK) { + return SCPI_ERROR_EXECUTION_ERROR; + } + + return 0; +} + +int VISA_Cmd_GetLeptonStats(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + cJSON *JSON; + char *JSONStr; + std::string Response; + + JSON = cJSON_CreateObject(); + if (JSON == NULL) { + return SCPI_ERROR_OUT_OF_MEMORY; + } + + Error = RemoteControl_GetLeptonSceneStats(JSON); + if (Error != ESP_OK) { + cJSON_Delete(JSON); + + return SCPI_ERROR_HARDWARE_ERROR; + } + + JSONStr = cJSON_PrintUnformatted(JSON); + cJSON_Delete(JSON); + + if (JSONStr == NULL) { + return SCPI_ERROR_OUT_OF_MEMORY; + } + + Response = std::string(JSONStr) + "\n"; + cJSON_free(JSONStr); + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_GetLeptonROI(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + cJSON *JSON; + char *JSONStr; + std::string Response; + + JSON = cJSON_CreateObject(); + if (JSON == NULL) { + return SCPI_ERROR_OUT_OF_MEMORY; + } + + Error = RemoteControl_GetLeptonROI(JSON); + if (Error != ESP_OK) { + cJSON_Delete(JSON); + + return SCPI_ERROR_HARDWARE_ERROR; + } + + JSONStr = cJSON_PrintUnformatted(JSON); + cJSON_Delete(JSON); + + if (JSONStr == NULL) { + return SCPI_ERROR_OUT_OF_MEMORY; + } + + Response = std::string(JSONStr) + "\n"; + cJSON_free(JSONStr); + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_SetLeptonROI(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + cJSON *JSON; + + if (Count < 5) { + return SCPI_ERROR_MISSING_PARAMETER; + } + + /* Expected format: SENS:IMG:LEP:ROI x,y,width,height */ + /* Parse as JSON: {"x":40,"y":30,"width":80,"height":60} */ + JSON = cJSON_Parse(pp_Tokens[4]); + if (JSON == NULL) { + return SCPI_ERROR_DATA_OUT_OF_RANGE; + } + + Error = RemoteControl_SetLeptonROI(JSON); + cJSON_Delete(JSON); + + if (Error != ESP_OK) { + return SCPI_ERROR_EXECUTION_ERROR; + } + + return 0; +} + +int VISA_Cmd_GetLeptonSpotmeter(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + cJSON *JSON; + char *JSONStr; + std::string Response; + + JSON = cJSON_CreateObject(); + if (JSON == NULL) { + return SCPI_ERROR_OUT_OF_MEMORY; + } + + Error = RemoteControl_GetLeptonSpotmeter(JSON); + if (Error != ESP_OK) { + cJSON_Delete(JSON); + + return SCPI_ERROR_HARDWARE_ERROR; + } + + JSONStr = cJSON_PrintUnformatted(JSON); + cJSON_Delete(JSON); + + if (JSONStr == NULL) { + return SCPI_ERROR_OUT_OF_MEMORY; + } + + Response = std::string(JSONStr) + "\n"; + cJSON_free(JSONStr); + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_GetFlashPower(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + std::string Response; + bool Enabled; + uint8_t Power; + + Error = RemoteControl_GetFlashConfig(&Enabled, &Power); + if (Error != ESP_OK) { + return SCPI_ERROR_HARDWARE_ERROR; + } + + Response = std::to_string(Power) + "\n"; + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_SetFlashPower(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + int Power; + + if (Count < 4) { + return SCPI_ERROR_MISSING_PARAMETER; + } + + Power = atoi(pp_Tokens[3]); + if ((Power < 0) || (Power > 100)) { + return SCPI_ERROR_DATA_OUT_OF_RANGE; + } + + Error = RemoteControl_SetFlashPower(static_cast(Power)); + if (Error != ESP_OK) { + return SCPI_ERROR_EXECUTION_ERROR; + } + + return 0; +} + +int VISA_Cmd_GetFlashState(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + std::string Response; + bool Enabled; + uint8_t Power; + + Error = RemoteControl_GetFlashConfig(&Enabled, &Power); + if (Error != ESP_OK) { + return Error; + } + + Response = std::string(Enabled ? "ON" : "OFF") + "\n"; + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_SetFlashState(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + bool Enabled; + std::string Value(pp_Tokens[3]); + + if (Count < 4) { + return SCPI_ERROR_MISSING_PARAMETER; + } + + std::transform(Value.begin(), Value.end(), Value.begin(), ::tolower); + if ((Value == "on") || (Value == "1")) { + Enabled = true; + } else if ((Value == "off") || (Value == "0")) { + Enabled = false; + } else { + return SCPI_ERROR_DATA_OUT_OF_RANGE; + } + + Error = RemoteControl_SetFlashState(Enabled); + if (Error != ESP_OK) { + return SCPI_ERROR_EXECUTION_ERROR; + } + + return 0; +} + +int VISA_Cmd_GetImageFormat(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + Settings_Image_Format_t Format; + const char *FormatStr; + std::string Response; + + Error = RemoteControl_GetImageFormat(&Format); + if (Error != ESP_OK) { + return SCPI_ERROR_HARDWARE_ERROR; + } + + switch (Format) { + case IMAGE_FORMAT_PNG: + FormatStr = "PNG"; + break; + case IMAGE_FORMAT_RAW: + FormatStr = "RAW"; + break; + case IMAGE_FORMAT_JPEG: + FormatStr = "JPEG"; + break; + default: + return SCPI_ERROR_HARDWARE_ERROR; + } + + Response = std::string(FormatStr) + "\n"; + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_SetImageFormat(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + Settings_Image_Format_t Format; + std::string FormatValue(pp_Tokens[3]); + + if (Count < 4) { + return SCPI_ERROR_MISSING_PARAMETER; + } + + std::transform(FormatValue.begin(), FormatValue.end(), FormatValue.begin(), ::toupper); + if (FormatValue == "PNG") { + Format = IMAGE_FORMAT_PNG; + } else if (FormatValue == "RAW") { + Format = IMAGE_FORMAT_RAW; + } else if (FormatValue == "JPEG") { + Format = IMAGE_FORMAT_JPEG; + } else { + return SCPI_ERROR_DATA_OUT_OF_RANGE; + } + + Error = RemoteControl_SetImageFormat(Format); + if (Error != ESP_OK) { + return SCPI_ERROR_EXECUTION_ERROR; + } + + return 0; +} + +int VISA_Cmd_SetStatusLED(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + Remote_LED_Color_t Color; + int Brightness; + std::string ColorValue(pp_Tokens[3]); + + if (Count < 5) { + return SCPI_ERROR_MISSING_PARAMETER; + } + + std::transform(ColorValue.begin(), ColorValue.end(), ColorValue.begin(), ::toupper); + if (ColorValue == "RED") { + Color = REMOTE_LED_RED; + } else if (ColorValue == "GREEN") { + Color = REMOTE_LED_GREEN; + } else if (ColorValue == "BLUE") { + Color = REMOTE_LED_BLUE; + } else { + return SCPI_ERROR_DATA_OUT_OF_RANGE; + } + + Brightness = atoi(pp_Tokens[4]); + if ((Brightness < 0) || (Brightness > 255)) { + return SCPI_ERROR_DATA_OUT_OF_RANGE; + } + + Error = RemoteControl_SetStatusLED(Color, static_cast(Brightness)); + if (Error != ESP_OK) { + return SCPI_ERROR_EXECUTION_ERROR; + } + + return 0; +} + +int VISA_Cmd_GetSDCardState(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + bool Available; + std::string Response; + + Error = RemoteControl_GetSDCardState(&Available); + if (Error != ESP_OK) { + return SCPI_ERROR_HARDWARE_ERROR; + } + + Response = std::string(Available ? "AVAILABLE" : "NOT_AVAILABLE") + "\n"; + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_FormatMemory(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + + Error = RemoteControl_FormatMemory(); + if (Error != ESP_OK) { + return SCPI_ERROR_EXECUTION_ERROR; + } + + return 0; +} + +int VISA_Cmd_DisplayMessageBox(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + + if (Count < 4) { + return SCPI_ERROR_MISSING_PARAMETER; + } + + Error = RemoteControl_DisplayMessageBox(pp_Tokens[3]); + if (Error != ESP_OK) { + return SCPI_ERROR_EXECUTION_ERROR; + } + + return 0; +} + +int VISA_Cmd_GetLockState(char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + bool Locked; + std::string Response; + + Error = RemoteControl_GetLockState(&Locked); + if (Error != ESP_OK) { + return SCPI_ERROR_HARDWARE_ERROR; + } + + Response = std::string(Locked ? "LOCKED" : "UNLOCKED") + "\n"; + strncpy(p_Response, Response.c_str(), MaxLen); + + return Response.size(); +} + +int VISA_Cmd_SetLockState(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen) +{ + esp_err_t Error; + bool Locked; + std::string LockValue(pp_Tokens[2]); + + if (Count < 3) { + return SCPI_ERROR_MISSING_PARAMETER; + } + + std::transform(LockValue.begin(), LockValue.end(), LockValue.begin(), ::toupper); + if ((LockValue == "LOCKED") || (LockValue == "1")) { + Locked = true; + } else if ((LockValue == "UNLOCKED") || (LockValue == "0")) { + Locked = false; + } else { + return SCPI_ERROR_DATA_OUT_OF_RANGE; + } + + Error = RemoteControl_SetLockState(Locked); + if (Error != ESP_OK) { + return SCPI_ERROR_EXECUTION_ERROR; + } + + return 0; +} + +int VISA_Cmd_SetImagePalette(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen) +{ + if (Count < 4) { + return SCPI_ERROR_MISSING_PARAMETER; + } + + std::string PaletteValue(pp_Tokens[3]); + std::transform(PaletteValue.begin(), PaletteValue.end(), PaletteValue.begin(), ::toupper); + + /* TODO: Implement palette setting via RemoteControl interface */ + /* Valid: IRON, GRAY, RAINBOW */ + if ((PaletteValue == "IRON") || (PaletteValue == "GRAY") || (PaletteValue == "RAINBOW")) { + return 0; + } else { + return SCPI_ERROR_DATA_OUT_OF_RANGE; + } +} + +int VISA_Cmd_SetLEDBrightness(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen) +{ + if (Count < 4) { + return SCPI_ERROR_MISSING_PARAMETER; + } + + int Brightness = atoi(pp_Tokens[3]); + if ((Brightness < 0) || (Brightness > 255)) { + return SCPI_ERROR_DATA_OUT_OF_RANGE; + } + + /* TODO: Implement LED brightness setting via RemoteControl interface */ + return 0; +} diff --git a/main/Application/Manager/Network/Server/VISA/Private/visaRemoteCommands.h b/main/Application/Manager/Network/Server/VISA/Private/visaRemoteCommands.h new file mode 100644 index 0000000..fdf3811 --- /dev/null +++ b/main/Application/Manager/Network/Server/VISA/Private/visaRemoteCommands.h @@ -0,0 +1,242 @@ +/* + * visaRemoteCommands.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: VISA commands for remote control interface. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef VISA_REMOTE_COMMANDS_H_ +#define VISA_REMOTE_COMMANDS_H_ + +#include + +/** @brief Handle SENS:TEMP? - Get temperature sensor value. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetTemperature(char *p_Response, size_t MaxLen); + +/** @brief Handle SYST:TIME? - Get system time. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetTime(char *p_Response, size_t MaxLen); + +/** @brief Handle SYST:TIME - Set system time. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetTime(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); + +/** @brief Handle SENS:BATT:VOLT? - Get battery voltage. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetBatteryVoltage(char *p_Response, size_t MaxLen); + +/** @brief Handle SENS:BATT:SOC? - Get state of charge. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetStateOfCharge(char *p_Response, size_t MaxLen); + +/** @brief Handle SENS:IMG:LEP:EMIS? - Get Lepton emissivity. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetLeptonEmissivity(char *p_Response, size_t MaxLen); + +/** @brief Handle SENS:IMG:LEP:EMIS - Set Lepton emissivity. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetLeptonEmissivity(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); + +/** @brief Handle SENS:IMG:LEP:STAT? - Get Lepton scene statistics. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetLeptonStats(char *p_Response, size_t MaxLen); + +/** @brief Handle SENS:IMG:LEP:ROI? - Get Lepton ROI. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetLeptonROI(char *p_Response, size_t MaxLen); + +/** @brief Handle SENS:IMG:LEP:ROI - Set Lepton ROI. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetLeptonROI(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); + +/** @brief Handle SENS:IMG:LEP:SPOT? - Get Lepton spotmeter. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetLeptonSpotmeter(char *p_Response, size_t MaxLen); + +/** @brief Handle DISP:FLASH:POW? - Get flash power. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetFlashPower(char *p_Response, size_t MaxLen); + +/** @brief Handle DISP:FLASH:POW - Set flash power. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetFlashPower(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); + +/** @brief Handle DISP:FLASH:STAT? - Get flash state. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetFlashState(char *p_Response, size_t MaxLen); + +/** @brief Handle DISP:FLASH:STAT - Set flash state. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetFlashState(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); + +/** @brief Handle SENS:IMG:FORM? - Get image format. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetImageFormat(char *p_Response, size_t MaxLen); + +/** @brief Handle SENS:IMG:FORM - Set image format. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetImageFormat(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); + +/** @brief Handle DISP:LED:STAT - Set status LED. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetStatusLED(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); + +/** @brief Handle MEM:SD:STAT? - Get SD card state. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetSDCardState(char *p_Response, size_t MaxLen); + +/** @brief Handle MEM:FORM - Format memory. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_FormatMemory(char *p_Response, size_t MaxLen); + +/** @brief Handle DISP:MBOX - Display message box. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_DisplayMessageBox(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); + +/** @brief Handle SYST:LOCK? - Get lock state. + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_GetLockState(char *p_Response, size_t MaxLen); + +/** @brief Handle SYST:LOCK - Set lock state. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetLockState(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); + +/** @brief Handle SENS:IMG:PAL - Set image color palette. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetImagePalette(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); + +/** @brief Handle DISP:LED:BRIG - Set LED brightness. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetLEDBrightness(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); +/** @brief Handle SENS:IMG:PAL - Set image color palette. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetImagePalette(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); + +/** @brief Handle DISP:LED:BRIG - Set LED brightness. + * @param pp_Tokens Command tokens + * @param Count Token count + * @param p_Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or negative error code + */ +int VISA_Cmd_SetLEDBrightness(char **pp_Tokens, int Count, char *p_Response, size_t MaxLen); +#endif /* VISA_REMOTE_COMMANDS_H_ */ diff --git a/main/Application/Manager/Network/Server/VISA/visaServer.cpp b/main/Application/Manager/Network/Server/VISA/visaServer.cpp new file mode 100644 index 0000000..f82e451 --- /dev/null +++ b/main/Application/Manager/Network/Server/VISA/visaServer.cpp @@ -0,0 +1,367 @@ +/* + * visaServer.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: VISA server implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "visaServer.h" +#include "Settings/settingsManager.h" +#include "Private/visaCommands.h" + +/** @brief Maximum command length. + */ +#define VISA_MAX_COMMAND_LENGTH 256 + +/** @brief Maximum response length. + */ +#define VISA_MAX_RESPONSE_LENGTH 1024 + +typedef struct { + int ListenSocket; /**< Listening socket */ + uint16_t Port; /**< Server port */ + uint16_t Timeout; /**< Socket timeout in milliseconds */ + TaskHandle_t ServerTask; /**< Server task handle */ + bool isRunning; /**< Server running flag */ + bool isInitialized; /**< Initialization flag */ + SemaphoreHandle_t Mutex; /**< Thread safety mutex */ +} VISA_Server_State_t; + +static VISA_Server_State_t _VISA_Server_State; + +static const char *TAG = "VISA-Server"; + +/** @brief Process VISA command and generate response + * @param Command Received command string + * @param Response Response buffer + * @param MaxLen Maximum response length + * @return Response length or error code + */ +static int VISA_ProcessCommand(const char *Command, char *Response, size_t MaxLen) +{ + size_t Length; + std::string Buffer(Command); + + if ((Command == NULL) || (Response == NULL)) { + return VISA_ERR_INVALID_ARG; + } + + ESP_LOGD(TAG, "Processing command: %s", Command); + + /* Remove trailing newline/carriage return */ + Length = Buffer.size(); + while ((Length > 0) && ((Buffer[Length - 1] == '\n') || (Buffer[Length - 1] == '\r'))) { + Buffer.resize(--Length); + } + + return VISACommands_Execute(Buffer.c_str(), Response, MaxLen); +} + +/** @brief Handle client connection. + * @param ClientSocket Client socket descriptor + */ +static void VISA_HandleClient(int ClientSocket) +{ + char RxBuffer[VISA_MAX_COMMAND_LENGTH]; + char TxBuffer[VISA_MAX_RESPONSE_LENGTH]; + struct timeval Timeout; + + Timeout.tv_sec = _VISA_Server_State.Timeout / 1000; + Timeout.tv_usec = (_VISA_Server_State.Timeout % 1000) * 1000; + + setsockopt(ClientSocket, SOL_SOCKET, SO_RCVTIMEO, &Timeout, sizeof(Timeout)); + setsockopt(ClientSocket, SOL_SOCKET, SO_SNDTIMEO, &Timeout, sizeof(Timeout)); + + ESP_LOGI(TAG, "Client connected"); + + while (_VISA_Server_State.isRunning) { + int Length; + + memset(RxBuffer, 0, sizeof(RxBuffer)); + + Length = recv(ClientSocket, RxBuffer, sizeof(RxBuffer) - 1, 0); + if (Length < 0) { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { + /* Timeout, continue */ + continue; + } + + ESP_LOGE(TAG, "recv failed: errno %d!", errno); + + break; + } else if (Length == 0) { + ESP_LOGI(TAG, "Client disconnected"); + + break; + } + + RxBuffer[Length] = '\0'; + ESP_LOGD(TAG, "Received: %s", RxBuffer); + + memset(TxBuffer, 0, sizeof(TxBuffer)); + + /* Process command */ + Length = VISA_ProcessCommand(RxBuffer, TxBuffer, sizeof(TxBuffer)); + if (Length > 0) { + int Sent; + + /* Send response */ + Sent = send(ClientSocket, TxBuffer, Length, 0); + if (Sent < 0) { + ESP_LOGE(TAG, "send failed: %d!", errno); + + break; + } + + ESP_LOGD(TAG, "Sent %d bytes", Sent); + } else if (Length < 0) { + std::string ErrorStr; + + ErrorStr = "ERROR: " + std::to_string(Length) + "\n"; + send(ClientSocket, ErrorStr.c_str(), ErrorStr.size(), 0); + } + } + + close(ClientSocket); + ESP_LOGI(TAG, "Client connection closed"); + + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_VISA_CLIENT_DISCONNECTED, NULL, 0, portMAX_DELAY); +} + +/** @brief VISA server task. + * @param p_Args Task arguments (unused) + */ +static void Task_VisaServer(void *p_Args) +{ + int opt = 1; + int Error; + struct sockaddr_in Addr; + + Addr.sin_addr.s_addr = htonl(INADDR_ANY); + Addr.sin_family = AF_INET; + Addr.sin_port = htons(_VISA_Server_State.Port); + + _VISA_Server_State.ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (_VISA_Server_State.ListenSocket < 0) { + ESP_LOGE(TAG, "Unable to create socket: %d!", errno); + + _VISA_Server_State.isRunning = false; + vTaskDelete(NULL); + + return; + } + + setsockopt(_VISA_Server_State.ListenSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + Error = bind(_VISA_Server_State.ListenSocket, (struct sockaddr *)&Addr, sizeof(Addr)); + if (Error != 0) { + ESP_LOGE(TAG, "Socket unable to bind: %d!", errno); + + close(_VISA_Server_State.ListenSocket); + _VISA_Server_State.isRunning = false; + vTaskDelete(NULL); + + return; + } + + Error = listen(_VISA_Server_State.ListenSocket, CONFIG_NETWORK_VISA_MAX_CLIENTS); + if (Error != 0) { + ESP_LOGE(TAG, "Error occurred during listen: %d!", errno); + + close(_VISA_Server_State.ListenSocket); + _VISA_Server_State.isRunning = false; + vTaskDelete(NULL); + + return; + } + + ESP_LOGI(TAG, "VISA server listening on port %d", _VISA_Server_State.Port); + + while (_VISA_Server_State.isRunning) { + struct sockaddr_in Source; + socklen_t Length = sizeof(Source); + int Socket; + std::string Address(16, '\0'); + + Socket = accept(_VISA_Server_State.ListenSocket, (struct sockaddr *)&Source, &Length); + if (Socket < 0) { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { + continue; + } + + ESP_LOGE(TAG, "Unable to accept connection: errno %d!", errno); + + break; + } + + inet_ntop(AF_INET, &Source.sin_addr, &Address[0], Address.size()); + Address.resize(strlen(Address.c_str())); + ESP_LOGI(TAG, "Client connected from %s:%d", Address.c_str(), ntohs(Source.sin_port)); + + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_VISA_CLIENT_CONNECTED, NULL, 0, portMAX_DELAY); + + VISA_HandleClient(Socket); + } + + close(_VISA_Server_State.ListenSocket); + _VISA_Server_State.ListenSocket = -1; + _VISA_Server_State.isRunning = false; + + ESP_LOGI(TAG, "VISA server stopped"); + + vTaskDelete(NULL); +} + +esp_err_t VISAServer_Init(void) +{ + esp_err_t Error; + Settings_VISA_Server_t Config; + + if (_VISA_Server_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + SettingsManager_GetVISAServer(&Config); + + memset(&_VISA_Server_State, 0, sizeof(_VISA_Server_State)); + _VISA_Server_State.Port = Config.Port; + _VISA_Server_State.Timeout = Config.Timeout; + + _VISA_Server_State.Mutex = xSemaphoreCreateMutex(); + if (_VISA_Server_State.Mutex == NULL) { + ESP_LOGE(TAG, "Failed to create mutex!"); + + return ESP_ERR_NO_MEM; + } + + Error = VISACommands_Init(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize command handler: 0x%x!", Error); + + vSemaphoreDelete(_VISA_Server_State.Mutex); + + return Error; + } + + _VISA_Server_State.ListenSocket = -1; + _VISA_Server_State.isInitialized = true; + + ESP_LOGD(TAG, "VISA server initialized"); + + return ESP_OK; +} + +esp_err_t VISAServer_Deinit(void) +{ + if (_VISA_Server_State.isInitialized == false) { + return ESP_OK; + } + + VISAServer_Stop(); + VISACommands_Deinit(); + + if (_VISA_Server_State.Mutex != NULL) { + vSemaphoreDelete(_VISA_Server_State.Mutex); + _VISA_Server_State.Mutex = NULL; + } + + _VISA_Server_State.isInitialized = false; + + ESP_LOGD(TAG, "VISA server deinitialized"); + + return ESP_OK; +} + +bool VISAServer_IsRunning(void) +{ + return _VISA_Server_State.isRunning; +} + +esp_err_t VISAServer_Start(void) +{ + BaseType_t Error; + + if (_VISA_Server_State.isInitialized == false) { + ESP_LOGE(TAG, "Not initialized!"); + + return ESP_ERR_INVALID_STATE; + } else if (_VISA_Server_State.isRunning) { + ESP_LOGW(TAG, "Already running"); + + return ESP_OK; + } + + xSemaphoreTake(_VISA_Server_State.Mutex, portMAX_DELAY); + + _VISA_Server_State.isRunning = true; + + Error = xTaskCreate(Task_VisaServer, "visa_server", 4096, NULL, 5, &_VISA_Server_State.ServerTask); + if (Error != pdPASS) { + ESP_LOGE(TAG, "Failed to create server task!"); + + _VISA_Server_State.isRunning = false; + xSemaphoreGive(_VISA_Server_State.Mutex); + + return ESP_ERR_NO_MEM; + } + + xSemaphoreGive(_VISA_Server_State.Mutex); + + ESP_LOGD(TAG, "VISA server started"); + + return ESP_OK; +} + +esp_err_t VISAServer_Stop(void) +{ + if (_VISA_Server_State.isRunning == false) { + return ESP_OK; + } + + xSemaphoreTake(_VISA_Server_State.Mutex, portMAX_DELAY); + + _VISA_Server_State.isRunning = false; + + if (_VISA_Server_State.ListenSocket >= 0) { + shutdown(_VISA_Server_State.ListenSocket, SHUT_RDWR); + close(_VISA_Server_State.ListenSocket); + _VISA_Server_State.ListenSocket = -1; + } + + xSemaphoreGive(_VISA_Server_State.Mutex); + + ESP_LOGD(TAG, "VISA server stopped"); + + return ESP_OK; +} diff --git a/main/Application/Manager/Network/Server/VISA/visaServer.h b/main/Application/Manager/Network/Server/VISA/visaServer.h new file mode 100644 index 0000000..d5cb962 --- /dev/null +++ b/main/Application/Manager/Network/Server/VISA/visaServer.h @@ -0,0 +1,68 @@ +/* + * visaServer.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: VISA server implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef VISA_SERVER_H_ +#define VISA_SERVER_H_ + +#include + +#include "../../networkTypes.h" + +/** @brief VISA error codes */ +typedef enum { + VISA_OK = 0, /**< Operation successful */ + VISA_ERR_INVALID_ARG = -1, /**< Invalid argument */ + VISA_ERR_NO_MEM = -2, /**< Out of memory */ + VISA_ERR_SOCKET = -3, /**< Socket error */ + VISA_ERR_TIMEOUT = -4, /**< Operation timeout */ + VISA_ERR_INVALID_COMMAND = -5, /**< Invalid command */ + VISA_ERR_EXECUTION = -6, /**< Command execution error */ + VISA_ERR_NOT_INITIALIZED = -7, /**< Server not initialized */ +} VISA_Error_t; + +/** @brief Initialize VISA server. + * @return VISA_OK on success, error code otherwise + */ +esp_err_t VISAServer_Init(void); + +/** @brief Deinitialize VISA server. + * @return VISA_OK on success, error code otherwise + */ +esp_err_t VISAServer_Deinit(void); + +/** @brief Check if VISA server is running + * @return true if running, false otherwise + */ +bool VISAServer_IsRunning(void); + +/** @brief Start VISA server. + * @return VISA_OK on success, error code otherwise + */ +esp_err_t VISAServer_Start(void); + +/** @brief Stop VISA server. + * @return VISA_OK on success, error code otherwise + */ +esp_err_t VISAServer_Stop(void); + +#endif /* VISA_SERVER_H_ */ diff --git a/main/Application/Manager/Network/Server/WebSocket/Private/websocketRemoteHandlers.cpp b/main/Application/Manager/Network/Server/WebSocket/Private/websocketRemoteHandlers.cpp new file mode 100644 index 0000000..4e8d833 --- /dev/null +++ b/main/Application/Manager/Network/Server/WebSocket/Private/websocketRemoteHandlers.cpp @@ -0,0 +1,513 @@ +/* + * websocketRemoteHandlers.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: WebSocket handlers for remote control interface. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include +#include + +#include "websocketRemoteHandlers.h" +#include "../../RemoteControl/remoteControl.h" + +static const char *TAG = "WebSocket-Remote"; + +static void WebSocket_SendResponse(int FD, const char *p_Cmd, const char *p_Status, cJSON *p_Data, const char *p_Error) +{ + cJSON *Response; + + Response = cJSON_CreateObject(); + cJSON_AddStringToObject(Response, "status", p_Status); + + if (p_Data != NULL) { + cJSON_AddItemReferenceToObject(Response, "data", p_Data); + } else if (p_Error != NULL) { + cJSON_AddStringToObject(Response, "error", p_Error); + } + + WebSocket_SendJSON(FD, p_Cmd, Response); + cJSON_Delete(Response); +} + +void WebSocket_Handle_GetTemperature(int FD, cJSON *p_Data) +{ + esp_err_t Error; + float Temperature; + cJSON *Data; + + Error = RemoteControl_GetTemperature(&Temperature); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "temperature", "error", NULL, "Failed to get temperature"); + + return; + } + + Data = cJSON_CreateObject(); + cJSON_AddNumberToObject(Data, "temperature", Temperature); + + WebSocket_SendResponse(FD, "temperature", "ok", Data, NULL); + cJSON_Delete(Data); +} + +void WebSocket_Handle_GetTime(int FD, cJSON *p_Data) +{ + esp_err_t Error; + char TimeStr[32]; + cJSON *Data; + + Error = RemoteControl_GetTime(TimeStr, sizeof(TimeStr)); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "time", "error", NULL, "Failed to get time"); + + return; + } + + Data = cJSON_CreateObject(); + cJSON_AddStringToObject(Data, "time", TimeStr); + + WebSocket_SendResponse(FD, "time", "ok", Data, NULL); + cJSON_Delete(Data); +} + +void WebSocket_Handle_SetTime(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *TimeField; + + TimeField = cJSON_GetObjectItem(p_Data, "time"); + if (cJSON_IsString(TimeField) == false) { + WebSocket_SendResponse(FD, "set_time", "error", NULL, "Missing or invalid 'time' field"); + + return; + } + + Error = RemoteControl_SetTime(TimeField->valuestring); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "set_time", "error", NULL, "Invalid time format"); + + return; + } + + WebSocket_SendResponse(FD, "set_time", "ok", NULL, NULL); +} + +void WebSocket_Handle_GetBattery(int FD, cJSON *p_Data) +{ + esp_err_t Error; + int Voltage; + uint8_t SOC; + cJSON *Data; + + Error = RemoteControl_GetBatteryVoltage(&Voltage); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "battery", "error", NULL, "Failed to get battery voltage"); + + return; + } + + Error = RemoteControl_GetStateOfCharge(&SOC); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "battery", "error", NULL, "Failed to get SOC"); + + return; + } + + Data = cJSON_CreateObject(); + cJSON_AddNumberToObject(Data, "voltage", Voltage); + cJSON_AddNumberToObject(Data, "soc", SOC); + + WebSocket_SendResponse(FD, "battery", "ok", Data, NULL); + cJSON_Delete(Data); +} + +void WebSocket_Handle_GetLeptonEmissivity(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *Data; + uint8_t Emissivity; + + Error = RemoteControl_GetLeptonEmissivity(&Emissivity); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "lepton_emissivity", "error", NULL, "Failed to get emissivity"); + + return; + } + + Data = cJSON_CreateObject(); + cJSON_AddNumberToObject(Data, "emissivity", Emissivity); + + WebSocket_SendResponse(FD, "lepton_emissivity", "ok", Data, NULL); + cJSON_Delete(Data); +} + +void WebSocket_Handle_SetLeptonEmissivity(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *EmisField; + + EmisField = cJSON_GetObjectItem(p_Data, "emissivity"); + if (cJSON_IsNumber(EmisField) == false) { + WebSocket_SendResponse(FD, "set_lepton_emissivity", "error", NULL, "Missing or invalid 'emissivity' field"); + + return; + } + + Error = RemoteControl_SetLeptonEmissivity(static_cast(EmisField->valueint)); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "set_lepton_emissivity", "error", NULL, "Invalid emissivity value"); + + return; + } + + WebSocket_SendResponse(FD, "set_lepton_emissivity", "ok", NULL, NULL); +} + +void WebSocket_Handle_GetLeptonStats(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *Data; + + Data = cJSON_CreateObject(); + Error = RemoteControl_GetLeptonSceneStats(Data); + if (Error != ESP_OK) { + cJSON_Delete(Data); + WebSocket_SendResponse(FD, "lepton_stats", "error", NULL, "Failed to get scene stats"); + + return; + } + + WebSocket_SendResponse(FD, "lepton_stats", "ok", Data, NULL); + cJSON_Delete(Data); +} + +void WebSocket_Handle_GetLeptonROI(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *Data; + + Data = cJSON_CreateObject(); + Error = RemoteControl_GetLeptonROI(Data); + if (Error != ESP_OK) { + cJSON_Delete(Data); + WebSocket_SendResponse(FD, "lepton_roi", "error", NULL, "Failed to get ROI"); + return; + } + + WebSocket_SendResponse(FD, "lepton_roi", "ok", Data, NULL); + cJSON_Delete(Data); +} + +void WebSocket_Handle_SetLeptonROI(int FD, cJSON *p_Data) +{ + esp_err_t Error; + + Error = RemoteControl_SetLeptonROI(p_Data); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "set_lepton_roi", "error", NULL, "Invalid ROI data"); + + return; + } + + WebSocket_SendResponse(FD, "set_lepton_roi", "ok", NULL, NULL); +} + +void WebSocket_Handle_GetLeptonSpotmeter(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *Data; + + Data = cJSON_CreateObject(); + Error = RemoteControl_GetLeptonSpotmeter(Data); + if (Error != ESP_OK) { + cJSON_Delete(Data); + WebSocket_SendResponse(FD, "lepton_spotmeter", "error", NULL, "Failed to get spotmeter"); + + return; + } + + WebSocket_SendResponse(FD, "lepton_spotmeter", "ok", Data, NULL); + cJSON_Delete(Data); +} + +void WebSocket_Handle_GetFlash(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *Data; + bool Enabled; + uint8_t Power; + + Error = RemoteControl_GetFlashConfig(&Enabled, &Power); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "flash", "error", NULL, "Failed to get flash state"); + + return; + } + + Data = cJSON_CreateObject(); + cJSON_AddBoolToObject(Data, "enabled", Enabled); + cJSON_AddNumberToObject(Data, "power", Power); + + WebSocket_SendResponse(FD, "flash", "ok", Data, NULL); + cJSON_Delete(Data); +} + +void WebSocket_Handle_SetFlash(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *EnabledField; + cJSON *PowerField; + + EnabledField = cJSON_GetObjectItem(p_Data, "enabled"); + if (cJSON_IsBool(EnabledField)) { + Error = RemoteControl_SetFlashState(cJSON_IsTrue(EnabledField)); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "set_flash", "error", NULL, "Failed to set flash state"); + + return; + } + } + + PowerField = cJSON_GetObjectItem(p_Data, "power"); + if (cJSON_IsNumber(PowerField)) { + Error = RemoteControl_SetFlashPower(static_cast(PowerField->valueint)); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "set_flash", "error", NULL, "Invalid flash power value"); + + return; + } + } + + WebSocket_SendResponse(FD, "set_flash", "ok", NULL, NULL); +} + +void WebSocket_Handle_GetImageFormat(int FD, cJSON *p_Data) +{ + esp_err_t Error; + Settings_Image_Format_t ImageFormat; + const char *Format; + cJSON *Data; + + Error = RemoteControl_GetImageFormat(&ImageFormat); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "image_format", "error", NULL, "Failed to get image format"); + + return; + } + + switch (ImageFormat) { + case IMAGE_FORMAT_PNG: { + Format = "PNG"; + + break; + } + case IMAGE_FORMAT_RAW: { + Format = "RAW"; + + break; + } + case IMAGE_FORMAT_JPEG: { + Format = "JPEG"; + + break; + } + default: { + WebSocket_SendResponse(FD, "image_format", "error", NULL, "Unknown format"); + + return; + } + } + + Data = cJSON_CreateObject(); + cJSON_AddStringToObject(Data, "format", Format); + + WebSocket_SendResponse(FD, "image_format", "ok", Data, NULL); + cJSON_Delete(Data); +} + +void WebSocket_Handle_SetImageFormat(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *FormatField; + Settings_Image_Format_t Format; + + FormatField = cJSON_GetObjectItem(p_Data, "format"); + if (cJSON_IsString(FormatField) == false) { + WebSocket_SendResponse(FD, "set_image_format", "error", NULL, "Missing or invalid 'format' field"); + return; + } + + if (strcasecmp(FormatField->valuestring, "PNG") == 0) { + Format = IMAGE_FORMAT_PNG; + } else if (strcasecmp(FormatField->valuestring, "RAW") == 0) { + Format = IMAGE_FORMAT_RAW; + } else if (strcasecmp(FormatField->valuestring, "JPEG") == 0) { + Format = IMAGE_FORMAT_JPEG; + } else { + WebSocket_SendResponse(FD, "set_image_format", "error", NULL, "Invalid format"); + return; + } + + Error = RemoteControl_SetImageFormat(Format); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "set_image_format", "error", NULL, "Failed to set image format"); + return; + } + + WebSocket_SendResponse(FD, "set_image_format", "ok", NULL, NULL); +} + +void WebSocket_Handle_SetStatusLED(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *ColorField; + cJSON *BrightnessField; + Remote_LED_Color_t Color; + uint8_t Brightness; + + ColorField = cJSON_GetObjectItem(p_Data, "color"); + BrightnessField = cJSON_GetObjectItem(p_Data, "brightness"); + + if ((cJSON_IsString(ColorField) == false) || (cJSON_IsNumber(BrightnessField) == false)) { + WebSocket_SendResponse(FD, "set_status_led", "error", NULL, "Missing or invalid fields"); + + return; + } + + if (strcasecmp(ColorField->valuestring, "RED") == 0) { + Color = REMOTE_LED_RED; + } else if (strcasecmp(ColorField->valuestring, "GREEN") == 0) { + Color = REMOTE_LED_GREEN; + } else if (strcasecmp(ColorField->valuestring, "BLUE") == 0) { + Color = REMOTE_LED_BLUE; + } else { + WebSocket_SendResponse(FD, "set_status_led", "error", NULL, "Invalid color"); + return; + } + + Brightness = static_cast(BrightnessField->valueint); + + Error = RemoteControl_SetStatusLED(Color, Brightness); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "set_status_led", "error", NULL, "Failed to set LED"); + return; + } + + WebSocket_SendResponse(FD, "set_status_led", "ok", NULL, NULL); +} + +void WebSocket_Handle_GetSDState(int FD, cJSON *p_Data) +{ + esp_err_t Error; + bool Available; + cJSON *Data; + + Error = RemoteControl_GetSDCardState(&Available); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "sd_state", "error", NULL, "Failed to get SD state"); + + return; + } + + Data = cJSON_CreateObject(); + cJSON_AddBoolToObject(Data, "available", Available); + + WebSocket_SendResponse(FD, "sd_state", "ok", Data, NULL); + cJSON_Delete(Data); +} + +void WebSocket_Handle_FormatMemory(int FD, cJSON *p_Data) +{ + esp_err_t Error; + + Error = RemoteControl_FormatMemory(); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "format_memory", "error", NULL, "Format operation failed"); + + return; + } + + WebSocket_SendResponse(FD, "format_memory", "ok", NULL, NULL); +} + +void WebSocket_Handle_DisplayMessage(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *MessageField; + + MessageField = cJSON_GetObjectItem(p_Data, "message"); + if (cJSON_IsString(MessageField) == false) { + WebSocket_SendResponse(FD, "display_message", "error", NULL, "Missing or invalid 'message' field"); + + return; + } + + Error = RemoteControl_DisplayMessageBox(MessageField->valuestring); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "display_message", "error", NULL, "Failed to display message"); + + return; + } + + WebSocket_SendResponse(FD, "display_message", "ok", NULL, NULL); +} + +void WebSocket_Handle_GetLock(int FD, cJSON *p_Data) +{ + esp_err_t Error; + bool Locked; + cJSON *Data; + + Error = RemoteControl_GetLockState(&Locked); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "lock", "error", NULL, "Failed to get lock state"); + + return; + } + + Data = cJSON_CreateObject(); + cJSON_AddBoolToObject(Data, "locked", Locked); + + WebSocket_SendResponse(FD, "lock", "ok", Data, NULL); + cJSON_Delete(Data); +} + +void WebSocket_Handle_SetLock(int FD, cJSON *p_Data) +{ + esp_err_t Error; + cJSON *LockedField; + + LockedField = cJSON_GetObjectItem(p_Data, "locked"); + if (cJSON_IsBool(LockedField) == false) { + WebSocket_SendResponse(FD, "set_lock", "error", NULL, "Missing or invalid 'locked' field"); + + return; + } + + Error = RemoteControl_SetLockState(cJSON_IsTrue(LockedField)); + if (Error != ESP_OK) { + WebSocket_SendResponse(FD, "set_lock", "error", NULL, "Failed to set lock state"); + + return; + } + + WebSocket_SendResponse(FD, "set_lock", "ok", NULL, NULL); +} diff --git a/main/Application/Manager/Network/Server/WebSocket/Private/websocketRemoteHandlers.h b/main/Application/Manager/Network/Server/WebSocket/Private/websocketRemoteHandlers.h new file mode 100644 index 0000000..7e42678 --- /dev/null +++ b/main/Application/Manager/Network/Server/WebSocket/Private/websocketRemoteHandlers.h @@ -0,0 +1,163 @@ +/* + * websocketRemoteHandlers.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: WebSocket handlers for remote control interface. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef WEBSOCKET_REMOTE_HANDLERS_H_ +#define WEBSOCKET_REMOTE_HANDLERS_H_ + +#include + +#include + +#include + +/** @brief Send JSON message to WebSocket client. + * @param FD File descriptor of client socket + * @param p_Cmd Command string + * @param p_Data Optional JSON data object (can be NULL) + * @return ESP_OK on success + * ESP_ERR_NO_MEM if JSON creation failed + * ESP_ERR_INVALID_STATE if send failed + */ +esp_err_t WebSocket_SendJSON(int FD, const char *p_Cmd, cJSON *p_Data); + +/** @brief Handle "get_temperature" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_GetTemperature(int FD, cJSON *p_Data); + +/** @brief Handle "get_time" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_GetTime(int FD, cJSON *p_Data); + +/** @brief Handle "set_time" command. + * @param FD Client file descriptor + * @param p_Data Command data (must contain "time" field) + */ +void WebSocket_Handle_SetTime(int FD, cJSON *p_Data); + +/** @brief Handle "get_battery" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_GetBattery(int FD, cJSON *p_Data); + +/** @brief Handle "get_lepton_emissivity" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_GetLeptonEmissivity(int FD, cJSON *p_Data); + +/** @brief Handle "set_lepton_emissivity" command. + * @param FD Client file descriptor + * @param p_Data Command data (must contain "emissivity" field) + */ +void WebSocket_Handle_SetLeptonEmissivity(int FD, cJSON *p_Data); + +/** @brief Handle "get_lepton_stats" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_GetLeptonStats(int FD, cJSON *p_Data); + +/** @brief Handle "get_lepton_roi" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_GetLeptonROI(int FD, cJSON *p_Data); + +/** @brief Handle "set_lepton_roi" command. + * @param FD Client file descriptor + * @param p_Data Command data (must contain ROI fields) + */ +void WebSocket_Handle_SetLeptonROI(int FD, cJSON *p_Data); + +/** @brief Handle "get_lepton_spotmeter" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_GetLeptonSpotmeter(int FD, cJSON *p_Data); + +/** @brief Handle "get_flash" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_GetFlash(int FD, cJSON *p_Data); + +/** @brief Handle "set_flash" command. + * @param FD Client file descriptor + * @param p_Data Command data (must contain "enabled" and/or "power" fields) + */ +void WebSocket_Handle_SetFlash(int FD, cJSON *p_Data); + +/** @brief Handle "get_image_format" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_GetImageFormat(int FD, cJSON *p_Data); + +/** @brief Handle "set_image_format" command. + * @param FD Client file descriptor + * @param p_Data Command data (must contain "format" field) + */ +void WebSocket_Handle_SetImageFormat(int FD, cJSON *p_Data); + +/** @brief Handle "set_status_led" command. + * @param FD Client file descriptor + * @param p_Data Command data (must contain "color" and "brightness" fields) + */ +void WebSocket_Handle_SetStatusLED(int FD, cJSON *p_Data); + +/** @brief Handle "get_sd_state" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_GetSDState(int FD, cJSON *p_Data); + +/** @brief Handle "format_memory" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_FormatMemory(int FD, cJSON *p_Data); + +/** @brief Handle "display_message" command. + * @param FD Client file descriptor + * @param p_Data Command data (must contain "message" field) + */ +void WebSocket_Handle_DisplayMessage(int FD, cJSON *p_Data); + +/** @brief Handle "get_lock" command. + * @param FD Client file descriptor + * @param p_Data Command data (unused) + */ +void WebSocket_Handle_GetLock(int FD, cJSON *p_Data); + +/** @brief Handle "set_lock" command. + * @param FD Client file descriptor + * @param p_Data Command data (must contain "locked" field) + */ +void WebSocket_Handle_SetLock(int FD, cJSON *p_Data); + +#endif /* WEBSOCKET_REMOTE_HANDLERS_H_ */ diff --git a/main/Application/Manager/Network/Server/WebSocket/websocket.cpp b/main/Application/Manager/Network/Server/WebSocket/websocket.cpp new file mode 100644 index 0000000..1c6721f --- /dev/null +++ b/main/Application/Manager/Network/Server/WebSocket/websocket.cpp @@ -0,0 +1,872 @@ +/* + * websocket_handler.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: WebSocket handler implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include + +#include +#include + +#include "websocket.h" +#include "../ImageEncoder/imageEncoder.h" +#include "Private/websocketRemoteHandlers.h" + +/** @brief WebSocket client state. + */ +typedef struct { + int fd; + bool active; + bool stream_enabled; + bool telemetry_enabled; + Settings_Image_Format_t stream_format; + uint8_t stream_fps; + uint32_t telemetry_interval_ms; + uint32_t last_telemetry_time; + uint32_t last_frame_time; +} WS_Client_t; + +typedef struct { + bool isInitialized; + bool isRunning; + httpd_handle_t ServerHandle; + WS_Client_t Clients[CONFIG_NETWORK_WEBSOCKET_CLIENTS]; + uint8_t ClientCount; + Network_Thermal_Frame_t *ThermalFrame; + SemaphoreHandle_t ClientsMutex; + TaskHandle_t BroadcastTask; + QueueHandle_t FrameReadyQueue; +} WebSocket_State_t; + +static WebSocket_State_t WebSocket_State; + +static const char *TAG = "WebSocket"; + +/** @brief Find client by file descriptor. + * @param FD File descriptor + * @return Pointer to client or NULL if not found + */ +static WS_Client_t *WebSocket_FindClient(int FD) +{ + xSemaphoreTake(WebSocket_State.ClientsMutex, portMAX_DELAY); + + for (uint8_t i = 0; i < CONFIG_NETWORK_WEBSOCKET_CLIENTS; i++) { + if (WebSocket_State.Clients[i].active && WebSocket_State.Clients[i].fd == FD) { + return &WebSocket_State.Clients[i]; + } + } + + xSemaphoreGive(WebSocket_State.ClientsMutex); + + return NULL; +} + +/** @brief Add a new client. + * @param FD File descriptor + * @return Pointer to new client or NULL if full + */ +static WS_Client_t *WebSocket_AddClient(int FD) +{ + xSemaphoreTake(WebSocket_State.ClientsMutex, portMAX_DELAY); + + for (uint8_t i = 0; i < CONFIG_NETWORK_WEBSOCKET_CLIENTS; i++) { + if (WebSocket_State.Clients[i].active == false) { + WebSocket_State.Clients[i].fd = FD; + WebSocket_State.Clients[i].active = true; + WebSocket_State.Clients[i].stream_enabled = false; + WebSocket_State.Clients[i].telemetry_enabled = false; + WebSocket_State.Clients[i].stream_format = IMAGE_FORMAT_JPEG; + WebSocket_State.Clients[i].stream_fps = 8; + WebSocket_State.Clients[i].telemetry_interval_ms = 1000; + WebSocket_State.Clients[i].last_telemetry_time = 0; + WebSocket_State.Clients[i].last_frame_time = 0; + WebSocket_State.ClientCount++; + + xSemaphoreGive(WebSocket_State.ClientsMutex); + + ESP_LOGI(TAG, "Client added: fd=%d, total=%d", FD, WebSocket_State.ClientCount); + + return &WebSocket_State.Clients[i]; + } + } + + xSemaphoreGive(WebSocket_State.ClientsMutex); + + ESP_LOGW(TAG, "Max clients reached, rejecting fd=%d", FD); + + return NULL; +} + +/** @brief Remove a client. + * @param FD File descriptor + */ +static void WebSocket_RemoveClient(int FD) +{ + xSemaphoreTake(WebSocket_State.ClientsMutex, portMAX_DELAY); + + for (uint8_t i = 0; i < CONFIG_NETWORK_WEBSOCKET_CLIENTS; i++) { + if (WebSocket_State.Clients[i].active && WebSocket_State.Clients[i].fd == FD) { + WebSocket_State.Clients[i].active = false; + WebSocket_State.ClientCount--; + + ESP_LOGI(TAG, "Client removed: fd=%d, total=%d", FD, WebSocket_State.ClientCount); + + break; + } + } + + xSemaphoreGive(WebSocket_State.ClientsMutex); +} + +/** @brief Send JSON message to a client. + * @param FD File descriptor + * @param p_Cmd Command/Event name + * @param p_Data Data JSON object (will not be freed, can be NULL) + * @return ESP_OK on success + */ +esp_err_t WebSocket_SendJSON(int FD, const char *p_Cmd, cJSON *p_Data) +{ + esp_err_t Error; + char *JSON; + httpd_ws_frame_t Frame = { + .final = true, + .fragmented = false, + .type = HTTPD_WS_TYPE_TEXT, + .payload = NULL, + .len = 0, + }; + + cJSON *msg = cJSON_CreateObject(); + cJSON_AddStringToObject(msg, "cmd", p_Cmd); + + if (p_Data != NULL) { + cJSON_AddItemReferenceToObject(msg, "data", p_Data); + } + + JSON = cJSON_PrintUnformatted(msg); + cJSON_Delete(msg); + + if (JSON == NULL) { + return ESP_ERR_NO_MEM; + } + + Frame.payload = reinterpret_cast(JSON); + Frame.len = std::string(JSON).size(); + + Error = httpd_ws_send_frame_async(WebSocket_State.ServerHandle, FD, &Frame); + + cJSON_free(JSON); + + return Error; +} + +/** @brief Send binary frame to a client. + * @param FD File descriptor + * @param p_Data Binary data + * @param Length Data length + * @return ESP_OK on success + */ +static esp_err_t WebSocket_SendBinary(int FD, const uint8_t *p_Data, size_t Length) +{ + esp_err_t Error; + httpd_ws_frame_t Frame = { + .final = true, + .fragmented = false, + .type = HTTPD_WS_TYPE_BINARY, + .payload = const_cast(p_Data), + .len = Length, + }; + + /* Try to send with retry logic to handle queue congestion */ + Error = ESP_FAIL; + for (uint8_t Retry = 0; Retry < 3; Retry++) { + Error = httpd_ws_send_frame_async(WebSocket_State.ServerHandle, FD, &Frame); + + if (Error == ESP_OK) { + /* Send queued successfully - add small delay to prevent queue overflow */ + vTaskDelay(pdMS_TO_TICKS(5)); + + break; + } + + /* Queue might be full, wait and retry */ + ESP_LOGW(TAG, "Failed to queue frame to fd=%d (retry %d): %d!", FD, Retry + 1, Error); + vTaskDelay(pdMS_TO_TICKS(50)); + } + + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to send binary frame to fd=%d after retries: %d (len=%zu)!", + FD, Error, Length); + } + + return Error; +} + +/** @brief Handle start command. + * @param p_Client Client pointer + * @param p_Data Command data + */ +static void WebSocket_HandleStart(WS_Client_t *p_Client, cJSON *p_Data) +{ + cJSON *fps = cJSON_GetObjectItem(p_Data, "fps"); + + if (cJSON_IsNumber(fps)) { + p_Client->stream_fps = static_cast(fps->valueint); + if (p_Client->stream_fps < 1) { + p_Client->stream_fps = 1; + } + if (p_Client->stream_fps > 30) { + p_Client->stream_fps = 30; + } + } + + /* Always use JPEG format for simplicity and efficiency */ + p_Client->stream_format = IMAGE_FORMAT_JPEG; + p_Client->stream_enabled = true; + p_Client->last_frame_time = 0; + + ESP_LOGI(TAG, "Stream started for fd=%d, fps=%d", p_Client->fd, p_Client->stream_fps); + + /* Send ACK */ + cJSON *JSON = cJSON_CreateObject(); + cJSON_AddStringToObject(JSON, "status", "ok"); + cJSON_AddNumberToObject(JSON, "fps", p_Client->stream_fps); + WebSocket_SendJSON(p_Client->fd, "started", JSON); + cJSON_Delete(JSON); +} + +/** @brief Handle stop command. + * @param p_Client Client pointer + */ +static void WebSocket_HandleStop(WS_Client_t *p_Client) +{ + cJSON *JSON; + + p_Client->stream_enabled = false; + p_Client->last_frame_time = 0; + + ESP_LOGI(TAG, "Stream stopped for fd=%d", p_Client->fd); + + JSON = cJSON_CreateObject(); + cJSON_AddStringToObject(JSON, "status", "ok"); + WebSocket_SendJSON(p_Client->fd, "stopped", JSON); + cJSON_Delete(JSON); +} + +/** @brief Handle subscribe command. + * @param p_Client Client pointer + * @param p_Data Command data + */ +static void WebSocket_HandleTelemetrySubscribe(WS_Client_t *p_Client, cJSON *p_Data) +{ + cJSON *JSON; + cJSON *Interval = cJSON_GetObjectItem(p_Data, "interval"); + + if (cJSON_IsNumber(Interval)) { + p_Client->telemetry_interval_ms = static_cast(Interval->valueint); + if (p_Client->telemetry_interval_ms < 100) { + p_Client->telemetry_interval_ms = 100; + } + } + + p_Client->telemetry_enabled = true; + + ESP_LOGI(TAG, "Telemetry subscribed for fd=%d, interval=%lu ms", + p_Client->fd, p_Client->telemetry_interval_ms); + + JSON = cJSON_CreateObject(); + cJSON_AddStringToObject(JSON, "status", "ok"); + WebSocket_SendJSON(p_Client->fd, "subscribed", JSON); + cJSON_Delete(JSON); +} + +/** @brief Handle unsubscribe command. + * @param p_Client Client pointer + */ +static void WebSocket_HandleTelemetryUnsubscribe(WS_Client_t *p_Client) +{ + cJSON *JSON; + + p_Client->telemetry_enabled = false; + + ESP_LOGI(TAG, "Telemetry unsubscribed for fd=%d", p_Client->fd); + + JSON = cJSON_CreateObject(); + cJSON_AddStringToObject(JSON, "status", "ok"); + WebSocket_SendJSON(p_Client->fd, "unsubscribed", JSON); + cJSON_Delete(JSON); +} + +/** @brief Process incoming WebSocket message. + * @param p_Client Client pointer + * @param p_Data Message data + * @param length Message length + */ +static void WebSocket_ProcessMessage(WS_Client_t *p_Client, const char *p_Data, size_t length) +{ + cJSON *JSON; + cJSON *Cmd; + cJSON *Data; + + JSON = cJSON_ParseWithLength(p_Data, length); + if (JSON == NULL) { + ESP_LOGW(TAG, "Invalid JSON from fd=%d", p_Client->fd); + + return; + } + + Cmd = cJSON_GetObjectItem(JSON, "cmd"); + Data = cJSON_GetObjectItem(JSON, "data"); + + if (cJSON_IsString(Cmd) == false) { + ESP_LOGW(TAG, "Invalid message format from fd=%d", p_Client->fd); + + cJSON_Delete(JSON); + + return; + } + + /* Handle commands */ + std::string Command(Cmd->valuestring); + if (Command == "start") { + WebSocket_HandleStart(p_Client, Data); + } else if (Command == "stop") { + WebSocket_HandleStop(p_Client); + } else if (Command == "subscribe") { + WebSocket_HandleTelemetrySubscribe(p_Client, Data); + } else if (Command == "unsubscribe") { + WebSocket_HandleTelemetryUnsubscribe(p_Client); + } + /* Remote Control Commands */ + else if (Command == "get_temperature") { + WebSocket_Handle_GetTemperature(p_Client->fd, Data); + } else if (Command == "get_time") { + WebSocket_Handle_GetTime(p_Client->fd, Data); + } else if (Command == "set_time") { + WebSocket_Handle_SetTime(p_Client->fd, Data); + } else if (Command == "get_battery") { + WebSocket_Handle_GetBattery(p_Client->fd, Data); + } else if (Command == "get_lepton_emissivity") { + WebSocket_Handle_GetLeptonEmissivity(p_Client->fd, Data); + } else if (Command == "set_lepton_emissivity") { + WebSocket_Handle_SetLeptonEmissivity(p_Client->fd, Data); + } else if (Command == "get_lepton_stats") { + WebSocket_Handle_GetLeptonStats(p_Client->fd, Data); + } else if (Command == "get_lepton_roi") { + WebSocket_Handle_GetLeptonROI(p_Client->fd, Data); + } else if (Command == "set_lepton_roi") { + WebSocket_Handle_SetLeptonROI(p_Client->fd, Data); + } else if (Command == "get_lepton_spotmeter") { + WebSocket_Handle_GetLeptonSpotmeter(p_Client->fd, Data); + } else if (Command == "get_flash") { + WebSocket_Handle_GetFlash(p_Client->fd, Data); + } else if (Command == "set_flash") { + WebSocket_Handle_SetFlash(p_Client->fd, Data); + } else if (Command == "get_image_format") { + WebSocket_Handle_GetImageFormat(p_Client->fd, Data); + } else if (Command == "set_image_format") { + WebSocket_Handle_SetImageFormat(p_Client->fd, Data); + } else if (Command == "set_status_led") { + WebSocket_Handle_SetStatusLED(p_Client->fd, Data); + } else if (Command == "get_sd_state") { + WebSocket_Handle_GetSDState(p_Client->fd, Data); + } else if (Command == "format_memory") { + WebSocket_Handle_FormatMemory(p_Client->fd, Data); + } else if (Command == "display_message") { + WebSocket_Handle_DisplayMessage(p_Client->fd, Data); + } else if (Command == "get_lock") { + WebSocket_Handle_GetLock(p_Client->fd, Data); + } else if (Command == "set_lock") { + WebSocket_Handle_SetLock(p_Client->fd, Data); + } else { + ESP_LOGW(TAG, "Unknown command from fd=%d: %s", p_Client->fd, Command.c_str()); + } + + cJSON_Delete(JSON); +} + +/** @brief WebSocket handler callback. + * @param p_Request HTTP request pointer + * @return ESP_OK on success, error code on failure + */ +static esp_err_t WebSocket_Handler(httpd_req_t *p_Request) +{ + httpd_ws_frame_t Frame; + esp_err_t Error; + WS_Client_t *Client; + int FD; + + /* Handle new connection */ + if (p_Request->method == HTTP_GET) { + WS_Client_t *Client; + + Client = WebSocket_AddClient(httpd_req_to_sockfd(p_Request)); + if (Client == NULL) { + httpd_resp_send_err(p_Request, HTTPD_500_INTERNAL_SERVER_ERROR, "Max clients reached"); + + return ESP_FAIL; + } + + ESP_LOGI(TAG, "WebSocket handshake with fd=%d", Client->fd); + + return ESP_OK; + } + + /* Handle WebSocket frame */ + memset(&Frame, 0, sizeof(httpd_ws_frame_t)); + + /* Get frame info */ + Error = httpd_ws_recv_frame(p_Request, &Frame, 0); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to get frame info: %d!", Error); + + return Error; + } + + if (Frame.len > 0) { + uint32_t Caps; + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + #else + Caps = MALLOC_CAP_8BIT; + #endif + + Frame.payload = static_cast(heap_caps_malloc(Frame.len + 1, Caps)); + if (Frame.payload == NULL) { + ESP_LOGE(TAG, "Failed to allocate frame buffer!"); + + return ESP_ERR_NO_MEM; + } + + Error = httpd_ws_recv_frame(p_Request, &Frame, Frame.len); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to receive frame: %d!", Error); + + free(Frame.payload); + + return Error; + } + + Frame.payload[Frame.len] = '\0'; + } + + FD = httpd_req_to_sockfd(p_Request); + Client = WebSocket_FindClient(FD); + + switch (Frame.type) { + case HTTPD_WS_TYPE_TEXT: { + if (Client != NULL && Frame.payload != NULL) { + WebSocket_ProcessMessage(Client, (const char *)Frame.payload, Frame.len); + } + + break; + } + case HTTPD_WS_TYPE_CLOSE: { + ESP_LOGI(TAG, "WebSocket close from fd=%d", FD); + WebSocket_RemoveClient(FD); + + break; + } + case HTTPD_WS_TYPE_PING: { + /* Must manually send PONG when handle_ws_control_frames=true */ + ESP_LOGD(TAG, "Ping received from fd=%d, sending Pong", FD); + + httpd_ws_frame_t pong_frame = { + .final = true, + .fragmented = false, + .type = HTTPD_WS_TYPE_PONG, + .payload = Frame.payload, /* Echo back the ping payload */ + .len = Frame.len, + }; + + Error = httpd_ws_send_frame_async(WebSocket_State.ServerHandle, FD, &pong_frame); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to send Pong to fd=%d: %d, retrying...", FD, Error); + + /* Pong fails sometimes. So we simply try again */ + vTaskDelay(pdMS_TO_TICKS(10)); + Error = httpd_ws_send_frame_async(WebSocket_State.ServerHandle, FD, &pong_frame); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to send Pong to fd=%d again: %d, removing client!", FD, Error); + + WebSocket_RemoveClient(FD); + } + } + + break; + } + case HTTPD_WS_TYPE_PONG: { + ESP_LOGD(TAG, "Pong received from fd=%d", FD); + + break; + } + default: { + ESP_LOGW(TAG, "Unknown frame type %d from fd=%d", Frame.type, FD); + + break; + } + } + + if (Frame.payload != NULL) { + free(Frame.payload); + } + + return ESP_OK; +} + +static const httpd_uri_t _URI_WebSocket = { + .uri = "/ws", + .method = HTTP_GET, + .handler = WebSocket_Handler, + .user_ctx = NULL, + .is_websocket = true, + .handle_ws_control_frames = true, + .supported_subprotocol = NULL, +}; + +esp_err_t WebSocket_Init(void) +{ + if (WebSocket_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + ESP_LOGI(TAG, "Initializing WebSocket handler"); + + memset(WebSocket_State.Clients, 0, sizeof(WebSocket_State.Clients)); + WebSocket_State.ClientCount = 0; + WebSocket_State.ThermalFrame = NULL; + WebSocket_State.ServerHandle = NULL; + WebSocket_State.BroadcastTask = NULL; + WebSocket_State.FrameReadyQueue = NULL; + WebSocket_State.isRunning = false; + + WebSocket_State.ClientsMutex = xSemaphoreCreateMutex(); + if (WebSocket_State.ClientsMutex == NULL) { + ESP_LOGE(TAG, "Failed to create mutex!"); + + return ESP_ERR_NO_MEM; + } + + /* Create queue for frame ready notifications (queue size = 1, only latest matters) */ + WebSocket_State.FrameReadyQueue = xQueueCreate(1, sizeof(uint8_t)); + if (WebSocket_State.FrameReadyQueue == NULL) { + ESP_LOGE(TAG, "Failed to create frame queue!"); + + vSemaphoreDelete(WebSocket_State.ClientsMutex); + + return ESP_ERR_NO_MEM; + } + + WebSocket_State.isInitialized = true; + + return ESP_OK; +} + +void WebSocket_Deinit(void) +{ + if (WebSocket_State.isInitialized == false) { + return; + } + + WebSocket_StopTask(); + + if (WebSocket_State.FrameReadyQueue != NULL) { + vQueueDelete(WebSocket_State.FrameReadyQueue); + WebSocket_State.FrameReadyQueue = NULL; + } + + if (WebSocket_State.ClientsMutex != NULL) { + vSemaphoreDelete(WebSocket_State.ClientsMutex); + WebSocket_State.ClientsMutex = NULL; + } + + WebSocket_State.isInitialized = false; + + ESP_LOGI(TAG, "WebSocket handler deinitialized"); +} + +esp_err_t WebSocket_Register(httpd_handle_t p_ServerHandle) +{ + esp_err_t Error; + + if (WebSocket_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (p_ServerHandle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + WebSocket_State.ServerHandle = p_ServerHandle; + + Error = httpd_register_uri_handler(p_ServerHandle, &_URI_WebSocket); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to register WebSocket URI: %d!", Error); + + return Error; + } + + ESP_LOGI(TAG, "WebSocket handler registered at /ws"); + + return ESP_OK; +} + +uint8_t WebSocket_GetClientCount(void) +{ + return WebSocket_State.ClientCount; +} + +bool WebSocket_HasClients(void) +{ + return WebSocket_State.ClientCount > 0; +} + +void WebSocket_SetThermalFrame(Network_Thermal_Frame_t *p_Frame) +{ + xSemaphoreTake(WebSocket_State.ClientsMutex, portMAX_DELAY); + WebSocket_State.ThermalFrame = p_Frame; + xSemaphoreGive(WebSocket_State.ClientsMutex); +} + +/** @brief Broadcast task function. Runs in separate task to avoid blocking GUI. + * @param p_Param Task parameter + */ +static void WebSocket_BroadcastTask(void *p_Param) +{ + uint8_t Signal; + esp_err_t Error; + Network_Encoded_Image_t Encoded; + + ESP_LOGI(TAG, "WebSocket broadcast task started"); + + while (WebSocket_State.isRunning) { + /* Wait for frame ready notification (blocking, 100ms timeout) */ + if (xQueueReceive(WebSocket_State.FrameReadyQueue, &Signal, pdMS_TO_TICKS(100)) == pdTRUE) { + uint32_t Now; + + Now = esp_timer_get_time() / 1000; + if ((WebSocket_State.isInitialized == false) || (WebSocket_State.ThermalFrame == NULL) || + (WebSocket_State.ClientCount == 0)) { + continue; + } + + /* Encode frame ONCE for all clients (assume JPEG format for simplicity) */ + if (xSemaphoreTake(WebSocket_State.ThermalFrame->Mutex, pdMS_TO_TICKS(50)) == pdTRUE) { + Error = ImageEncoder_Encode(WebSocket_State.ThermalFrame, + IMAGE_FORMAT_JPEG, PALETTE_IRON, &Encoded); + xSemaphoreGive(WebSocket_State.ThermalFrame->Mutex); + + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to encode frame: %d!", Error); + + continue; + } + } else { + /* Frame mutex busy, skip this frame */ + continue; + } + + /* Send to all active streaming clients */ + xSemaphoreTake(WebSocket_State.ClientsMutex, portMAX_DELAY); + for (uint8_t i = 0; i < CONFIG_NETWORK_WEBSOCKET_CLIENTS; i++) { + WS_Client_t *Client; + + Client = &WebSocket_State.Clients[i]; + if ((Client->active == false) || (Client->stream_enabled == false)) { + continue; + } + + /* Check frame rate limit */ + if ((Now - Client->last_frame_time) < (1000 / Client->stream_fps)) { + continue; + } + + /* Copy FD before releasing mutex */ + int client_fd = Client->fd; + uint8_t client_idx = i; + + xSemaphoreGive(WebSocket_State.ClientsMutex); + Error = WebSocket_SendBinary(client_fd, Encoded.Data, Encoded.Size); + xSemaphoreTake(WebSocket_State.ClientsMutex, portMAX_DELAY); + + /* Re-validate client is still active and same FD */ + if (WebSocket_State.Clients[client_idx].active && + WebSocket_State.Clients[client_idx].fd == client_fd) { + if (Error == ESP_OK) { + WebSocket_State.Clients[client_idx].last_frame_time = Now; + } else { + ESP_LOGW(TAG, "Removing client fd=%d due to send failure", client_fd); + WebSocket_State.Clients[client_idx].active = false; + WebSocket_State.ClientCount--; + } + } + } + + xSemaphoreGive(WebSocket_State.ClientsMutex); + + /* Free encoded frame after sending to all clients */ + ImageEncoder_Free(&Encoded); + } + + vTaskDelay(pdMS_TO_TICKS(10)); + } + + WebSocket_State.BroadcastTask = NULL; + vTaskDelete(NULL); +} + +esp_err_t WebSocket_NotifyFrameReady(void) +{ + uint8_t Signal; + + if ((WebSocket_State.isInitialized == false) || (WebSocket_State.FrameReadyQueue == NULL)) { + return ESP_ERR_INVALID_STATE; + } + + if (WebSocket_State.ClientCount == 0) { + return ESP_OK; + } + + /* Signal frame ready (overwrite if queue full - only latest frame matters) */ + Signal = 1; + xQueueOverwrite(WebSocket_State.FrameReadyQueue, &Signal); + + return ESP_OK; +} + +esp_err_t WebSocket_BroadcastTelemetry(void) +{ + uint32_t Now; + cJSON *JSON; + + if (WebSocket_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } + + if (WebSocket_State.ClientCount == 0) { + return ESP_OK; + } + + Now = esp_timer_get_time() / 1000; + + /* Build telemetry data */ + JSON = cJSON_CreateObject(); + + if (WebSocket_State.ThermalFrame != NULL) { + // TODO + //cJSON_AddNumberToObject(JSON, "temp", WebSocket_State.ThermalFrame->temp_avg); + } + + /* Non-blocking: skip telemetry if mutex is held by another task (called from LVGL timer context) */ + if (xSemaphoreTake(WebSocket_State.ClientsMutex, 0) == pdFALSE) { + cJSON_Delete(JSON); + return ESP_ERR_TIMEOUT; + } + + for (uint8_t i = 0; i < CONFIG_NETWORK_WEBSOCKET_CLIENTS; i++) { + WS_Client_t *Client; + + Client = &WebSocket_State.Clients[i]; + if ((Client->active == false) || (Client->telemetry_enabled == false)) { + continue; + } + + /* Check interval */ + if ((Now - Client->last_telemetry_time) < Client->telemetry_interval_ms) { + continue; + } + + WebSocket_SendJSON(Client->fd, "telemetry", JSON); + Client->last_telemetry_time = Now; + } + + xSemaphoreGive(WebSocket_State.ClientsMutex); + + cJSON_Delete(JSON); + + return ESP_OK; +} + +esp_err_t WebSocket_PingAll(void) +{ + httpd_ws_frame_t Frame = { + .final = true, + .fragmented = false, + .type = HTTPD_WS_TYPE_PING, + .payload = NULL, + .len = 0, + }; + + if ((WebSocket_State.isInitialized == false) || (WebSocket_State.ServerHandle == NULL)) { + return ESP_ERR_INVALID_STATE; + } + + xSemaphoreTake(WebSocket_State.ClientsMutex, portMAX_DELAY); + + for (uint8_t i = 0; i < CONFIG_NETWORK_WEBSOCKET_CLIENTS; i++) { + if (WebSocket_State.Clients[i].active) { + httpd_ws_send_frame_async(WebSocket_State.ServerHandle, WebSocket_State.Clients[i].fd, &Frame); + } + } + + xSemaphoreGive(WebSocket_State.ClientsMutex); + + return ESP_OK; +} + +esp_err_t WebSocket_StartTask(void) +{ + BaseType_t Error; + + if (WebSocket_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (WebSocket_State.BroadcastTask != NULL) { + ESP_LOGW(TAG, "Broadcast task already running"); + + return ESP_OK; + } + + WebSocket_State.isRunning = true; + + Error = xTaskCreatePinnedToCore(WebSocket_BroadcastTask, "WS_Broadcast", 4096, NULL, 5, &WebSocket_State.BroadcastTask, 1); + if (Error != pdPASS) { + ESP_LOGE(TAG, "Failed to create broadcast task!"); + WebSocket_State.isRunning = false; + + return ESP_ERR_NO_MEM; + } + + return ESP_OK; +} + +void WebSocket_StopTask(uint32_t Timeout_ms) +{ + if (WebSocket_State.BroadcastTask == NULL) { + return; + } + + ESP_LOGI(TAG, "Stopping WebSocket broadcast task..."); + + WebSocket_State.isRunning = false; +} diff --git a/main/Application/Manager/Network/Server/WebSocket/websocket.h b/main/Application/Manager/Network/Server/WebSocket/websocket.h new file mode 100644 index 0000000..d1c975a --- /dev/null +++ b/main/Application/Manager/Network/Server/WebSocket/websocket.h @@ -0,0 +1,88 @@ +/* + * websocket.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: WebSocket implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef WEBSOCKET_H_ +#define WEBSOCKET_H_ + +#include +#include +#include + +#include "../../networkTypes.h" + +/** @brief Initialize the WebSocket. + * @return ESP_OK on success + */ +esp_err_t WebSocket_Init(void); + +/** @brief Deinitialize the WebSocket. + */ +void WebSocket_Deinit(void); + +/** @brief Register WebSocket with HTTP server. + * @param p_ServerHandle HTTP server handle + * @return ESP_OK on success + */ +esp_err_t WebSocket_Register(httpd_handle_t p_ServerHandle); + +/** @brief Get the number of connected WebSocket clients. + * @return Number of connected clients + */ +uint8_t WebSocket_GetClientCount(void); + +/** @brief Check if there are any connected clients. + * @return true if at least one client is connected + */ +bool WebSocket_HasClients(void); + +/** @brief Set thermal frame data for streaming. + * @param p_Frame Pointer to thermal frame data + */ +void WebSocket_SetThermalFrame(Network_Thermal_Frame_t *p_Frame); + +/** @brief Signal that a new frame is ready for broadcasting (non-blocking). + * @return ESP_OK on success + */ +esp_err_t WebSocket_NotifyFrameReady(void); + +/** @brief Broadcast telemetry data to all subscribed clients. + * @return ESP_OK on success + */ +esp_err_t WebSocket_BroadcastTelemetry(void); + +/** @brief Start the WebSocket broadcast task. + * @return ESP_OK on success + */ +esp_err_t WebSocket_StartTask(void); + +/** @brief Stop the WebSocket broadcast task. + * @param Timeout_ms Timeout in milliseconds to wait for task to stop (default: 2000ms) + */ +void WebSocket_StopTask(uint32_t Timeout_ms = 2000); + +/** @brief Send ping to all connected clients. + * @return ESP_OK on success. + */ +esp_err_t WebSocket_PingAll(void); + +#endif /* WEBSOCKET_H_ */ diff --git a/main/Application/Manager/Network/Server/server.h b/main/Application/Manager/Network/Server/server.h new file mode 100644 index 0000000..5b1b75e --- /dev/null +++ b/main/Application/Manager/Network/Server/server.h @@ -0,0 +1,162 @@ +/* + * server.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Server module header. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef SERVER_H_ +#define SERVER_H_ + +#include "HTTP/http_server.h" +#include "WebSocket/websocket.h" +#include "VISA/visaServer.h" +#include "ImageEncoder/imageEncoder.h" +#include "Settings/settingsManager.h" + +/** @brief Initialize the complete server (HTTP + WebSocket + Image Encoder + VISA). + * @return ESP_OK on success + */ +static inline esp_err_t Server_Init(void) +{ + esp_err_t Error; + Settings_HTTP_Server_t Config; + Settings_System_t SystemConfig; + Network_HTTP_Server_Config_t ServerConfig; + + SettingsManager_GetSystem(&SystemConfig); + Error = ImageEncoder_Init(SystemConfig.JpegQuality); + if (Error != ESP_OK) { + return Error; + } + + SettingsManager_GetHTTPServer(&Config); + memcpy(ServerConfig.API_Key, Config.APIKey, sizeof(ServerConfig.API_Key)); + ServerConfig.EnableCORS = Config.useCORS; + ServerConfig.MaxClients = Config.MaxClients; + ServerConfig.Port = Config.Port; + ServerConfig.WSPingIntervalSec = Config.WSPingIntervalSec; + + Error = HTTP_Server_Init(&ServerConfig); + if (Error != ESP_OK) { + ImageEncoder_Deinit(); + return Error; + } + + Error = WebSocket_Init(); + if (Error != ESP_OK) { + HTTP_Server_Deinit(); + ImageEncoder_Deinit(); + return Error; + } + + Error = VISAServer_Init(); + if (Error != ESP_OK) { + WebSocket_Deinit(); + HTTP_Server_Deinit(); + ImageEncoder_Deinit(); + + return Error; + } + + return ESP_OK; +} + +/** @brief Deinitialize the complete server. + */ +static inline void Server_Deinit(void) +{ + VISAServer_Deinit(); + WebSocket_Deinit(); + HTTP_Server_Deinit(); + ImageEncoder_Deinit(); +} + +/** @brief Start the server (HTTP server and register WebSocket handler). + * @return ESP_OK on success + */ +static inline esp_err_t Server_Start(void) +{ + esp_err_t Error; + + Error = HTTP_Server_Start(); + if (Error != ESP_OK) { + return Error; + } + + Error = WebSocket_Register(HTTP_Server_GetHandle()); + if (Error != ESP_OK) { + HTTP_Server_Stop(); + return Error; + } + + Error = WebSocket_StartTask(); + if (Error != ESP_OK) { + HTTP_Server_Stop(); + return Error; + } + + Error = VISAServer_Start(); + if (Error != ESP_OK) { + WebSocket_StopTask(); + HTTP_Server_Stop(); + return Error; + } + + return ESP_OK; +} + +/** @brief Stop the server. + * @return ESP_OK on success + */ +static inline esp_err_t Server_Stop(void) +{ + VISAServer_Stop(); + WebSocket_StopTask(); + + return HTTP_Server_Stop(); +} + +/** @brief Check if the server is running. + * @return true if running + */ +static inline bool Server_IsRunning(void) +{ + return HTTP_Server_IsRunning() && VISAServer_IsRunning(); +} + +/** @brief Set the thermal frame data for both HTTP and WebSocket endpoints. + * @param p_Frame Pointer to thermal frame data + */ +static inline void Server_SetThermalFrame(Network_Thermal_Frame_t *p_Frame) +{ + HTTP_Server_SetThermalFrame(p_Frame); + WebSocket_SetThermalFrame(p_Frame); +} + +/** @brief Notify all clients that a new frame is ready (non-blocking). + */ +static inline void Server_NotifyClients(void) +{ + if (WebSocket_HasClients()) { + WebSocket_NotifyFrameReady(); + } +} + +#endif /* SERVER_H_ */ diff --git a/main/Application/Manager/Network/networkManager.cpp b/main/Application/Manager/Network/networkManager.cpp new file mode 100644 index 0000000..427f026 --- /dev/null +++ b/main/Application/Manager/Network/networkManager.cpp @@ -0,0 +1,571 @@ +/* + * networkManager.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Network Manager implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "networkTypes.h" +#include "Server/server.h" +#include "Provisioning/provisioning.h" + +#include "networkManager.h" + +ESP_EVENT_DEFINE_BASE(NETWORK_EVENTS); + +#define NVS_NAMESPACE "wifi_creds" +#define NVS_KEY_SSID "ssid" +#define NVS_KEY_PASSWORD "password" + +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_FAIL_BIT BIT1 +#define WIFI_STARTED_BIT BIT2 + +typedef struct { + bool isInitialized; + Network_State_t State; + esp_netif_t *STA_NetIF; + esp_netif_t *AP_NetIF; + EventGroupHandle_t EventGroup; + uint8_t RetryCount; + esp_netif_ip_info_t IP_Info; +} Network_Manager_State_t; + +static Network_Manager_State_t _Network_Manager_State; + +static const char *TAG = "Network-Manager"; + +/** @brief WiFi event handler. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_WiFi_Event(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + switch (ID) { + case WIFI_EVENT_STA_START: { + ESP_LOGD(TAG, "WiFi STA started"); + + xEventGroupSetBits(_Network_Manager_State.EventGroup, WIFI_STARTED_BIT); + esp_wifi_connect(); + + break; + } + case WIFI_EVENT_STA_CONNECTED: { + ESP_LOGD(TAG, "Connected to AP"); + + _Network_Manager_State.RetryCount = 0; + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_WIFI_CONNECTED, NULL, 0, portMAX_DELAY); + + break; + } + case WIFI_EVENT_STA_DISCONNECTED: { + wifi_event_sta_disconnected_t *Event = static_cast(p_Data); + Settings_WiFi_t WiFiSettings; + + SettingsManager_GetWiFi(&WiFiSettings); + + /* Decode disconnect reason for better debugging */ + const char *ReasonStr = "Unknown"; + switch (Event->reason) { + case WIFI_REASON_UNSPECIFIED: + ReasonStr = "Unspecified"; + break; + case WIFI_REASON_AUTH_EXPIRE: + ReasonStr = "Auth expired"; + break; + case WIFI_REASON_AUTH_LEAVE: + ReasonStr = "Auth leave"; + break; + case WIFI_REASON_ASSOC_EXPIRE: + ReasonStr = "Assoc expired"; + break; + case WIFI_REASON_ASSOC_TOOMANY: + ReasonStr = "Too many assocs"; + break; + case WIFI_REASON_NOT_AUTHED: + ReasonStr = "Not authenticated"; + break; + case WIFI_REASON_NOT_ASSOCED: + ReasonStr = "Not associated"; + break; + case WIFI_REASON_ASSOC_LEAVE: + ReasonStr = "Assoc leave"; + break; + case WIFI_REASON_ASSOC_NOT_AUTHED: + ReasonStr = "Assoc not authed"; + break; + case WIFI_REASON_DISASSOC_PWRCAP_BAD: + ReasonStr = "Bad power capability"; + break; + case WIFI_REASON_DISASSOC_SUPCHAN_BAD: + ReasonStr = "Bad supported channels"; + break; + case WIFI_REASON_IE_INVALID: + ReasonStr = "Invalid IE"; + break; + case WIFI_REASON_MIC_FAILURE: + ReasonStr = "MIC failure"; + break; + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + ReasonStr = "4-way handshake timeout"; + break; + case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: + ReasonStr = "Group key update timeout"; + break; + case WIFI_REASON_IE_IN_4WAY_DIFFERS: + ReasonStr = "IE in 4-way differs"; + break; + case WIFI_REASON_GROUP_CIPHER_INVALID: + ReasonStr = "Invalid group cipher"; + break; + case WIFI_REASON_PAIRWISE_CIPHER_INVALID: + ReasonStr = "Invalid pairwise cipher"; + break; + case WIFI_REASON_AKMP_INVALID: + ReasonStr = "Invalid AKMP"; + break; + case WIFI_REASON_UNSUPP_RSN_IE_VERSION: + ReasonStr = "Unsupported RSN IE version"; + break; + case WIFI_REASON_INVALID_RSN_IE_CAP: + ReasonStr = "Invalid RSN IE cap"; + break; + case WIFI_REASON_802_1X_AUTH_FAILED: + ReasonStr = "802.1X auth failed"; + break; + case WIFI_REASON_CIPHER_SUITE_REJECTED: + ReasonStr = "Cipher suite rejected"; + break; + case WIFI_REASON_BEACON_TIMEOUT: + ReasonStr = "Beacon timeout"; + break; + case WIFI_REASON_NO_AP_FOUND: + ReasonStr = "No AP found"; + break; + case WIFI_REASON_AUTH_FAIL: + ReasonStr = "Auth failed"; + break; + case WIFI_REASON_ASSOC_FAIL: + ReasonStr = "Assoc failed"; + break; + case WIFI_REASON_HANDSHAKE_TIMEOUT: + ReasonStr = "Handshake timeout"; + break; + case WIFI_REASON_CONNECTION_FAIL: + ReasonStr = "Connection failed"; + break; + default: + break; + } + + ESP_LOGW(TAG, "Disconnected from AP, reason: %d (%s)", Event->reason, ReasonStr); + + _Network_Manager_State.State = NETWORK_STATE_DISCONNECTED; + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_WIFI_DISCONNECTED, p_Data, sizeof(wifi_event_sta_disconnected_t), + portMAX_DELAY); + + if (_Network_Manager_State.RetryCount < WiFiSettings.MaxRetries) { + ESP_LOGD(TAG, "Retry %d/%d", _Network_Manager_State.RetryCount++, WiFiSettings.MaxRetries); + + vTaskDelay(pdMS_TO_TICKS(WiFiSettings.RetryInterval)); + esp_wifi_connect(); + _Network_Manager_State.State = NETWORK_STATE_CONNECTING; + } else { + ESP_LOGE(TAG, "Max retries reached!"); + + xEventGroupSetBits(_Network_Manager_State.EventGroup, WIFI_FAIL_BIT); + _Network_Manager_State.State = NETWORK_STATE_ERROR; + } + + break; + } + case WIFI_EVENT_AP_START: { + ESP_LOGD(TAG, "WiFi AP started"); + + _Network_Manager_State.State = NETWORK_STATE_AP_STARTED; + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_AP_STARTED, NULL, 0, portMAX_DELAY); + + break; + } + case WIFI_EVENT_AP_STOP: { + ESP_LOGD(TAG, "WiFi AP stopped"); + + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_AP_STOPPED, NULL, 0, portMAX_DELAY); + + break; + } + case WIFI_EVENT_AP_STACONNECTED: { + wifi_event_ap_staconnected_t *Event = static_cast(p_Data); + Network_Event_STA_Info_t StaInfo; + + ESP_LOGD(TAG, "Station " MACSTR " joined, AID=%d", MAC2STR(Event->mac), Event->aid); + memcpy(StaInfo.MAC, Event->mac, 6); + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_AP_STA_CONNECTED, &StaInfo, sizeof(StaInfo), portMAX_DELAY); + + break; + } + case WIFI_EVENT_AP_STADISCONNECTED: { + wifi_event_ap_stadisconnected_t *Event = static_cast(p_Data); + Network_Event_STA_Info_t StaInfo; + + ESP_LOGD(TAG, "Station " MACSTR " left, AID=%d", MAC2STR(Event->mac), Event->aid); + memcpy(StaInfo.MAC, Event->mac, 6); + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_AP_STA_DISCONNECTED, &StaInfo, sizeof(StaInfo), portMAX_DELAY); + + break; + } + default: { + break; + } + } +} + +/** @brief IP event handler. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_IP_Event(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + switch (ID) { + case IP_EVENT_STA_GOT_IP: { + ip_event_got_ip_t *Event = static_cast(p_Data); + Network_IP_Info_t IP_Data; + + ESP_LOGD(TAG, "Got IP: " IPSTR, IP2STR(&Event->ip_info.ip)); + + memcpy(&_Network_Manager_State.IP_Info, &Event->ip_info, sizeof(esp_netif_ip_info_t)); + _Network_Manager_State.State = NETWORK_STATE_CONNECTED; + + IP_Data.IP = Event->ip_info.ip.addr; + IP_Data.Netmask = Event->ip_info.netmask.addr; + IP_Data.Gateway = Event->ip_info.gw.addr; + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_WIFI_GOT_IP, &IP_Data, sizeof(IP_Data), portMAX_DELAY); + + xEventGroupSetBits(_Network_Manager_State.EventGroup, WIFI_CONNECTED_BIT); + + break; + } + case IP_EVENT_STA_LOST_IP: { + ESP_LOGW(TAG, "Lost IP address"); + + memset(&_Network_Manager_State.IP_Info, 0, sizeof(esp_netif_ip_info_t)); + + break; + } + default: { + break; + } + } +} + +esp_err_t NetworkManager_Init(void) +{ + esp_err_t Error; + wifi_init_config_t WifiInitConfig = WIFI_INIT_CONFIG_DEFAULT(); + + if (_Network_Manager_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + return ESP_OK; + } + + ESP_LOGD(TAG, "Initializing WiFi Manager"); + + memset(&_Network_Manager_State, 0, sizeof(Network_Manager_State_t)); + + ESP_ERROR_CHECK(esp_netif_init()); + + _Network_Manager_State.EventGroup = xEventGroupCreate(); + if (_Network_Manager_State.EventGroup == NULL) { + ESP_LOGE(TAG, "Failed to create event group!"); + + return ESP_ERR_NO_MEM; + } + + _Network_Manager_State.STA_NetIF = esp_netif_create_default_wifi_sta(); + if (_Network_Manager_State.STA_NetIF == NULL) { + ESP_LOGE(TAG, "Failed to create STA netif!"); + + vEventGroupDelete(_Network_Manager_State.EventGroup); + _Network_Manager_State.EventGroup = NULL; + + return ESP_FAIL; + } + + _Network_Manager_State.AP_NetIF = esp_netif_create_default_wifi_ap(); + if (_Network_Manager_State.AP_NetIF == NULL) { + ESP_LOGE(TAG, "Failed to create AP netif!"); + + esp_netif_destroy(_Network_Manager_State.STA_NetIF); + _Network_Manager_State.STA_NetIF = NULL; + vEventGroupDelete(_Network_Manager_State.EventGroup); + _Network_Manager_State.EventGroup = NULL; + + return ESP_FAIL; + } + + Error = esp_wifi_init(&WifiInitConfig); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to init WiFi: %d!", Error); + + esp_netif_destroy(_Network_Manager_State.AP_NetIF); + esp_netif_destroy(_Network_Manager_State.STA_NetIF); + vEventGroupDelete(_Network_Manager_State.EventGroup); + + _Network_Manager_State.AP_NetIF = NULL; + _Network_Manager_State.STA_NetIF = NULL; + _Network_Manager_State.EventGroup = NULL; + + return Error; + } + + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, + ESP_EVENT_ANY_ID, + &on_WiFi_Event, + NULL, + NULL)); + + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, + ESP_EVENT_ANY_ID, + &on_IP_Event, + NULL, + NULL)); + + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + _Network_Manager_State.isInitialized = true; + _Network_Manager_State.State = NETWORK_STATE_IDLE; + + ESP_LOGD(TAG, "Network Manager initialized"); + + return ESP_OK; +} + +void NetworkManager_Deinit(void) +{ + if (_Network_Manager_State.isInitialized == false) { + return; + } + + ESP_LOGD(TAG, "Deinitializing Network Manager"); + + NetworkManager_Stop(); + esp_wifi_deinit(); + + if (_Network_Manager_State.STA_NetIF) { + esp_netif_destroy(_Network_Manager_State.STA_NetIF); + _Network_Manager_State.STA_NetIF = NULL; + } + + if (_Network_Manager_State.AP_NetIF) { + esp_netif_destroy(_Network_Manager_State.AP_NetIF); + _Network_Manager_State.AP_NetIF = NULL; + } + + if (_Network_Manager_State.EventGroup) { + vEventGroupDelete(_Network_Manager_State.EventGroup); + _Network_Manager_State.EventGroup = NULL; + } + + _Network_Manager_State.isInitialized = false; + _Network_Manager_State.State = NETWORK_STATE_IDLE; +} + +esp_err_t NetworkManager_StartSTA(void) +{ + esp_err_t Error; + wifi_config_t WifiConfig; + Settings_WiFi_t WiFiSettings; + + if (_Network_Manager_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } + + _Network_Manager_State.RetryCount = 0; + + xEventGroupClearBits(_Network_Manager_State.EventGroup, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT); + + SettingsManager_GetWiFi(&WiFiSettings); + + ESP_LOGI(TAG, "Starting WiFi in STA mode"); + ESP_LOGI(TAG, "Connecting to SSID: %s", WiFiSettings.SSID); + + memset(&WifiConfig, 0, sizeof(wifi_config_t)); + WifiConfig.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; + WifiConfig.sta.pmf_cfg.capable = true; + WifiConfig.sta.pmf_cfg.required = false; + strncpy(reinterpret_cast(WifiConfig.sta.ssid), WiFiSettings.SSID, + sizeof(WifiConfig.sta.ssid) - 1); + strncpy(reinterpret_cast(WifiConfig.sta.password), WiFiSettings.Password, + sizeof(WifiConfig.sta.password) - 1); + + /* Set STA mode - WiFi might still be in APSTA mode from provisioning */ + Error = esp_wifi_set_mode(WIFI_MODE_STA); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi mode: %d!", Error); + + return Error; + } + + Error = esp_wifi_set_config(WIFI_IF_STA, &WifiConfig); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi config: %d!", Error); + + return Error; + } + + /* Start WiFi if not already running */ + Error = esp_wifi_start(); + if ((Error != ESP_OK) && (Error != ESP_ERR_WIFI_STATE)) { + ESP_LOGE(TAG, "Failed to start WiFi: %d!", Error); + + return Error; + } + + ESP_LOGI(TAG, "WiFi started, initiating connection..."); + + /* Explicitly connect - STA_START event may not fire if WiFi was already running */ + Error = esp_wifi_connect(); + if ((Error != ESP_OK) && (Error != ESP_ERR_WIFI_CONN)) { + ESP_LOGW(TAG, "esp_wifi_connect returned: %d!", Error); + } + + _Network_Manager_State.State = NETWORK_STATE_CONNECTING; + + ESP_LOGI(TAG, "WiFi connection initiated"); + + return ESP_OK; +} + +esp_err_t NetworkManager_StartServer(void) +{ + esp_err_t Error; + + Error = Server_Init(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize server: %d!", Error); + + return Error; + } + + Error = Server_Start(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to start server: %d!", Error); + + return Error; + } + + return ESP_OK; +} + +esp_err_t NetworkManager_Stop(void) +{ + if (_Network_Manager_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } + + SNTP_Deinit(); + + esp_wifi_stop(); + + _Network_Manager_State.State = NETWORK_STATE_IDLE; + + return ESP_OK; +} + +esp_err_t NetworkManager_DisconnectWiFi(void) +{ + if (_Network_Manager_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGD(TAG, "Disconnecting from WiFi"); + + return esp_wifi_disconnect(); +} + +bool NetworkManager_isConnected(void) +{ + return _Network_Manager_State.State == NETWORK_STATE_CONNECTED; +} + +Network_State_t NetworkManager_GetState(void) +{ + return _Network_Manager_State.State; +} + +esp_err_t NetworkManager_GetIP(esp_netif_ip_info_t *p_IP) +{ + if (p_IP == NULL) { + return ESP_ERR_INVALID_ARG; + } + + memcpy(p_IP, &_Network_Manager_State.IP_Info, sizeof(esp_netif_ip_info_t)); + + return ESP_OK; +} + +int8_t NetworkManager_GetRSSI(void) +{ + wifi_ap_record_t ApInfo; + + if (NetworkManager_isConnected() == false) { + return 0; + } + + if (esp_wifi_sta_get_ap_info(&ApInfo) == ESP_OK) { + return ApInfo.rssi; + } + + return 0; +} + +esp_err_t NetworkManager_GetMAC(uint8_t *p_MAC) +{ + if (p_MAC == NULL) { + return ESP_ERR_INVALID_ARG; + } + + return esp_wifi_get_mac(WIFI_IF_STA, p_MAC); +} + +uint8_t NetworkManager_GetConnectedStations(void) +{ + wifi_sta_list_t StaList; + + if (esp_wifi_ap_get_sta_list(&StaList) == ESP_OK) { + return StaList.num; + } + + return 0; +} diff --git a/main/Application/Manager/Network/networkManager.h b/main/Application/Manager/Network/networkManager.h new file mode 100644 index 0000000..3220b64 --- /dev/null +++ b/main/Application/Manager/Network/networkManager.h @@ -0,0 +1,161 @@ +/* + * networkManager.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Network Manager definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef NETWORKMANAGER_H_ +#define NETWORKMANAGER_H_ + +#include +#include +#include + +#include "networkTypes.h" +#include "SNTP/sntp.h" +#include "Server/server.h" +#include "Provisioning/provisioning.h" + +/** @brief Initialize the Network Manager. + * Initializes WiFi subsystem, creates network interfaces (STA and AP), + * and sets up event handlers. Must be called before any network operations. + * @note This function must be called after NVS and event loop init. + * WiFi is not started automatically - call NetworkManager_StartSTA(). + * @warning Not thread-safe during initialization. + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Config is NULL + * ESP_ERR_NO_MEM if memory allocation fails + * ESP_FAIL if WiFi initialization fails + */ +esp_err_t NetworkManager_Init(void); + +/** @brief Deinitialize the Network Manager. + * Stops WiFi, disconnects all connections, stops servers, and cleans up + * network interfaces. All network handles become invalid. + * @note Automatically disconnects from WiFi if connected. + * Stops HTTP server, WebSocket, and VISA server if running. + * @warning Cannot be undone without calling NetworkManager_Init() again. + */ +void NetworkManager_Deinit(void); + +/** @brief Start WiFi in station mode. + * Enables WiFi in station (client) mode and attempts to connect to + * the configured access point. + * @note Connection happens asynchronously - check NetworkManager_isConnected(). + * Posts NETWORK_EVENT_WIFI_CONNECTING event. + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not initialized + * ESP_FAIL if WiFi start fails + */ +esp_err_t NetworkManager_StartSTA(void); + +/** @brief Stop Network Manager. + * Stops WiFi radio and disconnects from network. Does not deinitialize + * the manager - use NetworkManager_StartSTA() to restart. + * @note Servers are stopped but not destroyed. + * Posts NETWORK_EVENT_WIFI_DISCONNECTED event. + * @return ESP_OK on success + * ESP_FAIL if stop operation fails + */ +esp_err_t NetworkManager_Stop(void); + +/** @brief Disconnect from WiFi. + * Disconnects from current WiFi access point. Keeps WiFi radio active. + * @note Posts NETWORK_EVENT_WIFI_DISCONNECTED event. + * To stop WiFi completely, use NetworkManager_Stop(). + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not connected + * ESP_FAIL if disconnect operation fails + */ +esp_err_t NetworkManager_DisconnectWiFi(void); + +/** @brief Check if WiFi is connected. + * Returns the current WiFi connection status. Returns true only when + * fully connected with valid IP address. + * @note This is thread-safe and can be called from any task. + * @return true if connected to WiFi with IP address + * false if disconnected, connecting, or no IP + */ +bool NetworkManager_isConnected(void); + +/** @brief Get current WiFi state. + * Returns detailed connection state including disconnected, connecting, + * connected, provisioning, etc. + * @note Thread-safe. + * Use NetworkManager_isConnected() for simple connected check. + * @return Current network state (see Network_State_t) + */ +Network_State_t NetworkManager_GetState(void); + +/** @brief Get IP information. + * Retrieves current IP address, netmask, and gateway for the station + * interface. + * @note Only valid when WiFi is connected. + * @param p_IP Pointer to esp_netif_ip_info_t structure to fill + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_IP is NULL + * ESP_ERR_INVALID_STATE if not connected + */ +esp_err_t NetworkManager_GetIP(esp_netif_ip_info_t *p_IP); + +/** @brief Get WiFi signal strength (RSSI). + * Returns the Received Signal Strength Indicator in dBm. + * Typical values: -30 dBm (excellent) to -90 dBm (poor). + * @note Only valid when connected to WiFi. + * Value updates periodically while connected. + * @return RSSI in dBm (negative value) + * 0 if not connected or error + */ +int8_t NetworkManager_GetRSSI(void); + +/** @brief Get MAC address of WiFi station interface. + * Retrieves the 6-byte hardware MAC address. + * @note MAC address is factory-programmed in ESP32 eFuse. + * @param p_MAC Buffer to store MAC address (must be at least 6 bytes) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_MAC is NULL + * ESP_FAIL if MAC read fails + */ +esp_err_t NetworkManager_GetMAC(uint8_t *p_MAC); + +/** @brief Get number of connected stations (AP mode). + * Returns the number of client devices currently connected to the + * soft AP (Access Point) interface. + * @note Only relevant when operating in AP or AP+STA mode. + * Returns 0 in pure STA mode. + * @return Number of connected stations (0-4 typical limit) + */ +uint8_t NetworkManager_GetConnectedStations(void); + +/** @brief Start the network server (HTTP + WebSocket + VISA). + * Starts HTTP server with WebSocket support and VISA/SCPI server. + * Servers listen on configured ports and handle client connections. + * @note HTTP server default port: 80, VISA default: 5025. + * WebSocket integrated into HTTP server. + * Can be called before or after WiFi connection. + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Config is NULL + * ESP_ERR_INVALID_STATE if network not initialized + * ESP_ERR_NO_MEM if memory allocation fails + * ESP_FAIL if server start fails + */ +esp_err_t NetworkManager_StartServer(void); + +#endif /* NETWORKMANAGER_H_ */ diff --git a/main/Application/Manager/Network/networkTypes.h b/main/Application/Manager/Network/networkTypes.h new file mode 100644 index 0000000..a1e2b44 --- /dev/null +++ b/main/Application/Manager/Network/networkTypes.h @@ -0,0 +1,192 @@ +/* + * networkTypes.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Common type definitions for the Network Manager component. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef NETWORK_TYPES_H_ +#define NETWORK_TYPES_H_ + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "Settings/settingsTypes.h" + +/** @brief Network Manager events base. + */ +ESP_EVENT_DECLARE_BASE(NETWORK_EVENTS); + +/** @brief Network connection state. + */ +typedef enum { + NETWORK_STATE_IDLE = 0, + NETWORK_STATE_CONNECTING, + NETWORK_STATE_CONNECTED, + NETWORK_STATE_DISCONNECTED, + NETWORK_STATE_PROVISIONING, + NETWORK_STATE_AP_STARTED, + NETWORK_STATE_ERROR, +} Network_State_t; + +/** @brief Provisioning method. + */ +typedef enum { + NETWORK_PROV_NONE = 0, + NETWORK_PROV_BLE, + NETWORK_PROV_SOFTAP, + NETWORK_PROV_BOTH, +} Network_ProvMethod_t; + +/** @brief Network event types (used as event IDs in NETWORK_EVENTS base). + */ +typedef enum { + NETWORK_EVENT_WIFI_CONNECTED = 0, + NETWORK_EVENT_WIFI_DISCONNECTED, + NETWORK_EVENT_WIFI_GOT_IP, /**< The device got an IP address + Data is of type Network_IP_Info_t */ + NETWORK_EVENT_VISA_CLIENT_CONNECTED, /**< A client connected to the VISA server */ + NETWORK_EVENT_VISA_CLIENT_DISCONNECTED, /**< A client disconnected from the VISA server */ + NETWORK_EVENT_REMOTE_LOCK_SET, /**< Set the remote lock state. + Data is of type bool. */ + NETWORK_EVENT_REMOTE_DISPLAY_MESSAGE, /**< Remote display message changed. + Data is of type Remote_Display_Message_t. */ + NETWORK_EVENT_AP_STARTED, + NETWORK_EVENT_AP_STOPPED, + NETWORK_EVENT_AP_STA_CONNECTED, + NETWORK_EVENT_AP_STA_DISCONNECTED, + NETWORK_EVENT_PROV_STARTED, + NETWORK_EVENT_PROV_STOPPED, + NETWORK_EVENT_PROV_CRED_RECV, + NETWORK_EVENT_PROV_SUCCESS, + NETWORK_EVENT_PROV_FAILED, + NETWORK_EVENT_PROV_TIMEOUT, + NETWORK_EVENT_OTA_STARTED, + NETWORK_EVENT_OTA_PROGRESS, + NETWORK_EVENT_OTA_COMPLETED, + NETWORK_EVENT_OTA_FAILED, + NETWORK_EVENT_OPEN_WIFI_REQUEST, /**< Request to open a WiFi connection */ + NETWORK_EVENT_SERVER_STARTED, /**< HTTP/WebSocket server started */ + NETWORK_EVENT_SERVER_STOPPED, /**< HTTP/WebSocket server stopped */ + NETWORK_EVENT_SERVER_ERROR, /**< HTTP/WebSocket server error */ +} Network_Event_t; + +/** @brief Color palette types. + */ +typedef enum { + PALETTE_IRON = 0, + PALETTE_GRAY, + PALETTE_RAINBOW, +} Server_Palette_t; + +/** @brief Scale mode for temperature visualization. + */ +typedef enum { + SCALE_LINEAR = 0, + SCALE_HISTOGRAM, +} Server_Scale_t; + +/** @brief LED state. + */ +typedef enum { + LED_STATE_OFF = 0, + LED_STATE_ON, + LED_STATE_BLINK, +} Server_LED_State_t; + +/** @brief WebSocket message types. + */ +typedef enum { + WS_MSG_TYPE_EVENT = 0, + WS_MSG_TYPE_COMMAND, + WS_MSG_TYPE_RESPONSE, +} Server_WS_Message_Type_t; + +/** @brief Remote control display message structure. + */ +typedef struct { + char Message[128]; /**< Message to display on the device */ +} Remote_Display_Message_t; + +/** @brief LED color enumeration. + */ +typedef enum { + REMOTE_LED_RED = 0, /**< Red LED. */ + REMOTE_LED_GREEN = 1, /**< Green LED. */ + REMOTE_LED_BLUE = 2, /**< Blue LED. */ +} Remote_LED_Color_t; + +/** @brief Thermal frame data structure. + */ +typedef struct { + uint8_t *Buffer; /**< Pointer to RGB888 image data */ + uint16_t Width; /**< Frame width in pixels */ + uint16_t Height; /**< Frame height in pixels */ + uint32_t Timestamp; /**< Timestamp in milliseconds */ + SemaphoreHandle_t Mutex; /**< Mutex for thread-safe access */ +} Network_Thermal_Frame_t; + +/** @brief Thermal telemetry data structure. + */ +typedef struct { +} Network_Thermal_Telemetry_t; + +/** @brief Encoded image data. + */ +typedef struct { + uint8_t *Data; /**< Encoded image data */ + size_t Size; /**< Size of encoded data */ + Settings_Image_Format_t Format; /**< Image format */ + uint16_t Width; /**< Image width */ + uint16_t Height; /**< Image height */ +} Network_Encoded_Image_t; + +/** @brief IP info event data (for NETWORK_EVENT_WIFI_GOT_IP). + */ +typedef struct { + uint32_t IP; + uint32_t Netmask; + uint32_t Gateway; +} Network_IP_Info_t; + +/** @brief Station info event data (for AP_STA_CONNECTED/DISCONNECTED). + */ +typedef struct { + uint8_t MAC[6]; +} Network_Event_STA_Info_t; + +/** @brief HTTP Server configuration. + */ +typedef struct { + uint16_t Port; + uint8_t MaxClients; + uint16_t WSPingIntervalSec; + bool EnableCORS; + char API_Key[64]; +} Network_HTTP_Server_Config_t; + +#endif /* NETWORK_TYPES_H_ */ diff --git a/main/Application/Manager/Settings/Private/settingsDefaultLoader.cpp b/main/Application/Manager/Settings/Private/settingsDefaultLoader.cpp new file mode 100644 index 0000000..1fc976a --- /dev/null +++ b/main/Application/Manager/Settings/Private/settingsDefaultLoader.cpp @@ -0,0 +1,190 @@ +/* + * settingsDefaultLoader.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Default settings loader implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include + +#include +#include + +#include + +#include "settingsLoader.h" +#include "../settingsManager.h" + +static const char *TAG = "Settings-Default-Loader"; + +void SettingsManager_InitDefaultLeptonROIs(Settings_t *p_Settings) +{ + /* Spotmeter defaults */ + p_Settings->Lepton.ROI[ROI_TYPE_SPOTMETER].Type = ROI_TYPE_SPOTMETER; + p_Settings->Lepton.ROI[ROI_TYPE_SPOTMETER].x = 60; + p_Settings->Lepton.ROI[ROI_TYPE_SPOTMETER].y = 40; + p_Settings->Lepton.ROI[ROI_TYPE_SPOTMETER].w = 40; + p_Settings->Lepton.ROI[ROI_TYPE_SPOTMETER].h = 40; + + /* Scene statistics defaults */ + p_Settings->Lepton.ROI[ROI_TYPE_SCENE].Type = ROI_TYPE_SCENE; + p_Settings->Lepton.ROI[ROI_TYPE_SCENE].x = 0; + p_Settings->Lepton.ROI[ROI_TYPE_SCENE].y = 0; + p_Settings->Lepton.ROI[ROI_TYPE_SCENE].w = 160; + p_Settings->Lepton.ROI[ROI_TYPE_SCENE].h = 120; + + /* AGC defaults */ + p_Settings->Lepton.ROI[ROI_TYPE_AGC].Type = ROI_TYPE_AGC; + p_Settings->Lepton.ROI[ROI_TYPE_AGC].x = 0; + p_Settings->Lepton.ROI[ROI_TYPE_AGC].y = 0; + p_Settings->Lepton.ROI[ROI_TYPE_AGC].w = 160; + p_Settings->Lepton.ROI[ROI_TYPE_AGC].h = 120; + + /* Video focus defaults */ + p_Settings->Lepton.ROI[ROI_TYPE_VIDEO_FOCUS].Type = ROI_TYPE_VIDEO_FOCUS; + p_Settings->Lepton.ROI[ROI_TYPE_VIDEO_FOCUS].x = 1; + p_Settings->Lepton.ROI[ROI_TYPE_VIDEO_FOCUS].y = 1; + p_Settings->Lepton.ROI[ROI_TYPE_VIDEO_FOCUS].w = 157; + p_Settings->Lepton.ROI[ROI_TYPE_VIDEO_FOCUS].h = 157; +} + +void SettingsManager_InitDefaultLeptonEmissivityPresets(Settings_t *p_Settings) +{ + /* No emissiviy values available */ + p_Settings->Lepton.EmissivityPresetsCount = 1; + p_Settings->Lepton.EmissivityPresets[0].Value = 1.0f; + strncpy(p_Settings->Lepton.EmissivityPresets[0].Description, "Unknown", + sizeof(p_Settings->Lepton.EmissivityPresets[0].Description)); + + p_Settings->Lepton.CurrentEmissivity = SETTINGS_DEFAULT_LEPTON_EMISSIVITY; +} + +void SettingsManager_InitDefaultDisplay(Settings_t *p_Settings) +{ + ESP_LOGW(TAG, "Loading default Display settings"); + + p_Settings->Display.Brightness = SETTINGS_DISPLAY_DEFAULT_BRIGHTNESS; + p_Settings->Display.Timeout = SETTINGS_DISPLAY_DEFAULT_TIMEOUT; +} + +void SettingsManager_InitDefaultProvisioning(Settings_t *p_Settings) +{ + ESP_LOGW(TAG, "Loading default Provisioning settings"); + + p_Settings->Provisioning.Timeout = SETTINGS_PROVISIONING_DEFAULT_TIMEOUT; + strncpy(p_Settings->Provisioning.Name, SETTINGS_PROVISIONING_DEFAULT_NAME, sizeof(p_Settings->Provisioning.Name)); +} + +void SettingsManager_InitDefaultWiFi(Settings_t *p_Settings) +{ + ESP_LOGW(TAG, "Loading default WiFi settings"); + + p_Settings->WiFi.AutoConnect = SETTINGS_WIFI_DEFAULT_AUTOCONNECT; + p_Settings->WiFi.MaxRetries = SETTINGS_WIFI_DEFAULT_MAX_RETRIES; + p_Settings->WiFi.RetryInterval = SETTINGS_WIFI_DEFAULT_RETRY_INTERVAL; + strncpy(p_Settings->WiFi.SSID, SETTINGS_WIFI_DEFAULT_SSID, sizeof(p_Settings->WiFi.SSID)); + strncpy(p_Settings->WiFi.Password, SETTINGS_WIFI_DEFAULT_PASSWORD, sizeof(p_Settings->WiFi.Password)); +} + +void SettingsManager_InitDefaultSystem(Settings_t *p_Settings) +{ + uint8_t Mac[6]; + + ESP_LOGW(TAG, "Loading default System settings"); + + if (esp_efuse_mac_get_default(Mac) == ESP_OK) { + snprintf(p_Settings->System.DeviceName, sizeof(p_Settings->System.DeviceName), + "PyroVision-%02X%02X%02X%02X%02X%02X", + Mac[0], Mac[1], Mac[2], Mac[3], Mac[4], Mac[5]); + } else { + snprintf(p_Settings->System.DeviceName, sizeof(p_Settings->System.DeviceName), + SETTINGS_SYSTEM_DEFAULT_DEVICENAME); + ESP_LOGW(TAG, "Failed to get MAC address, using default name"); + } + + p_Settings->System.SDCard_AutoMount = true; + p_Settings->System.ImageFormat = IMAGE_FORMAT_JPEG; + p_Settings->System.JpegQuality = 80; + strncpy(p_Settings->System.Timezone, SETTINGS_SYSTEM_DEFAULT_TIMEZONE, sizeof(p_Settings->System.Timezone)); + strncpy(p_Settings->System.NTPServer, SETTINGS_SYSTEM_DEFAULT_NTP_SERVER, sizeof(p_Settings->System.NTPServer)); +} + +void SettingsManager_InitDefaultLepton(Settings_t *p_Settings) +{ + ESP_LOGW(TAG, "Loading default Lepton settings"); + + SettingsManager_InitDefaultLeptonROIs(p_Settings); + SettingsManager_InitDefaultLeptonEmissivityPresets(p_Settings); +} + +void SettingsManager_InitDefaultHTTPServer(Settings_t *p_Settings) +{ + ESP_LOGW(TAG, "Loading default HTTP Server settings"); + + p_Settings->HTTPServer.Port = SETTINGS_DEFAULT_HTTP_PORT; + p_Settings->HTTPServer.WSPingIntervalSec = SETTINGS_DEFAULT_WS_PING_INTERVAL; + p_Settings->HTTPServer.MaxClients = SETTINGS_DEFAULT_HTTP_MAX_CLIENTS; + p_Settings->HTTPServer.useCORS = SETTINGS_DEFAULT_HTTP_ENABLE_CORS; + strncpy(p_Settings->HTTPServer.APIKey, SETTINGS_DEFAULT_HTTP_API_KEY, sizeof(p_Settings->HTTPServer.APIKey)); +} + +void SettingsManager_InitDefaultVISAServer(Settings_t *p_Settings) +{ + ESP_LOGW(TAG, "Loading default VISA Server settings"); + + p_Settings->VISAServer.Port = SETTINGS_DEFAULT_VISA_PORT; + p_Settings->VISAServer.Timeout = SETTINGS_DEFAULT_VISA_TIMEOUT_MS; +} + +void SettingsManager_InitDefaultLEDFlash(Settings_t *p_Settings) +{ + ESP_LOGW(TAG, "Loading default LED Flash settings"); + + p_Settings->LEDFlash.Enable = SETTINGS_DEFAULT_LED_FLASH_ENABLE; + p_Settings->LEDFlash.Power = SETTINGS_DEFAULT_LED_FLASH_POWER; +} + +void SettingsManager_InitDefaultUSB(Settings_t *p_Settings) +{ + ESP_LOGW(TAG, "Loading default USB settings"); + + p_Settings->USB.MSC_Enabled = SETTINGS_DEFAULT_USB_MSC_ENABLE; + p_Settings->USB.UVC_Enabled = SETTINGS_DEFAULT_USB_UVC_ENABLE; + p_Settings->USB.CDC_Enabled = SETTINGS_DEFAULT_USB_CDC_ENABLE; + + memset(p_Settings->USB.Reserved, 0, sizeof(p_Settings->USB.Reserved)); +} + +void SettingsManager_LoadFromDefaults(Settings_Manager_State_t *p_State) +{ + memset(&p_State->Settings, 0, sizeof(Settings_t)); + + p_State->Settings.Version = SETTINGS_VERSION; + SettingsManager_InitDefaultDisplay(&p_State->Settings); + SettingsManager_InitDefaultProvisioning(&p_State->Settings); + SettingsManager_InitDefaultWiFi(&p_State->Settings); + SettingsManager_InitDefaultSystem(&p_State->Settings); + SettingsManager_InitDefaultLepton(&p_State->Settings); + SettingsManager_InitDefaultHTTPServer(&p_State->Settings); + SettingsManager_InitDefaultVISAServer(&p_State->Settings); + SettingsManager_InitDefaultLEDFlash(&p_State->Settings); + SettingsManager_InitDefaultUSB(&p_State->Settings); +} \ No newline at end of file diff --git a/main/Application/Manager/Settings/Private/settingsJSONLoader.cpp b/main/Application/Manager/Settings/Private/settingsJSONLoader.cpp new file mode 100644 index 0000000..c15c9c1 --- /dev/null +++ b/main/Application/Manager/Settings/Private/settingsJSONLoader.cpp @@ -0,0 +1,544 @@ +/* + * settingsJSONLoader.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: JSON settings loader for factory defaults. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "settingsLoader.h" +#include "../settingsManager.h" + +static const char *TAG = "Settings-JSON-Loader"; + +/** @brief Load the Lepton settings from the JSON object and apply them to the Settings Manager state. If a setting is missing or invalid, the default value is used. + * @param p_State The Settings Manager state structure to update with the loaded settings + * @param p_JSON The cJSON object representing the root of the settings JSON document + */ +static void SettingsManager_LoadLepton(Settings_Manager_State_t *p_State, const cJSON *p_JSON) +{ + cJSON *lepton = NULL; + cJSON *emissivity_array = NULL; + cJSON *roi_array = NULL; + + lepton = cJSON_GetObjectItem(p_JSON, "lepton"); + if (lepton != NULL) { + emissivity_array = cJSON_GetObjectItem(lepton, "emissivity"); + if (cJSON_IsArray(emissivity_array)) { + p_State->Settings.Lepton.EmissivityPresetsCount = cJSON_GetArraySize(emissivity_array); + + ESP_LOGD(TAG, "Found %d emissivity presets in JSON", p_State->Settings.Lepton.EmissivityPresetsCount); + + for (uint32_t i = 0; i < p_State->Settings.Lepton.EmissivityPresetsCount; i++) { + cJSON *preset = cJSON_GetArrayItem(emissivity_array, i); + cJSON *name = cJSON_GetObjectItem(preset, "name"); + cJSON *value = cJSON_GetObjectItem(preset, "value"); + + if (cJSON_IsString(name) && cJSON_IsNumber(value)) { + p_State->Settings.Lepton.EmissivityPresets[i].Value = (float)(value->valuedouble); + + /* Cap the emissivity value between 0 and 100 */ + if (p_State->Settings.Lepton.EmissivityPresets[i].Value < 0.0f) { + p_State->Settings.Lepton.EmissivityPresets[i].Value = 0.0f; + } else if (p_State->Settings.Lepton.EmissivityPresets[i].Value > 100.0f) { + p_State->Settings.Lepton.EmissivityPresets[i].Value = 100.0f; + } + + memset(p_State->Settings.Lepton.EmissivityPresets[i].Description, 0, + sizeof(p_State->Settings.Lepton.EmissivityPresets[i].Description)); + strncpy(p_State->Settings.Lepton.EmissivityPresets[i].Description, name->valuestring, + sizeof(p_State->Settings.Lepton.EmissivityPresets[i].Description)); + + ESP_LOGD(TAG, " Preset %d: %s = %.2f", i, name->valuestring, value->valuedouble); + } + } + + p_State->Settings.Lepton.CurrentEmissivity = SETTINGS_DEFAULT_LEPTON_EMISSIVITY; + } else { + SettingsManager_InitDefaultLeptonEmissivityPresets(&p_State->Settings); + } + + roi_array = cJSON_GetObjectItem(lepton, "roi"); + if (cJSON_IsArray(roi_array)) { + } else { + SettingsManager_InitDefaultLeptonROIs(&p_State->Settings); + } + } else { + SettingsManager_InitDefaultLepton(&p_State->Settings); + } +} + +/** @brief Load the Display settings from the JSON object and apply them to the Settings Manager state. If a setting is missing or invalid, the default value is used. + * @param p_State The Settings Manager state structure to update with the loaded settings + * @param p_JSON The cJSON object representing the root of the settings JSON document + */ +static void SettingsManager_LoadDisplay(Settings_Manager_State_t *p_State, const cJSON *p_JSON) +{ + cJSON *display = NULL; + + display = cJSON_GetObjectItem(p_JSON, "display"); + if (display != NULL) { + cJSON *brightness = cJSON_GetObjectItem(display, "brightness"); + if (cJSON_IsNumber(brightness)) { + p_State->Settings.Display.Brightness = static_cast(brightness->valueint); + } else { + p_State->Settings.Display.Brightness = SETTINGS_DISPLAY_DEFAULT_BRIGHTNESS; + } + + cJSON *timeout = cJSON_GetObjectItem(display, "timeout"); + if (cJSON_IsNumber(timeout)) { + p_State->Settings.Display.Timeout = static_cast(timeout->valueint); + } else { + p_State->Settings.Display.Timeout = SETTINGS_DISPLAY_DEFAULT_TIMEOUT; + } + } else { + SettingsManager_InitDefaultDisplay(&p_State->Settings); + } +} + +/** @brief Load the WiFi settings from the JSON object and apply them to the Settings Manager state. If a setting is missing or invalid, the default value is used. + * @param p_State The Settings Manager state structure to update with the loaded settings + * @param p_JSON The cJSON object representing the root of the settings JSON document + */ +static void SettingsManager_LoadWiFi(Settings_Manager_State_t *p_State, const cJSON *p_JSON) +{ + cJSON *wifi = NULL; + + wifi = cJSON_GetObjectItem(p_JSON, "wifi"); + if (wifi != NULL) { + cJSON *maxRetries = cJSON_GetObjectItem(wifi, "maxRetries"); + if (cJSON_IsNumber(maxRetries)) { + p_State->Settings.WiFi.MaxRetries = static_cast(maxRetries->valueint); + } else { + p_State->Settings.WiFi.MaxRetries = SETTINGS_WIFI_DEFAULT_MAX_RETRIES; + } + + cJSON *retryInterval = cJSON_GetObjectItem(wifi, "retryInterval"); + if (cJSON_IsNumber(retryInterval)) { + p_State->Settings.WiFi.RetryInterval = static_cast(retryInterval->valueint); + } else { + p_State->Settings.WiFi.RetryInterval = SETTINGS_WIFI_DEFAULT_RETRY_INTERVAL; + } + + cJSON *autoConnect = cJSON_GetObjectItem(wifi, "autoConnect"); + if (cJSON_IsBool(autoConnect)) { + p_State->Settings.WiFi.AutoConnect = cJSON_IsTrue(autoConnect); + } else { + p_State->Settings.WiFi.AutoConnect = SETTINGS_WIFI_DEFAULT_AUTOCONNECT; + } + + cJSON *ssid = cJSON_GetObjectItem(wifi, "ssid"); + if (cJSON_IsString(ssid)) { + strncpy(p_State->Settings.WiFi.SSID, ssid->valuestring, sizeof(p_State->Settings.WiFi.SSID)); + } else { + strncpy(p_State->Settings.WiFi.SSID, SETTINGS_WIFI_DEFAULT_SSID, sizeof(p_State->Settings.WiFi.SSID)); + } + + cJSON *password = cJSON_GetObjectItem(wifi, "password"); + if (cJSON_IsString(password)) { + strncpy(p_State->Settings.WiFi.Password, password->valuestring, sizeof(p_State->Settings.WiFi.Password)); + } else { + strncpy(p_State->Settings.WiFi.Password, SETTINGS_WIFI_DEFAULT_PASSWORD, sizeof(p_State->Settings.WiFi.Password)); + } + } else { + SettingsManager_InitDefaultWiFi(&p_State->Settings); + } +} + +/** @brief Load the Provisioning settings from the JSON object and apply them to the Settings Manager state. If a setting is missing or invalid, the default value is used. + * @param p_State The Settings Manager state structure to update with the loaded settings + * @param p_JSON The cJSON object representing the root of the settings JSON document + */ +static void SettingsManager_LoadProvisioning(Settings_Manager_State_t *p_State, const cJSON *p_JSON) +{ + cJSON *provisioning = NULL; + + provisioning = cJSON_GetObjectItem(p_JSON, "provisioning"); + if (provisioning != NULL) { + cJSON *name = cJSON_GetObjectItem(provisioning, "name"); + if (cJSON_IsString(name)) { + strncpy(p_State->Settings.Provisioning.Name, name->valuestring, sizeof(p_State->Settings.Provisioning.Name)); + } else { + strncpy(p_State->Settings.Provisioning.Name, SETTINGS_PROVISIONING_DEFAULT_NAME, + sizeof(p_State->Settings.Provisioning.Name)); + } + + cJSON *timeout = cJSON_GetObjectItem(provisioning, "timeout"); + if (cJSON_IsNumber(timeout)) { + p_State->Settings.Provisioning.Timeout = static_cast(timeout->valueint); + } else { + p_State->Settings.Provisioning.Timeout = SETTINGS_PROVISIONING_DEFAULT_TIMEOUT; + } + } else { + SettingsManager_InitDefaultProvisioning(&p_State->Settings); + } +} + +/** @brief Load the System settings from the JSON object and apply them to the Settings Manager state. If a setting is missing or invalid, the default value is used. + * @param p_State The Settings Manager state structure to update with the loaded settings + * @param p_JSON The cJSON object representing the root of the settings JSON document + */ +static void SettingsManager_LoadSystem(Settings_Manager_State_t *p_State, const cJSON *p_JSON) +{ + cJSON *system = NULL; + + system = cJSON_GetObjectItem(p_JSON, "system"); + if (system != NULL) { + cJSON *timezone = cJSON_GetObjectItem(system, "timezone"); + if (cJSON_IsString(timezone)) { + strncpy(p_State->Settings.System.Timezone, timezone->valuestring, sizeof(p_State->Settings.System.Timezone)); + } else { + strncpy(p_State->Settings.System.Timezone, SETTINGS_SYSTEM_DEFAULT_TIMEZONE, sizeof(p_State->Settings.System.Timezone)); + } + + cJSON *devicename = cJSON_GetObjectItem(system, "devicename"); + if (cJSON_IsString(devicename)) { + strncpy(p_State->Settings.System.DeviceName, devicename->valuestring, sizeof(p_State->Settings.System.DeviceName)); + } else { + strncpy(p_State->Settings.System.DeviceName, SETTINGS_SYSTEM_DEFAULT_DEVICENAME, + sizeof(p_State->Settings.System.DeviceName)); + } + + cJSON *imageFormat = cJSON_GetObjectItem(system, "imageFormat"); + if (cJSON_IsString(imageFormat)) { + if (strcmp(imageFormat->valuestring, "PNG") == 0) { + p_State->Settings.System.ImageFormat = IMAGE_FORMAT_PNG; + } else if (strcmp(imageFormat->valuestring, "RAW") == 0) { + p_State->Settings.System.ImageFormat = IMAGE_FORMAT_RAW; + } else if (strcmp(imageFormat->valuestring, "JPEG") == 0) { + p_State->Settings.System.ImageFormat = IMAGE_FORMAT_JPEG; + } else if (strcmp(imageFormat->valuestring, "BITMAP") == 0 || strcmp(imageFormat->valuestring, "BMP") == 0) { + p_State->Settings.System.ImageFormat = IMAGE_FORMAT_BITMAP; + } else { + p_State->Settings.System.ImageFormat = IMAGE_FORMAT_JPEG; /* Default to JPEG */ + } + } else { + p_State->Settings.System.ImageFormat = IMAGE_FORMAT_JPEG; /* Default to JPEG */ + } + + cJSON *jpegQuality = cJSON_GetObjectItem(system, "jpegQuality"); + if (cJSON_IsNumber(jpegQuality)) { + p_State->Settings.System.JpegQuality = static_cast(jpegQuality->valueint); + /* Clamp to valid range 1-100 */ + if (p_State->Settings.System.JpegQuality < 1) { + p_State->Settings.System.JpegQuality = 1; + } else if (p_State->Settings.System.JpegQuality > 100) { + p_State->Settings.System.JpegQuality = 100; + } + } else { + p_State->Settings.System.JpegQuality = 80; /* Default to 80 */ + } + } else { + SettingsManager_InitDefaultSystem(&p_State->Settings); + } +} + +/** @brief Load the HTTP server settings from the JSON object and apply them to the Settings Manager state. If a setting is missing or invalid, the default value is used. + * @param p_State The Settings Manager state structure to update with the loaded settings + * @param p_JSON The cJSON object representing the root of the settings JSON document + */ +static void SettingsManager_LoadHTTPServer(Settings_Manager_State_t *p_State, const cJSON *p_JSON) +{ + cJSON *http_server = NULL; + + http_server = cJSON_GetObjectItem(p_JSON, "http-server"); + if (http_server != NULL) { + cJSON *port = cJSON_GetObjectItem(http_server, "port"); + if (cJSON_IsNumber(port)) { + p_State->Settings.HTTPServer.Port = static_cast(port->valueint); + } else { + p_State->Settings.HTTPServer.Port = SETTINGS_DEFAULT_HTTP_PORT; + } + + cJSON *wsPingIntervalSec = cJSON_GetObjectItem(http_server, "wsPingIntervalSec"); + if (cJSON_IsNumber(wsPingIntervalSec)) { + p_State->Settings.HTTPServer.WSPingIntervalSec = static_cast(wsPingIntervalSec->valueint); + } else { + p_State->Settings.HTTPServer.WSPingIntervalSec = SETTINGS_DEFAULT_WS_PING_INTERVAL; + } + + cJSON *maxClients = cJSON_GetObjectItem(http_server, "maxClients"); + if (cJSON_IsNumber(maxClients)) { + p_State->Settings.HTTPServer.MaxClients = static_cast(maxClients->valueint); + } else { + p_State->Settings.HTTPServer.MaxClients = SETTINGS_DEFAULT_HTTP_MAX_CLIENTS; + } + + cJSON *useCORS = cJSON_GetObjectItem(http_server, "enable-cors"); + if (cJSON_IsBool(useCORS)) { + p_State->Settings.HTTPServer.useCORS = cJSON_IsTrue(useCORS); + } else { + p_State->Settings.HTTPServer.useCORS = SETTINGS_DEFAULT_HTTP_ENABLE_CORS; + } + + cJSON *apiKey = cJSON_GetObjectItem(http_server, "api-key"); + if (cJSON_IsString(apiKey)) { + strncpy(p_State->Settings.HTTPServer.APIKey, apiKey->valuestring, sizeof(p_State->Settings.HTTPServer.APIKey)); + } else { + strncpy(p_State->Settings.HTTPServer.APIKey, SETTINGS_DEFAULT_HTTP_API_KEY, sizeof(p_State->Settings.HTTPServer.APIKey)); + } + } else { + SettingsManager_InitDefaultHTTPServer(&p_State->Settings); + } +} + +/** @brief Load the VISA server settings from the JSON object and apply them to the Settings Manager state. If a setting is missing or invalid, the default value is used. + * @param p_State The Settings Manager state structure to update with the loaded settings + * @param p_JSON The cJSON object representing the root of the settings JSON document + */ +static void SettingsManager_LoadVISAServer(Settings_Manager_State_t *p_State, const cJSON *p_JSON) +{ + cJSON *visa_server = NULL; + + visa_server = cJSON_GetObjectItem(p_JSON, "visa-server"); + if (visa_server != NULL) { + cJSON *port = cJSON_GetObjectItem(visa_server, "port"); + if (cJSON_IsNumber(port)) { + p_State->Settings.VISAServer.Port = static_cast(port->valueint); + } else { + p_State->Settings.VISAServer.Port = SETTINGS_DEFAULT_VISA_PORT; + } + + cJSON *timeout = cJSON_GetObjectItem(visa_server, "timeout"); + if (cJSON_IsNumber(timeout)) { + p_State->Settings.VISAServer.Timeout = static_cast(timeout->valueint); + } else { + p_State->Settings.VISAServer.Timeout = SETTINGS_DEFAULT_VISA_TIMEOUT_MS; + } + } else { + SettingsManager_InitDefaultVISAServer(&p_State->Settings); + } +} + +/** @brief Load the LED flash settings from the JSON object and apply them to the Settings Manager state. If a setting is missing or invalid, the default value is used. + * @param p_State The Settings Manager state structure to update with the loaded settings + * @param p_JSON The cJSON object representing the root of the settings JSON document + */ +static void SettingsManager_LoadLEDFlash(Settings_Manager_State_t *p_State, const cJSON *p_JSON) +{ + cJSON *led_flash = NULL; + + led_flash = cJSON_GetObjectItem(p_JSON, "led-flash"); + if (led_flash != NULL) { + cJSON *enable = cJSON_GetObjectItem(led_flash, "enable"); + if (cJSON_IsBool(enable)) { + p_State->Settings.LEDFlash.Enable = cJSON_IsTrue(enable); + } else { + p_State->Settings.LEDFlash.Enable = SETTINGS_DEFAULT_LED_FLASH_ENABLE; + } + + cJSON *power = cJSON_GetObjectItem(led_flash, "power"); + if (cJSON_IsNumber(power)) { + p_State->Settings.LEDFlash.Power = static_cast(power->valueint); + } else { + p_State->Settings.LEDFlash.Power = SETTINGS_DEFAULT_LED_FLASH_POWER; + } + } else { + SettingsManager_InitDefaultLEDFlash(&p_State->Settings); + } +} + +/** @brief Load the USB settings from the JSON object and apply them to the Settings Manager state. If a setting is missing or invalid, the default value is used. + * @param p_State The Settings Manager state structure to update with the loaded settings + * @param p_JSON The cJSON object representing the root of the settings JSON document + */ +static void SettingsManager_LoadUSB(Settings_Manager_State_t *p_State, const cJSON *p_JSON) +{ + cJSON *usb = NULL; + + usb = cJSON_GetObjectItem(p_JSON, "usb"); + if (usb != NULL) { + cJSON *msc_enabled = cJSON_GetObjectItem(usb, "msc-enabled"); + if (cJSON_IsBool(msc_enabled)) { + p_State->Settings.USB.MSC_Enabled = cJSON_IsTrue(msc_enabled); + } else { + p_State->Settings.USB.MSC_Enabled = SETTINGS_DEFAULT_USB_MSC_ENABLE; + } + + cJSON *uvc_enabled = cJSON_GetObjectItem(usb, "uvc-enabled"); + if (cJSON_IsBool(uvc_enabled)) { + p_State->Settings.USB.UVC_Enabled = cJSON_IsTrue(uvc_enabled); + } else { + p_State->Settings.USB.UVC_Enabled = SETTINGS_DEFAULT_USB_UVC_ENABLE; + } + + cJSON *cdc_enabled = cJSON_GetObjectItem(usb, "cdc-enabled"); + if (cJSON_IsBool(cdc_enabled)) { + p_State->Settings.USB.CDC_Enabled = cJSON_IsTrue(cdc_enabled); + } else { + p_State->Settings.USB.CDC_Enabled = SETTINGS_DEFAULT_USB_CDC_ENABLE; + } + } else { + SettingsManager_InitDefaultUSB(&p_State->Settings); + } +} + +esp_err_t SettingsManager_LoadFromJSON(Settings_Manager_State_t *p_State, const char *p_FilePath) +{ + FILE *File = NULL; + char *Buffer = NULL; + long FileSize; + size_t BytesRead; + cJSON *JSON = NULL; + esp_err_t Error; + uint32_t Caps; + + ESP_LOGD(TAG, "Loading JSON settings from: %s", p_FilePath); + + /* Check if file exists */ + struct stat st; + if (stat(p_FilePath, &st) != 0) { + ESP_LOGE(TAG, "File does not exist or cannot be accessed: %s!", p_FilePath); + return ESP_ERR_NOT_FOUND; + } + + ESP_LOGD(TAG, "Opening settings file..."); + File = fopen(p_FilePath, "r"); + if (File == NULL) { + ESP_LOGW(TAG, "Failed to open: %s!", p_FilePath); + + return ESP_ERR_NOT_FOUND; + } + + /* Get file size */ + fseek(File, 0, SEEK_END); + FileSize = ftell(File); + fseek(File, 0, SEEK_SET); + + if (FileSize <= 0) { + ESP_LOGE(TAG, "Invalid file size: %ld!", FileSize); + + fclose(File); + + return ESP_ERR_INVALID_SIZE; + } + + ESP_LOGD(TAG, "File size: %ld bytes", FileSize); + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + #else + Caps = MALLOC_CAP_8BIT; + #endif + + /* Allocate buffer for file content */ + Buffer = static_cast(heap_caps_malloc(FileSize + 1, Caps)); + if (Buffer == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for file buffer!"); + + fclose(File); + + return ESP_ERR_NO_MEM; + } + + /* Read file content */ + BytesRead = fread(Buffer, 1, FileSize, File); + fclose(File); + Buffer[FileSize] = '\0'; + + if (BytesRead != FileSize) { + ESP_LOGE(TAG, "Read error: got %zu of %ld bytes!", BytesRead, FileSize); + + heap_caps_free(Buffer); + + return ESP_FAIL; + } + + /* Parse JSON */ + JSON = cJSON_Parse(Buffer); + heap_caps_free(Buffer); + Buffer = NULL; + + if (JSON == NULL) { + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) { + ESP_LOGE(TAG, "JSON Parse Error: %s!", error_ptr); + } else { + ESP_LOGE(TAG, "JSON Parse Error: Unknown!"); + } + + return ESP_FAIL; + } + + ESP_LOGI(TAG, "JSON parsed successfully from %s", p_FilePath); + + /* Check the version number of the JSON and the version from the firmware. Skip the loading if the version in the JSON is older or invalid. */ + cJSON *version = cJSON_GetObjectItem(JSON, "version"); + if (cJSON_IsNumber(version)) { + p_State->Settings.Version = static_cast(version->valueint); + + if (p_State->Settings.Version != SETTINGS_VERSION) { + ESP_LOGW(TAG, "Settings version mismatch (expected %u, got %u), erasing and using defaults", + SETTINGS_VERSION, p_State->Settings.Version); + + Error = ESP_ERR_INVALID_VERSION; + + goto SettingsManager_Load_JSON_Exit; + } + } else { + Error = ESP_ERR_INVALID_VERSION; + + goto SettingsManager_Load_JSON_Exit; + } + + Error = ESP_OK; + + /* Load settings sections */ + SettingsManager_LoadDisplay(p_State, JSON); + + /* Extract Provisioning settings */ + SettingsManager_LoadProvisioning(p_State, JSON); + + /* Extract WiFi settings */ + SettingsManager_LoadWiFi(p_State, JSON); + + /* Extract System settings */ + SettingsManager_LoadSystem(p_State, JSON); + + /* Extract Lepton settings */ + SettingsManager_LoadLepton(p_State, JSON); + + /* Extract HTTP Server settings */ + SettingsManager_LoadHTTPServer(p_State, JSON); + + /* Extract VISA Server settings */ + SettingsManager_LoadVISAServer(p_State, JSON); + + /* Extract LED flash settings */ + SettingsManager_LoadLEDFlash(p_State, JSON); + + /* Extract USB settings */ + SettingsManager_LoadUSB(p_State, JSON); + +SettingsManager_Load_JSON_Exit: + cJSON_Delete(JSON); + + return Error; +} diff --git a/main/Application/Manager/Settings/Private/settingsLoader.h b/main/Application/Manager/Settings/Private/settingsLoader.h new file mode 100644 index 0000000..417adb4 --- /dev/null +++ b/main/Application/Manager/Settings/Private/settingsLoader.h @@ -0,0 +1,154 @@ +/* + * settingsLoader.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: JSON settings loader for factory defaults. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef SETTINGS_LOADER_H_ +#define SETTINGS_LOADER_H_ + +#include + +#include +#include + +#include +#include + +#include + +#include "../settingsTypes.h" + +#define SETTINGS_DEFAULT_LED_FLASH_ENABLE true +#define SETTINGS_DEFAULT_LED_FLASH_POWER 100 + +#define SETTINGS_DEFAULT_USB_MSC_ENABLE false +#define SETTINGS_DEFAULT_USB_UVC_ENABLE false +#define SETTINGS_DEFAULT_USB_CDC_ENABLE false +#define SETTINGS_DEFAULT_USB_UVC_ENABLE false +#define SETTINGS_DEFAULT_USB_CDC_ENABLE false + +#define SETTINGS_DEFAULT_LEPTON_EMISSIVITY 100 + +#define SETTINGS_DEFAULT_VISA_PORT 5025 +#define SETTINGS_DEFAULT_VISA_TIMEOUT_MS 5000 + +#define SETTINGS_DEFAULT_HTTP_PORT 80 +#define SETTINGS_DEFAULT_WS_PING_INTERVAL 30 +#define SETTINGS_DEFAULT_HTTP_MAX_CLIENTS 4 +#define SETTINGS_DEFAULT_HTTP_ENABLE_CORS false +#define SETTINGS_DEFAULT_HTTP_API_KEY "" + +#define SETTINGS_SYSTEM_DEFAULT_DEVICENAME "PyroVision-Device" +#define SETTINGS_SYSTEM_DEFAULT_TIMEZONE "CET-1CEST,M3.5.0,M10.5.0/3" +#define SETTINGS_SYSTEM_DEFAULT_NTP_SERVER "pool.ntp.org" + +#define SETTINGS_PROVISIONING_DEFAULT_TIMEOUT 300 +#define SETTINGS_PROVISIONING_DEFAULT_NAME "PyroVision-Provision" + +#define SETTINGS_WIFI_DEFAULT_SSID "" +#define SETTINGS_WIFI_DEFAULT_PASSWORD "" +#define SETTINGS_WIFI_DEFAULT_MAX_RETRIES 5 +#define SETTINGS_WIFI_DEFAULT_RETRY_INTERVAL 2000 +#define SETTINGS_WIFI_DEFAULT_AUTOCONNECT true + +#define SETTINGS_DISPLAY_DEFAULT_BRIGHTNESS 80 +#define SETTINGS_DISPLAY_DEFAULT_TIMEOUT 0 + +/** @brief Settings Manager state. + */ +typedef struct { + bool isInitialized; + nvs_handle_t NVS_Handle; + Settings_t Settings; + Settings_Info_t Info; + SemaphoreHandle_t Mutex; +} Settings_Manager_State_t; + + +/** @brief Load and parse JSON settings from file into RAM settings structure. + * If the file is missing or invalid, returns an error and leaves settings unchanged. + * @param p_State Settings state structure + * @param filepath Full path to JSON file + * @return ESP_OK on success + */ +esp_err_t SettingsManager_LoadFromJSON(Settings_Manager_State_t *p_State, const char *p_FilePath); + +/** @brief Load factory default settings into RAM settings structure. This is used when no valid settings are found in NVS or JSON config. + * @param p_State Pointer to Settings Manager state structure + */ +void SettingsManager_LoadFromDefaults(Settings_Manager_State_t *p_State); + +/** @brief Initialize Lepton ROIs with factory defaults. + * @param p_Settings Pointer to settings structure + */ +void SettingsManager_InitDefaultLeptonROIs(Settings_t *p_Settings); + +/** @brief Initialize Lepton emissivity presets with factory defaults. + * @param p_Settings Pointer to settings structure + */ +void SettingsManager_InitDefaultLeptonEmissivityPresets(Settings_t *p_Settings); + +/** @brief Initialize Display settings with factory defaults. + * @param p_Settings Pointer to settings structure + */ +void SettingsManager_InitDefaultDisplay(Settings_t *p_Settings); + +/** @brief Initialize Provisioning settings with factory defaults. + * @param p_Settings Pointer to settings structure + */ +void SettingsManager_InitDefaultProvisioning(Settings_t *p_Settings); + +/** @brief Initialize WiFi settings with factory defaults. + * @param p_Settings Pointer to settings structure + */ +void SettingsManager_InitDefaultWiFi(Settings_t *p_Settings); + +/** @brief Initialize System settings with factory defaults. + * @param p_Settings Pointer to settings structure + */ +void SettingsManager_InitDefaultSystem(Settings_t *p_Settings); + +/** @brief Initialize Lepton settings with factory defaults. + * @param p_Settings Pointer to settings structure + */ +void SettingsManager_InitDefaultLepton(Settings_t *p_Settings); + +/** @brief Initialize HTTP server settings with factory defaults. + * @param p_Settings Pointer to settings structure + */ +void SettingsManager_InitDefaultHTTPServer(Settings_t *p_Settings); + +/** @brief Initialize VISA server settings with factory defaults. + * @param p_Settings Pointer to settings structure + */ +void SettingsManager_InitDefaultVISAServer(Settings_t *p_Settings); + +/** @brief Initialize LED flash settings with factory defaults. + * @param p_Settings Pointer to settings structure + */ +void SettingsManager_InitDefaultLEDFlash(Settings_t *p_Settings); + +/** @brief Initialize USB settings with factory defaults. + * @param p_Settings Pointer to settings structure + */ +void SettingsManager_InitDefaultUSB(Settings_t *p_Settings); + +#endif /* SETTINGS_LOADER_H_ */ \ No newline at end of file diff --git a/main/Application/Manager/Settings/settingsManager.cpp b/main/Application/Manager/Settings/settingsManager.cpp new file mode 100644 index 0000000..44327aa --- /dev/null +++ b/main/Application/Manager/Settings/settingsManager.cpp @@ -0,0 +1,530 @@ +/* + * settingsManager.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Settings Manager implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "settingsManager.h" +#include "Private/settingsLoader.h" + +static const char *TAG = "Settings-Manager"; + +ESP_EVENT_DEFINE_BASE(SETTINGS_EVENTS); + +static Settings_Manager_State_t _Settings_Manager_State; + +/** @brief Update a specific settings section in the Settings Manager RAM and emit the corresponding event. + * @param p_Src Pointer to source settings structure + * @param p_Dst Pointer to destination settings structure in RAM + * @param Size Size of the settings structure to copy + * @param EventID Event identifier to emit after update + * @param p_ChangedSetting Pointer to structure to receive changed setting ID and value for event data (can be NULL if not needed) + * @return ESP_OK on success + */ +static esp_err_t SettingsManager_Update(void *p_Src, void *p_Dst, size_t Size, int EventID, + SettingsManager_ChangeNotification_t *p_ChangedSetting = NULL) +{ + if (_Settings_Manager_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if ((p_Src == NULL) || (p_Dst == NULL) || (Size == 0)) { + return ESP_ERR_INVALID_ARG; + } + + xSemaphoreTake(_Settings_Manager_State.Mutex, portMAX_DELAY); + + memcpy(p_Dst, p_Src, Size); + + xSemaphoreGive(_Settings_Manager_State.Mutex); + + /* Only include event data if p_ChangedSetting is valid */ + esp_event_post(SETTINGS_EVENTS, EventID, p_ChangedSetting, + p_ChangedSetting ? sizeof(SettingsManager_ChangeNotification_t) : 0, portMAX_DELAY); + + return ESP_OK; +} + +/** @brief Get a specific settings section from the Settings Manager RAM. + * @param p_Output Pointer to output structure to populate with settings data + * @param p_Source Pointer to source settings structure in RAM + * @param Size Size of the settings structure to copy + * @return ESP_OK on success, ESP_ERR_* on failure + */ +static esp_err_t SettingsManager_Get(void* p_Output, void* p_Source, size_t Size) +{ + if ( p_Output == NULL ) { + return ESP_ERR_INVALID_ARG; + } else if (_Settings_Manager_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } + + xSemaphoreTake(_Settings_Manager_State.Mutex, portMAX_DELAY); + memcpy(p_Output, p_Source, Size); + xSemaphoreGive(_Settings_Manager_State.Mutex); + + return ESP_OK; +} + +esp_err_t SettingsManager_Init(void) +{ + uint16_t Serial; + esp_err_t Error; + + if (_Settings_Manager_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + ESP_LOGI(TAG, "Initializing Settings Manager..."); + + memset(&_Settings_Manager_State, 0, sizeof(Settings_Manager_State_t)); + + ESP_ERROR_CHECK(nvs_flash_init()); + + Error = nvs_flash_init_partition("settings"); + if ((Error == ESP_ERR_NVS_NO_FREE_PAGES) || (Error == ESP_ERR_NVS_NEW_VERSION_FOUND)) { + ESP_LOGW(TAG, "Settings partition needs erase, erasing..."); + ESP_ERROR_CHECK(nvs_flash_erase_partition("settings")); + + nvs_flash_init_partition("settings"); + } else if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize settings partition: %d!", Error); + + return Error; + } + + _Settings_Manager_State.Mutex = xSemaphoreCreateMutex(); + if (_Settings_Manager_State.Mutex == NULL) { + ESP_LOGE(TAG, "Failed to create mutex!"); + + return ESP_ERR_NO_MEM; + } + + Error = nvs_open_from_partition("settings", CONFIG_SETTINGS_NAMESPACE, NVS_READWRITE, + &_Settings_Manager_State.NVS_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to open NVS handle: %d!", Error); + + vSemaphoreDelete(_Settings_Manager_State.Mutex); + + return Error; + } + + _Settings_Manager_State.isInitialized = true; + + xSemaphoreTake(_Settings_Manager_State.Mutex, portMAX_DELAY); + + /* Get the serial from NVS. Use a temporary variable to prevent alignment errors. */ + Error = nvs_get_u16(_Settings_Manager_State.NVS_Handle, "serial", &Serial); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to get serial number from NVS: %d!. Using 0", Error); + + Serial = 0; + } + + xSemaphoreGive(_Settings_Manager_State.Mutex); + + /* Copy the read-only data */ + sprintf(_Settings_Manager_State.Info.FirmwareVersion, "%u.%u.%u", PYROVISION_VERSION_MAJOR, PYROVISION_VERSION_MINOR, + PYROVISION_VERSION_BUILD); + sprintf(_Settings_Manager_State.Info.Manufacturer, "%s", CONFIG_DEVICE_MANUFACTURER); + sprintf(_Settings_Manager_State.Info.Name, "%s", CONFIG_DEVICE_NAME); + sprintf(_Settings_Manager_State.Info.Serial, "%u", Serial); + sprintf(_Settings_Manager_State.Info.SDK, "%s", IDF_VER); + + /* Get application description with version info */ + const esp_app_desc_t *p_AppDesc = esp_app_get_description(); + sprintf(_Settings_Manager_State.Info.Commit, "%s", p_AppDesc->version); + ESP_LOGI(TAG, "Firmware Version: %s", p_AppDesc->version); + ESP_LOGI(TAG, "Firmware Date: %s %s", p_AppDesc->date, p_AppDesc->time); + ESP_LOGI(TAG, "Firmware IDF: %s", p_AppDesc->idf_ver); + ESP_LOGI(TAG, "Manufacturer: %s", _Settings_Manager_State.Info.Manufacturer); + ESP_LOGI(TAG, "Device Name: %s", _Settings_Manager_State.Info.Name); + ESP_LOGI(TAG, "Serial: %s", _Settings_Manager_State.Info.Serial); + + /* Read bootloader information */ + const esp_partition_t *p_BootloaderPartition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, + ESP_PARTITION_SUBTYPE_APP_FACTORY, + NULL); + if (p_BootloaderPartition != NULL) { + Error = esp_ota_get_partition_description(p_BootloaderPartition, &_Settings_Manager_State.Info.Bootloader); + if (Error == ESP_OK) { + ESP_LOGD(TAG, "Bootloader version: %s", _Settings_Manager_State.Info.Bootloader.version); + ESP_LOGD(TAG, "Bootloader date: %s %s", _Settings_Manager_State.Info.Bootloader.date, + _Settings_Manager_State.Info.Bootloader.time); + ESP_LOGD(TAG, "Bootloader IDF version: %s", _Settings_Manager_State.Info.Bootloader.idf_ver); + } else { + ESP_LOGW(TAG, "Failed to read bootloader description: %d!", Error); + } + } else { + ESP_LOGW(TAG, "Bootloader partition not found"); + } + + /* Load the settings from the NVS */ + Error = SettingsManager_LoadFromNVS(&_Settings_Manager_State.Settings); + if (Error != ESP_OK) { + ESP_LOGI(TAG, "No settings found, using JSON config defaults"); + + /* Try to load default settings from JSON first (on first boot) */ + if (SettingsManager_LoadFromJSON(&_Settings_Manager_State, "/storage/settings.json") != ESP_OK) { + ESP_LOGW(TAG, "Failed to load default settings from JSON, using built-in defaults"); + + /* Use built-in defaults */ + SettingsManager_LoadFromDefaults(&_Settings_Manager_State); + } + + /* Save the default settings to NVS */ + SettingsManager_Save(); + + /* Load the JSON presets into the settings structure */ + SettingsManager_LoadFromNVS(&_Settings_Manager_State.Settings); + } + + ESP_LOGI(TAG, "Settings Manager initialized"); + + esp_event_post(SETTINGS_EVENTS, SETTINGS_EVENT_LOADED, &_Settings_Manager_State.Settings, sizeof(Settings_t), + portMAX_DELAY); + + return ESP_OK; +} + +esp_err_t SettingsManager_Deinit(void) +{ + if (_Settings_Manager_State.isInitialized == false) { + return ESP_OK; + } + + nvs_close(_Settings_Manager_State.NVS_Handle); + vSemaphoreDelete(_Settings_Manager_State.Mutex); + + _Settings_Manager_State.isInitialized = false; + + ESP_LOGI(TAG, "Settings Manager deinitialized"); + + return ESP_OK; +} + +esp_err_t SettingsManager_LoadFromNVS(Settings_t *p_Settings) +{ + esp_err_t Error; + size_t RequiredSize; + uint8_t ConfigValid; + + if (_Settings_Manager_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (p_Settings == NULL) { + return ESP_ERR_INVALID_ARG; + } + + xSemaphoreTake(_Settings_Manager_State.Mutex, portMAX_DELAY); + + /* Check if the config is valid */ + Error = nvs_get_u8(_Settings_Manager_State.NVS_Handle, "config_valid", &ConfigValid); + if ((Error != ESP_OK) || (ConfigValid != 1)) { + ESP_LOGE(TAG, "Failed to read config_valid flag: %d!", Error); + + Error = ESP_ERR_NVS_INVALID_STATE; + + goto SettingsManager_LoadFromNVS_Exit; + } + + /* Get the settings version from NVS. Continue loading if the version numbers match. */ + Error = nvs_get_u32(_Settings_Manager_State.NVS_Handle, "version", &p_Settings->Version); + if ((Error == ESP_OK) && (p_Settings->Version == SETTINGS_VERSION)) { + Error = nvs_get_blob(_Settings_Manager_State.NVS_Handle, "settings", NULL, &RequiredSize); + if (Error == ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGW(TAG, "Settings not found in NVS!"); + + Error = ESP_ERR_NVS_NOT_FOUND; + + goto SettingsManager_LoadFromNVS_Exit; + } else if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to get settings size: %d!", Error); + + xSemaphoreGive(_Settings_Manager_State.Mutex); + + goto SettingsManager_LoadFromNVS_Exit; + } + + if (RequiredSize != sizeof(Settings_t)) { + ESP_LOGW(TAG, "Settings size mismatch (expected %u, got %u), erasing and using defaults", + sizeof(Settings_t), RequiredSize); + + /* Erase the old settings */ + nvs_erase_key(_Settings_Manager_State.NVS_Handle, "settings"); + nvs_commit(_Settings_Manager_State.NVS_Handle); + + Error = ESP_ERR_INVALID_SIZE; + + goto SettingsManager_LoadFromNVS_Exit; + } + + Error = nvs_get_blob(_Settings_Manager_State.NVS_Handle, "settings", &_Settings_Manager_State.Settings, &RequiredSize); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to read settings: %d!", Error); + + goto SettingsManager_LoadFromNVS_Exit; + } + + memcpy(p_Settings, &_Settings_Manager_State.Settings, sizeof(Settings_t)); + + ESP_LOGD(TAG, "Settings loaded from NVS"); + + Error = ESP_OK; + } + /* We reach this case when we can no read a settings version because it does not exist or does not match */ + else { + ESP_LOGI(TAG, "Settings version mismatch or not found in NVS (expected %u, got %u)", + SETTINGS_VERSION, p_Settings->Version); + + Error = ESP_ERR_INVALID_VERSION; + } + +SettingsManager_LoadFromNVS_Exit: + xSemaphoreGive(_Settings_Manager_State.Mutex); + + return Error; +} + +esp_err_t SettingsManager_Save(void) +{ + esp_err_t Error; + + if (_Settings_Manager_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } + + xSemaphoreTake(_Settings_Manager_State.Mutex, portMAX_DELAY); + + /* Save the version number first */ + Error = nvs_set_u32(_Settings_Manager_State.NVS_Handle, "version", _Settings_Manager_State.Settings.Version); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to write version: %d!", Error); + + goto SettingsManager_Save_Error; + } + + Error = nvs_set_blob(_Settings_Manager_State.NVS_Handle, "settings", &_Settings_Manager_State.Settings, + sizeof(Settings_t)); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to write settings: %d!", Error); + + goto SettingsManager_Save_Error; + } + + /* Mark config as valid */ + Error = nvs_set_u8(_Settings_Manager_State.NVS_Handle, "config_valid", true); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to set config_valid flag: %d!", Error); + + goto SettingsManager_Save_Error; + } + + Error = nvs_commit(_Settings_Manager_State.NVS_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to commit settings: %d!", Error); + + goto SettingsManager_Save_Error; + } + + xSemaphoreGive(_Settings_Manager_State.Mutex); + + ESP_LOGI(TAG, "Settings saved to NVS (version %u)", _Settings_Manager_State.Settings.Version); + + esp_event_post(SETTINGS_EVENTS, SETTINGS_EVENT_SAVED, NULL, 0, portMAX_DELAY); + + return ESP_OK; + +SettingsManager_Save_Error: + xSemaphoreGive(_Settings_Manager_State.Mutex); + + ESP_LOGE(TAG, "Failed to save settings to NVS: %d!", Error); + + return Error; +} + +esp_err_t SettingsManager_GetInfo(Settings_Info_t *p_Settings) +{ + return SettingsManager_Get(p_Settings, &_Settings_Manager_State.Info, sizeof(Settings_Info_t)); +} + +esp_err_t SettingsManager_GetLepton(Settings_Lepton_t *p_Settings) +{ + return SettingsManager_Get(p_Settings, &_Settings_Manager_State.Settings.Lepton, sizeof(Settings_Lepton_t)); +} + +esp_err_t SettingsManager_UpdateLepton(Settings_Lepton_t *p_Settings, SettingsManager_ChangeNotification_t *p_ChangedSetting) +{ + return SettingsManager_Update(p_Settings, &_Settings_Manager_State.Settings.Lepton, sizeof(Settings_Lepton_t), + SETTINGS_EVENT_LEPTON_CHANGED, p_ChangedSetting); +} + +esp_err_t SettingsManager_GetWiFi(Settings_WiFi_t *p_Settings) +{ + return SettingsManager_Get(p_Settings, &_Settings_Manager_State.Settings.WiFi, sizeof(Settings_WiFi_t)); +} + +esp_err_t SettingsManager_UpdateWiFi(Settings_WiFi_t *p_Settings, SettingsManager_ChangeNotification_t *p_ChangedSetting) +{ + return SettingsManager_Update(p_Settings, &_Settings_Manager_State.Settings.WiFi, sizeof(Settings_WiFi_t), + SETTINGS_EVENT_WIFI_CHANGED, p_ChangedSetting); +} + +esp_err_t SettingsManager_GetProvisioning(Settings_Provisioning_t *p_Settings) +{ + return SettingsManager_Get(p_Settings, &_Settings_Manager_State.Settings.Provisioning, sizeof(Settings_Provisioning_t)); +} + +esp_err_t SettingsManager_UpdateProvisioning(Settings_Provisioning_t *p_Settings, SettingsManager_ChangeNotification_t *p_ChangedSetting) +{ + return SettingsManager_Update(p_Settings, &_Settings_Manager_State.Settings.Provisioning, + sizeof(Settings_Provisioning_t), + SETTINGS_EVENT_PROVISIONING_CHANGED, p_ChangedSetting); +} + +esp_err_t SettingsManager_GetDisplay(Settings_Display_t *p_Settings) +{ + return SettingsManager_Get(p_Settings, &_Settings_Manager_State.Settings.Display, sizeof(Settings_Display_t)); +} + +esp_err_t SettingsManager_UpdateDisplay(Settings_Display_t *p_Settings, SettingsManager_ChangeNotification_t *p_ChangedSetting) +{ + return SettingsManager_Update(p_Settings, &_Settings_Manager_State.Settings.Display, sizeof(Settings_Display_t), + SETTINGS_EVENT_DISPLAY_CHANGED, p_ChangedSetting); +} + +esp_err_t SettingsManager_GetHTTPServer(Settings_HTTP_Server_t *p_Settings) +{ + return SettingsManager_Get(p_Settings, &_Settings_Manager_State.Settings.HTTPServer, sizeof(Settings_HTTP_Server_t)); +} + +esp_err_t SettingsManager_UpdateHTTPServer(Settings_HTTP_Server_t *p_Settings, SettingsManager_ChangeNotification_t *p_ChangedSetting) +{ + return SettingsManager_Update(p_Settings, &_Settings_Manager_State.Settings.HTTPServer, + sizeof(Settings_HTTP_Server_t), + SETTINGS_EVENT_HTTP_SERVER_CHANGED, p_ChangedSetting); +} + +esp_err_t SettingsManager_GetVISAServer(Settings_VISA_Server_t *p_Settings) +{ + return SettingsManager_Get(p_Settings, &_Settings_Manager_State.Settings.VISAServer, sizeof(Settings_VISA_Server_t)); +} + +esp_err_t SettingsManager_UpdateVISAServer(Settings_VISA_Server_t *p_Settings, SettingsManager_ChangeNotification_t *p_ChangedSetting) +{ + return SettingsManager_Update(p_Settings, &_Settings_Manager_State.Settings.VISAServer, + sizeof(Settings_VISA_Server_t), + SETTINGS_EVENT_VISA_SERVER_CHANGED, p_ChangedSetting); +} + +esp_err_t SettingsManager_GetSystem(Settings_System_t *p_Settings) +{ + return SettingsManager_Get(p_Settings, &_Settings_Manager_State.Settings.System, sizeof(Settings_System_t)); +} + +esp_err_t SettingsManager_UpdateSystem(Settings_System_t *p_Settings, SettingsManager_ChangeNotification_t *p_ChangedSetting) +{ + return SettingsManager_Update(p_Settings, &_Settings_Manager_State.Settings.System, sizeof(Settings_System_t), + SETTINGS_EVENT_SYSTEM_CHANGED, p_ChangedSetting); +} + +esp_err_t SettingsManager_GetLEDFlash(Settings_LED_Flash_t *p_Settings) +{ + return SettingsManager_Get(p_Settings, &_Settings_Manager_State.Settings.LEDFlash, sizeof(Settings_LED_Flash_t)); +} + +esp_err_t SettingsManager_UpdateLEDFlash(Settings_LED_Flash_t *p_Settings, SettingsManager_ChangeNotification_t *p_ChangedSetting) +{ + return SettingsManager_Update(p_Settings, &_Settings_Manager_State.Settings.LEDFlash, sizeof(Settings_LED_Flash_t), + SETTINGS_EVENT_LED_FLASH_CHANGED, p_ChangedSetting); +} + +esp_err_t SettingsManager_GetUSB(Settings_USB_t *p_Settings) +{ + return SettingsManager_Get(p_Settings, &_Settings_Manager_State.Settings.USB, sizeof(Settings_USB_t)); +} + +esp_err_t SettingsManager_UpdateUSB(Settings_USB_t *p_Settings, SettingsManager_ChangeNotification_t *p_ChangedSetting) +{ + return SettingsManager_Update(p_Settings, &_Settings_Manager_State.Settings.USB, sizeof(Settings_USB_t), + SETTINGS_EVENT_USB_CHANGED, p_ChangedSetting); +} + +esp_err_t SettingsManager_ResetToDefaults(void) +{ + esp_err_t Error; + + if (_Settings_Manager_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGW(TAG, "Resetting settings to factory defaults"); + + xSemaphoreTake(_Settings_Manager_State.Mutex, portMAX_DELAY); + + Error = nvs_erase_key(_Settings_Manager_State.NVS_Handle, "settings"); + if (Error != ESP_OK && Error != ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGE(TAG, "Failed to erase settings: %d!", Error); + + xSemaphoreGive(_Settings_Manager_State.Mutex); + + return Error; + } + + Error = nvs_commit(_Settings_Manager_State.NVS_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to commit erase: %d!", Error); + + xSemaphoreGive(_Settings_Manager_State.Mutex); + + return Error; + } + + /* Reset config_valid flag to allow reloading default config */ + Error = nvs_set_u8(_Settings_Manager_State.NVS_Handle, "config_valid", false); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to set config_valid flag: %d!", Error); + + xSemaphoreGive(_Settings_Manager_State.Mutex); + + return Error; + } + + xSemaphoreGive(_Settings_Manager_State.Mutex); + + /* Reboot the ESP to allow reloading the settings config */ + esp_restart(); + + /* Never reached */ + return ESP_OK; +} + diff --git a/main/Application/Manager/Settings/settingsManager.h b/main/Application/Manager/Settings/settingsManager.h new file mode 100644 index 0000000..64f1b46 --- /dev/null +++ b/main/Application/Manager/Settings/settingsManager.h @@ -0,0 +1,234 @@ +/* + * settingsManager.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Settings Manager definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef SETTINGS_MANAGER_H_ +#define SETTINGS_MANAGER_H_ + +#include +#include + +#include "settingsTypes.h" + +/** @brief Initialize the Settings Manager and load settings from NVS. + * Opens NVS namespace, loads stored settings into RAM, or loads defaults + * if no settings exist. Creates event handlers for settings changes. + * @note Must be called after NVS flash initialization. + * Default settings loaded from JSON or hardcoded fallback. + * Call this before any other SettingsManager functions. + * @warning Not thread-safe during initialization. + * @return ESP_OK on success + * ESP_ERR_NVS_NOT_FOUND if NVS namespace doesn't exist (first boot) + * ESP_ERR_NO_MEM if memory allocation fails + * ESP_ERR_INVALID_STATE if already initialized + * ESP_FAIL if NVS initialization fails + */ +esp_err_t SettingsManager_Init(void); + +/** @brief Deinitialize the Settings Manager. + * Closes NVS handle and frees all resources. Unsaved settings in RAM + * are lost. + * @note Call SettingsManager_Save() first to persist changes. + * @warning All unsaved settings changes are lost permanently. + * @return ESP_OK on success + * ESP_FAIL if NVS close fails + */ +esp_err_t SettingsManager_Deinit(void); + +/** @brief Load all settings from NVS into RAM. + * Reloads settings from NVS, overwriting any unsaved changes in RAM. + * Use this to discard uncommitted changes. + * @note This overwrites all unsaved settings in RAM. + * Version mismatch triggers default settings reload. + * @warning All uncommitted changes are lost! + * @param p_Settings Pointer to settings structure to populate + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Settings is NULL + * ESP_ERR_NVS_INVALID_STATE if config_valid flag is missing or false + * ESP_ERR_NVS_NOT_FOUND if no settings exist in NVS + * ESP_ERR_INVALID_VERSION if version mismatch + * ESP_ERR_INVALID_SIZE if size mismatch (corrupted) + * ESP_FAIL on other NVS errors + */ +esp_err_t SettingsManager_LoadFromNVS(Settings_t *p_Settings); + +/** @brief Save all RAM settings to NVS. + * Writes current settings from RAM to non-volatile storage. Changes + * become permanent and survive power cycles. + * @note Call this after any Update functions to persist changes. + * NVS has limited write cycles (~100k) - avoid excessive saves. + * Posts SETTINGS_EVENT_SAVED event on success. + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not initialized + * ESP_ERR_NVS_NOT_ENOUGH_SPACE if NVS is full + * ESP_FAIL if NVS write fails + */ +esp_err_t SettingsManager_Save(void); + +/** @brief Get the device information from the Settings Manager RAM. + * @param p_Settings Pointer to Info structure to populate + * @return ESP_OK on success, ESP_ERR_* on failure +*/ +esp_err_t SettingsManager_GetInfo(Settings_Info_t *p_Settings); + +/** @brief Get the Lepton settings from the Settings Manager RAM. + * @param p_Settings Pointer to System settings structure to populate + * @return ESP_OK on success, ESP_ERR_* on failure +*/ +esp_err_t SettingsManager_GetLepton(Settings_Lepton_t *p_Settings); + +/** @brief Update Lepton settings in the Settings Manager RAM. + * This function triggers the SETTINGS_EVENT_LEPTON_CHANGED event. + * @param p_Settings Pointer to Lepton settings structure + * @param p_ChangedSetting Optional pointer to structure to receive changed setting ID and value for event data (can be NULL if not needed) + * @return ESP_OK on success, ESP_ERR_* on failure + */ +esp_err_t SettingsManager_UpdateLepton(Settings_Lepton_t *p_Settings, + SettingsManager_ChangeNotification_t *p_ChangedSetting = NULL); + +/** @brief Get the WiFi settings from the Settings Manager RAM. + * @param p_Settings Pointer to WiFi settings structure to populate + * @return ESP_OK on success, ESP_ERR_* on failure +*/ +esp_err_t SettingsManager_GetWiFi(Settings_WiFi_t *p_Settings); + +/** @brief Update WiFi settings in the Settings Manager RAM. + * This function triggers the SETTINGS_EVENT_WIFI_CHANGED event. + * @param p_Settings Pointer to WiFi settings structure + * @param p_ChangedSetting Optional pointer to structure to receive changed setting ID and value for event data (can be NULL if not needed) + * @return ESP_OK on success, ESP_ERR_* on failure + */ +esp_err_t SettingsManager_UpdateWiFi(Settings_WiFi_t *p_Settings, + SettingsManager_ChangeNotification_t *p_ChangedSetting = NULL); + +/** @brief Get the Provisioning settings from the Settings Manager RAM. + * @param p_Settings Pointer to Provisioning settings structure to populate + * @return ESP_OK on success, ESP_ERR_* on failure +*/ +esp_err_t SettingsManager_GetProvisioning(Settings_Provisioning_t *p_Settings); + +/** @brief Update Provisioning settings in the Settings Manager RAM. + * This function triggers the SETTINGS_EVENT_PROVISIONING_CHANGED event. + * @param p_Settings Pointer to Provisioning settings structure + * @param p_ChangedSetting Optional pointer to structure to receive changed setting ID and value for event data (can be NULL if not needed) + * @return ESP_OK on success, ESP_ERR_* on failure + */ +esp_err_t SettingsManager_UpdateProvisioning(Settings_Provisioning_t *p_Settings, + SettingsManager_ChangeNotification_t *p_ChangedSetting = NULL); + +/** @brief Get the Display settings from the Settings Manager RAM. + * @param p_Settings Pointer to Display settings structure to populate + * @return ESP_OK on success, ESP_ERR_* on failure +*/ +esp_err_t SettingsManager_GetDisplay(Settings_Display_t *p_Settings); + +/** @brief Update Display settings in the Settings Manager RAM. + * This function triggers the SETTINGS_EVENT_DISPLAY_CHANGED event. + * @param p_Settings Pointer to Display settings structure + * @param p_ChangedSetting Optional pointer to structure to receive changed setting ID and value for event data (can be NULL if not needed) + * @return ESP_OK on success, ESP_ERR_* on failure + */ +esp_err_t SettingsManager_UpdateDisplay(Settings_Display_t *p_Settings, + SettingsManager_ChangeNotification_t *p_ChangedSetting = NULL); + +/** @brief Get the HTTP Server settings from the Settings Manager RAM. + * @param p_Settings Pointer to HTTP Server settings structure to populate + * @return ESP_OK on success, ESP_ERR_* on failure +*/ +esp_err_t SettingsManager_GetHTTPServer(Settings_HTTP_Server_t *p_Settings); + +/** @brief Update HTTP Server settings in the Settings Manager RAM. + * This function triggers the SETTINGS_EVENT_HTTP_SERVER_CHANGED event. + * @param p_Settings Pointer to HTTP Server settings structure + * @param p_ChangedSetting Optional pointer to structure to receive changed setting ID and value for event data (can be NULL if not needed) + * @return ESP_OK on success, ESP_ERR_* on failure + */ +esp_err_t SettingsManager_UpdateHTTPServer(Settings_HTTP_Server_t *p_Settings, + SettingsManager_ChangeNotification_t *p_ChangedSetting = NULL); + +/** @brief Get the VISA Server settings from the Settings Manager RAM. + * @param p_Settings Pointer to VISA Server settings structure to populate + * @return ESP_OK on success, ESP_ERR_* on failure +*/ +esp_err_t SettingsManager_GetVISAServer(Settings_VISA_Server_t *p_Settings); + +/** @brief Update VISA Server settings in the Settings Manager RAM. + * This function triggers the SETTINGS_EVENT_VISA_SERVER_CHANGED event. + * @param p_Settings Pointer to VISA Server settings structure + * @param p_ChangedSetting Optional pointer to structure to receive changed setting ID and value for event data (can be NULL if not needed) + * @return ESP_OK on success, ESP_ERR_* on failure + */ +esp_err_t SettingsManager_UpdateVISAServer(Settings_VISA_Server_t *p_Settings, + SettingsManager_ChangeNotification_t *p_ChangedSetting = NULL); + +/** @brief Get the System settings from the Settings Manager RAM. + * @param p_Settings Pointer to System settings structure to populate + * @return ESP_OK on success, ESP_ERR_* on failure +*/ +esp_err_t SettingsManager_GetSystem(Settings_System_t *p_Settings); + +/** @brief Update System settings in the Settings Manager RAM. + * This function triggers the SETTINGS_EVENT_SYSTEM_CHANGED event. + * @param p_Settings Pointer to System settings structure + * @param p_ChangedSetting Optional pointer to structure to receive changed setting ID and value for event data (can be NULL if not needed) + * @return ESP_OK on success, ESP_ERR_* on failure + */ +esp_err_t SettingsManager_UpdateSystem(Settings_System_t *p_Settings, + SettingsManager_ChangeNotification_t *p_ChangedSetting = NULL); + +/** @brief Get the LED Flash settings from the Settings Manager RAM. + * @param p_Settings Pointer to LED Flash settings structure to populate + * @return ESP_OK on success, ESP_ERR_* on failure +*/ +esp_err_t SettingsManager_GetLEDFlash(Settings_LED_Flash_t *p_Settings); + +/** @brief Update LED Flash settings in the Settings Manager RAM. + * This function triggers the SETTINGS_EVENT_LED_FLASH_CHANGED event. + * @param p_Settings Pointer to LED Flash settings structure + * @param p_ChangedSetting Optional pointer to structure to receive changed setting ID and value for event data (can be NULL if not needed) + * @return ESP_OK on success, ESP_ERR_* on failure + */ +esp_err_t SettingsManager_UpdateLEDFlash(Settings_LED_Flash_t *p_Settings, + SettingsManager_ChangeNotification_t *p_ChangedSetting = NULL); + +/** @brief Get the USB settings from the Settings Manager RAM. + * @param p_Settings Pointer to USB settings structure to populate + * @return ESP_OK on success, ESP_ERR_* on failure +*/ +esp_err_t SettingsManager_GetUSB(Settings_USB_t *p_Settings); + +/** @brief Update USB settings in the Settings Manager RAM. + * This function triggers the SETTINGS_EVENT_USB_CHANGED event. + * @param p_Settings Pointer to USB settings structure + * @param p_ChangedSetting Optional pointer to structure to receive changed setting ID and value for event data (can be NULL if not needed) + * @return ESP_OK on success, ESP_ERR_* on failure + */ +esp_err_t SettingsManager_UpdateUSB(Settings_USB_t *p_Settings, + SettingsManager_ChangeNotification_t *p_ChangedSetting = NULL); + +/** @brief Reset all settings to factory defaults. + * Erases NVS partition and reloads defaults. + * @return ESP_OK on success + */ +esp_err_t SettingsManager_ResetToDefaults(void); + +#endif /* SETTINGS_MANAGER_H_ */ diff --git a/main/Application/Manager/Settings/settingsTypes.h b/main/Application/Manager/Settings/settingsTypes.h new file mode 100644 index 0000000..2bc5d35 --- /dev/null +++ b/main/Application/Manager/Settings/settingsTypes.h @@ -0,0 +1,247 @@ +/* + * settingsTypes.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Common type definitions for the Settings Manager component. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef SETTINGS_TYPES_H_ +#define SETTINGS_TYPES_H_ + +#include +#include +#include +#include + +#include +#include +#include + +/** @brief Version number for the NVS based settings structure. + * NOTE: Migration isnt suppored yet! + */ +#define SETTINGS_VERSION 1 + +/** @brief Settings Manager events base. + */ +ESP_EVENT_DECLARE_BASE(SETTINGS_EVENTS); + +/** @brief Settings Manager event identifiers. + */ +enum { + SETTINGS_EVENT_LOADED, /**< Settings loaded from NVS. */ + SETTINGS_EVENT_SAVED, /**< Settings saved to NVS. */ + SETTINGS_EVENT_LEPTON_CHANGED, /**< Lepton settings changed. + Data contains SettingsManager_ChangeNotification_t. */ + SETTINGS_EVENT_WIFI_CHANGED, /**< WiFi settings changed. + Data contains SettingsManager_ChangeNotification_t. */ + SETTINGS_EVENT_PROVISIONING_CHANGED, /**< Provisioning settings changed. + Data contains SettingsManager_ChangeNotification_t. */ + SETTINGS_EVENT_DISPLAY_CHANGED, /**< Display settings changed. + Data contains SettingsManager_ChangeNotification_t. */ + SETTINGS_EVENT_HTTP_SERVER_CHANGED, /**< HTTP server settings changed. + Data contains SettingsManager_ChangeNotification_t. */ + SETTINGS_EVENT_VISA_SERVER_CHANGED, /**< VISA server settings changed. + Data contains SettingsManager_ChangeNotification_t. */ + SETTINGS_EVENT_SYSTEM_CHANGED, /**< System settings changed. + Data contains SettingsManager_ChangeNotification_t. */ + SETTINGS_EVENT_LED_FLASH_CHANGED, /**< LED flash settings changed. + Data contains SettingsManager_ChangeNotification_t. */ + SETTINGS_EVENT_USB_CHANGED, /**< USB settings changed. + Data contains SettingsManager_ChangeNotification_t. */ + SETTINGS_EVENT_REQUEST_GET, /**< Request to get current settings. */ + SETTINGS_EVENT_REQUEST_SAVE, /**< Request to save settings to NVS. */ + SETTINGS_EVENT_REQUEST_RESET, /**< Request to reset settings to factory defaults. */ +}; + +/** @brief Settings change identifiers. + */ +enum { + SETTINGS_ID_LEPTON_EMISSIVITY, /**< Emissivity setting changed. + Data contains uint8_t with new emissivity value. */ + SETTINGS_ID_IMAGE_FORMAT, /**< Image format setting changed. + Data contains Settings_Image_Format_t. */ + SETTINGS_ID_WIFI_SSID, /**< WiFi settings changed. + Data must be set to 0. */ + SETTINGS_ID_SNTP_TIMEZONE, /**< SNTP timezone changed. + Data must be set to 0. */ + SETTINGS_ID_LED_FLASH_ENABLE, /**< LED flash enable setting changed. + Data contains bool with new enabled state. */ + SETTINGS_ID_LED_FLASH_POWER, /**< LED flash power setting changed. + Data contains uint8_t with new power value. */ +}; + +/** @brief GUI ROI types. + */ +typedef enum { + ROI_TYPE_SPOTMETER, /**< Spotmeter ROI. */ + ROI_TYPE_SCENE, /**< Scene statistics ROI. */ + ROI_TYPE_AGC, /**< AGC ROI. */ + ROI_TYPE_VIDEO_FOCUS, /**< Video focus ROI. */ +} Settings_ROI_Type_t; + +/** @brief Image format types. + */ +typedef enum { + IMAGE_FORMAT_JPEG = 0, /**< JPEG format. */ + IMAGE_FORMAT_PNG = 1, /**< PNG format. */ + IMAGE_FORMAT_RAW = 2, /**< Raw format. */ + IMAGE_FORMAT_BITMAP = 3, /**< Bitmap (BMP) format. */ +} Settings_Image_Format_t; + +/** @brief Structure to hold the modified settings value. + */ +typedef struct { + uint32_t ID; /**< Identifier for the changed setting. */ + uint32_t Value; /**< New value of the changed setting (can be cast to the appropriate type based on ID). */ +} SettingsManager_ChangeNotification_t; + +/** @brief Emissivity setting definition. + */ +typedef struct { + float Value; /**< Emissivity value (0-100). */ + char Description[32]; /**< Description of the emissivity setting. */ +} Settings_Emissivity_t; + +/** @brief Region of Interest (ROI) rectangle definition (based on Display coordinates). + */ +typedef struct { + Settings_ROI_Type_t Type; /**< ROI type (e.g., spotmeter). */ + uint16_t x; /**< X coordinate of the top-left corner. */ + uint16_t y; /**< Y coordinate of the top-left corner. */ + uint16_t w; /**< Width of the ROI. */ + uint16_t h; /**< Height of the ROI. */ +} Settings_ROI_t; + +/** @brief Device informations. + * NOTE: This structure is not covered by the settings version number because it is not stored in the NVS. + */ +typedef struct { + char FirmwareVersion[16]; /**< Firmware version string, null-terminated. */ + char Manufacturer[32]; /**< Manufacturer string, null-terminated. */ + char Name[32]; /**< Device name string, null-terminated. */ + char Serial[16]; /**< Device serial number string, null-terminated. */ + char SDK[16]; /**< Firmware SDK version string, null-terminated. */ + char Commit[32]; /**< Firmware commit hash string, null-terminated. */ + esp_app_desc_t Bootloader; /**< Bootloader information. */ +} Settings_Info_t; + +/** @brief Lepton camera settings. + * NOTE: This structure is covered by the settings version number because it is stored in the NVS. + */ +typedef struct { + Settings_ROI_t ROI[4]; /**< Camera ROIs. */ + Settings_Emissivity_t EmissivityPresets[128]; /**< Array of emissivity presets. */ + size_t EmissivityPresetsCount; /**< Number of emissivity presets. */ + uint8_t CurrentEmissivity; /**< Currently selected emissivity value in the range from 0 to 100. */ +} __attribute__((packed)) Settings_Lepton_t; + +/** @brief WiFi settings. + * NOTE: This structure is covered by the settings version number because it is stored in the NVS. + */ +typedef struct { + char SSID[33]; /**< WiFi SSID. */ + char Password[65]; /**< WiFi password. */ + bool AutoConnect; /**< Automatically connect to known WiFi networks. */ + uint8_t MaxRetries; /**< Maximum number of connection retries. */ + uint16_t RetryInterval; /**< Interval between connection retries in milliseconds. */ +} __attribute__((packed)) Settings_WiFi_t; + +/** @brief Provisioning settings. + * NOTE: This structure is covered by the settings version number because it is stored in the NVS. + */ +typedef struct { + char Name[32]; /**< Device name for provisioning. */ + uint32_t Timeout; /**< Provisioning timeout in seconds. */ +} __attribute__((packed)) Settings_Provisioning_t; + +/** @brief Display settings. + * NOTE: This structure is covered by the settings version number because it is stored in the NVS. + */ +typedef struct { + uint8_t Brightness; /**< Display brightness (0-100%). */ + uint16_t Timeout; /**< Screen timeout in seconds (0 = never). */ +} __attribute__((packed)) Settings_Display_t; + +/** @brief HTTP server settings. + * NOTE: This structure is covered by the settings version number because it is stored in the NVS. + */ +typedef struct { + uint16_t Port; /**< HTTP server port. */ + uint16_t WSPingIntervalSec; /**< WebSocket ping interval in seconds. */ + uint8_t MaxClients; /**< Maximum number of simultaneous clients. */ + bool useCORS; /**< Whether to enable CORS headers. */ + char APIKey[64]; /**< API key for authentication (null-terminated). */ +} __attribute__((packed)) Settings_HTTP_Server_t; + +/** @brief VISA server settings. + * NOTE: This structure is covered by the settings version number because it is stored in the NVS. + */ +typedef struct { + uint16_t Port; /**< VISA server port. */ + uint16_t Timeout; /**< VISA server socket timeout in milliseconds. */ +} __attribute__((packed)) Settings_VISA_Server_t; + +/** @brief System settings. + * NOTE: This structure is covered by the settings version number because it is stored in the NVS. + */ +typedef struct { + bool SDCard_AutoMount; /**< Automatically mount SD card. */ + char Timezone[32]; /**< Timezone string (e.g., "CET-1CEST,M3.5.0,M10.5.0/3"). */ + char NTPServer[32]; /**< NTP server address. */ + char DeviceName[32]; /**< Device name. */ + Settings_Image_Format_t ImageFormat; /**< Image format for captures (PNG, RAW, JPEG, Bitmap). */ + uint8_t JpegQuality; /**< JPEG compression quality (1-100). */ + uint8_t Reserved[98]; /**< Reserved for future use. */ +} __attribute__((packed)) Settings_System_t; + +/** @brief LED flash settings. + * NOTE: This structure is covered by the settings version number because it is stored in the NVS. + */ +typedef struct { + bool Enable; /**< Whether to enable LED flash when capturing images. */ + uint8_t Power; /**< LED flash power (0-100%). */ +} __attribute__((packed)) Settings_LED_Flash_t; + +/** @brief USB settings. + * NOTE: This structure is covered by the settings version number because it is stored in the NVS. + */ +typedef struct { + bool MSC_Enabled; /**< Enable USB Mass Storage Class mode. */ + bool UVC_Enabled; /**< Enable USB Video Class (UVC) mode. */ + bool CDC_Enabled; /**< Enable USB Communication Device Class (CDC-ACM) mode. */ + uint8_t Reserved[5]; /**< Reserved for future use. */ +} __attribute__((packed)) Settings_USB_t; + +/** @brief Complete application settings structure. + */ +typedef struct { + uint32_t Version; /**< Settings version number. */ + Settings_Lepton_t Lepton; /**< Lepton camera settings. */ + Settings_WiFi_t WiFi; /**< WiFi settings. */ + Settings_Provisioning_t Provisioning; /**< Provisioning settings. */ + Settings_Display_t Display; /**< Display settings. */ + Settings_HTTP_Server_t HTTPServer; /**< HTTP server settings. */ + Settings_VISA_Server_t VISAServer; /**< VISA server settings. */ + Settings_System_t System; /**< System settings. */ + Settings_LED_Flash_t LEDFlash; /**< LED flash settings. */ + Settings_USB_t USB; /**< USB settings. */ +} Settings_t; + +#endif /* SETTINGS_TYPES_H_ */ diff --git a/main/Application/Manager/Time/timeManager.cpp b/main/Application/Manager/Time/timeManager.cpp new file mode 100644 index 0000000..badc0fc --- /dev/null +++ b/main/Application/Manager/Time/timeManager.cpp @@ -0,0 +1,398 @@ +/* + * timeManager.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Time Manager implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include + +#include + +#include "timeManager.h" +#include "../Devices/devicesManager.h" + +ESP_EVENT_DEFINE_BASE(TIME_EVENTS); + +#define TIME_MANAGER_SYNC_INTERVAL_SEC 3600 /* Sync every hour */ +#define TIME_MANAGER_RTC_BACKUP_INTERVAL_SEC 300 /* Save to RTC every 5 minutes */ +#define TIME_MANAGER_VALID_YEAR_MIN 2025 /* Minimum valid year */ + +typedef struct { + bool isInitialized; + bool hasRTC; + bool hasNetwork; + bool timeSynchronized; + TimeManager_Source_t activeSource; + time_t lastSNTP_Sync; + time_t lastRTC_Sync; + time_t lastRTC_Backup; + uint32_t sntpSyncCount; + uint32_t rtcSyncCount; + esp_timer_handle_t syncTimer; +} TimeManager_State_t; + +static TimeManager_State_t _TimeManager_State; + +static const char *TAG = "Time-Manager"; + +/** @brief SNTP time synchronization notification callback. + * @param tv Pointer to the synchronized time value + */ +static void TimeManager_SNTP_Sync_Callback(struct timeval *tv) +{ + time_t Now = tv->tv_sec; + struct tm Timeinfo; + + localtime_r(&Now, &Timeinfo); + + ESP_LOGD(TAG, "SNTP time synchronized: %04d-%02d-%02d %02d:%02d:%02d", + Timeinfo.tm_year + 1900, Timeinfo.tm_mon + 1, Timeinfo.tm_mday, + Timeinfo.tm_hour, Timeinfo.tm_min, Timeinfo.tm_sec); + + _TimeManager_State.lastSNTP_Sync = Now; + _TimeManager_State.activeSource = TIME_SOURCE_SNTP; + _TimeManager_State.timeSynchronized = true; + _TimeManager_State.sntpSyncCount++; + + /* Post time synchronized event */ + esp_event_post(TIME_EVENTS, TIME_EVENT_SYNCHRONIZED, &Timeinfo, sizeof(struct tm), portMAX_DELAY); + + /* Backup time to RTC if available */ + if (_TimeManager_State.hasRTC) { + esp_err_t Error; + + Error = DevicesManager_SetTime(&Timeinfo); + if (Error == ESP_OK) { + ESP_LOGD(TAG, "Time backed up to RTC"); + + _TimeManager_State.lastRTC_Backup = Now; + } else { + ESP_LOGW(TAG, "Failed to backup time to RTC: %d!", Error); + } + } +} + +/** @brief Periodic timer callback for time synchronization. + * @param p_Arg Timer argument + */ +static void TimeManager_Sync_Timer_Callback(void *p_Arg) +{ + time_t Now; + + time(&Now); + + /* If network is available and SNTP sync is due */ + if (_TimeManager_State.hasNetwork) { + time_t TimeSinceSync; + + TimeSinceSync = Now - _TimeManager_State.lastSNTP_Sync; + + if (TimeSinceSync >= TIME_MANAGER_SYNC_INTERVAL_SEC) { + ESP_LOGD(TAG, "Periodic SNTP synchronization (last sync: %ld sec ago)", TimeSinceSync); + + /* Restart SNTP to trigger new sync */ + esp_sntp_restart(); + } + + /* Backup system time to RTC periodically */ + if (_TimeManager_State.hasRTC && _TimeManager_State.timeSynchronized) { + time_t TimeSinceBackup = Now - _TimeManager_State.lastRTC_Backup; + + if (TimeSinceBackup >= TIME_MANAGER_RTC_BACKUP_INTERVAL_SEC) { + struct tm Timeinfo; + localtime_r(&Now, &Timeinfo); + + if (DevicesManager_SetTime(&Timeinfo) == ESP_OK) { + ESP_LOGD(TAG, "Periodic RTC backup"); + + _TimeManager_State.lastRTC_Backup = Now; + } + } + } + } +} + +esp_err_t TimeManager_Init(void *p_RTC_Handle) +{ + esp_err_t Error; + struct tm RtcTime; + + if (_TimeManager_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + ESP_LOGD(TAG, "Initializing Time Manager"); + + memset(&_TimeManager_State, 0, sizeof(TimeManager_State_t)); + + _TimeManager_State.hasRTC = true; + ESP_LOGD(TAG, "RTC available for time backup"); + + /* Try to load time from RTC */ + Error = DevicesManager_GetTime(&RtcTime); + if (Error == ESP_OK) { + /* Validate RTC time (check if year is reasonable) */ + if ((RtcTime.tm_year + 1900) >= TIME_MANAGER_VALID_YEAR_MIN) { + /* Set system time from RTC */ + time_t T = mktime(&RtcTime); + struct timeval Tv = {.tv_sec = T, .tv_usec = 0}; + + settimeofday(&Tv, NULL); + + _TimeManager_State.activeSource = TIME_SOURCE_RTC; + _TimeManager_State.timeSynchronized = true; + _TimeManager_State.lastRTC_Sync = T; + _TimeManager_State.rtcSyncCount++; + + ESP_LOGD(TAG, "System time initialized from RTC: %04d-%02d-%02d %02d:%02d:%02d", + RtcTime.tm_year + 1900, RtcTime.tm_mon + 1, RtcTime.tm_mday, + RtcTime.tm_hour, RtcTime.tm_min, RtcTime.tm_sec); + } else { + ESP_LOGW(TAG, "RTC time invalid (year: %d), waiting for SNTP", + RtcTime.tm_year + 1900); + } + } else { + ESP_LOGW(TAG, "Failed to read time from RTC: %d!", Error); + } + + /* Set timezone to UTC by default */ + setenv("TZ", "UTC-0", 1); + tzset(); + + /* Create periodic sync timer */ + const esp_timer_create_args_t TimerArgs = { + .callback = &TimeManager_Sync_Timer_Callback, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "time_sync", + .skip_unhandled_events = false, + }; + + Error = esp_timer_create(&TimerArgs, &_TimeManager_State.syncTimer); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to create sync timer: %d!", Error); + + return Error; + } + + /* Start timer with 60 second period */ + Error = esp_timer_start_periodic(_TimeManager_State.syncTimer, 60 * 1000000ULL); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to start sync timer: %d!", Error); + + esp_timer_delete(_TimeManager_State.syncTimer); + + return Error; + } + + _TimeManager_State.isInitialized = true; + + ESP_LOGD(TAG, "Time Manager initialized (Source: %s)", + _TimeManager_State.activeSource == TIME_SOURCE_RTC ? "RTC" : + _TimeManager_State.activeSource == TIME_SOURCE_SNTP ? "SNTP" : "None"); + + return ESP_OK; +} + +esp_err_t TimeManager_Deinit(void) +{ + if (_TimeManager_State.isInitialized == false) { + return ESP_OK; + } + + if (_TimeManager_State.syncTimer != NULL) { + esp_timer_stop(_TimeManager_State.syncTimer); + esp_timer_delete(_TimeManager_State.syncTimer); + _TimeManager_State.syncTimer = NULL; + } + + if (esp_sntp_enabled()) { + esp_sntp_stop(); + } + + _TimeManager_State.isInitialized = false; + + ESP_LOGD(TAG, "Time Manager deinitialized"); + + return ESP_OK; +} + +esp_err_t TimeManager_OnNetworkConnected(void) +{ + ESP_LOGD(TAG, "Network connected, starting SNTP synchronization"); + + _TimeManager_State.hasNetwork = true; + + /* Initialize SNTP if not already done */ + if (esp_sntp_enabled()) { + /* Restart SNTP to immediately try to sync time */ + esp_sntp_restart(); + } else { + ESP_LOGD(TAG, "SNTP initialized"); + + /* First time init */ + esp_sntp_setoperatingmode(SNTP_OPMODE_POLL); + esp_sntp_setservername(0, "pool.ntp.org"); + sntp_set_time_sync_notification_cb(TimeManager_SNTP_Sync_Callback); + esp_sntp_init(); + } + + return ESP_OK; +} + +esp_err_t TimeManager_OnNetworkDisconnected(void) +{ + ESP_LOGD(TAG, "Network disconnected, switching to RTC time source"); + + _TimeManager_State.hasNetwork = false; + + /* Switch to RTC if available */ + if (_TimeManager_State.hasRTC) { + TimeManager_Source_t OldSource = _TimeManager_State.activeSource; + _TimeManager_State.activeSource = TIME_SOURCE_RTC; + + /* Post source changed event if source actually changed */ + if (OldSource != TIME_SOURCE_RTC) { + esp_event_post(TIME_EVENTS, TIME_EVENT_SOURCE_CHANGED, + &_TimeManager_State.activeSource, + sizeof(TimeManager_Source_t), portMAX_DELAY); + } + + ESP_LOGD(TAG, "Now using RTC as time source"); + } else { + ESP_LOGW(TAG, "No RTC available, system time will drift"); + _TimeManager_State.activeSource = TIME_SOURCE_SYSTEM; + } + + return ESP_OK; +} + +esp_err_t TimeManager_GetTime(struct tm *p_Time, TimeManager_Source_t *p_Source) +{ + time_t Now; + + if (p_Time == NULL) { + return ESP_ERR_INVALID_ARG; + } + + time(&Now); + + localtime_r(&Now, p_Time); + + if (p_Source != NULL) { + *p_Source = _TimeManager_State.activeSource; + } + + return ESP_OK; +} + +esp_err_t TimeManager_GetTimestamp(time_t *p_Time, TimeManager_Source_t *p_Source) +{ + if (p_Time == NULL) { + return ESP_ERR_INVALID_ARG; + } + + time(p_Time); + + if (p_Source != NULL) { + *p_Source = _TimeManager_State.activeSource; + } + + return ESP_OK; +} + +esp_err_t TimeManager_GetStatus(TimeManager_Status_t *p_Status) +{ + if (p_Status == NULL) { + return ESP_ERR_INVALID_ARG; + } + + p_Status->ActiveSource = _TimeManager_State.activeSource; + p_Status->SNTP_Available = _TimeManager_State.hasNetwork; + p_Status->RTC_Available = _TimeManager_State.hasRTC; + p_Status->LastSync_SNTP = _TimeManager_State.lastSNTP_Sync; + p_Status->LastSync_RTC = _TimeManager_State.lastRTC_Sync; + p_Status->SNTP_SyncCount = _TimeManager_State.sntpSyncCount; + p_Status->RTC_SyncCount = _TimeManager_State.rtcSyncCount; + + return ESP_OK; +} + +esp_err_t TimeManager_ForceSync(void) +{ + if (_TimeManager_State.hasNetwork == false) { + ESP_LOGW(TAG, "Cannot force sync: no network connection"); + + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGD(TAG, "Forcing SNTP synchronization"); + + esp_sntp_restart(); + + return ESP_OK; +} + +bool TimeManager_IsTimeSynchronized(void) +{ + return _TimeManager_State.timeSynchronized; +} + +esp_err_t TimeManager_GetTimeString(char *p_Buffer, size_t Size, const char *Format) +{ + esp_err_t Error; + struct tm Timeinfo; + size_t Written; + + if ((p_Buffer == NULL) || (Format == NULL) || (Size == 0)) { + return ESP_ERR_INVALID_ARG; + } + + Error = TimeManager_GetTime(&Timeinfo, NULL); + if (Error != ESP_OK) { + return Error; + } + + Written = strftime(p_Buffer, Size, Format, &Timeinfo); + if (Written == 0) { + return ESP_ERR_NO_MEM; + } + + return ESP_OK; +} + +esp_err_t TimeManager_SetTimezone(const char *p_Timezone) +{ + if (p_Timezone == NULL) { + return ESP_ERR_INVALID_ARG; + } + + ESP_LOGD(TAG, "Setting timezone to: %s", p_Timezone); + + setenv("TZ", p_Timezone, 1); + tzset(); + + return ESP_OK; +} \ No newline at end of file diff --git a/main/Application/Manager/Time/timeManager.h b/main/Application/Manager/Time/timeManager.h new file mode 100644 index 0000000..c294d8d --- /dev/null +++ b/main/Application/Manager/Time/timeManager.h @@ -0,0 +1,185 @@ +/* + * timeManager.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Time Manager definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef TIME_MANAGER_H_ +#define TIME_MANAGER_H_ + +#include "timeTypes.h" + +/** @brief Time source types. + */ +typedef enum { + TIME_SOURCE_NONE = 0, /**< No time source available. */ + TIME_SOURCE_RTC, /**< Time from RTC. */ + TIME_SOURCE_SNTP, /**< Time from SNTP. */ + TIME_SOURCE_SYSTEM, /**< Time from system (not synchronized). */ +} TimeManager_Source_t; + +/** @brief Time synchronization status. + */ +typedef struct { + TimeManager_Source_t ActiveSource; /**< Currently active time source. */ + bool SNTP_Available; /**< SNTP available (network connected). */ + bool RTC_Available; /**< RTC available. */ + time_t LastSync_SNTP; /**< Timestamp of last SNTP sync. */ + time_t LastSync_RTC; /**< Timestamp of last RTC sync. */ + uint32_t SNTP_SyncCount; /**< Number of successful SNTP syncs. */ + uint32_t RTC_SyncCount; /**< Number of RTC reads. */ +} TimeManager_Status_t; + +/** @brief Initialize the Time Manager. + * Initializes time management subsystem with RTC as backup time source + * and prepares SNTP synchronization (starts when network connected). + * @note Call this after DevicesManager_Init() for RTC support. + * SNTP starts automatically when network connects. + * Default timezone is UTC - use TimeManager_SetTimezone(). + * @param p_RTC_Handle Pointer to RTC device handle (NULL if RTC not available) + * @return ESP_OK on success + * ESP_ERR_NO_MEM if memory allocation fails + * ESP_FAIL if initialization fails + */ +esp_err_t TimeManager_Init(void *p_RTC_Handle); + +/** @brief Deinitialize the Time Manager. + * Stops SNTP synchronization and frees all allocated resources. + * @note Time functions become unavailable after this. + * System time reverts to 1970-01-01 00:00:00 UTC. + * @return ESP_OK on success + * ESP_FAIL if cleanup fails + */ +esp_err_t TimeManager_Deinit(void); + +/** @brief Called when network connection is established. + * Starts SNTP synchronization to obtain accurate time from internet. + * Time is automatically synced to system clock and RTC. + * @note This is called automatically by NetworkManager. + * SNTP sync happens periodically (typically every hour). + * First sync may take a few seconds. + * @return ESP_OK on success + * ESP_FAIL if SNTP start fails + */ +esp_err_t TimeManager_OnNetworkConnected(void); + +/** @brief Called when network connection is lost. + * Stops SNTP synchronization and switches to RTC as backup time source. + * System time continues from last synchronized value. + * @note This is called automatically by NetworkManager. + * RTC maintains time accuracy (typical drift: ±20ppm). + * @return ESP_OK on success + * ESP_FAIL if operation fails + */ +esp_err_t TimeManager_OnNetworkDisconnected(void); + +/** @brief Get the current time from best available source. + * Returns current time from the most reliable source available: + * Priority: SNTP (if connected) > RTC > System clock + * @note Time is in configured timezone (default: UTC). + * p_Source helps determine time reliability. + * @param p_Time Pointer to tm structure to store the time + * @param p_Source Optional pointer to store the time source used (can be NULL) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Time is NULL + * ESP_ERR_INVALID_STATE if no time source available + */ +esp_err_t TimeManager_GetTime(struct tm *p_Time, TimeManager_Source_t *p_Source); + +/** @brief Get the current time as UNIX timestamp. + * Returns seconds since 1970-01-01 00:00:00 UTC (UNIX epoch). + * @note Timestamp is always in UTC regardless of timezone setting. + * Useful for time calculations and comparisons. + * @param p_Time Pointer to store the timestamp (time_t) + * @param p_Source Optional pointer to store the time source used (can be NULL) + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Time is NULL + * ESP_ERR_INVALID_STATE if no time source available + */ +esp_err_t TimeManager_GetTimestamp(time_t *p_Time, TimeManager_Source_t *p_Source); + +/** @brief Get the time manager status and statistics. + * Returns detailed status including active source, availability of + * SNTP/RTC, last sync times, and sync counters. + * @note Useful for diagnostics and status display. + * Shows which time sources are available and when last synced. + * @param p_Status Pointer to TimeManager_Status_t structure to populate + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Status is NULL + */ +esp_err_t TimeManager_GetStatus(TimeManager_Status_t *p_Status); + +/** @brief Force a time synchronization from SNTP. + * Immediately triggers an SNTP query to synchronize system time. + * Only works when network is connected. + * @note Sync happens asynchronously - may take several seconds. + * System time is updated automatically when sync completes. + * RTC is also updated with synchronized time. + * @return ESP_OK if sync request initiated + * ESP_ERR_INVALID_STATE if network not connected + * ESP_FAIL if SNTP not available + */ +esp_err_t TimeManager_ForceSync(void); + +/** @brief Check if time is synchronized and reliable. + * Returns true if time has been synchronized from SNTP or is being + * maintained by RTC. False if only system clock (unreliable). + * @note Use this to determine if timestamps can be trusted. + * Time is reliable after first SNTP sync or RTC read. + * @return true if time is synchronized from SNTP or RTC + * false if time is from unsynchronized system clock + */ +bool TimeManager_IsTimeSynchronized(void); + +/** @brief Format the current time as string. + * Formats current time using strftime() format string. Useful for + * display and logging purposes. + * @note Time is formatted in configured timezone. + * Common formats: ISO 8601: "%Y-%m-%dT%H:%M:%S%z" + * @param p_Buffer Buffer to store the formatted time string + * @param Size Buffer size in bytes (including null terminator) + * @param Format Time format string in strftime() format + * Examples: "%Y-%m-%d %H:%M:%S", "%A, %B %d, %Y" + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Buffer or Format is NULL + * ESP_ERR_INVALID_ARG if Size is 0 + * ESP_ERR_INVALID_STATE if no time source available + */ +esp_err_t TimeManager_GetTimeString(char *p_Buffer, size_t Size, const char *Format); + +/** @brief Set the timezone for time display. + * Configures the timezone used for local time display. System time + * is stored in UTC internally and converted for display. + * @note Format: std offset [dst [offset],start[/time],end[/time]] + * Changes take effect immediately for all time queries. + * Default timezone is UTC if not set. + * @param p_Timezone Timezone string in POSIX format + * Examples: + * - "UTC+0" or "UTC-0" for UTC + * - "CET-1CEST,M3.5.0,M10.5.0/3" for Central European Time + * - "EST5EDT,M3.2.0,M11.1.0" for US Eastern Time + * - "JST-9" for Japan Standard Time + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Timezone is NULL + * ESP_FAIL if timezone string is invalid + */ +esp_err_t TimeManager_SetTimezone(const char *p_Timezone); + +#endif /* TIME_MANAGER_H_ */ diff --git a/main/Application/Manager/Time/timeTypes.h b/main/Application/Manager/Time/timeTypes.h new file mode 100644 index 0000000..e2edf03 --- /dev/null +++ b/main/Application/Manager/Time/timeTypes.h @@ -0,0 +1,48 @@ +/* + * timeTypes.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Common type definitions for the Time Manager component. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef TIME_TYPES_H_ +#define TIME_TYPES_H_ + +#include +#include + +#include + +#include +#include +#include + +/** @brief Time Manager events base. + */ +ESP_EVENT_DECLARE_BASE(TIME_EVENTS); + +/** @brief Time Manager event IDs. + */ +typedef enum { + TIME_EVENT_SYNCHRONIZED, /**< Time synchronized from SNTP */ + TIME_EVENT_SOURCE_CHANGED, /**< Time source changed (SNTP/RTC/System) + Data is of type struct tm */ +} Time_Event_ID_t; + +#endif /* TIME_TYPES_H_ */ diff --git a/main/Application/Manager/USB/CDC/usbCDC.cpp b/main/Application/Manager/USB/CDC/usbCDC.cpp new file mode 100644 index 0000000..ff13a4c --- /dev/null +++ b/main/Application/Manager/USB/CDC/usbCDC.cpp @@ -0,0 +1,176 @@ +/* + * usbCDC.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: USB CDC-ACM module implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include + +#include + +#include + +#include "usbCDC.h" +#include "Application/Manager/USB/usbManager.h" + +#include + +static const char *TAG = "USB-CDC"; + +/** @brief CDC module internal state. + */ +typedef struct { + bool isInitialized; /**< Module initialization state. */ + bool isConnected; /**< Host terminal is connected (DTR+RTS active). */ +} USB_CDC_State_t; + +static USB_CDC_State_t _CDC_State; + +/** @brief Callback for CDC line state changes (connect / disconnect events). + * @param itf Interface number + * @param p_Event Pointer to CDC event + */ +static void on_CDC_LineStateChanged(int itf, cdcacm_event_t *p_Event) +{ + esp_err_t Error; + bool Connected = p_Event->line_state_changed_data.dtr && p_Event->line_state_changed_data.rts; + + if (Connected == _CDC_State.isConnected) { + return; + } + + _CDC_State.isConnected = Connected; + + if (Connected) { + ESP_LOGD(TAG, "CDC host terminal connected on interface %d", itf); + + Error = esp_event_post(USB_EVENTS, USB_EVENT_CDC_CONNECTED, NULL, 0, pdMS_TO_TICKS(100)); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to post CDC connected event: %d", Error); + } + } else { + ESP_LOGD(TAG, "CDC host terminal disconnected on interface %d", itf); + + Error = esp_event_post(USB_EVENTS, USB_EVENT_CDC_DISCONNECTED, NULL, 0, pdMS_TO_TICKS(100)); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to post CDC disconnected event: %d", Error); + } + } +} + +esp_err_t USBCDC_Init(const USB_CDC_Config_t *p_Config) +{ + esp_err_t Error; + + if (p_Config == NULL) { + ESP_LOGE(TAG, "Invalid configuration pointer!"); + + return ESP_ERR_INVALID_ARG; + } else if (_CDC_State.isInitialized) { + ESP_LOGW(TAG, "CDC already initialized!"); + + return ESP_ERR_INVALID_STATE; + } + + memset(&_CDC_State, 0, sizeof(USB_CDC_State_t)); + + tinyusb_config_cdcacm_t CDC_Config = { + .cdc_port = TINYUSB_CDC_ACM_0, + .callback_rx = NULL, + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = on_CDC_LineStateChanged, + .callback_line_coding_changed = NULL, + }; + + Error = tinyusb_cdcacm_init(&CDC_Config); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize TinyUSB CDC-ACM: %d!", Error); + + return Error; + } + + _CDC_State.isInitialized = true; + + ESP_LOGD(TAG, "CDC-ACM initialized"); + + return ESP_OK; +} + +esp_err_t USBCDC_Deinit(void) +{ + esp_err_t Error; + + if (_CDC_State.isInitialized == false) { + ESP_LOGW(TAG, "CDC not initialized!"); + + return ESP_ERR_INVALID_STATE; + } + + Error = tinyusb_cdcacm_deinit(TINYUSB_CDC_ACM_0); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to deinitialize TinyUSB CDC-ACM: %d!", Error); + } + + memset(&_CDC_State, 0, sizeof(USB_CDC_State_t)); + + ESP_LOGD(TAG, "CDC-ACM deinitialized"); + + return ESP_OK; +} + +esp_err_t USBCDC_Write(const uint8_t *p_Data, size_t Size) +{ + esp_err_t Error; + + if (p_Data == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (Size == 0) { + return ESP_ERR_INVALID_ARG; + } else if (_CDC_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (_CDC_State.isConnected == false) { + return ESP_ERR_INVALID_STATE; + } + + if (tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, p_Data, Size) == 0) { + ESP_LOGW(TAG, "Write queue full - data dropped!"); + + return ESP_FAIL; + } + + /* Non-blocking flush (timeout = 0) */ + Error = tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0, 0); + if (Error != ESP_OK && Error != ESP_ERR_TIMEOUT) { + ESP_LOGW(TAG, "CDC flush failed: %d", Error); + } + + return ESP_OK; +} + +bool USBCDC_IsInitialized(void) +{ + return _CDC_State.isInitialized; +} + +bool USBCDC_IsConnected(void) +{ + return _CDC_State.isInitialized && _CDC_State.isConnected; +} diff --git a/main/Application/Manager/USB/CDC/usbCDC.h b/main/Application/Manager/USB/CDC/usbCDC.h new file mode 100644 index 0000000..3cf003d --- /dev/null +++ b/main/Application/Manager/USB/CDC/usbCDC.h @@ -0,0 +1,78 @@ +/* + * usbCDC.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: USB CDC-ACM module definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef USB_CDC_H_ +#define USB_CDC_H_ + +#include "usbCDCTypes.h" + +/** @brief Initialize the USB CDC-ACM module. + * Registers line-state and receive callbacks with TinyUSB CDC-ACM + * and enables the virtual serial port on the host. + * @note Must be called after TinyUSB driver is installed. + * Requires CONFIG_TINYUSB_CDC_ENABLED=y in sdkconfig. + * Posts USB_EVENT_CDC_CONNECTED / USB_EVENT_CDC_DISCONNECTED events + * when the host terminal connects or disconnects. + * @warning Not thread-safe. Call once during USB initialization. + * @param p_Config Pointer to CDC configuration structure + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Config is NULL + * ESP_ERR_INVALID_STATE if already initialized + * ESP_FAIL if TinyUSB CDC-ACM initialization fails + */ +esp_err_t USBCDC_Init(const USB_CDC_Config_t *p_Config); + +/** @brief Deinitialize the USB CDC-ACM module. + * Unregisters all callbacks and frees internal state. + * @note Safe to call even if not streaming. + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not initialized + */ +esp_err_t USBCDC_Deinit(void); + +/** @brief Write data to the CDC transmit buffer and flush to the host. + * Data is queued in TinyUSB's internal write buffer and immediately flushed. + * @note Non-blocking on flush (timeout = 0). Drops data if host is not connected. + * This function is NOT safe to call from ISR context. + * @param p_Data Pointer to data buffer to transmit + * @param Size Number of bytes to transmit + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Data is NULL or Size is 0 + * ESP_ERR_INVALID_STATE if not initialized or host not connected + */ +esp_err_t USBCDC_Write(const uint8_t *p_Data, size_t Size); + +/** @brief Check whether the CDC module is initialized. + * @return true if initialized + * false if not initialized + */ +bool USBCDC_IsInitialized(void); + +/** @brief Check whether a host terminal is currently connected (DTR+RTS set). + * @note Thread-safe. + * @return true if host terminal is connected + * false if not connected or not initialized + */ +bool USBCDC_IsConnected(void); + +#endif /* USB_CDC_H_ */ diff --git a/main/Application/Manager/USB/CDC/usbCDCTypes.h b/main/Application/Manager/USB/CDC/usbCDCTypes.h new file mode 100644 index 0000000..de3cedc --- /dev/null +++ b/main/Application/Manager/USB/CDC/usbCDCTypes.h @@ -0,0 +1,37 @@ +/* + * usbCDCTypes.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: USB CDC-ACM type definitions. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef USB_CDC_TYPES_H_ +#define USB_CDC_TYPES_H_ + +#include + +#include + +/** @brief USB CDC-ACM configuration structure. + */ +typedef struct { + uint8_t Reserved; /**< Reserved for future configuration options. */ +} USB_CDC_Config_t; + +#endif /* USB_CDC_TYPES_H_ */ diff --git a/main/Application/Manager/USB/Descriptors/descriptors.cpp b/main/Application/Manager/USB/Descriptors/descriptors.cpp new file mode 100644 index 0000000..6256710 --- /dev/null +++ b/main/Application/Manager/USB/Descriptors/descriptors.cpp @@ -0,0 +1,207 @@ +#include +#include + +#include "descriptors.h" + +#define USB_BCD_DEVICE(Major, Minor) ((((Major) / 10) << 12) | (((Major) % 10) << 8) | (((Minor) / 10) << 4) | ((Minor) % 10)) + +/* MSC Endpoint numbers */ +#define EPNUM_MSC_OUT 0x01 +#define EPNUM_MSC_IN 0x81 + +/* CDC Endpoint numbers */ +#define EPNUM_CDC_OUT 0x02 +#define EPNUM_CDC_NOTIFY 0x82 +#define EPNUM_CDC_IN 0x83 + +/* UVC Endpoint numbers */ +#define EPNUM_UVC_VIDEO_IN 0x84 + +/** @brief UVC MJPEG Video Capture Descriptor + * + * +------------------------------+-------+ + * | Descriptor | Bytes | + * +------------------------------+-------+ + * | IAD (Interface Association) | 8 | + * | VideoControl Interface | 9 | + * | Class-Specific VC Header | 13 | + * | Camera Terminal | 18 | + * | Output Terminal | 9 | + * | VideoStreaming Interface Alt0| 9 | + * | VS Input Header | 13 | + * | MJPEG Format Descriptor | 11 | + * | MJPEG Frame Descriptor | 38 | + * | Color Matching Descriptor | 6 | + * | VideoStreaming Interface Alt1| 9 | + * | Isochronous Endpoint | 7 | + * +------------------------------+-------+ + * | Total | 150 | + */ +#define TUD_UVC_DESC_LEN ( \ + TUD_VIDEO_DESC_IAD_LEN \ + /* Control */ \ + + TUD_VIDEO_DESC_STD_VC_LEN \ + + TUD_VIDEO_DESC_CS_VC_LEN \ + /* bInCollection */ \ + + 1 \ + + TUD_VIDEO_DESC_CAMERA_TERM_LEN \ + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN \ + /* Interface 1, Alternate 0 */ \ + + TUD_VIDEO_DESC_STD_VS_LEN \ + /* bNumFormats x bControlSize */ \ + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1) \ + + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN \ + + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN \ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN \ + /* Interface 1, Alternate 1 */ \ + + TUD_VIDEO_DESC_STD_VS_LEN \ + /* Endpoint */ \ + + 7 \ +) + +/** + * @brief UVC MJPEG Video Capture Descriptor Macro + * @param _itf Interface number (will use _itf and _itf + 1) + * @param _stridx String descriptor index + * @param _epin Endpoint IN address (e.g., 0x81 for EP1 IN) + * @param _width Frame width in pixels + * @param _height Frame height in pixels + * @param _fps Frame rate in frames per second + * @param _epsize Maximum packet size for isochronous endpoint + */ +#define TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG(_itf, _stridx, _epin, _width, _height, _fps, _epsize) \ + /* Interface Association Descriptor (IAD) */ \ + TUD_VIDEO_DESC_IAD(_itf, 0x02, _stridx), \ + \ + /* Video Control Interface (Interface _itf, Alternate 0) */ \ + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ + /* Class-Specific VC Interface Header */ \ + TUD_VIDEO_DESC_CS_VC( \ + 0x0150, /* bcdUVC: UVC 1.5 */ \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, /* wTotalLength */ \ + 27000000, /* dwClockFrequency: 27 MHz */ \ + _itf + 1), /* baInterfaceNr: VideoStreaming interface */ \ + /* Input Terminal Descriptor (Camera) */ \ + TUD_VIDEO_DESC_CAMERA_TERM( \ + 0x01, /* bTerminalID */ \ + 0, /* wTerminalType: Vendor specific (0x0000) */ \ + 0, /* bAssocTerminal: No association */ \ + 0, /* wObjectiveFocalLengthMin */ \ + 0, /* wObjectiveFocalLengthMax */ \ + 0, /* wObjectiveFocalLength */ \ + 0), /* bmControls: No controls */ \ + /* Output Terminal Descriptor */ \ + TUD_VIDEO_DESC_OUTPUT_TERM( \ + 0x02, /* bTerminalID */ \ + VIDEO_TT_STREAMING, /* wTerminalType: USB streaming */ \ + 0, /* bAssocTerminal: No association */ \ + 1, /* bSourceID: Connected to Input Terminal 1 */ \ + 0), /* iTerminal: No string descriptor */ \ + \ + /* Video Streaming Interface (Interface _itf+1, Alternate 0 - Zero Bandwidth) */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 0, 0, _stridx), \ + /* Class-Specific VS Interface Input Header */ \ + TUD_VIDEO_DESC_CS_VS_INPUT( \ + 1, /* bNumFormats: 1 format (MJPEG) */ \ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN \ + + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN \ + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN, /* wTotalLength */ \ + _epin, /* bEndpointAddress */ \ + 0, /* bmInfo: No dynamic format change */ \ + 0x02, /* bTerminalLink: Connected to Output Terminal 2 */ \ + 0, /* bStillCaptureMethod: No still capture */ \ + 0, /* bTriggerSupport: No hardware trigger */ \ + 0, /* bTriggerUsage */ \ + 0), /* bmaControls: No per-format controls */ \ + /* MJPEG Format Descriptor */ \ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG( \ + 1, /* bFormatIndex */ \ + 1, /* bNumFrameDescriptors: 1 frame resolution */ \ + 0, /* bmFlags: Fixed size samples */ \ + 1, /* bDefaultFrameIndex */ \ + 0, /* bAspectRatioX: Not specified */ \ + 0, /* bAspectRatioY: Not specified */ \ + 0, /* bmInterlaceFlags: Non-interlaced */ \ + 0), /* bCopyProtect: No restrictions */ \ + /* MJPEG Frame Descriptor */ \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT( \ + 1, /* bFrameIndex */ \ + 0, /* bmCapabilities: Still image not supported */ \ + _width, /* wWidth: Frame width */ \ + _height, /* wHeight: Frame height */ \ + _width * _height * 16, /* dwMinBitRate: bits/sec */ \ + _width * _height * 16 * _fps, /* dwMaxBitRate: bits/sec */ \ + _width * _height * 16 / 8, /* dwMaxVideoFrameBufferSize: bytes */ \ + (10000000 / _fps), /* dwDefaultFrameInterval: 100ns units */ \ + (10000000 / _fps), /* dwMinFrameInterval: 100ns units */ \ + (10000000 / _fps) * _fps, /* dwMaxFrameInterval: 100ns units */ \ + (10000000 / _fps)), /* dwFrameIntervalStep: 100ns units */ \ + /* Color Matching Descriptor */ \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING( \ + VIDEO_COLOR_PRIMARIES_BT709, /* bColorPrimaries: BT.709, sRGB */ \ + VIDEO_COLOR_XFER_CH_BT709, /* bTransferCharacteristics: BT.709 */ \ + VIDEO_COLOR_COEF_SMPTE170M), /* bMatrixCoefficients: SMPTE 170M */ \ + \ + /* Video Streaming Interface (Interface _itf+1, Alternate 1 - Operational) */ \ + TUD_VIDEO_DESC_STD_VS(_itf + 1, 1, 1, _stridx), \ + /* Isochronous Video Data Endpoint */ \ + TUD_VIDEO_DESC_EP_ISO(_epin, _epsize, 1) /* bInterval: 1 (one frame per microframe) */ + +#define _UVC_DESC_LEN TUD_UVC_DESC_LEN +#define _CDC_DESC_LEN TUD_CDC_DESC_LEN +#define _MSC_DESC_LEN TUD_MSC_DESC_LEN +#define DESCRIPTOR_TOTAL_LENGTH ( \ + TUD_CONFIG_DESC_LEN \ + + _UVC_DESC_LEN \ + + _CDC_DESC_LEN \ + + _MSC_DESC_LEN \ +) + +const tusb_desc_device_t Device_Descriptor = { + .bLength = sizeof(Device_Descriptor), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + /* Use Interface Association Descriptor (IAD) for CDC or VIDEO + As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) */ + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = CONFIG_USB_VID, + .idProduct = CONFIG_USB_PID, + .bcdDevice = USB_BCD_DEVICE(PYROVISION_VERSION_MAJOR, PYROVISION_VERSION_MINOR), + .iManufacturer = STRID_MANUFACTURER, + .iProduct = STRID_PRODUCT, + .iSerialNumber = STRID_SERIAL, + .bNumConfigurations = 0x01 +}; + +/** @brief USB Configuration Descriptor + * Interface 0: Video Control + * Interface 1: Video Streaming (Endpoint 0x81) + * Interface 2: CDC Control (Endpoint 0x82, NOTIFICATION) + * Interface 3: CDC Data (Endpoint 0x83 OUT, 0x84 IN) + * Interface 4: MSC Control (Endpoint 0x01 OUT, 0x84 IN) + */ +const uint8_t Config_Descriptor[] = { + TUD_CONFIG_DESCRIPTOR(1, 5, 0, + DESCRIPTOR_TOTAL_LENGTH, TUSB_DESC_CONFIG_ATT_SELF_POWERED, 100), + + /* UVC Video Interface (Interface 0 - 1) */ + TUD_VIDEO_CAPTURE_DESCRIPTOR_MJPEG(0, 0, EPNUM_UVC_VIDEO_IN, 160, 120, 15, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE), + + /* CDC Metadata Interface (Interface 2 - 3) */ + TUD_CDC_DESCRIPTOR(2, 0, EPNUM_CDC_NOTIFY, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64), + + /* MSC Interface (Interface 4) */ + TUD_MSC_DESCRIPTOR(4, 0, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64), +}; + +const tusb_desc_device_t* get_Desc_Device(void) { + return &Device_Descriptor; +} + +const uint8_t* get_Desc_Config(void) +{ + return Config_Descriptor; +} \ No newline at end of file diff --git a/main/Application/Manager/USB/Descriptors/descriptors.h b/main/Application/Manager/USB/Descriptors/descriptors.h new file mode 100644 index 0000000..d0e5797 --- /dev/null +++ b/main/Application/Manager/USB/Descriptors/descriptors.h @@ -0,0 +1,52 @@ +/* + * descriptors.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: USB composite device descriptor definitions (UVC + CDC + MSC). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef USB_DESCRIPTORS_H_ +#define USB_DESCRIPTORS_H_ + +#include + +/** @brief String Descriptor Index. + */ +enum { + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, +}; + +/** @brief Get the USB device descriptor. + * Returns the device descriptor for the composite USB device + * (MISC class with IAD protocol for UVC + CDC + MSC). + * @return Pointer to the device descriptor structure + */ +const tusb_desc_device_t *get_Desc_Device(void); + +/** @brief Get the USB composite configuration descriptor. + * Returns the full configuration descriptor including all enabled + * class interfaces (UVC, CDC, MSC) as selected by sdkconfig. + * @return Pointer to the configuration descriptor byte array + */ +const uint8_t *get_Desc_Config(void); + +#endif /* USB_DESCRIPTORS_H_ */ diff --git a/main/Application/Manager/USB/MSC/usbMSC.cpp b/main/Application/Manager/USB/MSC/usbMSC.cpp new file mode 100644 index 0000000..0d765db --- /dev/null +++ b/main/Application/Manager/USB/MSC/usbMSC.cpp @@ -0,0 +1,222 @@ +/* + * usbMSC.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: USB Mass Storage Class module implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include + +#include "usbMSC.h" + +#include +#include +#include + +#include + +#include "../../Memory/memoryManager.h" + +/** @brief USB MSC internal state. + */ +typedef struct { + bool isInitialized; /**< Initialization state. */ + tinyusb_msc_storage_handle_t Storage; /**< Storage handle. */ +} USBMSC_State_t; + +static USBMSC_State_t _USBMSC_State; + +static const char *TAG = "USB-MSC"; + +esp_err_t USBMSC_Init(const USB_MSC_Config_t *p_Config) +{ + esp_err_t Error; + MemoryManager_Location_t StorageLocation; + + if (p_Config == NULL) { + ESP_LOGE(TAG, "Invalid configuration pointer!"); + + return ESP_ERR_INVALID_ARG; + } else if (_USBMSC_State.isInitialized) { + ESP_LOGW(TAG, "USB MSC already initialized!"); + + return ESP_ERR_INVALID_STATE; + } else if (p_Config->MountPoint == NULL) { + ESP_LOGE(TAG, "Mount point is NULL!"); + + return ESP_ERR_INVALID_ARG; + } + + memset(&_USBMSC_State, 0, sizeof(USBMSC_State_t)); + + /* Detect active storage type from MemoryManager */ + StorageLocation = MemoryManager_GetStorageLocation(); + const char *p_StorageTypeName = (StorageLocation == MEMORY_LOCATION_SD_CARD) ? "SD Card (FAT32)" : + "Internal Flash (FAT32)"; + + ESP_LOGD(TAG, "Initializing USB MSC..."); + ESP_LOGD(TAG, " Storage type: %s (auto-detected)", p_StorageTypeName); + ESP_LOGD(TAG, " Mount point: %s", p_Config->MountPoint); + + /* Lock filesystem to prevent application writes during MSC operation */ + Error = MemoryManager_LockFilesystem(); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to lock filesystem: %d!", Error); + } + + /* Soft-unmount VFS so USB host gets exclusive block-level access. + The underlying storage handles (WL handle or sdmmc_card) are preserved. */ + Error = MemoryManager_SoftUnmountStorage(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to soft-unmount storage for MSC: %d!", Error); + + MemoryManager_UnlockFilesystem(); + + return Error; + } + + if (StorageLocation == MEMORY_LOCATION_SD_CARD) { + sdmmc_card_t *Card; + tinyusb_msc_storage_config_t Storage_Config; + + ESP_LOGD(TAG, "Configuring SD Card MSC..."); + + Error = MemoryManager_GetSDCardHandle(&Card); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to get SD card handle: %d!", Error); + + MemoryManager_SoftRemountStorage(); + MemoryManager_UnlockFilesystem(); + + return Error; + } + + ESP_LOGD(TAG, "Using SD card from MemoryManager"); + + memset(&Storage_Config, 0, sizeof(tinyusb_msc_storage_config_t)); + Storage_Config.medium.card = Card; + Storage_Config.mount_point = TINYUSB_MSC_STORAGE_MOUNT_USB; + + Error = tinyusb_msc_new_storage_sdmmc(&Storage_Config, &_USBMSC_State.Storage); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to create SD card MSC storage: %d!", Error); + + MemoryManager_SoftRemountStorage(); + MemoryManager_UnlockFilesystem(); + + return Error; + } + } else if (StorageLocation == MEMORY_LOCATION_INTERNAL) { + wl_handle_t WL_Handle; + tinyusb_msc_storage_config_t Storage_Config; + + ESP_LOGD(TAG, "Configuring internal flash MSC..."); + + Error = MemoryManager_GetWearLevelingHandle(&WL_Handle); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to get wear leveling handle: %d!", Error); + + MemoryManager_SoftRemountStorage(); + MemoryManager_UnlockFilesystem(); + + return Error; + } + + ESP_LOGD(TAG, "Using existing wear leveling handle: %d", WL_Handle); + + memset(&Storage_Config, 0, sizeof(tinyusb_msc_storage_config_t)); + Storage_Config.medium.wl_handle = WL_Handle; + Storage_Config.mount_point = TINYUSB_MSC_STORAGE_MOUNT_USB; + + Error = tinyusb_msc_new_storage_spiflash(&Storage_Config, &_USBMSC_State.Storage); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to create internal flash MSC storage: %d!", Error); + + MemoryManager_SoftRemountStorage(); + MemoryManager_UnlockFilesystem(); + + return Error; + } + } else { + ESP_LOGE(TAG, "Unknown storage location: %d!", StorageLocation); + + MemoryManager_SoftRemountStorage(); + MemoryManager_UnlockFilesystem(); + + return ESP_ERR_INVALID_ARG; + } + + ESP_LOGD(TAG, "USB Mass Storage Device ready"); + ESP_LOGD(TAG, " WARNING: Do not access filesystem from application while USB is connected!"); + + _USBMSC_State.isInitialized = true; + + return ESP_OK; +} + +esp_err_t USBMSC_Deinit(void) +{ + esp_err_t Error; + + if (_USBMSC_State.isInitialized == false) { + ESP_LOGW(TAG, "USB MSC not initialized!"); + + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGD(TAG, "Deinitializing USB MSC..."); + + if (_USBMSC_State.Storage != NULL) { + Error = tinyusb_msc_delete_storage(_USBMSC_State.Storage); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to delete MSC storage: %d!", Error); + } else { + ESP_LOGD(TAG, "MSC storage deleted successfully"); + } + + _USBMSC_State.Storage = NULL; + } + + Error = MemoryManager_SoftRemountStorage(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to soft-remount storage after MSC: %d!", Error); + } else { + ESP_LOGD(TAG, "Filesystem remounted for application"); + } + + Error = MemoryManager_UnlockFilesystem(); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to unlock filesystem: %d!", Error); + } else { + ESP_LOGD(TAG, "Filesystem unlocked for application"); + } + + _USBMSC_State.isInitialized = false; + + ESP_LOGD(TAG, "USB MSC deinitialized successfully"); + + return ESP_OK; +} + +bool USBMSC_IsInitialized(void) +{ + return _USBMSC_State.isInitialized; +} diff --git a/main/Application/Manager/USB/MSC/usbMSC.h b/main/Application/Manager/USB/MSC/usbMSC.h new file mode 100644 index 0000000..e9501d3 --- /dev/null +++ b/main/Application/Manager/USB/MSC/usbMSC.h @@ -0,0 +1,66 @@ +/* + * usbMSC.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: USB Mass Storage Class module definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef USB_MSC_H_ +#define USB_MSC_H_ + +#include "usbMSCTypes.h" + +/** @brief Initialize USB Mass Storage Class device. + * Locks the filesystem, soft-unmounts the VFS FAT layer, and creates a + * TinyUSB MSC storage with TINYUSB_MSC_STORAGE_MOUNT_USB to expose + * raw storage blocks to the USB host PC. The PC can then read and write + * the FAT filesystem directly. The application VFS path becomes + * inaccessible until USBMSC_Deinit() is called. + * @note Storage must be mounted via MemoryManager before calling this function. + * The storage type (SD card or internal flash) is auto-detected. + * On failure, VFS is automatically re-mounted and filesystem unlocked. + * @warning Not thread-safe. Call from the USB Manager task context only. + * Application file I/O will fail while MSC is active. + * @param p_Config Pointer to MSC configuration structure + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Config is NULL or MountPoint is NULL + * ESP_ERR_INVALID_STATE if already initialized + * ESP_FAIL if MSC storage creation fails + */ +esp_err_t USBMSC_Init(const USB_MSC_Config_t *p_Config); + +/** @brief Deinitialize USB Mass Storage Class device. + * Deletes the TinyUSB MSC storage, soft-remounts the VFS FAT layer to + * restore application file I/O access, and unlocks the filesystem. + * After this call, the camera can save images and access files again. + * @note Waits for host to safely eject device before calling. + * The storage handles (WL or SD card) remain valid throughout. + * @warning Ensure host has ejected the USB drive before calling. + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not initialized + */ +esp_err_t USBMSC_Deinit(void); + +/** @brief Check if USB MSC is initialized and active. + * @return true if MSC active + * false if not initialized + */ +bool USBMSC_IsInitialized(void); + +#endif /* USB_MSC_H_ */ diff --git a/main/Application/Manager/USB/MSC/usbMSCTypes.h b/main/Application/Manager/USB/MSC/usbMSCTypes.h new file mode 100644 index 0000000..789fe7f --- /dev/null +++ b/main/Application/Manager/USB/MSC/usbMSCTypes.h @@ -0,0 +1,37 @@ +/* + * usbMSCTypes.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: USB Mass Storage Class type definitions. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef USB_MSC_TYPES_H_ +#define USB_MSC_TYPES_H_ + +#include + +#include + +/** @brief USB MSC configuration structure. + */ +typedef struct { + const char *MountPoint; /**< Mount point of the storage (must match MemoryManager mount point). */ +} USB_MSC_Config_t; + +#endif /* USB_MSC_TYPES_H_ */ diff --git a/main/Application/Manager/USB/UVC/usbUVC.cpp b/main/Application/Manager/USB/UVC/usbUVC.cpp new file mode 100644 index 0000000..d6dd3df --- /dev/null +++ b/main/Application/Manager/USB/UVC/usbUVC.cpp @@ -0,0 +1,352 @@ +/* + * usbUVC.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: USB UVC (Video Class) module implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include + +#include +#include + +#include + +#include + +#include "usbUVC.h" +#include "Application/Manager/USB/usbManager.h" + +#include + +static const char *TAG = "USB-UVC"; + +/* Number of ping-pong buffers */ +#define UVC_NUM_BUFFERS 2 + +/* Maximum frame size for MJPEG (RGB frame can be compressed to ~10% of original) */ +#define UVC_MAX_FRAME_SIZE (160 * 120 * 3) + +/** @brief UVC module internal state. + */ +typedef struct { + bool isInitialized; /**< Module initialization state. */ + bool isStreaming; /**< Host is actively streaming. */ + uint16_t Width; /**< Frame width in pixels. */ + uint16_t Height; /**< Frame height in pixels. */ + uint8_t FrameRate; /**< Target frame rate. */ + uint8_t CurrentWriteBuffer; /**< Index of current write buffer. */ + uint8_t CurrentReadBuffer; /**< Index of current read buffer. */ + USB_UVC_FrameBuffer_t Buffers[UVC_NUM_BUFFERS]; /**< Ping-pong frame buffers. */ + SemaphoreHandle_t BufferMutex; /**< Mutex for buffer access. */ +} USB_UVC_State_t; + +static USB_UVC_State_t _UVC_State; + +/** @brief Callback when streaming starts. + * @param itf Interface number + * @param p_Event Pointer to UVC event + */ +static void on_UVC_StreamingStart(int itf, uvc_event_t *p_Event) +{ + if (p_Event->type == UVC_EVENT_STREAMING_START) { + ESP_LOGD(TAG, "UVC streaming started on interface %d", itf); + + _UVC_State.isStreaming = true; + + esp_err_t Error = esp_event_post(USB_EVENTS, USB_EVENT_UVC_STREAMING_START, NULL, 0, 0); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to post streaming start event: %d", Error); + } + } else if (p_Event->type == UVC_EVENT_FRAME_END) { + ESP_LOGD(TAG, "Frame transfer complete on interface %d", itf); + } +} + +/** @brief Callback when streaming stops. + * @param itf Interface number + * @param p_Event Pointer to UVC event + */ +static void on_UVC_StreamingStop(int itf, uvc_event_t *p_Event) +{ + if (p_Event->type == UVC_EVENT_STREAMING_STOP) { + ESP_LOGI(TAG, "UVC streaming stopped on interface %d", itf); + + _UVC_State.isStreaming = false; + + esp_err_t Error = esp_event_post(USB_EVENTS, USB_EVENT_UVC_STREAMING_STOP, NULL, 0, 0); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to post streaming stop event: %d", Error); + } + } +} + +/** @brief Callback to request a frame buffer from the application. + * @param itf Interface number + * @param pp_Buffer Pointer to store frame buffer address + * @param p_BufferSize Pointer to store frame buffer size + * @return true if buffer available, false otherwise + */ +static bool on_UVC_FrameRequest(int itf, uint8_t **pp_Buffer, size_t *p_BufferSize) +{ + if (xSemaphoreTake(_UVC_State.BufferMutex, 0) == pdFALSE) { + return false; + } + + /* Check if read buffer has valid data */ + uint8_t ReadIdx = _UVC_State.CurrentReadBuffer; + if (_UVC_State.Buffers[ReadIdx].isReady == false) { + xSemaphoreGive(_UVC_State.BufferMutex); + + return false; + } + + *pp_Buffer = _UVC_State.Buffers[ReadIdx].p_Buffer; + *p_BufferSize = _UVC_State.Buffers[ReadIdx].Size; + + ESP_LOGD(TAG, "Frame request: buffer[%d], size=%d", ReadIdx, _UVC_State.Buffers[ReadIdx].Size); + + xSemaphoreGive(_UVC_State.BufferMutex); + + return true; +} + +/** @brief Callback to return a frame buffer after transmission. + * @param itf Interface number + * @param p_Buffer Frame buffer address + */ +static void on_UVC_FrameReturn(int itf, uint8_t *p_Buffer) +{ + if (xSemaphoreTake(_UVC_State.BufferMutex, 0) == pdFALSE) { + return; + } + + /* Find and mark buffer as available */ + for (int i = 0; i < UVC_NUM_BUFFERS; i++) { + if (_UVC_State.Buffers[i].p_Buffer == p_Buffer) { + _UVC_State.Buffers[i].isReady = false; + + /* Advance read buffer to next */ + _UVC_State.CurrentReadBuffer = (i + 1) % UVC_NUM_BUFFERS; + + ESP_LOGD(TAG, "Frame returned: buffer[%d]", i); + + break; + } + } + + xSemaphoreGive(_UVC_State.BufferMutex); +} + +esp_err_t USBUVC_Init(const USB_UVC_Config_t *p_Config) +{ + esp_err_t Error; + uint32_t Caps; + + if (p_Config == NULL) { + ESP_LOGE(TAG, "Invalid configuration!"); + + return ESP_ERR_INVALID_ARG; + } else if (_UVC_State.isInitialized) { + ESP_LOGW(TAG, "UVC already initialized!"); + + return ESP_ERR_INVALID_STATE; + } + + memset(&_UVC_State, 0, sizeof(USB_UVC_State_t)); + + _UVC_State.Width = p_Config->Width; + _UVC_State.Height = p_Config->Height; + _UVC_State.FrameRate = p_Config->FrameRate; + + /* Create mutex for buffer protection */ + _UVC_State.BufferMutex = xSemaphoreCreateMutex(); + if (_UVC_State.BufferMutex == NULL) { + ESP_LOGE(TAG, "Failed to create buffer mutex!"); + + return ESP_ERR_NO_MEM; + } + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM; + #else + Caps = 0; + #endif + + for (int i = 0; i < UVC_NUM_BUFFERS; i++) { + _UVC_State.Buffers[i].p_Buffer = static_cast(heap_caps_malloc(UVC_MAX_FRAME_SIZE, Caps)); + if (_UVC_State.Buffers[i].p_Buffer == NULL) { + ESP_LOGE(TAG, "Failed to allocate buffer %d!", i); + + /* Cleanup previously allocated buffers */ + for (int j = 0; j < i; j++) { + heap_caps_free(_UVC_State.Buffers[j].p_Buffer); + _UVC_State.Buffers[j].p_Buffer = NULL; + } + + vSemaphoreDelete(_UVC_State.BufferMutex); + _UVC_State.BufferMutex = NULL; + + return ESP_ERR_NO_MEM; + } + + _UVC_State.Buffers[i].Size = 0; + _UVC_State.Buffers[i].isReady = false; + + ESP_LOGD(TAG, "Allocated buffer[%d] at %p, size=%d", i, _UVC_State.Buffers[i].p_Buffer, UVC_MAX_FRAME_SIZE); + } + + tinyusb_config_uvc_t UVC_Config = { + .uvc_port = TINYUSB_UVC_ITF_0, + .callback_streaming_start = on_UVC_StreamingStart, + .callback_streaming_stop = on_UVC_StreamingStop, + .fb_request_cb = on_UVC_FrameRequest, + .fb_return_cb = on_UVC_FrameReturn, + .stop_cb = NULL, + .uvc_buffer = NULL, + .uvc_buffer_size = 0, + }; + + Error = tinyusb_uvc_init(&UVC_Config); + + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize TinyUSB UVC: %d!", Error); + + for (uint8_t i = 0; i < UVC_NUM_BUFFERS; i++) { + heap_caps_free(_UVC_State.Buffers[i].p_Buffer); + _UVC_State.Buffers[i].p_Buffer = NULL; + } + + vSemaphoreDelete(_UVC_State.BufferMutex); + _UVC_State.BufferMutex = NULL; + + return Error; + } + + _UVC_State.isInitialized = true; + + ESP_LOGI(TAG, "UVC initialized: %dx%d @ %d fps", _UVC_State.Width, _UVC_State.Height, _UVC_State.FrameRate); + + return ESP_OK; +} + +esp_err_t USBUVC_Deinit(void) +{ + bool WasStreaming = false; + + if (_UVC_State.isInitialized == false) { + ESP_LOGW(TAG, "UVC not initialized!"); + + return ESP_ERR_INVALID_STATE; + } + + /* Remember if we were streaming before deinit */ + WasStreaming = _UVC_State.isStreaming; + + tinyusb_uvc_deinit(TINYUSB_UVC_ITF_0); + + for (uint8_t i = 0; i < UVC_NUM_BUFFERS; i++) { + if (_UVC_State.Buffers[i].p_Buffer != NULL) { + heap_caps_free(_UVC_State.Buffers[i].p_Buffer); + _UVC_State.Buffers[i].p_Buffer = NULL; + } + } + + if (_UVC_State.BufferMutex != NULL) { + vSemaphoreDelete(_UVC_State.BufferMutex); + _UVC_State.BufferMutex = NULL; + } + + memset(&_UVC_State, 0, sizeof(USB_UVC_State_t)); + + /* If we were streaming, post STOP event so tasks can clean up */ + if (WasStreaming) { + esp_event_post(USB_EVENTS, USB_EVENT_UVC_STREAMING_STOP, NULL, 0, 0); + } + + ESP_LOGI(TAG, "UVC deinitialized"); + + return ESP_OK; +} + +esp_err_t USBUVC_SubmitFrame(const uint8_t *p_Data, size_t Size) +{ + if (p_Data == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (Size == 0) { + return ESP_ERR_INVALID_ARG; + } else if (_UVC_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (_UVC_State.isStreaming == false) { + return ESP_ERR_INVALID_STATE; + } else if (Size > UVC_MAX_FRAME_SIZE) { + ESP_LOGW(TAG, "Frame size %d exceeds maximum %d!", Size, UVC_MAX_FRAME_SIZE); + + return ESP_ERR_NO_MEM; + } + + if (xSemaphoreTake(_UVC_State.BufferMutex, pdMS_TO_TICKS(10)) == pdFALSE) { + ESP_LOGW(TAG, "Failed to acquire buffer mutex!"); + + return ESP_ERR_TIMEOUT; + } + + /* Find next available write buffer */ + uint8_t WriteIdx = _UVC_State.CurrentWriteBuffer; + + /* If buffer is still in use, skip this frame */ + if (_UVC_State.Buffers[WriteIdx].isReady) { + xSemaphoreGive(_UVC_State.BufferMutex); + + ESP_LOGD(TAG, "Buffer[%d] still in use, dropping frame", WriteIdx); + + return ESP_ERR_NO_MEM; + } + + /* Copy frame data to buffer */ + memcpy(_UVC_State.Buffers[WriteIdx].p_Buffer, p_Data, Size); + _UVC_State.Buffers[WriteIdx].Size = Size; + _UVC_State.Buffers[WriteIdx].isReady = true; + + /* Advance write buffer index */ + _UVC_State.CurrentWriteBuffer = (WriteIdx + 1) % UVC_NUM_BUFFERS; + + /* Update read buffer if it hasn't been set */ + if (_UVC_State.Buffers[_UVC_State.CurrentReadBuffer].isReady == false) { + _UVC_State.CurrentReadBuffer = WriteIdx; + } + + ESP_LOGD(TAG, "Frame submitted: buffer[%d], size=%d", WriteIdx, Size); + + xSemaphoreGive(_UVC_State.BufferMutex); + + return ESP_OK; +} + +bool USBUVC_IsStreaming(void) +{ + return _UVC_State.isInitialized && _UVC_State.isStreaming; +} + +bool USBUVC_IsInitialized(void) +{ + return _UVC_State.isInitialized; +} diff --git a/main/Application/Manager/USB/UVC/usbUVC.h b/main/Application/Manager/USB/UVC/usbUVC.h new file mode 100644 index 0000000..d040001 --- /dev/null +++ b/main/Application/Manager/USB/UVC/usbUVC.h @@ -0,0 +1,98 @@ +/* + * usbUVC.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: USB UVC (Video Class) module definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef USB_UVC_H_ +#define USB_UVC_H_ + +#include + +#include +#include + +/** @brief UVC frame buffer structure for ping-pong buffering. + */ +typedef struct { + uint8_t *p_Buffer; /**< Pointer to frame data. */ + size_t Size; /**< Size of frame data in bytes. */ + bool isReady; /**< Buffer contains valid frame data. */ +} USB_UVC_FrameBuffer_t; + +/** @brief UVC configuration structure. + */ +typedef struct { + uint16_t Width; /**< Frame width in pixels. */ + uint16_t Height; /**< Frame height in pixels. */ + uint8_t FrameRate; /**< Target frame rate in fps. */ +} USB_UVC_Config_t; + +/** @brief Initialize the USB UVC module. + * Sets up ping-pong frame buffers and UVC streaming infrastructure. + * @note Must be called after TinyUSB driver is installed. + * Requires CONFIG_TINYUSB_UVC_ENABLED=y in sdkconfig. + * @warning Frame buffers are allocated in PSRAM for performance. + * @param p_Config Pointer to UVC configuration structure + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Config is NULL + * ESP_ERR_NO_MEM if buffer allocation fails + * ESP_ERR_INVALID_STATE if already initialized + */ +esp_err_t USBUVC_Init(const USB_UVC_Config_t *p_Config); + +/** @brief Deinitialize the USB UVC module. + * Stops streaming and frees all allocated buffers. + * @note Safe to call even if not initialized. + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not initialized + */ +esp_err_t USBUVC_Deinit(void); + +/** @brief Submit a frame for USB streaming. + * Copies frame data to internal ping-pong buffer and queues for transmission. + * @note Non-blocking. If previous frame not transmitted, this frame is dropped. + * Frame data must be in JPEG format for UVC MJPEG mode. + * @warning Do not call from ISR context. + * @param p_Data Pointer to frame data buffer + * @param Size Size of frame data in bytes + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_Data is NULL or Size is 0 + * ESP_ERR_INVALID_STATE if not initialized or not streaming + * ESP_ERR_NO_MEM if no buffer available + */ +esp_err_t USBUVC_SubmitFrame(const uint8_t *p_Data, size_t Size); + +/** @brief Check if UVC streaming is currently active. + * Returns true when host has started video streaming. + * @note Thread-safe. + * @return true if USB host is actively streaming + * false if not streaming or not initialized + */ +bool USBUVC_IsStreaming(void); + +/** @brief Check if UVC module is initialized. + * @note Thread-safe. + * @return true if module is initialized + * false if not initialized + */ +bool USBUVC_IsInitialized(void); + +#endif /* USB_UVC_H_ */ diff --git a/main/Application/Manager/USB/usbManager.cpp b/main/Application/Manager/USB/usbManager.cpp new file mode 100644 index 0000000..5316758 --- /dev/null +++ b/main/Application/Manager/USB/usbManager.cpp @@ -0,0 +1,411 @@ +/* + * usbManager.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: USB Manager implementation - Composite USB device coordinator (MSC + UVC + CDC). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include "usbManager.h" +#include "MSC/usbMSC.h" +#include "UVC/usbUVC.h" +#include "CDC/usbCDC.h" +#include "Descriptors/descriptors.h" +#include "../Memory/memoryManager.h" + +ESP_EVENT_DEFINE_BASE(USB_EVENTS); + +/** @brief USB Manager internal command IDs for the task queue. + */ +typedef enum { + USB_CMD_ENABLE_MSC, /**< Initialize and enable USB MSC. */ + USB_CMD_DISABLE_MSC, /**< Deinitialize and disable USB MSC. */ + USB_CMD_ENABLE_UVC, /**< Initialize and enable USB UVC. */ + USB_CMD_DISABLE_UVC, /**< Deinitialize and disable USB UVC. */ +} USB_Manager_Cmd_ID_t; + +/** @brief USB Manager command structure passed through the internal queue. + */ +typedef struct { + USB_Manager_Cmd_ID_t ID; /**< Command identifier. */ +} USB_Manager_Cmd_t; + +/** @brief USB Manager internal state. + */ +typedef struct { + bool isInitialized; /**< TinyUSB driver installed and CDC active. */ + bool isCableConnected; /**< USB cable connected and enumerated by host. */ + QueueHandle_t CommandQueue; /**< Queue for MSC/UVC enable/disable commands. */ + TaskHandle_t MonitoringTask; /**< Handle for the USB monitoring/command task. */ + const char *StringDescriptors[4]; /**< String descriptor pointers (LangID, Manufacturer, Product, Serial). */ +} USB_Manager_State_t; + +static USB_Manager_State_t _USB_Manager_State; + +static const char *TAG = "USB-Manager"; + +/* Language ID descriptor for USB (English US - 0x0409) */ +static const char USB_LangID[2] = {0x09, 0x04}; + +/** @brief USB Manager monitoring and command processing task. + * Polls tud_mounted() every 500 ms to detect cable connect / disconnect + * transitions and posts the corresponding USB event. Also processes + * MSC and UVC enable / disable commands from the internal command queue. + * @param p_Arg Unused task argument. + */ +static void USB_Monitoring_Task(void *p_Arg) +{ + USB_Manager_Cmd_t Cmd; + + esp_task_wdt_add(NULL); + + ESP_LOGD(TAG, "USB monitoring task started"); + + while (true) { + if ((tud_connected() == true) && (_USB_Manager_State.isCableConnected == false)) { + ESP_LOGD(TAG, "USB cable connected (host enumeration complete)"); + + _USB_Manager_State.isCableConnected = true; + + esp_event_post(USB_EVENTS, USB_EVENT_CABLE_CONNECTED, NULL, 0, portMAX_DELAY); + } else if ((tud_connected() == false) && (_USB_Manager_State.isCableConnected == true)) { + ESP_LOGD(TAG, "USB cable disconnected"); + + _USB_Manager_State.isCableConnected = false; + + if (USBMSC_IsInitialized()) { + ESP_LOGD(TAG, "Force-deinit MSC on cable disconnect"); + USBMSC_Deinit(); + } + + if (USBUVC_IsInitialized()) { + ESP_LOGD(TAG, "Force-deinit UVC on cable disconnect"); + USBUVC_Deinit(); + } + + esp_event_post(USB_EVENTS, USB_EVENT_CABLE_DISCONNECTED, NULL, 0, portMAX_DELAY); + } + + if (xQueueReceive(_USB_Manager_State.CommandQueue, &Cmd, 0) == pdTRUE) { + esp_err_t Error; + + switch (Cmd.ID) { + case USB_CMD_ENABLE_MSC: { + if (USBMSC_IsInitialized()) { + ESP_LOGW(TAG, "MSC already initialized!"); + + break; + } + + USB_MSC_Config_t MSC_Config = { + .MountPoint = MemoryManager_GetStoragePath(), + }; + + Error = USBMSC_Init(&MSC_Config); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize USB MSC: %d!", Error); + } else { + ESP_LOGD(TAG, "USB MSC enabled"); + } + + break; + } + case USB_CMD_DISABLE_MSC: { + if (USBMSC_IsInitialized() == false) { + ESP_LOGW(TAG, "MSC not active, nothing to disable!"); + + break; + } + + Error = USBMSC_Deinit(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to deinitialize USB MSC: %d!", Error); + } else { + ESP_LOGD(TAG, "USB MSC disabled"); + } + + break; + } + case USB_CMD_ENABLE_UVC: { + if (USBUVC_IsInitialized()) { + ESP_LOGW(TAG, "UVC already initialized!"); + + break; + } + + USB_UVC_Config_t UVC_Config = { + .Width = 160, + .Height = 120, + .FrameRate = 9, + }; + + Error = USBUVC_Init(&UVC_Config); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize USB UVC: %d!", Error); + } else { + ESP_LOGD(TAG, "USB UVC enabled"); + } + + break; + } + case USB_CMD_DISABLE_UVC: { + if (USBUVC_IsInitialized() == false) { + ESP_LOGW(TAG, "UVC not active, nothing to disable!"); + + break; + } + + Error = USBUVC_Deinit(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to deinitialize USB UVC: %d!", Error); + } else { + ESP_LOGD(TAG, "USB UVC disabled"); + } + + break; + } + default: { + ESP_LOGW(TAG, "Unknown USB Manager command: %d", static_cast(Cmd.ID)); + + break; + } + } + } + + esp_task_wdt_reset(); + vTaskDelay(pdMS_TO_TICKS(500)); + } +} + +esp_err_t USBManager_Init(void) +{ + esp_err_t USB_Error; + BaseType_t Error; + tinyusb_config_t USB_Config = TINYUSB_DEFAULT_CONFIG(); + USB_CDC_Config_t CDC_Config = { + .Reserved = 0, + }; + + if (_USB_Manager_State.isInitialized) { + ESP_LOGW(TAG, "USB Manager already initialized!"); + + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGD(TAG, "Initializing USB Manager..."); + + memset(&_USB_Manager_State, 0, sizeof(USB_Manager_State_t)); + + _USB_Manager_State.StringDescriptors[0] = USB_LangID; + _USB_Manager_State.StringDescriptors[1] = CONFIG_DEVICE_MANUFACTURER; + _USB_Manager_State.StringDescriptors[2] = CONFIG_DEVICE_NAME; + _USB_Manager_State.StringDescriptors[3] = NULL; + + ESP_LOGD(TAG, "USB Descriptors: VID:PID = 0x%04X:0x%04X", + get_Desc_Device()->idVendor, + get_Desc_Device()->idProduct); + ESP_LOGD(TAG, " Manufacturer: %s, Product: %s", + _USB_Manager_State.StringDescriptors[1], _USB_Manager_State.StringDescriptors[2]); + + USB_Config.descriptor.device = get_Desc_Device(); + USB_Config.descriptor.full_speed_config = get_Desc_Config(); + USB_Config.descriptor.string = _USB_Manager_State.StringDescriptors; + USB_Config.descriptor.string_count = 3; + + ESP_LOGD(TAG, "Installing TinyUSB driver..."); + USB_Error = tinyusb_driver_install(&USB_Config); + if (USB_Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to install TinyUSB driver: %d!", USB_Error); + + return USB_Error; + } + + USB_Error = USBCDC_Init(&CDC_Config); + if (USB_Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize USB CDC: %d!", USB_Error); + + tinyusb_driver_uninstall(); + + return USB_Error; + } + + _USB_Manager_State.CommandQueue = xQueueCreate(8, sizeof(USB_Manager_Cmd_t)); + if (_USB_Manager_State.CommandQueue == NULL) { + ESP_LOGE(TAG, "Failed to create USB command queue!"); + + USBCDC_Deinit(); + tinyusb_driver_uninstall(); + + return ESP_ERR_NO_MEM; + } + + Error = xTaskCreate(USB_Monitoring_Task, "USBMonTask", 4096, NULL, 5, &_USB_Manager_State.MonitoringTask); + if (Error != pdPASS) { + ESP_LOGE(TAG, "Failed to create USB monitoring task: %d!", Error); + + vQueueDelete(_USB_Manager_State.CommandQueue); + _USB_Manager_State.CommandQueue = NULL; + + USBCDC_Deinit(); + tinyusb_driver_uninstall(); + + return ESP_ERR_NO_MEM; + } + + tud_connect(); + + _USB_Manager_State.isInitialized = true; + + esp_event_post(USB_EVENTS, USB_EVENT_INITIALIZED, NULL, 0, portMAX_DELAY); + + ESP_LOGD(TAG, "USB Manager initialized successfully"); + + return ESP_OK; +} + +esp_err_t USBManager_Deinit(void) +{ + esp_err_t Error; + + if (_USB_Manager_State.isInitialized == false) { + ESP_LOGW(TAG, "USB Manager not initialized"); + + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGD(TAG, "Deinitializing USB Manager..."); + + if (_USB_Manager_State.MonitoringTask != NULL) { + vTaskDelete(_USB_Manager_State.MonitoringTask); + _USB_Manager_State.MonitoringTask = NULL; + } + + if (_USB_Manager_State.CommandQueue != NULL) { + vQueueDelete(_USB_Manager_State.CommandQueue); + _USB_Manager_State.CommandQueue = NULL; + } + + _USB_Manager_State.isInitialized = false; + _USB_Manager_State.isCableConnected = false; + + ESP_LOGD(TAG, "Disconnecting from USB bus..."); + if (tud_disconnect() == false) { + ESP_LOGW(TAG, "Failed to initiate USB disconnect"); + } + + vTaskDelay(pdMS_TO_TICKS(500)); + + Error = USBCDC_Deinit(); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to deinitialize USB CDC: %d!", Error); + } else { + ESP_LOGD(TAG, "USB CDC deinitialized"); + } + + Error = USBUVC_Deinit(); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to deinitialize USB UVC: %d!", Error); + } else { + ESP_LOGD(TAG, "USB UVC deinitialized"); + } + + Error = USBMSC_Deinit(); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to deinitialize USB MSC: %d!", Error); + } else { + ESP_LOGD(TAG, "USB MSC deinitialized"); + } + + Error = tinyusb_driver_uninstall(); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "Failed to uninstall TinyUSB driver: %d!", Error); + + return Error; + } + + ESP_LOGD(TAG, "TinyUSB driver uninstalled"); + + vTaskDelay(pdMS_TO_TICKS(1000)); + + esp_event_post(USB_EVENTS, USB_EVENT_UNINITIALIZED, NULL, 0, portMAX_DELAY); + + ESP_LOGD(TAG, "USB Manager deinitialized successfully"); + + return ESP_OK; +} + +esp_err_t USBManager_EnableMSC(bool Enable) +{ + USB_Manager_Cmd_t Cmd = { + .ID = Enable ? USB_CMD_ENABLE_MSC : USB_CMD_DISABLE_MSC, + }; + + if (_USB_Manager_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } + + if (xQueueSend(_USB_Manager_State.CommandQueue, &Cmd, 0) != pdTRUE) { + ESP_LOGW(TAG, "USB command queue full, EnableMSC command dropped!"); + + return ESP_ERR_TIMEOUT; + } + + return ESP_OK; +} + +esp_err_t USBManager_EnableUVC(bool Enable) +{ + USB_Manager_Cmd_t Cmd = { + .ID = Enable ? USB_CMD_ENABLE_UVC : USB_CMD_DISABLE_UVC, + }; + + if (_USB_Manager_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } + + if (xQueueSend(_USB_Manager_State.CommandQueue, &Cmd, 0) != pdTRUE) { + ESP_LOGW(TAG, "USB command queue full, EnableUVC command dropped!"); + + return ESP_ERR_TIMEOUT; + } + + return ESP_OK; +} + +bool USBManager_IsInitialized(void) +{ + return _USB_Manager_State.isInitialized; +} + +bool USBManager_IsCableConnected(void) +{ + return _USB_Manager_State.isCableConnected; +} \ No newline at end of file diff --git a/main/Application/Manager/USB/usbManager.h b/main/Application/Manager/USB/usbManager.h new file mode 100644 index 0000000..e62dd12 --- /dev/null +++ b/main/Application/Manager/USB/usbManager.h @@ -0,0 +1,97 @@ +/* + * usbManager.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: USB Manager definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef USB_MANAGER_H_ +#define USB_MANAGER_H_ + +#include "usbTypes.h" + +/** @brief Initialize the USB Manager. + * Installs the TinyUSB driver with the composite descriptor, initializes the CDC + * class at boot time, creates an internal command queue and starts a monitoring + * task. The monitoring task polls the USB cable connection state and posts + * USB_EVENT_CABLE_CONNECTED or USB_EVENT_CABLE_DISCONNECTED via the ESP event + * system when the state changes. MSC and UVC classes are NOT enabled at init; + * use USBManager_EnableMSC() and USBManager_EnableUVC() to activate them. + * @note Must be called after MemoryManager_Init() so the storage path is available. + * Call once during boot before starting application tasks. + * @warning MSC requires the filesystem to be mounted before USBManager_EnableMSC(true) + * is called. + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if already initialized + * ESP_ERR_NO_MEM if queue or task creation fails + * ESP_FAIL if TinyUSB driver installation or CDC initialization fails + */ +esp_err_t USBManager_Init(void); + +/** @brief Deinitialize the USB Manager. + * Disables all active USB classes, disconnects from the USB bus, stops the + * internal monitoring task, and uninstalls the TinyUSB driver. + * @note After deinitialization the filesystem is accessible again. + * @warning Ensure the host has safely ejected the USB drive before calling this function. + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not initialized + */ +esp_err_t USBManager_Deinit(void); + +/** @brief Enable or disable the USB Mass Storage Class. + * Posts a command to the internal USB Manager task and returns immediately + * without blocking the caller. The actual USBMSC_Init() or USBMSC_Deinit() + * operation runs in the USB Manager task context. + * @note Call only when USBManager_IsInitialized() returns true. + * MSC requires a mounted filesystem at MemoryManager_GetStoragePath(). + * Enable is silently ignored when the cable is not connected. + * @warning While MSC is active the application must not write to the filesystem. + * @param Enable true to enable MSC, false to disable + * @return ESP_OK if command was enqueued successfully + * ESP_ERR_INVALID_STATE if USB Manager is not initialized + * ESP_ERR_TIMEOUT if command queue is full + */ +esp_err_t USBManager_EnableMSC(bool Enable); + +/** @brief Enable or disable the USB Video Class. + * Posts a command to the internal USB Manager task and returns immediately + * without blocking the caller. The actual USBUVC_Init() or USBUVC_Deinit() + * operation runs in the USB Manager task context. + * @note Call only when USBManager_IsInitialized() returns true. + * Enable is silently ignored when the cable is not connected. + * @param Enable true to enable UVC, false to disable + * @return ESP_OK if command was enqueued successfully + * ESP_ERR_INVALID_STATE if USB Manager is not initialized + * ESP_ERR_TIMEOUT if command queue is full + */ +esp_err_t USBManager_EnableUVC(bool Enable); + +/** @brief Check if the USB Manager is initialized and the TinyUSB driver is active. + * @return true if USB Manager is initialized + * false if not initialized + */ +bool USBManager_IsInitialized(void); + +/** @brief Check if a USB cable is currently connected and enumerated by the host. + * @return true if USB cable is connected and device has been enumerated by the host + * false if cable is absent or device has not yet been enumerated + */ +bool USBManager_IsCableConnected(void); + +#endif /* USB_MANAGER_H_ */ diff --git a/main/Application/Manager/USB/usbTypes.h b/main/Application/Manager/USB/usbTypes.h new file mode 100644 index 0000000..b9788d6 --- /dev/null +++ b/main/Application/Manager/USB/usbTypes.h @@ -0,0 +1,49 @@ +/* + * usbTypes.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Common type definitions for the USB Manager component. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef USB_TYPES_H_ +#define USB_TYPES_H_ + +#include +#include + +#include + +/** @brief USB Manager events base. + */ +ESP_EVENT_DECLARE_BASE(USB_EVENTS); + +/** @brief USB Manager event IDs. + */ +typedef enum { + USB_EVENT_INITIALIZED, /**< USB subsystem (TinyUSB driver + CDC) initialized and ready. */ + USB_EVENT_UNINITIALIZED, /**< USB subsystem uninitialized and stopped. */ + USB_EVENT_CABLE_CONNECTED, /**< USB cable connected and enumerated by host. */ + USB_EVENT_CABLE_DISCONNECTED, /**< USB cable disconnected from host. */ + USB_EVENT_UVC_STREAMING_START, /**< UVC streaming started by USB host. */ + USB_EVENT_UVC_STREAMING_STOP, /**< UVC streaming stopped by USB host. */ + USB_EVENT_CDC_CONNECTED, /**< CDC host terminal connected (DTR+RTS set). */ + USB_EVENT_CDC_DISCONNECTED, /**< CDC host terminal disconnected. */ +} USB_Event_ID_t; + +#endif /* USB_TYPES_H_ */ diff --git a/main/Application/Manager/managers.h b/main/Application/Manager/managers.h new file mode 100644 index 0000000..3e1f970 --- /dev/null +++ b/main/Application/Manager/managers.h @@ -0,0 +1,34 @@ +/* + * managers.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Central header for all manager modules (Network, Devices, Time, Memory, Settings). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef MANAGERS_H_ +#define MANAGERS_H_ + +#include "Network/networkManager.h" +#include "Devices/devicesManager.h" +#include "Time/timeManager.h" +#include "Memory/memoryManager.h" +#include "Settings/settingsManager.h" +#include "USB/usbManager.h" + +#endif /* MANAGERS_H_ */ \ No newline at end of file diff --git a/main/Application/Tasks/Camera/cameraTask.cpp b/main/Application/Tasks/Camera/cameraTask.cpp new file mode 100644 index 0000000..f0c45aa --- /dev/null +++ b/main/Application/Tasks/Camera/cameraTask.cpp @@ -0,0 +1,168 @@ +/* + * cameraTask.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Camera task implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "cameraTask.h" +#include "Application/application.h" + +#define CAMERA_TASK_STOP_REQUEST BIT0 + +ESP_EVENT_DEFINE_BASE(CAMERA_EVENTS); + +typedef struct { + bool isInitialized; + bool isRunning; + TaskHandle_t TaskHandle; + EventGroupHandle_t EventGroup; +} Camera_Task_State_t; + +static Camera_Task_State_t _Camera_Task_State; + +static const char *TAG = "cameraTask"; + +/** @brief Camera task main loop. + * @param p_Parameters Pointer to App_Context_t structure + */ +static void Task_Camera(void *p_Parameters) +{ + esp_task_wdt_add(NULL); + + ESP_LOGD(TAG, "Camera task started on core %d", xPortGetCoreID()); + + while (_Camera_Task_State.isRunning) { + vTaskDelay(pdMS_TO_TICKS(10)); + } + + ESP_LOGD(TAG, "Camera task shutting down"); + //CameraManager_Deinit(); + + _Camera_Task_State.TaskHandle = NULL; + + esp_task_wdt_delete(NULL); + vTaskDelete(NULL); +} + +esp_err_t Camera_Task_Init(void) +{ + esp_err_t Error; + + Error = ESP_OK; + + if (_Camera_Task_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + _Camera_Task_State.EventGroup = xEventGroupCreate(); + if (_Camera_Task_State.EventGroup == NULL) { + ESP_LOGE(TAG, "Failed to create event group!"); + + return ESP_ERR_NO_MEM; + } + + //Error = CameraManager_Init(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize Camera Manager: 0x%x!", Error); + + return Error; + } + + _Camera_Task_State.isInitialized = true; + + return ESP_OK; +} + +void Camera_Task_Deinit(void) +{ + if (_Camera_Task_State.isInitialized == false) { + return; + } + + //CameraManager_Deinit(); + + if (_Camera_Task_State.EventGroup != NULL) { + vEventGroupDelete(_Camera_Task_State.EventGroup); + _Camera_Task_State.EventGroup = NULL; + } + + _Camera_Task_State.isInitialized = false; + + return; +} + +esp_err_t Camera_Task_Start(App_Context_t *p_AppContext) +{ + BaseType_t Error; + + if (p_AppContext == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (_Camera_Task_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (_Camera_Task_State.isRunning) { + ESP_LOGW(TAG, "Task already running"); + return ESP_OK; + } + + _Camera_Task_State.isRunning = true; + + ESP_LOGD(TAG, "Starting Camera Task"); + + Error = xTaskCreatePinnedToCore(Task_Camera, "Task_Camera", CONFIG_CAMERA_TASK_STACKSIZE, p_AppContext, CONFIG_CAMERA_TASK_PRIO, &_Camera_Task_State.TaskHandle, CONFIG_CAMERA_TASK_CORE); + if (Error != pdPASS) { + ESP_LOGE(TAG, "Failed to create Camera Task: %d!", Error); + + return ESP_ERR_NO_MEM; + } + + return ESP_OK; +} + +esp_err_t Camera_Task_Stop(void) +{ + if (_Camera_Task_State.isRunning == false) { + return ESP_OK; + } + + ESP_LOGD(TAG, "Stopping Camera Task"); + + xEventGroupSetBits(_Camera_Task_State.EventGroup, CAMERA_TASK_STOP_REQUEST); + + return ESP_OK; +} + +bool Camera_Task_IsRunning(void) +{ + return _Camera_Task_State.isRunning; +} \ No newline at end of file diff --git a/main/Application/Tasks/Camera/cameraTask.h b/main/Application/Tasks/Camera/cameraTask.h new file mode 100644 index 0000000..6e2b51f --- /dev/null +++ b/main/Application/Tasks/Camera/cameraTask.h @@ -0,0 +1,58 @@ +/* + * cameraTask.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Camera task definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef CAMERA_TASK_H_ +#define CAMERA_TASK_H_ + +#include +#include + +#include + +#include "Application/application.h" + +/** @brief Initializes the camera task. + * @return ESP_OK on success, error code otherwise + */ +esp_err_t Camera_Task_Init(void); + +/** @brief Deinitializes the camera task. + */ +void Camera_Task_Deinit(void); + +/** @brief Starts the camera task. + * @return ESP_OK on success, error code otherwise + */ +esp_err_t Camera_Task_Start(App_Context_t *p_AppContext); + +/** @brief Stops the camera task. + * @return ESP_OK on success, error code otherwise + */ +esp_err_t Camera_Task_Stop(void); + +/** @brief Checks if the camera task is running. + * @return false if the task is not running, true if it is running + */ +bool Camera_Task_IsRunning(void); + +#endif /* CAMERA_TASK_H_ */ \ No newline at end of file diff --git a/main/Application/Tasks/Devices/devicesTask.cpp b/main/Application/Tasks/Devices/devicesTask.cpp new file mode 100644 index 0000000..49a5057 --- /dev/null +++ b/main/Application/Tasks/Devices/devicesTask.cpp @@ -0,0 +1,222 @@ +/* + * devicesTask.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Devices Task implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "devicesTask.h" +#include "Application/application.h" +#include "Application/Manager/Time/timeTypes.h" + +#define DEVICES_TASK_STOP_REQUEST BIT0 +#define DEVICES_TASK_TIME_SYNCED BIT1 + +ESP_EVENT_DEFINE_BASE(DEVICE_EVENTS); + +typedef struct { + bool isInitialized; + bool isRunning; + TaskHandle_t TaskHandle; + EventGroupHandle_t EventGroup; + uint32_t LastBatteryUpdate; + uint32_t LastTimeUpdate; + struct timeval TimeOfDay; +} Devices_Task_State_t; + +static Devices_Task_State_t _Devices_Task_State; + +static const char *TAG = "Devices-Task"; + +/** @brief Event handler for GUI events. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_GUI_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "GUI event received: ID=%d", ID); +} + +/** @brief Devices task main loop. + * @param p_Parameters Pointer to App_Context_t structure + */ +static void Task_Devices(void *p_Parameters) +{ + uint32_t Now; + + esp_task_wdt_add(NULL); + + ESP_LOGD(TAG, "Devices task started on core %d", xPortGetCoreID()); + + while (_Devices_Task_State.isRunning) { + EventBits_t EventBits; + + esp_task_wdt_reset(); + + EventBits = xEventGroupGetBits(_Devices_Task_State.EventGroup); + if (EventBits & DEVICES_TASK_STOP_REQUEST) { + ESP_LOGD(TAG, "Stop request received"); + + _Devices_Task_State.isRunning = false; + + xEventGroupClearBits(_Devices_Task_State.EventGroup, DEVICES_TASK_STOP_REQUEST); + + break; + } + + Now = esp_timer_get_time() / 1000; + + if ((Now - _Devices_Task_State.LastBatteryUpdate) >= (60 * 1000)) { + App_Devices_Battery_t BatteryInfo; + + ESP_LOGD(TAG, "Updating battery voltage..."); + + if (DevicesManager_GetBatteryVoltage(&BatteryInfo.Voltage, &BatteryInfo.Percentage) == ESP_OK) { + if (esp_event_post(DEVICE_EVENTS, DEVICE_EVENT_RESPONSE_BATTERY_VOLTAGE, &BatteryInfo, sizeof(BatteryInfo), pdMS_TO_TICKS(100)) != ESP_OK) { + ESP_LOGW(TAG, "Failed to post battery event - event queue full!"); + } + } else { + ESP_LOGE(TAG, "Failed to read battery voltage!"); + } + + _Devices_Task_State.LastBatteryUpdate = esp_timer_get_time() / 1000; + } + + vTaskDelay(pdMS_TO_TICKS(10)); + } + + ESP_LOGD(TAG, "Devices task shutting down"); + DevicesManager_Deinit(); + + _Devices_Task_State.TaskHandle = NULL; + + esp_task_wdt_delete(NULL); + vTaskDelete(NULL); +} + +esp_err_t Devices_Task_Init(void) +{ + esp_err_t Error; + + if (_Devices_Task_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + _Devices_Task_State.EventGroup = xEventGroupCreate(); + if (_Devices_Task_State.EventGroup == NULL) { + ESP_LOGE(TAG, "Failed to create event group!"); + + return ESP_ERR_NO_MEM; + } + + Error = DevicesManager_Init(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize Devices Manager: 0x%x!", Error); + + return Error; + } + + esp_event_handler_register(GUI_EVENTS, ESP_EVENT_ANY_ID, on_GUI_Event_Handler, NULL); + + _Devices_Task_State.isInitialized = true; + + return ESP_OK; +} + +void Devices_Task_Deinit(void) +{ + if (_Devices_Task_State.isInitialized == false) { + return; + } + + esp_event_handler_unregister(GUI_EVENTS, ESP_EVENT_ANY_ID, on_GUI_Event_Handler); + + DevicesManager_Deinit(); + + if (_Devices_Task_State.EventGroup != NULL) { + vEventGroupDelete(_Devices_Task_State.EventGroup); + _Devices_Task_State.EventGroup = NULL; + } + + _Devices_Task_State.isInitialized = false; + + return; +} + +esp_err_t Devices_Task_Start(App_Context_t *p_AppContext) +{ + BaseType_t Error; + + if (p_AppContext == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (_Devices_Task_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (_Devices_Task_State.isRunning) { + ESP_LOGW(TAG, "Task already running"); + + return ESP_OK; + } + + _Devices_Task_State.isRunning = true; + + ESP_LOGD(TAG, "Starting Devices Task"); + + Error = xTaskCreatePinnedToCore(Task_Devices, "Task_Devices", CONFIG_DEVICES_TASK_STACKSIZE, p_AppContext, CONFIG_DEVICES_TASK_PRIO, &_Devices_Task_State.TaskHandle, CONFIG_DEVICES_TASK_CORE); + if (Error != pdPASS) { + ESP_LOGE(TAG, "Failed to create Devices Task: %d!", Error); + + return ESP_ERR_NO_MEM; + } + + return ESP_OK; +} + +esp_err_t Devices_Task_Stop(void) +{ + if (_Devices_Task_State.isRunning == false) { + return ESP_OK; + } + + ESP_LOGD(TAG, "Stopping Devices Task"); + + xEventGroupSetBits(_Devices_Task_State.EventGroup, DEVICES_TASK_STOP_REQUEST); + + return ESP_OK; +} + +bool Devices_Task_IsRunning(void) +{ + return _Devices_Task_State.isRunning; +} \ No newline at end of file diff --git a/main/Application/Tasks/Devices/devicesTask.h b/main/Application/Tasks/Devices/devicesTask.h new file mode 100644 index 0000000..3fc14e1 --- /dev/null +++ b/main/Application/Tasks/Devices/devicesTask.h @@ -0,0 +1,58 @@ +/* + * devicesTask.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Devices task definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef DEVICES_TASK_H_ +#define DEVICES_TASK_H_ + +#include +#include + +#include + +#include "Application/application.h" + +/** @brief Initializes the devices task. + * @return ESP_OK on success, error code otherwise + */ +esp_err_t Devices_Task_Init(void); + +/** @brief Deinitializes the devices task. + */ +void Devices_Task_Deinit(void); + +/** @brief Starts the devices task. + * @return ESP_OK on success, error code otherwise + */ +esp_err_t Devices_Task_Start(App_Context_t *p_AppContext); + +/** @brief Stops the devices task. + * @return ESP_OK on success, error code otherwise + */ +esp_err_t Devices_Task_Stop(void); + +/** @brief Checks if the devices task is running. + * @return false if the task is not running, true if it is running + */ +bool Devices_Task_IsRunning(void); + +#endif /* DEVICES_TASK_H_ */ \ No newline at end of file diff --git a/main/Application/Tasks/GUI/Export/CMakeLists.txt b/main/Application/Tasks/GUI/Export/CMakeLists.txt new file mode 100644 index 0000000..a135262 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/CMakeLists.txt @@ -0,0 +1,13 @@ +SET(SOURCES screens/ui_Splash.c + screens/ui_Main.c + screens/ui_Menu.c + screens/ui_Info.c + ui.c + components/ui_comp_hook.c + ui_helpers.c + ui_events.cpp + images/ui_img_logo_80x44_png.c + images/ui_img_text_218x40_png.c + fonts/ui_font_fa.c) + +add_library(ui ${SOURCES}) diff --git a/main/Application/Tasks/GUI/Export/components/ui_comp_hook.c b/main/Application/Tasks/GUI/Export/components/ui_comp_hook.c new file mode 100644 index 0000000..d00cee9 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/components/ui_comp_hook.c @@ -0,0 +1,5 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.5.4 +// LVGL version: 9.1.0 +// Project name: PyroVision + diff --git a/main/Application/Tasks/GUI/Export/filelist.txt b/main/Application/Tasks/GUI/Export/filelist.txt new file mode 100644 index 0000000..49c08b9 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/filelist.txt @@ -0,0 +1,11 @@ +screens/ui_Splash.c +screens/ui_Main.c +screens/ui_Menu.c +screens/ui_Info.c +ui.c +components/ui_comp_hook.c +ui_helpers.c +ui_events.cpp +images/ui_img_logo_80x44_png.c +images/ui_img_text_218x40_png.c +fonts/ui_font_fa.c diff --git a/main/Application/Tasks/GUI/Export/fonts/ui_font_fa.c b/main/Application/Tasks/GUI/Export/fonts/ui_font_fa.c new file mode 100644 index 0000000..4544656 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/fonts/ui_font_fa.c @@ -0,0 +1,17288 @@ +/******************************************************************************* + * Size: 16 px + * Bpp: 1 + * Opts: --bpp 1 --size 16 --font C:/Users/konta/Desktop/ThermalCam/firmware/ui/assets/Font-Awesome/otfs/Font Awesome 7 Free-Solid-900.otf -o C:/Users/konta/Desktop/ThermalCam/firmware/ui/assets/Font-Awesome/otfs\ui_font_fa.c --format lvgl -r 0x20-0xFFFF --no-compress --no-prefilter + ******************************************************************************/ + +#include "../ui.h" + +#ifndef UI_FONT_FA +#define UI_FONT_FA 1 +#endif + +#if UI_FONT_FA + +/*----------------- + * BITMAPS + *----------------*/ + +/*Store the image of the glyphs*/ +static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { + /* U+0020 " " */ + 0x0, + + /* U+0021 "!" */ + 0xff, 0xff, 0xff, 0xf, + + /* U+0022 "\"" */ + 0x0, + + /* U+0023 "#" */ + 0x2, 0xc, 0x6, 0xc, 0x6, 0x18, 0x6, 0x18, + 0x7f, 0xff, 0x7f, 0xff, 0xc, 0x38, 0xc, 0x30, + 0xc, 0x30, 0x1c, 0x30, 0xff, 0xfe, 0xff, 0xfe, + 0x18, 0x60, 0x18, 0x60, 0x30, 0x60, 0x30, 0x40, + + /* U+0024 "$" */ + 0x18, 0xc, 0x1f, 0xdf, 0xec, 0x6, 0x3, 0x80, + 0xfc, 0x3f, 0x1, 0xc0, 0x60, 0x37, 0xf3, 0xf0, + 0x60, 0x30, + + /* U+0025 "%" */ + 0x78, 0xf, 0xf0, 0x7f, 0xc3, 0xbf, 0x1c, 0xfc, + 0xe1, 0xe7, 0x0, 0x38, 0x1, 0xc0, 0xe, 0x78, + 0x73, 0xf3, 0x8f, 0xdc, 0x3f, 0xe0, 0xff, 0x1, + 0xe0, + + /* U+0026 "&" */ + 0x0, + + /* U+0027 "'" */ + 0x0, + + /* U+0028 "(" */ + 0x0, + + /* U+0029 ")" */ + 0x0, + + /* U+002A "*" */ + 0x3, 0x0, 0xc, 0x0, 0x30, 0x0, 0xc0, 0xe3, + 0x1d, 0xed, 0xe3, 0xff, 0x3, 0xf0, 0xf, 0xc0, + 0xff, 0xc7, 0xb7, 0xb8, 0xc7, 0x3, 0x0, 0xc, + 0x0, 0x30, 0x0, 0xc0, + + /* U+002B "+" */ + 0x3, 0x0, 0xc, 0x0, 0x30, 0x0, 0xc0, 0x3, + 0x0, 0xc, 0xf, 0xff, 0xff, 0xff, 0x3, 0x0, + 0xc, 0x0, 0x30, 0x0, 0xc0, 0x3, 0x0, 0xc, + 0x0, + + /* U+002C "," */ + 0x0, + + /* U+002D "-" */ + 0xff, 0xff, 0xf0, + + /* U+002E "." */ + 0x0, + + /* U+002F "/" */ + 0x0, + + /* U+0030 "0" */ + 0x1e, 0x1f, 0xe6, 0x1b, 0x3, 0xc0, 0xf0, 0x3c, + 0xf, 0x3, 0xc0, 0xf0, 0x3c, 0xd, 0x86, 0x7f, + 0x87, 0x80, + + /* U+0031 "1" */ + 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, + + /* U+0032 "2" */ + 0x7f, 0x1f, 0xe0, 0xc, 0x3, 0x1, 0xc1, 0xe1, + 0xf0, 0xf0, 0x70, 0x30, 0xc, 0x3, 0x0, 0xff, + 0xff, 0xf0, + + /* U+0033 "3" */ + 0xff, 0x3f, 0xe0, 0x1c, 0x3, 0x0, 0xc0, 0x73, + 0xf8, 0xfe, 0x1, 0xc0, 0x30, 0xc, 0x7, 0xff, + 0xbf, 0xc0, + + /* U+0034 "4" */ + 0xc0, 0xcc, 0xc, 0xc0, 0xcc, 0xc, 0xc0, 0xcc, + 0xc, 0xc0, 0xcc, 0xc, 0xff, 0xf7, 0xff, 0x0, + 0xc0, 0xc, 0x0, 0xc0, 0xc, + + /* U+0035 "5" */ + 0xff, 0xbf, 0xec, 0x3, 0x0, 0xc0, 0x30, 0xf, + 0xf3, 0xfe, 0x1, 0xc0, 0x30, 0xc, 0x7, 0xff, + 0xbf, 0xc0, + + /* U+0036 "6" */ + 0x1f, 0x8f, 0xf1, 0x80, 0x60, 0xc, 0x1, 0xfe, + 0x3f, 0xf7, 0x6, 0xc0, 0x78, 0xf, 0x1, 0xf0, + 0x77, 0xfc, 0x7f, 0x0, + + /* U+0037 "7" */ + 0xff, 0xff, 0xf0, 0x18, 0xe, 0x3, 0x1, 0xc0, + 0x60, 0x30, 0x1c, 0x6, 0x3, 0x80, 0xc0, 0x60, + 0x18, 0x0, + + /* U+0038 "8" */ + 0x1f, 0xf, 0xe7, 0x1d, 0x83, 0x60, 0xdc, 0x73, + 0xf9, 0xfe, 0xe1, 0xf0, 0x3c, 0xf, 0x87, 0x7f, + 0x8f, 0xc0, + + /* U+0039 "9" */ + 0x1f, 0xf, 0xfb, 0x83, 0xe0, 0x3c, 0x7, 0x80, + 0xf8, 0x3b, 0xff, 0x1f, 0xe0, 0xc, 0x1, 0x80, + 0x63, 0xfc, 0x7e, 0x0, + + /* U+003A ":" */ + 0x0, + + /* U+003B ";" */ + 0x0, + + /* U+003C "<" */ + 0x0, 0x6, 0x0, 0x3c, 0x1, 0xe0, 0x1f, 0x0, + 0xf8, 0x7, 0xc0, 0x3e, 0x0, 0x78, 0x0, 0x3e, + 0x0, 0x1f, 0x0, 0xf, 0x80, 0x7, 0xc0, 0x1, + 0xe0, 0x0, 0xc0, + + /* U+003D "=" */ + 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xff, 0xff, + + /* U+003E ">" */ + 0xc0, 0x1, 0xe0, 0x0, 0xf0, 0x0, 0x7c, 0x0, + 0x3e, 0x0, 0x1f, 0x0, 0xf, 0x80, 0xf, 0x0, + 0xf8, 0x7, 0xc0, 0x3e, 0x1, 0xf0, 0xf, 0x80, + 0x18, 0x0, 0x0, + + /* U+003F "?" */ + 0x1e, 0x1f, 0xe6, 0x1b, 0x3, 0xc0, 0xf0, 0x30, + 0xc, 0x6, 0x7, 0x83, 0xc0, 0xc0, 0x30, 0x0, + 0x0, 0x0, 0xc0, 0x30, + + /* U+0040 "@" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3c, 0x3c, 0x70, 0xe, + 0x63, 0xc6, 0xe7, 0xf7, 0xce, 0x73, 0xcc, 0x33, + 0xcc, 0x33, 0xce, 0x73, 0xe7, 0xff, 0x63, 0xde, + 0x70, 0x0, 0x3c, 0x0, 0x1f, 0x80, 0x7, 0x80, + + /* U+0041 "A" */ + 0x6, 0x0, 0x60, 0xf, 0x0, 0xf0, 0x1f, 0x81, + 0x98, 0x19, 0x83, 0xc, 0x30, 0xc7, 0xfe, 0x7f, + 0xe6, 0x6, 0xc0, 0x3c, 0x3, + + /* U+0042 "B" */ + 0xfe, 0x3f, 0xcc, 0x3b, 0x6, 0xc1, 0xb0, 0xef, + 0xf3, 0xfe, 0xc1, 0xf0, 0x3c, 0xf, 0x7, 0xff, + 0xbf, 0xc0, + + /* U+0043 "C" */ + 0x0, 0x0, 0x7f, 0x7, 0xfc, 0x70, 0x37, 0x0, + 0x30, 0x1, 0x80, 0xc, 0x0, 0x60, 0x3, 0x0, + 0x1c, 0x0, 0x70, 0x31, 0xff, 0x87, 0xf0, 0x0, + 0x0, + + /* U+0044 "D" */ + 0xff, 0xf, 0xf8, 0xc1, 0xcc, 0x6, 0xc0, 0x7c, + 0x3, 0xc0, 0x3c, 0x3, 0xc0, 0x3c, 0x7, 0xc0, + 0x6c, 0x1c, 0xff, 0x8f, 0xf0, + + /* U+0045 "E" */ + 0xff, 0xff, 0xfc, 0x3, 0x0, 0xc0, 0x30, 0xf, + 0xf3, 0xfc, 0xc0, 0x30, 0xc, 0x3, 0x0, 0xff, + 0xff, 0xf0, + + /* U+0046 "F" */ + 0xff, 0xff, 0xfc, 0x3, 0x0, 0xc0, 0x30, 0xf, + 0xf3, 0xfc, 0xc0, 0x30, 0xc, 0x3, 0x0, 0xc0, + 0x30, 0x0, + + /* U+0047 "G" */ + 0xf, 0xc0, 0x7f, 0x83, 0x87, 0x18, 0x0, 0xe0, + 0x3, 0x0, 0xc, 0x1f, 0xf0, 0x7f, 0xc0, 0xf, + 0x80, 0x76, 0x3, 0x8e, 0x1c, 0x1f, 0xe0, 0x3f, + 0x0, + + /* U+0048 "H" */ + 0xc0, 0x3c, 0x3, 0xc0, 0x3c, 0x3, 0xc0, 0x3c, + 0x3, 0xff, 0xff, 0xff, 0xc0, 0x3c, 0x3, 0xc0, + 0x3c, 0x3, 0xc0, 0x3c, 0x3, + + /* U+0049 "I" */ + 0xff, 0xff, 0xf0, 0xc0, 0x30, 0xc, 0x3, 0x0, + 0xc0, 0x30, 0xc, 0x3, 0x0, 0xc0, 0x30, 0xff, + 0xff, 0xf0, + + /* U+004A "J" */ + 0x0, 0xc0, 0x30, 0xc, 0x3, 0x0, 0xc0, 0x30, + 0xf, 0x3, 0xc0, 0xf0, 0x3c, 0xd, 0x86, 0x7f, + 0x87, 0x80, + + /* U+004B "K" */ + 0xc0, 0xf0, 0x7c, 0x3b, 0x1c, 0xce, 0x37, 0xf, + 0xc3, 0xf0, 0xe6, 0x31, 0xcc, 0x33, 0x6, 0xc1, + 0xf0, 0x30, + + /* U+004C "L" */ + 0xc0, 0x60, 0x30, 0x18, 0xc, 0x6, 0x3, 0x1, + 0x80, 0xc0, 0x60, 0x30, 0x18, 0xf, 0xff, 0xfc, + + /* U+004D "M" */ + 0xc0, 0xf, 0x0, 0x3e, 0x1, 0xfc, 0xf, 0xf8, + 0x7f, 0x61, 0xbd, 0xce, 0xf3, 0xf3, 0xc7, 0x8f, + 0x1e, 0x3c, 0x30, 0xf0, 0x3, 0xc0, 0xf, 0x0, + 0x30, + + /* U+004E "N" */ + 0xc0, 0x3e, 0x3, 0xe0, 0x3f, 0x3, 0xd8, 0x3d, + 0xc3, 0xce, 0x3c, 0x73, 0xc3, 0xbc, 0x1b, 0xc0, + 0xfc, 0xf, 0xc0, 0x7c, 0x3, + + /* U+004F "O" */ + 0xf, 0xc0, 0x7f, 0x83, 0x87, 0x18, 0x6, 0xe0, + 0x1f, 0x0, 0x3c, 0x0, 0xf0, 0x3, 0xc0, 0xf, + 0x80, 0x76, 0x1, 0x8e, 0x1c, 0x1f, 0xe0, 0x3f, + 0x0, + + /* U+0050 "P" */ + 0xfe, 0x3f, 0xec, 0x1b, 0x3, 0xc0, 0xf0, 0x3c, + 0xf, 0x6, 0xff, 0xbf, 0x8c, 0x3, 0x0, 0xc0, + 0x30, 0x0, + + /* U+0051 "Q" */ + 0xf, 0xc0, 0x7f, 0x83, 0x87, 0x18, 0x6, 0xe0, + 0x1f, 0x0, 0x3c, 0x0, 0xf0, 0x63, 0xc1, 0xcf, + 0x83, 0xf6, 0x7, 0x8e, 0x1c, 0x1f, 0xf8, 0x3f, + 0x70, 0x0, 0xc0, + + /* U+0052 "R" */ + 0xff, 0x3f, 0xec, 0x1f, 0x3, 0xc0, 0xf0, 0x3c, + 0x1f, 0xfe, 0xff, 0x31, 0xcc, 0x33, 0x6, 0xc1, + 0xf0, 0x30, + + /* U+0053 "S" */ + 0x3f, 0x9f, 0xee, 0x3, 0x0, 0xc0, 0x38, 0x7, + 0xf0, 0xfe, 0x1, 0xc0, 0x30, 0xc, 0x7, 0x7f, + 0x9f, 0xc0, + + /* U+0054 "T" */ + 0xff, 0xff, 0xff, 0x6, 0x0, 0x60, 0x6, 0x0, + 0x60, 0x6, 0x0, 0x60, 0x6, 0x0, 0x60, 0x6, + 0x0, 0x60, 0x6, 0x0, 0x60, + + /* U+0055 "U" */ + 0xc0, 0x3c, 0x3, 0xc0, 0x3c, 0x3, 0xc0, 0x3c, + 0x3, 0xc0, 0x3c, 0x3, 0xc0, 0x3c, 0x3, 0x60, + 0x67, 0xe, 0x3f, 0xc0, 0xf0, + + /* U+0056 "V" */ + 0xc0, 0x3c, 0x3, 0x60, 0x76, 0x6, 0x70, 0x63, + 0xc, 0x30, 0xc1, 0x98, 0x19, 0x81, 0xf8, 0xf, + 0x0, 0xf0, 0x6, 0x0, 0x60, + + /* U+0057 "W" */ + 0xc0, 0xc0, 0xf0, 0x30, 0x3e, 0x1c, 0x1d, 0x87, + 0x86, 0x61, 0xe1, 0x8c, 0x78, 0xc3, 0x33, 0x30, + 0xcc, 0xdc, 0x1b, 0x36, 0x7, 0xcf, 0x81, 0xe1, + 0xe0, 0x38, 0x70, 0xe, 0x1c, 0x1, 0x2, 0x0, + + /* U+0058 "X" */ + 0xc0, 0x3e, 0x7, 0x60, 0x63, 0xc, 0x19, 0x81, + 0xf8, 0xf, 0x0, 0xf0, 0x1f, 0x81, 0x98, 0x30, + 0xc7, 0xe, 0xe0, 0x7c, 0x3, + + /* U+0059 "Y" */ + 0xc0, 0x3e, 0x7, 0x60, 0x67, 0xe, 0x39, 0xc1, + 0x98, 0xf, 0x0, 0xf0, 0x6, 0x0, 0x60, 0x6, + 0x0, 0x60, 0x6, 0x0, 0x60, + + /* U+005A "Z" */ + 0xff, 0xff, 0xff, 0x0, 0xe0, 0xc, 0x1, 0x80, + 0x38, 0x7, 0x0, 0xe0, 0x1c, 0x1, 0x80, 0x30, + 0x7, 0x0, 0xff, 0xff, 0xff, + + /* U+005B "[" */ + 0x0, + + /* U+005C "\\" */ + 0x0, + + /* U+005D "]" */ + 0x0, + + /* U+005E "^" */ + 0x0, + + /* U+005F "_" */ + 0x0, + + /* U+0060 "`" */ + 0x0, + + /* U+0061 "a" */ + 0x6, 0x0, 0x60, 0xf, 0x0, 0xf0, 0x1f, 0x81, + 0x98, 0x19, 0x83, 0xc, 0x30, 0xc7, 0xfe, 0x7f, + 0xe6, 0x6, 0xc0, 0x3c, 0x3, + + /* U+0062 "b" */ + 0xfe, 0x3f, 0xcc, 0x3b, 0x6, 0xc1, 0xb0, 0xef, + 0xf3, 0xfe, 0xc1, 0xf0, 0x3c, 0xf, 0x7, 0xff, + 0xbf, 0xc0, + + /* U+0063 "c" */ + 0x0, 0x0, 0x7f, 0x7, 0xfc, 0x70, 0x37, 0x0, + 0x30, 0x1, 0x80, 0xc, 0x0, 0x60, 0x3, 0x0, + 0x1c, 0x0, 0x70, 0x31, 0xff, 0x87, 0xf0, 0x0, + 0x0, + + /* U+0064 "d" */ + 0xff, 0xf, 0xf8, 0xc1, 0xcc, 0x6, 0xc0, 0x7c, + 0x3, 0xc0, 0x3c, 0x3, 0xc0, 0x3c, 0x7, 0xc0, + 0x6c, 0x1c, 0xff, 0x8f, 0xf0, + + /* U+0065 "e" */ + 0xff, 0xff, 0xfc, 0x3, 0x0, 0xc0, 0x30, 0xf, + 0xf3, 0xfc, 0xc0, 0x30, 0xc, 0x3, 0x0, 0xff, + 0xff, 0xf0, + + /* U+0066 "f" */ + 0xff, 0xff, 0xfc, 0x3, 0x0, 0xc0, 0x30, 0xf, + 0xf3, 0xfc, 0xc0, 0x30, 0xc, 0x3, 0x0, 0xc0, + 0x30, 0x0, + + /* U+0067 "g" */ + 0xf, 0xc0, 0x7f, 0x83, 0x87, 0x18, 0x0, 0xe0, + 0x3, 0x0, 0xc, 0x1f, 0xf0, 0x7f, 0xc0, 0xf, + 0x80, 0x76, 0x3, 0x8e, 0x1c, 0x1f, 0xe0, 0x3f, + 0x0, + + /* U+0068 "h" */ + 0xc0, 0x3c, 0x3, 0xc0, 0x3c, 0x3, 0xc0, 0x3c, + 0x3, 0xff, 0xff, 0xff, 0xc0, 0x3c, 0x3, 0xc0, + 0x3c, 0x3, 0xc0, 0x3c, 0x3, + + /* U+0069 "i" */ + 0xff, 0xff, 0xf0, 0xc0, 0x30, 0xc, 0x3, 0x0, + 0xc0, 0x30, 0xc, 0x3, 0x0, 0xc0, 0x30, 0xff, + 0xff, 0xf0, + + /* U+006A "j" */ + 0x0, 0xc0, 0x30, 0xc, 0x3, 0x0, 0xc0, 0x30, + 0xf, 0x3, 0xc0, 0xf0, 0x3c, 0xd, 0x86, 0x7f, + 0x87, 0x80, + + /* U+006B "k" */ + 0xc0, 0xf0, 0x7c, 0x3b, 0x1c, 0xce, 0x37, 0xf, + 0xc3, 0xf0, 0xe6, 0x31, 0xcc, 0x33, 0x6, 0xc1, + 0xf0, 0x30, + + /* U+006C "l" */ + 0xc0, 0x60, 0x30, 0x18, 0xc, 0x6, 0x3, 0x1, + 0x80, 0xc0, 0x60, 0x30, 0x18, 0xf, 0xff, 0xfc, + + /* U+006D "m" */ + 0xc0, 0xf, 0x0, 0x3e, 0x1, 0xfc, 0xf, 0xf8, + 0x7f, 0x61, 0xbd, 0xce, 0xf3, 0xf3, 0xc7, 0x8f, + 0x1e, 0x3c, 0x30, 0xf0, 0x3, 0xc0, 0xf, 0x0, + 0x30, + + /* U+006E "n" */ + 0xc0, 0x3e, 0x3, 0xe0, 0x3f, 0x3, 0xd8, 0x3d, + 0xc3, 0xce, 0x3c, 0x73, 0xc3, 0xbc, 0x1b, 0xc0, + 0xfc, 0xf, 0xc0, 0x7c, 0x3, + + /* U+006F "o" */ + 0xf, 0xc0, 0x7f, 0x83, 0x87, 0x18, 0x6, 0xe0, + 0x1f, 0x0, 0x3c, 0x0, 0xf0, 0x3, 0xc0, 0xf, + 0x80, 0x76, 0x1, 0x8e, 0x1c, 0x1f, 0xe0, 0x3f, + 0x0, + + /* U+0070 "p" */ + 0xfe, 0x3f, 0xec, 0x1b, 0x3, 0xc0, 0xf0, 0x3c, + 0xf, 0x6, 0xff, 0xbf, 0x8c, 0x3, 0x0, 0xc0, + 0x30, 0x0, + + /* U+0071 "q" */ + 0xf, 0xc0, 0x7f, 0x83, 0x87, 0x18, 0x6, 0xe0, + 0x1f, 0x0, 0x3c, 0x0, 0xf0, 0x63, 0xc1, 0xcf, + 0x83, 0xf6, 0x7, 0x8e, 0x1c, 0x1f, 0xf8, 0x3f, + 0x70, 0x0, 0xc0, + + /* U+0072 "r" */ + 0xff, 0x3f, 0xec, 0x1f, 0x3, 0xc0, 0xf0, 0x3c, + 0x1f, 0xfe, 0xff, 0x31, 0xcc, 0x33, 0x6, 0xc1, + 0xf0, 0x30, + + /* U+0073 "s" */ + 0x3f, 0x9f, 0xee, 0x3, 0x0, 0xc0, 0x38, 0x7, + 0xf0, 0xfe, 0x1, 0xc0, 0x30, 0xc, 0x7, 0x7f, + 0x9f, 0xc0, + + /* U+0074 "t" */ + 0xff, 0xff, 0xff, 0x6, 0x0, 0x60, 0x6, 0x0, + 0x60, 0x6, 0x0, 0x60, 0x6, 0x0, 0x60, 0x6, + 0x0, 0x60, 0x6, 0x0, 0x60, + + /* U+0075 "u" */ + 0xc0, 0x3c, 0x3, 0xc0, 0x3c, 0x3, 0xc0, 0x3c, + 0x3, 0xc0, 0x3c, 0x3, 0xc0, 0x3c, 0x3, 0x60, + 0x67, 0xe, 0x3f, 0xc0, 0xf0, + + /* U+0076 "v" */ + 0xc0, 0x3c, 0x3, 0x60, 0x76, 0x6, 0x70, 0x63, + 0xc, 0x30, 0xc1, 0x98, 0x19, 0x81, 0xf8, 0xf, + 0x0, 0xf0, 0x6, 0x0, 0x60, + + /* U+0077 "w" */ + 0xc0, 0xc0, 0xf0, 0x30, 0x3e, 0x1c, 0x1d, 0x87, + 0x86, 0x61, 0xe1, 0x8c, 0x78, 0xc3, 0x33, 0x30, + 0xcc, 0xdc, 0x1b, 0x36, 0x7, 0xcf, 0x81, 0xe1, + 0xe0, 0x38, 0x70, 0xe, 0x1c, 0x1, 0x2, 0x0, + + /* U+0078 "x" */ + 0xc0, 0x3e, 0x7, 0x60, 0x63, 0xc, 0x19, 0x81, + 0xf8, 0xf, 0x0, 0xf0, 0x1f, 0x81, 0x98, 0x30, + 0xc7, 0xe, 0xe0, 0x7c, 0x3, + + /* U+0079 "y" */ + 0xc0, 0x3e, 0x7, 0x60, 0x67, 0xe, 0x39, 0xc1, + 0x98, 0xf, 0x0, 0xf0, 0x6, 0x0, 0x60, 0x6, + 0x0, 0x60, 0x6, 0x0, 0x60, + + /* U+007A "z" */ + 0xff, 0xff, 0xff, 0x0, 0xe0, 0xc, 0x1, 0x80, + 0x38, 0x7, 0x0, 0xe0, 0x1c, 0x1, 0x80, 0x30, + 0x7, 0x0, 0xff, 0xff, 0xff, + + /* U+007B "{" */ + 0x0, + + /* U+007C "|" */ + 0x0, + + /* U+007D "}" */ + 0x0, + + /* U+007E "~" */ + 0x0, + + /* U+007F "" */ + 0x0, + + /* U+0080 "€" */ + 0x0, + + /* U+0081 "" */ + 0x0, + + /* U+0082 "‚" */ + 0x0, + + /* U+0083 "ƒ" */ + 0x0, + + /* U+0084 "„" */ + 0x0, + + /* U+0085 "…" */ + 0x0, + + /* U+0086 "†" */ + 0x0, + + /* U+0087 "‡" */ + 0x0, + + /* U+0088 "ˆ" */ + 0x0, + + /* U+0089 "‰" */ + 0x0, + + /* U+008A "Š" */ + 0x0, + + /* U+008B "‹" */ + 0x0, + + /* U+008C "Œ" */ + 0x0, + + /* U+008D "" */ + 0x0, + + /* U+008E "Ž" */ + 0x0, + + /* U+008F "" */ + 0x0, + + /* U+0090 "" */ + 0x0, + + /* U+0091 "‘" */ + 0x0, + + /* U+0092 "’" */ + 0x0, + + /* U+0093 "“" */ + 0x0, + + /* U+0094 "”" */ + 0x0, + + /* U+0095 "•" */ + 0x0, + + /* U+0096 "–" */ + 0x0, + + /* U+0097 "—" */ + 0x0, + + /* U+0098 "˜" */ + 0x0, + + /* U+0099 "™" */ + 0x0, + + /* U+009A "š" */ + 0x0, + + /* U+009B "›" */ + 0x0, + + /* U+009C "œ" */ + 0x0, + + /* U+009D "" */ + 0x0, + + /* U+009E "ž" */ + 0x0, + + /* U+009F "Ÿ" */ + 0x0, + + /* U+00A0 " " */ + 0x0, + + /* U+00A1 "¡" */ + 0x0, + + /* U+00A2 "¢" */ + 0x0, + + /* U+00A3 "£" */ + 0xf, 0xf, 0xe3, 0x1d, 0x80, 0x60, 0x1c, 0xf, + 0xfb, 0xfe, 0x30, 0xc, 0x6, 0x3, 0x80, 0xff, + 0xff, 0xf0, + + /* U+00A4 "¤" */ + 0x0, + + /* U+00A5 "¥" */ + 0x0, 0x6, 0x6, 0x60, 0x63, 0xc, 0x39, 0xc1, + 0x98, 0xf, 0x0, 0xf0, 0x3f, 0xc3, 0xfc, 0x6, + 0x3, 0xfc, 0x3f, 0xc0, 0x60, 0x6, 0x0, + + /* U+00A6 "¦" */ + 0x0, + + /* U+00A7 "§" */ + 0x0, + + /* U+00A8 "¨" */ + 0x0, + + /* U+00A9 "©" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xf8, 0x1f, 0xf9, 0x9f, 0xf3, 0xff, + 0xf3, 0xff, 0xfb, 0xdf, 0xf9, 0x1f, 0x7e, 0x7e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+00AA "ª" */ + 0x0, + + /* U+00AB "«" */ + 0x6, 0x18, 0x71, 0xc7, 0x1c, 0x71, 0xc7, 0x1c, + 0x71, 0xc3, 0x8e, 0xe, 0x38, 0x38, 0xe0, 0xe3, + 0x83, 0x8e, 0xc, 0x30, + + /* U+00AC "¬" */ + 0x0, + + /* U+00AD "­" */ + 0x0, + + /* U+00AE "®" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7c, 0x3e, 0xfc, 0x1f, 0xfd, 0x9f, 0xfd, 0x9f, + 0xfc, 0x3f, 0xfd, 0x7f, 0xfd, 0x3f, 0x7d, 0xbe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+00AF "¯" */ + 0x0, + + /* U+00B0 "°" */ + 0x0, + + /* U+00B1 "±" */ + 0x0, + + /* U+00B2 "²" */ + 0x0, + + /* U+00B3 "³" */ + 0x0, + + /* U+00B4 "´" */ + 0x0, + + /* U+00B5 "µ" */ + 0x0, + + /* U+00B6 "¶" */ + 0x1f, 0xfc, 0x7f, 0xf9, 0xfe, 0x63, 0xfc, 0xc7, + 0xf9, 0x8f, 0xf3, 0x1f, 0xe6, 0x3f, 0xcc, 0x3f, + 0x98, 0x3f, 0x30, 0x6, 0x60, 0xc, 0xc0, 0x19, + 0x80, 0x33, 0x0, 0x66, 0x0, 0xcc, + + /* U+00B7 "·" */ + 0x0, + + /* U+00B8 "¸" */ + 0x0, + + /* U+00B9 "¹" */ + 0x0, + + /* U+00BA "º" */ + 0x0, + + /* U+00BB "»" */ + 0xc3, 0x7, 0x1c, 0x1c, 0x70, 0x71, 0xc1, 0xc7, + 0x7, 0x1c, 0x38, 0xe3, 0x8e, 0x38, 0xe3, 0x8e, + 0x38, 0xe1, 0x86, 0x0, + + /* U+00BC "¼" */ + 0x0, + + /* U+00BD "½" */ + 0x0, + + /* U+00BE "¾" */ + 0x0, + + /* U+00BF "¿" */ + 0x0, + + /* U+00C0 "À" */ + 0x0, + + /* U+00C1 "Á" */ + 0x0, + + /* U+00C2 "Â" */ + 0x0, + + /* U+00C3 "Ã" */ + 0x0, + + /* U+00C4 "Ä" */ + 0x0, + + /* U+00C5 "Å" */ + 0x0, + + /* U+00C6 "Æ" */ + 0x0, + + /* U+00C7 "Ç" */ + 0x0, + + /* U+00C8 "È" */ + 0x0, + + /* U+00C9 "É" */ + 0x0, + + /* U+00CA "Ê" */ + 0x0, + + /* U+00CB "Ë" */ + 0x0, + + /* U+00CC "Ì" */ + 0x0, + + /* U+00CD "Í" */ + 0x0, + + /* U+00CE "Î" */ + 0x0, + + /* U+00CF "Ï" */ + 0x0, + + /* U+00D0 "Ð" */ + 0x0, + + /* U+00D1 "Ñ" */ + 0x0, + + /* U+00D2 "Ò" */ + 0x0, + + /* U+00D3 "Ó" */ + 0x0, + + /* U+00D4 "Ô" */ + 0x0, + + /* U+00D5 "Õ" */ + 0x0, + + /* U+00D6 "Ö" */ + 0x0, + + /* U+00D7 "×" */ + 0xc0, 0x3e, 0x7, 0x70, 0xe3, 0x9c, 0x1f, 0x80, + 0xf0, 0xf, 0x1, 0xf8, 0x39, 0xc7, 0xe, 0xe0, + 0x7c, 0x3, + + /* U+00D8 "Ø" */ + 0x0, + + /* U+00D9 "Ù" */ + 0x0, + + /* U+00DA "Ú" */ + 0x0, + + /* U+00DB "Û" */ + 0x0, + + /* U+00DC "Ü" */ + 0x0, + + /* U+00DD "Ý" */ + 0x0, + + /* U+00DE "Þ" */ + 0x0, + + /* U+00DF "ß" */ + 0x0, + + /* U+00E0 "à" */ + 0x0, + + /* U+00E1 "á" */ + 0x0, + + /* U+00E2 "â" */ + 0x0, + + /* U+00E3 "ã" */ + 0x0, + + /* U+00E4 "ä" */ + 0x0, + + /* U+00E5 "å" */ + 0x0, + + /* U+00E6 "æ" */ + 0x0, + + /* U+00E7 "ç" */ + 0x0, + + /* U+00E8 "è" */ + 0x0, + + /* U+00E9 "é" */ + 0x0, + + /* U+00EA "ê" */ + 0x0, + + /* U+00EB "ë" */ + 0x0, + + /* U+00EC "ì" */ + 0x0, + + /* U+00ED "í" */ + 0x0, + + /* U+00EE "î" */ + 0x0, + + /* U+00EF "ï" */ + 0x0, + + /* U+00F0 "ð" */ + 0x0, + + /* U+00F1 "ñ" */ + 0x0, + + /* U+00F2 "ò" */ + 0x0, + + /* U+00F3 "ó" */ + 0x0, + + /* U+00F4 "ô" */ + 0x0, + + /* U+00F5 "õ" */ + 0x0, + + /* U+00F6 "ö" */ + 0x0, + + /* U+00F7 "÷" */ + 0x3, 0x0, 0x1e, 0x0, 0x30, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0x30, 0x1, 0xe0, 0x3, 0x0, + + /* U+00F8 "ø" */ + 0x0, + + /* U+00F9 "ù" */ + 0x0, + + /* U+00FA "ú" */ + 0x0, + + /* U+00FB "û" */ + 0x0, + + /* U+00FC "ü" */ + 0x0, + + /* U+00FD "ý" */ + 0x0, + + /* U+00FE "þ" */ + 0x0, + + /* U+00FF "ÿ" */ + 0x0, + + /* U+0100 "Ā" */ + 0x0, + + /* U+0101 "ā" */ + 0x0, + + /* U+0102 "Ă" */ + 0x0, + + /* U+0103 "ă" */ + 0x0, + + /* U+0104 "Ą" */ + 0x0, + + /* U+0105 "ą" */ + 0x0, + + /* U+0106 "Ć" */ + 0x0, + + /* U+0107 "ć" */ + 0x0, + + /* U+0108 "Ĉ" */ + 0x0, + + /* U+0109 "ĉ" */ + 0x0, + + /* U+010A "Ċ" */ + 0x0, + + /* U+010B "ċ" */ + 0x0, + + /* U+010C "Č" */ + 0x0, + + /* U+010D "č" */ + 0x0, + + /* U+010E "Ď" */ + 0x0, + + /* U+010F "ď" */ + 0x0, + + /* U+0110 "Đ" */ + 0x0, + + /* U+0111 "đ" */ + 0x0, + + /* U+0112 "Ē" */ + 0x0, + + /* U+0113 "ē" */ + 0x0, + + /* U+0114 "Ĕ" */ + 0x0, + + /* U+0115 "ĕ" */ + 0x0, + + /* U+0116 "Ė" */ + 0x0, + + /* U+0117 "ė" */ + 0x0, + + /* U+0118 "Ę" */ + 0x0, + + /* U+0119 "ę" */ + 0x0, + + /* U+011A "Ě" */ + 0x0, + + /* U+011B "ě" */ + 0x0, + + /* U+011C "Ĝ" */ + 0x0, + + /* U+011D "ĝ" */ + 0x0, + + /* U+011E "Ğ" */ + 0x0, + + /* U+011F "ğ" */ + 0x0, + + /* U+0120 "Ġ" */ + 0x0, + + /* U+0121 "ġ" */ + 0x0, + + /* U+0122 "Ģ" */ + 0x0, + + /* U+0123 "ģ" */ + 0x0, + + /* U+0124 "Ĥ" */ + 0x0, + + /* U+0125 "ĥ" */ + 0x0, + + /* U+0126 "Ħ" */ + 0x0, + + /* U+0127 "ħ" */ + 0x0, + + /* U+0128 "Ĩ" */ + 0x0, + + /* U+0129 "ĩ" */ + 0x0, + + /* U+012A "Ī" */ + 0x0, + + /* U+012B "ī" */ + 0x0, + + /* U+012C "Ĭ" */ + 0x0, + + /* U+012D "ĭ" */ + 0x0, + + /* U+012E "Į" */ + 0x0, + + /* U+012F "į" */ + 0x0, + + /* U+0130 "İ" */ + 0x0, + + /* U+0131 "ı" */ + 0x0, + + /* U+0132 "IJ" */ + 0x0, + + /* U+0133 "ij" */ + 0x0, + + /* U+0134 "Ĵ" */ + 0x0, + + /* U+0135 "ĵ" */ + 0x0, + + /* U+0136 "Ķ" */ + 0x0, + + /* U+0137 "ķ" */ + 0x0, + + /* U+0138 "ĸ" */ + 0x0, + + /* U+0139 "Ĺ" */ + 0x0, + + /* U+013A "ĺ" */ + 0x0, + + /* U+013B "Ļ" */ + 0x0, + + /* U+013C "ļ" */ + 0x0, + + /* U+013D "Ľ" */ + 0x0, + + /* U+013E "ľ" */ + 0x0, + + /* U+013F "Ŀ" */ + 0x0, + + /* U+0140 "ŀ" */ + 0x0, + + /* U+0141 "Ł" */ + 0x0, + + /* U+0142 "ł" */ + 0x0, + + /* U+0143 "Ń" */ + 0x0, + + /* U+0144 "ń" */ + 0x0, + + /* U+0145 "Ņ" */ + 0x0, + + /* U+0146 "ņ" */ + 0x0, + + /* U+0147 "Ň" */ + 0x0, + + /* U+0148 "ň" */ + 0x0, + + /* U+0149 "ʼn" */ + 0x0, + + /* U+014A "Ŋ" */ + 0x0, + + /* U+014B "ŋ" */ + 0x0, + + /* U+014C "Ō" */ + 0x0, + + /* U+014D "ō" */ + 0x0, + + /* U+014E "Ŏ" */ + 0x0, + + /* U+014F "ŏ" */ + 0x0, + + /* U+0150 "Ő" */ + 0x0, + + /* U+0151 "ő" */ + 0x0, + + /* U+0152 "Œ" */ + 0x0, + + /* U+0153 "œ" */ + 0x0, + + /* U+0154 "Ŕ" */ + 0x0, + + /* U+0155 "ŕ" */ + 0x0, + + /* U+0156 "Ŗ" */ + 0x0, + + /* U+0157 "ŗ" */ + 0x0, + + /* U+0158 "Ř" */ + 0x0, + + /* U+0159 "ř" */ + 0x0, + + /* U+015A "Ś" */ + 0x0, + + /* U+015B "ś" */ + 0x0, + + /* U+015C "Ŝ" */ + 0x0, + + /* U+015D "ŝ" */ + 0x0, + + /* U+015E "Ş" */ + 0x0, + + /* U+015F "ş" */ + 0x0, + + /* U+0160 "Š" */ + 0x0, + + /* U+0161 "š" */ + 0x0, + + /* U+0162 "Ţ" */ + 0x0, + + /* U+0163 "ţ" */ + 0x0, + + /* U+0164 "Ť" */ + 0x0, + + /* U+0165 "ť" */ + 0x0, + + /* U+0166 "Ŧ" */ + 0x0, + + /* U+0167 "ŧ" */ + 0x0, + + /* U+0168 "Ũ" */ + 0x0, + + /* U+0169 "ũ" */ + 0x0, + + /* U+016A "Ū" */ + 0x0, + + /* U+016B "ū" */ + 0x0, + + /* U+016C "Ŭ" */ + 0x0, + + /* U+016D "ŭ" */ + 0x0, + + /* U+016E "Ů" */ + 0x0, + + /* U+016F "ů" */ + 0x0, + + /* U+0170 "Ű" */ + 0x0, + + /* U+0171 "ű" */ + 0x0, + + /* U+0172 "Ų" */ + 0x0, + + /* U+0173 "ų" */ + 0x0, + + /* U+0174 "Ŵ" */ + 0x0, + + /* U+0175 "ŵ" */ + 0x0, + + /* U+0176 "Ŷ" */ + 0x0, + + /* U+0177 "ŷ" */ + 0x0, + + /* U+0178 "Ÿ" */ + 0x0, + + /* U+0179 "Ź" */ + 0x0, + + /* U+017A "ź" */ + 0x0, + + /* U+017B "Ż" */ + 0x0, + + /* U+017C "ż" */ + 0x0, + + /* U+017D "Ž" */ + 0x0, + + /* U+017E "ž" */ + 0x0, + + /* U+017F "ſ" */ + 0x0, + + /* U+0180 "ƀ" */ + 0x0, + + /* U+0181 "Ɓ" */ + 0x0, + + /* U+0182 "Ƃ" */ + 0x0, + + /* U+0183 "ƃ" */ + 0x0, + + /* U+0184 "Ƅ" */ + 0x0, + + /* U+0185 "ƅ" */ + 0x0, + + /* U+0186 "Ɔ" */ + 0x0, + + /* U+0187 "Ƈ" */ + 0x0, + + /* U+0188 "ƈ" */ + 0x0, + + /* U+0189 "Ɖ" */ + 0x0, + + /* U+018A "Ɗ" */ + 0x0, + + /* U+018B "Ƌ" */ + 0x0, + + /* U+018C "ƌ" */ + 0x0, + + /* U+018D "ƍ" */ + 0x0, + + /* U+018E "Ǝ" */ + 0x0, + + /* U+018F "Ə" */ + 0x0, + + /* U+0190 "Ɛ" */ + 0x0, + + /* U+0191 "Ƒ" */ + 0x0, + + /* U+0192 "ƒ" */ + 0x0, + + /* U+0193 "Ɠ" */ + 0x0, + + /* U+0194 "Ɣ" */ + 0x0, + + /* U+0195 "ƕ" */ + 0x0, + + /* U+0196 "Ɩ" */ + 0x0, + + /* U+0197 "Ɨ" */ + 0x0, + + /* U+0198 "Ƙ" */ + 0x0, + + /* U+0199 "ƙ" */ + 0x0, + + /* U+019A "ƚ" */ + 0x0, + + /* U+019B "ƛ" */ + 0x0, + + /* U+019C "Ɯ" */ + 0x0, + + /* U+019D "Ɲ" */ + 0x0, + + /* U+019E "ƞ" */ + 0x0, + + /* U+019F "Ɵ" */ + 0x0, + + /* U+01A0 "Ơ" */ + 0x0, + + /* U+01A1 "ơ" */ + 0x0, + + /* U+01A2 "Ƣ" */ + 0x0, + + /* U+01A3 "ƣ" */ + 0x0, + + /* U+01A4 "Ƥ" */ + 0x0, + + /* U+01A5 "ƥ" */ + 0x0, + + /* U+01A6 "Ʀ" */ + 0x0, + + /* U+01A7 "Ƨ" */ + 0x0, + + /* U+01A8 "ƨ" */ + 0x0, + + /* U+01A9 "Ʃ" */ + 0x0, + + /* U+01AA "ƪ" */ + 0x0, + + /* U+01AB "ƫ" */ + 0x0, + + /* U+01AC "Ƭ" */ + 0x0, + + /* U+01AD "ƭ" */ + 0x0, + + /* U+01AE "Ʈ" */ + 0x0, + + /* U+01AF "Ư" */ + 0x0, + + /* U+01B0 "ư" */ + 0x0, + + /* U+01B1 "Ʊ" */ + 0x0, + + /* U+01B2 "Ʋ" */ + 0x0, + + /* U+01B3 "Ƴ" */ + 0x0, + + /* U+01B4 "ƴ" */ + 0x0, + + /* U+01B5 "Ƶ" */ + 0x0, + + /* U+01B6 "ƶ" */ + 0x0, + + /* U+01B7 "Ʒ" */ + 0x0, + + /* U+01B8 "Ƹ" */ + 0x0, + + /* U+01B9 "ƹ" */ + 0x0, + + /* U+01BA "ƺ" */ + 0x0, + + /* U+01BB "ƻ" */ + 0x0, + + /* U+01BC "Ƽ" */ + 0x0, + + /* U+01BD "ƽ" */ + 0x0, + + /* U+01BE "ƾ" */ + 0x0, + + /* U+01BF "ƿ" */ + 0x0, + + /* U+01C0 "ǀ" */ + 0x0, + + /* U+01C1 "ǁ" */ + 0x0, + + /* U+01C2 "ǂ" */ + 0x0, + + /* U+01C3 "ǃ" */ + 0x0, + + /* U+01C4 "DŽ" */ + 0x0, + + /* U+01C5 "Dž" */ + 0x0, + + /* U+01C6 "dž" */ + 0x0, + + /* U+01C7 "LJ" */ + 0x0, + + /* U+01C8 "Lj" */ + 0x0, + + /* U+01C9 "lj" */ + 0x0, + + /* U+01CA "NJ" */ + 0x0, + + /* U+01CB "Nj" */ + 0x0, + + /* U+01CC "nj" */ + 0x0, + + /* U+01CD "Ǎ" */ + 0x0, + + /* U+01CE "ǎ" */ + 0x0, + + /* U+01CF "Ǐ" */ + 0x0, + + /* U+01D0 "ǐ" */ + 0x0, + + /* U+01D1 "Ǒ" */ + 0x0, + + /* U+01D2 "ǒ" */ + 0x0, + + /* U+01D3 "Ǔ" */ + 0x0, + + /* U+01D4 "ǔ" */ + 0x0, + + /* U+01D5 "Ǖ" */ + 0x0, + + /* U+01D6 "ǖ" */ + 0x0, + + /* U+01D7 "Ǘ" */ + 0x0, + + /* U+01D8 "ǘ" */ + 0x0, + + /* U+01D9 "Ǚ" */ + 0x0, + + /* U+01DA "ǚ" */ + 0x0, + + /* U+01DB "Ǜ" */ + 0x0, + + /* U+01DC "ǜ" */ + 0x0, + + /* U+01DD "ǝ" */ + 0x0, + + /* U+01DE "Ǟ" */ + 0x0, + + /* U+01DF "ǟ" */ + 0x0, + + /* U+01E0 "Ǡ" */ + 0x0, + + /* U+01E1 "ǡ" */ + 0x0, + + /* U+01E2 "Ǣ" */ + 0x0, + + /* U+01E3 "ǣ" */ + 0x0, + + /* U+01E4 "Ǥ" */ + 0x0, + + /* U+01E5 "ǥ" */ + 0x0, + + /* U+01E6 "Ǧ" */ + 0x0, + + /* U+01E7 "ǧ" */ + 0x0, + + /* U+01E8 "Ǩ" */ + 0x0, + + /* U+01E9 "ǩ" */ + 0x0, + + /* U+01EA "Ǫ" */ + 0x0, + + /* U+01EB "ǫ" */ + 0x0, + + /* U+01EC "Ǭ" */ + 0x0, + + /* U+01ED "ǭ" */ + 0x0, + + /* U+01EE "Ǯ" */ + 0x0, + + /* U+01EF "ǯ" */ + 0x0, + + /* U+01F0 "ǰ" */ + 0x0, + + /* U+01F1 "DZ" */ + 0x0, + + /* U+01F2 "Dz" */ + 0x0, + + /* U+01F3 "dz" */ + 0x0, + + /* U+01F4 "Ǵ" */ + 0x0, + + /* U+01F5 "ǵ" */ + 0x0, + + /* U+01F6 "Ƕ" */ + 0x0, + + /* U+01F7 "Ƿ" */ + 0x0, + + /* U+01F8 "Ǹ" */ + 0x0, + + /* U+01F9 "ǹ" */ + 0x0, + + /* U+01FA "Ǻ" */ + 0x0, + + /* U+01FB "ǻ" */ + 0x0, + + /* U+01FC "Ǽ" */ + 0x0, + + /* U+01FD "ǽ" */ + 0x0, + + /* U+01FE "Ǿ" */ + 0x0, + + /* U+01FF "ǿ" */ + 0x0, + + /* U+0200 "Ȁ" */ + 0x0, + + /* U+0201 "ȁ" */ + 0x0, + + /* U+0202 "Ȃ" */ + 0x0, + + /* U+0203 "ȃ" */ + 0x0, + + /* U+0204 "Ȅ" */ + 0x0, + + /* U+0205 "ȅ" */ + 0x0, + + /* U+0206 "Ȇ" */ + 0x0, + + /* U+0207 "ȇ" */ + 0x0, + + /* U+0208 "Ȉ" */ + 0x0, + + /* U+0209 "ȉ" */ + 0x0, + + /* U+020A "Ȋ" */ + 0x0, + + /* U+020B "ȋ" */ + 0x0, + + /* U+020C "Ȍ" */ + 0x0, + + /* U+020D "ȍ" */ + 0x0, + + /* U+020E "Ȏ" */ + 0x0, + + /* U+020F "ȏ" */ + 0x0, + + /* U+0210 "Ȑ" */ + 0x0, + + /* U+0211 "ȑ" */ + 0x0, + + /* U+0212 "Ȓ" */ + 0x0, + + /* U+0213 "ȓ" */ + 0x0, + + /* U+0214 "Ȕ" */ + 0x0, + + /* U+0215 "ȕ" */ + 0x0, + + /* U+0216 "Ȗ" */ + 0x0, + + /* U+0217 "ȗ" */ + 0x0, + + /* U+0218 "Ș" */ + 0x0, + + /* U+0219 "ș" */ + 0x0, + + /* U+021A "Ț" */ + 0x0, + + /* U+021B "ț" */ + 0x0, + + /* U+021C "Ȝ" */ + 0x0, + + /* U+021D "ȝ" */ + 0x0, + + /* U+021E "Ȟ" */ + 0x0, + + /* U+021F "ȟ" */ + 0x0, + + /* U+0220 "Ƞ" */ + 0x0, + + /* U+0221 "ȡ" */ + 0x0, + + /* U+0222 "Ȣ" */ + 0x0, + + /* U+0223 "ȣ" */ + 0x0, + + /* U+0224 "Ȥ" */ + 0x0, + + /* U+0225 "ȥ" */ + 0x0, + + /* U+0226 "Ȧ" */ + 0x0, + + /* U+0227 "ȧ" */ + 0x0, + + /* U+0228 "Ȩ" */ + 0x0, + + /* U+0229 "ȩ" */ + 0x0, + + /* U+022A "Ȫ" */ + 0x0, + + /* U+022B "ȫ" */ + 0x0, + + /* U+022C "Ȭ" */ + 0x0, + + /* U+022D "ȭ" */ + 0x0, + + /* U+022E "Ȯ" */ + 0x0, + + /* U+022F "ȯ" */ + 0x0, + + /* U+0230 "Ȱ" */ + 0x0, + + /* U+0231 "ȱ" */ + 0x0, + + /* U+0232 "Ȳ" */ + 0x0, + + /* U+0233 "ȳ" */ + 0x0, + + /* U+0234 "ȴ" */ + 0x0, + + /* U+0235 "ȵ" */ + 0x0, + + /* U+0236 "ȶ" */ + 0x0, + + /* U+0237 "ȷ" */ + 0x0, + + /* U+0238 "ȸ" */ + 0x0, + + /* U+0239 "ȹ" */ + 0x0, + + /* U+023A "Ⱥ" */ + 0x0, + + /* U+023B "Ȼ" */ + 0x0, + + /* U+023C "ȼ" */ + 0x0, + + /* U+023D "Ƚ" */ + 0x0, + + /* U+023E "Ⱦ" */ + 0x0, + + /* U+023F "ȿ" */ + 0x0, + + /* U+0240 "ɀ" */ + 0x0, + + /* U+0241 "Ɂ" */ + 0x0, + + /* U+0242 "ɂ" */ + 0x0, + + /* U+0243 "Ƀ" */ + 0x0, + + /* U+0244 "Ʉ" */ + 0x0, + + /* U+0245 "Ʌ" */ + 0x0, + + /* U+0246 "Ɇ" */ + 0x0, + + /* U+0247 "ɇ" */ + 0x0, + + /* U+0248 "Ɉ" */ + 0x0, + + /* U+0249 "ɉ" */ + 0x0, + + /* U+024A "Ɋ" */ + 0x0, + + /* U+024B "ɋ" */ + 0x0, + + /* U+024C "Ɍ" */ + 0x0, + + /* U+024D "ɍ" */ + 0x0, + + /* U+024E "Ɏ" */ + 0x0, + + /* U+024F "ɏ" */ + 0x0, + + /* U+0250 "ɐ" */ + 0x0, + + /* U+0251 "ɑ" */ + 0x0, + + /* U+0252 "ɒ" */ + 0x0, + + /* U+0253 "ɓ" */ + 0x0, + + /* U+0254 "ɔ" */ + 0x0, + + /* U+0255 "ɕ" */ + 0x0, + + /* U+0256 "ɖ" */ + 0x0, + + /* U+0257 "ɗ" */ + 0x0, + + /* U+0258 "ɘ" */ + 0x0, + + /* U+0259 "ə" */ + 0x0, + + /* U+025A "ɚ" */ + 0x0, + + /* U+025B "ɛ" */ + 0x0, + + /* U+025C "ɜ" */ + 0x0, + + /* U+025D "ɝ" */ + 0x0, + + /* U+025E "ɞ" */ + 0x0, + + /* U+025F "ɟ" */ + 0x0, + + /* U+0260 "ɠ" */ + 0x0, + + /* U+0261 "ɡ" */ + 0x0, + + /* U+0262 "ɢ" */ + 0x0, + + /* U+0263 "ɣ" */ + 0x0, + + /* U+0264 "ɤ" */ + 0x0, + + /* U+0265 "ɥ" */ + 0x0, + + /* U+0266 "ɦ" */ + 0x0, + + /* U+0267 "ɧ" */ + 0x0, + + /* U+0268 "ɨ" */ + 0x0, + + /* U+0269 "ɩ" */ + 0x0, + + /* U+026A "ɪ" */ + 0x0, + + /* U+026B "ɫ" */ + 0x0, + + /* U+026C "ɬ" */ + 0x0, + + /* U+026D "ɭ" */ + 0x0, + + /* U+026E "ɮ" */ + 0x0, + + /* U+026F "ɯ" */ + 0x0, + + /* U+0270 "ɰ" */ + 0x0, + + /* U+0271 "ɱ" */ + 0x0, + + /* U+0272 "ɲ" */ + 0x0, + + /* U+0273 "ɳ" */ + 0x0, + + /* U+0274 "ɴ" */ + 0x0, + + /* U+0275 "ɵ" */ + 0x0, + + /* U+0276 "ɶ" */ + 0x0, + + /* U+0277 "ɷ" */ + 0x0, + + /* U+0278 "ɸ" */ + 0x0, + + /* U+0279 "ɹ" */ + 0x0, + + /* U+027A "ɺ" */ + 0x0, + + /* U+027B "ɻ" */ + 0x0, + + /* U+027C "ɼ" */ + 0x0, + + /* U+027D "ɽ" */ + 0x0, + + /* U+027E "ɾ" */ + 0x0, + + /* U+027F "ɿ" */ + 0x0, + + /* U+0280 "ʀ" */ + 0x0, + + /* U+0281 "ʁ" */ + 0x0, + + /* U+0282 "ʂ" */ + 0x0, + + /* U+0283 "ʃ" */ + 0x0, + + /* U+0284 "ʄ" */ + 0x0, + + /* U+0285 "ʅ" */ + 0x0, + + /* U+0286 "ʆ" */ + 0x0, + + /* U+0287 "ʇ" */ + 0x0, + + /* U+0288 "ʈ" */ + 0x0, + + /* U+0289 "ʉ" */ + 0x0, + + /* U+028A "ʊ" */ + 0x0, + + /* U+028B "ʋ" */ + 0x0, + + /* U+028C "ʌ" */ + 0x0, + + /* U+028D "ʍ" */ + 0x0, + + /* U+028E "ʎ" */ + 0x0, + + /* U+028F "ʏ" */ + 0x0, + + /* U+0290 "ʐ" */ + 0x0, + + /* U+0291 "ʑ" */ + 0x0, + + /* U+0292 "ʒ" */ + 0x0, + + /* U+0293 "ʓ" */ + 0x0, + + /* U+0294 "ʔ" */ + 0x0, + + /* U+0295 "ʕ" */ + 0x0, + + /* U+0296 "ʖ" */ + 0x0, + + /* U+0297 "ʗ" */ + 0x0, + + /* U+0298 "ʘ" */ + 0x0, + + /* U+0299 "ʙ" */ + 0x0, + + /* U+029A "ʚ" */ + 0x0, + + /* U+029B "ʛ" */ + 0x0, + + /* U+029C "ʜ" */ + 0x0, + + /* U+029D "ʝ" */ + 0x0, + + /* U+029E "ʞ" */ + 0x0, + + /* U+029F "ʟ" */ + 0x0, + + /* U+02A0 "ʠ" */ + 0x0, + + /* U+02A1 "ʡ" */ + 0x0, + + /* U+02A2 "ʢ" */ + 0x0, + + /* U+02A3 "ʣ" */ + 0x0, + + /* U+02A4 "ʤ" */ + 0x0, + + /* U+02A5 "ʥ" */ + 0x0, + + /* U+02A6 "ʦ" */ + 0x0, + + /* U+02A7 "ʧ" */ + 0x0, + + /* U+02A8 "ʨ" */ + 0x0, + + /* U+02A9 "ʩ" */ + 0x0, + + /* U+02AA "ʪ" */ + 0x0, + + /* U+02AB "ʫ" */ + 0x0, + + /* U+02AC "ʬ" */ + 0x0, + + /* U+02AD "ʭ" */ + 0x0, + + /* U+02AE "ʮ" */ + 0x0, + + /* U+02AF "ʯ" */ + 0x0, + + /* U+02B0 "ʰ" */ + 0x0, + + /* U+02B1 "ʱ" */ + 0x0, + + /* U+02B2 "ʲ" */ + 0x0, + + /* U+02B3 "ʳ" */ + 0x0, + + /* U+02B4 "ʴ" */ + 0x0, + + /* U+02B5 "ʵ" */ + 0x0, + + /* U+02B6 "ʶ" */ + 0x0, + + /* U+02B7 "ʷ" */ + 0x0, + + /* U+02B8 "ʸ" */ + 0x0, + + /* U+02B9 "ʹ" */ + 0x0, + + /* U+02BA "ʺ" */ + 0x0, + + /* U+02BB "ʻ" */ + 0x0, + + /* U+02BC "ʼ" */ + 0x0, + + /* U+02BD "ʽ" */ + 0x0, + + /* U+02BE "ʾ" */ + 0x0, + + /* U+02BF "ʿ" */ + 0x0, + + /* U+02C0 "ˀ" */ + 0x0, + + /* U+02C1 "ˁ" */ + 0x0, + + /* U+02C2 "˂" */ + 0x0, + + /* U+02C3 "˃" */ + 0x0, + + /* U+02C4 "˄" */ + 0x0, + + /* U+02C5 "˅" */ + 0x0, + + /* U+02C6 "ˆ" */ + 0x0, + + /* U+02C7 "ˇ" */ + 0x0, + + /* U+02C8 "ˈ" */ + 0x0, + + /* U+02C9 "ˉ" */ + 0x0, + + /* U+02CA "ˊ" */ + 0x0, + + /* U+02CB "ˋ" */ + 0x0, + + /* U+02CC "ˌ" */ + 0x0, + + /* U+02CD "ˍ" */ + 0x0, + + /* U+02CE "ˎ" */ + 0x0, + + /* U+02CF "ˏ" */ + 0x0, + + /* U+02D0 "ː" */ + 0x0, + + /* U+02D1 "ˑ" */ + 0x0, + + /* U+02D2 "˒" */ + 0x0, + + /* U+02D3 "˓" */ + 0x0, + + /* U+02D4 "˔" */ + 0x0, + + /* U+02D5 "˕" */ + 0x0, + + /* U+02D6 "˖" */ + 0x0, + + /* U+02D7 "˗" */ + 0x0, + + /* U+02D8 "˘" */ + 0x0, + + /* U+02D9 "˙" */ + 0x0, + + /* U+02DA "˚" */ + 0x0, + + /* U+02DB "˛" */ + 0x0, + + /* U+02DC "˜" */ + 0x0, + + /* U+02DD "˝" */ + 0x0, + + /* U+02DE "˞" */ + 0x0, + + /* U+02DF "˟" */ + 0x0, + + /* U+02E0 "ˠ" */ + 0x0, + + /* U+02E1 "ˡ" */ + 0x0, + + /* U+02E2 "ˢ" */ + 0x0, + + /* U+02E3 "ˣ" */ + 0x0, + + /* U+02E4 "ˤ" */ + 0x0, + + /* U+02E5 "˥" */ + 0x0, + + /* U+02E6 "˦" */ + 0x0, + + /* U+02E7 "˧" */ + 0x0, + + /* U+02E8 "˨" */ + 0x0, + + /* U+02E9 "˩" */ + 0x0, + + /* U+02EA "˪" */ + 0x0, + + /* U+02EB "˫" */ + 0x0, + + /* U+02EC "ˬ" */ + 0x0, + + /* U+02ED "˭" */ + 0x0, + + /* U+02EE "ˮ" */ + 0x0, + + /* U+02EF "˯" */ + 0x0, + + /* U+02F0 "˰" */ + 0x0, + + /* U+02F1 "˱" */ + 0x0, + + /* U+02F2 "˲" */ + 0x0, + + /* U+02F3 "˳" */ + 0x0, + + /* U+02F4 "˴" */ + 0x0, + + /* U+02F5 "˵" */ + 0x0, + + /* U+02F6 "˶" */ + 0x0, + + /* U+02F7 "˷" */ + 0x0, + + /* U+02F8 "˸" */ + 0x0, + + /* U+02F9 "˹" */ + 0x0, + + /* U+02FA "˺" */ + 0x0, + + /* U+02FB "˻" */ + 0x0, + + /* U+02FC "˼" */ + 0x0, + + /* U+02FD "˽" */ + 0x0, + + /* U+02FE "˾" */ + 0x0, + + /* U+02FF "˿" */ + 0x0, + + /* U+0300 "̀" */ + 0x0, + + /* U+0301 "́" */ + 0x0, + + /* U+0302 "̂" */ + 0x0, + + /* U+0303 "̃" */ + 0x0, + + /* U+0304 "̄" */ + 0x0, + + /* U+0305 "̅" */ + 0x0, + + /* U+0306 "̆" */ + 0x0, + + /* U+0307 "̇" */ + 0x0, + + /* U+0308 "̈" */ + 0x0, + + /* U+0309 "̉" */ + 0x0, + + /* U+030A "̊" */ + 0x0, + + /* U+030B "̋" */ + 0x0, + + /* U+030C "̌" */ + 0x0, + + /* U+030D "̍" */ + 0x0, + + /* U+030E "̎" */ + 0x0, + + /* U+030F "̏" */ + 0x0, + + /* U+0310 "̐" */ + 0x0, + + /* U+0311 "̑" */ + 0x0, + + /* U+0312 "̒" */ + 0x0, + + /* U+0313 "̓" */ + 0x0, + + /* U+0314 "̔" */ + 0x0, + + /* U+0315 "̕" */ + 0x0, + + /* U+0316 "̖" */ + 0x0, + + /* U+0317 "̗" */ + 0x0, + + /* U+0318 "̘" */ + 0x0, + + /* U+0319 "̙" */ + 0x0, + + /* U+031A "̚" */ + 0x0, + + /* U+031B "̛" */ + 0x0, + + /* U+031C "̜" */ + 0x0, + + /* U+031D "̝" */ + 0x0, + + /* U+031E "̞" */ + 0x0, + + /* U+031F "̟" */ + 0x0, + + /* U+0320 "̠" */ + 0x0, + + /* U+0321 "̡" */ + 0x0, + + /* U+0322 "̢" */ + 0x0, + + /* U+0323 "̣" */ + 0x0, + + /* U+0324 "̤" */ + 0x0, + + /* U+0325 "̥" */ + 0x0, + + /* U+0326 "̦" */ + 0x0, + + /* U+0327 "̧" */ + 0x0, + + /* U+0328 "̨" */ + 0x0, + + /* U+0329 "̩" */ + 0x0, + + /* U+032A "̪" */ + 0x0, + + /* U+032B "̫" */ + 0x0, + + /* U+032C "̬" */ + 0x0, + + /* U+032D "̭" */ + 0x0, + + /* U+032E "̮" */ + 0x0, + + /* U+032F "̯" */ + 0x0, + + /* U+0330 "̰" */ + 0x0, + + /* U+0331 "̱" */ + 0x0, + + /* U+0332 "̲" */ + 0x0, + + /* U+0333 "̳" */ + 0x0, + + /* U+0334 "̴" */ + 0x0, + + /* U+0335 "̵" */ + 0x0, + + /* U+0336 "̶" */ + 0x0, + + /* U+0337 "̷" */ + 0x0, + + /* U+0338 "̸" */ + 0x0, + + /* U+0339 "̹" */ + 0x0, + + /* U+033A "̺" */ + 0x0, + + /* U+033B "̻" */ + 0x0, + + /* U+033C "̼" */ + 0x0, + + /* U+033D "̽" */ + 0x0, + + /* U+033E "̾" */ + 0x0, + + /* U+033F "̿" */ + 0x0, + + /* U+0340 "̀" */ + 0x0, + + /* U+0341 "́" */ + 0x0, + + /* U+0342 "͂" */ + 0x0, + + /* U+0343 "̓" */ + 0x0, + + /* U+0344 "̈́" */ + 0x0, + + /* U+0345 "ͅ" */ + 0x0, + + /* U+0346 "͆" */ + 0x0, + + /* U+0347 "͇" */ + 0x0, + + /* U+0348 "͈" */ + 0x0, + + /* U+0349 "͉" */ + 0x0, + + /* U+034A "͊" */ + 0x0, + + /* U+034B "͋" */ + 0x0, + + /* U+034C "͌" */ + 0x0, + + /* U+034D "͍" */ + 0x0, + + /* U+034E "͎" */ + 0x0, + + /* U+034F "͏" */ + 0x0, + + /* U+0350 "͐" */ + 0x0, + + /* U+0351 "͑" */ + 0x0, + + /* U+0352 "͒" */ + 0x0, + + /* U+0353 "͓" */ + 0x0, + + /* U+0354 "͔" */ + 0x0, + + /* U+0355 "͕" */ + 0x0, + + /* U+0356 "͖" */ + 0x0, + + /* U+0357 "͗" */ + 0x0, + + /* U+0358 "͘" */ + 0x0, + + /* U+0359 "͙" */ + 0x0, + + /* U+035A "͚" */ + 0x0, + + /* U+035B "͛" */ + 0x0, + + /* U+035C "͜" */ + 0x0, + + /* U+035D "͝" */ + 0x0, + + /* U+035E "͞" */ + 0x0, + + /* U+035F "͟" */ + 0x0, + + /* U+0360 "͠" */ + 0x0, + + /* U+0361 "͡" */ + 0x0, + + /* U+0362 "͢" */ + 0x0, + + /* U+0363 "ͣ" */ + 0x0, + + /* U+0364 "ͤ" */ + 0x0, + + /* U+0365 "ͥ" */ + 0x0, + + /* U+0366 "ͦ" */ + 0x0, + + /* U+0367 "ͧ" */ + 0x0, + + /* U+0368 "ͨ" */ + 0x0, + + /* U+0369 "ͩ" */ + 0x0, + + /* U+036A "ͪ" */ + 0x0, + + /* U+036B "ͫ" */ + 0x0, + + /* U+036C "ͬ" */ + 0x0, + + /* U+036D "ͭ" */ + 0x0, + + /* U+036E "ͮ" */ + 0x0, + + /* U+036F "ͯ" */ + 0x0, + + /* U+0370 "Ͱ" */ + 0x0, + + /* U+0371 "ͱ" */ + 0x0, + + /* U+0372 "Ͳ" */ + 0x0, + + /* U+0373 "ͳ" */ + 0x0, + + /* U+0374 "ʹ" */ + 0x0, + + /* U+0375 "͵" */ + 0x0, + + /* U+0376 "Ͷ" */ + 0x0, + + /* U+0377 "ͷ" */ + 0x0, + + /* U+0378 "͸" */ + 0x0, + + /* U+0379 "͹" */ + 0x0, + + /* U+037A "ͺ" */ + 0x0, + + /* U+037B "ͻ" */ + 0x0, + + /* U+037C "ͼ" */ + 0x0, + + /* U+037D "ͽ" */ + 0x0, + + /* U+037E ";" */ + 0x0, + + /* U+037F "Ϳ" */ + 0x0, + + /* U+0380 "΀" */ + 0x0, + + /* U+0381 "΁" */ + 0x0, + + /* U+0382 "΂" */ + 0x0, + + /* U+0383 "΃" */ + 0x0, + + /* U+0384 "΄" */ + 0x0, + + /* U+0385 "΅" */ + 0x0, + + /* U+0386 "Ά" */ + 0x0, + + /* U+0387 "·" */ + 0x0, + + /* U+0388 "Έ" */ + 0x0, + + /* U+0389 "Ή" */ + 0x0, + + /* U+038A "Ί" */ + 0x0, + + /* U+038B "΋" */ + 0x0, + + /* U+038C "Ό" */ + 0x0, + + /* U+038D "΍" */ + 0x0, + + /* U+038E "Ύ" */ + 0x0, + + /* U+038F "Ώ" */ + 0x0, + + /* U+0390 "ΐ" */ + 0x0, + + /* U+0391 "Α" */ + 0x0, + + /* U+0392 "Β" */ + 0x0, + + /* U+0393 "Γ" */ + 0x0, + + /* U+0394 "Δ" */ + 0x0, + + /* U+0395 "Ε" */ + 0x0, + + /* U+0396 "Ζ" */ + 0x0, + + /* U+0397 "Η" */ + 0x0, + + /* U+0398 "Θ" */ + 0x0, + + /* U+0399 "Ι" */ + 0x0, + + /* U+039A "Κ" */ + 0x0, + + /* U+039B "Λ" */ + 0x0, + + /* U+039C "Μ" */ + 0x0, + + /* U+039D "Ν" */ + 0x0, + + /* U+039E "Ξ" */ + 0x0, + + /* U+039F "Ο" */ + 0x0, + + /* U+03A0 "Π" */ + 0x0, + + /* U+03A1 "Ρ" */ + 0x0, + + /* U+03A2 "΢" */ + 0x0, + + /* U+03A3 "Σ" */ + 0x0, + + /* U+03A4 "Τ" */ + 0x0, + + /* U+03A5 "Υ" */ + 0x0, + + /* U+03A6 "Φ" */ + 0x0, + + /* U+03A7 "Χ" */ + 0x0, + + /* U+03A8 "Ψ" */ + 0x0, + + /* U+03A9 "Ω" */ + 0x0, + + /* U+03AA "Ϊ" */ + 0x0, + + /* U+03AB "Ϋ" */ + 0x0, + + /* U+03AC "ά" */ + 0x0, + + /* U+03AD "έ" */ + 0x0, + + /* U+03AE "ή" */ + 0x0, + + /* U+03AF "ί" */ + 0x0, + + /* U+03B0 "ΰ" */ + 0x0, + + /* U+03B1 "α" */ + 0x0, + + /* U+03B2 "β" */ + 0x0, + + /* U+03B3 "γ" */ + 0x0, + + /* U+03B4 "δ" */ + 0x0, + + /* U+03B5 "ε" */ + 0x0, + + /* U+03B6 "ζ" */ + 0x0, + + /* U+03B7 "η" */ + 0x0, + + /* U+03B8 "θ" */ + 0x0, + + /* U+03B9 "ι" */ + 0x0, + + /* U+03BA "κ" */ + 0x0, + + /* U+03BB "λ" */ + 0x0, + + /* U+03BC "μ" */ + 0x0, + + /* U+03BD "ν" */ + 0x0, + + /* U+03BE "ξ" */ + 0x0, + + /* U+03BF "ο" */ + 0x0, + + /* U+03C0 "π" */ + 0x0, + + /* U+03C1 "ρ" */ + 0x0, + + /* U+03C2 "ς" */ + 0x0, + + /* U+03C3 "σ" */ + 0x0, + + /* U+03C4 "τ" */ + 0x0, + + /* U+03C5 "υ" */ + 0x0, + + /* U+03C6 "φ" */ + 0x0, + + /* U+03C7 "χ" */ + 0x0, + + /* U+03C8 "ψ" */ + 0x0, + + /* U+03C9 "ω" */ + 0x0, + + /* U+03CA "ϊ" */ + 0x0, + + /* U+03CB "ϋ" */ + 0x0, + + /* U+03CC "ό" */ + 0x0, + + /* U+03CD "ύ" */ + 0x0, + + /* U+03CE "ώ" */ + 0x0, + + /* U+03CF "Ϗ" */ + 0x0, + + /* U+03D0 "ϐ" */ + 0x0, + + /* U+03D1 "ϑ" */ + 0x0, + + /* U+03D2 "ϒ" */ + 0x0, + + /* U+03D3 "ϓ" */ + 0x0, + + /* U+03D4 "ϔ" */ + 0x0, + + /* U+03D5 "ϕ" */ + 0x0, + + /* U+03D6 "ϖ" */ + 0x0, + + /* U+03D7 "ϗ" */ + 0x0, + + /* U+03D8 "Ϙ" */ + 0x0, + + /* U+03D9 "ϙ" */ + 0x0, + + /* U+03DA "Ϛ" */ + 0x0, + + /* U+03DB "ϛ" */ + 0x0, + + /* U+03DC "Ϝ" */ + 0x0, + + /* U+03DD "ϝ" */ + 0x0, + + /* U+03DE "Ϟ" */ + 0x0, + + /* U+03DF "ϟ" */ + 0x0, + + /* U+03E0 "Ϡ" */ + 0x0, + + /* U+03E1 "ϡ" */ + 0x0, + + /* U+03E2 "Ϣ" */ + 0x0, + + /* U+03E3 "ϣ" */ + 0x0, + + /* U+03E4 "Ϥ" */ + 0x0, + + /* U+03E5 "ϥ" */ + 0x0, + + /* U+03E6 "Ϧ" */ + 0x0, + + /* U+03E7 "ϧ" */ + 0x0, + + /* U+03E8 "Ϩ" */ + 0x0, + + /* U+03E9 "ϩ" */ + 0x0, + + /* U+03EA "Ϫ" */ + 0x0, + + /* U+03EB "ϫ" */ + 0x0, + + /* U+03EC "Ϭ" */ + 0x0, + + /* U+03ED "ϭ" */ + 0x0, + + /* U+03EE "Ϯ" */ + 0x0, + + /* U+03EF "ϯ" */ + 0x0, + + /* U+03F0 "ϰ" */ + 0x0, + + /* U+03F1 "ϱ" */ + 0x0, + + /* U+03F2 "ϲ" */ + 0x0, + + /* U+03F3 "ϳ" */ + 0x0, + + /* U+03F4 "ϴ" */ + 0x0, + + /* U+03F5 "ϵ" */ + 0x0, + + /* U+03F6 "϶" */ + 0x0, + + /* U+03F7 "Ϸ" */ + 0x0, + + /* U+03F8 "ϸ" */ + 0x0, + + /* U+03F9 "Ϲ" */ + 0x0, + + /* U+03FA "Ϻ" */ + 0x0, + + /* U+03FB "ϻ" */ + 0x0, + + /* U+03FC "ϼ" */ + 0x0, + + /* U+03FD "Ͻ" */ + 0x0, + + /* U+03FE "Ͼ" */ + 0x0, + + /* U+03FF "Ͽ" */ + 0x0, + + /* U+0400 "Ѐ" */ + 0x0, + + /* U+0401 "Ё" */ + 0x0, + + /* U+0402 "Ђ" */ + 0x0, + + /* U+0403 "Ѓ" */ + 0x0, + + /* U+0404 "Є" */ + 0x0, + + /* U+0405 "Ѕ" */ + 0x0, + + /* U+0406 "І" */ + 0x0, + + /* U+0407 "Ї" */ + 0x0, + + /* U+0408 "Ј" */ + 0x0, + + /* U+0409 "Љ" */ + 0x0, + + /* U+040A "Њ" */ + 0x0, + + /* U+040B "Ћ" */ + 0x0, + + /* U+040C "Ќ" */ + 0x0, + + /* U+040D "Ѝ" */ + 0x0, + + /* U+040E "Ў" */ + 0x0, + + /* U+040F "Џ" */ + 0x0, + + /* U+0410 "А" */ + 0x0, + + /* U+0411 "Б" */ + 0x0, + + /* U+0412 "В" */ + 0x0, + + /* U+0413 "Г" */ + 0x0, + + /* U+0414 "Д" */ + 0x0, + + /* U+0415 "Е" */ + 0x0, + + /* U+0416 "Ж" */ + 0x0, + + /* U+0417 "З" */ + 0x0, + + /* U+0418 "И" */ + 0x0, + + /* U+0419 "Й" */ + 0x0, + + /* U+041A "К" */ + 0x0, + + /* U+041B "Л" */ + 0x0, + + /* U+041C "М" */ + 0x0, + + /* U+041D "Н" */ + 0x0, + + /* U+041E "О" */ + 0x0, + + /* U+041F "П" */ + 0x0, + + /* U+0420 "Р" */ + 0x0, + + /* U+0421 "С" */ + 0x0, + + /* U+0422 "Т" */ + 0x0, + + /* U+0423 "У" */ + 0x0, + + /* U+0424 "Ф" */ + 0x0, + + /* U+0425 "Х" */ + 0x0, + + /* U+0426 "Ц" */ + 0x0, + + /* U+0427 "Ч" */ + 0x0, + + /* U+0428 "Ш" */ + 0x0, + + /* U+0429 "Щ" */ + 0x0, + + /* U+042A "Ъ" */ + 0x0, + + /* U+042B "Ы" */ + 0x0, + + /* U+042C "Ь" */ + 0x0, + + /* U+042D "Э" */ + 0x0, + + /* U+042E "Ю" */ + 0x0, + + /* U+042F "Я" */ + 0x0, + + /* U+0430 "а" */ + 0x0, + + /* U+0431 "б" */ + 0x0, + + /* U+0432 "в" */ + 0x0, + + /* U+0433 "г" */ + 0x0, + + /* U+0434 "д" */ + 0x0, + + /* U+0435 "е" */ + 0x0, + + /* U+0436 "ж" */ + 0x0, + + /* U+0437 "з" */ + 0x0, + + /* U+0438 "и" */ + 0x0, + + /* U+0439 "й" */ + 0x0, + + /* U+043A "к" */ + 0x0, + + /* U+043B "л" */ + 0x0, + + /* U+043C "м" */ + 0x0, + + /* U+043D "н" */ + 0x0, + + /* U+043E "о" */ + 0x0, + + /* U+043F "п" */ + 0x0, + + /* U+0440 "р" */ + 0x0, + + /* U+0441 "с" */ + 0x0, + + /* U+0442 "т" */ + 0x0, + + /* U+0443 "у" */ + 0x0, + + /* U+0444 "ф" */ + 0x0, + + /* U+0445 "х" */ + 0x0, + + /* U+0446 "ц" */ + 0x0, + + /* U+0447 "ч" */ + 0x0, + + /* U+0448 "ш" */ + 0x0, + + /* U+0449 "щ" */ + 0x0, + + /* U+044A "ъ" */ + 0x0, + + /* U+044B "ы" */ + 0x0, + + /* U+044C "ь" */ + 0x0, + + /* U+044D "э" */ + 0x0, + + /* U+044E "ю" */ + 0x0, + + /* U+044F "я" */ + 0x0, + + /* U+0450 "ѐ" */ + 0x0, + + /* U+0451 "ё" */ + 0x0, + + /* U+0452 "ђ" */ + 0x0, + + /* U+0453 "ѓ" */ + 0x0, + + /* U+0454 "є" */ + 0x0, + + /* U+0455 "ѕ" */ + 0x0, + + /* U+0456 "і" */ + 0x0, + + /* U+0457 "ї" */ + 0x0, + + /* U+0458 "ј" */ + 0x0, + + /* U+0459 "љ" */ + 0x0, + + /* U+045A "њ" */ + 0x0, + + /* U+045B "ћ" */ + 0x0, + + /* U+045C "ќ" */ + 0x0, + + /* U+045D "ѝ" */ + 0x0, + + /* U+045E "ў" */ + 0x0, + + /* U+045F "џ" */ + 0x0, + + /* U+0460 "Ѡ" */ + 0x0, + + /* U+0461 "ѡ" */ + 0x0, + + /* U+0462 "Ѣ" */ + 0x0, + + /* U+0463 "ѣ" */ + 0x0, + + /* U+0464 "Ѥ" */ + 0x0, + + /* U+0465 "ѥ" */ + 0x0, + + /* U+0466 "Ѧ" */ + 0x0, + + /* U+0467 "ѧ" */ + 0x0, + + /* U+0468 "Ѩ" */ + 0x0, + + /* U+0469 "ѩ" */ + 0x0, + + /* U+046A "Ѫ" */ + 0x0, + + /* U+046B "ѫ" */ + 0x0, + + /* U+046C "Ѭ" */ + 0x0, + + /* U+046D "ѭ" */ + 0x0, + + /* U+046E "Ѯ" */ + 0x0, + + /* U+046F "ѯ" */ + 0x0, + + /* U+0470 "Ѱ" */ + 0x0, + + /* U+0471 "ѱ" */ + 0x0, + + /* U+0472 "Ѳ" */ + 0x0, + + /* U+0473 "ѳ" */ + 0x0, + + /* U+0474 "Ѵ" */ + 0x0, + + /* U+0475 "ѵ" */ + 0x0, + + /* U+0476 "Ѷ" */ + 0x0, + + /* U+0477 "ѷ" */ + 0x0, + + /* U+0478 "Ѹ" */ + 0x0, + + /* U+0479 "ѹ" */ + 0x0, + + /* U+047A "Ѻ" */ + 0x0, + + /* U+047B "ѻ" */ + 0x0, + + /* U+047C "Ѽ" */ + 0x0, + + /* U+047D "ѽ" */ + 0x0, + + /* U+047E "Ѿ" */ + 0x0, + + /* U+047F "ѿ" */ + 0x0, + + /* U+0480 "Ҁ" */ + 0x0, + + /* U+0481 "ҁ" */ + 0x0, + + /* U+0482 "҂" */ + 0x0, + + /* U+0483 "҃" */ + 0x0, + + /* U+0484 "҄" */ + 0x0, + + /* U+0485 "҅" */ + 0x0, + + /* U+0486 "҆" */ + 0x0, + + /* U+0487 "҇" */ + 0x0, + + /* U+0488 "҈" */ + 0x0, + + /* U+0489 "҉" */ + 0x0, + + /* U+048A "Ҋ" */ + 0x0, + + /* U+048B "ҋ" */ + 0x0, + + /* U+048C "Ҍ" */ + 0x0, + + /* U+048D "ҍ" */ + 0x0, + + /* U+048E "Ҏ" */ + 0x0, + + /* U+048F "ҏ" */ + 0x0, + + /* U+0490 "Ґ" */ + 0x0, + + /* U+0491 "ґ" */ + 0x0, + + /* U+0492 "Ғ" */ + 0x0, + + /* U+0493 "ғ" */ + 0x0, + + /* U+0494 "Ҕ" */ + 0x0, + + /* U+0495 "ҕ" */ + 0x0, + + /* U+0496 "Җ" */ + 0x0, + + /* U+0497 "җ" */ + 0x0, + + /* U+0498 "Ҙ" */ + 0x0, + + /* U+0499 "ҙ" */ + 0x0, + + /* U+049A "Қ" */ + 0x0, + + /* U+049B "қ" */ + 0x0, + + /* U+049C "Ҝ" */ + 0x0, + + /* U+049D "ҝ" */ + 0x0, + + /* U+049E "Ҟ" */ + 0x0, + + /* U+049F "ҟ" */ + 0x0, + + /* U+04A0 "Ҡ" */ + 0x0, + + /* U+04A1 "ҡ" */ + 0x0, + + /* U+04A2 "Ң" */ + 0x0, + + /* U+04A3 "ң" */ + 0x0, + + /* U+04A4 "Ҥ" */ + 0x0, + + /* U+04A5 "ҥ" */ + 0x0, + + /* U+04A6 "Ҧ" */ + 0x0, + + /* U+04A7 "ҧ" */ + 0x0, + + /* U+04A8 "Ҩ" */ + 0x0, + + /* U+04A9 "ҩ" */ + 0x0, + + /* U+04AA "Ҫ" */ + 0x0, + + /* U+04AB "ҫ" */ + 0x0, + + /* U+04AC "Ҭ" */ + 0x0, + + /* U+04AD "ҭ" */ + 0x0, + + /* U+04AE "Ү" */ + 0x0, + + /* U+04AF "ү" */ + 0x0, + + /* U+04B0 "Ұ" */ + 0x0, + + /* U+04B1 "ұ" */ + 0x0, + + /* U+04B2 "Ҳ" */ + 0x0, + + /* U+04B3 "ҳ" */ + 0x0, + + /* U+04B4 "Ҵ" */ + 0x0, + + /* U+04B5 "ҵ" */ + 0x0, + + /* U+04B6 "Ҷ" */ + 0x0, + + /* U+04B7 "ҷ" */ + 0x0, + + /* U+04B8 "Ҹ" */ + 0x0, + + /* U+04B9 "ҹ" */ + 0x0, + + /* U+04BA "Һ" */ + 0x0, + + /* U+04BB "һ" */ + 0x0, + + /* U+04BC "Ҽ" */ + 0x0, + + /* U+04BD "ҽ" */ + 0x0, + + /* U+04BE "Ҿ" */ + 0x0, + + /* U+04BF "ҿ" */ + 0x0, + + /* U+04C0 "Ӏ" */ + 0x0, + + /* U+04C1 "Ӂ" */ + 0x0, + + /* U+04C2 "ӂ" */ + 0x0, + + /* U+04C3 "Ӄ" */ + 0x0, + + /* U+04C4 "ӄ" */ + 0x0, + + /* U+04C5 "Ӆ" */ + 0x0, + + /* U+04C6 "ӆ" */ + 0x0, + + /* U+04C7 "Ӈ" */ + 0x0, + + /* U+04C8 "ӈ" */ + 0x0, + + /* U+04C9 "Ӊ" */ + 0x0, + + /* U+04CA "ӊ" */ + 0x0, + + /* U+04CB "Ӌ" */ + 0x0, + + /* U+04CC "ӌ" */ + 0x0, + + /* U+04CD "Ӎ" */ + 0x0, + + /* U+04CE "ӎ" */ + 0x0, + + /* U+04CF "ӏ" */ + 0x0, + + /* U+04D0 "Ӑ" */ + 0x0, + + /* U+04D1 "ӑ" */ + 0x0, + + /* U+04D2 "Ӓ" */ + 0x0, + + /* U+04D3 "ӓ" */ + 0x0, + + /* U+04D4 "Ӕ" */ + 0x0, + + /* U+04D5 "ӕ" */ + 0x0, + + /* U+04D6 "Ӗ" */ + 0x0, + + /* U+04D7 "ӗ" */ + 0x0, + + /* U+04D8 "Ә" */ + 0x0, + + /* U+04D9 "ә" */ + 0x0, + + /* U+04DA "Ӛ" */ + 0x0, + + /* U+04DB "ӛ" */ + 0x0, + + /* U+04DC "Ӝ" */ + 0x0, + + /* U+04DD "ӝ" */ + 0x0, + + /* U+04DE "Ӟ" */ + 0x0, + + /* U+04DF "ӟ" */ + 0x0, + + /* U+04E0 "Ӡ" */ + 0x0, + + /* U+04E1 "ӡ" */ + 0x0, + + /* U+04E2 "Ӣ" */ + 0x0, + + /* U+04E3 "ӣ" */ + 0x0, + + /* U+04E4 "Ӥ" */ + 0x0, + + /* U+04E5 "ӥ" */ + 0x0, + + /* U+04E6 "Ӧ" */ + 0x0, + + /* U+04E7 "ӧ" */ + 0x0, + + /* U+04E8 "Ө" */ + 0x0, + + /* U+04E9 "ө" */ + 0x0, + + /* U+04EA "Ӫ" */ + 0x0, + + /* U+04EB "ӫ" */ + 0x0, + + /* U+04EC "Ӭ" */ + 0x0, + + /* U+04ED "ӭ" */ + 0x0, + + /* U+04EE "Ӯ" */ + 0x0, + + /* U+04EF "ӯ" */ + 0x0, + + /* U+04F0 "Ӱ" */ + 0x0, + + /* U+04F1 "ӱ" */ + 0x0, + + /* U+04F2 "Ӳ" */ + 0x0, + + /* U+04F3 "ӳ" */ + 0x0, + + /* U+04F4 "Ӵ" */ + 0x0, + + /* U+04F5 "ӵ" */ + 0x0, + + /* U+04F6 "Ӷ" */ + 0x0, + + /* U+04F7 "ӷ" */ + 0x0, + + /* U+04F8 "Ӹ" */ + 0x0, + + /* U+04F9 "ӹ" */ + 0x0, + + /* U+04FA "Ӻ" */ + 0x0, + + /* U+04FB "ӻ" */ + 0x0, + + /* U+04FC "Ӽ" */ + 0x0, + + /* U+04FD "ӽ" */ + 0x0, + + /* U+04FE "Ӿ" */ + 0x0, + + /* U+04FF "ӿ" */ + 0x0, + + /* U+0500 "Ԁ" */ + 0x0, + + /* U+0501 "ԁ" */ + 0x0, + + /* U+0502 "Ԃ" */ + 0x0, + + /* U+0503 "ԃ" */ + 0x0, + + /* U+0504 "Ԅ" */ + 0x0, + + /* U+0505 "ԅ" */ + 0x0, + + /* U+0506 "Ԇ" */ + 0x0, + + /* U+0507 "ԇ" */ + 0x0, + + /* U+0508 "Ԉ" */ + 0x0, + + /* U+0509 "ԉ" */ + 0x0, + + /* U+050A "Ԋ" */ + 0x0, + + /* U+050B "ԋ" */ + 0x0, + + /* U+050C "Ԍ" */ + 0x0, + + /* U+050D "ԍ" */ + 0x0, + + /* U+050E "Ԏ" */ + 0x0, + + /* U+050F "ԏ" */ + 0x0, + + /* U+0510 "Ԑ" */ + 0x0, + + /* U+0511 "ԑ" */ + 0x0, + + /* U+0512 "Ԓ" */ + 0x0, + + /* U+0513 "ԓ" */ + 0x0, + + /* U+0514 "Ԕ" */ + 0x0, + + /* U+0515 "ԕ" */ + 0x0, + + /* U+0516 "Ԗ" */ + 0x0, + + /* U+0517 "ԗ" */ + 0x0, + + /* U+0518 "Ԙ" */ + 0x0, + + /* U+0519 "ԙ" */ + 0x0, + + /* U+051A "Ԛ" */ + 0x0, + + /* U+051B "ԛ" */ + 0x0, + + /* U+051C "Ԝ" */ + 0x0, + + /* U+051D "ԝ" */ + 0x0, + + /* U+051E "Ԟ" */ + 0x0, + + /* U+051F "ԟ" */ + 0x0, + + /* U+0520 "Ԡ" */ + 0x0, + + /* U+0521 "ԡ" */ + 0x0, + + /* U+0522 "Ԣ" */ + 0x0, + + /* U+0523 "ԣ" */ + 0x0, + + /* U+0524 "Ԥ" */ + 0x0, + + /* U+0525 "ԥ" */ + 0x0, + + /* U+0526 "Ԧ" */ + 0x0, + + /* U+0527 "ԧ" */ + 0x0, + + /* U+0528 "Ԩ" */ + 0x0, + + /* U+0529 "ԩ" */ + 0x0, + + /* U+052A "Ԫ" */ + 0x0, + + /* U+052B "ԫ" */ + 0x0, + + /* U+052C "Ԭ" */ + 0x0, + + /* U+052D "ԭ" */ + 0x0, + + /* U+052E "Ԯ" */ + 0x0, + + /* U+052F "ԯ" */ + 0x0, + + /* U+200B "​" */ + 0x0, + + /* U+2010 "‐" */ + 0x0, + + /* U+2011 "‑" */ + 0x0, + + /* U+2013 "–" */ + 0xff, 0xff, 0xff, 0xf0, + + /* U+2014 "—" */ + 0x0, + + /* U+2018 "‘" */ + 0x0, + + /* U+2019 "’" */ + 0x0, + + /* U+201C "“" */ + 0x38, 0x39, 0xe1, 0xee, 0xe, 0x30, 0x30, 0xf8, + 0xfb, 0xf3, 0xff, 0xcf, 0xff, 0x3f, 0xfc, 0xfd, + 0xe1, 0xe0, + + /* U+201D "”" */ + 0x78, 0x7b, 0xf3, 0xff, 0xcf, 0xff, 0x3f, 0xfc, + 0xfd, 0xf1, 0xf0, 0xc0, 0xc7, 0x7, 0x78, 0x79, + 0xc1, 0xc0, + + /* U+2020 "†" */ + 0x0, + + /* U+2021 "‡" */ + 0x0, + + /* U+2026 "…" */ + 0x0, + + /* U+2030 "‰" */ + 0x0, + + /* U+2032 "′" */ + 0x0, + + /* U+2033 "″" */ + 0x0, + + /* U+2039 "‹" */ + 0x6, 0x1c, 0x71, 0xc7, 0x1c, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x30, + + /* U+203A "›" */ + 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc3, 0x8e, 0x38, + 0xe3, 0x86, 0x0, + + /* U+20A4 "₤" */ + 0xf, 0x83, 0xf8, 0xe3, 0x18, 0x3, 0x0, 0xff, + 0x9f, 0xf0, 0xc0, 0x7f, 0xcf, 0xf8, 0xc0, 0x38, + 0x7, 0xfe, 0xff, 0xc0, + + /* U+20A8 "₨" */ + 0xfc, 0x3, 0xf8, 0xc, 0x70, 0x30, 0xc0, 0xc3, + 0x3, 0xc, 0xc, 0x77, 0xbf, 0x9e, 0xfc, 0x63, + 0x31, 0xec, 0xc3, 0xf1, 0x83, 0xc6, 0x7f, 0x19, + 0xe0, + + /* U+20A9 "₩" */ + 0x0, 0x0, 0xc1, 0x83, 0xc1, 0x83, 0xe3, 0xc7, + 0x63, 0xc6, 0x63, 0xc6, 0x73, 0xce, 0xff, 0xff, + 0xff, 0xff, 0x36, 0x6c, 0x1e, 0x78, 0x1e, 0x78, + 0x1c, 0x38, 0xc, 0x30, 0xc, 0x30, + + /* U+20AA "₪" */ + 0xff, 0xf, 0xfe, 0x3c, 0x1c, 0xf0, 0x33, 0xcc, + 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, 0xcc, 0xcf, + 0x33, 0x3c, 0xc0, 0xf3, 0x7, 0xcf, 0xfb, 0x3f, + 0xc0, + + /* U+20AC "€" */ + 0x3, 0xe0, 0xfe, 0x1c, 0x3, 0x80, 0x30, 0xf, + 0xf8, 0xff, 0x8f, 0xf8, 0xff, 0x83, 0x0, 0x38, + 0x1, 0xc0, 0xf, 0xe0, 0x3e, + + /* U+20B4 "₴" */ + 0x3, 0x81, 0xfc, 0x1c, 0xe0, 0x6, 0x0, 0x6f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x0, 0x30, + 0x3, 0xbe, 0x1f, 0xe0, 0xc0, + + /* U+20B8 "₸" */ + 0xff, 0xff, 0xff, 0x0, 0xf, 0xff, 0xff, 0xf0, + 0x60, 0x6, 0x0, 0x60, 0x6, 0x0, 0x60, 0x6, + 0x0, 0x60, 0x6, 0x0, 0x60, + + /* U+20BD "₽" */ + 0x3f, 0xc3, 0xfe, 0x30, 0x73, 0x3, 0x30, 0x33, + 0x3, 0x30, 0x7f, 0xfe, 0xff, 0xc3, 0x0, 0xff, + 0xcf, 0xfc, 0x30, 0x3, 0x0, + + /* U+2122 "™" */ + 0xff, 0x60, 0x7f, 0xee, 0x1c, 0x61, 0xc3, 0x8c, + 0x3c, 0xf1, 0x87, 0xfe, 0x30, 0xde, 0xc6, 0x1b, + 0xd8, 0xc3, 0x33, 0x18, 0x60, 0x63, 0xc, 0xc, + + /* U+2190 "←" */ + 0x6, 0x0, 0xe, 0x0, 0x1c, 0x0, 0x38, 0x0, + 0x70, 0x0, 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, + 0x38, 0x0, 0x1c, 0x0, 0xe, 0x0, 0x6, 0x0, + + /* U+2191 "↑" */ + 0x6, 0x0, 0xf0, 0x1f, 0x83, 0xfc, 0x76, 0xee, + 0x67, 0xc6, 0x30, 0x60, 0x6, 0x0, 0x60, 0x6, + 0x0, 0x60, 0x6, 0x0, 0x60, 0x6, 0x0, 0x60, + + /* U+2192 "→" */ + 0x0, 0x60, 0x0, 0x70, 0x0, 0x38, 0x0, 0x1c, + 0x0, 0xe, 0xff, 0xff, 0xff, 0xff, 0x0, 0xe, + 0x0, 0x1c, 0x0, 0x38, 0x0, 0x70, 0x0, 0x60, + + /* U+2193 "↓" */ + 0x6, 0x0, 0x60, 0x6, 0x0, 0x60, 0x6, 0x0, + 0x60, 0x6, 0x0, 0x60, 0x6, 0xc, 0x63, 0xe6, + 0x77, 0x6e, 0x3f, 0xc1, 0xf8, 0xf, 0x0, 0x60, + + /* U+2194 "↔" */ + 0x18, 0x0, 0x38, 0x0, 0x78, 0x0, 0xff, 0xff, + 0xff, 0xff, 0x78, 0x0, 0x38, 0x0, 0x18, 0x0, + 0x0, 0x18, 0x0, 0x1c, 0x0, 0x1e, 0xff, 0xff, + 0xff, 0xff, 0x0, 0x1e, 0x0, 0x1c, 0x0, 0x18, + + /* U+2195 "↕" */ + 0x18, 0x18, 0x3c, 0x18, 0x7e, 0x18, 0xff, 0x18, + 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, + 0x18, 0xff, 0x18, 0x7e, 0x18, 0x3c, 0x18, 0x18, + + /* U+2197 "↗" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0x3f, 0xe0, 0xff, 0x83, 0xfe, 0xf, 0xf1, 0x3f, + 0x8e, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+21BA "↺" */ + 0xc7, 0xe0, 0xdf, 0xf8, 0xfc, 0x3c, 0xf0, 0xe, + 0xfc, 0x6, 0xfc, 0x7, 0x0, 0x3, 0x0, 0x3, + 0x0, 0x3, 0x0, 0x3, 0x0, 0x7, 0x60, 0x6, + 0x70, 0xe, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+21BB "↻" */ + 0x0, 0x0, 0x1, 0xf8, 0xc3, 0xff, 0x63, 0x81, + 0xf3, 0x0, 0x79, 0x80, 0xfd, 0x80, 0x7e, 0xc0, + 0x0, 0x60, 0x0, 0x30, 0x0, 0x18, 0x0, 0xc, + 0x0, 0x7, 0x0, 0x21, 0x80, 0x30, 0x70, 0x38, + 0x1f, 0xf8, 0x7, 0xf0, 0x0, 0x0, 0x0, + + /* U+21C4 "⇄" */ + 0x0, 0x18, 0x0, 0x1c, 0x0, 0xe, 0xff, 0xff, + 0xff, 0xff, 0x0, 0xe, 0x0, 0x1c, 0x0, 0x18, + 0x18, 0x0, 0x38, 0x0, 0x70, 0x0, 0xff, 0xff, + 0xff, 0xff, 0x70, 0x0, 0x38, 0x0, 0x18, 0x0, + + /* U+2212 "−" */ + 0xff, 0xff, 0xff, 0xf0, + + /* U+221E "∞" */ + 0x1e, 0x7, 0x87, 0xf9, 0xfe, 0x61, 0x98, 0x6c, + 0xf, 0x3, 0xc0, 0xf0, 0x3c, 0xf, 0x3, 0xc0, + 0xf0, 0x36, 0x19, 0x86, 0x7f, 0x9f, 0xe1, 0xe0, + 0x78, + + /* U+2303 "⌃" */ + 0x6, 0x0, 0xf0, 0x1f, 0x83, 0x9c, 0x70, 0xee, + 0x7, 0xc0, 0x30, + + /* U+2304 "⌄" */ + 0xc0, 0x3e, 0x7, 0x70, 0xe3, 0x9c, 0x1f, 0x80, + 0xf0, 0x6, 0x0, + + /* U+231B "⌛" */ + 0xff, 0xff, 0xff, 0x60, 0x66, 0x6, 0x70, 0xc3, + 0x9c, 0x1f, 0x80, 0xf0, 0xf, 0x1, 0xf8, 0x3f, + 0xc7, 0xfc, 0x7f, 0xe7, 0xfe, 0xff, 0xff, 0xff, + + /* U+2328 "⌨" */ + 0x7f, 0xff, 0xbf, 0xff, 0xfc, 0x92, 0x4f, 0x24, + 0x93, 0xff, 0xff, 0xf2, 0x49, 0x3c, 0x92, 0x4f, + 0xff, 0xff, 0xf8, 0x7, 0xfe, 0x1, 0xff, 0xff, + 0xfd, 0xff, 0xfe, + + /* U+2329 "〈" */ + 0x3, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xe0, + 0x70, 0x38, 0x1c, 0xe, 0x7, 0x3, + + /* U+232A "〉" */ + 0xc0, 0xe0, 0x70, 0x38, 0x1c, 0xe, 0x7, 0x7, + 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xc0, + + /* U+232B "⌫" */ + 0x7, 0xff, 0x83, 0xff, 0xf1, 0xff, 0xfc, 0xfc, + 0xdf, 0x7f, 0x27, 0xff, 0xe3, 0xff, 0xf8, 0xfd, + 0xfc, 0x9f, 0x3f, 0x37, 0xc7, 0xff, 0xf0, 0xff, + 0xfc, 0x1f, 0xfe, + + /* U+2399 "⎙" */ + 0x1f, 0xf0, 0x3f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0xff, 0xff, + 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf, + 0x30, 0xc, 0x30, 0xc, 0x3f, 0xfc, 0x1f, 0xf8, + + /* U+23CF "⏏" */ + 0x3, 0x0, 0x1e, 0x0, 0xfc, 0x7, 0xf8, 0x3f, + 0xf1, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, 0xff, 0xff, + 0xf0, + + /* U+23E9 "⏩" */ + 0xc0, 0xc0, 0xe1, 0xe0, 0xf1, 0xf0, 0xf9, 0xf8, + 0xfd, 0xfc, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0xfd, 0xfc, 0xf9, 0xf8, 0xf1, 0xf0, + 0xe1, 0xe0, 0xc0, 0xc0, + + /* U+23EA "⏪" */ + 0x3, 0x3, 0x7, 0x87, 0xf, 0x8f, 0x1f, 0x9f, + 0x3f, 0xbf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0x3f, 0xbf, 0x1f, 0x9f, 0xf, 0x8f, + 0x7, 0x87, 0x3, 0x3, + + /* U+23ED "⏭" */ + 0xc1, 0x83, 0xe1, 0xc3, 0xf1, 0xe3, 0xf9, 0xf3, + 0xfd, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfd, 0xfb, 0xf9, 0xf3, 0xf1, 0xe3, + 0xe1, 0xc3, 0xc1, 0x83, + + /* U+23EE "⏮" */ + 0xc1, 0x83, 0xc3, 0x87, 0xc7, 0x8f, 0xcf, 0x9f, + 0xdf, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xdf, 0xbf, 0xcf, 0x9f, 0xc7, 0x8f, + 0xc3, 0x87, 0xc1, 0x83, + + /* U+23F0 "⏰" */ + 0x78, 0x1e, 0xf0, 0xf, 0xe7, 0xe7, 0xcf, 0xf3, + 0x1f, 0xf8, 0x3e, 0x7c, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7e, 0x3e, 0x7f, 0xbe, 0x7f, 0xfc, + 0x3f, 0xfc, 0x3f, 0xfc, 0x7f, 0xfe, 0x63, 0xc6, + + /* U+23F1 "⏱" */ + 0xf, 0x80, 0x10, 0x3, 0xe0, 0x7f, 0xf7, 0xff, + 0xbe, 0xfb, 0xf7, 0xff, 0xbf, 0xfd, 0xff, 0xef, + 0xff, 0xff, 0xff, 0xf7, 0xff, 0x1f, 0xf0, 0x7f, + 0x1, 0xf0, + + /* U+23F3 "⏳" */ + 0xff, 0xff, 0xff, 0x60, 0x66, 0x6, 0x70, 0xc3, + 0x9c, 0x1f, 0x80, 0xf0, 0xf, 0x1, 0xf8, 0x39, + 0xc7, 0xc, 0x60, 0x66, 0x6, 0xff, 0xff, 0xff, + + /* U+23F8 "⏸" */ + 0xf9, 0xff, 0x9f, 0xf9, 0xff, 0x9f, 0xf9, 0xff, + 0x9f, 0xf9, 0xff, 0x9f, 0xf9, 0xff, 0x9f, 0xf9, + 0xff, 0x9f, 0xf9, 0xff, 0x9f, + + /* U+23F9 "⏹" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+23FB "⏻" */ + 0x1, 0x80, 0x1, 0x80, 0x19, 0x98, 0x39, 0x9c, + 0x71, 0x8e, 0x61, 0x86, 0xe1, 0x87, 0xc1, 0x83, + 0xc1, 0x83, 0xc1, 0x83, 0xc0, 0x3, 0xe0, 0x7, + 0x60, 0x6, 0x70, 0xe, 0x3c, 0x3c, 0x1f, 0xf8, + 0x7, 0xe0, + + /* U+23FE "⏾" */ + 0xf, 0xe0, 0x7f, 0x3, 0xf0, 0x1f, 0xc0, 0x7e, + 0x3, 0xf8, 0xf, 0xe0, 0x3f, 0x80, 0xfe, 0x3, + 0xfc, 0x7, 0xf8, 0x1f, 0xf0, 0x3f, 0xfc, 0x7f, + 0xe0, 0x7e, 0x0, + + /* U+24BD "Ⓗ" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7b, 0x9e, 0xfb, 0x9f, 0xfb, 0x9f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xfb, 0x9f, 0xfb, 0x9f, 0x7b, 0x9e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+25A0 "■" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+25B6 "▶" */ + 0xc0, 0x7, 0x80, 0x3f, 0x1, 0xfe, 0xf, 0xfc, + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xfe, + 0x3f, 0xc1, 0xf8, 0xf, 0x0, 0x60, 0x0, + + /* U+25CF "●" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+25D0 "◐" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0x3c, 0x7f, 0xe, + 0x7f, 0x6, 0xff, 0x7, 0xff, 0x3, 0xff, 0x3, + 0xff, 0x3, 0xff, 0x3, 0xff, 0x7, 0x7f, 0x6, + 0x7f, 0xe, 0x3f, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+25FB "◻" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+25FC "◼" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+2600 "☀" */ + 0x4, 0x10, 0x7, 0xbc, 0x3, 0xfe, 0x1, 0xff, + 0x7, 0xff, 0xf7, 0xe0, 0xfd, 0xe7, 0x3c, 0xf7, + 0xde, 0x3b, 0xee, 0x3d, 0xf7, 0x9e, 0x73, 0xdf, + 0x83, 0xf7, 0xff, 0xf0, 0x7f, 0xc0, 0x3f, 0xe0, + 0x1e, 0xf0, 0x4, 0x10, 0x0, + + /* U+2601 "☁" */ + 0x7, 0x80, 0x3, 0xf8, 0x1, 0xff, 0xe0, 0x7f, + 0xfc, 0x1f, 0xff, 0xf, 0xff, 0xc7, 0xff, 0xf3, + 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x7f, 0xff, 0x8f, 0xff, 0xc0, + + /* U+2603 "☃" */ + 0x0, 0xf0, 0x0, 0x1f, 0x80, 0x3, 0xfc, 0x0, + 0x36, 0xc0, 0x3, 0xfc, 0x0, 0x39, 0xc0, 0x1, + 0x98, 0x3, 0x1f, 0x8c, 0xf3, 0xfc, 0xf7, 0xff, + 0xfe, 0x1f, 0x9f, 0x80, 0x79, 0xe0, 0x7, 0xfe, + 0x0, 0x79, 0xe0, 0x7, 0x9e, 0x0, 0x7f, 0xe0, + 0x3, 0xfc, 0x0, 0x1f, 0x80, + + /* U+2604 "☄" */ + 0x0, 0x3, 0x0, 0x8f, 0x3, 0xfe, 0xf, 0xfe, + 0x1f, 0xfc, 0x3f, 0xfc, 0x70, 0xfc, 0xe0, 0x7e, + 0xcc, 0x3c, 0xcc, 0x3c, 0xc0, 0x38, 0xc2, 0x38, + 0x60, 0x70, 0x70, 0xe0, 0x3f, 0xc0, 0xf, 0x80, + + /* U+2611 "☑" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x9f, 0xfe, 0xff, 0xf3, 0xf9, 0x9f, 0xe4, 0xff, + 0xc7, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+2615 "☕" */ + 0x0, 0x0, 0x36, 0x0, 0x33, 0x0, 0x19, 0x80, + 0x9, 0x80, 0x0, 0x0, 0x0, 0x0, 0xff, 0xfc, + 0xff, 0xfe, 0xff, 0xf7, 0xff, 0xf3, 0xff, 0xf3, + 0xff, 0xf7, 0xff, 0xfe, 0xff, 0xfc, 0xff, 0xf0, + 0x7f, 0xe0, + + /* U+261D "☝" */ + 0x60, 0x6, 0x0, 0x60, 0x6, 0x0, 0x6c, 0x6, + 0xd8, 0x6d, 0xb0, 0x1b, 0x1, 0xb7, 0xc3, 0xfd, + 0xce, 0x3f, 0xff, 0xf7, 0xfe, 0x7f, 0xe1, 0xf8, + + /* U+2620 "☠" */ + 0x7, 0x80, 0x7f, 0x81, 0xfe, 0xf, 0xfc, 0x33, + 0x30, 0xcc, 0xc1, 0xfe, 0x7, 0xf8, 0xf, 0xc0, + 0x3f, 0xc, 0x0, 0xfe, 0x1f, 0x3f, 0xf0, 0x3f, + 0x3, 0xff, 0x3e, 0x1f, 0xc0, 0xc, + + /* U+2622 "☢" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x73, 0xce, + 0x63, 0xc6, 0xe3, 0xc7, 0xc6, 0x63, 0xc6, 0x63, + 0xfe, 0x7f, 0xfe, 0x7f, 0xff, 0xff, 0x7c, 0x3e, + 0x78, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+2623 "☣" */ + 0x2, 0x20, 0x2, 0x8, 0x3, 0x76, 0x1, 0xbb, + 0x0, 0xc1, 0x80, 0x71, 0xc0, 0x7f, 0xf0, 0x7e, + 0xfe, 0x66, 0x33, 0x65, 0xf6, 0xe3, 0x76, 0x31, + 0xbb, 0x10, 0x1c, 0x0, 0x1b, 0x0, 0xf8, 0xf8, + 0x0, 0x0, + + /* U+2625 "☥" */ + 0x1e, 0xf, 0xc7, 0x39, 0x86, 0x61, 0x98, 0x63, + 0x30, 0xfc, 0xff, 0xff, 0xf0, 0xc0, 0x30, 0xc, + 0x3, 0x0, 0xc0, 0x30, + + /* U+262A "☪" */ + 0x7, 0xe0, 0x1f, 0x0, 0x3e, 0x0, 0x7c, 0x0, + 0x78, 0x8, 0xf0, 0x18, 0xf0, 0x3e, 0xf0, 0x7f, + 0xf0, 0x3e, 0xf0, 0x3c, 0xf0, 0x3e, 0x78, 0x2, + 0x7c, 0x0, 0x3c, 0x0, 0x1f, 0x0, 0x7, 0xe0, + + /* U+262C "☬" */ + 0x1, 0x80, 0x3, 0xc0, 0x13, 0xc4, 0x67, 0xe6, + 0x6d, 0xb6, 0xed, 0xb7, 0xed, 0xb7, 0xe7, 0xe7, + 0xf3, 0xcf, 0x7b, 0xde, 0x7d, 0xbe, 0x3f, 0xfc, + 0x19, 0x98, 0x7, 0xe0, 0x5, 0xa0, 0x1, 0x80, + + /* U+262E "☮" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3d, 0xbc, 0x71, 0x8e, + 0x61, 0x86, 0xe1, 0x87, 0xc1, 0x83, 0xc1, 0x83, + 0xc3, 0xc3, 0xc7, 0xe3, 0xff, 0xff, 0x79, 0x9e, + 0x71, 0x8e, 0x3d, 0xbc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+262F "☯" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3c, 0x3c, 0x70, 0x1e, + 0x61, 0x9e, 0xe1, 0x9f, 0xc0, 0x1f, 0xc0, 0x3f, + 0xc3, 0xff, 0xc7, 0xff, 0xe6, 0x7f, 0x66, 0x7e, + 0x77, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+2638 "☸" */ + 0x1, 0x80, 0x7, 0xe0, 0x3f, 0xfc, 0x3d, 0xbc, + 0x3d, 0xbc, 0x7f, 0xfe, 0x67, 0xe6, 0xfe, 0x7f, + 0xfe, 0x7f, 0x67, 0xe6, 0x7f, 0xfe, 0x3d, 0xbc, + 0x3d, 0xbc, 0x3f, 0xfc, 0x7, 0xe0, 0x1, 0x80, + + /* U+2639 "☹" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0xdf, 0xf9, 0xdf, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x3f, 0xf8, 0x1f, 0x73, 0xce, + 0x77, 0xee, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+263F "☿" */ + 0x0, 0xc, 0x31, 0xfe, 0x3f, 0xc7, 0x1d, 0xc1, + 0xf0, 0x1e, 0x3, 0xc0, 0x7c, 0x1d, 0xc7, 0x3f, + 0xc1, 0xe0, 0x18, 0xf, 0xc1, 0xf8, 0xc, 0x1, + 0x80, + + /* U+2640 "♀" */ + 0x1f, 0x7, 0xf1, 0xc7, 0x70, 0x7c, 0x7, 0x80, + 0xf0, 0x1f, 0x7, 0x71, 0xcf, 0xf0, 0x78, 0x6, + 0x0, 0xc0, 0x7e, 0xf, 0xc0, 0x60, 0xc, 0x0, + + /* U+2642 "♂" */ + 0x0, 0x7e, 0x0, 0xfc, 0x0, 0x78, 0x1, 0xf1, + 0xf7, 0x67, 0xfc, 0xdc, 0x70, 0x70, 0x70, 0xc0, + 0x61, 0x80, 0xc3, 0x1, 0x87, 0x7, 0x7, 0x1c, + 0x7, 0xf0, 0x7, 0xc0, 0x0, + + /* U+265A "♚" */ + 0x3, 0x0, 0xc, 0x0, 0xfc, 0x3, 0xf0, 0x3, + 0x0, 0xc, 0xf, 0xff, 0xff, 0xff, 0x7f, 0xf9, + 0xff, 0xe3, 0xff, 0xf, 0xfc, 0x1f, 0xe0, 0x7f, + 0x83, 0xff, 0x1f, 0xfe, 0x7f, 0xf8, + + /* U+265B "♛" */ + 0x1, 0x80, 0x3, 0xc0, 0x1, 0x80, 0x4, 0x20, + 0x4e, 0x72, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8, 0xf, 0xf0, + 0xf, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, + + /* U+265C "♜" */ + 0xee, 0x7e, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x7f, 0xe3, 0xfc, 0x3f, 0xc3, 0xfc, 0x3f, + 0xc3, 0xfc, 0x3f, 0xc7, 0xfe, 0xff, 0xff, 0xff, + + /* U+265D "♝" */ + 0x3f, 0xf, 0xc1, 0xe0, 0xfc, 0x7e, 0x1f, 0x2f, + 0x8f, 0xe7, 0xff, 0xff, 0xf7, 0xf9, 0xfc, 0x3f, + 0x1f, 0xef, 0xff, 0xff, + + /* U+265E "♞" */ + 0x1f, 0x1, 0xfc, 0x3f, 0xe3, 0x3e, 0x73, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x67, 0xf0, 0xff, 0x1f, + 0xf3, 0xfe, 0x3f, 0xe7, 0xfe, 0xff, 0xff, 0xff, + 0xff, 0xf0, + + /* U+265F "♟" */ + 0xe, 0x7, 0xe0, 0xfe, 0x1f, 0xc3, 0xf8, 0x7f, + 0xf, 0xe1, 0xfc, 0x1f, 0x3, 0xe0, 0xfe, 0x1f, + 0xc3, 0xf8, 0x7f, 0x1f, 0xf7, 0xff, 0xff, 0xe0, + + /* U+2665 "♥" */ + 0x3c, 0x3c, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, + 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8, 0xf, 0xf0, + 0x7, 0xe0, 0x1, 0x80, + + /* U+2666 "♦" */ + 0x1, 0x80, 0x3, 0xc0, 0x7, 0xe0, 0xf, 0xf0, + 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, + 0xf, 0xf0, 0x7, 0xe0, 0x3, 0xc0, 0x1, 0x80, + + /* U+2672 "♲" */ + 0x0, 0x0, 0x0, 0xfc, 0x0, 0x33, 0x0, 0x8, + 0x68, 0x0, 0x1e, 0xf, 0x7, 0x87, 0xc1, 0xe0, + 0xf0, 0x0, 0x34, 0x0, 0x1c, 0x0, 0x66, 0x0, + 0x19, 0x80, 0x86, 0x60, 0x61, 0x8f, 0x3f, 0xc1, + 0xcf, 0xe0, 0x1, 0x80, 0x0, 0x20, 0x0, + + /* U+267A "♺" */ + 0x0, 0x0, 0x0, 0xfc, 0x0, 0x33, 0x0, 0x8, + 0x68, 0x0, 0x1e, 0xf, 0x7, 0x87, 0xc1, 0xe0, + 0xf0, 0x0, 0x34, 0x0, 0x1c, 0x0, 0x66, 0x0, + 0x19, 0x80, 0x86, 0x60, 0x61, 0x8f, 0x3f, 0xc1, + 0xcf, 0xe0, 0x1, 0x80, 0x0, 0x20, 0x0, + + /* U+267B "♻" */ + 0x0, 0x0, 0x0, 0xfc, 0x0, 0x33, 0x0, 0x8, + 0x68, 0x0, 0x1e, 0xf, 0x7, 0x87, 0xc1, 0xe0, + 0xf0, 0x0, 0x34, 0x0, 0x1c, 0x0, 0x66, 0x0, + 0x19, 0x80, 0x86, 0x60, 0x61, 0x8f, 0x3f, 0xc1, + 0xcf, 0xe0, 0x1, 0x80, 0x0, 0x20, 0x0, + + /* U+267E "♾" */ + 0x1e, 0x7, 0x87, 0xf9, 0xfe, 0x61, 0x98, 0x6c, + 0xf, 0x3, 0xc0, 0xf0, 0x3c, 0xf, 0x3, 0xc0, + 0xf0, 0x36, 0x19, 0x86, 0x7f, 0x9f, 0xe1, 0xe0, + 0x78, + + /* U+2680 "⚀" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xcf, 0xff, 0x3f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+2681 "⚁" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xe7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x7f, 0xf9, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+2682 "⚂" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xe7, + 0xff, 0xff, 0xff, 0xcf, 0xff, 0x3f, 0xff, 0xff, + 0xfe, 0x7f, 0xf9, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+2683 "⚃" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf9, 0xe7, 0xe7, + 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x9e, 0x7e, 0x79, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+2684 "⚄" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf9, 0xe7, 0xe7, + 0x9f, 0xff, 0xff, 0xcf, 0xff, 0x3f, 0xff, 0xff, + 0x9e, 0x7e, 0x79, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+2685 "⚅" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf9, 0xe7, 0xe7, + 0x9f, 0xff, 0xfe, 0x79, 0xf9, 0xe7, 0xff, 0xff, + 0x9e, 0x7e, 0x79, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+2693 "⚓" */ + 0x3, 0xc0, 0x3, 0xf0, 0x1, 0x98, 0x0, 0xcc, + 0x0, 0x7e, 0x0, 0x1e, 0x0, 0x6, 0x0, 0x63, + 0xc, 0x79, 0x8f, 0x7e, 0xcf, 0xcc, 0x61, 0x86, + 0x31, 0xc1, 0x98, 0xc0, 0xed, 0xe0, 0x3f, 0xe0, + 0x7, 0xc0, + + /* U+2696 "⚖" */ + 0x0, 0x60, 0x1, 0xff, 0xf8, 0x1f, 0xff, 0x80, + 0xf, 0x0, 0x18, 0xe1, 0x81, 0x86, 0x18, 0x3c, + 0x63, 0xc6, 0x46, 0x66, 0x66, 0x66, 0x6c, 0x36, + 0xc3, 0xff, 0x6f, 0xff, 0xe6, 0x7e, 0x3c, 0x63, + 0xc0, 0x6, 0x0, 0x1f, 0xff, 0x81, 0xff, 0xf8, + + /* U+2699 "⚙" */ + 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x77, 0xee, + 0x7f, 0xff, 0xff, 0xff, 0x7e, 0x7e, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x7e, 0xff, 0xff, + 0x7f, 0xff, 0x77, 0xee, 0x3, 0xc0, 0x3, 0xc0, + 0x3, 0xc0, + + /* U+269B "⚛" */ + 0x7, 0x0, 0x1e, 0x0, 0xcc, 0x1f, 0xfe, 0xff, + 0xff, 0x7f, 0xbf, 0x87, 0xde, 0xde, 0x7b, 0x7b, + 0xe1, 0xfd, 0xfe, 0xff, 0xff, 0x7f, 0xf8, 0x33, + 0x0, 0x78, 0x1, 0xc0, + + /* U+26A0 "⚠" */ + 0x1, 0x80, 0x3, 0xc0, 0x3, 0xc0, 0x7, 0xe0, + 0x7, 0xe0, 0xe, 0x70, 0xe, 0x70, 0x1e, 0x78, + 0x1e, 0x78, 0x3e, 0x7c, 0x3f, 0xfc, 0x7e, 0x7e, + 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, + + /* U+26A1 "⚡" */ + 0x0, 0x0, 0x3, 0x80, 0x1e, 0x0, 0xf0, 0x7, + 0xc0, 0x3f, 0x3, 0xf8, 0x1f, 0xe0, 0xff, 0xff, + 0xff, 0xf0, 0x7f, 0x81, 0xfc, 0x7, 0xc0, 0x3e, + 0x0, 0xf0, 0x7, 0x80, 0x1c, 0x0, 0x0, 0x0, + + /* U+26A2 "⚢" */ + 0x1f, 0x1f, 0x7, 0xf3, 0xf1, 0xc7, 0x7, 0x70, + 0x70, 0x7c, 0x6, 0x7, 0x80, 0xc0, 0xf0, 0x18, + 0x1f, 0x7, 0x7, 0x71, 0xc1, 0xcf, 0xf3, 0xf0, + 0x78, 0x7c, 0x6, 0x7, 0x0, 0xc0, 0xe0, 0x7e, + 0x3e, 0xf, 0xc7, 0xc0, 0x60, 0x70, 0xc, 0x4, + 0x0, + + /* U+26A3 "⚣" */ + 0x0, 0x7e, 0xf0, 0x7, 0xef, 0x0, 0x1e, 0xf0, + 0x3, 0xef, 0x1f, 0x76, 0xb3, 0xfe, 0x63, 0x71, + 0xc0, 0xe, 0xe, 0x70, 0xc0, 0x63, 0xc, 0x6, + 0x30, 0xc0, 0x63, 0xe, 0xe, 0x70, 0x71, 0xce, + 0x3, 0xf9, 0xc0, 0x1f, 0x38, 0x0, + + /* U+26A4 "⚤" */ + 0x0, 0x7, 0xe0, 0x0, 0xfc, 0x0, 0x3, 0x8f, + 0x3f, 0xf3, 0xff, 0xf6, 0xe7, 0x8e, 0xf8, 0xf8, + 0xe6, 0x1b, 0xc, 0xc7, 0x61, 0x98, 0x6c, 0x33, + 0x8f, 0x8e, 0x39, 0xe3, 0x87, 0xff, 0xe0, 0x3c, + 0xf8, 0x3, 0x0, 0x1, 0xf8, 0x0, 0x3f, 0x0, + 0x1, 0x80, 0x0, 0x30, 0x0, 0x0, + + /* U+26A5 "⚥" */ + 0x0, 0x7c, 0x1, 0xf0, 0x3, 0xc7, 0xdf, 0x3f, + 0xed, 0xc7, 0xe, 0xe, 0x30, 0x18, 0xc0, 0x63, + 0x1, 0x8e, 0xe, 0x1c, 0x70, 0x3f, 0x80, 0x7c, + 0x0, 0xe0, 0x7, 0xc0, 0x1f, 0x0, 0x38, 0x0, + 0x40, 0x0, + + /* U+26A6 "⚦" */ + 0x0, 0x3f, 0x0, 0x3f, 0x0, 0xf, 0x0, 0x5f, + 0x0, 0x7b, 0x1f, 0x73, 0x3f, 0xf8, 0x71, 0xc0, + 0xe0, 0xe0, 0xc0, 0x60, 0xc0, 0x60, 0xc0, 0x60, + 0xe0, 0xe0, 0x71, 0xc0, 0x3f, 0x80, 0x1f, 0x0, + + /* U+26A7 "⚧" */ + 0xf8, 0x7, 0xfe, 0x1, 0xfe, 0x0, 0x3f, 0xf0, + 0x1f, 0xdb, 0xfe, 0xc7, 0xff, 0x0, 0x61, 0xc0, + 0x30, 0x30, 0xc, 0xc, 0x3, 0x3, 0x0, 0xc0, + 0xc0, 0x18, 0x60, 0x7, 0xf8, 0x0, 0x78, 0x0, + 0xc, 0x0, 0xf, 0xc0, 0x3, 0xf0, 0x0, 0x30, + 0x0, 0x0, 0x0, + + /* U+26A8 "⚨" */ + 0xc, 0x3, 0xc0, 0xfc, 0x16, 0x80, 0xc0, 0x7e, + 0xf, 0xc0, 0x60, 0x3f, 0xf, 0xfb, 0x83, 0x60, + 0x3c, 0x7, 0x80, 0xf8, 0x3b, 0x8e, 0x3f, 0x83, + 0xe0, + + /* U+26A9 "⚩" */ + 0x0, 0x0, 0xf, 0xc0, 0x7, 0xf8, 0x1, 0x87, + 0x6c, 0xc0, 0xd9, 0xb0, 0x3f, 0xfc, 0xf, 0xff, + 0x3, 0x66, 0xe0, 0xdb, 0x1c, 0x60, 0x3, 0xf8, + 0x0, 0x78, 0x0, + + /* U+26AA "⚪" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+26AB "⚫" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+26B2 "⚲" */ + 0x1e, 0xf, 0xc6, 0x3b, 0x87, 0xc0, 0xf0, 0x3c, + 0xf, 0x87, 0x63, 0x9f, 0xc1, 0xe0, 0x30, 0xc, + 0x3, 0x0, 0xc0, 0x30, 0xc, 0x0, + + /* U+26BD "⚽" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x71, 0x8e, + 0x60, 0x6, 0xf0, 0xf, 0xf1, 0x8f, 0xe3, 0xc7, + 0xc3, 0xc3, 0xc3, 0xc3, 0xe0, 0x7, 0x7c, 0x3e, + 0x7c, 0x3e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+26BE "⚾" */ + 0x6, 0x60, 0x1e, 0x78, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7d, 0xfe, 0xf9, 0xff, 0xf3, 0xff, 0x3f, 0xfc, + 0x3f, 0xfc, 0xff, 0xcf, 0xff, 0x9f, 0x7f, 0xbe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1e, 0x78, 0x6, 0x60, + + /* U+26C4 "⛄" */ + 0x0, 0xf0, 0x0, 0x1f, 0x80, 0x3, 0xfc, 0x0, + 0x36, 0xc0, 0x3, 0xfc, 0x0, 0x39, 0xc0, 0x1, + 0x98, 0x3, 0x1f, 0x8c, 0xf3, 0xfc, 0xf7, 0xff, + 0xfe, 0x1f, 0x9f, 0x80, 0x79, 0xe0, 0x7, 0xfe, + 0x0, 0x79, 0xe0, 0x7, 0x9e, 0x0, 0x7f, 0xe0, + 0x3, 0xfc, 0x0, 0x1f, 0x80, + + /* U+26C5 "⛅" */ + 0x0, 0x44, 0x0, 0x3b, 0x80, 0xf, 0xe0, 0xf, + 0xfe, 0x7, 0x83, 0xc0, 0x2e, 0xe0, 0x0, 0xb8, + 0x38, 0xe, 0x1f, 0x73, 0x8f, 0xfe, 0xf3, 0xff, + 0x81, 0xff, 0xf8, 0xff, 0xff, 0x3f, 0xff, 0xcf, + 0xff, 0xf3, 0xff, 0xfc, 0x7f, 0xfe, 0x0, + + /* U+26C6 "⛆" */ + 0xe, 0x0, 0x1f, 0x70, 0x3f, 0xf8, 0x3f, 0xf8, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, + 0x10, 0x8, 0x31, 0x8c, 0x31, 0x8c, 0x31, 0x8c, + + /* U+26DF "⛟" */ + 0x7f, 0xf8, 0x3f, 0xfe, 0xf, 0xff, 0xc3, 0xff, + 0xfc, 0xff, 0xff, 0xbf, 0xff, 0x7f, 0xff, 0xcf, + 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xcf, 0xcf, 0x73, 0xf3, 0x84, 0x84, 0xc1, + 0xc0, 0xe0, + + /* U+26E9 "⛩" */ + 0x0, 0x3, 0x0, 0x3f, 0xff, 0xff, 0xff, 0x7f, + 0xf8, 0xcc, 0xc3, 0x33, 0x1f, 0xfe, 0x7f, 0xf8, + 0xc0, 0xc3, 0x3, 0xc, 0xc, 0x30, 0x30, 0xc0, + 0xc3, 0x3, 0xc, 0xc, + + /* U+26EA "⛪" */ + 0x0, 0x0, 0x1, 0x80, 0x7, 0xe0, 0x1, 0x80, + 0x1, 0x80, 0x3, 0xc0, 0xf, 0xf0, 0xf, 0xf0, + 0x1f, 0xf8, 0x1f, 0xf8, 0x7f, 0xfe, 0xfe, 0x7f, + 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, + 0x7f, 0xfe, + + /* U+26F7 "⛷" */ + 0x0, 0x3, 0xc, 0x3, 0xc7, 0x1c, 0xc2, 0x7f, + 0x0, 0xf, 0x80, 0x10, 0xf0, 0x6, 0x3c, 0x1, + 0x80, 0x0, 0xe0, 0x60, 0x30, 0x38, 0x18, 0x7, + 0x18, 0x0, 0xfc, 0x0, 0x1c, 0x0, 0x3, 0x80, + 0x0, 0x70, 0x0, 0xf, 0x0, + + /* U+26FA "⛺" */ + 0x6, 0x60, 0x7, 0xe0, 0x3, 0xc0, 0x1, 0x80, + 0x3, 0xc0, 0x7, 0xe0, 0xf, 0xf0, 0x1f, 0xf8, + 0x1f, 0xf8, 0x3f, 0xfc, 0x7e, 0x7e, 0xfc, 0x3f, + 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, + + /* U+26FD "⛽" */ + 0x7f, 0x80, 0x7f, 0x80, 0xe1, 0xcc, 0xe1, 0xce, + 0xe1, 0xcf, 0xe1, 0xc7, 0xff, 0xc7, 0xff, 0xc3, + 0xff, 0xf3, 0xff, 0xdb, 0xff, 0xdb, 0xff, 0xdb, + 0xff, 0xdb, 0xff, 0xce, 0xff, 0xc0, 0x7f, 0x80, + + /* U+2700 "✀" */ + 0x38, 0x0, 0x7c, 0x6, 0xc6, 0x1f, 0xc6, 0x3e, + 0xc6, 0x7c, 0x7f, 0xf8, 0x3f, 0xf0, 0x7, 0xe0, + 0x3, 0xc0, 0x3f, 0x90, 0x7f, 0x38, 0xc6, 0x7c, + 0xc6, 0x3e, 0xc6, 0x1f, 0x7c, 0x6, 0x38, 0x0, + + /* U+2702 "✂" */ + 0x38, 0x0, 0x7c, 0x6, 0xc6, 0x1f, 0xc6, 0x3e, + 0xc6, 0x7c, 0x7f, 0xf8, 0x3f, 0xf0, 0x7, 0xe0, + 0x3, 0xc0, 0x3f, 0x90, 0x7f, 0x38, 0xc6, 0x7c, + 0xc6, 0x3e, 0xc6, 0x1f, 0x7c, 0x6, 0x38, 0x0, + + /* U+2704 "✄" */ + 0x38, 0x0, 0x7c, 0x6, 0xc6, 0x1f, 0xc6, 0x3e, + 0xc6, 0x7c, 0x7f, 0xf8, 0x3f, 0xf0, 0x7, 0xe0, + 0x3, 0xc0, 0x3f, 0x90, 0x7f, 0x38, 0xc6, 0x7c, + 0xc6, 0x3e, 0xc6, 0x1f, 0x7c, 0x6, 0x38, 0x0, + + /* U+2705 "✅" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x9f, 0xfe, 0xff, 0xf3, 0xf9, 0x9f, 0xe4, 0xff, + 0xc7, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+2709 "✉" */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, + 0x1f, 0xf8, 0xcf, 0xf3, 0xe3, 0xc7, 0xf1, 0x8f, + 0xf8, 0x1f, 0xfe, 0x7f, 0xff, 0xff, 0x7f, 0xfe, + + /* U+270A "✊" */ + 0x4, 0x6, 0xf8, 0x7f, 0x87, 0xfb, 0x1, 0xb0, + 0x1b, 0x7c, 0x3f, 0xdc, 0xe3, 0xff, 0xff, 0x7f, + 0xe3, 0xfe, 0x1f, 0xc1, 0xfc, 0x1f, 0xc0, 0xfc, + + /* U+270B "✋" */ + 0x1, 0x80, 0x1b, 0x60, 0x36, 0xc0, 0x6d, 0xb0, + 0xdb, 0x61, 0xb6, 0xc3, 0x6d, 0x86, 0xdb, 0xf, + 0xff, 0xdf, 0xff, 0xff, 0xfb, 0xff, 0xf3, 0xff, + 0xe3, 0xff, 0x83, 0xfe, 0x1, 0xf8, + + /* U+270C "✌" */ + 0x6, 0x6, 0x30, 0x19, 0x80, 0xcc, 0x7, 0x60, + 0x1b, 0x60, 0xdb, 0x60, 0x1b, 0x0, 0xd9, 0xf0, + 0xdf, 0xb8, 0xe3, 0xf7, 0xff, 0x9f, 0xf8, 0xff, + 0xc1, 0xf8, + + /* U+270F "✏" */ + 0x0, 0x1c, 0x0, 0x3e, 0x0, 0x3f, 0x0, 0x9f, + 0x1, 0xcf, 0x3, 0xe6, 0x7, 0xf0, 0xf, 0xf8, + 0x1f, 0xf0, 0x3f, 0xe0, 0x7f, 0xc0, 0x7f, 0x80, + 0x4f, 0x0, 0xce, 0x0, 0xfc, 0x0, 0xe0, 0x0, + + /* U+2711 "✑" */ + 0x0, 0xc, 0x0, 0x1e, 0x0, 0x3f, 0x1, 0x9f, + 0xf, 0xce, 0x1f, 0xe4, 0x1f, 0xf0, 0x3f, 0xf8, + 0x38, 0xf8, 0x38, 0xf0, 0x70, 0xf0, 0x63, 0xf0, + 0x47, 0xe0, 0x8f, 0x80, 0x1c, 0x0, 0x20, 0x0, + + /* U+2712 "✒" */ + 0x0, 0xe, 0x0, 0x1f, 0x0, 0x3f, 0x0, 0x7f, + 0x0, 0xfe, 0x0, 0xfc, 0x1, 0xf8, 0x1, 0xf0, + 0x18, 0xc0, 0x3c, 0x0, 0x7e, 0x0, 0x67, 0x0, + 0x67, 0x0, 0x5e, 0x0, 0xbc, 0x0, 0x40, 0x0, + + /* U+2713 "✓" */ + 0x0, 0xc, 0x0, 0x70, 0x1, 0x80, 0xc, 0x0, + 0x70, 0x3, 0x80, 0xc, 0x30, 0x70, 0xe3, 0x81, + 0xcc, 0x3, 0xe0, 0x7, 0x80, 0xc, 0x0, + + /* U+2714 "✔" */ + 0x0, 0xc, 0x0, 0x70, 0x1, 0x80, 0xc, 0x0, + 0x70, 0x3, 0x80, 0xc, 0x30, 0x70, 0xe3, 0x81, + 0xcc, 0x3, 0xe0, 0x7, 0x80, 0xc, 0x0, + + /* U+2715 "✕" */ + 0xc0, 0x3e, 0x7, 0x70, 0xe3, 0x9c, 0x1f, 0x80, + 0xf0, 0xf, 0x1, 0xf8, 0x39, 0xc7, 0xe, 0xe0, + 0x7c, 0x3, + + /* U+2716 "✖" */ + 0xc0, 0x3e, 0x7, 0x70, 0xe3, 0x9c, 0x1f, 0x80, + 0xf0, 0xf, 0x1, 0xf8, 0x39, 0xc7, 0xe, 0xe0, + 0x7c, 0x3, + + /* U+271D "✝" */ + 0xf, 0x0, 0xf0, 0xf, 0x0, 0xf0, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf, 0x0, 0xf0, 0xf, + 0x0, 0xf0, 0xf, 0x0, 0xf0, 0xf, 0x0, 0xf0, + + /* U+2721 "✡" */ + 0x1, 0x80, 0x3, 0xc0, 0x3, 0xc0, 0x6, 0x60, + 0xff, 0xff, 0xff, 0xff, 0x4c, 0x32, 0x78, 0x1e, + 0x30, 0xc, 0x30, 0xc, 0x78, 0x1e, 0x4c, 0x32, + 0xff, 0xff, 0xff, 0xff, 0x6, 0x60, 0x3, 0xc0, + 0x3, 0xc0, 0x1, 0x80, + + /* U+2731 "✱" */ + 0x3, 0x0, 0xc, 0x0, 0x30, 0x0, 0xc0, 0xe3, + 0x1d, 0xed, 0xe3, 0xff, 0x3, 0xf0, 0xf, 0xc0, + 0xff, 0xc7, 0xb7, 0xb8, 0xc7, 0x3, 0x0, 0xc, + 0x0, 0x30, 0x0, 0xc0, + + /* U+2744 "❄" */ + 0x1, 0x80, 0x1, 0x80, 0x7, 0xe0, 0x7, 0xe0, + 0xdb, 0xdb, 0xf9, 0x9f, 0x3d, 0xbc, 0x7f, 0xfe, + 0x7, 0xe0, 0x7, 0xe0, 0x7f, 0xfe, 0x3d, 0xbc, + 0xf9, 0x9f, 0xdb, 0xdb, 0x7, 0xe0, 0x7, 0xe0, + 0x1, 0x80, 0x1, 0x80, + + /* U+2746 "❆" */ + 0x1, 0x80, 0x1, 0x80, 0x7, 0xe0, 0x7, 0xe0, + 0xdb, 0xdb, 0xf9, 0x9f, 0x3d, 0xbc, 0x7f, 0xfe, + 0x7, 0xe0, 0x7, 0xe0, 0x7f, 0xfe, 0x3d, 0xbc, + 0xf9, 0x9f, 0xdb, 0xdb, 0x7, 0xe0, 0x7, 0xe0, + 0x1, 0x80, 0x1, 0x80, + + /* U+274C "❌" */ + 0xc0, 0x3e, 0x7, 0x70, 0xe3, 0x9c, 0x1f, 0x80, + 0xf0, 0xf, 0x1, 0xf8, 0x39, 0xc7, 0xe, 0xe0, + 0x7c, 0x3, + + /* U+274E "❎" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, + 0x3f, 0xc0, 0xff, 0x87, 0xfe, 0x1f, 0xf0, 0x3f, + 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+2753 "❓" */ + 0x1e, 0x1f, 0xe6, 0x1b, 0x3, 0xc0, 0xf0, 0x30, + 0xc, 0x6, 0x7, 0x83, 0xc0, 0xc0, 0x30, 0x0, + 0x0, 0x0, 0xc0, 0x30, + + /* U+2754 "❔" */ + 0x1e, 0x1f, 0xe6, 0x1b, 0x3, 0xc0, 0xf0, 0x30, + 0xc, 0x6, 0x7, 0x83, 0xc0, 0xc0, 0x30, 0x0, + 0x0, 0x0, 0xc0, 0x30, + + /* U+2755 "❕" */ + 0xff, 0xff, 0xff, 0xf, + + /* U+2757 "❗" */ + 0xff, 0xff, 0xff, 0xf, + + /* U+2764 "❤" */ + 0x3c, 0x3c, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, + 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8, 0xf, 0xf0, + 0x7, 0xe0, 0x1, 0x80, + + /* U+2795 "➕" */ + 0x3, 0x0, 0xc, 0x0, 0x30, 0x0, 0xc0, 0x3, + 0x0, 0xc, 0xf, 0xff, 0xff, 0xff, 0x3, 0x0, + 0xc, 0x0, 0x30, 0x0, 0xc0, 0x3, 0x0, 0xc, + 0x0, + + /* U+2796 "➖" */ + 0xff, 0xff, 0xff, 0xf0, + + /* U+2797 "➗" */ + 0x3, 0x0, 0x1e, 0x0, 0x30, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0x30, 0x1, 0xe0, 0x3, 0x0, + + /* U+2934 "⤴" */ + 0x6, 0x1, 0xe0, 0x7e, 0x1f, 0xe7, 0xfe, 0xff, + 0xc3, 0xc0, 0x78, 0xf, 0x1, 0xe0, 0x3c, 0x7, + 0x8f, 0xf1, 0xfc, 0x3f, 0x87, 0xc0, + + /* U+2935 "⤵" */ + 0xf8, 0x1f, 0xc3, 0xf8, 0x7f, 0x80, 0xf0, 0x1e, + 0x3, 0xc0, 0x78, 0xf, 0x1, 0xe1, 0xff, 0xbf, + 0xf3, 0xfc, 0x3f, 0x3, 0xc0, 0x30, + + /* U+2B0D "⬍" */ + 0x18, 0x18, 0x3c, 0x18, 0x7e, 0x18, 0xff, 0x18, + 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, + 0x18, 0xff, 0x18, 0x7e, 0x18, 0x3c, 0x18, 0x18, + + /* U+2B1B "⬛" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+2B1C "⬜" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+2B23 "⬣" */ + 0xf, 0xf8, 0xf, 0xfe, 0x7, 0xff, 0x7, 0xff, + 0xc7, 0xff, 0xf3, 0xff, 0xfb, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xbf, 0xff, 0x9f, 0xff, 0xc7, + 0xff, 0xc1, 0xff, 0xc0, 0xff, 0xe0, 0x3f, 0xe0, + + /* U+2B24 "⬤" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+2B50 "⭐" */ + 0x0, 0xc0, 0x0, 0x30, 0x0, 0x1e, 0x0, 0x7, + 0x80, 0x3, 0xf0, 0xf, 0xff, 0xcf, 0xff, 0xfd, + 0xff, 0xfe, 0x3f, 0xff, 0x7, 0xff, 0x80, 0xff, + 0xc0, 0x3f, 0xf0, 0xf, 0xfc, 0x3, 0xff, 0x0, + 0xf3, 0xc0, 0x70, 0x38, + + /* U+E005 "" */ + 0x3, 0x0, 0x3f, 0xf0, 0x3f, 0xf0, 0x3, 0x0, + 0x3, 0x0, 0xf, 0xc0, 0xff, 0xf8, 0xff, 0xfe, + 0xff, 0xfe, 0x7f, 0xff, 0x7, 0x8f, 0x0, 0xf, + + /* U+E006 "" */ + 0x3, 0x0, 0x3f, 0xf0, 0x3f, 0xf0, 0x3, 0x0, + 0x3, 0x0, 0xf, 0xc0, 0xff, 0xf8, 0xff, 0xfe, + 0xff, 0xfe, 0x7f, 0xff, 0x7, 0x8f, 0x0, 0xf, + 0x0, 0x0, 0x0, 0x4, 0x0, 0x6, 0x0, 0x6, + + /* U+E00D "" */ + 0x1, 0x80, 0x1, 0xe2, 0x1, 0xfb, 0x81, 0xff, + 0xc1, 0xff, 0xe1, 0xff, 0xf1, 0xff, 0xfd, 0xfc, + 0x7e, 0x7e, 0x3f, 0x1f, 0x1f, 0xf, 0x8f, 0x87, + 0xff, 0xc3, 0xff, 0xe1, 0xff, 0xf0, 0xff, 0xf8, + 0x3f, 0xf8, + + /* U+E012 "" */ + 0x0, 0xe0, 0x0, 0x7c, 0x0, 0x3f, 0x80, 0x1f, + 0xf0, 0xf, 0xfe, 0xf, 0xff, 0xe3, 0xf1, 0xf8, + 0xfc, 0x7e, 0xf, 0x1f, 0x38, 0xff, 0xc7, 0x9f, + 0xf0, 0x33, 0xfc, 0xe6, 0x7f, 0x1d, 0x9f, 0x83, + 0xb0, 0x3, 0x6c, 0x0, 0xdb, 0x0, 0x0, + + /* U+E03F "" */ + 0x1c, 0xc, 0x3e, 0xc, 0x3e, 0xc, 0x3e, 0xc, + 0x3e, 0xc, 0x3e, 0xc, 0x3e, 0x3f, 0x3e, 0x3f, + 0x77, 0x1e, 0x77, 0xc, 0xe3, 0x80, 0xe3, 0x80, + 0xe3, 0x80, 0xf7, 0x80, 0x7f, 0x0, 0x3e, 0x0, + + /* U+E040 "" */ + 0x1e, 0xc, 0x3f, 0x1e, 0x3b, 0x3f, 0x33, 0x3f, + 0x33, 0xc, 0x33, 0xc, 0x33, 0xc, 0x33, 0xc, + 0x73, 0xc, 0x73, 0x8c, 0xe1, 0x80, 0xe1, 0xc0, + 0xe1, 0x80, 0xf3, 0x80, 0x7f, 0x0, 0x1e, 0x0, + + /* U+E041 "" */ + 0x7f, 0xfe, 0xf, 0xff, 0xf0, 0xee, 0x77, 0xe, + 0xe7, 0x70, 0xee, 0x77, 0xe, 0xe7, 0x70, 0xee, + 0x77, 0xe, 0xe7, 0x70, 0xee, 0x77, 0xf, 0xff, + 0xf0, 0xff, 0xff, 0xe7, 0x9f, 0xfe, 0x19, 0x80, + 0x0, 0x98, 0x0, 0x7, 0x0, 0x0, + + /* U+E059 "" */ + 0x1, 0x80, 0x0, 0x7e, 0x0, 0x7, 0xe0, 0x1, + 0xff, 0x0, 0x1f, 0xf0, 0x7, 0xde, 0x0, 0x7f, + 0xe1, 0x8e, 0x78, 0x7e, 0xe7, 0x87, 0xe7, 0xe1, + 0xff, 0x7e, 0x1f, 0xf1, 0x87, 0xfe, 0x0, 0x73, + 0xe0, 0xf, 0x38, 0x0, 0xff, 0x80, 0x7, 0xe0, + 0x0, 0x7e, 0x0, 0x1, 0x80, + + /* U+E05A "" */ + 0x0, 0x30, 0x1, 0x3e, 0x1, 0xfe, 0x5, 0xfe, + 0x7, 0xff, 0x3, 0xff, 0x1f, 0xfc, 0xf, 0xfc, + 0x7c, 0xfe, 0x3e, 0xf0, 0xf3, 0xd8, 0xf3, 0xc0, + 0x7f, 0x40, 0x3f, 0x0, 0x5d, 0x0, 0xc, 0x0, + + /* U+E05B "" */ + 0x3f, 0x80, 0x3f, 0xc0, 0x1f, 0xc0, 0x1f, 0xf8, + 0x1f, 0xfc, 0x1f, 0xf8, 0x1f, 0xf8, 0x4f, 0xfa, + 0xcf, 0xf3, 0xc0, 0x3, 0xe0, 0x7, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + + /* U+E05C "" */ + 0x0, 0xc0, 0x0, 0x78, 0x0, 0x3f, 0x0, 0x1f, + 0xe0, 0x7, 0xf8, 0x0, 0xfc, 0x0, 0x1e, 0x0, + 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, + 0xc, 0x7f, 0xcf, 0xff, 0x7, 0xbf, 0xff, 0xcf, + 0xff, 0xc3, 0xff, 0xe0, + + /* U+E05D "" */ + 0x0, 0x60, 0x0, 0x36, 0xc0, 0x23, 0x6c, 0x2, + 0x36, 0xd8, 0xfb, 0x6d, 0x82, 0x36, 0xd8, 0x23, + 0x6d, 0x80, 0x36, 0xd8, 0x3, 0xff, 0x83, 0xbf, + 0xf8, 0x3f, 0xbf, 0x1, 0xf1, 0xf4, 0xf, 0xbc, + 0x40, 0x7f, 0xdf, 0x3, 0xfc, 0x40, 0xf, 0xc4, + + /* U+E05E "" */ + 0x2, 0x98, 0x6, 0x98, 0x7, 0x80, 0xd, 0x60, + 0xc8, 0x13, 0xf9, 0xb3, 0xf7, 0x80, 0xef, 0x0, + 0xcf, 0xfe, 0x5f, 0xc0, 0x1f, 0xff, 0x1f, 0xc0, + 0xf, 0xfe, 0x37, 0xfe, 0x77, 0xf8, 0x33, 0xfc, + + /* U+E05F "" */ + 0xc0, 0x0, 0x38, 0x0, 0x7, 0x0, 0x4, 0xf9, + 0xf7, 0x9c, 0xff, 0xf3, 0xff, 0xfe, 0x7f, 0xff, + 0xce, 0xff, 0xf9, 0xdf, 0xff, 0x39, 0xff, 0xe7, + 0x3d, 0xfc, 0xe6, 0x3f, 0x9c, 0x7, 0xf3, 0x80, + 0xfe, 0x70, 0x1c, 0xce, 0x0, 0x1, 0xc0, 0x0, + 0x30, + + /* U+E060 "" */ + 0xc0, 0x0, 0x38, 0x0, 0x7, 0x0, 0x4, 0xf9, + 0xf7, 0x9c, 0xff, 0xf3, 0xff, 0xfe, 0x7f, 0xff, + 0xce, 0xff, 0xf9, 0xdf, 0xff, 0x39, 0xff, 0xe7, + 0x3d, 0xfc, 0xe6, 0x3f, 0x9c, 0x7, 0xf3, 0x80, + 0xfe, 0x70, 0x1c, 0xce, 0x0, 0x1, 0xc0, 0x0, + 0x30, + + /* U+E061 "" */ + 0xf0, 0x0, 0xfc, 0x0, 0xfe, 0x0, 0xff, 0x0, + 0xff, 0x0, 0xf7, 0x80, 0xf7, 0x80, 0xff, 0xc0, + 0xff, 0xe3, 0xff, 0xc3, 0xff, 0xc, 0xff, 0x33, + 0xf3, 0x33, 0xff, 0xc, 0xff, 0x3, 0xfe, 0x3, + + /* U+E062 "" */ + 0x0, 0x0, 0x5e, 0x0, 0x37, 0xe0, 0x19, 0xfc, + 0xc, 0x7f, 0x86, 0x1f, 0xe3, 0x7, 0x9d, 0x81, + 0xe7, 0xc0, 0x7f, 0xe0, 0x1f, 0xf0, 0x67, 0xf8, + 0x19, 0xfc, 0x18, 0x7e, 0x59, 0x9f, 0x36, 0x67, + 0x98, 0x61, 0xce, 0x6, 0xe7, 0x1, 0xb0, 0x0, + 0x0, + + /* U+E063 "" */ + 0xf, 0xc0, 0x1f, 0xe0, 0x3f, 0xf0, 0x7f, 0xf8, + 0x3f, 0xfc, 0x8f, 0x9c, 0xc7, 0x9c, 0xf1, 0xfe, + 0xfc, 0xff, 0xfe, 0x2, 0x7f, 0x3e, 0x3f, 0x6, + 0x3f, 0x3e, 0x3f, 0x6, 0x3f, 0xc, 0x3f, 0xf8, + + /* U+E064 "" */ + 0x7, 0xc0, 0x1f, 0xf0, 0x3e, 0xf8, 0x70, 0x18, + 0xf0, 0x1c, 0xf6, 0x1c, 0xe0, 0xe, 0xf0, 0x9f, + 0xf0, 0x1f, 0x70, 0x1f, 0x7e, 0xfc, 0x3f, 0xfc, + 0x3f, 0xfc, 0x3f, 0xf8, 0x3f, 0xe0, 0x1f, 0xc0, + + /* U+E065 "" */ + 0x0, 0x80, 0x0, 0xe2, 0x0, 0xff, 0x81, 0xff, + 0xc1, 0xff, 0xe1, 0xff, 0xf1, 0xf8, 0xfd, 0xfc, + 0x7e, 0x7e, 0x3f, 0x1f, 0x1f, 0xf, 0xff, 0x87, + 0x83, 0xc3, 0x80, 0xe1, 0xc0, 0x70, 0xff, 0xf8, + 0x3f, 0xf8, + + /* U+E066 "" */ + 0x1, 0x80, 0x0, 0x3c, 0x0, 0x7, 0xe0, 0x0, + 0xff, 0x0, 0x1f, 0xf8, 0x7, 0xff, 0xe0, 0x7e, + 0x7e, 0x7, 0xe7, 0xe0, 0x3e, 0x0, 0x3, 0xf7, + 0xfc, 0x3f, 0x7f, 0xc3, 0xf6, 0xc, 0x3f, 0x60, + 0xc1, 0xf6, 0xc, 0x0, 0x60, 0xc0, 0xf, 0xff, + 0x0, 0xff, 0xe0, + + /* U+E067 "" */ + 0x0, 0xc0, 0x1, 0xb6, 0x0, 0xed, 0xc0, 0x7b, + 0x78, 0x3e, 0xde, 0xf, 0x87, 0xc7, 0xe1, 0xf9, + 0xc3, 0xe, 0xf6, 0xdb, 0xbd, 0xfe, 0xfe, 0x3f, + 0x1f, 0xbf, 0xf7, 0xef, 0xfd, 0xf8, 0xfc, 0x7f, + 0x7f, 0xbd, 0xdb, 0x6e, 0x0, 0xc0, 0x0, + + /* U+E068 "" */ + 0x30, 0xc, 0x78, 0x1e, 0x78, 0x1e, 0x30, 0xc, + 0x0, 0x0, 0x60, 0xe, 0xee, 0x67, 0xdf, 0xf3, + 0xdf, 0xf3, 0xee, 0x67, 0x70, 0xe, 0x70, 0x1e, + 0x78, 0x1e, 0x78, 0x1e, 0x78, 0x1e, 0x70, 0x1e, + + /* U+E069 "" */ + 0xc0, 0x0, 0x39, 0x80, 0x7, 0x70, 0x0, 0xfe, + 0x0, 0x1f, 0xc0, 0x33, 0xf8, 0xe, 0x7f, 0x1, + 0xcf, 0xfe, 0x79, 0xff, 0xdf, 0x3f, 0xf7, 0xe7, + 0xfb, 0x8c, 0xe0, 0xc3, 0x9c, 0x1, 0xf3, 0x80, + 0x78, 0x70, 0x1c, 0xe, 0x6, 0x1, 0xc0, 0x0, + 0x30, + + /* U+E06A "" */ + 0x1e, 0x7, 0xf1, 0xe0, 0x0, 0x0, 0x1f, 0xef, + 0xff, 0xff, 0xf3, 0xfc, 0xfc, 0xf, 0x3, 0xf3, + 0xfc, 0xff, 0xff, 0xff, 0x7f, 0x80, + + /* U+E06B "" */ + 0x1e, 0x7, 0xf1, 0xe0, 0x0, 0x0, 0x1f, 0xef, + 0xff, 0xff, 0xf3, 0xfc, 0xfe, 0x1f, 0x87, 0xe1, + 0xfc, 0xff, 0xff, 0xff, 0x7f, 0x80, + + /* U+E06C "" */ + 0x3, 0x80, 0x1f, 0xc1, 0xff, 0xf7, 0xff, 0xff, + 0xef, 0xfe, 0x3, 0xfc, 0x7, 0xfb, 0xf, 0x60, + 0xc, 0xe1, 0x39, 0xc0, 0x71, 0x80, 0xc1, 0xef, + 0x3, 0xfc, 0x1, 0xf0, 0x1, 0xc0, + + /* U+E06D "" */ + 0x0, 0xf0, 0x1, 0xf8, 0x1, 0x98, 0x1, 0x98, + 0x1, 0x80, 0x1, 0x80, 0x71, 0x9e, 0x19, 0x98, + 0x19, 0x98, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x3f, 0xfc, + + /* U+E06E "" */ + 0x6, 0x18, 0xf, 0x18, 0x6, 0x0, 0x0, 0x0, + 0x0, 0xe0, 0x0, 0xe0, 0x7c, 0xe6, 0xfc, 0xe7, + 0xe0, 0xf, 0xc0, 0x7, 0xdf, 0xf3, 0xdf, 0xf3, + 0xdf, 0xf3, 0xe0, 0x7, 0xff, 0xff, 0x7f, 0xfe, + + /* U+E06F "" */ + 0xf, 0x80, 0x10, 0x7, 0xe0, 0x7f, 0xf7, 0xff, + 0xbb, 0xbb, 0x88, 0xfd, 0x57, 0xf2, 0xbf, 0xb5, + 0xf9, 0xae, 0xc4, 0x67, 0xff, 0x1f, 0xf0, 0x3e, + 0x0, + + /* U+E070 "" */ + 0x40, 0x0, 0xc, 0x0, 0x0, 0xff, 0xfc, 0xf, + 0xff, 0x80, 0xff, 0xf8, 0x4f, 0xff, 0x1c, 0xff, + 0xf3, 0xcf, 0xfe, 0x7c, 0xff, 0xc3, 0xe, 0x30, + 0x60, 0xc6, 0xc, 0xc, 0xc1, 0x80, 0xd8, 0x3f, + 0xcf, 0x7, 0xfc, 0xe0, 0xff, 0x8c, 0x0, 0x0, + 0xc0, 0x0, 0x8, + + /* U+E071 "" */ + 0xc0, 0x0, 0x38, 0x0, 0x7, 0xff, 0xf0, 0xff, + 0xfc, 0x1f, 0xff, 0x1b, 0xff, 0xe7, 0x7f, 0xf8, + 0xef, 0xdc, 0x1, 0xc0, 0x0, 0x38, 0x3, 0x7, + 0x30, 0xc0, 0xec, 0x3f, 0x9f, 0xf, 0xf3, 0xc3, + 0xfe, 0x70, 0xff, 0xce, 0x0, 0x1, 0xc0, 0x0, + 0x30, + + /* U+E072 "" */ + 0xc0, 0x0, 0x1c, 0x0, 0x1, 0xff, 0x98, 0x1f, + 0xf7, 0x80, 0xfc, 0xf0, 0x8f, 0xbf, 0x18, 0xf6, + 0x63, 0xce, 0xcc, 0x7c, 0xd9, 0x8f, 0xcf, 0x31, + 0xfc, 0xfe, 0x3f, 0xcf, 0x87, 0xfc, 0xf0, 0xff, + 0x8c, 0x1f, 0xf0, 0xc3, 0xfe, 0xc, 0x7f, 0xc0, + 0xc7, 0xf0, 0x8, + + /* U+E073 "" */ + 0x60, 0x0, 0x7, 0x0, 0x0, 0x38, 0xf0, 0x1, + 0xdf, 0x80, 0x6f, 0xf9, 0xe7, 0x7f, 0x9e, 0x7b, + 0xf9, 0xe3, 0x1e, 0xc, 0x0, 0xe0, 0x0, 0x7, + 0x0, 0x39, 0x39, 0xc7, 0x39, 0xce, 0xf7, 0xce, + 0x7f, 0x7e, 0x73, 0xf7, 0xf3, 0x9f, 0x7f, 0x9c, + 0x0, 0x0, 0x60, 0x0, 0x2, + + /* U+E074 "" */ + 0x3, 0x80, 0x3, 0x80, 0x37, 0xdc, 0x3f, 0xfc, + 0x1f, 0xf8, 0x1f, 0xf8, 0x39, 0xfc, 0xf9, 0xff, + 0xff, 0x9f, 0x3f, 0x9c, 0x1f, 0xf8, 0x1f, 0xf8, + 0x3f, 0xfc, 0x37, 0xdc, 0x3, 0x80, 0x3, 0x80, + + /* U+E075 "" */ + 0xc0, 0x0, 0x38, 0x30, 0x7, 0xc, 0x0, 0xe7, + 0x98, 0x1f, 0xfe, 0x3, 0xff, 0x0, 0x7f, 0xc0, + 0x4f, 0xf8, 0x79, 0xff, 0x9f, 0x3f, 0xe1, 0xe7, + 0xe0, 0x3c, 0xf0, 0xf, 0x9c, 0x7, 0xf3, 0x81, + 0x9e, 0x70, 0x3, 0xe, 0x0, 0xc1, 0xc0, 0x0, + 0x30, + + /* U+E076 "" */ + 0x6, 0x0, 0x2, 0x64, 0x0, 0x7f, 0xe0, 0x3, + 0xfc, 0x0, 0x37, 0xc0, 0xf, 0xff, 0x0, 0xff, + 0xf0, 0x3, 0xec, 0x0, 0x3f, 0xc3, 0x7, 0xfe, + 0xbe, 0x26, 0x4f, 0xc0, 0x60, 0xdc, 0x0, 0x1d, + 0xf0, 0x0, 0xfe, 0x0, 0xf, 0xc0, 0x0, 0xfe, + 0x0, 0x3, 0x0, + + /* U+E085 "" */ + 0x38, 0x70, 0xff, 0xc3, 0x7b, 0xe, 0x1c, 0x38, + 0x70, 0xe1, 0xc7, 0x87, 0xbf, 0x3f, 0xfc, 0xff, + 0xf3, 0xff, 0xcf, 0xff, 0x3f, 0xfc, 0xff, 0xf3, + 0xff, 0xcf, 0xff, 0x3f, + + /* U+E086 "" */ + 0x18, 0x70, 0xff, 0xc3, 0x7b, 0xe, 0xc, 0x38, + 0x70, 0xe1, 0xc7, 0x87, 0xbf, 0x3f, 0xcc, 0xdf, + 0x33, 0x3f, 0xcc, 0xff, 0x3f, 0xcc, 0xff, 0x33, + 0xff, 0xcf, 0xff, 0x3f, + + /* U+E097 "" */ + 0xc0, 0x0, 0x38, 0x18, 0x7, 0xf, 0x0, 0xe7, + 0xe3, 0x1f, 0x9c, 0xc3, 0xc3, 0xb0, 0x60, 0x7c, + 0x0, 0xf, 0x0, 0x1f, 0xc0, 0x7, 0xf0, + + /* U+E098 "" */ + 0x0, 0x1f, 0xc0, 0x7, 0xf0, 0x0, 0x3c, 0x18, + 0x1f, 0xf, 0xe, 0xc7, 0xe7, 0x33, 0x9f, 0x8d, + 0xc3, 0xc0, 0xe0, 0x60, 0x30, 0x0, 0x0, + + /* U+E09A "" */ + 0x3, 0x0, 0x1e, 0x0, 0xfc, 0x7, 0xf8, 0x3b, + 0x70, 0xcc, 0xc0, 0x30, 0x0, 0xc0, 0x3, 0x0, + 0xc, 0xc, 0x30, 0xf0, 0x3, 0xc0, 0xf, 0x0, + 0x3f, 0xff, 0xdf, 0xfe, + + /* U+E0A9 "" */ + 0x0, 0x0, 0xc, 0x0, 0x78, 0x1, 0xe0, 0xf, + 0xc0, 0x33, 0x0, 0xcc, 0x3f, 0xff, 0x18, 0x60, + 0xe1, 0xcf, 0xff, 0xdc, 0xe, 0x60, 0x19, 0x80, + 0x60, + + /* U+E0AC "" */ + 0x18, 0x6, 0xf, 0xe3, 0xfc, 0xd9, 0xb6, 0x6d, + 0x9b, 0xfc, 0xff, 0xb6, 0x3d, 0x8f, 0x63, 0xff, + 0xbf, 0xc1, 0x80, 0x60, + + /* U+E0B4 "" */ + 0x2c, 0xb, 0xf, 0xe3, 0xfc, 0xc1, 0xb0, 0x6c, + 0x1b, 0xfc, 0xff, 0xb0, 0x3c, 0xf, 0x3, 0xff, + 0xbf, 0xc2, 0xc0, 0xb0, + + /* U+E0B7 "" */ + 0x7f, 0x7, 0xf0, 0x7e, 0x7, 0xe0, 0xfe, 0xf, + 0xff, 0xff, 0xff, 0xfe, 0xff, 0xe0, 0x7c, 0x7, + 0x80, 0x78, 0x7, 0x0, 0xe0, 0xe, 0x0, 0xc0, + + /* U+E0BB "" */ + 0x7c, 0x1f, 0xf0, 0x7f, 0xc1, 0xff, 0x7, 0xfc, + 0x9f, 0xf7, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1b, 0x0, + 0x6f, 0xff, 0xdf, 0xff, + + /* U+E0CF "" */ + 0x0, 0xc0, 0x6, 0xc0, 0x6, 0x1f, 0x0, 0xf8, + 0xf, 0xc0, 0x7d, 0x80, 0xc1, 0x80, 0x1, 0x80, + 0x3f, 0xfc, 0x7f, 0xfe, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, + 0x3f, 0xfc, + + /* U+E0D8 "" */ + 0x7, 0xe0, 0xf, 0xf0, 0x7f, 0xfe, 0xff, 0xff, + 0xfc, 0x7f, 0xf8, 0x7f, 0xf9, 0xcf, 0xf3, 0x87, + 0xe1, 0xcf, 0xe1, 0xdf, 0xf2, 0x1f, 0xfe, 0x3f, + 0xff, 0xff, 0x7f, 0xfe, + + /* U+E0DF "" */ + 0x6, 0x0, 0xc0, 0x7f, 0x3f, 0xf6, 0x67, 0xcc, + 0x31, 0x86, 0x30, 0xc6, 0x18, 0xc1, 0x99, 0xbf, + 0xf1, 0xfc, 0xc, 0x1, 0x80, + + /* U+E0E3 "" */ + 0xc0, 0x0, 0xc0, 0x3, 0xc0, 0x3, 0xc1, 0x83, + 0xc1, 0x83, 0xc1, 0xb3, 0xd9, 0xb3, 0xd9, 0xb3, + 0xd9, 0xb3, 0xd9, 0xb3, 0xc0, 0x0, 0xc0, 0x0, + 0xff, 0xff, 0x7f, 0xff, + + /* U+E0E4 "" */ + 0xc0, 0x0, 0xcf, 0x80, 0xcf, 0x80, 0xc0, 0x0, + 0xc0, 0x0, 0xc3, 0xf0, 0xc3, 0xf0, 0xc0, 0x0, + 0xc0, 0x1e, 0xc0, 0x1e, 0xc0, 0x0, 0xc0, 0x0, + 0xff, 0xff, 0x7f, 0xff, + + /* U+E131 "" */ + 0x73, 0x9c, 0xe7, 0x39, 0xce, 0x73, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+E139 "" */ + 0xe, 0xe0, 0x1f, 0xc0, 0x3f, 0x80, 0x7f, 0xf, + 0x7d, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xfe, 0xff, + 0xff, 0xff, 0xff, 0xdf, 0x78, 0x7f, 0x0, 0xfe, + 0x1, 0xfc, 0x3, 0xb0, 0x0, + + /* U+E13A "" */ + 0x0, 0x8, 0xc, 0x78, 0x7c, 0xd9, 0xf9, 0xf2, + 0x61, 0xc4, 0x1, 0x8, 0x2, 0x10, 0x4, 0x20, + 0x8, 0x40, 0x10, 0x80, 0x21, 0x0, 0x47, 0xc, + 0x9b, 0x7e, 0x3e, 0x70, 0x38, 0x60, + + /* U+E13B "" */ + 0x70, 0x3b, 0xe1, 0xfd, 0x86, 0xfe, 0x1f, 0x70, + 0x38, 0x80, 0x43, 0x3, 0xf, 0xfc, 0x1f, 0xe0, + 0xc, 0x0, 0x30, 0x1, 0xe0, 0x7, 0x80, 0x33, + 0x0, 0x78, 0x1, 0xe0, + + /* U+E13C "" */ + 0x70, 0x41, 0xf1, 0x83, 0x67, 0xc6, 0xcf, 0xc7, + 0xd, 0x84, 0x9, 0x8, 0x2, 0x10, 0x4, 0x20, + 0x8, 0x40, 0x10, 0x80, 0x21, 0x0, 0x4f, 0x83, + 0xfb, 0x6, 0xfe, 0xf, 0xb8, 0xe, + + /* U+E140 "" */ + 0x0, 0x0, 0xd8, 0x3e, 0x1f, 0xe7, 0xde, 0xdb, + 0x32, 0x46, 0x48, 0xdb, 0x1b, 0x63, 0xc8, 0x39, + 0x33, 0xfe, 0x7f, 0x89, 0x1, 0x20, + + /* U+E152 "" */ + 0xf, 0xc1, 0xfe, 0x38, 0x76, 0x1, 0xe0, 0xc, + 0x0, 0xc6, 0xc, 0x7c, 0xc6, 0xe, 0x60, 0x66, + 0x3, 0x67, 0x1f, 0xe0, 0xfc, + + /* U+E163 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xc0, 0x3, 0xc0, 0x3, + 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x3, + 0xc0, 0x3, 0xc0, 0x3, 0xff, 0xff, 0x7f, 0xfe, + 0x3, 0xc0, 0x3, 0xc0, 0x1f, 0xf8, + + /* U+E169 "" */ + 0x0, 0xc0, 0xc, 0xf, 0xf0, 0xc, 0x0, 0xc1, + 0xfc, 0x3f, 0xc7, 0x1c, 0x60, 0xc6, 0xc, 0x60, + 0xc7, 0x1c, 0x3f, 0xc1, 0xfc, 0x0, 0xf, 0xff, + + /* U+E16D "" */ + 0x8, 0x7c, 0x1c, 0x38, 0x3e, 0x10, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0xf1, 0xcf, 0xf1, 0xcf, 0xf1, 0xcf, 0xff, 0xff, + 0xe0, 0x87, 0xe0, 0x87, 0xe0, 0x87, 0xff, 0xff, + 0x7f, 0xfe, + + /* U+E17B "" */ + 0xff, 0xff, 0x3f, 0xff, 0xc7, 0xff, 0xe0, 0xff, + 0xf0, 0x1f, 0xf8, 0x3, 0xf8, 0x0, 0x7d, 0xf0, + 0xe, 0xfe, 0x3, 0x6e, 0xc0, 0xdd, 0x70, 0x37, + 0xbc, 0xd, 0xd7, 0x1, 0x6e, 0xc0, 0xf, 0xe0, + 0x1, 0xf0, + + /* U+E184 "" */ + 0x0, 0xf0, 0x1f, 0x1, 0x80, 0x38, 0x3, 0x0, + 0x30, 0x7f, 0xe7, 0xfe, 0xc, 0x0, 0xc0, 0x1c, + 0x1, 0x80, 0xf8, 0xf, 0x0, + + /* U+E185 "" */ + 0x3c, 0x0, 0xff, 0x0, 0xff, 0xfe, 0xff, 0xff, + 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xfe, + + /* U+E18F "" */ + 0x3f, 0xc7, 0xf8, 0xc0, 0x18, 0x3, 0x0, 0x7f, + 0xf, 0xe1, 0x80, 0x30, 0x1f, 0xe0, 0xc0, 0x18, + 0x3, 0x0, 0x60, 0x0, + + /* U+E19A "" */ + 0x6, 0x0, 0x60, 0x1f, 0x83, 0xfc, 0x76, 0xee, + 0x60, 0xc6, 0xc, 0x7f, 0xc7, 0xfc, 0x63, 0x66, + 0x77, 0x6e, 0x3f, 0xc1, 0xf8, 0x6, 0x0, 0x60, + + /* U+E19B "" */ + 0x0, 0x1, 0x3f, 0xff, 0xff, 0xff, 0xff, 0x0, + 0x7, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xf1, 0x0, + 0xf8, 0x40, 0x3e, 0x30, 0xf, 0xf8, 0x7, 0xe0, + 0x1, 0xf0, 0x0, 0x7c, 0x0, 0xe, 0x0, 0x0, + + /* U+E1A8 "" */ + 0x2, 0x64, 0x3, 0x6c, 0x1, 0x68, 0x0, 0x0, + 0xc, 0x0, 0x36, 0x33, 0x3b, 0x33, 0x5d, 0xb3, + 0x6f, 0xf7, 0x37, 0xf7, 0xdf, 0xf7, 0xef, 0xf7, + 0x7f, 0xf7, 0x3f, 0xe6, 0x1f, 0xee, 0x7, 0x88, + + /* U+E1B0 "" */ + 0x0, 0x80, 0x0, 0xe0, 0x0, 0xf8, 0x1, 0xff, + 0x1, 0xff, 0xc1, 0xff, 0xf1, 0xf8, 0xfc, 0xfc, + 0x7e, 0x7e, 0x3f, 0x1f, 0x1f, 0xf, 0xff, 0x87, + 0x83, 0xc3, 0x80, 0xe1, 0xc0, 0x70, 0xff, 0xf8, + 0x3f, 0xf8, + + /* U+E1BC "" */ + 0xff, 0xdf, 0x80, 0x70, 0xc, 0xff, 0xc0, 0xc0, + 0xe3, 0xf8, 0xf8, 0xe, 0x1, 0xe0, 0x3c, 0x3, + 0x80, 0x60, + + /* U+E1C4 "" */ + 0x30, 0x63, 0xe, 0x31, 0xc3, 0x78, 0x3e, 0x3, + 0xc0, 0xff, 0xff, 0xff, 0x3c, 0x3, 0xe0, 0x37, + 0x83, 0x1c, 0x30, 0xe3, 0x6, + + /* U+E1C8 "" */ + 0x9, 0x0, 0x90, 0xf, 0x3, 0xfc, 0x79, 0xe6, + 0x96, 0xc9, 0x3c, 0x93, 0xc0, 0xc, 0x0, 0x60, + 0x7, 0x0, 0xff, 0xff, 0xff, + + /* U+E1D3 "" */ + 0x30, 0x3, 0x0, 0x30, 0x3, 0x0, 0x31, 0x83, + 0xf8, 0x7c, 0xf, 0x0, 0x30, 0x3, 0x0, 0x30, + 0x3, 0x0, 0x3f, 0xf3, 0xff, + + /* U+E1D5 "" */ + 0x6, 0x0, 0x60, 0x1f, 0x3, 0xfc, 0x76, 0xee, + 0x66, 0xc6, 0x3c, 0x63, 0xc6, 0x3c, 0x63, 0xc6, + 0x3c, 0x63, 0xc6, 0x3c, 0x63, + + /* U+E1D7 "" */ + 0x0, 0xf0, 0x0, 0x3f, 0xc0, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xdc, 0x3, 0xbd, 0xc0, 0x3b, 0xdf, + 0xff, 0xbd, 0xe0, 0x7b, 0x7e, 0x7, 0x67, 0xff, + 0xfe, 0xf, 0xff, 0x0, 0x1f, 0x80, + + /* U+E1ED "" */ + 0x0, 0xc0, 0x8, 0x1, 0x8f, 0x9c, 0xff, 0xec, + 0x73, 0xc6, 0x3c, 0x63, 0xc6, 0x3c, 0x63, 0xce, + 0x3c, 0xe3, 0xde, 0x3d, 0xe3, 0x30, 0x3, 0x0, + 0x20, 0x0, + + /* U+E1F3 "" */ + 0xf, 0xff, 0x87, 0xff, 0xf1, 0x9f, 0xcc, 0x6e, + 0x3b, 0xdf, 0x7, 0xf7, 0xc1, 0xfd, 0xf0, 0x7f, + 0x6e, 0x3b, 0xd9, 0xfc, 0xf7, 0xff, 0xfc, 0xff, + 0xfb, 0x0, 0x0, 0xc0, 0x0, 0x1f, 0xff, 0xc0, + + /* U+E1F6 "" */ + 0x30, 0x30, 0xc0, 0xc3, 0x83, 0xf, 0xc, 0x3e, + 0x30, 0xd8, 0xc3, 0x73, 0x3f, 0xff, 0x31, 0xf0, + 0xc3, 0xc3, 0x7, 0xc, 0x1c, 0x30, 0x30, 0x0, + 0x0, + + /* U+E1FE "" */ + 0xff, 0xff, 0xff, 0xf0, 0xff, 0xf, 0xf9, 0xfd, + 0xfb, 0xcf, 0x3c, 0xf3, 0xcf, 0x3c, 0xf3, 0xdf, + 0xbf, 0x9f, 0xf0, 0xff, 0xf, 0xff, 0xff, 0xff, + + /* U+E209 "" */ + 0xe0, 0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, + 0x7f, 0xcf, 0x8f, 0xff, 0xc3, 0xff, 0x0, 0x7f, + 0xc0, 0xf, 0xe0, 0x1, 0xf8, 0x0, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xe0, 0x1, 0xc0, + + /* U+E221 "" */ + 0x3f, 0x83, 0xfe, 0x1c, 0x38, 0xe0, 0xcf, 0xff, + 0xb8, 0x31, 0xc1, 0x8e, 0x1c, 0x7f, 0xc3, 0xf8, + 0x1c, 0x0, 0xe0, 0x7, 0x0, 0x10, 0x0, + + /* U+E222 "" */ + 0x3f, 0x83, 0xfe, 0x1c, 0x39, 0xff, 0xf7, 0x6, + 0x38, 0x33, 0xff, 0xee, 0x1c, 0x7f, 0xc3, 0xfc, + 0x1c, 0x0, 0xe0, 0x7, 0x0, 0x10, 0x0, + + /* U+E22D "" */ + 0x1, 0x80, 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, + 0x3, 0xc0, 0x3, 0xc0, 0x7, 0xe0, 0xf, 0xf0, + 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0xff, 0xff, + 0xf3, 0xcf, 0x3, 0xc0, 0x3, 0xc0, 0x7, 0xe0, + 0xf, 0xf0, 0xc, 0x30, + + /* U+E23D "" */ + 0xfc, 0x0, 0xfe, 0x0, 0xc7, 0x0, 0xc3, 0x0, + 0xc3, 0x0, 0xc3, 0x0, 0xc7, 0x7c, 0xfe, 0x7e, + 0xfc, 0x63, 0xcc, 0x63, 0xcc, 0x63, 0xce, 0x7e, + 0xc6, 0x7c, 0xc6, 0x60, 0x0, 0x60, 0x0, 0x60, + + /* U+E289 "" */ + 0x0, 0xf, 0xc0, 0x3, 0xf0, 0x0, 0xc0, 0x0, + 0x30, 0x0, 0xfc, 0x0, 0x3f, 0x0, 0xc, 0x0, + 0x3, 0x0, 0xf, 0xc0, 0x3, 0xf0, 0x0, 0xc0, + 0x0, 0x30, 0x0, 0xfc, 0x0, 0x3f, 0x0, 0x0, + + /* U+E29C "" */ + 0x0, 0x0, 0xf, 0x3, 0xc3, 0xc0, 0xf0, 0xf0, + 0x3c, 0x3c, 0xf, 0x6, 0x1, 0x81, 0x80, 0x63, + 0xff, 0xff, 0xff, 0xff, 0xc0, 0x30, 0x0, 0xc, + 0x0, 0x7, 0x80, 0x1, 0xe0, 0x0, 0x78, 0x0, + 0x1e, 0x0, 0x0, 0x0, + + /* U+E2B7 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0x1f, 0x80, 0x7e, 0x1, 0xff, 0xff, 0xff, 0xff, + 0x3f, 0x3c, 0xfc, 0xff, 0xff, 0x7f, 0xf9, 0xc0, + 0xe7, 0x3, 0x80, + + /* U+E2BB "" */ + 0x30, 0x6, 0x20, 0xfc, 0x3e, 0x7, 0x10, 0x7f, + 0x1f, 0x3, 0x80, 0x30, 0x66, 0xc, 0xc1, 0x98, + 0x63, 0xfc, 0x7e, 0x0, + + /* U+E2C5 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, + 0xe0, 0x63, 0xc6, 0x23, 0xcf, 0x23, 0xcf, 0x37, + 0xc6, 0x37, 0xe0, 0x77, 0xf0, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, 0x30, 0xc, + + /* U+E2C7 "" */ + 0x60, 0x6, 0xff, 0xff, 0xff, 0xff, 0x60, 0xe, + 0x60, 0x1c, 0x60, 0x18, 0x60, 0x70, 0x60, 0xf0, + 0x60, 0xf0, 0x60, 0x70, 0x60, 0x18, 0x60, 0x1c, + 0x60, 0xe, 0xff, 0xff, 0xff, 0xff, 0x60, 0x6, + + /* U+E2CA "" */ + 0x0, 0x0, 0x0, 0x80, 0xc, 0xe3, 0xc6, 0x23, + 0xef, 0xc1, 0xf7, 0xe2, 0x70, 0xc3, 0x90, 0x63, + 0xc0, 0x3, 0xf0, 0x3, 0xf0, 0x3, 0xf0, 0x3, + 0xf8, 0xc1, 0xf8, 0x61, 0xf8, 0xfd, 0xf8, 0x7e, + 0xf8, 0xc, 0x38, 0x6, 0x8, 0x0, 0x0, + + /* U+E2CD "" */ + 0x0, 0xd, 0x80, 0x6d, 0x80, 0x3d, 0x80, 0x1f, + 0xb0, 0x6f, 0xb0, 0x3b, 0xf0, 0x1e, 0xfc, 0x6f, + 0x3e, 0x7b, 0xe6, 0x3c, 0xf8, 0xe, 0x3e, 0x7, + 0xee, 0x1, 0xf8, 0x1, 0xfe, 0x1, 0x9e, 0x1, + 0x80, 0x0, + + /* U+E2CE "" */ + 0x0, 0x10, 0x0, 0xe0, 0x63, 0x83, 0xe4, 0x19, + 0xc0, 0xf, 0x81, 0xfc, 0xf, 0xf0, 0x61, 0xfb, + 0x3, 0xfc, 0xd, 0xb0, 0x36, 0xc0, 0xd9, 0x86, + 0x67, 0xf9, 0x87, 0x80, + + /* U+E2E6 "" */ + 0xe0, 0xf, 0x0, 0x30, 0x3, 0x0, 0xfc, 0xe7, + 0x8f, 0x30, 0x33, 0x3, 0x30, 0x33, 0x3, 0x30, + 0x63, 0xe, 0x3f, 0xc1, 0xf0, + + /* U+E2EB "" */ + 0x7, 0xe0, 0x0, 0x0, 0x3f, 0xfc, 0x0, 0x0, + 0x6d, 0xb6, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfc, + 0x1f, 0xf8, 0x1f, 0xf0, + + /* U+E307 "" */ + 0xf3, 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, + 0x0, 0x0, 0x0, 0x0, 0xf3, 0xcf, 0xf3, 0xcf, + 0xf3, 0xcf, 0xf3, 0xcf, + + /* U+E31E "" */ + 0xe, 0x3, 0x80, 0xe0, 0x0, 0x0, 0x7, 0x3, + 0xe0, 0xfc, 0x7f, 0xbf, 0xff, 0xfc, 0xff, 0x3f, + 0x8f, 0xc1, 0xb0, 0x6c, 0x1b, 0x6, 0xc0, + + /* U+E3AF "" */ + 0x1, 0x80, 0x1, 0xe0, 0x1, 0xfb, 0x81, 0xff, + 0xc1, 0xff, 0xe1, 0xff, 0xf1, 0xff, 0xfd, 0xff, + 0xfe, 0x7f, 0xff, 0x1f, 0xff, 0xf, 0x8f, 0x87, + 0xc7, 0xc3, 0xe3, 0xe1, 0xf1, 0xf0, 0xf8, 0xf8, + 0x3f, 0xf8, + + /* U+E3B1 "" */ + 0x1, 0x80, 0x1, 0xe0, 0x1, 0xfc, 0x1, 0xff, + 0x1, 0xff, 0xc1, 0xff, 0xf1, 0xff, 0xfd, 0xff, + 0xff, 0x7d, 0xff, 0x1f, 0x3f, 0xf, 0xcf, 0x87, + 0xe3, 0xc3, 0xe3, 0xe1, 0xe3, 0xf0, 0xf1, 0xf8, + 0x78, 0xf8, + + /* U+E3B2 "" */ + 0x0, 0x80, 0x0, 0xe0, 0x1, 0xfc, 0x1, 0xff, + 0x1, 0xff, 0xc1, 0xff, 0xf1, 0xff, 0xfd, 0xfe, + 0xff, 0x3f, 0x7e, 0x1e, 0xf, 0xf, 0x7, 0x87, + 0xef, 0xc3, 0xf7, 0xe1, 0xff, 0xf0, 0xff, 0xf8, + 0x3f, 0xf8, + + /* U+E3F5 "" */ + 0x6, 0x0, 0xc0, 0x3e, 0x1f, 0xe7, 0xe, 0xc0, + 0x30, 0x6, 0x0, 0xc0, 0x18, 0x1, 0x80, 0x38, + 0x73, 0xfc, 0x1f, 0x1, 0x80, 0x30, + + /* U+E43C "" */ + 0x6, 0x0, 0x60, 0x6, 0x0, 0x60, 0x6, 0xf, + 0xff, 0xff, 0xf0, 0x60, 0x6, 0x0, 0x60, 0x6, + 0x0, 0x60, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, + + /* U+E445 "" */ + 0x0, 0x0, 0x1, 0x80, 0x1, 0xc0, 0x1, 0xc0, + 0x5, 0xe0, 0xd, 0xf0, 0x1d, 0xf0, 0x1d, 0xf8, + 0x3d, 0xfc, 0x3d, 0xfc, 0x3d, 0xfc, 0x1, 0x80, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0x3f, 0xfc, + + /* U+E447 "" */ + 0x3e, 0x7e, 0xc0, 0xc0, 0xe0, 0x7c, 0xfe, 0xc7, + 0xe3, 0x7f, 0x3e, 0x7, 0x3, 0x3, 0x7e, 0x7c, + + /* U+E448 "" */ + 0x7f, 0xff, 0xc0, 0x0, 0xc0, 0x0, 0xff, 0xb8, + 0x3d, 0xbc, 0x3d, 0xbe, 0x1f, 0xbf, 0xf, 0xbc, + 0x7, 0xbb, 0x0, 0x7, 0x0, 0xf, 0x1, 0xc2, + 0x1, 0xdc, 0x1, 0xd8, + + /* U+E46C "" */ + 0x0, 0x8, 0xfc, 0x1c, 0xfe, 0x1e, 0xc7, 0x3e, + 0xc3, 0x60, 0xc3, 0x60, 0xc3, 0x60, 0xc7, 0x3c, + 0xfe, 0x1e, 0xfc, 0x3, 0xcc, 0x3, 0xcc, 0x3, + 0xce, 0x3e, 0xc6, 0x3c, 0xc6, 0x1c, 0x0, 0x8, + + /* U+E473 "" */ + 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xcf, 0x3, 0xcf, + 0x3, 0xcf, 0x3, 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, + 0xf3, 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, + 0xf3, 0xcf, 0xf3, 0xcf, + + /* U+E476 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfd, 0xff, 0xe0, 0x30, 0x0, 0xc0, 0x67, 0x9b, + 0x9e, 0x7f, 0x33, 0xfe, 0x1f, 0xfc, 0xfd, 0xff, + 0xe0, + + /* U+E477 "" */ + 0x7f, 0xe3, 0xf8, 0xcf, 0xe3, 0x3f, 0xbf, 0xfe, + 0x78, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfb, + 0xff, 0xfc, 0x0, 0xf0, 0x3, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+E47A "" */ + 0x7f, 0xe3, 0xf8, 0xcc, 0x63, 0x31, 0xbf, 0xfe, + 0x79, 0xf0, 0xc0, 0x0, 0x0, 0x0, 0x7f, 0xfb, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+E47B "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7c, 0x7e, 0xfe, 0x7f, 0xff, 0xff, 0xfa, 0xff, + 0xf0, 0x7b, 0xe0, 0x3b, 0xe0, 0x3f, 0x7e, 0x36, + 0x7f, 0xee, 0x3e, 0x7c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+E490 "" */ + 0xc0, 0x0, 0x38, 0x78, 0x7, 0x3f, 0x0, 0xe7, + 0xc0, 0x1c, 0xf1, 0x83, 0x80, 0xe0, 0x7e, 0xf0, + 0xf, 0xf0, 0x9, 0xfc, 0x3f, 0x3f, 0xff, 0xe7, + 0xfc, 0x3c, 0xf0, 0xf, 0x1c, 0xf, 0xd3, 0x87, + 0x72, 0x71, 0x9c, 0xce, 0x1, 0x21, 0xc0, 0x0, + 0x30, + + /* U+E494 "" */ + 0x7f, 0x0, 0x7f, 0xc0, 0x3f, 0xb0, 0x1f, 0xdc, + 0xf, 0xe7, 0x7, 0xff, 0x83, 0xff, 0xc1, 0xff, + 0x0, 0xff, 0x3e, 0x7f, 0xbf, 0xbf, 0xbd, 0xff, + 0xde, 0xff, 0xec, 0x1f, 0xf7, 0xbf, 0xfb, 0xde, + 0xfe, 0xfe, 0x0, 0x3e, 0x0, + + /* U+E4A5 "" */ + 0x1f, 0xfe, 0x3, 0xff, 0xf0, 0x3f, 0xff, 0x7, + 0xff, 0xf8, 0x7f, 0xff, 0x87, 0xff, 0xc0, 0x3f, + 0xf9, 0xc3, 0x6, 0x36, 0x30, 0x63, 0x63, 0x6, + 0x36, 0x30, 0x67, 0xf3, 0xfe, 0x7f, 0x3f, 0xe7, + 0xf3, 0xfe, 0x7f, 0x0, 0x7, 0xf0, 0x0, 0x7f, + + /* U+E4A8 "" */ + 0x3, 0xc0, 0x19, 0x98, 0x3b, 0xdc, 0x3f, 0xfc, + 0xf, 0xf0, 0x1f, 0xf8, 0xdf, 0xfb, 0xff, 0xff, + 0xff, 0xff, 0xdf, 0xfb, 0x1f, 0xf8, 0xf, 0xf0, + 0x3f, 0xfc, 0x3b, 0xdc, 0x19, 0x98, 0x3, 0xc0, + + /* U+E4A9 "" */ + 0xc0, 0x0, 0x38, 0x78, 0x7, 0xcc, 0xc0, 0xe7, + 0x98, 0x1f, 0xfe, 0x3, 0xfe, 0x0, 0x7f, 0xc1, + 0xaf, 0xf6, 0x7d, 0xff, 0x9f, 0x3f, 0xe6, 0xe7, + 0xd8, 0x3c, 0xf0, 0x7, 0x98, 0x7, 0xf3, 0x81, + 0x9e, 0x60, 0x33, 0xc, 0x1, 0xe1, 0x80, 0x0, + 0x30, + + /* U+E4AA "" */ + 0x3, 0xc0, 0x0, 0xfc, 0x0, 0x19, 0x80, 0x3, + 0x30, 0x0, 0x7e, 0x0, 0x7, 0x80, 0x0, 0x60, + 0x1, 0x8c, 0x0, 0x79, 0x8f, 0x9f, 0xb3, 0xf8, + 0xc6, 0xff, 0x98, 0xdf, 0x71, 0x9b, 0x5e, 0x3b, + 0x73, 0xc3, 0xef, 0xf8, 0x1c, 0xfe, 0x0, 0xf, + 0x80, + + /* U+E4AB "" */ + 0x3, 0xc0, 0x0, 0xfc, 0x0, 0x19, 0x80, 0x3, + 0x30, 0x0, 0x7e, 0x0, 0x7, 0x80, 0x0, 0x60, + 0x1, 0x8c, 0x0, 0x79, 0x8f, 0x9f, 0xb3, 0xb8, + 0xc6, 0xf7, 0x98, 0xde, 0xf1, 0x9b, 0xde, 0x3b, + 0x7f, 0xc3, 0xef, 0x78, 0x1c, 0xfe, 0x0, 0xf, + 0x80, + + /* U+E4AC "" */ + 0x3, 0xc0, 0x0, 0xfc, 0x0, 0x19, 0x80, 0x3, + 0x30, 0x0, 0x7e, 0x0, 0x7, 0x80, 0x0, 0x60, + 0x1, 0x8c, 0x0, 0x79, 0x8f, 0x9f, 0xb3, 0xf8, + 0xc6, 0xed, 0x98, 0xdc, 0x71, 0x9b, 0xce, 0x3b, + 0x71, 0xc3, 0xee, 0xd8, 0x3c, 0xfe, 0x0, 0xf, + 0x80, + + /* U+E4AD "" */ + 0x3, 0xc0, 0x0, 0xfc, 0x0, 0x19, 0x80, 0x3, + 0x30, 0x0, 0x7e, 0x0, 0x7, 0x80, 0x0, 0x60, + 0x1, 0x8c, 0x1c, 0x79, 0x86, 0xdf, 0xb0, 0xd8, + 0xc6, 0x1b, 0x18, 0xc7, 0xf1, 0x98, 0xfe, 0x3b, + 0x5f, 0xc3, 0xfb, 0xf8, 0x1f, 0x7f, 0x0, 0xf, + 0xe0, + + /* U+E4AF "" */ + 0x18, 0x18, 0x18, 0x3c, 0x18, 0x7e, 0x18, 0xff, + 0x18, 0x18, 0x0, 0x18, 0x0, 0x18, 0xff, 0xff, + 0xff, 0xff, 0x18, 0x0, 0x18, 0x0, 0x18, 0x18, + 0xff, 0x18, 0x7e, 0x18, 0x3c, 0x18, 0x18, 0x18, + + /* U+E4B0 "" */ + 0xc, 0x30, 0x3, 0x1e, 0x0, 0xcf, 0xc0, 0x33, + 0xf0, 0xc, 0x30, 0x0, 0xc, 0x0, 0x3, 0x1, + 0xff, 0x9c, 0x7f, 0xef, 0x83, 0x3, 0x60, 0xc0, + 0xd8, 0xb4, 0x7f, 0x7f, 0x9f, 0xcf, 0xc7, 0xf1, + 0xe1, 0xfc, 0x30, 0x7f, 0x0, 0x1f, 0xc0, + + /* U+E4B3 "" */ + 0x0, 0xfc, 0x0, 0x1f, 0x90, 0x3, 0xf6, 0x0, + 0x66, 0xc0, 0xc, 0xfe, 0x21, 0xff, 0xc7, 0x33, + 0xff, 0xf6, 0x73, 0xfe, 0xfe, 0x63, 0x99, 0xfc, + 0x43, 0x39, 0x80, 0x7f, 0x30, 0xf, 0xfe, 0x1, + 0xff, 0xc0, 0x3f, 0xf8, 0x7, 0xff, + + /* U+E4B5 "" */ + 0x0, 0x60, 0x0, 0xf, 0x0, 0x1, 0xf8, 0x0, + 0x36, 0xc0, 0x0, 0x60, 0x0, 0x6, 0x0, 0x3f, + 0x6f, 0xc3, 0xf6, 0xfc, 0x3f, 0x6f, 0xc3, 0xf6, + 0xfc, 0x3f, 0x6f, 0xc3, 0x86, 0xc, 0x0, 0x0, + 0x0, 0x60, 0xe0, 0xf, 0x9f, 0xf, 0x8f, 0x1f, + 0x0, 0x0, 0x0, + + /* U+E4B6 "" */ + 0x1f, 0xc3, 0x1, 0xfc, 0x78, 0x1f, 0xcf, 0xc1, + 0xfd, 0xb4, 0x1f, 0xc3, 0x1, 0xfc, 0x30, 0x1f, + 0xc3, 0x7, 0xff, 0xfe, 0x7f, 0xff, 0xe7, 0xff, + 0xfe, 0x70, 0x70, 0xe0, 0x0, 0x0, 0x7, 0xe, + 0x0, 0xf9, 0xf0, 0xf8, 0xf1, 0xf0, 0x0, 0x0, + + /* U+E4B7 "" */ + 0x3e, 0x6, 0x7c, 0xc, 0x78, 0x1, 0xf0, 0x7, + 0x66, 0x7c, 0xc, 0xf0, 0x0, 0x0, 0x0, 0x6, + 0x66, 0xc, 0xcc, 0x0, 0x0, 0x0, 0x6, 0x66, + 0x6c, 0xcc, 0xc0, + + /* U+E4B8 "" */ + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x3c, 0x18, 0x18, + 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, + + /* U+E4B9 "" */ + 0x10, 0xc, 0x10, 0xc, 0x10, 0xc, 0xfc, 0x3f, + 0x78, 0x1e, 0x30, 0xc, 0x0, 0x0, 0x1, 0x80, + 0x23, 0xc4, 0x71, 0x8e, 0x70, 0xe, 0x0, 0x0, + 0x3, 0xc4, 0x77, 0xee, 0xf7, 0xef, 0xf7, 0xef, + 0xf7, 0xef, + + /* U+E4BA "" */ + 0xc0, 0x0, 0xf0, 0x0, 0x3c, 0x21, 0x8f, 0x18, + 0x73, 0xcf, 0xfe, 0xf3, 0xff, 0xbc, 0x61, 0xcf, + 0x8, 0x63, 0xc0, 0x0, 0xf0, 0x0, 0x30, + + /* U+E4BB "" */ + 0x7c, 0xf0, 0x1e, 0x7c, 0x8f, 0x7, 0xef, 0x81, + 0xf6, 0x40, 0xff, 0x0, 0x7f, 0x0, 0x1, 0x80, + 0x0, 0xc0, 0x1, 0x0, 0x1, 0xbe, 0x0, 0xdf, + 0x2, 0x6f, 0x3, 0xe7, 0xe1, 0xe3, 0x7e, 0xf8, + 0xf, 0x3c, + + /* U+E4BC "" */ + 0x0, 0x60, 0x1, 0xe0, 0x7, 0xe0, 0x1f, 0xe0, + 0x36, 0xc0, 0xc, 0x6, 0x18, 0x1c, 0x30, 0x70, + 0x61, 0xff, 0xc3, 0xff, 0x83, 0x87, 0x3, 0x86, + 0x3, 0xc, 0x0, 0x1c, 0x0, 0x1f, 0x0, 0x1e, + + /* U+E4BD "" */ + 0xc0, 0x3, 0xec, 0x37, 0x7c, 0x3e, 0x3c, 0x3c, + 0x7c, 0x3e, 0x7c, 0x3e, 0x1, 0x80, 0x3, 0xc0, + 0x3, 0xc0, 0x1, 0x80, 0x7c, 0x3e, 0x7c, 0x3e, + 0x3c, 0x3c, 0x7c, 0x3e, 0xec, 0x37, 0xc0, 0x3, + + /* U+E4BE "" */ + 0x1, 0x80, 0x1, 0x80, 0x7, 0xe0, 0x7, 0xe0, + 0x3, 0xc0, 0x31, 0x8c, 0x38, 0x1c, 0xfd, 0xbf, + 0xfd, 0xbf, 0x38, 0x1c, 0x31, 0x8c, 0x3, 0xc0, + 0x7, 0xe0, 0x7, 0xe0, 0x1, 0x80, 0x1, 0x80, + + /* U+E4BF "" */ + 0x98, 0x6, 0x76, 0x1, 0xb7, 0x80, 0x7b, 0xe7, + 0x9f, 0x73, 0xf3, 0x81, 0xce, 0x0, 0xe1, 0xc0, + 0x38, 0x70, 0x7, 0x38, 0x3e, 0xfc, 0xff, 0x9e, + 0x7d, 0xe0, 0x1e, 0xd8, 0x6, 0xe6, 0x1, 0x90, + + /* U+E4C0 "" */ + 0x0, 0x20, 0x1, 0xc0, 0x3, 0x8f, 0xff, 0x7f, + 0xff, 0x80, 0xec, 0x7, 0x30, 0x8, 0xc1, 0x0, + 0xe, 0x0, 0x1c, 0x1f, 0xf8, 0xff, 0xe3, 0x7, + 0xc, 0x38, 0x30, 0x40, + + /* U+E4C1 "" */ + 0x0, 0x0, 0x3, 0x6, 0x18, 0x3c, 0xfe, 0xf3, + 0xfd, 0x8e, 0x30, 0x1c, 0xc0, 0x3, 0x0, 0x0, + 0x20, 0x0, 0xe0, 0x1, 0xc6, 0x7f, 0x3f, 0xfc, + 0xfc, 0x61, 0xb3, 0x0, 0xc0, 0x0, + + /* U+E4C2 "" */ + 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x30, + 0x31, 0xe1, 0xef, 0xcf, 0xff, 0x3f, 0x30, 0x30, + 0xc0, 0xc3, 0x3, 0xc, 0xc, 0x30, 0x30, 0xc0, + 0xc3, 0x3, 0xc, 0xc, + + /* U+E4C3 "" */ + 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, + 0x21, 0x84, 0xf9, 0x9f, 0xf9, 0x9f, 0xf9, 0x9f, + 0xf9, 0x9f, 0xfb, 0xdf, 0xfb, 0xdf, 0xf9, 0x9f, + 0xfc, 0x3f, 0xfc, 0x7f, 0xff, 0xff, 0x7f, 0xfe, + + /* U+E4C4 "" */ + 0x1e, 0x7, 0x81, 0xe0, 0x78, 0x1e, 0x7, 0x87, + 0xf9, 0xfe, 0xff, 0xfc, 0xff, 0x3f, 0x87, 0xe1, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xe0, + + /* U+E4C5 "" */ + 0x3e, 0x1f, 0x0, 0x0, 0x7, 0xf7, 0xff, 0xfe, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, + 0xfd, 0xff, 0xff, 0xbf, 0x80, + + /* U+E4C6 "" */ + 0x1, 0x80, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfc, + 0x1f, 0xf8, 0x1f, 0xf0, + + /* U+E4C7 "" */ + 0x3, 0x8e, 0x7, 0x8f, 0xf, 0x8f, 0xf, 0xff, + 0xf, 0xff, 0x0, 0x1f, 0xff, 0xef, 0xff, 0xef, + 0xff, 0xef, 0x0, 0xf, 0x0, 0x1f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xdf, + + /* U+E4C8 "" */ + 0xff, 0xff, 0xff, 0xff, 0x66, 0x66, 0x66, 0x66, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7c, 0x3e, 0x38, 0x1c, 0x38, 0x1c, 0x38, 0x1c, + 0x38, 0x1c, 0x38, 0x1c, + + /* U+E4C9 "" */ + 0xff, 0xff, 0x3f, 0xff, 0xc6, 0x66, 0x61, 0x99, + 0x98, 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xfc, 0x3, + 0xfe, 0x7c, 0x7c, 0x3f, 0x8e, 0x1f, 0xf3, 0x87, + 0xdc, 0xe1, 0xaf, 0x38, 0x73, 0xce, 0x1f, 0xf0, + 0x3, 0xf8, 0x0, 0x7c, + + /* U+E4CA "" */ + 0xff, 0xff, 0x3f, 0xff, 0xc6, 0x66, 0x61, 0x99, + 0x98, 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xfc, 0x3, + 0xfe, 0x7c, 0x3c, 0x3b, 0x8e, 0x1e, 0xf3, 0x87, + 0xbc, 0xe1, 0xef, 0x38, 0x7f, 0xce, 0x1e, 0xf0, + 0x3, 0xf8, 0x0, 0x7c, + + /* U+E4CB "" */ + 0xff, 0xff, 0x3f, 0xff, 0xc6, 0x66, 0x61, 0x99, + 0x98, 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xfc, 0x3, + 0xfe, 0x7c, 0x7c, 0x3f, 0x8e, 0x1d, 0x73, 0x87, + 0x5c, 0xe1, 0xef, 0x38, 0x75, 0xce, 0x1d, 0x70, + 0x3, 0xf8, 0x0, 0x7c, + + /* U+E4CC "" */ + 0xff, 0xff, 0x3f, 0xff, 0xc6, 0x66, 0x61, 0x99, + 0xbc, 0xff, 0xff, 0x3f, 0xfc, 0xf, 0xfe, 0x73, + 0xff, 0xbe, 0x7c, 0x2d, 0x8e, 0x3, 0x63, 0x81, + 0xfc, 0xe0, 0x7f, 0x38, 0x1f, 0xce, 0x7, 0xf0, + 0x1, 0xfc, 0x0, 0x7f, + + /* U+E4CE "" */ + 0x3f, 0xff, 0xc3, 0xff, 0xfc, 0x3f, 0xff, 0xc3, + 0xff, 0xfc, 0x1f, 0xf, 0x80, 0xe0, 0x70, 0xe, + 0x7, 0x0, 0xe0, 0x70, 0x8, 0x0, 0x0, 0x0, + 0x0, 0x6, 0x6, 0x0, 0xf9, 0xf0, 0x78, 0xf1, + 0xf0, 0x0, 0x0, + + /* U+E4CF "" */ + 0x7, 0x80, 0x21, 0x1, 0x6, 0x4, 0x8, 0x30, + 0x30, 0x0, 0x0, 0x0, 0x3f, 0xff, 0x7f, 0xf9, + 0xff, 0xe7, 0xff, 0x8f, 0xfe, 0x3f, 0xf0, 0xff, + 0xc3, 0xff, 0xf, 0xfc, + + /* U+E4D0 "" */ + 0x36, 0x0, 0xe, 0x0, 0x7, 0x0, 0x7, 0xc0, + 0xf, 0xf8, 0x7, 0xfc, 0x1, 0xfc, 0x91, 0xff, + 0x78, 0x3e, 0x3c, 0x1f, 0x3f, 0x7, 0x3f, 0xc0, + 0x1f, 0xe0, 0x7, 0xe0, 0x7, 0xf8, 0x1, 0xf8, + 0x0, 0xfc, 0x0, 0x3c, 0x0, + + /* U+E4D1 "" */ + 0x7f, 0xe0, 0x7f, 0xf8, 0x3f, 0xfc, 0x1c, 0xce, + 0xe, 0x67, 0x7, 0xff, 0x83, 0xff, 0xc1, 0xcc, + 0x0, 0xe6, 0x3e, 0x7f, 0x3f, 0xbf, 0xbd, 0xff, + 0x1f, 0x7f, 0x8c, 0x1f, 0xc7, 0xdf, 0xe3, 0xde, + 0xfc, 0xfe, 0x0, 0x3e, 0x0, + + /* U+E4D2 "" */ + 0x7f, 0xe0, 0x7f, 0xf8, 0x3f, 0xfc, 0x1c, 0xce, + 0xe, 0x67, 0x7, 0xff, 0x83, 0xff, 0xc1, 0xcc, + 0x0, 0xe6, 0x3c, 0x7f, 0xbf, 0xbf, 0xbf, 0xdf, + 0x1f, 0x7f, 0x8d, 0x3f, 0xc7, 0x3f, 0xe3, 0xbc, + 0xfc, 0xfe, 0x0, 0x3c, 0x0, + + /* U+E4D3 "" */ + 0x7f, 0xe0, 0x7f, 0xf8, 0x3f, 0xfc, 0x1c, 0xce, + 0xe, 0x67, 0x7, 0xff, 0x83, 0xff, 0xc1, 0xcc, + 0x0, 0xe6, 0x3e, 0x7f, 0x3b, 0xbf, 0xbd, 0xff, + 0x1e, 0xff, 0x8f, 0x7f, 0xc7, 0xff, 0xfb, 0xde, + 0xfc, 0xfe, 0x0, 0x3e, 0x0, + + /* U+E4D4 "" */ + 0x7f, 0xe0, 0x7f, 0xf8, 0x3f, 0xfc, 0x1c, 0xce, + 0xe, 0x67, 0x7, 0xff, 0x83, 0xff, 0xc1, 0xcc, + 0x0, 0xe6, 0x3e, 0x7f, 0x3f, 0xbf, 0xb7, 0x7f, + 0x1d, 0x7f, 0x8e, 0x3f, 0xc7, 0x1f, 0xe3, 0x76, + 0xfc, 0xfe, 0x0, 0x3e, 0x0, + + /* U+E4D5 "" */ + 0x7f, 0xe6, 0xf, 0xff, 0x7e, 0xff, 0xf7, 0xfe, + 0x67, 0x7f, 0xe6, 0x77, 0xff, 0xff, 0x7e, 0xff, + 0xf6, 0xe, 0x67, 0x60, 0xe6, 0x76, 0xf, 0xff, + 0x60, 0xff, 0xf6, 0xf, 0x9f, 0x60, 0xf9, 0xf6, + 0xf, 0x9f, 0x60, 0xf9, 0xf6, 0x7, 0xfe, 0x60, + + /* U+E4D6 "" */ + 0x7f, 0xe0, 0x7f, 0xf8, 0x3f, 0xfc, 0x1c, 0xce, + 0xe, 0x67, 0x7, 0xff, 0x83, 0xff, 0x81, 0xcd, + 0x9c, 0xe6, 0xdb, 0x7f, 0xe9, 0xbf, 0xe4, 0xdf, + 0x37, 0xff, 0x9b, 0xff, 0xcd, 0xff, 0xfe, 0xfe, + 0xff, 0x7f, 0x0, 0x3f, 0x80, + + /* U+E4D7 "" */ + 0x7f, 0xe0, 0xff, 0xf0, 0xff, 0xf0, 0xe6, 0x70, + 0xe6, 0x70, 0xff, 0xf0, 0xff, 0xf0, 0xe6, 0x70, + 0xe6, 0x70, 0xff, 0xf0, 0xf0, 0x0, 0xf0, 0x0, + 0xf5, 0x77, 0xf7, 0x45, 0xf7, 0x55, 0x37, 0x55, + 0x5, 0x77, + + /* U+E4D8 "" */ + 0x7f, 0xf0, 0x7f, 0xf8, 0x3f, 0xfc, 0x1c, 0xce, + 0xe, 0x67, 0x7, 0xff, 0x83, 0xff, 0xc1, 0xcc, + 0x0, 0xe6, 0x1c, 0x7f, 0x7f, 0xff, 0xbe, 0x7f, + 0x1f, 0x3f, 0x8f, 0x9f, 0xc7, 0xcf, 0xf9, 0xec, + 0xfe, 0x7c, 0x0, 0x1c, 0x0, + + /* U+E4D9 "" */ + 0x7f, 0xe1, 0xff, 0xe3, 0xff, 0xc7, 0x33, 0x8e, + 0x67, 0x1f, 0xfe, 0x3f, 0xfc, 0x73, 0x38, 0xe6, + 0x71, 0xff, 0xe3, 0xf8, 0x7, 0xc0, 0xf, 0x8a, + 0xbf, 0x15, 0xfe, 0x2b, 0xbf, 0x57, 0x0, 0xea, + + /* U+E4DA "" */ + 0x7f, 0xe0, 0x3f, 0xfc, 0xf, 0xff, 0x3, 0x99, + 0xc0, 0xe6, 0x70, 0x3f, 0xfc, 0xf, 0xfe, 0x3, + 0x9b, 0x38, 0xe6, 0xdf, 0x3f, 0xf7, 0xcf, 0xfd, + 0xf3, 0xe7, 0x38, 0xf9, 0x80, 0x3e, 0xcf, 0xef, + 0xf7, 0xfd, 0xfd, 0xff, 0x0, 0x7f, 0xc0, + + /* U+E4DB "" */ + 0x7f, 0xe0, 0x1f, 0xfe, 0x3, 0xff, 0x8c, 0x73, + 0x35, 0xae, 0x66, 0xb5, 0xff, 0xd6, 0xbf, 0xf0, + 0xc7, 0x32, 0xc3, 0xe6, 0x5c, 0xff, 0xf9, 0x9b, + 0xff, 0x0, 0xfc, 0xee, 0x7f, 0x9c, 0xcf, 0xf3, + 0x89, 0x3f, 0xf6, 0x1b, 0xfe, 0xe7, 0x0, 0xc, + 0xc0, + + /* U+E4DC "" */ + 0xc1, 0x80, 0xe1, 0x80, 0x79, 0x80, 0x3f, 0xd8, + 0x3f, 0xf8, 0x1f, 0xf0, 0x1f, 0xf8, 0xff, 0xff, + 0xff, 0xff, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, + 0x3f, 0xfc, 0x31, 0x8c, 0x1, 0x80, 0x1, 0x80, + + /* U+E4DD "" */ + 0x3, 0x3, 0xc, 0x3e, 0x31, 0xd8, 0x6, 0x0, + 0x0, 0x0, 0x1, 0xfe, 0xf, 0xfc, 0x30, 0x31, + 0x80, 0x67, 0xff, 0xbf, 0xff, 0xcf, 0xcf, 0xff, + 0xff, 0xff, 0xf8, 0x7, 0xc0, 0xc, + + /* U+E4DE "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, + 0xf0, 0xf, 0xf7, 0xcf, 0xe7, 0xc7, 0xe0, 0x7, + 0xec, 0x67, 0xe0, 0x7, 0xe0, 0x7, 0x6f, 0xe6, + + /* U+E4E0 "" */ + 0xe, 0xc, 0x7, 0x2, 0x3, 0x81, 0x1, 0xc0, + 0x80, 0x0, 0xe0, 0xf8, 0x70, 0xfe, 0x7e, 0xff, + 0xbf, 0x7f, 0xdf, 0xbf, 0xef, 0x87, 0xc7, 0x83, + 0xe3, 0xf1, 0xb0, 0x60, 0xd8, 0x30, 0x6c, 0x1c, + 0x36, 0xe, + + /* U+E4E1 "" */ + 0xe, 0x3, 0x81, 0xe0, 0x78, 0x1e, 0x7, 0x80, + 0xe0, 0x38, 0x0, 0x0, 0x1, 0xe0, 0x78, 0x3f, + 0xf, 0xc7, 0xf8, 0xfe, 0x7f, 0xdf, 0xed, 0xfd, + 0xff, 0x3f, 0x7, 0xc3, 0xf8, 0x7c, 0x1e, 0x7, + 0xc1, 0xe0, 0x7c, 0x1e, 0x7, 0xc0, 0x20, 0x8, + + /* U+E4E2 "" */ + 0x0, 0x1c, 0x0, 0x7c, 0x0, 0xf8, 0xf, 0xf7, + 0x7d, 0xdf, 0xe3, 0x3e, 0x6, 0x7c, 0xc, 0x7c, + 0x30, 0x1c, 0x60, 0xe, 0xc0, 0xf, 0x80, 0xf, + 0x80, 0x1f, 0x0, 0x3e, 0x0, 0x38, + + /* U+E4E3 "" */ + 0x1f, 0x87, 0xfe, 0xf0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf9, 0xff, 0x2f, 0xf6, 0xff, 0x4f, 0xfd, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xfe, + + /* U+E4E4 "" */ + 0xf, 0x0, 0x1f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, + 0x7f, 0xfe, 0x7f, 0xfe, 0x3f, 0xfc, 0x0, 0x0, + 0x0, 0x0, 0x26, 0x44, 0x66, 0xcc, 0x44, 0xc8, + 0x0, 0x0, 0x8, 0x10, 0x3e, 0x7c, 0xe3, 0xc7, + + /* U+E4E5 "" */ + 0x7f, 0xef, 0xff, 0xfb, 0xfc, 0x0, 0xcf, 0x0, + 0x33, 0xc0, 0xf, 0xf0, 0x3, 0x3c, 0x0, 0xff, + 0x0, 0x3f, 0xc0, 0xf, 0xff, 0xfb, 0xf7, 0xfe, + 0xfc, 0x0, 0x3f, 0x0, 0xf, 0xc7, 0xfb, 0xf0, + + /* U+E4E6 "" */ + 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, + 0x0, 0x0, 0x0, 0x0, 0xc, 0x30, 0x1e, 0x78, + 0x1e, 0x78, 0x1e, 0x78, 0xc, 0x0, 0x0, 0x0, + 0xf3, 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, + + /* U+E4E8 "" */ + 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xff, 0xf0, 0xff, + 0x80, 0x9f, 0xc0, 0x33, 0xe7, 0xce, 0x33, 0xfb, + 0xe5, 0xff, 0xfc, 0x7d, 0xff, 0xda, 0xff, 0xf7, + 0x3c, 0xfd, 0xff, 0x0, 0x3f, 0x80, 0x7, 0xc0, + + /* U+E4E9 "" */ + 0x1, 0x80, 0x1, 0x83, 0x1, 0x83, 0x0, 0x6, + 0x0, 0x6, 0x1, 0x8c, 0x1, 0x9c, 0x31, 0x98, + 0x19, 0xb8, 0x1d, 0xf8, 0xf, 0xf3, 0xef, 0xff, + 0x7c, 0x3e, 0x38, 0x1c, 0x0, 0x0, 0x0, 0x0, + 0xff, 0xff, + + /* U+E4EA "" */ + 0x1, 0xf8, 0x0, 0x1f, 0x80, 0xf, 0xff, 0x1, + 0xff, 0xf8, 0x18, 0x1, 0x81, 0x80, 0x18, 0x18, + 0x1, 0x81, 0x80, 0x18, 0x1f, 0xff, 0x81, 0xff, + 0xf8, 0xf, 0xff, 0x0, 0xf, 0x0, 0x0, 0x0, + 0x0, 0xf0, 0xf0, 0x1f, 0x9f, 0xa7, 0x8f, 0x1f, + 0x0, 0x0, 0x0, + + /* U+E4EB "" */ + 0x7f, 0x0, 0x7f, 0xc0, 0x3f, 0xb0, 0x1f, 0xdc, + 0xf, 0xe7, 0x7, 0xff, 0x83, 0xff, 0xc1, 0xff, + 0x0, 0xff, 0x3e, 0x7f, 0xbb, 0xbf, 0xbd, 0xff, + 0xde, 0xff, 0xef, 0x7f, 0xf7, 0xff, 0xfb, 0xde, + 0xfe, 0xfe, 0x0, 0x3e, 0x0, + + /* U+E4ED "" */ + 0x7f, 0x0, 0x7f, 0xc0, 0x3f, 0xb0, 0x1f, 0xdc, + 0xf, 0xe7, 0x7, 0xff, 0xc3, 0xff, 0xe1, 0xff, + 0x80, 0xff, 0xbe, 0x7f, 0xbf, 0xbf, 0xbf, 0xff, + 0xdf, 0xff, 0xec, 0x1f, 0xf7, 0xff, 0xfb, 0xfe, + 0xfe, 0xfe, 0x0, 0x3e, 0x0, + + /* U+E4EE "" */ + 0x7f, 0x0, 0x7f, 0xc0, 0x3f, 0xb0, 0x1f, 0xdc, + 0xf, 0xe7, 0x7, 0xff, 0x83, 0xff, 0xc1, 0xff, + 0x0, 0xff, 0x3e, 0x7f, 0xbf, 0xbf, 0xbd, 0xff, + 0xde, 0xff, 0xec, 0x1f, 0xf7, 0xbf, 0xfb, 0xde, + 0xfe, 0xfe, 0x0, 0x3e, 0x0, + + /* U+E4EF "" */ + 0x7f, 0x0, 0x7f, 0xc0, 0x3f, 0xb0, 0x1f, 0xdc, + 0xf, 0xe7, 0x7, 0xff, 0x83, 0xff, 0xc1, 0xff, + 0x80, 0xff, 0x3e, 0x7f, 0xbf, 0xbf, 0xba, 0xff, + 0xdf, 0x7f, 0xef, 0x7f, 0xf7, 0xff, 0xfb, 0xde, + 0xfe, 0xfe, 0x0, 0x3e, 0x0, + + /* U+E4F0 "" */ + 0x7f, 0x0, 0x7f, 0xc0, 0x3f, 0xb0, 0x1f, 0xcc, + 0xf, 0xe3, 0x7, 0xff, 0xc3, 0xff, 0xe1, 0xff, + 0x80, 0xff, 0x1c, 0x7f, 0x7f, 0xff, 0xbe, 0x7f, + 0xdf, 0x3f, 0xef, 0x9f, 0xf7, 0xcf, 0xfd, 0xec, + 0xfe, 0x7c, 0x0, 0x1c, 0x0, + + /* U+E4F1 "" */ + 0x3, 0x0, 0x7, 0xe0, 0xf, 0xf0, 0xf, 0xf0, + 0x1f, 0xf8, 0x1e, 0x78, 0x1c, 0x38, 0x1c, 0x38, + 0xc, 0x38, 0xf, 0xf0, 0xe3, 0xe7, 0xf8, 0x1f, + 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, + + /* U+E4F2 "" */ + 0x1, 0x80, 0x0, 0x70, 0x0, 0x1f, 0x83, 0xf, + 0xf8, 0xf7, 0xff, 0x1f, 0xff, 0xe7, 0xff, 0x3d, + 0xff, 0xcf, 0x7f, 0xff, 0xbd, 0xff, 0xcc, 0x3f, + 0xe0, 0x7, 0xe0, 0x1, 0xc0, 0x0, 0x60, 0x0, + + /* U+E4F3 "" */ + 0xff, 0x7f, 0x9f, 0xef, 0xf1, 0x98, 0xcc, 0x33, + 0x19, 0x86, 0x63, 0x30, 0xcc, 0x66, 0x1f, 0x8c, + 0xc3, 0xf1, 0x98, 0x7e, 0x61, 0x8f, 0xcc, 0x31, + 0xfb, 0xff, 0x3e, 0x7f, 0xe7, 0xcf, 0xfc, 0x7b, + 0xff, 0xc0, 0x7f, 0xf8, 0x7, 0xfe, + + /* U+E4F4 "" */ + 0xff, 0xff, 0xff, 0xc0, 0x3c, 0x3, 0xc0, 0x3f, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xe7, 0xfe, 0x7f, + 0xe7, 0xfe, 0x7f, 0xe7, 0xfe, 0x7f, 0xe3, 0xfc, + + /* U+E4F5 "" */ + 0xff, 0xff, 0xff, 0xc0, 0x3c, 0x63, 0xc6, 0x3c, + 0xf3, 0xcf, 0x3c, 0x63, 0x60, 0x66, 0x6, 0x7f, + 0xe7, 0xfe, 0x7f, 0xe7, 0xfe, 0x7f, 0xe3, 0xfc, + + /* U+E4F6 "" */ + 0x66, 0x66, 0xfe, 0x7f, 0xfc, 0x3f, 0x70, 0xe, + 0x60, 0x6, 0xe3, 0xe7, 0xc6, 0x63, 0x4, 0x60, + 0x6, 0x0, 0xc6, 0x63, 0xe7, 0xe7, 0x60, 0x6, + 0x70, 0xe, 0xfc, 0x3f, 0xfe, 0x7f, 0x66, 0x66, + + /* U+E4F7 "" */ + 0x7, 0xff, 0xc3, 0xff, 0xf3, 0xff, 0xfd, 0xe0, + 0xff, 0xf3, 0xfe, 0x30, 0xff, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7, 0xf0, 0x7, 0xfc, 0x31, 0xf0, + 0x3f, 0xfc, 0x1e, 0xff, 0xff, 0x3f, 0xff, 0xf, + 0xff, 0x80, + + /* U+E4F8 "" */ + 0x0, 0xc0, 0x0, 0x30, 0x0, 0x61, 0x80, 0x18, + 0x60, 0x0, 0x0, 0x3, 0xcf, 0x0, 0xf1, 0xc0, + 0xff, 0x3c, 0x70, 0xe3, 0x98, 0x18, 0x6c, 0x3, + 0xf, 0x0, 0xc3, 0xc0, 0x30, 0xf0, 0xc, 0x36, + 0x6, 0x19, 0xc3, 0x8e, 0x3f, 0xcf, 0x3, 0xc7, + 0x0, + + /* U+E4F9 "" */ + 0xc0, 0x0, 0xf0, 0x0, 0x3c, 0x0, 0xf, 0x0, + 0x3, 0xcc, 0xc, 0xfb, 0x87, 0x7e, 0x73, 0x9f, + 0xdc, 0xef, 0x7f, 0x3f, 0x8f, 0xcf, 0xc1, 0xf3, + 0xe0, 0xff, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x3, + 0xff, 0xf0, 0x3c, 0xf0, + + /* U+E4FA "" */ + 0x0, 0x70, 0x0, 0x7, 0x0, 0x0, 0x20, 0xe, + 0x7, 0x7, 0xe1, 0xfc, 0x7e, 0x1f, 0xc7, 0xe0, + 0x70, 0x7e, 0x5, 0x7, 0xe4, 0x53, 0x7e, 0x60, + 0x77, 0xe7, 0x6, 0x7f, 0x38, 0xcf, 0xfb, 0x8d, + 0xf7, 0xf8, 0xfe, 0x3f, 0x8f, 0xc1, 0xf8, 0xf8, + 0xf, 0x8f, 0x0, + + /* U+E4FB "" */ + 0x0, 0xf0, 0x0, 0x1f, 0x80, 0xe3, 0xfc, 0x7e, + 0x3f, 0xc7, 0xe3, 0xfc, 0x7e, 0x3f, 0xc7, 0xe1, + 0xf8, 0x7e, 0xcf, 0x37, 0xee, 0x7, 0x7e, 0x70, + 0xe7, 0xf3, 0x9c, 0xff, 0xb9, 0xdf, 0x7f, 0x9f, + 0xe3, 0xf9, 0xfc, 0x1f, 0x9f, 0x80, 0xf9, 0xf0, + + /* U+E4FC "" */ + 0x3c, 0x3c, 0x1f, 0x9f, 0x8f, 0xff, 0xf3, 0xff, + 0xfc, 0xff, 0xff, 0x3f, 0xf8, 0x7, 0xfc, 0x1, + 0xfe, 0x7c, 0x3f, 0x3f, 0x8f, 0xdf, 0x71, 0xf7, + 0x3c, 0x3d, 0x83, 0x7, 0x79, 0xc0, 0x5c, 0xf0, + 0x3, 0xf8, 0x0, 0x7c, + + /* U+E4FD "" */ + 0x3c, 0x3c, 0x1f, 0x9f, 0x8f, 0xff, 0xf3, 0xff, + 0xfc, 0xff, 0xff, 0x3f, 0xf8, 0x7, 0xfc, 0x1, + 0xfe, 0x7c, 0x3f, 0x3f, 0x87, 0xdf, 0xf1, 0xf7, + 0xdc, 0x3d, 0xaf, 0x3, 0x73, 0xc0, 0x5f, 0xf0, + 0x3, 0xf8, 0x0, 0x7c, + + /* U+E4FE "" */ + 0x3c, 0x3c, 0x1f, 0x9f, 0x8f, 0xff, 0xf3, 0xff, + 0xfc, 0xff, 0xff, 0x3f, 0xf8, 0x7, 0xfc, 0x1, + 0xfe, 0x7c, 0x3f, 0x3b, 0x8f, 0xde, 0xf1, 0xf7, + 0xbc, 0x3d, 0xef, 0x7, 0x7f, 0xc0, 0x5e, 0xf0, + 0x3, 0xf8, 0x0, 0x7c, + + /* U+E4FF "" */ + 0x3c, 0x3c, 0x1f, 0x9f, 0x8f, 0xff, 0xf3, 0xff, + 0xfc, 0xff, 0xff, 0x3f, 0xf8, 0xf, 0xfc, 0x1, + 0xfe, 0x7c, 0x3f, 0x3f, 0x8f, 0xdf, 0xf1, 0xf7, + 0xfc, 0x3d, 0x83, 0x7, 0x7f, 0xc0, 0x5f, 0xf0, + 0x3, 0xf8, 0x0, 0x7c, + + /* U+E500 "" */ + 0x3c, 0x3c, 0x1f, 0x9f, 0x8f, 0xff, 0xf3, 0xff, + 0xfc, 0xff, 0xff, 0x3f, 0xf8, 0xf, 0xfc, 0x1, + 0xfe, 0x7c, 0x3f, 0x3f, 0x8f, 0xde, 0xf1, 0xf7, + 0xbc, 0x3d, 0x83, 0x7, 0x7b, 0xc0, 0x5e, 0xf0, + 0x3, 0xf8, 0x0, 0x7c, + + /* U+E501 "" */ + 0x3c, 0x3c, 0x1f, 0x9f, 0x8f, 0xff, 0xf3, 0xff, + 0xfc, 0xff, 0xff, 0x3f, 0xf8, 0xf, 0xfc, 0x1, + 0xfe, 0x7c, 0x7f, 0x3f, 0x8f, 0xdd, 0x71, 0xf7, + 0x5c, 0x3d, 0xef, 0x7, 0x75, 0xc0, 0xdd, 0x70, + 0x3, 0xf8, 0x0, 0x7c, + + /* U+E502 "" */ + 0x6, 0xc0, 0x3d, 0xe0, 0xe0, 0xe3, 0x80, 0xee, + 0xc6, 0xf9, 0x8c, 0xf3, 0x19, 0x87, 0xf0, 0xf, + 0xe1, 0x98, 0xcf, 0x31, 0x9f, 0x63, 0x77, 0x1, + 0xc7, 0x7, 0x7, 0xbc, 0x3, 0x60, + + /* U+E503 "" */ + 0x7, 0xc0, 0x1f, 0xf0, 0x3f, 0xfc, 0x75, 0x5c, + 0x75, 0x1e, 0xf5, 0x1e, 0xf1, 0x5e, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x30, 0xff, 0x30, 0x7f, 0x30, + 0x1, 0xb0, 0x0, 0xf0, 0x0, 0x30, 0x0, 0x1e, + + /* U+E507 "" */ + 0xe, 0xc6, 0x13, 0xf9, 0x8e, 0x7f, 0x3, 0xcf, + 0xcc, 0xf9, 0xfb, 0x3f, 0x3e, 0xf, 0xe7, 0x83, + 0xfc, 0xce, 0xff, 0x98, 0xff, 0xf3, 0x3f, 0xfe, + 0x7f, 0xff, 0xce, 0xff, 0xf8, 0x3f, 0xff, 0x7, + 0xff, 0x80, + + /* U+E508 "" */ + 0xc, 0x60, 0x13, 0x3c, 0xe, 0xf, 0x3, 0xc1, + 0x80, 0xf8, 0x0, 0x3f, 0x0, 0xf, 0xe0, 0x3, + 0xfc, 0x60, 0xff, 0x81, 0xbf, 0xf0, 0xff, 0xfe, + 0x3f, 0xff, 0xc6, 0xff, 0xf8, 0x3f, 0xff, 0x7, + 0xff, 0x80, + + /* U+E509 "" */ + 0x0, 0xc0, 0x0, 0x1e, 0x0, 0x3, 0xf0, 0x0, + 0xff, 0x80, 0x1f, 0xfc, 0x3, 0xff, 0xe0, 0x7f, + 0xff, 0xf, 0xff, 0x0, 0x7f, 0xe7, 0x83, 0xfc, + 0xfe, 0x3e, 0x1f, 0xe3, 0xe1, 0xf7, 0x3e, 0x1a, + 0x73, 0xe1, 0xcf, 0x3e, 0x1d, 0xe1, 0xfc, 0xfe, + 0x0, 0x7, 0x80, + + /* U+E50A "" */ + 0x0, 0xc0, 0x0, 0x1e, 0x0, 0x3, 0xf0, 0x0, + 0xff, 0x80, 0x1f, 0xfc, 0x3, 0xff, 0xe0, 0x7f, + 0xff, 0xf, 0xff, 0x0, 0x7f, 0xe7, 0xc3, 0xfc, + 0xfe, 0x3e, 0x1e, 0xf3, 0xe1, 0xef, 0x3e, 0x1e, + 0xf3, 0xe1, 0xff, 0x3f, 0xde, 0xf1, 0xfc, 0xfe, + 0x0, 0x7, 0xc0, + + /* U+E50B "" */ + 0x0, 0xc0, 0x0, 0x1e, 0x0, 0x3, 0xf0, 0x0, + 0xff, 0x80, 0x1f, 0xfc, 0x3, 0xff, 0xe0, 0x7f, + 0xff, 0x8f, 0xff, 0x0, 0x3f, 0xe7, 0xc3, 0xfc, + 0xfe, 0x3e, 0x1b, 0xb3, 0xe1, 0xd7, 0x3e, 0x1e, + 0xf3, 0xe1, 0xd7, 0x3e, 0x1b, 0xb1, 0xfc, 0xfe, + 0x0, 0x7, 0xc0, + + /* U+E50C "" */ + 0x0, 0xc0, 0x0, 0x1e, 0x0, 0x3, 0xf0, 0x0, + 0x7f, 0x80, 0xf, 0xfc, 0x3, 0xff, 0x0, 0x7f, + 0xe0, 0x7, 0xfc, 0x68, 0x7f, 0xcf, 0xc3, 0xf9, + 0xfe, 0x3e, 0x3f, 0xf3, 0xe3, 0xcf, 0x3e, 0x38, + 0x73, 0xe3, 0x87, 0x3e, 0x18, 0x63, 0xf9, 0xce, + 0x0, 0x7, 0x80, + + /* U+E50D "" */ + 0x0, 0x3, 0x0, 0x0, 0x3f, 0x3, 0xc3, 0xf0, + 0x7e, 0x3f, 0xf, 0xf3, 0xf1, 0xff, 0x3f, 0x3f, + 0xf3, 0x7, 0xff, 0x30, 0x7e, 0x73, 0x7, 0xe7, + 0x30, 0x3e, 0x73, 0x3, 0xff, 0x30, 0x3f, 0xf3, + 0x3, 0xff, 0x30, 0x3f, 0xf3, 0x1, 0xff, 0x30, + + /* U+E50E "" */ + 0x0, 0xe0, 0x0, 0x1f, 0x0, 0x3, 0xf8, 0x0, + 0x7f, 0xc0, 0xf, 0xfe, 0x1, 0xf1, 0xf0, 0xf, + 0x1e, 0x0, 0xf1, 0xe0, 0xf, 0x1e, 0x0, 0xff, + 0xe0, 0x0, 0x60, 0x0, 0x0, 0x0, 0xf, 0x9f, + 0xf, 0x8f, 0x1f, 0x7, 0xe, 0x0, 0xf9, 0xf0, + 0xf8, 0xf1, 0xf0, 0x0, 0x0, + + /* U+E50F "" */ + 0x3e, 0x0, 0x7, 0xf1, 0xc0, 0xf7, 0xbe, 0xf, + 0xbb, 0xf0, 0xc1, 0xbf, 0x8f, 0xbb, 0xfe, 0x7f, + 0x33, 0xe3, 0xe7, 0x3c, 0x8, 0xf3, 0xc0, 0x1f, + 0xfc, 0x0, 0xf1, 0xc0, 0x4, 0x4, 0x6, 0x6, + 0x0, 0xf9, 0xf0, 0xf8, 0xf1, 0xf0, 0x70, 0xe0, + 0xf, 0x9f, 0xf, 0x8f, 0x1f, 0x0, 0x0, 0x0, + + /* U+E510 "" */ + 0x0, 0xc0, 0x0, 0x1e, 0x0, 0x3, 0xf0, 0x0, + 0x7f, 0x80, 0x1f, 0xfc, 0x3, 0xff, 0xe0, 0x7f, + 0xfc, 0xf, 0xff, 0x9c, 0x7f, 0xfb, 0xe3, 0xff, + 0xb6, 0x3e, 0x33, 0x63, 0xe6, 0x7f, 0x3e, 0x67, + 0xf3, 0xe6, 0x7f, 0x3f, 0xe7, 0xf1, 0xfe, 0x7f, + 0x0, 0x7, 0xf0, + + /* U+E511 "" */ + 0x0, 0xc0, 0x0, 0x1e, 0x0, 0x7, 0xf0, 0x0, + 0xff, 0x80, 0x1f, 0xfc, 0x3, 0xff, 0xe0, 0x7f, + 0xff, 0x7, 0xf3, 0x0, 0x7f, 0x27, 0x83, 0xc0, + 0xfe, 0x3c, 0x1f, 0xe3, 0xf1, 0xf7, 0x3f, 0x1a, + 0xf3, 0xf9, 0xdf, 0x3f, 0xdf, 0xe1, 0xfc, 0xfe, + 0x0, 0x7, 0x80, + + /* U+E512 "" */ + 0x0, 0xc0, 0x0, 0x1e, 0x0, 0x7, 0xf0, 0x0, + 0xff, 0x80, 0x1f, 0xfc, 0x3, 0xff, 0xe0, 0x7f, + 0xff, 0x7, 0xf3, 0x0, 0x3f, 0x27, 0xc3, 0xc0, + 0xee, 0x3c, 0x1e, 0xf3, 0xf1, 0xef, 0x3f, 0x1e, + 0xf3, 0xf9, 0xff, 0x3f, 0xde, 0xf1, 0xfc, 0xfe, + 0x0, 0x7, 0xc0, + + /* U+E513 "" */ + 0x0, 0x80, 0x0, 0x7c, 0x0, 0x1f, 0xc0, 0x7, + 0xfc, 0x1, 0xff, 0xc0, 0x7f, 0xfc, 0x1f, 0xff, + 0xc7, 0xf9, 0x80, 0x3f, 0x27, 0x87, 0x81, 0xf8, + 0xf0, 0x6d, 0x9f, 0x8c, 0x73, 0xf1, 0xce, 0x7f, + 0x39, 0xcf, 0xf6, 0xd8, 0xfe, 0x7e, 0x0, 0x7, + 0x80, + + /* U+E514 "" */ + 0x0, 0x3, 0x0, 0x0, 0x3f, 0x3, 0x83, 0xf0, + 0x7c, 0x3f, 0xf, 0xe3, 0xf1, 0xff, 0x3f, 0x3f, + 0xf3, 0x7, 0xcf, 0x30, 0xfc, 0xf3, 0x7, 0x83, + 0x30, 0x70, 0x33, 0x7, 0x83, 0x30, 0x7c, 0xf3, + 0x7, 0xcf, 0x30, 0x7f, 0xf3, 0x3, 0xff, 0x30, + + /* U+E515 "" */ + 0x3, 0xc0, 0x0, 0xe0, 0x0, 0x18, 0x0, 0x3, + 0x3c, 0x0, 0x67, 0x86, 0x6, 0xe0, 0xf0, 0xdc, + 0x1f, 0x8d, 0x83, 0xfc, 0xd8, 0x3f, 0xcd, 0x83, + 0xfc, 0xd8, 0x30, 0xcc, 0xc0, 0x4, 0x6c, 0x6, + 0x6, 0x71, 0xf1, 0x31, 0xf1, 0xf1, 0x80, 0xe0, + 0xe, 0x1f, 0x10, 0x3f, 0x1f, 0x0, 0x0, 0x0, + + /* U+E516 "" */ + 0x7f, 0x80, 0x0, 0x1, 0xfe, 0xff, 0xff, 0xff, + 0xff, 0x3, 0xc0, 0xf0, 0x3c, 0xf, 0x3, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0x80, + + /* U+E517 "" */ + 0x7f, 0x80, 0x0, 0x1, 0xfe, 0xff, 0xff, 0xfc, + 0xf, 0x87, 0xff, 0xf0, 0x3e, 0x1f, 0xff, 0xc0, + 0xf0, 0x3f, 0xff, 0xff, 0x7f, 0x80, + + /* U+E518 "" */ + 0x1, 0x80, 0x1, 0x80, 0x3, 0xc0, 0x3, 0xc0, + 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x7, 0xe0, + 0xcf, 0xf3, 0xdf, 0xfb, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xc3, 0xc3, 0x3, 0xc0, 0x7, 0xe0, + 0xe, 0x70, 0xe, 0x70, + + /* U+E519 "" */ + 0xc, 0x1, 0xe0, 0x3f, 0x0, 0x0, 0x3f, 0xc7, + 0xfe, 0xff, 0xff, 0xf3, 0xff, 0x3f, 0xf3, 0xff, + 0x3f, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xfe, + + /* U+E51A "" */ + 0x3c, 0x0, 0x31, 0x80, 0x30, 0x40, 0x19, 0xb1, + 0xed, 0xdf, 0xfe, 0xef, 0xff, 0x76, 0x3c, 0xc6, + 0x0, 0x3e, 0x8, 0x0, 0x7f, 0xdf, 0x80, 0xf, + 0x4f, 0xe7, 0xe7, 0xf3, 0xc3, 0xf9, 0xf1, 0xfd, + 0xfc, 0xfe, + + /* U+E51B "" */ + 0x0, 0xc0, 0x0, 0x30, 0x0, 0xc, 0x1, 0x3, + 0x3, 0xe0, 0xc1, 0xcc, 0x1, 0xc1, 0x80, 0x60, + 0x0, 0x0, 0x7, 0xf8, 0x1, 0xfe, 0x7, 0xff, + 0xf9, 0xff, 0xfe, 0x7f, 0xff, 0x9f, 0xff, 0xe7, + 0xff, 0xf9, 0xff, 0xfe, + + /* U+E51C "" */ + 0x1, 0xf0, 0x1, 0xf0, 0x1, 0xf0, 0x1, 0x80, + 0x1, 0x80, 0x7f, 0xfe, 0x7f, 0xfe, 0x36, 0x6c, + 0x36, 0x6c, 0x36, 0x6c, 0x36, 0x6c, 0x36, 0x6c, + 0x36, 0x6c, 0x36, 0x6c, 0x7f, 0xfe, 0xff, 0xff, + 0xff, 0xff, + + /* U+E51D "" */ + 0x1f, 0xfc, 0x1f, 0xff, 0xc, 0x1, 0x86, 0x0, + 0xc3, 0x0, 0x1, 0x83, 0xe0, 0xc1, 0xf8, 0x60, + 0xf6, 0xff, 0x79, 0xff, 0xbf, 0xdf, 0xdf, 0xe0, + 0xf, 0xf0, 0x7, 0xf8, 0x3, 0xfc, 0x1, 0xfe, + 0x0, 0xff, + + /* U+E51E "" */ + 0xd, 0xb0, 0xdb, 0x1d, 0xb1, 0x9b, 0x1b, 0x33, + 0xb3, 0x33, 0x33, 0x33, 0x73, 0x36, 0x33, 0x66, + 0x3e, 0x63, 0xc6, 0x3c, 0x63, + + /* U+E51F "" */ + 0xf, 0x0, 0x3f, 0xc0, 0x7f, 0xe0, 0xff, 0xf0, + 0xf9, 0xf0, 0xf0, 0xf0, 0xf0, 0xc0, 0xf9, 0x9c, + 0xff, 0xb6, 0x7f, 0xb6, 0x7f, 0x36, 0x3f, 0x7f, + 0x1f, 0x7f, 0x1f, 0x7f, 0xf, 0x7f, 0x6, 0x7f, + 0x0, 0x7f, + + /* U+E520 "" */ + 0x0, 0x78, 0x0, 0x1, 0x80, 0x0, 0x30, 0x0, + 0x6, 0x1, 0x98, 0x80, 0xfe, 0x30, 0x36, 0xcc, + 0x9f, 0xff, 0x6f, 0xff, 0xd7, 0xff, 0xb3, 0xff, + 0xfc, 0xdf, 0xfe, 0x66, 0x63, 0x33, 0x30, 0x40, + 0x80, 0x10, + + /* U+E521 "" */ + 0xf, 0x80, 0x3f, 0xe0, 0x7f, 0xf0, 0x7d, 0xf0, + 0xfc, 0xf8, 0xe2, 0x78, 0xe0, 0x38, 0xe0, 0x78, + 0xfe, 0xf8, 0x7d, 0xf0, 0x7f, 0xf0, 0x3f, 0xf8, + 0xf, 0x9c, 0x0, 0xe, 0x0, 0x7, 0x0, 0x3, + + /* U+E522 "" */ + 0xf, 0x80, 0x3f, 0xe0, 0x7f, 0xf0, 0x7d, 0xf0, + 0xfd, 0xf8, 0xfd, 0x38, 0xe5, 0x38, 0xe5, 0x38, + 0xe5, 0x38, 0x65, 0x30, 0x7f, 0xf0, 0x3f, 0xf8, + 0xf, 0x9c, 0x0, 0xe, 0x0, 0x7, 0x0, 0x3, + + /* U+E523 "" */ + 0x0, 0x1, 0xf0, 0x40, 0x1f, 0x6, 0x0, 0xf7, + 0x67, 0xdf, 0x3c, 0xff, 0xb3, 0xdc, 0x70, 0x7b, + 0x83, 0x8f, 0xb0, 0x18, 0x1b, 0x1, 0x81, 0xb0, + 0x18, 0x1b, 0x83, 0x80, 0x1c, 0x70, 0x0, 0xfe, + 0x0, 0x7, 0xc0, 0x0, 0x38, 0x0, 0x7, 0xc0, + 0x0, 0x7c, 0x0, 0x3, 0x80, 0x0, 0x10, 0x0, + + /* U+E524 "" */ + 0x0, 0xf0, 0x0, 0x1f, 0x80, 0x3, 0xfc, 0x7, + 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x79, 0xe3, 0xcf, + 0xf, 0x3c, 0xe0, 0x73, 0xce, 0x67, 0x37, 0xe6, + 0x7e, 0x7e, 0x67, 0xe1, 0xf6, 0xf8, 0x3, 0x6c, + 0x0, 0x16, 0x80, 0x0, 0x60, 0x0, + + /* U+E525 "" */ + 0x7f, 0x7f, 0xbf, 0xdf, 0xfc, 0x37, 0xff, 0xd, + 0xff, 0xc3, 0x7f, 0xf0, 0xdf, 0xfc, 0x37, 0xff, + 0xd, 0xff, 0xc3, 0x7f, 0xf0, 0xdf, 0xff, 0xf7, + 0xfd, 0xfd, 0xfe, + + /* U+E527 "" */ + 0x7f, 0xde, 0x3f, 0xc7, 0xff, 0xfe, 0xf, 0xc1, + 0xf8, 0x3f, 0x7, 0xff, 0xf9, 0x4f, 0x29, 0xff, + 0xfc, 0xa7, 0x94, 0xff, 0xfb, 0xfe, + + /* U+E528 "" */ + 0x20, 0x0, 0x18, 0x0, 0xf, 0xff, 0xf1, 0x9f, + 0xfe, 0x27, 0xf9, 0x81, 0x87, 0xe0, 0x40, 0xf9, + 0x30, 0x3e, 0x7c, 0xc, 0x9f, 0x2, 0x7, 0xe1, + 0x81, 0x9f, 0xec, 0x7f, 0xf9, 0x8f, 0xff, 0xf0, + 0x0, 0x18, 0x0, 0xc, + + /* U+E529 "" */ + 0x0, 0x3e, 0x0, 0xe, 0x4, 0x1e, 0xf, 0x36, + 0x19, 0xe0, 0x30, 0xc0, 0x0, 0x0, 0x0, 0x0, + 0xff, 0xff, 0xdf, 0xfb, 0xfe, 0x3f, 0xfe, 0x3f, + 0xfe, 0x3f, 0xde, 0x3f, 0xdf, 0xfb, 0xff, 0xff, + + /* U+E52A "" */ + 0x4, 0x88, 0x76, 0xcc, 0x7, 0xee, 0xf8, 0x22, + 0x0, 0x0, 0x3, 0x66, 0x76, 0xee, 0x0, 0x88, + 0x0, 0x0, 0xff, 0xff, 0xdf, 0xfb, 0xfc, 0x3f, + 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xdf, 0xfb, + 0xff, 0xff, + + /* U+E52B "" */ + 0xc, 0x18, 0x3, 0x36, 0x0, 0x6f, 0x0, 0x33, + 0x60, 0x6, 0xf8, 0x0, 0xf8, 0x7, 0xff, 0xfb, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xef, + 0xf8, 0x1b, 0xc0, 0x6, 0xf0, 0x3, 0x6, 0x0, + 0x80, 0x80, + + /* U+E52C "" */ + 0x8, 0x8, 0x1, 0x81, 0x80, 0x1b, 0x60, 0x2, + 0x6c, 0x0, 0xed, 0x80, 0xf, 0xe0, 0x3, 0xff, + 0x3, 0xff, 0xfc, 0xff, 0xff, 0xdf, 0xff, 0xfb, + 0xff, 0xc0, 0x3b, 0x63, 0x60, 0x6c, 0xfe, 0xd, + 0x9f, 0xc3, 0x1, 0xb0, 0x40, 0x7f, 0x0, 0xf, + 0xe0, 0x0, 0xd8, + + /* U+E52D "" */ + 0x3, 0xc0, 0xf, 0xf0, 0xf, 0xf0, 0x1f, 0xf8, + 0x3f, 0xfc, 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe, + 0xff, 0xff, 0xff, 0xff, + + /* U+E52E "" */ + 0x0, 0xfd, 0x80, 0x1f, 0xb0, 0x3, 0xf6, 0x0, + 0x67, 0xf0, 0x4, 0xfe, 0x6, 0x7f, 0xc0, 0xc3, + 0x98, 0x3c, 0x73, 0xc, 0xdf, 0xe1, 0x99, 0xcc, + 0x67, 0xb9, 0x8f, 0xf3, 0xf3, 0xff, 0x7e, 0xff, + 0xf7, 0xdf, 0xfe, 0x7b, 0xff, 0xcf, + + /* U+E52F "" */ + 0x1, 0x83, 0x80, 0xf1, 0xf0, 0x3c, 0x7c, 0x1f, + 0x9f, 0x6, 0x63, 0x83, 0x9c, 0x0, 0xc3, 0x0, + 0x73, 0xe0, 0x1d, 0xf8, 0xf, 0xff, 0x3, 0xff, + 0xc1, 0xff, 0xf8, 0x7f, 0xfe, 0x3f, 0xff, 0xcf, + 0xff, 0xf0, + + /* U+E532 "" */ + 0x0, 0x6, 0x0, 0x3, 0xc0, 0x0, 0x78, 0x0, + 0x1e, 0x0, 0x8f, 0x88, 0x7f, 0xe6, 0x7f, 0xbd, + 0xfe, 0xf, 0x78, 0x3, 0xd8, 0x58, 0xe6, 0x36, + 0x1, 0x9f, 0xc0, 0x66, 0x30, 0x1b, 0x8e, 0xf, + 0xff, 0xff, 0xff, 0xff, + + /* U+E533 "" */ + 0x1, 0x80, 0x33, 0xcc, 0x7b, 0xde, 0x30, 0xc, + 0x0, 0x0, 0x0, 0x0, 0x3, 0xc0, 0x77, 0xee, + 0xf7, 0xef, 0xf7, 0xef, 0xf7, 0xef, 0x73, 0xce, + 0x7b, 0xde, 0x7b, 0xde, 0x7b, 0xde, 0x3, 0xc0, + + /* U+E534 "" */ + 0x0, 0xc0, 0x6, 0x79, 0x83, 0xde, 0xf0, 0x60, + 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, 0x0, + 0xef, 0xdc, 0x7b, 0xf7, 0x9e, 0xfd, 0xe7, 0xbf, + 0x78, 0xe7, 0x9c, 0x3d, 0xef, 0x0, 0x0, 0x0, + 0x0, 0x3, 0xff, 0xff, + + /* U+E535 "" */ + 0x70, 0x7, 0x1c, 0x1, 0xc7, 0x0, 0x70, 0x0, + 0x0, 0x78, 0x1e, 0x3f, 0xdf, 0xcf, 0xfe, 0xf3, + 0xf7, 0xbe, 0xfc, 0x5d, 0xff, 0x3, 0xf, 0xc0, + 0xe0, 0xf8, 0x5c, 0x36, 0x33, 0xd, 0x9c, 0xc3, + 0x6e, 0x18, 0x5b, 0x6, + + /* U+E536 "" */ + 0x61, 0x99, 0xb8, 0xce, 0xd8, 0x36, 0x60, 0x1c, + 0x67, 0x87, 0xf7, 0xc1, 0xf3, 0xf0, 0xf9, 0xfe, + 0x7c, 0xff, 0x3e, 0x7c, 0x1f, 0x3e, 0xf, 0x9f, + 0x7, 0xcf, 0x83, 0xe7, 0xc1, 0xf3, 0xe0, 0xf9, + 0xa0, 0x68, + + /* U+E537 "" */ + 0x0, 0x0, 0x0, 0x78, 0x0, 0x7f, 0x80, 0x78, + 0x78, 0x78, 0x7, 0xb8, 0x0, 0x70, 0xc, 0x0, + 0x47, 0x88, 0x38, 0xc7, 0xe, 0x1, 0xc0, 0x0, + 0x0, 0x7, 0x88, 0x3b, 0xf7, 0x1e, 0xfd, 0xe7, + 0xbf, 0x79, 0xef, 0xde, + + /* U+E538 "" */ + 0x6, 0x2, 0x3, 0xc1, 0xc0, 0x60, 0x70, 0x0, + 0x1c, 0x0, 0x7, 0x3, 0xc7, 0xf1, 0xf9, 0xfc, + 0xff, 0x3e, 0x7f, 0xe7, 0x1f, 0xf8, 0x8d, 0xfb, + 0x0, 0x7e, 0x0, 0x1f, 0x80, 0x7, 0xe0, 0x1, + 0xf8, 0x0, 0x7e, 0x0, 0xff, 0xff, 0xff, 0xff, + 0xf0, + + /* U+E539 "" */ + 0x6, 0x2, 0x3, 0xc1, 0xc0, 0x60, 0xf8, 0x0, + 0x7f, 0x0, 0x1f, 0xc3, 0xc1, 0xc1, 0xf8, 0x70, + 0xff, 0x1c, 0x7f, 0xe2, 0x1f, 0xf8, 0x8d, 0xfb, + 0x0, 0x7e, 0x0, 0x1f, 0x80, 0x7, 0xe0, 0x1, + 0xf8, 0x0, 0x7e, 0x0, 0xff, 0xff, 0xff, 0xff, + 0xf0, + + /* U+E53A "" */ + 0x3, 0x80, 0x1f, 0x0, 0x7c, 0x1, 0xf0, 0x3, + 0x80, 0x0, 0x0, 0x7e, 0x7, 0xfc, 0x1f, 0xf8, + 0xec, 0xe7, 0xb3, 0xde, 0x5f, 0x7c, 0xf8, 0xfc, + 0xe0, 0xf3, 0x0, 0xc0, + + /* U+E53B "" */ + 0x6, 0x0, 0x6, 0x70, 0x66, 0x70, 0x30, 0x70, + 0x0, 0x0, 0x0, 0x0, 0xe0, 0xf8, 0x61, 0xfc, + 0x3, 0xfe, 0x33, 0xfe, 0x66, 0xfb, 0x6, 0xfb, + 0x0, 0xf8, 0x0, 0xd8, 0x0, 0xd8, 0x0, 0xd8, + 0x0, 0xd8, 0x0, 0xd8, 0x0, 0xd8, 0x0, 0x0, + + /* U+E53C "" */ + 0xe, 0x0, 0x78, 0x3, 0x80, 0x0, 0x0, 0xe0, + 0xf, 0x80, 0xfe, 0xf, 0xf8, 0xff, 0xe6, 0xfb, + 0x37, 0xc8, 0x3e, 0x1, 0xb7, 0xd, 0xbc, 0x6c, + 0x63, 0x63, 0x1b, 0x18, 0xd8, 0x40, + + /* U+E53D "" */ + 0xe, 0x0, 0x1, 0xcf, 0xfc, 0x39, 0xff, 0x82, + 0x30, 0x30, 0x6, 0x6, 0x3f, 0xe0, 0xcf, 0xfc, + 0x1b, 0xf8, 0x3, 0xff, 0x60, 0x7b, 0xef, 0xfe, + 0x7d, 0xff, 0x8f, 0x80, 0x1, 0xb0, 0x0, 0x36, + 0x0, 0x6, 0xc0, 0x0, 0xd8, 0x0, 0x1b, 0x0, + 0x3, 0x60, 0x0, 0x0, 0x0, 0x0, + + /* U+E53E "" */ + 0xe, 0x0, 0x1, 0xc0, 0x0, 0x38, 0x0, 0x0, + 0x0, 0x0, 0xe0, 0x0, 0x7f, 0x0, 0xf, 0xf0, + 0x3, 0xfe, 0x0, 0xff, 0xe0, 0x1b, 0xe9, 0xe2, + 0x7c, 0x7f, 0xf, 0x9f, 0xe1, 0xb3, 0xee, 0x36, + 0x6b, 0xc6, 0xce, 0xf8, 0xd9, 0xfe, 0x1b, 0x1f, + 0xc3, 0x61, 0xe0, + + /* U+E53F "" */ + 0xe, 0x0, 0x3, 0x80, 0x0, 0xe0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7, 0x80, 0x3, 0xf0, 0x0, + 0xfe, 0x0, 0x7f, 0xc0, 0x3f, 0xf1, 0xd, 0xf1, + 0xf0, 0x7c, 0xee, 0x1b, 0x7b, 0xc6, 0xde, 0xf1, + 0xb7, 0xfc, 0x6d, 0xef, 0x1b, 0x3f, 0x86, 0xc7, + 0xc0, + + /* U+E540 "" */ + 0xe, 0x0, 0x3, 0x80, 0x0, 0xe0, 0x0, 0x0, + 0x0, 0xe, 0x0, 0xf, 0xc0, 0x3, 0xf8, 0x1, + 0xfe, 0x0, 0xff, 0xc0, 0x37, 0xc7, 0xc9, 0xf3, + 0xf8, 0x7d, 0xff, 0x1b, 0x7f, 0xc6, 0xd8, 0x31, + 0xb7, 0xfc, 0x6d, 0xff, 0x1b, 0x3f, 0x86, 0xc7, + 0xc0, + + /* U+E541 "" */ + 0xe, 0x0, 0x3, 0x80, 0x0, 0xe0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x7, 0x80, 0x3, 0xf0, 0x0, + 0xfe, 0x0, 0x7f, 0xc0, 0x3f, 0xf1, 0xd, 0xf1, + 0xf0, 0x7c, 0xfe, 0x1b, 0x7b, 0xc6, 0xd8, 0x31, + 0xb7, 0xbc, 0x6d, 0xef, 0x1b, 0x3f, 0x86, 0xc7, + 0xc0, + + /* U+E542 "" */ + 0xe, 0x0, 0x1, 0xc0, 0x0, 0x38, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3c, 0x0, 0xf, 0xc0, + 0x1, 0xfc, 0x0, 0x7f, 0xc0, 0x1f, 0xf8, 0x3, + 0x7c, 0x7e, 0xf, 0x9e, 0xe1, 0xb7, 0xce, 0x36, + 0xf3, 0xc6, 0xdf, 0xf8, 0xdb, 0xcf, 0x1b, 0x3f, + 0xc3, 0x61, 0xe0, + + /* U+E543 "" */ + 0xe, 0x0, 0x3, 0x80, 0x0, 0xe0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3, 0xc0, 0x3, 0xf8, 0x0, + 0xff, 0x0, 0x7f, 0xc0, 0x3f, 0xd0, 0xd, 0xf1, + 0xf8, 0x7c, 0x7e, 0x1b, 0x36, 0xc6, 0xce, 0x71, + 0xb3, 0x9c, 0x6c, 0xdb, 0x1b, 0x1f, 0x86, 0xc3, + 0xc0, + + /* U+E544 "" */ + 0x6, 0x0, 0x6, 0x70, 0x66, 0x70, 0x30, 0x70, + 0x0, 0x0, 0x0, 0x0, 0xe0, 0xf8, 0xc1, 0xfc, + 0x3, 0xfe, 0x33, 0xfe, 0x66, 0xfb, 0x7, 0xff, + 0x1, 0xfc, 0x1, 0xfc, 0x0, 0xd8, 0x0, 0xd8, + 0x0, 0xd8, 0x0, 0xd8, 0x0, 0xd8, + + /* U+E545 "" */ + 0x0, 0x60, 0x0, 0xf, 0x0, 0x18, 0x60, 0x0, + 0xc0, 0x0, 0xe, 0x7, 0x80, 0x7f, 0xf0, 0x3, + 0xf8, 0x0, 0x1f, 0x0, 0x1, 0xf0, 0x0, 0x6, + 0x0, 0x6, 0xe, 0x1, 0xf9, 0xf2, 0xf0, 0xf1, + 0xe0, 0x0, 0x0, + + /* U+E546 "" */ + 0x1, 0x80, 0xc3, 0x3, 0xc6, 0x3, 0x18, 0x0, + 0x70, 0x3, 0xc0, 0x7, 0xc0, 0x1f, 0xc0, 0x3f, + 0xf8, 0x6f, 0xf8, 0xce, 0x31, 0x8c, 0x20, 0xc, + 0x0, 0x1c, 0x0, 0x1c, 0x0, 0x18, + + /* U+E547 "" */ + 0x1, 0xb0, 0x30, 0xd8, 0x3c, 0x6c, 0xcc, 0x60, + 0xc0, 0x70, 0x0, 0xf0, 0x18, 0x7c, 0xe, 0x7f, + 0x0, 0x3f, 0xf0, 0x1b, 0xfc, 0xc, 0xe7, 0x6, + 0x31, 0x80, 0xc, 0x0, 0x7, 0x0, 0x1, 0xc0, + 0x0, 0x60, + + /* U+E548 "" */ + 0xe, 0x1, 0xc0, 0x38, 0x0, 0x0, 0xa0, 0x77, + 0xe, 0xe3, 0xde, 0xfb, 0xff, 0x6c, 0xec, 0x1d, + 0x83, 0xb0, 0x36, 0x6, 0xc0, 0xd8, 0x1b, 0x0, + 0x0, + + /* U+E549 "" */ + 0x6, 0x0, 0x1, 0xdc, 0x0, 0x60, 0x0, 0x3, + 0x0, 0x1e, 0x20, 0xf, 0xc0, 0x3, 0xf8, 0xc1, + 0xff, 0x38, 0xfe, 0xec, 0x37, 0x80, 0x61, 0xe0, + 0x3c, 0x78, 0x2f, 0x1e, 0x1f, 0xc7, 0x87, 0xf1, + 0xe3, 0x3c, 0x68, 0xc6, + + /* U+E54A "" */ + 0x1, 0xf0, 0x3, 0xf0, 0x0, 0x0, 0x3, 0xf0, + 0x1, 0xe0, 0x1, 0xe0, 0x0, 0x0, 0xff, 0xe8, + 0xff, 0xd8, 0x3, 0xbc, 0x2, 0x7e, 0x0, 0xfe, + 0x1, 0xf7, 0x0, 0x7, 0x3, 0xf0, 0x3, 0xf0, + + /* U+E54B "" */ + 0x7, 0xc0, 0x3f, 0x20, 0x0, 0xc3, 0xf6, 0x7, + 0x98, 0x1e, 0xc0, 0x3, 0x1, 0xfc, 0x17, 0x60, + 0xed, 0xc3, 0xdf, 0x1f, 0x7e, 0x6e, 0xdb, 0xbd, + 0x7c, 0xf0, 0xc3, 0xe0, + + /* U+E54C "" */ + 0x3c, 0x0, 0x0, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, + 0x18, 0x18, 0x0, 0x0, 0x1c, 0x3c, 0x66, 0x7e, + 0x72, 0x7e, 0x7c, 0x7e, 0x0, 0x0, 0x8, 0x10, + 0x1c, 0x38, 0xfe, 0x7f, 0x1c, 0x38, 0x8, 0x10, + + /* U+E54D "" */ + 0x1, 0xc0, 0x41, 0xc1, 0x61, 0xc3, 0x30, 0x86, + 0x10, 0xc, 0x3, 0xe0, 0x7, 0xf0, 0xf, 0xf8, + 0x1f, 0xf8, 0x1b, 0xec, 0x13, 0xe4, 0x3, 0xe0, + 0x3, 0x60, 0x13, 0x6c, 0x33, 0x66, 0x63, 0x63, + 0x43, 0x61, 0x3, 0x60, + + /* U+E54E "" */ + 0x0, 0x10, 0x1c, 0x60, 0x7c, 0x40, 0xf8, 0x80, + 0xe3, 0x80, 0x7, 0x0, 0xe, 0x87, 0xbf, 0x3f, + 0x7e, 0xfe, 0xff, 0xfd, 0xe6, 0xfb, 0xfd, 0xf3, + 0xfb, 0xe3, 0x37, 0xc7, 0xf, 0x8e, + + /* U+E54F "" */ + 0x1, 0x80, 0x7, 0xe0, 0x1f, 0x78, 0x7c, 0x3e, + 0xf1, 0x8f, 0xc1, 0x83, 0xc0, 0x3, 0xc3, 0xc3, + 0xc7, 0xe3, 0xcf, 0xf3, 0xcb, 0xf3, 0xcb, 0xd3, + 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x0, 0x0, + + /* U+E551 "" */ + 0xe, 0x0, 0x1, 0xe0, 0x0, 0xe, 0x0, 0x0, + 0x0, 0x0, 0x1c, 0x0, 0x3, 0xf0, 0x0, 0x7f, + 0x83, 0xef, 0xfe, 0x7, 0xde, 0xe0, 0x3d, 0xe0, + 0x63, 0xde, 0xe, 0x30, 0xf0, 0xfe, 0x17, 0x8e, + 0x3, 0x98, 0x60, 0x71, 0x80, 0xe, 0x18, 0x0, + 0xc1, 0x80, 0x0, 0x8, 0x0, + + /* U+E552 "" */ + 0xe, 0x0, 0x3, 0xc0, 0x0, 0x38, 0x0, 0x0, + 0x0, 0x1, 0xe0, 0x0, 0x7e, 0x0, 0x1f, 0xe0, + 0x67, 0xff, 0x6, 0xde, 0x6f, 0xfb, 0xc0, 0x18, + 0x38, 0x6, 0x3, 0x80, 0x1, 0x38, 0x0, 0x73, + 0x0, 0x1c, 0x60, 0x7, 0xc, 0x0, 0xc0, 0x80, + 0x0, + + /* U+E553 "" */ + 0xe, 0x0, 0x1, 0xe0, 0xc0, 0xe, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0xe0, 0xc0, 0x3f, + 0xc, 0xcf, 0xfe, 0x6, 0xde, 0x67, 0xfd, 0xe0, + 0x6, 0xde, 0x0, 0xc0, 0xe0, 0xc0, 0x17, 0x8c, + 0x3, 0x98, 0x0, 0x79, 0x80, 0xf, 0x18, 0xc0, + 0xe1, 0x8c, 0x0, 0x8, 0x0, + + /* U+E554 "" */ + 0x0, 0x70, 0x0, 0x78, 0x0, 0x1c, 0x0, 0x0, + 0x0, 0xe, 0x0, 0xf, 0xc0, 0xf, 0xf0, 0xf, + 0xfe, 0x6, 0xf7, 0x3, 0x78, 0x1, 0xbc, 0x0, + 0x8e, 0x7, 0xc3, 0x83, 0xc4, 0xe3, 0xe7, 0x31, + 0xe7, 0x18, 0xf7, 0xc, 0x33, 0x2, 0x0, + + /* U+E555 "" */ + 0x1, 0x80, 0x0, 0x78, 0x0, 0xf, 0x0, 0x1, + 0xe0, 0x0, 0x3c, 0x0, 0x7, 0x80, 0x1, 0xf8, + 0x0, 0x7f, 0x80, 0x1f, 0xe0, 0x7, 0xf8, 0xf1, + 0xfe, 0x7f, 0x7f, 0xcf, 0xfe, 0x3b, 0xee, 0x7, + 0x61, 0xc0, 0xee, 0x78, 0x3c, 0xff, 0xf, 0xdf, + 0xc1, 0x80, 0xf0, + + /* U+E556 "" */ + 0x1, 0x80, 0x0, 0x78, 0x0, 0xf, 0x0, 0x1, + 0xe0, 0x0, 0x3c, 0x0, 0x7, 0x80, 0x1, 0xf8, + 0x0, 0x7f, 0x80, 0x1f, 0xe0, 0x7, 0xf8, 0xf3, + 0xfe, 0x77, 0x7d, 0xce, 0xfc, 0x3b, 0xde, 0x7, + 0x7b, 0xc0, 0xef, 0xf8, 0x3c, 0xef, 0xf, 0xdf, + 0xc1, 0x80, 0xf0, + + /* U+E557 "" */ + 0x1, 0x80, 0x0, 0x78, 0x0, 0xf, 0x0, 0x1, + 0xe0, 0x0, 0x3c, 0x0, 0x7, 0x80, 0x1, 0xf8, + 0x0, 0x7f, 0x80, 0x1f, 0xf0, 0x7, 0xf8, 0xf3, + 0xfe, 0x7f, 0x7d, 0xcd, 0xbc, 0x3b, 0x8e, 0x7, + 0x79, 0xc0, 0xee, 0x38, 0x3c, 0xdb, 0xf, 0x9f, + 0xc1, 0xc8, 0xf0, + + /* U+E558 "" */ + 0x1, 0xc0, 0x0, 0x78, 0x0, 0xf, 0x0, 0x1, + 0xe0, 0x0, 0x3c, 0x0, 0x7, 0x80, 0x0, 0xf8, + 0x0, 0x7f, 0x80, 0x1f, 0xf3, 0x87, 0xfe, 0xd9, + 0xff, 0xdb, 0x7f, 0xf3, 0x6e, 0x3c, 0xfe, 0x7, + 0x9f, 0xc0, 0xf3, 0xf8, 0x3f, 0x7f, 0xf, 0xef, + 0xe1, 0x85, 0xfc, + + /* U+E55A "" */ + 0x6, 0xcc, 0x76, 0xee, 0x7, 0xee, 0xf8, 0x22, + 0x0, 0x0, 0x3, 0x66, 0x76, 0xee, 0x6, 0xcc, + 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x3f, 0xfc, 0x1f, 0xf8, + + /* U+E55B "" */ + 0x18, 0x60, 0x6, 0x18, 0x1, 0x86, 0x0, 0x61, + 0x80, 0xff, 0xfc, 0x3f, 0xff, 0x7, 0xff, 0x81, + 0xff, 0xe0, 0x7f, 0xc0, 0x1f, 0xe7, 0xc3, 0xf3, + 0xf8, 0xfd, 0xf7, 0x1f, 0x73, 0xc1, 0xd8, 0x30, + 0x37, 0x9c, 0xd, 0xcf, 0x3, 0x3f, 0x80, 0x7, + 0xc0, + + /* U+E55C "" */ + 0x18, 0x60, 0x6, 0x18, 0x1, 0x86, 0x0, 0x61, + 0x80, 0xff, 0xfc, 0x3f, 0xff, 0x7, 0xff, 0x81, + 0xff, 0xe0, 0x7f, 0xc0, 0x1f, 0xe7, 0xc3, 0xf3, + 0xf8, 0xfd, 0xff, 0x1f, 0x7d, 0xc1, 0xda, 0xf0, + 0x37, 0x3c, 0xd, 0xff, 0x3, 0x3f, 0x80, 0x7, + 0xc0, + + /* U+E55D "" */ + 0x18, 0x60, 0x6, 0x18, 0x1, 0x86, 0x0, 0x61, + 0x80, 0xff, 0xfc, 0x3f, 0xff, 0x7, 0xff, 0x81, + 0xff, 0xe0, 0x7f, 0xc0, 0x1f, 0xe7, 0xc3, 0xf3, + 0xb8, 0xfd, 0xef, 0x1f, 0x7b, 0xc1, 0xde, 0xf0, + 0x37, 0xfc, 0xd, 0xef, 0x3, 0x3f, 0x80, 0x7, + 0xc0, + + /* U+E55E "" */ + 0x18, 0x60, 0x6, 0x18, 0x1, 0x86, 0x0, 0x61, + 0x80, 0xff, 0xfc, 0x3f, 0xff, 0x7, 0xff, 0x81, + 0xff, 0xe0, 0x7f, 0xc0, 0x1f, 0xe7, 0xc3, 0xf3, + 0xf8, 0xfd, 0xff, 0x1f, 0x7f, 0xc1, 0xd8, 0x30, + 0x37, 0xfc, 0xd, 0xff, 0x3, 0x3f, 0x80, 0x7, + 0xc0, + + /* U+E55F "" */ + 0x18, 0x60, 0x6, 0x18, 0x1, 0x86, 0x0, 0x61, + 0x80, 0xff, 0xfc, 0x3f, 0xff, 0x7, 0xff, 0x81, + 0xff, 0xe0, 0x7f, 0xc0, 0x1f, 0xe7, 0xc3, 0xf3, + 0xf8, 0xfd, 0xef, 0x1f, 0x7b, 0xc1, 0xd8, 0x30, + 0x37, 0xbc, 0xd, 0xef, 0x3, 0x3f, 0x80, 0x7, + 0xc0, + + /* U+E560 "" */ + 0x18, 0x60, 0x6, 0x18, 0x1, 0x86, 0x0, 0x61, + 0x80, 0xff, 0xfc, 0x3f, 0xff, 0x7, 0xff, 0x81, + 0xff, 0xe0, 0x7f, 0xc0, 0x1f, 0xe7, 0xc7, 0xf3, + 0xf8, 0xfd, 0xdb, 0x1f, 0x75, 0xc3, 0xde, 0xf0, + 0x37, 0x5c, 0xd, 0xdb, 0x3, 0x3f, 0x80, 0x7, + 0xc0, + + /* U+E561 "" */ + 0x0, 0x80, 0x0, 0x40, 0x0, 0xf8, 0x0, 0x38, + 0x0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x3, 0xe0, 0x1, 0xf0, 0x3e, 0xf8, 0x1f, + 0x7c, 0xf, 0xbe, 0xff, 0xdf, 0x7f, 0xef, 0xbf, + 0xf7, 0xdf, + + /* U+E562 "" */ + 0xc0, 0x0, 0x3c, 0x0, 0x3, 0xf3, 0x33, 0x3f, + 0x33, 0x33, 0xe6, 0x66, 0x7e, 0x66, 0x67, 0xcc, + 0xcc, 0xfc, 0xcc, 0xcf, 0xc0, 0x0, 0x3c, 0x0, + 0x3, 0xc0, 0x0, 0x3c, 0x0, 0x3, 0xc0, 0x0, + 0x3c, 0x0, 0x3, + + /* U+E563 "" */ + 0xff, 0x7b, 0xff, 0xde, 0xf6, 0x27, 0xbd, 0x89, + 0xff, 0xff, 0x7f, 0xff, 0xde, 0xff, 0xf7, 0xbf, + 0xfd, 0xef, 0x3e, 0x7b, 0xcf, 0x1f, 0xf3, 0x87, + 0xfc, 0xe1, 0xef, 0x38, 0x7b, 0xce, 0x1e, 0xf0, + + /* U+E564 "" */ + 0x1e, 0x78, 0x7, 0x9e, 0x3, 0xe7, 0x80, 0xff, + 0xf0, 0x3f, 0xfc, 0xf, 0x98, 0x7, 0xe4, 0x1, + 0xf8, 0x78, 0x7e, 0x3f, 0x9f, 0xdf, 0xef, 0xf7, + 0xdf, 0xfd, 0xaf, 0xff, 0x77, 0xdf, 0xdf, 0xe0, + 0x3, 0xf8, 0x0, 0x78, + + /* U+E565 "" */ + 0x1e, 0x78, 0x7, 0x9e, 0x3, 0xe7, 0xc0, 0xff, + 0xf0, 0x3f, 0xfc, 0xf, 0x98, 0x7, 0xe4, 0x1, + 0xf8, 0x7c, 0x7f, 0x3b, 0x9f, 0xde, 0xff, 0xf7, + 0xbf, 0xfd, 0xef, 0xff, 0x7f, 0xdf, 0xde, 0xf0, + 0x3, 0xf8, 0x0, 0x7c, + + /* U+E566 "" */ + 0x1e, 0x78, 0x7, 0x9e, 0x3, 0xe7, 0xc0, 0xff, + 0xf0, 0x3f, 0xfc, 0xf, 0x98, 0x7, 0xe4, 0x1, + 0xf8, 0x7c, 0x7f, 0x3f, 0x9f, 0xdb, 0xb7, 0xf7, + 0x5f, 0xfd, 0xef, 0xff, 0x75, 0xff, 0xdb, 0xb0, + 0x3, 0xf8, 0x0, 0x7c, + + /* U+E567 "" */ + 0x1e, 0x70, 0xf, 0x3c, 0xf, 0x9e, 0x7, 0xff, + 0x83, 0xff, 0xc1, 0xf3, 0x1, 0xf9, 0x38, 0xfc, + 0xb6, 0x7e, 0x5b, 0x3f, 0xed, 0xbf, 0xe7, 0xff, + 0xc7, 0xff, 0xe3, 0xff, 0xf1, 0xfc, 0x0, 0xfe, + 0x0, 0x3f, + + /* U+E568 "" */ + 0x22, 0x22, 0x3, 0x33, 0x30, 0x33, 0x33, 0x3, + 0xbb, 0xb8, 0x3f, 0xff, 0xc3, 0xff, 0xfc, 0x3f, + 0xff, 0xe3, 0xff, 0xfe, 0x0, 0x0, 0xf, 0xff, + 0xff, 0xff, 0xff, 0xf0, + + /* U+E569 "" */ + 0xef, 0xfd, 0xdb, 0xff, 0x6e, 0xff, 0xdf, 0xbf, + 0xf7, 0x6f, 0xfd, 0xbb, 0xff, 0x76, 0xff, 0xd9, + 0xbf, 0xf6, 0xef, 0xfd, 0xdb, 0xff, 0x66, 0xff, + 0xdb, 0xbf, 0xf7, + + /* U+E56A "" */ + 0xf, 0xe0, 0x1f, 0xc0, 0x1f, 0x0, 0x0, 0x0, + 0x0, 0x1, 0xfc, 0x7, 0xfc, 0x1f, 0xfc, 0x79, + 0x3c, 0xf2, 0x7b, 0xf1, 0xff, 0xe3, 0xff, 0x93, + 0xff, 0x27, 0xff, 0xff, 0xbf, 0xfe, + + /* U+E56B "" */ + 0x0, 0xc0, 0x0, 0x1e, 0x0, 0x3, 0xf0, 0x0, + 0x7f, 0x80, 0x7f, 0x3f, 0x8f, 0xe1, 0xfc, 0xce, + 0x18, 0xc, 0xf3, 0x0, 0xff, 0xe7, 0xcf, 0xfc, + 0xfe, 0xce, 0x1f, 0xfc, 0xe1, 0xf7, 0xfe, 0x1b, + 0x7f, 0xe1, 0xcf, 0xfe, 0x1f, 0xf7, 0xfc, 0xfe, + 0x0, 0x7, 0xc0, + + /* U+E56C "" */ + 0x0, 0xc0, 0x0, 0x1e, 0x0, 0x3, 0xf0, 0x0, + 0x7f, 0x80, 0x7f, 0x3f, 0x8f, 0xe1, 0xfc, 0xce, + 0x18, 0xc, 0xf3, 0x0, 0xff, 0xe7, 0xcf, 0xfc, + 0xee, 0xce, 0x1e, 0xfc, 0xe1, 0xef, 0xfe, 0x1e, + 0xff, 0xe1, 0xff, 0xff, 0xde, 0xf7, 0xfc, 0xfe, + 0x0, 0x7, 0xc0, + + /* U+E56D "" */ + 0x0, 0xc0, 0x0, 0x1e, 0x0, 0x3, 0xf0, 0x0, + 0x7f, 0x80, 0x7f, 0x3f, 0x8f, 0xe1, 0xfc, 0xce, + 0x18, 0xc, 0xf3, 0x0, 0xff, 0xe7, 0xcf, 0xfc, + 0xfe, 0xce, 0x1d, 0x7c, 0xe1, 0xd7, 0xfe, 0x1e, + 0xff, 0xe1, 0xd7, 0xfe, 0x1d, 0x77, 0xfc, 0xfe, + 0x0, 0x7, 0xc0, + + /* U+E56E "" */ + 0x0, 0xf8, 0x0, 0x3e, 0x0, 0xf, 0x80, 0x3, + 0x0, 0x1, 0xe0, 0x1, 0xfe, 0x7, 0xff, 0xfb, + 0xfc, 0xff, 0xcf, 0x3c, 0xf3, 0xff, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xcf, 0x3c, 0xf3, 0xcf, 0x3f, + 0xf3, 0xff, 0xfc, 0xff, 0x7f, 0xff, 0x80, + + /* U+E56F "" */ + 0x0, 0xc0, 0x0, 0x1e, 0x0, 0x3, 0xf0, 0x0, + 0x7f, 0x80, 0x7f, 0x1f, 0x8f, 0xe1, 0xfc, 0xce, + 0x1c, 0xc, 0xf3, 0x9c, 0xff, 0xfb, 0x6f, 0xff, + 0xb6, 0xcf, 0x33, 0x6c, 0xe3, 0x7f, 0xfe, 0x37, + 0xff, 0xe3, 0x7f, 0xff, 0xf7, 0xf7, 0xff, 0x7f, + 0x0, 0x7, 0xf0, + + /* U+E571 "" */ + 0x7f, 0xef, 0xff, 0xe7, 0xfc, 0x4f, 0xc8, 0xff, + 0x1f, 0xe3, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xe7, 0xfe, 0xef, 0xfc, 0xff, 0x87, 0xf0, + + /* U+E572 "" */ + 0x3, 0x80, 0x1f, 0xc1, 0xff, 0xf7, 0xff, 0xff, + 0xbb, 0xff, 0x77, 0xfe, 0xf, 0xfc, 0x1f, 0x7a, + 0x7c, 0xf0, 0x79, 0xe0, 0xf1, 0xe3, 0xc1, 0xff, + 0x3, 0xfc, 0x1, 0xf0, 0x1, 0xc0, + + /* U+E573 "" */ + 0x3, 0x80, 0x1f, 0xc1, 0xff, 0xf7, 0xff, 0xff, + 0xff, 0xfe, 0x47, 0xfc, 0xa3, 0xf9, 0x7, 0x7c, + 0xc, 0xe0, 0x39, 0xc3, 0xf1, 0xf7, 0xc1, 0xff, + 0x3, 0xfc, 0x1, 0xf0, 0x1, 0xc0, + + /* U+E574 "" */ + 0x3, 0x80, 0x1f, 0xc0, 0xff, 0xe7, 0xff, 0xff, + 0xff, 0xff, 0x27, 0xfc, 0x7, 0xf8, 0xf, 0x70, + 0x3c, 0xf0, 0x79, 0xfb, 0xf1, 0xff, 0xc1, 0xff, + 0x3, 0xfc, 0x1, 0xf0, 0x1, 0xc0, + + /* U+E576 "" */ + 0x7f, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xa1, 0x79, 0x5e, 0xf0, 0x8d, 0xe1, 0x1b, 0xd2, + 0xf7, 0xb5, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xef, 0xff, 0x80, + + /* U+E577 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0x8f, 0xfe, 0x3f, 0xf8, + 0xff, 0xfc, 0xff, 0xf1, 0xfe, 0xc7, 0xf1, 0x1f, + 0xc0, 0x7e, 0x61, 0xf9, 0xcf, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+E578 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xfc, 0xf, 0xf0, + 0x3f, 0xd8, 0xfe, 0x1, 0xf8, 0x47, 0xf0, 0x3f, + 0xc0, 0xff, 0x3, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+E579 "" */ + 0x3, 0x0, 0x18, 0x3f, 0xfd, 0xff, 0xfe, 0x31, + 0x81, 0x8c, 0x6d, 0xe7, 0x6e, 0x33, 0x1, 0x98, + 0xf, 0xf8, 0x3f, 0xe0, 0x33, 0x1, 0x98, 0x3d, + 0xc1, 0xec, 0x3, 0x0, 0x18, 0x0, + + /* U+E57A "" */ + 0xc, 0x0, 0x4, 0xc8, 0x0, 0x3f, 0x0, 0x3, + 0x30, 0x1c, 0xed, 0xc3, 0xee, 0xdc, 0x32, 0x33, + 0xf, 0x23, 0xf1, 0xf7, 0x4c, 0x93, 0x70, 0xc3, + 0xb7, 0x0, 0x3b, 0x20, 0x3, 0xb0, 0x0, 0x13, + 0x0, 0x0, 0x30, 0xff, 0xff, 0xff, 0xff, 0xff, + + /* U+E57B "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xcf, 0xff, 0xcf, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, + 0xff, 0xee, 0xff, 0xfc, 0xff, 0xf8, 0x7f, 0xf0, + + /* U+E57C "" */ + 0x1, 0x80, 0x1, 0x80, 0x7b, 0xde, 0xfb, 0xdf, + 0xc9, 0x9f, 0xcc, 0x3f, 0xfe, 0x7f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe7, 0xff, 0xee, 0xff, 0xfc, + 0xff, 0xf8, 0x7f, 0xf0, + + /* U+E57D "" */ + 0x1, 0x80, 0x7, 0xe0, 0xf, 0xf0, 0x3f, 0xfc, + 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, + 0x7f, 0x7f, 0xff, 0x3f, 0xff, 0x3f, 0xff, 0x1f, + 0xff, 0x1f, 0xff, 0xf, + + /* U+E57E "" */ + 0x30, 0x0, 0xc, 0x0, 0x3, 0x0, 0x3, 0xf0, + 0x0, 0x78, 0x30, 0xc, 0x3e, 0x0, 0x1f, 0xe0, + 0x1f, 0xfe, 0x7, 0xff, 0x81, 0xfb, 0xe0, 0x7e, + 0xf8, 0x3f, 0x9f, 0xf, 0xe7, 0xc3, 0xf8, 0xff, + 0xff, 0xff, 0xff, 0xff, + + /* U+E57F "" */ + 0x30, 0x6, 0x30, 0x1, 0xbf, 0xff, 0xec, 0x0, + 0x63, 0x0, 0x60, 0xe, 0x0, 0xf, 0x80, 0x1f, + 0xf0, 0x3f, 0xfe, 0x1f, 0xff, 0xf, 0xff, 0x87, + 0xef, 0xc3, 0xf3, 0xe1, 0xf9, 0xf0, 0xfc, 0x78, + + /* U+E580 "" */ + 0x30, 0x0, 0x30, 0x0, 0x3f, 0xff, 0xcc, 0x0, + 0x33, 0x0, 0x18, 0xe, 0xc, 0xf, 0x80, 0x1f, + 0xf0, 0x3f, 0xfe, 0x1f, 0xff, 0xf, 0xff, 0x87, + 0xef, 0xc3, 0xf3, 0xe1, 0xf9, 0xf0, 0xfc, 0x78, + + /* U+E581 "" */ + 0x30, 0xc, 0x30, 0xc, 0x30, 0xc, 0xb4, 0x2d, + 0xfc, 0x3f, 0x78, 0x1e, 0x33, 0xcc, 0xf, 0xe0, + 0x1f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0x7c, + 0x7f, 0x7c, 0x7f, 0x3e, 0x7f, 0x3e, 0x7f, 0x1e, + + /* U+E582 "" */ + 0x0, 0x10, 0x0, 0x1f, 0x0, 0x1f, 0xf0, 0xf, + 0xfe, 0x0, 0x7f, 0x80, 0x8f, 0xe0, 0xf8, 0xf8, + 0x7f, 0x1f, 0x7f, 0xf3, 0xdf, 0xfc, 0xf7, 0xff, + 0x3d, 0xff, 0xc0, 0xfe, 0xf8, 0x3f, 0xbe, 0xf, + 0xe7, 0x83, 0xf1, 0xe0, + + /* U+E583 "" */ + 0x7f, 0xbf, 0xf0, 0x0, 0x0, 0xff, 0xff, 0xff, + 0xff, 0xfb, 0xfe, 0xff, 0xbf, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfe, 0x1, + + /* U+E584 "" */ + 0x7c, 0x3e, 0xfe, 0x7f, 0x0, 0x0, 0x0, 0x0, + 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xf6, 0x7b, + 0xf6, 0x7b, 0xf6, 0x7b, 0xfe, 0x7f, 0xfe, 0x7f, + 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0x82, 0x41, + + /* U+E585 "" */ + 0x40, 0x0, 0xb2, 0x1, 0x3d, 0x80, 0x6f, 0x67, + 0x9b, 0xd9, 0xe6, 0xf6, 0x79, 0xbc, 0x9e, 0x4d, + 0xf, 0xc2, 0x3, 0x30, 0x0, 0xcc, 0x0, 0x7f, + 0x80, 0x18, 0x60, 0xc, 0xc, 0x3, 0xff, 0x1, + 0x80, 0x60, 0x60, 0x18, + + /* U+E586 "" */ + 0x3, 0x0, 0x3f, 0x3, 0xff, 0x1f, 0xfe, 0x70, + 0x38, 0xc0, 0xc3, 0xff, 0xf, 0xfc, 0x18, 0x60, + 0x73, 0x81, 0xfe, 0x5, 0xe8, 0x1c, 0xe0, 0xe1, + 0xcf, 0xff, 0xff, 0xff, + + /* U+E587 "" */ + 0x0, 0x7e, 0x40, 0x7, 0xec, 0x3c, 0x7e, 0xc7, + 0xe6, 0x7f, 0x7e, 0x67, 0xf7, 0xe7, 0xff, 0xfe, + 0x67, 0x3f, 0xe6, 0x73, 0xfe, 0x7f, 0xf7, 0xc6, + 0x73, 0x18, 0x67, 0x31, 0x87, 0xff, 0x18, 0x7f, + 0xf1, 0x87, 0xff, 0x18, 0x7f, 0xf1, 0x87, 0xff, + + /* U+E589 "" */ + 0x0, 0x7, 0x0, 0x7, 0xc0, 0x7, 0xe0, 0x7, + 0xf0, 0x3, 0xf0, 0x1, 0xf0, 0x1, 0xf0, 0x21, + 0xc0, 0x39, 0xc0, 0x1f, 0xc0, 0xf, 0xc0, 0xf, + 0xe0, 0x7, 0xf8, 0x3, 0xfe, 0x3, 0xfe, 0x1, + 0xf8, 0x0, 0xe0, 0x0, 0x0, + + /* U+E58A "" */ + 0x6, 0x0, 0x1e, 0x0, 0x3e, 0x0, 0xff, 0xff, + 0xff, 0xff, 0x3e, 0x0, 0x1e, 0x0, 0x6, 0x7f, + 0x0, 0x7f, 0x0, 0x7f, 0x0, 0x7f, 0x0, 0x0, + 0xfb, 0xff, 0xfb, 0xff, 0xfb, 0xff, 0xfb, 0xff, + + /* U+E58B "" */ + 0x7f, 0xf0, 0x3f, 0xfe, 0xf, 0xff, 0x83, 0xf3, + 0xfc, 0xfe, 0x7f, 0xb8, 0xe, 0x7f, 0xe7, 0x8f, + 0xf3, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xcf, 0xcf, 0x63, 0xf3, 0x8c, 0x84, 0xc1, + 0xc0, 0xe0, + + /* U+E58C "" */ + 0x7f, 0xf0, 0x3f, 0xfe, 0xf, 0xff, 0x83, 0xf3, + 0xfc, 0xf8, 0xff, 0xbe, 0x1e, 0x7f, 0x87, 0x8f, + 0xe3, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xcf, 0xcf, 0x73, 0xf3, 0x84, 0x84, 0xc1, + 0xc0, 0xe0, + + /* U+E58D "" */ + 0x3f, 0xe0, 0x7, 0xff, 0xe0, 0x7f, 0xfe, 0xf, + 0xff, 0x30, 0xff, 0xf3, 0xf, 0xff, 0x38, 0xff, + 0xff, 0xef, 0xff, 0xff, 0x7f, 0xff, 0xf7, 0xff, + 0xff, 0x73, 0xfc, 0xe3, 0x20, 0xcc, 0x32, 0xc, + 0xc1, 0xc0, 0x78, + + /* U+E58E "" */ + 0x3f, 0xe0, 0xf, 0xff, 0xc1, 0xff, 0xf8, 0x75, + 0x59, 0x8e, 0xa3, 0x31, 0xd4, 0x67, 0x38, 0xaf, + 0xff, 0xff, 0xff, 0x7f, 0xff, 0xef, 0xff, 0xfd, + 0xcf, 0xf3, 0x99, 0x86, 0x63, 0x20, 0xcc, 0x38, + 0xf, 0x0, + + /* U+E58F "" */ + 0x6, 0x0, 0x0, 0x70, 0x0, 0x7, 0x0, 0x0, + 0x70, 0x0, 0x7, 0x3f, 0xe0, 0xf3, 0xff, 0x1f, + 0x7f, 0xf3, 0xf7, 0x7, 0x7f, 0x70, 0x77, 0xf7, + 0x7, 0xff, 0x7f, 0xfe, 0x76, 0xfb, 0x8f, 0x7f, + 0xf1, 0xf3, 0xfe, 0x3f, 0xb0, 0x63, 0x8b, 0x6, + + /* U+E591 "" */ + 0xff, 0xff, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x3, + 0x0, 0x19, 0xe6, 0xf, 0x7b, 0xc1, 0x8c, 0x60, + 0x0, 0x0, 0x39, 0xe7, 0x1c, 0xfc, 0xe7, 0x3f, + 0x39, 0xcf, 0xce, 0x0, 0x0, 0x0, 0x0, 0xf, + 0xff, 0xfc, + + /* U+E592 "" */ + 0x0, 0xc0, 0x6, 0x79, 0x83, 0xde, 0xf0, 0x63, + 0x18, 0x0, 0x0, 0x6, 0x79, 0x3, 0x3f, 0x31, + 0xcf, 0xce, 0x73, 0xf3, 0x80, 0x0, 0x0, 0x0, + 0x3, 0xff, 0xff, + + /* U+E593 "" */ + 0xc0, 0x0, 0x7c, 0x0, 0x18, 0x80, 0x2, 0x0, + 0x0, 0x0, 0x8e, 0x20, 0x3b, 0xee, 0x7, 0x39, + 0xc0, 0xc2, 0x10, 0x0, 0x0, 0x3, 0x3e, 0x60, + 0xef, 0xee, 0x39, 0xfc, 0xe7, 0x3f, 0x9c, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x0, 0x4, 0x60, 0x0, + 0xf8, 0x0, 0xc, + + /* U+E594 "" */ + 0x7f, 0xff, 0xbf, 0xff, 0xff, 0xf3, 0xff, 0xd8, + 0x6f, 0xf2, 0x13, 0xfc, 0xcc, 0xff, 0x3f, 0x3f, + 0xf8, 0x7f, 0xfe, 0x1b, 0xfb, 0x2, 0x7e, 0xc0, + 0x9f, 0xb0, 0x27, 0xff, 0xff, 0xdf, 0xff, 0xe0, + + /* U+E595 "" */ + 0x7c, 0xf, 0xb0, 0x0, 0x3c, 0x0, 0xf, 0x0, + 0x3, 0x8, 0xc4, 0x7, 0x7b, 0x81, 0xcc, 0xe0, + 0x20, 0x10, 0x9, 0xe0, 0x6, 0xfd, 0x81, 0xbf, + 0x60, 0x6f, 0xd8, 0xc0, 0x0, 0xf0, 0x0, 0x3c, + 0x0, 0xd, 0xf0, 0x3e, + + /* U+E596 "" */ + 0xff, 0x81, 0xff, 0x1, 0x8c, 0x3, 0x18, 0x6, + 0x30, 0xc, 0x60, 0x1f, 0xc0, 0x3f, 0x0, 0x7c, + 0xf8, 0xf3, 0xf9, 0xef, 0xfb, 0xdf, 0x77, 0xb5, + 0xef, 0x73, 0xce, 0xff, 0x8c, 0xfe, 0x0, 0xf8, + + /* U+E597 "" */ + 0xff, 0x80, 0xff, 0x80, 0x63, 0x0, 0x63, 0x0, + 0x63, 0x0, 0x63, 0x0, 0x7f, 0x0, 0x7c, 0x30, + 0x7d, 0x32, 0x7d, 0xfe, 0x78, 0xbc, 0x7b, 0xbf, + 0x7b, 0xf7, 0x78, 0xe6, 0x3d, 0xfe, 0x1d, 0x32, + 0x0, 0x30, + + /* U+E598 "" */ + 0x0, 0xd, 0x80, 0xf, 0x60, 0x3, 0xd8, 0x0, + 0x7e, 0xc0, 0x67, 0xb0, 0xe, 0xfc, 0x1, 0xef, + 0xc1, 0xbc, 0x80, 0x7b, 0xe0, 0xf, 0xb8, 0xf0, + 0xf3, 0x77, 0x1f, 0x8e, 0xf1, 0xfb, 0xde, 0x7f, + 0x7b, 0xdd, 0xef, 0xff, 0x1c, 0xef, 0x40, 0x1f, + 0xc0, 0x0, 0xf0, + + /* U+E599 "" */ + 0x0, 0xf8, 0x7, 0xf0, 0x1d, 0xc0, 0x7f, 0x1, + 0xfc, 0xf3, 0xf7, 0xe3, 0xff, 0xcf, 0xe7, 0x3f, + 0x9c, 0xfe, 0x73, 0xf9, 0xcf, 0xe7, 0x3f, 0x9f, + 0xfe, 0x3f, 0xb8, 0x7c, + + /* U+E59A "" */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xcd, 0x8b, 0x3f, 0xdf, 0xbf, 0x78, + 0xf1, 0xe7, 0x8f, 0x1e, 0xfd, 0xfb, 0xfc, 0xd1, + 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0xff, 0xff, 0xff, + + /* U+E59C "" */ + 0xe, 0x7, 0x81, 0xe0, 0x38, 0x0, 0x7, 0x83, + 0xf1, 0xfe, 0x7f, 0xf7, 0xf3, 0xf0, 0xfe, 0x1f, + 0x7, 0xc1, 0xf0, 0x8, + + /* U+E59D "" */ + 0xe, 0x1, 0xc0, 0x38, 0x67, 0x3e, 0xe, 0xe3, + 0x8f, 0xe0, 0xf8, 0x1f, 0x3, 0xe0, 0x7c, 0xf, + 0x81, 0xb0, 0x36, 0x6, 0xc0, 0xd8, + + /* U+E5A0 "" */ + 0x7f, 0x0, 0x7f, 0xc0, 0x3f, 0xb0, 0x1f, 0xdc, + 0xf, 0xe7, 0x7, 0xff, 0xc3, 0xff, 0xe1, 0xff, + 0x80, 0xff, 0xbc, 0x7f, 0xbf, 0xbf, 0xbf, 0x5f, + 0xdf, 0x3f, 0xec, 0xbf, 0xf7, 0x3f, 0xfb, 0xfc, + 0xfe, 0xfe, 0x0, 0x3c, 0x0, + + /* U+E5A1 "" */ + 0x7f, 0x0, 0x7f, 0xc0, 0x3f, 0xf0, 0x1f, 0xdc, + 0xf, 0xe7, 0x7, 0xff, 0xc3, 0xff, 0xe1, 0xff, + 0x80, 0xff, 0xbe, 0x7f, 0xbf, 0xbf, 0xb3, 0x7f, + 0xdc, 0x7f, 0xef, 0x3f, 0xf7, 0x1f, 0xfb, 0x36, + 0xfc, 0xfe, 0x0, 0x3e, 0x0, + + /* U+E5A9 "" */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x60, 0x3d, + 0xc6, 0x3, 0xdc, 0xe0, 0x3d, 0xfc, 0x3, 0xc7, + 0x80, 0x3c, 0xf8, 0x3, 0xcf, 0xc0, 0x3c, 0xff, + 0x83, 0xcd, 0xfc, 0x3c, 0xcc, 0x63, 0xff, 0xe7, + 0xff, 0xff, 0x7f, 0x0, 0x30, 0x0, 0x1, 0x0, + + /* U+E5AA "" */ + 0x0, 0x78, 0x0, 0xfc, 0x1, 0xcc, 0x1, 0x8c, + 0x1f, 0x8e, 0x3f, 0x9f, 0x33, 0x9f, 0x71, 0x9f, + 0x71, 0x8e, 0x71, 0x84, 0xf9, 0x80, 0xf9, 0x80, + 0xf9, 0x80, 0x71, 0x80, 0x21, 0x80, 0x1, 0x80, + 0x1, 0x80, 0x1, 0x80, + + /* U+E5AF "" */ + 0x0, 0x0, 0x1, 0xf8, 0x0, 0x3f, 0xe0, 0x7, + 0xff, 0x80, 0xff, 0xfe, 0xf, 0xff, 0xf8, 0x3f, + 0xff, 0xe3, 0xf, 0xff, 0x30, 0x3, 0xf3, 0xff, + 0xff, 0x3f, 0xff, 0xf0, 0x0, 0x3f, 0x7f, 0xff, + 0xf7, 0xff, 0xff, + + /* U+E5B4 "" */ + 0xff, 0xfc, 0x63, 0x6, 0x3, 0xfc, 0x7f, 0xe6, + 0x6, 0x60, 0x66, 0x6, 0x7f, 0xe7, 0xfe, 0x66, + 0x66, 0x66, 0x7f, 0xe3, 0xfc, 0x60, 0x64, 0x3, + 0xc0, 0x30, + + /* U+E678 "" */ + 0x7f, 0xf8, 0x3f, 0xff, 0xf, 0xff, 0xc3, 0xff, + 0xf0, 0xff, 0xf0, 0x3f, 0xf9, 0xcc, 0xce, 0xdb, + 0x33, 0xb6, 0xcc, 0xed, 0xb3, 0x37, 0xfc, 0xcd, + 0xff, 0x33, 0x7f, 0xff, 0xdf, 0xdf, 0xf7, 0xf0, + 0x1, 0xfc, + + /* U+E67A "" */ + 0x7f, 0xf8, 0x3f, 0xff, 0xf, 0xc0, 0xc3, 0xf0, + 0x30, 0xff, 0xf0, 0x3f, 0xf9, 0xcf, 0xc0, 0xdb, + 0xf0, 0x36, 0xff, 0xcd, 0xbf, 0xf7, 0xff, 0xc1, + 0xff, 0xf0, 0x7f, 0xff, 0xdf, 0xdf, 0xf7, 0xf0, + 0x1, 0xfc, + + /* U+E682 "" */ + 0x0, 0xe0, 0x0, 0xe, 0x0, 0x0, 0xe0, 0xe, + 0xe, 0x7, 0xe1, 0xf0, 0x7f, 0x3f, 0x8f, 0x3f, + 0xff, 0xc3, 0xff, 0xfc, 0x1f, 0xff, 0x81, 0xff, + 0xf8, 0x1f, 0xff, 0x80, 0xff, 0xf0, 0xf, 0xff, + 0x0, 0x7f, 0xe0, 0x7, 0xfe, 0x0, + + /* U+E68F "" */ + 0xc0, 0x0, 0x39, 0xff, 0x7, 0x3f, 0xc0, 0xef, + 0xc0, 0x1f, 0xf0, 0x3, 0xfc, 0x0, 0x7f, 0x0, + 0xf, 0xe0, 0x9, 0xfc, 0x7, 0x3f, 0x81, 0xe7, + 0xe0, 0x7c, 0xf8, 0x0, 0x1c, 0x0, 0x3, 0x80, + 0xc, 0x70, 0x3, 0xe, 0x0, 0xc1, 0xc0, 0x30, + 0x30, + + /* U+E691 "" */ + 0x7f, 0xf8, 0x3f, 0xff, 0xf, 0xff, 0xc3, 0xff, + 0xf0, 0xff, 0xf0, 0x3f, 0xf9, 0xcc, 0xce, 0xdb, + 0x33, 0xb0, 0xcc, 0xec, 0x33, 0x37, 0xfc, 0xcd, + 0xff, 0x33, 0x7f, 0xff, 0xdf, 0xdf, 0xf7, 0xf0, + 0x1, 0xfc, + + /* U+E695 "" */ + 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x1, 0x80, + 0x1, 0x80, 0x3, 0xc0, 0x3f, 0xfc, 0x63, 0xc6, + 0x61, 0x86, 0x61, 0x86, 0xf3, 0xcf, 0xf3, 0xcf, + 0xf3, 0xcf, 0xf3, 0xcf, + + /* U+E696 "" */ + 0x7, 0xe0, 0x3, 0xff, 0x0, 0xff, 0xf8, 0x3f, + 0xff, 0x7, 0xff, 0xf1, 0xff, 0xfe, 0x3f, 0xfe, + 0x7, 0xff, 0x80, 0xff, 0xe6, 0x1f, 0xfd, 0xe1, + 0xff, 0xbc, 0x3f, 0xe7, 0x7, 0xf0, 0x80, 0xfe, + 0x71, 0xfc, 0x1f, 0xfe, 0x1, 0xc7, 0x0, 0x0, + 0x0, + + /* U+E697 "" */ + 0x3e, 0x7, 0xfc, 0x3f, 0xf1, 0xfd, 0xcf, 0xe7, + 0x7f, 0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc0, 0xff, 0xf3, 0xff, 0x9f, 0xfc, 0xff, 0xe7, + 0xdf, 0x3e, + + /* U+E698 "" */ + 0x3e, 0xf, 0xf8, 0xff, 0xcf, 0xee, 0xfe, 0x7f, + 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, + 0xc, 0x3, 0xc0, 0x30, 0x0, 0xdd, 0xb5, 0xda, + + /* U+E699 "" */ + 0x3, 0x0, 0x1e, 0x0, 0x78, 0x18, 0xc6, 0xf3, + 0x3f, 0xcc, 0xf7, 0xf7, 0x99, 0xe6, 0x67, 0x99, + 0xef, 0xef, 0x33, 0xfc, 0xcf, 0x63, 0x18, 0x1e, + 0x0, 0x78, 0x0, 0xc0, + + /* U+E69A "" */ + 0x3, 0x0, 0x1, 0xe0, 0x0, 0x78, 0x1, 0x8c, + 0x20, 0xf3, 0x1c, 0x3c, 0xcf, 0x7, 0xf2, 0x1, + 0x9e, 0x3c, 0x67, 0xbf, 0x9e, 0xef, 0x7f, 0x37, + 0x9f, 0xcd, 0xc3, 0x63, 0x79, 0xc1, 0xed, 0xf0, + 0x7b, 0xf8, 0xc, 0x3c, + + /* U+E69B "" */ + 0x7f, 0xfb, 0xff, 0xfe, 0x13, 0xfa, 0x6f, 0xe9, + 0xbf, 0x86, 0xff, 0xff, 0xfd, 0xc7, 0xf6, 0x1f, + 0xfb, 0x7f, 0xed, 0xff, 0xc7, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+E790 "" */ + 0x0, 0xc0, 0x0, 0xfc, 0x0, 0x7f, 0x80, 0x3f, + 0xf0, 0x3f, 0xff, 0x1f, 0xff, 0xef, 0xff, 0xfd, + 0xff, 0xfe, 0x7f, 0xff, 0x9f, 0xff, 0xe3, 0xff, + 0xf0, 0xff, 0xfc, 0x3f, 0xff, 0x7, 0xff, 0x81, + 0xff, 0xe0, 0x3f, 0xf0, + + /* U+E807 "" */ + 0xc, 0x3, 0x7, 0xf8, 0xfc, 0x3f, 0x1f, 0xe0, + 0xc0, 0x78, 0x3f, 0x1c, 0xee, 0x1f, 0x3, 0xc0, + 0xf0, 0x3e, 0x1d, 0x86, 0x3f, 0x7, 0x80, + + /* U+E80A "" */ + 0x18, 0x0, 0x38, 0x0, 0x31, 0xf0, 0x67, 0xfc, + 0x6e, 0xe, 0xcc, 0xe6, 0xd9, 0xf3, 0xd9, 0xb3, + 0xd9, 0xb3, 0xd8, 0x33, 0xfc, 0x67, 0x6f, 0xe6, + 0x77, 0xce, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+E816 "" */ + 0x7, 0xf8, 0x3, 0xff, 0x0, 0xff, 0xc0, 0x3f, + 0xf0, 0x6f, 0xfd, 0xb3, 0xff, 0x3c, 0xff, 0xcd, + 0xbf, 0xf6, 0x6f, 0xfd, 0xb3, 0xff, 0x3c, 0xff, + 0xcd, 0xbf, 0xf6, 0xf, 0xfc, 0x3, 0x87, 0x0, + 0xe1, 0xc0, 0x1f, 0xe0, + + /* U+E81B "" */ + 0x39, 0xee, 0x30, 0xfb, 0xff, 0xff, 0xfd, 0xe0, + + /* U+E81C "" */ + 0x7b, 0xff, 0xff, 0xfd, 0xf0, 0xc7, 0x79, 0xc0, + + /* U+E81D "" */ + 0x7f, 0xff, 0x8f, 0xff, 0xfe, 0xc3, 0xc, 0x6c, + 0x30, 0xc3, 0xc3, 0xc, 0x3c, 0x30, 0xc3, 0xc3, + 0xc, 0x3f, 0xff, 0xc3, 0xff, 0xfc, 0x3f, 0xff, + 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, + 0xf7, 0x3f, 0xce, 0x33, 0xc, 0xc1, 0xe0, 0x78, + + /* U+E820 "" */ + 0x0, 0xc0, 0x0, 0xfe, 0x1, 0xff, 0xe0, 0xff, + 0xfc, 0x3f, 0xff, 0x1f, 0xff, 0xe7, 0xff, 0xf9, + 0xff, 0xfe, 0x7f, 0xff, 0xbf, 0xff, 0xff, 0xff, + 0xfd, 0xff, 0xfe, 0x7f, 0xff, 0x8f, 0xff, 0xc1, + 0xff, 0xe0, 0x3f, 0xf0, 0x7, 0xf8, 0x0, + + /* U+F000 "" */ + 0xff, 0xff, 0xff, 0xff, 0x70, 0xe, 0x38, 0x1c, + 0x1c, 0x38, 0xe, 0x70, 0x7, 0xe0, 0x3, 0xc0, + 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, + 0x1, 0x80, 0xf, 0xf0, 0xf, 0xf0, + + /* U+F001 "" */ + 0x0, 0xe, 0x3, 0xfc, 0x3f, 0xf8, 0x7f, 0xf0, + 0xff, 0xe1, 0xf0, 0xc3, 0x1, 0x86, 0x3, 0xc, + 0x3e, 0x18, 0xfd, 0xf1, 0xff, 0xe3, 0xff, 0xc3, + 0xdf, 0x80, 0x1e, 0x0, 0x0, + + /* U+F002 "" */ + 0xf, 0x80, 0x3f, 0xe0, 0x70, 0x70, 0x60, 0x30, + 0xc0, 0x18, 0xc0, 0x18, 0xc0, 0x18, 0xc0, 0x18, + 0xc0, 0x18, 0x60, 0x30, 0x70, 0x70, 0x3f, 0xf8, + 0xf, 0x9c, 0x0, 0xe, 0x0, 0x7, 0x0, 0x3, + + /* U+F003 "" */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, + 0x1f, 0xf8, 0xcf, 0xf3, 0xe3, 0xc7, 0xf1, 0x8f, + 0xf8, 0x1f, 0xfe, 0x7f, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F004 "" */ + 0x3c, 0x3c, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, + 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8, 0xf, 0xf0, + 0x7, 0xe0, 0x1, 0x80, + + /* U+F005 "" */ + 0x0, 0xc0, 0x0, 0x30, 0x0, 0x1e, 0x0, 0x7, + 0x80, 0x3, 0xf0, 0xf, 0xff, 0xcf, 0xff, 0xfd, + 0xff, 0xfe, 0x3f, 0xff, 0x7, 0xff, 0x80, 0xff, + 0xc0, 0x3f, 0xf0, 0xf, 0xfc, 0x3, 0xff, 0x0, + 0xf3, 0xc0, 0x70, 0x38, + + /* U+F006 "" */ + 0x0, 0xc0, 0x0, 0x30, 0x0, 0x1e, 0x0, 0x7, + 0x80, 0x3, 0xf0, 0xf, 0xff, 0xcf, 0xff, 0xfd, + 0xff, 0xfe, 0x3f, 0xff, 0x7, 0xff, 0x80, 0xff, + 0xc0, 0x3f, 0xf0, 0xf, 0xfc, 0x3, 0xff, 0x0, + 0xf3, 0xc0, 0x70, 0x38, + + /* U+F007 "" */ + 0x7, 0x0, 0xfe, 0x7, 0xf0, 0x3f, 0x81, 0xfc, + 0xf, 0xe0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0xfe, + 0xf, 0xf8, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, + 0xe0, + + /* U+F008 "" */ + 0x7f, 0xfb, 0xff, 0xfc, 0xfe, 0xf3, 0xfb, 0xff, + 0xff, 0xff, 0xfc, 0xfe, 0xf3, 0xfb, 0xff, 0xff, + 0xff, 0xfc, 0xfe, 0xf3, 0xfb, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F009 "" */ + 0x7f, 0xfb, 0xff, 0xfc, 0x30, 0xf0, 0xc3, 0xc3, + 0xf, 0xc, 0x3f, 0xff, 0xff, 0xff, 0xc3, 0xf, + 0xc, 0x3c, 0x30, 0xf0, 0xc3, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F00A "" */ + 0x7f, 0xfb, 0xff, 0xfc, 0xcc, 0xf3, 0x33, 0xff, + 0xff, 0xff, 0xfc, 0xcc, 0xf3, 0x33, 0xff, 0xff, + 0xff, 0xfc, 0xcc, 0xf3, 0x33, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F00B "" */ + 0x7f, 0xfb, 0xff, 0xfc, 0xc0, 0xf3, 0x3, 0xff, + 0xff, 0xff, 0xfc, 0xc0, 0xf3, 0x3, 0xff, 0xff, + 0xff, 0xfc, 0xc0, 0xf3, 0x3, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F00C "" */ + 0x0, 0xc, 0x0, 0x70, 0x1, 0x80, 0xc, 0x0, + 0x70, 0x3, 0x80, 0xc, 0x30, 0x70, 0xe3, 0x81, + 0xcc, 0x3, 0xe0, 0x7, 0x80, 0xc, 0x0, + + /* U+F00D "" */ + 0xc0, 0x3e, 0x7, 0x70, 0xe3, 0x9c, 0x1f, 0x80, + 0xf0, 0xf, 0x1, 0xf8, 0x39, 0xc7, 0xe, 0xe0, + 0x7c, 0x3, + + /* U+F00E "" */ + 0xf, 0x80, 0x3f, 0xe0, 0x7f, 0xf0, 0x7d, 0xf0, + 0xfd, 0xf8, 0xf0, 0x78, 0xf0, 0x78, 0xf0, 0x78, + 0xfd, 0xf8, 0x7d, 0xf0, 0x7f, 0xf0, 0x3f, 0xf0, + 0xf, 0x9c, 0x0, 0xe, 0x0, 0x7, 0x0, 0x3, + + /* U+F010 "" */ + 0xf, 0x80, 0x3f, 0xe0, 0x7f, 0xf0, 0x7f, 0xf0, + 0xff, 0xf8, 0xff, 0xf8, 0xf0, 0x78, 0xff, 0xf8, + 0xff, 0xf8, 0x7f, 0xf0, 0x7f, 0xf0, 0x3f, 0xf8, + 0xf, 0x9c, 0x0, 0xe, 0x0, 0x7, 0x0, 0x3, + + /* U+F011 "" */ + 0x1, 0x80, 0x1, 0x80, 0x19, 0x98, 0x39, 0x9c, + 0x71, 0x8e, 0x61, 0x86, 0xe1, 0x87, 0xc1, 0x83, + 0xc1, 0x83, 0xc1, 0x83, 0xc0, 0x3, 0xe0, 0x7, + 0x60, 0x6, 0x70, 0xe, 0x3c, 0x3c, 0x1f, 0xf8, + 0x7, 0xe0, + + /* U+F012 "" */ + 0x0, 0x6, 0x0, 0xc, 0x0, 0x18, 0x1, 0xb0, + 0x3, 0x60, 0x6, 0xc0, 0xcd, 0x81, 0x9b, 0x3, + 0x36, 0x36, 0x6c, 0x6c, 0xde, 0xd9, 0xbd, 0xb3, + 0x7b, 0x66, 0xc0, + + /* U+F013 "" */ + 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x77, 0xee, + 0x7f, 0xff, 0xff, 0xff, 0x7e, 0x7e, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x7e, 0xff, 0xff, + 0x7f, 0xff, 0x77, 0xee, 0x3, 0xc0, 0x3, 0xc0, + 0x3, 0xc0, + + /* U+F014 "" */ + 0x7, 0x80, 0x3f, 0xf, 0xff, 0xff, 0xff, 0x0, + 0x0, 0x0, 0x7, 0xff, 0x9f, 0xfe, 0x6c, 0xd9, + 0xb3, 0x66, 0xcd, 0x9b, 0x36, 0x6c, 0xd9, 0xb3, + 0x66, 0xcd, 0x9f, 0xfe, 0x3f, 0xf0, + + /* U+F015 "" */ + 0x0, 0x80, 0x0, 0xe0, 0x1, 0xfc, 0x1, 0xff, + 0x1, 0xff, 0xc1, 0xff, 0xf1, 0xff, 0xfd, 0xff, + 0xff, 0x7f, 0xff, 0x1f, 0xff, 0xf, 0x8f, 0x87, + 0xc7, 0xc3, 0xe3, 0xe1, 0xf1, 0xf0, 0xf8, 0xf8, + 0x3f, 0xf8, + + /* U+F016 "" */ + 0x3e, 0x7, 0xfc, 0x3f, 0xf1, 0xfd, 0xcf, 0xe7, + 0x7f, 0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xcf, 0xfe, + + /* U+F017 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7e, 0x7e, + 0x7e, 0x7e, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, + 0xfe, 0x3f, 0xff, 0x8f, 0xff, 0xef, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F018 "" */ + 0x1e, 0x78, 0x1e, 0x78, 0x3e, 0x7c, 0x3f, 0xfc, + 0x3f, 0xfc, 0x3e, 0x7c, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7f, 0xfe, 0xff, 0xff, 0xfe, 0x7f, + 0xfe, 0x7f, 0xfe, 0x7f, + + /* U+F019 "" */ + 0x3, 0x0, 0xc, 0x0, 0x30, 0x0, 0xc0, 0x3, + 0x0, 0xc, 0x1, 0xb6, 0x7, 0xf8, 0xf, 0xc0, + 0x1e, 0x7, 0x33, 0xbe, 0x1f, 0xff, 0xef, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F01A "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, + 0xf8, 0x1f, 0xf8, 0x1f, 0xfc, 0x3f, 0x7e, 0x7e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F01B "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfc, 0x3f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0x7e, 0x7e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F01C "" */ + 0x3f, 0xfc, 0x7f, 0xfe, 0x60, 0x6, 0x60, 0x6, + 0xe0, 0x7, 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x3, + 0xf8, 0x1f, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, + + /* U+F01D "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7d, 0xfe, 0xfc, 0x7f, 0xfc, 0x3f, 0xfc, 0x1f, + 0xfc, 0x1f, 0xfc, 0x3f, 0xfc, 0x7f, 0x7d, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F01E "" */ + 0x0, 0x0, 0x1, 0xf8, 0xc3, 0xff, 0x63, 0x81, + 0xf3, 0x0, 0x79, 0x80, 0xfd, 0x80, 0x7e, 0xc0, + 0x0, 0x60, 0x0, 0x30, 0x0, 0x18, 0x0, 0xc, + 0x0, 0x7, 0x0, 0x21, 0x80, 0x30, 0x70, 0x38, + 0x1f, 0xf8, 0x7, 0xf0, 0x0, 0x0, 0x0, + + /* U+F021 "" */ + 0x7, 0xe3, 0x1f, 0xfb, 0x3c, 0x3f, 0x70, 0xf, + 0x60, 0x3f, 0xe0, 0x3f, 0xc0, 0x0, 0xc0, 0x0, + 0x0, 0x3, 0x0, 0x3, 0xfc, 0x7, 0xfc, 0x6, + 0xf0, 0xe, 0xfc, 0x3c, 0xdf, 0xf8, 0xc7, 0xe0, + + /* U+F022 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x7, + 0xe6, 0x7, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x7, + 0xe6, 0x7, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F023 "" */ + 0xf, 0x1, 0xf8, 0x39, 0xc3, 0xc, 0x30, 0xc3, + 0xc, 0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xe0, + + /* U+F024 "" */ + 0xc0, 0x3, 0x7c, 0x3f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xbf, 0xf0, 0x18, 0xc0, 0x3, 0x0, + 0xc, 0x0, 0x30, 0x0, + + /* U+F025 "" */ + 0xf, 0xc0, 0x7f, 0x83, 0x87, 0x18, 0x6, 0xe0, + 0x1f, 0x0, 0x3c, 0x0, 0xf0, 0x3, 0xf8, 0x7f, + 0xe1, 0xff, 0x87, 0xfe, 0x1f, 0xf8, 0x7f, 0xe1, + 0xf7, 0x87, 0x80, + + /* U+F026 "" */ + 0x1, 0x81, 0xc1, 0xe1, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xc3, 0xe0, 0xf0, 0x38, 0xc, + + /* U+F027 "" */ + 0x1, 0x80, 0x1c, 0x1, 0xe0, 0x1f, 0xf, 0xf8, + 0x7f, 0xdb, 0xfe, 0x7f, 0xf3, 0xff, 0xbf, 0xfc, + 0x3, 0xe0, 0xf, 0x0, 0x38, 0x0, 0xc0, + + /* U+F028 "" */ + 0x1, 0x83, 0x0, 0x70, 0x30, 0x1e, 0x26, 0x7, + 0xc7, 0x6f, 0xf8, 0x67, 0xff, 0x66, 0xff, 0xe6, + 0xdf, 0xfc, 0xdb, 0xff, 0xb3, 0x7f, 0xf0, 0xcc, + 0x3e, 0x3b, 0x3, 0xc4, 0xe0, 0x38, 0x18, 0x3, + 0x6, 0x0, + + /* U+F029 "" */ + 0xfc, 0xff, 0xf3, 0xfc, 0xcc, 0xf3, 0x33, 0xfc, + 0xff, 0xf3, 0xf0, 0x0, 0x0, 0x0, 0xfc, 0xcf, + 0xf3, 0x3c, 0xc3, 0x33, 0xc, 0xfc, 0xcf, 0xf3, + 0x30, + + /* U+F02A "" */ + 0xdb, 0x6f, 0x6d, 0xbd, 0xb6, 0xf6, 0xdb, 0xdb, + 0x6f, 0x6d, 0xbd, 0xb6, 0xf6, 0xdb, 0xdb, 0x6f, + 0x6d, 0xbd, 0xb6, 0xf6, 0xdb, 0xdb, 0x6f, 0x6d, + 0xb0, + + /* U+F02B "" */ + 0x7f, 0x1, 0xff, 0x3, 0x9f, 0x7, 0x3f, 0xf, + 0xff, 0x9f, 0xff, 0xbf, 0xff, 0xff, 0xff, 0x7f, + 0xfe, 0x7f, 0xf8, 0x7f, 0xe0, 0x7f, 0x80, 0x3e, + 0x0, 0x38, 0x0, + + /* U+F02C "" */ + 0x7f, 0x10, 0x7f, 0xcc, 0x3f, 0xf3, 0x1c, 0xfc, + 0xcf, 0xff, 0x37, 0xff, 0xdf, 0xff, 0xe7, 0xff, + 0xf6, 0x3f, 0xf7, 0xf, 0xf3, 0x3, 0xf3, 0x0, + 0xf3, 0x0, 0x31, 0x0, + + /* U+F02D "" */ + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0x1f, 0xc0, 0x7f, 0xff, 0xfc, 0x7, 0xf0, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1b, 0x0, + 0x6f, 0xff, 0xdf, 0xff, + + /* U+F02E "" */ + 0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x9f, 0xf0, 0xfc, 0x3, + + /* U+F02F "" */ + 0x1f, 0xf0, 0x3f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0xff, 0xff, + 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf, + 0x30, 0xc, 0x30, 0xc, 0x3f, 0xfc, 0x1f, 0xf8, + + /* U+F030 "" */ + 0x7, 0xe0, 0xf, 0xf0, 0x7f, 0xfe, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x3f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1f, 0xfc, 0x3f, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, + + /* U+F031 "" */ + 0x1, 0x0, 0x7, 0x0, 0xe, 0x0, 0x3e, 0x0, + 0x6c, 0x0, 0xdc, 0x3, 0x18, 0x6, 0x30, 0x1c, + 0x70, 0x3f, 0xe0, 0xff, 0xe1, 0x80, 0xcf, 0x83, + 0xff, 0x7, 0xc0, + + /* U+F032 "" */ + 0xff, 0x8f, 0xfc, 0x30, 0xe3, 0x6, 0x30, 0x63, + 0xe, 0x3f, 0xc3, 0xfe, 0x30, 0x73, 0x3, 0x30, + 0x33, 0x7, 0xff, 0xef, 0xfc, + + /* U+F033 "" */ + 0xf, 0xf0, 0xff, 0x1, 0x80, 0x38, 0x3, 0x0, + 0x30, 0x6, 0x0, 0x60, 0xc, 0x0, 0xc0, 0x1c, + 0x1, 0x80, 0xff, 0xf, 0xf0, + + /* U+F034 "" */ + 0xff, 0xc3, 0x3f, 0xf1, 0xec, 0xcc, 0xff, 0x33, + 0x3f, 0xc, 0x3, 0x3, 0x0, 0xc0, 0xc0, 0x30, + 0x30, 0xc, 0xc, 0x3, 0x3, 0x0, 0xc0, 0xc0, + 0xfc, 0x30, 0x3f, 0x3f, 0x7, 0x8f, 0xc0, 0xc0, + + /* U+F035 "" */ + 0xff, 0xff, 0xff, 0xfc, 0x30, 0xf0, 0xc3, 0x3, + 0x0, 0xc, 0x0, 0x30, 0x3, 0xf0, 0xf, 0xc0, + 0x0, 0x0, 0x0, 0xc, 0xc, 0x70, 0x3b, 0xff, + 0xff, 0xff, 0xdc, 0xe, 0x30, 0x30, + + /* U+F036 "" */ + 0xff, 0x83, 0xfe, 0x0, 0x0, 0x0, 0x0, 0xff, + 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0xff, 0x83, + 0xfe, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, + 0xf0, + + /* U+F037 "" */ + 0x1f, 0xe0, 0x7f, 0x80, 0x0, 0x0, 0x0, 0xff, + 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x1f, 0xe0, + 0x7f, 0x80, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, + 0xf0, + + /* U+F038 "" */ + 0x7, 0xfc, 0x1f, 0xf0, 0x0, 0x0, 0x0, 0xff, + 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x7, 0xfc, + 0x1f, 0xf0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, + 0xf0, + + /* U+F039 "" */ + 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0xff, + 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0xff, 0xf0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, + 0xf0, + + /* U+F03A "" */ + 0x40, 0x1, 0xdf, 0xff, 0xbf, 0xfa, 0x0, 0x0, + 0x0, 0x8, 0x0, 0x3b, 0xff, 0xf7, 0xff, 0x40, + 0x0, 0x0, 0x1, 0x0, 0x7, 0x7f, 0xfe, 0xff, + 0xe8, 0x0, 0x0, + + /* U+F03B "" */ + 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x13, + 0xfc, 0xcf, 0xff, 0x0, 0x3c, 0x0, 0x33, 0xfc, + 0x4f, 0xf0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, + 0xf0, + + /* U+F03C "" */ + 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x83, + 0xff, 0x8f, 0xff, 0x0, 0x3c, 0x0, 0xe3, 0xfe, + 0xf, 0xf0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, + 0xf0, + + /* U+F03D "" */ + 0x7f, 0xe0, 0x7f, 0xf8, 0x3f, 0xfc, 0xff, 0xfe, + 0xff, 0xff, 0x7f, 0xff, 0xbf, 0xff, 0xdf, 0xff, + 0xef, 0xff, 0xf7, 0xff, 0xf9, 0xff, 0xfc, 0xf, + 0xfc, 0x0, + + /* U+F03E "" */ + 0x7f, 0xfb, 0xff, 0xfe, 0x7f, 0xf9, 0xff, 0xe7, + 0xff, 0x9f, 0xff, 0xf7, 0xff, 0x8f, 0xf4, 0x1f, + 0x80, 0x7c, 0x0, 0xf0, 0x3, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F040 "" */ + 0x0, 0x1c, 0x0, 0x3e, 0x0, 0x3f, 0x0, 0x9f, + 0x1, 0xcf, 0x3, 0xe6, 0x7, 0xf0, 0xf, 0xf8, + 0x1f, 0xf0, 0x3f, 0xe0, 0x7f, 0xc0, 0x7f, 0x80, + 0x4f, 0x0, 0xce, 0x0, 0xfc, 0x0, 0xe0, 0x0, + + /* U+F041 "" */ + 0xf, 0x3, 0xfc, 0x7f, 0xef, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xe7, 0xfe, 0x7f, + 0xe3, 0xfc, 0x1f, 0x81, 0xf0, 0xf, 0x0, 0x60, + + /* U+F042 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0x3c, 0x7f, 0xe, + 0x7f, 0x6, 0xff, 0x7, 0xff, 0x3, 0xff, 0x3, + 0xff, 0x3, 0xff, 0x3, 0xff, 0x7, 0x7f, 0x6, + 0x7f, 0xe, 0x3f, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F043 "" */ + 0x6, 0x0, 0xf0, 0xf, 0x1, 0xf8, 0x3f, 0xc3, + 0xfc, 0x7f, 0xe7, 0xfe, 0xff, 0xff, 0xff, 0xdf, + 0xfc, 0xff, 0xe7, 0xe7, 0x1e, 0x3f, 0xc0, 0xf0, + + /* U+F044 "" */ + 0x0, 0x4, 0x0, 0xf, 0x7e, 0x7, 0xfe, 0x63, + 0xc0, 0xf0, 0xc1, 0xf8, 0xc3, 0xf8, 0xc7, 0xf0, + 0xc7, 0xe0, 0xc7, 0xcc, 0xcf, 0x8c, 0xcc, 0xc, + 0xc0, 0xc, 0xc0, 0xc, 0xff, 0xfc, 0x7f, 0xf8, + + /* U+F045 "" */ + 0x0, 0x18, 0x0, 0xe, 0x0, 0x7, 0x8e, 0x3f, + 0xef, 0x7f, 0xfe, 0x7f, 0xff, 0x3f, 0xfd, 0x9e, + 0x3c, 0xce, 0x1c, 0x63, 0xc, 0x31, 0x80, 0x18, + 0x20, 0xc, 0x1, 0x86, 0x0, 0xc3, 0xff, 0xe0, + 0xff, 0xe0, + + /* U+F046 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x9f, 0xfe, 0xff, 0xf3, 0xf9, 0x9f, 0xe4, 0xff, + 0xc7, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F047 "" */ + 0x1, 0x80, 0x3, 0xc0, 0x7, 0xe0, 0x7, 0xe0, + 0x1, 0x80, 0x31, 0x8c, 0x71, 0x8e, 0xff, 0xff, + 0xff, 0xff, 0x71, 0x8e, 0x31, 0x8c, 0x1, 0x80, + 0x7, 0xe0, 0x7, 0xe0, 0x3, 0xc0, 0x1, 0x80, + + /* U+F048 "" */ + 0xc0, 0x3c, 0xf, 0xc1, 0xfc, 0x7f, 0xcf, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xc7, + 0xfc, 0x1f, 0xc0, 0xfc, 0x3, + + /* U+F049 "" */ + 0xc1, 0x83, 0xc3, 0x87, 0xc7, 0x8f, 0xcf, 0x9f, + 0xdf, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xdf, 0xbf, 0xcf, 0x9f, 0xc7, 0x8f, + 0xc3, 0x87, 0xc1, 0x83, + + /* U+F04A "" */ + 0x3, 0x3, 0x7, 0x87, 0xf, 0x8f, 0x1f, 0x9f, + 0x3f, 0xbf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xff, 0x3f, 0xbf, 0x1f, 0x9f, 0xf, 0x8f, + 0x7, 0x87, 0x3, 0x3, + + /* U+F04B "" */ + 0xc0, 0x7, 0x80, 0x3f, 0x1, 0xfe, 0xf, 0xfc, + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xfe, + 0x3f, 0xc1, 0xf8, 0xf, 0x0, 0x60, 0x0, + + /* U+F04C "" */ + 0xf9, 0xff, 0x9f, 0xf9, 0xff, 0x9f, 0xf9, 0xff, + 0x9f, 0xf9, 0xff, 0x9f, 0xf9, 0xff, 0x9f, 0xf9, + 0xff, 0x9f, 0xf9, 0xff, 0x9f, + + /* U+F04D "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F04E "" */ + 0xc0, 0xc0, 0xe1, 0xe0, 0xf1, 0xf0, 0xf9, 0xf8, + 0xfd, 0xfc, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0xfd, 0xfc, 0xf9, 0xf8, 0xf1, 0xf0, + 0xe1, 0xe0, 0xc0, 0xc0, + + /* U+F050 "" */ + 0xc1, 0x83, 0xe1, 0xc3, 0xf1, 0xe3, 0xf9, 0xf3, + 0xfd, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfd, 0xfb, 0xf9, 0xf3, 0xf1, 0xe3, + 0xe1, 0xc3, 0xc1, 0x83, + + /* U+F051 "" */ + 0xc0, 0x3f, 0x3, 0xf8, 0x3f, 0xe3, 0xff, 0x3f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfe, + 0x3f, 0x83, 0xf0, 0x3c, 0x3, + + /* U+F052 "" */ + 0x3, 0x0, 0x1e, 0x0, 0xfc, 0x7, 0xf8, 0x3f, + 0xf1, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, 0xff, 0xff, + 0xf0, + + /* U+F053 "" */ + 0x3, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xe0, + 0x70, 0x38, 0x1c, 0xe, 0x7, 0x3, + + /* U+F054 "" */ + 0xc0, 0xe0, 0x70, 0x38, 0x1c, 0xe, 0x7, 0x7, + 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xc0, + + /* U+F055 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, + 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F056 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, + 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F057 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0x9f, 0xf8, 0x1f, 0xfc, 0x3f, + 0xfc, 0x3f, 0xf8, 0x1f, 0xf9, 0x9f, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F058 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xcf, 0xff, 0xdf, 0xff, 0x9f, + 0xf3, 0x3f, 0xf2, 0x7f, 0xf8, 0xff, 0x7c, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F059 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfc, 0x3f, 0xfd, 0xbf, 0xfd, 0xbf, + 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F05A "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfe, 0x7f, 0xff, 0xff, 0xfc, 0x7f, + 0xfc, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x7c, 0x3e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F05B "" */ + 0x0, 0x80, 0x0, 0xe0, 0x1, 0xfc, 0x1, 0xff, + 0x1, 0x89, 0xc1, 0x80, 0x70, 0xc0, 0x18, 0xe3, + 0x8e, 0xf9, 0xcf, 0xb8, 0xe3, 0x8c, 0x1, 0x86, + 0x1, 0xc1, 0xc9, 0xc0, 0x7f, 0xc0, 0x1f, 0xc0, + 0x3, 0x80, 0x0, 0x80, 0x0, + + /* U+F05C "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0x9f, 0xf8, 0x1f, 0xfc, 0x3f, + 0xfc, 0x3f, 0xf8, 0x1f, 0xf9, 0x9f, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F05D "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xcf, 0xff, 0xdf, 0xff, 0x9f, + 0xf3, 0x3f, 0xf2, 0x7f, 0xf8, 0xff, 0x7c, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F05E "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3c, 0x3c, 0x78, 0xe, + 0x7c, 0x6, 0xee, 0x7, 0xc7, 0x3, 0xc3, 0x83, + 0xc1, 0xc3, 0xc0, 0xe3, 0xe0, 0x77, 0x60, 0x3e, + 0x70, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F060 "" */ + 0x6, 0x0, 0xe, 0x0, 0x1c, 0x0, 0x38, 0x0, + 0x70, 0x0, 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, + 0x38, 0x0, 0x1c, 0x0, 0xe, 0x0, 0x6, 0x0, + + /* U+F061 "" */ + 0x0, 0x60, 0x0, 0x70, 0x0, 0x38, 0x0, 0x1c, + 0x0, 0xe, 0xff, 0xff, 0xff, 0xff, 0x0, 0xe, + 0x0, 0x1c, 0x0, 0x38, 0x0, 0x70, 0x0, 0x60, + + /* U+F062 "" */ + 0x6, 0x0, 0xf0, 0x1f, 0x83, 0xfc, 0x76, 0xee, + 0x67, 0xc6, 0x30, 0x60, 0x6, 0x0, 0x60, 0x6, + 0x0, 0x60, 0x6, 0x0, 0x60, 0x6, 0x0, 0x60, + + /* U+F063 "" */ + 0x6, 0x0, 0x60, 0x6, 0x0, 0x60, 0x6, 0x0, + 0x60, 0x6, 0x0, 0x60, 0x6, 0xc, 0x63, 0xe6, + 0x77, 0x6e, 0x3f, 0xc1, 0xf8, 0xf, 0x0, 0x60, + + /* U+F064 "" */ + 0x0, 0x0, 0x0, 0x60, 0x0, 0x70, 0x0, 0x78, + 0x1f, 0xfc, 0x3f, 0xfe, 0x7f, 0xff, 0xff, 0xfe, + 0xff, 0xfc, 0xf0, 0x78, 0xe0, 0x70, 0xe0, 0x60, + 0x60, 0x0, 0x60, 0x0, 0x30, 0x0, + + /* U+F065 "" */ + 0xf8, 0x7f, 0xe1, 0xfc, 0x0, 0xf0, 0x3, 0xc0, + 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, + 0x0, 0x3c, 0x0, 0xf0, 0x3, 0xf8, 0x7f, 0xe1, + 0xf0, + + /* U+F066 "" */ + 0x18, 0x60, 0x61, 0x81, 0x86, 0x3e, 0x1f, 0xf8, + 0x7c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, + 0xe1, 0xff, 0x87, 0xc6, 0x18, 0x18, 0x60, 0x61, + 0x80, + + /* U+F067 "" */ + 0x3, 0x0, 0xc, 0x0, 0x30, 0x0, 0xc0, 0x3, + 0x0, 0xc, 0xf, 0xff, 0xff, 0xff, 0x3, 0x0, + 0xc, 0x0, 0x30, 0x0, 0xc0, 0x3, 0x0, 0xc, + 0x0, + + /* U+F068 "" */ + 0xff, 0xff, 0xff, 0xf0, + + /* U+F069 "" */ + 0x3, 0x0, 0xc, 0x0, 0x30, 0x0, 0xc0, 0xe3, + 0x1d, 0xed, 0xe3, 0xff, 0x3, 0xf0, 0xf, 0xc0, + 0xff, 0xc7, 0xb7, 0xb8, 0xc7, 0x3, 0x0, 0xc, + 0x0, 0x30, 0x0, 0xc0, + + /* U+F06A "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, + 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0x7e, 0x7e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F06B "" */ + 0x1c, 0x38, 0x32, 0x4c, 0x33, 0xcc, 0x31, 0x8c, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7e, 0x7e, 0x3e, 0x7c, + + /* U+F06C "" */ + 0x0, 0x3, 0x0, 0x7, 0x0, 0xf, 0x1, 0xff, + 0x7, 0xff, 0xf, 0xff, 0x8, 0x3f, 0x0, 0x3f, + 0xf, 0xff, 0x3f, 0xfe, 0x7f, 0xfe, 0x6f, 0xfc, + 0xc7, 0xf8, 0xc3, 0xe0, 0xc0, 0x0, + + /* U+F06D "" */ + 0x6, 0x0, 0x1e, 0x0, 0x7c, 0x81, 0xff, 0x83, + 0xff, 0x8f, 0xff, 0x1f, 0xff, 0x7d, 0xfe, 0xf9, + 0xfd, 0xe1, 0xbb, 0xc0, 0x77, 0x80, 0xef, 0x81, + 0xcf, 0x8f, 0x8f, 0xfe, 0xf, 0xf8, 0x7, 0xc0, + + /* U+F06E "" */ + 0x1, 0xf0, 0x1, 0xff, 0x80, 0x7c, 0x7c, 0x1e, + 0x3, 0xc7, 0xc6, 0x7c, 0xf0, 0xe7, 0xbe, 0x1c, + 0xff, 0xdf, 0x9f, 0x79, 0xf3, 0xcf, 0xbe, 0xf8, + 0xf0, 0x1e, 0xf, 0x8f, 0x80, 0xff, 0xe0, 0x7, + 0xf0, 0x0, + + /* U+F070 "" */ + 0xc0, 0x0, 0xc, 0x0, 0x0, 0xcf, 0xc0, 0xf, + 0xfe, 0x0, 0xf1, 0xf0, 0x4c, 0xf, 0x1c, 0xf9, + 0xf3, 0xcf, 0x9e, 0xf8, 0xf3, 0xff, 0xe, 0x7d, + 0xe0, 0xcf, 0x3e, 0xf, 0xe3, 0xc0, 0xf8, 0x3e, + 0xe, 0x1, 0xf8, 0xc0, 0xf, 0xcc, 0x0, 0x0, + 0xc0, 0x0, 0xc, + + /* U+F071 "" */ + 0x1, 0x80, 0x3, 0xc0, 0x3, 0xc0, 0x7, 0xe0, + 0x7, 0xe0, 0xe, 0x70, 0xe, 0x70, 0x1e, 0x78, + 0x1e, 0x78, 0x3e, 0x7c, 0x3f, 0xfc, 0x7e, 0x7e, + 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, + + /* U+F072 "" */ + 0x6, 0x0, 0x1, 0xc0, 0x0, 0x78, 0x0, 0x1f, + 0x0, 0xc3, 0xe0, 0x38, 0xfc, 0x7, 0xff, 0xf9, + 0xff, 0xff, 0x7f, 0xff, 0xdf, 0xff, 0xee, 0x3f, + 0x3, 0xf, 0x80, 0x7, 0xc0, 0x1, 0xe0, 0x0, + 0x70, 0x0, 0x18, 0x0, + + /* U+F073 "" */ + 0x18, 0x60, 0x61, 0x87, 0xff, 0xbf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0xcc, 0xcf, + 0xff, 0xff, 0xff, 0xf3, 0x33, 0xcc, 0xcf, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F074 "" */ + 0x0, 0xc, 0x0, 0xe, 0xf8, 0x7f, 0xfc, 0xff, + 0xc, 0xce, 0x6, 0xc, 0x7, 0x0, 0x3, 0x80, + 0x1, 0x8c, 0xc, 0xce, 0xfc, 0xff, 0xf8, 0x7f, + 0x0, 0xe, 0x0, 0xc, + + /* U+F075 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfe, + 0x7f, 0xfc, 0x7f, 0xf8, 0xf7, 0xe0, 0xc0, 0x0, + + /* U+F076 "" */ + 0xf0, 0x3f, 0xc0, 0xff, 0x3, 0xc0, 0x0, 0xf0, + 0x3f, 0xc0, 0xff, 0x3, 0xfc, 0xf, 0xf0, 0x3f, + 0xc0, 0xff, 0x87, 0xdf, 0xfe, 0x3f, 0xf0, 0x7f, + 0x80, 0xfc, 0x0, + + /* U+F077 "" */ + 0x3, 0x0, 0x1e, 0x0, 0xfc, 0x7, 0x38, 0x38, + 0x71, 0xc0, 0xee, 0x1, 0xf0, 0x3, + + /* U+F078 "" */ + 0xc0, 0xf, 0x80, 0x77, 0x3, 0x8e, 0x1c, 0x1c, + 0xe0, 0x3f, 0x0, 0x78, 0x0, 0xc0, + + /* U+F079 "" */ + 0x30, 0xfe, 0x1e, 0x3f, 0xcf, 0xc0, 0x33, 0xf0, + 0xc, 0x30, 0x3, 0xc, 0x0, 0xc3, 0x0, 0x30, + 0xc0, 0xc, 0x30, 0x3, 0xc, 0x0, 0xc3, 0x0, + 0xfc, 0xc0, 0x3f, 0x3f, 0xc7, 0x87, 0xf0, 0xc0, + + /* U+F07A "" */ + 0x40, 0x0, 0x3c, 0x0, 0x3, 0xff, 0xfc, 0x7f, + 0xff, 0x1f, 0xff, 0xc7, 0xff, 0xe1, 0xff, 0xf8, + 0x7f, 0xfe, 0xf, 0xff, 0x83, 0xff, 0xc0, 0x80, + 0x0, 0x30, 0x0, 0x7, 0xfe, 0x0, 0x0, 0x0, + 0x70, 0xe0, 0x1c, 0x38, 0x7, 0xe, 0x0, + + /* U+F07B "" */ + 0x3c, 0x0, 0xff, 0x0, 0xff, 0xfe, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xfe, + + /* U+F07C "" */ + 0x3c, 0x0, 0x7f, 0x80, 0x3f, 0xff, 0x1f, 0xff, + 0xcc, 0x0, 0x4, 0x0, 0x2, 0xff, 0xff, 0x7f, + 0xff, 0x3f, 0xff, 0xbf, 0xff, 0x9f, 0xff, 0xcf, + 0xff, 0xe7, 0xff, 0xe0, + + /* U+F07D "" */ + 0x18, 0x3c, 0x7e, 0xff, 0xdb, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0xdb, 0xff, 0x7e, + 0x3c, 0x18, + + /* U+F07E "" */ + 0x18, 0x6, 0xe, 0x1, 0xc7, 0x0, 0x3b, 0xff, + 0xff, 0xff, 0xff, 0xdc, 0x0, 0xe3, 0x80, 0x70, + 0x60, 0x18, + + /* U+F080 "" */ + 0xc0, 0x0, 0xcf, 0xf0, 0xcf, 0xf0, 0xc0, 0x0, + 0xc0, 0x0, 0xcf, 0xc0, 0xcf, 0xc0, 0xc0, 0x0, + 0xcf, 0xfc, 0xcf, 0xfc, 0xc0, 0x0, 0xc0, 0x0, + 0xff, 0xff, 0x7f, 0xff, + + /* U+F083 "" */ + 0x39, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7, 0xe0, 0xfc, 0x3f, 0xfc, 0x3f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xfc, 0x3f, 0xfc, 0x3f, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, + + /* U+F084 "" */ + 0x0, 0xf8, 0x1, 0xfc, 0x3, 0xfe, 0x7, 0xe7, + 0x7, 0xe7, 0x7, 0xe7, 0x7, 0xff, 0x7, 0xff, + 0xf, 0xfe, 0x1f, 0xfc, 0x3f, 0xf8, 0x7f, 0x0, + 0xfc, 0x0, 0xfc, 0x0, 0xf0, 0x0, 0xf0, 0x0, + + /* U+F085 "" */ + 0x0, 0x0, 0x0, 0xe0, 0x1, 0xfc, 0x0, 0x3f, + 0xc0, 0x3, 0xfe, 0x0, 0x63, 0xc0, 0x3c, 0x70, + 0x7, 0xfc, 0x0, 0x1f, 0xc0, 0x3, 0xfb, 0x80, + 0x70, 0x7e, 0x0, 0xf, 0xe0, 0xf, 0xf8, 0x1, + 0xe3, 0x0, 0xc, 0x78, 0x1, 0xff, 0x0, 0x7f, + 0x0, 0xf, 0xe0, 0x0, 0x1c, 0x0, 0x0, 0x0, + + /* U+F086 "" */ + 0x1f, 0x80, 0xf, 0xf0, 0x7, 0xfe, 0x3, 0xff, + 0xc0, 0xff, 0xf0, 0x3f, 0xfc, 0xf, 0xff, 0x43, + 0xff, 0xdc, 0x7f, 0xe7, 0x9f, 0xf3, 0xff, 0xf0, + 0xff, 0x0, 0x7f, 0x0, 0x7f, 0xc0, 0xff, 0xf0, + 0x1f, 0xf8, 0x3, 0xfe, 0x0, 0x7f, 0xc0, 0x0, + 0x30, + + /* U+F087 "" */ + 0x0, 0x0, 0x1, 0xc0, 0x1, 0xc0, 0x3, 0xc0, + 0x3, 0x80, 0xf7, 0xff, 0xf7, 0xff, 0xf7, 0xff, + 0xf7, 0xff, 0xf7, 0xff, 0xf7, 0xfe, 0xf7, 0xfe, + 0xf7, 0xfe, 0xf3, 0xfc, 0xf1, 0xfc, + + /* U+F088 "" */ + 0x1, 0xfc, 0x3, 0xfc, 0xf7, 0xfe, 0xf7, 0xfe, + 0xf7, 0xfe, 0xf7, 0xff, 0xf7, 0xff, 0xf7, 0xff, + 0xf7, 0xff, 0xf7, 0xff, 0xf3, 0x80, 0xf3, 0xc0, + 0x1, 0xc0, 0x1, 0xc0, 0x0, 0x0, + + /* U+F089 "" */ + 0x0, 0x0, 0x30, 0x1c, 0x7, 0x3, 0xcf, 0xff, + 0xfd, 0xff, 0x3f, 0xc7, 0xf0, 0xfc, 0x3f, 0xf, + 0xc3, 0xe0, 0xf0, 0x70, + + /* U+F08A "" */ + 0x3c, 0x3c, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, + 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8, 0xf, 0xf0, + 0x7, 0xe0, 0x1, 0x80, + + /* U+F08B "" */ + 0x7c, 0x0, 0xfc, 0x0, 0xc0, 0x30, 0xc0, 0x38, + 0xc0, 0x1c, 0xc0, 0xe, 0xc7, 0xff, 0xc7, 0xff, + 0xc0, 0xe, 0xc0, 0x1c, 0xc0, 0x38, 0xc0, 0x30, + 0xfc, 0x0, 0x7c, 0x0, + + /* U+F08D "" */ + 0x7f, 0xe7, 0xfe, 0x1f, 0x81, 0xf8, 0x1f, 0x81, + 0xf8, 0x3f, 0xc7, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0xf0, 0x0, 0x0, 0x0, 0x60, 0x6, 0x0, 0x60, + 0x6, 0x0, + + /* U+F08E "" */ + 0x0, 0x7f, 0x0, 0x7f, 0x0, 0xf, 0x7c, 0x1f, + 0xfc, 0x3b, 0xc0, 0x73, 0xc0, 0xe3, 0xc1, 0xc0, + 0xc3, 0x80, 0xc3, 0x0, 0xc0, 0x18, 0xc0, 0x18, + 0xc0, 0x18, 0xc0, 0x18, 0xff, 0xf8, 0x7f, 0xf0, + + /* U+F090 "" */ + 0x0, 0x3e, 0x0, 0x3f, 0x6, 0x3, 0x7, 0x3, + 0x3, 0x83, 0x1, 0xc3, 0xff, 0xe3, 0xff, 0xe3, + 0x1, 0xc3, 0x3, 0x83, 0x7, 0x3, 0x6, 0x3, + 0x0, 0x3f, 0x0, 0x3e, + + /* U+F091 "" */ + 0x1f, 0xf8, 0x1f, 0xf8, 0xff, 0xff, 0xdf, 0xf9, + 0xdf, 0xfb, 0xcf, 0xfb, 0x6f, 0xf6, 0x7f, 0xfe, + 0x3f, 0xfc, 0xf, 0xf0, 0x3, 0xc0, 0x1, 0x80, + 0x1, 0x80, 0x1, 0x80, 0xf, 0xf0, 0xf, 0xf0, + + /* U+F093 "" */ + 0x3, 0x0, 0x1e, 0x0, 0xfc, 0x7, 0xf8, 0x1b, + 0x60, 0xc, 0x0, 0x30, 0x0, 0xc0, 0x3, 0x0, + 0xc, 0x7, 0xb7, 0xbf, 0x1f, 0xff, 0xef, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F094 "" */ + 0x3, 0xf8, 0x3f, 0xf3, 0x9f, 0xcc, 0x7f, 0x67, + 0xfd, 0x3f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfb, + 0xff, 0xef, 0xff, 0x3f, 0xfc, 0xff, 0xc1, 0xfc, + 0x0, + + /* U+F095 "" */ + 0x38, 0x0, 0x78, 0x0, 0xfc, 0x0, 0xfc, 0x0, + 0xfc, 0x0, 0xfc, 0x0, 0xf8, 0x0, 0x78, 0x0, + 0x7c, 0x0, 0x3e, 0x0, 0x1f, 0x3c, 0x1f, 0xbf, + 0xf, 0xff, 0x3, 0xff, 0x1, 0xfe, 0x0, 0x3c, + + /* U+F096 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F097 "" */ + 0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x9f, 0xf0, 0xfc, 0x3, + + /* U+F098 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xe3, + 0xff, 0x8f, 0xfe, 0x7f, 0xfc, 0xff, 0xf9, 0x1f, + 0xe0, 0x7f, 0xe3, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F09C "" */ + 0xf, 0x1, 0xf8, 0x39, 0xc3, 0xc, 0x30, 0x3, + 0x0, 0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xe0, + + /* U+F09D "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xc6, 0x3f, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F09E "" */ + 0xf8, 0x3, 0xf8, 0x0, 0xf8, 0x0, 0xf0, 0xf0, + 0xe3, 0xf1, 0xc0, 0xe3, 0x1, 0xce, 0x3, 0x98, + 0x6, 0x66, 0xc, 0xfc, 0x33, 0xf0, 0xcd, 0x83, + 0x30, + + /* U+F0A0 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x7f, 0xfb, + 0xff, 0xff, 0xe4, 0xff, 0x93, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F0A1 "" */ + 0x0, 0x0, 0x0, 0x1c, 0x0, 0x78, 0x3, 0xf3, + 0xff, 0x6f, 0xf8, 0xff, 0x81, 0xff, 0x3, 0xfe, + 0x7, 0xfc, 0xd, 0xff, 0x19, 0xff, 0xb0, 0xe3, + 0xe1, 0xc3, 0xc3, 0x81, 0x87, 0x0, + + /* U+F0A2 "" */ + 0x3, 0x0, 0x1e, 0x1, 0xfe, 0x7, 0xf8, 0x3f, + 0xf0, 0xff, 0xc3, 0xff, 0xf, 0xfc, 0x3f, 0xf1, + 0xff, 0xe7, 0xff, 0xbf, 0xff, 0xff, 0xfc, 0x0, + 0x0, 0x0, 0x1, 0xe0, + + /* U+F0A3 "" */ + 0x2, 0x10, 0x0, 0xcc, 0x1, 0x3f, 0x20, 0xff, + 0xfc, 0x1f, 0xfe, 0xf, 0xff, 0xcf, 0xff, 0xfd, + 0xff, 0xfe, 0x3f, 0xff, 0x1f, 0xff, 0xef, 0xff, + 0xfc, 0xff, 0xfc, 0x1f, 0xfe, 0xf, 0xff, 0xc1, + 0x3f, 0x20, 0xc, 0xc0, 0x2, 0x10, 0x0, + + /* U+F0A4 "" */ + 0x1e, 0x0, 0x1f, 0xbf, 0x9f, 0xdf, 0xde, 0xe0, + 0xf, 0x77, 0x7, 0xbb, 0x83, 0xe0, 0x1, 0xfb, + 0xc0, 0xfd, 0xe0, 0x3e, 0x0, 0x1e, 0xf0, 0x3, + 0x78, 0x0, + + /* U+F0A5 "" */ + 0x0, 0x78, 0xff, 0x7e, 0xff, 0x7e, 0x0, 0x6f, + 0xf, 0x6f, 0xf, 0x6f, 0x0, 0x1f, 0x7, 0xbf, + 0x7, 0xbf, 0x0, 0x3e, 0x1, 0xde, 0x1, 0xd8, + + /* U+F0A6 "" */ + 0x60, 0x6, 0x0, 0x60, 0x6, 0x0, 0x6c, 0x6, + 0xd8, 0x6d, 0xb0, 0x1b, 0x1, 0xb7, 0xc3, 0xfd, + 0xce, 0x3f, 0xff, 0xf7, 0xfe, 0x7f, 0xe1, 0xf8, + + /* U+F0A7 "" */ + 0x1f, 0x87, 0xfe, 0x7f, 0xef, 0xff, 0xe3, 0xff, + 0xdc, 0x7c, 0x30, 0x1b, 0x1, 0xb6, 0xdb, 0x6d, + 0x86, 0xc0, 0x60, 0x6, 0x0, 0x60, 0x6, 0x0, + + /* U+F0A8 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0xfe, 0xfc, 0xff, 0xf9, 0xff, 0xf0, 0xf, + 0xf0, 0xf, 0xf9, 0xff, 0xfc, 0xff, 0x7e, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F0A9 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0x7e, 0xff, 0x3f, 0xff, 0x9f, 0xf0, 0xf, + 0xf0, 0xf, 0xff, 0x9f, 0xff, 0x3f, 0x7f, 0x7e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F0AA "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfc, 0x3f, 0xf8, 0x1f, 0xf9, 0x9f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F0AB "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf9, 0x9f, 0xf8, 0x1f, 0xfc, 0x3f, 0x7e, 0x7e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F0AC "" */ + 0x1, 0x80, 0x1b, 0xd8, 0x33, 0xcc, 0x77, 0xce, + 0x77, 0xee, 0xf7, 0xef, 0xf7, 0xef, 0x0, 0x0, + 0x0, 0x0, 0xf7, 0xef, 0xf7, 0xef, 0x77, 0xee, + 0x77, 0xce, 0x33, 0xcc, 0x1b, 0xd8, 0x1, 0x80, + + /* U+F0AD "" */ + 0x0, 0x78, 0x1, 0xf8, 0x1, 0xf0, 0x3, 0xe3, + 0x3, 0xc7, 0x3, 0xcf, 0x3, 0xff, 0x7, 0xfe, + 0xf, 0xfe, 0x1f, 0xf8, 0x3f, 0x80, 0x7f, 0x0, + 0xfe, 0x0, 0xfc, 0x0, 0xf8, 0x0, 0xf0, 0x0, + + /* U+F0AE "" */ + 0x30, 0x1, 0x67, 0xff, 0x8f, 0xfa, 0x0, 0x0, + 0x0, 0x6, 0x0, 0x2c, 0xff, 0xf1, 0xff, 0x40, + 0x0, 0x0, 0x0, 0x0, 0x7, 0x7f, 0xfe, 0xff, + 0xe0, + + /* U+F0B0 "" */ + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0x3f, 0xfc, + 0x1f, 0xf8, 0xf, 0xf0, 0x7, 0xe0, 0x3, 0xc0, + 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, + 0x1, 0xc0, 0x0, 0xc0, + + /* U+F0B1 "" */ + 0x7, 0xe0, 0x4, 0x20, 0xc, 0x30, 0x7f, 0xfe, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0, 0x0, 0xfc, 0x3f, 0xfc, 0x3f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F0B2 "" */ + 0x1, 0x80, 0x3, 0xc0, 0x7, 0xe0, 0x7, 0xe0, + 0x1, 0x80, 0x31, 0x8c, 0x71, 0x8e, 0xff, 0xff, + 0xff, 0xff, 0x71, 0x8e, 0x31, 0x8c, 0x1, 0x80, + 0x7, 0xe0, 0x7, 0xe0, 0x3, 0xc0, 0x1, 0x80, + + /* U+F0C0 "" */ + 0x0, 0xf0, 0x0, 0x1f, 0x80, 0x31, 0xf8, 0xc7, + 0x9f, 0x9e, 0x79, 0xf9, 0xe3, 0x6, 0xc, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x39, 0xf9, 0xc7, 0xbf, + 0xde, 0xf7, 0xfe, 0xff, 0x7f, 0xef, 0xf7, 0xfe, + 0xff, 0x7f, 0xef, + + /* U+F0C1 "" */ + 0x0, 0x1e, 0x0, 0xf, 0xe0, 0x78, 0x18, 0x3f, + 0x83, 0x1c, 0x60, 0xcc, 0xc, 0x36, 0x33, 0x1f, + 0xc, 0xc6, 0xc3, 0x3, 0x30, 0x63, 0x8c, 0x1f, + 0xc1, 0x81, 0xe0, 0x7e, 0x0, 0x7, 0xc0, 0x0, + + /* U+F0C2 "" */ + 0x7, 0x80, 0x3, 0xf8, 0x1, 0xff, 0xe0, 0x7f, + 0xfc, 0x1f, 0xff, 0xf, 0xff, 0xc7, 0xff, 0xf3, + 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x7f, 0xff, 0x8f, 0xff, 0xc0, + + /* U+F0C3 "" */ + 0x1f, 0xe0, 0x7f, 0x80, 0xcc, 0x3, 0x30, 0xc, + 0xc0, 0x33, 0x0, 0xcc, 0x3, 0x30, 0x18, 0x60, + 0xe1, 0xc3, 0xff, 0x1f, 0xfe, 0x7f, 0xfb, 0xff, + 0xff, 0xff, 0xdf, 0xfe, + + /* U+F0C4 "" */ + 0x38, 0x0, 0x7c, 0x6, 0xc6, 0x1f, 0xc6, 0x3e, + 0xc6, 0x7c, 0x7f, 0xf8, 0x3f, 0xf0, 0x7, 0xe0, + 0x3, 0xc0, 0x3f, 0x90, 0x7f, 0x38, 0xc6, 0x7c, + 0xc6, 0x3e, 0xc6, 0x1f, 0x7c, 0x6, 0x38, 0x0, + + /* U+F0C5 "" */ + 0x7, 0xf0, 0x3f, 0xe0, 0xff, 0xc3, 0xff, 0x6f, + 0xff, 0xbf, 0xfc, 0xff, 0xf3, 0xff, 0xcf, 0xff, + 0x3f, 0xfc, 0xff, 0xf1, 0xfe, 0xc0, 0x3, 0x3, + 0xf, 0xfc, 0x1f, 0xe0, + + /* U+F0C6 "" */ + 0x0, 0x20, 0x0, 0xf8, 0x1, 0x9c, 0x3, 0xc, + 0x6, 0x66, 0xc, 0xe6, 0x19, 0xcc, 0x31, 0x9c, + 0x63, 0x38, 0x66, 0x70, 0x4c, 0xe7, 0x4d, 0xce, + 0x67, 0x9c, 0x63, 0x38, 0x30, 0x70, 0x3d, 0xe0, + 0xf, 0xc0, 0x0, 0x0, + + /* U+F0C7 "" */ + 0x7f, 0xe3, 0xff, 0xce, 0x3, 0xb8, 0xf, 0xe0, + 0x3f, 0x80, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, + 0xe1, 0xff, 0x87, 0xff, 0x3f, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F0C8 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F0C9 "" */ + 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, + 0x3, 0xff, 0xff, 0xff, 0xc0, 0x0, 0x0, 0x0, + 0x0, 0xf, 0xff, 0xff, 0xff, + + /* U+F0CA "" */ + 0x40, 0x0, 0xe7, 0xff, 0xe7, 0xff, 0x40, 0x0, + 0x0, 0x0, 0x40, 0x0, 0xe7, 0xff, 0xe7, 0xff, + 0x40, 0x0, 0x0, 0x0, 0x40, 0x0, 0xe7, 0xff, + 0xe7, 0xff, 0x40, 0x0, + + /* U+F0CB "" */ + 0x60, 0x0, 0x38, 0xff, 0xcc, 0x7f, 0xe6, 0x0, + 0x3, 0x0, 0x7, 0xe0, 0x0, 0x7, 0xfe, 0x3, + 0xff, 0x78, 0x0, 0x24, 0x0, 0xe, 0x0, 0xe, + 0x3f, 0xf7, 0xcf, 0xf0, + + /* U+F0CC "" */ + 0x7, 0xf0, 0xf, 0xf0, 0x1c, 0x0, 0x18, 0x0, + 0x18, 0x0, 0x1c, 0x0, 0xff, 0xff, 0xff, 0xff, + 0x0, 0x0, 0x0, 0x18, 0x0, 0x18, 0x0, 0x38, + 0xf, 0xf0, 0xf, 0xe0, + + /* U+F0CD "" */ + 0xf0, 0xff, 0xf, 0x60, 0x66, 0x6, 0x60, 0x66, + 0x6, 0x60, 0x66, 0x6, 0x60, 0x63, 0xc, 0x3f, + 0xc0, 0xf0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, + + /* U+F0CE "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, + 0xf, 0xc, 0x3c, 0x30, 0xff, 0xff, 0xff, 0xff, + 0xc, 0x3c, 0x30, 0xf0, 0xc3, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F0D0 "" */ + 0x0, 0xc, 0x0, 0x1e, 0x0, 0x3f, 0x0, 0x3f, + 0x0, 0x9e, 0x1, 0xcc, 0x3, 0xe0, 0x7, 0xf0, + 0xf, 0xe0, 0x1f, 0xc0, 0x3f, 0x80, 0x7f, 0x0, + 0xfe, 0x0, 0xfc, 0x0, 0x78, 0x0, 0x30, 0x0, + + /* U+F0D1 "" */ + 0x7f, 0xf8, 0x3f, 0xfe, 0xf, 0xff, 0xc3, 0xff, + 0xfc, 0xff, 0xff, 0xbf, 0xff, 0x7f, 0xff, 0xcf, + 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xcf, 0xcf, 0x73, 0xf3, 0x84, 0x84, 0xc1, + 0xc0, 0xe0, + + /* U+F0D6 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xcf, 0xf3, 0xdc, 0x3b, + 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xdc, 0x3b, 0xcf, 0xf3, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F0D7 "" */ + 0xff, 0xff, 0xf7, 0xf8, 0xfc, 0x1e, 0x3, 0x0, + + /* U+F0D8 "" */ + 0xc, 0x7, 0x83, 0xf1, 0xfe, 0xff, 0xff, 0xf0, + + /* U+F0D9 "" */ + 0xc, 0x73, 0xdf, 0xff, 0xf7, 0xcf, 0x1c, 0x30, + + /* U+F0DA "" */ + 0xc3, 0x8f, 0x3e, 0xff, 0xff, 0xbc, 0xe3, 0x0, + + /* U+F0DB "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, + 0xf, 0xc, 0x3c, 0x30, 0xf0, 0xc3, 0xc3, 0xf, + 0xc, 0x3c, 0x30, 0xf0, 0xc3, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F0DC "" */ + 0x6, 0x0, 0xf0, 0x1f, 0x83, 0xfc, 0x7f, 0xef, + 0xff, 0xff, 0xf0, 0x0, 0x0, 0xf, 0xff, 0xff, + 0xf7, 0xfe, 0x3f, 0xc1, 0xf8, 0xf, 0x0, 0x60, + + /* U+F0DD "" */ + 0xff, 0xff, 0xff, 0x7f, 0xe3, 0xfc, 0x1f, 0x80, + 0xf0, 0x6, 0x0, + + /* U+F0DE "" */ + 0x6, 0x0, 0xf0, 0x1f, 0x83, 0xfc, 0x7f, 0xef, + 0xff, 0xff, 0xf0, + + /* U+F0E0 "" */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, + 0x1f, 0xf8, 0xcf, 0xf3, 0xe3, 0xc7, 0xf1, 0x8f, + 0xf8, 0x1f, 0xfe, 0x7f, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F0E2 "" */ + 0xc7, 0xe0, 0xdf, 0xf8, 0xfc, 0x3c, 0xf0, 0xe, + 0xfc, 0x6, 0xfc, 0x7, 0x0, 0x3, 0x0, 0x3, + 0x0, 0x3, 0x0, 0x3, 0x0, 0x7, 0x60, 0x6, + 0x70, 0xe, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F0E3 "" */ + 0x1, 0x80, 0x1, 0xe0, 0x1, 0xe0, 0x1, 0xe4, + 0x1, 0xe7, 0x0, 0x67, 0xc0, 0x7, 0xf0, 0x3, + 0xf2, 0x1, 0xf3, 0x81, 0xd3, 0xc1, 0xc3, 0xc1, + 0xc3, 0xc1, 0xc1, 0xc1, 0xc0, 0x41, 0xc0, 0x1, + 0xc0, 0x0, 0xc0, 0x0, 0x0, + + /* U+F0E4 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xcf, 0xff, 0x9f, 0xff, 0x9f, + 0xff, 0x3f, 0xfe, 0x3f, 0xfc, 0x3f, 0x7c, 0x3e, + 0x7e, 0x7e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F0E5 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfe, + 0x7f, 0xfc, 0x7f, 0xf8, 0xf7, 0xe0, 0xc0, 0x0, + + /* U+F0E6 "" */ + 0x1f, 0x80, 0xf, 0xf0, 0x7, 0xfe, 0x3, 0xff, + 0xc0, 0xff, 0xf0, 0x3f, 0xfc, 0xf, 0xff, 0x43, + 0xff, 0xdc, 0x7f, 0xe7, 0x9f, 0xf3, 0xff, 0xf0, + 0xff, 0x0, 0x7f, 0x0, 0x7f, 0xc0, 0xff, 0xf0, + 0x1f, 0xf8, 0x3, 0xfe, 0x0, 0x7f, 0xc0, 0x0, + 0x30, + + /* U+F0E7 "" */ + 0x0, 0x0, 0x3, 0x80, 0x1e, 0x0, 0xf0, 0x7, + 0xc0, 0x3f, 0x3, 0xf8, 0x1f, 0xe0, 0xff, 0xff, + 0xff, 0xf0, 0x7f, 0x81, 0xfc, 0x7, 0xc0, 0x3e, + 0x0, 0xf0, 0x7, 0x80, 0x1c, 0x0, 0x0, 0x0, + + /* U+F0E8 "" */ + 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, + 0x1, 0x80, 0x1, 0x80, 0x3f, 0xfc, 0x61, 0x86, + 0x61, 0x86, 0x61, 0x86, 0xf3, 0xcf, 0xf3, 0xcf, + 0xf3, 0xcf, 0xf3, 0xcf, + + /* U+F0E9 "" */ + 0x1, 0x80, 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, + 0x7f, 0xfe, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x84, 0x21, 0x1, 0x80, 0x1, 0x80, + 0x1, 0x80, 0x1, 0x80, 0xd, 0x80, 0xf, 0x80, + 0x7, 0x0, + + /* U+F0EA "" */ + 0x7f, 0xc0, 0xff, 0xe0, 0xe0, 0xe0, 0xff, 0xe0, + 0xff, 0x0, 0xfe, 0xf8, 0xfd, 0xfe, 0xfd, 0xfe, + 0xfd, 0xff, 0xfd, 0xff, 0xfd, 0xff, 0xfd, 0xff, + 0xfd, 0xff, 0xfd, 0xff, 0x3d, 0xff, 0x0, 0xfe, + + /* U+F0EB "" */ + 0xf, 0x3, 0xfc, 0x73, 0xe6, 0x3e, 0xcf, 0xfc, + 0xff, 0xdf, 0xff, 0xff, 0x7f, 0xe7, 0xfe, 0x3f, + 0xc1, 0xf8, 0x0, 0x0, 0x0, 0x1f, 0x81, 0xf8, + 0xf, 0x0, + + /* U+F0EC "" */ + 0x0, 0x18, 0x0, 0x1c, 0x0, 0xe, 0xff, 0xff, + 0xff, 0xff, 0x0, 0xe, 0x0, 0x1c, 0x0, 0x18, + 0x18, 0x0, 0x38, 0x0, 0x70, 0x0, 0xff, 0xff, + 0xff, 0xff, 0x70, 0x0, 0x38, 0x0, 0x18, 0x0, + + /* U+F0ED "" */ + 0x3, 0xc0, 0x3, 0xf8, 0x0, 0xfe, 0x0, 0x7f, + 0xf8, 0x1f, 0xff, 0x7, 0xcf, 0xc7, 0xf3, 0xf1, + 0xfc, 0xfe, 0xfd, 0x2f, 0xff, 0x83, 0xff, 0xe1, + 0xff, 0xfc, 0xff, 0x7f, 0xff, 0x8f, 0xff, 0xc0, + + /* U+F0EE "" */ + 0x7, 0xc0, 0x3, 0xf8, 0x1, 0xff, 0xe0, 0x7f, + 0xfc, 0x1f, 0x3f, 0xf, 0xc7, 0xc7, 0xe0, 0xfb, + 0xf4, 0xbe, 0xff, 0x3f, 0xff, 0xcf, 0xff, 0xf3, + 0xfd, 0xff, 0xff, 0x7f, 0xff, 0x8f, 0xff, 0xc0, + + /* U+F0F0 "" */ + 0x7, 0x80, 0x7e, 0x7, 0xf0, 0x3f, 0xc1, 0xfc, + 0x7, 0xe0, 0x1e, 0x0, 0x0, 0x0, 0x0, 0xbc, + 0xd, 0xec, 0xce, 0x2e, 0x35, 0xf1, 0xaf, 0xff, + 0xe0, + + /* U+F0F1 "" */ + 0xf3, 0xc0, 0x79, 0xe0, 0x30, 0x30, 0x18, 0x19, + 0xec, 0xd, 0xfe, 0x6, 0xcf, 0x3, 0x67, 0x81, + 0xbf, 0x61, 0x8f, 0x3f, 0xc3, 0x7, 0x81, 0x81, + 0x80, 0xc0, 0xc0, 0x60, 0x70, 0x70, 0x1c, 0x70, + 0x7, 0xf0, 0x1, 0xf0, 0x0, + + /* U+F0F2 "" */ + 0x7, 0xe0, 0xc, 0x30, 0xc, 0x30, 0x6f, 0xf6, + 0xef, 0xf7, 0xef, 0xf7, 0xef, 0xf7, 0xef, 0xf7, + 0xef, 0xf7, 0xef, 0xf7, 0xef, 0xf7, 0xef, 0xf7, + 0xef, 0xf7, 0xef, 0xf7, 0x6f, 0xf6, + + /* U+F0F3 "" */ + 0x3, 0x0, 0x1e, 0x1, 0xfe, 0x7, 0xf8, 0x3f, + 0xf0, 0xff, 0xc3, 0xff, 0xf, 0xfc, 0x3f, 0xf1, + 0xff, 0xe7, 0xff, 0xbf, 0xff, 0xff, 0xfc, 0x0, + 0x0, 0x0, 0x1, 0xe0, + + /* U+F0F4 "" */ + 0x7f, 0xfe, 0x3f, 0xff, 0x9f, 0xfe, 0xef, 0xff, + 0x37, 0xff, 0x9b, 0xff, 0xdd, 0xff, 0xfc, 0xff, + 0xfc, 0x7f, 0xf8, 0x3f, 0xfc, 0xf, 0xfc, 0x0, + 0x0, 0x0, 0x0, 0x7, 0xff, 0xe3, 0xff, 0xf0, + + /* U+F0F5 "" */ + 0x5a, 0x6, 0xb4, 0x3d, 0x68, 0x7a, 0xd1, 0xf5, + 0xa3, 0xff, 0xc7, 0xdf, 0x8f, 0x9e, 0x1f, 0x18, + 0x3e, 0x30, 0x7c, 0x60, 0x78, 0xc0, 0x31, 0x80, + 0x63, 0x0, 0xc6, 0x1, 0x8c, 0x3, + + /* U+F0F6 "" */ + 0x3e, 0xf, 0xf8, 0xff, 0xcf, 0xee, 0xfe, 0x7f, + 0xe3, 0xff, 0xff, 0xff, 0xe0, 0x7e, 0x7, 0xff, + 0xfe, 0x7, 0xe0, 0x7f, 0xff, 0xff, 0xf7, 0xfe, + + /* U+F0F7 "" */ + 0x7f, 0xef, 0xff, 0xff, 0xfe, 0x67, 0xe6, 0x7f, + 0xff, 0xff, 0xfe, 0x67, 0xe6, 0x7f, 0xff, 0xff, + 0xff, 0x9f, 0xf9, 0xff, 0x9f, 0xf9, 0xf7, 0xfe, + + /* U+F0F8 "" */ + 0x7, 0xf8, 0x3, 0xff, 0x0, 0xff, 0xc0, 0x3c, + 0xf0, 0x7f, 0x3f, 0xbf, 0xcf, 0xfc, 0xf3, 0xcf, + 0x3f, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, + 0xcf, 0x3c, 0xf3, 0xfe, 0x1f, 0xff, 0x87, 0xff, + 0xe1, 0xfd, 0xff, 0xfe, + + /* U+F0F9 "" */ + 0x7f, 0xf0, 0x3f, 0xfe, 0xf, 0xff, 0x83, 0xf7, + 0xfc, 0xfd, 0xff, 0xbc, 0x1e, 0x7f, 0x7, 0x8f, + 0xf7, 0xe3, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xdf, 0xef, 0x73, 0xf3, 0x8c, 0x84, 0xc1, + 0xc0, 0xe0, + + /* U+F0FA "" */ + 0x3, 0xe0, 0x6, 0x30, 0x6, 0x30, 0x67, 0xf6, + 0xe7, 0xf7, 0xe7, 0xf7, 0xe7, 0x77, 0xe7, 0x77, + 0xe4, 0x17, 0xe4, 0x17, 0xe7, 0x77, 0xe7, 0x77, + 0xe7, 0xf7, 0xe7, 0xf7, 0x67, 0xf6, + + /* U+F0FB "" */ + 0xf, 0xc0, 0x1, 0xc0, 0x0, 0x78, 0x0, 0x1f, + 0x0, 0xc7, 0xc0, 0x39, 0xf8, 0xf, 0xff, 0xf0, + 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xce, 0x7e, + 0x3, 0x1f, 0x0, 0x7, 0x80, 0x1, 0xe0, 0x0, + 0x70, 0x0, 0x3f, 0x0, + + /* U+F0FC "" */ + 0xff, 0xf0, 0xff, 0xf0, 0xff, 0xfe, 0xe9, 0x7f, + 0xe9, 0x73, 0xe9, 0x73, 0xe9, 0x73, 0xe9, 0x73, + 0xe9, 0x7f, 0xe9, 0x7c, 0xe9, 0x70, 0xff, 0xf0, + 0xff, 0xf0, 0x7f, 0xe0, + + /* U+F0FD "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xfd, 0xcf, 0xf7, + 0x3f, 0xdc, 0xff, 0x3, 0xfc, 0xf, 0xf7, 0x3f, + 0xdc, 0xff, 0x73, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F0FE "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3, 0xfc, 0xf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F100 "" */ + 0x6, 0x18, 0x71, 0xc7, 0x1c, 0x71, 0xc7, 0x1c, + 0x71, 0xc3, 0x8e, 0xe, 0x38, 0x38, 0xe0, 0xe3, + 0x83, 0x8e, 0xc, 0x30, + + /* U+F101 "" */ + 0xc3, 0x7, 0x1c, 0x1c, 0x70, 0x71, 0xc1, 0xc7, + 0x7, 0x1c, 0x38, 0xe3, 0x8e, 0x38, 0xe3, 0x8e, + 0x38, 0xe1, 0x86, 0x0, + + /* U+F102 "" */ + 0x6, 0x0, 0xf0, 0x1f, 0x83, 0x9c, 0x70, 0xee, + 0x7, 0xc6, 0x30, 0xf0, 0x1f, 0x83, 0x9c, 0x70, + 0xee, 0x7, 0xc0, 0x30, + + /* U+F103 "" */ + 0xc0, 0x3e, 0x7, 0x70, 0xe3, 0x9c, 0x1f, 0x80, + 0xf0, 0xc6, 0x3e, 0x7, 0x70, 0xe3, 0x9c, 0x1f, + 0x80, 0xf0, 0x6, 0x0, + + /* U+F104 "" */ + 0x6, 0x1c, 0x71, 0xc7, 0x1c, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x30, + + /* U+F105 "" */ + 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc3, 0x8e, 0x38, + 0xe3, 0x86, 0x0, + + /* U+F106 "" */ + 0x6, 0x0, 0xf0, 0x1f, 0x83, 0x9c, 0x70, 0xee, + 0x7, 0xc0, 0x30, + + /* U+F107 "" */ + 0xc0, 0x3e, 0x7, 0x70, 0xe3, 0x9c, 0x1f, 0x80, + 0xf0, 0x6, 0x0, + + /* U+F108 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xc0, 0x3, 0xc0, 0x3, + 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x3, + 0xc0, 0x3, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x3, 0xc0, 0x3, 0xc0, 0x1f, 0xf8, + + /* U+F109 "" */ + 0x1f, 0xff, 0x83, 0xff, 0xfc, 0x30, 0x0, 0xc3, + 0x0, 0xc, 0x30, 0x0, 0xc3, 0x0, 0xc, 0x30, + 0x0, 0xc3, 0x0, 0xc, 0x30, 0x0, 0xc3, 0x0, + 0xc, 0x0, 0x0, 0xf, 0xff, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0xfe, + + /* U+F10A "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xf3, + 0xff, 0xcf, 0xdf, 0xfe, + + /* U+F10B "" */ + 0x7f, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xbf, 0xf7, 0xfe, 0xfb, 0xfe, + + /* U+F10C "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F10D "" */ + 0x38, 0x39, 0xe1, 0xee, 0xe, 0x30, 0x30, 0xf8, + 0xfb, 0xf3, 0xff, 0xcf, 0xff, 0x3f, 0xfc, 0xfd, + 0xe1, 0xe0, + + /* U+F10E "" */ + 0x78, 0x7b, 0xf3, 0xff, 0xcf, 0xff, 0x3f, 0xfc, + 0xfd, 0xf1, 0xf0, 0xc0, 0xc7, 0x7, 0x78, 0x79, + 0xc1, 0xc0, + + /* U+F110 "" */ + 0x1, 0x0, 0x3, 0x80, 0x33, 0x80, 0x38, 0x0, + 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe0, 0x6, + 0xe0, 0x7, 0xe0, 0x6, 0x0, 0x0, 0x10, 0x8, + 0x38, 0x1c, 0x33, 0x9c, 0x3, 0x80, 0x3, 0x80, + + /* U+F111 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F112 "" */ + 0x0, 0x0, 0x6, 0x0, 0xe, 0x0, 0x1e, 0x0, + 0x3f, 0xf8, 0x7f, 0xfc, 0xff, 0xfe, 0x7f, 0xff, + 0x3f, 0xff, 0x1e, 0xf, 0xe, 0x7, 0x6, 0x7, + 0x0, 0x6, 0x0, 0x6, 0x0, 0xc, + + /* U+F114 "" */ + 0x3c, 0x0, 0xff, 0x0, 0xff, 0xfe, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xfe, + + /* U+F115 "" */ + 0x3c, 0x0, 0x7f, 0x80, 0x3f, 0xff, 0x1f, 0xff, + 0xcc, 0x0, 0x4, 0x0, 0x2, 0xff, 0xff, 0x7f, + 0xff, 0x3f, 0xff, 0xbf, 0xff, 0x9f, 0xff, 0xcf, + 0xff, 0xe7, 0xff, 0xe0, + + /* U+F118 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0xdf, 0xf9, 0xdf, 0xff, 0xff, + 0xff, 0xff, 0xf7, 0xef, 0xf3, 0xcf, 0x78, 0x1e, + 0x7c, 0x3e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F119 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0xdf, 0xf9, 0xdf, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x3f, 0xf8, 0x1f, 0x73, 0xce, + 0x77, 0xee, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F11A "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0xdf, 0xf9, 0xdf, 0xf9, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0x78, 0x1e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F11B "" */ + 0xf, 0xff, 0x3, 0xff, 0xfc, 0x7f, 0xff, 0xe7, + 0xdf, 0xce, 0xfd, 0xfc, 0xff, 0xf, 0xcf, 0xf0, + 0xfb, 0xff, 0xdf, 0xbf, 0x7d, 0xfb, 0xe7, 0xff, + 0xfe, 0x3f, 0xff, 0xc0, 0xff, 0xf0, + + /* U+F11C "" */ + 0x7f, 0xff, 0xbf, 0xff, 0xfc, 0x92, 0x4f, 0x24, + 0x93, 0xff, 0xff, 0xf2, 0x49, 0x3c, 0x92, 0x4f, + 0xff, 0xff, 0xf8, 0x7, 0xfe, 0x1, 0xff, 0xff, + 0xfd, 0xff, 0xfe, + + /* U+F11D "" */ + 0xc0, 0x3, 0x7c, 0x3f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xbf, 0xf0, 0x18, 0xc0, 0x3, 0x0, + 0xc, 0x0, 0x30, 0x0, + + /* U+F11E "" */ + 0xc0, 0x3, 0xfc, 0x3f, 0xff, 0xf3, 0x3f, 0xcc, + 0xcf, 0xf, 0x3f, 0x33, 0xff, 0xf, 0xcc, 0xcf, + 0x33, 0x3f, 0xff, 0xfc, 0x7f, 0xc0, 0x23, 0x0, + 0xc, 0x0, 0x30, 0x0, + + /* U+F120 "" */ + 0xc0, 0x0, 0xe0, 0x0, 0x70, 0x0, 0x38, 0x0, + 0x1c, 0x0, 0xe, 0x0, 0xe, 0x0, 0x1c, 0x0, + 0x38, 0x0, 0x70, 0x0, 0xe3, 0xff, 0xc3, 0xff, + + /* U+F121 "" */ + 0x0, 0x30, 0x0, 0xc, 0x0, 0x3, 0x0, 0x1, + 0x80, 0x18, 0x66, 0xe, 0x19, 0xc7, 0xc, 0x3b, + 0x83, 0x7, 0xe0, 0xc1, 0xdc, 0x30, 0xe3, 0x98, + 0x70, 0x66, 0x18, 0x1, 0x80, 0x0, 0xc0, 0x0, + 0x30, 0x0, 0xc, 0x0, + + /* U+F122 "" */ + 0x6, 0x60, 0x3, 0x38, 0x1, 0x9e, 0x0, 0xcf, + 0x80, 0x67, 0xff, 0x33, 0xff, 0xec, 0xff, 0xf9, + 0x9f, 0xff, 0x33, 0xef, 0xc6, 0x78, 0xf0, 0xce, + 0x1c, 0x19, 0x86, 0x0, 0x1, 0x80, 0x0, 0xc0, + + /* U+F123 "" */ + 0x0, 0x0, 0x30, 0x1c, 0x7, 0x3, 0xcf, 0xff, + 0xfd, 0xff, 0x3f, 0xc7, 0xf0, 0xfc, 0x3f, 0xf, + 0xc3, 0xe0, 0xf0, 0x70, + + /* U+F124 "" */ + 0x0, 0xe, 0x0, 0x7c, 0x7, 0xf0, 0x3f, 0xe3, + 0xff, 0xdf, 0xff, 0x3f, 0xfe, 0x1f, 0xfc, 0x7, + 0xf0, 0x7, 0xe0, 0x7, 0x80, 0xf, 0x0, 0x1e, + 0x0, 0x18, 0x0, 0x30, 0x0, + + /* U+F125 "" */ + 0x30, 0x3, 0x30, 0x7, 0xf7, 0xfe, 0xf7, 0xfc, + 0x30, 0x3c, 0x30, 0x7c, 0x30, 0xec, 0x31, 0xcc, + 0x33, 0x8c, 0x37, 0xc, 0x3e, 0xc, 0x3c, 0xc, + 0x3f, 0xef, 0x1f, 0xef, 0x0, 0xc, 0x0, 0xc, + + /* U+F126 "" */ + 0x70, 0x3b, 0xe1, 0xfd, 0x86, 0xf6, 0x1b, 0x70, + 0x38, 0x80, 0x42, 0x1, 0x8, 0x1c, 0x3f, 0xe0, + 0x80, 0x2, 0x0, 0x8, 0x0, 0xf8, 0x3, 0x60, + 0xf, 0x80, 0x1c, 0x0, + + /* U+F127 "" */ + 0xc0, 0x0, 0x38, 0x0, 0x7, 0x1, 0xe0, 0xe0, + 0xfe, 0x1f, 0x81, 0x83, 0xf8, 0x30, 0x76, 0xc, + 0xce, 0xc3, 0x61, 0xf1, 0xf8, 0x3c, 0x6c, 0x27, + 0x33, 0x4, 0xf8, 0xc1, 0x9c, 0x18, 0x13, 0x87, + 0xe0, 0x70, 0x7c, 0xe, 0x0, 0x1, 0xc0, 0x0, + 0x30, + + /* U+F128 "" */ + 0x1e, 0x1f, 0xe6, 0x1b, 0x3, 0xc0, 0xf0, 0x30, + 0xc, 0x6, 0x7, 0x83, 0xc0, 0xc0, 0x30, 0x0, + 0x0, 0x0, 0xc0, 0x30, + + /* U+F129 "" */ + 0x31, 0xe3, 0x0, 0x3, 0xcf, 0xc, 0x30, 0xc3, + 0xc, 0x30, 0xcf, 0xff, + + /* U+F12A "" */ + 0xff, 0xff, 0xff, 0xf, + + /* U+F12B "" */ + 0x0, 0x6, 0x0, 0xe, 0xe0, 0xe6, 0xf1, 0xe6, + 0x31, 0x86, 0x1b, 0xf, 0x1f, 0xf, 0xe, 0x0, + 0xe, 0x0, 0x1f, 0x0, 0x1b, 0x0, 0x31, 0x80, + 0xf1, 0xe0, 0xe0, 0xe0, + + /* U+F12C "" */ + 0xe0, 0xe0, 0xf1, 0xe0, 0x31, 0x80, 0x1b, 0x0, + 0x1f, 0x0, 0xe, 0x0, 0xe, 0x0, 0x1f, 0x6, + 0x1b, 0xe, 0x31, 0x86, 0xf1, 0xe6, 0xe0, 0xe6, + 0x0, 0xf, 0x0, 0xf, + + /* U+F12D "" */ + 0x0, 0xe0, 0x0, 0xf8, 0x0, 0xfe, 0x0, 0xff, + 0x80, 0xff, 0xe0, 0xff, 0xf8, 0xe7, 0xfe, 0xe1, + 0xff, 0xe0, 0x7f, 0xe0, 0x1f, 0xb8, 0x7, 0x8e, + 0x3, 0x83, 0x83, 0x80, 0xff, 0xfc, 0x3f, 0xfe, + + /* U+F12E "" */ + 0x3, 0x0, 0x7, 0x80, 0x7, 0x80, 0x3, 0x0, + 0xff, 0xf0, 0xff, 0xf0, 0xff, 0xf0, 0xdf, 0xf6, + 0xf, 0xff, 0xf, 0xff, 0xdf, 0xf6, 0xff, 0xf0, + 0xfc, 0xf0, 0xf8, 0x70, 0xfc, 0xf0, 0xfc, 0xf0, + + /* U+F130 "" */ + 0xf, 0x1, 0xf8, 0x1f, 0x81, 0xf8, 0x1f, 0x8d, + 0xfb, 0xdf, 0xbd, 0xfb, 0xdf, 0xbc, 0xf7, 0x60, + 0x63, 0xc, 0x1f, 0x80, 0x60, 0x6, 0x1, 0xf8, + + /* U+F131 "" */ + 0x0, 0x0, 0x30, 0x0, 0x6, 0x1e, 0x0, 0xcf, + 0xc0, 0x1b, 0xf0, 0x3, 0xfc, 0x0, 0x7f, 0x0, + 0x4f, 0xd8, 0x19, 0xf6, 0x6, 0x3d, 0x81, 0x86, + 0x60, 0x30, 0xf0, 0xe, 0x18, 0x0, 0xf3, 0x0, + 0xc, 0x60, 0x3, 0xc, 0x3, 0xf1, 0x80, 0x0, + 0x30, + + /* U+F132 "" */ + 0x3, 0x80, 0x1f, 0xc1, 0xff, 0xf7, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, + 0xfc, 0xff, 0xf9, 0xff, 0xf1, 0xff, 0xc1, 0xff, + 0x3, 0xfe, 0x1, 0xf0, 0x1, 0xc0, + + /* U+F133 "" */ + 0x18, 0x60, 0x61, 0x87, 0xff, 0xbf, 0xff, 0xff, + 0xfc, 0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F134 "" */ + 0xe, 0x3, 0xe, 0x7f, 0x3f, 0xff, 0x6e, 0x7f, + 0xdf, 0x3, 0xbf, 0x80, 0x3f, 0x80, 0x3f, 0x80, + 0x3f, 0x80, 0x3f, 0x80, 0x3f, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x3f, 0x80, 0x3f, 0x80, 0x1f, 0x0, + + /* U+F135 "" */ + 0x0, 0x1e, 0x0, 0x7f, 0x0, 0xe7, 0x1, 0xe7, + 0x1f, 0xe7, 0x3f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfc, + 0x7f, 0xf8, 0x3, 0xf0, 0x1, 0xf0, 0x1, 0xf0, + 0x1, 0xf0, 0x1, 0xe0, 0x1, 0xc0, 0x0, 0x0, + + /* U+F137 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0x3e, 0xfe, 0x3f, 0xfc, 0xff, 0xf9, 0xff, + 0xf9, 0xff, 0xfc, 0xff, 0xfe, 0x3f, 0x7f, 0x3e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F138 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7c, 0xfe, 0xfc, 0x7f, 0xff, 0x3f, 0xff, 0x9f, + 0xff, 0x9f, 0xff, 0x3f, 0xfc, 0x7f, 0x7c, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F139 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xfe, 0x7f, 0xfc, 0x3f, 0xf9, 0x9f, + 0xf3, 0xcf, 0xf3, 0xcf, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F13A "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xf3, 0xcf, 0xf3, 0xcf, + 0xf9, 0x9f, 0xfc, 0x3f, 0xfe, 0x7f, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F13D "" */ + 0x3, 0xc0, 0x3, 0xf0, 0x1, 0x98, 0x0, 0xcc, + 0x0, 0x7e, 0x0, 0x1e, 0x0, 0x6, 0x0, 0x63, + 0xc, 0x79, 0x8f, 0x7e, 0xcf, 0xcc, 0x61, 0x86, + 0x31, 0xc1, 0x98, 0xc0, 0xed, 0xe0, 0x3f, 0xe0, + 0x7, 0xc0, + + /* U+F13E "" */ + 0xf, 0x1, 0xf8, 0x39, 0xc3, 0xc, 0x30, 0x3, + 0x0, 0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xe0, + + /* U+F140 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3c, 0x3c, 0x70, 0xe, + 0x67, 0xc6, 0xef, 0xe7, 0xdc, 0x73, 0xd9, 0xb3, + 0xd9, 0xb3, 0xd8, 0x33, 0xfc, 0x77, 0x6f, 0xe6, + 0x77, 0xce, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F141 "" */ + 0x63, 0x1b, 0xde, 0xf6, 0x31, 0x80, + + /* U+F142 "" */ + 0x6f, 0x60, 0x0, 0x6f, 0x60, 0x6, 0xf6, + + /* U+F143 "" */ + 0x7f, 0xfb, 0xff, 0xfe, 0x3f, 0xf8, 0x3f, 0xfc, + 0x7f, 0x9c, 0xfe, 0x1b, 0xfe, 0x67, 0xfc, 0xdf, + 0x9b, 0x7e, 0x6d, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F144 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7d, 0xfe, 0xfc, 0x7f, 0xfc, 0x3f, 0xfc, 0x1f, + 0xfc, 0x1f, 0xfc, 0x3f, 0xfc, 0x7f, 0x7d, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F145 "" */ + 0x7f, 0xff, 0xcf, 0xff, 0xfb, 0xc0, 0x7, 0xf8, + 0x0, 0xf7, 0x7f, 0x9c, 0x6f, 0xf3, 0xd, 0xfe, + 0x63, 0xbf, 0xce, 0xf7, 0xf9, 0xfe, 0x0, 0x3d, + 0xff, 0xff, 0x3f, 0xff, 0xe0, + + /* U+F146 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3, 0xfc, 0xf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F147 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3, 0xfc, 0xf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F148 "" */ + 0x6, 0x1, 0xe0, 0x7e, 0x1f, 0xe7, 0x6e, 0xcc, + 0xc1, 0x80, 0x30, 0x6, 0x0, 0xc0, 0x18, 0x3, + 0x0, 0x60, 0xc, 0x3f, 0x87, 0xe0, + + /* U+F149 "" */ + 0xfc, 0x1f, 0xc0, 0x18, 0x3, 0x0, 0x60, 0xc, + 0x1, 0x80, 0x30, 0x6, 0x0, 0xc1, 0x99, 0xbb, + 0x73, 0xfc, 0x3f, 0x3, 0xc0, 0x30, + + /* U+F14A "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x9f, 0xfe, 0xff, 0xf3, 0xf9, 0x9f, 0xe4, 0xff, + 0xc7, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F14B "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, + 0x1f, 0xf6, 0x7f, 0x8f, 0xfc, 0x1f, 0xe0, 0xff, + 0x87, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe0, + + /* U+F14C "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, + 0x3f, 0xf8, 0xff, 0xc3, 0xfe, 0x2f, 0xf1, 0xbf, + 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F14D "" */ + 0x0, 0x18, 0x0, 0xe, 0x0, 0x7, 0x8e, 0x3f, + 0xef, 0x7f, 0xfe, 0x7f, 0xff, 0x3f, 0xfd, 0x9e, + 0x3c, 0xce, 0x1c, 0x63, 0xc, 0x31, 0x80, 0x18, + 0x20, 0xc, 0x1, 0x86, 0x0, 0xc3, 0xff, 0xe0, + 0xff, 0xe0, + + /* U+F14E "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xce, 0xfe, 0xf, 0xfc, 0x1f, 0xf9, 0x9f, + 0xf9, 0x9f, 0xf8, 0x3f, 0xf0, 0x7f, 0x73, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F150 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x80, 0x7e, 0x1, 0xfc, 0xf, 0xf8, 0x7f, + 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F151 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xff, 0xe1, 0xff, 0x3, 0xf8, 0x7, 0xe0, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F152 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xf8, + 0xff, 0xe1, 0xff, 0x83, 0xfe, 0xf, 0xf8, 0x7f, + 0xe3, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F153 "" */ + 0x3, 0xe0, 0xfe, 0x1c, 0x3, 0x80, 0x30, 0xf, + 0xf8, 0xff, 0x8f, 0xf8, 0xff, 0x83, 0x0, 0x38, + 0x1, 0xc0, 0xf, 0xe0, 0x3e, + + /* U+F154 "" */ + 0xf, 0xf, 0xe3, 0x1d, 0x80, 0x60, 0x1c, 0xf, + 0xfb, 0xfe, 0x30, 0xc, 0x6, 0x3, 0x80, 0xff, + 0xff, 0xf0, + + /* U+F155 "" */ + 0x18, 0xc, 0x1f, 0xdf, 0xec, 0x6, 0x3, 0x80, + 0xfc, 0x3f, 0x1, 0xc0, 0x60, 0x37, 0xf3, 0xf0, + 0x60, 0x30, + + /* U+F156 "" */ + 0xfc, 0x3, 0xf8, 0xc, 0x70, 0x30, 0xc0, 0xc3, + 0x3, 0xc, 0xc, 0x77, 0xbf, 0x9e, 0xfc, 0x63, + 0x31, 0xec, 0xc3, 0xf1, 0x83, 0xc6, 0x7f, 0x19, + 0xe0, + + /* U+F157 "" */ + 0x0, 0x6, 0x6, 0x60, 0x63, 0xc, 0x39, 0xc1, + 0x98, 0xf, 0x0, 0xf0, 0x3f, 0xc3, 0xfc, 0x6, + 0x3, 0xfc, 0x3f, 0xc0, 0x60, 0x6, 0x0, + + /* U+F158 "" */ + 0x3f, 0xc3, 0xfe, 0x30, 0x73, 0x3, 0x30, 0x33, + 0x3, 0x30, 0x7f, 0xfe, 0xff, 0xc3, 0x0, 0xff, + 0xcf, 0xfc, 0x30, 0x3, 0x0, + + /* U+F159 "" */ + 0x0, 0x0, 0xc1, 0x83, 0xc1, 0x83, 0xe3, 0xc7, + 0x63, 0xc6, 0x63, 0xc6, 0x73, 0xce, 0xff, 0xff, + 0xff, 0xff, 0x36, 0x6c, 0x1e, 0x78, 0x1e, 0x78, + 0x1c, 0x38, 0xc, 0x30, 0xc, 0x30, + + /* U+F15B "" */ + 0x3e, 0x7, 0xfc, 0x3f, 0xf1, 0xfd, 0xcf, 0xe7, + 0x7f, 0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xcf, 0xfe, + + /* U+F15C "" */ + 0x3e, 0xf, 0xf8, 0xff, 0xcf, 0xee, 0xfe, 0x7f, + 0xe3, 0xff, 0xff, 0xff, 0xe0, 0x7e, 0x7, 0xff, + 0xfe, 0x7, 0xe0, 0x7f, 0xff, 0xff, 0xf7, 0xfe, + + /* U+F15D "" */ + 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x18, 0x3c, + 0x18, 0x7e, 0x18, 0x7e, 0x18, 0x42, 0x18, 0x0, + 0x18, 0x7e, 0x5a, 0x7e, 0x7e, 0x1c, 0x7e, 0x38, + 0x3c, 0x7e, 0x18, 0x7e, + + /* U+F15E "" */ + 0x0, 0x0, 0x18, 0x18, 0x3c, 0x3c, 0x7e, 0x3c, + 0x5a, 0x7e, 0x18, 0x7e, 0x18, 0x42, 0x18, 0x0, + 0x18, 0x7e, 0x18, 0x7e, 0x18, 0x1c, 0x18, 0x38, + 0x18, 0x7e, 0x18, 0x7e, + + /* U+F160 "" */ + 0x18, 0x7f, 0xc6, 0x1f, 0xf1, 0x80, 0x0, 0x60, + 0x0, 0x18, 0x7f, 0x6, 0x1f, 0xc1, 0x80, 0x0, + 0x60, 0x0, 0x18, 0x7c, 0x36, 0xdf, 0xf, 0xf0, + 0x1, 0xf8, 0x0, 0x3c, 0x70, 0x6, 0x1c, 0x0, + + /* U+F161 "" */ + 0x18, 0x7f, 0xcf, 0x1f, 0xf7, 0xe0, 0x3, 0xfc, + 0x0, 0xdb, 0x7f, 0x6, 0x1f, 0xc1, 0x80, 0x0, + 0x60, 0x0, 0x18, 0x7c, 0x6, 0x1f, 0x1, 0x80, + 0x0, 0x60, 0x0, 0x18, 0x70, 0x6, 0x1c, 0x0, + + /* U+F162 "" */ + 0x18, 0x18, 0x30, 0x78, 0x60, 0x70, 0xc0, 0xe1, + 0x83, 0xe3, 0x7, 0xc6, 0x0, 0xc, 0xe, 0x18, + 0x3f, 0xb6, 0x6f, 0xfc, 0xfb, 0xf0, 0xe3, 0xc1, + 0xc3, 0x3, 0x0, + + /* U+F163 "" */ + 0x18, 0x18, 0x78, 0x79, 0xf8, 0x77, 0xf8, 0xed, + 0xb3, 0xe3, 0x7, 0xc6, 0x0, 0xc, 0xe, 0x18, + 0x3e, 0x30, 0x6c, 0x60, 0xf8, 0xc0, 0xe1, 0x81, + 0xc3, 0x3, 0x0, + + /* U+F164 "" */ + 0x0, 0x0, 0x1, 0xc0, 0x1, 0xc0, 0x3, 0xc0, + 0x3, 0x80, 0xf7, 0xff, 0xf7, 0xff, 0xf7, 0xff, + 0xf7, 0xff, 0xf7, 0xff, 0xf7, 0xfe, 0xf7, 0xfe, + 0xf7, 0xfe, 0xf3, 0xfc, 0xf1, 0xfc, + + /* U+F165 "" */ + 0x1, 0xfc, 0x3, 0xfc, 0xf7, 0xfe, 0xf7, 0xfe, + 0xf7, 0xfe, 0xf7, 0xff, 0xf7, 0xff, 0xf7, 0xff, + 0xf7, 0xff, 0xf7, 0xff, 0xf3, 0x80, 0xf3, 0xc0, + 0x1, 0xc0, 0x1, 0xc0, 0x0, 0x0, + + /* U+F175 "" */ + 0xc, 0x3, 0x0, 0xc0, 0x30, 0xc, 0x3, 0x0, + 0xc0, 0x30, 0xc, 0x3, 0x0, 0xc0, 0x30, 0xcc, + 0xfb, 0x77, 0xf8, 0xfc, 0x1e, 0x3, 0x0, + + /* U+F176 "" */ + 0xc, 0x7, 0x83, 0xf1, 0xfe, 0xed, 0xf3, 0x30, + 0xc0, 0x30, 0xc, 0x3, 0x0, 0xc0, 0x30, 0xc, + 0x3, 0x0, 0xc0, 0x30, 0xc, 0x3, 0x0, + + /* U+F177 "" */ + 0xc, 0x0, 0x7, 0x0, 0x3, 0x80, 0x1, 0xc0, + 0x0, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x0, 0x0, + 0xe0, 0x0, 0x1c, 0x0, 0x3, 0x0, 0x0, + + /* U+F178 "" */ + 0x0, 0xc, 0x0, 0x3, 0x80, 0x0, 0x70, 0x0, + 0xe, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x0, 0x38, + 0x0, 0x1c, 0x0, 0xe, 0x0, 0x3, 0x0, + + /* U+F182 "" */ + 0xe, 0x1, 0xc0, 0x38, 0x0, 0x0, 0x0, 0x3e, + 0xf, 0xe3, 0xfe, 0x7f, 0xdb, 0xef, 0xff, 0x9f, + 0xc3, 0xf8, 0x36, 0x6, 0xc0, 0xd8, 0x1b, 0x3, + 0x60, + + /* U+F183 "" */ + 0xe, 0x1, 0xc0, 0x38, 0x0, 0x0, 0x0, 0x1c, + 0xf, 0xe1, 0xfc, 0x7f, 0xdf, 0xff, 0x7d, 0x8f, + 0x81, 0xb0, 0x36, 0x6, 0xc0, 0xd8, 0x1b, 0x3, + 0x60, 0x0, 0x0, + + /* U+F185 "" */ + 0x4, 0x10, 0x7, 0xbc, 0x3, 0xfe, 0x1, 0xff, + 0x7, 0xff, 0xf7, 0xe0, 0xfd, 0xe7, 0x3c, 0xf7, + 0xde, 0x3b, 0xee, 0x3d, 0xf7, 0x9e, 0x73, 0xdf, + 0x83, 0xf7, 0xff, 0xf0, 0x7f, 0xc0, 0x3f, 0xe0, + 0x1e, 0xf0, 0x4, 0x10, 0x0, + + /* U+F186 "" */ + 0xf, 0xe0, 0x7f, 0x3, 0xf0, 0x1f, 0xc0, 0x7e, + 0x3, 0xf8, 0xf, 0xe0, 0x3f, 0x80, 0xfe, 0x3, + 0xfc, 0x7, 0xf8, 0x1f, 0xf0, 0x3f, 0xfc, 0x7f, + 0xe0, 0x7e, 0x0, + + /* U+F187 "" */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xfe, 0x7f, 0xfe, 0x78, 0x1e, + 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, + + /* U+F188 "" */ + 0x1, 0xe0, 0x0, 0xfc, 0x0, 0x3f, 0x1, 0x8f, + 0xc6, 0x70, 0x3, 0x8f, 0x7b, 0xc0, 0xff, 0xc0, + 0x3f, 0xf0, 0xff, 0x3f, 0xff, 0xcf, 0xf0, 0xf3, + 0xc0, 0x3c, 0xf0, 0x3f, 0x3f, 0x1d, 0xce, 0xe6, + 0x73, 0x98, 0x4, 0x80, + + /* U+F18E "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0x7e, 0xff, 0x3f, 0xf0, 0x1f, 0xf0, 0xf, + 0xf0, 0xf, 0xf0, 0x1f, 0xff, 0x3f, 0x7f, 0x7e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F190 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0xfe, 0xfc, 0xff, 0xf8, 0xf, 0xf0, 0xf, + 0xf0, 0xf, 0xf8, 0xf, 0xfc, 0xff, 0x7e, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F191 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xfc, + 0x7f, 0xe1, 0xff, 0x7, 0xfc, 0x1f, 0xf8, 0x7f, + 0xf1, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F192 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xfc, 0x3f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1f, 0xfc, 0x3f, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F193 "" */ + 0x6, 0x0, 0xf, 0x0, 0x6, 0x0, 0x0, 0x0, + 0x3, 0x0, 0x7, 0x80, 0x13, 0x80, 0x73, 0x80, + 0x63, 0x80, 0xc3, 0xf8, 0xc1, 0xfc, 0xc0, 0xc, + 0xc0, 0xcc, 0x61, 0xcf, 0x7f, 0x86, 0x1e, 0x0, + + /* U+F195 "" */ + 0xf, 0x83, 0xf8, 0xe3, 0x18, 0x3, 0x0, 0xff, + 0x9f, 0xf0, 0xc0, 0x7f, 0xcf, 0xf8, 0xc0, 0x38, + 0x7, 0xfe, 0xff, 0xc0, + + /* U+F196 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3, 0xfc, 0xf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F197 "" */ + 0xfc, 0x0, 0x6f, 0x80, 0x36, 0x60, 0x1b, 0x1c, + 0xf, 0x80, 0x7, 0xff, 0xe3, 0xff, 0xfd, 0xff, + 0xf7, 0xff, 0xff, 0x7f, 0xff, 0x3e, 0x0, 0x1b, + 0x18, 0xd, 0xb0, 0x7, 0xe0, 0x0, + + /* U+F199 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0x1f, 0xc0, 0xfe, 0xcd, 0xf8, 0xc7, 0xe0, 0x1f, + 0x80, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F19C "" */ + 0x1, 0x80, 0x7, 0xe0, 0x3e, 0x7c, 0xfe, 0x7f, + 0xff, 0xff, 0x36, 0x6c, 0x36, 0x6c, 0x36, 0x6c, + 0x36, 0x6c, 0x36, 0x6c, 0x36, 0x6c, 0x36, 0x6c, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, + + /* U+F19D "" */ + 0x1, 0xe0, 0x0, 0xff, 0x80, 0xff, 0xfc, 0x7f, + 0xff, 0xef, 0xff, 0xfd, 0xbf, 0xfe, 0x31, 0xff, + 0x7, 0x7, 0x0, 0xd8, 0x6, 0x1b, 0xc3, 0xc3, + 0x7f, 0xf8, 0x6f, 0xff, 0xc, 0xff, 0xc1, 0x87, + 0xe0, 0x0, + + /* U+F1AB "" */ + 0xc, 0x0, 0x3, 0x0, 0xf, 0xfe, 0x3, 0xff, + 0x80, 0x1, 0xc0, 0x4, 0x63, 0x3, 0xb8, 0xc0, + 0x7c, 0x78, 0xe, 0x1e, 0x7, 0xef, 0xc7, 0x9f, + 0x31, 0x81, 0xce, 0x0, 0x7f, 0x80, 0x1f, 0xe0, + 0xc, 0xc, 0x3, 0x3, + + /* U+F1AC "" */ + 0x3, 0xf8, 0x7, 0xfc, 0x6, 0x6, 0x6, 0x6, + 0xf6, 0x6, 0xf0, 0x0, 0xf7, 0xff, 0xf7, 0xff, + 0xf6, 0x4b, 0xf6, 0x4b, 0xf7, 0xff, 0xf6, 0x4b, + 0xf6, 0x4b, 0xf7, 0xff, 0xf7, 0xff, + + /* U+F1AD "" */ + 0x7f, 0xef, 0xff, 0xff, 0xfe, 0x67, 0xe6, 0x7f, + 0xff, 0xff, 0xfe, 0x67, 0xe6, 0x7f, 0xff, 0xff, + 0xff, 0x9f, 0xf9, 0xff, 0x9f, 0xf9, 0xf7, 0xfe, + + /* U+F1AE "" */ + 0xe, 0x1, 0xc0, 0x38, 0x7, 0x0, 0x0, 0x1e, + 0xf, 0xe1, 0xfc, 0x7f, 0xcf, 0xf9, 0x7d, 0xf, + 0x81, 0xb0, 0x36, 0x6, 0xc0, 0xd8, + + /* U+F1B0 "" */ + 0xc, 0x30, 0xe, 0x70, 0xe, 0x70, 0xe, 0x70, + 0xe6, 0x67, 0xe0, 0x7, 0xe3, 0xc7, 0x67, 0xe6, + 0xf, 0xf0, 0x1f, 0xf8, 0x1f, 0xf8, 0x3f, 0xfc, + 0x3f, 0xfc, 0x3c, 0x3c, + + /* U+F1B1 "" */ + 0x0, 0x1e, 0x0, 0x7f, 0x0, 0xff, 0x1, 0xff, + 0x1, 0xff, 0x1, 0xfe, 0x1, 0xfe, 0x1, 0xfc, + 0x3, 0xf8, 0x7, 0x0, 0xe, 0x0, 0x1c, 0x0, + 0x38, 0x0, 0x70, 0x0, 0xe0, 0x0, 0xc0, 0x0, + + /* U+F1B2 "" */ + 0x3, 0x80, 0xf, 0x80, 0x7f, 0xc3, 0xff, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, + 0x87, 0xfe, 0xf, 0xfc, 0x1f, 0xf8, 0x3f, 0xf1, + 0xef, 0xe7, 0x87, 0xfc, 0x7, 0xe0, 0x3, 0x80, + + /* U+F1B3 "" */ + 0x3, 0x80, 0xf, 0xe0, 0x1f, 0xf0, 0x1f, 0xb0, + 0x1f, 0x30, 0x1f, 0x30, 0x1f, 0x30, 0x7f, 0xfc, + 0xff, 0xff, 0xfd, 0xfb, 0xf9, 0xf3, 0xf1, 0xe3, + 0xf1, 0xe3, 0xf7, 0xef, 0x3e, 0x7c, 0x8, 0x10, + + /* U+F1B8 "" */ + 0x0, 0x0, 0x0, 0xfc, 0x0, 0x33, 0x0, 0x8, + 0x68, 0x0, 0x1e, 0xf, 0x7, 0x87, 0xc1, 0xe0, + 0xf0, 0x0, 0x34, 0x0, 0x1c, 0x0, 0x66, 0x0, + 0x19, 0x80, 0x86, 0x60, 0x61, 0x8f, 0x3f, 0xc1, + 0xcf, 0xe0, 0x1, 0x80, 0x0, 0x20, 0x0, + + /* U+F1B9 "" */ + 0xf, 0xf0, 0x1f, 0xf8, 0x30, 0xc, 0x30, 0xc, + 0x70, 0xe, 0x7f, 0xfe, 0xff, 0xff, 0xcf, 0xf3, + 0xcf, 0xf3, 0xcf, 0xf3, 0xff, 0xff, 0xff, 0xff, + 0xe0, 0x7, 0xe0, 0x7, + + /* U+F1BA "" */ + 0x7, 0xe0, 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, + 0x30, 0xc, 0x70, 0xe, 0x7f, 0xfe, 0xff, 0xff, + 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, 0xf3, 0xff, 0xff, + 0xff, 0xff, 0xe0, 0x7, 0xe0, 0x7, + + /* U+F1BB "" */ + 0x3, 0x0, 0x1e, 0x0, 0x78, 0x3, 0xf0, 0x1f, + 0xe0, 0xff, 0xc1, 0xfe, 0x7, 0xf8, 0x3f, 0xf1, + 0xff, 0xe3, 0xff, 0xf, 0xfc, 0x7f, 0xfb, 0xff, + 0xff, 0xff, 0xc0, 0xc0, 0x3, 0x0, 0xc, 0x0, + + /* U+F1C0 "" */ + 0x1f, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0x7f, 0x88, 0x0, 0x78, 0x7, 0xff, 0xfd, + 0xff, 0xe1, 0xfe, 0x20, 0x1, 0xe0, 0x1f, 0xff, + 0xf7, 0xff, 0x87, 0xf8, + + /* U+F1C1 "" */ + 0x3e, 0x0, 0x7f, 0xc0, 0x3f, 0xf0, 0x1f, 0xdc, + 0xf, 0xe7, 0x7, 0xf1, 0x83, 0xff, 0xe1, 0xff, + 0xf0, 0xff, 0xf8, 0x7f, 0xfc, 0x3f, 0xfe, 0x1f, + 0x80, 0xf, 0xbb, 0xbf, 0xdb, 0x53, 0xef, 0xae, + 0xf7, 0x54, 0x3, 0x3a, 0x0, + + /* U+F1C2 "" */ + 0x7f, 0x7, 0xfc, 0x3f, 0xb1, 0xfc, 0xcf, 0xe3, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xe5, 0x3f, 0x49, + 0xf8, 0x1f, 0xe4, 0xff, 0x27, 0xf9, 0x3f, 0xff, + 0xcf, 0xfe, + + /* U+F1C3 "" */ + 0x3e, 0x7, 0xfc, 0x3f, 0xf1, 0xfd, 0xcf, 0xe7, + 0x7f, 0x1b, 0xff, 0xff, 0xff, 0xf7, 0xff, 0x93, + 0xfe, 0x3f, 0xf1, 0xff, 0x8f, 0xf9, 0x3f, 0xff, + 0xcf, 0xfe, + + /* U+F1C4 "" */ + 0x3e, 0xf, 0xf8, 0xff, 0xcf, 0xee, 0xfe, 0x7f, + 0xe7, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xf, 0xf6, + 0xff, 0xf, 0xf7, 0xff, 0x7f, 0xff, 0xf7, 0xfe, + + /* U+F1C5 "" */ + 0x7f, 0xf, 0xf8, 0xff, 0xcf, 0xee, 0xfe, 0x7f, + 0xff, 0xff, 0xfc, 0xff, 0xcf, 0xff, 0x9f, 0xf0, + 0xfe, 0x7, 0xc0, 0x3c, 0x3, 0xff, 0xf7, 0xfe, + + /* U+F1C6 "" */ + 0x7f, 0x6, 0x3c, 0x31, 0xb1, 0xfc, 0xcc, 0x67, + 0x63, 0x1f, 0xff, 0xf8, 0xff, 0xc7, 0xfe, 0xbf, + 0xf1, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xcf, 0xfe, + + /* U+F1C7 "" */ + 0x7f, 0xf, 0xf8, 0xff, 0xcf, 0xee, 0xfe, 0x7f, + 0xe3, 0xff, 0xff, 0xff, 0xf7, 0x7c, 0x73, 0xc6, + 0xbc, 0x6b, 0xc7, 0x3f, 0x77, 0xff, 0xf7, 0xfe, + + /* U+F1C8 "" */ + 0x3e, 0xf, 0xf8, 0xff, 0xcf, 0xee, 0xfe, 0x7f, + 0xe7, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xe0, + 0x7e, 0x7, 0xe0, 0x7e, 0x1f, 0xff, 0xf7, 0xfe, + + /* U+F1C9 "" */ + 0x7f, 0xf, 0xf8, 0xff, 0xcf, 0xee, 0xfe, 0x7f, + 0xe3, 0xff, 0xff, 0xff, 0xf6, 0xfe, 0x67, 0xcf, + 0x3e, 0x67, 0xf6, 0xff, 0xff, 0xff, 0xf7, 0xfe, + + /* U+F1CD "" */ + 0x7, 0xe0, 0x1f, 0xf0, 0x78, 0x3c, 0x30, 0x1c, + 0x78, 0x3e, 0xff, 0xf7, 0xcf, 0xe3, 0xcc, 0x63, + 0xcc, 0x63, 0xcc, 0x63, 0xcf, 0xe3, 0x7f, 0xf6, + 0x78, 0x3e, 0x78, 0x3c, 0x3f, 0xf0, 0x7, 0xe0, + + /* U+F1CE "" */ + 0x6, 0x60, 0x1e, 0x78, 0x3c, 0x3c, 0x70, 0xe, + 0x60, 0x6, 0xe0, 0x7, 0xc0, 0x3, 0xc0, 0x3, + 0xc0, 0x3, 0xc0, 0x3, 0xe0, 0x7, 0x60, 0x6, + 0x70, 0xe, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F1D8 "" */ + 0x0, 0x0, 0xc0, 0x1, 0xf0, 0x3, 0xf8, 0x3, + 0xfe, 0x7, 0xff, 0x8f, 0xff, 0xc7, 0xff, 0xf1, + 0xff, 0x7c, 0x1f, 0xbe, 0x1, 0x8f, 0x80, 0x7, + 0xe0, 0x3, 0xf0, 0x0, 0xfc, 0x0, 0x1e, 0x0, + 0x7, 0x80, 0x0, 0xe0, 0x0, 0x30, 0x0, 0x0, + 0x0, + + /* U+F1D9 "" */ + 0x0, 0x0, 0xc0, 0x1, 0xf0, 0x3, 0xf8, 0x3, + 0xfe, 0x7, 0xff, 0x8f, 0xff, 0xc7, 0xff, 0xf1, + 0xff, 0x7c, 0x1f, 0xbe, 0x1, 0x8f, 0x80, 0x7, + 0xe0, 0x3, 0xf0, 0x0, 0xfc, 0x0, 0x1e, 0x0, + 0x7, 0x80, 0x0, 0xe0, 0x0, 0x30, 0x0, 0x0, + 0x0, + + /* U+F1DA "" */ + 0x3, 0xe0, 0xf, 0xf8, 0xdc, 0x1c, 0xf8, 0xe, + 0xf1, 0x86, 0xf9, 0x87, 0xf9, 0x83, 0x1, 0x83, + 0x1, 0xc3, 0x0, 0xe3, 0x0, 0x67, 0x60, 0x6, + 0x70, 0xe, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F1DB "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F1DC "" */ + 0x78, 0x3c, 0xf0, 0x78, 0xc0, 0x61, 0x80, 0xc3, + 0x1, 0x86, 0x3, 0xf, 0xfe, 0x1f, 0xfc, 0x30, + 0x18, 0x60, 0x30, 0xc0, 0x61, 0x80, 0xc7, 0x83, + 0xcf, 0x7, 0x80, + + /* U+F1DD "" */ + 0x1f, 0xfc, 0x7f, 0xf9, 0xfe, 0x63, 0xfc, 0xc7, + 0xf9, 0x8f, 0xf3, 0x1f, 0xe6, 0x3f, 0xcc, 0x3f, + 0x98, 0x3f, 0x30, 0x6, 0x60, 0xc, 0xc0, 0x19, + 0x80, 0x33, 0x0, 0x66, 0x0, 0xcc, + + /* U+F1DE "" */ + 0x0, 0x0, 0xf, 0x0, 0xff, 0xff, 0xff, 0xff, + 0xf, 0x0, 0x0, 0x0, 0x0, 0x78, 0xff, 0xff, + 0xff, 0xff, 0x0, 0x78, 0x0, 0x0, 0x1e, 0x0, + 0xff, 0xff, 0xff, 0xff, 0x1e, 0x0, 0x0, 0x0, + + /* U+F1E0 "" */ + 0x0, 0x3c, 0x0, 0xfc, 0x1, 0xf8, 0x3, 0xf0, + 0x1f, 0xef, 0xff, 0xbf, 0xc0, 0x7e, 0x0, 0xfc, + 0x1, 0xfe, 0x1, 0xff, 0xf0, 0xf, 0xf0, 0x7, + 0xe0, 0xf, 0xc0, 0x1f, 0x80, 0x1e, + + /* U+F1E1 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xfb, 0xff, 0xc7, 0xff, + 0x1f, 0x90, 0xfc, 0x1f, 0xf0, 0x7f, 0xe4, 0x3f, + 0xfc, 0x7f, 0xf1, 0xff, 0xef, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F1E2 "" */ + 0x0, 0x0, 0x0, 0x3, 0x0, 0x3, 0xc0, 0x1, + 0xe0, 0xfe, 0x61, 0xff, 0x81, 0xc7, 0xe0, 0xcf, + 0xf0, 0xcf, 0xf8, 0x6f, 0xfc, 0x37, 0xfe, 0x1f, + 0xff, 0xf, 0xff, 0x83, 0xff, 0x81, 0xff, 0xc0, + 0x7f, 0xc0, 0xf, 0x80, 0x0, + + /* U+F1E3 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x71, 0x8e, + 0x60, 0x6, 0xf0, 0xf, 0xf1, 0x8f, 0xe3, 0xc7, + 0xc3, 0xc3, 0xc3, 0xc3, 0xe0, 0x7, 0x7c, 0x3e, + 0x7c, 0x3e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F1E4 "" */ + 0x0, 0x0, 0xf, 0xfe, 0x1f, 0xff, 0xcf, 0x83, + 0xef, 0xc0, 0xff, 0xe0, 0xfd, 0xc0, 0x1c, 0x0, + 0x0, 0x0, 0x0, 0x32, 0x49, 0x99, 0x24, 0xc0, + 0x0, 0x6, 0x7f, 0x33, 0x3f, 0x98, + + /* U+F1E5 "" */ + 0x1c, 0x38, 0x1c, 0x38, 0x0, 0x0, 0x3d, 0xbc, + 0x3d, 0xbc, 0x3d, 0xbc, 0x7d, 0xbc, 0x7d, 0xbe, + 0x7c, 0x3e, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, + 0xfc, 0x3f, 0xfc, 0x3f, + + /* U+F1E6 "" */ + 0x18, 0x60, 0x61, 0x81, 0x86, 0x6, 0x18, 0xff, + 0xff, 0xff, 0xf7, 0xff, 0x9f, 0xfe, 0x7f, 0xf9, + 0xff, 0xe3, 0xff, 0xf, 0xfc, 0x1f, 0xe0, 0x1e, + 0x0, 0x30, 0x0, 0xc0, 0x3, 0x0, + + /* U+F1EA "" */ + 0xf, 0xfe, 0x1f, 0xff, 0x18, 0x7f, 0xd8, 0x7f, + 0xd8, 0x7f, 0xd8, 0x63, 0xdf, 0xff, 0xd8, 0x3, + 0xd8, 0x3, 0xdf, 0xff, 0xd8, 0x3, 0xd8, 0x3, + 0xdf, 0xff, 0x7f, 0xfe, + + /* U+F1EB "" */ + 0x3, 0xf0, 0x7, 0xff, 0x83, 0xc0, 0xf1, 0xc0, + 0xe, 0xe0, 0x1, 0xf0, 0x0, 0x20, 0x3f, 0x0, + 0x1f, 0xe0, 0xe, 0x1c, 0x2, 0x1, 0x0, 0x0, + 0x0, 0x3, 0x0, 0x0, 0xc0, 0x0, 0x30, 0x0, + + /* U+F1EC "" */ + 0x7f, 0xef, 0xff, 0xc0, 0x3c, 0x3, 0xc0, 0x3f, + 0xff, 0xd9, 0xbd, 0x9b, 0xff, 0xfd, 0x9b, 0xd9, + 0xbf, 0xff, 0xc1, 0xbc, 0x1b, 0xff, 0xf7, 0xfe, + + /* U+F1F6 "" */ + 0xc0, 0x0, 0x38, 0x30, 0x7, 0x1e, 0x0, 0xff, + 0xe0, 0x1f, 0xf8, 0x3, 0xff, 0x0, 0x7f, 0xc0, + 0xf, 0xf0, 0x9, 0xfc, 0x3, 0x3f, 0x1, 0xe7, + 0xe0, 0x7c, 0xf8, 0x3f, 0x9f, 0xf, 0xf3, 0xc0, + 0x0, 0x70, 0x0, 0xe, 0x1, 0xe1, 0xc0, 0x0, + 0x30, + + /* U+F1F7 "" */ + 0xc0, 0x0, 0x38, 0x30, 0x7, 0x1e, 0x0, 0xff, + 0xe0, 0x1f, 0xf8, 0x3, 0xff, 0x0, 0x7f, 0xc0, + 0xf, 0xf0, 0x9, 0xfc, 0x3, 0x3f, 0x1, 0xe7, + 0xe0, 0x7c, 0xf8, 0x3f, 0x9f, 0xf, 0xf3, 0xc0, + 0x0, 0x70, 0x0, 0xe, 0x1, 0xe1, 0xc0, 0x0, + 0x30, + + /* U+F1F8 "" */ + 0x7, 0x80, 0x3f, 0xf, 0xff, 0xff, 0xff, 0x0, + 0x0, 0x0, 0x7, 0xff, 0x9f, 0xfe, 0x7f, 0xf9, + 0xff, 0xe7, 0xff, 0x9f, 0xfe, 0x7f, 0xf9, 0xff, + 0xe7, 0xff, 0x9f, 0xfe, 0x3f, 0xf0, + + /* U+F1F9 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xf8, 0x1f, 0xf9, 0x9f, 0xf3, 0xff, + 0xf3, 0xff, 0xfb, 0xdf, 0xf9, 0x1f, 0x7e, 0x7e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F1FA "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3c, 0x3c, 0x70, 0xe, + 0x63, 0xc6, 0xe7, 0xf7, 0xce, 0x73, 0xcc, 0x33, + 0xcc, 0x33, 0xce, 0x73, 0xe7, 0xff, 0x63, 0xde, + 0x70, 0x0, 0x3c, 0x0, 0x1f, 0x80, 0x7, 0x80, + + /* U+F1FB "" */ + 0x0, 0x1e, 0x0, 0x3f, 0x0, 0x7f, 0x2, 0xff, + 0x7, 0xff, 0x3, 0xfe, 0x1, 0xfc, 0xc, 0xf8, + 0x1c, 0x70, 0x38, 0x38, 0x71, 0x90, 0x63, 0x80, + 0x67, 0x0, 0x7e, 0x0, 0xfc, 0x0, 0xc0, 0x0, + + /* U+F1FC "" */ + 0x0, 0x1, 0x80, 0x1, 0xf0, 0x0, 0xfc, 0x0, + 0xfe, 0x0, 0x7f, 0x80, 0x7f, 0xc0, 0x1f, 0xe0, + 0x7, 0xf8, 0x0, 0xfc, 0x3, 0x9e, 0x1, 0xf3, + 0x80, 0xfe, 0x80, 0x3f, 0x80, 0xf, 0xe0, 0xf, + 0xf0, 0x3, 0xf8, 0x0, + + /* U+F1FD "" */ + 0x33, 0x30, 0xcc, 0xc3, 0x33, 0x0, 0x0, 0x33, + 0x30, 0xcc, 0xc3, 0x33, 0x1f, 0xfe, 0xff, 0xff, + 0xff, 0xf3, 0x87, 0x0, 0x0, 0xc7, 0x8f, 0xff, + 0xff, 0xff, 0xdf, 0xfe, + + /* U+F1FE "" */ + 0xc0, 0x0, 0xc0, 0x0, 0xc1, 0x0, 0xc3, 0x98, + 0xc7, 0xfc, 0xcf, 0xfe, 0xcf, 0xfe, 0xcf, 0xfe, + 0xcf, 0xfe, 0xcf, 0xfe, 0xc0, 0x0, 0xc0, 0x0, + 0xff, 0xff, 0x7f, 0xff, + + /* U+F200 "" */ + 0x0, 0x60, 0x3, 0x7c, 0x7, 0xbf, 0x7, 0xdf, + 0xc7, 0xef, 0xe3, 0xf7, 0xfb, 0xfb, 0xfd, 0xfc, + 0xfc, 0xfe, 0x0, 0x7f, 0x8f, 0xff, 0xe7, 0xef, + 0xf3, 0xe7, 0xfc, 0xf1, 0xff, 0x30, 0x7f, 0xc0, + 0xf, 0x80, + + /* U+F201 "" */ + 0xc0, 0x0, 0xc0, 0x0, 0xc0, 0x6, 0xc1, 0xe, + 0xc3, 0x9c, 0xc7, 0xf8, 0xce, 0xf0, 0xdc, 0x60, + 0xc8, 0x0, 0xc0, 0x0, 0xc0, 0x0, 0xc0, 0x0, + 0xff, 0xff, 0x7f, 0xff, + + /* U+F204 "" */ + 0xf, 0xfc, 0xf, 0xff, 0xc7, 0x0, 0x39, 0x80, + 0x6, 0xce, 0x0, 0xf7, 0xc0, 0x3d, 0xf0, 0xf, + 0x7c, 0x3, 0x6e, 0x1, 0x9c, 0x0, 0xe3, 0xff, + 0xf0, 0x3f, 0xf0, + + /* U+F205 "" */ + 0xf, 0xfc, 0xf, 0xff, 0xc7, 0xff, 0xf9, 0xff, + 0xe, 0xff, 0x81, 0xff, 0xe0, 0x7f, 0xf8, 0x1f, + 0xfe, 0x7, 0x7f, 0xc3, 0x9f, 0xff, 0xe3, 0xff, + 0xf0, 0x3f, 0xf0, + + /* U+F206 "" */ + 0x0, 0x3e, 0x0, 0x6, 0x0, 0x38, 0xc0, 0x7, + 0xc, 0x0, 0x3f, 0x80, 0xe, 0x78, 0xf, 0xcd, + 0xe3, 0xef, 0x7e, 0xec, 0xde, 0xf9, 0xfb, 0x6f, + 0xc, 0x6d, 0xf3, 0x8e, 0x77, 0xe0, 0xfc, 0x78, + 0xf, 0x0, + + /* U+F207 "" */ + 0x7f, 0xfb, 0xe1, 0xff, 0x87, 0xff, 0xff, 0xc3, + 0xf, 0xc, 0x3c, 0x30, 0xf0, 0xc3, 0xff, 0xff, + 0xff, 0xfc, 0xfc, 0xf3, 0xf3, 0xff, 0xfd, 0xff, + 0xe7, 0x3, 0x8c, 0xc, + + /* U+F20A "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x8f, + 0xe0, 0x87, 0xec, 0xb7, 0xef, 0xbf, 0xec, 0xb7, + 0xf1, 0x87, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F20B "" */ + 0xff, 0xf, 0xfe, 0x3c, 0x1c, 0xf0, 0x33, 0xcc, + 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, 0xcc, 0xcf, + 0x33, 0x3c, 0xc0, 0xf3, 0x7, 0xcf, 0xfb, 0x3f, + 0xc0, + + /* U+F217 "" */ + 0xf0, 0x0, 0xf, 0xff, 0xf1, 0xfd, 0xfc, 0x7f, + 0x7f, 0x1f, 0x7, 0x87, 0xc1, 0xe1, 0xfd, 0xf8, + 0x3f, 0x7e, 0xf, 0xff, 0x2, 0x0, 0x0, 0xc0, + 0x0, 0x1f, 0xf8, 0x0, 0x0, 0x1, 0xc3, 0x80, + 0x70, 0xe0, 0x1c, 0x38, + + /* U+F218 "" */ + 0xf0, 0x0, 0xf, 0xf7, 0xf1, 0xfd, 0xfc, 0x7d, + 0x5f, 0x1f, 0x7, 0x87, 0xe3, 0xe1, 0xfd, 0xf8, + 0x3f, 0xfe, 0xf, 0xff, 0x2, 0x0, 0x0, 0xc0, + 0x0, 0x1f, 0xf8, 0x0, 0x0, 0x1, 0xc3, 0x80, + 0x70, 0xe0, 0x1c, 0x38, + + /* U+F219 "" */ + 0x1, 0x80, 0x3, 0xc0, 0x7, 0xe0, 0xf, 0xf0, + 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, + 0xf, 0xf0, 0x7, 0xe0, 0x3, 0xc0, 0x1, 0x80, + + /* U+F21A "" */ + 0x1, 0xf8, 0x0, 0x1f, 0x80, 0x7, 0xfe, 0x0, + 0xff, 0xf0, 0xc, 0x3, 0x0, 0xc0, 0x30, 0xd, + 0x9b, 0x0, 0xf9, 0xf0, 0x1f, 0x9f, 0x81, 0xf9, + 0xf8, 0x1f, 0x9f, 0x80, 0xf9, 0xf0, 0x0, 0x0, + 0x0, 0x60, 0x60, 0xf, 0x9f, 0xf, 0x8f, 0x1f, + 0x0, 0x0, 0x0, + + /* U+F21B "" */ + 0xf, 0x80, 0xfe, 0x7, 0xf0, 0xff, 0xe3, 0xfe, + 0x1f, 0xf0, 0x77, 0x7, 0x1c, 0x7f, 0xf3, 0xc7, + 0x8c, 0x18, 0x6e, 0xc7, 0x27, 0x7d, 0x7f, 0xff, + 0xff, 0xff, + + /* U+F21C "" */ + 0x1, 0xe0, 0x0, 0x7, 0xc0, 0x0, 0xf8, 0x0, + 0x1f, 0x3, 0xef, 0x80, 0x7, 0xf8, 0xf, 0x79, + 0xe3, 0xf6, 0x7e, 0xe7, 0xde, 0xfb, 0xfb, 0x6f, + 0xc, 0x61, 0xf3, 0xe, 0x77, 0xe0, 0xfc, 0x78, + 0xf, 0x0, + + /* U+F21D "" */ + 0x1, 0x80, 0x3, 0xc0, 0x3, 0xc0, 0x1, 0x80, + 0x0, 0x0, 0x3, 0xc0, 0x7, 0xe0, 0x7, 0xe0, + 0x7, 0xe0, 0x3, 0xc0, 0x1b, 0xd8, 0x7b, 0xde, + 0xc1, 0x83, 0xc0, 0x3, 0x70, 0xe, 0x1f, 0xf8, + + /* U+F21E "" */ + 0x3c, 0x3c, 0x7e, 0x7e, 0xff, 0xff, 0xfb, 0xff, + 0xf9, 0x9f, 0xf1, 0x9f, 0x5, 0x0, 0xe, 0x20, + 0x3e, 0x7c, 0x1e, 0x78, 0x1f, 0xf8, 0xf, 0xf0, + 0x7, 0xe0, 0x1, 0x80, + + /* U+F221 "" */ + 0x1f, 0x7, 0xf1, 0xc7, 0x70, 0x7c, 0x7, 0x80, + 0xf0, 0x1f, 0x7, 0x71, 0xcf, 0xf0, 0x78, 0x6, + 0x0, 0xc0, 0x7e, 0xf, 0xc0, 0x60, 0xc, 0x0, + + /* U+F222 "" */ + 0x0, 0x7e, 0x0, 0xfc, 0x0, 0x78, 0x1, 0xf1, + 0xf7, 0x67, 0xfc, 0xdc, 0x70, 0x70, 0x70, 0xc0, + 0x61, 0x80, 0xc3, 0x1, 0x87, 0x7, 0x7, 0x1c, + 0x7, 0xf0, 0x7, 0xc0, 0x0, + + /* U+F223 "" */ + 0x0, 0xc, 0x31, 0xfe, 0x3f, 0xc7, 0x1d, 0xc1, + 0xf0, 0x1e, 0x3, 0xc0, 0x7c, 0x1d, 0xc7, 0x3f, + 0xc1, 0xe0, 0x18, 0xf, 0xc1, 0xf8, 0xc, 0x1, + 0x80, + + /* U+F224 "" */ + 0x0, 0x7c, 0x1, 0xf0, 0x3, 0xc7, 0xdf, 0x3f, + 0xed, 0xc7, 0xe, 0xe, 0x30, 0x18, 0xc0, 0x63, + 0x1, 0x8e, 0xe, 0x1c, 0x70, 0x3f, 0x80, 0x7c, + 0x0, 0xe0, 0x7, 0xc0, 0x1f, 0x0, 0x38, 0x0, + 0x40, 0x0, + + /* U+F225 "" */ + 0xf8, 0x7, 0xfe, 0x1, 0xfe, 0x0, 0x3f, 0xf0, + 0x1f, 0xdb, 0xfe, 0xc7, 0xff, 0x0, 0x61, 0xc0, + 0x30, 0x30, 0xc, 0xc, 0x3, 0x3, 0x0, 0xc0, + 0xc0, 0x18, 0x60, 0x7, 0xf8, 0x0, 0x78, 0x0, + 0xc, 0x0, 0xf, 0xc0, 0x3, 0xf0, 0x0, 0x30, + 0x0, 0x0, 0x0, + + /* U+F226 "" */ + 0x1f, 0x1f, 0x7, 0xf3, 0xf1, 0xc7, 0x7, 0x70, + 0x70, 0x7c, 0x6, 0x7, 0x80, 0xc0, 0xf0, 0x18, + 0x1f, 0x7, 0x7, 0x71, 0xc1, 0xcf, 0xf3, 0xf0, + 0x78, 0x7c, 0x6, 0x7, 0x0, 0xc0, 0xe0, 0x7e, + 0x3e, 0xf, 0xc7, 0xc0, 0x60, 0x70, 0xc, 0x4, + 0x0, + + /* U+F227 "" */ + 0x0, 0x7e, 0xf0, 0x7, 0xef, 0x0, 0x1e, 0xf0, + 0x3, 0xef, 0x1f, 0x76, 0xb3, 0xfe, 0x63, 0x71, + 0xc0, 0xe, 0xe, 0x70, 0xc0, 0x63, 0xc, 0x6, + 0x30, 0xc0, 0x63, 0xe, 0xe, 0x70, 0x71, 0xce, + 0x3, 0xf9, 0xc0, 0x1f, 0x38, 0x0, + + /* U+F228 "" */ + 0x0, 0x7, 0xe0, 0x0, 0xfc, 0x0, 0x3, 0x8f, + 0x3f, 0xf3, 0xff, 0xf6, 0xe7, 0x8e, 0xf8, 0xf8, + 0xe6, 0x1b, 0xc, 0xc7, 0x61, 0x98, 0x6c, 0x33, + 0x8f, 0x8e, 0x39, 0xe3, 0x87, 0xff, 0xe0, 0x3c, + 0xf8, 0x3, 0x0, 0x1, 0xf8, 0x0, 0x3f, 0x0, + 0x1, 0x80, 0x0, 0x30, 0x0, 0x0, + + /* U+F229 "" */ + 0x0, 0x3f, 0x0, 0x3f, 0x0, 0xf, 0x0, 0x5f, + 0x0, 0x7b, 0x1f, 0x73, 0x3f, 0xf8, 0x71, 0xc0, + 0xe0, 0xe0, 0xc0, 0x60, 0xc0, 0x60, 0xc0, 0x60, + 0xe0, 0xe0, 0x71, 0xc0, 0x3f, 0x80, 0x1f, 0x0, + + /* U+F22A "" */ + 0xc, 0x3, 0xc0, 0xfc, 0x16, 0x80, 0xc0, 0x7e, + 0xf, 0xc0, 0x60, 0x3f, 0xf, 0xfb, 0x83, 0x60, + 0x3c, 0x7, 0x80, 0xf8, 0x3b, 0x8e, 0x3f, 0x83, + 0xe0, + + /* U+F22B "" */ + 0x0, 0x0, 0xf, 0xc0, 0x7, 0xf8, 0x1, 0x87, + 0x6c, 0xc0, 0xd9, 0xb0, 0x3f, 0xfc, 0xf, 0xff, + 0x3, 0x66, 0xe0, 0xdb, 0x1c, 0x60, 0x3, 0xf8, + 0x0, 0x78, 0x0, + + /* U+F22C "" */ + 0x1e, 0xf, 0xc6, 0x3b, 0x87, 0xc0, 0xf0, 0x3c, + 0xf, 0x87, 0x63, 0x9f, 0xc1, 0xe0, 0x30, 0xc, + 0x3, 0x0, 0xc0, 0x30, 0xc, 0x0, + + /* U+F22D "" */ + 0xf, 0x3, 0xfc, 0x70, 0xe6, 0x6, 0xc0, 0x3c, + 0x3, 0xc0, 0x3c, 0x3, 0x60, 0x67, 0xe, 0x3f, + 0xc0, 0xf0, + + /* U+F233 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xf2, 0xff, 0xcb, 0xff, + 0xfd, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x7f, 0xfb, + 0xff, 0xff, 0xf2, 0xff, 0xcb, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F234 "" */ + 0x7, 0x0, 0x3, 0xe0, 0x1, 0xfc, 0x0, 0x7f, + 0xc, 0x1f, 0xc3, 0x7, 0xf1, 0xf0, 0xf8, 0x7c, + 0x1c, 0xc, 0x0, 0x3, 0x0, 0x0, 0x1, 0xfc, + 0x0, 0xff, 0x80, 0x7f, 0xf0, 0x3f, 0xfe, 0xf, + 0xff, 0x83, 0xff, 0xe0, + + /* U+F235 "" */ + 0xf, 0x80, 0x7, 0xf0, 0x1, 0xfc, 0x0, 0x7f, + 0x1b, 0x1f, 0xc3, 0xc7, 0xf0, 0xe0, 0xf8, 0x3c, + 0x0, 0x1b, 0x0, 0x0, 0x7, 0xf0, 0x3, 0xfe, + 0x1, 0xff, 0xc0, 0xff, 0xf8, 0x3f, 0xfe, 0xf, + 0xff, 0x80, + + /* U+F236 "" */ + 0xc0, 0x0, 0x30, 0x0, 0xc, 0x0, 0x3, 0x31, + 0xfe, 0xde, 0x7f, 0xf7, 0x9f, 0xfc, 0xc7, 0xff, + 0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0, 0x3, 0xc0, 0x0, 0xf0, 0x0, 0x30, + + /* U+F238 "" */ + 0x7f, 0xef, 0xff, 0xff, 0xfc, 0x3, 0xc0, 0x3c, + 0x3, 0xc0, 0x3c, 0x3, 0xff, 0xff, 0xff, 0xf9, + 0xff, 0x9f, 0xff, 0xf7, 0xfe, 0x60, 0x6c, 0x3, + 0xc0, 0x30, + + /* U+F239 "" */ + 0x7f, 0xef, 0xff, 0xff, 0xfc, 0x63, 0xc6, 0x3c, + 0x63, 0xc6, 0x3c, 0x63, 0xff, 0xff, 0xff, 0xcf, + 0x3c, 0xf3, 0xff, 0xf7, 0xfe, 0x60, 0x6c, 0x3, + 0xc0, 0x10, + + /* U+F240 "" */ + 0x7f, 0xff, 0x9f, 0xff, 0xfb, 0x0, 0x3, 0x60, + 0x0, 0x6d, 0xff, 0xcd, 0xbf, 0xf9, 0xf7, 0xff, + 0x3e, 0xff, 0xe7, 0xdf, 0xfc, 0xd8, 0x0, 0x1b, + 0xff, 0xff, 0x3f, 0xff, 0xc0, + + /* U+F241 "" */ + 0x7f, 0xff, 0x9f, 0xff, 0xfb, 0x0, 0x3, 0x60, + 0x0, 0x6d, 0xff, 0xd, 0xbf, 0xe1, 0xf7, 0xfc, + 0x3e, 0xff, 0x87, 0xdf, 0xf0, 0xd8, 0x0, 0x1b, + 0xff, 0xff, 0x3f, 0xff, 0xc0, + + /* U+F242 "" */ + 0x7f, 0xff, 0x9f, 0xff, 0xfb, 0x0, 0x3, 0x60, + 0x0, 0x6d, 0xf8, 0xd, 0xbf, 0x1, 0xf7, 0xe0, + 0x3e, 0xfc, 0x7, 0xdf, 0x80, 0xd8, 0x0, 0x1b, + 0xff, 0xff, 0x3f, 0xff, 0xc0, + + /* U+F243 "" */ + 0x7f, 0xff, 0x9f, 0xff, 0xfb, 0x0, 0x3, 0x60, + 0x0, 0x6d, 0xe0, 0xd, 0xbc, 0x1, 0xf7, 0x80, + 0x3e, 0xf0, 0x7, 0xde, 0x0, 0xd8, 0x0, 0x1b, + 0xff, 0xff, 0x3f, 0xff, 0xc0, + + /* U+F244 "" */ + 0x7f, 0xff, 0x9f, 0xff, 0xfb, 0x0, 0x3, 0x60, + 0x0, 0x6c, 0x0, 0xf, 0x80, 0x1, 0xf0, 0x0, + 0x3e, 0x0, 0x7, 0xc0, 0x0, 0xd8, 0x0, 0x1b, + 0xff, 0xff, 0x3f, 0xff, 0xc0, + + /* U+F245 "" */ + 0xc0, 0xe, 0x0, 0xf0, 0xf, 0xc0, 0xfe, 0xf, + 0xf0, 0xff, 0xcf, 0xfe, 0xff, 0xef, 0xc0, 0xf6, + 0xe, 0x60, 0xc7, 0xc, 0x30, 0x3, 0x80, 0x18, + + /* U+F246 "" */ + 0xe7, 0xff, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x7e, + 0x7e, 0x18, 0x18, 0x18, 0x18, 0x3c, 0xff, 0xe7, + + /* U+F247 "" */ + 0x60, 0x1, 0xbf, 0xff, 0xff, 0xff, 0xfd, 0x80, + 0x6, 0x67, 0x81, 0x9b, 0xf0, 0x66, 0xfc, 0x19, + 0xbf, 0x66, 0x67, 0x9d, 0x98, 0xf, 0x66, 0xf, + 0xd9, 0x81, 0xe6, 0x60, 0x1, 0xbf, 0xff, 0xff, + 0xff, 0xfd, 0x80, 0x6, + + /* U+F248 "" */ + 0x60, 0x18, 0xf, 0xff, 0xc0, 0xff, 0xfc, 0x6, + 0x1, 0x80, 0x60, 0x18, 0x6, 0x1, 0x86, 0x60, + 0x1b, 0xf6, 0x1, 0x9f, 0xff, 0xfc, 0x6f, 0xff, + 0xc6, 0x60, 0x18, 0x60, 0x18, 0x6, 0x1, 0x80, + 0x60, 0x3f, 0xff, 0x3, 0xff, 0xf0, 0x18, 0x6, + + /* U+F249 "" */ + 0x7f, 0xfd, 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x8d, 0xff, 0x3b, 0xfe, 0xe7, 0xff, 0x8f, 0xfe, + 0x7, 0xf0, 0x0, + + /* U+F24A "" */ + 0x7f, 0xfd, 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x8d, 0xff, 0x3b, 0xfe, 0xe7, 0xff, 0x8f, 0xfe, + 0x7, 0xf0, 0x0, + + /* U+F24D "" */ + 0x3, 0xfe, 0x7, 0xff, 0x7, 0xff, 0x7, 0xff, + 0x7, 0xff, 0x77, 0xff, 0xf7, 0xff, 0xc7, 0xff, + 0xc7, 0xff, 0xc7, 0xff, 0xc3, 0xfe, 0xc0, 0x0, + 0xc0, 0x60, 0xc0, 0x60, 0xff, 0xe0, 0x7f, 0xc0, + + /* U+F24E "" */ + 0x0, 0x60, 0x1, 0xff, 0xf8, 0x1f, 0xff, 0x80, + 0xf, 0x0, 0x18, 0xe1, 0x81, 0x86, 0x18, 0x3c, + 0x63, 0xc6, 0x46, 0x66, 0x66, 0x66, 0x6c, 0x36, + 0xc3, 0xff, 0x6f, 0xff, 0xe6, 0x7e, 0x3c, 0x63, + 0xc0, 0x6, 0x0, 0x1f, 0xff, 0x81, 0xff, 0xf8, + + /* U+F250 "" */ + 0xff, 0xff, 0xff, 0x60, 0x66, 0x6, 0x70, 0xc3, + 0x9c, 0x1f, 0x80, 0xf0, 0xf, 0x1, 0xf8, 0x39, + 0xc7, 0xc, 0x60, 0x66, 0x6, 0xff, 0xff, 0xff, + + /* U+F251 "" */ + 0xff, 0xff, 0xff, 0x7f, 0xe7, 0xfe, 0x7f, 0xc3, + 0xfc, 0x1f, 0x80, 0xf0, 0xf, 0x1, 0xf8, 0x39, + 0xc7, 0xc, 0x60, 0x66, 0x6, 0xff, 0xff, 0xff, + + /* U+F252 "" */ + 0xff, 0xff, 0xff, 0x60, 0x66, 0x6, 0x7f, 0xc3, + 0xfc, 0x1f, 0x80, 0xf0, 0xf, 0x1, 0xf8, 0x39, + 0xc7, 0xc, 0x7f, 0xe7, 0xfe, 0xff, 0xff, 0xff, + + /* U+F253 "" */ + 0xff, 0xff, 0xff, 0x60, 0x66, 0x6, 0x70, 0xc3, + 0x9c, 0x1f, 0x80, 0xf0, 0xf, 0x1, 0xf8, 0x3f, + 0xc7, 0xfc, 0x7f, 0xe7, 0xfe, 0xff, 0xff, 0xff, + + /* U+F254 "" */ + 0xff, 0xff, 0xff, 0x60, 0x66, 0x6, 0x70, 0xc3, + 0x9c, 0x1f, 0x80, 0xf0, 0xf, 0x1, 0xf8, 0x39, + 0xc7, 0xc, 0x60, 0x66, 0x6, 0xff, 0xff, 0xff, + + /* U+F255 "" */ + 0x16, 0x3, 0xfc, 0x3f, 0xe3, 0xff, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xfe, 0x3f, + 0xc0, 0x0, 0x0, 0x7, 0xfe, 0x7f, 0xe7, 0xfe, + + /* U+F256 "" */ + 0x1, 0x80, 0x1b, 0x60, 0x36, 0xc0, 0x6d, 0xb0, + 0xdb, 0x61, 0xb6, 0xc3, 0x6d, 0x86, 0xdb, 0xf, + 0xff, 0xdf, 0xff, 0xff, 0xfb, 0xff, 0xf3, 0xff, + 0xe3, 0xff, 0x83, 0xfe, 0x1, 0xf8, + + /* U+F257 "" */ + 0x0, 0x18, 0x1e, 0x1f, 0xf, 0xe3, 0xc1, 0xff, + 0xf0, 0x1f, 0xfb, 0xff, 0xfd, 0xff, 0xfe, 0x3, + 0xff, 0x1, 0xff, 0x80, 0x7f, 0xc0, 0x3f, 0xc0, + 0x1f, 0xc0, + + /* U+F258 "" */ + 0xff, 0xe0, 0xff, 0xf0, 0xff, 0xf8, 0x0, 0x78, + 0x0, 0x7c, 0x0, 0x7e, 0x3f, 0xfe, 0x3f, 0xff, + 0x3f, 0xff, 0x0, 0x7f, 0x0, 0x1f, 0x0, 0x1f, + + /* U+F259 "" */ + 0x6, 0x0, 0x6, 0x18, 0x6, 0x18, 0x36, 0x1b, + 0x3b, 0x1b, 0x1b, 0x3b, 0x1b, 0x36, 0x1f, 0xb6, + 0xf, 0xfe, 0xef, 0xfc, 0xff, 0xfc, 0x7f, 0xfc, + 0x3f, 0xfc, 0x1f, 0xf8, 0xf, 0xf8, 0x3, 0xe0, + + /* U+F25A "" */ + 0x4, 0x0, 0x1c, 0x0, 0x38, 0x0, 0x70, 0x0, + 0xe0, 0x1, 0xc0, 0x3, 0xe8, 0x7, 0xfe, 0xf, + 0xff, 0xdd, 0x5f, 0xba, 0xbb, 0xf5, 0x73, 0xea, + 0xc7, 0xff, 0x87, 0xff, 0x3, 0xf8, + + /* U+F25B "" */ + 0x6, 0x6, 0x30, 0x19, 0x80, 0xcc, 0x7, 0x60, + 0x1b, 0x60, 0xdb, 0x60, 0x1b, 0x0, 0xd9, 0xf0, + 0xdf, 0xb8, 0xe3, 0xf7, 0xff, 0x9f, 0xf8, 0xff, + 0xc1, 0xf8, + + /* U+F25C "" */ + 0xff, 0x60, 0x7f, 0xee, 0x1c, 0x61, 0xc3, 0x8c, + 0x3c, 0xf1, 0x87, 0xfe, 0x30, 0xde, 0xc6, 0x1b, + 0xd8, 0xc3, 0x33, 0x18, 0x60, 0x63, 0xc, 0xc, + + /* U+F25D "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7c, 0x3e, 0xfc, 0x1f, 0xfd, 0x9f, 0xfd, 0x9f, + 0xfc, 0x3f, 0xfd, 0x7f, 0xfd, 0x3f, 0x7d, 0xbe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F26C "" */ + 0x7f, 0xff, 0xbf, 0xff, 0xfc, 0x0, 0xf, 0x0, + 0x3, 0xc0, 0x0, 0xf0, 0x0, 0x3c, 0x0, 0xf, + 0x0, 0x3, 0xc0, 0x0, 0xf0, 0x0, 0x3f, 0xff, + 0xfd, 0xff, 0xfe, 0x0, 0x0, 0x3, 0xff, 0x0, + 0xff, 0xc0, + + /* U+F271 "" */ + 0x18, 0x60, 0x61, 0x87, 0xff, 0xbf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xcf, 0xff, 0x3f, 0xf0, 0x3f, + 0xc0, 0xff, 0xcf, 0xff, 0x3f, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F272 "" */ + 0x18, 0x60, 0x61, 0x87, 0xff, 0xbf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, + 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F273 "" */ + 0x18, 0x60, 0x61, 0x87, 0xff, 0xbf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xb7, 0xfe, 0x1f, 0xfc, 0x7f, + 0xf1, 0xff, 0x87, 0xfe, 0xdf, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F274 "" */ + 0x18, 0x60, 0x61, 0x8f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0x7f, 0xfb, 0xff, 0xcf, 0xe6, 0x7f, + 0x93, 0xff, 0x1f, 0xfe, 0x7f, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F275 "" */ + 0xf0, 0x0, 0xf0, 0x0, 0xf0, 0x0, 0xf0, 0xc3, + 0xf3, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + + /* U+F276 "" */ + 0x1e, 0x1c, 0xce, 0x3b, 0x3e, 0xcf, 0xff, 0xe7, + 0xf9, 0xfc, 0x1e, 0x3, 0x0, 0xc0, 0x30, 0xc, + 0x3, 0x0, 0xc0, 0x30, + + /* U+F277 "" */ + 0x1, 0x80, 0x1, 0x80, 0x7f, 0xfe, 0x7f, 0xff, + 0x7f, 0xff, 0x7f, 0xfc, 0x1, 0x80, 0x1, 0x80, + 0x7f, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfe, + 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, + + /* U+F278 "" */ + 0x0, 0x0, 0x1f, 0x7, 0x7f, 0xff, 0xfc, 0xff, + 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, + 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, + 0xff, 0x3f, 0xff, 0xfe, 0xe0, 0xf8, 0x0, 0x0, + + /* U+F279 "" */ + 0x0, 0x0, 0x1f, 0x7, 0x7f, 0xff, 0xfc, 0xff, + 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, + 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, + 0xff, 0x3f, 0xff, 0xfe, 0xe0, 0xf8, 0x0, 0x0, + + /* U+F27A "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xfe, 0xf, 0x0, 0xe, 0x0, 0xc, 0x0, + + /* U+F27B "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xe6, 0x67, 0xe6, 0x67, + 0xe6, 0x67, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfe, + 0x7f, 0xfc, 0x7f, 0xf8, 0xff, 0xe0, 0xc0, 0x0, + + /* U+F283 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xc6, 0x3f, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F28B "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0x9f, 0xf9, 0x9f, 0xf9, 0x9f, + 0xf9, 0x9f, 0xf9, 0x9f, 0xf9, 0x9f, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F28C "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0x9f, 0xf9, 0x9f, 0xf9, 0x9f, + 0xf9, 0x9f, 0xf9, 0x9f, 0xf9, 0x9f, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F28D "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F28E "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F290 "" */ + 0x7, 0x80, 0x12, 0x0, 0x84, 0x2, 0x10, 0x8, + 0x43, 0xff, 0xff, 0xff, 0xfd, 0xef, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xdf, 0xfe, + + /* U+F291 "" */ + 0x0, 0x80, 0x0, 0xe0, 0x0, 0xd8, 0x0, 0xc6, + 0x0, 0xc1, 0x87, 0xff, 0xff, 0xff, 0xfe, 0xf6, + 0xde, 0x7b, 0x6f, 0x3d, 0xb7, 0x8e, 0xdb, 0xc7, + 0x6d, 0xc3, 0xb6, 0xe1, 0xff, 0xf0, 0x7f, 0xf0, + + /* U+F292 "" */ + 0x2, 0xc, 0x6, 0xc, 0x6, 0x18, 0x6, 0x18, + 0x7f, 0xff, 0x7f, 0xff, 0xc, 0x38, 0xc, 0x30, + 0xc, 0x30, 0x1c, 0x30, 0xff, 0xfe, 0xff, 0xfe, + 0x18, 0x60, 0x18, 0x60, 0x30, 0x60, 0x30, 0x40, + + /* U+F295 "" */ + 0x78, 0xf, 0xf0, 0x7f, 0xc3, 0xbf, 0x1c, 0xfc, + 0xe1, 0xe7, 0x0, 0x38, 0x1, 0xc0, 0xe, 0x78, + 0x73, 0xf3, 0x8f, 0xdc, 0x3f, 0xe0, 0xff, 0x1, + 0xe0, + + /* U+F29A "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3e, 0x7c, 0x7e, 0x7e, + 0x7e, 0x7e, 0xfb, 0xdf, 0xf8, 0x1f, 0xfe, 0x7f, + 0xfe, 0x7f, 0xfe, 0x7f, 0xff, 0x7f, 0x7d, 0x3e, + 0x7d, 0xbe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F29C "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfc, 0x3f, 0xfd, 0xbf, 0xfd, 0xbf, + 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F29D "" */ + 0xe, 0x0, 0x1e, 0x0, 0xe, 0x0, 0x0, 0x0, + 0x1e, 0x0, 0x3f, 0x0, 0x7f, 0x80, 0xdf, 0xc0, + 0xde, 0xc0, 0xde, 0x40, 0x1e, 0x0, 0xf, 0x30, + 0x13, 0x98, 0x19, 0x98, 0x39, 0x8c, 0x71, 0x86, + 0xe1, 0xc3, 0x0, 0x83, + + /* U+F29E "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x8f, + 0xe0, 0x87, 0xec, 0xb7, 0xec, 0xb7, 0xec, 0xb7, + 0xec, 0x8f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F2A0 "" */ + 0x0, 0x70, 0x1c, 0x6, 0x1e, 0x1, 0x9f, 0x8e, + 0x6f, 0xc1, 0xb7, 0xe0, 0x6f, 0xf1, 0xb7, 0xf0, + 0xdb, 0x78, 0x0, 0x3e, 0x0, 0xf, 0x80, 0x3, + 0xe7, 0x81, 0xfb, 0xf0, 0x7f, 0xf8, 0xf, 0xfc, + 0x3, 0xfc, 0x0, 0x7c, 0x0, + + /* U+F2A1 "" */ + 0x70, 0xef, 0x9f, 0xf9, 0xff, 0x9f, 0x70, 0xe0, + 0x0, 0x70, 0xef, 0x9b, 0xf9, 0xb7, 0xe, 0x0, + 0x7, 0xe, 0xf9, 0xfd, 0x9b, 0xd9, 0xbf, 0x9f, + 0x70, 0xe0, + + /* U+F2A2 "" */ + 0x0, 0x1c, 0x0, 0x1e, 0x7, 0xc6, 0xf, 0xe7, + 0x1c, 0x73, 0x3b, 0xbb, 0x36, 0xda, 0x36, 0xd8, + 0x0, 0x18, 0x0, 0x38, 0xc, 0x70, 0x6c, 0x60, + 0x70, 0x60, 0x38, 0xe0, 0xd9, 0xc0, 0xc1, 0x80, + + /* U+F2A3 "" */ + 0x19, 0x80, 0x1, 0xb8, 0x0, 0x37, 0x0, 0x3, + 0xfc, 0x0, 0x7f, 0xcf, 0xc7, 0xf1, 0xfe, 0xff, + 0x99, 0xff, 0xf9, 0x1f, 0xf8, 0x9f, 0xff, 0x99, + 0xff, 0x7f, 0x8f, 0xe3, 0xf3, 0xfe, 0x0, 0x3f, + 0xc0, 0x0, 0xec, 0x0, 0x1d, 0x80, 0x1, 0x98, + + /* U+F2A4 "" */ + 0x0, 0x3, 0x0, 0x7, 0x7, 0xc6, 0xf, 0xe0, + 0x1c, 0x70, 0x3b, 0xb8, 0x36, 0xd8, 0x36, 0xd8, + 0x0, 0x18, 0x0, 0x38, 0xc, 0x70, 0x1c, 0x60, + 0x38, 0x60, 0x70, 0xe0, 0xe1, 0xc0, 0xc1, 0x80, + + /* U+F2A7 "" */ + 0x0, 0x40, 0x3, 0x40, 0x1, 0xe0, 0x6, 0xb0, + 0x0, 0x33, 0x9, 0x1f, 0xd, 0xcf, 0x0, 0xe7, + 0x7f, 0xf3, 0x3, 0xfa, 0xff, 0xf8, 0x3, 0xf8, + 0x7f, 0xf8, 0x3, 0xf0, 0x3, 0xf0, 0x3f, 0xc0, + + /* U+F2A8 "" */ + 0x40, 0x0, 0xc, 0x0, 0x0, 0xcf, 0xe0, 0xf, + 0xff, 0x0, 0xf0, 0xf0, 0x4e, 0xf, 0xc, 0xfd, + 0xf0, 0xcf, 0x9e, 0x4c, 0xf3, 0xec, 0xce, 0x7c, + 0xcc, 0xef, 0xc, 0xc7, 0xc0, 0xcc, 0x70, 0xc, + 0xc6, 0x0, 0x8c, 0x60, 0x0, 0x6, 0x0, 0x0, + 0x60, 0x0, 0x0, + + /* U+F2B4 "" */ + 0xe0, 0x1, 0xc0, 0x3, 0x80, 0x3, 0xff, 0xf7, + 0xff, 0xef, 0xff, 0x9f, 0xff, 0x3f, 0xfc, 0x7f, + 0xf8, 0xff, 0xf1, 0xff, 0xf3, 0xff, 0xe7, 0xff, + 0xef, 0xff, 0xd0, 0x0, 0x20, 0x0, + + /* U+F2B5 "" */ + 0x80, 0x0, 0x7b, 0x9f, 0x7f, 0xcf, 0xff, 0xe7, + 0xff, 0xf3, 0xff, 0xf9, 0xe7, 0xfe, 0x30, 0xff, + 0xc1, 0x9f, 0xfd, 0xf3, 0xff, 0xfe, 0x73, 0xff, + 0xc0, 0x7f, 0xf0, 0xf, 0xe4, 0x0, 0xcc, 0x0, + + /* U+F2B6 "" */ + 0x0, 0x0, 0x3, 0xc0, 0x7, 0xe0, 0x1e, 0x78, + 0x38, 0x1c, 0x70, 0xe, 0xe0, 0x7, 0xe0, 0x7, + 0xf0, 0xf, 0xf8, 0x1f, 0xfe, 0x7f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F2B7 "" */ + 0x0, 0x0, 0x3, 0xc0, 0x7, 0xe0, 0x1e, 0x78, + 0x38, 0x1c, 0x70, 0xe, 0xe0, 0x7, 0xe0, 0x7, + 0xf0, 0xf, 0xf8, 0x1f, 0xfe, 0x7f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F2B9 "" */ + 0x7f, 0xf1, 0xff, 0xf3, 0xff, 0xef, 0xff, 0xdf, + 0xdf, 0xbf, 0x1f, 0x3e, 0x3e, 0xfc, 0x7d, 0xff, + 0xfb, 0xe0, 0xf3, 0x80, 0xef, 0x1, 0xdf, 0xff, + 0xbf, 0xff, 0x3f, 0xfe, 0x3f, 0xf8, + + /* U+F2BA "" */ + 0x7f, 0xf1, 0xff, 0xf3, 0xff, 0xef, 0xff, 0xdf, + 0xdf, 0xbf, 0x1f, 0x3e, 0x3e, 0xfc, 0x7d, 0xff, + 0xfb, 0xe0, 0xf3, 0x80, 0xef, 0x1, 0xdf, 0xff, + 0xbf, 0xff, 0x3f, 0xfe, 0x3f, 0xf8, + + /* U+F2BB "" */ + 0x7f, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xc7, + 0x7, 0xf1, 0xc1, 0xfc, 0x7f, 0xff, 0x3f, 0xff, + 0xff, 0x7, 0xe0, 0xff, 0xf0, 0x3f, 0xfc, 0xf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xe0, + + /* U+F2BC "" */ + 0x7f, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xc7, + 0x7, 0xf1, 0xc1, 0xfc, 0x7f, 0xff, 0x3f, 0xff, + 0xff, 0x7, 0xe0, 0xff, 0xf0, 0x3f, 0xfc, 0xf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xe0, + + /* U+F2BD "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, + 0xfe, 0x7f, 0xff, 0xff, 0xf8, 0x1f, 0x70, 0xe, + 0x70, 0xe, 0x3c, 0x1c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F2BE "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, + 0xfe, 0x7f, 0xff, 0xff, 0xf8, 0x1f, 0x70, 0xe, + 0x70, 0xe, 0x3c, 0x1c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F2C0 "" */ + 0x7, 0x0, 0xfe, 0x7, 0xf0, 0x3f, 0x81, 0xfc, + 0xf, 0xe0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0xfe, + 0xf, 0xf8, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, + 0xe0, + + /* U+F2C1 "" */ + 0x7f, 0xef, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf9, 0xff, 0x9f, 0xf9, 0xff, 0x9f, 0xff, + 0xff, 0xf, 0xe0, 0x7e, 0x7, 0xff, 0xf7, 0xfe, + + /* U+F2C2 "" */ + 0x7f, 0xff, 0xbf, 0xff, 0xf0, 0x0, 0x3, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0x78, 0x7f, 0x1e, 0x1f, + 0xc7, 0xff, 0xf1, 0xff, 0xff, 0xf8, 0x7e, 0xf, + 0xff, 0x3, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xe0, + + /* U+F2C3 "" */ + 0x7f, 0xff, 0xbf, 0xff, 0xf0, 0x0, 0x3, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0x78, 0x7f, 0x1e, 0x1f, + 0xc7, 0xff, 0xf1, 0xff, 0xff, 0xf8, 0x7e, 0xf, + 0xff, 0x3, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xe0, + + /* U+F2C7 "" */ + 0x1e, 0xf, 0xc3, 0x30, 0xcc, 0x33, 0xc, 0xc3, + 0x30, 0xcc, 0x33, 0x1c, 0xef, 0x1b, 0x87, 0xe1, + 0xbc, 0xe7, 0xf0, 0xf8, + + /* U+F2C8 "" */ + 0x1e, 0xf, 0xc3, 0xf0, 0xfc, 0x33, 0xc, 0xc3, + 0x30, 0xcc, 0x33, 0x1c, 0xef, 0x1b, 0x87, 0xe1, + 0xbc, 0xe7, 0xf0, 0xf8, + + /* U+F2C9 "" */ + 0x1e, 0xf, 0xc3, 0xf0, 0xfc, 0x3f, 0xf, 0xc3, + 0x30, 0xcc, 0x33, 0x1c, 0xef, 0x1b, 0x87, 0xe1, + 0xbc, 0xe7, 0xf0, 0xf8, + + /* U+F2CA "" */ + 0x1c, 0x1f, 0xf, 0x87, 0xc3, 0xe1, 0xf0, 0xf8, + 0x7c, 0x77, 0x3b, 0xb8, 0xfc, 0x7e, 0x3f, 0xbd, + 0xfc, 0x7c, + + /* U+F2CB "" */ + 0x1c, 0x1f, 0xf, 0x87, 0xc3, 0xe1, 0xf0, 0xf8, + 0x7c, 0x7f, 0x7b, 0xf8, 0xfc, 0x7e, 0x3f, 0xb9, + 0xfc, 0x78, + + /* U+F2CC "" */ + 0x78, 0x0, 0xff, 0x80, 0xcf, 0xe0, 0xcf, 0xc0, + 0xcf, 0x98, 0xcf, 0x1b, 0xc6, 0x63, 0xc4, 0x6c, + 0xc1, 0x8c, 0xc1, 0xb0, 0xc0, 0x30, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0x0, + + /* U+F2CD "" */ + 0x38, 0x0, 0x7f, 0x0, 0x6f, 0x80, 0x6f, 0x0, + 0x6e, 0x0, 0x64, 0x0, 0x60, 0x0, 0x60, 0x0, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x3f, 0xfc, 0x30, 0xc, + + /* U+F2CE "" */ + 0xf, 0xc0, 0x61, 0x83, 0x3, 0x19, 0xe6, 0xcc, + 0xcf, 0x61, 0xbd, 0xb6, 0xf6, 0xdb, 0xd8, 0x4f, + 0x0, 0x76, 0x31, 0x8d, 0xec, 0x17, 0xa0, 0xc, + 0x0, 0x30, 0x0, 0xc0, + + /* U+F2D0 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xc0, 0x3, 0xc0, 0x3, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F2D1 "" */ + 0xff, 0xff, 0xff, 0xff, + + /* U+F2D2 "" */ + 0x3, 0xff, 0x81, 0xff, 0xf0, 0x0, 0xc, 0x0, + 0x3, 0x7f, 0xf0, 0xff, 0xfe, 0x3c, 0x1, 0x8f, + 0x0, 0x63, 0xff, 0xfb, 0xff, 0xfe, 0xef, 0xff, + 0x83, 0xff, 0xe0, 0xff, 0xf8, 0x1f, 0xfc, 0x0, + + /* U+F2D3 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, + 0x3f, 0xc0, 0xff, 0x87, 0xfe, 0x1f, 0xf0, 0x3f, + 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F2D4 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x9f, + 0xf8, 0x1f, 0xfc, 0x3f, 0xfc, 0x3f, 0xf8, 0x1f, + 0xf9, 0x9f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F2DB "" */ + 0x9, 0x90, 0xd, 0xb0, 0x1f, 0xf8, 0x3f, 0xfc, + 0xf0, 0xf, 0x77, 0xee, 0x37, 0xec, 0xf7, 0xef, + 0xf7, 0xef, 0x37, 0xec, 0x77, 0xee, 0xf0, 0xf, + 0x3f, 0xfc, 0x1f, 0xf8, 0xd, 0xb0, 0x9, 0x90, + + /* U+F2DC "" */ + 0x1, 0x80, 0x1, 0x80, 0x7, 0xe0, 0x7, 0xe0, + 0xdb, 0xdb, 0xf9, 0x9f, 0x3d, 0xbc, 0x7f, 0xfe, + 0x7, 0xe0, 0x7, 0xe0, 0x7f, 0xfe, 0x3d, 0xbc, + 0xf9, 0x9f, 0xdb, 0xdb, 0x7, 0xe0, 0x7, 0xe0, + 0x1, 0x80, 0x1, 0x80, + + /* U+F2E5 "" */ + 0x0, 0x1e, 0x0, 0x7f, 0x0, 0xff, 0x1, 0xff, + 0x1, 0xff, 0x1, 0xfe, 0x1, 0xfe, 0x1, 0xfc, + 0x3, 0xf8, 0x7, 0x0, 0xe, 0x0, 0x1c, 0x0, + 0x38, 0x0, 0x70, 0x0, 0xe0, 0x0, 0xc0, 0x0, + + /* U+F2E7 "" */ + 0x5a, 0x6, 0xb4, 0x3d, 0x68, 0x7a, 0xd1, 0xf5, + 0xa3, 0xff, 0xc7, 0xdf, 0x8f, 0x9e, 0x1f, 0x18, + 0x3e, 0x30, 0x7c, 0x60, 0x78, 0xc0, 0x31, 0x80, + 0x63, 0x0, 0xc6, 0x1, 0x8c, 0x3, + + /* U+F2EA "" */ + 0x0, 0x0, 0x63, 0xf8, 0x3f, 0xfe, 0x1f, 0x1, + 0x8f, 0x80, 0x67, 0xe0, 0x39, 0xf0, 0xc, 0x0, + 0x6, 0x0, 0x3, 0x0, 0x1, 0x80, 0x0, 0xc0, + 0x0, 0x62, 0x0, 0x71, 0x80, 0x30, 0xe0, 0x70, + 0x3f, 0xf0, 0x7, 0xf0, 0x0, 0x0, 0x0, + + /* U+F2ED "" */ + 0x7, 0x80, 0x3f, 0xf, 0xff, 0xff, 0xff, 0x0, + 0x0, 0x0, 0x7, 0xff, 0x9f, 0xfe, 0x6c, 0xd9, + 0xb3, 0x66, 0xcd, 0x9b, 0x36, 0x6c, 0xd9, 0xb3, + 0x66, 0xcd, 0x9f, 0xfe, 0x3f, 0xf0, + + /* U+F2F1 "" */ + 0x7, 0xe3, 0x1f, 0xff, 0x3c, 0x3f, 0x70, 0x1f, + 0x60, 0x3f, 0xe0, 0x3f, 0xc0, 0x0, 0xc0, 0x0, + 0x0, 0x3, 0x0, 0x3, 0xfc, 0x7, 0xfc, 0x6, + 0xf8, 0xe, 0xfc, 0x3c, 0xff, 0xf8, 0xc7, 0xe0, + + /* U+F2F2 "" */ + 0xf, 0x80, 0x10, 0x3, 0xe0, 0x7f, 0xf7, 0xff, + 0xbe, 0xfb, 0xf7, 0xff, 0xbf, 0xfd, 0xff, 0xef, + 0xff, 0xff, 0xff, 0xf7, 0xff, 0x1f, 0xf0, 0x7f, + 0x1, 0xf0, + + /* U+F2F5 "" */ + 0x7c, 0x0, 0xfc, 0x0, 0xc0, 0x30, 0xc0, 0x38, + 0xc0, 0x3c, 0xc7, 0xfe, 0xc7, 0xff, 0xc7, 0xff, + 0xc7, 0xfe, 0xc0, 0x3c, 0xc0, 0x38, 0xc0, 0x30, + 0xfc, 0x0, 0x7c, 0x0, + + /* U+F2F6 "" */ + 0x0, 0x3e, 0x0, 0x3f, 0x6, 0x3, 0x7, 0x3, + 0x7, 0x83, 0xff, 0xc3, 0xff, 0xe3, 0xff, 0xe3, + 0xff, 0xc3, 0x7, 0x83, 0x7, 0x3, 0x6, 0x3, + 0x0, 0x3f, 0x0, 0x3e, + + /* U+F2F9 "" */ + 0x0, 0x0, 0x3, 0xf8, 0xc3, 0xff, 0xe3, 0x1, + 0xf3, 0x0, 0xfb, 0x0, 0xfd, 0x80, 0x7c, 0xc0, + 0x0, 0x60, 0x0, 0x30, 0x0, 0x18, 0x0, 0xc, + 0x0, 0x7, 0x0, 0x21, 0x80, 0x30, 0x70, 0x38, + 0x1f, 0xf8, 0x7, 0xf0, 0x0, 0x0, 0x0, + + /* U+F2FE "" */ + 0x0, 0xc0, 0x0, 0xe0, 0x0, 0xe0, 0x1, 0xe0, + 0xf, 0xf0, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, + 0x79, 0x9e, 0x79, 0x9e, 0x7f, 0xfe, 0x3f, 0xfc, + 0xf8, 0x1f, 0xfc, 0x3f, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F302 "" */ + 0xf, 0xff, 0xf, 0xff, 0xc6, 0x7f, 0xe3, 0x3f, + 0xfd, 0xfe, 0xfe, 0xfe, 0x3f, 0x79, 0x1f, 0xb8, + 0x7, 0xdc, 0x1, 0xec, 0x0, 0xf7, 0xff, 0xf9, + 0xff, 0xec, 0x0, 0x6, 0x0, 0x1, 0xff, 0xf8, + + /* U+F303 "" */ + 0x0, 0x1c, 0x0, 0x3e, 0x0, 0x3f, 0x0, 0x9f, + 0x1, 0xcf, 0x3, 0xe6, 0x7, 0xf0, 0xf, 0xf8, + 0x1f, 0xf0, 0x3f, 0xe0, 0x7f, 0xc0, 0x7f, 0x80, + 0x4f, 0x0, 0xce, 0x0, 0xfc, 0x0, 0xe0, 0x0, + + /* U+F304 "" */ + 0x0, 0x1c, 0x0, 0x3e, 0x0, 0x3f, 0x0, 0x9f, + 0x1, 0xcf, 0x3, 0xe6, 0x7, 0xf0, 0xf, 0xf8, + 0x1f, 0xf0, 0x3f, 0xe0, 0x7f, 0xc0, 0x7f, 0x80, + 0x7f, 0x0, 0xfe, 0x0, 0xfc, 0x0, 0xe0, 0x0, + + /* U+F305 "" */ + 0x0, 0xc, 0x0, 0x3e, 0x7, 0x3f, 0xd, 0x9f, + 0x19, 0xcf, 0x33, 0xe6, 0x27, 0xf0, 0xf, 0xf0, + 0x1f, 0xe0, 0x3f, 0xc0, 0x7f, 0x80, 0x7f, 0x0, + 0x7e, 0x0, 0xfc, 0x0, 0xf8, 0x0, 0x80, 0x0, + + /* U+F306 "" */ + 0x7, 0xe0, 0xf, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x3f, 0xfc, 0x1f, 0xf8, 0xf, 0xf0, 0x7, 0xe0, + + /* U+F309 "" */ + 0x1e, 0x7, 0x81, 0xe0, 0x78, 0x1e, 0x7, 0x81, + 0xe0, 0x78, 0x1e, 0x7, 0x81, 0xe0, 0x78, 0xff, + 0xff, 0xf7, 0xf8, 0xfc, 0x1e, 0x3, 0x0, + + /* U+F30A "" */ + 0xc, 0x0, 0x7, 0x0, 0x3, 0xc0, 0x1, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xfc, + 0xf0, 0x0, 0x1c, 0x0, 0x3, 0x0, 0x0, + + /* U+F30B "" */ + 0x0, 0xc, 0x0, 0x3, 0x80, 0x0, 0xf3, 0xff, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, + 0x0, 0x3c, 0x0, 0xe, 0x0, 0x3, 0x0, + + /* U+F30C "" */ + 0xc, 0x7, 0x83, 0xf1, 0xfe, 0xff, 0xff, 0xf1, + 0xe0, 0x78, 0x1e, 0x7, 0x81, 0xe0, 0x78, 0x1e, + 0x7, 0x81, 0xe0, 0x78, 0x1e, 0x7, 0x80, + + /* U+F312 "" */ + 0xf, 0xf8, 0xf, 0xfe, 0x7, 0xff, 0x7, 0xff, + 0xc7, 0xff, 0xf3, 0xff, 0xfb, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xbf, 0xff, 0x9f, 0xff, 0xc7, + 0xff, 0xc1, 0xff, 0xc0, 0xff, 0xe0, 0x3f, 0xe0, + + /* U+F31C "" */ + 0x3e, 0x0, 0x3f, 0xe0, 0xf, 0xfc, 0x3, 0xfb, + 0x80, 0xfe, 0x70, 0x3f, 0x8c, 0xf, 0xff, 0x83, + 0xff, 0xe0, 0xff, 0xf9, 0xbf, 0xfc, 0xff, 0xfe, + 0x5f, 0xff, 0x3a, 0xff, 0x9f, 0x3f, 0xcf, 0x8f, + 0xe7, 0xc1, 0xf9, 0xe0, 0x0, 0xe0, 0x0, + + /* U+F31E "" */ + 0xfc, 0xff, 0xf3, 0xff, 0x87, 0xff, 0x3f, 0xff, + 0xff, 0x7f, 0xb0, 0xfc, 0x3, 0xf0, 0xdf, 0xef, + 0xff, 0xff, 0xcf, 0xfe, 0x1f, 0xfc, 0xff, 0xf3, + 0xf0, + + /* U+F328 "" */ + 0x1f, 0x83, 0xfc, 0xf0, 0xff, 0xf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xfe, + + /* U+F332 "" */ + 0x7, 0xe0, 0xf, 0xf0, 0x7f, 0xfe, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x3f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1f, 0xfc, 0x3f, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, + + /* U+F337 "" */ + 0x18, 0x0, 0x38, 0x0, 0x78, 0x0, 0xff, 0xff, + 0xff, 0xff, 0x78, 0x0, 0x38, 0x0, 0x18, 0x0, + 0x0, 0x18, 0x0, 0x1c, 0x0, 0x1e, 0xff, 0xff, + 0xff, 0xff, 0x0, 0x1e, 0x0, 0x1c, 0x0, 0x18, + + /* U+F338 "" */ + 0x18, 0x18, 0x3c, 0x18, 0x7e, 0x18, 0xff, 0x18, + 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, + 0x18, 0xff, 0x18, 0x7e, 0x18, 0x3c, 0x18, 0x18, + + /* U+F34E "" */ + 0x78, 0x1e, 0xf0, 0xf, 0xe7, 0xe7, 0xcf, 0xf3, + 0x1f, 0xf8, 0x3e, 0x7c, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7e, 0x3e, 0x7f, 0xbe, 0x7f, 0xfc, + 0x3f, 0xfc, 0x3f, 0xfc, 0x7f, 0xfe, 0x63, 0xc6, + + /* U+F358 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, + 0xf8, 0x1f, 0xf8, 0x1f, 0xfc, 0x3f, 0x7e, 0x7e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F359 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0xfe, 0xfc, 0xff, 0xf8, 0xf, 0xf0, 0xf, + 0xf0, 0xf, 0xf8, 0xf, 0xfc, 0xff, 0x7e, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F35A "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0x7e, 0xff, 0x3f, 0xf0, 0x1f, 0xf0, 0xf, + 0xf0, 0xf, 0xf0, 0x1f, 0xff, 0x3f, 0x7f, 0x7e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F35B "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfc, 0x3f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0x7e, 0x7e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F35D "" */ + 0x0, 0x7f, 0x0, 0x7f, 0x0, 0x3f, 0x7c, 0x1f, + 0xfc, 0x3f, 0xc0, 0x77, 0xc0, 0xe3, 0xc1, 0xc0, + 0xc3, 0x80, 0xc3, 0x0, 0xc0, 0x18, 0xc0, 0x18, + 0xc0, 0x18, 0xc0, 0x18, 0xff, 0xf8, 0x7f, 0xf0, + + /* U+F360 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0x3f, 0xe0, 0xff, 0x83, 0xfe, 0xf, 0xf1, 0x3f, + 0x8e, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F362 "" */ + 0x0, 0x18, 0x0, 0x1c, 0x0, 0x1e, 0xff, 0xff, + 0xff, 0xff, 0x0, 0x1e, 0x0, 0x1c, 0x0, 0x18, + 0x18, 0x0, 0x38, 0x0, 0x78, 0x0, 0xff, 0xff, + 0xff, 0xff, 0x78, 0x0, 0x38, 0x0, 0x18, 0x0, + + /* U+F363 "" */ + 0x0, 0x18, 0x0, 0x1c, 0xf, 0xfe, 0x3f, 0xfe, + 0x70, 0x1c, 0x60, 0x18, 0xc0, 0x0, 0xc0, 0x3, + 0xc0, 0x3, 0x0, 0x3, 0x18, 0x6, 0x38, 0xe, + 0x7f, 0xfc, 0x7f, 0xf0, 0x38, 0x0, 0x18, 0x0, + + /* U+F381 "" */ + 0x3, 0xc0, 0x3, 0xf8, 0x0, 0xfe, 0x0, 0x7f, + 0xf8, 0x1f, 0xff, 0x7, 0xcf, 0xc7, 0xf3, 0xf1, + 0xfc, 0xfe, 0xfd, 0x2f, 0xff, 0x83, 0xff, 0xe1, + 0xff, 0xfc, 0xff, 0x7f, 0xff, 0x8f, 0xff, 0xc0, + + /* U+F382 "" */ + 0x7, 0xc0, 0x3, 0xf8, 0x1, 0xff, 0xe0, 0x7f, + 0xfc, 0x1f, 0x3f, 0xf, 0xc7, 0xc7, 0xe0, 0xfb, + 0xf4, 0xbe, 0xff, 0x3f, 0xff, 0xcf, 0xff, 0xf3, + 0xfd, 0xff, 0xff, 0x7f, 0xff, 0x8f, 0xff, 0xc0, + + /* U+F386 "" */ + 0x1, 0xe0, 0x0, 0xfc, 0x0, 0x73, 0x80, 0x18, + 0x60, 0xfe, 0x1f, 0xff, 0x87, 0xf0, 0x61, 0x80, + 0x1c, 0xe0, 0x3, 0xf0, 0x0, 0x78, 0x0, + + /* U+F387 "" */ + 0x70, 0x3, 0xe0, 0xd, 0x80, 0x36, 0x0, 0x70, + 0x0, 0xc0, 0x3, 0x0, 0xe, 0xe, 0x3f, 0xec, + 0xbf, 0xb2, 0x3, 0x88, 0x0, 0xf8, 0x3, 0x60, + 0xf, 0x80, 0x1c, 0x0, + + /* U+F390 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xc0, 0x3, 0xc0, 0x3, + 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x3, + 0xc0, 0x3, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x3, 0xc0, 0x3, 0xc0, 0x1f, 0xf8, + + /* U+F3A0 "" */ + 0x7f, 0xfb, 0xff, 0xfc, 0xfe, 0xf3, 0xfb, 0xff, + 0xff, 0xff, 0xfc, 0xfe, 0xf3, 0xfb, 0xff, 0xff, + 0xff, 0xfc, 0xfe, 0xf3, 0xfb, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F3A5 "" */ + 0xf, 0xf8, 0x1f, 0xf8, 0x3b, 0xdc, 0x7d, 0xbe, + 0x7e, 0x3f, 0xc0, 0x3, 0x7f, 0xfe, 0x3f, 0xfc, + 0x1f, 0xf8, 0xf, 0xf8, 0xf, 0xf0, 0x7, 0xe0, + 0x3, 0xc0, 0x1, 0x80, + + /* U+F3BE "" */ + 0xf8, 0x1f, 0xc3, 0xf8, 0x7f, 0x80, 0xf0, 0x1e, + 0x3, 0xc0, 0x78, 0xf, 0x1, 0xe1, 0xff, 0xbf, + 0xf3, 0xfc, 0x3f, 0x3, 0xc0, 0x30, + + /* U+F3BF "" */ + 0x6, 0x1, 0xe0, 0x7e, 0x1f, 0xe7, 0xfe, 0xff, + 0xc3, 0xc0, 0x78, 0xf, 0x1, 0xe0, 0x3c, 0x7, + 0x8f, 0xf1, 0xfc, 0x3f, 0x87, 0xc0, + + /* U+F3C1 "" */ + 0x0, 0x78, 0x1, 0xf8, 0x7, 0x38, 0xc, 0x30, + 0x18, 0x60, 0x30, 0xdf, 0xf8, 0x7f, 0xf8, 0xff, + 0xf1, 0xff, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0xff, + 0x1f, 0xfe, 0x3f, 0xfc, 0x7f, 0xf8, 0x7f, 0xe0, + + /* U+F3C5 "" */ + 0xf, 0x3, 0xfc, 0x7f, 0xef, 0xfe, 0xf9, 0xff, + 0xf, 0xf0, 0xff, 0x9f, 0x7f, 0xe7, 0xfe, 0x7f, + 0xc3, 0xfc, 0x1f, 0x81, 0xf8, 0xf, 0x0, 0x60, + + /* U+F3C9 "" */ + 0xf, 0x1, 0xf0, 0x1f, 0x81, 0xe0, 0x1f, 0x8d, + 0xe3, 0xde, 0x3d, 0xfb, 0xdf, 0x36, 0xe7, 0x60, + 0x63, 0xc, 0x1f, 0x80, 0x20, 0x2, 0x1, 0xf8, + + /* U+F3CD "" */ + 0x7f, 0xdf, 0xff, 0x1, 0xe0, 0x3c, 0x7, 0x80, + 0xf0, 0x1e, 0x3, 0xc0, 0x78, 0xf, 0x1, 0xe0, + 0x3f, 0xff, 0xf7, 0xfe, 0xfb, 0xfe, + + /* U+F3CE "" */ + 0x7f, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xe3, 0xfc, 0x7b, 0xfe, + + /* U+F3CF "" */ + 0x7f, 0xdf, 0xff, 0x1, 0xe0, 0x3c, 0x7, 0x80, + 0xf0, 0x1e, 0x3, 0xc0, 0x78, 0xf, 0x1, 0xe0, + 0x3f, 0xff, 0xe3, 0xfc, 0x7b, 0xfe, + + /* U+F3D1 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xcf, 0xf3, 0xdc, 0x33, + 0xfb, 0x1f, 0xf9, 0x1f, 0xf9, 0x1f, 0xfb, 0x9f, + 0xdc, 0x3b, 0xcf, 0xf3, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F3DD "" */ + 0x0, 0x0, 0xc7, 0x0, 0x73, 0xc0, 0x39, 0xf8, + 0x1c, 0x7e, 0xe, 0x1f, 0x87, 0x7, 0xe3, 0x81, + 0xf1, 0xc0, 0x3c, 0xe0, 0xf, 0xf0, 0x1, 0xf8, + 0x0, 0x3c, 0x78, 0xe, 0x5f, 0x87, 0x3f, 0xe3, + 0x9f, 0xf9, 0xc3, 0xfc, 0xe0, 0x3e, 0x30, 0x0, + 0x0, + + /* U+F3E0 "" */ + 0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, + 0x9f, 0xf9, 0xff, 0x9f, 0xff, 0xff, 0xf, 0xe0, + 0x7e, 0x7, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xfe, + + /* U+F3E5 "" */ + 0x0, 0x0, 0x6, 0x0, 0xe, 0x0, 0x1e, 0x0, + 0x3f, 0xf8, 0x7f, 0xfc, 0xff, 0xfe, 0x7f, 0xff, + 0x3f, 0xff, 0x1e, 0xf, 0xe, 0x7, 0x6, 0x7, + 0x0, 0x6, 0x0, 0x6, 0x0, 0xc, + + /* U+F3ED "" */ + 0x3, 0x80, 0x1f, 0xc1, 0xfd, 0xf7, 0xf8, 0xff, + 0xf0, 0x7f, 0xe0, 0xff, 0xc1, 0xff, 0x83, 0x7f, + 0xc, 0xfe, 0x19, 0xfc, 0x71, 0xf8, 0xc1, 0xf3, + 0x3, 0xee, 0x1, 0xf0, 0x1, 0xc0, + + /* U+F3FA "" */ + 0x7f, 0xfb, 0xff, 0xfc, 0x0, 0xf0, 0x3, 0xc0, + 0xf, 0x0, 0x3c, 0x0, 0xf0, 0x3, 0xc0, 0xf, + 0x0, 0x3c, 0x0, 0xff, 0xff, 0xfc, 0xff, 0xf3, + 0xff, 0xcf, 0xdf, 0xfe, + + /* U+F3FB "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0xe1, + 0xff, 0xff, 0xdf, 0xfe, + + /* U+F3FD "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3e, 0x7c, 0x7e, 0x7e, + 0x77, 0xfe, 0xff, 0xcf, 0xff, 0x9f, 0xcf, 0x93, + 0xcf, 0x33, 0xfe, 0x3f, 0xfc, 0x3f, 0x7c, 0x3e, + 0x7e, 0x7e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F3FF "" */ + 0x7f, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xdf, 0xff, 0xe7, 0xff, 0xfb, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfd, 0xff, 0xfe, + + /* U+F406 "" */ + 0x7, 0x0, 0xfe, 0x7, 0xf0, 0x3f, 0x81, 0xfc, + 0xf, 0xe0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0xfe, + 0xf, 0xf8, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, + 0xe0, + + /* U+F410 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x9f, + 0xf8, 0x1f, 0xfc, 0x3f, 0xfc, 0x3f, 0xf8, 0x1f, + 0xf9, 0x9f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F422 "" */ + 0x0, 0x3, 0x0, 0x3, 0x80, 0x1b, 0xc0, 0xf, + 0xc0, 0x7, 0xc0, 0x3, 0xe0, 0x1, 0xf8, 0x0, + 0x78, 0x3e, 0x0, 0x1f, 0x80, 0x7, 0xc0, 0x3, + 0xe0, 0x3, 0xf0, 0x3, 0xd8, 0x3, 0xc0, 0x0, + 0xc0, 0x0, + + /* U+F424 "" */ + 0x0, 0x3f, 0x0, 0x1f, 0x0, 0xf, 0x0, 0x1f, + 0x0, 0x3f, 0x0, 0x79, 0x0, 0x20, 0x0, 0x0, + 0x0, 0x0, 0x6, 0x0, 0x8f, 0x0, 0xde, 0x0, + 0xfc, 0x0, 0xf8, 0x0, 0xfc, 0x0, 0xfc, 0x0, + + /* U+F425 "" */ + 0xe0, 0x1, 0xc0, 0x3, 0x80, 0x3, 0xff, 0xf7, + 0xff, 0xef, 0xff, 0x9f, 0xff, 0x3f, 0xfc, 0x7f, + 0xf8, 0xff, 0xf1, 0xff, 0xf3, 0xff, 0xe7, 0xff, + 0xef, 0xff, 0xd0, 0x0, 0x20, 0x0, + + /* U+F432 "" */ + 0x0, 0x3, 0x0, 0x3, 0xe0, 0x1, 0xfc, 0x0, + 0xff, 0x0, 0x7f, 0xc0, 0x3f, 0xe0, 0x1f, 0xf0, + 0x7, 0xf8, 0x3, 0xfc, 0x0, 0xfe, 0x0, 0x7f, + 0x0, 0x1f, 0x0, 0xf, 0x0, 0x7, 0x1, 0xc3, + 0x80, 0xfb, 0xc0, 0x3e, 0xe0, 0xf, 0x98, 0x1, + 0xc0, + + /* U+F433 "" */ + 0x6, 0x60, 0x1e, 0x78, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7d, 0xfe, 0xf9, 0xff, 0xf3, 0xff, 0x3f, 0xfc, + 0x3f, 0xfc, 0xff, 0xcf, 0xff, 0x9f, 0x7f, 0xbe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1e, 0x78, 0x6, 0x60, + + /* U+F434 "" */ + 0x4, 0xe0, 0x1c, 0xf8, 0xc, 0xf0, 0x45, 0xe2, + 0x61, 0xc6, 0xf1, 0x8f, 0x0, 0x1f, 0x1c, 0x3f, + 0xfc, 0x38, 0xf8, 0x0, 0xf1, 0x8f, 0x63, 0x86, + 0x47, 0xa2, 0xf, 0x30, 0x1f, 0x38, 0x7, 0x20, + + /* U+F436 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3e, 0xfc, 0x7e, 0xfe, + 0x73, 0xfe, 0xf2, 0xff, 0xf2, 0xff, 0xfe, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F439 "" */ + 0xc, 0x0, 0x3c, 0x0, 0x30, 0x0, 0x60, 0x7, + 0xf8, 0xf, 0xf0, 0xf, 0xc0, 0x1f, 0x9b, 0x7f, + 0x3e, 0x7e, 0x7c, 0xfd, 0xf9, 0xfb, 0xf3, 0xf7, + 0xc7, 0xe7, 0x9f, 0xe7, 0x7f, 0xef, 0xff, 0xde, + + /* U+F43A "" */ + 0x3f, 0xf, 0xc1, 0xe0, 0xfc, 0x7e, 0x1f, 0x2f, + 0x8f, 0xe7, 0xff, 0xff, 0xf7, 0xf9, 0xfc, 0x3f, + 0x1f, 0xef, 0xff, 0xff, + + /* U+F43C "" */ + 0x7f, 0xfb, 0xff, 0xfc, 0xcc, 0xf3, 0x33, 0xf3, + 0x3f, 0xcc, 0xfc, 0xcc, 0xf3, 0x33, 0xf3, 0x3f, + 0xcc, 0xfc, 0xcc, 0xf3, 0x33, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F43F "" */ + 0x3, 0x0, 0xc, 0x0, 0xfc, 0x3, 0xf0, 0x3, + 0x0, 0xc, 0xf, 0xff, 0xff, 0xff, 0x7f, 0xf9, + 0xff, 0xe3, 0xff, 0xf, 0xfc, 0x1f, 0xe0, 0x7f, + 0x83, 0xff, 0x1f, 0xfe, 0x7f, 0xf8, + + /* U+F441 "" */ + 0x1f, 0x1, 0xfc, 0x3f, 0xe3, 0x3e, 0x73, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x67, 0xf0, 0xff, 0x1f, + 0xf3, 0xfe, 0x3f, 0xe7, 0xfe, 0xff, 0xff, 0xff, + 0xff, 0xf0, + + /* U+F443 "" */ + 0xe, 0x7, 0xe0, 0xfe, 0x1f, 0xc3, 0xf8, 0x7f, + 0xf, 0xe1, 0xfc, 0x1f, 0x3, 0xe0, 0xfe, 0x1f, + 0xc3, 0xf8, 0x7f, 0x1f, 0xf7, 0xff, 0xff, 0xe0, + + /* U+F445 "" */ + 0x1, 0x80, 0x3, 0xc0, 0x1, 0x80, 0x4, 0x20, + 0x4e, 0x72, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8, 0xf, 0xf0, + 0xf, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, + + /* U+F447 "" */ + 0xee, 0x7e, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x7f, 0xe3, 0xfc, 0x3f, 0xc3, 0xfc, 0x3f, + 0xc3, 0xfc, 0x3f, 0xc7, 0xfe, 0xff, 0xff, 0xff, + + /* U+F44B "" */ + 0x1c, 0x3, 0x81, 0xc0, 0x38, 0x7c, 0x3, 0xe7, + 0xc0, 0x3e, 0x7c, 0x3, 0xef, 0xff, 0xff, 0xff, + 0xff, 0xf7, 0xc0, 0x3e, 0x7c, 0x3, 0xe7, 0xc0, + 0x3e, 0x1c, 0x3, 0x81, 0xc0, 0x38, + + /* U+F44E "" */ + 0x0, 0x30, 0x3, 0x9e, 0xf, 0xce, 0x1f, 0xe7, + 0x3f, 0x33, 0x3f, 0x19, 0x7c, 0x8c, 0x7c, 0x4e, + 0x72, 0x3e, 0x31, 0x3e, 0x98, 0xfc, 0xcc, 0xfc, + 0xe7, 0xf8, 0x73, 0xf0, 0x79, 0xc0, 0x14, 0x0, + + /* U+F450 "" */ + 0xf, 0x81, 0xfe, 0x1f, 0xf9, 0xff, 0xef, 0xff, + 0x7e, 0xdb, 0xfc, 0xdf, 0xfe, 0x7d, 0xf3, 0xff, + 0x0, 0x0, 0x0, 0x3, 0xfe, 0x3, 0x0, 0x18, + 0x0, 0xc0, 0x0, 0x0, + + /* U+F453 "" */ + 0xf, 0xf0, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xfe, 0x1f, 0xf0, 0x80, 0x1, 0xf0, 0xf, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0x1f, 0xf0, + + /* U+F458 "" */ + 0x0, 0x0, 0xc0, 0x0, 0x70, 0x0, 0x38, 0x0, + 0x1c, 0x0, 0xe, 0x0, 0x37, 0x0, 0xf, 0x80, + 0x19, 0xc0, 0x1f, 0x38, 0x7, 0xe6, 0x3, 0xfc, + 0x0, 0xbf, 0x80, 0x1f, 0xe3, 0x8f, 0xf1, 0xf7, + 0xf8, 0x7f, 0xfc, 0x1f, 0x0, 0x3, 0x80, + + /* U+F45C "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F45D "" */ + 0x1, 0xf0, 0x7, 0xfc, 0xf, 0xfe, 0x1f, 0xfe, + 0xf, 0xff, 0x27, 0xff, 0x73, 0xff, 0x79, 0xff, + 0x7c, 0xff, 0x3e, 0x42, 0x3f, 0x0, 0x3f, 0xbc, + 0x7f, 0x7e, 0xff, 0x7e, 0xf1, 0x7e, 0x60, 0x7e, + 0x0, 0x3c, + + /* U+F45F "" */ + 0x3, 0x0, 0x13, 0x90, 0x39, 0xcc, 0x71, 0xe4, + 0x64, 0xe6, 0xce, 0xf3, 0x9e, 0xf3, 0xbe, 0xf3, + 0x3c, 0x2, 0x39, 0x80, 0x73, 0xfe, 0x67, 0xfe, + 0x61, 0xf8, 0x2c, 0x0, 0xf, 0xf8, 0x7, 0xe0, + + /* U+F461 "" */ + 0x1, 0x80, 0x1b, 0x60, 0x36, 0xc0, 0x6d, 0xb0, + 0xdb, 0x61, 0xb6, 0xc3, 0x6d, 0x86, 0xdb, 0x4f, + 0xff, 0xdf, 0x3f, 0xf3, 0xfb, 0xff, 0x33, 0xff, + 0xe3, 0xef, 0x81, 0xfe, 0x1, 0xf8, + + /* U+F462 "" */ + 0x77, 0xfb, 0xbd, 0xfe, 0xff, 0x7f, 0xbf, 0xdb, + 0x6f, 0xf7, 0xfb, 0xfd, 0xfe, 0xff, 0x6d, 0xbf, + 0xdf, 0xef, 0xf7, 0xfb, 0xdd, 0xfe, 0xe0, + + /* U+F466 "" */ + 0x3f, 0xf1, 0xff, 0xe6, 0x1, 0xbf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F468 "" */ + 0x2, 0x40, 0xe, 0x70, 0xe, 0x70, 0xf, 0xf0, + 0xf, 0xf0, 0xf, 0xf0, 0xf, 0xf0, 0x0, 0x0, + 0x24, 0x24, 0xe7, 0x67, 0xe7, 0x67, 0xff, 0x7f, + 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x7e, 0x7e, + + /* U+F469 "" */ + 0x7, 0xe0, 0x6, 0x18, 0x3, 0xc, 0xf, 0xff, + 0xef, 0xff, 0xf7, 0xff, 0xff, 0xf9, 0xff, 0xfc, + 0xff, 0xf8, 0x1f, 0xfc, 0xf, 0xff, 0x9f, 0xff, + 0xcf, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xfc, + + /* U+F46A "" */ + 0x6, 0x0, 0xf0, 0x1f, 0x81, 0xf8, 0x3f, 0xc7, + 0xfe, 0x79, 0xe7, 0x1e, 0xf0, 0xfe, 0x7, 0xe0, + 0x7e, 0x7, 0x60, 0x67, 0xe, 0x3f, 0xc0, 0xf0, + + /* U+F46B "" */ + 0x38, 0x0, 0x3e, 0x0, 0x31, 0x80, 0x18, 0xdc, + 0xc, 0x7f, 0x6, 0x38, 0xc3, 0x1c, 0x71, 0xfe, + 0x18, 0xff, 0x86, 0x7f, 0xe7, 0xbf, 0xbf, 0xdf, + 0xcf, 0xff, 0xe7, 0xff, 0xf1, 0xfd, 0xf0, 0x7c, + 0x70, 0x1c, 0x0, 0x0, 0x0, + + /* U+F46C "" */ + 0x1f, 0x87, 0xfe, 0xf0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe7, 0xfc, 0xfe, 0xcf, 0xe1, + 0xff, 0x3f, 0xf3, 0xff, 0xff, 0xff, 0xf7, 0xfe, + + /* U+F46D "" */ + 0x1f, 0x87, 0xfe, 0xf0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x83, 0xc8, 0x3f, 0xff, 0xff, + 0xfc, 0x83, 0xc8, 0x3f, 0xff, 0xff, 0xf7, 0xfe, + + /* U+F470 "" */ + 0x1, 0x80, 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, + 0x0, 0x0, 0x30, 0x0, 0x7, 0xe0, 0xf, 0xf0, + 0x1f, 0xbb, 0x3f, 0xfc, 0x37, 0xec, 0x36, 0xec, + 0x37, 0xec, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + + /* U+F471 "" */ + 0xc0, 0x3f, 0xff, 0xff, 0xf6, 0x6, 0x7f, 0xe3, + 0xfc, 0x1f, 0x80, 0xf0, 0xf, 0x1, 0xf8, 0x3f, + 0xc7, 0xfe, 0xe0, 0x6f, 0xff, 0xff, 0xfc, 0x3, + + /* U+F472 "" */ + 0xf8, 0x0, 0x3f, 0x0, 0x1, 0xc3, 0xc0, 0x33, + 0xf8, 0xe, 0xfe, 0x1, 0xbf, 0x80, 0x6f, 0xf0, + 0x1d, 0xfc, 0x3, 0x7c, 0x0, 0xc8, 0xf0, 0x3d, + 0xf8, 0x1d, 0xf8, 0x6, 0x30, 0x1, 0x88, 0x0, + 0x76, 0x0, 0xf, 0x0, + + /* U+F474 "" */ + 0xe0, 0x0, 0x3c, 0x0, 0x3, 0x7f, 0xf0, 0xdf, + 0xfc, 0x37, 0xff, 0xd, 0xff, 0xc3, 0x7f, 0xf0, + 0xdf, 0xfc, 0x37, 0xff, 0xd, 0xff, 0xc3, 0x0, + 0x0, 0xc0, 0x0, 0x3f, 0xff, 0xc7, 0xff, 0xf0, + 0xe0, 0x70, 0x30, 0x1c, + + /* U+F477 "" */ + 0x3e, 0xf, 0xf8, 0xff, 0xcf, 0xce, 0xfc, 0x7f, + 0xc3, 0xff, 0xff, 0xff, 0xf9, 0xff, 0x9f, 0xe0, + 0x7e, 0x7, 0xf9, 0xff, 0x9f, 0xff, 0xf7, 0xfe, + + /* U+F478 "" */ + 0x3e, 0xf, 0xf8, 0xff, 0xcf, 0xee, 0xfe, 0x7f, + 0xe3, 0xff, 0xff, 0xff, 0xfd, 0xff, 0x8f, 0x12, + 0x3c, 0x3f, 0xe7, 0xff, 0xff, 0xff, 0xf7, 0xfe, + + /* U+F479 "" */ + 0x6f, 0xf6, 0xef, 0xf7, 0xef, 0xf7, 0xee, 0x77, + 0xee, 0x77, 0xe8, 0x17, 0xe8, 0x17, 0xee, 0x77, + 0xee, 0x77, 0xef, 0xf7, 0xef, 0xf7, 0x6f, 0xf6, + + /* U+F47D "" */ + 0x7, 0xf8, 0x3, 0xff, 0x0, 0xff, 0xc0, 0x3c, + 0xf0, 0x7f, 0x3f, 0xbf, 0xcf, 0xfc, 0xf3, 0xcf, + 0x3f, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, + 0xcf, 0x3c, 0xf3, 0xfe, 0x1f, 0xff, 0x87, 0xff, + 0xe1, 0xfd, 0xff, 0xfe, + + /* U+F47E "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7b, 0x9e, 0xfb, 0x9f, 0xfb, 0x9f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xfb, 0x9f, 0xfb, 0x9f, 0x7b, 0x9e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F47F "" */ + 0x1, 0xe0, 0x0, 0x78, 0x7, 0xde, 0xfb, 0xf0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, + 0xfc, 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xff, 0xff, + 0xff, 0xf8, 0x7f, 0xfc, 0xf, 0xff, 0x3, 0xff, + 0xff, 0xfd, 0xff, 0xfe, + + /* U+F481 "" */ + 0xf, 0xfe, 0x1f, 0xff, 0x1f, 0xff, 0x1f, 0xbf, + 0xdf, 0xbf, 0xde, 0xf, 0xde, 0xf, 0xde, 0xf, + 0xdf, 0xbf, 0xdf, 0xbe, 0xdf, 0xfc, 0xdf, 0xf8, + 0xcf, 0xf0, 0xc0, 0x0, 0xc0, 0x0, 0x7f, 0xf0, + + /* U+F482 "" */ + 0xff, 0xff, 0xff, 0xff, 0xf3, 0xc, 0x30, 0xc3, + 0xc, 0xff, 0xff, 0xff, 0xff, 0xf0, + + /* U+F484 "" */ + 0x38, 0x0, 0x7c, 0x0, 0xc6, 0x0, 0xc6, 0x0, + 0xc6, 0x0, 0xc6, 0x0, 0xc6, 0x0, 0xfe, 0x7c, + 0xfc, 0xf8, 0xfd, 0xf1, 0xfd, 0xe3, 0xfd, 0xc7, + 0xfd, 0x8f, 0xfd, 0x1f, 0x7c, 0x3e, 0x38, 0x7c, + + /* U+F485 "" */ + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x7, + 0xfe, 0x7f, 0xe0, 0xfe, 0x7f, 0xe0, 0xfe, 0xf, + 0xe7, 0xfe, 0xf, 0xe0, 0xfe, 0x7f, 0xe3, 0xfc, + + /* U+F486 "" */ + 0xff, 0xff, 0xff, 0xff, 0xf0, 0x0, 0x7f, 0xe7, + 0xfe, 0x7f, 0xe7, 0x9e, 0x79, 0xe6, 0x6, 0x60, + 0x67, 0x9e, 0x79, 0xe7, 0xfe, 0x7f, 0xe3, 0xfc, + + /* U+F487 "" */ + 0x0, 0x3, 0x0, 0x0, 0x73, 0x1, 0xdf, 0xe0, + 0x1f, 0x6c, 0x0, 0xc1, 0x98, 0xd3, 0x37, 0x98, + 0x76, 0xf3, 0xfe, 0xcc, 0x7f, 0xd8, 0xf, 0xfb, + 0xff, 0xff, 0x7f, 0xff, 0xef, 0xff, 0xfd, 0x80, + 0x1, 0xb0, 0x0, 0x36, 0x0, 0x6, + + /* U+F48B "" */ + 0x1f, 0xfc, 0x3, 0xff, 0xe0, 0xff, 0xfe, 0x0, + 0x1f, 0xfc, 0x1, 0xff, 0xef, 0xff, 0xe7, 0x7, + 0xfe, 0x30, 0x7f, 0xe3, 0xff, 0xff, 0xf7, 0xff, + 0xff, 0x3f, 0xff, 0xf3, 0xcf, 0xcf, 0x3c, 0xfc, + 0xf0, 0xc8, 0x4c, 0x7, 0x87, 0x80, + + /* U+F48D "" */ + 0x0, 0x12, 0x0, 0xd, 0x80, 0x1, 0x20, 0x0, + 0x6c, 0x0, 0x19, 0x80, 0x3, 0x30, 0x0, 0x6c, + 0x0, 0x1b, 0x0, 0x2, 0xc0, 0x0, 0x0, 0x0, + 0x1, 0xff, 0xfb, 0xff, 0xfe, 0xff, 0xf1, 0xbf, + 0xff, 0xed, 0xff, 0xfb, + + /* U+F48E "" */ + 0x0, 0x6, 0x0, 0x31, 0x80, 0xd, 0xe0, 0xf, + 0x80, 0xf, 0xc0, 0x7, 0xf0, 0x9, 0xfc, 0xf, + 0xf8, 0x3, 0xf8, 0xd, 0xf8, 0xf, 0xf8, 0x7, + 0xf8, 0x3, 0xf0, 0x1, 0xf0, 0x1, 0x80, 0x1, + 0x80, 0x0, + + /* U+F490 "" */ + 0x0, 0x7c, 0x0, 0xf8, 0x1, 0xf0, 0x1, 0xe3, + 0x1, 0xc7, 0x1, 0x8f, 0x1, 0x1f, 0x3c, 0x3e, + 0x7e, 0x7c, 0x7f, 0x0, 0xff, 0x80, 0x0, 0x0, + 0xff, 0x80, 0x7f, 0x0, 0x7f, 0x0, 0x1c, 0x0, + + /* U+F491 "" */ + 0x0, 0xc, 0x0, 0x1e, 0x0, 0xf, 0x0, 0xcf, + 0x0, 0x7f, 0x2, 0x7e, 0x3, 0xfc, 0x1, 0xf8, + 0x19, 0xf0, 0x1f, 0xe0, 0x1f, 0xc0, 0x1f, 0x80, + 0x1f, 0x0, 0x30, 0x0, 0x60, 0x0, 0xc0, 0x0, + + /* U+F492 "" */ + 0x0, 0x60, 0x0, 0x70, 0x0, 0x78, 0x0, 0xfc, + 0x1, 0xce, 0x3, 0x8f, 0x7, 0x1f, 0xe, 0x38, + 0x1f, 0xf0, 0x3f, 0xe0, 0x7f, 0xc0, 0xff, 0x80, + 0xff, 0x0, 0xfe, 0x0, 0xfc, 0x0, 0x78, 0x0, + + /* U+F493 "" */ + 0xff, 0xff, 0xff, 0xff, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, + 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x3c, + + /* U+F494 "" */ + 0x1, 0xe0, 0x3, 0xff, 0x7, 0xff, 0xfb, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0, 0xf, + 0x0, 0x3, 0xdf, 0xfc, 0xf7, 0xff, 0x3c, 0x0, + 0xf, 0x7f, 0xf3, 0xdf, 0xfc, 0xf0, 0x0, 0x3d, + 0xff, 0xcf, 0x7f, 0xf3, + + /* U+F496 "" */ + 0x7, 0x81, 0xf3, 0xef, 0x87, 0xfc, 0xf, 0xf1, + 0xbf, 0xc4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F497 "" */ + 0xff, 0xff, 0xff, 0xff, 0x7e, 0x7e, 0x78, 0x1e, + 0x78, 0x1e, 0x7e, 0x7e, 0x70, 0xe, 0x70, 0xe, + 0x7e, 0x7e, 0x78, 0x1e, 0x78, 0x1e, 0x7e, 0x7e, + 0xff, 0xff, 0xff, 0xff, + + /* U+F49E "" */ + 0x0, 0xe0, 0x0, 0x7f, 0x0, 0x3e, 0xf8, 0xf, + 0x7, 0x83, 0x80, 0x38, 0xfc, 0x1f, 0x9b, 0xc7, + 0xff, 0x1f, 0xfe, 0x78, 0xff, 0x87, 0xdf, 0xf0, + 0xff, 0xfe, 0x1f, 0xff, 0xc1, 0xff, 0xf0, 0xf, + 0xf8, 0x0, 0xfe, 0x0, 0x7, 0x0, + + /* U+F4A1 "" */ + 0x2, 0x40, 0xe, 0x70, 0xe, 0x70, 0xf, 0xf0, + 0xf, 0xf0, 0xf, 0xf0, 0xf, 0xf0, 0x0, 0x0, + 0x24, 0x24, 0xe7, 0x67, 0xe7, 0x67, 0xff, 0x7f, + 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x7e, 0x7e, + + /* U+F4AD "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xe6, 0x67, 0xe6, 0x67, + 0xe6, 0x67, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfe, + 0x7f, 0xfc, 0x7f, 0xf8, 0xff, 0xe0, 0xc0, 0x0, + + /* U+F4B3 "" */ + 0xc0, 0x0, 0x38, 0xfc, 0x7, 0xff, 0xc0, 0xff, + 0xf8, 0x1f, 0xff, 0x3, 0xff, 0xc6, 0x7f, 0xf9, + 0xcf, 0xfe, 0x79, 0xff, 0x9f, 0x3f, 0xe7, 0xe7, + 0xf8, 0xfc, 0xfc, 0x3f, 0x9f, 0xf, 0xf3, 0x83, + 0xfe, 0x71, 0xef, 0xce, 0x60, 0x1, 0xc0, 0x0, + 0x30, + + /* U+F4B8 "" */ + 0xf, 0xff, 0x1, 0xff, 0xf8, 0x3f, 0xff, 0xc3, + 0xff, 0xfc, 0x1f, 0xff, 0xe, 0xff, 0xe7, 0xef, + 0xfe, 0x7e, 0x0, 0x7, 0xe0, 0x0, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xf7, 0xff, 0xfe, + + /* U+F4B9 "" */ + 0x1, 0xe0, 0x1, 0xfe, 0x0, 0xfb, 0xc0, 0x38, + 0xf0, 0x1e, 0x3e, 0x7, 0xbf, 0x81, 0xf1, 0xe0, + 0x7f, 0x78, 0xe, 0x1c, 0x3, 0xef, 0xc, 0x7f, + 0x8f, 0x7, 0x83, 0xc0, 0x0, 0xf0, 0x0, 0x37, + 0xff, 0xf8, + + /* U+F4BA "" */ + 0x0, 0x0, 0x6, 0x0, 0x7, 0x0, 0x47, 0x1f, + 0x71, 0xbe, 0x7c, 0x36, 0x7f, 0x3e, 0x7f, 0xbe, + 0x7f, 0xfe, 0x3f, 0xfe, 0x1f, 0xfe, 0xf, 0xfe, + 0xf, 0xfe, 0x7f, 0xfc, 0x7f, 0xf8, 0x3e, 0x0, + 0x8, 0x0, + + /* U+F4BD "" */ + 0xf, 0xf0, 0xc7, 0xfc, 0xff, 0xf0, 0x7b, 0xff, + 0xfc, 0xff, 0xfc, 0x3f, 0xfe, 0x0, + + /* U+F4BE "" */ + 0x0, 0x0, 0x1, 0xfe, 0x0, 0x7f, 0x80, 0x1f, + 0xe0, 0x7, 0xf8, 0x0, 0xfc, 0x0, 0x3e, 0x0, + 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, + 0xc, 0x7f, 0xcf, 0xff, 0x7, 0xbf, 0xff, 0xcf, + 0xff, 0xc3, 0xff, 0xe0, + + /* U+F4C0 "" */ + 0x0, 0xc0, 0x0, 0x78, 0x0, 0x3e, 0x0, 0xc, + 0x0, 0x3, 0xe0, 0x0, 0x1c, 0x0, 0x1f, 0x0, + 0x7, 0x80, 0x0, 0xc0, 0x0, 0x0, 0x0, 0xff, + 0xc, 0x7f, 0xcf, 0xff, 0x7, 0xbf, 0xff, 0xcf, + 0xff, 0xe3, 0xff, 0xe0, + + /* U+F4C1 "" */ + 0x0, 0xc0, 0x0, 0x30, 0x0, 0x1e, 0x0, 0xf, + 0xc0, 0x3, 0xf0, 0x0, 0xfc, 0x0, 0x3f, 0x0, + 0x7, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, + 0xc, 0x7f, 0xcf, 0xff, 0x7, 0xbf, 0xff, 0xcf, + 0xff, 0xc3, 0xff, 0xe0, + + /* U+F4C2 "" */ + 0xe0, 0x0, 0x7e, 0x0, 0x7, 0xe0, 0x0, 0x7e, + 0x0, 0x7, 0xe0, 0x0, 0x7e, 0x40, 0x37, 0xe6, + 0x7, 0x7e, 0x70, 0x67, 0xf3, 0x8e, 0xff, 0xb8, + 0xff, 0x7f, 0xdf, 0xe3, 0xfd, 0xfc, 0x1f, 0xdf, + 0x80, 0xf8, 0xf0, + + /* U+F4C4 "" */ + 0x0, 0x0, 0x43, 0x9f, 0x71, 0xcf, 0xfc, 0xe7, + 0xff, 0x73, 0xff, 0xf9, 0xe7, 0xfe, 0x30, 0xff, + 0xc1, 0x9f, 0xfd, 0xf3, 0xff, 0xfe, 0x7f, 0xff, + 0x83, 0xff, 0xf0, 0xff, 0xe4, 0x3c, 0xcc, 0xe, + 0x0, 0x3, 0x0, 0x0, 0x80, 0x0, 0x0, + + /* U+F4C6 "" */ + 0x80, 0x0, 0x7b, 0x9f, 0x7f, 0xcf, 0xff, 0xe7, + 0xff, 0xf3, 0xff, 0xf9, 0xe7, 0xfe, 0x30, 0xff, + 0xc1, 0x9f, 0xfd, 0xf3, 0xff, 0xfe, 0x73, 0xff, + 0xc0, 0x7f, 0xf0, 0xf, 0xe4, 0x0, 0xcc, 0x0, + + /* U+F4CD "" */ + 0x9, 0x90, 0x13, 0xc8, 0x37, 0xec, 0x67, 0xee, + 0x6f, 0xf6, 0xef, 0xf7, 0xef, 0xf7, 0xef, 0xf7, + 0x71, 0x8e, 0x39, 0x9c, 0x1d, 0xb8, 0xf, 0xf0, + 0x7, 0xe0, 0x7, 0xe0, 0x7, 0xe0, 0x7, 0xe0, + 0x3, 0xc0, + + /* U+F4CE "" */ + 0x70, 0x1, 0xbc, 0x0, 0xf7, 0x0, 0x18, 0x0, + 0x0, 0x3, 0xf0, 0x18, 0xfc, 0x7f, 0x3f, 0x3f, + 0xef, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, + 0x3d, 0xc0, 0xf, 0x38, 0x7, 0x27, 0x3, 0x8c, + 0xc0, 0xcf, 0x30, 0x33, 0xcc, 0xc, 0xf3, 0x3, + 0x30, + + /* U+F4D3 "" */ + 0x3, 0x80, 0x7, 0xc0, 0x7, 0xc0, 0x7, 0xc0, + 0x7, 0xc4, 0x13, 0x9c, 0x38, 0x3c, 0x7f, 0xfc, + 0xff, 0xfe, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, 0x3f, 0xf8, 0x1f, 0xf0, + 0x1c, 0x70, + + /* U+F4D6 "" */ + 0x1f, 0x7, 0xf1, 0xff, 0x7f, 0xef, 0x1f, 0xf7, + 0xdf, 0xf3, 0xfe, 0x3f, 0x83, 0xe0, 0x3e, 0x33, + 0xee, 0x7f, 0xe7, 0xcc, 0x60, 0x0, + + /* U+F4D7 "" */ + 0x0, 0x1e, 0x0, 0x3f, 0x0, 0x33, 0x0, 0x33, + 0x0, 0x3f, 0x0, 0x1e, 0x0, 0xfc, 0x1, 0xfc, + 0x79, 0x80, 0xfd, 0x80, 0xcd, 0xfe, 0xcc, 0xff, + 0xfc, 0x3, 0x78, 0x3, 0x33, 0xff, 0x7, 0xfe, + + /* U+F4D8 "" */ + 0x0, 0x1f, 0x0, 0x3f, 0xf8, 0x7f, 0xfc, 0xff, + 0xfe, 0x7e, 0xff, 0x3c, 0x7f, 0xb8, 0x3f, 0xb0, + 0x1f, 0x80, 0xf, 0x80, 0x1, 0x80, 0x1, 0x80, + 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, + + /* U+F4D9 "" */ + 0x30, 0x0, 0x30, 0x0, 0xff, 0xff, 0xff, 0xff, + 0x30, 0x0, 0x33, 0xfc, 0x37, 0xfe, 0x37, 0xfe, + 0x37, 0xfe, 0x37, 0xfe, 0x37, 0xfe, 0x33, 0xfe, + 0x30, 0x0, 0x30, 0x0, 0x30, 0x0, 0x30, 0x0, + + /* U+F4DA "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0x87, 0xf9, 0xb7, 0xff, 0xff, + 0xff, 0xff, 0xf7, 0xef, 0xf3, 0xcf, 0x78, 0x1e, + 0x7c, 0x3e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F4DB "" */ + 0xf, 0xc0, 0xf, 0xf0, 0xf, 0xfc, 0xf, 0xff, + 0xf, 0x87, 0xc7, 0x81, 0xe3, 0xdc, 0xf1, 0xee, + 0x78, 0xf7, 0x3c, 0x7c, 0x3e, 0x1f, 0xfe, 0x7, + 0xfe, 0x1, 0xff, 0xf8, 0x7f, 0xfc, + + /* U+F4DE "" */ + 0x0, 0xf, 0xe0, 0x3, 0xfc, 0x0, 0xff, 0x8f, + 0x9f, 0xf3, 0xf3, 0xfe, 0xfe, 0x7f, 0xdf, 0xef, + 0xf9, 0xfd, 0xff, 0x3f, 0xbf, 0xe7, 0xe7, 0xfc, + 0x0, 0xff, 0x81, 0xff, 0x31, 0xfc, 0x66, 0xfc, + 0xc, 0xd8, 0x0, 0xf0, + + /* U+F4DF "" */ + 0x7f, 0xfc, 0xf, 0xff, 0xe0, 0xff, 0xfe, 0xf, + 0xff, 0xfc, 0xff, 0xff, 0xef, 0xff, 0xe7, 0xff, + 0xfe, 0x3f, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0xcf, 0xe7, 0xcc, 0xfe, + 0x6c, 0xc8, 0x26, 0x7f, 0x1, 0xc0, + + /* U+F4E2 "" */ + 0xc0, 0x0, 0x38, 0x0, 0x7, 0x0, 0x0, 0xff, + 0xc0, 0x1f, 0xf8, 0x13, 0xfe, 0x76, 0x7f, 0xbd, + 0xcf, 0xef, 0x79, 0xfb, 0xdf, 0x3e, 0xf7, 0xe7, + 0xbd, 0xfc, 0xef, 0x7f, 0x9d, 0xdf, 0xf3, 0x83, + 0xfe, 0x70, 0x0, 0xe, 0x0, 0x1, 0xc0, 0x0, + 0x30, + + /* U+F4E3 "" */ + 0x7f, 0x9f, 0xe6, 0x1b, 0x87, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xdf, 0xe3, 0xf0, 0x30, 0xc, + 0x3, 0x0, 0xc1, 0xfe, 0x7f, 0x80, + + /* U+F4E6 "" */ + 0xe0, 0x1, 0xc0, 0x3, 0x80, 0x3, 0xff, 0xf7, + 0xff, 0xef, 0xff, 0x9f, 0xff, 0x3f, 0xfc, 0x7f, + 0xf8, 0xff, 0xf1, 0xff, 0xf3, 0xff, 0xe7, 0xff, + 0xef, 0xff, 0xd0, 0x0, 0x20, 0x0, + + /* U+F4FA "" */ + 0xc0, 0x0, 0x18, 0x78, 0x3, 0x3f, 0x0, 0x7f, + 0xe0, 0xf, 0xf8, 0x1, 0xfe, 0x0, 0x3f, 0x0, + 0x7, 0xc0, 0x0, 0xc0, 0x0, 0x18, 0x0, 0x3, + 0x0, 0x1c, 0x60, 0xf, 0x8c, 0x7, 0xf3, 0x81, + 0xfe, 0x70, 0x7f, 0xce, 0x1f, 0xf9, 0xc0, 0x0, + 0x30, + + /* U+F4FB "" */ + 0x0, 0x0, 0x3f, 0x3, 0xfe, 0xf, 0xfc, 0x70, + 0x79, 0x80, 0xe7, 0xe3, 0x98, 0xe, 0x30, 0x30, + 0xe1, 0x81, 0xfc, 0x1, 0xe0, 0x30, 0x21, 0xff, + 0xce, 0x3, 0xb8, 0x6, 0xed, 0x98, + + /* U+F4FC "" */ + 0x7, 0x0, 0x7, 0xf0, 0x1, 0xfc, 0x4, 0x7f, + 0x3, 0x1f, 0xc1, 0x87, 0xf0, 0x60, 0x70, 0xf0, + 0x0, 0x18, 0x0, 0x0, 0x7, 0xf0, 0x3, 0xfe, + 0x1, 0xff, 0xc0, 0xff, 0xf8, 0x3f, 0xfe, 0xf, + 0xff, 0x80, + + /* U+F4FD "" */ + 0x6, 0x0, 0x3, 0xe0, 0x1, 0xf8, 0x0, 0x7f, + 0x0, 0x1f, 0xc0, 0x7, 0xe0, 0x0, 0xf8, 0x0, + 0x18, 0x0, 0x0, 0x1e, 0x0, 0xdf, 0xe3, 0xef, + 0x79, 0xfb, 0xdf, 0x7e, 0xf1, 0xff, 0xbf, 0xff, + 0xef, 0xfb, 0xfd, 0xfe, 0x0, 0x1e, 0x0, + + /* U+F4FE "" */ + 0x3, 0x0, 0x3, 0xf0, 0x1, 0xfe, 0x0, 0x7f, + 0x80, 0x1f, 0xe0, 0x7, 0xf8, 0x0, 0xfc, 0x0, + 0xc, 0x38, 0x0, 0xe, 0x1, 0xcf, 0xf1, 0xf7, + 0x1c, 0xfc, 0xc6, 0x7f, 0x31, 0xdf, 0xdc, 0x77, + 0xf3, 0xfd, 0xfe, 0x38, 0x0, 0xe, 0x0, + + /* U+F4FF "" */ + 0x7, 0x80, 0x1, 0xf8, 0x0, 0x7f, 0x80, 0xf, + 0xf0, 0x1, 0xfe, 0x0, 0x1f, 0x80, 0x1, 0xe0, + 0x0, 0x0, 0x6, 0x0, 0x1, 0xc1, 0xfc, 0x18, + 0xff, 0x19, 0x3f, 0xcf, 0x87, 0xf3, 0xe1, 0xfe, + 0x78, 0x3f, 0xde, 0x0, 0x3, 0x80, + + /* U+F500 "" */ + 0x4, 0x0, 0x7, 0xc0, 0x3, 0xf8, 0xe0, 0xfe, + 0x7c, 0x3f, 0x9f, 0xf, 0xe7, 0xc1, 0xf1, 0xf0, + 0x10, 0x38, 0x0, 0x0, 0x7, 0xc1, 0x3, 0xf9, + 0xf1, 0xff, 0x3e, 0xff, 0xef, 0xff, 0xfb, 0xff, + 0xfe, 0xff, 0xff, 0xbf, + + /* U+F501 "" */ + 0xf, 0xc3, 0xff, 0xff, 0xff, 0xf7, 0xf8, 0xdf, + 0xe3, 0x61, 0x8c, 0x84, 0x1, 0xe0, 0x0, 0x0, + 0x40, 0x3, 0xcf, 0x1f, 0x3e, 0x7c, 0xf9, 0xf3, + 0xef, 0xcf, 0xdf, 0x3e, + + /* U+F502 "" */ + 0x7, 0x0, 0x7, 0xc0, 0x7, 0xf0, 0x3, 0xf8, + 0x1, 0xfc, 0x0, 0xfc, 0x0, 0x3e, 0x0, 0x4, + 0x1c, 0x0, 0x1b, 0x7, 0xcd, 0x8f, 0xe6, 0xcf, + 0xf7, 0xff, 0xfb, 0xff, 0xfd, 0xff, 0xfe, 0xff, + 0xff, 0x7f, 0x0, 0x3f, 0x80, + + /* U+F503 "" */ + 0x7, 0x0, 0x3, 0xf8, 0x0, 0x7f, 0x0, 0xf, + 0xe0, 0x1, 0xfc, 0x0, 0x3f, 0x8f, 0x81, 0xc0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8, 0x0, + 0xff, 0x80, 0x3f, 0xf8, 0xf, 0xff, 0x81, 0xff, + 0xf0, 0x3f, 0xfe, 0x0, + + /* U+F504 "" */ + 0x7, 0x6, 0xfe, 0x1f, 0xf0, 0x60, 0x87, 0xfc, + 0x6f, 0xe0, 0x7f, 0x0, 0xf0, 0x0, 0x0, 0x0, + 0x4, 0x10, 0xfb, 0xe7, 0xff, 0x7f, 0xff, 0xff, + 0xff, 0xff, + + /* U+F505 "" */ + 0x7, 0x0, 0x3, 0xe0, 0x1, 0xfc, 0x0, 0x7f, + 0x0, 0x1f, 0xc0, 0x7, 0xf0, 0x0, 0xf8, 0x0, + 0x18, 0x0, 0x0, 0x1e, 0x1, 0xbf, 0xf3, 0xef, + 0x8d, 0xfb, 0xe3, 0x7e, 0xf8, 0xff, 0xbe, 0x6f, + 0xf7, 0x9b, 0xfc, 0xfc, 0x0, 0xc, 0x0, + + /* U+F506 "" */ + 0xc0, 0x0, 0x18, 0x78, 0x3, 0x3f, 0x0, 0x7f, + 0xe0, 0xf, 0xf8, 0x1, 0xfe, 0x0, 0x3f, 0x0, + 0x7, 0xc0, 0x0, 0xc0, 0x0, 0x18, 0x0, 0x3, + 0x0, 0x1c, 0x60, 0xf, 0x8c, 0x7, 0xf3, 0x81, + 0xfe, 0x70, 0x7f, 0xce, 0x1f, 0xf9, 0xc0, 0x0, + 0x30, + + /* U+F507 "" */ + 0xf, 0x0, 0x7, 0xe0, 0x1, 0xfc, 0x0, 0x7f, + 0x0, 0x1f, 0xc0, 0x7, 0xe0, 0x0, 0xf0, 0x0, + 0x0, 0x0, 0x0, 0x38, 0x7, 0xdf, 0x83, 0xf6, + 0xf1, 0xfd, 0xfe, 0xff, 0x7f, 0xff, 0xcf, 0xef, + 0xf9, 0xf0, 0x0, 0x38, 0x0, 0x0, 0x0, + + /* U+F508 "" */ + 0x7, 0x0, 0xfe, 0x7, 0xf0, 0x3f, 0x81, 0xfc, + 0xf, 0xe0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0xba, + 0xc, 0x98, 0xe4, 0xef, 0xff, 0xff, 0xff, 0xff, + 0xe0, + + /* U+F509 "" */ + 0x0, 0xf0, 0x0, 0x1f, 0x80, 0x39, 0xf8, 0xe7, + 0x9f, 0x9e, 0x79, 0xf9, 0xe3, 0x6, 0xe, 0x0, + 0x0, 0x0, 0xf, 0x0, 0x39, 0xf1, 0x87, 0x3e, + 0x7f, 0xf7, 0xef, 0xff, 0x7e, 0x66, 0xf7, 0xe6, + 0x7f, 0x7e, 0xff, 0x0, 0x7, 0xc0, 0x0, 0x18, + + /* U+F515 "" */ + 0x0, 0xf3, 0x80, 0xf, 0xf8, 0x1, 0xfe, 0x0, + 0x7f, 0x18, 0x1f, 0xf1, 0x81, 0xc6, 0x3c, 0x0, + 0x66, 0x41, 0x86, 0x66, 0x18, 0x6c, 0x33, 0xc6, + 0xff, 0x66, 0x6f, 0xe6, 0x66, 0x3c, 0xc3, 0x60, + 0xf, 0xf6, 0x0, 0xfe, 0x7f, 0x83, 0xc7, 0xf8, + + /* U+F516 "" */ + 0x1c, 0x60, 0x1, 0xff, 0x0, 0x7, 0xf8, 0x1, + 0x8f, 0xe0, 0x18, 0xff, 0x83, 0xc6, 0x38, 0x66, + 0x60, 0x6, 0x66, 0x18, 0xc3, 0x61, 0x8f, 0xf6, + 0x3c, 0x7e, 0x66, 0x63, 0xc6, 0x66, 0x0, 0x6c, + 0x30, 0x6, 0xff, 0x1f, 0xef, 0xe1, 0xfe, 0x3c, + + /* U+F517 "" */ + 0x7f, 0xff, 0xbf, 0xfb, 0x7f, 0x86, 0xff, 0xd, + 0xff, 0xdb, 0xff, 0x3f, 0xf8, 0x3f, 0xf0, 0xf, + 0xf8, 0x1f, 0xf0, 0x3f, 0xc0, 0x0, 0x1, 0xff, + 0x83, 0xcf, 0x7, 0x9e, 0xf, 0xfc, + + /* U+F518 "" */ + 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xc3, 0xff, 0x3, + 0xff, 0x3, 0xff, 0x3, 0xff, 0x3, 0xff, 0x3, + 0xff, 0x3, 0xff, 0x3, 0xff, 0x3, 0xff, 0x3, + 0xff, 0x3f, 0xff, 0xff, 0x3, 0xc0, + + /* U+F519 "" */ + 0x20, 0x2, 0x30, 0x1, 0xb3, 0x6, 0x7b, 0x19, + 0xbd, 0x9e, 0xde, 0xcf, 0x6f, 0x63, 0x37, 0x99, + 0xb3, 0x60, 0xc3, 0x10, 0x61, 0x0, 0x30, 0x0, + 0x18, 0x0, 0xc, 0x0, 0x6, 0x0, 0x3, 0x0, + 0x1, 0x80, + + /* U+F51A "" */ + 0x0, 0x0, 0xc0, 0x0, 0x70, 0x0, 0x38, 0x0, + 0x1c, 0x0, 0xe, 0x0, 0x37, 0x0, 0xf, 0x80, + 0x19, 0xc0, 0xf, 0x38, 0x7, 0xe6, 0x3, 0xfc, + 0x0, 0xbf, 0x80, 0x1f, 0xe0, 0xf, 0xf0, 0x7, + 0xfc, 0x3, 0xfc, 0x0, + + /* U+F51B "" */ + 0x3f, 0xff, 0x1f, 0xff, 0xe6, 0x0, 0x19, 0x80, + 0x6, 0x60, 0x1, 0x98, 0x0, 0x66, 0x0, 0x19, + 0x80, 0x6, 0x60, 0xf9, 0x98, 0x3e, 0x6f, 0xff, + 0xff, 0xff, 0xff, + + /* U+F51C "" */ + 0x7, 0xff, 0xc1, 0xff, 0xfc, 0x3f, 0xff, 0x81, + 0xff, 0xf0, 0xf, 0xfe, 0x39, 0xff, 0xcf, 0xbf, + 0xf9, 0xf7, 0xff, 0x3e, 0xf0, 0xe3, 0x80, 0x0, + 0x0, 0x0, 0x3f, 0x9f, 0xff, 0xfb, 0xfd, 0xff, + 0x0, 0x3f, 0xe0, 0x0, + + /* U+F51D "" */ + 0x0, 0x0, 0x1, 0x80, 0x7, 0xe0, 0x1, 0x80, + 0x1, 0x80, 0x3, 0xc0, 0xf, 0xf0, 0xf, 0xf0, + 0x1f, 0xf8, 0x1f, 0xf8, 0x7f, 0xfe, 0xfe, 0x7f, + 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, + 0x7f, 0xfe, + + /* U+F51E "" */ + 0x3, 0xfc, 0x7, 0xfe, 0xf, 0xff, 0x0, 0x7f, + 0x0, 0x1e, 0x3f, 0xc8, 0xff, 0xf1, 0xff, 0xf6, + 0xff, 0xf0, 0x3f, 0xc7, 0x80, 0x16, 0xe0, 0x60, + 0x3f, 0xc0, 0x80, 0x10, 0xe0, 0x60, 0x3f, 0xc0, + + /* U+F51F "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x38, 0xfc, 0x73, 0xfe, + 0x6f, 0xfe, 0xcc, 0x3f, 0xd8, 0x1f, 0xd9, 0x9f, + 0xf9, 0x9f, 0xf8, 0x1f, 0xfc, 0x3f, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F520 "" */ + 0x0, 0x7, 0x0, 0x0, 0xff, 0x0, 0xd, 0xf0, + 0x0, 0xfe, 0x0, 0x1f, 0xc0, 0x3, 0xfc, 0x0, + 0xff, 0x80, 0x1f, 0xf8, 0x3, 0xff, 0x80, 0xff, + 0xf0, 0x1f, 0xfe, 0x3, 0xff, 0xc0, 0x70, 0x48, + 0x6, 0x6, 0xc0, 0x0, 0x24, 0x0, 0x2, 0x40, + + /* U+F521 "" */ + 0x1, 0xc0, 0x0, 0xe0, 0x10, 0x70, 0x5c, 0x38, + 0x7e, 0x3e, 0x31, 0x9f, 0xb0, 0xff, 0xf8, 0x7f, + 0xfc, 0x3f, 0xfe, 0x1f, 0xff, 0xf, 0xff, 0x87, + 0xff, 0x81, 0xff, 0xc0, + + /* U+F522 "" */ + 0x3c, 0x0, 0x3f, 0xc0, 0x77, 0xf0, 0x77, 0xb0, + 0x7f, 0x30, 0x79, 0xf0, 0xf9, 0xe6, 0xcf, 0xe7, + 0xee, 0x6f, 0xfe, 0x6f, 0x3f, 0xcf, 0x3, 0xcf, + 0x0, 0x1f, 0x3, 0xff, 0x3, 0xff, 0x1, 0xfe, + + /* U+F523 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf9, 0xe7, 0xe7, + 0x9f, 0xff, 0xff, 0xcf, 0xff, 0x3f, 0xff, 0xff, + 0x9e, 0x7e, 0x79, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F524 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf9, 0xe7, 0xe7, + 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x9e, 0x7e, 0x79, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F525 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xcf, 0xff, 0x3f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F526 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf9, 0xe7, 0xe7, + 0x9f, 0xff, 0xfe, 0x79, 0xf9, 0xe7, 0xff, 0xff, + 0x9e, 0x7e, 0x79, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F527 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xe7, + 0xff, 0xff, 0xff, 0xcf, 0xff, 0x3f, 0xff, 0xff, + 0xfe, 0x7f, 0xf9, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F528 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xe7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x7f, 0xf9, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F529 "" */ + 0x3, 0x0, 0x1e, 0x0, 0x30, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0x30, 0x1, 0xe0, 0x3, 0x0, + + /* U+F52A "" */ + 0x3f, 0xf1, 0xff, 0xe7, 0xff, 0x9f, 0xfe, 0x7f, + 0xf9, 0xff, 0xe7, 0xff, 0x9f, 0xe6, 0x7f, 0x99, + 0xff, 0xe7, 0xff, 0x9f, 0xfe, 0x7f, 0xf9, 0xff, + 0xef, 0xff, 0xff, 0xff, + + /* U+F52B "" */ + 0x3f, 0xf1, 0xff, 0xe7, 0xf9, 0x9f, 0xe6, 0x7f, + 0x99, 0xfe, 0x67, 0xf9, 0x9e, 0x66, 0x79, 0x99, + 0xfe, 0x67, 0xf9, 0x9f, 0xe6, 0x7f, 0x99, 0xfe, + 0x6f, 0xf9, 0xff, 0xe7, + + /* U+F52C "" */ + 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xf, 0xff, 0xff, 0xff, + + /* U+F52D "" */ + 0x0, 0x7c, 0x0, 0x7f, 0x0, 0x7f, 0xc0, 0x7f, + 0xe0, 0x7f, 0xf0, 0x7f, 0xf8, 0x79, 0xfc, 0x39, + 0xfc, 0x39, 0xf0, 0x19, 0xfe, 0x9, 0xfe, 0x1, + 0xf8, 0x1, 0xfe, 0x1, 0xfc, 0x1, 0x80, 0x1, + 0x80, 0x0, + + /* U+F52E "" */ + 0x0, 0x38, 0x0, 0x1f, 0x0, 0x1e, 0xe0, 0x1f, + 0xfe, 0x1f, 0xff, 0xcf, 0xff, 0xf3, 0xc7, 0xf9, + 0xee, 0xf8, 0x77, 0xdc, 0x3b, 0xf6, 0xf, 0xfd, + 0xc3, 0xfe, 0x38, 0xff, 0xf7, 0xdf, 0xfc, 0xf0, + + /* U+F52F "" */ + 0x7f, 0x80, 0x7f, 0x80, 0xe1, 0xcc, 0xe1, 0xce, + 0xe1, 0xcf, 0xe1, 0xc7, 0xff, 0xc7, 0xff, 0xc3, + 0xff, 0xf3, 0xff, 0xdb, 0xff, 0xdb, 0xff, 0xdb, + 0xff, 0xdb, 0xff, 0xce, 0xff, 0xc0, 0x7f, 0x80, + + /* U+F530 "" */ + 0x1c, 0xe, 0xf, 0x3, 0xc7, 0x0, 0x39, 0x80, + 0x6, 0x60, 0x1, 0x98, 0x0, 0x6c, 0x0, 0xf, + 0xfc, 0xff, 0xff, 0xff, 0xf0, 0xfc, 0x3c, 0x33, + 0xf, 0xc, 0xc3, 0xff, 0x3f, 0xdf, 0x87, 0xe0, + + /* U+F531 "" */ + 0xc0, 0x1, 0xe0, 0x0, 0xf0, 0x0, 0x7c, 0x0, + 0x3e, 0x0, 0x1f, 0x0, 0xf, 0x80, 0xf, 0x0, + 0xf8, 0x7, 0xc0, 0x3e, 0x1, 0xf0, 0xf, 0x80, + 0x18, 0x0, 0x0, + + /* U+F532 "" */ + 0xe0, 0x3, 0xf0, 0x3, 0xf8, 0x1, 0xfc, 0x0, + 0xfc, 0x3, 0xf0, 0x7f, 0xf, 0xe0, 0xfc, 0x3, + 0x80, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, + 0xf0, + + /* U+F533 "" */ + 0xf, 0xff, 0xb0, 0xc, 0xe, 0x3, 0x1, 0xff, + 0xf8, 0x7f, 0xff, 0x1f, 0xfc, 0xe0, 0x3f, 0x18, + 0xf, 0xc6, 0x1, 0xf1, 0x80, 0x3c, 0xe0, 0xf, + 0xf0, 0x1, 0xf8, 0x0, 0x0, 0xc0, 0x7f, 0xe0, + + /* U+F534 "" */ + 0x1e, 0x7, 0x87, 0xf9, 0xfe, 0x61, 0x98, 0x6c, + 0xf, 0x3, 0xc0, 0xf0, 0x3c, 0xf, 0x3, 0xc0, + 0xf0, 0x36, 0x19, 0x86, 0x7f, 0x9f, 0xe1, 0xe0, + 0x78, + + /* U+F535 "" */ + 0xf, 0x0, 0xf, 0xf0, 0x7, 0xff, 0xf1, 0xff, + 0xfe, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0x9f, + 0xff, 0xff, 0x7f, 0xff, 0xdf, 0xff, 0xf3, 0xfc, + 0x1c, 0x7c, 0x7, 0x13, 0x0, 0xc4, 0xc0, 0x10, + + /* U+F536 "" */ + 0x0, 0x6, 0x0, 0x3c, 0x1, 0xe0, 0x1f, 0x0, + 0xf8, 0x7, 0xc0, 0x3e, 0x0, 0x78, 0x0, 0x3e, + 0x0, 0x1f, 0x0, 0xf, 0x80, 0x7, 0xc0, 0x1, + 0xe0, 0x0, 0xc0, + + /* U+F537 "" */ + 0x0, 0x1c, 0x3, 0xf0, 0x7f, 0xf, 0xe0, 0xfc, + 0x3, 0xf0, 0x3, 0xf8, 0x1, 0xfc, 0x0, 0xfc, + 0x0, 0x70, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, + 0xf0, + + /* U+F538 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xe6, 0x67, 0x66, 0x66, + 0x66, 0x66, 0xe6, 0x67, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0xff, 0xff, 0xee, 0x77, 0x6e, 0x76, + + /* U+F539 "" */ + 0xc0, 0x0, 0x38, 0x78, 0x7, 0x3e, 0x0, 0xef, + 0xc0, 0x1f, 0xc0, 0x3, 0xfc, 0x1, 0x7c, 0x60, + 0x6f, 0x18, 0x19, 0xc6, 0x6, 0x3d, 0x80, 0xc6, + 0x40, 0x30, 0xf0, 0x6, 0x18, 0x0, 0xf3, 0x0, + 0x4, 0x60, 0x1, 0xc, 0x3, 0xf1, 0x80, 0x0, + 0x30, + + /* U+F53A "" */ + 0x0, 0x7e, 0x43, 0xff, 0xff, 0xf3, 0xff, 0xfb, + 0xce, 0x7f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, + 0xfc, 0x3f, 0xfe, 0x73, 0xdf, 0xff, 0xcf, 0xff, + 0xff, 0xc2, 0x7e, 0x0, + + /* U+F53B "" */ + 0x0, 0x7e, 0x43, 0xff, 0xff, 0xf7, 0xff, 0xf3, + 0xcc, 0x3f, 0xdb, 0x1f, 0xf9, 0x1f, 0xf9, 0x1f, + 0xfb, 0x9f, 0xfc, 0x33, 0xde, 0x7f, 0xcf, 0xff, + 0xff, 0xc2, 0x7e, 0x0, + + /* U+F53C "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7, + 0xe0, 0x7, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, + 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F53D "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xf7, 0xff, 0xc3, 0x3, + 0xcf, 0x3, 0xcf, 0xff, 0xf3, 0xff, 0xfb, 0x3, + 0xc3, 0x3, 0xf7, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F53E "" */ + 0x0, 0x30, 0x1, 0xc0, 0x6, 0x3f, 0xff, 0xff, + 0xfc, 0x6, 0x0, 0x38, 0x1, 0xc0, 0x6, 0x3, + 0xff, 0xff, 0xff, 0xc6, 0x0, 0x38, 0x0, 0xc0, + 0x0, + + /* U+F53F "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3e, 0x7c, 0x7e, 0x7e, + 0x67, 0xe6, 0xe7, 0xe7, 0xff, 0xff, 0xff, 0xff, + 0xcf, 0xff, 0xcf, 0xfe, 0xff, 0xc0, 0x7f, 0x80, + 0x7f, 0xc0, 0x3f, 0xc0, 0x1f, 0xc0, 0x7, 0x80, + + /* U+F540 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xfc, 0x1f, 0xf0, + 0x3f, 0xce, 0xff, 0x3b, 0xfc, 0xf, 0xf0, 0x7f, + 0xcf, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F541 "" */ + 0x78, 0xf, 0xf0, 0x7f, 0xc3, 0xbf, 0x1c, 0xfc, + 0xe1, 0xe7, 0x0, 0x38, 0x1, 0xc0, 0xe, 0x78, + 0x73, 0xf3, 0x8f, 0xdc, 0x3f, 0xe0, 0xff, 0x1, + 0xe0, + + /* U+F542 "" */ + 0xfc, 0x3f, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0x3f, 0xfc, 0x3f, 0xe, 0x0, 0x7, 0x0, + 0x3, 0xf0, 0x3, 0xf0, 0x3, 0xf0, 0x3, 0xf0, + 0x3, 0xf0, 0x3, 0xf0, + + /* U+F543 "" */ + 0xc9, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7e, + 0x7, 0xff, 0xfe, 0x7, 0xe0, 0x7f, 0xff, 0xe0, + 0x7e, 0x7, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x93, + + /* U+F544 "" */ + 0x0, 0x60, 0x0, 0x6, 0x0, 0x0, 0x60, 0x0, + 0xff, 0xf0, 0x1f, 0xff, 0x81, 0xff, 0xf8, 0x1f, + 0xff, 0x8d, 0xc6, 0x3b, 0xdc, 0x63, 0xbd, 0xff, + 0xfb, 0xdf, 0xff, 0xbd, 0xff, 0xfb, 0x18, 0x91, + 0x81, 0xff, 0xf8, 0x1f, 0xff, 0x80, 0xff, 0xf0, + + /* U+F545 "" */ + 0x0, 0x38, 0x0, 0xe, 0x0, 0x33, 0x80, 0xc, + 0xe0, 0x33, 0xf8, 0xd, 0xfc, 0x23, 0xfc, 0x19, + 0xfc, 0x27, 0xfc, 0x19, 0xfc, 0x27, 0xfc, 0x19, + 0xfc, 0xe, 0xfc, 0x3, 0xfc, 0x0, 0xfc, 0x0, + 0x3c, 0x0, 0xc, 0x0, 0x0, + + /* U+F546 "" */ + 0xfc, 0x3, 0xf0, 0xf, 0xc0, 0x38, 0x0, 0xe0, + 0x3, 0xf0, 0xf, 0xc0, 0x38, 0x0, 0xfd, 0x9f, + 0xf6, 0x7f, 0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf0, + + /* U+F547 "" */ + 0xdb, 0x6c, 0xf6, 0xdb, 0x3d, 0xb6, 0xcf, 0x7d, + 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, + + /* U+F548 "" */ + 0xff, 0xff, 0xf0, 0xf0, 0xff, 0xf8, 0xf8, 0xff, + 0xf0, 0xf0, 0xff, 0xf8, 0xf8, 0xff, 0xf0, 0xf0, + 0xff, 0xff, + + /* U+F549 "" */ + 0x0, 0xc0, 0x0, 0x78, 0x0, 0x3f, 0x0, 0x1f, + 0xe0, 0x7f, 0x3f, 0xbf, 0x87, 0xfc, 0xe1, 0xcf, + 0x3c, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xe1, + 0xcf, 0x38, 0x73, 0xfe, 0x1f, 0xff, 0x87, 0xff, + 0xe1, 0xfd, 0xff, 0xfe, + + /* U+F54A "" */ + 0x0, 0x3, 0x80, 0x7, 0xc0, 0x7, 0xe0, 0x3, + 0xe0, 0x1, 0xf0, 0x1, 0xf0, 0x1, 0xc0, 0x7, + 0xc0, 0xf, 0xc0, 0xf, 0xe0, 0xf, 0xf0, 0xf, + 0xf0, 0x7, 0xf8, 0x3, 0xf8, 0x1, 0xf8, 0x0, + 0x78, 0x0, 0x0, 0x0, 0x0, + + /* U+F54B "" */ + 0x0, 0xfc, 0x7, 0x7f, 0xc3, 0xdf, 0xf8, 0xf7, + 0xff, 0x1d, 0xff, 0xc0, 0x3f, 0xe0, 0x3, 0xf0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x0, 0x3f, + 0xe1, 0xdf, 0xfc, 0xf7, 0xff, 0x3d, 0xff, 0x87, + 0x7f, 0xc0, 0xf, 0xc0, + + /* U+F54C "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xf3, 0xcf, 0xe1, 0x87, + 0xe1, 0x87, 0xf3, 0xcf, 0x7f, 0xfe, 0x7f, 0xfe, + 0x3f, 0xfc, 0x1f, 0xf8, 0x1d, 0xb8, 0x1d, 0xb8, + + /* U+F54D "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3c, 0x3c, 0x78, 0x8e, + 0x7c, 0xf6, 0xee, 0x17, 0xc7, 0x3, 0xdf, 0xfb, + 0xdf, 0xcb, 0xdf, 0xfb, 0xe0, 0x77, 0x60, 0x3e, + 0x70, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F54E "" */ + 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0xff, 0xff, + 0xff, 0xff, 0x77, 0x6e, 0x0, 0x0, 0x0, 0x0, + 0x60, 0x6, 0x60, 0x6, 0x7f, 0xfe, 0x7f, 0xfe, + 0x7f, 0xfe, 0x7f, 0xfe, + + /* U+F54F "" */ + 0x3f, 0xfe, 0x1f, 0xff, 0x1f, 0xff, 0xcf, 0xff, + 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xe1, + 0xce, 0x70, 0xe7, 0x38, 0x73, 0x9c, 0x39, 0xcf, + 0xfc, 0xe7, 0xfe, 0x71, 0xfe, 0x10, + + /* U+F550 "" */ + 0xff, 0xfc, 0xff, 0xfc, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3f, 0xff, 0x3f, 0xff, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xff, 0xfc, 0xff, 0xfc, + + /* U+F551 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3d, 0xbc, 0x7e, 0x7e, + 0x76, 0x6e, 0xf9, 0x9f, 0xd9, 0x9b, 0xe6, 0x67, + 0xe6, 0x67, 0xd9, 0x9b, 0xf9, 0x9f, 0x76, 0x6e, + 0x7e, 0x7e, 0x3d, 0xbc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F552 "" */ + 0xf, 0xf0, 0x8, 0x30, 0x8, 0x30, 0x3f, 0xfc, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xcf, + 0x0, 0x0, 0x0, 0x0, 0xf7, 0xcf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F553 "" */ + 0x6, 0xc, 0x1, 0xc1, 0xc0, 0x7c, 0x7c, 0x1f, + 0xff, 0xc7, 0xff, 0xfd, 0xff, 0xff, 0xdd, 0xff, + 0x71, 0x3f, 0xe4, 0x7, 0xfc, 0x0, 0xff, 0x80, + 0x1f, 0xf0, 0x3, 0xfe, 0x0, 0x7f, 0xc0, 0xf, + 0xf8, 0x1, 0xff, 0x0, + + /* U+F554 "" */ + 0xe, 0x1, 0xe0, 0xe, 0x0, 0x0, 0x1c, 0x3, + 0xf0, 0x7f, 0x8f, 0xfe, 0xde, 0xed, 0xe0, 0xde, + 0x0, 0xe0, 0x7, 0x1, 0x38, 0x39, 0x87, 0x18, + 0xe1, 0x8c, 0x8, + + /* U+F555 "" */ + 0x7f, 0xff, 0xc0, 0x0, 0xc0, 0x0, 0xff, 0xfe, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, + 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xfe, + + /* U+F556 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xf1, 0xcf, 0xf8, 0x9f, + 0xf9, 0xdf, 0xff, 0xff, 0xfc, 0x3f, 0x78, 0x1e, + 0x79, 0x9e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F557 "" */ + 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, + 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7c, 0x3e, + 0x78, 0x1e, 0x78, 0x1e, 0x78, 0x1e, 0x78, 0x1e, + 0xf8, 0x1f, 0xf8, 0x1f, + + /* U+F558 "" */ + 0x7f, 0xff, 0xff, 0xff, 0x87, 0xfc, 0xf, 0xeb, + 0x9f, 0xad, 0x7e, 0xb5, 0xfa, 0xe7, 0xf0, 0x3f, + 0xe1, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1b, 0x0, + 0x6f, 0xff, 0xdf, 0xff, + + /* U+F559 "" */ + 0x3, 0x0, 0x7f, 0x83, 0xff, 0x1f, 0xfe, 0x78, + 0x79, 0xc0, 0xef, 0x3, 0xfc, 0xf, 0x70, 0x39, + 0xe1, 0xe7, 0xff, 0x8f, 0xfc, 0x1f, 0xe0, 0xff, + 0xc3, 0xff, 0x1f, 0x3e, 0x3c, 0xf0, 0x21, 0x0, + + /* U+F55A "" */ + 0x7, 0xff, 0x83, 0xff, 0xf1, 0xff, 0xfc, 0xfc, + 0xdf, 0x7f, 0x27, 0xff, 0xe3, 0xff, 0xf8, 0xfd, + 0xfc, 0x9f, 0x3f, 0x37, 0xc7, 0xff, 0xf0, 0xff, + 0xfc, 0x1f, 0xfe, + + /* U+F55B "" */ + 0x0, 0xf0, 0xf, 0x1f, 0x8f, 0xff, 0x9f, 0xff, + 0x7f, 0xef, 0xe, 0xf7, 0x0, 0xc0, 0x30, 0x18, + 0x1, 0x81, 0x80, 0x18, 0x10, 0x0, 0x87, 0xc0, + 0x3e, 0x7c, 0x3, 0xe6, 0xc0, 0x36, 0x7c, 0x3, + 0xe7, 0x80, 0x1e, + + /* U+F55C "" */ + 0x3f, 0xc0, 0xff, 0x1, 0x98, 0x6, 0x60, 0x19, + 0x80, 0x66, 0x61, 0x99, 0xce, 0x7f, 0x70, 0xf1, + 0x81, 0x8f, 0xff, 0x3f, 0xfc, 0xff, 0xf3, 0xff, + 0xc7, 0xfe, 0x1f, 0xf8, + + /* U+F55D "" */ + 0xc9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0xff, + 0xf7, 0xfe, 0xf, 0x0, 0xf0, 0xf, 0x0, 0x60, + + /* U+F55E "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf0, 0x3, 0xc0, + 0xf, 0x0, 0x3c, 0x0, 0xf0, 0x3, 0xff, 0xff, + 0xff, 0xfc, 0xfc, 0xf3, 0xf3, 0xff, 0xfd, 0xff, + 0xe7, 0x3, 0x8c, 0xc, + + /* U+F55F "" */ + 0x1, 0x0, 0x7, 0x0, 0xe, 0x0, 0x1c, 0xc, + 0x7c, 0x7e, 0xfb, 0xdf, 0xff, 0x3f, 0xfe, 0x7f, + 0xfc, 0x7f, 0xf3, 0xff, 0xfb, 0xff, 0xe0, 0xfe, + 0x1, 0xac, 0x0, 0x40, 0x0, 0x80, + + /* U+F560 "" */ + 0x0, 0x0, 0x30, 0x7, 0x0, 0xe0, 0xcc, 0xf, + 0x83, 0x78, 0x73, 0xe, 0x0, 0xc0, 0x1c, 0x63, + 0x87, 0x30, 0x3e, 0x1, 0xe0, 0xc, 0x0, + + /* U+F561 "" */ + 0x0, 0x1f, 0x0, 0xf, 0xe0, 0x7, 0x1c, 0x0, + 0x3, 0xff, 0xfc, 0xff, 0xff, 0x37, 0x3, 0x9c, + 0xc0, 0xce, 0x18, 0x67, 0x3, 0x30, 0x0, 0xfc, + 0x0, 0x1e, 0x0, 0x3, 0x0, 0x0, 0xc0, 0x0, + 0x30, 0x0, 0xc, 0x0, 0x1f, 0xe0, 0x7, 0xf8, + 0x0, + + /* U+F562 "" */ + 0x3, 0xc0, 0x1, 0x80, 0x7, 0xc0, 0xf, 0xf0, + 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe, + 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, + + /* U+F563 "" */ + 0x0, 0x0, 0x7, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, + 0x3f, 0xfe, 0x79, 0xfe, 0x79, 0xfe, 0xff, 0xfe, + 0x7f, 0xfe, 0x7f, 0xce, 0x73, 0xce, 0x73, 0xfc, + 0x3f, 0xfc, 0x1f, 0xf8, 0xf, 0xe0, 0x0, 0x80, + + /* U+F564 "" */ + 0x7, 0x0, 0x3e, 0x0, 0xfe, 0x3, 0xfe, 0x7, + 0x9e, 0x1f, 0x3e, 0x3f, 0xfe, 0x7f, 0xff, 0xff, + 0xcf, 0xe7, 0x9d, 0xcf, 0xf3, 0xff, 0xe3, 0xff, + 0x83, 0xfe, 0x1, 0xf0, 0x0, + + /* U+F565 "" */ + 0x30, 0x0, 0x30, 0x0, 0xf7, 0xf8, 0xf7, 0xfc, + 0x30, 0xc, 0x30, 0xc, 0x30, 0xc, 0x30, 0xc, + 0x30, 0xc, 0x30, 0xc, 0x30, 0xc, 0x30, 0xc, + 0x3f, 0xef, 0x1f, 0xef, 0x0, 0xc, 0x0, 0xc, + + /* U+F566 "" */ + 0x7f, 0xff, 0xbf, 0xff, 0xfc, 0x7, 0x9f, 0x1, + 0xc7, 0xc0, 0x71, 0xff, 0xfe, 0x7c, 0xaf, 0xff, + 0x2b, 0xff, 0xff, 0xff, 0xf0, 0x30, 0x3c, 0xc, + 0xd, 0xff, 0xfe, + + /* U+F567 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x6d, 0xb6, 0xe1, 0x87, 0xf3, 0xcf, 0xe1, 0x87, + 0xed, 0xb7, 0xfe, 0x7f, 0xfc, 0x3f, 0x7c, 0x3e, + 0x7c, 0x3e, 0x3e, 0x7c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F568 "" */ + 0x3, 0xc0, 0x7, 0xe0, 0x6, 0x60, 0x6, 0x60, + 0x7, 0xe0, 0x7, 0xe0, 0xf, 0xf0, 0x7e, 0x66, + 0x7e, 0xe, 0x3c, 0x1c, 0x3f, 0xf8, 0x7f, 0xe2, + 0x70, 0xf, 0xf0, 0xf, 0xe0, 0x7, 0xc0, 0x3, + + /* U+F569 "" */ + 0x0, 0x2, 0x0, 0xe, 0x1f, 0xf8, 0x70, 0x7e, + 0xe0, 0xc3, 0xc0, 0x83, 0xe0, 0x7, 0xf8, 0x1f, + 0xff, 0xff, 0xef, 0xf7, 0xef, 0x77, 0xef, 0x77, + 0x2f, 0x74, 0xf, 0x70, + + /* U+F56A "" */ + 0xf, 0xfc, 0xf, 0xcf, 0xc7, 0xb3, 0x9b, 0x84, + 0xc3, 0xc1, 0xf0, 0xf8, 0x44, 0x7f, 0xf1, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfd, 0xff, 0xfe, 0x3f, 0xff, 0x3, 0xff, 0x0, + + /* U+F56B "" */ + 0x0, 0xf, 0x0, 0x7f, 0x3, 0xff, 0x7, 0xff, + 0xf, 0xfe, 0x1f, 0xfe, 0x1e, 0xfe, 0x3d, 0xfc, + 0x3b, 0xf0, 0x37, 0xf8, 0xf, 0xf8, 0x1f, 0xc0, + 0x3f, 0xe0, 0x7f, 0x80, 0xe0, 0x0, 0xc0, 0x0, + + /* U+F56C "" */ + 0x7f, 0xf, 0xf8, 0xc7, 0xcc, 0x6e, 0xfe, 0x7c, + 0x63, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x3f, 0xe1, 0xfc, 0x83, 0xff, 0xf7, 0xfe, + + /* U+F56D "" */ + 0x3e, 0xf, 0xf0, 0xff, 0x8f, 0xcc, 0xfc, 0x6f, + 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xf9, + 0xfe, 0xa7, 0xe0, 0x7f, 0xf, 0xf9, 0xff, 0xff, + 0x7f, 0xe0, + + /* U+F56E "" */ + 0x3e, 0x0, 0x7f, 0xc0, 0x3f, 0xf0, 0x1f, 0xdc, + 0xf, 0xe7, 0x7, 0xf1, 0x83, 0xff, 0xe1, 0xff, + 0xf4, 0xff, 0xfb, 0x7f, 0xfd, 0xff, 0x8f, 0xff, + 0xff, 0x6f, 0xff, 0xa7, 0xff, 0xc3, 0xff, 0xc0, + 0xff, 0xe0, + + /* U+F56F "" */ + 0x7f, 0xf, 0xf8, 0xff, 0xcf, 0xde, 0xfc, 0xff, + 0xc7, 0xff, 0xff, 0x3f, 0xf3, 0xf0, 0x9f, 0x0, + 0xf0, 0x1f, 0xfb, 0xff, 0x3f, 0xff, 0xf7, 0xfe, + + /* U+F570 "" */ + 0x7f, 0xf, 0xf8, 0xc7, 0xcc, 0x6e, 0xfe, 0x7c, + 0x63, 0xc7, 0xff, 0xff, 0xff, 0xfc, 0x3, 0xc0, + 0x3c, 0x3, 0xc0, 0x3f, 0xff, 0xff, 0xf7, 0xfe, + + /* U+F571 "" */ + 0x7f, 0xf, 0xf8, 0xc6, 0xcf, 0xe6, 0xfe, 0x3c, + 0x7f, 0xc7, 0xff, 0xff, 0xf9, 0xff, 0x1f, 0xf7, + 0xff, 0x8f, 0xfe, 0xff, 0xf, 0xfd, 0xf7, 0xfe, + + /* U+F572 "" */ + 0x3e, 0xf, 0xf0, 0xff, 0x8f, 0xec, 0xfe, 0x6f, + 0xe3, 0xff, 0xfc, 0x1f, 0xd9, 0xfc, 0x1f, 0xc3, + 0xbd, 0x93, 0xdc, 0x7f, 0x83, 0xf9, 0x3f, 0xff, + 0x7f, 0xe0, + + /* U+F573 "" */ + 0x3e, 0x0, 0x3f, 0xe0, 0xf, 0xfc, 0x3, 0xfb, + 0x80, 0xfe, 0x70, 0x3f, 0x8c, 0xf, 0xff, 0x83, + 0xff, 0xe0, 0xff, 0xf9, 0xbf, 0xfc, 0xff, 0xfe, + 0x1f, 0xc7, 0x32, 0xe1, 0x9e, 0x33, 0xf, 0x8f, + 0xe7, 0xc1, 0xf9, 0xe0, 0x0, 0x70, 0x0, 0x0, + 0x0, + + /* U+F574 "" */ + 0x7f, 0xf, 0xf8, 0xfd, 0xcf, 0xce, 0xfc, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xf, 0xe0, + 0x7f, 0x9f, 0xf9, 0xff, 0x9f, 0xff, 0xf7, 0xfe, + + /* U+F575 "" */ + 0x0, 0xe0, 0x31, 0xf0, 0x3b, 0x38, 0x1e, 0x1c, + 0xe, 0xe, 0x1f, 0x7, 0x33, 0x3, 0x60, 0x3, + 0xc0, 0x6, 0xff, 0xfc, 0xff, 0xf8, 0x7f, 0xf0, + 0x3f, 0xe0, 0x1f, 0xc0, 0xf, 0x80, 0x7, 0x0, + + /* U+F576 "" */ + 0x0, 0xe0, 0x18, 0xf8, 0xe, 0xce, 0x3, 0xe3, + 0x80, 0xe0, 0xe0, 0xf8, 0x38, 0xec, 0xc, 0xe0, + 0x6, 0xe0, 0x6, 0x60, 0x7, 0x3f, 0xff, 0xf, + 0xff, 0x3, 0xff, 0x30, 0xff, 0x18, 0x3f, 0x1e, + 0xf, 0xf, 0x0, 0x3, 0x0, + + /* U+F577 "" */ + 0x7, 0xe0, 0xe, 0xc, 0xc, 0x3, 0xc, 0x78, + 0xc6, 0x83, 0x6, 0xde, 0xcb, 0x59, 0xb5, 0xab, + 0x5b, 0xd5, 0xad, 0x8a, 0xd6, 0x85, 0x6b, 0x2, + 0xb5, 0x0, 0x32, 0x0, 0x18, 0x0, 0x8, 0x0, + + /* U+F578 "" */ + 0x0, 0x20, 0x0, 0x7f, 0xc, 0x3f, 0xf3, 0xdf, + 0xfe, 0x7f, 0xff, 0x9f, 0xfc, 0xf7, 0xff, 0x3d, + 0xff, 0xfe, 0xf7, 0xff, 0x30, 0xff, 0x80, 0xf, + 0x80, + + /* U+F579 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf3, 0xcf, 0xed, 0xb7, 0xe1, 0x87, + 0xf3, 0xcf, 0xff, 0xff, 0xff, 0xff, 0x7c, 0x3e, + 0x7c, 0x3e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F57A "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0xdf, 0xf9, 0xdf, 0xf9, 0xdf, + 0xff, 0xff, 0xfe, 0x7f, 0xfc, 0x3f, 0x78, 0x1e, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F57B "" */ + 0xff, 0xff, 0xff, 0xff, 0x70, 0xe, 0x3f, 0xfc, + 0x1f, 0xf8, 0xf, 0xf0, 0x7, 0xe0, 0x3, 0xc0, + 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, 0x1, 0x80, + 0x1, 0x80, 0xf, 0xf0, 0xf, 0xf0, + + /* U+F57C "" */ + 0x7, 0xe0, 0x1c, 0x38, 0x3c, 0xc, 0x7c, 0x6, + 0x7d, 0x82, 0xff, 0x83, 0xfc, 0x1f, 0xf8, 0x1f, + 0xf8, 0xf, 0xf8, 0xf, 0xff, 0x1f, 0x7f, 0x1e, + 0x7f, 0x3e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F57D "" */ + 0x7, 0xe0, 0x18, 0xf8, 0x30, 0xfc, 0x61, 0xfe, + 0x40, 0xfe, 0xc0, 0xf3, 0xc3, 0xf3, 0xf3, 0xf3, + 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0x7e, 0x3e, + 0x7e, 0x7e, 0x3e, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F57E "" */ + 0x7, 0xe0, 0x1c, 0x78, 0x30, 0x7c, 0x60, 0x7e, + 0x60, 0x5e, 0xc0, 0xdf, 0xc0, 0xff, 0xc1, 0xff, + 0xc1, 0x7f, 0xf9, 0x47, 0xff, 0x83, 0x7b, 0x86, + 0x7f, 0x86, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F57F "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0xdf, 0xf9, 0xdf, 0xf9, 0xdf, + 0xff, 0xff, 0xfe, 0xaf, 0xf6, 0xaf, 0x76, 0xae, + 0x7e, 0xae, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F580 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0xdf, 0xf9, 0xdf, 0xf9, 0xdf, + 0xff, 0xff, 0xe7, 0xe7, 0xf0, 0xf, 0x70, 0xe, + 0x78, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F581 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x79, 0xde, 0xf9, 0xdf, 0xf9, 0xdf, 0xf9, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7, 0x70, 0xe, + 0x78, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F582 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x73, 0x8e, 0xe1, 0x7, 0xed, 0x67, 0xff, 0xff, + 0xff, 0xff, 0xef, 0xf7, 0xe0, 0xf, 0x70, 0xe, + 0x78, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F583 "" */ + 0x0, 0x3, 0x3, 0xf3, 0xc7, 0xf9, 0xe7, 0xfc, + 0xf7, 0xfe, 0x33, 0x9c, 0x83, 0x84, 0x21, 0xda, + 0xde, 0xff, 0xff, 0x7f, 0xff, 0xbb, 0xff, 0xde, + 0x1, 0xe7, 0x0, 0xe3, 0xc0, 0xf0, 0xf0, 0xf0, + 0x3f, 0xf0, 0x7, 0xe0, 0x0, + + /* U+F584 "" */ + 0x3, 0xe0, 0x1f, 0xf8, 0x7f, 0xfe, 0xc0, 0x93, + 0xc0, 0x83, 0xc0, 0x83, 0xe1, 0xc7, 0xf3, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xe7, 0xe7, 0x70, 0xe, + 0x70, 0xe, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F585 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x77, 0xee, 0xf1, 0x8f, 0xf3, 0xcf, 0xff, 0xff, + 0xff, 0xff, 0xef, 0xf7, 0xe0, 0xf, 0x70, 0xe, + 0x78, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F586 "" */ + 0x0, 0x0, 0x0, 0xfc, 0xe0, 0xff, 0xb8, 0x7f, + 0xee, 0x3f, 0xb8, 0xf, 0xc7, 0xc7, 0xf1, 0xf9, + 0xff, 0xee, 0x79, 0xf3, 0x9c, 0x7c, 0xe7, 0x9e, + 0x39, 0xff, 0xe, 0x3f, 0x7, 0x1, 0x83, 0xc7, + 0x7f, 0xe1, 0xdf, 0xf0, 0x73, 0xf0, 0x0, 0x0, + 0x0, + + /* U+F587 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x77, 0xee, + 0xe3, 0xc7, 0xc1, 0x83, 0xe3, 0xc7, 0xe3, 0xc7, + 0xff, 0xff, 0xff, 0xff, 0xef, 0xf7, 0x70, 0xe, + 0x70, 0xe, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F588 "" */ + 0x1, 0xf8, 0x0, 0x7f, 0xe0, 0xf, 0xff, 0x1, + 0xff, 0xf8, 0x1c, 0x63, 0x83, 0x84, 0x1c, 0x19, + 0x49, 0x80, 0xff, 0xe0, 0xe7, 0xfe, 0x7e, 0xbf, + 0xd7, 0x68, 0x3, 0x60, 0xc0, 0x30, 0x1e, 0x7, + 0x80, 0xf0, 0xf0, 0x7, 0xfe, 0x0, 0x1f, 0x80, + + /* U+F589 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xfb, 0xdf, 0xfb, 0xdf, 0xff, 0xff, + 0xff, 0xff, 0xf3, 0xcf, 0xf0, 0xf, 0x78, 0x1e, + 0x7b, 0xde, 0x3b, 0xdc, 0xb, 0xd0, 0x1, 0x80, + + /* U+F58A "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x77, 0xee, 0xf9, 0x9f, 0xf1, 0x8f, 0xf7, 0xef, + 0xff, 0xff, 0xef, 0xf7, 0xe0, 0x7, 0x70, 0xe, + 0x7b, 0xde, 0x3b, 0xdc, 0x1b, 0xd8, 0x1, 0x80, + + /* U+F58B "" */ + 0x3, 0xe0, 0x7, 0xfc, 0xf, 0xff, 0xf, 0xff, + 0xc7, 0xfc, 0xf7, 0xd, 0xbb, 0xb6, 0xdf, 0xff, + 0x9f, 0xff, 0xff, 0xf7, 0xff, 0xf8, 0x1, 0xce, + 0x0, 0xe7, 0x34, 0xe1, 0x9e, 0x70, 0x4f, 0x20, + 0x3, 0x80, + + /* U+F58C "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0x87, 0xf9, 0x87, 0xf9, 0xff, + 0xff, 0xff, 0xef, 0xf7, 0xe0, 0xf, 0x70, 0xe, + 0x78, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F58D "" */ + 0xf3, 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, 0xf3, 0xcf, + 0x0, 0x0, 0x0, 0x0, 0xf3, 0xcf, 0xf3, 0xcf, + 0xf3, 0xcf, 0xf3, 0xcf, + + /* U+F58E "" */ + 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0x0, 0x0, 0xf, + 0x3f, 0xcf, 0xf3, 0xfc, 0xf0, 0x0, 0x0, 0xf3, + 0xfc, 0xff, 0x3f, 0xcf, + + /* U+F58F "" */ + 0xf, 0xc0, 0x7f, 0x83, 0x87, 0x18, 0x6, 0xe0, + 0x1f, 0x0, 0x3c, 0x0, 0xf0, 0x3, 0xf8, 0x7f, + 0xe1, 0xff, 0x87, 0xfe, 0x1f, 0xf8, 0x7f, 0xe1, + 0xf7, 0x87, 0x80, + + /* U+F590 "" */ + 0xf, 0xc0, 0x7f, 0x83, 0x87, 0x18, 0xe, 0xe0, + 0x1f, 0x0, 0x7f, 0x87, 0xfe, 0x3f, 0xf8, 0xff, + 0xe3, 0xff, 0x8f, 0xde, 0x1f, 0x0, 0xc, 0x8, + 0x30, 0x71, 0x81, 0xfc, + + /* U+F591 "" */ + 0x0, 0x6, 0x0, 0xf, 0x80, 0xf, 0xe0, 0xe, + 0x70, 0x1e, 0x30, 0x1c, 0x38, 0x1c, 0x38, 0x1e, + 0x18, 0xf, 0x98, 0x7, 0xfc, 0x7, 0xfc, 0x3, + 0xfc, 0x1, 0xfc, 0x2, 0x70, 0x3, 0x80, 0x1, + 0xe0, 0x0, + + /* U+F593 "" */ + 0x2, 0x61, 0x89, 0x8e, 0x33, 0x18, 0x66, 0x0, + 0xcd, 0xc0, 0xf, 0xe0, 0x3f, 0xfe, 0xff, 0xff, + 0x77, 0x3d, 0xdc, 0xf7, 0x73, 0xdd, 0xcf, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F594 "" */ + 0xff, 0xfe, 0xff, 0xf9, 0xff, 0xf3, 0x36, 0x66, + 0x6c, 0xcf, 0xff, 0x99, 0xb3, 0x33, 0x66, 0x7f, + 0xfc, 0xf8, 0xf9, 0xe0, 0xf3, 0xc1, 0xe7, 0xef, + 0xcf, 0xdf, 0x9f, 0xbf, 0x7f, 0xff, + + /* U+F595 "" */ + 0x0, 0x12, 0x0, 0xd, 0x80, 0x1, 0x20, 0x0, + 0x6c, 0x0, 0x19, 0x80, 0x3, 0x30, 0x0, 0x6c, + 0x0, 0x1b, 0x0, 0x2, 0x40, 0x0, 0x0, 0x1c, + 0xe1, 0xe7, 0x3f, 0xfc, 0xe7, 0xdf, 0xbd, 0xf0, + 0xe7, 0x20, + + /* U+F596 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0xdf, 0xf9, 0xdf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0x7f, 0x3e, + 0x7f, 0xfe, 0x3f, 0x7c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F597 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x73, 0x8e, 0xe1, 0x7, 0xed, 0x67, 0xed, 0x67, + 0xff, 0xff, 0xfe, 0x3f, 0xff, 0x3f, 0x7e, 0x7e, + 0x7f, 0x3e, 0x3e, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F598 "" */ + 0x7, 0xe0, 0x3, 0xff, 0x0, 0xff, 0xf0, 0x3f, + 0xff, 0x7, 0xff, 0xe1, 0xff, 0x9e, 0x3e, 0x61, + 0xc7, 0xfd, 0xb8, 0xff, 0xf8, 0x1f, 0xc6, 0x43, + 0xfe, 0x9c, 0x3f, 0x17, 0xe7, 0xfa, 0xfe, 0x7c, + 0x4f, 0xc7, 0xfd, 0xf0, 0x1f, 0x10, + + /* U+F599 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0xdf, 0xf9, 0xdf, 0xff, 0xff, + 0xff, 0xff, 0xe0, 0x7, 0xe0, 0x7, 0x70, 0xe, + 0x78, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F59A "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x73, 0x8e, 0xed, 0x67, 0xed, 0x67, 0xff, 0xff, + 0xff, 0xff, 0xe0, 0x7, 0xe0, 0x7, 0x70, 0xe, + 0x78, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F59B "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x77, 0xee, 0xf9, 0x9f, 0xf3, 0xcf, 0xff, 0xff, + 0xff, 0xff, 0xe0, 0x7, 0xf0, 0xf, 0x70, 0xe, + 0x78, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F59C "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xce, 0xf9, 0x87, 0xf9, 0xb7, 0xff, 0xff, + 0xff, 0xff, 0xe0, 0x7, 0xe0, 0x7, 0x70, 0xe, + 0x78, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F59D "" */ + 0xe0, 0x70, 0x3c, 0x3e, 0x3, 0x8, 0x80, 0xdf, + 0xfc, 0x37, 0xff, 0xd, 0xff, 0xc3, 0x7f, 0xf0, + 0xdf, 0xfc, 0x37, 0xff, 0xd, 0xff, 0xc3, 0x0, + 0x0, 0xc0, 0x0, 0x3f, 0xff, 0xc7, 0xff, 0xf0, + 0xe0, 0x70, 0x30, 0x1c, + + /* U+F59F "" */ + 0x0, 0x0, 0x7, 0xc1, 0xc7, 0xff, 0xf3, 0xf3, + 0xfc, 0xfc, 0x3f, 0x3f, 0xf, 0xcf, 0xc2, 0x3, + 0xf0, 0x3c, 0xfc, 0x1f, 0xbf, 0xf, 0xff, 0xc3, + 0xff, 0xf0, 0xff, 0xff, 0x1f, 0xbf, 0xe7, 0xee, + 0xc, 0xf0, 0x0, 0x3c, 0x0, 0x6, 0x0, + + /* U+F5A0 "" */ + 0x0, 0x0, 0x7, 0xc1, 0xc7, 0xff, 0xf3, 0xf3, + 0xfc, 0xfc, 0x3f, 0x3f, 0xf, 0xcf, 0xc2, 0x3, + 0xf0, 0x3c, 0xfc, 0x1f, 0xbf, 0xe, 0x7f, 0xc3, + 0x9f, 0xf0, 0xe7, 0xff, 0x1f, 0xbf, 0xe7, 0xee, + 0xc, 0xf0, 0x0, 0x3c, 0x0, 0x6, 0x0, + + /* U+F5A1 "" */ + 0x1, 0xbe, 0x2, 0x7f, 0xc, 0xff, 0x19, 0xff, + 0x13, 0xfe, 0x3, 0xfc, 0x9, 0xf8, 0x1c, 0xf0, + 0x3e, 0x60, 0x3f, 0x0, 0x7f, 0x80, 0x7f, 0x0, + 0xfe, 0x0, 0xfc, 0x0, 0xf0, 0x0, + + /* U+F5A2 "" */ + 0x10, 0x61, 0xe1, 0xef, 0x8f, 0x9f, 0x3e, 0x7f, + 0xf0, 0xff, 0xc3, 0xfe, 0xf, 0xfc, 0x3d, 0xf1, + 0xf3, 0xc7, 0x7, 0x9e, 0x3e, 0x78, 0x70, 0xfd, + 0xc3, 0xff, 0x7, 0xf8, 0xf, 0x80, + + /* U+F5A4 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0xdf, 0xf9, 0xdf, 0xf9, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F5A5 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xed, 0xb7, 0xe1, 0x87, + 0xf3, 0xcf, 0xff, 0xff, 0xff, 0xff, 0x78, 0x1e, + 0x78, 0x1e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F5A6 "" */ + 0x4, 0x1, 0xc0, 0x7c, 0x1f, 0xc3, 0xf8, 0x7f, + 0xf, 0xe1, 0xfc, 0x3f, 0x87, 0xf0, 0xc6, 0x1f, + 0xc3, 0xf8, 0xff, 0x9f, 0xf7, 0xff, 0xff, 0xe0, + + /* U+F5A7 "" */ + 0x0, 0x3, 0x0, 0x7, 0x0, 0x1e, 0x0, 0x3c, + 0x0, 0x78, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfc, 0x3f, 0xfc, + 0x1f, 0xf8, 0x7, 0xe0, 0xf, 0xf0, 0xf, 0xf0, + + /* U+F5AA "" */ + 0x7f, 0xf0, 0xff, 0xf8, 0xff, 0xfe, 0xff, 0xff, + 0xff, 0xfb, 0x7f, 0xf3, 0x0, 0x3, 0x0, 0x3, + 0x1, 0xff, 0x3, 0xfe, 0x3, 0x0, 0x7, 0x80, + 0x7, 0x80, 0x7, 0x80, 0x7, 0x80, 0x7, 0x80, + 0x7, 0x80, + + /* U+F5AB "" */ + 0x7f, 0xef, 0xff, 0xf0, 0xfe, 0x7, 0xd7, 0x3d, + 0x6b, 0xd6, 0xbd, 0x73, 0xe0, 0x7f, 0xf, 0xff, + 0xff, 0xff, 0xe0, 0x7e, 0x7, 0xff, 0xf7, 0xfe, + + /* U+F5AC "" */ + 0x0, 0xe, 0x0, 0x1f, 0x0, 0x3f, 0x0, 0x7f, + 0x0, 0xfe, 0x0, 0xfc, 0x1, 0xf8, 0x1, 0xf0, + 0x18, 0xc0, 0x3c, 0x0, 0x7e, 0x0, 0x67, 0x0, + 0x67, 0x0, 0x5e, 0x0, 0xbc, 0x0, 0x40, 0x0, + + /* U+F5AD "" */ + 0x0, 0xc, 0x0, 0x1e, 0x0, 0x3f, 0x1, 0x9f, + 0xf, 0xce, 0x1f, 0xe4, 0x1f, 0xf0, 0x3f, 0xf8, + 0x38, 0xf8, 0x38, 0xf0, 0x70, 0xf0, 0x63, 0xf0, + 0x47, 0xe0, 0x8f, 0x80, 0x1c, 0x0, 0x20, 0x0, + + /* U+F5AE "" */ + 0x18, 0x1c, 0x30, 0x3e, 0x66, 0x3f, 0xfc, 0x9f, + 0xf9, 0xcf, 0x73, 0xe6, 0x27, 0xf0, 0xf, 0xf8, + 0x1f, 0xf0, 0x3f, 0xe4, 0x7f, 0xcc, 0x7f, 0x99, + 0x7f, 0x3b, 0xfe, 0x7e, 0xfc, 0x3c, 0xe0, 0x18, + + /* U+F5AF "" */ + 0x3, 0x0, 0x0, 0xe0, 0x0, 0x3c, 0x3, 0xf, + 0x0, 0xe3, 0xe0, 0x3f, 0xf8, 0xf, 0xff, 0x1, + 0xff, 0xfe, 0x1f, 0xff, 0xc0, 0x1f, 0xf0, 0x0, + 0x18, 0xd, 0x80, 0x0, 0x60, 0x0, 0x0, 0xf, + 0xff, 0xff, 0xff, 0xff, + + /* U+F5B0 "" */ + 0xc, 0x0, 0x7, 0xc0, 0x0, 0xfc, 0x38, 0x1f, + 0xff, 0x7, 0xff, 0xe0, 0xff, 0xcf, 0xff, 0xc1, + 0xff, 0x80, 0x7f, 0x0, 0xe, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, + 0xff, 0xfc, + + /* U+F5B1 "" */ + 0xfe, 0x3, 0xfc, 0xc, 0x18, 0x30, 0x60, 0xc1, + 0x83, 0xfc, 0xf, 0xe0, 0xf3, 0x87, 0xc7, 0x3b, + 0xf, 0xc0, 0x1e, 0x0, 0x78, 0x3, 0xf0, 0x1c, + 0xe0, 0xe1, 0xc3, 0x3, + + /* U+F5B3 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xe1, 0x87, 0xed, 0xb7, 0xff, 0xff, + 0xf7, 0xef, 0xf6, 0x6f, 0xf6, 0x6f, 0x76, 0x6e, + 0x76, 0x6e, 0x37, 0xec, 0x17, 0xe8, 0x7, 0xe0, + + /* U+F5B4 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xfb, 0x9f, 0xfb, 0x9f, 0xfb, 0x9f, + 0xff, 0xff, 0xff, 0xff, 0xf3, 0x1f, 0x73, 0xce, + 0x73, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F5B6 "" */ + 0x7f, 0xfe, 0x3f, 0xff, 0xcc, 0x63, 0x33, 0x18, + 0xc6, 0xc6, 0x31, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xdc, 0xfe, 0x73, 0x21, + 0x10, 0xc8, 0x6c, 0x1c, 0xe, 0x0, + + /* U+F5B7 "" */ + 0x7, 0x80, 0x0, 0xfc, 0x0, 0xc, 0xc0, 0x0, + 0xcc, 0x0, 0xc, 0xce, 0x0, 0xf, 0xe0, 0x3, + 0xfe, 0x0, 0xf8, 0xfc, 0x1d, 0x8f, 0xc1, 0x98, + 0x0, 0x7b, 0x3f, 0xef, 0xb3, 0xff, 0x1e, 0x0, + 0x0, 0xe0, 0x0, + + /* U+F5B8 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x73, 0x8e, 0xe1, 0x7, 0xed, 0x67, 0xff, 0xff, + 0xff, 0xff, 0xf7, 0xef, 0xf3, 0xcf, 0x78, 0x1e, + 0x7c, 0x3e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F5BA "" */ + 0x3f, 0xff, 0xf, 0xff, 0xc3, 0x33, 0x31, 0xcc, + 0xce, 0x62, 0x11, 0x9f, 0xff, 0xe6, 0x21, 0x19, + 0x88, 0x46, 0xc2, 0x10, 0xff, 0xff, 0xf7, 0xff, + 0xf8, 0x3, 0x0, 0x0, 0xc0, 0x1, 0xfe, 0x0, + 0x7f, 0x80, + + /* U+F5BB "" */ + 0x0, 0xc0, 0x0, 0x78, 0x0, 0x1e, 0x0, 0xf, + 0xc0, 0x3, 0xf0, 0x3c, 0x78, 0xff, 0xcc, 0xff, + 0xf8, 0x7f, 0xff, 0x3f, 0x9f, 0xff, 0xe3, 0xff, + 0xf0, 0xff, 0xfc, 0xf, 0xfc, 0x1, 0xfe, 0x0, + + /* U+F5BC "" */ + 0x0, 0xc0, 0x1, 0xe0, 0x3, 0xe0, 0xff, 0xf0, + 0xff, 0xf0, 0xff, 0xf8, 0x7f, 0xfe, 0x3f, 0xff, + 0x3f, 0xff, 0x3f, 0xfe, 0x7f, 0xf8, 0x7f, 0xf0, + 0x7f, 0xf0, 0x7f, 0xf0, 0x0, 0xf0, 0x0, 0x70, + + /* U+F5BD "" */ + 0x1c, 0x0, 0x1e, 0xdb, 0x1e, 0xdb, 0x1e, 0x0, + 0x0, 0x1b, 0x7f, 0x9b, 0xff, 0xc0, 0xff, 0xc3, + 0xf3, 0xc3, 0xe1, 0xc0, 0xe1, 0xc0, 0xe1, 0xc0, + 0xf3, 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0xff, 0xc0, + + /* U+F5BF "" */ + 0x3, 0xc0, 0x7, 0xe0, 0x7, 0xe0, 0x7, 0xe0, + 0x7, 0xe0, 0x3, 0xc0, 0x3, 0xc0, 0x3, 0xc0, + 0x3f, 0xfc, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, + + /* U+F5C0 "" */ + 0x0, 0xc0, 0x0, 0x30, 0x0, 0x1e, 0x0, 0x7, + 0x80, 0x3, 0xb0, 0xf, 0xef, 0xcf, 0xf9, 0xfd, + 0xfe, 0xe, 0x3f, 0x87, 0x7, 0xe3, 0x80, 0xf8, + 0xc0, 0x3e, 0x30, 0xf, 0xcc, 0x3, 0xff, 0x0, + 0xf3, 0xc0, 0x70, 0x38, + + /* U+F5C1 "" */ + 0x1f, 0x1, 0x18, 0x11, 0x81, 0x18, 0x7f, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, + 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xe3, 0xc, + + /* U+F5C2 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf9, 0xdf, 0xf9, 0xdf, 0xff, 0xff, + 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x7f, 0x7e, 0x7e, + 0x7e, 0x7e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F5C3 "" */ + 0xfc, 0x0, 0xfc, 0x0, 0xcc, 0x20, 0xcc, 0x70, + 0xfc, 0xf8, 0xfc, 0xfc, 0xcc, 0xfc, 0xcc, 0xf8, + 0xfc, 0xf0, 0xfc, 0xe0, 0xfc, 0xcf, 0xfc, 0x9f, + 0xcc, 0x3f, 0xcc, 0x7f, 0xfc, 0xff, 0x79, 0xff, + + /* U+F5C4 "" */ + 0x0, 0x3, 0x0, 0x70, 0x78, 0xf, 0xe7, 0x81, + 0xcf, 0x30, 0x1, 0xf8, 0x0, 0x3f, 0x80, 0x0, + 0xe0, 0x0, 0x0, 0x0, 0xf, 0x9f, 0xf, 0x8f, + 0x1f, 0x0, 0x0, 0x0, + + /* U+F5C5 "" */ + 0x3, 0xc3, 0xc0, 0x7e, 0x7e, 0x6, 0x66, 0x60, + 0x66, 0x66, 0x6, 0x6, 0x0, 0x60, 0x60, 0x7, + 0xfe, 0x0, 0x7f, 0xe0, 0x6, 0x6, 0x0, 0x40, + 0x60, 0x0, 0x0, 0x0, 0x60, 0x60, 0xf, 0x9f, + 0x7, 0x8f, 0x1f, 0x0, 0x0, 0x0, + + /* U+F5C7 "" */ + 0x0, 0x0, 0x30, 0x30, 0x6, 0x1e, 0x0, 0xc7, + 0x80, 0x1b, 0xf0, 0x3, 0xfe, 0x0, 0x7f, 0x80, + 0xf, 0xf0, 0x9, 0xfc, 0x7, 0x3f, 0x81, 0xe7, + 0xe0, 0x7c, 0xf8, 0x1f, 0x9e, 0x3, 0xf3, 0x80, + 0xfe, 0x60, 0x1f, 0xce, 0x1, 0xe1, 0xc0, 0x0, + 0x30, + + /* U+F5C8 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xf3, 0xcf, 0xf9, 0x9f, 0xf3, 0xcf, + 0xff, 0xff, 0xfc, 0x3f, 0xf8, 0x1f, 0x70, 0xe, + 0x77, 0xee, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F5C9 "" */ + 0x3c, 0xf1, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xf8, + 0xff, 0xc3, 0xcf, 0xf, 0x3c, 0x3c, 0xf0, 0xe1, + 0xc3, 0x87, 0x6, 0x18, + + /* U+F5CA "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0x1f, 0xff, 0x7, 0xff, 0x1, 0xff, + 0x3, 0xff, 0x3, 0x1f, 0x7, 0x7, 0x6, 0x0, + 0xe, 0x0, 0xc, 0x0, 0xff, 0xff, 0xff, 0xff, + + /* U+F5CD "" */ + 0x3, 0xc0, 0x7, 0xe0, 0x6, 0x60, 0x6, 0x60, + 0x1f, 0xf8, 0x1f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, + 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + + /* U+F5CE "" */ + 0x7f, 0x9f, 0xe6, 0x1b, 0x87, 0xc0, 0xf0, 0x3c, + 0xf, 0x3, 0xe1, 0xdf, 0xe3, 0xf0, 0x30, 0xc, + 0x3, 0x0, 0xc1, 0xfe, 0x7f, 0x80, + + /* U+F5D0 "" */ + 0x1e, 0x22, 0x1e, 0x77, 0x1e, 0x22, 0x0, 0x8, + 0x0, 0x1c, 0x7f, 0x88, 0xff, 0xc2, 0xff, 0xc7, + 0xf3, 0xc2, 0xe1, 0xc0, 0xe1, 0xc0, 0xe1, 0xc0, + 0xf3, 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0xff, 0xc0, + + /* U+F5D1 "" */ + 0x0, 0xc0, 0x7, 0x0, 0x3c, 0x0, 0x40, 0x3c, + 0xf1, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xf9, 0xff, + 0xe3, 0xff, 0x7, 0xf8, + + /* U+F5D2 "" */ + 0x7, 0x0, 0x1e, 0x0, 0xcc, 0x1f, 0xfe, 0xff, + 0xff, 0x7f, 0xbf, 0x87, 0xde, 0xde, 0x7b, 0x7b, + 0xe1, 0xfd, 0xfe, 0xff, 0xff, 0x7f, 0xf8, 0x33, + 0x0, 0x78, 0x1, 0xc0, + + /* U+F5D7 "" */ + 0x78, 0x1, 0xef, 0xc0, 0x3f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xfe, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0x3f, 0x78, 0x1, 0xe0, + + /* U+F5DA "" */ + 0x3, 0xc0, 0x7, 0xe0, 0x7, 0xe0, 0x7, 0xe0, + 0x3, 0xc0, 0x0, 0x0, 0xfc, 0x3f, 0xff, 0xff, + 0xff, 0xc3, 0xff, 0x3, 0xff, 0x3, 0xff, 0x3, + 0xff, 0x3, 0xff, 0x3f, 0xff, 0xff, 0x3, 0xc0, + + /* U+F5DC "" */ + 0x7, 0x70, 0x7, 0xbc, 0x3, 0xde, 0x7, 0xef, + 0xc3, 0xf7, 0xe1, 0xfb, 0xf0, 0xfd, 0xf8, 0xfe, + 0xfe, 0xff, 0x7f, 0xbf, 0xbf, 0x9f, 0xdf, 0xcf, + 0xef, 0xe7, 0xf7, 0xf3, 0xfb, 0xf8, 0x7d, 0xf0, + 0x1e, 0xf0, + + /* U+F5DE "" */ + 0xf, 0xf0, 0x1f, 0xf8, 0x30, 0xc, 0x30, 0xc, + 0x30, 0xe, 0x7f, 0xfe, 0xff, 0xff, 0xcf, 0xf3, + 0xce, 0x73, 0xfe, 0x7f, 0xff, 0xff, 0x7f, 0xfe, + 0x70, 0xe, 0x70, 0xe, + + /* U+F5DF "" */ + 0x1c, 0x38, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xef, 0xff, 0xef, 0xc1, 0x83, 0xff, 0xef, + 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xfe, + + /* U+F5E1 "" */ + 0x6, 0x0, 0x0, 0xc0, 0x1, 0x99, 0x80, 0x18, + 0x60, 0x0, 0x0, 0x0, 0x0, 0xe0, 0x38, 0x3f, + 0xc0, 0x6, 0x7c, 0x1, 0x80, 0xc6, 0x78, 0x19, + 0x9f, 0xe3, 0x3, 0x3f, 0xe0, 0x67, 0xfe, 0x1f, + 0xfc, 0xc3, 0x9f, 0x98, 0x20, 0x3e, 0x0, 0x1, + 0xc0, 0x0, 0x38, 0x0, 0x2, 0x0, + + /* U+F5E4 "" */ + 0xf, 0xf8, 0x1, 0xff, 0xc0, 0x39, 0x8c, 0x3, + 0x18, 0x60, 0x31, 0x87, 0xf, 0xff, 0xfc, 0xff, + 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x73, 0xfc, 0xe3, 0x20, 0xc8, 0x32, 0xc, + 0x81, 0xc0, 0x70, + + /* U+F5E7 "" */ + 0x7f, 0x80, 0x3f, 0xc0, 0x3e, 0xf1, 0x5f, 0x78, + 0xaf, 0x3d, 0xff, 0xe, 0xff, 0xc7, 0x7f, 0xe7, + 0x9e, 0xf7, 0xc2, 0x7f, 0xe1, 0x3f, 0xfc, 0x9f, + 0xfe, 0x4f, 0xff, 0x27, 0xfe, 0xf3, 0xff, 0x1, + 0xff, 0x80, + + /* U+F5EB "" */ + 0x1, 0x80, 0x3, 0xc0, 0x7, 0xe0, 0xf, 0xf0, + 0x1f, 0xf8, 0x3f, 0x3c, 0x7f, 0x9e, 0xfc, 0xf, + 0xfb, 0x9f, 0x7b, 0x3e, 0x3b, 0xfc, 0x1f, 0xf8, + 0xf, 0xf0, 0x7, 0xe0, 0x3, 0xc0, 0x1, 0x80, + + /* U+F5EE "" */ + 0x60, 0x6, 0xff, 0xff, 0xff, 0xff, 0x60, 0xe, + 0x60, 0x1c, 0x60, 0x18, 0x60, 0x70, 0x60, 0xf0, + 0x60, 0xf0, 0x60, 0x70, 0x60, 0x18, 0x60, 0x1c, + 0x60, 0xe, 0xff, 0xff, 0xff, 0xff, 0x60, 0x6, + + /* U+F5FC "" */ + 0x1f, 0xff, 0x83, 0xff, 0xfc, 0x30, 0x0, 0xc3, + 0x0, 0xc, 0x30, 0x0, 0xc3, 0x19, 0x8c, 0x33, + 0xc, 0xc3, 0x30, 0xcc, 0x31, 0x98, 0xc3, 0x0, + 0xc, 0x0, 0x0, 0xf, 0xff, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0xfe, + + /* U+F5FD "" */ + 0x1, 0x80, 0xf, 0xf0, 0x3f, 0xfc, 0xff, 0xff, + 0xff, 0xff, 0x3f, 0xfc, 0xf, 0xf0, 0x41, 0x87, + 0xf0, 0x1f, 0x7c, 0x7c, 0xf, 0xf0, 0x43, 0x87, + 0xf0, 0x1f, 0x7c, 0x7c, 0xf, 0xf0, 0x3, 0x80, + + /* U+F601 "" */ + 0x0, 0xc0, 0x0, 0xf0, 0x1, 0xfe, 0x1, 0xc7, + 0x81, 0x80, 0xc1, 0xcf, 0x70, 0xcf, 0xd9, 0xe7, + 0xef, 0xf3, 0xf7, 0x99, 0xfb, 0xe, 0x7b, 0x83, + 0x1, 0x80, 0xe3, 0xc0, 0x3f, 0xc0, 0x7, 0x80, + 0x1, 0x80, + + /* U+F604 "" */ + 0x0, 0xc0, 0x1, 0xb6, 0x0, 0xed, 0xc0, 0x7b, + 0x78, 0x3e, 0xdf, 0xf, 0xb7, 0xc7, 0xff, 0xf9, + 0xfc, 0xfe, 0xfe, 0x1f, 0xbf, 0x87, 0xff, 0xe1, + 0xff, 0xf8, 0x7f, 0xfe, 0x1f, 0xff, 0x87, 0xff, + 0xc0, 0xfd, 0xe0, 0x1e, + + /* U+F610 "" */ + 0xf, 0x80, 0xf, 0x80, 0xf, 0x80, 0xf, 0x80, + 0xf, 0xf8, 0xf, 0xfc, 0xf, 0x8e, 0xf, 0x87, + 0xf, 0x83, 0x0, 0x3, 0x0, 0x3, 0x1f, 0xc3, + 0x0, 0x6, 0x0, 0xe, 0xff, 0xff, 0xff, 0xff, + + /* U+F613 "" */ + 0x7, 0xe0, 0x0, 0x7e, 0x0, 0x1, 0x80, 0xf, + 0xff, 0xdf, 0xdf, 0xff, 0xed, 0xff, 0xfc, 0xff, + 0xff, 0x87, 0xff, 0xf0, 0x1f, 0xfe, 0x1, 0xff, + 0xc0, 0x7, 0xf8, 0x0, + + /* U+F619 "" */ + 0x0, 0x0, 0x0, 0xc0, 0x0, 0xe0, 0x1, 0xe0, + 0x7, 0xe0, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, + 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe, 0x3f, 0xfc, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F61F "" */ + 0x1, 0x0, 0xe, 0x0, 0x7c, 0x1, 0xf0, 0xf, + 0xe0, 0x3f, 0x80, 0xfe, 0x0, 0x0, 0x0, 0x0, + 0xe1, 0xe7, 0xcf, 0xff, 0xbf, 0xfe, 0xff, 0xfb, + 0xf7, 0xcf, 0xce, 0x1e, + + /* U+F621 "" */ + 0x3, 0x80, 0x7, 0x0, 0xe, 0x3, 0x1c, 0x6f, + 0x39, 0xff, 0xff, 0xcf, 0xfe, 0x7, 0xf0, 0xf, + 0xe0, 0x7f, 0xf3, 0xff, 0xff, 0x9c, 0xf6, 0x38, + 0xc0, 0x70, 0x0, 0xe0, 0x1, 0xc0, + + /* U+F624 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7e, 0x7e, + 0x76, 0x6e, 0xfe, 0x7f, 0xfe, 0x7f, 0xde, 0x7b, + 0xde, 0x7b, 0xfe, 0x7f, 0xfc, 0x3f, 0x7c, 0x3e, + 0x7e, 0x7e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F625 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3e, 0x7c, 0x7e, 0x7e, + 0x77, 0xfe, 0xff, 0xcf, 0xff, 0x9f, 0xcf, 0x93, + 0xcf, 0x33, 0xfe, 0x3f, 0xfc, 0x3f, 0x7c, 0x3e, + 0x7e, 0x7e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F629 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7e, 0x7e, + 0x7e, 0x7e, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, + 0xfe, 0x7f, 0xfe, 0x7f, 0xfc, 0x3f, 0x7c, 0x3e, + 0x7e, 0x7e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F62A "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xcf, 0xff, 0x9f, 0xff, 0x9f, + 0xff, 0x3f, 0xfe, 0x3f, 0xfc, 0x3f, 0x7c, 0x3e, + 0x7e, 0x7e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F62E "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0x8f, 0xc8, 0x8b, 0xc8, 0x8b, 0xff, 0xff, + 0xc8, 0x8b, 0xc8, 0x8b, 0xf8, 0x8f, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, + + /* U+F62F "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xf8, 0x8f, 0xc8, 0x8b, + 0xc8, 0x8b, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x7f, 0xfe, 0xc8, 0x8b, 0xc8, 0x8b, + 0xf8, 0x8f, 0x7f, 0xfe, + + /* U+F630 "" */ + 0xf, 0xc0, 0xf, 0xc0, 0x7, 0xc7, 0xf3, 0xf7, + 0xfe, 0xfd, 0xff, 0xd8, 0x7f, 0xf6, 0xdc, 0xcd, + 0xf7, 0x33, 0x7d, 0xff, 0xde, 0x7f, 0xf3, 0x1d, + 0xec, 0xf7, 0x3, 0xc, 0xe1, 0x80, 0x3f, 0xe0, + 0x7, 0xf0, 0x0, 0x70, + + /* U+F637 "" */ + 0x7f, 0xbf, 0xff, 0x3f, 0x87, 0xe1, 0xfc, 0xff, + 0xff, 0xcf, 0xe1, 0xf8, 0x7f, 0x3f, 0xff, 0xf3, + 0xf8, 0x7e, 0x1d, 0xce, 0x7f, 0x87, 0x80, + + /* U+F63B "" */ + 0x3, 0xf0, 0x0, 0xfe, 0x0, 0x31, 0x80, 0xc, + 0x30, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xff, 0x99, 0x86, 0x6e, 0x73, + 0x9f, 0x9c, 0xe7, 0x66, 0x19, 0x9f, 0x87, 0xe1, + 0x80, 0x60, + + /* U+F63C "" */ + 0x1, 0xf8, 0x0, 0x1f, 0x80, 0x1, 0x8c, 0x0, + 0x18, 0xe0, 0x1, 0x86, 0x7, 0xff, 0xfe, 0x7f, + 0xff, 0xe7, 0xff, 0xfe, 0x7f, 0xff, 0xef, 0xff, + 0xff, 0xf3, 0xfc, 0xf3, 0x20, 0xcc, 0x32, 0x4, + 0xc1, 0xc0, 0x38, + + /* U+F641 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xf7, + 0xe1, 0xc7, 0xed, 0x7, 0xe1, 0x37, 0xed, 0x37, + 0xed, 0x87, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F644 "" */ + 0x1e, 0xf, 0xc7, 0x39, 0x86, 0x61, 0x98, 0x63, + 0x30, 0xfc, 0xff, 0xff, 0xf0, 0xc0, 0x30, 0xc, + 0x3, 0x0, 0xc0, 0x30, + + /* U+F647 "" */ + 0x7f, 0xff, 0xff, 0xff, 0xcf, 0xff, 0x3f, 0xf0, + 0x3f, 0xc0, 0xff, 0xcf, 0xff, 0x3f, 0xfc, 0xff, + 0xf3, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1b, 0x0, + 0x6f, 0xff, 0xdf, 0xff, + + /* U+F64A "" */ + 0x7, 0xe0, 0x1, 0x8, 0x0, 0xc3, 0x1, 0xff, + 0xf8, 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xfe, 0x3, + 0xff, 0x0, 0x0, 0x1f, 0x3f, 0xf, 0xef, 0xc7, + 0xbf, 0xfd, 0xef, 0xff, 0x79, 0xff, 0xdf, 0xf7, + 0xf7, 0xfc, 0x0, 0xfe, 0x0, 0x1f, 0x0, + + /* U+F64F "" */ + 0x24, 0x78, 0x19, 0xbf, 0x6, 0x6f, 0xc1, 0xff, + 0x30, 0xff, 0xcc, 0x3f, 0xff, 0xc, 0xcc, 0xfb, + 0x33, 0x3f, 0xff, 0xff, 0xf3, 0x33, 0x3c, 0xcc, + 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0xff, 0xfe, + + /* U+F651 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0x7e, 0xfc, 0x7f, 0xfd, 0xff, 0xfc, 0x3f, + 0xff, 0xbf, 0xfc, 0x3f, 0x7f, 0x7e, 0x7f, 0xfe, + 0x7f, 0xfc, 0x7f, 0xf8, 0xff, 0xe0, 0xc0, 0x0, + + /* U+F653 "" */ + 0x1f, 0x0, 0xf, 0xf0, 0x7, 0xde, 0x3, 0xc7, + 0xc0, 0xf7, 0xf0, 0x3c, 0x3c, 0xf, 0xef, 0x43, + 0xc3, 0xdc, 0x7d, 0xe7, 0x9f, 0xf3, 0xff, 0xf8, + 0xff, 0x0, 0xff, 0x0, 0x7f, 0xc0, 0xff, 0xf0, + 0x1f, 0xf8, 0x3, 0xfe, 0x0, 0x7f, 0xc0, 0x0, + 0x30, + + /* U+F654 "" */ + 0xf, 0x0, 0xf0, 0xf, 0x0, 0xf0, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf, 0x0, 0xf0, 0xf, + 0x0, 0xf0, 0xf, 0x0, 0xf0, 0xf, 0x0, 0xf0, + + /* U+F655 "" */ + 0x1, 0x80, 0x7, 0xe0, 0x3f, 0xfc, 0x3d, 0xbc, + 0x3d, 0xbc, 0x7f, 0xfe, 0x67, 0xe6, 0xfe, 0x7f, + 0xfe, 0x7f, 0x67, 0xe6, 0x7f, 0xfe, 0x3d, 0xbc, + 0x3d, 0xbc, 0x3f, 0xfc, 0x7, 0xe0, 0x1, 0x80, + + /* U+F658 "" */ + 0x1, 0x80, 0x3, 0xf0, 0x3, 0x9c, 0x3, 0x87, + 0x7, 0x81, 0xc7, 0x0, 0x3b, 0x0, 0x1, 0xc0, + 0xff, 0xf8, 0x7f, 0xfe, 0x30, 0xff, 0x98, 0x7f, + 0xef, 0xff, 0xf6, 0x3f, 0xfb, 0x1f, 0xfd, 0xfe, + 0xfe, 0xff, 0x0, 0x7f, 0x80, + + /* U+F65D "" */ + 0x3c, 0x0, 0xff, 0x0, 0xff, 0xfe, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xfe, + + /* U+F65E "" */ + 0x7f, 0x0, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x7f, 0xfe, 0x7f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xfe, 0x7f, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xfe, + + /* U+F662 "" */ + 0xff, 0xff, 0x3f, 0xff, 0xc7, 0xff, 0xf0, 0xff, + 0xf8, 0x1f, 0xfc, 0x3, 0xf8, 0x0, 0x3d, 0xf0, + 0xe, 0xfe, 0x3, 0x73, 0xc0, 0xdc, 0xf0, 0x37, + 0xfc, 0xd, 0xff, 0x1, 0x71, 0xc0, 0x4f, 0xe0, + 0x1, 0xf0, + + /* U+F664 "" */ + 0x16, 0xd8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1e, 0xf8, + 0x1c, 0xf8, 0x37, 0xcc, 0x76, 0xce, 0x74, 0xce, + 0x74, 0xce, 0xef, 0xe7, 0xef, 0xe7, 0xec, 0x67, + 0xec, 0x67, 0xec, 0x67, 0xef, 0xe7, 0xef, 0xe7, + + /* U+F665 "" */ + 0x1, 0x80, 0x19, 0x98, 0x1d, 0xb8, 0x1d, 0xb8, + 0x1d, 0xb8, 0x1d, 0xb8, 0x1d, 0xb8, 0x1f, 0xf8, + 0x1f, 0xf8, 0xfe, 0x7f, 0xf9, 0x9f, 0x79, 0x9e, + 0x3e, 0x7c, 0x1f, 0xf8, 0xf, 0xf0, 0x7, 0xc0, + + /* U+F666 "" */ + 0x0, 0x80, 0x0, 0xe0, 0x7, 0x77, 0x3, 0xff, + 0x81, 0xf7, 0xc0, 0xe0, 0xe3, 0xf0, 0x7f, 0xe0, + 0xf, 0x38, 0xe, 0xc, 0x6, 0xf, 0x7, 0x8f, + 0xd7, 0xe7, 0xff, 0xf0, 0x3f, 0x80, 0x1d, 0xc0, + 0xc, 0x60, + + /* U+F669 "" */ + 0x18, 0x86, 0xc, 0x20, 0xc3, 0x8, 0x31, 0xc2, + 0x1e, 0x78, 0x87, 0x9f, 0x23, 0xe7, 0xc8, 0xf1, + 0xe2, 0x1e, 0x79, 0xa7, 0x9e, 0x31, 0xe3, 0xbf, + 0x70, 0xe7, 0x9c, 0x3c, 0xcf, 0x7, 0xff, 0x0, + 0x7f, 0x80, + + /* U+F66A "" */ + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xf5, + 0xbf, 0xd6, 0x7e, 0x49, 0xfd, 0xa7, 0xf0, 0x3f, + 0xe1, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1b, 0x0, + 0x6f, 0xff, 0xdf, 0xff, + + /* U+F66B "" */ + 0x1, 0x80, 0xf, 0xf0, 0x7c, 0x3e, 0xe0, 0x7, + 0xfc, 0x1f, 0x7f, 0xfe, 0x1f, 0xf8, 0xc3, 0xc3, + 0xf8, 0xf, 0x1e, 0x78, 0x8f, 0xf1, 0xfc, 0x3f, + 0xfe, 0x3f, 0x7f, 0xfe, 0xf, 0xf0, 0x3, 0xc0, + + /* U+F66D "" */ + 0x1, 0x80, 0x3, 0xc0, 0x13, 0xc4, 0x67, 0xe6, + 0x6d, 0xb6, 0xed, 0xb7, 0xed, 0xb7, 0xe7, 0xe7, + 0xf3, 0xcf, 0x7b, 0xde, 0x7d, 0xbe, 0x3f, 0xfc, + 0x19, 0x98, 0x7, 0xe0, 0x5, 0xa0, 0x1, 0x80, + + /* U+F66F "" */ + 0x1, 0x80, 0x7, 0xe0, 0xf, 0xf0, 0x3f, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0x36, 0x6c, 0x36, 0x6c, + 0x36, 0x6c, 0x36, 0x6c, 0x36, 0x6c, 0x36, 0x6c, + 0x36, 0x6c, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, + + /* U+F674 "" */ + 0x3f, 0xfc, 0xf, 0xff, 0x3, 0xc0, 0x0, 0xe0, + 0x0, 0x33, 0xff, 0xcd, 0xff, 0xf3, 0x7f, 0x8c, + 0x0, 0xe3, 0xff, 0xd8, 0xff, 0xf7, 0xf7, 0xf9, + 0xfc, 0x78, 0x7f, 0xc0, 0xdf, 0xfc, 0xf0, 0xf, + 0xfc, 0x3, 0xff, 0x0, + + /* U+F676 "" */ + 0x2, 0x8, 0x2d, 0xb6, 0xdb, 0xdb, 0x6d, 0xb0, + 0x0, 0x0, 0xdb, 0x6d, 0xbd, 0xb6, 0xdb, 0xdb, + 0x6d, 0xbd, 0xb6, 0xdb, 0xdb, 0x6d, 0xbc, 0x6, + 0x3, 0xff, 0xff, 0xf7, 0xff, 0xfe, 0x0, 0x60, + 0x0, 0x6, 0x0, 0xf, 0xff, 0x0, 0xff, 0xf0, + + /* U+F678 "" */ + 0x0, 0xc0, 0x0, 0x78, 0x0, 0x7f, 0x80, 0x3f, + 0xf0, 0x1f, 0xfe, 0x7, 0xff, 0x81, 0xff, 0xe0, + 0x3f, 0xf0, 0xc0, 0x0, 0xf0, 0x0, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xff, + 0xf3, 0xff, 0xfc, 0xff, 0x7f, 0xff, 0x80, + + /* U+F679 "" */ + 0x0, 0x10, 0x0, 0x10, 0x0, 0xc6, 0x1c, 0xfe, + 0x3e, 0x38, 0x3, 0x0, 0x3, 0x1e, 0x3, 0x3f, + 0x1f, 0xf3, 0x1f, 0xe3, 0x3, 0x83, 0xc1, 0xb3, + 0xc1, 0xb3, 0xe3, 0xbf, 0x7f, 0x1e, 0x3e, 0x0, + + /* U+F67B "" */ + 0x6, 0x6, 0x0, 0xf0, 0xf0, 0xf, 0xf, 0x0, + 0x70, 0xe0, 0x3, 0xfc, 0x0, 0x3f, 0xc0, 0xc7, + 0xfe, 0x3e, 0xff, 0xf6, 0x3f, 0xff, 0xc0, 0x7f, + 0xe0, 0xc7, 0xfe, 0xe, 0xdf, 0xb7, 0x38, 0x91, + 0xc0, 0x19, 0x80, 0x1, 0x8, 0x0, 0x60, 0x60, + + /* U+F67C "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3d, 0xbc, 0x71, 0x8e, + 0x61, 0x86, 0xe1, 0x87, 0xc1, 0x83, 0xc1, 0x83, + 0xc3, 0xc3, 0xc7, 0xe3, 0xff, 0xff, 0x79, 0x9e, + 0x71, 0x8e, 0x3d, 0xbc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F67F "" */ + 0x1, 0x80, 0x3, 0xc0, 0x7, 0xe0, 0x7, 0xe0, + 0x7, 0xe0, 0x7, 0xe0, 0x1f, 0xf8, 0x1f, 0xf8, + 0x1f, 0xf8, 0x7f, 0xfe, 0xfe, 0x7f, 0xfc, 0x3f, + 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0x7f, 0xfe, + + /* U+F681 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, + 0xff, 0xf3, 0xfe, 0xcf, 0xfb, 0x3f, 0xec, 0xdf, + 0xb3, 0x7e, 0xcd, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F682 "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xff, + 0xff, 0xff, 0xfe, 0x1, 0xf8, 0x7, 0xff, 0xff, + 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F683 "" */ + 0x1, 0x80, 0x3c, 0x3, 0xc0, 0x18, 0x0, 0x0, + 0xe0, 0x1f, 0x73, 0xff, 0x3f, 0xe7, 0xd8, 0x78, + 0x7, 0x80, 0x3c, 0x1, 0xf0, 0xff, 0x8f, 0xf8, + + /* U+F684 "" */ + 0x1, 0x8c, 0x0, 0x38, 0xe0, 0x7, 0x7, 0x0, + 0x70, 0x70, 0xe, 0x3, 0x81, 0xc0, 0x1c, 0x1d, + 0x99, 0xc1, 0xd9, 0x9c, 0x1d, 0x99, 0xc1, 0xd9, + 0x9c, 0x1f, 0x9f, 0xcf, 0xf9, 0xff, 0xff, 0x9f, + 0xff, 0xf0, 0xff, 0xfc, 0x3, 0xfe, 0x0, 0x7, + + /* U+F687 "" */ + 0xff, 0xfb, 0xff, 0xff, 0x87, 0xfc, 0x1f, 0xe1, + 0xff, 0x8d, 0xfe, 0x33, 0xf8, 0xcf, 0xf1, 0xff, + 0xe1, 0xff, 0xff, 0xff, 0xff, 0x60, 0xd, 0x80, + 0x3f, 0xff, 0xff, 0xfe, + + /* U+F688 "" */ + 0xf, 0x80, 0x3f, 0xe0, 0x7f, 0xf0, 0x7c, 0xf0, + 0xf0, 0xf8, 0xf3, 0xf8, 0xf0, 0x78, 0xfe, 0x78, + 0xf0, 0x78, 0x7c, 0xf0, 0x7f, 0xf0, 0x3f, 0xf8, + 0xf, 0x9c, 0x0, 0xe, 0x0, 0x7, 0x0, 0x3, + + /* U+F689 "" */ + 0xf, 0x80, 0x3f, 0xe0, 0x7f, 0xf0, 0x78, 0xf0, + 0xf0, 0x78, 0xf6, 0x78, 0xf6, 0x78, 0xf8, 0xf8, + 0xf8, 0xf8, 0x7d, 0xf0, 0x7f, 0xf0, 0x3f, 0xf8, + 0xf, 0x9c, 0x0, 0xe, 0x0, 0x7, 0x0, 0x3, + + /* U+F696 "" */ + 0x7, 0x7e, 0x1e, 0xfc, 0x0, 0x0, 0x0, 0x0, + 0xf7, 0xe1, 0xef, 0xc3, 0xdf, 0x87, 0xbf, 0xe, + 0x7e, 0x39, 0xfc, 0xe7, 0xfb, 0x9f, 0xff, 0x7f, + 0xde, 0xff, 0x3c, 0xf8, 0x3c, 0xe0, + + /* U+F698 "" */ + 0x0, 0x7f, 0xc0, 0x1f, 0xf0, 0x6, 0x0, 0x3, + 0x0, 0x0, 0xc0, 0x0, 0x30, 0xf, 0x1c, 0xcf, + 0xe6, 0x3f, 0x19, 0x87, 0x87, 0x61, 0xe0, 0xf0, + 0xfc, 0x1c, 0x33, 0x7, 0x0, 0x0, 0x80, 0x0, + + /* U+F699 "" */ + 0x7, 0xe0, 0x1f, 0x0, 0x3e, 0x0, 0x7c, 0x0, + 0x78, 0x8, 0xf0, 0x18, 0xf0, 0x3e, 0xf0, 0x7f, + 0xf0, 0x3e, 0xf0, 0x3c, 0xf0, 0x3e, 0x78, 0x2, + 0x7c, 0x0, 0x3c, 0x0, 0x1f, 0x0, 0x7, 0xe0, + + /* U+F69A "" */ + 0x1, 0x80, 0x3, 0xc0, 0x3, 0xc0, 0x6, 0x60, + 0xff, 0xff, 0xff, 0xff, 0x4c, 0x32, 0x78, 0x1e, + 0x30, 0xc, 0x30, 0xc, 0x78, 0x1e, 0x4c, 0x32, + 0xff, 0xff, 0xff, 0xff, 0x6, 0x60, 0x3, 0xc0, + 0x3, 0xc0, 0x1, 0x80, + + /* U+F69B "" */ + 0x60, 0x3, 0x78, 0x3, 0xfc, 0x21, 0xe0, 0x78, + 0x0, 0x77, 0x7, 0xfb, 0xff, 0xfd, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, + 0xc7, 0xff, 0xe3, 0xff, 0xf1, 0xff, 0xf8, 0xfe, + 0xff, 0xfe, + + /* U+F6A0 "" */ + 0xe0, 0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xc0, 0xff, + 0xf8, 0x7f, 0xfe, 0x1f, 0xff, 0x3, 0xff, 0xf3, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x80, 0x7, + + /* U+F6A1 "" */ + 0x0, 0x3, 0x0, 0x3f, 0xff, 0xff, 0xff, 0x7f, + 0xf8, 0xcc, 0xc3, 0x33, 0x1f, 0xfe, 0x7f, 0xf8, + 0xc0, 0xc3, 0x3, 0xc, 0xc, 0x30, 0x30, 0xc0, + 0xc3, 0x3, 0xc, 0xc, + + /* U+F6A7 "" */ + 0x0, 0xc0, 0x0, 0x78, 0x0, 0x7f, 0x80, 0x3f, + 0xf0, 0x1f, 0xfe, 0x3, 0x3, 0x0, 0xc0, 0xc0, + 0x7f, 0xf8, 0x3f, 0xff, 0x1f, 0xff, 0xe3, 0x0, + 0x30, 0xc0, 0xc, 0x3f, 0xff, 0x3f, 0xff, 0xf3, + 0xc, 0x30, 0xc3, 0xc, + + /* U+F6A9 "" */ + 0x1, 0x80, 0x1, 0xc0, 0x1, 0xe0, 0x1, 0xf0, + 0xf, 0xf9, 0xf, 0xfc, 0xff, 0xfe, 0x3d, 0xff, + 0x1e, 0xff, 0x9f, 0xff, 0xc8, 0x43, 0xe0, 0x0, + 0xf0, 0x0, 0x38, 0x0, 0xc, 0x0, + + /* U+F6AD "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3c, 0x3c, 0x70, 0x1e, + 0x61, 0x9e, 0xe1, 0x9f, 0xc0, 0x1f, 0xc0, 0x3f, + 0xc3, 0xff, 0xc7, 0xff, 0xe6, 0x7f, 0x66, 0x7e, + 0x77, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F6B6 "" */ + 0x39, 0xff, 0xcf, 0x7f, 0xe7, 0x9f, 0xc1, 0x87, + 0xf0, 0x61, 0xff, 0xb8, 0x7f, 0xce, 0xf, 0xc1, + 0x83, 0xf0, 0x78, 0xff, 0x1e, 0x3f, 0xc3, 0xcf, + 0xe0, 0x0, 0x0, 0x1, 0xff, 0x0, 0x7b, 0xc0, + 0x1e, 0xf0, 0x7, 0xfc, + + /* U+F6B7 "" */ + 0x7f, 0xff, 0xe3, 0xff, 0x87, 0xfe, 0xdf, 0xf8, + 0x7f, 0xf3, 0xff, 0xff, 0xfc, 0xcf, 0xfc, 0xff, + 0xcc, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1b, 0x0, + 0x6f, 0xff, 0xdf, 0xff, + + /* U+F6BB "" */ + 0x6, 0x60, 0x7, 0xe0, 0x3, 0xc0, 0x1, 0x80, + 0x3, 0xc0, 0x7, 0xe0, 0xf, 0xf0, 0x1f, 0xf8, + 0x1f, 0xf8, 0x3f, 0xfc, 0x7e, 0x7e, 0xfc, 0x3f, + 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, + + /* U+F6BE "" */ + 0x0, 0x21, 0x0, 0x33, 0x0, 0x3f, 0x40, 0x3f, + 0xe0, 0x2d, 0x70, 0x3f, 0x30, 0x1e, 0x37, 0x9c, + 0x3f, 0xe0, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xec, + 0x3f, 0xcc, 0x3f, 0x8c, 0x3f, 0xcc, 0x1f, 0xcc, + + /* U+F6C0 "" */ + 0x7, 0x80, 0x73, 0x81, 0x4a, 0xd, 0x2c, 0x34, + 0xb0, 0xd2, 0xc3, 0x4b, 0xd, 0x2c, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x60, 0x19, 0x80, + 0x66, 0x1, 0x98, 0x6, + + /* U+F6C3 "" */ + 0x0, 0xf, 0x0, 0xf, 0x80, 0x7, 0xc0, 0x1, + 0xe0, 0x0, 0xf8, 0x0, 0x3e, 0x0, 0xf, 0x80, + 0x38, 0x30, 0x1f, 0xe6, 0x7, 0xfd, 0xf7, 0xff, + 0x3f, 0xff, 0xe4, 0xff, 0xfc, 0x3f, 0xff, 0xf, + 0xff, 0xc1, 0xff, 0xe0, + + /* U+F6C4 "" */ + 0x0, 0x44, 0x0, 0x3b, 0x80, 0xf, 0xe0, 0xf, + 0xfe, 0x7, 0x83, 0xc0, 0x2e, 0xe0, 0x0, 0xb8, + 0x38, 0xe, 0x1f, 0x73, 0x8f, 0xfe, 0xf3, 0xff, + 0x81, 0xff, 0xf8, 0xff, 0xff, 0x3f, 0xff, 0xcf, + 0xff, 0xf3, 0xff, 0xfc, 0x7f, 0xfe, 0x0, + + /* U+F6C8 "" */ + 0x1f, 0xfe, 0x3, 0xff, 0xf0, 0x78, 0xf, 0x87, + 0xc1, 0xff, 0x7e, 0x3f, 0xf7, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0xff, 0xdf, 0x1d, 0xfd, 0xd1, 0xcb, + 0xdf, 0x1c, 0x1c, 0xf1, 0xc1, 0xc6, + + /* U+F6CF "" */ + 0x1, 0x80, 0x7, 0xe0, 0x1e, 0x78, 0x3e, 0x7c, + 0xfd, 0x3f, 0x9d, 0xb9, 0xc0, 0x3, 0xf0, 0xf, + 0xf0, 0xf, 0xe3, 0xc7, 0xed, 0xb7, 0xcd, 0xb3, + 0xce, 0x73, 0xe0, 0x7, 0x3c, 0x3c, 0x1e, 0x78, + 0x6, 0x60, 0x1, 0x80, + + /* U+F6D1 "" */ + 0x3, 0x80, 0x1f, 0xc0, 0xff, 0xe3, 0xff, 0xef, + 0xff, 0xf9, 0xfc, 0xf8, 0xe3, 0xfc, 0x9f, 0xfe, + 0xff, 0xfd, 0xff, 0xfb, 0xff, 0xf7, 0xf7, 0xef, + 0xc3, 0xde, 0x3, 0xf8, 0x1, 0xc0, + + /* U+F6D3 "" */ + 0x0, 0x30, 0x0, 0xf, 0x80, 0x3, 0xdc, 0x0, + 0xf7, 0x0, 0x3f, 0xf0, 0xf, 0xef, 0xf8, 0x81, + 0xff, 0x80, 0x3f, 0xf8, 0xf, 0xfe, 0x3, 0xff, + 0x80, 0xff, 0xe0, 0x39, 0x38, 0xe, 0xe, 0x3, + 0x83, 0x80, 0xe0, 0xe0, 0x38, 0x38, 0x0, + + /* U+F6D5 "" */ + 0x0, 0x7f, 0x0, 0x3, 0xf8, 0x0, 0x7e, 0xc0, + 0xc7, 0xfe, 0x1e, 0x1f, 0xe3, 0xf9, 0xde, 0x7f, + 0xdc, 0xf, 0x7d, 0xe0, 0xf, 0xdf, 0x81, 0xfc, + 0xfc, 0x0, 0xf, 0xe0, 0x0, 0x7f, 0x0, 0x3, + 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xef, 0xff, 0xfc, + + /* U+F6D7 "" */ + 0x0, 0xf8, 0x1, 0xfc, 0x3, 0xfe, 0x3, 0xff, + 0x7, 0xff, 0x7, 0xf8, 0x7, 0xf0, 0x7, 0xe0, + 0x7, 0xe0, 0xf, 0xe0, 0xff, 0xf0, 0xfe, 0x0, + 0xfc, 0x0, 0x7c, 0x0, 0x3c, 0x0, 0x1c, 0x0, + + /* U+F6D9 "" */ + 0x3, 0xc0, 0x13, 0xc8, 0x39, 0x9c, 0x78, 0x1e, + 0x38, 0x1c, 0x41, 0x82, 0xe5, 0xa7, 0xf5, 0xaf, + 0xf5, 0xaf, 0x5, 0xa0, 0x5, 0xa0, 0xf5, 0xaf, + 0xf5, 0xaf, 0xf5, 0xaf, 0xf5, 0xaf, 0xf5, 0xaf, + + /* U+F6DD "" */ + 0x3e, 0x0, 0x7f, 0xc0, 0x3f, 0xf0, 0x1f, 0xdc, + 0xf, 0xe7, 0x7, 0xf1, 0x83, 0xff, 0xe1, 0xff, + 0xf0, 0xff, 0xf8, 0x7f, 0xfc, 0x3f, 0xfe, 0x1f, + 0x80, 0xf, 0x9b, 0xaf, 0xd5, 0x17, 0xe8, 0xea, + 0xf5, 0x17, 0x1, 0xb9, 0x0, + + /* U+F6DE "" */ + 0x4, 0x6, 0xf8, 0x7f, 0x87, 0xfb, 0x1, 0xb0, + 0x1b, 0x7c, 0x3f, 0xdc, 0xe3, 0xff, 0xff, 0x7f, + 0xe3, 0xfe, 0x1f, 0xc1, 0xfc, 0x1f, 0xc0, 0xfc, + + /* U+F6E2 "" */ + 0xf, 0x3, 0xfc, 0x7f, 0xe7, 0xfe, 0xff, 0xfe, + 0x67, 0xe6, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xd1, 0x98, + + /* U+F6E3 "" */ + 0x0, 0x0, 0x0, 0x3f, 0x0, 0x7, 0xf0, 0x0, + 0x3f, 0x0, 0x7, 0xf0, 0x0, 0xfe, 0x0, 0xf, + 0xc0, 0xc, 0xff, 0x3, 0xc7, 0xe0, 0xfc, 0x78, + 0x3f, 0xe, 0xf, 0xc0, 0x3, 0xf0, 0x0, 0xfc, + 0x0, 0x1f, 0x0, 0x3, 0xc0, 0x0, 0x70, 0x0, + 0x0, + + /* U+F6E6 "" */ + 0x0, 0x40, 0x1, 0x56, 0xa8, 0xd5, 0xa, 0xbd, + 0x50, 0xab, 0x0, 0x60, 0xd, 0x56, 0xab, 0xd5, + 0x6a, 0xbd, 0x56, 0xab, 0xd5, 0x6a, 0xbc, 0x6, + 0x3, 0xff, 0xff, 0xf7, 0xff, 0xfe, 0x0, 0x60, + 0x0, 0x6, 0x0, 0xf, 0xff, 0x0, 0xff, 0xf0, + + /* U+F6E8 "" */ + 0x0, 0x18, 0x0, 0x70, 0x1, 0xf0, 0x3, 0xf0, + 0x6, 0x30, 0x7, 0x70, 0xf, 0xf0, 0xf, 0xf0, + 0x1c, 0xf8, 0x18, 0x78, 0x18, 0x78, 0x3c, 0xfc, + 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, + + /* U+F6EC "" */ + 0x7, 0x0, 0x78, 0x3, 0xc0, 0x0, 0x6, 0x1, + 0xf7, 0x8f, 0xbe, 0x7d, 0xff, 0xef, 0xff, 0x78, + 0xc3, 0xc6, 0xe, 0x30, 0x39, 0x84, 0xec, 0x73, + 0x67, 0x1b, 0x70, 0xdb, 0x2, 0xc0, + + /* U+F6ED "" */ + 0x0, 0x4, 0x40, 0x7f, 0x7c, 0x1f, 0xff, 0xc3, + 0xff, 0xd4, 0x3f, 0xff, 0xe7, 0xff, 0xff, 0x7f, + 0xdd, 0xb7, 0xfd, 0xff, 0x7f, 0xef, 0xef, 0xff, + 0x66, 0xbf, 0xf0, 0x3, 0xc3, 0xc0, 0x3c, 0x3c, + 0x3, 0xc3, 0xc0, + + /* U+F6F0 "" */ + 0x0, 0xf, 0xc0, 0x7, 0xf0, 0x3, 0xec, 0x0, + 0xff, 0x1f, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xeb, + 0x7f, 0xf8, 0xdf, 0xfc, 0x33, 0xff, 0x0, 0xe7, + 0x80, 0x78, 0xe0, 0x1c, 0x38, 0x7, 0x8e, 0x0, + 0xe3, 0x80, 0x38, 0xe0, + + /* U+F6F1 "" */ + 0x0, 0x80, 0x0, 0xe0, 0x0, 0xfb, 0x80, 0xff, + 0xc0, 0xff, 0xe0, 0xff, 0xf1, 0xff, 0xfc, 0xff, + 0xfe, 0x7f, 0xff, 0x1f, 0x3f, 0xf, 0xcf, 0x87, + 0xe3, 0xc3, 0xe3, 0xe1, 0xe3, 0xf0, 0xf1, 0xf8, + 0x38, 0xf8, + + /* U+F6F2 "" */ + 0x3, 0x81, 0xfc, 0x1c, 0xe0, 0x6, 0x0, 0x6f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x0, 0x30, + 0x3, 0xbe, 0x1f, 0xe0, 0xc0, + + /* U+F6FA "" */ + 0x7, 0xf8, 0xf, 0xff, 0xc7, 0xff, 0xf9, 0xff, + 0xfe, 0xf3, 0xf3, 0xf8, 0x78, 0x7e, 0x1e, 0x1f, + 0xcf, 0xcf, 0xff, 0xff, 0xdf, 0xcf, 0xe3, 0xf3, + 0xf0, 0x78, 0x78, + + /* U+F6FC "" */ + 0x1, 0x80, 0x1, 0x80, 0x3, 0xc0, 0x7, 0xe0, + 0x6, 0x60, 0xe, 0x70, 0xc, 0x30, 0x1c, 0xf8, + 0x1d, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, + + /* U+F6FF "" */ + 0x3, 0xf0, 0x0, 0xfc, 0x0, 0x33, 0x0, 0xf, + 0xc0, 0x3, 0xf0, 0x0, 0x30, 0xf, 0xff, 0xff, + 0xff, 0xff, 0xc, 0xc, 0xf, 0xcf, 0xc3, 0xf3, + 0xf0, 0xcc, 0xcc, 0x3f, 0x3f, 0xf, 0xcf, 0xc0, + + /* U+F700 "" */ + 0x1f, 0x0, 0x1, 0x8c, 0xc, 0x18, 0x30, 0x38, + 0xeb, 0x80, 0xe6, 0x6f, 0x3, 0x3f, 0xf8, 0xdc, + 0xff, 0x86, 0xe6, 0x1e, 0x77, 0x30, 0x77, 0xb9, + 0xf8, 0x3f, 0xc7, 0xc1, 0xfc, 0x3f, 0xff, 0xc0, + 0xff, 0xfc, 0x1, 0xff, 0x80, + + /* U+F70B "" */ + 0xf, 0xf0, 0x3f, 0xfc, 0x70, 0xe, 0xc0, 0x3, + 0xcf, 0xf3, 0xf8, 0x1f, 0xf8, 0x1f, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, 0x3f, 0xfc, 0xf, 0xf0, + + /* U+F70C "" */ + 0x1, 0x80, 0x1e, 0x0, 0x60, 0x0, 0x0, 0x0, + 0x1f, 0x81, 0xfe, 0xc, 0xf8, 0x6f, 0xf8, 0x7b, + 0xc3, 0x80, 0x1f, 0x80, 0x3e, 0xc, 0x30, 0xe3, + 0xe, 0x18, 0xe1, 0x86, 0xc, 0x0, + + /* U+F70E "" */ + 0x7f, 0xf8, 0x33, 0xff, 0xc, 0xff, 0xe3, 0x3f, + 0xf8, 0xff, 0xfe, 0x3, 0xff, 0x80, 0xff, 0xe0, + 0x3f, 0xf8, 0xf, 0x0, 0x3, 0x80, 0x0, 0xe7, + 0xfc, 0x3b, 0xff, 0xe, 0xff, 0xc3, 0xbf, 0xe0, + + /* U+F714 "" */ + 0x7, 0x80, 0x7f, 0x81, 0xfe, 0xf, 0xfc, 0x33, + 0x30, 0xcc, 0xc1, 0xfe, 0x7, 0xf8, 0xf, 0xc0, + 0x3f, 0xc, 0x0, 0xfe, 0x1f, 0x3f, 0xf0, 0x3f, + 0x3, 0xff, 0x3e, 0x1f, 0xc0, 0xc, + + /* U+F715 "" */ + 0xc0, 0x0, 0x38, 0x0, 0x7, 0x0, 0x0, 0xe0, + 0x0, 0x1c, 0x0, 0x3, 0x80, 0x0, 0x70, 0x0, + 0xe, 0x0, 0x1, 0xc0, 0x0, 0x38, 0x0, 0x7, + 0x0, 0x0, 0xe0, 0x0, 0x1c, 0x0, 0x3, 0x80, + 0x0, 0x70, 0x0, 0xe, 0x0, 0x1, 0xc0, 0x0, + 0x30, + + /* U+F717 "" */ + 0x8, 0x10, 0x18, 0x18, 0x18, 0x8, 0x10, 0x8, + 0x33, 0xcc, 0xfb, 0xdf, 0xdf, 0xfb, 0x7f, 0xfe, + 0x3f, 0xfc, 0x3f, 0xfc, 0x7f, 0xfe, 0xdf, 0xfb, + 0xff, 0xff, 0x37, 0xec, 0x13, 0xc8, 0x18, 0x8, + 0x18, 0x18, 0x8, 0x18, + + /* U+F71E "" */ + 0x3f, 0xcc, 0x7f, 0xde, 0x7f, 0x9e, 0xff, 0xbf, + 0xff, 0xb3, 0xff, 0xb3, 0xff, 0xb3, 0xff, 0xb3, + 0xff, 0xbf, 0xff, 0x9e, 0xff, 0x9e, 0xff, 0x8c, + 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0x7f, 0x0, + + /* U+F71F "" */ + 0x3f, 0xcc, 0x7f, 0xde, 0x7f, 0x9e, 0xff, 0xbf, + 0xff, 0xb3, 0xff, 0xb3, 0xff, 0xb3, 0xff, 0xb3, + 0xff, 0xbf, 0xff, 0x9e, 0xff, 0x9e, 0xff, 0x8c, + 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0x7f, 0x0, + + /* U+F722 "" */ + 0x1f, 0x80, 0x7, 0xe0, 0x1, 0x8c, 0x20, 0x63, + 0x8, 0x18, 0x62, 0x7, 0xff, 0xf7, 0xff, 0xfd, + 0xff, 0xff, 0x73, 0xff, 0xf8, 0x7f, 0xee, 0x1f, + 0xf9, 0xce, 0x33, 0x7f, 0x8c, 0xdf, 0xc3, 0xf0, + 0xc0, 0x78, + + /* U+F728 "" */ + 0x6, 0x0, 0xec, 0x0, 0x0, 0x0, 0x1, 0xfc, + 0xf, 0xe0, 0x3e, 0x0, 0xe0, 0x0, 0x0, 0x0, + 0x3, 0xf0, 0x4f, 0xc7, 0x7, 0x78, 0x1f, 0xee, + 0xff, 0x37, + + /* U+F729 "" */ + 0x7f, 0xff, 0xbf, 0xff, 0xff, 0x3f, 0xbf, 0x87, + 0xc7, 0xe1, 0xf1, 0xf8, 0x7c, 0x7f, 0x3f, 0xbf, + 0xff, 0xff, 0xff, 0x3f, 0xdf, 0x87, 0xe0, + + /* U+F72B "" */ + 0x2, 0x3, 0x7, 0x7, 0x22, 0xe, 0x20, 0x1c, + 0xf8, 0x38, 0x20, 0x78, 0x21, 0xe0, 0x3, 0xc0, + 0xf, 0xc0, 0xf, 0x84, 0x1f, 0x4, 0x3e, 0x1f, + 0x7c, 0x4, 0xf8, 0x4, 0xf0, 0x0, 0x60, 0x0, + + /* U+F72E "" */ + 0x0, 0x78, 0x0, 0x7c, 0x0, 0xc, 0x0, 0xc, + 0xff, 0xfc, 0xff, 0xf8, 0x0, 0x0, 0xff, 0xfe, + 0xff, 0xff, 0x0, 0x3, 0x78, 0x3, 0xfe, 0x1f, + 0x7f, 0x1e, 0x3, 0x0, 0x1f, 0x0, 0x1e, 0x0, + + /* U+F72F "" */ + 0x1e, 0x7, 0x81, 0xe0, 0x78, 0x1e, 0x7, 0x81, + 0xe1, 0xfe, 0x7f, 0xbf, 0xff, 0xff, 0x3, 0xc0, + 0xf0, 0x3c, 0xf, 0x3, 0xc0, 0xff, 0xff, 0xfd, + 0xfe, + + /* U+F73B "" */ + 0xe, 0x0, 0x1f, 0x70, 0x3f, 0xf8, 0x3f, 0xf8, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0x1f, 0x71, 0x8e, 0x7, 0xe0, 0xe7, 0xe7, + 0xef, 0xf7, 0xe7, 0xe7, 0x7, 0xe0, 0x1, 0x80, + + /* U+F73C "" */ + 0x0, 0xe, 0x0, 0xf, 0x0, 0x3, 0xc0, 0xf0, + 0xe0, 0x3f, 0x38, 0x1f, 0xe6, 0x7, 0xfc, 0xc3, + 0xff, 0xb8, 0xff, 0xef, 0xff, 0xfb, 0xc7, 0xfc, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x19, 0x10, 0x4, + 0xcc, 0x1, 0x32, 0x0, + + /* U+F73D "" */ + 0xe, 0x0, 0x1f, 0x70, 0x3f, 0xf8, 0x3f, 0xf8, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, + 0x10, 0x8, 0x31, 0x8c, 0x31, 0x8c, 0x31, 0x8c, + + /* U+F740 "" */ + 0xe, 0x0, 0x1f, 0x70, 0x3f, 0xf8, 0x3f, 0xf8, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, + 0x26, 0x44, 0x66, 0x4c, 0x64, 0xcc, 0x44, 0x88, + + /* U+F743 "" */ + 0x0, 0x0, 0x7, 0x70, 0x1, 0xfc, 0x0, 0x7f, + 0x0, 0xf8, 0x6, 0x1c, 0x9f, 0xc7, 0x67, 0xf9, + 0xd3, 0xfe, 0x71, 0xff, 0xfe, 0x7f, 0xf1, 0xdf, + 0xfc, 0x73, 0xfe, 0x1c, 0x0, 0x0, 0x0, 0x0, + 0x8, 0x98, 0x6, 0x66, 0x1, 0x91, 0x0, + + /* U+F747 "" */ + 0x24, 0x0, 0x3, 0xc0, 0x0, 0x18, 0x0, 0x3, + 0xc0, 0x0, 0x7e, 0x0, 0xf, 0xff, 0xfc, 0xff, + 0xff, 0xee, 0x66, 0xdf, 0x6, 0x6d, 0xa0, 0x3f, + 0xf8, 0x0, 0x0, 0x0, 0x3f, 0xf8, 0x3, 0xff, + 0x80, 0x38, 0x38, 0x3, 0x83, 0x80, 0x38, 0x38, + + /* U+F74D "" */ + 0xc0, 0x3, 0xfc, 0x3f, 0x7f, 0xff, 0x3e, 0xff, + 0x7, 0xff, 0xfc, 0xf, 0xbf, 0xc1, 0xff, 0xff, + 0x3, 0xef, 0xf0, 0x7f, 0xff, 0xc0, 0xfb, 0x0, + 0xc, 0x0, 0x30, 0x0, + + /* U+F751 "" */ + 0xc, 0x3, 0xc0, 0x7c, 0x7, 0xf0, 0xff, 0xcf, + 0xe, 0xe0, 0x6e, 0x67, 0xe6, 0x76, 0x7, 0x70, + 0xf3, 0xff, 0xf, 0xe0, 0x3e, 0x3, 0xc0, 0x30, + + /* U+F752 "" */ + 0x1, 0x80, 0x3, 0xc0, 0xf, 0xf0, 0x1f, 0xf8, + 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0x7f, 0xfe, + 0x36, 0x6c, 0x36, 0x6c, 0x36, 0x6c, 0x36, 0x6c, + 0x36, 0x6c, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, + + /* U+F753 "" */ + 0x0, 0x3, 0x0, 0x8f, 0x3, 0xfe, 0xf, 0xfe, + 0x1f, 0xfc, 0x3f, 0xfc, 0x70, 0xfc, 0xe0, 0x7e, + 0xcc, 0x3c, 0xcc, 0x3c, 0xc0, 0x38, 0xc2, 0x38, + 0x60, 0x70, 0x70, 0xe0, 0x3f, 0xc0, 0xf, 0x80, + + /* U+F756 "" */ + 0x70, 0x0, 0x78, 0xff, 0xdc, 0x7f, 0xe0, 0x3f, + 0xf0, 0x1f, 0xfb, 0x8f, 0xff, 0xc6, 0xff, 0xf0, + 0x7f, 0xff, 0x1f, 0xff, 0x8f, 0xfc, 0xf, 0xee, + 0x7, 0xf3, 0x9b, 0xfc, 0xed, 0xff, 0x36, 0xff, + 0x9b, 0x3, 0xcd, 0x81, 0xe2, 0xc0, 0xc0, + + /* U+F75A "" */ + 0x0, 0x0, 0x0, 0xc0, 0x0, 0xe0, 0x1, 0xe0, + 0x1f, 0xf8, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, + 0x7e, 0x1e, 0xfc, 0x9f, 0xf9, 0x9f, 0xf3, 0x9f, + 0x67, 0x8e, 0xf, 0xe0, 0x3, 0xc0, 0x3, 0x80, + 0x3, 0x0, 0x6, 0x0, + + /* U+F75B "" */ + 0x1, 0xf8, 0x0, 0x7f, 0xe0, 0x1e, 0x7, 0x83, + 0x80, 0x1c, 0x31, 0xf0, 0xc6, 0x7f, 0xc6, 0x6e, + 0xe, 0x6c, 0xcf, 0x63, 0xd9, 0xfb, 0x3d, 0x99, + 0xb3, 0xd9, 0x9b, 0x3d, 0x99, 0xb3, 0xd9, 0x9b, + 0x30, + + /* U+F75E "" */ + 0x1f, 0xfc, 0x7, 0xff, 0xf0, 0x7f, 0xff, 0xe, + 0xee, 0x78, 0xee, 0xe7, 0x8f, 0xff, 0xf8, 0xff, + 0xff, 0x80, 0x0, 0x0, 0xff, 0xff, 0x8f, 0xff, + 0xfb, 0xff, 0xfd, 0xbf, 0x7, 0xdb, 0xf0, 0x7d, + 0xfe, 0x3, 0xce, + + /* U+F75F "" */ + 0x1f, 0xf0, 0xf, 0xfe, 0x7, 0xff, 0xe1, 0xff, + 0xfc, 0x7f, 0xff, 0x9f, 0xff, 0xe7, 0xff, 0xf8, + 0xff, 0xfc, 0x1f, 0xfe, 0x0, 0x1e, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xf7, 0xff, + + /* U+F769 "" */ + 0x1e, 0x3c, 0x7e, 0xfc, 0xcd, 0x99, 0x9b, 0x33, + 0x33, 0xc6, 0x60, 0xc, 0xc0, 0x19, 0x80, 0x33, + 0x0, 0xe6, 0x3, 0xc6, 0x7, 0xc, 0xe, 0x18, + 0x1e, 0x60, 0x1f, 0xc0, 0x1e, 0x0, + + /* U+F76B "" */ + 0x1c, 0x3c, 0x7c, 0xfc, 0xf9, 0x99, 0xf3, 0xf3, + 0xe3, 0xc7, 0xc0, 0xf, 0x80, 0x1f, 0x0, 0x77, + 0x0, 0xee, 0x3, 0x8e, 0x7, 0x1c, 0xe, 0x38, + 0x1e, 0xf0, 0x1f, 0xc0, 0x1f, 0x0, + + /* U+F76C "" */ + 0xe, 0x0, 0x1f, 0x70, 0x3f, 0xf8, 0x3f, 0xf8, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0x1f, 0xfe, 0x1f, + 0xfc, 0xcf, 0x79, 0xde, 0x3, 0x80, 0x7, 0xe0, + 0x7, 0xe0, 0x1, 0xc0, 0x3, 0x80, 0x3, 0x0, + 0x0, 0x0, + + /* U+F76F "" */ + 0xff, 0xfb, 0xff, 0xef, 0xff, 0x0, 0x0, 0x0, + 0x1, 0xff, 0x83, 0xff, 0x0, 0x0, 0x0, 0x0, + 0x1f, 0xf0, 0x1f, 0xc0, 0x0, 0x0, 0x0, 0x1, + 0xe0, 0x7, 0x0, 0x18, + + /* U+F770 "" */ + 0x1, 0x80, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, + 0x3, 0xc0, 0x1, 0x80, 0x0, 0x0, 0x3, 0xc0, + 0x4, 0x20, 0xc, 0x30, 0x1f, 0x18, 0x3f, 0xdc, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + + /* U+F772 "" */ + 0xf, 0xfc, 0x7, 0xff, 0x81, 0xff, 0xe0, 0x7f, + 0x38, 0x1f, 0xce, 0x7, 0x67, 0x81, 0xc3, 0xe0, + 0x78, 0xf8, 0x1f, 0x7e, 0x37, 0xff, 0xbc, 0xff, + 0xcf, 0x0, 0x3, 0xc0, 0x0, 0xdf, 0xff, 0xe0, + + /* U+F773 "" */ + 0x0, 0x0, 0x3e, 0x7c, 0xe3, 0xc7, 0x0, 0x0, + 0x0, 0x0, 0x3e, 0x7c, 0xe3, 0xc7, 0x0, 0x0, + 0x0, 0x0, 0x1c, 0x38, 0x3e, 0x7c, 0xe3, 0xc7, + + /* U+F77C "" */ + 0x6, 0x0, 0xf0, 0xf, 0xc, 0xe3, 0xf0, 0xf7, + 0xfe, 0x3f, 0xc1, 0xf8, 0x0, 0x0, 0x0, 0x30, + 0xc7, 0x9e, 0x79, 0xe3, 0x9c, 0x19, 0x80, + + /* U+F77D "" */ + 0x1e, 0x1, 0xf8, 0x7, 0xe0, 0x3f, 0x80, 0xfe, + 0x1c, 0x0, 0xc0, 0x3, 0x3f, 0xfc, 0xff, 0xf3, + 0xff, 0xcf, 0xff, 0x1f, 0xf8, 0x0, 0x3, 0x81, + 0xce, 0x7, 0x38, 0x1c, + + /* U+F780 "" */ + 0x2, 0x20, 0x2, 0x8, 0x3, 0x76, 0x1, 0xbb, + 0x0, 0xc1, 0x80, 0x71, 0xc0, 0x7f, 0xf0, 0x7e, + 0xfe, 0x66, 0x33, 0x65, 0xf6, 0xe3, 0x76, 0x31, + 0xbb, 0x10, 0x1c, 0x0, 0x1b, 0x0, 0xf8, 0xf8, + 0x0, 0x0, + + /* U+F781 "" */ + 0x3, 0xc0, 0x0, 0xe0, 0x0, 0x67, 0x1c, 0x6e, + 0xe, 0xdc, 0xc, 0xf8, 0xd, 0xf7, 0x1b, 0xef, + 0x1, 0xdf, 0x3, 0x8e, 0x7, 0x1c, 0xe, 0x38, + 0x1f, 0xf0, 0x1f, 0xc0, 0x1f, 0x0, + + /* U+F783 "" */ + 0x18, 0x60, 0x61, 0x87, 0xff, 0xbf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0xff, + 0x87, 0xfe, 0x1f, 0xf8, 0x7f, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F784 "" */ + 0x18, 0x60, 0x61, 0x87, 0xff, 0xbf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x1f, + 0x80, 0x7e, 0x1, 0xf8, 0x7, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0x80, + + /* U+F786 "" */ + 0x3, 0xc0, 0x7e, 0x7, 0xe0, 0x3e, 0x31, 0xe3, + 0x86, 0x3c, 0x3, 0xe0, 0x0, 0x0, 0x0, 0x3e, + 0x3, 0xe0, 0x0, 0x0, 0x0, 0x7e, 0x3, 0xe0, + 0x1e, 0x0, 0x60, 0x0, + + /* U+F787 "" */ + 0x0, 0x8, 0x0, 0xe, 0x0, 0x7, 0x0, 0x1, + 0xe0, 0x3c, 0x78, 0x3f, 0x98, 0xf, 0xc0, 0x13, + 0xf0, 0x1f, 0xf8, 0xf, 0xfc, 0xf, 0xfe, 0x7, + 0xfe, 0x3, 0x3e, 0x3, 0xcc, 0x1, 0xf0, 0x1, + 0xe0, 0x0, 0xc0, 0x0, 0x0, + + /* U+F788 "" */ + 0x3f, 0x80, 0x60, 0x40, 0x60, 0x40, 0x3f, 0x80, + 0xe, 0x0, 0x7f, 0xfe, 0x7f, 0xfe, 0x6d, 0xb6, + 0xff, 0xff, 0xff, 0xff, 0xf2, 0x4f, 0xff, 0xff, + 0xff, 0xff, 0xc0, 0x3, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F78C "" */ + 0x60, 0x6, 0xf6, 0x6f, 0xfe, 0x7f, 0x7e, 0x7e, + 0x3e, 0x7c, 0x7e, 0x7e, 0x7e, 0x7e, 0x0, 0x0, + 0x0, 0x0, 0x7e, 0x7e, 0x7e, 0x7e, 0x3e, 0x7c, + 0x7e, 0x7e, 0xfe, 0x7f, 0xf6, 0x6f, 0x60, 0x6, + + /* U+F793 "" */ + 0x77, 0x3b, 0xbd, 0xce, 0xff, 0x73, 0xbf, 0x9c, + 0xe7, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff, 0xf9, + 0xff, 0xfe, 0x7f, 0xff, 0x9f, 0xff, 0xe3, 0xff, + 0xf0, 0xff, 0xfc, 0x30, 0x3, 0x0, 0x0, 0x0, + + /* U+F794 "" */ + 0x77, 0x33, 0x9e, 0xe7, 0x7b, 0xdc, 0xef, 0x73, + 0x90, 0x20, 0x0, 0x0, 0x0, 0x1a, 0x1f, 0xe7, + 0xe3, 0xf9, 0xfe, 0x7f, 0x7f, 0xef, 0xcf, 0x3d, + 0xf9, 0xc3, 0x9f, 0x38, 0x73, 0x3, 0xc, 0x0, + 0x73, 0x80, 0x3, 0xc0, + + /* U+F796 "" */ + 0xf, 0xe0, 0x1f, 0xf0, 0x3f, 0xfc, 0x3f, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xd9, 0xb3, 0xd9, 0xb3, 0xd9, 0xb3, 0xd9, 0xb3, + + /* U+F79C "" */ + 0xa, 0x0, 0xb, 0xe0, 0x3, 0xf0, 0x3, 0xff, + 0x0, 0xff, 0xe0, 0x3f, 0xe0, 0xf, 0xf7, 0x3b, + 0xfd, 0xfe, 0xff, 0x6e, 0xff, 0x9f, 0xef, 0xef, + 0xff, 0xfb, 0xcf, 0xfe, 0xf3, 0xff, 0xbc, 0xff, + 0xef, 0x3f, 0xfb, 0xcf, 0xfe, 0x73, 0x80, + + /* U+F79F "" */ + 0x6, 0x6, 0x0, 0xff, 0xf0, 0xd, 0xfb, 0x1, + 0xcf, 0x38, 0x1e, 0xf7, 0x83, 0xff, 0xfc, 0x3f, + 0xff, 0xc3, 0xff, 0xfc, 0x3f, 0x9f, 0xc1, 0xf9, + 0xf8, 0x1f, 0xf, 0x81, 0x80, 0x18, 0x58, 0x1, + 0xaf, 0x80, 0x1f, 0x7c, 0x3, 0xe0, 0xc0, 0x30, + + /* U+F7A0 "" */ + 0xff, 0xff, 0xff, 0xfc, 0x0, 0xf0, 0x3, 0xc0, + 0xf, 0x0, 0x3c, 0x0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xf8, 0xff, + 0xc0, + + /* U+F7A2 "" */ + 0x7, 0xe0, 0x1e, 0x38, 0x3e, 0xc, 0x77, 0x6, + 0x77, 0x6, 0xfe, 0x1b, 0xfc, 0xf3, 0xff, 0xf3, + 0xf8, 0x33, 0xf8, 0x3b, 0xf8, 0xf, 0x78, 0x6, + 0x78, 0x6, 0x3c, 0xc, 0x1f, 0x18, 0x7, 0xe0, + + /* U+F7A4 "" */ + 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0xff, + 0xff, 0xff, 0xf0, + + /* U+F7A5 "" */ + 0xcf, 0x3c, 0xf3, 0xcf, 0x3c, 0xf3, 0xcf, 0x3c, + 0xf3, 0xcf, 0x30, + + /* U+F7A6 "" */ + 0x0, 0x4, 0x0, 0xe, 0x0, 0xf, 0x0, 0x1e, + 0x0, 0x38, 0x7, 0x70, 0xf, 0xe0, 0xf, 0xc0, + 0x78, 0xe0, 0xf8, 0xe0, 0xf8, 0xe0, 0xff, 0xc0, + 0xff, 0x0, 0x7f, 0x0, 0x3f, 0x0, 0x1e, 0x0, + + /* U+F7A9 "" */ + 0x3c, 0x3c, 0x7c, 0x7e, 0xfe, 0x3f, 0xfe, 0x3f, + 0xfc, 0xff, 0xf9, 0xff, 0xfc, 0xfe, 0x7e, 0xfe, + 0x3f, 0x7c, 0x3f, 0xfc, 0x1f, 0xf8, 0xf, 0xf0, + 0x7, 0xe0, 0x1, 0x80, + + /* U+F7AA "" */ + 0x1, 0xc0, 0x3, 0x80, 0x7, 0x0, 0x31, 0x80, + 0xf7, 0x0, 0xc6, 0x0, 0x0, 0xd, 0xa4, 0x1f, + 0x78, 0x3e, 0xf0, 0x7d, 0xe3, 0xfb, 0xf7, 0xc3, + 0xef, 0x3, 0xde, 0x7, 0xa4, 0x9, + + /* U+F7AB "" */ + 0x0, 0x0, 0x0, 0xf0, 0x7, 0xe0, 0x1f, 0xf0, + 0x3f, 0xf8, 0x7f, 0xdc, 0x7f, 0xdc, 0xff, 0xfe, + 0xff, 0xfe, 0xfb, 0xfe, 0xf9, 0xff, 0xfc, 0x3e, + 0xff, 0x9c, 0xff, 0x80, 0xff, 0xc0, 0xff, 0xc0, + + /* U+F7AD "" */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, + 0x7f, 0xfe, 0x7f, 0xde, 0x36, 0xde, 0x36, 0xde, + 0x36, 0xde, 0x36, 0xde, 0x6, 0x1c, 0x6, 0xc, + 0x0, 0xc, 0x0, 0xc, 0x0, 0xc, 0x0, 0xc, + + /* U+F7AE "" */ + 0x3, 0xc0, 0x3, 0xf3, 0x1, 0xfc, 0xe0, 0xff, + 0x3c, 0x0, 0x0, 0x0, 0x0, 0xe, 0xff, 0xdf, + 0xbf, 0xf7, 0xef, 0x3d, 0xc1, 0x86, 0x0, 0x61, + 0x83, 0xf8, 0x7f, 0xfe, 0x1f, 0xff, 0xff, 0xf0, + + /* U+F7B5 "" */ + 0x3c, 0x1, 0xfc, 0xf, 0xf0, 0x3f, 0xe0, 0xff, + 0x83, 0xfe, 0xf, 0xfc, 0x1f, 0xff, 0x7f, 0xfd, + 0xff, 0xf3, 0xff, 0x8f, 0xfc, 0x0, 0x0, 0x0, + 0x7, 0xff, 0x1f, 0xfc, + + /* U+F7B6 "" */ + 0x0, 0x0, 0x36, 0x0, 0x33, 0x0, 0x19, 0x80, + 0x9, 0x80, 0x0, 0x0, 0x0, 0x0, 0xff, 0xfc, + 0xff, 0xfe, 0xff, 0xf7, 0xff, 0xf3, 0xff, 0xf3, + 0xff, 0xf7, 0xff, 0xfe, 0xff, 0xfc, 0xff, 0xf0, + 0x7f, 0xe0, + + /* U+F7B9 "" */ + 0x0, 0x0, 0x1c, 0x6, 0xf, 0x7, 0x8f, 0x83, + 0xe7, 0xe3, 0xf7, 0xe0, 0xff, 0xf7, 0x7e, 0xfb, + 0xbe, 0x1, 0xc0, 0x0, 0x0, 0x0, 0xf8, 0x0, + 0x7c, 0x0, 0x7f, 0x0, 0x7f, 0xc0, 0xf, 0xc0, + + /* U+F7BA "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x73, 0xce, + 0x63, 0xc6, 0xe3, 0xc7, 0xc6, 0x63, 0xc6, 0x63, + 0xfe, 0x7f, 0xfe, 0x7f, 0xff, 0xff, 0x7c, 0x3e, + 0x78, 0x1e, 0x3c, 0x3c, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F7BD "" */ + 0x18, 0x61, 0x81, 0xc6, 0x1c, 0x18, 0x61, 0x80, + 0x6, 0x0, 0x1c, 0x61, 0xc3, 0xe6, 0x3e, 0x7e, + 0x67, 0xe7, 0xe6, 0x7f, 0x7f, 0x67, 0xff, 0xf6, + 0xff, 0x3c, 0x67, 0xe3, 0xc6, 0x7f, 0x3c, 0x63, + 0xe3, 0xc6, 0x3e, 0x3c, 0x63, 0xe3, 0x46, 0x34, + + /* U+F7BF "" */ + 0x3, 0x0, 0x7, 0x88, 0xd, 0xdc, 0x18, 0xfe, + 0x1c, 0x7c, 0xe, 0xf8, 0x7, 0xfc, 0x7b, 0xee, + 0xff, 0xc7, 0x7f, 0xe3, 0x3f, 0x76, 0x1f, 0xbc, + 0x3f, 0x98, 0x77, 0x80, 0x63, 0x80, 0x1, 0x0, + + /* U+F7C0 "" */ + 0x3, 0xc0, 0x0, 0x70, 0x0, 0x18, 0x3, 0x8c, + 0x60, 0x66, 0xe0, 0x36, 0xf0, 0x1b, 0xf9, 0x9b, + 0xff, 0x1b, 0xff, 0x0, 0xff, 0x80, 0x7f, 0xc0, + 0x7f, 0xc0, 0x3f, 0xe0, 0x1f, 0xf0, 0x7, 0xc0, + + /* U+F7C2 "" */ + 0x1f, 0xe3, 0xff, 0x74, 0xbf, 0x4b, 0xf4, 0xbf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xfe, + + /* U+F7C4 "" */ + 0x7f, 0x8f, 0xfc, 0xff, 0xef, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xc3, 0x3c, 0x33, 0xff, + 0xfc, 0xc3, 0xcc, 0x3c, 0xc3, 0xff, 0xf7, 0xfe, + + /* U+F7C5 "" */ + 0x0, 0xc, 0x0, 0x1c, 0x0, 0x38, 0x7f, 0x0, + 0xfe, 0x0, 0x1c, 0x0, 0x70, 0x1, 0xc0, 0x3, + 0xc0, 0x13, 0xe0, 0x30, 0xc0, 0xc1, 0x89, 0x3, + 0x8, 0x6, 0xe, 0x1, 0x0, 0x3c, + + /* U+F7C9 "" */ + 0x0, 0x3, 0xc, 0x3, 0xc7, 0x1c, 0xc2, 0x7f, + 0x0, 0xf, 0x80, 0x10, 0xf0, 0x6, 0x3c, 0x1, + 0x80, 0x0, 0xe0, 0x60, 0x30, 0x38, 0x18, 0x7, + 0x18, 0x0, 0xfc, 0x0, 0x1c, 0x0, 0x3, 0x80, + 0x0, 0x70, 0x0, 0xf, 0x0, + + /* U+F7CA "" */ + 0x0, 0x6, 0x0, 0x1, 0xe0, 0x7, 0x18, 0x1, + 0xf8, 0x0, 0x37, 0xc0, 0x5, 0xfb, 0x1, 0x3f, + 0xe0, 0x27, 0x3c, 0x8, 0xe0, 0x82, 0x1e, 0x10, + 0x44, 0xe2, 0x11, 0x9c, 0x46, 0x73, 0x8, 0x1c, + 0x60, 0x3f, 0xff, 0xf0, + + /* U+F7CC "" */ + 0xf8, 0x0, 0x1f, 0xc0, 0x1, 0xf8, 0x7, 0x3f, + 0x80, 0xe7, 0xf0, 0x18, 0xff, 0x87, 0x1f, 0xff, + 0xe3, 0xff, 0xfc, 0x7f, 0xff, 0x87, 0xff, 0xe0, + 0x60, 0x30, 0x8c, 0x6, 0x3f, 0xff, 0xfc, + + /* U+F7CD "" */ + 0xf, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7f, 0xfe, 0xe5, 0x47, 0xfd, 0x5f, 0xe4, 0x47, + 0xfc, 0x77, 0xe5, 0x47, 0x7f, 0xfe, 0x7f, 0xfe, + 0x7f, 0xfc, 0x7f, 0xf8, 0xf7, 0xe0, 0xc0, 0x0, + + /* U+F7CE "" */ + 0x6, 0xe, 0x7, 0x1e, 0x3, 0xce, 0x0, 0xe0, + 0x1, 0xf8, 0x3, 0xfc, 0x7, 0xce, 0x7, 0x86, + 0x7, 0xc0, 0x6, 0xe0, 0xc6, 0x60, 0x76, 0x60, + 0x3f, 0xe0, 0x3, 0xff, 0x0, 0x1f, + + /* U+F7D0 "" */ + 0x0, 0xf0, 0x0, 0x1f, 0x80, 0x3, 0xfc, 0x0, + 0x36, 0xc0, 0x3, 0xfc, 0x0, 0x39, 0xc0, 0x1, + 0x98, 0x3, 0x1f, 0x8c, 0xf3, 0xfc, 0xf7, 0xff, + 0xfe, 0x1f, 0x9f, 0x80, 0x79, 0xe0, 0x7, 0xfe, + 0x0, 0x79, 0xe0, 0x7, 0x9e, 0x0, 0x7f, 0xe0, + 0x3, 0xfc, 0x0, 0x1f, 0x80, + + /* U+F7D2 "" */ + 0xf, 0xc0, 0x3, 0xf8, 0x0, 0x61, 0x80, 0xc, + 0x30, 0x1, 0x83, 0xe, 0x78, 0x63, 0x1f, 0x86, + 0x63, 0xff, 0xcc, 0x7f, 0xf9, 0x8f, 0xff, 0xf1, + 0xff, 0xfe, 0x60, 0x6, 0xcd, 0xb6, 0xd9, 0x80, + 0x1b, 0x1f, 0xfe, 0x71, 0xff, 0x87, + + /* U+F7D7 "" */ + 0xff, 0xff, 0xff, 0x0, 0xf, 0xff, 0xff, 0xf0, + 0x60, 0x6, 0x0, 0x60, 0x6, 0x0, 0x60, 0x6, + 0x0, 0x60, 0x6, 0x0, 0x60, + + /* U+F7D8 "" */ + 0xff, 0xfd, 0xff, 0xe6, 0x7f, 0x9f, 0xfe, 0x7f, + 0xf9, 0xff, 0xe7, 0xff, 0xf8, 0x7, 0xe0, 0x1f, + 0xff, 0xf7, 0xff, 0x9f, 0xfe, 0x3f, 0xf0, 0xff, + 0xc3, 0xff, 0xf, 0xfc, + + /* U+F7D9 "" */ + 0x60, 0x78, 0x7c, 0x7c, 0x1f, 0x3c, 0xf, 0x9c, + 0x23, 0xce, 0x30, 0xf1, 0xf8, 0xc, 0x1c, 0x3, + 0xe6, 0x4, 0xf8, 0x6, 0x7e, 0x7, 0x3f, 0x8f, + 0xdf, 0xe7, 0xe7, 0xff, 0xe1, 0xff, 0xe0, 0x7c, + 0xe0, 0x8, + + /* U+F7DA "" */ + 0x0, 0xc0, 0x6, 0xc0, 0x6, 0x1f, 0x0, 0xf8, + 0xf, 0xc0, 0x7d, 0x80, 0xc1, 0x80, 0x1, 0x80, + 0x3f, 0xfc, 0x7f, 0xfe, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, + 0x3f, 0xfc, + + /* U+F7E4 "" */ + 0x4, 0x0, 0xe0, 0xe, 0x0, 0xf0, 0xf, 0x80, + 0x7c, 0x67, 0xee, 0x3e, 0xe1, 0xfe, 0x1f, 0xf3, + 0xff, 0xff, 0x7f, 0xf7, 0xfe, 0x3f, 0xc0, 0xf8, + + /* U+F7E5 "" */ + 0x0, 0xc, 0x0, 0x7, 0x80, 0x7, 0xc0, 0x3, + 0xc6, 0x1, 0xe3, 0xc0, 0x73, 0xe0, 0x39, 0xf0, + 0xc, 0xf8, 0x7, 0x3c, 0x3, 0x9e, 0x1, 0xe7, + 0x0, 0xf3, 0xc0, 0x79, 0xe0, 0x3c, 0xf8, 0x6, + 0x7c, 0x0, 0x3e, 0x0, 0x1e, 0x0, 0x3, 0x0, + 0x0, + + /* U+F7E6 "" */ + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, + 0xff, 0xc0, 0xff, 0x3, 0xff, 0x3f, 0xfc, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1b, 0x0, + 0x6f, 0xff, 0xdf, 0xff, + + /* U+F7EC "" */ + 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x3f, 0xfc, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0xfe, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, + 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, + + /* U+F7EF "" */ + 0x0, 0xf0, 0x1, 0xfc, 0x7, 0xfe, 0xf, 0xfe, + 0x3f, 0xff, 0x7f, 0xff, 0xff, 0xff, 0x0, 0x0, + 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, + + /* U+F7F2 "" */ + 0x0, 0x80, 0x1, 0xe0, 0x1, 0xfb, 0x81, 0xff, + 0xc1, 0xff, 0xe1, 0xff, 0xf1, 0xff, 0xfd, 0xfe, + 0xff, 0x3f, 0x7e, 0x1e, 0xf, 0xf, 0x7, 0x87, + 0xef, 0xc3, 0xf7, 0xe1, 0xff, 0xf0, 0xff, 0xf8, + 0x3f, 0xf8, + + /* U+F7F3 "" */ + 0x1f, 0x87, 0xfe, 0xf0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x9f, 0xf9, 0xff, 0x9f, 0xf9, + 0xff, 0xff, 0xf0, 0xfe, 0x7, 0xff, 0xf7, 0xfe, + + /* U+F7F5 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, + 0x7e, 0x7e, 0xfe, 0x7f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xfe, 0x7f, 0x7e, 0x7e, 0x7f, 0xfe, + 0x3f, 0xfc, 0x7f, 0xf8, 0xff, 0xe0, 0xc0, 0x0, + + /* U+F7F7 "" */ + 0x0, 0x20, 0x0, 0x38, 0x0, 0xe, 0x0, 0xf, + 0x80, 0xc, 0xe0, 0xc, 0x38, 0xe, 0x1e, 0xf, + 0x9a, 0xc, 0xf8, 0x6, 0x38, 0x3, 0x18, 0x1, + 0xf8, 0x1, 0xf8, 0x1, 0xc0, 0x1, 0xc0, 0x1, + 0xc0, 0x0, 0x40, 0x0, 0x0, + + /* U+F7FA "" */ + 0x0, 0xc0, 0x1, 0xe0, 0x3, 0xe0, 0x7f, 0xf0, + 0xff, 0xf0, 0xff, 0x3c, 0x73, 0x3f, 0x73, 0xff, + 0x3f, 0xff, 0x3f, 0x3e, 0x7f, 0x38, 0xff, 0xf0, + 0xff, 0xf0, 0x41, 0xf0, 0x0, 0xf0, 0x0, 0x70, + + /* U+F7FB "" */ + 0xf, 0x1, 0xf8, 0x3f, 0xc7, 0x7e, 0x6f, 0xee, + 0xff, 0xdf, 0xfd, 0xff, 0xdf, 0xff, 0xff, 0xff, + 0xf7, 0xfe, 0x7f, 0xe3, 0xfc, 0xf, 0x0, + + /* U+F802 "" */ + 0xc0, 0x78, 0x30, 0x1f, 0xfc, 0x7, 0xff, 0xfd, + 0xff, 0xff, 0x7f, 0xf0, 0x1f, 0xfc, 0x7, 0xff, + 0x0, 0x0, 0xc0, 0x0, 0x30, 0x1e, 0xc, 0x7, + 0xff, 0x1, 0xff, 0xff, 0x7f, 0xdf, 0xdf, 0xf0, + 0x7, 0xfc, 0x1, 0xff, + + /* U+F805 "" */ + 0x7, 0xe0, 0x1e, 0x78, 0x36, 0x6c, 0x77, 0xec, + 0x7f, 0xfe, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, + 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x7f, 0xfe, + 0x7f, 0xfe, 0x3f, 0xfc, + + /* U+F806 "" */ + 0x2, 0x0, 0x38, 0x1, 0xc0, 0xe, 0x0, 0x70, + 0x3, 0x80, 0x1c, 0x3, 0xf8, 0x3f, 0xfb, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xc3, 0xfc, + + /* U+F807 "" */ + 0x1, 0xe0, 0x1, 0x7a, 0x0, 0xde, 0xc0, 0x77, + 0xb8, 0x3d, 0xef, 0x1f, 0x7b, 0xe7, 0xde, 0xf9, + 0xf7, 0xbe, 0x7f, 0xff, 0x9f, 0xff, 0xe0, 0x0, + 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xf0, + + /* U+F80A "" */ + 0x0, 0x80, 0x0, 0xe0, 0x1, 0xfc, 0x1, 0xff, + 0x1, 0xff, 0xc1, 0xff, 0xf1, 0xff, 0xfd, 0xff, + 0xff, 0x7f, 0xff, 0x1f, 0xff, 0xf, 0x8f, 0x87, + 0xc7, 0xc3, 0xe3, 0xe1, 0xf1, 0xf0, 0xf8, 0xf8, + 0x3f, 0xf8, + + /* U+F80B "" */ + 0x1, 0x80, 0x1, 0xe0, 0x1, 0xfb, 0x81, 0xff, + 0xc1, 0xff, 0xe1, 0xff, 0xf1, 0xff, 0xfd, 0xff, + 0xfe, 0x7f, 0xff, 0x1f, 0xff, 0xf, 0x8f, 0x87, + 0xc7, 0xc3, 0xe3, 0xe1, 0xf1, 0xf0, 0xf8, 0xf8, + 0x3f, 0xf8, + + /* U+F80C "" */ + 0x0, 0x80, 0x0, 0xe0, 0x1, 0xfc, 0x1, 0xff, + 0x1, 0xff, 0xc1, 0xff, 0xf1, 0xff, 0xfd, 0xff, + 0xff, 0x7f, 0xff, 0x1f, 0xff, 0xf, 0x8f, 0x87, + 0xc7, 0xc3, 0xe3, 0xe1, 0xf1, 0xf0, 0xf8, 0xf8, + 0x3f, 0xf8, + + /* U+F80D "" */ + 0x7f, 0xe0, 0x3f, 0xfc, 0xf, 0xff, 0x3, 0xff, + 0xc0, 0xff, 0xf0, 0x3c, 0x3c, 0xf, 0xfe, 0x3, + 0xff, 0x38, 0xff, 0xdf, 0x3f, 0xf7, 0xcf, 0xfd, + 0xf3, 0xe7, 0x38, 0xf9, 0x80, 0x3e, 0x4f, 0xef, + 0xf7, 0xfd, 0xfd, 0xff, 0x0, 0x7f, 0xc0, + + /* U+F80F "" */ + 0x0, 0xc0, 0x3, 0x9c, 0x7, 0x3e, 0xe, 0x7e, + 0x1c, 0xfe, 0x39, 0xfc, 0x73, 0xf9, 0x67, 0xf3, + 0xcf, 0xe6, 0x9f, 0xce, 0x3f, 0x9c, 0x7f, 0x38, + 0x7e, 0x70, 0x7c, 0xe0, 0x39, 0xc0, 0x3, 0x0, + + /* U+F810 "" */ + 0xf, 0x1, 0xf8, 0x3f, 0xc3, 0xfc, 0x7f, 0xcf, + 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x3f, + 0xc1, 0xf8, 0x1f, 0x80, 0xf0, 0xf, 0x0, 0x60, + 0x6, 0x0, + + /* U+F812 "" */ + 0x1f, 0xff, 0x83, 0xff, 0xfc, 0x30, 0x0, 0xc3, + 0x0, 0xc, 0x30, 0x60, 0xc3, 0x6, 0xc, 0x31, + 0xf8, 0xc3, 0x6, 0xc, 0x30, 0x60, 0xc3, 0x0, + 0xc, 0x0, 0x0, 0xf, 0xff, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0xfe, + + /* U+F815 "" */ + 0x7f, 0xfe, 0xff, 0xff, 0xc0, 0x3, 0xc0, 0x3, + 0xc0, 0x3, 0xc0, 0x3, 0xff, 0xff, 0xff, 0xff, + 0xc4, 0x7f, 0xc4, 0x7f, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F816 "" */ + 0x0, 0x3d, 0x80, 0x3f, 0xc0, 0x7, 0xc0, 0x1b, + 0xf0, 0xd, 0xf8, 0xe, 0xfc, 0xf, 0x6, 0xf, + 0xfa, 0x7, 0xfc, 0x7, 0xf8, 0x7, 0xf8, 0xf, + 0xf8, 0xf, 0xf0, 0x3, 0xe0, 0x0, + + /* U+F818 "" */ + 0xf, 0x0, 0x3f, 0x80, 0xf, 0x80, 0x7, 0x81, + 0xc7, 0x87, 0xe7, 0x8f, 0xe7, 0x19, 0xe6, 0x33, + 0xee, 0xff, 0xdd, 0xf9, 0xdb, 0xf3, 0xb6, 0xff, + 0x5f, 0xfe, 0x3f, 0xc0, 0x78, 0x0, 0x0, 0x0, + + /* U+F81D "" */ + 0xf, 0xe0, 0xf, 0xc0, 0x1f, 0x0, 0x0, 0x0, + 0x0, 0x1, 0xfc, 0x7, 0xfc, 0x1f, 0x3c, 0x7c, + 0x7c, 0xfb, 0xfb, 0xf0, 0xff, 0xfd, 0xff, 0xc3, + 0xff, 0xcf, 0xff, 0xff, 0xbf, 0xfe, + + /* U+F827 "" */ + 0xff, 0xfb, 0xff, 0xff, 0xcf, 0xff, 0x3f, 0xf0, + 0x3f, 0xc0, 0xff, 0x3, 0xfc, 0xf, 0xfc, 0xff, + 0xf3, 0xff, 0xff, 0xff, 0xff, 0x60, 0xd, 0x80, + 0x3f, 0xff, 0xff, 0xfe, + + /* U+F828 "" */ + 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xe3, 0xff, + 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0xff, 0xff, + 0xff, 0xff, 0x80, 0xfe, 0x3, 0xff, 0xff, 0xff, + 0xf0, + + /* U+F829 "" */ + 0x7, 0x80, 0x3f, 0xf, 0xff, 0xff, 0xff, 0x0, + 0x0, 0x0, 0x7, 0xff, 0x9f, 0xfe, 0x7c, 0xf9, + 0xe1, 0xe7, 0x3, 0x9f, 0x3e, 0x7c, 0xf9, 0xf3, + 0xe7, 0xff, 0x9f, 0xfe, 0x3f, 0xf0, + + /* U+F82A "" */ + 0x7, 0x80, 0x3f, 0xf, 0xff, 0xff, 0xff, 0x0, + 0x0, 0x0, 0x7, 0xff, 0x9f, 0xfe, 0x7c, 0xf9, + 0xe1, 0xe7, 0x3, 0x9f, 0x3e, 0x7c, 0xf9, 0xf3, + 0xe7, 0xff, 0x9f, 0xfe, 0x3f, 0xf0, + + /* U+F82F "" */ + 0x7, 0x0, 0xee, 0xf, 0x78, 0x7f, 0xc3, 0x6, + 0x8, 0x20, 0x63, 0x1, 0xf0, 0x2, 0x0, 0x0, + 0x4, 0x10, 0xfb, 0xe7, 0xff, 0x7f, 0xff, 0xff, + 0xff, 0xff, + + /* U+F83E "" */ + 0x3f, 0x80, 0x3f, 0x80, 0x31, 0x80, 0x31, 0x80, + 0x31, 0x80, 0xf1, 0x8f, 0xf1, 0x8f, 0x1, 0x8c, + 0x1, 0x8c, 0x1, 0x8c, 0x1, 0xfc, 0x1, 0xfc, + + /* U+F84A "" */ + 0x0, 0x38, 0x0, 0xe, 0x0, 0x3, 0x80, 0x1, + 0x0, 0x1, 0xf0, 0x0, 0xff, 0x80, 0x79, 0xe0, + 0x1e, 0x0, 0x3, 0xc0, 0xe, 0x31, 0xc7, 0xcc, + 0xfb, 0xfb, 0x7f, 0xfe, 0xdf, 0xff, 0xb7, 0xf7, + 0xc0, 0xf8, 0xe0, 0x1c, + + /* U+F84C "" */ + 0x7f, 0xfb, 0xff, 0xfc, 0x30, 0xf0, 0xc3, 0xc3, + 0xf, 0xc, 0x3f, 0xff, 0xff, 0xff, 0xc3, 0xf, + 0xc, 0x3c, 0x30, 0xf0, 0xc3, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F850 "" */ + 0xcc, 0xcf, 0x33, 0x30, 0x0, 0x0, 0x0, 0xc0, + 0xf, 0x0, 0x30, 0x0, 0x0, 0x0, 0xc0, 0xf, + 0x0, 0x30, 0x0, 0x0, 0x0, 0xcc, 0xcf, 0x33, + 0x30, + + /* U+F853 "" */ + 0x7f, 0xff, 0xff, 0xfc, 0x0, 0x30, 0x0, 0xc0, + 0xf, 0x0, 0x3c, 0x0, 0x30, 0x0, 0xc0, 0xf, + 0x0, 0x3c, 0x0, 0x30, 0x0, 0xcc, 0xcf, 0x33, + 0x30, + + /* U+F85E "" */ + 0x1, 0xc0, 0x0, 0xf0, 0x0, 0x1c, 0x0, 0x10, + 0x0, 0x1f, 0x0, 0x1f, 0xc0, 0xf, 0xf8, 0x0, + 0xfe, 0x0, 0xf, 0x80, 0x0, 0xf1, 0x87, 0x8f, + 0x61, 0xf8, 0xfc, 0x6e, 0x1f, 0xb9, 0x87, 0xec, + 0x63, 0xff, 0x18, 0xff, + + /* U+F863 "" */ + 0x1, 0xc0, 0x3, 0xc0, 0x7, 0xc0, 0x7, 0xc0, + 0x7, 0xc0, 0x7, 0xfc, 0xff, 0xfe, 0xfe, 0x7f, + 0xfe, 0x7f, 0x7f, 0xff, 0x3f, 0xe0, 0x3, 0xe0, + 0x3, 0xe0, 0x3, 0xe0, 0x3, 0xc0, 0x3, 0x80, + + /* U+F86D "" */ + 0x0, 0x0, 0xff, 0x7, 0xff, 0x3f, 0xff, 0x7b, + 0xff, 0x63, 0x7e, 0x6f, 0x3c, 0x7f, 0x19, 0xee, + 0x1, 0xc0, 0x1c, 0x4, 0x7f, 0xc, 0xff, 0x9c, + 0xe3, 0xbe, 0xe3, 0x8e, 0xe3, 0x9c, 0xff, 0x98, + 0xff, 0x90, + + /* U+F879 "" */ + 0x0, 0x1c, 0x0, 0x1e, 0x0, 0x3f, 0x0, 0x3f, + 0x0, 0x3f, 0x0, 0x3f, 0x0, 0x1f, 0x0, 0x1e, + 0x0, 0x3e, 0x0, 0x7c, 0x3c, 0xf8, 0xfd, 0xf8, + 0xff, 0xf0, 0xff, 0xc0, 0x7f, 0x80, 0x3c, 0x0, + + /* U+F87B "" */ + 0x7f, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, + 0x1f, 0xfc, 0x7f, 0xf9, 0xff, 0xcf, 0xe2, 0x7f, + 0x83, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xfd, 0xff, + 0xe0, + + /* U+F87C "" */ + 0x1, 0xff, 0xe0, 0x3f, 0xff, 0x3, 0x3f, 0xf0, + 0x33, 0xdf, 0x7b, 0xf8, 0xfc, 0xbc, 0x7, 0xcb, + 0x80, 0x7f, 0xb8, 0x3, 0xcb, 0xff, 0xfc, 0xbf, + 0xff, 0xf8, 0xff, 0xcc, 0xc0, 0x0, 0xce, 0x0, + 0xc, 0xff, 0xc0, 0x7f, 0xfc, 0x0, + + /* U+F87D "" */ + 0xc0, 0x0, 0x38, 0x0, 0x7, 0xff, 0xf0, 0xff, + 0xfe, 0x1c, 0x61, 0x83, 0x98, 0x40, 0x76, 0x0, + 0xf, 0x80, 0x1, 0xc0, 0x0, 0x38, 0x0, 0x7, + 0x0, 0x4, 0xe0, 0x1, 0x9c, 0x0, 0x63, 0x80, + 0xfe, 0x70, 0x3f, 0x8e, 0x0, 0x1, 0xc0, 0x0, + 0x30, + + /* U+F881 "" */ + 0x18, 0x7e, 0x18, 0x7e, 0x18, 0x1c, 0x18, 0x38, + 0x18, 0x7e, 0x18, 0x7e, 0x18, 0x0, 0x18, 0x18, + 0x18, 0x18, 0x5a, 0x3c, 0x7f, 0x3c, 0x7e, 0x7e, + 0x3c, 0x7e, 0x18, 0x42, + + /* U+F882 "" */ + 0x18, 0x7e, 0x3c, 0x7e, 0x7e, 0x1c, 0x7f, 0x38, + 0x5a, 0x7e, 0x18, 0x7e, 0x18, 0x0, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x3c, 0x18, 0x3c, 0x18, 0x7e, + 0x18, 0x7e, 0x18, 0x42, + + /* U+F884 "" */ + 0x18, 0x70, 0x6, 0x1c, 0x1, 0x80, 0x0, 0x60, + 0x0, 0x18, 0x7c, 0x6, 0x1f, 0x1, 0x80, 0x0, + 0x60, 0x0, 0x18, 0x7f, 0x36, 0xdf, 0xcf, 0xf0, + 0x1, 0xf8, 0x0, 0x3c, 0x7f, 0xc6, 0x1f, 0xf0, + + /* U+F885 "" */ + 0x18, 0x70, 0xf, 0x1c, 0x7, 0xe0, 0x3, 0xfc, + 0x0, 0xdb, 0x7c, 0x6, 0x1f, 0x1, 0x80, 0x0, + 0x60, 0x0, 0x18, 0x7f, 0x6, 0x1f, 0xc1, 0x80, + 0x0, 0x60, 0x0, 0x18, 0x7f, 0xc6, 0x1f, 0xf0, + + /* U+F886 "" */ + 0x18, 0x1c, 0x30, 0x7c, 0x60, 0xd8, 0xc1, 0xf1, + 0x81, 0xc3, 0x3, 0x86, 0x6, 0xc, 0x0, 0x18, + 0x18, 0xb4, 0x79, 0xfc, 0x73, 0xf0, 0xe3, 0xc3, + 0xe3, 0x7, 0xc0, + + /* U+F887 "" */ + 0x18, 0x1c, 0x78, 0x7d, 0xf8, 0xdf, 0xf9, 0xfd, + 0xb1, 0xc3, 0x3, 0x86, 0x6, 0xc, 0x0, 0x18, + 0x18, 0x30, 0x78, 0x60, 0x70, 0xc0, 0xe1, 0x83, + 0xe3, 0x7, 0xc0, + + /* U+F891 "" */ + 0x79, 0xf0, 0x7e, 0xfc, 0x33, 0x66, 0x19, 0xbf, + 0xf, 0xdf, 0x87, 0xef, 0xc3, 0x36, 0x61, 0x9b, + 0xf0, 0xcd, 0xf1, 0x80, 0x1, 0xc0, 0x1, 0xc0, + 0xd, 0xc0, 0x7, 0xc0, 0x1, 0xc0, 0x0, 0x60, + + /* U+F897 "" */ + 0x3e, 0x7, 0xc7, 0xf0, 0xfe, 0xe3, 0x9c, 0x7c, + 0x19, 0x83, 0xc1, 0x98, 0x3c, 0x19, 0x83, 0xe3, + 0x9c, 0x77, 0xff, 0xfe, 0x3f, 0xff, 0xc0, + + /* U+F8C0 "" */ + 0x3, 0x9c, 0x0, 0x3f, 0xc0, 0x7, 0xfe, 0x0, + 0x7f, 0xe0, 0x7, 0xfe, 0x0, 0x7f, 0xe0, 0xcf, + 0xff, 0x3c, 0xff, 0xf3, 0xcf, 0xff, 0x36, 0x0, + 0x6, 0x70, 0x0, 0xe3, 0xff, 0xfc, 0xf, 0xff, + 0x0, + + /* U+F8C1 "" */ + 0x3, 0x9c, 0x0, 0x3f, 0xc0, 0x7, 0xfe, 0x0, + 0x7f, 0xe0, 0x0, 0xfe, 0x0, 0x3, 0xe0, 0x1f, + 0x1f, 0xc3, 0xf8, 0x7e, 0x7f, 0xe3, 0xff, 0xff, + 0xf, 0xff, 0xfc, 0x7f, 0xff, 0xe1, 0xff, 0xff, + 0x80, + + /* U+F8CC "" */ + 0x19, 0x87, 0x9e, 0x79, 0xef, 0x9f, 0xf9, 0xff, + 0x9f, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf7, 0xfe, 0x7f, 0xe1, 0xf8, + + /* U+F8D7 "" */ + 0x0, 0x7, 0x0, 0x3e, 0x1, 0xf0, 0x1f, 0x80, + 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, + 0xc1, 0x83, 0xc1, 0x83, 0xff, 0x83, 0xc1, 0x83, + 0xc1, 0xc7, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, + + /* U+F8D9 "" */ + 0x7, 0xe0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7c, 0x3e, + 0x78, 0x1e, 0xfb, 0xdf, 0xf7, 0xef, 0xf6, 0x6f, + 0xf6, 0x6f, 0xf7, 0xef, 0xfb, 0xdf, 0x78, 0x1e, + 0x7c, 0x3e, 0x3f, 0xfc, 0x1f, 0xf8, 0x7, 0xe0, + + /* U+F8E5 "" */ + 0x7f, 0xff, 0xbf, 0xff, 0xfc, 0x0, 0xf, 0x0, + 0x3, 0xc0, 0x0, 0xf0, 0x0, 0x3c, 0x0, 0xf, + 0x0, 0x3, 0xc0, 0x0, 0xf0, 0x0, 0x3f, 0xff, + 0xfd, 0xff, 0xfe, 0x0, 0x0, 0x3, 0xff, 0x0, + 0xff, 0xc0, + + /* U+F8EF "" */ + 0x20, 0x3, 0x0, 0x36, 0xcf, 0xff, 0xff, 0xfe, + 0x7, 0xe0, 0x7f, 0xff, 0xe0, 0x7e, 0x7, 0xff, + 0xf7, 0xfe, 0x7f, 0xe7, 0xfe, 0x7f, 0xe7, 0xfe, + 0x3f, 0xc0, + + /* U+F8FF "" */ + 0x7f, 0xfc, 0x1f, 0xff, 0xc3, 0xff, 0xfc, 0x70, + 0xc3, 0x8e, 0x18, 0x71, 0xc3, 0xe, 0x3c, 0xe7, + 0xc7, 0xfc, 0x38, 0xff, 0x87, 0x1f, 0xf0, 0xe3, + 0xff, 0xff, 0xbc, 0xff, 0xf1, 0x98, 0x0, 0x12, + 0x0, 0x3, 0xc0, 0x0 +}; + + +/*--------------------- + * GLYPH DESCRIPTION + *--------------------*/ + +static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { + {.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */, + {.bitmap_index = 0, .adv_w = 128, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1, .adv_w = 64, .box_w = 2, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 5, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 6, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38, .adv_w = 160, .box_w = 9, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 56, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 81, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 82, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 83, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 84, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 85, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 113, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 138, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 139, .adv_w = 160, .box_w = 10, .box_h = 2, .ofs_x = 0, .ofs_y = 5}, + {.bitmap_index = 142, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 143, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 144, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 162, .adv_w = 128, .box_w = 8, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 176, .adv_w = 192, .box_w = 10, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 194, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 212, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 233, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 251, .adv_w = 192, .box_w = 11, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 271, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 289, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 307, .adv_w = 192, .box_w = 11, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 327, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 328, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 329, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 356, .adv_w = 224, .box_w = 14, .box_h = 8, .ofs_x = 0, .ofs_y = 2}, + {.bitmap_index = 370, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 397, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 417, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 449, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 470, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 488, .adv_w = 192, .box_w = 13, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 513, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 534, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 552, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 570, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 595, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 616, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 634, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 652, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 670, .adv_w = 160, .box_w = 9, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 686, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 711, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 732, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 757, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 775, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 802, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 820, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 838, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 859, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 880, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 901, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 933, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 954, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 975, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 996, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 997, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 998, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 999, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1000, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1001, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1002, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1023, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1041, .adv_w = 192, .box_w = 13, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 1066, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1087, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1105, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1123, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1148, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1169, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1187, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1205, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1223, .adv_w = 160, .box_w = 9, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1239, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1264, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1285, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1310, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1328, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 1355, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1373, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1391, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1412, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1433, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1454, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1486, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1507, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1528, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1549, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1550, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1551, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1552, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1553, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1554, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1555, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1556, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1557, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1558, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1559, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1560, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1561, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1562, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1563, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1564, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1565, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1566, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1567, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1568, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1569, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1570, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1571, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1572, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1573, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1574, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1575, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1576, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1577, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1578, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1579, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1580, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1581, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1582, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1583, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1584, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1585, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1586, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1587, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1588, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1589, .adv_w = 192, .box_w = 10, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 1607, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1608, .adv_w = 192, .box_w = 12, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 1631, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1632, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1633, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1634, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 1666, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1667, .adv_w = 224, .box_w = 13, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1687, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1688, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1689, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 1721, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1722, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1723, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1724, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1725, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1726, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1727, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1728, .adv_w = 224, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 1758, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1759, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1760, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1761, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1762, .adv_w = 224, .box_w = 13, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 1782, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1783, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1784, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1785, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1786, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1787, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1788, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1789, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1790, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1791, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1792, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1793, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1794, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1795, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1796, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1797, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1798, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1799, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1800, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1801, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1802, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1803, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1804, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1805, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1806, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1807, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1808, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1809, .adv_w = 192, .box_w = 12, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1827, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1828, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1829, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1830, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1831, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1832, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1833, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1834, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1835, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1836, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1837, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1838, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1839, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1840, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1841, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1842, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1843, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1844, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1845, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1846, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1847, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1848, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1849, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1850, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1851, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1852, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1853, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1854, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1855, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1856, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1857, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1858, .adv_w = 224, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1881, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1882, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1883, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1884, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1885, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1886, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1887, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1888, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1889, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1890, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1891, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1892, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1893, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1894, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1895, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1896, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1897, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1898, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1899, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1900, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1901, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1902, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1903, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1904, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1905, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1906, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1907, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1908, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1909, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1910, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1911, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1912, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1913, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1914, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1915, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1916, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1917, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1918, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1919, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1920, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1921, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1922, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1923, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1924, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1925, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1926, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1927, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1928, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1929, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1930, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1931, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1932, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1933, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1934, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1935, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1936, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1937, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1938, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1939, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1940, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1941, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1942, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1943, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1944, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1945, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1946, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1947, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1948, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1949, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1950, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1951, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1952, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1953, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1954, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1955, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1956, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1957, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1958, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1959, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1960, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1961, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1962, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1963, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1964, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1965, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1966, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1967, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1968, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1969, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1970, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1971, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1972, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1973, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1974, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1975, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1976, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1977, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1978, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1979, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1980, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1981, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1982, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1983, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1984, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1985, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1986, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1987, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1988, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1989, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1990, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1991, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1992, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1993, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1994, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1995, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1996, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1997, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1998, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 1999, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2000, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2001, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2002, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2003, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2004, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2005, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2006, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2007, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2008, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2009, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2010, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2011, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2012, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2013, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2014, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2015, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2016, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2017, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2018, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2019, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2020, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2021, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2022, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2023, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2024, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2025, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2026, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2027, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2028, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2029, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2030, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2031, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2032, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2033, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2034, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2035, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2036, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2037, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2038, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2039, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2040, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2041, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2042, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2043, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2044, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2045, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2046, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2047, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2048, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2049, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2050, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2051, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2052, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2053, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2054, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2055, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2056, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2057, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2058, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2059, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2060, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2061, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2062, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2063, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2064, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2065, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2066, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2067, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2068, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2069, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2070, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2071, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2072, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2073, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2074, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2075, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2076, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2077, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2078, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2079, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2080, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2081, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2082, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2083, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2084, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2085, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2086, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2087, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2088, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2089, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2090, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2091, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2092, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2093, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2094, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2095, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2096, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2097, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2098, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2099, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2100, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2101, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2102, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2103, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2104, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2105, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2106, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2107, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2108, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2109, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2110, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2111, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2112, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2113, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2114, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2115, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2116, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2117, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2118, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2119, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2120, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2121, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2122, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2123, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2124, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2125, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2126, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2127, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2128, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2129, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2130, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2131, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2132, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2133, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2134, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2135, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2136, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2137, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2138, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2139, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2140, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2141, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2142, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2143, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2144, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2145, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2146, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2147, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2148, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2149, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2150, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2151, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2152, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2153, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2154, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2155, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2156, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2157, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2158, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2159, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2160, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2161, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2162, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2163, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2164, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2165, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2166, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2167, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2168, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2169, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2170, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2171, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2172, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2173, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2174, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2175, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2176, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2177, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2178, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2179, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2180, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2181, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2182, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2183, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2184, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2185, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2186, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2187, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2188, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2189, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2190, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2191, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2192, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2193, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2194, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2195, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2196, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2197, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2198, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2199, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2200, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2201, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2202, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2203, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2204, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2205, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2206, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2207, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2208, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2209, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2210, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2211, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2212, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2213, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2214, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2215, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2216, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2217, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2218, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2219, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2220, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2221, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2222, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2223, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2224, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2225, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2226, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2227, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2228, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2229, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2230, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2231, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2232, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2233, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2234, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2235, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2236, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2237, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2238, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2239, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2240, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2241, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2242, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2243, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2244, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2245, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2246, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2247, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2248, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2249, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2250, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2251, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2252, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2253, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2254, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2255, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2256, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2257, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2258, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2259, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2260, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2261, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2262, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2263, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2264, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2265, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2266, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2267, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2268, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2269, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2270, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2271, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2272, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2273, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2274, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2275, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2276, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2277, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2278, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2279, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2280, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2281, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2282, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2283, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2284, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2285, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2286, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2287, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2288, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2289, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2290, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2291, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2292, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2293, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2294, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2295, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2296, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2297, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2298, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2299, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2300, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2301, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2302, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2303, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2304, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2305, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2306, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2307, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2308, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2309, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2310, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2311, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2312, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2313, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2314, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2315, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2316, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2317, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2318, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2319, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2320, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2321, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2322, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2323, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2324, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2325, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2326, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2327, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2328, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2329, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2330, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2331, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2332, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2333, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2334, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2335, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2336, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2337, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2338, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2339, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2340, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2341, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2342, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2343, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2344, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2345, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2346, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2347, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2348, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2349, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2350, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2351, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2352, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2353, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2354, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2355, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2356, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2357, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2358, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2359, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2360, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2361, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2362, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2363, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2364, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2365, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2366, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2367, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2368, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2369, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2370, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2371, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2372, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2373, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2374, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2375, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2376, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2377, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2378, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2379, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2380, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2381, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2382, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2383, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2384, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2385, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2386, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2387, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2388, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2389, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2390, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2391, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2392, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2393, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2394, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2395, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2396, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2397, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2398, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2399, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2400, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2401, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2402, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2403, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2404, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2405, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2406, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2407, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2408, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2409, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2410, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2411, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2412, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2413, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2414, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2415, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2416, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2417, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2418, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2419, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2420, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2421, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2422, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2423, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2424, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2425, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2426, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2427, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2428, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2429, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2430, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2431, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2432, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2433, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2434, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2435, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2436, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2437, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2438, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2439, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2440, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2441, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2442, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2443, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2444, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2445, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2446, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2447, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2448, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2449, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2450, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2451, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2452, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2453, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2454, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2455, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2456, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2457, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2458, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2459, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2460, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2461, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2462, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2463, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2464, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2465, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2466, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2467, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2468, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2469, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2470, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2471, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2472, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2473, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2474, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2475, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2476, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2477, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2478, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2479, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2480, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2481, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2482, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2483, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2484, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2485, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2486, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2487, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2488, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2489, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2490, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2491, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2492, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2493, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2494, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2495, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2496, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2497, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2498, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2499, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2500, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2501, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2502, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2503, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2504, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2505, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2506, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2507, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2508, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2509, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2510, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2511, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2512, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2513, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2514, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2515, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2516, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2517, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2518, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2519, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2520, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2521, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2522, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2523, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2524, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2525, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2526, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2527, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2528, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2529, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2530, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2531, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2532, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2533, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2534, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2535, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2536, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2537, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2538, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2539, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2540, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2541, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2542, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2543, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2544, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2545, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2546, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2547, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2548, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2549, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2550, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2551, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2552, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2553, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2554, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2555, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2556, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2557, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2558, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2559, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2560, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2561, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2562, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2563, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2564, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2565, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2566, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2567, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2568, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2569, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2570, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2571, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2572, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2573, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2574, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2575, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2576, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2577, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2578, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2579, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2580, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2581, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2582, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2583, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2584, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2585, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2586, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2587, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2588, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2589, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2590, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2591, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2592, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2593, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2594, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2595, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2596, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2597, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2598, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2599, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2600, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2601, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2602, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2603, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2604, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2605, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2606, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2607, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2608, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2609, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2610, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2611, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2612, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2613, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2614, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2615, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2616, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2617, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2618, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2619, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2620, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2621, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2622, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2623, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2624, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2625, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2626, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2627, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2628, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2629, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2630, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2631, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2632, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2633, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2634, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2635, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2636, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2637, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2638, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2639, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2640, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2641, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2642, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2643, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2644, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2645, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2646, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2647, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2648, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2649, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2650, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2651, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2652, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2653, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2654, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2655, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2656, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2657, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2658, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2659, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2660, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2661, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2662, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2663, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2664, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2665, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2666, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2667, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2668, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2669, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2670, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2671, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2672, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2673, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2674, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2675, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2676, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2677, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2678, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2679, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2680, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2681, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2682, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2683, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2684, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2685, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2686, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2687, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2688, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2689, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2690, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2691, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2692, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2693, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2694, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2695, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2696, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2697, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2698, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2699, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2700, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2701, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2702, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2703, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2704, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2705, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2706, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2707, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2708, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2709, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2710, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2711, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2712, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2713, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2714, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2715, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2716, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2717, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2718, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2719, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2720, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2721, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2722, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2723, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2724, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2725, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2726, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2727, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2728, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2729, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2730, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2731, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2732, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2733, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2734, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2735, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2736, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2737, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2738, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2739, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2740, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2741, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2742, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2743, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2744, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2745, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2746, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2747, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2748, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2749, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2750, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2751, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2752, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2753, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2754, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2755, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2756, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2757, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2758, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2759, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2760, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2761, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2762, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2763, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2764, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2765, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2766, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2767, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2768, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2769, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2770, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2771, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2772, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2773, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2774, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2775, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2776, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2777, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2778, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2779, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2780, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2781, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2782, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2783, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2784, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2785, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2786, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2787, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2788, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2789, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2790, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2791, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2792, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2793, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2794, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2795, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2796, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2797, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2798, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2799, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2800, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2801, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2802, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2803, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2804, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2805, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2806, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2807, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2808, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2809, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2810, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2811, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2812, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2813, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2814, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2815, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2816, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2817, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2818, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2819, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2820, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2821, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2822, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2823, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2824, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2825, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2826, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2827, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2828, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2829, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2830, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2831, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2832, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2833, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2834, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2835, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2836, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2837, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2838, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2839, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2840, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2841, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2842, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2843, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2844, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2845, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2846, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2847, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2848, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2849, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2850, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2851, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2852, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2853, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2854, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2855, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2856, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2857, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2858, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2859, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2860, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2861, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2862, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2863, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2864, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2865, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2866, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2867, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2868, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2869, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2870, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2871, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2872, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2873, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2874, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2875, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2876, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2877, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2878, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2879, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2880, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2881, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2882, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2883, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2884, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2885, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2886, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2887, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2888, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2889, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2890, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2891, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2892, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2893, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2894, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2895, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2896, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2897, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2898, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2899, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2900, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2901, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2902, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2903, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2904, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2905, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2906, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2907, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2908, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2909, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2910, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2911, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2912, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2913, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2914, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2915, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2916, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2917, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2918, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2919, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2920, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2921, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2922, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2923, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2924, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2925, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2926, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2927, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2928, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2929, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2930, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2931, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2932, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2933, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2934, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2935, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2936, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2937, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2938, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2939, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2940, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2941, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2942, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2943, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2944, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2945, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2946, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2947, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2948, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2949, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2950, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2951, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2952, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2953, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2954, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2955, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2956, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2957, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2958, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2959, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2960, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2961, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2962, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2963, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2964, .adv_w = 224, .box_w = 14, .box_h = 2, .ofs_x = 0, .ofs_y = 5}, + {.bitmap_index = 2968, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2969, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2970, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 2971, .adv_w = 224, .box_w = 14, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 2989, .adv_w = 224, .box_w = 14, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 3007, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3008, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3009, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3010, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3011, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3012, .adv_w = 0, .box_w = 1, .box_h = 1, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3013, .adv_w = 128, .box_w = 7, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3024, .adv_w = 128, .box_w = 7, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 3035, .adv_w = 192, .box_w = 11, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3055, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3080, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3110, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3135, .adv_w = 224, .box_w = 12, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3156, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3177, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3198, .adv_w = 224, .box_w = 12, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 3219, .adv_w = 320, .box_w = 19, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 3243, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3267, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 3291, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3315, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 3339, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 3371, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 3403, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3428, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 3460, .adv_w = 256, .box_w = 17, .box_h = 18, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 3499, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 3531, .adv_w = 224, .box_w = 14, .box_h = 2, .ofs_x = 0, .ofs_y = 5}, + {.bitmap_index = 3535, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 3560, .adv_w = 192, .box_w = 12, .box_h = 7, .ofs_x = 0, .ofs_y = 3}, + {.bitmap_index = 3571, .adv_w = 192, .box_w = 12, .box_h = 7, .ofs_x = 0, .ofs_y = 2}, + {.bitmap_index = 3582, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 3606, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3633, .adv_w = 160, .box_w = 8, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3647, .adv_w = 160, .box_w = 8, .box_h = 14, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 3661, .adv_w = 320, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 3688, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 3720, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3745, .adv_w = 288, .box_w = 16, .box_h = 14, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 3773, .adv_w = 288, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3801, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3829, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3857, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 3889, .adv_w = 224, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 3915, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 3939, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3960, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 3985, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4019, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4046, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4078, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 4103, .adv_w = 224, .box_w = 13, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 4126, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4158, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4190, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 4215, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 4240, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 4277, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 4309, .adv_w = 320, .box_w = 20, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 4354, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4386, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 4411, .adv_w = 288, .box_w = 16, .box_h = 17, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 4445, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4469, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 4499, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4531, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 4565, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4585, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4617, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4649, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4681, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4713, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4745, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4777, .adv_w = 192, .box_w = 11, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 4802, .adv_w = 192, .box_w = 11, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 4826, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 4855, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4885, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4917, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4941, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4961, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 4987, .adv_w = 192, .box_w = 11, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 5011, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 5039, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 5071, .adv_w = 256, .box_w = 18, .box_h = 17, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 5110, .adv_w = 256, .box_w = 18, .box_h = 17, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 5149, .adv_w = 256, .box_w = 18, .box_h = 17, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 5188, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 5213, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 5238, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 5263, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 5288, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 5313, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 5338, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 5363, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 5397, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 5437, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 5471, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 5499, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 5529, .adv_w = 224, .box_w = 14, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 5561, .adv_w = 320, .box_w = 19, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 5602, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 5640, .adv_w = 320, .box_w = 19, .box_h = 19, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 5686, .adv_w = 256, .box_w = 14, .box_h = 19, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 5720, .adv_w = 288, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 5752, .adv_w = 288, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 5795, .adv_w = 192, .box_w = 11, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 5820, .adv_w = 320, .box_w = 18, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 5847, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 5879, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 5911, .adv_w = 192, .box_w = 10, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 5933, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 5965, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 5997, .adv_w = 320, .box_w = 20, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 6042, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6081, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6113, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6147, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6175, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6209, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 6246, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 6276, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6308, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6340, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6372, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6404, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 6429, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 6453, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6477, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6507, .adv_w = 224, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6533, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6565, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6597, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6629, .adv_w = 224, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 6652, .adv_w = 224, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 6675, .adv_w = 192, .box_w = 12, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 6693, .adv_w = 192, .box_w = 12, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 6711, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6735, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 6771, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6799, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 6835, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 6871, .adv_w = 192, .box_w = 12, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 6889, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 6914, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6934, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 6954, .adv_w = 64, .box_w = 2, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 6958, .adv_w = 64, .box_w = 2, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 6962, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 6990, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 7015, .adv_w = 224, .box_w = 14, .box_h = 2, .ofs_x = 0, .ofs_y = 5}, + {.bitmap_index = 7019, .adv_w = 224, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 7042, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7064, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7086, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7118, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7150, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7182, .adv_w = 288, .box_w = 17, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7214, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7246, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 7282, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 7306, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 7338, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 7372, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 7411, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7443, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7475, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 7513, .adv_w = 320, .box_w = 20, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 7558, .adv_w = 288, .box_w = 16, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 7590, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 7618, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7654, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7694, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7726, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 7767, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 7808, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7840, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 7881, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7913, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 7945, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 7979, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 8022, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 8061, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8093, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 8134, .adv_w = 160, .box_w = 10, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8156, .adv_w = 160, .box_w = 10, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8178, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8208, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8240, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 8272, .adv_w = 224, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 8297, .adv_w = 288, .box_w = 19, .box_h = 18, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 8340, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 8381, .adv_w = 288, .box_w = 19, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 8424, .adv_w = 320, .box_w = 20, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 8469, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8501, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 8542, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 8585, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8613, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8641, .adv_w = 288, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 8664, .adv_w = 288, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 8687, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8715, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 8740, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8760, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8780, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8804, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8832, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8866, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 8894, .adv_w = 192, .box_w = 11, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 8915, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 8943, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 8971, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 8995, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9024, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9054, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9082, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9112, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9134, .adv_w = 224, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 9155, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9185, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9209, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9243, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 9277, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 9298, .adv_w = 256, .box_w = 16, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 9324, .adv_w = 160, .box_w = 11, .box_h = 14, .ofs_x = -1, .ofs_y = -1}, + {.bitmap_index = 9344, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9368, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 9400, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9432, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 9466, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 9484, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 9505, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 9526, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 9547, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 9568, .adv_w = 320, .box_w = 20, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 9598, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 9624, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 9656, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 9681, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9705, .adv_w = 288, .box_w = 18, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 9735, .adv_w = 224, .box_w = 13, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 9758, .adv_w = 224, .box_w = 13, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 9781, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 9817, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 9849, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 9881, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9917, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9944, .adv_w = 224, .box_w = 11, .box_h = 14, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 9964, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 9994, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10026, .adv_w = 288, .box_w = 17, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 10065, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10099, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10127, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 10148, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 10176, .adv_w = 256, .box_w = 16, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 10196, .adv_w = 192, .box_w = 10, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 10219, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 10253, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10287, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 10321, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10343, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10367, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10399, .adv_w = 128, .box_w = 8, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10415, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 10443, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10475, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 10503, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 10528, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 10553, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 10578, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10610, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 10651, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 10688, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 10728, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10760, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 10801, .adv_w = 320, .box_w = 19, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 10842, .adv_w = 320, .box_w = 19, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 10883, .adv_w = 320, .box_w = 19, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 10924, .adv_w = 320, .box_w = 19, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 10965, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 10997, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 11036, .adv_w = 320, .box_w = 19, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 11074, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 11117, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 11157, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 11184, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11216, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11250, .adv_w = 288, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 11273, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11307, .adv_w = 256, .box_w = 15, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11339, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11371, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11403, .adv_w = 320, .box_w = 18, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 11435, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 11463, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11493, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11521, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11553, .adv_w = 160, .box_w = 10, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 11576, .adv_w = 160, .box_w = 9, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 11597, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 11625, .adv_w = 288, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11657, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 11685, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 11721, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 11757, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 11793, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 11829, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11864, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11892, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 11929, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 11966, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12003, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12040, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12077, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 12117, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12154, .adv_w = 288, .box_w = 16, .box_h = 17, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 12188, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12225, .adv_w = 256, .box_w = 15, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12257, .adv_w = 320, .box_w = 18, .box_h = 17, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 12296, .adv_w = 320, .box_w = 19, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12337, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 12369, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 12399, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 12431, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 12465, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 12505, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 12535, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 12559, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 12591, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 12623, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 12655, .adv_w = 320, .box_w = 18, .box_h = 14, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 12687, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 12721, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 12764, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12801, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12838, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12875, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12912, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 12949, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 12979, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 13011, .adv_w = 320, .box_w = 19, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13049, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13073, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13097, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13129, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13163, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13204, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13240, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13283, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13323, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 13359, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 13395, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 13431, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 13467, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 13503, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 13539, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13569, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13601, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13635, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13669, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 13712, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 13755, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 13798, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 13841, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 13881, .adv_w = 320, .box_w = 20, .box_h = 18, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 13926, .adv_w = 320, .box_w = 20, .box_h = 19, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 13974, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 14017, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 14060, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 14103, .adv_w = 320, .box_w = 19, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 14144, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14184, .adv_w = 320, .box_w = 20, .box_h = 19, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 14232, .adv_w = 160, .box_w = 10, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14254, .adv_w = 160, .box_w = 10, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14276, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 14312, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14336, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14370, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14406, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14440, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14474, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 14495, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 14529, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14563, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14595, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14627, .adv_w = 320, .box_w = 20, .box_h = 19, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 14675, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14713, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 14740, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14762, .adv_w = 320, .box_w = 18, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 14798, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14830, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14864, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14898, .adv_w = 320, .box_w = 19, .box_h = 18, .ofs_x = 1, .ofs_y = -4}, + {.bitmap_index = 14941, .adv_w = 256, .box_w = 16, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 14961, .adv_w = 320, .box_w = 19, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 14999, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 15033, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 15069, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 15101, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 15137, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 15173, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 15207, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 15243, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 15284, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 15325, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 15353, .adv_w = 320, .box_w = 16, .box_h = 20, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 15393, .adv_w = 224, .box_w = 13, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 15423, .adv_w = 320, .box_w = 19, .box_h = 19, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 15469, .adv_w = 320, .box_w = 19, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 15512, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 15553, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 15594, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 15635, .adv_w = 320, .box_w = 19, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 15678, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 15719, .adv_w = 320, .box_w = 16, .box_h = 19, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 15757, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 15792, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 15822, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 15856, .adv_w = 192, .box_w = 11, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 15881, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 15917, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 15949, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 15977, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 16009, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 16045, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 16075, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 16107, .adv_w = 320, .box_w = 20, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 16152, .adv_w = 288, .box_w = 19, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 16193, .adv_w = 320, .box_w = 20, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 16238, .adv_w = 256, .box_w = 17, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 16277, .adv_w = 320, .box_w = 19, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 16320, .adv_w = 320, .box_w = 19, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 16363, .adv_w = 320, .box_w = 19, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 16406, .adv_w = 320, .box_w = 19, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 16449, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 16477, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 16518, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 16559, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 16600, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 16641, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 16682, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 16723, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 16757, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 16792, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 16824, .adv_w = 320, .box_w = 18, .box_h = 16, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 16860, .adv_w = 320, .box_w = 18, .box_h = 16, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 16896, .adv_w = 320, .box_w = 18, .box_h = 16, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 16932, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 16966, .adv_w = 320, .box_w = 20, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 16994, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 17021, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 17051, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 17094, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 17137, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 17180, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17219, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 17262, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17286, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17316, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17346, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17376, .adv_w = 224, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 17403, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 17428, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 17453, .adv_w = 224, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 17483, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17523, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 17547, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 17575, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 17603, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17639, .adv_w = 256, .box_w = 17, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 17671, .adv_w = 256, .box_w = 17, .box_h = 15, .ofs_x = -1, .ofs_y = -1}, + {.bitmap_index = 17703, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17735, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17771, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17791, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17823, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17859, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17887, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17927, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 17964, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 17996, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 18030, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 18064, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 18099, .adv_w = 320, .box_w = 19, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 18133, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 18173, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 18207, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 18234, .adv_w = 320, .box_w = 19, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 18277, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 18309, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 18345, .adv_w = 256, .box_w = 15, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 18377, .adv_w = 288, .box_w = 16, .box_h = 17, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 18411, .adv_w = 320, .box_w = 19, .box_h = 18, .ofs_x = 1, .ofs_y = -4}, + {.bitmap_index = 18454, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 18482, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 18517, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 18537, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 18559, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 18596, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 18633, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 18673, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 18709, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 18744, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 18770, .adv_w = 320, .box_w = 18, .box_h = 15, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 18804, .adv_w = 320, .box_w = 18, .box_h = 15, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 18838, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 18876, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 18917, .adv_w = 320, .box_w = 18, .box_h = 15, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 18951, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 18979, .adv_w = 320, .box_w = 19, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 19020, .adv_w = 192, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19046, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19070, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19098, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19134, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 19159, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19195, .adv_w = 192, .box_w = 10, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 19218, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19250, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19286, .adv_w = 96, .box_w = 6, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 19294, .adv_w = 96, .box_w = 6, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 19302, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19342, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19381, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19411, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 19440, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19472, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 19496, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 19524, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 19560, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 19596, .adv_w = 224, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19621, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 19646, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 19671, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 19696, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 19721, .adv_w = 224, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 19744, .adv_w = 192, .box_w = 12, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 19762, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19794, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19826, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19860, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 19887, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 19921, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 19951, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 19985, .adv_w = 192, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20011, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20043, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20071, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20098, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20130, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20162, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20190, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20222, .adv_w = 256, .box_w = 17, .box_h = 18, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 20261, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20293, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 20317, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20343, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20371, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20398, .adv_w = 160, .box_w = 9, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20414, .adv_w = 224, .box_w = 13, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20437, .adv_w = 320, .box_w = 19, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 20471, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20496, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20521, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 20548, .adv_w = 288, .box_w = 17, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 20576, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20604, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20628, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 20660, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20688, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20715, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20736, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20757, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20789, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 20819, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20844, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20869, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20894, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20919, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20946, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20971, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 20996, .adv_w = 288, .box_w = 17, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 21022, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 21047, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21079, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21103, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21135, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21159, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21191, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 21225, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 21250, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21282, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 21303, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 21331, .adv_w = 288, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 21359, .adv_w = 224, .box_w = 13, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 21382, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 21403, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 21428, .adv_w = 288, .box_w = 16, .box_h = 14, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 21456, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 21484, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 21505, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 21530, .adv_w = 160, .box_w = 8, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 21544, .adv_w = 160, .box_w = 8, .box_h = 14, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 21558, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21590, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21622, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21654, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21686, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21718, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21750, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 21787, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21819, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21851, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21883, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 21907, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 21931, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21955, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 21979, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22009, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22034, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22059, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22084, .adv_w = 224, .box_w = 14, .box_h = 2, .ofs_x = 0, .ofs_y = 5}, + {.bitmap_index = 22088, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 22116, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 22148, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22178, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22208, .adv_w = 224, .box_w = 15, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 22240, .adv_w = 288, .box_w = 19, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22274, .adv_w = 288, .box_w = 19, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 22317, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22347, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 22383, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22410, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22438, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 22470, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 22497, .adv_w = 224, .box_w = 14, .box_h = 8, .ofs_x = 0, .ofs_y = 3}, + {.bitmap_index = 22511, .adv_w = 224, .box_w = 14, .box_h = 8, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 22525, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22557, .adv_w = 320, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 22596, .adv_w = 256, .box_w = 16, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 22622, .adv_w = 288, .box_w = 17, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 22650, .adv_w = 128, .box_w = 8, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 22668, .adv_w = 288, .box_w = 18, .box_h = 8, .ofs_x = 0, .ofs_y = 2}, + {.bitmap_index = 22686, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22714, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22742, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 22774, .adv_w = 320, .box_w = 19, .box_h = 20, .ofs_x = 1, .ofs_y = -4}, + {.bitmap_index = 22822, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 22863, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22893, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 22923, .adv_w = 288, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22943, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22971, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 22999, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 23025, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23057, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 23085, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23117, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 23144, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 23169, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23201, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 23226, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23250, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 23275, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23301, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 23325, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 23350, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 23375, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23405, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23433, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 23472, .adv_w = 256, .box_w = 17, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 23498, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 23522, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23546, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23570, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23602, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23634, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23666, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23698, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23730, .adv_w = 288, .box_w = 16, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 23762, .adv_w = 256, .box_w = 15, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 23787, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23815, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 23845, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 23877, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 23912, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 23944, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 23976, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24004, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24036, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24064, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 24100, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 24125, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 24150, .adv_w = 224, .box_w = 14, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 24171, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 24199, .adv_w = 256, .box_w = 17, .box_h = 13, .ofs_x = -1, .ofs_y = 0}, + {.bitmap_index = 24227, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 24255, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24279, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 24304, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24336, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24370, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 24394, .adv_w = 160, .box_w = 10, .box_h = 6, .ofs_x = 0, .ofs_y = 2}, + {.bitmap_index = 24402, .adv_w = 160, .box_w = 10, .box_h = 6, .ofs_x = 0, .ofs_y = 4}, + {.bitmap_index = 24410, .adv_w = 128, .box_w = 6, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 24418, .adv_w = 128, .box_w = 6, .box_h = 10, .ofs_x = 2, .ofs_y = 1}, + {.bitmap_index = 24426, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 24451, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24475, .adv_w = 192, .box_w = 12, .box_h = 7, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24486, .adv_w = 192, .box_w = 12, .box_h = 7, .ofs_x = 0, .ofs_y = 7}, + {.bitmap_index = 24497, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 24521, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24553, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 24590, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24622, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24654, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 24695, .adv_w = 224, .box_w = 14, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 24727, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 24755, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24789, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24821, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 24847, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24879, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 24911, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 24943, .adv_w = 224, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 24968, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 25005, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 25035, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25063, .adv_w = 288, .box_w = 17, .box_h = 15, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 25095, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25125, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25149, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25173, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25209, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25243, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 25273, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25309, .adv_w = 288, .box_w = 16, .box_h = 14, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 25337, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 25362, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 25387, .adv_w = 224, .box_w = 13, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 25407, .adv_w = 224, .box_w = 13, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 25427, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 25447, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 25467, .adv_w = 128, .box_w = 7, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 25478, .adv_w = 128, .box_w = 7, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 25489, .adv_w = 192, .box_w = 12, .box_h = 7, .ofs_x = 0, .ofs_y = 3}, + {.bitmap_index = 25500, .adv_w = 192, .box_w = 12, .box_h = 7, .ofs_x = 0, .ofs_y = 2}, + {.bitmap_index = 25511, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25541, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 25576, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25604, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25626, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25658, .adv_w = 224, .box_w = 14, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 25676, .adv_w = 224, .box_w = 14, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 25694, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25726, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25758, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 25788, .adv_w = 256, .box_w = 16, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 25814, .adv_w = 288, .box_w = 17, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 25842, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25874, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25906, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 25938, .adv_w = 320, .box_w = 20, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 25968, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 25995, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26023, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26051, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 26075, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26111, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 26143, .adv_w = 288, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 26163, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26192, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26224, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26252, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 26293, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26313, .adv_w = 96, .box_w = 6, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26325, .adv_w = 64, .box_w = 2, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 26329, .adv_w = 288, .box_w = 16, .box_h = 14, .ofs_x = 2, .ofs_y = 0}, + {.bitmap_index = 26357, .adv_w = 288, .box_w = 16, .box_h = 14, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 26385, .adv_w = 288, .box_w = 17, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 26417, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26449, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26473, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 26514, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26544, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 26571, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26603, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 26635, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26667, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26699, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26731, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26763, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 26797, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26823, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26855, .adv_w = 224, .box_w = 14, .box_h = 3, .ofs_x = 0, .ofs_y = 4}, + {.bitmap_index = 26861, .adv_w = 64, .box_w = 4, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 26868, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 26893, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 26925, .adv_w = 288, .box_w = 19, .box_h = 12, .ofs_x = -1, .ofs_y = 0}, + {.bitmap_index = 26954, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 26979, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27004, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 27026, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 27048, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27073, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27098, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27123, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 27157, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 27189, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27214, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27239, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27264, .adv_w = 224, .box_w = 12, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 27285, .adv_w = 192, .box_w = 10, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 27303, .adv_w = 160, .box_w = 9, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 27321, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27346, .adv_w = 192, .box_w = 12, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27369, .adv_w = 224, .box_w = 12, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 27390, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 27420, .adv_w = 192, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 27446, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 27470, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27498, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27526, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27558, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27590, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27617, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27644, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27674, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 27704, .adv_w = 160, .box_w = 10, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 27727, .adv_w = 160, .box_w = 10, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 27750, .adv_w = 288, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 27773, .adv_w = 288, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 27796, .adv_w = 192, .box_w = 11, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 27821, .adv_w = 192, .box_w = 11, .box_h = 19, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 27848, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 27885, .adv_w = 256, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 27912, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 27940, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 27976, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28008, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28040, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 28065, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28097, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28129, .adv_w = 192, .box_w = 11, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 28149, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 28174, .adv_w = 288, .box_w = 17, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 28204, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 28229, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28259, .adv_w = 288, .box_w = 19, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 28293, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28329, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 28359, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28383, .adv_w = 160, .box_w = 11, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 28405, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 28433, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28465, .adv_w = 256, .box_w = 15, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 28497, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28529, .adv_w = 256, .box_w = 18, .box_h = 17, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 28568, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 28596, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 28626, .adv_w = 224, .box_w = 14, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 28658, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28686, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 28723, .adv_w = 192, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28749, .adv_w = 192, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28775, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28799, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28823, .adv_w = 192, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28849, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28873, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28897, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28921, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28953, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 28985, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 29026, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 29067, .adv_w = 288, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29099, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29131, .adv_w = 224, .box_w = 15, .box_h = 14, .ofs_x = -1, .ofs_y = -1}, + {.bitmap_index = 29158, .adv_w = 224, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29188, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29220, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29250, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 29275, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 29312, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29344, .adv_w = 256, .box_w = 17, .box_h = 14, .ofs_x = -1, .ofs_y = -1}, + {.bitmap_index = 29374, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 29402, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29432, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 29460, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 29492, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29516, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 29557, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 29598, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29628, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29660, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29692, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29724, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29760, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29788, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 29816, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 29850, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 29878, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 29905, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 29932, .adv_w = 320, .box_w = 19, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 29966, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 29994, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 30018, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 30043, .adv_w = 320, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 30079, .adv_w = 320, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 30115, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 30147, .adv_w = 320, .box_w = 20, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 30190, .adv_w = 224, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 30216, .adv_w = 320, .box_w = 19, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 30250, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 30282, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 30310, .adv_w = 192, .box_w = 11, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 30334, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 30363, .adv_w = 192, .box_w = 11, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 30388, .adv_w = 256, .box_w = 14, .box_h = 19, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 30422, .adv_w = 288, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 30465, .adv_w = 320, .box_w = 19, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 30506, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 30544, .adv_w = 320, .box_w = 19, .box_h = 19, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 30590, .adv_w = 288, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = -1}, + {.bitmap_index = 30622, .adv_w = 192, .box_w = 11, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 30647, .adv_w = 320, .box_w = 18, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 30674, .adv_w = 192, .box_w = 10, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 30696, .adv_w = 192, .box_w = 12, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 30714, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 30739, .adv_w = 320, .box_w = 18, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 30775, .adv_w = 320, .box_w = 18, .box_h = 15, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 30809, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 30841, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 30867, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 30893, .adv_w = 320, .box_w = 19, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 30922, .adv_w = 320, .box_w = 19, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 30951, .adv_w = 320, .box_w = 19, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 30980, .adv_w = 320, .box_w = 19, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 31009, .adv_w = 320, .box_w = 19, .box_h = 12, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 31038, .adv_w = 224, .box_w = 12, .box_h = 16, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 31062, .adv_w = 128, .box_w = 8, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31078, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31114, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31154, .adv_w = 224, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 31181, .adv_w = 224, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 31208, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31240, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31280, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31304, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31328, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31352, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31376, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31400, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31424, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31454, .adv_w = 256, .box_w = 17, .box_h = 12, .ofs_x = -1, .ofs_y = 0}, + {.bitmap_index = 31480, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 31504, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31536, .adv_w = 224, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31566, .adv_w = 224, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31592, .adv_w = 320, .box_w = 19, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 31616, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31648, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31682, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 31709, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 31736, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 31763, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 31790, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 31818, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31838, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31870, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31902, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31934, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 31966, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31998, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 32022, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32054, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32086, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32118, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32150, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32178, .adv_w = 288, .box_w = 17, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32210, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32242, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32267, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32299, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32331, .adv_w = 288, .box_w = 16, .box_h = 18, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 32367, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 32391, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 32428, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 32454, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32486, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32526, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32558, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32590, .adv_w = 288, .box_w = 19, .box_h = 18, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 32633, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32663, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32695, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32727, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32759, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 32789, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 32819, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32851, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32883, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32915, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32947, .adv_w = 224, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32972, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32996, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 33028, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 33060, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33080, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33100, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33120, .adv_w = 160, .box_w = 9, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33138, .adv_w = 160, .box_w = 9, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33156, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 33184, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33216, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33244, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 33268, .adv_w = 256, .box_w = 16, .box_h = 2, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 33272, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 33304, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 33329, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 33353, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33385, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 33421, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33453, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33483, .adv_w = 256, .box_w = 17, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 33522, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33552, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33584, .adv_w = 224, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33610, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 33638, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 33666, .adv_w = 256, .box_w = 17, .box_h = 18, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 33705, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33737, .adv_w = 288, .box_w = 17, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33769, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33801, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33833, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33865, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33897, .adv_w = 160, .box_w = 10, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 33920, .adv_w = 288, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 33943, .adv_w = 288, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 33966, .adv_w = 160, .box_w = 10, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 33989, .adv_w = 288, .box_w = 17, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34021, .adv_w = 320, .box_w = 18, .box_h = 17, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 34060, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 34085, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34109, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 34137, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34169, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34201, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34233, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34265, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34297, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34329, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34361, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34393, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 34418, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34450, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34482, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 34514, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 34546, .adv_w = 288, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 34569, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34597, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34627, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 34652, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 34680, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34702, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34724, .adv_w = 288, .box_w = 15, .box_h = 17, .ofs_x = 3, .ofs_y = -2}, + {.bitmap_index = 34756, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34780, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34804, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34826, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34848, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34870, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 34894, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 34935, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34959, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 34989, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35019, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35047, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35075, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35107, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 35134, .adv_w = 224, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35159, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 35183, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35217, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35249, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35279, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 35320, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35352, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35384, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35416, .adv_w = 256, .box_w = 15, .box_h = 17, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 35448, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35468, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 35493, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35523, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35549, .adv_w = 192, .box_w = 11, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35573, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35605, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35629, .adv_w = 320, .box_w = 20, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 35659, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35691, .adv_w = 192, .box_w = 13, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 35719, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 35743, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 35782, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35814, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 35848, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35880, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35910, .adv_w = 288, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 35933, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 35958, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 35990, .adv_w = 256, .box_w = 17, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 36022, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36046, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 36083, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36107, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36131, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36163, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36187, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36223, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36259, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36283, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36307, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 36331, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36367, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36399, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 36435, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36467, .adv_w = 288, .box_w = 18, .box_h = 6, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36481, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36513, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36537, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36561, .adv_w = 320, .box_w = 19, .box_h = 16, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 36599, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36637, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36673, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 36707, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36739, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36771, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36803, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36835, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36871, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 36898, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 36926, .adv_w = 320, .box_w = 19, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36964, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 36996, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37028, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 37069, .adv_w = 320, .box_w = 20, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 37099, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37133, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37167, .adv_w = 288, .box_w = 18, .box_h = 6, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37181, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37217, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37253, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37289, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37324, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 37363, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 37395, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37429, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 37470, .adv_w = 288, .box_w = 16, .box_h = 17, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 37504, .adv_w = 192, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37526, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37558, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37590, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37622, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37654, .adv_w = 288, .box_w = 17, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 37684, .adv_w = 288, .box_w = 19, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 37720, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37758, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 37799, .adv_w = 160, .box_w = 10, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 37821, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37851, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 37892, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 37922, .adv_w = 320, .box_w = 18, .box_h = 15, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 37956, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 37995, .adv_w = 320, .box_w = 18, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 38034, .adv_w = 320, .box_w = 19, .box_h = 16, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 38072, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38108, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38136, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 38173, .adv_w = 320, .box_w = 19, .box_h = 15, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 38209, .adv_w = 224, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38235, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 38274, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 38315, .adv_w = 320, .box_w = 18, .box_h = 17, .ofs_x = 1, .ofs_y = -4}, + {.bitmap_index = 38354, .adv_w = 224, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38379, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 38419, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38459, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38499, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38529, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38559, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38593, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38629, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 38656, .adv_w = 320, .box_w = 19, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38692, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38726, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38758, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38790, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38830, .adv_w = 288, .box_w = 17, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 38858, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38890, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 38915, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 38940, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 38965, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 38990, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39015, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39040, .adv_w = 224, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 39063, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 39091, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 39119, .adv_w = 224, .box_w = 14, .box_h = 8, .ofs_x = 0, .ofs_y = 2}, + {.bitmap_index = 39133, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 39167, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39199, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 39231, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39263, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, + {.bitmap_index = 39290, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39315, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39347, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 39372, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39404, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39431, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39456, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 39480, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 39521, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39549, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39577, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 39601, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 39625, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39650, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 39682, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39707, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39732, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39760, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 39784, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39824, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 39861, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39886, .adv_w = 288, .box_w = 18, .box_h = 8, .ofs_x = 0, .ofs_y = 2}, + {.bitmap_index = 39904, .adv_w = 128, .box_w = 8, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 39922, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 39958, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 39995, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40031, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40063, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40095, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 40123, .adv_w = 288, .box_w = 17, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 40153, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 40177, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40209, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 40239, .adv_w = 320, .box_w = 19, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40275, .adv_w = 192, .box_w = 12, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 40302, .adv_w = 256, .box_w = 16, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 40328, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40360, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 40388, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40416, .adv_w = 224, .box_w = 14, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 40448, .adv_w = 320, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 40475, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 40510, .adv_w = 256, .box_w = 14, .box_h = 16, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 40538, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40562, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40590, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40620, .adv_w = 192, .box_w = 12, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 40643, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 40684, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 40708, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40740, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40769, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40801, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 40828, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40860, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40892, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 40920, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 40952, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40984, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41008, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41034, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 41068, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41092, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41116, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41140, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41166, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 2, .ofs_y = -4}, + {.bitmap_index = 41207, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41231, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41263, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 41300, .adv_w = 256, .box_w = 17, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 41332, .adv_w = 288, .box_w = 18, .box_h = 11, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 41357, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41389, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41421, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41451, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41483, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41515, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41547, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41579, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41611, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41643, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41675, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 41712, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41744, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41776, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 41817, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41849, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41889, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41921, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41953, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41987, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42019, .adv_w = 256, .box_w = 16, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 42039, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42059, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 42086, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42114, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42148, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42175, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42205, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 42239, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42271, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42303, .adv_w = 320, .box_w = 19, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 42341, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42373, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42405, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42437, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42469, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42505, .adv_w = 320, .box_w = 18, .box_h = 17, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 42544, .adv_w = 320, .box_w = 18, .box_h = 17, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 42583, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42613, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42643, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42675, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42707, .adv_w = 192, .box_w = 11, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42731, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42763, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 42797, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42821, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42853, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42885, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42917, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42953, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 42987, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43015, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43047, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43079, .adv_w = 288, .box_w = 18, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 43109, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 43144, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43176, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43210, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 43242, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43274, .adv_w = 288, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 43306, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43338, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 43374, .adv_w = 192, .box_w = 12, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 43401, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43433, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43465, .adv_w = 320, .box_w = 20, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 43493, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43531, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 43572, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43604, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43632, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43664, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43696, .adv_w = 160, .box_w = 10, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 43718, .adv_w = 288, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 43750, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43778, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43806, .adv_w = 320, .box_w = 20, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 43834, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 43866, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 43900, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 43928, .adv_w = 256, .box_w = 16, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 43954, .adv_w = 320, .box_w = 19, .box_h = 19, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 44000, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 44035, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 44069, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44101, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44133, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 44168, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44200, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44234, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44270, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44302, .adv_w = 320, .box_w = 20, .box_h = 11, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 44330, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44362, .adv_w = 256, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44390, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 44420, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44452, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44484, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44516, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44548, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 44576, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 44604, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 44640, .adv_w = 160, .box_w = 10, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 44663, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44697, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 44732, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 44756, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44776, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44804, .adv_w = 320, .box_w = 18, .box_h = 17, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 44843, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44879, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44911, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 44952, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 44976, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45008, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 45045, .adv_w = 256, .box_w = 16, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 45071, .adv_w = 256, .box_w = 16, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 45097, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 45131, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45163, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45195, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45229, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45263, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45291, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45323, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45355, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45387, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45423, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45463, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45502, .adv_w = 288, .box_w = 16, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 45534, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45574, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45606, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45638, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 45663, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 45688, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45712, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 45752, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45780, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45812, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45844, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45874, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 45906, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 45938, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 45974, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46008, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46044, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46072, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46108, .adv_w = 288, .box_w = 17, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 46138, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46170, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 46206, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46234, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 46264, .adv_w = 288, .box_w = 16, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 46296, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46324, .adv_w = 320, .box_w = 18, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 46360, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46399, .adv_w = 320, .box_w = 20, .box_h = 12, .ofs_x = -1, .ofs_y = 0}, + {.bitmap_index = 46429, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 46465, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46495, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46534, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46574, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46606, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46638, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 46675, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46699, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46723, .adv_w = 320, .box_w = 19, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46764, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46804, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46834, .adv_w = 224, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 46864, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 46899, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46935, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 46969, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 46990, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 47017, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 47047, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 47079, .adv_w = 320, .box_w = 21, .box_h = 14, .ofs_x = -1, .ofs_y = -1}, + {.bitmap_index = 47116, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 47140, .adv_w = 224, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 47170, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 47202, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 47232, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 47273, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 47309, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 47341, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 47373, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47407, .adv_w = 224, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47433, .adv_w = 288, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 47456, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47488, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47520, .adv_w = 160, .box_w = 10, .box_h = 20, .ofs_x = 0, .ofs_y = -4}, + {.bitmap_index = 47545, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47577, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47613, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47645, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47677, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47716, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47756, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47784, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47808, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47840, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47872, .adv_w = 288, .box_w = 17, .box_h = 18, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 47911, .adv_w = 256, .box_w = 16, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 47947, .adv_w = 320, .box_w = 20, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 47980, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48015, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48051, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 48081, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 48111, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 48145, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48173, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48205, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48237, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 48261, .adv_w = 192, .box_w = 12, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48284, .adv_w = 256, .box_w = 14, .box_h = 16, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 48312, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 48346, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 48376, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48403, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48430, .adv_w = 224, .box_w = 13, .box_h = 17, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 48458, .adv_w = 320, .box_w = 17, .box_h = 17, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 48495, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48527, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48559, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48591, .adv_w = 320, .box_w = 19, .box_h = 15, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 48627, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 48651, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48690, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48730, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48755, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48787, .adv_w = 224, .box_w = 14, .box_h = 6, .ofs_x = 0, .ofs_y = 3}, + {.bitmap_index = 48798, .adv_w = 96, .box_w = 6, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48809, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48841, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48869, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48899, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48931, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48963, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48995, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49023, .adv_w = 288, .box_w = 16, .box_h = 17, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 49057, .adv_w = 288, .box_w = 17, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49089, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49121, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49161, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49193, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49225, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49249, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49273, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49303, .adv_w = 288, .box_w = 17, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 49340, .adv_w = 288, .box_w = 19, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 49376, .adv_w = 320, .box_w = 19, .box_h = 13, .ofs_x = 1, .ofs_y = 0}, + {.bitmap_index = 49407, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49439, .adv_w = 288, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49469, .adv_w = 320, .box_w = 20, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 49514, .adv_w = 320, .box_w = 19, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 49552, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 49573, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49601, .adv_w = 288, .box_w = 17, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 49635, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49669, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49693, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 49734, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49762, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 49794, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 49822, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 49856, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49880, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49912, .adv_w = 256, .box_w = 17, .box_h = 17, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 49949, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49981, .adv_w = 192, .box_w = 12, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50004, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50040, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 50068, .adv_w = 224, .box_w = 13, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 50094, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 50126, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 50160, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 50194, .adv_w = 256, .box_w = 17, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 50228, .adv_w = 320, .box_w = 18, .box_h = 17, .ofs_x = 2, .ofs_y = -3}, + {.bitmap_index = 50267, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50299, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 50325, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 50360, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 50384, .adv_w = 288, .box_w = 17, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 50414, .adv_w = 256, .box_w = 15, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 50446, .adv_w = 256, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50476, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50504, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 50529, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50559, .adv_w = 224, .box_w = 14, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50589, .adv_w = 224, .box_w = 13, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50615, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 50639, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50675, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 50700, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 50725, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 50750, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50786, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50818, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50852, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50884, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 50909, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 50947, .adv_w = 288, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 50988, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 51016, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 51044, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 51076, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 51108, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 51135, .adv_w = 256, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 51162, .adv_w = 288, .box_w = 17, .box_h = 15, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 51194, .adv_w = 320, .box_w = 20, .box_h = 9, .ofs_x = 0, .ofs_y = 2}, + {.bitmap_index = 51217, .adv_w = 320, .box_w = 20, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 51250, .adv_w = 320, .box_w = 20, .box_h = 13, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 51283, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 51307, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 51339, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 51371, .adv_w = 288, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 51405, .adv_w = 192, .box_w = 12, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 51431, .adv_w = 320, .box_w = 19, .box_h = 15, .ofs_x = 1, .ofs_y = -2} +}; + +/*--------------------- + * CHARACTER MAPPING + *--------------------*/ + +static const uint16_t unicode_list_6[] = { + 0x0, 0x5, 0x6, 0x8, 0x9, 0xd, 0xe, 0x11, + 0x12, 0x15, 0x16, 0x1b, 0x25, 0x27, 0x28, 0x2e, + 0x2f, 0x99, 0x9d, 0x9e, 0x9f, 0xa1, 0xa9, 0xad, + 0xb2, 0x117, 0x185, 0x186, 0x187, 0x188, 0x189, 0x18a, + 0x18c, 0x1af, 0x1b0, 0x1b9, 0x207, 0x213, 0x2f8, 0x2f9, + 0x310, 0x31d, 0x31e, 0x31f, 0x320, 0x38e, 0x3c4, 0x3de, + 0x3df, 0x3e2, 0x3e3, 0x3e5, 0x3e6, 0x3e8, 0x3ed, 0x3ee, + 0x3f0, 0x3f3, 0x4b2, 0x595, 0x5ab, 0x5c4, 0x5c5, 0x5f0, + 0x5f1, 0x5f5, 0x5f6, 0x5f8, 0x5f9, 0x606, 0x60a, 0x612, + 0x615, 0x617, 0x618, 0x61a, 0x61f, 0x621, 0x623, 0x624, + 0x62d, 0x62e, 0x634, 0x635, 0x637, 0x64f, 0x650, 0x651, + 0x652, 0x653, 0x654, 0x65a, 0x65b, 0x667, 0x66f, 0x670, + 0x673, 0x675, 0x676, 0x677, 0x678, 0x679, 0x67a, 0x688, + 0x68b, 0x68e, 0x690, 0x695, 0x696, 0x697, 0x698, 0x699, + 0x69a, 0x69b, 0x69c, 0x69d, 0x69e, 0x69f, 0x6a0, 0x6a7, + 0x6b2, 0x6b3, 0x6b9, 0x6ba, 0x6bb, 0x6d4, 0x6de, 0x6df, + 0x6ec, 0x6ef, 0x6f2, 0x6f5, 0x6f7, 0x6f9, 0x6fa, 0x6fe, + 0x6ff, 0x700, 0x701, 0x704, 0x706, 0x707, 0x708, 0x709, + 0x70a, 0x70b, 0x712, 0x716, 0x726, 0x739, 0x73b, 0x741, + 0x743, 0x748, 0x749, 0x74a, 0x74c, 0x759, 0x78a, 0x78b, + 0x78c, 0x929, 0x92a, 0xb02, 0xb10, 0xb11, 0xb18, 0xb19, + 0xb45, 0xbffa, 0xbffb, 0xc002, 0xc007, 0xc034, 0xc035, 0xc036 +}; + +static const uint16_t unicode_list_8[] = { + 0x0, 0x1, 0x12, 0x13, 0x15, 0x24, 0x27, 0x2f, + 0x32, 0x36, 0x4a, 0x53, 0x5a, 0x5e, 0x5f, 0xac, + 0xb4, 0xb5, 0xb6, 0xb7, 0xbb, 0xcd, 0xde, 0xe4, + 0xe8, 0xf6, 0xff, 0x100, 0x10a, 0x115, 0x116, 0x123, + 0x12b, 0x137, 0x13f, 0x143, 0x14e, 0x150, 0x152, 0x168, + 0x16e, 0x171, 0x179, 0x184, 0x19c, 0x19d, 0x1a8, 0x1b8, + 0x204, 0x217, 0x232, 0x236, 0x240, 0x242, 0x245, 0x248, + 0x249, 0x261, 0x266, 0x282, 0x299, 0x32a, 0x32c, 0x32d, + 0x370, 0x3b7, 0x3c0, 0x3c2, 0x3c3, 0x3e7, 0x3ee, 0x3f1, + 0x3f2, 0x3f5, 0x3f6, 0x40b, 0x40f, 0x420, 0x423, 0x424, + 0x425, 0x426, 0x427, 0x428, 0x42a, 0x42b, 0x42e +}; + +static const uint8_t glyph_id_ofs_list_11[] = { + 0, 1, 2, 3, 4, 5, 6, 0, + 7, 8, 9, 10 +}; + +static const uint8_t glyph_id_ofs_list_16[] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 0, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 0, + 30, 31, 32, 33, 0, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 0, + 52, 53, 54, 55, 56, 57, 58, 0, + 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 0, 69, 70 +}; + +static const uint16_t unicode_list_17[] = { + 0x0, 0x1, 0x9, 0xa, 0xf, 0x14, 0xd8, 0xda, + 0xe2, 0xef, 0xf1, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0x1f0, 0x267, 0x26a, 0x276, 0x27b, 0x27c, + 0x27d, 0x280 +}; + +static const uint8_t glyph_id_ofs_list_24[] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 0, + 15, 0, 0, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 0, 25, 26, 0, + 27, 28, 0, 29, 30, 31, 32, 33, + 34, 0, 0, 0, 35, 36, 37, 0, + 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 0, + 53, 54, 55, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 0, + 71, 72, 0, 0, 0, 0, 73, 74, + 75, 76, 77, 78, 79, 80, 81, 0, + 82, 0, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 0, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 0, + 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 124, 125, 0, + 126, 127, 128, 0, 129, 130, 0, 0, + 131, 132, 133, 134, 135, 136, 137, 0, + 138, 139, 140, 141, 142, 143, 144, 145, + 146, 147, 148, 149, 150, 151, 152, 0, + 153, 154, 155, 156, 157, 158, 0, 159, + 160, 161, 162, 0, 0, 163, 164, 0, + 165, 166, 167, 168, 169, 170, 171, 172, + 173, 174, 175, 176, 177, 178, 179, 0, + 180, 181, 182, 183, 184, 185, 186, 187, + 188, 189, 0, 190, 191, 192, 193, 0, + 194, 195, 196, 197, 198, 199 +}; + +static const uint8_t glyph_id_ofs_list_25[] = { + 0, 1, 2, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 4, 5, 0, + 6, 7, 8, 9, 0, 0, 0, 0, + 0, 10, 0, 11, 12, 13, 14, 0, + 15, 16, 17, 0, 18, 0, 0, 19, + 20, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 21, 22, + 23, 24, 0, 25, 26, 27, 28, 0, + 0, 0, 0, 29, 30, 31, 32, 0, + 0, 0, 0, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 0, 0, 0, + 43, 44, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 45, 46, 47, 48, 49, + 50, 51, 0, 52, 53, 54, 55, 56, + 57, 58, 0, 0, 0, 59, 60, 61, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 62, 63, 64, 65, 66, 67, 68, + 69, 70, 0, 71, 72, 0, 0, 73, + 74, 75, 76, 0, 0, 77, 78, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 79, 80, 81, 82, 83, 84, + 85, 86, 0, 0, 87, 88, 89, 90, + 91, 92, 93, 94, 95, 96, 97, 98, + 99, 0, 0, 0, 0, 0, 100, 101, + 102, 103, 0, 104, 105, 0, 0, 0, + 0, 0, 0, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 115, 116, 0, 0, + 117, 118, 0, 119, 120, 121, 122, 123, + 124, 125, 126, 127, 128, 129, 130, 131, + 132 +}; + +static const uint16_t unicode_list_26[] = { + 0x0, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, + 0xc, 0xd, 0xe, 0xf, 0x17, 0x1f, 0x20, 0x21, + 0x22, 0x24, 0x25, 0x26, 0x29, 0x2e, 0x30, 0x31, + 0x32, 0x34, 0x35, 0x36, 0x37, 0x38, 0x3b, 0x3c, + 0x48, 0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x54, 0x55, 0x56, 0x57, 0x5b, 0x5c, + 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x6f, 0x70, 0x79, 0x7b, 0x7e, + 0x81, 0x85, 0x86, 0x89, 0x8a, 0x8d, 0x92, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0x9d, 0x9e, 0x9f, 0xa0, + 0xa6, 0xb0, 0xb2, 0xbc, 0xc6, 0xcb, 0xcc, 0xe2, + 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf4, 0xf6, 0xf7, + 0x115, 0x116, 0x11a, 0x11b, 0x124, 0x134, 0x139, 0x152, + 0x153, 0x155, 0x159, 0x15d, 0x161, 0x162, 0x163, 0x165, + 0x171, 0x174, 0x179, 0x181, 0x18e, 0x18f, 0x191, 0x193, + 0x19a, 0x1a4, 0x1b6, 0x1b8, 0x1b9, 0x1c6, 0x1c7, 0x1c8, + 0x1ca, 0x1cd, 0x1ce, 0x1d0, 0x1d3, 0x1d5, 0x1d7, 0x1d9, + 0x1db, 0x1df, 0x1e2, 0x1e4, 0x1e7, 0x1ec, 0x1f0, 0x1f1, + 0x1f3, 0x1f5, 0x1f6, 0x1fa, 0x1fc, 0x1fd, 0x1fe, 0x1ff, + 0x200, 0x201, 0x204, 0x205, 0x206, 0x208, 0x20b, 0x20c, + 0x20d, 0x211, 0x212, 0x213, 0x215, 0x216, 0x218, 0x219, + 0x21a, 0x21b, 0x21f, 0x221, 0x222, 0x224, 0x225, 0x226, + 0x227, 0x228, 0x22a, 0x22b, 0x232, 0x235, 0x241, 0x247, + 0x24c, 0x24d, 0x24e, 0x251, 0x252, 0x254, 0x255, 0x256, + 0x258, 0x25a, 0x261, 0x262, 0x267, 0x26a, 0x26b, 0x26c, + 0x26d, 0x26e, 0x26f, 0x272, 0x273, 0x276, 0x277, 0x27a +}; + +static const uint8_t glyph_id_ofs_list_29[] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 0, 11, 12, 13, 14, + 0, 15, 16, 17, 18, 0, 0, 19, + 20, 21, 22, 23, 24, 25, 26, 0, + 27, 28, 0, 29, 30, 31, 0, 32, + 33, 34, 35, 0, 36, 37, 38, 39, + 40, 41, 42, 0, 43, 44, 45, 46, + 0, 0, 47, 48, 0, 49, 50, 51 +}; + +static const uint16_t unicode_list_30[] = { + 0x0, 0x3, 0x5, 0x7, 0x8, 0xa, 0xd, 0x10, + 0x14, 0x17, 0x25, 0x26, 0x2a, 0x2d, 0x39, 0x3c, + 0x42, 0x48, 0x4a, 0x4d, 0x4e, 0x52, 0x53, 0x57, + 0x58, 0x59, 0x60, 0x64, 0x65, 0x6a, 0x6d, 0x70, + 0x73, 0x78, 0x7a, 0x7c, 0x7d, 0x7e, 0x81, 0x86, + 0x87, 0x8b, 0x8d, 0x8e, 0x8f, 0x92, 0x93, 0x94, + 0x96, 0x98, 0x9d, 0x9f, 0xa1, 0xa2, 0xa4, 0xa5, + 0xa8, 0xaa, 0xab, 0xac, 0xad, 0xb0, 0xb1, 0xb2, + 0xbf, 0xc1, 0xc2, 0xc3, 0xc4, 0xc9, 0xca, 0xd0, + 0xd2, 0xd6, 0xdf, 0xe0, 0xe4, 0xe7, 0xe9, 0xec, + 0xed, 0xf1, 0xf8, 0xfa, 0xfc, 0xfe, 0x100, 0x102, + 0x106, 0x107, 0x10b, 0x10c, 0x10f, 0x111, 0x115, 0x116, + 0x119, 0x11a, 0x11b, 0x123, 0x125, 0x128, 0x129, 0x134, + 0x135, 0x137, 0x13d, 0x13e, 0x140, 0x147, 0x148, 0x14b, + 0x151, 0x152, 0x154, 0x157, 0x158, 0x164, 0x165, 0x166, + 0x169, 0x16c, 0x170, 0x176, 0x17a, 0x17b, 0x17c, 0x17f, + 0x183, 0x184, 0x187, 0x188, 0x192, 0x194, 0x195, 0x198, + 0x199, 0x19b, 0x19c, 0x1a5, 0x1a6, 0x1a9, 0x1aa, 0x1ac, + 0x1ad, 0x1af, 0x1b0, 0x1b1, 0x1b5, 0x1bc, 0x1bd, 0x1bf, + 0x1c5, 0x1c8, 0x1c9, 0x1cb, 0x1cd, 0x1ce, 0x1cf, 0x1d2, + 0x1d3, 0x1d4, 0x1d6, 0x1d7, 0x1de, 0x1df, 0x1e2, 0x1e3, + 0x1e6, 0x1e8, 0x1e9, 0x1eb, 0x1ed, 0x1ee, 0x1f2, 0x1f3, + 0x1f5, 0x1f6, 0x1f7, 0x1f9, 0x1fb, 0x200, 0x201, 0x202, + 0x203, 0x20d, 0x20e, 0x20f, 0x215, 0x218, 0x21b, 0x21c, + 0x21e, 0x220, 0x223, 0x224, 0x22b, 0x22e, 0x22f, 0x230, + 0x233, 0x234, 0x235, 0x236, 0x238, 0x239, 0x23b, 0x23e, + 0x23f, 0x241, 0x246, 0x250, 0x251, 0x252, 0x253, 0x258, + 0x267, 0x273, 0x275, 0x279, 0x27c, 0x287, 0x28c, 0x296, + 0x2a2, 0x2a4, 0x2a5, 0x2a6, 0x2aa, 0x2ab, 0x2ad, 0x2ae, + 0x2af, 0x2b0, 0x2ba, 0x2c0, 0x2e9, 0x2ea, 0x2f5, 0x300, + 0x302, 0x30e, 0x318, 0x328 +}; + +/*Collect the unicode lists and glyph_id offsets*/ +static const lv_font_fmt_txt_cmap_t cmaps[] = +{ + { + .range_start = 32, .range_length = 16, .glyph_id_start = 1, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 48, .range_length = 256, .glyph_id_start = 17, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 304, .range_length = 256, .glyph_id_start = 273, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 560, .range_length = 256, .glyph_id_start = 529, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 816, .range_length = 256, .glyph_id_start = 785, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 1072, .range_length = 256, .glyph_id_start = 1041, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 8203, .range_length = 49207, .glyph_id_start = 1297, + .unicode_list = unicode_list_6, .glyph_id_ofs_list = NULL, .list_length = 176, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + }, + { + .range_start = 57433, .range_length = 30, .glyph_id_start = 1473, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 57477, .range_length = 1071, .glyph_id_start = 1503, + .unicode_list = unicode_list_8, .glyph_id_ofs_list = NULL, .list_length = 87, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + }, + { + .range_start = 58549, .range_length = 24, .glyph_id_start = 1590, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 58574, .range_length = 17, .glyph_id_start = 1614, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 58592, .range_length = 12, .glyph_id_start = 1631, + .unicode_list = NULL, .glyph_id_ofs_list = glyph_id_ofs_list_11, .list_length = 12, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL + }, + { + .range_start = 58605, .range_length = 23, .glyph_id_start = 1642, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 58631, .range_length = 31, .glyph_id_start = 1665, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 58663, .range_length = 9, .glyph_id_start = 1696, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 58674, .range_length = 30, .glyph_id_start = 1705, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 58705, .range_length = 77, .glyph_id_start = 1735, + .unicode_list = NULL, .glyph_id_ofs_list = glyph_id_ofs_list_16, .list_length = 77, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL + }, + { + .range_start = 58784, .range_length = 641, .glyph_id_start = 1806, + .unicode_list = unicode_list_17, .glyph_id_ofs_list = NULL, .list_length = 26, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + }, + { + .range_start = 61440, .range_length = 15, .glyph_id_start = 1832, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 61456, .range_length = 15, .glyph_id_start = 1847, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 61473, .range_length = 30, .glyph_id_start = 1862, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 61504, .range_length = 15, .glyph_id_start = 1892, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 61520, .range_length = 15, .glyph_id_start = 1907, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 61536, .range_length = 15, .glyph_id_start = 1922, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 61552, .range_length = 246, .glyph_id_start = 1937, + .unicode_list = NULL, .glyph_id_ofs_list = glyph_id_ofs_list_24, .list_length = 246, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL + }, + { + .range_start = 61813, .range_length = 233, .glyph_id_start = 2137, + .unicode_list = NULL, .glyph_id_ofs_list = glyph_id_ofs_list_25, .list_length = 233, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL + }, + { + .range_start = 62060, .range_length = 635, .glyph_id_start = 2270, + .unicode_list = unicode_list_26, .glyph_id_ofs_list = NULL, .list_length = 208, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + }, + { + .range_start = 62714, .range_length = 16, .glyph_id_start = 2478, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 62741, .range_length = 125, .glyph_id_start = 2494, + .unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY + }, + { + .range_start = 62867, .range_length = 64, .glyph_id_start = 2619, + .unicode_list = NULL, .glyph_id_ofs_list = glyph_id_ofs_list_29, .list_length = 64, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_FULL + }, + { + .range_start = 62935, .range_length = 809, .glyph_id_start = 2671, + .unicode_list = unicode_list_30, .glyph_id_ofs_list = NULL, .list_length = 244, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + } +}; + + + +/*-------------------- + * ALL CUSTOM DATA + *--------------------*/ + +#if LVGL_VERSION_MAJOR == 8 +/*Store all the custom data of the font*/ +static lv_font_fmt_txt_glyph_cache_t cache; +#endif + +#if LVGL_VERSION_MAJOR >= 8 +static const lv_font_fmt_txt_dsc_t font_dsc = { +#else +static lv_font_fmt_txt_dsc_t font_dsc = { +#endif + .glyph_bitmap = glyph_bitmap, + .glyph_dsc = glyph_dsc, + .cmaps = cmaps, + .kern_dsc = NULL, + .kern_scale = 0, + .cmap_num = 31, + .bpp = 1, + .kern_classes = 0, + .bitmap_format = 0, +#if LVGL_VERSION_MAJOR == 8 + .cache = &cache +#endif +}; + + + +/*----------------- + * PUBLIC FONT + *----------------*/ + +/*Initialize a public general font descriptor*/ +#if LVGL_VERSION_MAJOR >= 8 +const lv_font_t ui_font_fa = { +#else +lv_font_t ui_font_fa = { +#endif + .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/ + .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/ + .line_height = 20, /*The maximum line height required by the font*/ + .base_line = 4, /*Baseline measured from the bottom of the line*/ +#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0) + .subpx = LV_FONT_SUBPX_NONE, +#endif +#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8 + .underline_position = 0, + .underline_thickness = 0, +#endif + .dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */ +#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9 + .fallback = NULL, +#endif + .user_data = NULL, +}; + + + +#endif /*#if UI_FONT_FA*/ + diff --git a/main/Application/Tasks/GUI/Export/images/ui_img_logo_80x44_png.c b/main/Application/Tasks/GUI/Export/images/ui_img_logo_80x44_png.c new file mode 100644 index 0000000..f3cf13a --- /dev/null +++ b/main/Application/Tasks/GUI/Export/images/ui_img_logo_80x44_png.c @@ -0,0 +1,136 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#include "../ui.h" + +#ifndef LV_ATTRIBUTE_MEM_ALIGN + #define LV_ATTRIBUTE_MEM_ALIGN +#endif + +// IMAGE DATA: assets/Logo_80x44.png +const LV_ATTRIBUTE_MEM_ALIGN uint8_t ui_img_logo_80x44_png_data[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0xFD,0x23,0xFD,0x45,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xE4,0xFB,0x40,0xFC,0xA0,0xFC,0x47,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0xFB,0x22,0xFB,0xA0,0xFB,0x00,0xFC,0x80,0xFC,0xCA,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x88,0xFD,0x23,0xFD,0x43,0xFD,0x43,0xFD,0x4D,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x65,0xFA,0xA1,0xFA,0x20,0xFB,0xA0,0xFB,0xE0,0xFB,0x40,0xFC,0xAC,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0xFD,0x00,0xFD,0x00,0xFD,0x20,0xFD,0xEF,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0xFC,0xC3,0xF9,0x01,0xFA,0x81,0xFA,0x20,0xFB,0x80,0xFB,0xC0,0xFB,0x00,0xFC,0x87,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC4,0xFC,0xA0,0xFC,0xA0,0xFC,0xE0,0xFC,0xAE,0xFD,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x29,0xFA,0x02,0xF9,0x62,0xF9,0xE1,0xF9,0x80,0xFA, + 0x00,0xFB,0x60,0xFB,0xA0,0xFB,0xE0,0xFB,0x64,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC5,0xFC,0x81,0xFC,0x81,0xFC,0xC1,0xFC,0x8F,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x45,0xF9,0x63,0xF8,0xE2,0xF8,0x62,0xF9,0xC1,0xF9,0x60,0xFA,0xE0,0xFA,0x80,0xFB,0xC0,0xFB,0x40,0xFC,0x81,0xFC,0x00,0x00,0xCF,0xFD,0xC7,0xFD,0xE4,0xFD,0x8A,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEC,0xFA,0x84,0xF8,0x03,0xF8,0x63,0xF8,0xE2,0xF8,0x42,0xF9,0xC1,0xF9,0x40,0xFA,0xC0,0xFA,0x60,0xFB,0xA0,0xFB,0x20,0xFC,0x80,0xFC,0xE0,0xFC,0x20,0xFD,0x60,0xFD,0xC1,0xFD,0xC9,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE7,0xFD,0xA1,0xFD,0xA1,0xFD,0xA4,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0xFC,0xE7,0xF8,0x04,0xF8,0x04,0xF8,0x03,0xF8,0x43,0xF8,0xC2,0xF8,0x41,0xF9,0xC0,0xF9,0x20,0xFA,0x80,0xFA,0xE0,0xFA,0x00,0xFB,0x60,0xFB,0xA0,0xFB,0xA0,0xFC,0xE0,0xFC,0x20,0xFD,0xA1,0xFD,0x0B,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xA6,0xFD,0x80,0xFD,0x60,0xFD,0x63,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEE,0xF9,0xAB,0xF8,0x28,0xF8,0x06,0xF8,0x05,0xF8,0x05,0xF8,0x04,0xF8,0x43,0xF8,0xA2,0xF8,0x21,0xF9,0xA0,0xF9,0x00,0xFA, + 0x60,0xFA,0xA0,0xFA,0xC0,0xFA,0x00,0xFB,0x40,0xFB,0x60,0xFC,0xC0,0xFC,0x00,0xFD,0x81,0xFD,0x4D,0xFE,0x00,0x00,0x00,0x00,0x2A,0xFE,0x25,0xFE,0x25,0xFE,0x25,0xFE,0x6D,0xFE,0x87,0xFD,0x41,0xFD,0x21,0xFD,0x23,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xBC,0x5F,0xB3,0x1F,0xC3,0xBF,0xCB,0x3F,0xDC,0x9F,0xF4,0x3D,0xFC,0x7B,0xFB,0x97,0xFA, + 0xD4,0xF9,0x31,0xF9,0xAF,0xF8,0x2E,0xF8,0x0C,0xF8,0x0A,0xF8,0x09,0xF8,0x07,0xF8,0x06,0xF8,0x05,0xF8,0x04,0xF8,0x43,0xF8,0xA2,0xF8,0x21,0xF9,0xA0,0xF9,0x00,0xFA,0x40,0xFA,0x80,0xFA,0xA0,0xFA,0xC0,0xFA,0xE0,0xFA,0x20,0xFC,0xA0,0xFC,0xE0,0xFC,0x40,0xFD,0x62,0xFC,0xA2,0xFB,0xC2,0xFB,0x81,0xFD,0x20,0xFE,0x20,0xFE,0x20,0xFE,0x67,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x5F,0xC3,0xBF,0xC2,0x7F,0xCA,0x1F,0xE2,0x7D,0xF9,0xD9,0xF8,0x76,0xF8,0x53,0xF8,0x31,0xF8,0x0F,0xF8,0x2E,0xF8,0x2D,0xF8,0x0B,0xF8,0x09,0xF8,0x08,0xF8,0x06,0xF8,0x05,0xF8,0x04,0xF8,0x23,0xF8,0x62,0xF8,0xE1,0xF8,0x20,0xFA,0x60,0xFA,0xC0,0xFA,0xC0,0xFA,0xC0,0xFA,0x40,0xFA,0x60,0xFA,0x40,0xFB,0x80,0xFC,0xC0,0xFC,0x00,0xFD,0x20,0xFC,0xA0,0xFB,0xA0,0xFB,0x40,0xFD,0x40,0xFE,0x40,0xFE,0x40,0xFE,0x87,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0xCB,0x1F,0xE2,0x7E,0xF9,0xFA,0xF8,0x96,0xF8,0x74,0xF8,0x31,0xF8,0x30,0xF8,0x2E,0xF8,0x2D,0xF8,0x2B,0xF8,0x0A,0xF8,0x08,0xF8,0x07,0xF8,0x05,0xF8,0x04,0xF8,0x23,0xF8,0x61,0xF8,0xA1,0xF8,0x00,0xF9,0xC0,0xF9,0xA0,0xFA,0xC0,0xFA,0x80,0xFA,0x00,0xFA,0x20,0xFA,0x60,0xFA,0x20,0xFC,0x80,0xFC,0xA0,0xFC,0xE0,0xFB,0xA0,0xFB,0xC0,0xFB,0x40,0xFD,0x40,0xFE,0x40,0xFE,0x40,0xFE, + 0x87,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0xFD,0x03,0xFD,0x03,0xFD,0xE3,0xFC,0x30,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x5F,0xEF,0x1F,0xDF,0x7F,0xEF,0x3F,0xDF,0x7F,0xE7,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x5F,0xFE,0xFE,0xF9,0x1B,0xF9,0xB7,0xF8,0x74,0xF8,0x52,0xF8,0x30,0xF8,0x2F,0xF8,0x2D,0xF8,0x2B,0xF8,0x2A,0xF8,0x08,0xF8,0x07,0xF8,0x05,0xF8,0x04,0xF8,0x23,0xF8,0x41,0xF8,0x00,0xF9,0x80,0xF9,0xC0,0xF9, + 0x00,0xFA,0x00,0xFA,0x00,0xFA,0xE0,0xF9,0x00,0xFA,0x40,0xFA,0x20,0xFB,0x20,0xFB,0x40,0xFB,0x20,0xFC,0xA0,0xFC,0x00,0xFD,0x00,0xFD,0xC0,0xFC,0xE0,0xFC,0x40,0xFD,0xC7,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0xE4,0xFC,0xE0,0xFC,0xC0,0xFC,0xC0,0xFC,0x0C,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0x7F,0xEF,0xFF,0xFF,0xDF,0xFF,0xBF,0xF7,0xDF,0xFF,0xBF,0xF7,0x9F,0xF7,0x9F,0xF7,0xDF,0xFF,0xDE,0xFF,0x9F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xF9,0xD8,0xF8, + 0x74,0xF8,0x52,0xF8,0x30,0xF8,0x2F,0xF8,0x2D,0xF8,0x2B,0xF8,0x09,0xF8,0x08,0xF8,0x07,0xF8,0x05,0xF8,0xE5,0xFA,0x44,0xFA,0x41,0xF8,0x40,0xF9,0xE0,0xF9,0x44,0xFB,0x85,0xFC,0x60,0xF9,0x80,0xF9,0xA0,0xF9,0xC0,0xF9,0x87,0xFE,0x24,0xFD,0x60,0xFA,0xA0,0xFA,0xE0,0xFB,0xA0,0xFC,0xE0,0xFC,0xC0,0xFC,0xA0,0xFC,0xC0,0xFC,0x20,0xFD,0x84,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0xA4,0xFC,0xA0,0xFC,0x80,0xFC,0x80,0xFC,0xCC,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0xEF,0xDF,0xFF,0xDF,0xFF,0x5F,0xD7,0x7F,0xDF,0xBF,0xE7, + 0xBF,0xE7,0xBF,0xE7,0xBF,0xE7,0x9F,0xE7,0x7F,0xD7,0xDF,0xB6,0xBF,0xF7,0x9E,0xFF,0x9F,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0x98,0xF9,0x75,0xF8,0x52,0xF8,0x30,0xF8,0x2F,0xF8,0x2D,0xF8,0x2B,0xF8,0x0A,0xF8,0x08,0xF8,0x07,0xF8,0x06,0xF8,0x68,0xFD,0x89,0xFF,0x83,0xFA,0x20,0xF9,0xA0,0xF9,0xE4,0xFB,0xE9,0xFF,0x25,0xFD,0x60,0xF9,0x80,0xF9,0x80,0xF9,0xA6,0xFE,0xC7,0xFF,0x82,0xFB,0xA0,0xFA,0xC0,0xFB,0x60,0xFC,0x80,0xFC,0xA0,0xFC,0xC0,0xFC,0xE0,0xFC,0x20,0xFD,0x84,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0xA5,0xFC,0x62,0xFC,0x62,0xFC,0x42,0xFC,0x51,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xDF,0xFF,0xBF,0xFF,0xBF,0xFF,0x3F,0xD7,0xBF,0xE7,0xBF,0xEF,0x9F,0xE7,0x5F,0xD7,0x3F,0xE7,0x5F,0xF7,0xFF,0xD6,0xFF,0xCE,0x5F,0xD7,0x7F,0xE7,0x7F,0xE7,0x1F,0xC7,0xBF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xFF,0x7E,0xFF,0xF5,0xF8,0x53,0xF8,0x31,0xF8,0x2F,0xF8,0x2D,0xF8,0x2C,0xF8,0x0A,0xF8,0x08,0xF8,0x27,0xF8,0x26,0xF8,0x47,0xFD,0xE7,0xFF,0xE4,0xFD,0x80,0xF8,0x60,0xF8,0x63,0xFB,0xE8,0xFF,0x06,0xFF,0x40,0xF9,0x40,0xF9,0x40,0xF9,0xC6,0xFE,0xE7,0xFF,0x24,0xFD,0xE0,0xFA,0xE0,0xFA,0x20,0xFB,0x80,0xFB,0x20,0xFC,0x00,0xFD,0x20,0xFD,0x60,0xFD, + 0x82,0xFD,0xC2,0xFD,0xE2,0xFD,0x22,0xFE,0x49,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xDF,0xFF,0xBF,0xF7,0x5F,0xD7,0x7F,0xDF,0x3F,0xD7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xFF,0x9F,0xEF,0xDF,0xBE,0xFF,0x64,0xFF,0xC6,0x7F,0xDF,0x7F,0xD7,0x9F,0xF7,0xDE,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x39,0xFC,0x74,0xF8,0x31,0xF8,0x10,0xF8,0x0E,0xF8,0x2C,0xF8,0x0A,0xF8,0x09,0xF8,0x07,0xF8,0x26,0xF8,0x65,0xFE,0xC5,0xFF,0x64,0xFE,0x60,0xF8,0x40,0xF8,0x04,0xFD, + 0xE6,0xFF,0x26,0xFF,0x40,0xF9,0x20,0xF9,0x41,0xFA,0xA6,0xFF,0xE7,0xFF,0xA3,0xFC,0xE0,0xFA,0xC0,0xFA,0x00,0xFB,0x40,0xFB,0x20,0xFC,0x00,0xFD,0x20,0xFD,0x40,0xFD,0x40,0xFD,0x80,0xFD,0xA0,0xFD,0x00,0xFE,0x47,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0xFB,0x40,0xFB,0x40,0xFB,0x40,0xFB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xBF,0xF7,0x9F,0xEF,0x5F,0xCF,0x7F,0xD7,0xBF,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBF,0xFF,0x7F,0xEF,0xBF,0xAE,0x3F,0x5D,0x3F,0x2C,0x7F,0x6B,0xBF,0xE7,0x7F,0xD7,0x5F,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF7, + 0xFF,0xFF,0xB5,0xF9,0x53,0xF8,0x31,0xF8,0x2F,0xF8,0x0D,0xF8,0x0B,0xF8,0x09,0xF8,0x08,0xF8,0x47,0xFA,0x45,0xFF,0xA5,0xFF,0x03,0xFD,0x60,0xF8,0xE0,0xF8,0x04,0xFF,0xC5,0xFF,0x24,0xFE,0x20,0xF9,0xE0,0xF8,0xA4,0xFD,0xA5,0xFF,0x45,0xFF,0x00,0xFA,0x20,0xFA,0xC0,0xFA,0x40,0xFB,0x80,0xFB,0xE0,0xFB,0x60,0xFC,0xA0,0xFC,0xC0,0xFC,0x20,0xFD,0x40,0xFD,0x80,0xFD,0xC0,0xFD,0x27,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0xFB,0x20,0xFB,0x00,0xFB,0x00,0xFB,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3F,0xEF,0xBF,0xFF,0x1F,0xC7,0x5F,0xCF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xBF,0xF7,0x5F,0xD7,0x3F,0x8E,0xDF,0x24,0xDF,0x1B,0xDF,0x22,0x9F,0x31,0xFF,0xEF,0x3F,0xCF,0xBF,0xFF,0x9F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1B,0xFE,0x74,0xF8,0x32,0xF8,0x30,0xF8,0x0E,0xF8,0x0B,0xF8,0x0A,0xF8,0x08,0xF8,0x64,0xFE,0x43,0xFF,0xE3,0xFE,0xA0,0xF8,0x40,0xF8,0x62,0xFD,0x63,0xFF,0x44,0xFF,0x41,0xFA,0x00,0xF9,0x61,0xFB,0x64,0xFF,0x84,0xFF,0x62,0xFC,0x80,0xF9,0xA0,0xF9,0xE0,0xFA,0xA0,0xFB,0xE0,0xFB,0xE0,0xFB,0xA0,0xFB,0x00,0xFC,0x60,0xFC,0x00,0xFD,0x20,0xFD,0x60,0xFD,0xA0,0xFD,0x07,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0xFB,0xE0,0xFA,0xE0,0xFA,0xC0,0xFA, + 0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xBF,0xFF,0xDF,0xB6,0x3F,0xCF,0x3F,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xFF,0xBF,0xF7,0x3F,0x5C,0x9F,0x23,0xDF,0x12,0x3F,0x12,0x1F,0x40,0x00,0xF8,0x3F,0xD7,0x9F,0xAE,0x7E,0xFF,0xBF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x55,0xF9,0x33,0xF8,0x31,0xF8,0x2F,0xF8,0x2C,0xF8,0x0A,0xF8,0x66,0xFD,0x22,0xFF,0x22,0xFF,0x41,0xFC,0x41,0xF8,0x21,0xFB,0x03,0xFF,0x23,0xFF,0x42,0xFD,0xC0,0xF8,0xE0,0xF8,0x42,0xFE,0x63,0xFF,0x82,0xFE,0x00,0xF9,0x60,0xF9,0x80,0xF9,0xE0,0xFA,0x80,0xFB,0xC0,0xFB,0x40,0xFC,0x60,0xFD,0xA0,0xFD,0xC0,0xFD, + 0x41,0xFE,0x81,0xFE,0xA2,0xFE,0xC1,0xFE,0x82,0xFE,0x63,0xFE,0x83,0xFE,0x23,0xFE,0x02,0xFA,0x02,0xFA,0xE2,0xF9,0x82,0xF9,0x46,0xFA,0x08,0xFB,0xE8,0xFA,0xE8,0xFA,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3F,0xE7,0x9F,0xF7,0x7F,0xD7,0xBF,0xB6,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9F,0xE7,0x3F,0xDF,0x3F,0xEF,0x5F,0xFF,0xBF,0x2A,0x1F,0x42,0x1F,0xF8,0x1F,0x50,0x5F,0xAD,0x3F,0xCF,0xBF,0xCE,0xBF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x17,0xFB,0x53,0xF8,0x51,0xF8,0x2E,0xF8,0x2A,0xF8,0x27,0xF9,0x02,0xFF,0x01,0xFF,0x41,0xFE,0x21,0xF8,0x40,0xF8,0xC1,0xFD,0x02,0xFF,0x82,0xFE, + 0xA0,0xF8,0xE0,0xF8,0x00,0xFB,0x02,0xFF,0x22,0xFF,0x21,0xFC,0xE0,0xF9,0x20,0xFA,0x40,0xFA,0x80,0xFA,0xC0,0xFA,0x00,0xFB,0x60,0xFC,0xC0,0xFE,0xC0,0xFE,0xE0,0xFE,0x61,0xFF,0xE2,0xFF,0xE2,0xFF,0xE2,0xFF,0x81,0xFE,0x40,0xFE,0x40,0xFE,0xE0,0xFD,0x00,0xFA,0x00,0xFA,0xE0,0xF9,0x80,0xF9,0x06,0xFA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xBE,0xFF,0xDF,0xF7,0xFF,0xBE,0x7F,0xCF,0x9F,0xC6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xBF,0xF7,0x7F,0xDF,0xFF,0x64,0x3F,0x11,0x9F,0xE6,0xFD,0xFE,0x1F,0xF8,0x1F,0x30,0x1F,0x18,0x1F,0x18,0x7F,0xDF,0x9F,0xB6,0x9F,0xF7,0xFF,0xFF,0xFF,0xFF, + 0xDF,0xFF,0xFF,0xFF,0x7E,0xFF,0x74,0xF8,0x52,0xF8,0x30,0xF8,0x2C,0xF8,0x66,0xFD,0xE1,0xFE,0xE1,0xFE,0x40,0xFC,0x80,0xF8,0xA0,0xF8,0x61,0xFE,0xC1,0xFE,0xC1,0xFD,0xC0,0xF8,0xE0,0xF8,0xE0,0xFB,0xC1,0xFE,0xE1,0xFE,0x60,0xFA,0x40,0xFA,0x80,0xFA,0xA0,0xFA,0x60,0xFA,0x60,0xFA,0x80,0xFA,0x40,0xFC,0xC0,0xFE,0xC0,0xFE,0xC0,0xFE,0x61,0xFF,0xC2,0xFF,0xC2,0xFF,0xC2,0xFF,0x61,0xFE,0x20,0xFE,0x40,0xFE,0xC0,0xFD,0x00,0xFA,0x00,0xFA,0xC0,0xF9,0x80,0xF9,0x06,0xFA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xDE,0xFF,0x7F,0xF7,0x9F,0xF7,0x1F,0xCF,0x3F,0xCF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBF,0xF7, + 0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0x1F,0x63,0x1F,0x30,0x1F,0x08,0xBF,0xC5,0x7F,0x93,0x1F,0x10,0x1F,0x08,0x5F,0x08,0x3F,0xCF,0x9F,0xDF,0xBF,0xFF,0x3F,0xDF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD6,0xF8,0x53,0xF8,0x31,0xF8,0x2F,0xF8,0xE6,0xFD,0xC2,0xFE,0xA1,0xFE,0x21,0xFC,0x60,0xF8,0xA0,0xF8,0x21,0xFE,0xA1,0xFE,0xE1,0xFD,0xC0,0xF8,0xE0,0xF8,0x20,0xFB,0xA1,0xFE,0xA1,0xFE,0xC0,0xFC,0x20,0xFA,0x60,0xFA,0x80,0xFA,0x60,0xFA,0x40,0xFA,0x80,0xFA,0x00,0xFC,0x40,0xFE,0x40,0xFE,0x40,0xFE,0xC1,0xFE,0x22,0xFF,0x02,0xFF,0x22,0xFF,0x21,0xFE,0xE0,0xFD,0x00,0xFE,0x80,0xFD,0xE0,0xF9,0xC0,0xF9,0xA0,0xF9,0x60,0xF9,0x06,0xFA,0x00,0x00,0x00,0x00,0x00,0x00, + 0x5D,0xFC,0x00,0x00,0xFF,0xFF,0xDE,0xFF,0x1F,0x74,0xBF,0xF7,0x5F,0xD7,0xDF,0xBE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xF7,0xFF,0x21,0x5F,0x11,0x7F,0x08,0x7F,0x29,0xBF,0x94,0x7F,0x08,0x9F,0x10,0x1F,0x11,0x5F,0x4A,0xBF,0xDF,0x9F,0xF7,0x5F,0xEF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0x57,0xF9,0x54,0xF8,0x53,0xF8,0x50,0xF8,0x49,0xFC,0xA1,0xFE,0x81,0xFE,0xA1,0xFD,0x40,0xF8,0x41,0xF8,0x61,0xFD,0x60,0xFE,0x41,0xFE,0x40,0xFB,0xE0,0xF8,0x00,0xF9,0x01,0xFE,0x61,0xFE,0x00,0xFE,0xA0,0xFA,0x80,0xF9,0x80,0xF9,0xA0,0xF9,0xC0,0xFA,0x00,0xFB,0x20,0xFB,0xE0,0xFB,0xA0,0xFC,0xC0,0xFC, + 0x00,0xFD,0x20,0xFD,0x40,0xFD,0x60,0xFD,0xA0,0xFD,0xC0,0xFD,0xC1,0xFD,0x44,0xFD,0x04,0xFA,0xE4,0xF9,0xE4,0xF9,0xC5,0xF9,0x4C,0xFB,0x00,0x00,0x00,0x00,0x00,0x00,0x3D,0xFC,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x3F,0xBE,0xBF,0xF7,0x1F,0xCF,0xDF,0x8D,0x9F,0xB6,0x7F,0x9E,0x5F,0x8E,0x9F,0x6D,0xBF,0xF7,0xBF,0x9E,0xBF,0xAE,0x3F,0xE7,0xBF,0xF7,0xBF,0xF7,0xFF,0x08,0x9F,0x09,0xDF,0x09,0x1F,0x11,0x5F,0x09,0xBF,0x63,0x1F,0x11,0x7F,0x11,0xDF,0x19,0x9F,0x19,0xBF,0xDF,0x5F,0xE7,0xBF,0xF7,0xFE,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0x78,0xF9,0x75,0xF8,0x54,0xF8,0x52,0xF8,0x4F,0xF8,0x23,0xFE,0x20,0xFE,0x00,0xFE,0x41,0xFC,0x01,0xF8,0xE1,0xF9,0x01,0xFE,0x20,0xFE, + 0xC0,0xFD,0x20,0xFA,0x00,0xF9,0x81,0xFB,0x21,0xFE,0x00,0xFE,0x81,0xFD,0x80,0xF9,0x20,0xF9,0x40,0xF9,0xC0,0xFA,0x20,0xFB,0x20,0xFB,0xA0,0xFB,0x60,0xFC,0xA0,0xFC,0xE0,0xFC,0x00,0xFD,0x40,0xFD,0x60,0xFD,0x80,0xFD,0xA0,0xFD,0xC1,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC9,0xFB,0x62,0xFB,0x61,0xFB,0x44,0xFB,0x00,0x00,0xFD,0xFB,0x00,0x00,0xFF,0xFF,0xDE,0xFF,0x5F,0xBE,0x9F,0xFF,0x3F,0xCF,0xBF,0x85,0xBF,0x65,0x1F,0x66,0xDF,0x44,0x9F,0x2B,0x9F,0xEF,0x9F,0x12,0x1F,0x00,0x7F,0x62,0x1F,0x20,0x5F,0x10,0x5F,0x11,0x5F,0x12,0x5F,0x12,0x3F,0x1A,0x7F,0x1A,0xBF,0x53,0x3F,0x1A,0x5F,0x22,0x5F,0x2A,0xDF,0x21,0xBF,0xE7,0x5F,0xDF,0x9F,0xF7,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7A,0xF9,0x97,0xF8,0x75,0xF8,0x53,0xF8,0x51,0xF8,0x4E,0xFA,0xA2,0xFD,0x80,0xFD,0x40,0xFD,0xE1,0xF9,0x00,0xF8,0xA2,0xFB,0xA0,0xFD,0xC0,0xFD,0xE0,0xFC,0x00,0xF9,0x01,0xF8,0x21,0xFC,0xA0,0xFD,0xA0,0xFD,0x60,0xFC,0x20,0xF9,0x40,0xF9,0x60,0xFA,0xA0,0xFA,0xA0,0xFA,0xE0,0xFA,0x60,0xFB,0xC0,0xFB,0x40,0xFC,0x80,0xFC,0xA0,0xFC,0xC0,0xFC,0xE0,0xFC,0xE0,0xFC,0x20,0xFD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0xFB,0x20,0xFB,0x20,0xFB,0x43,0xFB,0x00,0x00,0x7C,0xFB,0x00,0x00,0xFF,0xFF,0xDE,0xFF,0x1F,0x53,0x5F,0xF7,0xFF,0xBE,0x9F,0xA6,0x5F,0x34,0x7F,0x24,0x1F,0x23,0x9F,0x19,0x3D,0xFF,0x1F,0x00,0x1F,0x00,0x1F,0x40, + 0x1F,0x10,0xFF,0x10,0x5F,0x12,0xDF,0x12,0x7F,0x12,0xDF,0x1A,0x1F,0x2B,0xDF,0x53,0xFF,0x2A,0xFF,0x2A,0xFF,0x32,0x7F,0x22,0xBF,0xE7,0x9F,0xEF,0xBF,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7B,0xF9,0x98,0xF8,0x76,0xF8,0x75,0xF8,0x53,0xF8,0x51,0xF8,0x4B,0xFB,0x00,0xFD,0x20,0xFD,0x81,0xFC,0x02,0xF8,0x03,0xF8,0x81,0xFC,0x60,0xFD,0x60,0xFD,0x61,0xFB,0x01,0xF8,0x01,0xF8,0xA0,0xFC,0x60,0xFD,0x40,0xFD,0x60,0xF9,0x20,0xF9,0x40,0xF9,0x60,0xF9,0xA0,0xF9,0xE0,0xF9,0x20,0xFA,0xC0,0xFA,0xC0,0xFB,0x00,0xFC,0x40,0xFC,0x60,0xFC,0x80,0xFC,0x80,0xFC,0xA0,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0xFB,0x01,0xFB,0x01,0xFB,0x24,0xFB,0x00,0x00, + 0x9C,0xFB,0x00,0x00,0xFF,0xFF,0xDE,0xFF,0xDF,0xDE,0x5F,0xEF,0xFF,0xBE,0x3F,0xC7,0x3F,0x33,0x5F,0x22,0x1F,0x41,0x1F,0xF8,0x5B,0xFE,0xFF,0xFD,0x1F,0x00,0x1F,0xF8,0xBF,0x10,0xDF,0x11,0x9F,0x12,0xDF,0x12,0xDF,0x22,0x1F,0x23,0xFF,0x4B,0x7F,0x43,0x5F,0x33,0x5F,0x3B,0x3F,0x3B,0x1F,0xCF,0xBF,0xE7,0xDF,0xFF,0xFF,0xCE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xF9,0xDA,0xF8,0xB8,0xF8,0x76,0xF8,0x74,0xF8,0x51,0xF8,0x2F,0xF8,0x83,0xFC,0xE0,0xFC,0xA0,0xFC,0x03,0xF8,0x03,0xF8,0x43,0xF8,0x00,0xFD,0x00,0xFD,0x60,0xFC,0x01,0xF8,0x01,0xF8,0x61,0xFA,0x20,0xFD,0x20,0xFD,0xA0,0xFA,0x00,0xF9,0x20,0xF9,0x60,0xF9,0x80,0xF9,0xC0,0xF9,0x00,0xFA,0xA0,0xFA, + 0xA0,0xFB,0xE0,0xFB,0x20,0xFC,0x40,0xFC,0x60,0xFC,0x80,0xFC,0x80,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x9C,0xFB,0x00,0x00,0xFF,0xFF,0xDE,0xFF,0xFF,0xFF,0x7F,0xEF,0x5F,0xA6,0x3F,0xC7,0xBF,0x1A,0xBF,0xAA,0x00,0xF8,0x00,0xF8,0x1F,0xF8,0xBE,0xFE,0x1F,0x6A,0x7F,0x08,0xFF,0x28,0x9F,0x21,0x1F,0x22,0xFF,0x2A,0x7F,0x2B,0xFF,0x43,0x3F,0x54,0x9F,0x43,0x9F,0x43,0x7F,0x43,0x1F,0x43,0x9F,0xE7,0x7F,0xDF,0xBF,0xFF,0xBF,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9F,0xFD,0x1E,0xF9,0xDC,0xF8,0xBA,0xF8,0x97,0xF8,0x75,0xF8,0x73,0xF8,0x70,0xF8,0x06,0xFC,0xA0,0xFC,0x60,0xFC,0x03,0xF8,0x03,0xF8,0x03,0xF8, + 0x80,0xFC,0xC0,0xFC,0x20,0xFC,0x01,0xF8,0x01,0xF8,0x81,0xF8,0xE0,0xFC,0xE0,0xFC,0x80,0xFA,0xE0,0xF8,0x00,0xF9,0x40,0xF9,0xC0,0xF9,0x00,0xFB,0x20,0xFB,0x40,0xFB,0x80,0xFB,0xA0,0xFB,0xE1,0xFB,0x43,0xFC,0x63,0xFC,0x63,0xFC,0x44,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0xFB,0x1C,0xFC,0xDF,0xFF,0xFF,0xFF,0xDF,0xFF,0x5F,0xE7,0xFF,0xE6,0x1F,0xBF,0xDF,0x8D,0xBF,0x52,0x00,0xF8,0x1F,0xF8,0x1F,0x20,0xDF,0x51,0xFF,0xEC,0x3F,0xAB,0x9F,0x39,0x5F,0x32,0xDF,0x32,0x3F,0x3B,0x3F,0x64,0x5F,0x64,0xBF,0x53,0xDF,0x53,0x9F,0x53,0x7F,0x53,0x5F,0x3B,0xDF,0xEF,0x3F,0xCF,0x9F,0xF7,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xEA,0x3F,0xF9,0x1D,0xF9,0xFB,0xF8,0xB9,0xF8,0x97,0xF8,0x94,0xF8,0x71,0xF8,0xA6,0xFB,0x40,0xFC,0x00,0xFC,0x04,0xF8,0x05,0xF8,0x04,0xF8,0x60,0xFC,0x80,0xFC,0xA0,0xFB,0x01,0xF8,0x01,0xF8,0x61,0xF9,0xA0,0xFC,0x80,0xFC,0xE0,0xF8,0x20,0xF9,0x80,0xF9,0xC0,0xF9,0x20,0xFA,0x20,0xFB,0x40,0xFB,0x40,0xFB,0x60,0xFB,0x80,0xFB,0xC2,0xFB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA2,0xF8,0x60,0xF8,0x60,0xF8,0x60,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDC,0xFB,0xDC,0xFB,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0x5F,0xF7,0x9F,0xFF,0x7F,0xA6,0xFF,0xBE,0x9F,0x1B,0x1F,0x80,0x1F,0x48,0x1F,0x68,0x7F,0x90,0x7F,0x88,0x1F,0xBB, + 0xFF,0xAB,0xDF,0x93,0x1F,0x84,0x3F,0x84,0x9F,0x6B,0xBF,0x73,0x1F,0x7C,0x3F,0x84,0xDF,0x7B,0x3F,0x63,0xDF,0xEF,0xBF,0xEF,0xBF,0xF7,0xBF,0xCE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3F,0xDA,0x7F,0xF9,0x3E,0xF9,0x1D,0xF9,0xDB,0xF8,0xB9,0xF8,0x96,0xF8,0x73,0xF8,0xA4,0xFB,0x00,0xFC,0x82,0xFB,0x06,0xF8,0x06,0xF8,0x25,0xF9,0x20,0xFC,0x40,0xFC,0x81,0xF9,0x01,0xF8,0x01,0xF8,0x01,0xFB,0x60,0xFC,0xE0,0xFB,0x80,0xF8,0x00,0xFA,0xA0,0xFA,0xC0,0xFA,0xA0,0xFA,0xE0,0xFA,0x00,0xFB,0x00,0xFB,0x20,0xFB,0x20,0xFB,0x42,0xFB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xF8,0x20,0xF8,0x20,0xF8,0x40,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x7E,0xFC,0xBD,0xFB,0x7F,0xFE,0xFF,0xFF,0xFF,0xFF,0xDE,0xFF,0xFF,0xE6,0x7F,0xF7,0xFF,0xBE,0x1F,0xBF,0x1F,0x23,0xBF,0xD8,0x7F,0xF0,0xBF,0xE8,0x5F,0xF1,0x7F,0xF1,0x1F,0xE2,0x1F,0xD2,0x7F,0xBA,0xDF,0xB2,0x3F,0xA3,0x9F,0xA3,0x3F,0xCE,0xDF,0xFF,0xBF,0x9C,0xBF,0xEF,0xFF,0xF7,0x5F,0xDF,0xBF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0xCB,0x1F,0xDA,0xBF,0xF1,0x7F,0xF9,0x3E,0xF9,0xFC,0xF8,0xDA,0xF8,0xD8,0xF8,0xB1,0xF9,0xC0,0xFB,0xC0,0xFB,0xA6,0xF9,0x08,0xF8,0x07,0xF8,0x23,0xFB,0xE0,0xFB,0x60,0xFB,0x02,0xF8,0x01,0xF8,0x01,0xF8,0x80,0xFB,0xE0,0xFB,0xE0,0xF9,0x60,0xF8,0x00,0xFA,0xA0,0xFA,0xC0,0xFA,0x60,0xFA,0x80,0xF9,0x80,0xF9,0x80,0xF9, + 0x80,0xF9,0x60,0xF9,0x41,0xF9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x61,0xF8,0x00,0xF8,0x00,0xF8,0x20,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBE,0xFB,0xFE,0xFB,0xFF,0xFF,0xDF,0xFF,0xDE,0xFF,0xDF,0xFF,0x7F,0xF7,0xBF,0xCE,0x1F,0xC7,0xBF,0xB6,0xBF,0x3A,0x3E,0xF9,0xBB,0xF8,0x1A,0xF9,0x3B,0xF9,0xBC,0xF9,0xFE,0xF9,0x5F,0xF2,0xBF,0xEA,0xFF,0xD2,0x3F,0xCB,0xFF,0xCC,0x5F,0xEF,0x9F,0xE7,0xFF,0xFF,0x9F,0xE7,0xDF,0xFF,0x3F,0xE7,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xFF,0x9F,0xBA,0x3F,0xD2,0xFF,0xE1,0x7F,0xF1,0x5F,0xF9,0x3E,0xF9,0x1C,0xF9,0xD9,0xF8,0x47,0xFB,0xA0,0xFB,0xC6,0xFA,0x2A,0xF8,0x09,0xF8,0x27,0xF9,0x80,0xFB, + 0x60,0xFB,0x82,0xF8,0x02,0xF8,0x02,0xF8,0x20,0xFA,0x40,0xFB,0x60,0xFA,0x41,0xF8,0x41,0xF8,0xC0,0xF9,0x60,0xFA,0x80,0xFA,0x40,0xFA,0x00,0xFA,0x00,0xFA,0x60,0xF9,0x60,0xF9,0x40,0xF9,0x62,0xF9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE7,0xF9,0xA6,0xF9,0xA6,0xF9,0xE7,0xF9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xFC,0xBF,0xFB,0x3F,0xFD,0xDF,0xFF,0xDF,0xFF,0xDE,0xFF,0x7F,0xE7,0xBF,0xFF,0xFF,0xD6,0x1F,0xC7,0xFF,0xC6,0x3F,0x5D,0x3F,0xBA,0x39,0xF9,0xF8,0xF8,0x19,0xF9,0x7B,0xF9,0xDD,0xF9,0x5E,0xFA,0xBF,0xEA,0xDF,0xDA,0x5F,0x73,0xDF,0xF7,0xFF,0xF7,0x9F,0xE7,0xDF,0xFF,0x5F,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xA2,0x7F,0xB2,0x3F,0xC2,0xFF,0xD9,0xDF,0xE9,0x7F,0xF9,0x5F,0xF9,0x3D,0xF9,0x58,0xF9,0x09,0xFB,0xC9,0xFA,0x2E,0xF8,0x0C,0xF8,0x0A,0xF8,0x67,0xF9,0x84,0xFA,0x44,0xF9,0x03,0xF8,0x02,0xF8,0x02,0xF8,0x01,0xF8,0x61,0xF8,0x21,0xF8,0x42,0xF8,0x42,0xF8,0x41,0xF9,0xE0,0xF9,0x40,0xFA,0x80,0xFA,0xC0,0xFA,0x00,0xFB,0x00,0x00,0x00,0x00,0x00,0x00,0xAA,0xFA,0x08,0xFA,0xE7,0xF9,0x69,0xFA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBF,0xF3,0xBF,0xF3,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xFF,0x5F,0xE7,0xBF,0xFF,0xBF,0xD6,0xBF,0xBE,0x1F,0xCF,0xFF,0xC6,0xBF,0x8D,0x5F,0x1B, + 0x9F,0xAA,0x1F,0xFA,0x7F,0xEA,0xFF,0x42,0xDF,0xBE,0xFF,0xF7,0xFF,0xF7,0xDF,0xF7,0xBF,0xE7,0xDF,0xF7,0x7F,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1F,0x9C,0xBF,0x9A,0x7F,0xAA,0x3F,0xBA,0x1F,0xD2,0xFF,0xE9,0xBF,0xF1,0x7F,0xF9,0x5E,0xF9,0x1B,0xF9,0xB7,0xF8,0x53,0xF8,0x30,0xF8,0x0D,0xF8,0x0B,0xF8,0x09,0xF8,0x07,0xF8,0x05,0xF8,0x04,0xF8,0x03,0xF8,0x03,0xF8,0x02,0xF8,0x22,0xF8,0x21,0xF8,0x22,0xF8,0x22,0xF8,0x40,0xF9,0xC0,0xF9,0x00,0xFA,0x60,0xFA,0xA0,0xFA,0xC0,0xFA,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0xF8,0x80,0xF8,0x80,0xF8,0xE3,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x7F,0xF5,0x7F,0xEB,0x7F,0xE3,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xFF,0xBF,0xF7,0x1F,0xEF,0x1F,0xEF,0x7F,0xBE,0xBF,0xBE,0x3F,0xD7,0x7F,0xDF,0x7F,0xE7,0xDF,0xEF,0xDF,0xF7,0xFF,0xF7,0xFF,0xF7,0xFF,0xF7,0xBF,0xEF,0xDF,0xFF,0xFF,0xFF,0xFF,0xD6,0xFF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0x8C,0x1F,0x7B,0xDF,0x92,0x7F,0xA2,0x5F,0xB2,0x3F,0xD2,0xFF,0xE9,0xDF,0xF9,0x9F,0xF9,0x7E,0xF9,0x1C,0xF9,0xD8,0xF8,0x74,0xF8,0x30,0xF8,0x0D,0xF8,0x0B,0xF8,0x09,0xF8,0x08,0xF8,0x06,0xF8,0x05,0xF8,0x04,0xF8,0x23,0xF8,0x80,0xF8,0x80,0xF8,0x40,0xF8,0x21,0xF8,0x40,0xF8,0x60,0xF8,0x80,0xF8,0xC0,0xF8,0x21,0xF9,0x07,0xFB,0x28,0xFB,0x00,0x00, + 0x00,0x00,0x00,0x00,0x60,0xF8,0x60,0xF8,0x60,0xF8,0xE3,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xEC,0x5F,0xE3,0x7F,0xDB,0xFF,0xFF,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3F,0x8D,0x1F,0xEF,0x5F,0xEF,0x1F,0xEF,0xFF,0xCE,0x3F,0xCF,0x7F,0xE7,0x9F,0xE7,0x9F,0xE7,0x9F,0xF7,0xBF,0xFF,0xDF,0xFF,0x7F,0xE7,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0x7C,0x5F,0x5B,0x3F,0x73,0xFF,0x8A,0x9F,0x9A,0x5F,0xB2,0x3F,0xCA,0x1F,0xE2,0x1F,0xFA,0xDF,0xF9,0x7E,0xF9,0x3B,0xF9,0xF8,0xF8,0xB5,0xF8,0x51,0xF8,0x2D,0xF8,0x0B,0xF8,0x09,0xF8,0x08,0xF8, + 0x06,0xF8,0x05,0xF8,0x04,0xF8,0x23,0xF8,0x80,0xF8,0xA0,0xF8,0x40,0xF8,0x20,0xF8,0x40,0xF8,0x60,0xF8,0x80,0xF8,0xA0,0xF8,0x23,0xF9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0xF8,0x20,0xF8,0x20,0xF8,0xC2,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5F,0xDC,0x1F,0xCB,0x3F,0xC3,0xBF,0xEE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,0x5F,0xEF,0x5F,0xB6,0x5F,0xEF,0xBF,0xFF,0xBF,0xFF,0xDF,0xFF,0xDF,0xFF,0x9F,0xE7,0xBF,0x95,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBF,0x5B,0x7F,0x4B,0x7F,0x53,0x5F,0x6B, + 0x3F,0x7B,0xFF,0x9A,0x5F,0xAB,0x5F,0xC4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5F,0xFC,0xDA,0xFA,0xB6,0xF9,0xF2,0xF8,0x2D,0xF8,0x0B,0xF8,0x09,0xF8,0x08,0xF8,0x07,0xF8,0x06,0xF8,0x05,0xF8,0x24,0xF8,0x61,0xF8,0x81,0xF8,0x21,0xF8,0x20,0xF8,0x20,0xF8,0x60,0xF8,0x60,0xF8,0x60,0xF8,0x4C,0xFB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8E,0xFB,0x6D,0xFB,0xCF,0xFB,0x30,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDF,0xD4,0x1F,0xBB,0x1F,0xB3,0x1F,0xB4,0xFF,0xFF,0xDF,0xFF,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xDE,0xFF,0xFF,0xFF,0x9F,0x94,0x1F,0x5B,0x3F,0x4B,0x9F,0x5B,0xDF,0x8C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xCE,0xF9,0xEA,0xF8,0x48,0xF8,0x06,0xF8,0x05,0xF8,0x04,0xF8,0x03,0xF8,0x02,0xF8,0x02,0xF8,0x01,0xF8,0x00,0xF8,0x20,0xF8,0x60,0xF8,0xA1,0xF8,0x8A,0xFA,0x00,0x00,0xE8,0xFA,0x63,0xFA,0x63,0xFA,0x09,0xFB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5F,0xAB,0xFF,0xA2,0x1F,0xA3,0x7F,0xAC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x5F,0xAD,0xDF,0x7B,0x1F,0xE7,0xFF,0xFF,0x5F,0xEF,0x1F,0xA5,0x1F,0x94,0x9F,0x9B,0x5F,0xAB,0x5F,0xBB,0x3F,0xC3,0x9F,0xD3,0x1F,0xE4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6F,0xFB,0xA8,0xF9,0xE6,0xF8,0xA4,0xF8,0x63,0xF8,0x63,0xF8,0x62,0xF8,0xA3,0xF8,0x04,0xF9,0x49,0xFA,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0xFA,0x20,0xFA,0x40,0xFA, + 0x85,0xFA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xBC,0x3F,0x9B,0xDF,0x82,0x1F,0x73,0x9F,0x73,0xDF,0x94,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBF,0xCE,0x5F,0x6C,0xDF,0x4B,0x9F,0x43,0x7F,0x4B,0x7F,0x63,0x5F,0x6B,0x3F,0x73,0x3F,0x83,0x1F,0x93,0xDF,0xA2,0x9F,0xB2,0x5F,0xC2,0x3F,0xD2,0x3F,0xE2,0x3F,0xEA,0x7F,0xFA,0x7D,0xFA,0x3B,0xFB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0xFA,0x20,0xFA,0x20,0xFA,0x65,0xFA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDF,0xAC,0x5F,0x7B,0x3F,0x63,0x5F,0x53,0x5F,0x4B,0x5F,0x43,0x7F,0x3B,0x7F,0x3B,0x9F,0x33,0x9F,0x33,0x9F,0x2B,0x9F,0x2B,0x9F,0x2B,0x7F,0x2B,0x9F,0x2B,0x9F,0x33,0x9F,0x3B,0x7F,0x4B,0x7F,0x5B,0x7F,0x6B,0x5F,0x73,0x3F,0x83, + 0x1F,0x93,0xFF,0xA2,0xDF,0xB2,0x9F,0xC2,0x9F,0xD2,0x7F,0xE2,0x5F,0xF2,0x3E,0xFA,0xBB,0xF9,0x78,0xF9,0x56,0xF9,0xF4,0xF9,0x56,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xCD,0xFB,0xEE,0xFB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBF,0x8C,0xDF,0x5B,0x7F,0x4B, + 0x7F,0x3B,0x7F,0x33,0x9F,0x2B,0x9F,0x2B,0x7F,0x2B,0x9F,0x2B,0x9F,0x2B,0x9F,0x33,0xBF,0x3B,0xDF,0x43,0x1F,0x54,0x5F,0x6C,0x3F,0x9D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x52,0xFB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + //alpha channel data: + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0xAC,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x9A,0xEF,0xFD,0x75,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0xD9,0xFE,0xFE,0xFE,0x53,0x00,0x00,0x00,0x00,0x6C,0xA9,0xAA,0xAB,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x9C,0xF7,0xFE,0xFE,0xFE,0xFD,0x48,0x00,0x00,0x00,0x00,0xA2,0xFE,0xFE,0xFD,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0xC6,0xFE,0xFD,0xFD,0xFE,0xFE,0xFD,0x6D,0x00,0x00,0x00,0x00,0xA1,0xFE,0xFE,0xFD,0x3C,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0xE4,0xFD,0xFE,0xFD, + 0xFE,0xFE,0xFE,0xFE,0xA3,0x00,0x00,0x00,0x00,0x8A,0xD6,0xD5,0xD4,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x93,0xF5,0xFE,0xFC,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0xEA,0x00,0x37,0x6E,0xAA,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4E,0xC8,0xFA,0xFD,0xFD,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xF9,0xFC,0xFE,0xFE,0x5D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6A,0xD7,0xD9,0xA4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x39,0xA2,0xEC,0xFA,0xFB,0xFC,0xFD,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0x4C,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x7D,0xFE,0xFE,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0xA9,0xDA,0xE9,0xF2,0xF8,0xFA,0xFB,0xFD,0xFD,0xFD,0xFE, + 0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0x44,0x00,0x00,0x55,0x87,0x88,0x8B,0x43,0x6F,0xE5,0xE3,0xA8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x62,0x67,0x53,0x41,0x37,0x37,0x42,0x53, + 0x66,0x80,0xA0,0xC1,0xD1,0xD8,0xDE,0xE5,0xEE,0xF6,0xF8,0xFA,0xFC,0xFD,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xD0,0xCE,0xD0,0xE4,0xFE,0xFE,0xFE,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x9E,0xBB, + 0xC8,0xCE,0xCC,0xC1,0xA6,0x79,0x00,0x00,0x00,0x60,0x8D,0xA1,0x96,0x90,0x98,0xA1,0xAA,0xB3,0xBE,0xC7,0xCE,0xD3,0xDA,0xE1,0xE9,0xF1,0xF7,0xF8,0xFB,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFD,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xAF,0xE9,0xFC,0xFB,0xF4,0xEA,0xE8,0xEA,0xF1,0xF6,0xF7,0xEC,0xBD,0x5F,0x1E,0x1E,0x53,0x85,0x8C,0x97,0xA1,0xAB,0xB2,0xBB,0xC2,0xC8,0xCD,0xD5,0xDD,0xE4,0xED,0xF3,0xF7,0xFB,0xFD,0xFE,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE, + 0x71,0x00,0x00,0x00,0x7F,0xBE,0xBE,0xBF,0x3A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x93,0xE8,0xF6,0xDB,0xA8,0x69,0x2D,0x25,0x2D,0x26,0x29,0x5E,0x99,0xCE,0xEE,0xEB,0xB2,0x3C,0x1E,0x26,0x6C,0x91,0x9C,0xA7,0xAF,0xB7,0xBD,0xC3,0xCA,0xD0,0xD7,0xDE,0xE8,0xF0,0xF5,0xFA,0xFC,0xFE,0xFD, + 0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFD,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0x6F,0x00,0x00,0x00,0xA9,0xFE,0xFE,0xFD,0x45,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6B,0xD8,0xF2,0xC4,0x70,0x31,0x4C,0x4D,0x4F,0x49,0x48,0x45,0x4B,0x4D,0x48,0x27,0x69,0xC2,0xE4,0xD9,0x71,0x1E,0x1E,0x64,0x97, + 0xA3,0xAC,0xB3,0xB9,0xBF,0xC6,0xCC,0xD2,0xDA,0xE3,0xED,0xF4,0xFA,0xFD,0xFE,0xFD,0xFC,0xFC,0xFD,0xFD,0xFE,0xFD,0xF6,0xF2,0xFB,0xFD,0xFE,0xFE,0xFE,0xFE,0xFD,0xFE,0x9D,0x00,0x00,0x00,0xA9,0xFE,0xFE,0xFD,0x47,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x9A,0xEF,0xE6,0x89,0x2C,0x4E,0x4B,0x2E,0x3F,0x4A, + 0x4F,0x4C,0x48,0x40,0x31,0x21,0x34,0x42,0x28,0x86,0xDD,0xE2,0x84,0x1E,0x1E,0x6E,0x9E,0xA8,0xAE,0xB4,0xBA,0xC0,0xC5,0xCC,0xD3,0xDC,0xEC,0xFC,0xEE,0xFA,0xFD,0xFD,0xFE,0xEC,0xF5,0xFC,0xFD,0xFD,0xFD,0xDC,0xF3,0xFC,0xFE,0xFE,0xFE,0xFD,0xFD,0xFD,0xA9,0x00,0x00,0x00,0x87,0xC9,0xC9,0xC7,0x35,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x99,0xEF,0xD8,0x5B,0x42,0x4B,0x34,0x52,0x58,0x41,0x2C,0x29,0x27,0x1E,0x1E,0x26,0x31,0x31,0x21,0x38,0x39,0x61,0xD3,0xDC,0x7A,0x1E,0x20,0x80,0xA1,0xA9,0xAD,0xB3,0xB9,0xBF,0xC5,0xCD,0xD4,0xE5,0xFE,0xE4,0xE8,0xFA,0xFB,0xFE,0xEF,0xDF,0xFA,0xFD,0xFD,0xFE,0xE1,0xEA,0xFC,0xFC,0xFD,0xFD,0xFC,0xFD,0xFC, + 0xE0,0xCA,0xCB,0xCC,0x5D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0xE7,0xD7,0x4A,0x4B,0x3C,0x42,0x52,0x35,0x7B,0xBF,0xAA,0x95,0x6D,0x49,0x35,0x22,0x10,0x16,0x2E,0x28,0x29,0x3B,0x55,0xD2,0xD6,0x56,0x1E,0x35,0x96,0xA3,0xA8,0xAD,0xB3,0xB9,0xC0,0xC7,0xCE,0xEB,0xFE,0xE4,0xD7,0xF5,0xF7, + 0xFE,0xEE,0xD4,0xF6,0xFC,0xFE,0xFE,0xDE,0xE8,0xFC,0xFD,0xFD,0xFD,0xFE,0xFE,0xFD,0xFE,0xFE,0xFE,0xFB,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA0,0xF0,0xF0,0xE6,0x00,0x00,0x00,0x00,0xD1,0xE0,0x66,0x3C,0x39,0x3F,0x44,0x4B,0xD1,0xFE,0xFF,0xE6,0x95,0x66,0x43,0x2F,0x20,0x17,0x11,0x07,0x20,0x2C,0x24,0x3B,0x68,0xD4,0xB8,0x1F, + 0x1E,0x67,0x9B,0xA0,0xA7,0xAD,0xB4,0xBA,0xC1,0xCB,0xFA,0xFD,0xD1,0xD3,0xF0,0xFA,0xFD,0xDC,0xD3,0xF5,0xFB,0xFE,0xF5,0xDA,0xF0,0xFB,0xFD,0xFD,0xFD,0xFE,0xFE,0xFD,0xFE,0xFD,0xFD,0xFC,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA8,0xFE,0xFE,0xF0,0x00,0x00,0x00,0x8F,0xE9,0x9F,0x26,0x3C,0x36,0x3D,0x45,0xE9,0xFF,0xFF,0xFF,0xF1, + 0x8F,0x5A,0x38,0x26,0x1A,0x14,0x11,0x0E,0x05,0x1C,0x28,0x2E,0x2C,0x9E,0xD2,0x7B,0x1E,0x26,0x8C,0x99,0xA0,0xA6,0xAD,0xB3,0xB8,0xE9,0xFE,0xED,0xC1,0xDF,0xF6,0xFE,0xF7,0xC3,0xDE,0xF8,0xFD,0xFD,0xE5,0xE6,0xF9,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFD,0xFD,0xFC,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xAA,0xFE,0xFE,0xF0, + 0x00,0x00,0x00,0xD2,0xD7,0x35,0x43,0x27,0x3D,0x27,0xB3,0xFD,0xFE,0xFE,0xFE,0xD1,0x9F,0x81,0x5A,0x37,0x11,0x10,0x0E,0x0B,0x04,0x01,0x20,0x21,0x35,0x3C,0xC4,0xB5,0x1E,0x1E,0x6A,0x91,0x98,0xA0,0xA4,0xA3,0xC4,0xFE,0xFB,0xB0,0xC1,0xEA,0xFE,0xFE,0xCC,0xC5,0xEC,0xFB,0xFE,0xE8,0xE0,0xF2,0xFD,0xFD,0xFE,0xFE,0xFD,0xFD,0xFD,0xFE, + 0xFE,0xFD,0xFD,0xFC,0xC4,0xB9,0xBA,0xBB,0xC1,0xC3,0xC5,0xC8,0x78,0x6C,0x6A,0x65,0x00,0x00,0x6C,0xE3,0xA1,0x28,0x33,0x38,0x24,0x7E,0xB4,0xD1,0xF0,0xEC,0xE7,0xC7,0x88,0x54,0x36,0x2B,0x2A,0x17,0x06,0x04,0x01,0x03,0x06,0x28,0x20,0x2F,0x97,0xC1,0x5F,0x1E,0x42,0x79,0x74,0x75,0x76,0x77,0xF0,0xFE,0xCE,0x99,0xC9,0xF0,0xFE,0xEF, + 0xBE,0xDA,0xF1,0xFE,0xFE,0xCF,0xEB,0xFC,0xFE,0xFD,0xFE,0xFE,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFD,0xFE,0xFE,0xFE,0xFE,0x7F,0x00,0x00,0x00,0x00,0x00,0xA2,0xDE,0x5F,0x3C,0x24,0x35,0x1B,0x90,0x97,0x98,0x93,0xBD,0xA1,0x81,0x5A,0x32,0x1B,0x10,0x0E,0x1D,0x17,0x01,0x05,0x0A,0x09,0x20,0x22,0x38,0x62,0xBC, + 0x8B,0x1E,0x1F,0x5E,0x61,0x67,0x6D,0x9C,0xFE,0xFE,0xAE,0xB7,0xC6,0xF5,0xFE,0xDF,0xD1,0xED,0xF6,0xFE,0xFE,0xC5,0xF1,0xFE,0xFE,0xFE,0xFE,0xFE,0xFD,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFD,0xFE,0xFE,0xFE,0xFE,0x7E,0x00,0x00,0x00,0x00,0x00,0xBB,0xD2,0x20,0x3C,0x2B,0x27,0x44,0x77,0x73,0x71,0x81,0x82,0x5F,0x35, + 0xC9,0xEE,0x66,0x10,0x05,0x11,0x1F,0x07,0x0B,0x13,0x1A,0x15,0x2A,0x37,0x1F,0xB4,0x9D,0x1E,0x1E,0x55,0x5E,0x61,0x66,0xA4,0xFE,0xFE,0xB4,0xB7,0xC4,0xF5,0xFE,0xE7,0xD0,0xED,0xF3,0xFE,0xFE,0xD5,0xEF,0xFD,0xFD,0xFD,0xFE,0xFE,0xFD,0xFE,0xFE,0xFE,0xFE,0xFD,0xFD,0xFD,0xFE,0xFE,0xFE,0xFC,0xFE,0xFE,0xFE,0xFE,0x7F,0x00,0x00,0x00, + 0x58,0x00,0xC8,0xBE,0x10,0x39,0x2D,0x1B,0x49,0x54,0x4E,0x49,0x73,0x39,0x29,0x4E,0xF2,0xF8,0x6A,0x2A,0x18,0x14,0x1D,0x22,0x13,0x1E,0x28,0x0E,0x31,0x34,0x27,0xA5,0xAA,0x1E,0x1E,0x4D,0x5C,0x5D,0x62,0x81,0xFC,0xFE,0xD6,0xA1,0xB3,0xDF,0xFE,0xFC,0xD5,0xE0,0xE1,0xEF,0xFE,0xF8,0xE2,0xF1,0xF9,0xFC,0xFE,0xFD,0xFD,0xFD,0xFE,0xFE, + 0xFE,0xFD,0xFD,0xFD,0xFD,0xFE,0xF9,0xA7,0xA1,0x9C,0x96,0x8F,0x48,0x00,0x00,0x00,0x7C,0x00,0xCD,0xB2,0x1A,0x2F,0x2D,0x12,0x1D,0x1F,0x1D,0x1A,0x43,0x20,0x06,0x29,0x44,0x4A,0x23,0x37,0x3A,0x16,0x2F,0x32,0x20,0x2B,0x35,0x19,0x34,0x33,0x33,0x9B,0xAF,0x1F,0x1E,0x4B,0x5C,0x5D,0x61,0x62,0xCC,0xFE,0xFC,0xA9,0x9D,0xB0,0xF2,0xFE, + 0xF7,0xD8,0xD2,0xAC,0xF7,0xFE,0xF5,0xD7,0xF2,0xFA,0xFE,0xFD,0xFD,0xFD,0xFE,0xFD,0xFD,0xFD,0xFD,0xFD,0xFC,0xFD,0xF3,0x00,0x00,0x00,0x00,0x5D,0xD4,0xD5,0x95,0x00,0x97,0x00,0xCA,0xB1,0x18,0x2B,0x2B,0x11,0x15,0x19,0x17,0x12,0x2C,0x10,0x01,0x0A,0x0D,0x20,0x36,0x4D,0x4C,0x25,0x44,0x46,0x33,0x3D,0x47,0x20,0x39,0x34,0x35,0x98, + 0xAF,0x20,0x1E,0x4D,0x5E,0x60,0x63,0x64,0x69,0xE4,0xFE,0xF4,0x9F,0xAF,0xA1,0xF8,0xFE,0xF0,0xCC,0x9E,0xB4,0xFC,0xFE,0xE3,0xDF,0xF6,0xFC,0xFC,0xFC,0xFB,0xFD,0xFC,0xFD,0xFD,0xFD,0xFD,0xFC,0xFC,0xF0,0x00,0x00,0x00,0x00,0x6E,0xFE,0xFE,0xB1,0x00,0xA5,0x00,0xC0,0xB8,0x10,0x2D,0x28,0x16,0x0F,0x14,0x10,0x0A,0x1E,0x04,0x01,0x04, + 0x1A,0x31,0x4A,0x62,0x3C,0x41,0x54,0x58,0x4B,0x53,0x5D,0x1D,0x42,0x38,0x2F,0x9D,0xAC,0x1F,0x1E,0x55,0x62,0x64,0x66,0x67,0x65,0x7C,0xF7,0xFE,0xC8,0x80,0x86,0xBB,0xFE,0xFE,0xA3,0x99,0x9A,0xD2,0xFE,0xFA,0xD1,0xEA,0xF6,0xF9,0xFC,0xFB,0xFC,0xFC,0xFE,0xFD,0xFD,0xFD,0xFD,0xFD,0xF0,0x00,0x00,0x00,0x00,0x5D,0xDA,0xD9,0x99,0x00, + 0xB8,0x00,0xAE,0xC5,0x1A,0x2E,0x24,0x1E,0x0A,0x0E,0x08,0x01,0x17,0x0C,0x06,0x01,0x16,0x36,0x4A,0x3A,0x35,0x64,0x73,0x5E,0x67,0x6E,0x6B,0x22,0x48,0x3C,0x1F,0xA9,0xA2,0x1E,0x1E,0x5F,0x66,0x68,0x69,0x69,0x69,0x67,0xCC,0xFE,0xEA,0x73,0x89,0x8A,0xF5,0xFE,0xBE,0x93,0x9F,0xA7,0xFD,0xFE,0xD4,0xE1,0xF5,0xF7,0xFB,0xFC,0xFC,0xFC, + 0xFE,0xFD,0xFE,0xFE,0xFE,0xFD,0xF1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xCA,0x00,0x95,0xC8,0x5D,0x2D,0x1D,0x29,0x09,0x03,0x02,0x02,0x01,0x1F,0x0C,0x11,0x11,0x15,0x23,0x43,0x67,0x77,0x81,0x7A,0x85,0x8B,0x5F,0x3A,0x41,0x3F,0x3A,0xB1,0x8F,0x1E,0x28,0x69,0x6A,0x6C,0x6C,0x6B,0x6D,0x6C,0xB1,0xFE,0xF2,0x75,0x8A,0x8E, + 0xE5,0xFE,0xC2,0x90,0xA3,0xA2,0xFB,0xFE,0xD1,0xDF,0xF5,0xF7,0xFA,0xFD,0xFD,0xFD,0xFE,0xFD,0xE9,0xAE,0xAC,0xAA,0x9F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC4,0x6D,0x64,0xC3,0x8E,0x21,0x20,0x2C,0x16,0x03,0x02,0x02,0x07,0x09,0x20,0x23,0x23,0x34,0x46,0x5A,0x79,0x85,0x89,0x9B,0xA2,0x97,0x22,0x50,0x2F,0x3A,0x73,0xB2, + 0x71,0x1E,0x4B,0x6C,0x6E,0x6F,0x6F,0x6E,0x70,0x70,0xB7,0xFE,0xE8,0x79,0x8C,0x92,0xE9,0xFE,0xB7,0x96,0xA9,0xAD,0xFD,0xFA,0xC5,0xE2,0xF6,0xF9,0xFB,0xFE,0xFE,0xFE,0xFE,0xFD,0xD7,0x00,0x00,0x00,0x00,0xC7,0xF6,0xF6,0xE1,0x00,0x00,0x00,0x00,0x00,0xA0,0xB0,0x20,0xB1,0xB4,0x23,0x2A,0x1E,0x28,0x09,0x02,0x07,0x0E,0x11,0x12,0x1D, + 0x31,0x41,0x55,0x66,0x6D,0x8B,0xAB,0xB2,0xB8,0x5C,0x44,0x4F,0x3B,0x20,0xA7,0xB5,0x38,0x1E,0x84,0x6E,0x70,0x71,0x72,0x72,0x73,0x76,0xD6,0xFE,0xC4,0x80,0x92,0x9D,0xFB,0xF9,0x9A,0xA1,0xAF,0xCA,0xFE,0xE3,0xC2,0xEB,0xFC,0xFD,0xFD,0xFE,0xFE,0xFE,0xFD,0xFD,0xD7,0x00,0x00,0x00,0x00,0xCE,0xFD,0xFD,0xE6,0x00,0x00,0x00,0x00,0x00, + 0x54,0xDD,0x29,0x80,0xBE,0x81,0x1F,0x28,0x28,0x24,0x08,0x0D,0x13,0x18,0x1F,0x28,0x30,0x3A,0x4D,0x65,0x81,0x9B,0xCB,0xF5,0x8B,0x3D,0x68,0x39,0x46,0x66,0xBD,0x94,0x1E,0x4E,0xA2,0x7D,0x73,0x74,0x75,0x76,0x76,0x8A,0xF9,0xF5,0x87,0x8F,0x9C,0xCD,0xFE,0xD2,0xA2,0xB1,0xB4,0xEF,0xFA,0xBC,0xCA,0xF2,0xFD,0xFE,0xFC,0xFA,0xFB,0xFD, + 0xFD,0xFC,0xD6,0x00,0x00,0x00,0x00,0xD0,0xFE,0xFD,0xE8,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x93,0x29,0xB0,0xBA,0x47,0x2F,0x20,0x2D,0x20,0x09,0x14,0x23,0x2D,0x37,0x43,0x4E,0x5D,0x6F,0x86,0xA1,0xB0,0x85,0x41,0x7A,0x48,0x57,0x29,0xAD,0xB8,0x48,0x1E,0x93,0xA5,0x95,0x7A,0x78,0x79,0x7A,0x7C,0xD7,0xFE,0xB0,0x8C,0xA2,0xB0,0xF9, + 0xED,0xAA,0xB3,0xC0,0xD5,0xFC,0xCC,0xBA,0xD7,0xF2,0xFD,0xFD,0xFD,0xFC,0xFA,0xE9,0xE7,0xE6,0xC3,0x00,0x00,0x00,0x00,0x69,0x7A,0x78,0x6B,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0xCF,0x3C,0x65,0xBC,0xA8,0x28,0x35,0x1E,0x29,0x28,0x11,0x12,0x2A,0x42,0x58,0x64,0x70,0x79,0x79,0x63,0x20,0x5F,0x82,0x50,0x5F,0x30,0x96,0xBE,0x86,0x1E, + 0x6E,0xB1,0xA9,0xA2,0x88,0x7E,0x7D,0x7E,0x89,0xD4,0xC3,0x90,0xA2,0xAE,0xBD,0xD9,0xBE,0xBB,0xC4,0xC8,0xC9,0xCA,0xD0,0xC8,0xD1,0xEE,0xFD,0xFD,0xFD,0xFE,0xEA,0x00,0x00,0x00,0x55,0x63,0x6E,0x5B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA7,0xAB,0x1E,0x85,0xBA,0x9E,0x23,0x2E,0x20,0x24,0x2E,0x25,0x18,0x11, + 0x19,0x22,0x25,0x16,0x22,0x54,0x78,0x75,0x45,0x62,0x31,0x87,0xC2,0xA7,0x24,0x4A,0xB2,0xB4,0xAE,0xA7,0x9F,0x85,0x80,0x82,0x85,0x8C,0x96,0xA4,0xAD,0xB5,0xBB,0xC3,0xC7,0xC9,0xCB,0xCE,0xD1,0xD0,0xDE,0xCF,0xD5,0xEF,0xFE,0xFE,0xFE,0xFD,0xE8,0x00,0x00,0x00,0xF4,0xFE,0xFE,0xC2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x35,0xBD,0x8C,0x1F,0x91,0xB8,0x9F,0x36,0x25,0x2E,0x1E,0x26,0x34,0x37,0x3C,0x45,0x55,0x68,0x79,0x6A,0x43,0x51,0x56,0x28,0x99,0xC7,0xAE,0x39,0x42,0xB5,0xBD,0xB7,0xB2,0xAC,0xA6,0x97,0x83,0x85,0x8A,0x91,0x9A,0xA6,0xB1,0xBA,0xBF,0xC6,0xCB,0xCD,0xCF,0xD6,0xF2,0xF2,0xEF,0xF1,0xF6,0xF9,0xFC,0xFD,0xD5,0x6C,0x64,0x00, + 0x00,0x00,0xF1,0xFE,0xFE,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0xBA,0x83,0x20,0x86,0xB2,0xAA,0x67,0x14,0x2A,0x2F,0x2C,0x25,0x29,0x31,0x35,0x37,0x43,0x4F,0x50,0x36,0x5E,0xBC,0xD0,0xAE,0x3A,0x4A,0xBC,0xC9,0xC1,0xBA,0xB4,0xAD,0xA4,0x9B,0x8D,0x87,0x8E,0x95,0x9B,0xA8,0xB5,0xBE,0xC3,0xC8, + 0xCD,0xD0,0xD3,0xDC,0xFB,0xFD,0xF6,0xF6,0xF9,0xF9,0xFB,0xFD,0xAE,0x00,0x00,0x00,0x00,0x00,0xEF,0xFC,0xFC,0xBB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0xB0,0x8C,0x25,0x63,0xA8,0xB4,0xA0,0x6E,0x25,0x1C,0x2C,0x38,0x43,0x49,0x47,0x36,0x18,0x69,0xB2,0xD7,0xD4,0x93,0x24,0x71,0xC3,0xCE,0xBB, + 0x9B,0x79,0x5B,0x37,0x00,0x00,0x00,0x00,0x1C,0x4C,0x69,0x8B,0xB2,0xC2,0xC6,0xCC,0xD0,0xD3,0xD7,0xDF,0xF4,0xF6,0xF3,0xF6,0xF9,0xFA,0xFC,0xE9,0x45,0x00,0x00,0x00,0x00,0x00,0x3D,0x3F,0x3D,0x15,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x99,0x99,0x48,0x32,0x81,0xAA,0xB2,0xB2,0xA0,0x8D, + 0x80,0x79,0x7F,0x92,0xAA,0xCC,0xDC,0xD8,0xAE,0x59,0x3E,0x8E,0xA2,0x7D,0x3E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0x98,0xBB,0xD2,0xD8,0xDD,0xE3,0xE8,0xEC,0xF1,0xF8,0xFB,0xFA,0xD1,0x57,0x00,0x67,0xA7,0xA9,0x5B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6C,0x9A,0x76,0x3B,0x2A,0x6B,0x92,0xAE,0xBC,0xBF,0xC0,0xC5,0xCC,0xD0,0xBB,0x95,0x54,0x1E,0x32,0x50,0x22,0x1E,0x21,0x36,0x48,0x55,0x59,0x56,0x52,0x49,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x75,0x9A,0xB5,0xC5,0xCA,0xCD,0xC2,0xA5,0x5D,0x00,0x00,0x00,0x9D,0xFE,0xFE, + 0x86,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x71,0x8F,0x7C,0x5D,0x38,0x1F,0x34,0x59,0x66,0x67,0x55,0x27,0x1E,0x1E,0x25,0x45,0x60,0x6E,0x77,0x7D,0x86,0x86,0x85,0x85,0x82,0x7F,0x7C,0x77,0x73,0x6F,0x65,0x58,0x42,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x9C,0xFD,0xFC,0x87,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2D,0x61,0x81,0x92,0x8D,0x88,0x89,0x8A,0x8A,0x8B,0x8D,0x93,0x9A,0xA1,0xA0,0x9B,0x94,0x8E,0x88,0x83,0x78,0x72, + 0x72,0x70,0x6D,0x69,0x66,0x65,0x63,0x62,0x69,0x71,0x6F,0x5D,0x2D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0x3D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3B,0x62,0x7C, + 0x94,0xA1,0xA8,0xA8,0xA7,0xA4,0x9F,0x92,0x81,0x70,0x5E,0x4A,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +}; +const lv_image_dsc_t ui_img_logo_80x44_png = { + .header.w = 80, + .header.h = 44, + .data_size = sizeof(ui_img_logo_80x44_png_data), + .header.cf = LV_COLOR_FORMAT_NATIVE_WITH_ALPHA, + .header.magic = LV_IMAGE_HEADER_MAGIC, + .data = ui_img_logo_80x44_png_data +}; + diff --git a/main/Application/Tasks/GUI/Export/images/ui_img_text_218x40_png.c b/main/Application/Tasks/GUI/Export/images/ui_img_text_218x40_png.c new file mode 100644 index 0000000..0aadb66 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/images/ui_img_text_218x40_png.c @@ -0,0 +1,298 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#include "../ui.h" + +#ifndef LV_ATTRIBUTE_MEM_ALIGN + #define LV_ATTRIBUTE_MEM_ALIGN +#endif + +// IMAGE DATA: assets/Text_218x40.png +const LV_ATTRIBUTE_MEM_ALIGN uint8_t ui_img_text_218x40_png_data[] = { + 0xE1,0x18,0xE2,0x10,0x02,0x19,0x02,0x11,0xC1,0x00,0xA1,0x00,0xC1,0x08,0x02,0x19,0x22,0x21,0x22,0x19,0x22,0x21,0x22,0x21,0x42,0x21,0x42,0x21,0x02,0x21,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE6,0x08,0xE5,0x10,0xA4,0x00,0x23,0x00,0x23,0x00,0x84,0x00,0xC5,0x10,0x46,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC5,0x10,0x26,0x19,0x46,0x21,0x46,0x21,0x67,0x21,0xA7,0x29,0xA8,0x29,0xE5,0x10,0x92,0x94,0x9A,0xD6,0xFB,0xDE,0xFB,0xDE, + 0xFB,0xDE,0xFB,0xDE,0xDB,0xDE,0x14,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA7,0x31,0x79,0xCE,0x1C,0xE7,0x3C,0xE7,0x5D,0xE7,0x5D,0xEF,0x3D,0xE7,0x39,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xCD,0x61,0xDD,0x61,0xDD,0x61,0xDD,0x61,0xDD,0x41,0xDD,0x41,0xDD,0x61,0xDD,0x61,0xDD,0x61,0xDD,0x61,0xDD,0x61,0xDD,0x61,0xDD,0x61,0xDD,0x62,0xDD,0x61,0xDD,0x02,0xCD,0xA3,0xBC,0xA2,0x93,0xA1,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0xBE,0xDB,0xDE,0xDB,0xDE,0xBB,0xD6,0xBB,0xD6,0xDB,0xDE,0xDB,0xDE,0x39,0xC6, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6E,0x6B,0x7A,0xCE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xFB,0xDE,0xBA,0xD6,0xCC,0x52,0x38,0xC6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF7,0xBD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD0,0x73,0xBE,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3C,0xE7,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA1,0xED,0x20,0xFE,0x20,0xFE,0x20,0xFE,0x20,0xFE,0x20,0xFE,0x20,0xFE,0x20,0xFE,0x20,0xFE,0x20,0xFE,0x20,0xFE,0x20,0xFE, + 0x20,0xFE,0x20,0xFE,0x20,0xFE,0x20,0xFE,0x21,0xFE,0x41,0xFE,0x21,0xF6,0xA1,0xE5,0x61,0xB4,0x21,0x62,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xCE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7D,0xEF,0xF0,0x7B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x75,0xAD,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0xDE,0x93,0x8C,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x38,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x32,0x7C,0xBF,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x5D,0xEF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xED,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x21,0xFE,0x21,0xFE,0x21,0xFE,0x41,0xFE,0x81,0xE5,0x21,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x51,0x8C,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xB6,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xBB,0xD6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBA,0xD6,0x00,0x00,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x18,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8F,0x6B,0xBE,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3C,0xE7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xED,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x00,0xFE,0x20,0xFE,0x20,0xFE,0xE1,0xF5,0x21,0xBC, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8E,0x6B,0x1C,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9A,0xD6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xAF,0x73,0x3D,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xD3,0x9C,0x00,0x00,0xB7,0xAD,0xBE,0xF7,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xBE,0xF7,0x35,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xAC,0x4A,0x5D,0xEF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xF7,0x7A,0xCE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x81,0xED,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0x00,0xFE,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0x00,0xFE,0x61,0xED,0x61,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0xC6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x0C,0x5B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB3,0x94,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0xEC,0x5A,0x00,0x00,0x00,0x00,0xB3,0x94, + 0x39,0xC6,0xD3,0x94,0x76,0xAD,0x7A,0xCE,0xF4,0x9C,0x6A,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x73,0x8C,0x7A,0xCE,0xDB,0xD6,0x7A,0xC6,0x5A,0xC6,0x76,0xAD,0x29,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xED,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0x21,0xDD,0x21,0xE5,0x21,0xE5,0x21,0xE5,0x21,0xE5,0x21,0xE5,0x01,0xE5,0x21,0xE5,0xA0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xE0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0x61,0xCC,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x7B,0xBE,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0x92,0x94,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x42,0x00,0xF8,0xBD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x59,0xCE,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x61,0xED,0xA0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xA1,0xFD,0xC2,0x82,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA2,0x41,0x81,0xA3,0xE1,0xD4,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xC0,0xFD,0xA0,0xFD,0xA0,0xFD,0xA0,0xFD,0x21,0xED,0x62,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x31,0x41,0x6A,0xA2,0xB3,0x82,0xAB,0x61,0x72,0x01,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0x41,0xC3,0x7A,0x22,0x93,0x82,0xA3,0x81,0xA3,0x61,0x9B,0x41,0x72,0x81,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2A,0x3A,0xFC,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x18,0xC6,0xE4,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8F,0x6B,0xDB,0xDE,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0x14,0x9D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x29,0x42,0xCF,0x73,0xF3,0x9C,0xF4,0x9C,0x14,0xA5,0xD3,0x9C,0xB3,0x94,0x6A,0x4A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4B,0x08,0x88,0x31,0x6E,0x6B,0xB3,0x94,0xF4,0x9C,0xF4,0x9C,0xB3,0x94,0x31,0x84,0x4A,0x4A,0xAC,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4B,0x00,0x8B,0x01,0x6B,0x00,0x8B,0x01,0x2B,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8B,0x52,0xB3,0x94,0x14,0x9D,0x14,0xA5,0xB3,0x94,0x11,0x7C,0xC8,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xED,0xA0,0xFD,0xA0,0xFD,0xA0,0xFD,0xA0,0xFD,0xA0,0xFD,0x80,0xFD,0xC1,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0x62,0xE1,0xD4,0xA0,0xFD,0xA0,0xFD,0xA0,0xFD,0xA0,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x82,0xA3,0x22,0xAC,0xA0,0xD4,0xA0,0xD4,0xA0,0xD4,0xA1,0xCC,0xA1,0xCC,0xA1,0xCC,0x61,0xC4,0x03,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x62,0xC4,0x81,0xD4,0x81,0xCC,0x60,0xCC, + 0x60,0xCC,0x80,0xCC,0xA0,0xCC,0x22,0xB4,0x00,0x00,0x03,0xAC,0xA1,0xCC,0xA0,0xCC,0xA1,0xCC,0xA1,0xD4,0x80,0xD4,0xE1,0xB3,0x00,0x00,0x00,0x00,0xC2,0x72,0x01,0xB4,0x01,0xE5,0x61,0xF5,0xA1,0xFD,0x81,0xFD,0x60,0xF5,0xA1,0xD4,0x62,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x42,0x6A,0xC1,0xA3,0xE1,0xD4,0x41,0xED,0xA1,0xF5,0xC1,0xFD,0xC1,0xFD,0xC1,0xFD,0xC1,0xFD,0xA1,0xF5,0x61,0xED,0xC1,0xD4,0xA1,0xA3,0x21,0x62,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0xBE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0xDE,0xEF,0x7B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x31,0x84,0xBE,0xF7,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3C,0xE7,0x2D,0x63,0x00,0x00,0x00,0x00,0x76,0xAD,0x9A,0xCE,0x9A,0xD6,0x9A,0xD6,0xBA,0xD6,0xBA,0xD6,0x9A,0xD6,0x35,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4E,0x6B,0x96,0xB5,0xBA,0xD6,0x7D,0xEF,0xBE,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xDF,0xFF,0x7D,0xEF,0xDB,0xDE,0x59,0xCE,0xB3,0x94,0x0D,0x5B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x7C,0x39,0xC6,0x79,0xCE,0x79,0xCE,0x79,0xCE,0x79,0xCE,0x79,0xCE,0xF8,0xBD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x7B,0x55,0xA5,0x9A,0xD6, + 0x3D,0xE7,0xBE,0xF7,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xBF,0xF7,0x7D,0xEF,0xBA,0xD6,0x18,0xC6,0x52,0x8C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6A,0x4A,0xB7,0xB5,0x9A,0xCE,0x9A,0xD6,0x9A,0xD6,0x9A,0xD6,0x7A,0xCE,0x14,0xA5,0x00,0x00,0xF0,0x7B,0x76,0xAD,0xDB,0xDE,0x7E,0xEF,0xDF,0xF7,0xDF,0xFF,0xFF,0xFF,0xDF,0xFF,0xBE,0xF7,0x5D,0xE7,0xBB,0xD6,0x35,0x9D,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0xED,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0xA1,0x7A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE2,0x7A,0x61,0xF5,0x80,0xFD,0x80,0xFD,0x80,0xFD, + 0x80,0xFD,0x80,0xFD,0x80,0xFD,0x42,0xCC,0xE1,0x8A,0x80,0xFD,0x80,0xFD,0xA0,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x60,0xF5,0x42,0x9B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE1,0xDC,0x80,0xFD,0x80,0xFD,0x60,0xFD,0x60,0xFD,0x80,0xFD,0x80,0xFD,0x81,0xA3,0x00,0x00,0x62,0xC4,0xA0,0xFD,0xA0,0xFD,0xA0,0xFD,0x80,0xFD,0x80,0xFD,0x00,0xED,0x81,0x08,0xC2,0xAB,0x01,0xE5,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0xC1,0xE4,0x81,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0x92,0xE1,0xB3,0x01,0xE5,0x80,0xFD,0x80,0xFD,0xA0,0xFD,0xA0,0xFD,0xA0,0xFD,0xA1,0xFD,0xA1,0xFD,0xA1,0xFD,0xA1,0xFD, + 0xC1,0xFD,0xC1,0xFD,0xA1,0xFD,0x01,0xDD,0x81,0xB3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x31,0x84,0x7E,0xEF,0xFF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xBE,0xF7,0x71,0x8C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x59,0xC6,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0x79,0xCE,0x00,0x00,0x00,0x00,0x00,0x00,0xD7,0xBD,0xFF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0x38,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x35,0xA5,0xFC,0xDE,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0x9E,0xF7,0xDB,0xDE,0x51,0x8C,0x00,0x00,0x00,0x00,0x00,0x00,0xEA,0x31,0x9E,0xEF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDB,0xDE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x76,0xAD,0x3C,0xE7,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7D,0xEF,0xF8,0xBD,0x30,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8A,0x4A,0xBB,0xD6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x38,0xC6,0x92,0x8C,0xBB,0xD6,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xBE,0xF7,0xF8,0xBD,0x00,0x00,0x00,0x00,0x21,0xED,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x80,0xFD,0x60,0xFD,0xA2,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0x62,0xA1,0xDC,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x41,0xCC,0xE1,0x9A,0xC1,0xDC,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x21,0xCC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x93,0x40,0xF5,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x60,0xFD,0xE1,0xE4,0x41,0x39,0x00,0x00,0x22,0xC4,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x40,0xFD,0x40,0xFD,0xE1,0xEC, + 0xE1,0x8A,0x20,0xF5,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x40,0xFD,0x61,0xD4,0x00,0x00,0x00,0x00,0x00,0x00,0x61,0x49,0x01,0xC4,0x20,0xFD,0x40,0xFD,0x60,0xFD,0x60,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x80,0xFD,0x81,0xFD,0xA1,0xFD,0x61,0xF5,0xE1,0xBB,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBA,0xD6,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xF7,0xBD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6B,0x29,0xBB,0xD6,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xBE,0xF7,0x55,0xAD,0x00,0x00,0x00,0x00,0x00,0x00, + 0xD7,0xB5,0xDF,0xFF,0xDF,0xFF,0xDF,0xF7,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0x18,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x43,0x00,0x76,0xAD,0x9E,0xEF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x5D,0xEF,0x71,0x8C,0x00,0x00,0x00,0x00,0x46,0x21,0x7E,0xEF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDB,0xD6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0xF8,0xBD,0xBE,0xF7,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF, + 0xFC,0xDE,0x55,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8A,0x4A,0xBB,0xD6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9A,0xD6,0xFC,0xDE,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBE,0xF7,0xB6,0xB5,0x00,0x00,0x01,0xED,0x60,0xFD,0x60,0xFD,0x40,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0xA2,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA1,0x41,0x61,0xD4,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x41,0xD4,0x00,0x00,0xA1,0xB3,0x00,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD, + 0x81,0xDC,0x61,0x51,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0xC4,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x40,0xFD,0x41,0xCC,0x00,0x00,0x00,0x00,0xE1,0xC3,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x00,0xFD,0x01,0xFD,0xA1,0xE4,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0xC1,0xC3,0x00,0x00,0x00,0x00,0x61,0x49,0x01,0xC4,0x00,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x60,0xFD,0x61,0xFD,0x61,0xFD,0x41,0xFD,0xC1,0xB3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB6,0xB5, + 0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xFF,0xDB,0xD6,0x31,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0D,0x5B,0x9E,0xEF,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0x3D,0xE7,0x2D,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0xD7,0xB5,0xDF,0xFF,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0x18,0xBE,0x00,0x00,0x00,0x00,0x00,0x00,0x35,0xA5,0x7D,0xEF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDB,0xDE,0xAB,0x52,0x00,0x00,0x00,0x00,0x66,0x29,0x7E,0xEF,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7, + 0xDF,0xFF,0xDF,0xFF,0xDB,0xD6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD7,0xBD,0xBE,0xF7,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0x9A,0xD6,0x2A,0x3A,0x00,0x00,0x00,0x00,0x00,0x00,0x8B,0x52,0xBB,0xD6,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xBE,0xF7,0xDF,0xF7,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x2D,0x63,0xE0,0xEC,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x40,0xFD,0x20,0xFD,0x81,0x82, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x61,0x72,0x61,0xDC,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x00,0xFD,0x20,0xFD,0x01,0xCC,0x00,0x00,0x01,0x62,0xA0,0xEC,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0xE0,0xF4,0x61,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA1,0xDC,0x20,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x22,0x9B,0x00,0x00,0x00,0x00,0xC1,0xBB,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x81,0xB3,0x00,0x00,0x00,0x00, + 0x82,0xAB,0x00,0xF5,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x40,0xFD,0x40,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x40,0xFD,0x40,0xFD,0x40,0xFD,0xE0,0xF4,0x81,0x8A,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x7C,0x7D,0xEF,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0x9E,0xEF,0x8E,0x6B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7A,0xCE,0xBF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0x7A,0xCE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF7,0xBD,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0x18,0xBE,0x00,0x00,0x00,0x00,0x00,0x00,0x9A,0xD6, + 0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xBE,0xF7,0xBA,0xD6,0xFB,0xDE,0x1C,0xDF,0x9A,0xD6,0xDB,0xDE,0x9E,0xF7,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0x7D,0xEF,0xD3,0x9C,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x29,0x7E,0xEF,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xFF,0xDB,0xD6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB3,0x94,0x7E,0xEF,0xDF,0xFF,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xFF,0xDF,0xFF,0xBE,0xF7,0x7D,0xEF,0x7D,0xEF,0xDF,0xF7,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBE,0xF7,0x35,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0xAB,0x52,0xBB,0xD6,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF, + 0xDF,0xFF,0xDF,0xFF,0xDF,0xF7,0xDF,0xFF,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0x96,0xAD,0xA0,0xEC,0x00,0xFD,0x20,0xFD,0x20,0xFD,0x20,0xFD,0x00,0xFD,0x00,0xFD,0x61,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE2,0x59,0xA0,0xEC,0x00,0xFD,0x00,0xFD,0x00,0xFD,0xE0,0xFC,0xE0,0xFC,0x00,0xFD,0xA1,0xC3,0x00,0x00,0x00,0x00,0x41,0xD4,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xC0,0xFC,0xC0,0xFC,0xE0,0xFC,0x81,0xBB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC2,0x82,0xE0,0xEC,0x00,0xFD,0x00,0xFD, + 0x00,0xFD,0x00,0xFD,0x00,0xFD,0x81,0xE4,0x62,0x82,0x00,0x00,0x00,0x00,0xA1,0xBB,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xA0,0xFC,0xA0,0xFC,0xC0,0xFC,0xC0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xC1,0xF4,0xC1,0xEC,0xA1,0xEC,0xA0,0xF4,0xA0,0xF4,0xA1,0x8A,0x00,0x00,0x00,0x00,0x81,0xE4,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0x00,0xFD,0xE0,0xFC,0x21,0xD4,0x62,0xAB,0x02,0x9B,0xE2,0x92,0xA1,0xBB,0x81,0xDC,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x00,0xFD,0x01,0xD4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBB,0xD6,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xDF,0xF7,0xDF,0xF7,0xD7,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xDB,0xD6,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xD7,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0xBD,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0x18,0xBE,0x00,0x00,0x00,0x00,0x08,0x00,0x3C,0xE7,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0x7D,0xEF,0x14,0xA5,0x2D,0x63,0x00,0x00,0x00,0x00,0x88,0x29,0x8C,0x52,0xAF,0x73,0x7A,0xCE,0x3C,0xE7,0xDF,0xF7,0x18,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x21,0x7E,0xEF,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDB,0xD6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDB,0xD6,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7, + 0xDF,0xF7,0xDF,0xF7,0x9E,0xF7,0x39,0xC6,0xB3,0x94,0x8A,0x52,0x6A,0x4A,0x72,0x8C,0xF8,0xBD,0x9E,0xEF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFB,0xDE,0xEC,0x5A,0x00,0x00,0x00,0x00,0xAB,0x52,0xDB,0xD6,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xBE,0xF7,0xBB,0xD6,0xD7,0xB5,0xB7,0xB5,0x18,0xBE,0x38,0xC6,0x1C,0xDF,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0x59,0xC6,0xA1,0xEC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xC0,0xFC,0x61,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA1,0xC3,0xC0,0xFC,0xC0,0xFC, + 0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xA0,0xFC,0x01,0xA3,0x00,0x00,0x00,0x00,0x62,0xA3,0xC0,0xFC,0xA0,0xFC,0xA0,0xFC,0xA0,0xFC,0xA0,0xFC,0xA0,0xFC,0xE0,0xD3,0x81,0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA1,0xAB,0x00,0xFD,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0x21,0xD4,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xBB,0xA0,0xFC,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x80,0xFC,0xA0,0xFC,0xA0,0xFC,0xA0,0xFC,0xE1,0xD3,0x63,0xB3,0x83,0x72,0x62,0x39,0x61,0x41,0x81,0x82,0x20,0xA3,0xE1,0x51,0x00,0x00,0xC1,0x9A,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xA0,0xFC,0x61,0xB3,0x22,0x31,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x31,0xA1,0xC3,0xC0,0xFC,0xC0,0xFC,0xE0,0xFC,0xE0,0xFC,0xE0,0xFC,0xC0,0xFC,0x80,0xF4,0x61,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0xB7,0xAD,0xBE,0xEF,0xBE,0xF7,0xBE,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBA,0xD6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xCD,0x52,0x5D,0xE7,0xBF,0xF7,0xBF,0xF7,0xBE,0xF7,0xBE,0xF7,0xBE,0xF7,0x7D,0xEF,0x6E,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0xBD,0xDF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0x18,0xBE,0x00,0x00,0x00,0x00,0xC5,0x08,0x9E,0xF7,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xFB,0xDE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x8E,0x6B,0xD7,0xB5,0x0C,0x5B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x29,0x7E,0xEF,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xDF,0xF7,0xBA,0xD6,0x0A,0x00,0x00,0x00,0x00,0x00,0xB3,0x94,0x5D,0xE7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBE,0xF7,0x96,0xB5,0x49,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0xA5,0x9E,0xEF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0xBE,0xF7,0x92,0x8C,0x00,0x00,0x00,0x00,0xAB,0x52,0xBB,0xD6,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0x9E,0xEF,0x76,0xAD,0x4E,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8E,0x6B,0xDB,0xD6,0xDF,0xF7, + 0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0x9A,0xCE,0x81,0xEC,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xA0,0xFC,0x61,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0x49,0x60,0xBB,0x60,0xF4,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x60,0xFC,0x60,0xFC,0x20,0xEC,0x01,0x7A,0x00,0x00,0x00,0x00,0xA1,0x51,0x20,0xE4,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x60,0xF4,0x20,0x7A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0xE1,0xCB,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xC0,0xFC,0xA0,0xFC,0xE1,0xA2,0x00,0x00,0x00,0x00,0x00,0x00,0x61,0xBB,0x60,0xFC,0x60,0xFC,0x60,0xFC,0x60,0xFC, + 0x40,0xFC,0x60,0xFC,0x60,0xFC,0xA0,0xCB,0x41,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xC3,0x80,0xFC,0x80,0xFC,0xA0,0xFC,0xA0,0xFC,0xA0,0xFC,0x80,0xFC,0x00,0xE4,0x21,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE1,0x69,0x21,0xE4,0xA0,0xFC,0xA0,0xFC,0xA0,0xFC,0xA0,0xFC,0xA0,0xFC,0xA0,0xFC,0x41,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x53,0x84,0x7E,0xE7,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0xBE,0xEF,0xBE,0xEF,0x5D,0xE7,0xCC,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD8,0xB5,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0xBB,0xD6,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xD7,0xB5,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0x18,0xBE,0x00,0x00,0x00,0x00,0x07,0x01,0xBE,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDB,0xD6,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x29,0x7E,0xEF,0xBE,0xF7,0xBE,0xF7,0xBE,0xF7,0xBE,0xF7,0xBF,0xF7,0xBA,0xD6,0x0A,0x00,0x00,0x00,0x00,0x00,0xB7,0xB5,0xBE,0xF7,0xBE,0xF7,0xBE,0xEF,0xBE,0xEF,0xBE,0xF7,0xBF,0xF7,0xFC,0xDE,0x8F,0x6B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7A,0xCE,0xDF,0xF7, + 0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0x79,0xCE,0x00,0x00,0x00,0x00,0x8B,0x52,0xBB,0xD6,0xBF,0xF7,0xDF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xDF,0xF7,0x79,0xCE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x76,0xAD,0xBE,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0xDF,0xF7,0x3D,0xE7,0x41,0xEC,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x62,0xBB,0xC1,0xC3,0xA1,0xC3,0xA0,0xC3,0x81,0xC3,0x80,0xC3,0x80,0xC3,0x61,0xCB,0xE0,0xE3,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x20,0xFC,0x20,0xFC,0x40,0xCB,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xBB,0x60,0xFC,0x60,0xFC, + 0x60,0xFC,0x60,0xFC,0x60,0xFC,0x60,0xFC,0x40,0xBB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,0x7A,0x20,0xE4,0xA0,0xFC,0xA0,0xFC,0xA0,0xFC,0xA0,0xFC,0x80,0xFC,0x00,0xE4,0xC1,0x69,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xBB,0x40,0xFC,0x40,0xFC,0x20,0xFC,0x20,0xFC,0x20,0xFC,0x20,0xFC,0xC0,0xE3,0x41,0x51,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0xDB,0x60,0xFC,0x60,0xFC,0x60,0xFC,0x60,0xFC,0x60,0xFC,0x60,0xFC,0x40,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA1,0xC3,0x60,0xFC,0x60,0xFC,0x60,0xFC,0x60,0xFC,0x60,0xFC,0x61,0xFC,0x81,0xCB,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x9A,0xCE,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0xD7,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x9A,0xCE,0x9E,0xE7,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0xD7,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB7,0xB5,0xBF,0xF7,0xBF,0xEF,0xBF,0xEF,0xBF,0xEF,0xBF,0xEF,0xBF,0xEF,0xF8,0xBD,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xE7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBE,0xEF,0x9A,0xCE,0x35,0xA5,0x0C,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x21,0x5D,0xE7,0xBE,0xEF, + 0xBE,0xEF,0xBE,0xEF,0xBE,0xEF,0xBE,0xEF,0xBA,0xD6,0x00,0x00,0x00,0x00,0x00,0x00,0x59,0xC6,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0xBE,0xEF,0x18,0xBE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x96,0xB5,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0x9A,0xCE,0x00,0x00,0x00,0x00,0x8B,0x4A,0xBB,0xCE,0xBF,0xEF,0xBF,0xEF,0xBF,0xEF,0xBF,0xEF,0xBF,0xF7,0x7E,0xEF,0xF4,0x94,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6B,0x4A,0x3C,0xDF,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xF7,0xBF,0xEF,0x3D,0xE7,0x01,0xEC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC, + 0x60,0xFC,0x60,0xFC,0x60,0xFC,0x60,0xFC,0x40,0xFC,0x40,0xFC,0x20,0xFC,0x20,0xFC,0x20,0xFC,0x20,0xFC,0x00,0xFC,0x00,0xFC,0x00,0xFC,0x00,0xFC,0x00,0xFC,0x00,0xFC,0xE0,0xFB,0xC0,0xF3,0x01,0x8A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x82,0x20,0xF4,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x60,0xFC,0xA0,0xD3,0x81,0x59,0x00,0x00,0x00,0x00,0x00,0x00,0xA1,0x92,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x80,0xFC,0x60,0xFC,0x61,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0xBB,0x00,0xFC,0x00,0xFC,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0x00,0xFC,0x40,0xCB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xC1,0xE3,0x20,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x01,0xCB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xB3,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x40,0xFC,0xA1,0xDB,0x82,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0xB7,0xB5,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x9E,0xE7,0x9A,0xCE,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x32,0xFC,0xD6,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x9E,0xE7,0x5D,0xDF,0xF0,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x96,0xAD,0xBE,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0xF8,0xB5,0x00,0x00,0x00,0x00, + 0x00,0x00,0x9A,0xCE,0xBE,0xEF,0xBE,0xEF,0xBE,0xEF,0xBE,0xEF,0xBE,0xEF,0xBE,0xEF,0xBE,0xEF,0x9E,0xEF,0x5D,0xE7,0xBB,0xD6,0x18,0xBE,0xB3,0x94,0x33,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0x21,0x5D,0xE7,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0xBA,0xCE,0x00,0x00,0x00,0x00,0x87,0x21,0xDB,0xD6,0x7E,0xEF,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x9E,0xE7,0x9E,0xEF,0x14,0x9D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0x9D,0x9E,0xEF,0xBE,0xEF,0xBE,0xEF,0x9E,0xEF,0xBE,0xEF,0xBE,0xEF,0x1C,0xDF,0x00,0x00,0x00,0x00,0x6A,0x42,0x9A,0xCE,0x9E,0xEF,0x9E,0xEF, + 0x9E,0xEF,0x9E,0xEF,0xBE,0xEF,0x3D,0xE7,0xAB,0x4A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDB,0xD6,0xBF,0xEF,0xBE,0xEF,0xBE,0xEF,0xBE,0xEF,0xBE,0xEF,0x1C,0xDF,0xC1,0xEB,0x20,0xFC,0x20,0xFC,0x20,0xFC,0x00,0xFC,0x00,0xFC,0x20,0xFC,0x20,0xFC,0x20,0xFC,0x00,0xFC,0x00,0xFC,0x00,0xFC,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC1,0xB2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0x10,0x81,0xD3,0x20,0xFC,0x20,0xFC,0x20,0xFC,0x40,0xFC,0x40,0xFC,0x20,0xF4,0xC1,0x79,0x00,0x00,0x00,0x00,0xE2,0x28,0x81,0xCB,0x60,0xFC,0x60,0xFC, + 0x60,0xFC,0x40,0xFC,0x60,0xFC,0x40,0xF4,0x61,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE1,0xBA,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0x60,0xDB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xEB,0x00,0xFC,0x00,0xFC,0x00,0xFC,0x00,0xFC,0x20,0xFC,0x20,0xFC,0x81,0xAA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0xA2,0x20,0xFC,0x00,0xFC,0x00,0xFC,0x00,0xFC,0x00,0xFC,0x20,0xFC,0x81,0xDB,0xC2,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0xD0,0x73,0x3D,0xDF,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x1C,0xD7,0x0A,0x2A, + 0x00,0x00,0x00,0x00,0x93,0x8C,0x5E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x9B,0xCE,0xAB,0x4A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x76,0xA5,0x9E,0xEF,0x9E,0xE7,0x7E,0xE7,0x9E,0xE7,0x9E,0xE7,0x9E,0xE7,0xF8,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x52,0x84,0x7D,0xE7,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0xBF,0xEF,0xBF,0xEF,0x9E,0xEF,0xDB,0xD6,0x59,0xC6,0x72,0x8C,0x45,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDD,0x64,0x5E,0xE7,0x9E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0xBB,0xCE,0x00,0x00,0x00,0x00,0x4A,0x21,0x9A,0xCE,0x5D,0xE7,0x5E,0xE7,0x7E,0xE7, + 0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x76,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4A,0x42,0x5D,0xE7,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x1C,0xDF,0xC5,0x08,0x00,0x00,0x4A,0x42,0x9A,0xCE,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x3D,0xDF,0x4A,0x3A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0xD6,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x1C,0xDF,0xA1,0xE3,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB, + 0x80,0xFB,0x80,0xFB,0x80,0xFB,0x80,0xF3,0xA1,0xBA,0xE1,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE1,0xBA,0x00,0xFC,0x00,0xFC,0x20,0xFC,0x20,0xFC,0x20,0xFC,0x20,0xFC,0x01,0xBB,0x00,0x00,0x00,0x00,0x42,0x51,0xC1,0xDB,0x40,0xFC,0x40,0xFC,0x40,0xFC,0x20,0xFC,0x20,0xFC,0xA1,0xDB,0x21,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0xBA,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0x41,0xDB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x87,0x39,0xA1,0xF3,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0x00,0xFC,0x21,0x92,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0x92,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xA1,0xE3,0xA2,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x2E,0x5B,0x7A,0xC6,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0xF4,0x94,0x00,0x00,0x00,0x00,0xF8,0xB5,0x7E,0xE7,0x7E,0xDF,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0xF8,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x55,0xA5,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0xF8,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x6B,0x42,0xD7,0xB5,0x5D,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x9E,0xE7,0x9E,0xE7, + 0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x9E,0xEF,0x5D,0xE7,0x7A,0xCE,0x31,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD6,0x4B,0x3D,0xDF,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x9A,0xCE,0x00,0x00,0x00,0x00,0x6C,0x42,0x7A,0xC6,0x5D,0xDF,0x5D,0xDF,0x5D,0xDF,0x5E,0xDF,0x5E,0xDF,0x3D,0xDF,0x39,0xBE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x21,0x3D,0xDF,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x3D,0xDF,0xA8,0x29,0x00,0x00,0x4A,0x42,0x9A,0xCE,0x9E,0xE7,0x9E,0xE7,0x7E,0xE7,0x7E,0xE7,0x9E,0xE7,0x1D,0xDF,0x6B,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xDB,0xD6,0x9E,0xE7,0x9E,0xE7,0x9E,0xE7,0x9E,0xE7,0x9E,0xE7,0xFC,0xD6,0x81,0xEB,0xC0,0xFB,0xC0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0x80,0xFB,0x80,0xFB,0x80,0xFB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x40,0xFB,0x40,0xFB,0x40,0xFB,0x01,0xE3,0x61,0xB2,0x01,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0x69,0xC0,0xEB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0xE0,0xFB,0x00,0xFC,0x41,0xD3,0x00,0x00,0x00,0x00,0x02,0x82,0x00,0xF4,0x00,0xFC,0x00,0xFC,0x00,0xFC,0x00,0xFC,0x00,0xFC,0x01,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0xBA,0x80,0xFB,0x80,0xFB, + 0x60,0xFB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x00,0xDB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x80,0xEB,0xA0,0xFB,0xA0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0xE1,0x89,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0x92,0xC0,0xFB,0xC0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0x81,0xE3,0xA2,0x79,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x19,0xB6,0x3E,0xDF,0x3D,0xD7,0x3E,0xD7,0x3E,0xD7,0x5E,0xDF,0x5E,0xDF,0xB7,0xAD,0x00,0x00,0x29,0x3A,0x7A,0xC6,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x3D,0xDF,0x72,0x84,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0xA5,0x7E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x7E,0xDF,0xF8,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4A,0x42,0x15,0x9D,0xDB,0xCE,0x5E,0xE7,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0xFC,0xD6,0xF4,0x94,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x19,0xFC,0xD6,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x7A,0xC6,0x00,0x00,0x00,0x00,0x4D,0x42,0x7A,0xC6,0x3D,0xDF,0x3D,0xDF,0x3D,0xDF,0x3D,0xDF,0x3D,0xDF,0x3D,0xD7,0x18,0xB6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x46,0x21,0x1D,0xD7,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x1D,0xD7,0x09,0x32,0x00,0x00,0x8B,0x4A,0x7A,0xC6,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x1D,0xD7,0x6B,0x3A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBB,0xCE,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0x7E,0xE7,0xFC,0xD6,0x61,0xEB,0xA0,0xFB,0xA0,0xFB,0x80,0xFB,0x80,0xFB,0x80,0xFB,0x80,0xFB,0x60,0xFB,0x40,0xFB,0x40,0xFB,0x40,0xFB,0x20,0xFB,0x20,0xFB,0x20,0xFB,0x20,0xFB,0x00,0xFB,0xE0,0xF2,0xA0,0xDA,0xA1,0xD2,0x22,0xAA,0xA2,0x79,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x21,0xD3,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0xC0,0xFB,0x80,0xEB,0x01,0x51,0x00,0x00,0xE2,0xBA,0xE0,0xFB,0xC0,0xFB,0xC0,0xFB,0xE0,0xFB,0xE0,0xFB,0xC0,0xF3,0x21,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0xBA,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x40,0xFB,0x40,0xFB,0x40,0xFB,0xE0,0xDA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0xEB,0x80,0xFB,0x80,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0xA0,0xFB,0xC1,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0x92,0x80,0xFB,0xA0,0xFB,0x80,0xFB,0x80,0xFB,0x80,0xFB,0x80,0xFB, + 0x21,0xDB,0x42,0x61,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x74,0x1D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x39,0xB6,0x46,0x19,0xD0,0x6B,0xFD,0xCE,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3E,0xD7,0x5E,0xD7,0x9B,0xC6,0xEB,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x76,0xA5,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0xD8,0xAD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6F,0x63,0x76,0xA5,0x7A,0xC6,0x1D,0xD7,0x3D,0xD7,0x3D,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0xDC,0xCE,0x6E,0x63,0x00,0x00,0x00,0x00,0x26,0x19, + 0xDC,0xCE,0x3D,0xDF,0x3D,0xDF,0x3D,0xDF,0x5D,0xDF,0x5E,0xDF,0x5A,0xBE,0x00,0x00,0x00,0x00,0xCB,0x31,0x39,0xBE,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x1D,0xD7,0x19,0xB6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x87,0x29,0x1D,0xD7,0x5E,0xDF,0x3E,0xD7,0x3E,0xD7,0x5E,0xDF,0x5E,0xDF,0xFC,0xCE,0x87,0x21,0x00,0x00,0x8B,0x4A,0x7A,0xC6,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x1C,0xD7,0x6B,0x3A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBB,0xC6,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0x5E,0xDF,0xDB,0xCE,0x41,0xEB,0x60,0xFB,0x60,0xFB,0x60,0xFB, + 0x60,0xFB,0x60,0xFB,0x40,0xFB,0xA2,0x89,0x82,0x79,0xA2,0x89,0xC1,0x99,0xC1,0x99,0xA1,0x91,0xA1,0x91,0xA2,0x91,0x42,0x69,0x03,0x59,0xE2,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xAA,0xA0,0xFB,0x80,0xFB,0x80,0xFB,0x80,0xFB,0x80,0xFB,0x80,0xFB,0x41,0xB2,0x00,0x00,0x21,0xD3,0x80,0xFB,0x80,0xFB,0x80,0xFB,0x80,0xFB,0xA0,0xFB,0x40,0xE3,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA1,0xBA,0x40,0xFB,0x40,0xFB,0x20,0xFB,0x20,0xFB,0x20,0xFB,0x20,0xFB,0xC0,0xDA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x20,0xEB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0xC1,0xD2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xA2,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x40,0xFB,0x40,0xFB,0xE1,0xDA,0xA2,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA9,0x29,0x7B,0xBE,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0xBC,0xC6,0xD0,0x6B,0x56,0x9D,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xD7,0x3D,0xD7,0xB7,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x9D,0x5E,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3E,0xD7,0x3E,0xD7,0xD8,0xAD, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xCC,0x4A,0xAF,0x63,0xB7,0xA5,0x7A,0xBE,0xBB,0xCE,0x3D,0xD7,0x3E,0xD7,0x3E,0xD7,0x3E,0xD7,0x3D,0xD7,0x3E,0xD7,0x3E,0xD7,0x3E,0xD7,0x3E,0xDF,0x56,0xA5,0x00,0x00,0x00,0x00,0x26,0x19,0xBC,0xCE,0x1D,0xD7,0x1D,0xD7,0x1D,0xD7,0x1D,0xD7,0x3D,0xD7,0x39,0xB6,0x00,0x00,0x00,0x00,0x00,0x00,0xDC,0xCE,0x1D,0xD7,0x1D,0xD7,0x1D,0xCF,0x1D,0xCF,0x1D,0xD7,0x1D,0xD7,0x15,0x95,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x93,0x8C,0x1D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0xBB,0xC6,0x00,0x00,0x00,0x00,0x6B,0x4A,0x5A,0xBE, + 0x5E,0xD7,0x3E,0xD7,0x3E,0xD7,0x3E,0xD7,0x5E,0xDF,0x1C,0xD7,0x8C,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x9B,0xC6,0x3E,0xD7,0x3E,0xD7,0x3D,0xD7,0x3D,0xDF,0x5D,0xDF,0xBB,0xCE,0x21,0xEB,0x40,0xFB,0x40,0xFB,0x40,0xFB,0x20,0xFB,0x20,0xFB,0x20,0xFB,0x82,0x89,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x62,0x71,0x20,0xEB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0x60,0xFB,0xA0,0xCA,0x63,0x51,0x40,0xF3,0x60,0xFB, + 0x40,0xFB,0x40,0xFB,0x40,0xFB,0x60,0xFB,0xA1,0xCA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x61,0xBA,0x20,0xFB,0x00,0xFB,0x00,0xFB,0xE0,0xFA,0xE0,0xFA,0xE0,0xFA,0x80,0xD2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA0,0xD2,0x20,0xFB,0x20,0xFB,0x20,0xFB,0x40,0xFB,0x40,0xFB,0x20,0xFB,0x81,0xCA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xAA,0x40,0xFB,0x40,0xFB,0x40,0xFB,0x40,0xFB,0x20,0xFB,0x20,0xFB,0xA1,0xDA,0x82,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB8,0xA5,0xFD,0xCE,0xFD,0xC6,0xFD,0xC6,0xFD,0xC6, + 0xFD,0xC6,0xFD,0xC6,0x15,0x95,0xF9,0xAD,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0xDC,0xC6,0x11,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x9D,0x3E,0xD7,0x1D,0xD7,0x1D,0xD7,0x1D,0xD7,0x3D,0xD7,0x3E,0xD7,0xB8,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x52,0x7C,0xB4,0x8C,0x19,0xB6,0xFC,0xCE,0x1D,0xD7,0x1D,0xD7,0x1D,0xD7,0x1D,0xD7,0x1D,0xD7,0x1D,0xD7,0x39,0xB6,0x09,0x3A,0x00,0x00,0x05,0x19,0x9C,0xC6,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0x1D,0xCF,0x19,0xB6,0x00,0x00,0x00,0x00,0x00,0x00,0x19,0xB6,0xFD,0xCE, + 0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0x1D,0xCF,0xB7,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF4,0x94,0x1D,0xD7,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0xF9,0xAD,0x00,0x00,0x00,0x00,0x2A,0x3A,0x3A,0xBE,0x3E,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3E,0xD7,0xFC,0xCE,0xED,0x4A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7B,0xBE,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0x3D,0xD7,0xBB,0xC6,0x01,0xE3,0x20,0xFB,0x00,0xFB,0x00,0xFB,0x00,0xFB,0x00,0xFB,0xE0,0xFA,0x62,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xD2,0x20,0xFB,0x20,0xFB,0x20,0xFB,0x20,0xFB,0x20,0xFB,0xC0,0xE2,0x41,0x9A,0x20,0xFB,0x20,0xFB,0x20,0xFB,0x00,0xFB,0x00,0xFB,0x00,0xF3,0x81,0x89,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xBA,0xE0,0xFA,0xE0,0xFA,0xC0,0xFA,0xC0,0xFA,0xC0,0xFA,0xA0,0xFA,0x40,0xD2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xCA,0x00,0xFB,0x00,0xFB,0x00,0xFB,0x00,0xFB,0x00,0xFB,0x00,0xFB,0x80,0xDA,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0x28,0x81,0xCA,0x00,0xFB,0x20,0xFB,0x20,0xFB,0x00,0xFB,0x00,0xFB,0x00,0xFB,0x42,0xCA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x32,0x74,0xBC,0xBE,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xB8,0x9D,0x1A,0xAE,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xDC,0xC6,0x19,0xAE,0xC8,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x35,0x95,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x97,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xD4,0x8C,0xDC,0xC6,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0x1D,0xCF,0x3A,0xB6,0xF1,0x73,0x00,0x00,0x26,0x19,0x7B,0xBE,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xFD,0xC6,0xF9,0xAD,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x9D,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xF9,0xAD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB8,0xA5,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0xFD,0xCE,0xB8,0xA5,0x00,0x00,0x00,0x00,0x09,0x3A,0x19,0xB6,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0xFC,0xCE,0xED,0x4A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x5B,0xBE,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0x1D,0xCF,0xBB,0xC6,0xC1,0xEA,0xE0,0xFA,0xC0,0xFA,0xC0,0xFA,0xC0,0xFA,0xC0,0xFA,0xC0,0xFA,0x42,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE1,0xA1,0xC0,0xF2,0xE0,0xFA,0x00,0xFB,0x00,0xFB,0x00,0xFB,0xE0,0xF2,0x61,0xBA,0x00,0xFB,0xE0,0xFA,0xE0,0xFA,0xE0,0xFA,0xE0,0xFA,0x60,0xDA,0x81,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0xBA, + 0xA0,0xFA,0x80,0xFA,0x80,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x00,0xD2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0xB9,0xA0,0xFA,0xC0,0xFA,0xC0,0xFA,0xC0,0xFA,0xC0,0xFA,0xC0,0xFA,0x80,0xF2,0x61,0x91,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0x81,0xC0,0xF2,0xE0,0xFA,0xE0,0xFA,0xE0,0xFA,0xC0,0xFA,0xA0,0xFA,0xA0,0xFA,0xA2,0xA1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0x19,0x1A,0xAE,0xBD,0xBE,0xBD,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0x7B,0xB6,0x9C,0xB6,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0x15,0x95,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF5,0x8C,0xFD,0xC6,0xFD,0xC6,0xFD,0xC6,0xFD,0xC6,0xFD,0xC6,0xFD,0xC6,0x97,0x9D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x84,0xF5,0x84,0xC8,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x01,0x5B,0xB6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xFD,0xC6,0x3A,0xB6,0x11,0x74,0x00,0x00,0x26,0x19,0x7B,0xB6,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xB8,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0xF1,0x6B,0xBC,0xBE,0xBD,0xC6,0xDC,0xC6,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0x9C,0xBE,0x12,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x6F,0x5B,0x5A,0xB6,0xDC,0xC6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0x32,0x74,0x00,0x00,0x00,0x00,0xE9,0x31,0x19,0xAE,0xFD,0xCE,0xFD,0xC6,0xFD,0xC6,0xFD,0xCE,0x1D,0xCF,0xDC,0xC6,0x8C,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3A,0xB6,0xFD,0xC6,0xFD,0xC6,0xFD,0xC6,0xFD,0xC6,0xFD,0xC6,0x9B,0xBE,0x81,0xEA,0xA0,0xFA,0xA0,0xFA,0x80,0xFA,0x80,0xFA,0xA0,0xFA,0x80,0xFA,0x21,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x43,0x49,0x41,0xDA,0xC0,0xFA,0xC0,0xFA,0xE0,0xFA,0xE0,0xFA,0xE0,0xFA,0xC0,0xF2,0xE0,0xFA,0xC0,0xFA,0xC0,0xFA,0xC0,0xFA,0xC0,0xFA,0x00,0xCA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE1,0xB9,0x60,0xFA,0x40,0xFA,0x40,0xFA,0x20,0xFA,0x20,0xFA,0x20,0xFA,0xC0,0xD1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0x58,0x40,0xEA,0x80,0xFA,0xA0,0xFA,0xA0,0xFA,0x80,0xFA,0x80,0xFA,0x80,0xFA,0x20,0xDA,0x61,0x99,0xE1,0x58,0x00,0x00,0x00,0x00,0xC2,0x48,0x81,0x89,0x60,0xDA,0xA0,0xFA,0xA0,0xFA,0xA0,0xFA,0xA0,0xFA,0x80,0xFA, + 0x60,0xFA,0x00,0xDA,0x01,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x98,0x9D,0xBC,0xBE,0xBC,0xBE,0xBC,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x7B,0xB6,0xF1,0x6B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF5,0x84,0xDD,0xBE,0xDD,0xBE,0xDD,0xBE,0xDD,0xBE,0xDD,0xC6,0xDD,0xBE,0x77,0x9D,0x00,0x00,0x00,0x00,0x00,0x00,0xD1,0x63,0x1A,0xAE,0x9C,0xB6,0x98,0x9D,0x93,0x7C,0x0E,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x84,0x00,0x73,0x7C,0x9C,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xDD,0xBE,0x97,0x9D,0x09,0x32, + 0x00,0x00,0x05,0x19,0x3B,0xAE,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0xB8,0x9D,0x00,0x00,0x00,0x00,0x00,0x00,0x6B,0x3A,0xF9,0xA5,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0x1A,0xAE,0x73,0x7C,0x6B,0x3A,0x00,0x00,0x00,0x00,0xE9,0x31,0xF1,0x6B,0xD8,0xA5,0x9C,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xBD,0xBE,0x3A,0xAE,0x09,0x32,0x00,0x00,0x00,0x00,0x09,0x32,0xF9,0xAD,0xDD,0xC6,0xDD,0xC6,0xDD,0xC6,0xFD,0xC6,0xFD,0xC6,0xBC,0xBE,0x4B,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x1A,0xAE,0xDD,0xBE,0xDD,0xBE,0xDD,0xBE,0xDD,0xC6,0xDD,0xBE,0x5B,0xB6,0x41,0xEA,0x60,0xFA, + 0x60,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x01,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0xB1,0xA0,0xFA,0xA0,0xFA,0xA0,0xFA,0xA0,0xFA,0xA0,0xFA,0xA0,0xFA,0xA0,0xFA,0xA0,0xFA,0x80,0xFA,0x80,0xFA,0x80,0xFA,0x41,0x91,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0xB9,0x20,0xFA,0x00,0xFA,0x00,0xFA,0xE0,0xF9,0xE0,0xF9,0xE0,0xF9,0xA0,0xD1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA1,0xB9,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x40,0xF2,0x20,0xDA,0x41,0xDA,0x41,0xD2,0x41,0xE2,0x80,0xF2,0x80,0xFA,0x80,0xFA,0x80,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x20,0xFA,0x80,0xB9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB4,0x84,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xAE,0x7C,0xAE,0x7C,0xAE,0x7C,0xAE,0x7C,0xAE,0x7C,0xAE,0xD9,0xA5,0xC8,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD4,0x84,0xBC,0xBE,0x9C,0xB6,0x9C,0xB6,0xBC,0xBE,0xBC,0xBE, + 0xBD,0xBE,0x56,0x95,0x00,0x00,0x00,0x00,0xD3,0x4B,0x98,0x9D,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7B,0xB6,0xD9,0xA5,0x77,0x95,0x57,0x95,0x52,0x74,0x52,0x74,0xF5,0x8C,0xB8,0xA5,0x5B,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x36,0x95,0x00,0x00,0x00,0x00,0x05,0x11,0x3A,0xAE,0x7C,0xB6,0x7C,0xB6,0x7C,0xAE,0x7C,0xB6,0x7C,0xB6,0x98,0x95,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD4,0x84,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xBE,0x9C,0xB6,0xF9,0xA5,0xB8,0xA5,0xB8,0xA5,0xD9,0xA5,0x7B,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xBE,0x9C,0xBE,0x9C,0xB6,0x16,0x8D,0x00,0x00,0x00,0x00,0x00,0x00, + 0xE9,0x31,0xD9,0xA5,0xBD,0xBE,0xDD,0xBE,0xDD,0xBE,0xDD,0xBE,0xDD,0xBE,0x9B,0xB6,0x2A,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0xF9,0xA5,0xBC,0xB6,0xBC,0xBE,0xBC,0xBE,0xBC,0xBE,0xBC,0xB6,0x1A,0xAE,0x21,0xE2,0x40,0xFA,0x40,0xFA,0x20,0xFA,0x20,0xFA,0x20,0xFA,0x20,0xFA,0x01,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x42,0x79,0x40,0xEA,0x60,0xFA,0x80,0xFA,0x80,0xFA,0x80,0xFA,0x80,0xFA, + 0x60,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x20,0xE2,0xC1,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0xB9,0xE0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0x80,0xD1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x69,0x01,0xE2,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x60,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x20,0xFA,0x20,0xFA,0xC0,0xD9,0xA1,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x11,0xFA,0x9D, + 0x5C,0xAE,0x5C,0xAE,0x5C,0xAE,0x5C,0xAE,0x5C,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x37,0x8D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB4,0x7C,0x9C,0xB6,0x7C,0xB6,0x7C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x36,0x8D,0x00,0x00,0x00,0x00,0x15,0x8D,0x7B,0xB6,0x7B,0xB6,0x7B,0xB6,0x7B,0xAE,0x5B,0xAE,0x7B,0xAE,0x7C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xBE,0x9C,0xBE,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xAE,0x1A,0xA6,0x90,0x63,0x00,0x00,0x00,0x00,0x26,0x11,0x1A,0xA6,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x78,0x95,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x68,0x09,0x98,0x9D,0x7C,0xAE,0x7C,0xAE,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x9C,0xB6,0xD9,0x9D,0x8B,0x3A,0x00,0x00,0x00,0x00,0x00,0x00,0xC8,0x29,0xB9,0x9D,0xBC,0xBE,0xBC,0xBE,0xBC,0xB6,0xBC,0xB6,0xBC,0xBE,0x5B,0xAE,0x2A,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0xF9,0xA5,0x9C,0xB6,0x9C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0xD9,0x9D,0xE1,0xE1,0x00,0xFA,0x00,0xFA,0x00,0xFA,0x00,0xFA,0xE0,0xF9,0x00,0xFA,0xE1,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,0x00,0xC1,0xD1,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0x40,0xFA,0xC0,0xD1,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x61,0xB1,0xA0,0xF9,0xA0,0xF9,0xA0,0xF9,0x80,0xF9,0x80,0xF9,0xA0,0xF9,0x60,0xD1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,0x91,0xE1,0xE1,0x00,0xFA,0x20,0xFA,0x20,0xFA, + 0x20,0xFA,0x20,0xFA,0x20,0xFA,0x20,0xFA,0x20,0xFA,0x20,0xFA,0x20,0xFA,0x20,0xFA,0x20,0xFA,0x00,0xFA,0x00,0xFA,0x00,0xFA,0x00,0xFA,0xA0,0xE1,0xE1,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x8D,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x32,0x6C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x7C,0x7C,0xAE,0x5B,0xAE,0x5C,0xAE,0x5C,0xAE,0x7C,0xAE,0x7C,0xAE,0xF5,0x84,0x00,0x00,0x00,0x00,0x57,0x95,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE, + 0x5B,0xAE,0x7B,0xAE,0x7C,0xAE,0x7C,0xAE,0x7C,0xAE,0x7C,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x1A,0xA6,0x94,0x7C,0x84,0x00,0x00,0x00,0x00,0x00,0x4D,0x22,0xFA,0xA5,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x77,0x8D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x4B,0x98,0x95,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x7C,0xAE,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xB6,0x7C,0xAE,0x7B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x98,0x95,0xB1,0x5B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC8,0x29,0x98,0x9D,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x9C,0xB6,0x3B,0xAE,0x2A,0x2A,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0xD9,0x9D,0x7C,0xAE,0x7C,0xAE,0x5C,0xAE,0x5B,0xAE,0x5B,0xAE,0xB9,0x9D,0xA0,0xE1,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xE2,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xA1,0x00,0xFA,0x00,0xFA,0x20,0xFA,0x20,0xFA,0x00,0xFA,0x00,0xFA,0x00,0xFA,0x00,0xFA,0xE0,0xF9,0x21,0xA1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x41,0xB9,0x80,0xF9,0x80,0xF9,0x60,0xF9,0x60,0xF9,0x60,0xF9,0x60,0xF9,0x40,0xD1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x81,0xC1,0xD9,0x00,0xFA,0xE0,0xF9,0xE0,0xF9,0xE0,0xF9,0xE0,0xF9,0xE0,0xF9,0xE0,0xF9,0xE0,0xF9,0x00,0xFA,0xE0,0xF9,0xE0,0xF9,0xE0,0xF9,0xC0,0xF9,0xC0,0xF9,0xA1,0xD9,0xE1,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0x6C,0xFA,0x9D,0x1B,0x9E,0x1B,0x9E,0x1B,0x9E,0x1B,0x9E,0x1B,0x9E,0x1B,0x9E,0xFA,0x9D,0xFA,0x9D,0xB9,0x95,0x4B,0x32,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x7C,0x5B,0xAE,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x5B,0xA6,0x5B,0xAE,0xD5,0x84,0x00,0x00,0x00,0x00,0x6B,0x32,0xB5,0x7C,0xB9,0x9D,0x3B,0xA6,0x3B,0xAE,0x3B,0xAE,0x3B,0xAE,0x3B,0xAE,0x3B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x3B,0xAE,0x3B,0xA6,0x3B,0xA6,0xD9,0x9D,0x74,0x74,0x4B,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x9C,0x44,0xFA,0x9D,0x1B,0xA6,0x1B,0xA6,0x1B,0xA6,0x1B,0x9E,0x1B,0xA6,0x57,0x8D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xED,0x4A,0x37,0x8D,0x1B,0xA6,0x3B,0xA6,0x3B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE, + 0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x5B,0xAE,0x3B,0xAE,0x3B,0xA6,0x1B,0xA6,0x37,0x8D,0xD1,0x5B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE9,0x29,0x98,0x95,0x7C,0xAE,0x7C,0xAE,0x7C,0xAE,0x7C,0xAE,0x7C,0xAE,0x1A,0xA6,0xEA,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0xB9,0x95,0x5B,0xA6,0x5B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0xB9,0x9D,0x60,0xE1,0x80,0xF9,0x80,0xF9,0x80,0xF9,0x80,0xF9,0x80,0xF9,0x80,0xF9,0xC2,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x81,0x38,0x81,0xC9,0xE0,0xF9,0x00,0xFA,0xE0,0xF9,0xE0,0xF9,0xE0,0xF9,0xE0,0xF9,0xC0,0xF9,0xA0,0xE1,0xE2,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0xB1,0x60,0xF1,0x40,0xF1,0x40,0xF1,0x20,0xF1,0x20,0xF9,0x40,0xF9,0x00,0xE1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0x50,0x61,0xB9,0x81,0xD1,0xA0,0xF1,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xA0,0xF1,0x61,0xD9,0x41,0xB1, + 0xA2,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2A,0x32,0x58,0x85,0xDA,0x95,0xDA,0x95,0xDA,0x95,0xDA,0x95,0xDA,0x95,0xDA,0x95,0xBA,0x95,0xB9,0x95,0xD5,0x7C,0xE9,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x74,0x3B,0xA6,0x1B,0x9E,0xFB,0x9D,0xFB,0x9D,0x1B,0x9E,0x1B,0xA6,0xD5,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x4B,0xD5,0x7C,0x98,0x95,0x1B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x1B,0xA6,0xB9,0x95,0xF6,0x84,0xB0,0x5B,0x06,0x09,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x9F,0x3C,0xD9,0x95,0xDA,0x9D,0xFA,0x9D,0xDA,0x9D,0xDA,0x9D,0xFA,0x9D,0x37,0x85,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xCD,0x42,0x12,0x6C,0x57,0x8D,0xFA,0xA5,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xAE,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x1B,0xA6,0xFA,0x9D,0x57,0x8D,0x53,0x74,0xCD,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE9,0x29,0x78,0x95,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0x3B,0xA6,0xF9,0x9D,0xA8,0x19,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x98,0x95,0x1B,0x9E,0x1B,0x9E,0x1B,0x9E,0x1B,0xA6,0x1B,0xA6,0xB8,0x95, + 0xE1,0xB0,0x21,0xC1,0x41,0xB9,0x41,0xC1,0x41,0xC9,0x41,0xD9,0x20,0xD9,0xC7,0xA9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x99,0xC0,0xF9,0xE0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xC0,0xF9,0xA0,0xF9,0x40,0xC9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE2,0x80,0x01,0xB9,0x21,0xC1,0x21,0xC1,0x01,0xC1,0x01,0xC1,0x01,0xD1,0xE2,0xC0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x62,0x30,0xE1,0x88,0x41,0xC1,0x60,0xC9,0x80,0xD9,0x80,0xE1,0x80,0xE1,0x80,0xD9,0x61,0xC9,0x41,0xC1,0xE1,0x90,0xC6,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x11,0xD1,0x5B,0x75,0x6C,0x33,0x6C,0x13,0x64,0x12,0x64,0x13,0x64,0x33,0x6C,0xF2,0x63,0xD1,0x63,0x6F,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x5B,0xD5,0x7C,0x94,0x74,0x94,0x74, + 0x94,0x74,0x94,0x7C,0x94,0x7C,0x6F,0x5B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x88,0x21,0x12,0x6C,0x16,0x85,0x16,0x8D,0x77,0x95,0x98,0x95,0x98,0x95,0x78,0x95,0x16,0x8D,0x37,0x8D,0x53,0x74,0xC9,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x53,0x6C,0x53,0x6C,0x33,0x6C,0x32,0x6C,0x12,0x6C,0x12,0x6C,0x90,0x5B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB0,0x5B,0xB5,0x7C,0xD9,0x9D,0x57,0x95,0x98,0x9D,0x98,0x9D,0x57,0x95,0x98,0x95,0xB4,0x7C,0x0E,0x4B,0x06,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x67,0x21,0x11,0x6C,0xB5,0x7C,0xB5,0x7C,0xF6,0x84,0x16,0x85,0x16,0x8D,0x94,0x7C,0x26,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0x00,0xF2,0x63,0x74,0x74,0x94,0x7C,0xB5,0x7C,0xB5,0x84,0xD5,0x84,0x94,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x81,0xA0,0xE1,0xC0,0xF9,0xA0,0xF9, + 0xA0,0xF9,0xA0,0xF9,0x80,0xF9,0x80,0xF9,0x60,0xF1,0xC1,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x00,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE1,0xA8,0xE0,0xC0,0xE0,0xB8,0xE1,0xB0,0x21,0xB1,0x61,0xC9,0xA0,0xE9,0xA0,0xF9,0xA0,0xF9,0xA0,0xF9,0x80,0xF9,0x80,0xF9,0x60,0xF9,0x60,0xF9,0x01,0xC1,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xC1,0x40,0xF9,0x40,0xF9,0x60,0xF9,0x60,0xF9,0x60,0xF9,0x80,0xF9,0x80,0xF9,0x80,0xF9,0x60,0xF9,0x60,0xF9,0x40,0xF9,0x40,0xF9,0x20,0xD9,0xA1,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD1,0x40,0xF9,0x40,0xF9,0x40,0xF9,0x40,0xF9,0x60,0xF9,0x60,0xF9,0x60,0xF9,0x40,0xF9,0x40,0xF9,0x40,0xF9,0x20,0xF9,0xE0,0xE0,0xC1,0xA0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0x00,0xE0,0xD8,0x20,0xF9,0x20,0xF9,0x20,0xF9,0x20,0xF9,0x20,0xF9,0x40,0xF9,0x40,0xF9,0x20,0xF9,0x20,0xF9,0x00,0xF9,0x00,0xD1,0xA1,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x50,0xC0,0xD8,0x00,0xF9,0x20,0xF9,0x00,0xF9,0x00,0xF9,0x00,0xF9,0x20,0xF9,0x20,0xF1, + 0x00,0xF1,0xE0,0xD8,0xE1,0xB8,0xA2,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x62,0x60,0xA1,0xB0,0xC0,0xD0,0xC1,0xD0,0xC1,0xD0,0xC1,0xD0,0xC1,0xC8,0xE3,0xD0,0xE1,0xB8,0xA2,0x90,0x82,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + //alpha channel data: + 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x12,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x11,0x11,0x11,0x11,0x11,0x11,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x11,0x11,0x11,0x11,0x11,0x11,0x05,0x47,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0x75,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0xDC,0xFF,0xFF,0xFF,0xFF,0xFF,0xB4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xCF,0xC2,0x96,0x56,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xCB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2A,0xF8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0x40,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEE,0xAD,0x2A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x87,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,0x02,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF7,0x51,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x35,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xCC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xB1,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4C,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFA,0x5A, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x02,0xDB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1B,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x75,0x00,0x5D,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x16,0xD5,0xFF,0xFF,0xFF,0xFF,0xFF,0xAE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF9,0x4A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x88,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0x21,0x00,0x00,0x3C, + 0x4C,0x93,0x73,0x4B,0x45,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x16,0x45,0x4D,0x55,0x4E,0x45,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE7,0xD3,0xD3,0xD3,0xD3,0xD3,0xD5,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xB6,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x34,0xFE,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0x77,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0xC4,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBB,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00, + 0x00,0x00,0x00,0x00,0x1F,0x5F,0xD7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1A,0x3F,0x3F,0x3F,0x3F,0x10,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x33,0x43,0x76,0x8E,0x8E,0x69,0x3F,0x19,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xD8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x6B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x2D,0x52,0x8F,0x97,0x8F,0x86,0x46,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x2C,0x49,0x7F,0x8E,0x8E,0x8C,0x40,0x35,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x0C,0x0C,0x0C,0x03, + 0x00,0x00,0x00,0x00,0x3C,0x52,0x8F,0x8E,0x84,0x43,0x1A,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xB8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x75,0x85,0xEF,0xEF,0xEF,0xEF,0xEF,0xEF,0xDA,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0xEF,0xEF,0xEF, + 0xEF,0xEF,0xEF,0x6C,0x00,0x78,0xEF,0xEF,0xEF,0xEF,0xEF,0xD8,0x00,0x00,0x1E,0xB7,0xF6,0xFF,0xFF,0xFF,0xFF,0xF5,0x2A,0x00,0x00,0x00,0x00,0x00,0x1C,0x87,0xDE,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,0xD4,0x79,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x89,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x52,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1F,0x00,0x00,0x5B,0xF0,0xEF,0xEF,0xEF,0xEF,0xEF,0xA0,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0x7F,0xE4,0xFB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xED,0x9C,0x2F,0x01,0x00,0x00,0x00,0x00,0x01,0xE0,0xEF,0xEF,0xEF,0xEF,0xEF,0xBF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0D,0x7A,0xDF, + 0xF9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0xF0,0x8F,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0xD0,0xEF,0xF1,0xF2,0xF2,0xF2,0x9C,0x00,0x05,0x7C,0xDA,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,0xBB,0x34,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x32,0xFC,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0x95,0x50,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x66,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xF0,0x05,0x58,0xF5,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0x2A,0x00,0x00,0x00,0x02,0x82,0xF6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xE8,0x4D,0x00,0x00,0x00,0x00,0x00,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x5C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC8,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x00,0x01,0x57,0xEE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFD,0xAD,0x2E,0x00,0x00,0x00,0x27,0xF9,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x61,0xE6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF8,0x87,0x03,0x00,0x00,0x00,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xDB,0x19,0xD6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xF7,0x80,0x02,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xF8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC6,0x01,0xD4,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x34,0xFA,0xFF,0xFF,0xFF,0xFF,0xFF,0xF2,0x0E,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x8A,0xFA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,0x00,0x00,0x00,0x15,0xBC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xA0,0x07,0x00,0x00,0x00,0x05,0xDA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xA9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xE3,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x71,0x00,0x00,0x00, + 0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x01,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEE,0x2B,0x00,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0x00,0x00,0x06,0x9B,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x98,0x03,0x00,0x00,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xEB,0xD6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x6A,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xF8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC6,0x00,0x82,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xED,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x86,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xCF,0x00,0x00,0x02,0xB9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8F,0x00,0x00,0x00,0x00,0x8A, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEB,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0xFA,0xFF,0xFF,0xFF,0xFF,0xFF,0xF9,0x22,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x29,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD9,0x11,0x00,0x00,0x3E,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xE6,0x00,0x00,0x00,0x00,0x00,0x88,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x99,0x03,0x00,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,0x1F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xF8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC6,0x00,0x2D,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x57,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x55,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8F,0x00,0x00, + 0x4E,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0x55,0x00,0x00,0x00,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC8,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0xAA, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0xBC,0xB6,0xEB,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x00,0x00,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0x00,0x35,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x6C,0x00,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x77,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x34,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0xBD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9B,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xF6,0x08,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x5A,0x00,0x00,0xC1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xA2,0x56,0x54,0x98,0xD7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x00,0x00,0x00,0x00,0xE1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8B,0x00,0x00,0x00,0x00, + 0x00,0x00,0xB2,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x72,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x0C,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF8,0x5C,0x08,0x01,0x00,0x07,0x0D,0x4C,0x8B,0xF6,0xFF,0x92,0x01,0x00,0x00,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE7,0x00,0x00,0x00,0x01,0xA9,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xCE,0x69,0x55,0x55,0x58,0xC2,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDE,0x06,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEF,0xA5,0x64,0x55,0xA1,0xF6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xCC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8F,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0x77,0x00,0x00,0x68,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE9,0x06,0x00,0x00,0x00,0x00,0x00,0x7D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xA7,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEF,0x80,0x43,0x43,0x43,0x45,0x8C,0x14,0x00,0x3B,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xB2,0x08,0x00,0x00,0x00, + 0x00,0x14,0xA0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x39,0x00,0x00,0x00,0x9C,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD0,0x00,0x00,0x00,0x00,0x00,0x15,0xF1,0xFF,0xFF,0xFF,0xFF,0xFF,0xF8,0x24,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x38,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xB0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x21,0x89,0x0A,0x00,0x00,0x00,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEB,0x08,0x00,0x00,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9A,0x02,0x00,0x00,0x00,0x00,0x00,0x6B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x4B,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9E,0x03,0x00,0x00,0x00,0x00,0x20,0xDD,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0x85,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x35,0x00,0x00,0x17,0xF8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0x46,0x00,0x00,0x00,0x00,0x03,0xCF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x51,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xAF,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x93,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE8,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x1A,0xF2,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x75,0x00,0x00,0x00,0x36,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0x29,0x00,0x00,0x00,0x00,0x5B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC8,0x00,0x00,0x00, + 0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x1E,0xF6,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEA,0x07,0x00,0x00,0x6E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE2,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0xD3,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0x90,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAC,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x4D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC2,0x90,0x90,0x90,0x90,0x90,0x90,0xC8,0xF0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDD,0x03,0x00,0x00,0x00,0xBB,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0x9B,0x00,0x00,0x00,0x00,0x13,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEF,0x0C,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xED,0x1B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xB7,0x00, + 0x00,0x00,0x00,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x61,0x00,0x00,0x00,0x00,0xB1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0xEA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC3,0x4D,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0xAF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x97,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x89,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD9,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x71,0x00,0x00,0x00,0x00,0x65,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEE,0x02,0x00,0x00,0x00,0x78,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xA7,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xCB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xE4,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x51,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8A,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE3,0x06,0x00,0x00,0x00,0xA2,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xB7,0x00,0x00,0x00,0x14,0xF1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0x21,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00, + 0x00,0xB7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF2,0xD5,0x9A,0x46,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x02,0xC6,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEA,0x00,0x00,0x03,0xF7,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC4,0x00,0x00,0x00,0x00,0x00,0x0D,0xEB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0x37,0x00,0x00,0x01,0xB8,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0x5C,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE4,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0x09,0x00,0x00,0x00,0x4C,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEE,0x13, + 0x00,0x00,0x5A,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDD,0x04,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x5D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFB,0x9F,0x46,0x04,0x00,0x00,0x00,0x00,0x00,0x04,0xF0,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x09,0xE4,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0x3B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x39,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF3,0x16,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xBE,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x97,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x86,0x00,0x00,0x12,0xFB,0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,0x18,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0xE8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x41,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x6D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0x09,0x00,0x00,0x00,0x05,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x47,0x00,0x00,0x97,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8C,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x01,0x8C,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x43,0x00,0x00,0x00,0x00,0x09,0xF1,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x08,0xF9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x39,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x39,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE3,0x6A,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x32,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xD8,0x00,0x00,0x53,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xA8,0x00,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xE9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0x09,0x00,0x00,0x00,0x00,0x88,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9D,0x00,0x09,0xE3,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x37,0x00,0x00, + 0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x00,0x01,0x84,0xF5,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF8,0x50,0x00,0x00,0x00,0x3B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x0C,0xFA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x39,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x39,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFA,0xB1,0x5C,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xC1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0x2D,0x00,0x9C,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x52,0x00,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE4,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFC,0x09,0x00,0x00,0x00,0x00,0x49,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE7,0x0A,0x41,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE4,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x92,0xE1,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE7,0x1D,0x00,0x00,0x3E, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x0A,0xF8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x39,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF9,0x28,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xA1,0x53,0x53,0x53,0x53,0x53,0x53,0x53,0x54,0x40,0x0D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6C,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x67,0x00,0xED,0xFF,0xFF,0xFF,0xFF,0xFF,0xEE,0x0F,0x00,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xE4,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0x09,0x00,0x00,0x00,0x00,0x0B,0xEA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2E,0x7E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xA6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x3D,0x78,0xB3,0xF1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8C,0x00,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0xB5,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x5A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x39,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEA,0x00,0x00,0x03,0xF7, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0xF3,0xFF,0xFF,0xFF,0xFF,0xFF,0xBD,0x2F,0xFB,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xAA,0x00,0x00,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE4,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC8,0x02,0x00,0x00,0x00,0x00,0x00,0xA1,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0x83,0xB8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x47,0xB2,0xFA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC7,0x01,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0xAF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x75,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE2,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA4,0xFF,0xFF,0xFF,0xFF,0xFF,0xF0,0x7A,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x6C,0x00,0x00,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xB9,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x06,0xDD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAA,0x00,0x00,0x00,0x00,0x00,0x00,0x4B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDA,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xEB,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x63,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF3,0x02,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0x87,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD0,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB2,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x9B,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,0xFF,0xFF,0xFF,0xFF,0xFF,0xF8,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x96, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x65,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0xE8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC1,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x00,0x4F,0x91,0x0D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0xFB,0xFF,0xFF,0xFF,0xFF,0xFF,0xF3,0x02,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0x4B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x56,0x00,0x00,0x00,0x00,0x00, + 0x00,0x38,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x6D,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x08,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x19,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEB,0x4A,0x06,0x00,0x00,0x04,0x67,0xF8,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFE,0x1A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA2,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x6A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x29,0xF7,0xFF,0xE6,0x4A,0x07,0x00,0x00,0x00,0x00,0x00,0x01,0x4C,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF1,0x02, + 0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0x0B,0xEA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFA,0x54,0x05,0x00,0x00,0x06,0x46,0xE8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF7,0x1D,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x6E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xB4,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xE5,0xB6,0xB6,0xDB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xA9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4C,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xAB,0x00,0x00,0x05,0xD5,0xFF,0xFF,0xFF,0xFE,0xEF,0xB0,0x69,0x69,0x67,0x67,0xA8,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xA0,0x00,0x00,0x3E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0x00,0x80,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xE0,0xB6,0xB6,0xE3,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8D,0x00,0x00,0x00, + 0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFD,0x1A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0xDD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x26,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1B,0xF4, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x9E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0x32,0x00,0x00,0x3B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00, + 0x00,0x06,0xC4,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD3,0x18,0x00,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x73,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xCB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC1,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x41,0xF3,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x9B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x83,0x01,0x00,0x00,0x22,0xF8,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0x00,0x00,0x22,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF5,0x2D,0x00,0x00,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x6C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x6C,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0xE1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE5,0x5A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFC,0x32,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x04,0x77,0xFA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0x83,0x01,0x00,0x00,0x00,0x08,0xF1,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD2,0x1E,0x00,0x00,0x00,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x6F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0xD2,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xED,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x96,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x8E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x83,0xF3,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF3,0x96, + 0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0xF9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD9,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAB,0x00,0x00,0x00,0x00,0x25,0xA8,0xF8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFA,0xB2,0x2F,0x01,0x00, + 0x00,0x00,0x00,0x04,0xF0,0xFF,0xFF,0xFF,0xFF,0xFF,0xE7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x70,0xDF,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD9,0x67,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x46,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA3,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xA6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x25,0x3F,0x3F,0x3F,0x3F,0x3F,0x40,0x26,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x61,0x9D,0xE0,0xDF,0xDF,0xDF,0xDF,0xDE,0x91,0x54,0x0D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x3C,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1E,0x3F,0x3F,0x3F, + 0x3F,0x3F,0x3F,0x2A,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x53,0x8F,0xC7,0xDF,0xDF,0xE5,0xE3,0xD7,0x8E,0x62,0x2A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3B,0x3F,0x3F,0x3F,0x3F,0x3F,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x89,0x93,0xDF,0xDF,0xDF,0xDC,0x8F,0x82,0x3F,0x05,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x3D,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0xFC,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x91,0xBD,0x94,0x94,0x94,0xB9,0xFB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xBD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,0x2A,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xBD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF5,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xD0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xF9,0x9B,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xA9,0xB0,0xDD,0xFF,0xFF,0xFF,0xBD,0xAF,0x5F,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +const lv_image_dsc_t ui_img_text_218x40_png = { + .header.w = 218, + .header.h = 40, + .data_size = sizeof(ui_img_text_218x40_png_data), + .header.cf = LV_COLOR_FORMAT_NATIVE_WITH_ALPHA, + .header.magic = LV_IMAGE_HEADER_MAGIC, + .data = ui_img_text_218x40_png_data +}; + diff --git a/main/Application/Tasks/GUI/Export/project.info b/main/Application/Tasks/GUI/Export/project.info new file mode 100644 index 0000000..2c9bdb8 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/project.info @@ -0,0 +1,9 @@ +{ + "project_name": "SquareLine_Project.spj", + "datetime": "2026-02-07T23:00:07.4624365+01:00", + "editor_version": "1.6.0", + "project_version": 109, + "user": "Kampert Daniel", + "export_datetime": "2026-02-22T19:19:22.1177613+01:00", + "export_user": "Kampert Daniel" +} diff --git a/main/Application/Tasks/GUI/Export/screens/ui_Info.c b/main/Application/Tasks/GUI/Export/screens/ui_Info.c new file mode 100644 index 0000000..bd99515 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/screens/ui_Info.c @@ -0,0 +1,956 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#include "../ui.h" + +lv_obj_t * ui_Info = NULL; +lv_obj_t * ui_Panel_Info_Header = NULL; +lv_obj_t * ui_Label_Info_Header = NULL; +lv_obj_t * ui_Panel_Info_Content = NULL; +lv_obj_t * ui_Panel_Device = NULL; +lv_obj_t * ui_Label_Info_Device = NULL; +lv_obj_t * ui_Container_Info_Device = NULL; +lv_obj_t * ui_Info_Container4 = NULL; +lv_obj_t * ui_Label_Info_MAC_Name = NULL; +lv_obj_t * ui_Label_Info_IP_Name = NULL; +lv_obj_t * ui_Label_Info_Serial_Name = NULL; +lv_obj_t * ui_Label_Info_PSRAM_Free_Name = NULL; +lv_obj_t * ui_Label_Info_RAM_Free_Name = NULL; +lv_obj_t * ui_Info_Container5 = NULL; +lv_obj_t * ui_Label_Info_MAC = NULL; +lv_obj_t * ui_Label_Info_IP = NULL; +lv_obj_t * ui_Label_Info_Serial = NULL; +lv_obj_t * ui_Label_Info_PSRAM_Free = NULL; +lv_obj_t * ui_Label_Info_RAM_Free = NULL; +lv_obj_t * ui_Panel_Battery = NULL; +lv_obj_t * ui_Label_Info_Battery = NULL; +lv_obj_t * ui_Container_Battery = NULL; +lv_obj_t * ui_Info_Container2 = NULL; +lv_obj_t * ui_Label_Info_Battery_Status_Name = NULL; +lv_obj_t * ui_Label_Info_Battery_Voltage_Name = NULL; +lv_obj_t * ui_Label_Info_Battery_Remaining_Name = NULL; +lv_obj_t * ui_Info_Container3 = NULL; +lv_obj_t * ui_Label_Info_Battery_Status = NULL; +lv_obj_t * ui_Label_Info_Battery_Voltage = NULL; +lv_obj_t * ui_Label_Info_Battery_Remaining = NULL; +lv_obj_t * ui_Info_Bar2 = NULL; +lv_obj_t * ui_Panel_Lepton = NULL; +lv_obj_t * ui_Label_Info_Lepton = NULL; +lv_obj_t * ui_Container_Lepton = NULL; +lv_obj_t * ui_Info_Container1 = NULL; +lv_obj_t * ui_Label_Info_Lepton_Serial_Name = NULL; +lv_obj_t * ui_Label_Info_Lepton_Part_Name = NULL; +lv_obj_t * ui_Label_Info_Lepton_GPP_Revision_Name = NULL; +lv_obj_t * ui_Label_Info_Lepton_DSP_Revision_Main = NULL; +lv_obj_t * ui_Label_Info_Lepton_Uptime_Name = NULL; +lv_obj_t * ui_Label_Info_Lepton_FPA_Name = NULL; +lv_obj_t * ui_Label_Info_Lepton_AUX_Name = NULL; +lv_obj_t * ui_Info_Container7 = NULL; +lv_obj_t * ui_Label_Info_Lepton_Serial = NULL; +lv_obj_t * ui_Label_Info_Lepton_Part = NULL; +lv_obj_t * ui_Label_Info_Lepton_GPP_Revision = NULL; +lv_obj_t * ui_Label_Info_Lepton_DSP_Revision = NULL; +lv_obj_t * ui_Label_Info_Lepton_Uptime = NULL; +lv_obj_t * ui_Label_Info_Lepton_FPA = NULL; +lv_obj_t * ui_Label_Info_Lepton_AUX = NULL; +lv_obj_t * ui_Container_Info_Buttons = NULL; +lv_obj_t * ui_Button_Info_Back = NULL; +lv_obj_t * ui_Label_Info_Back = NULL; +// event funtions +void ui_event_Info(lv_event_t * e) +{ + lv_event_code_t event_code = lv_event_get_code(e); + + if(event_code == LV_EVENT_GESTURE && lv_indev_get_gesture_dir(lv_indev_active()) == LV_DIR_RIGHT) { + lv_indev_wait_release(lv_indev_active()); + _ui_screen_change(&ui_Main, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_Main_screen_init); + } + if(event_code == LV_EVENT_SCREEN_LOADED) { + ScreenInfoLoaded(e); + } +} + +void ui_event_Button_Info_Back(lv_event_t * e) +{ + lv_event_code_t event_code = lv_event_get_code(e); + + if(event_code == LV_EVENT_CLICKED) { + _ui_screen_change(&ui_Main, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_Main_screen_init); + } +} + +// build funtions + +void ui_Info_screen_init(void) +{ + ui_Info = lv_obj_create(NULL); + lv_obj_remove_flag(ui_Info, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_obj_set_flex_flow(ui_Info, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Info, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_set_style_bg_color(ui_Info, lv_color_hex(0x1E1E1E), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Info, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Panel_Info_Header = lv_obj_create(ui_Info); + lv_obj_set_width(ui_Panel_Info_Header, 320); + lv_obj_set_height(ui_Panel_Info_Header, lv_pct(8)); + lv_obj_set_x(ui_Panel_Info_Header, 1); + lv_obj_set_y(ui_Panel_Info_Header, 1); + lv_obj_set_align(ui_Panel_Info_Header, LV_ALIGN_TOP_MID); + lv_obj_remove_flag(ui_Panel_Info_Header, + LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_obj_set_style_radius(ui_Panel_Info_Header, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(ui_Panel_Info_Header, lv_color_hex(0x7B3FF0), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Panel_Info_Header, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Panel_Info_Header, lv_color_hex(0x7B3FF0), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Panel_Info_Header, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Header = lv_label_create(ui_Panel_Info_Header); + lv_obj_set_width(ui_Label_Info_Header, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Header, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Header, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Header, "SYSTEM INFORMATION"); + lv_obj_remove_flag(ui_Label_Info_Header, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Header, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Header, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Header, &lv_font_montserrat_14, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Panel_Info_Content = lv_obj_create(ui_Info); + lv_obj_set_width(ui_Panel_Info_Content, 320); + lv_obj_set_height(ui_Panel_Info_Content, lv_pct(75)); + lv_obj_set_x(ui_Panel_Info_Content, -276); + lv_obj_set_y(ui_Panel_Info_Content, 104); + lv_obj_set_align(ui_Panel_Info_Content, LV_ALIGN_CENTER); + lv_obj_set_flex_flow(ui_Panel_Info_Content, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Panel_Info_Content, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_set_scrollbar_mode(ui_Panel_Info_Content, LV_SCROLLBAR_MODE_ON); + lv_obj_set_scroll_dir(ui_Panel_Info_Content, LV_DIR_VER); + lv_obj_set_style_radius(ui_Panel_Info_Content, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(ui_Panel_Info_Content, lv_color_hex(0x1E1E1E), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Panel_Info_Content, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Panel_Info_Content, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Panel_Info_Content, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Panel_Info_Content, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Panel_Info_Content, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Panel_Info_Content, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_set_style_radius(ui_Panel_Info_Content, 2, LV_PART_SCROLLBAR | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(ui_Panel_Info_Content, lv_color_hex(0x7B3FF0), LV_PART_SCROLLBAR | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Panel_Info_Content, 255, LV_PART_SCROLLBAR | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Panel_Info_Content, 0, LV_PART_SCROLLBAR | LV_STATE_DEFAULT); + + ui_Panel_Device = lv_obj_create(ui_Panel_Info_Content); + lv_obj_set_width(ui_Panel_Device, 300); + lv_obj_set_height(ui_Panel_Device, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Panel_Device, LV_ALIGN_CENTER); + lv_obj_set_flex_flow(ui_Panel_Device, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Panel_Device, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_remove_flag(ui_Panel_Device, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | + LV_OBJ_FLAG_GESTURE_BUBBLE | LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | + LV_OBJ_FLAG_SCROLL_MOMENTUM | LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_style_radius(ui_Panel_Device, 6, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(ui_Panel_Device, lv_color_hex(0x252525), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Panel_Device, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Panel_Device, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Panel_Device, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Panel_Device, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Panel_Device, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Device = lv_label_create(ui_Panel_Device); + lv_obj_set_width(ui_Label_Info_Device, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Device, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Device, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Device, "DEVICE"); + lv_obj_remove_flag(ui_Label_Info_Device, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Device, lv_color_hex(0xB998FF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Device, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Device, &lv_font_montserrat_14, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Container_Info_Device = lv_obj_create(ui_Panel_Device); + lv_obj_remove_style_all(ui_Container_Info_Device); + lv_obj_set_width(ui_Container_Info_Device, lv_pct(100)); + lv_obj_set_height(ui_Container_Info_Device, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Container_Info_Device, LV_ALIGN_TOP_MID); + lv_obj_set_flex_flow(ui_Container_Info_Device, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(ui_Container_Info_Device, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_remove_flag(ui_Container_Info_Device, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_obj_set_style_pad_left(ui_Container_Info_Device, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Container_Info_Device, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Container_Info_Device, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Container_Info_Device, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Info_Container4 = lv_obj_create(ui_Container_Info_Device); + lv_obj_remove_style_all(ui_Info_Container4); + lv_obj_set_width(ui_Info_Container4, lv_pct(50)); + lv_obj_set_height(ui_Info_Container4, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Info_Container4, LV_ALIGN_LEFT_MID); + lv_obj_set_flex_flow(ui_Info_Container4, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Info_Container4, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_remove_flag(ui_Info_Container4, + LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Label_Info_MAC_Name = lv_label_create(ui_Info_Container4); + lv_obj_set_width(ui_Label_Info_MAC_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_MAC_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_MAC_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_MAC_Name, "MAC:"); + lv_obj_remove_flag(ui_Label_Info_MAC_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_MAC_Name, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_MAC_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_MAC_Name, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_MAC_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_MAC_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_MAC_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_MAC_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_IP_Name = lv_label_create(ui_Info_Container4); + lv_obj_set_width(ui_Label_Info_IP_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_IP_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_IP_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_IP_Name, "IP:"); + lv_obj_remove_flag(ui_Label_Info_IP_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_IP_Name, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_IP_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_IP_Name, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_IP_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_IP_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_IP_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_IP_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Serial_Name = lv_label_create(ui_Info_Container4); + lv_obj_set_width(ui_Label_Info_Serial_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Serial_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Serial_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Serial_Name, "Serial:"); + lv_obj_remove_flag(ui_Label_Info_Serial_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Serial_Name, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Serial_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Serial_Name, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Serial_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Serial_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Serial_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Serial_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_PSRAM_Free_Name = lv_label_create(ui_Info_Container4); + lv_obj_set_width(ui_Label_Info_PSRAM_Free_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_PSRAM_Free_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_PSRAM_Free_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_PSRAM_Free_Name, "Free PSRAM:"); + lv_obj_remove_flag(ui_Label_Info_PSRAM_Free_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_PSRAM_Free_Name, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_PSRAM_Free_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_PSRAM_Free_Name, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_PSRAM_Free_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_PSRAM_Free_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_PSRAM_Free_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_PSRAM_Free_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_RAM_Free_Name = lv_label_create(ui_Info_Container4); + lv_obj_set_width(ui_Label_Info_RAM_Free_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_RAM_Free_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_RAM_Free_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_RAM_Free_Name, "Free RAM:"); + lv_obj_remove_flag(ui_Label_Info_RAM_Free_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_RAM_Free_Name, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_RAM_Free_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_RAM_Free_Name, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_RAM_Free_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_RAM_Free_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_RAM_Free_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_RAM_Free_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Info_Container5 = lv_obj_create(ui_Container_Info_Device); + lv_obj_remove_style_all(ui_Info_Container5); + lv_obj_set_width(ui_Info_Container5, lv_pct(50)); + lv_obj_set_height(ui_Info_Container5, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Info_Container5, LV_ALIGN_RIGHT_MID); + lv_obj_set_flex_flow(ui_Info_Container5, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Info_Container5, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_END); + lv_obj_remove_flag(ui_Info_Container5, + LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Label_Info_MAC = lv_label_create(ui_Info_Container5); + lv_obj_set_width(ui_Label_Info_MAC, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_MAC, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_MAC, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_MAC, "AA:BB:CC:DD:EE:FF"); + lv_obj_remove_flag(ui_Label_Info_MAC, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_MAC, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_MAC, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_MAC, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_MAC, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_MAC, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_MAC, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_MAC, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_MAC, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_IP = lv_label_create(ui_Info_Container5); + lv_obj_set_width(ui_Label_Info_IP, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_IP, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_IP, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_IP, "127.0.0.1"); + lv_obj_remove_flag(ui_Label_Info_IP, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_IP, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_IP, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_IP, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_IP, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_IP, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_IP, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_IP, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_IP, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Serial = lv_label_create(ui_Info_Container5); + lv_obj_set_width(ui_Label_Info_Serial, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_Serial, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Serial, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Serial, "00000001"); + lv_obj_remove_flag(ui_Label_Info_Serial, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Serial, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Serial, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_Serial, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Serial, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Serial, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Serial, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Serial, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Serial, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_PSRAM_Free = lv_label_create(ui_Info_Container5); + lv_obj_set_width(ui_Label_Info_PSRAM_Free, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_PSRAM_Free, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_PSRAM_Free, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_PSRAM_Free, "0 MB"); + lv_obj_remove_flag(ui_Label_Info_PSRAM_Free, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_PSRAM_Free, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_PSRAM_Free, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_PSRAM_Free, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_PSRAM_Free, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_PSRAM_Free, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_PSRAM_Free, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_PSRAM_Free, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_PSRAM_Free, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_RAM_Free = lv_label_create(ui_Info_Container5); + lv_obj_set_width(ui_Label_Info_RAM_Free, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_RAM_Free, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_RAM_Free, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_RAM_Free, "0 MB"); + lv_obj_remove_flag(ui_Label_Info_RAM_Free, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_RAM_Free, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_RAM_Free, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_RAM_Free, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_RAM_Free, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_RAM_Free, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_RAM_Free, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_RAM_Free, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_RAM_Free, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Panel_Battery = lv_obj_create(ui_Panel_Info_Content); + lv_obj_set_width(ui_Panel_Battery, 300); + lv_obj_set_height(ui_Panel_Battery, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Panel_Battery, LV_ALIGN_CENTER); + lv_obj_set_flex_flow(ui_Panel_Battery, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Panel_Battery, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_remove_flag(ui_Panel_Battery, + LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_style_radius(ui_Panel_Battery, 6, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(ui_Panel_Battery, lv_color_hex(0x252525), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Panel_Battery, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Panel_Battery, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Panel_Battery, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Panel_Battery, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Panel_Battery, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Battery = lv_label_create(ui_Panel_Battery); + lv_obj_set_width(ui_Label_Info_Battery, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Battery, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Battery, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Battery, "BATTERY"); + lv_obj_remove_flag(ui_Label_Info_Battery, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Battery, lv_color_hex(0xB998FF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Battery, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Battery, &lv_font_montserrat_14, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Container_Battery = lv_obj_create(ui_Panel_Battery); + lv_obj_remove_style_all(ui_Container_Battery); + lv_obj_set_width(ui_Container_Battery, lv_pct(100)); + lv_obj_set_height(ui_Container_Battery, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Container_Battery, LV_ALIGN_TOP_MID); + lv_obj_set_flex_flow(ui_Container_Battery, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(ui_Container_Battery, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_remove_flag(ui_Container_Battery, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_obj_set_style_pad_left(ui_Container_Battery, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Container_Battery, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Container_Battery, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Container_Battery, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Info_Container2 = lv_obj_create(ui_Container_Battery); + lv_obj_remove_style_all(ui_Info_Container2); + lv_obj_set_width(ui_Info_Container2, lv_pct(50)); + lv_obj_set_height(ui_Info_Container2, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Info_Container2, LV_ALIGN_LEFT_MID); + lv_obj_set_flex_flow(ui_Info_Container2, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Info_Container2, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_remove_flag(ui_Info_Container2, + LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Label_Info_Battery_Status_Name = lv_label_create(ui_Info_Container2); + lv_obj_set_width(ui_Label_Info_Battery_Status_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Battery_Status_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Battery_Status_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Battery_Status_Name, "Status:"); + lv_obj_remove_flag(ui_Label_Info_Battery_Status_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Battery_Status_Name, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Battery_Status_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Battery_Status_Name, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Battery_Status_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Battery_Status_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Battery_Status_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Battery_Status_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Battery_Voltage_Name = lv_label_create(ui_Info_Container2); + lv_obj_set_width(ui_Label_Info_Battery_Voltage_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Battery_Voltage_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Battery_Voltage_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Battery_Voltage_Name, "Voltage:"); + lv_obj_remove_flag(ui_Label_Info_Battery_Voltage_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Battery_Voltage_Name, lv_color_hex(0xFFFFFF), + LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Battery_Voltage_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Battery_Voltage_Name, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Battery_Voltage_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Battery_Voltage_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Battery_Voltage_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Battery_Voltage_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Battery_Remaining_Name = lv_label_create(ui_Info_Container2); + lv_obj_set_width(ui_Label_Info_Battery_Remaining_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Battery_Remaining_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Battery_Remaining_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Battery_Remaining_Name, "Remaining:"); + lv_obj_remove_flag(ui_Label_Info_Battery_Remaining_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Battery_Remaining_Name, lv_color_hex(0xFFFFFF), + LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Battery_Remaining_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Battery_Remaining_Name, &lv_font_montserrat_12, + LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Battery_Remaining_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Battery_Remaining_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Battery_Remaining_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Battery_Remaining_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Info_Container3 = lv_obj_create(ui_Container_Battery); + lv_obj_remove_style_all(ui_Info_Container3); + lv_obj_set_width(ui_Info_Container3, lv_pct(50)); + lv_obj_set_height(ui_Info_Container3, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Info_Container3, LV_ALIGN_BOTTOM_RIGHT); + lv_obj_set_flex_flow(ui_Info_Container3, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Info_Container3, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_END); + lv_obj_remove_flag(ui_Info_Container3, + LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Label_Info_Battery_Status = lv_label_create(ui_Info_Container3); + lv_obj_set_width(ui_Label_Info_Battery_Status, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_Battery_Status, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Battery_Status, LV_ALIGN_TOP_RIGHT); + lv_label_set_text(ui_Label_Info_Battery_Status, "Nothing"); + lv_obj_remove_flag(ui_Label_Info_Battery_Status, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Battery_Status, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Battery_Status, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_Battery_Status, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Battery_Status, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Battery_Status, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Battery_Status, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Battery_Status, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Battery_Status, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Battery_Voltage = lv_label_create(ui_Info_Container3); + lv_obj_set_width(ui_Label_Info_Battery_Voltage, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_Battery_Voltage, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Battery_Voltage, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Battery_Voltage, "4.2 V"); + lv_obj_remove_flag(ui_Label_Info_Battery_Voltage, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Battery_Voltage, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Battery_Voltage, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_Battery_Voltage, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Battery_Voltage, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Battery_Voltage, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Battery_Voltage, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Battery_Voltage, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Battery_Voltage, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Battery_Remaining = lv_label_create(ui_Info_Container3); + lv_obj_set_width(ui_Label_Info_Battery_Remaining, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_Battery_Remaining, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Battery_Remaining, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Battery_Remaining, "75%"); + lv_obj_remove_flag(ui_Label_Info_Battery_Remaining, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Battery_Remaining, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Battery_Remaining, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_Battery_Remaining, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Battery_Remaining, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Battery_Remaining, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Battery_Remaining, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Battery_Remaining, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Battery_Remaining, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Info_Bar2 = lv_bar_create(ui_Panel_Battery); + lv_bar_set_value(ui_Info_Bar2, 100, LV_ANIM_OFF); + lv_bar_set_start_value(ui_Info_Bar2, 0, LV_ANIM_OFF); + lv_obj_set_height(ui_Info_Bar2, 15); + lv_obj_set_width(ui_Info_Bar2, lv_pct(100)); + lv_obj_set_align(ui_Info_Bar2, LV_ALIGN_BOTTOM_MID); + lv_obj_set_style_bg_color(ui_Info_Bar2, lv_color_hex(0x333333), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Info_Bar2, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Info_Bar2, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Info_Bar2, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Info_Bar2, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Info_Bar2, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_set_style_radius(ui_Info_Bar2, 4, LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(ui_Info_Bar2, lv_color_hex(0xFF0000), LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Info_Bar2, 255, LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_bg_grad_color(ui_Info_Bar2, lv_color_hex(0x61FF00), LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_bg_grad_dir(ui_Info_Bar2, LV_GRAD_DIR_HOR, LV_PART_INDICATOR | LV_STATE_DEFAULT); + + //Compensating for LVGL9.1 draw crash with bar/slider max value when top-padding is nonzero and right-padding is 0 + if(lv_obj_get_style_pad_top(ui_Info_Bar2, LV_PART_MAIN) > 0) lv_obj_set_style_pad_right(ui_Info_Bar2, + lv_obj_get_style_pad_right(ui_Info_Bar2, LV_PART_MAIN) + 1, LV_PART_MAIN); + ui_Panel_Lepton = lv_obj_create(ui_Panel_Info_Content); + lv_obj_set_width(ui_Panel_Lepton, 300); + lv_obj_set_height(ui_Panel_Lepton, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_Panel_Lepton, 0); + lv_obj_set_y(ui_Panel_Lepton, 80); + lv_obj_set_align(ui_Panel_Lepton, LV_ALIGN_CENTER); + lv_obj_set_flex_flow(ui_Panel_Lepton, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Panel_Lepton, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_remove_flag(ui_Panel_Lepton, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | + LV_OBJ_FLAG_GESTURE_BUBBLE | LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | + LV_OBJ_FLAG_SCROLL_MOMENTUM | LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_style_radius(ui_Panel_Lepton, 6, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(ui_Panel_Lepton, lv_color_hex(0x252525), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Panel_Lepton, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Panel_Lepton, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Panel_Lepton, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Panel_Lepton, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Panel_Lepton, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton = lv_label_create(ui_Panel_Lepton); + lv_obj_set_width(ui_Label_Info_Lepton, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Lepton, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton, "LEPTON"); + lv_obj_remove_flag(ui_Label_Info_Lepton, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton, lv_color_hex(0xB998FF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton, &lv_font_montserrat_14, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Container_Lepton = lv_obj_create(ui_Panel_Lepton); + lv_obj_remove_style_all(ui_Container_Lepton); + lv_obj_set_width(ui_Container_Lepton, lv_pct(100)); + lv_obj_set_height(ui_Container_Lepton, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Container_Lepton, LV_ALIGN_TOP_MID); + lv_obj_set_flex_flow(ui_Container_Lepton, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(ui_Container_Lepton, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_remove_flag(ui_Container_Lepton, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Info_Container1 = lv_obj_create(ui_Container_Lepton); + lv_obj_remove_style_all(ui_Info_Container1); + lv_obj_set_width(ui_Info_Container1, lv_pct(50)); + lv_obj_set_height(ui_Info_Container1, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Info_Container1, LV_ALIGN_CENTER); + lv_obj_set_flex_flow(ui_Info_Container1, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Info_Container1, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_remove_flag(ui_Info_Container1, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Label_Info_Lepton_Serial_Name = lv_label_create(ui_Info_Container1); + lv_obj_set_width(ui_Label_Info_Lepton_Serial_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Lepton_Serial_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_Serial_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_Serial_Name, "Serial:"); + lv_obj_remove_flag(ui_Label_Info_Lepton_Serial_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_Serial_Name, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_Serial_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_Serial_Name, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_Serial_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_Serial_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_Serial_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_Serial_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_Part_Name = lv_label_create(ui_Info_Container1); + lv_obj_set_width(ui_Label_Info_Lepton_Part_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Lepton_Part_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_Part_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_Part_Name, "Part Number:"); + lv_obj_remove_flag(ui_Label_Info_Lepton_Part_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_Part_Name, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_Part_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_Part_Name, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_Part_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_Part_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_Part_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_Part_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_GPP_Revision_Name = lv_label_create(ui_Info_Container1); + lv_obj_set_width(ui_Label_Info_Lepton_GPP_Revision_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Lepton_GPP_Revision_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_GPP_Revision_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_GPP_Revision_Name, "GPP Revision:"); + lv_obj_remove_flag(ui_Label_Info_Lepton_GPP_Revision_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_GPP_Revision_Name, lv_color_hex(0xFFFFFF), + LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_GPP_Revision_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_GPP_Revision_Name, &lv_font_montserrat_12, + LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_GPP_Revision_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_GPP_Revision_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_GPP_Revision_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_GPP_Revision_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_DSP_Revision_Main = lv_label_create(ui_Info_Container1); + lv_obj_set_width(ui_Label_Info_Lepton_DSP_Revision_Main, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Lepton_DSP_Revision_Main, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_DSP_Revision_Main, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_DSP_Revision_Main, "DSP Revision:"); + lv_obj_remove_flag(ui_Label_Info_Lepton_DSP_Revision_Main, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_DSP_Revision_Main, lv_color_hex(0xFFFFFF), + LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_DSP_Revision_Main, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_DSP_Revision_Main, &lv_font_montserrat_12, + LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_DSP_Revision_Main, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_DSP_Revision_Main, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_DSP_Revision_Main, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_DSP_Revision_Main, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_Uptime_Name = lv_label_create(ui_Info_Container1); + lv_obj_set_width(ui_Label_Info_Lepton_Uptime_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Lepton_Uptime_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_Uptime_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_Uptime_Name, "Uptime:"); + lv_obj_remove_flag(ui_Label_Info_Lepton_Uptime_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_Uptime_Name, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_Uptime_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_Uptime_Name, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_Uptime_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_Uptime_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_Uptime_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_Uptime_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_FPA_Name = lv_label_create(ui_Info_Container1); + lv_obj_set_width(ui_Label_Info_Lepton_FPA_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Lepton_FPA_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_Label_Info_Lepton_FPA_Name, 0); + lv_obj_set_y(ui_Label_Info_Lepton_FPA_Name, 1); + lv_obj_set_align(ui_Label_Info_Lepton_FPA_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_FPA_Name, "FPA Temperature:"); + lv_obj_remove_flag(ui_Label_Info_Lepton_FPA_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_FPA_Name, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_FPA_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_FPA_Name, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_FPA_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_FPA_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_FPA_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_FPA_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_AUX_Name = lv_label_create(ui_Info_Container1); + lv_obj_set_width(ui_Label_Info_Lepton_AUX_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Lepton_AUX_Name, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_AUX_Name, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_AUX_Name, "AUX Temperature:"); + lv_obj_remove_flag(ui_Label_Info_Lepton_AUX_Name, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_AUX_Name, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_AUX_Name, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_AUX_Name, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_AUX_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_AUX_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_AUX_Name, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_AUX_Name, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Info_Container7 = lv_obj_create(ui_Container_Lepton); + lv_obj_remove_style_all(ui_Info_Container7); + lv_obj_set_width(ui_Info_Container7, lv_pct(50)); + lv_obj_set_height(ui_Info_Container7, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_Info_Container7, 172); + lv_obj_set_y(ui_Info_Container7, -2); + lv_obj_set_align(ui_Info_Container7, LV_ALIGN_CENTER); + lv_obj_set_flex_flow(ui_Info_Container7, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Info_Container7, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_END); + lv_obj_remove_flag(ui_Info_Container7, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Label_Info_Lepton_Serial = lv_label_create(ui_Info_Container7); + lv_obj_set_width(ui_Label_Info_Lepton_Serial, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_Lepton_Serial, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_Serial, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_Serial, "Nothing"); + lv_obj_remove_flag(ui_Label_Info_Lepton_Serial, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_Serial, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_Serial, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_Lepton_Serial, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_Serial, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_Serial, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_Serial, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_Serial, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_Serial, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_Part = lv_label_create(ui_Info_Container7); + lv_obj_set_width(ui_Label_Info_Lepton_Part, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_Lepton_Part, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_Part, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_Part, "Nothing"); + lv_obj_remove_flag(ui_Label_Info_Lepton_Part, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_Part, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_Part, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_Lepton_Part, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_Part, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_Part, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_Part, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_Part, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_Part, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_GPP_Revision = lv_label_create(ui_Info_Container7); + lv_obj_set_width(ui_Label_Info_Lepton_GPP_Revision, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_Lepton_GPP_Revision, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_GPP_Revision, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_GPP_Revision, "Nothing"); + lv_obj_remove_flag(ui_Label_Info_Lepton_GPP_Revision, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_GPP_Revision, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_GPP_Revision, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_Lepton_GPP_Revision, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_GPP_Revision, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_GPP_Revision, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_GPP_Revision, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_GPP_Revision, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_GPP_Revision, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_DSP_Revision = lv_label_create(ui_Info_Container7); + lv_obj_set_width(ui_Label_Info_Lepton_DSP_Revision, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_Lepton_DSP_Revision, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_DSP_Revision, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_DSP_Revision, "Nothing"); + lv_obj_remove_flag(ui_Label_Info_Lepton_DSP_Revision, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_DSP_Revision, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_DSP_Revision, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_Lepton_DSP_Revision, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_DSP_Revision, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_DSP_Revision, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_DSP_Revision, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_DSP_Revision, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_DSP_Revision, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_Uptime = lv_label_create(ui_Info_Container7); + lv_obj_set_width(ui_Label_Info_Lepton_Uptime, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_Lepton_Uptime, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_Uptime, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_Uptime, "0"); + lv_obj_remove_flag(ui_Label_Info_Lepton_Uptime, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_Uptime, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_Uptime, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_Lepton_Uptime, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_Uptime, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_Uptime, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_Uptime, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_Uptime, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_Uptime, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_FPA = lv_label_create(ui_Info_Container7); + lv_obj_set_width(ui_Label_Info_Lepton_FPA, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_Lepton_FPA, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_FPA, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_FPA, "0"); + lv_obj_remove_flag(ui_Label_Info_Lepton_FPA, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_FPA, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_FPA, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_Lepton_FPA, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_FPA, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_FPA, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_FPA, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_FPA, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_FPA, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Lepton_AUX = lv_label_create(ui_Info_Container7); + lv_obj_set_width(ui_Label_Info_Lepton_AUX, lv_pct(100)); + lv_obj_set_height(ui_Label_Info_Lepton_AUX, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Lepton_AUX, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Lepton_AUX, "0"); + lv_obj_remove_flag(ui_Label_Info_Lepton_AUX, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Info_Lepton_AUX, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Info_Lepton_AUX, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_Info_Lepton_AUX, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Info_Lepton_AUX, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Info_Lepton_AUX, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Info_Lepton_AUX, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Info_Lepton_AUX, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Info_Lepton_AUX, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Container_Info_Buttons = lv_obj_create(ui_Info); + lv_obj_remove_style_all(ui_Container_Info_Buttons); + lv_obj_set_width(ui_Container_Info_Buttons, lv_pct(100)); + lv_obj_set_height(ui_Container_Info_Buttons, lv_pct(10)); + lv_obj_set_x(ui_Container_Info_Buttons, 0); + lv_obj_set_y(ui_Container_Info_Buttons, 100); + lv_obj_set_align(ui_Container_Info_Buttons, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Container_Info_Buttons, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Button_Info_Back = lv_button_create(ui_Container_Info_Buttons); + lv_obj_set_width(ui_Button_Info_Back, 50); + lv_obj_set_height(ui_Button_Info_Back, lv_pct(95)); + lv_obj_set_x(ui_Button_Info_Back, -80); + lv_obj_set_y(ui_Button_Info_Back, 0); + lv_obj_set_align(ui_Button_Info_Back, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Button_Info_Back, + LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_style_bg_color(ui_Button_Info_Back, lv_color_hex(0x323232), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Button_Info_Back, 200, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Button_Info_Back, lv_color_hex(0xB998FF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Button_Info_Back, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Button_Info_Back, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Info_Back = lv_label_create(ui_Button_Info_Back); + lv_obj_set_width(ui_Label_Info_Back, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Info_Back, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Info_Back, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Info_Back, "B"); + lv_obj_set_style_text_font(ui_Label_Info_Back, &ui_font_fa, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_add_event_cb(ui_Button_Info_Back, ui_event_Button_Info_Back, LV_EVENT_ALL, NULL); + lv_obj_add_event_cb(ui_Info, ui_event_Info, LV_EVENT_ALL, NULL); + +} + +void ui_Info_screen_destroy(void) +{ + if(ui_Info) lv_obj_del(ui_Info); + + // NULL screen variables + ui_Info = NULL; + ui_Panel_Info_Header = NULL; + ui_Label_Info_Header = NULL; + ui_Panel_Info_Content = NULL; + ui_Panel_Device = NULL; + ui_Label_Info_Device = NULL; + ui_Container_Info_Device = NULL; + ui_Info_Container4 = NULL; + ui_Label_Info_MAC_Name = NULL; + ui_Label_Info_IP_Name = NULL; + ui_Label_Info_Serial_Name = NULL; + ui_Label_Info_PSRAM_Free_Name = NULL; + ui_Label_Info_RAM_Free_Name = NULL; + ui_Info_Container5 = NULL; + ui_Label_Info_MAC = NULL; + ui_Label_Info_IP = NULL; + ui_Label_Info_Serial = NULL; + ui_Label_Info_PSRAM_Free = NULL; + ui_Label_Info_RAM_Free = NULL; + ui_Panel_Battery = NULL; + ui_Label_Info_Battery = NULL; + ui_Container_Battery = NULL; + ui_Info_Container2 = NULL; + ui_Label_Info_Battery_Status_Name = NULL; + ui_Label_Info_Battery_Voltage_Name = NULL; + ui_Label_Info_Battery_Remaining_Name = NULL; + ui_Info_Container3 = NULL; + ui_Label_Info_Battery_Status = NULL; + ui_Label_Info_Battery_Voltage = NULL; + ui_Label_Info_Battery_Remaining = NULL; + ui_Info_Bar2 = NULL; + ui_Panel_Lepton = NULL; + ui_Label_Info_Lepton = NULL; + ui_Container_Lepton = NULL; + ui_Info_Container1 = NULL; + ui_Label_Info_Lepton_Serial_Name = NULL; + ui_Label_Info_Lepton_Part_Name = NULL; + ui_Label_Info_Lepton_GPP_Revision_Name = NULL; + ui_Label_Info_Lepton_DSP_Revision_Main = NULL; + ui_Label_Info_Lepton_Uptime_Name = NULL; + ui_Label_Info_Lepton_FPA_Name = NULL; + ui_Label_Info_Lepton_AUX_Name = NULL; + ui_Info_Container7 = NULL; + ui_Label_Info_Lepton_Serial = NULL; + ui_Label_Info_Lepton_Part = NULL; + ui_Label_Info_Lepton_GPP_Revision = NULL; + ui_Label_Info_Lepton_DSP_Revision = NULL; + ui_Label_Info_Lepton_Uptime = NULL; + ui_Label_Info_Lepton_FPA = NULL; + ui_Label_Info_Lepton_AUX = NULL; + ui_Container_Info_Buttons = NULL; + ui_Button_Info_Back = NULL; + ui_Label_Info_Back = NULL; + +} diff --git a/main/Application/Tasks/GUI/Export/screens/ui_Info.h b/main/Application/Tasks/GUI/Export/screens/ui_Info.h new file mode 100644 index 0000000..6c2ae0d --- /dev/null +++ b/main/Application/Tasks/GUI/Export/screens/ui_Info.h @@ -0,0 +1,78 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#ifndef UI_INFO_H +#define UI_INFO_H + +#ifdef __cplusplus +extern "C" { +#endif + +// SCREEN: ui_Info +extern void ui_Info_screen_init(void); +extern void ui_Info_screen_destroy(void); +extern void ui_event_Info(lv_event_t * e); +extern lv_obj_t * ui_Info; +extern lv_obj_t * ui_Panel_Info_Header; +extern lv_obj_t * ui_Label_Info_Header; +extern lv_obj_t * ui_Panel_Info_Content; +extern lv_obj_t * ui_Panel_Device; +extern lv_obj_t * ui_Label_Info_Device; +extern lv_obj_t * ui_Container_Info_Device; +extern lv_obj_t * ui_Info_Container4; +extern lv_obj_t * ui_Label_Info_MAC_Name; +extern lv_obj_t * ui_Label_Info_IP_Name; +extern lv_obj_t * ui_Label_Info_Serial_Name; +extern lv_obj_t * ui_Label_Info_PSRAM_Free_Name; +extern lv_obj_t * ui_Label_Info_RAM_Free_Name; +extern lv_obj_t * ui_Info_Container5; +extern lv_obj_t * ui_Label_Info_MAC; +extern lv_obj_t * ui_Label_Info_IP; +extern lv_obj_t * ui_Label_Info_Serial; +extern lv_obj_t * ui_Label_Info_PSRAM_Free; +extern lv_obj_t * ui_Label_Info_RAM_Free; +extern lv_obj_t * ui_Panel_Battery; +extern lv_obj_t * ui_Label_Info_Battery; +extern lv_obj_t * ui_Container_Battery; +extern lv_obj_t * ui_Info_Container2; +extern lv_obj_t * ui_Label_Info_Battery_Status_Name; +extern lv_obj_t * ui_Label_Info_Battery_Voltage_Name; +extern lv_obj_t * ui_Label_Info_Battery_Remaining_Name; +extern lv_obj_t * ui_Info_Container3; +extern lv_obj_t * ui_Label_Info_Battery_Status; +extern lv_obj_t * ui_Label_Info_Battery_Voltage; +extern lv_obj_t * ui_Label_Info_Battery_Remaining; +extern lv_obj_t * ui_Info_Bar2; +extern lv_obj_t * ui_Panel_Lepton; +extern lv_obj_t * ui_Label_Info_Lepton; +extern lv_obj_t * ui_Container_Lepton; +extern lv_obj_t * ui_Info_Container1; +extern lv_obj_t * ui_Label_Info_Lepton_Serial_Name; +extern lv_obj_t * ui_Label_Info_Lepton_Part_Name; +extern lv_obj_t * ui_Label_Info_Lepton_GPP_Revision_Name; +extern lv_obj_t * ui_Label_Info_Lepton_DSP_Revision_Main; +extern lv_obj_t * ui_Label_Info_Lepton_Uptime_Name; +extern lv_obj_t * ui_Label_Info_Lepton_FPA_Name; +extern lv_obj_t * ui_Label_Info_Lepton_AUX_Name; +extern lv_obj_t * ui_Info_Container7; +extern lv_obj_t * ui_Label_Info_Lepton_Serial; +extern lv_obj_t * ui_Label_Info_Lepton_Part; +extern lv_obj_t * ui_Label_Info_Lepton_GPP_Revision; +extern lv_obj_t * ui_Label_Info_Lepton_DSP_Revision; +extern lv_obj_t * ui_Label_Info_Lepton_Uptime; +extern lv_obj_t * ui_Label_Info_Lepton_FPA; +extern lv_obj_t * ui_Label_Info_Lepton_AUX; +extern lv_obj_t * ui_Container_Info_Buttons; +extern void ui_event_Button_Info_Back(lv_event_t * e); +extern lv_obj_t * ui_Button_Info_Back; +extern lv_obj_t * ui_Label_Info_Back; +// CUSTOM VARIABLES + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif + diff --git a/main/Application/Tasks/GUI/Export/screens/ui_Main.c b/main/Application/Tasks/GUI/Export/screens/ui_Main.c new file mode 100644 index 0000000..b7f90d8 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/screens/ui_Main.c @@ -0,0 +1,492 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#include "../ui.h" + +lv_obj_t * ui_Main = NULL; +lv_obj_t * ui_Container_Main_StatusBar = NULL; +lv_obj_t * ui_Image_Main_WiFi = NULL; +lv_obj_t * ui_Image_Main_SDCard = NULL; +lv_obj_t * ui_Label_Main_Time = NULL; +lv_obj_t * ui_Label_Main_Battery_Remaining = NULL; +lv_obj_t * ui_Container_Main_Thermal = NULL; +lv_obj_t * ui_Image_Thermal = NULL; +lv_obj_t * ui_Image_Main_Thermal_Scene_ROI = NULL; +lv_obj_t * ui_Image_Main_Thermal_Spotmeter_ROI = NULL; +lv_obj_t * ui_Image_Main_Thermal_Video_Focus_ROI = NULL; +lv_obj_t * ui_Image_Main_Thermal_AGC_ROI = NULL; +lv_obj_t * ui_Label_Main_Thermal_Crosshair = NULL; +lv_obj_t * ui_Label_Main_Thermal_PixelTemperature = NULL; +lv_obj_t * ui_Container_Main_Thermal_Scene_Statistics = NULL; +lv_obj_t * ui_Label_Main_Thermal_Scene_Max = NULL; +lv_obj_t * ui_Label_Main_Thermal_Scene_Min = NULL; +lv_obj_t * ui_Label_Main_Thermal_Scene_Mean = NULL; +lv_obj_t * ui_Container_Gradient = NULL; +lv_obj_t * ui_Label_TempScaleMax = NULL; +lv_obj_t * ui_Image_Gradient = NULL; +lv_obj_t * ui_Label_TempScaleMin = NULL; +lv_obj_t * ui_Container_Main_Buttons = NULL; +lv_obj_t * ui_Button_Main_Save = NULL; +lv_obj_t * ui_Label_Main_Button_Save = NULL; +lv_obj_t * ui_Button_Main_ROI = NULL; +lv_obj_t * ui_Label_Main_Button_ROI = NULL; +lv_obj_t * ui_Button_Main_Info = NULL; +lv_obj_t * ui_Label_Main_Button_Info = NULL; +lv_obj_t * ui_Button_Main_Menu = NULL; +lv_obj_t * ui_Label_Main_Button_Menu = NULL; +// event funtions +void ui_event_Main(lv_event_t * e) +{ + lv_event_code_t event_code = lv_event_get_code(e); + + if(event_code == LV_EVENT_SCREEN_LOADED) { + ScreenMainLoaded(e); + } +} + +void ui_event_Button_Main_Save(lv_event_t * e) +{ + lv_event_code_t event_code = lv_event_get_code(e); + + if(event_code == LV_EVENT_CLICKED) { + ButtonMainSaveClicked(e); + } +} + +void ui_event_Button_Main_Info(lv_event_t * e) +{ + lv_event_code_t event_code = lv_event_get_code(e); + + if(event_code == LV_EVENT_CLICKED) { + _ui_screen_change(&ui_Info, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_Info_screen_init); + } +} + +void ui_event_Button_Main_Menu(lv_event_t * e) +{ + lv_event_code_t event_code = lv_event_get_code(e); + + if(event_code == LV_EVENT_CLICKED) { + _ui_screen_change(&ui_Menu, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_Menu_screen_init); + } +} + +// build funtions + +void ui_Main_screen_init(void) +{ + ui_Main = lv_obj_create(NULL); + lv_obj_remove_flag(ui_Main, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_obj_set_flex_flow(ui_Main, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Main, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_set_style_bg_color(ui_Main, lv_color_hex(0x1E1E1E), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Main, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Container_Main_StatusBar = lv_obj_create(ui_Main); + lv_obj_remove_style_all(ui_Container_Main_StatusBar); + lv_obj_set_width(ui_Container_Main_StatusBar, 320); + lv_obj_set_height(ui_Container_Main_StatusBar, lv_pct(8)); + lv_obj_set_align(ui_Container_Main_StatusBar, LV_ALIGN_TOP_MID); + lv_obj_remove_flag(ui_Container_Main_StatusBar, + LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_obj_set_style_bg_color(ui_Container_Main_StatusBar, lv_color_hex(0x000000), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Container_Main_StatusBar, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_image_opa(ui_Container_Main_StatusBar, 180, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Image_Main_WiFi = lv_label_create(ui_Container_Main_StatusBar); + lv_obj_set_width(ui_Image_Main_WiFi, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Image_Main_WiFi, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_Image_Main_WiFi, 5); + lv_obj_set_y(ui_Image_Main_WiFi, 0); + lv_label_set_text(ui_Image_Main_WiFi, "W"); + lv_obj_remove_flag(ui_Image_Main_WiFi, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Image_Main_WiFi, lv_color_hex(0xFF0000), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Image_Main_WiFi, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Image_Main_WiFi, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Image_Main_SDCard = lv_label_create(ui_Container_Main_StatusBar); + lv_obj_set_width(ui_Image_Main_SDCard, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Image_Main_SDCard, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_Image_Main_SDCard, 25); + lv_obj_set_y(ui_Image_Main_SDCard, 0); + lv_label_set_text(ui_Image_Main_SDCard, "C"); + lv_obj_remove_flag(ui_Image_Main_SDCard, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Image_Main_SDCard, lv_color_hex(0xFF0000), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Image_Main_SDCard, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Image_Main_SDCard, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Main_Time = lv_label_create(ui_Container_Main_StatusBar); + lv_obj_set_width(ui_Label_Main_Time, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Main_Time, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_Label_Main_Time, 200); + lv_obj_set_y(ui_Label_Main_Time, 0); + lv_label_set_text(ui_Label_Main_Time, "12:34:56"); + lv_obj_remove_flag(ui_Label_Main_Time, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Main_Time, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Main_Time, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Main_Time, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Main_Battery_Remaining = lv_label_create(ui_Container_Main_StatusBar); + lv_obj_set_width(ui_Label_Main_Battery_Remaining, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Main_Battery_Remaining, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_Label_Main_Battery_Remaining, 280); + lv_obj_set_y(ui_Label_Main_Battery_Remaining, 0); + lv_label_set_text(ui_Label_Main_Battery_Remaining, "75%"); + lv_obj_set_style_text_color(ui_Label_Main_Battery_Remaining, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Main_Battery_Remaining, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Main_Battery_Remaining, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Container_Main_Thermal = lv_obj_create(ui_Main); + lv_obj_remove_style_all(ui_Container_Main_Thermal); + lv_obj_set_width(ui_Container_Main_Thermal, lv_pct(100)); + lv_obj_set_height(ui_Container_Main_Thermal, lv_pct(75)); + lv_obj_set_x(ui_Container_Main_Thermal, 0); + lv_obj_set_y(ui_Container_Main_Thermal, 10); + lv_obj_set_align(ui_Container_Main_Thermal, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Container_Main_Thermal, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Image_Thermal = lv_image_create(ui_Container_Main_Thermal); + lv_obj_set_width(ui_Image_Thermal, 240); + lv_obj_set_height(ui_Image_Thermal, 180); + lv_obj_set_x(ui_Image_Thermal, 10); + lv_obj_set_y(ui_Image_Thermal, 0); + lv_obj_set_align(ui_Image_Thermal, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Image_Thermal, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | LV_OBJ_FLAG_SNAPPABLE | + LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_image_set_rotation(ui_Image_Thermal, 1800); + lv_obj_set_style_radius(ui_Image_Thermal, 3, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(ui_Image_Thermal, lv_color_hex(0x000000), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Image_Thermal, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_grad_dir(ui_Image_Thermal, LV_GRAD_DIR_HOR, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Image_Main_Thermal_Scene_ROI = lv_obj_create(ui_Image_Thermal); + lv_obj_set_width(ui_Image_Main_Thermal_Scene_ROI, 240); + lv_obj_set_height(ui_Image_Main_Thermal_Scene_ROI, 180); + lv_obj_set_align(ui_Image_Main_Thermal_Scene_ROI, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Image_Main_Thermal_Scene_ROI, + LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_scrollbar_mode(ui_Image_Main_Thermal_Scene_ROI, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_bg_color(ui_Image_Main_Thermal_Scene_ROI, lv_color_hex(0x000000), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Image_Main_Thermal_Scene_ROI, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Image_Main_Thermal_Scene_ROI, lv_color_hex(0xF03FEE), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Image_Main_Thermal_Scene_ROI, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Image_Main_Thermal_Scene_ROI, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Image_Main_Thermal_Spotmeter_ROI = lv_obj_create(ui_Image_Thermal); + lv_obj_set_width(ui_Image_Main_Thermal_Spotmeter_ROI, 2); + lv_obj_set_height(ui_Image_Main_Thermal_Spotmeter_ROI, 2); + lv_obj_set_x(ui_Image_Main_Thermal_Spotmeter_ROI, -1); + lv_obj_set_y(ui_Image_Main_Thermal_Spotmeter_ROI, -1); + lv_obj_set_align(ui_Image_Main_Thermal_Spotmeter_ROI, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Image_Main_Thermal_Spotmeter_ROI, + LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_scrollbar_mode(ui_Image_Main_Thermal_Spotmeter_ROI, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_bg_color(ui_Image_Main_Thermal_Spotmeter_ROI, lv_color_hex(0x000000), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Image_Main_Thermal_Spotmeter_ROI, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Image_Main_Thermal_Spotmeter_ROI, lv_color_hex(0x7B3FF0), + LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Image_Main_Thermal_Spotmeter_ROI, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Image_Main_Thermal_Spotmeter_ROI, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Image_Main_Thermal_Video_Focus_ROI = lv_obj_create(ui_Image_Thermal); + lv_obj_set_width(ui_Image_Main_Thermal_Video_Focus_ROI, 238); + lv_obj_set_height(ui_Image_Main_Thermal_Video_Focus_ROI, 178); + lv_obj_set_align(ui_Image_Main_Thermal_Video_Focus_ROI, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Image_Main_Thermal_Video_Focus_ROI, + LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_scrollbar_mode(ui_Image_Main_Thermal_Video_Focus_ROI, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_bg_color(ui_Image_Main_Thermal_Video_Focus_ROI, lv_color_hex(0x000000), + LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Image_Main_Thermal_Video_Focus_ROI, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Image_Main_Thermal_Video_Focus_ROI, lv_color_hex(0xEBF03F), + LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Image_Main_Thermal_Video_Focus_ROI, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Image_Main_Thermal_Video_Focus_ROI, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Image_Main_Thermal_AGC_ROI = lv_obj_create(ui_Image_Thermal); + lv_obj_set_width(ui_Image_Main_Thermal_AGC_ROI, 240); + lv_obj_set_height(ui_Image_Main_Thermal_AGC_ROI, 180); + lv_obj_set_align(ui_Image_Main_Thermal_AGC_ROI, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Image_Main_Thermal_AGC_ROI, + LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_scrollbar_mode(ui_Image_Main_Thermal_AGC_ROI, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_style_bg_color(ui_Image_Main_Thermal_AGC_ROI, lv_color_hex(0x000000), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Image_Main_Thermal_AGC_ROI, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Image_Main_Thermal_AGC_ROI, lv_color_hex(0x3F96F0), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Image_Main_Thermal_AGC_ROI, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Image_Main_Thermal_AGC_ROI, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Main_Thermal_Crosshair = lv_label_create(ui_Image_Thermal); + lv_obj_set_width(ui_Label_Main_Thermal_Crosshair, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Main_Thermal_Crosshair, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Main_Thermal_Crosshair, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Main_Thermal_Crosshair, "CH"); + lv_obj_set_style_text_font(ui_Label_Main_Thermal_Crosshair, &ui_font_fa, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Main_Thermal_PixelTemperature = lv_label_create(ui_Image_Thermal); + lv_obj_set_width(ui_Label_Main_Thermal_PixelTemperature, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Main_Thermal_PixelTemperature, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_Label_Main_Thermal_PixelTemperature, 0); + lv_obj_set_y(ui_Label_Main_Thermal_PixelTemperature, -15); + lv_obj_set_align(ui_Label_Main_Thermal_PixelTemperature, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Main_Thermal_PixelTemperature, "101.1"); + lv_obj_set_style_text_color(ui_Label_Main_Thermal_PixelTemperature, lv_color_hex(0xFFFFFF), + LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Main_Thermal_PixelTemperature, 192, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Main_Thermal_PixelTemperature, &lv_font_montserrat_14, + LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Container_Main_Thermal_Scene_Statistics = lv_obj_create(ui_Image_Thermal); + lv_obj_remove_style_all(ui_Container_Main_Thermal_Scene_Statistics); + lv_obj_set_height(ui_Container_Main_Thermal_Scene_Statistics, 20); + lv_obj_set_width(ui_Container_Main_Thermal_Scene_Statistics, lv_pct(100)); + lv_obj_set_x(ui_Container_Main_Thermal_Scene_Statistics, 0); + lv_obj_set_y(ui_Container_Main_Thermal_Scene_Statistics, -5); + lv_obj_set_align(ui_Container_Main_Thermal_Scene_Statistics, LV_ALIGN_BOTTOM_MID); + lv_obj_remove_flag(ui_Container_Main_Thermal_Scene_Statistics, + LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Label_Main_Thermal_Scene_Max = lv_label_create(ui_Container_Main_Thermal_Scene_Statistics); + lv_obj_set_width(ui_Label_Main_Thermal_Scene_Max, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Main_Thermal_Scene_Max, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_Label_Main_Thermal_Scene_Max, 10); + lv_obj_set_y(ui_Label_Main_Thermal_Scene_Max, 0); + lv_obj_set_align(ui_Label_Main_Thermal_Scene_Max, LV_ALIGN_LEFT_MID); + lv_label_set_text(ui_Label_Main_Thermal_Scene_Max, "100.0"); + lv_obj_set_style_text_color(ui_Label_Main_Thermal_Scene_Max, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Main_Thermal_Scene_Max, 192, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Main_Thermal_Scene_Max, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_image_opa(ui_Label_Main_Thermal_Scene_Max, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_left(ui_Label_Main_Thermal_Scene_Max, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_right(ui_Label_Main_Thermal_Scene_Max, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_top(ui_Label_Main_Thermal_Scene_Max, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(ui_Label_Main_Thermal_Scene_Max, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Main_Thermal_Scene_Min = lv_label_create(ui_Container_Main_Thermal_Scene_Statistics); + lv_obj_set_width(ui_Label_Main_Thermal_Scene_Min, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Main_Thermal_Scene_Min, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Main_Thermal_Scene_Min, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Main_Thermal_Scene_Min, "-40.0"); + lv_obj_set_style_text_color(ui_Label_Main_Thermal_Scene_Min, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Main_Thermal_Scene_Min, 192, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Main_Thermal_Scene_Min, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_image_opa(ui_Label_Main_Thermal_Scene_Min, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Main_Thermal_Scene_Mean = lv_label_create(ui_Container_Main_Thermal_Scene_Statistics); + lv_obj_set_width(ui_Label_Main_Thermal_Scene_Mean, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Main_Thermal_Scene_Mean, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_Label_Main_Thermal_Scene_Mean, -10); + lv_obj_set_y(ui_Label_Main_Thermal_Scene_Mean, 0); + lv_obj_set_align(ui_Label_Main_Thermal_Scene_Mean, LV_ALIGN_RIGHT_MID); + lv_label_set_text(ui_Label_Main_Thermal_Scene_Mean, "80.0"); + lv_obj_set_style_text_color(ui_Label_Main_Thermal_Scene_Mean, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Main_Thermal_Scene_Mean, 192, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Main_Thermal_Scene_Mean, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_image_opa(ui_Label_Main_Thermal_Scene_Mean, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Container_Gradient = lv_obj_create(ui_Container_Main_Thermal); + lv_obj_remove_style_all(ui_Container_Gradient); + lv_obj_set_width(ui_Container_Gradient, 40); + lv_obj_set_height(ui_Container_Gradient, 190); + lv_obj_set_x(ui_Container_Gradient, -135); + lv_obj_set_y(ui_Container_Gradient, 0); + lv_obj_set_align(ui_Container_Gradient, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Container_Gradient, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Label_TempScaleMax = lv_label_create(ui_Container_Gradient); + lv_obj_set_height(ui_Label_TempScaleMax, 20); + lv_obj_set_width(ui_Label_TempScaleMax, lv_pct(100)); + lv_obj_set_x(ui_Label_TempScaleMax, 0); + lv_obj_set_y(ui_Label_TempScaleMax, -80); + lv_obj_set_align(ui_Label_TempScaleMax, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_TempScaleMax, "Max"); + lv_obj_set_style_text_color(ui_Label_TempScaleMax, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_TempScaleMax, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_TempScaleMax, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_decor(ui_Label_TempScaleMax, LV_TEXT_DECOR_NONE, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_TempScaleMax, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Image_Gradient = lv_image_create(ui_Container_Gradient); + lv_obj_set_height(ui_Image_Gradient, 150); + lv_obj_set_width(ui_Image_Gradient, lv_pct(50)); + lv_obj_set_align(ui_Image_Gradient, LV_ALIGN_CENTER); + lv_obj_add_flag(ui_Image_Gradient, LV_OBJ_FLAG_CLICKABLE); /// Flags + lv_obj_remove_flag(ui_Image_Gradient, LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_obj_set_style_radius(ui_Image_Gradient, 3, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(ui_Image_Gradient, lv_color_hex(0x000000), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Image_Gradient, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_grad_color(ui_Image_Gradient, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_grad_dir(ui_Image_Gradient, LV_GRAD_DIR_VER, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_TempScaleMin = lv_label_create(ui_Container_Gradient); + lv_obj_set_height(ui_Label_TempScaleMin, 20); + lv_obj_set_width(ui_Label_TempScaleMin, lv_pct(100)); + lv_obj_set_x(ui_Label_TempScaleMin, 0); + lv_obj_set_y(ui_Label_TempScaleMin, 90); + lv_obj_set_align(ui_Label_TempScaleMin, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_TempScaleMin, "Min"); + lv_obj_set_style_text_color(ui_Label_TempScaleMin, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_TempScaleMin, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_Label_TempScaleMin, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_TempScaleMin, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Container_Main_Buttons = lv_obj_create(ui_Main); + lv_obj_remove_style_all(ui_Container_Main_Buttons); + lv_obj_set_width(ui_Container_Main_Buttons, lv_pct(100)); + lv_obj_set_height(ui_Container_Main_Buttons, lv_pct(10)); + lv_obj_set_x(ui_Container_Main_Buttons, 106); + lv_obj_set_y(ui_Container_Main_Buttons, -14); + lv_obj_set_align(ui_Container_Main_Buttons, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Container_Main_Buttons, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Button_Main_Save = lv_button_create(ui_Container_Main_Buttons); + lv_obj_set_width(ui_Button_Main_Save, 50); + lv_obj_set_height(ui_Button_Main_Save, lv_pct(95)); + lv_obj_set_x(ui_Button_Main_Save, 100); + lv_obj_set_y(ui_Button_Main_Save, 0); + lv_obj_set_align(ui_Button_Main_Save, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Button_Main_Save, + LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_style_bg_color(ui_Button_Main_Save, lv_color_hex(0x323232), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Button_Main_Save, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Button_Main_Save, lv_color_hex(0xB998FF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Button_Main_Save, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Button_Main_Save, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Main_Button_Save = lv_label_create(ui_Button_Main_Save); + lv_obj_set_width(ui_Label_Main_Button_Save, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Main_Button_Save, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Main_Button_Save, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Main_Button_Save, "S"); + lv_obj_set_style_text_color(ui_Label_Main_Button_Save, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Main_Button_Save, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Main_Button_Save, &ui_font_fa, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Button_Main_ROI = lv_button_create(ui_Container_Main_Buttons); + lv_obj_set_width(ui_Button_Main_ROI, 50); + lv_obj_set_height(ui_Button_Main_ROI, lv_pct(95)); + lv_obj_set_x(ui_Button_Main_ROI, -20); + lv_obj_set_y(ui_Button_Main_ROI, 0); + lv_obj_set_align(ui_Button_Main_ROI, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Button_Main_ROI, + LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_style_bg_color(ui_Button_Main_ROI, lv_color_hex(0x323232), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Button_Main_ROI, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Button_Main_ROI, lv_color_hex(0xB998FF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Button_Main_ROI, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Button_Main_ROI, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Main_Button_ROI = lv_label_create(ui_Button_Main_ROI); + lv_obj_set_width(ui_Label_Main_Button_ROI, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Main_Button_ROI, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Main_Button_ROI, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Main_Button_ROI, "R"); + lv_obj_set_style_text_color(ui_Label_Main_Button_ROI, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Main_Button_ROI, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Main_Button_ROI, &ui_font_fa, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Button_Main_Info = lv_button_create(ui_Container_Main_Buttons); + lv_obj_set_width(ui_Button_Main_Info, 50); + lv_obj_set_height(ui_Button_Main_Info, lv_pct(95)); + lv_obj_set_x(ui_Button_Main_Info, 40); + lv_obj_set_y(ui_Button_Main_Info, 0); + lv_obj_set_align(ui_Button_Main_Info, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Button_Main_Info, + LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_style_bg_color(ui_Button_Main_Info, lv_color_hex(0x323232), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Button_Main_Info, 200, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Button_Main_Info, lv_color_hex(0xB998FF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Button_Main_Info, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Button_Main_Info, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Main_Button_Info = lv_label_create(ui_Button_Main_Info); + lv_obj_set_width(ui_Label_Main_Button_Info, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Main_Button_Info, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Main_Button_Info, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Main_Button_Info, "I"); + lv_obj_set_style_text_font(ui_Label_Main_Button_Info, &ui_font_fa, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Button_Main_Menu = lv_button_create(ui_Container_Main_Buttons); + lv_obj_set_width(ui_Button_Main_Menu, 50); + lv_obj_set_height(ui_Button_Main_Menu, lv_pct(95)); + lv_obj_set_x(ui_Button_Main_Menu, -80); + lv_obj_set_y(ui_Button_Main_Menu, 0); + lv_obj_set_align(ui_Button_Main_Menu, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Button_Main_Menu, + LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_style_bg_color(ui_Button_Main_Menu, lv_color_hex(0x323232), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Button_Main_Menu, 200, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Button_Main_Menu, lv_color_hex(0xB998FF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Button_Main_Menu, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Button_Main_Menu, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Main_Button_Menu = lv_label_create(ui_Button_Main_Menu); + lv_obj_set_width(ui_Label_Main_Button_Menu, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Main_Button_Menu, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Main_Button_Menu, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Main_Button_Menu, "M"); + lv_obj_set_style_text_font(ui_Label_Main_Button_Menu, &ui_font_fa, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_add_event_cb(ui_Button_Main_Save, ui_event_Button_Main_Save, LV_EVENT_ALL, NULL); + lv_obj_add_event_cb(ui_Button_Main_Info, ui_event_Button_Main_Info, LV_EVENT_ALL, NULL); + lv_obj_add_event_cb(ui_Button_Main_Menu, ui_event_Button_Main_Menu, LV_EVENT_ALL, NULL); + lv_obj_add_event_cb(ui_Main, ui_event_Main, LV_EVENT_ALL, NULL); + +} + +void ui_Main_screen_destroy(void) +{ + if(ui_Main) lv_obj_del(ui_Main); + + // NULL screen variables + ui_Main = NULL; + ui_Container_Main_StatusBar = NULL; + ui_Image_Main_WiFi = NULL; + ui_Image_Main_SDCard = NULL; + ui_Label_Main_Time = NULL; + ui_Label_Main_Battery_Remaining = NULL; + ui_Container_Main_Thermal = NULL; + ui_Image_Thermal = NULL; + ui_Image_Main_Thermal_Scene_ROI = NULL; + ui_Image_Main_Thermal_Spotmeter_ROI = NULL; + ui_Image_Main_Thermal_Video_Focus_ROI = NULL; + ui_Image_Main_Thermal_AGC_ROI = NULL; + ui_Label_Main_Thermal_Crosshair = NULL; + ui_Label_Main_Thermal_PixelTemperature = NULL; + ui_Container_Main_Thermal_Scene_Statistics = NULL; + ui_Label_Main_Thermal_Scene_Max = NULL; + ui_Label_Main_Thermal_Scene_Min = NULL; + ui_Label_Main_Thermal_Scene_Mean = NULL; + ui_Container_Gradient = NULL; + ui_Label_TempScaleMax = NULL; + ui_Image_Gradient = NULL; + ui_Label_TempScaleMin = NULL; + ui_Container_Main_Buttons = NULL; + ui_Button_Main_Save = NULL; + ui_Label_Main_Button_Save = NULL; + ui_Button_Main_ROI = NULL; + ui_Label_Main_Button_ROI = NULL; + ui_Button_Main_Info = NULL; + ui_Label_Main_Button_Info = NULL; + ui_Button_Main_Menu = NULL; + ui_Label_Main_Button_Menu = NULL; + +} diff --git a/main/Application/Tasks/GUI/Export/screens/ui_Main.h b/main/Application/Tasks/GUI/Export/screens/ui_Main.h new file mode 100644 index 0000000..92bcc47 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/screens/ui_Main.h @@ -0,0 +1,58 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#ifndef UI_MAIN_H +#define UI_MAIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +// SCREEN: ui_Main +extern void ui_Main_screen_init(void); +extern void ui_Main_screen_destroy(void); +extern void ui_event_Main(lv_event_t * e); +extern lv_obj_t * ui_Main; +extern lv_obj_t * ui_Container_Main_StatusBar; +extern lv_obj_t * ui_Image_Main_WiFi; +extern lv_obj_t * ui_Image_Main_SDCard; +extern lv_obj_t * ui_Label_Main_Time; +extern lv_obj_t * ui_Label_Main_Battery_Remaining; +extern lv_obj_t * ui_Container_Main_Thermal; +extern lv_obj_t * ui_Image_Thermal; +extern lv_obj_t * ui_Image_Main_Thermal_Scene_ROI; +extern lv_obj_t * ui_Image_Main_Thermal_Spotmeter_ROI; +extern lv_obj_t * ui_Image_Main_Thermal_Video_Focus_ROI; +extern lv_obj_t * ui_Image_Main_Thermal_AGC_ROI; +extern lv_obj_t * ui_Label_Main_Thermal_Crosshair; +extern lv_obj_t * ui_Label_Main_Thermal_PixelTemperature; +extern lv_obj_t * ui_Container_Main_Thermal_Scene_Statistics; +extern lv_obj_t * ui_Label_Main_Thermal_Scene_Max; +extern lv_obj_t * ui_Label_Main_Thermal_Scene_Min; +extern lv_obj_t * ui_Label_Main_Thermal_Scene_Mean; +extern lv_obj_t * ui_Container_Gradient; +extern lv_obj_t * ui_Label_TempScaleMax; +extern lv_obj_t * ui_Image_Gradient; +extern lv_obj_t * ui_Label_TempScaleMin; +extern lv_obj_t * ui_Container_Main_Buttons; +extern void ui_event_Button_Main_Save(lv_event_t * e); +extern lv_obj_t * ui_Button_Main_Save; +extern lv_obj_t * ui_Label_Main_Button_Save; +extern lv_obj_t * ui_Button_Main_ROI; +extern lv_obj_t * ui_Label_Main_Button_ROI; +extern void ui_event_Button_Main_Info(lv_event_t * e); +extern lv_obj_t * ui_Button_Main_Info; +extern lv_obj_t * ui_Label_Main_Button_Info; +extern void ui_event_Button_Main_Menu(lv_event_t * e); +extern lv_obj_t * ui_Button_Main_Menu; +extern lv_obj_t * ui_Label_Main_Button_Menu; +// CUSTOM VARIABLES + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif + diff --git a/main/Application/Tasks/GUI/Export/screens/ui_Menu.c b/main/Application/Tasks/GUI/Export/screens/ui_Menu.c new file mode 100644 index 0000000..719b107 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/screens/ui_Menu.c @@ -0,0 +1,169 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#include "../ui.h" + +lv_obj_t * ui_Menu = NULL; +lv_obj_t * ui_Panel_Menu_Header = NULL; +lv_obj_t * ui_Label_Menu_Header = NULL; +lv_obj_t * ui_Container_Menu = NULL; +lv_obj_t * ui_Container_Menu_Buttons = NULL; +lv_obj_t * ui_Button_Menu_Back = NULL; +lv_obj_t * ui_Label_Menu_Back = NULL; +lv_obj_t * ui_Button_Menu_Save = NULL; +lv_obj_t * ui_Label_Menu_Button_Save = NULL; +// event funtions +void ui_event_Menu(lv_event_t * e) +{ + lv_event_code_t event_code = lv_event_get_code(e); + + if(event_code == LV_EVENT_GESTURE && lv_indev_get_gesture_dir(lv_indev_active()) == LV_DIR_RIGHT) { + lv_indev_wait_release(lv_indev_active()); + _ui_screen_change(&ui_Main, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_Main_screen_init); + } +} + +void ui_event_Button_Menu_Back(lv_event_t * e) +{ + lv_event_code_t event_code = lv_event_get_code(e); + + if(event_code == LV_EVENT_CLICKED) { + _ui_screen_change(&ui_Main, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_Main_screen_init); + } +} + +void ui_event_Button_Menu_Save(lv_event_t * e) +{ + lv_event_code_t event_code = lv_event_get_code(e); + + if(event_code == LV_EVENT_CLICKED) { + ButtonMenuSaveClicked(e); + } +} + +// build funtions + +void ui_Menu_screen_init(void) +{ + ui_Menu = lv_obj_create(NULL); + lv_obj_remove_flag(ui_Menu, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_obj_set_flex_flow(ui_Menu, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(ui_Menu, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_set_style_bg_color(ui_Menu, lv_color_hex(0x1E1E1E), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Menu, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Panel_Menu_Header = lv_obj_create(ui_Menu); + lv_obj_set_width(ui_Panel_Menu_Header, 320); + lv_obj_set_height(ui_Panel_Menu_Header, lv_pct(8)); + lv_obj_set_x(ui_Panel_Menu_Header, 1); + lv_obj_set_y(ui_Panel_Menu_Header, 1); + lv_obj_set_align(ui_Panel_Menu_Header, LV_ALIGN_TOP_MID); + lv_obj_remove_flag(ui_Panel_Menu_Header, + LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_obj_set_style_radius(ui_Panel_Menu_Header, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(ui_Panel_Menu_Header, lv_color_hex(0x7B3FF0), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Panel_Menu_Header, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Panel_Menu_Header, lv_color_hex(0x7B3FF0), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Panel_Menu_Header, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Menu_Header = lv_label_create(ui_Panel_Menu_Header); + lv_obj_set_width(ui_Label_Menu_Header, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Menu_Header, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Menu_Header, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Menu_Header, "MENU"); + lv_obj_remove_flag(ui_Label_Menu_Header, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_Label_Menu_Header, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Menu_Header, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Menu_Header, &lv_font_montserrat_14, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Container_Menu = lv_obj_create(ui_Menu); + lv_obj_remove_style_all(ui_Container_Menu); + lv_obj_set_width(ui_Container_Menu, lv_pct(100)); + lv_obj_set_height(ui_Container_Menu, lv_pct(75)); + lv_obj_set_x(ui_Container_Menu, 0); + lv_obj_set_y(ui_Container_Menu, 15); + lv_obj_set_align(ui_Container_Menu, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Container_Menu, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Container_Menu_Buttons = lv_obj_create(ui_Menu); + lv_obj_remove_style_all(ui_Container_Menu_Buttons); + lv_obj_set_width(ui_Container_Menu_Buttons, lv_pct(100)); + lv_obj_set_height(ui_Container_Menu_Buttons, lv_pct(10)); + lv_obj_set_x(ui_Container_Menu_Buttons, 0); + lv_obj_set_y(ui_Container_Menu_Buttons, 100); + lv_obj_set_align(ui_Container_Menu_Buttons, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Container_Menu_Buttons, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_Button_Menu_Back = lv_button_create(ui_Container_Menu_Buttons); + lv_obj_set_width(ui_Button_Menu_Back, 50); + lv_obj_set_height(ui_Button_Menu_Back, lv_pct(95)); + lv_obj_set_x(ui_Button_Menu_Back, -80); + lv_obj_set_y(ui_Button_Menu_Back, 0); + lv_obj_set_align(ui_Button_Menu_Back, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Button_Menu_Back, + LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_style_bg_color(ui_Button_Menu_Back, lv_color_hex(0x323232), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Button_Menu_Back, 200, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Button_Menu_Back, lv_color_hex(0xB998FF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Button_Menu_Back, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Button_Menu_Back, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Menu_Back = lv_label_create(ui_Button_Menu_Back); + lv_obj_set_width(ui_Label_Menu_Back, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Menu_Back, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Menu_Back, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Menu_Back, "B"); + lv_obj_set_style_text_font(ui_Label_Menu_Back, &ui_font_fa, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Button_Menu_Save = lv_button_create(ui_Container_Menu_Buttons); + lv_obj_set_width(ui_Button_Menu_Save, 50); + lv_obj_set_height(ui_Button_Menu_Save, lv_pct(95)); + lv_obj_set_x(ui_Button_Menu_Save, 100); + lv_obj_set_y(ui_Button_Menu_Save, 0); + lv_obj_set_align(ui_Button_Menu_Save, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_Button_Menu_Save, + LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_ELASTIC | LV_OBJ_FLAG_SCROLL_MOMENTUM | + LV_OBJ_FLAG_SCROLL_CHAIN); /// Flags + lv_obj_set_style_bg_color(ui_Button_Menu_Save, lv_color_hex(0x323232), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Button_Menu_Save, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_color(ui_Button_Menu_Save, lv_color_hex(0xB998FF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_opa(ui_Button_Menu_Save, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(ui_Button_Menu_Save, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_Label_Menu_Button_Save = lv_label_create(ui_Button_Menu_Save); + lv_obj_set_width(ui_Label_Menu_Button_Save, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_Label_Menu_Button_Save, LV_SIZE_CONTENT); /// 1 + lv_obj_set_align(ui_Label_Menu_Button_Save, LV_ALIGN_CENTER); + lv_label_set_text(ui_Label_Menu_Button_Save, "S"); + lv_obj_set_style_text_color(ui_Label_Menu_Button_Save, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_Label_Menu_Button_Save, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_Label_Menu_Button_Save, &ui_font_fa, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_add_event_cb(ui_Button_Menu_Back, ui_event_Button_Menu_Back, LV_EVENT_ALL, NULL); + lv_obj_add_event_cb(ui_Button_Menu_Save, ui_event_Button_Menu_Save, LV_EVENT_ALL, NULL); + lv_obj_add_event_cb(ui_Menu, ui_event_Menu, LV_EVENT_ALL, NULL); + +} + +void ui_Menu_screen_destroy(void) +{ + if(ui_Menu) lv_obj_del(ui_Menu); + + // NULL screen variables + ui_Menu = NULL; + ui_Panel_Menu_Header = NULL; + ui_Label_Menu_Header = NULL; + ui_Container_Menu = NULL; + ui_Container_Menu_Buttons = NULL; + ui_Button_Menu_Back = NULL; + ui_Label_Menu_Back = NULL; + ui_Button_Menu_Save = NULL; + ui_Label_Menu_Button_Save = NULL; + +} diff --git a/main/Application/Tasks/GUI/Export/screens/ui_Menu.h b/main/Application/Tasks/GUI/Export/screens/ui_Menu.h new file mode 100644 index 0000000..654db67 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/screens/ui_Menu.h @@ -0,0 +1,35 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#ifndef UI_MENU_H +#define UI_MENU_H + +#ifdef __cplusplus +extern "C" { +#endif + +// SCREEN: ui_Menu +extern void ui_Menu_screen_init(void); +extern void ui_Menu_screen_destroy(void); +extern void ui_event_Menu(lv_event_t * e); +extern lv_obj_t * ui_Menu; +extern lv_obj_t * ui_Panel_Menu_Header; +extern lv_obj_t * ui_Label_Menu_Header; +extern lv_obj_t * ui_Container_Menu; +extern lv_obj_t * ui_Container_Menu_Buttons; +extern void ui_event_Button_Menu_Back(lv_event_t * e); +extern lv_obj_t * ui_Button_Menu_Back; +extern lv_obj_t * ui_Label_Menu_Back; +extern void ui_event_Button_Menu_Save(lv_event_t * e); +extern lv_obj_t * ui_Button_Menu_Save; +extern lv_obj_t * ui_Label_Menu_Button_Save; +// CUSTOM VARIABLES + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif + diff --git a/main/Application/Tasks/GUI/Export/screens/ui_Splash.c b/main/Application/Tasks/GUI/Export/screens/ui_Splash.c new file mode 100644 index 0000000..e0ab155 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/screens/ui_Splash.c @@ -0,0 +1,137 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#include "../ui.h" + +lv_obj_t * ui_Splash = NULL; +lv_obj_t * ui_SplashScreen_Logo = NULL; +lv_obj_t * ui_SplashScreen_LogoText = NULL; +lv_obj_t * ui_SplashScreen_Subtext = NULL; +lv_obj_t * ui_SplashScreen_LoadingBar = NULL; +lv_obj_t * ui_SplashScreen_StatusText = NULL; +lv_obj_t * ui_SplashScreen_FirmwareVersion = NULL; +// event funtions +void ui_event_Splash(lv_event_t * e) +{ + lv_event_code_t event_code = lv_event_get_code(e); + + if(event_code == LV_EVENT_SCREEN_LOADED) { + ScreenSplashLoaded(e); + } +} + +// build funtions + +void ui_Splash_screen_init(void) +{ + ui_Splash = lv_obj_create(NULL); + lv_obj_remove_flag(ui_Splash, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_SCROLLABLE); /// Flags + lv_obj_set_style_bg_color(ui_Splash, lv_color_hex(0x1E1E1E), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_Splash, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_SplashScreen_Logo = lv_image_create(ui_Splash); + lv_image_set_src(ui_SplashScreen_Logo, &ui_img_logo_80x44_png); + lv_obj_set_width(ui_SplashScreen_Logo, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_SplashScreen_Logo, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_SplashScreen_Logo, 0); + lv_obj_set_y(ui_SplashScreen_Logo, -80); + lv_obj_set_align(ui_SplashScreen_Logo, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_SplashScreen_Logo, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | LV_OBJ_FLAG_SNAPPABLE | + LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_SplashScreen_LogoText = lv_image_create(ui_Splash); + lv_image_set_src(ui_SplashScreen_LogoText, &ui_img_text_218x40_png); + lv_obj_set_width(ui_SplashScreen_LogoText, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_SplashScreen_LogoText, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_SplashScreen_LogoText, 0); + lv_obj_set_y(ui_SplashScreen_LogoText, -20); + lv_obj_set_align(ui_SplashScreen_LogoText, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_SplashScreen_LogoText, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | LV_OBJ_FLAG_SNAPPABLE | + LV_OBJ_FLAG_SCROLLABLE); /// Flags + + ui_SplashScreen_Subtext = lv_label_create(ui_Splash); + lv_obj_set_width(ui_SplashScreen_Subtext, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_SplashScreen_Subtext, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_SplashScreen_Subtext, 0); + lv_obj_set_y(ui_SplashScreen_Subtext, 20); + lv_obj_set_align(ui_SplashScreen_Subtext, LV_ALIGN_CENTER); + lv_label_set_text(ui_SplashScreen_Subtext, "OPEN SOURCE THERMAL CAMERA"); + lv_obj_remove_flag(ui_SplashScreen_Subtext, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_SplashScreen_Subtext, lv_color_hex(0xFFFFFF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_SplashScreen_Subtext, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_SplashScreen_Subtext, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_SplashScreen_LoadingBar = lv_bar_create(ui_Splash); + lv_bar_set_value(ui_SplashScreen_LoadingBar, 1, LV_ANIM_OFF); + lv_bar_set_start_value(ui_SplashScreen_LoadingBar, 0, LV_ANIM_OFF); + lv_obj_set_width(ui_SplashScreen_LoadingBar, 200); + lv_obj_set_height(ui_SplashScreen_LoadingBar, 5); + lv_obj_set_x(ui_SplashScreen_LoadingBar, 0); + lv_obj_set_y(ui_SplashScreen_LoadingBar, 45); + lv_obj_set_align(ui_SplashScreen_LoadingBar, LV_ALIGN_CENTER); + lv_obj_remove_flag(ui_SplashScreen_LoadingBar, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_bg_color(ui_SplashScreen_LoadingBar, lv_color_hex(0x333333), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_SplashScreen_LoadingBar, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_set_style_bg_color(ui_SplashScreen_LoadingBar, lv_color_hex(0xB998FF), LV_PART_INDICATOR | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(ui_SplashScreen_LoadingBar, 255, LV_PART_INDICATOR | LV_STATE_DEFAULT); + + //Compensating for LVGL9.1 draw crash with bar/slider max value when top-padding is nonzero and right-padding is 0 + if(lv_obj_get_style_pad_top(ui_SplashScreen_LoadingBar, + LV_PART_MAIN) > 0) lv_obj_set_style_pad_right(ui_SplashScreen_LoadingBar, + lv_obj_get_style_pad_right(ui_SplashScreen_LoadingBar, LV_PART_MAIN) + 1, LV_PART_MAIN); + ui_SplashScreen_StatusText = lv_label_create(ui_Splash); + lv_obj_set_width(ui_SplashScreen_StatusText, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_SplashScreen_StatusText, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_SplashScreen_StatusText, 0); + lv_obj_set_y(ui_SplashScreen_StatusText, 65); + lv_obj_set_align(ui_SplashScreen_StatusText, LV_ALIGN_CENTER); + lv_label_set_text(ui_SplashScreen_StatusText, "Initialize system..."); + lv_obj_remove_flag(ui_SplashScreen_StatusText, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_SplashScreen_StatusText, lv_color_hex(0xB998FF), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_SplashScreen_StatusText, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_SplashScreen_StatusText, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + + ui_SplashScreen_FirmwareVersion = lv_label_create(ui_Splash); + lv_obj_set_width(ui_SplashScreen_FirmwareVersion, LV_SIZE_CONTENT); /// 1 + lv_obj_set_height(ui_SplashScreen_FirmwareVersion, LV_SIZE_CONTENT); /// 1 + lv_obj_set_x(ui_SplashScreen_FirmwareVersion, 0); + lv_obj_set_y(ui_SplashScreen_FirmwareVersion, 90); + lv_obj_set_align(ui_SplashScreen_FirmwareVersion, LV_ALIGN_CENTER); + lv_label_set_text(ui_SplashScreen_FirmwareVersion, "Firmware 1.0.0\n(c) 2026 PyroVision Project"); + lv_obj_remove_flag(ui_SplashScreen_FirmwareVersion, + LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_CLICK_FOCUSABLE | LV_OBJ_FLAG_GESTURE_BUBBLE | + LV_OBJ_FLAG_SNAPPABLE); /// Flags + lv_obj_set_style_text_color(ui_SplashScreen_FirmwareVersion, lv_color_hex(0x666666), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_opa(ui_SplashScreen_FirmwareVersion, 255, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_align(ui_SplashScreen_FirmwareVersion, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(ui_SplashScreen_FirmwareVersion, &lv_font_montserrat_10, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_add_event_cb(ui_Splash, ui_event_Splash, LV_EVENT_ALL, NULL); + +} + +void ui_Splash_screen_destroy(void) +{ + if(ui_Splash) lv_obj_del(ui_Splash); + + // NULL screen variables + ui_Splash = NULL; + ui_SplashScreen_Logo = NULL; + ui_SplashScreen_LogoText = NULL; + ui_SplashScreen_Subtext = NULL; + ui_SplashScreen_LoadingBar = NULL; + ui_SplashScreen_StatusText = NULL; + ui_SplashScreen_FirmwareVersion = NULL; + +} diff --git a/main/Application/Tasks/GUI/Export/screens/ui_Splash.h b/main/Application/Tasks/GUI/Export/screens/ui_Splash.h new file mode 100644 index 0000000..e14dc0c --- /dev/null +++ b/main/Application/Tasks/GUI/Export/screens/ui_Splash.h @@ -0,0 +1,31 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#ifndef UI_SPLASH_H +#define UI_SPLASH_H + +#ifdef __cplusplus +extern "C" { +#endif + +// SCREEN: ui_Splash +extern void ui_Splash_screen_init(void); +extern void ui_Splash_screen_destroy(void); +extern void ui_event_Splash(lv_event_t * e); +extern lv_obj_t * ui_Splash; +extern lv_obj_t * ui_SplashScreen_Logo; +extern lv_obj_t * ui_SplashScreen_LogoText; +extern lv_obj_t * ui_SplashScreen_Subtext; +extern lv_obj_t * ui_SplashScreen_LoadingBar; +extern lv_obj_t * ui_SplashScreen_StatusText; +extern lv_obj_t * ui_SplashScreen_FirmwareVersion; +// CUSTOM VARIABLES + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif + diff --git a/main/Application/Tasks/GUI/Export/ui.c b/main/Application/Tasks/GUI/Export/ui.c new file mode 100644 index 0000000..1783e2a --- /dev/null +++ b/main/Application/Tasks/GUI/Export/ui.c @@ -0,0 +1,47 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#include "ui.h" +#include "ui_helpers.h" + +///////////////////// VARIABLES //////////////////// + +// EVENTS +lv_obj_t * ui____initial_actions0; + +// IMAGES AND IMAGE SETS + +///////////////////// TEST LVGL SETTINGS //////////////////// +#if LV_COLOR_DEPTH != 16 + #error "LV_COLOR_DEPTH should be 16bit to match SquareLine Studio's settings" +#endif + +///////////////////// ANIMATIONS //////////////////// + +///////////////////// FUNCTIONS //////////////////// + +///////////////////// SCREENS //////////////////// + +void ui_init(void) +{ + lv_disp_t * dispp = lv_display_get_default(); + lv_theme_t * theme = lv_theme_default_init(dispp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED), + false, LV_FONT_DEFAULT); + lv_disp_set_theme(dispp, theme); + ui_Splash_screen_init(); + ui_Main_screen_init(); + ui_Menu_screen_init(); + ui_Info_screen_init(); + ui____initial_actions0 = lv_obj_create(NULL); + lv_disp_load_scr(ui_Splash); +} + +void ui_destroy(void) +{ + ui_Splash_screen_destroy(); + ui_Main_screen_destroy(); + ui_Menu_screen_destroy(); + ui_Info_screen_destroy(); +} diff --git a/main/Application/Tasks/GUI/Export/ui.h b/main/Application/Tasks/GUI/Export/ui.h new file mode 100644 index 0000000..b974ddc --- /dev/null +++ b/main/Application/Tasks/GUI/Export/ui.h @@ -0,0 +1,48 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#ifndef _PYROVISION_UI_H +#define _PYROVISION_UI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "lvgl.h" + +#include "ui_helpers.h" +#include "ui_events.h" + + +///////////////////// SCREENS //////////////////// + +#include "screens/ui_Splash.h" +#include "screens/ui_Main.h" +#include "screens/ui_Menu.h" +#include "screens/ui_Info.h" + +///////////////////// VARIABLES //////////////////// + + +// EVENTS + +extern lv_obj_t * ui____initial_actions0; + +// IMAGES AND IMAGE SETS +LV_IMG_DECLARE(ui_img_logo_80x44_png); // assets/Logo_80x44.png +LV_IMG_DECLARE(ui_img_text_218x40_png); // assets/Text_218x40.png + +// FONTS +LV_FONT_DECLARE(ui_font_fa); + +// UI INIT +void ui_init(void); +void ui_destroy(void); + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif diff --git a/main/Application/Tasks/GUI/Export/ui_events.cpp b/main/Application/Tasks/GUI/Export/ui_events.cpp new file mode 100644 index 0000000..d3c4996 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/ui_events.cpp @@ -0,0 +1,72 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.5.4 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#include +#include + +#include "ui.h" + +#include "../../../application.h" +#include "../guiTask.h" +#include "../UI/ui_settings.h" +#include "../UI/ui_messagebox.h" + +static const char *TAG = "ui_events"; + +void ScreenMainLoaded(lv_event_t *e) +{ + char Buf[128]; + Settings_Info_t Info; + + SettingsManager_GetInfo(&Info); + + snprintf(Buf, sizeof(Buf), "Firmware %s\n(c) 2026 PyroVision Project", Info.FirmwareVersion); + lv_label_set_text(ui_SplashScreen_FirmwareVersion, Buf); + + /* Set the symbols for the UI */ + lv_label_set_text(ui_Image_Main_WiFi, LV_SYMBOL_WIFI); + lv_label_set_text(ui_Image_Main_SDCard, LV_SYMBOL_SD_CARD); + lv_label_set_text(ui_Label_Menu_Button_Save, LV_SYMBOL_SAVE); + lv_label_set_text(ui_Label_Main_Button_Save, LV_SYMBOL_SAVE); + lv_label_set_text(ui_Label_Main_Button_Menu, "\uF0C9"); + lv_label_set_text(ui_Label_Main_Button_Info, "\uF129"); + lv_label_set_text(ui_Label_Main_Button_ROI, "\uE595"); + lv_label_set_text(ui_Label_Main_Thermal_Crosshair, "\uF05B"); + lv_label_set_text(ui_Label_Menu_Back, "\uF060"); + lv_label_set_text(ui_Label_Info_Back, "\uF060"); + + /* Force full screen repaint to clear any artifacts from previous screen */ + lv_obj_invalidate(lv_screen_active()); +} + +void ScreenInfoLoaded(lv_event_t *e) +{ + esp_event_post(GUI_EVENTS, GUI_EVENT_REQUEST_UPTIME, NULL, 0, 0); + esp_event_post(GUI_EVENTS, GUI_EVENT_REQUEST_FPA_AUX_TEMP, NULL, 0, 0); + + /* Force full screen repaint to clear any artifacts from previous screen */ + lv_obj_invalidate(lv_screen_active()); +} + +void ButtonMainWiFiClicked(lv_event_t *e) +{ + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_OPEN_WIFI_REQUEST, NULL, 0, 0); +} + +void ScreenSplashLoaded(lv_event_t *e) +{ + ui_settings_init(ui_Container_Menu); +} + +void ButtonMainSaveClicked(lv_event_t *e) +{ + MessageBox_ImageSaveError(GUI_SaveThermalImage()); +} + +void ButtonMenuSaveClicked(lv_event_t *e) +{ + SettingsManager_Save(); + MessageBox_Show("Settings Saved"); +} \ No newline at end of file diff --git a/main/Application/Tasks/GUI/Export/ui_events.h b/main/Application/Tasks/GUI/Export/ui_events.h new file mode 100644 index 0000000..8102b99 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/ui_events.h @@ -0,0 +1,23 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#ifndef _UI_EVENTS_H +#define _UI_EVENTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +void ScreenSplashLoaded(lv_event_t * e); +void ScreenMainLoaded(lv_event_t * e); +void ButtonMainSaveClicked(lv_event_t * e); +void ButtonMenuSaveClicked(lv_event_t * e); +void ScreenInfoLoaded(lv_event_t * e); + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif diff --git a/main/Application/Tasks/GUI/Export/ui_helpers.c b/main/Application/Tasks/GUI/Export/ui_helpers.c new file mode 100644 index 0000000..7e5ad39 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/ui_helpers.c @@ -0,0 +1,354 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#include "ui_helpers.h" + +void _ui_bar_set_property(lv_obj_t * target, int id, int val) +{ + if(id == _UI_BAR_PROPERTY_VALUE_WITH_ANIM) lv_bar_set_value(target, val, LV_ANIM_ON); + if(id == _UI_BAR_PROPERTY_VALUE) lv_bar_set_value(target, val, LV_ANIM_OFF); +} + +void _ui_basic_set_property(lv_obj_t * target, int id, int val) +{ + if(id == _UI_BASIC_PROPERTY_POSITION_X) lv_obj_set_x(target, val); + if(id == _UI_BASIC_PROPERTY_POSITION_Y) lv_obj_set_y(target, val); + if(id == _UI_BASIC_PROPERTY_WIDTH) lv_obj_set_width(target, val); + if(id == _UI_BASIC_PROPERTY_HEIGHT) lv_obj_set_height(target, val); +} + + +void _ui_dropdown_set_property(lv_obj_t * target, int id, int val) +{ + if(id == _UI_DROPDOWN_PROPERTY_SELECTED) lv_dropdown_set_selected(target, val); +} + +void _ui_image_set_property(lv_obj_t * target, int id, uint8_t * val) +{ + if(id == _UI_IMAGE_PROPERTY_IMAGE) lv_image_set_src(target, val); +} + +void _ui_label_set_property(lv_obj_t * target, int id, const char * val) +{ + if(id == _UI_LABEL_PROPERTY_TEXT) lv_label_set_text(target, val); +} + + +void _ui_roller_set_property(lv_obj_t * target, int id, int val) +{ + if(id == _UI_ROLLER_PROPERTY_SELECTED_WITH_ANIM) lv_roller_set_selected(target, val, LV_ANIM_ON); + if(id == _UI_ROLLER_PROPERTY_SELECTED) lv_roller_set_selected(target, val, LV_ANIM_OFF); +} + +void _ui_slider_set_property(lv_obj_t * target, int id, int val) +{ + if(id == _UI_SLIDER_PROPERTY_VALUE_WITH_ANIM) lv_slider_set_value(target, val, LV_ANIM_ON); + if(id == _UI_SLIDER_PROPERTY_VALUE) lv_slider_set_value(target, val, LV_ANIM_OFF); +} + + +void _ui_screen_change(lv_obj_t ** target, lv_screen_load_anim_t fademode, int spd, int delay, + void (*target_init)(void)) +{ + if(*target == NULL) + target_init(); + lv_screen_load_anim(*target, fademode, spd, delay, false); +} + +void _ui_screen_delete(void (*target)(void)) +{ + if(target != NULL) { + target(); + } +} + +void _ui_arc_increment(lv_obj_t * target, int val) +{ + int old = lv_arc_get_value(target); + lv_arc_set_value(target, old + val); + lv_obj_send_event(target, LV_EVENT_VALUE_CHANGED, 0); +} + +void _ui_bar_increment(lv_obj_t * target, int val, int anm) +{ + int old = lv_bar_get_value(target); + lv_bar_set_value(target, old + val, anm); +} + +void _ui_slider_increment(lv_obj_t * target, int val, int anm) +{ + int old = lv_slider_get_value(target); + lv_slider_set_value(target, old + val, anm); + lv_obj_send_event(target, LV_EVENT_VALUE_CHANGED, 0); +} + +void _ui_keyboard_set_target(lv_obj_t * keyboard, lv_obj_t * textarea) +{ + lv_keyboard_set_textarea(keyboard, textarea); +} + +void _ui_flag_modify(lv_obj_t * target, int32_t flag, int value) +{ + if(value == _UI_MODIFY_FLAG_TOGGLE) { + if(lv_obj_has_flag(target, flag)) lv_obj_remove_flag(target, flag); + else lv_obj_add_flag(target, flag); + } + else if(value == _UI_MODIFY_FLAG_ADD) lv_obj_add_flag(target, flag); + else lv_obj_remove_flag(target, flag); +} +void _ui_state_modify(lv_obj_t * target, int32_t state, int value) +{ + if(value == _UI_MODIFY_STATE_TOGGLE) { + if(lv_obj_has_state(target, state)) lv_obj_remove_state(target, state); + else lv_obj_add_state(target, state); + } + else if(value == _UI_MODIFY_STATE_ADD) lv_obj_add_state(target, state); + else lv_obj_remove_state(target, state); +} + + +void _ui_textarea_move_cursor(lv_obj_t * target, int val) + +{ + + if(val == UI_MOVE_CURSOR_UP) lv_textarea_cursor_up(target); + if(val == UI_MOVE_CURSOR_RIGHT) lv_textarea_cursor_right(target); + if(val == UI_MOVE_CURSOR_DOWN) lv_textarea_cursor_down(target); + if(val == UI_MOVE_CURSOR_LEFT) lv_textarea_cursor_left(target); + lv_obj_add_state(target, LV_STATE_FOCUSED); +} + +typedef void (*screen_destroy_cb_t)(void); + +void scr_unloaded_delete_cb(lv_event_t * e) + +{ + + // Get the destroy callback from user_data + + screen_destroy_cb_t destroy_cb = lv_event_get_user_data(e); + if(destroy_cb) { + + destroy_cb(); // call the specific screen destroy function + + } + +} + +void _ui_opacity_set(lv_obj_t * target, int val) +{ + lv_obj_set_style_opa(target, val, 0); +} + +void _ui_anim_callback_free_user_data(lv_anim_t * a) +{ + lv_free(a->user_data); + a->user_data = NULL; +} + +void _ui_anim_callback_set_x(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_obj_set_x(usr->target, v); + +} + + +void _ui_anim_callback_set_y(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_obj_set_y(usr->target, v); + +} + + +void _ui_anim_callback_set_width(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_obj_set_width(usr->target, v); + +} + + +void _ui_anim_callback_set_height(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_obj_set_height(usr->target, v); + +} + + +void _ui_anim_callback_set_opacity(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_obj_set_style_opa(usr->target, v, 0); + +} + + +void _ui_anim_callback_set_image_zoom(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_image_set_scale(usr->target, v); + +} + + +void _ui_anim_callback_set_image_angle(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + lv_image_set_rotation(usr->target, v); + +} + + +void _ui_anim_callback_set_image_frame(lv_anim_t * a, int32_t v) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + usr->val = v; + + if(v < 0) v = 0; + if(v >= usr->imgset_size) v = usr->imgset_size - 1; + lv_image_set_src(usr->target, usr->imgset[v]); +} + +int32_t _ui_anim_callback_get_x(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_obj_get_x_aligned(usr->target); + +} + + +int32_t _ui_anim_callback_get_y(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_obj_get_y_aligned(usr->target); + +} + + +int32_t _ui_anim_callback_get_width(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_obj_get_width(usr->target); + +} + + +int32_t _ui_anim_callback_get_height(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_obj_get_height(usr->target); + +} + + +int32_t _ui_anim_callback_get_opacity(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_obj_get_style_opa(usr->target, 0); + +} + +int32_t _ui_anim_callback_get_image_zoom(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_image_get_scale(usr->target); + +} + +int32_t _ui_anim_callback_get_image_angle(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return lv_image_get_rotation(usr->target); + +} + +int32_t _ui_anim_callback_get_image_frame(lv_anim_t * a) + +{ + + ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data; + return usr->val; + +} + +void _ui_arc_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix) +{ + char buf[_UI_TEMPORARY_STRING_BUFFER_SIZE]; + + lv_snprintf(buf, sizeof(buf), "%s%d%s", prefix, (int)lv_arc_get_value(src), postfix); + + lv_label_set_text(trg, buf); +} + +void _ui_slider_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix) +{ + char buf[_UI_TEMPORARY_STRING_BUFFER_SIZE]; + + lv_snprintf(buf, sizeof(buf), "%s%d%s", prefix, (int)lv_slider_get_value(src), postfix); + + lv_label_set_text(trg, buf); +} +void _ui_checked_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * txt_on, const char * txt_off) +{ + if(lv_obj_has_state(src, LV_STATE_CHECKED)) lv_label_set_text(trg, txt_on); + else lv_label_set_text(trg, txt_off); +} + + +void _ui_spinbox_step(lv_obj_t * target, int val) + +{ + + if(val > 0) lv_spinbox_increment(target); + + else lv_spinbox_decrement(target); + + + lv_obj_send_event(target, LV_EVENT_VALUE_CHANGED, 0); +} + +void _ui_switch_theme(int val) + +{ + +#ifdef UI_THEME_ACTIVE + ui_theme_set(val); +#endif +} + + diff --git a/main/Application/Tasks/GUI/Export/ui_helpers.h b/main/Application/Tasks/GUI/Export/ui_helpers.h new file mode 100644 index 0000000..cf49380 --- /dev/null +++ b/main/Application/Tasks/GUI/Export/ui_helpers.h @@ -0,0 +1,149 @@ +// This file was generated by SquareLine Studio +// SquareLine Studio version: SquareLine Studio 1.6.0 +// LVGL version: 9.1.0 +// Project name: PyroVision + +#ifndef _PYROVISION_UI_HELPERS_H +#define _PYROVISION_UI_HELPERS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ui.h" + +#define _UI_TEMPORARY_STRING_BUFFER_SIZE 32 +#define _UI_BAR_PROPERTY_VALUE 0 +#define _UI_BAR_PROPERTY_VALUE_WITH_ANIM 1 +void _ui_bar_set_property(lv_obj_t * target, int id, int val); + +#define _UI_BASIC_PROPERTY_POSITION_X 0 +#define _UI_BASIC_PROPERTY_POSITION_Y 1 +#define _UI_BASIC_PROPERTY_WIDTH 2 +#define _UI_BASIC_PROPERTY_HEIGHT 3 +void _ui_basic_set_property(lv_obj_t * target, int id, int val); + +#define _UI_DROPDOWN_PROPERTY_SELECTED 0 +void _ui_dropdown_set_property(lv_obj_t * target, int id, int val); + +#define _UI_IMAGE_PROPERTY_IMAGE 0 +void _ui_image_set_property(lv_obj_t * target, int id, uint8_t * val); + +#define _UI_LABEL_PROPERTY_TEXT 0 +void _ui_label_set_property(lv_obj_t * target, int id, const char * val); + +#define _UI_ROLLER_PROPERTY_SELECTED 0 +#define _UI_ROLLER_PROPERTY_SELECTED_WITH_ANIM 1 +void _ui_roller_set_property(lv_obj_t * target, int id, int val); + +#define _UI_SLIDER_PROPERTY_VALUE 0 +#define _UI_SLIDER_PROPERTY_VALUE_WITH_ANIM 1 +void _ui_slider_set_property(lv_obj_t * target, int id, int val); + +void _ui_screen_change(lv_obj_t ** target, lv_screen_load_anim_t fademode, int spd, int delay, + void (*target_init)(void)); + +void _ui_screen_delete(void (*target)(void)); + +void _ui_arc_increment(lv_obj_t * target, int val); + +void _ui_bar_increment(lv_obj_t * target, int val, int anm); + +void _ui_slider_increment(lv_obj_t * target, int val, int anm); + +void _ui_keyboard_set_target(lv_obj_t * keyboard, lv_obj_t * textarea); + +#define _UI_MODIFY_FLAG_ADD 0 +#define _UI_MODIFY_FLAG_REMOVE 1 +#define _UI_MODIFY_FLAG_TOGGLE 2 +void _ui_flag_modify(lv_obj_t * target, int32_t flag, int value); + +#define _UI_MODIFY_STATE_ADD 0 +#define _UI_MODIFY_STATE_REMOVE 1 +#define _UI_MODIFY_STATE_TOGGLE 2 +void _ui_state_modify(lv_obj_t * target, int32_t state, int value); + +#define UI_MOVE_CURSOR_UP 0 +#define UI_MOVE_CURSOR_RIGHT 1 +#define UI_MOVE_CURSOR_DOWN 2 +#define UI_MOVE_CURSOR_LEFT 3 +void _ui_textarea_move_cursor(lv_obj_t * target, int val) +; + + +void scr_unloaded_delete_cb(lv_event_t * e); + +void _ui_opacity_set(lv_obj_t * target, int val); + +/** Describes an animation*/ +typedef struct _ui_anim_user_data_t { + lv_obj_t * target; + lv_image_dsc_t ** imgset; + int32_t imgset_size; + int32_t val; +} ui_anim_user_data_t; +void _ui_anim_callback_free_user_data(lv_anim_t * a); + +void _ui_anim_callback_set_x(lv_anim_t * a, int32_t v); + +void _ui_anim_callback_set_y(lv_anim_t * a, int32_t v); + +void _ui_anim_callback_set_width(lv_anim_t * a, int32_t v); + +void _ui_anim_callback_set_height(lv_anim_t * a, int32_t v); + + +void _ui_anim_callback_set_opacity(lv_anim_t * a, int32_t v); + + +void _ui_anim_callback_set_image_zoom(lv_anim_t * a, int32_t v); + + +void _ui_anim_callback_set_image_angle(lv_anim_t * a, int32_t v); + + +void _ui_anim_callback_set_image_frame(lv_anim_t * a, int32_t v); + + +int32_t _ui_anim_callback_get_x(lv_anim_t * a); + +int32_t _ui_anim_callback_get_y(lv_anim_t * a); + +int32_t _ui_anim_callback_get_width(lv_anim_t * a); + + +int32_t _ui_anim_callback_get_height(lv_anim_t * a); + + +int32_t _ui_anim_callback_get_opacity(lv_anim_t * a); + + +int32_t _ui_anim_callback_get_image_zoom(lv_anim_t * a); + + +int32_t _ui_anim_callback_get_image_angle(lv_anim_t * a); + + +int32_t _ui_anim_callback_get_image_frame(lv_anim_t * a); + + +void _ui_arc_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix); + +void _ui_slider_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix); + +void _ui_checked_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * txt_on, const char * txt_off); + +void _ui_spinbox_step(lv_obj_t * target, int val) +; + + +void _ui_switch_theme(int val) +; + + + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif diff --git a/main/Application/Tasks/GUI/Private/guiHelper.cpp b/main/Application/Tasks/GUI/Private/guiHelper.cpp new file mode 100644 index 0000000..f654b3f --- /dev/null +++ b/main/Application/Tasks/GUI/Private/guiHelper.cpp @@ -0,0 +1,450 @@ +/* + * guiHelper.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Helper functions for the GUI task. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include + +#include "guiHelper.h" +#include "Application/application.h" +#include "../Export/ui.h" +#include "../UI/ui_settings.h" + +#if defined(CONFIG_LCD_SPI2_HOST) +#define LCD_SPI_HOST SPI2_HOST +#elif defined(CONFIG_LCD_SPI3_HOST) +#define LCD_SPI_HOST SPI3_HOST +#else +#error "No SPI host defined for LCD!" +#endif + +/* Partial render buffer: 1/5 of the screen (2 * 320 * 24 * 2 = 30720 bytes) in PSRAM. + * + * IMPORTANT: PSRAM is NOT in the ESP32-S3 DMA-capable address range (0x3FC88000-0x3FD00000), + * so the SPI driver allocates an internal DMA bounce buffer for EACH queued transaction + * (chunk_size = CONFIG_SPI_TRANSFER_SIZE = 4096 bytes each). The trans_queue_depth MUST be + * kept low (currently 3) to limit simultaneous bounce buffer allocations (3 * 4096 = 12 KB), + * because internal DMA RAM is scarce (~28 KB free after USB init). A higher queue depth + * (e.g. 10 = 8 chunks * 4096 = 32 KB) would exceed free internal DMA RAM and cause + * ESP_ERR_NO_MEM in spi_device_queue_trans, freezing the display. + * + * With ISR-based flush_ready (on_lcd_color_trans_done), LVGL correctly sequences + * partial flushes without tearing. */ +#define GUI_DRAW_BUFFER_SIZE (2 * CONFIG_GUI_WIDTH * CONFIG_GUI_HEIGHT * sizeof(uint16_t) / 10) + +/** @brief LCD color transfer done callback (called from ISR context after DMA transfer completes). + * @param PanelIO Panel IO handle + * @param p_Edata Event data (unused) + * @param p_UserCtx User context (pointer to LVGL display) + */ +static IRAM_ATTR bool on_lcd_color_trans_done(esp_lcd_panel_io_handle_t PanelIO, + esp_lcd_panel_io_event_data_t *p_Edata, + void *p_UserCtx) +{ + (void)PanelIO; + (void)p_Edata; + + lv_display_t *p_Display = static_cast(p_UserCtx); + lv_display_flush_ready(p_Display); + + return false; +} + +static const esp_lcd_panel_io_callbacks_t _GUI_Panel_Callbacks = { + .on_color_trans_done = on_lcd_color_trans_done, +}; + +static const esp_lcd_panel_dev_config_t _GUI_Panel_Config = { + .reset_gpio_num = CONFIG_LCD_RST, + .color_space = ESP_LCD_COLOR_SPACE_BGR, + .data_endian = LCD_RGB_DATA_ENDIAN_BIG, + .bits_per_pixel = 16, + .flags = { + .reset_active_high = 0, + }, + .vendor_config = NULL, +}; + +static const esp_lcd_panel_io_spi_config_t _GUI_Panel_IO_Config = { + .cs_gpio_num = CONFIG_LCD_CS, + .dc_gpio_num = CONFIG_LCD_DC, + .spi_mode = 0, + .pclk_hz = CONFIG_LCD_CLOCK, + /* NOTE: This parameter must be set to a lower value (e.g., 3) to avoid exceeding internal DMA RAM when not using the `psram_mode` flag. + */ + .trans_queue_depth = 3, + .on_color_trans_done = NULL, + .user_ctx = NULL, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .flags = { + .dc_high_on_cmd = 0, + .dc_low_on_data = 0, + .dc_low_on_param = 0, + .octal_mode = 0, + .quad_mode = 0, + .sio_mode = 0, + .lsb_first = 0, + .cs_high_active = 0, + .psram_mode = 1, + }, +}; + +static const esp_lcd_panel_io_i2c_config_t _GUI_Touch_IO_Config = { +#if((CONFIG_TOUCH_IRQ != -1) && (CONFIG_TOUCH_RST != -1)) + .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, +#else + .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP, +#endif + .on_color_trans_done = NULL, + .user_ctx = NULL, + .control_phase_bytes = 1, + .dc_bit_offset = 0, + .lcd_cmd_bits = 16, + .lcd_param_bits = 0, + .flags = + { + .dc_low_on_data = 0, + .disable_control_phase = 1, + }, + .scl_speed_hz = 400000, +}; + +static esp_lcd_touch_io_gt911_config_t _GUI_Touch_GT911_Config = { + .dev_addr = static_cast(_GUI_Touch_IO_Config.dev_addr), +}; + +static const esp_lcd_touch_config_t _GUI_Touch_Config = { + .x_max = CONFIG_GUI_WIDTH, + .y_max = CONFIG_GUI_HEIGHT, + .rst_gpio_num = static_cast(CONFIG_TOUCH_RST), + .int_gpio_num = static_cast(CONFIG_TOUCH_IRQ), + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 1, + .mirror_x = 0, + .mirror_y = 0, + }, + .process_coordinates = NULL, + .interrupt_callback = NULL, + .user_data = NULL, + .driver_data = &_GUI_Touch_GT911_Config, +}; + +static const char *TAG = "GUI-Helper"; + +/** @brief LVGL tick timer callback for GUI task. + * @param p_Arg User data (pointer to GUI_Task_State_t) + */ +inline void GUI_LVGL_TickTimer_CB(void *p_Arg) +{ + lv_tick_inc(CONFIG_GUI_LVGL_TICK_PERIOD_MS); +} + +/** @brief Initialize the GUI helper functions. + * @param p_Disp Display handle + * @param p_Area Area to update + * @param p_PxMap Pixel data to flush + */ +static void GUI_LCD_Flush_CB(lv_display_t *p_Disp, const lv_area_t *p_Area, uint8_t *p_PxMap) +{ + int offsetx1 = p_Area->x1; + int offsetx2 = p_Area->x2; + int offsety1 = p_Area->y1; + int offsety2 = p_Area->y2; + + esp_lcd_panel_draw_bitmap(static_cast(lv_display_get_user_data(p_Disp)), offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, p_PxMap); +} + +esp_err_t GUI_Helper_Init(GUI_Task_State_t *p_GUI_Task_State, lv_indev_read_cb_t Touch_Read_Callback) +{ + esp_err_t Error; + uint32_t Caps; + +#if (CONFIG_LCD_BL != -1) + ESP_LOGD(TAG, "Configure LCD backlight GPIO..."); + gpio_config_t bk_gpio_config = { + .pin_bit_mask = 1ULL << CONFIG_LCD_BL, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_ERROR_CHECK(gpio_config(&bk_gpio_config)); + gpio_set_level(static_cast(CONFIG_LCD_BL), LCD_BK_LIGHT_ON_LEVEL); +#endif + + ESP_LOGD(TAG, "Create I2C bus for touch controller..."); + + p_GUI_Task_State->Touch_Bus_Handle = DevicesManager_GetTouchI2CBusHandle(); + + ESP_LOGD(TAG, "Create panel IO..."); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(static_cast(LCD_SPI_HOST), &_GUI_Panel_IO_Config, + &p_GUI_Task_State->Panel_IO_Handle)); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(p_GUI_Task_State->Touch_Bus_Handle, &_GUI_Touch_IO_Config, + &p_GUI_Task_State->Touch_IO_Handle)); + + ESP_LOGD(TAG, "Initialize LVGL library and display..."); + lv_init(); + p_GUI_Task_State->Display = lv_display_create(CONFIG_GUI_WIDTH, CONFIG_GUI_HEIGHT); + + ESP_LOGD(TAG, "Register LCD panel IO callbacks..."); + ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(p_GUI_Task_State->Panel_IO_Handle, &_GUI_Panel_Callbacks, + p_GUI_Task_State->Display)); + + ESP_LOGD(TAG, "Install ILI9341 panel driver..."); + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(p_GUI_Task_State->Panel_IO_Handle, &_GUI_Panel_Config, + &p_GUI_Task_State->PanelHandle)); + ESP_LOGD(TAG, " ILI9341 panel driver installed"); + + ESP_LOGD(TAG, "Reset the panel..."); + ESP_ERROR_CHECK(esp_lcd_panel_reset(p_GUI_Task_State->PanelHandle)); + vTaskDelay(pdMS_TO_TICKS(100)); + ESP_LOGD(TAG, " Panel reset complete"); + + ESP_LOGD(TAG, "Initialize the panel..."); + ESP_ERROR_CHECK(esp_lcd_panel_init(p_GUI_Task_State->PanelHandle)); + vTaskDelay(pdMS_TO_TICKS(100)); + ESP_LOGD(TAG, " Panel initialized"); + + ESP_LOGD(TAG, "Configure panel for landscape mode..."); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(p_GUI_Task_State->PanelHandle, true)); + ESP_LOGD(TAG, " Panel swap_xy enabled for landscape"); + + ESP_LOGD(TAG, "Configure panel mirroring..."); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(p_GUI_Task_State->PanelHandle, true, true)); + ESP_LOGD(TAG, " Panel mirroring configured (180 degree rotation)"); + + ESP_LOGD(TAG, "Turn ON the panel display..."); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(p_GUI_Task_State->PanelHandle, true)); + vTaskDelay(pdMS_TO_TICKS(100)); + ESP_LOGD(TAG, " Panel display turned ON"); + + lv_display_set_flush_cb(p_GUI_Task_State->Display, GUI_LCD_Flush_CB); + lv_display_set_user_data(p_GUI_Task_State->Display, p_GUI_Task_State->PanelHandle); + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM; + #else + Caps = 0; + #endif + + p_GUI_Task_State->DisplayBuffer1 = heap_caps_malloc(GUI_DRAW_BUFFER_SIZE, Caps); + p_GUI_Task_State->DisplayBuffer2 = heap_caps_malloc(GUI_DRAW_BUFFER_SIZE, Caps); + ESP_LOGD(TAG, "Allocated LVGL buffers: %d bytes each in PSRAM", GUI_DRAW_BUFFER_SIZE); + if ((p_GUI_Task_State->DisplayBuffer1 == NULL) || (p_GUI_Task_State->DisplayBuffer2 == NULL)) { + ESP_LOGE(TAG, "Failed to allocate LVGL draw buffers!"); + + return ESP_ERR_NO_MEM; + } + + lv_display_set_buffers(p_GUI_Task_State->Display, + p_GUI_Task_State->DisplayBuffer1, + p_GUI_Task_State->DisplayBuffer2, + GUI_DRAW_BUFFER_SIZE, + LV_DISPLAY_RENDER_MODE_PARTIAL); + + ESP_LOGD(TAG, "Initialize GT911 touch controller..."); + Error = esp_lcd_touch_new_i2c_gt911(p_GUI_Task_State->Touch_IO_Handle, &_GUI_Touch_Config, + &p_GUI_Task_State->TouchHandle); + if (Error != ESP_OK) { + ESP_LOGW(TAG, "GT911 touch controller initialization failed (0x%x)", Error); + ESP_LOGW(TAG, "System will continue without touch functionality"); + + p_GUI_Task_State->TouchHandle = NULL; + p_GUI_Task_State->Touch = NULL; + } else { + ESP_LOGD(TAG, "GT911 touch controller initialized successfully"); + + /* Register touchpad input device */ + ESP_LOGD(TAG, "Register touch input device to LVGL"); + p_GUI_Task_State->Touch = lv_indev_create(); + lv_indev_set_type(p_GUI_Task_State->Touch, LV_INDEV_TYPE_POINTER); + lv_indev_set_display(p_GUI_Task_State->Touch, p_GUI_Task_State->Display); + lv_indev_set_read_cb(p_GUI_Task_State->Touch, Touch_Read_Callback); + lv_indev_set_user_data(p_GUI_Task_State->Touch, p_GUI_Task_State->TouchHandle); + } + + const esp_timer_create_args_t LVGL_TickTimer_args = { + .callback = &GUI_LVGL_TickTimer_CB, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "lvgl_tick", + .skip_unhandled_events = false, + }; + + p_GUI_Task_State->EventGroup = xEventGroupCreate(); + if (p_GUI_Task_State->EventGroup == NULL) { + ESP_LOGE(TAG, "Failed to create GUI event group!"); + + return ESP_ERR_NO_MEM; + } + + ESP_ERROR_CHECK(esp_timer_create(&LVGL_TickTimer_args, &p_GUI_Task_State->LVGL_TickTimer)); + ESP_ERROR_CHECK(esp_timer_start_periodic(p_GUI_Task_State->LVGL_TickTimer, CONFIG_GUI_LVGL_TICK_PERIOD_MS * 1000)); + + p_GUI_Task_State->UpdateTimer[0] = lv_timer_create(GUI_Helper_Timer_ClockUpdate, 100, NULL); + p_GUI_Task_State->UpdateTimer[1] = lv_timer_create(GUI_Helper_Timer_SpotUpdate, 2000, NULL); + p_GUI_Task_State->UpdateTimer[2] = lv_timer_create(GUI_Helper_Timer_SpotmeterUpdate, 5000, NULL); + p_GUI_Task_State->UpdateTimer[3] = lv_timer_create(GUI_Helper_Timer_SceneStatisticsUpdate, 5000, NULL); + p_GUI_Task_State->UpdateTimer[4] = lv_timer_create(GUI_Helper_Timer_RAMUpdate, 5000, NULL); + p_GUI_Task_State->UpdateTimer[5] = lv_timer_create(GUI_Helper_Timer_MemoryUpdate, 3000, NULL); + + _lock_init(&p_GUI_Task_State->LVGL_API_Lock); + + return ESP_OK; +} + +void GUI_Helper_Deinit(GUI_Task_State_t *p_GUI_Task_State) +{ + if (p_GUI_Task_State->isInitialized == false) { + return; + } + + for (uint32_t i = 0; i < sizeof(p_GUI_Task_State->UpdateTimer) / sizeof(p_GUI_Task_State->UpdateTimer[0]); i++) { + if (p_GUI_Task_State->UpdateTimer[i] != NULL) { + lv_timer_delete(p_GUI_Task_State->UpdateTimer[i]); + p_GUI_Task_State->UpdateTimer[i] = NULL; + } + } + + if (p_GUI_Task_State->LVGL_TickTimer != NULL) { + esp_timer_stop(p_GUI_Task_State->LVGL_TickTimer); + esp_timer_delete(p_GUI_Task_State->LVGL_TickTimer); + p_GUI_Task_State->LVGL_TickTimer = NULL; + } + + if (p_GUI_Task_State->EventGroup != NULL) { + vEventGroupDelete(p_GUI_Task_State->EventGroup); + p_GUI_Task_State->EventGroup = NULL; + } + + if (p_GUI_Task_State->Touch != NULL) { + lv_indev_delete(p_GUI_Task_State->Touch); + p_GUI_Task_State->Touch = NULL; + } + + esp_lcd_touch_del(p_GUI_Task_State->TouchHandle); + + if (p_GUI_Task_State->Display != NULL) { + lv_display_delete(p_GUI_Task_State->Display); + p_GUI_Task_State->Display = NULL; + } + + if (p_GUI_Task_State->DisplayBuffer1 != NULL) { + heap_caps_free(p_GUI_Task_State->DisplayBuffer1); + p_GUI_Task_State->DisplayBuffer1 = NULL; + } + + if (p_GUI_Task_State->DisplayBuffer2 != NULL) { + heap_caps_free(p_GUI_Task_State->DisplayBuffer2); + p_GUI_Task_State->DisplayBuffer2 = NULL; + } + + if (p_GUI_Task_State->Touch_IO_Handle != NULL) { + esp_lcd_panel_io_del(p_GUI_Task_State->Touch_IO_Handle); + p_GUI_Task_State->Touch_IO_Handle = NULL; + } + + if (p_GUI_Task_State->PanelHandle != NULL) { + esp_lcd_panel_del(p_GUI_Task_State->PanelHandle); + p_GUI_Task_State->PanelHandle = NULL; + } + + _lock_close(&p_GUI_Task_State->LVGL_API_Lock); +} + +void GUI_Helper_Timer_ClockUpdate(lv_timer_t *p_Timer) +{ + (void)p_Timer; + char Buffer[9]; + struct tm Now; + + TimeManager_GetTime(&Now, NULL); + + snprintf(Buffer, sizeof(Buffer), "%02d:%02d:%02d", Now.tm_hour, Now.tm_min, Now.tm_sec); + lv_label_set_text(ui_Label_Main_Time, Buffer); + + if (Server_IsRunning()) { + WebSocket_BroadcastTelemetry(); + } +} + +void GUI_Helper_Timer_SpotUpdate(lv_timer_t *p_Timer) +{ + (void)p_Timer; + App_GUI_Screenposition_t ScreenPosition; + + if ((lv_obj_get_width(ui_Image_Thermal) == 0) || (lv_obj_get_height(ui_Image_Thermal) == 0)) { + return; + } + + /* Get crosshair position relative to its parent (ui_Image_Thermal) */ + ScreenPosition.x = lv_obj_get_x(ui_Label_Main_Thermal_Crosshair) + lv_obj_get_width( + ui_Label_Main_Thermal_Crosshair) / 2; + ScreenPosition.y = lv_obj_get_y(ui_Label_Main_Thermal_Crosshair) + lv_obj_get_height( + ui_Label_Main_Thermal_Crosshair) / 2; + ScreenPosition.Width = lv_obj_get_width(ui_Image_Thermal); + ScreenPosition.Height = lv_obj_get_height(ui_Image_Thermal); + + ESP_LOGD(TAG, "Crosshair center in thermal canvas: (%d,%d), size (%d,%d)", ScreenPosition.x, ScreenPosition.y, + ScreenPosition.Width, ScreenPosition.Height); + + esp_event_post(GUI_EVENTS, GUI_EVENT_REQUEST_PIXEL_TEMPERATURE, &ScreenPosition, sizeof(ScreenPosition), 0); +} + +void GUI_Helper_Timer_SpotmeterUpdate(lv_timer_t *p_Timer) +{ + (void)p_Timer; + + esp_event_post(GUI_EVENTS, GUI_EVENT_REQUEST_SPOTMETER, NULL, 0, 0); +} + +void GUI_Helper_Timer_SceneStatisticsUpdate(lv_timer_t *p_Timer) +{ + (void)p_Timer; + + esp_event_post(GUI_EVENTS, GUI_EVENT_REQUEST_SCENE_STATISTICS, NULL, 0, 0); +} + +void GUI_Helper_Timer_RAMUpdate(lv_timer_t *p_Timer) +{ + (void)p_Timer; + char Buffer[16]; + + sprintf(Buffer, "%u KB", heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024); + lv_label_set_text(ui_Label_Info_PSRAM_Free, Buffer); + sprintf(Buffer, "%u KB", heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024); + lv_label_set_text(ui_Label_Info_RAM_Free, Buffer); +} + +void GUI_Helper_Timer_MemoryUpdate(lv_timer_t *p_Timer) +{ + (void)p_Timer; + + ui_settings_update_memory_usage(); +} \ No newline at end of file diff --git a/main/Application/Tasks/GUI/Private/guiHelper.h b/main/Application/Tasks/GUI/Private/guiHelper.h new file mode 100644 index 0000000..4fc3929 --- /dev/null +++ b/main/Application/Tasks/GUI/Private/guiHelper.h @@ -0,0 +1,148 @@ +/* + * guiHelper.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Helper functions for the GUI task. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef GUI_HELPER_H_ +#define GUI_HELPER_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "Application/application.h" +#include "Application/Manager/Network/networkTypes.h" + +#define GUI_TASK_STOP_REQUEST BIT0 +#define BATTERY_VOLTAGE_READY BIT1 +#define BATTERY_CHARGING_STATUS_READY BIT2 +#define WIFI_CONNECTION_STATE_CHANGED BIT3 +#define PROVISIONING_STATE_CHANGED BIT7 +#define SD_CARD_STATE_CHANGED BIT8 +#define SD_CARD_MOUNTED BIT9 +#define SD_CARD_MOUNT_ERROR BIT10 +#define LEPTON_UPTIME_READY BIT11 +#define LEPTON_TEMP_READY BIT12 +#define LEPTON_PIXEL_TEMPERATURE_READY BIT13 +#define LEPTON_CAMERA_READY BIT4 +#define LEPTON_CAMERA_ERROR BIT14 +#define LEPTON_SPOTMETER_READY BIT5 +#define LEPTON_SCENE_STATISTICS_READY BIT6 +#define UVC_STREAMING_STATE_CHANGED BIT15 + +typedef struct { + bool isInitialized; + bool isRunning; + bool WiFiConnected; + bool ProvisioningActive; + bool CardPresent; + bool SaveNextFrameRequested; + bool isUVCStreaming; + TaskHandle_t TaskHandle; + TaskHandle_t ImageSaveTaskHandle; + void *DisplayBuffer1; + void *DisplayBuffer2; + i2c_master_bus_handle_t Touch_Bus_Handle; + esp_timer_handle_t LVGL_TickTimer; + esp_lcd_panel_handle_t PanelHandle; + esp_lcd_touch_handle_t TouchHandle; + esp_lcd_panel_io_handle_t Panel_IO_Handle; + esp_lcd_panel_io_handle_t Touch_IO_Handle; + lv_obj_t *UVCOverlayLabel; + lv_display_t *Display; + lv_indev_t *Touch; + lv_img_dsc_t ThermalImageDescriptor; + lv_img_dsc_t GradientImageDescriptor; + lv_timer_t *UpdateTimer[6]; + _lock_t LVGL_API_Lock; + App_Devices_Battery_t BatteryInfo; + App_Lepton_ROI_Result_t ROIResult; + App_Lepton_Device_t LeptonDeviceInfo; + App_Lepton_Temperatures_t LeptonTemperatures; + App_Context_t *AppContext; + EventGroupHandle_t EventGroup; + uint8_t *ThermalCanvasBuffer; + uint8_t *GradientCanvasBuffer; + uint8_t *NetworkRGBBuffer; + uint32_t LeptonUptime; + float SpotTemperature; + QueueHandle_t ImageSaveQueue; + Network_IP_Info_t IP_Info; + Network_Thermal_Frame_t NetworkFrame; + +#ifdef CONFIG_GUI_TOUCH_DEBUG + /* Touch debug visualization */ + lv_obj_t *TouchDebugOverlay; + lv_obj_t *TouchDebugCircle; + lv_obj_t *TouchDebugLabel; +#endif +} GUI_Task_State_t; + +/** @brief Initialize the GUI helper functions. + * @param p_GUI_Task_State Pointer to the GUI task state structure. + * @param Touch_Read_Callback LVGL touch read callback function. + */ +esp_err_t GUI_Helper_Init(GUI_Task_State_t *p_GUI_Task_State, lv_indev_read_cb_t Touch_Read_Callback); + +/** @brief Deinitialize the GUI helper functions. + * @param p_GUI_Task_State Pointer to the GUI task state structure. + */ +void GUI_Helper_Deinit(GUI_Task_State_t *p_GUI_Task_State); + +/** @brief LVGL timer callback to update the clock display. + * @param p_Timer Pointer to the LVGL timer structure. + */ +void GUI_Helper_Timer_ClockUpdate(lv_timer_t *p_Timer); + +/** @brief LVGL timer callback to update the spotmeter display. + * @param p_Timer Pointer to the LVGL timer structure. + */ +void GUI_Helper_Timer_SpotUpdate(lv_timer_t *p_Timer); + +/** @brief LVGL timer callback to request spotmeter data update. + * @param p_Timer Pointer to the LVGL timer structure. + */ +void GUI_Helper_Timer_SpotmeterUpdate(lv_timer_t *p_Timer); + +/** @brief LVGL timer callback to request scene statistics data update. + * @param p_Timer Pointer to the LVGL timer structure. + */ +void GUI_Helper_Timer_SceneStatisticsUpdate(lv_timer_t *p_Timer); + +/** @brief LVGL timer callback to request RAM usage update. + * @param p_Timer Pointer to the LVGL timer structure. + */ +void GUI_Helper_Timer_RAMUpdate(lv_timer_t *p_Timer); + +/** @brief LVGL timer callback to request Flash usage update. + * @param p_Timer Pointer to the LVGL timer structure. + */ +void GUI_Helper_Timer_MemoryUpdate(lv_timer_t *p_Timer); + +#endif /* GUI_HELPER_H_ */ \ No newline at end of file diff --git a/main/Application/Tasks/GUI/Private/guiImageSave.cpp b/main/Application/Tasks/GUI/Private/guiImageSave.cpp new file mode 100644 index 0000000..c289870 --- /dev/null +++ b/main/Application/Tasks/GUI/Private/guiImageSave.cpp @@ -0,0 +1,189 @@ +/* + * guiImageSave.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: GUI task image save implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include + +#include +#include +#include +#include + +#include "guiImageSave.h" +#include "Application/application.h" +#include "Application/Manager/Memory/memoryManager.h" + +extern GUI_Task_State_t _GUI_Task_State; + +static const char *TAG = "GUI-ImgSave"; + +void Task_ImageSave(void *p_Param) +{ + App_Lepton_FrameReady_t Frame; + char FilePath[128]; + + ESP_LOGD(TAG, "Image save task started"); + + while (true) { + uint32_t Caps; + + if (xQueueReceive(_GUI_Task_State.ImageSaveQueue, &Frame, portMAX_DELAY) != pdTRUE) { + continue; + } + + /* Check if filesystem is locked (USB active) */ + if (MemoryManager_IsFilesystemLocked()) { + ESP_LOGW(TAG, "Cannot save image - USB mode active!"); + + esp_event_post(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED, NULL, 0, pdMS_TO_TICKS(100)); + + continue; + } + + /* Validate frame data */ + if ((Frame.Buffer == NULL) || (Frame.Width == 0) || (Frame.Height == 0)) { + ESP_LOGE(TAG, "Invalid frame data!"); + + esp_event_post(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED, NULL, 0, pdMS_TO_TICKS(100)); + + continue; + } + + const char *p_StoragePath = MemoryManager_GetStoragePath(); + static uint32_t ImageCounter = 0; + snprintf(FilePath, sizeof(FilePath), "%s/IMG_%03u.PNG", p_StoragePath, (unsigned int)(ImageCounter % 1000)); + ImageCounter++; + + ESP_LOGD(TAG, "Saving PNG: %s (%dx%d)", FilePath, Frame.Width, Frame.Height); + + /* Open file for writing PNG */ + FILE *PNGFile = fopen(FilePath, "wb"); + if (PNGFile == NULL) { + ESP_LOGE(TAG, "Failed to open file for writing: %s", FilePath); + + esp_event_post(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED, NULL, 0, pdMS_TO_TICKS(100)); + + continue; + } + + /* Initialize PNG structures */ + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) { + ESP_LOGE(TAG, "Failed to create PNG write struct!"); + + fclose(PNGFile); + esp_event_post(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED, NULL, 0, pdMS_TO_TICKS(100)); + + continue; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + ESP_LOGE(TAG, "Failed to create PNG info struct!"); + + png_destroy_write_struct(&png_ptr, NULL); + fclose(PNGFile); + esp_event_post(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED, NULL, 0, pdMS_TO_TICKS(100)); + + continue; + } + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM ; + #else + Caps = 0; + #endif + + /* Allocate line buffer before setjmp (to avoid crossing initialization) */ + uint8_t *LineBuffer = static_cast(heap_caps_malloc(Frame.Width * 3, Caps)); + if (LineBuffer == NULL) { + ESP_LOGE(TAG, "Failed to allocate line buffer!"); + + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(PNGFile); + esp_event_post(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED, NULL, 0, pdMS_TO_TICKS(100)); + + continue; + } + + /* Set error handling */ + if (setjmp(png_jmpbuf(png_ptr))) { + ESP_LOGE(TAG, "PNG encoding error!"); + + heap_caps_free(LineBuffer); + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(PNGFile); + esp_event_post(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED, NULL, 0, pdMS_TO_TICKS(100)); + + continue; + } + + /* Set up PNG output */ + png_init_io(png_ptr, PNGFile); + + /* Set PNG image parameters */ + png_set_IHDR(png_ptr, info_ptr, Frame.Width, Frame.Height, + 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + /* Write PNG header */ + png_write_info(png_ptr, info_ptr); + + /* Write image data row by row */ + for (uint32_t y = 0; y < Frame.Height; y++) { + for (uint32_t x = 0; x < Frame.Width; x++) { + uint32_t idx = (y * Frame.Width + x) * 2; /* RGB565 = 2 bytes per pixel */ + uint16_t rgb565 = Frame.Buffer[idx] | (Frame.Buffer[idx + 1] << 8); + + /* Convert RGB565 to RGB888 */ + uint8_t r = ((rgb565 >> 11) & 0x1F) << 3; + uint8_t g = ((rgb565 >> 5) & 0x3F) << 2; + uint8_t b = (rgb565 & 0x1F) << 3; + + /* PNG uses RGB format */ + LineBuffer[x * 3 + 0] = r; + LineBuffer[x * 3 + 1] = g; + LineBuffer[x * 3 + 2] = b; + } + + png_write_row(png_ptr, LineBuffer); + } + + /* Finish writing PNG */ + png_write_end(png_ptr, NULL); + + heap_caps_free(LineBuffer); + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(PNGFile); + + /* Get file size for logging */ + struct stat FileStat; + if (stat(FilePath, &FileStat) == 0) { + ESP_LOGD(TAG, "PNG image saved: %s (%u bytes)", FilePath, static_cast(FileStat.st_size)); + } else { + ESP_LOGD(TAG, "PNG image saved: %s", FilePath); + } + + esp_event_post(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVED, NULL, 0, pdMS_TO_TICKS(100)); + } +} diff --git a/main/Application/Tasks/GUI/Private/guiImageSave.h b/main/Application/Tasks/GUI/Private/guiImageSave.h new file mode 100644 index 0000000..e513020 --- /dev/null +++ b/main/Application/Tasks/GUI/Private/guiImageSave.h @@ -0,0 +1,37 @@ +/* + * guiImageSave.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: GUI task image save implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef GUI_IMAGE_SAVE_H_ +#define GUI_IMAGE_SAVE_H_ + +#include +#include + +#include "guiHelper.h" + +/** @brief Image save task. Runs in background to avoid blocking GUI. + * @param p_Param Task parameters (unused) + */ +void Task_ImageSave(void *p_Param); + +#endif /* GUI_IMAGE_SAVE_H_ */ diff --git a/main/Application/Tasks/GUI/UI/ui_messagebox.cpp b/main/Application/Tasks/GUI/UI/ui_messagebox.cpp new file mode 100644 index 0000000..91a8dc7 --- /dev/null +++ b/main/Application/Tasks/GUI/UI/ui_messagebox.cpp @@ -0,0 +1,78 @@ +/* + * ui_messagenbox.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: GUI task image save implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include + +#include "ui_messagebox.h" + +static const char *TAG = "ui_messagenbox"; + +static void MessageBox_on_Close(lv_timer_t *p_Timer) +{ + lv_obj_t *Box = static_cast(lv_timer_get_user_data(p_Timer)); + + lv_msgbox_close(Box); +} + +void MessageBox_Show(const char *p_Title, uint32_t AutoCloseDelay) +{ + lv_obj_t *Box = lv_msgbox_create(NULL); + + lv_msgbox_add_title(Box, p_Title); + + /* Auto-close message box after specified delay */ + lv_timer_t *Timer = lv_timer_create(MessageBox_on_Close, AutoCloseDelay * 1000, Box); + lv_timer_set_repeat_count(Timer, 1); +} + +void MessageBox_ImageSaveError(esp_err_t Error) +{ + lv_obj_t *Box = lv_msgbox_create(NULL); + + if (Error == ESP_OK) { + lv_msgbox_add_title(Box, "Image Saved"); + lv_msgbox_add_text(Box, "Thermal image saved to storage"); + + ESP_LOGD(TAG, "Thermal image saved successfully"); + } else if (Error == ESP_ERR_INVALID_STATE) { + lv_msgbox_add_title(Box, "USB Active"); + lv_msgbox_add_text(Box, "Cannot save - USB mode is active!\nDisable USB first."); + + ESP_LOGW(TAG, "Cannot save image - USB mode active"); + } else if (Error == ESP_ERR_NO_MEM) { + lv_msgbox_add_title(Box, "No Frame"); + lv_msgbox_add_text(Box, "No thermal frame available"); + + ESP_LOGW(TAG, "No thermal frame available"); + } else { + lv_msgbox_add_title(Box, "Save Failed"); + lv_msgbox_add_text(Box, "Failed to save image"); + + ESP_LOGE(TAG, "Failed to save thermal image: %d!", Error); + } + + /* Auto-close message box after 2 seconds */ + lv_timer_t *Timer = lv_timer_create(MessageBox_on_Close, 2000, Box); + lv_timer_set_repeat_count(Timer, 1); +} diff --git a/main/Application/Tasks/GUI/UI/ui_messagebox.h b/main/Application/Tasks/GUI/UI/ui_messagebox.h new file mode 100644 index 0000000..8e6e7ed --- /dev/null +++ b/main/Application/Tasks/GUI/UI/ui_messagebox.h @@ -0,0 +1,42 @@ +/* + * ui_messagebox.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Settings UI implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef UI_MESSAGEBOX_H_ +#define UI_MESSAGEBOX_H_ + +#include + +#include + +/** @brief Show a message box with the specified title and auto-close it after a delay. + * @param p_Title Title to show in the message box + * @param AutoCloseDelay Time in seconds after which the message box should auto-close (default: 1 second) + */ +void MessageBox_Show(const char *p_Title, uint32_t AutoCloseDelay = 1); + +/** @brief Show a message box indicating the result of a thermal image save operation. + * @param Error ESP_OK if the image was saved successfully, or an error code if it failed + */ +void MessageBox_ImageSaveError(esp_err_t Error); + +#endif /* UI_MESSAGEBOX_H_ */ \ No newline at end of file diff --git a/main/Application/Tasks/GUI/UI/ui_settings.cpp b/main/Application/Tasks/GUI/UI/ui_settings.cpp new file mode 100644 index 0000000..819fb33 --- /dev/null +++ b/main/Application/Tasks/GUI/UI/ui_settings.cpp @@ -0,0 +1,856 @@ +/* + * ui_settings.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Settings UI implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include +#include + +#include "../Export/ui.h" +#include "../Export/screens/ui_Menu.h" + +#include "managers.h" +#include "ui_settings.h" +#include "ui_settings_events.h" + +Slider_Widgets_t brightness_widgets; +Slider_Widgets_t emissivity_widgets; +Slider_Widgets_t jpeg_quality_widgets; + +static lv_obj_t *memory_storage_used_label = NULL; +static lv_obj_t *memory_storage_free_label = NULL; +static lv_obj_t *memory_coredump_used_label = NULL; +static lv_obj_t *memory_coredump_total_label = NULL; +static lv_obj_t *root_page; + +lv_obj_t *cont; +lv_obj_t *section; +lv_obj_t *about_Page; +lv_obj_t *wifi_Page; +lv_obj_t *display_Page; +lv_obj_t *lepton_Page; +lv_obj_t *memory_Page; +lv_obj_t *settings_Menu; +lv_obj_t *emissivity_Dropdown; +lv_obj_t *usb_Page; +lv_obj_t *image_Page; +lv_obj_t *usb_mode_switch; +lv_obj_t *usb_uvc_switch; +lv_obj_t *image_format_dropdown; +lv_obj_t *jpeg_quality_row; +lv_obj_t *ui_settings_wifi_status_label; +lv_obj_t *ui_settings_wifi_connect_btn; + +static const char *TAG = "ui_settings"; + +/** @brief Event handler for menu page changes to control Save button visibility. + * @note Hides Save button when USB or Flash settings are active because + * filesystem is locked during USB mode and Flash operations don't + * require explicit saving. + * @param e Pointer to the event + */ +static void on_Menu_PageChanged(lv_event_t *e) +{ + lv_obj_t *menu_obj = static_cast(lv_event_get_target(e)); + lv_obj_t *cur_page = lv_menu_get_cur_main_page(menu_obj); + + if (cur_page == NULL) { + return; + } + + /* Hide Save button on USB and Flash pages */ + if ((cur_page == usb_Page) || (cur_page == memory_Page)) { + lv_obj_add_flag(ui_Button_Menu_Save, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_remove_flag(ui_Button_Menu_Save, LV_OBJ_FLAG_HIDDEN); + } +} + +static lv_obj_t *ui_Settings_Create_Menu_Container(lv_obj_t *parent) +{ + lv_obj_t *container = lv_obj_create(parent); + lv_obj_clear_flag(container, LV_OBJ_FLAG_CLICKABLE); + lv_obj_clear_flag(container, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(container, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(container, 8, 0); + lv_obj_set_style_bg_color(container, lv_color_hex(0x2A2A2A), 0); + lv_obj_set_style_bg_opa(container, 255, 0); + lv_obj_set_style_border_width(container, 0, 0); + lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(container, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_set_scrollbar_mode(container, LV_SCROLLBAR_MODE_OFF); + + return container; +} + +static lv_obj_t *ui_Settings_Create_Row(lv_obj_t *parent, lv_flex_align_t main_align) +{ + lv_obj_t *row = lv_obj_create(parent); + lv_obj_clear_flag(row, static_cast(LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_GESTURE_BUBBLE)); + lv_obj_set_size(row, LV_PCT(100), 28); + lv_obj_set_style_border_width(row, 0, 0); + lv_obj_set_style_bg_opa(row, 0, 0); + lv_obj_set_style_pad_all(row, 4, 0); + lv_obj_set_style_pad_row(row, 4, 0); + lv_obj_set_scrollbar_mode(row, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_scroll_dir(row, LV_DIR_NONE); + lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(row, main_align, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + return row; +} + +static lv_obj_t *ui_Settings_Create_Compact_Slider(lv_obj_t *parent, const char *label_text, + int min_val, int max_val, int init_val, Slider_Widgets_t *widgets) +{ + char Buffer[8]; + + if (widgets == NULL) { + return NULL; + } + + lv_obj_t *label_row = ui_Settings_Create_Row(parent, LV_FLEX_ALIGN_START); + lv_obj_t *label = lv_label_create(label_row); + lv_label_set_text(label, label_text); + lv_obj_set_width(label, LV_PCT(50)); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + + snprintf(Buffer, sizeof(Buffer), "%i", init_val); + lv_obj_t *value_label = lv_label_create(label_row); + lv_label_set_text(value_label, Buffer); + lv_label_set_long_mode(value_label, LV_LABEL_LONG_CLIP); + lv_obj_set_width(value_label, 40); + lv_obj_set_height(value_label, 24); + lv_obj_set_style_text_align(value_label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_bg_color(value_label, lv_color_hex(0x7B3FF0), 0); + lv_obj_set_style_bg_opa(value_label, 255, 0); + lv_obj_set_style_text_color(value_label, lv_color_white(), 0); + lv_obj_set_style_radius(value_label, 6, 0); + lv_obj_set_style_pad_all(value_label, 5, 0); + lv_obj_clear_flag(value_label, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_style_margin_left(value_label, 10, 0); + + lv_obj_t *slider_row = ui_Settings_Create_Row(parent, LV_FLEX_ALIGN_CENTER); + lv_obj_t *slider = lv_slider_create(slider_row); + lv_obj_clear_flag(slider, LV_OBJ_FLAG_GESTURE_BUBBLE); + + widgets->Slider = slider; + widgets->Label = value_label; + lv_obj_set_user_data(slider, widgets); + + lv_slider_set_range(slider, min_val, max_val); + lv_slider_set_value(slider, init_val, LV_ANIM_OFF); + lv_obj_set_width(slider, LV_PCT(90)); + lv_obj_set_style_bg_color(slider, lv_color_hex(0x7B3FF0), LV_PART_INDICATOR); + lv_obj_set_style_bg_color(slider, lv_color_hex(0x2E2E2E), LV_PART_MAIN); + lv_obj_set_style_bg_color(slider, lv_color_hex(0xFF9500), LV_PART_KNOB); + + return slider; +} + +static Menu_Page_Result_t ui_Settings_Create_Menu_Page_With_Container(lv_obj_t *menu) +{ + lv_obj_t *page = lv_menu_page_create(menu, NULL); + lv_obj_t *section = lv_menu_section_create(page); + lv_obj_t *container = ui_Settings_Create_Menu_Container(section); + + lv_obj_set_scrollbar_mode(menu, LV_SCROLLBAR_MODE_OFF); + + lv_obj_set_scrollbar_mode(page, LV_SCROLLBAR_MODE_AUTO); + lv_obj_add_flag(page, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_scroll_dir(page, LV_DIR_VER); + lv_obj_set_style_pad_all(page, 0, 0); + lv_obj_set_style_border_width(page, 0, 0); + lv_obj_set_style_bg_color(page, lv_color_hex(0x1E1E1E), 0); + lv_obj_set_style_bg_opa(page, 0, 0); + + lv_obj_set_scrollbar_mode(section, LV_SCROLLBAR_MODE_AUTO); + lv_obj_add_flag(section, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_scroll_dir(section, LV_DIR_VER); + lv_obj_set_style_pad_all(section, 0, 0); + lv_obj_set_style_border_width(section, 0, 0); + lv_obj_set_style_bg_color(section, lv_color_hex(0x1E1E1E), 0); + lv_obj_set_style_bg_opa(section, 0, 0); + lv_obj_set_style_pad_right(section, 8, LV_PART_SCROLLBAR); + + return {container, page}; +} + +static lv_obj_t *ui_Settings_Create_Text(lv_obj_t *parent, const char *txt) +{ + lv_obj_t *label = NULL; + + lv_obj_t *obj = lv_menu_cont_create(parent); + lv_obj_set_style_bg_color(obj, lv_color_hex(0x3A3A3A), LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(obj, lv_color_hex(0xFF9500), LV_STATE_PRESSED); + lv_obj_set_style_bg_color(obj, lv_color_hex(0x3A3A3A), LV_STATE_FOCUSED); + lv_obj_set_style_bg_color(obj, lv_color_hex(0xFF9500), LV_STATE_CHECKED); + lv_obj_set_style_bg_color(obj, lv_color_hex(0xFF9500), LV_STATE_EDITED); + lv_obj_set_style_bg_color(obj, lv_color_hex(0x3A3A3A), LV_STATE_FOCUS_KEY); + lv_obj_set_style_bg_color(obj, lv_color_hex(0xFF9500), LV_STATE_PRESSED | LV_STATE_CHECKED); + lv_obj_set_style_bg_opa(obj, 255, 0); + lv_obj_set_style_radius(obj, 6, 0); + lv_obj_set_style_pad_all(obj, 10, 0); + lv_obj_set_style_margin_bottom(obj, 4, 0); + lv_obj_set_style_border_width(obj, 0, LV_STATE_FOCUSED); + lv_obj_set_style_border_width(obj, 15, LV_STATE_CHECKED); + lv_obj_set_style_border_color(obj, lv_color_hex(0xFF9500), LV_STATE_CHECKED); + lv_obj_set_style_border_width(obj, 15, LV_STATE_PRESSED); + lv_obj_set_style_border_color(obj, lv_color_hex(0xFF9500), LV_STATE_PRESSED); + lv_obj_set_style_border_width(obj, 15, LV_STATE_EDITED); + lv_obj_set_style_border_color(obj, lv_color_hex(0xFF9500), LV_STATE_EDITED); + lv_obj_set_style_border_width(obj, 0, LV_STATE_FOCUS_KEY); + lv_obj_set_style_shadow_width(obj, 0, LV_STATE_FOCUSED); + lv_obj_set_style_shadow_width(obj, 15, LV_STATE_CHECKED); + lv_obj_set_style_shadow_color(obj, lv_color_hex(0xFF9500), LV_STATE_CHECKED); + lv_obj_set_style_shadow_width(obj, 15, LV_STATE_PRESSED); + lv_obj_set_style_shadow_color(obj, lv_color_hex(0xFF9500), LV_STATE_PRESSED); + lv_obj_set_style_shadow_opa(obj, 0, LV_STATE_FOCUSED); + lv_obj_set_style_shadow_opa(obj, 180, LV_STATE_CHECKED); + lv_obj_set_style_shadow_opa(obj, 180, LV_STATE_PRESSED); + lv_obj_set_size(obj, LV_PCT(95), 36); + + if (txt) { + label = lv_label_create(obj); + lv_label_set_text(label, txt); + lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0); + lv_obj_set_flex_grow(label, 1); + } + + return obj; +} + +/** @brief Creates the About page. + * @param p_Menu Pointer to the menu object + */ +static lv_obj_t *ui_Settings_Create_About_Page(lv_obj_t *p_parent) +{ + lv_obj_t *cont; + lv_obj_t *section; + lv_obj_t *about_page; + lv_obj_t *about_container; + Menu_Page_Result_t about_result; + lv_obj_t *sub_software_info_page; + lv_obj_t *sub_legal_info_page; + char Buffer[64]; + + about_result = ui_Settings_Create_Menu_Page_With_Container(p_parent); + about_container = about_result.Container; + about_page = about_result.Page; + + lv_menu_separator_create(about_page); + + sub_software_info_page = lv_menu_page_create(p_parent, NULL); + section = lv_menu_section_create(sub_software_info_page); + lv_obj_set_style_bg_color(section, lv_color_hex(0x1E1E1E), 0); + lv_obj_set_style_bg_opa(section, 255, 0); + lv_obj_set_style_pad_all(section, 8, 0); + ui_Settings_Create_Text(section, "PyroVision Firmware"); + + memset(Buffer, 0, sizeof(Buffer)); + snprintf(Buffer, sizeof(Buffer), "Version %u.%u.%u", 1, 0, 0); + ui_Settings_Create_Text(section, Buffer); + + memset(Buffer, 0, sizeof(Buffer)); + snprintf(Buffer, sizeof(Buffer), "Platform: %s", CONFIG_IDF_TARGET); + ui_Settings_Create_Text(section, Buffer); + + memset(Buffer, 0, sizeof(Buffer)); + snprintf(Buffer, sizeof(Buffer), "LVGL Version: %d.%d.%d\n", LVGL_VERSION_MAJOR, LVGL_VERSION_MINOR, + LVGL_VERSION_PATCH); + ui_Settings_Create_Text(section, Buffer); + + sub_legal_info_page = lv_menu_page_create(p_parent, NULL); + section = lv_menu_section_create(sub_legal_info_page); + lv_obj_set_style_bg_color(section, lv_color_hex(0x1E1E1E), 0); + lv_obj_set_style_bg_opa(section, 255, 0); + lv_obj_set_style_pad_all(section, 8, 0); + + lv_obj_t *license_label = lv_label_create(section); + lv_label_set_text(license_label, + "(c) 2026 PyroVision Project\n" + "Licensed under GNU GPL v3\n" + "\n" + "This program is free software.\n" + "See LICENSE file for details."); + lv_obj_set_width(license_label, LV_PCT(95)); + lv_obj_set_style_text_color(license_label, lv_color_white(), 0); + lv_obj_set_style_text_align(license_label, LV_TEXT_ALIGN_LEFT, 0); + lv_label_set_long_mode(license_label, LV_LABEL_LONG_WRAP); + + cont = ui_Settings_Create_Text(about_container, "Software information"); + lv_menu_set_load_page_event(p_parent, cont, sub_software_info_page); + cont = ui_Settings_Create_Text(about_container, "Legal information"); + lv_menu_set_load_page_event(p_parent, cont, sub_legal_info_page); + + return about_page; +} + +/** @brief Creates the WiFi settings page and load it with the values from the settings. + * @param p_Menu Pointer to the menu object + */ +static lv_obj_t *ui_Settings_Create_WiFi_Page(lv_obj_t *p_Menu) +{ + Menu_Page_Result_t WiFiResult = ui_Settings_Create_Menu_Page_With_Container(p_Menu); + lv_obj_t *WiFiContainer = WiFiResult.Container; + lv_obj_t *WiFiPage = WiFiResult.Page; + Settings_WiFi_t WiFiSettings; + + SettingsManager_GetWiFi(&WiFiSettings); + + lv_obj_t *wifi_row1 = ui_Settings_Create_Row(WiFiContainer, LV_FLEX_ALIGN_SPACE_BETWEEN); + lv_obj_t *wifi_label = lv_label_create(wifi_row1); + lv_label_set_text(wifi_label, "Autoconnect"); + lv_obj_set_style_text_color(wifi_label, lv_color_white(), 0); + + lv_obj_t *wifi_switch = lv_switch_create(wifi_row1); + lv_obj_set_style_bg_color(wifi_switch, lv_color_hex(0x7B3FF0), 0); + lv_obj_set_style_bg_color(wifi_switch, lv_color_hex(0xFF9500), LV_PART_KNOB); + lv_obj_add_event_cb(wifi_switch, on_WiFi_Autoconnect_Callback, LV_EVENT_VALUE_CHANGED, NULL); + + if (WiFiSettings.AutoConnect) { + lv_obj_add_state(wifi_switch, LV_STATE_CHECKED); + } else { + lv_obj_remove_state(wifi_switch, LV_STATE_CHECKED); + } + + lv_obj_t *wifi_row2 = ui_Settings_Create_Row(WiFiContainer, LV_FLEX_ALIGN_SPACE_BETWEEN); + lv_obj_t *ssid_label = lv_label_create(wifi_row2); + lv_label_set_text(ssid_label, "Status"); + lv_obj_set_style_text_color(ssid_label, lv_color_white(), 0); + ui_settings_wifi_status_label = lv_label_create(wifi_row2); + lv_label_set_text(ui_settings_wifi_status_label, "Offline"); + lv_obj_set_style_text_color(ui_settings_wifi_status_label, lv_color_hex(0xFF9500), 0); + + /* WiFi Connect Button */ + lv_obj_t *wifi_btn_row = ui_Settings_Create_Row(WiFiContainer, LV_FLEX_ALIGN_CENTER); + lv_obj_set_height(wifi_btn_row, LV_SIZE_CONTENT); + lv_obj_set_style_pad_top(wifi_btn_row, 12, 0); + lv_obj_set_style_pad_bottom(wifi_btn_row, 8, 0); + ui_settings_wifi_connect_btn = lv_btn_create(wifi_btn_row); + lv_obj_set_size(ui_settings_wifi_connect_btn, 140, 36); + lv_obj_set_style_bg_color(ui_settings_wifi_connect_btn, lv_color_hex(0xFF9500), LV_STATE_DEFAULT); + lv_obj_set_style_radius(ui_settings_wifi_connect_btn, 6, 0); + lv_obj_set_style_shadow_width(ui_settings_wifi_connect_btn, 0, 0); + lv_obj_add_event_cb(ui_settings_wifi_connect_btn, on_WiFi_Connect_Callback, LV_EVENT_CLICKED, NULL); + lv_obj_t *wifi_btn_label = lv_label_create(ui_settings_wifi_connect_btn); + lv_label_set_text(wifi_btn_label, "Connect WiFi"); + lv_obj_set_style_text_color(wifi_btn_label, lv_color_white(), 0); + lv_obj_center(wifi_btn_label); + + return WiFiPage; +} + +/** @brief Creates the display settings page and load it with the values from the settings. + * @param p_Menu Pointer to the menu object + */ +static lv_obj_t *ui_Settings_Create_Display_Page(lv_obj_t *p_Menu) +{ + Menu_Page_Result_t DisplayResult = ui_Settings_Create_Menu_Page_With_Container(p_Menu); + lv_obj_t *DisplayContainer = DisplayResult.Container; + lv_obj_t *DisplayPage = DisplayResult.Page; + Settings_Display_t DisplaySettings; + + SettingsManager_GetDisplay(&DisplaySettings); + + lv_obj_t *brightness_slider = ui_Settings_Create_Compact_Slider(DisplayContainer, "Brightness", 0, 100, DisplaySettings.Brightness, + &brightness_widgets); + + lv_obj_add_event_cb(brightness_slider, on_Display_Brightness_Slider_Callback, LV_EVENT_VALUE_CHANGED, NULL); + + return DisplayPage; +} + +/** @brief Creates the Lepton settings page and load it with the values from the settings. + * @param p_Menu Pointer to the menu object + */ +static lv_obj_t *ui_Settings_Create_Lepton_Page(lv_obj_t *p_Menu) +{ + std::string Buffer; + Menu_Page_Result_t LeptonResult = ui_Settings_Create_Menu_Page_With_Container(p_Menu); + lv_obj_t *LeptonContainer = LeptonResult.Container; + lv_obj_t *LeptonPage = LeptonResult.Page; + Settings_Lepton_t LeptonSettings; + + SettingsManager_GetLepton(&LeptonSettings); + + for (size_t i = 0; i < LeptonSettings.EmissivityPresetsCount; i++) { + Buffer += LeptonSettings.EmissivityPresets[i].Description + std::string("\n"); + } + + lv_obj_t *emissivity_slider = ui_Settings_Create_Compact_Slider(LeptonContainer, "Emissivity", 0, 100, + LeptonSettings.CurrentEmissivity, &emissivity_widgets); + + lv_obj_add_event_cb(emissivity_slider, on_Lepton_Emissivity_Slider_Callback, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_add_event_cb(emissivity_slider, on_Lepton_Emissivity_Slider_Callback, LV_EVENT_RELEASED, NULL); + + lv_obj_t *dropdown_row = ui_Settings_Create_Row(LeptonContainer, LV_FLEX_ALIGN_CENTER); + emissivity_Dropdown = lv_dropdown_create(dropdown_row); + lv_obj_set_width(emissivity_Dropdown, LV_PCT(95)); + lv_obj_set_style_bg_color(emissivity_Dropdown, lv_color_hex(0x3A3A3A), LV_PART_MAIN); + lv_obj_set_style_border_color(emissivity_Dropdown, lv_color_hex(0xFF9500), LV_PART_MAIN); + lv_obj_set_style_border_width(emissivity_Dropdown, 2, LV_PART_MAIN); + lv_obj_set_style_radius(emissivity_Dropdown, 6, LV_PART_MAIN); + lv_obj_set_style_text_color(emissivity_Dropdown, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_pad_all(emissivity_Dropdown, 8, LV_PART_MAIN); + lv_obj_set_style_bg_color(emissivity_Dropdown, lv_color_hex(0xFF9500), LV_PART_SELECTED); + lv_obj_set_style_text_color(emissivity_Dropdown, lv_color_white(), LV_PART_SELECTED); + lv_obj_set_user_data(emissivity_Dropdown, &emissivity_widgets); + lv_obj_add_event_cb(emissivity_Dropdown, on_Lepton_Dropdown_Callback, LV_EVENT_VALUE_CHANGED, NULL); + lv_dropdown_set_options(emissivity_Dropdown, Buffer.c_str()); + lv_dropdown_set_selected(emissivity_Dropdown, 0); + + return LeptonPage; +} + +/** @brief Creates the USB settings page. + * @param p_Menu Pointer to the menu object + */ +static lv_obj_t *ui_Settings_Create_USB_Page(lv_obj_t *p_Menu) +{ + Menu_Page_Result_t USBResult = ui_Settings_Create_Menu_Page_With_Container(p_Menu); + lv_obj_t *USBContainer = USBResult.Container; + lv_obj_t *USBPage = USBResult.Page; + + lv_obj_t *usb_section_label = lv_label_create(USBContainer); + lv_label_set_text(usb_section_label, "USB Mass Storage"); + lv_obj_set_style_text_color(usb_section_label, lv_color_hex(0xFF9500), 0); + lv_obj_set_style_text_font(usb_section_label, &lv_font_montserrat_14, 0); + lv_obj_set_style_pad_top(usb_section_label, 0, 0); + lv_obj_set_style_pad_bottom(usb_section_label, 8, 0); + + lv_obj_t *usb_desc_label = lv_label_create(USBContainer); + lv_label_set_text(usb_desc_label, "Expose storage via USB to PC"); + lv_obj_set_style_text_color(usb_desc_label, lv_color_hex(0xAAAAAA), 0); + lv_obj_set_style_text_font(usb_desc_label, &lv_font_montserrat_12, 0); + lv_obj_set_style_pad_bottom(usb_desc_label, 8, 0); + + lv_obj_t *usb_warning_label = lv_label_create(USBContainer); + lv_label_set_text(usb_warning_label, " App cannot save while USB is active!"); + lv_obj_set_style_text_color(usb_warning_label, lv_color_hex(0xFF9500), 0); + lv_obj_set_style_text_font(usb_warning_label, &lv_font_montserrat_10, 0); + lv_obj_set_style_pad_bottom(usb_warning_label, 12, 0); + + lv_obj_t *usb_switch_row = ui_Settings_Create_Row(USBContainer, LV_FLEX_ALIGN_SPACE_BETWEEN); + lv_obj_set_style_pad_top(usb_switch_row, 4, 0); + lv_obj_set_style_pad_bottom(usb_switch_row, 4, 0); + + lv_obj_t *usb_mode_label = lv_label_create(usb_switch_row); + lv_label_set_text(usb_mode_label, "MSC Mode"); + lv_obj_set_style_text_color(usb_mode_label, lv_color_white(), 0); + lv_obj_set_style_text_font(usb_mode_label, &lv_font_montserrat_14, 0); + + usb_mode_switch = lv_switch_create(usb_switch_row); + lv_obj_set_size(usb_mode_switch, 50, 25); + lv_obj_set_style_bg_color(usb_mode_switch, lv_color_hex(0x7B3FF0), 0); + lv_obj_set_style_bg_color(usb_mode_switch, lv_color_hex(0xFF9500), LV_PART_KNOB); + lv_obj_add_event_cb(usb_mode_switch, on_USB_Mode_Switch_Callback, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_add_state(usb_mode_switch, LV_STATE_DISABLED); + + /* Separator */ + lv_obj_t *separator = lv_obj_create(USBContainer); + lv_obj_set_size(separator, LV_PCT(100), 1); + lv_obj_set_style_bg_color(separator, lv_color_hex(0x505050), 0); + lv_obj_set_style_border_width(separator, 0, 0); + lv_obj_set_style_pad_all(separator, 0, 0); + lv_obj_set_style_margin_top(separator, 12, 0); + lv_obj_set_style_margin_bottom(separator, 12, 0); + + /* UVC Section */ + lv_obj_t *uvc_section_label = lv_label_create(USBContainer); + lv_label_set_text(uvc_section_label, "USB Video Class"); + lv_obj_set_style_text_color(uvc_section_label, lv_color_hex(0xFF9500), 0); + lv_obj_set_style_text_font(uvc_section_label, &lv_font_montserrat_14, 0); + lv_obj_set_style_pad_top(uvc_section_label, 0, 0); + lv_obj_set_style_pad_bottom(uvc_section_label, 8, 0); + + lv_obj_t *uvc_desc_label = lv_label_create(USBContainer); + lv_label_set_text(uvc_desc_label, "Stream thermal camera via USB"); + lv_obj_set_style_text_color(uvc_desc_label, lv_color_hex(0xAAAAAA), 0); + lv_obj_set_style_text_font(uvc_desc_label, &lv_font_montserrat_12, 0); + lv_obj_set_style_pad_bottom(uvc_desc_label, 12, 0); + + lv_obj_t *uvc_switch_row = ui_Settings_Create_Row(USBContainer, LV_FLEX_ALIGN_SPACE_BETWEEN); + lv_obj_set_style_pad_top(uvc_switch_row, 4, 0); + lv_obj_set_style_pad_bottom(uvc_switch_row, 4, 0); + + lv_obj_t *uvc_mode_label = lv_label_create(uvc_switch_row); + lv_label_set_text(uvc_mode_label, "UVC Mode"); + lv_obj_set_style_text_color(uvc_mode_label, lv_color_white(), 0); + lv_obj_set_style_text_font(uvc_mode_label, &lv_font_montserrat_14, 0); + + usb_uvc_switch = lv_switch_create(uvc_switch_row); + lv_obj_set_size(usb_uvc_switch, 50, 25); + lv_obj_set_style_bg_color(usb_uvc_switch, lv_color_hex(0x7B3FF0), 0); + lv_obj_set_style_bg_color(usb_uvc_switch, lv_color_hex(0xFF9500), LV_PART_KNOB); + lv_obj_add_event_cb(usb_uvc_switch, on_USB_UVC_Switch_Callback, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_add_state(usb_uvc_switch, LV_STATE_DISABLED); + + return USBPage; +} + +/** @brief Creates the Flash settings page and load it with the values from the settings. + * @param p_Menu Pointer to the menu object + */ +static lv_obj_t *ui_Settings_Create_Memory_Page(lv_obj_t *p_Menu) +{ + Menu_Page_Result_t MemoryResult = ui_Settings_Create_Menu_Page_With_Container(p_Menu); + lv_obj_t *MemoryContainer = MemoryResult.Container; + lv_obj_t *MemoryPage = MemoryResult.Page; + + lv_obj_t *nvs_section_label = lv_label_create(MemoryContainer); + lv_label_set_text(nvs_section_label, "NVS Settings"); + lv_obj_set_style_text_color(nvs_section_label, lv_color_hex(0xFF9500), 0); + lv_obj_set_style_text_font(nvs_section_label, &lv_font_montserrat_14, 0); + lv_obj_set_style_pad_top(nvs_section_label, 0, 0); + lv_obj_set_style_pad_bottom(nvs_section_label, 8, 0); + + lv_obj_t *nvs_desc_label = lv_label_create(MemoryContainer); + lv_label_set_text(nvs_desc_label, "Factory reset"); + lv_obj_set_style_text_color(nvs_desc_label, lv_color_hex(0xAAAAAA), 0); + lv_obj_set_style_text_font(nvs_desc_label, &lv_font_montserrat_12, 0); + lv_obj_set_style_pad_bottom(nvs_desc_label, 8, 0); + + lv_obj_t *nvs_btn_row = ui_Settings_Create_Row(MemoryContainer, LV_FLEX_ALIGN_CENTER); + lv_obj_set_height(nvs_btn_row, LV_SIZE_CONTENT); + lv_obj_set_style_pad_top(nvs_btn_row, 4, 0); + lv_obj_set_style_pad_bottom(nvs_btn_row, 4, 0); + lv_obj_t *nvs_clear_btn = lv_btn_create(nvs_btn_row); + lv_obj_set_size(nvs_clear_btn, 120, 36); + lv_obj_set_style_bg_color(nvs_clear_btn, lv_color_hex(0xFF3B3B), LV_STATE_DEFAULT); + lv_obj_set_style_radius(nvs_clear_btn, 6, 0); + lv_obj_set_style_shadow_width(nvs_clear_btn, 0, 0); + lv_obj_add_event_cb(nvs_clear_btn, on_Memory_ClearNVS_Callback, LV_EVENT_CLICKED, NULL); + lv_obj_t *nvs_btn_label = lv_label_create(nvs_clear_btn); + lv_label_set_text(nvs_btn_label, "Reset"); + lv_obj_set_style_text_color(nvs_btn_label, lv_color_white(), 0); + lv_obj_center(nvs_btn_label); + + /* Separator */ + lv_obj_t *separator1 = lv_obj_create(MemoryContainer); + lv_obj_set_size(separator1, LV_PCT(100), 1); + lv_obj_set_style_bg_color(separator1, lv_color_hex(0x505050), 0); + lv_obj_set_style_border_width(separator1, 0, 0); + lv_obj_set_style_pad_all(separator1, 0, 0); + lv_obj_set_style_margin_top(separator1, 12, 0); + lv_obj_set_style_margin_bottom(separator1, 12, 0); + + lv_obj_t *storage_section_label = lv_label_create(MemoryContainer); + lv_label_set_text(storage_section_label, "Storage Partition"); + lv_obj_set_style_text_color(storage_section_label, lv_color_hex(0xFF9500), 0); + lv_obj_set_style_text_font(storage_section_label, &lv_font_montserrat_14, 0); + lv_obj_set_style_pad_top(storage_section_label, 0, 0); + lv_obj_set_style_pad_bottom(storage_section_label, 8, 0); + + lv_obj_t *storage_info_row = ui_Settings_Create_Row(MemoryContainer, LV_FLEX_ALIGN_SPACE_BETWEEN); + lv_obj_t *storage_used_label = lv_label_create(storage_info_row); + lv_label_set_text(storage_used_label, "Used:"); + lv_obj_set_style_text_color(storage_used_label, lv_color_white(), 0); + memory_storage_used_label = lv_label_create(storage_info_row); + lv_label_set_text(memory_storage_used_label, "-- KB"); + lv_obj_set_style_text_color(memory_storage_used_label, lv_color_hex(0xB998FF), 0); + + lv_obj_t *storage_free_row = ui_Settings_Create_Row(MemoryContainer, LV_FLEX_ALIGN_SPACE_BETWEEN); + lv_obj_t *storage_free_label = lv_label_create(storage_free_row); + lv_label_set_text(storage_free_label, "Free:"); + lv_obj_set_style_text_color(storage_free_label, lv_color_white(), 0); + memory_storage_free_label = lv_label_create(storage_free_row); + lv_label_set_text(memory_storage_free_label, "-- KB"); + lv_obj_set_style_text_color(memory_storage_free_label, lv_color_hex(0xB998FF), 0); + + lv_obj_t *storage_btn_row = ui_Settings_Create_Row(MemoryContainer, LV_FLEX_ALIGN_CENTER); + lv_obj_set_height(storage_btn_row, LV_SIZE_CONTENT); + lv_obj_set_style_pad_top(storage_btn_row, 8, 0); + lv_obj_set_style_pad_bottom(storage_btn_row, 8, 0); + lv_obj_t *storage_clear_btn = lv_btn_create(storage_btn_row); + lv_obj_set_size(storage_clear_btn, 120, 36); + lv_obj_set_style_bg_color(storage_clear_btn, lv_color_hex(0xFF3B3B), LV_STATE_DEFAULT); + lv_obj_set_style_radius(storage_clear_btn, 6, 0); + lv_obj_set_style_shadow_width(storage_clear_btn, 0, 0); + lv_obj_add_event_cb(storage_clear_btn, on_Memory_ClearStorage_Callback, LV_EVENT_CLICKED, NULL); + lv_obj_t *storage_btn_label = lv_label_create(storage_clear_btn); + lv_label_set_text(storage_btn_label, "Clear"); + lv_obj_set_style_text_color(storage_btn_label, lv_color_white(), 0); + lv_obj_center(storage_btn_label); + + /* Separator */ + lv_obj_t *separator2 = lv_obj_create(MemoryContainer); + lv_obj_set_size(separator2, LV_PCT(100), 1); + lv_obj_set_style_bg_color(separator2, lv_color_hex(0x505050), 0); + lv_obj_set_style_border_width(separator2, 0, 0); + lv_obj_set_style_pad_all(separator2, 0, 0); + lv_obj_set_style_margin_top(separator2, 12, 0); + lv_obj_set_style_margin_bottom(separator2, 12, 0); + + lv_obj_t *coredump_section_label = lv_label_create(MemoryContainer); + lv_label_set_text(coredump_section_label, "Coredump Partition"); + lv_obj_set_style_text_color(coredump_section_label, lv_color_hex(0xFF9500), 0); + lv_obj_set_style_text_font(coredump_section_label, &lv_font_montserrat_14, 0); + lv_obj_set_style_pad_top(coredump_section_label, 0, 0); + lv_obj_set_style_pad_bottom(coredump_section_label, 8, 0); + + lv_obj_t *coredump_info_row = ui_Settings_Create_Row(MemoryContainer, LV_FLEX_ALIGN_SPACE_BETWEEN); + lv_obj_t *coredump_used_label = lv_label_create(coredump_info_row); + lv_label_set_text(coredump_used_label, "Used:"); + lv_obj_set_style_text_color(coredump_used_label, lv_color_white(), 0); + memory_coredump_used_label = lv_label_create(coredump_info_row); + lv_label_set_text(memory_coredump_used_label, "-- KB"); + lv_obj_set_style_text_color(memory_coredump_used_label, lv_color_hex(0xB998FF), 0); + + lv_obj_t *coredump_total_row = ui_Settings_Create_Row(MemoryContainer, LV_FLEX_ALIGN_SPACE_BETWEEN); + lv_obj_t *coredump_total_label = lv_label_create(coredump_total_row); + lv_label_set_text(coredump_total_label, "Total:"); + lv_obj_set_style_text_color(coredump_total_label, lv_color_white(), 0); + memory_coredump_total_label = lv_label_create(coredump_total_row); + lv_label_set_text(memory_coredump_total_label, "-- KB"); + lv_obj_set_style_text_color(memory_coredump_total_label, lv_color_hex(0xB998FF), 0); + + lv_obj_t *coredump_btn_row = ui_Settings_Create_Row(MemoryContainer, LV_FLEX_ALIGN_CENTER); + lv_obj_set_height(coredump_btn_row, LV_SIZE_CONTENT); + lv_obj_set_style_pad_top(coredump_btn_row, 8, 0); + lv_obj_set_style_pad_bottom(coredump_btn_row, 16, 0); + lv_obj_t *coredump_clear_btn = lv_btn_create(coredump_btn_row); + lv_obj_set_size(coredump_clear_btn, 120, 36); + lv_obj_set_style_bg_color(coredump_clear_btn, lv_color_hex(0xFF3B3B), LV_STATE_DEFAULT); + lv_obj_set_style_radius(coredump_clear_btn, 6, 0); + lv_obj_set_style_shadow_width(coredump_clear_btn, 0, 0); + lv_obj_add_event_cb(coredump_clear_btn, on_Memory_ClearCoredump_Callback, LV_EVENT_CLICKED, NULL); + lv_obj_t *coredump_btn_label = lv_label_create(coredump_clear_btn); + lv_label_set_text(coredump_btn_label, "Clear"); + lv_obj_set_style_text_color(coredump_btn_label, lv_color_white(), 0); + lv_obj_center(coredump_btn_label); + + return MemoryPage; +} + +/** @brief Creates the Image/Capture settings page. + * @param p_Menu Pointer to the menu object + */ +static lv_obj_t *ui_Settings_Create_Image_Page(lv_obj_t *p_Menu) +{ + Menu_Page_Result_t ImageResult = ui_Settings_Create_Menu_Page_With_Container(p_Menu); + lv_obj_t *ImageContainer = ImageResult.Container; + lv_obj_t *ImagePage = ImageResult.Page; + Settings_System_t SystemSettings; + + SettingsManager_GetSystem(&SystemSettings); + + /* Section Label */ + lv_obj_t *image_section_label = lv_label_create(ImageContainer); + lv_label_set_text(image_section_label, "Image Format"); + lv_obj_set_style_text_color(image_section_label, lv_color_hex(0xFF9500), 0); + lv_obj_set_style_text_font(image_section_label, &lv_font_montserrat_14, 0); + lv_obj_set_style_pad_top(image_section_label, 0, 0); + lv_obj_set_style_pad_bottom(image_section_label, 8, 0); + + /* Format Dropdown */ + lv_obj_t *format_dropdown_row = ui_Settings_Create_Row(ImageContainer, LV_FLEX_ALIGN_CENTER); + lv_obj_set_height(format_dropdown_row, LV_SIZE_CONTENT); + image_format_dropdown = lv_dropdown_create(format_dropdown_row); + lv_obj_set_width(image_format_dropdown, LV_PCT(95)); + lv_obj_set_style_bg_color(image_format_dropdown, lv_color_hex(0x3A3A3A), LV_PART_MAIN); + lv_obj_set_style_border_color(image_format_dropdown, lv_color_hex(0xFF9500), LV_PART_MAIN); + lv_obj_set_style_border_width(image_format_dropdown, 2, LV_PART_MAIN); + lv_obj_set_style_radius(image_format_dropdown, 6, LV_PART_MAIN); + lv_obj_set_style_text_color(image_format_dropdown, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_pad_all(image_format_dropdown, 8, LV_PART_MAIN); + lv_obj_set_style_bg_color(image_format_dropdown, lv_color_hex(0xFF9500), LV_PART_SELECTED); + lv_obj_set_style_text_color(image_format_dropdown, lv_color_white(), LV_PART_SELECTED); + lv_dropdown_set_options(image_format_dropdown, "JPEG\nPNG\nRAW\nBitmap"); + lv_dropdown_set_selected(image_format_dropdown, static_cast(SystemSettings.ImageFormat)); + lv_obj_add_event_cb(image_format_dropdown, on_Image_Format_Dropdown_Callback, LV_EVENT_VALUE_CHANGED, NULL); + + /* Separator */ + lv_obj_t *separator = lv_obj_create(ImageContainer); + lv_obj_set_size(separator, LV_PCT(100), 1); + lv_obj_set_style_bg_color(separator, lv_color_hex(0x505050), 0); + lv_obj_set_style_border_width(separator, 0, 0); + lv_obj_set_style_pad_all(separator, 0, 0); + lv_obj_set_style_margin_top(separator, 16, 0); + lv_obj_set_style_margin_bottom(separator, 16, 0); + + /* JPEG Quality Section */ + lv_obj_t *quality_section_label = lv_label_create(ImageContainer); + lv_label_set_text(quality_section_label, "JPEG Settings"); + lv_obj_set_style_text_color(quality_section_label, lv_color_hex(0xFF9500), 0); + lv_obj_set_style_text_font(quality_section_label, &lv_font_montserrat_14, 0); + lv_obj_set_style_pad_top(quality_section_label, 0, 0); + lv_obj_set_style_pad_bottom(quality_section_label, 8, 0); + + /* JPEG Quality Slider (only visible when JPEG is selected) */ + lv_obj_t *quality_slider = ui_Settings_Create_Compact_Slider(ImageContainer, "Quality", 1, 100, + SystemSettings.JpegQuality, &jpeg_quality_widgets); + lv_obj_add_event_cb(quality_slider, on_Image_JpegQuality_Slider_Callback, LV_EVENT_VALUE_CHANGED, NULL); + + /* Store the rows for visibility control */ + jpeg_quality_row = lv_obj_get_parent(quality_slider); + + /* Show/hide quality slider based on current format */ + if (SystemSettings.ImageFormat == IMAGE_FORMAT_JPEG) { + lv_obj_remove_flag(jpeg_quality_row, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(lv_obj_get_parent(jpeg_quality_row), LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(quality_section_label, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(jpeg_quality_row, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(lv_obj_get_parent(jpeg_quality_row), LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(quality_section_label, LV_OBJ_FLAG_HIDDEN); + } + + /* Info text */ + lv_obj_t *info_label = lv_label_create(ImageContainer); + lv_label_set_text(info_label, + "JPEG: Compressed, small files\n" + "PNG: Lossless (not yet implemented)\n" + "RAW: Uncompressed RGB data\n" + "Bitmap: BMP file format"); + lv_obj_set_width(info_label, LV_PCT(95)); + lv_obj_set_style_text_color(info_label, lv_color_hex(0xAAAAAA), 0); + lv_obj_set_style_text_font(info_label, &lv_font_montserrat_10, 0); + lv_obj_set_style_text_align(info_label, LV_TEXT_ALIGN_LEFT, 0); + lv_label_set_long_mode(info_label, LV_LABEL_LONG_WRAP); + lv_obj_set_style_margin_top(info_label, 16, 0); + + return ImagePage; +} + +void ui_settings_init(lv_obj_t *p_Parent) +{ + settings_Menu = lv_menu_create(p_Parent); + + lv_obj_set_size(settings_Menu, lv_pct(100), lv_pct(100)); + lv_obj_center(settings_Menu); + lv_obj_set_style_bg_color(settings_Menu, lv_color_hex(0x1E1E1E), 0); + lv_obj_set_style_bg_opa(settings_Menu, 0, 0); + lv_menu_set_mode_header(settings_Menu, LV_MENU_HEADER_TOP_FIXED); + + wifi_Page = ui_Settings_Create_WiFi_Page(settings_Menu); + display_Page = ui_Settings_Create_Display_Page(settings_Menu); + lepton_Page = ui_Settings_Create_Lepton_Page(settings_Menu); + image_Page = ui_Settings_Create_Image_Page(settings_Menu); + memory_Page = ui_Settings_Create_Memory_Page(settings_Menu); + usb_Page = ui_Settings_Create_USB_Page(settings_Menu); + about_Page = ui_Settings_Create_About_Page(settings_Menu); + + root_page = lv_menu_page_create(settings_Menu, NULL); + lv_obj_set_style_bg_color(root_page, lv_color_hex(0x1E1E1E), 0); + lv_obj_set_style_bg_opa(root_page, 0, 0); + + section = lv_menu_section_create(root_page); + lv_obj_set_style_bg_color(section, lv_color_hex(0x1E1E1E), 0); + lv_obj_set_style_bg_opa(section, 0, 0); + lv_obj_set_style_pad_all(section, 8, 0); + + cont = ui_Settings_Create_Text(section, "WiFi"); + lv_menu_set_load_page_event(settings_Menu, cont, wifi_Page); + + cont = ui_Settings_Create_Text(section, "Display"); + lv_menu_set_load_page_event(settings_Menu, cont, display_Page); + + cont = ui_Settings_Create_Text(section, "Lepton"); + lv_menu_set_load_page_event(settings_Menu, cont, lepton_Page); + + cont = ui_Settings_Create_Text(section, "Image"); + lv_menu_set_load_page_event(settings_Menu, cont, image_Page); + + cont = ui_Settings_Create_Text(section, "Memory"); + lv_menu_set_load_page_event(settings_Menu, cont, memory_Page); + lv_obj_add_event_cb(cont, [](lv_event_t *e) { + if (lv_event_get_code(e) == LV_EVENT_CLICKED) { + ui_settings_update_memory_usage(); + } + }, LV_EVENT_CLICKED, NULL); + + cont = ui_Settings_Create_Text(section, "USB"); + lv_menu_set_load_page_event(settings_Menu, cont, usb_Page); + + cont = ui_Settings_Create_Text(section, "About"); + lv_menu_set_load_page_event(settings_Menu, cont, about_Page); + + lv_menu_set_sidebar_page(settings_Menu, root_page); + + /* Register event handler for menu page changes to control Save button visibility */ + lv_obj_add_event_cb(settings_Menu, on_Menu_PageChanged, LV_EVENT_VALUE_CHANGED, NULL); + + /* Initially check current page (root page doesn't need Save button hidden) */ + lv_obj_remove_flag(ui_Button_Menu_Save, LV_OBJ_FLAG_HIDDEN); + + esp_event_handler_register(USB_EVENTS, ESP_EVENT_ANY_ID, on_USB_Event_Handler, NULL); + esp_event_handler_register(SETTINGS_EVENTS, ESP_EVENT_ANY_ID, on_Settings_Event_Handler, NULL); + esp_event_handler_register(NETWORK_EVENTS, ESP_EVENT_ANY_ID, on_Network_Event_Handler, NULL); + + /* Synchronize USB switch state with current cable connection status. + If the cable was already connected before the settings page was opened, + the USB_EVENT_CABLE_CONNECTED event was missed - enable switches now. */ + if (USBManager_IsCableConnected()) { + lv_obj_remove_state(usb_mode_switch, LV_STATE_DISABLED); + lv_obj_remove_state(usb_uvc_switch, LV_STATE_DISABLED); + } +} + +void ui_settings_deinit(lv_obj_t *p_Parent) +{ + if (ui_Splash) { + lv_obj_del(settings_Menu); + lv_obj_delete(settings_Menu); + + esp_event_handler_unregister(USB_EVENTS, ESP_EVENT_ANY_ID, on_USB_Event_Handler); + esp_event_handler_unregister(SETTINGS_EVENTS, ESP_EVENT_ANY_ID, on_Settings_Event_Handler); + esp_event_handler_unregister(NETWORK_EVENTS, ESP_EVENT_ANY_ID, on_Network_Event_Handler); + } +} + +void ui_settings_update_memory_usage(void) +{ + MemoryManager_Usage_t Usage; + + if(MemoryManager_IsFilesystemLocked()) { + ESP_LOGD(TAG, "Cannot update memory usage - filesystem is locked"); + + return; + } + + if (memory_storage_used_label && memory_storage_free_label) { + if (MemoryManager_GetStorageUsage(&Usage) == ESP_OK) { + if (Usage.TotalBytes >= (1024 * 1024)) { + lv_label_set_text_fmt(memory_storage_used_label, "%.2f MB", Usage.UsedBytes / (1024.0f * 1024.0f)); + lv_label_set_text_fmt(memory_storage_free_label, "%.2f MB", Usage.FreeBytes / (1024.0f * 1024.0f)); + } else { + lv_label_set_text_fmt(memory_storage_used_label, "%.1f KB", Usage.UsedBytes / 1024.0f); + lv_label_set_text_fmt(memory_storage_free_label, "%.1f KB", Usage.FreeBytes / 1024.0f); + } + + ESP_LOGD(TAG, "Updated flash storage usage: Used %u bytes, Free %u bytes", Usage.UsedBytes, Usage.FreeBytes); + } + } + + if (memory_coredump_used_label && memory_coredump_total_label) { + if (MemoryManager_GetCoredumpUsage(&Usage) == ESP_OK) { + lv_label_set_text_fmt(memory_coredump_used_label, "%u KB", Usage.UsedBytes / 1024); + lv_label_set_text_fmt(memory_coredump_total_label, "%u KB", Usage.TotalBytes / 1024); + + ESP_LOGD(TAG, "Updated flash coredump usage: Used %u bytes, Total %u bytes", Usage.UsedBytes, Usage.TotalBytes); + } + } +} \ No newline at end of file diff --git a/main/Application/Tasks/GUI/UI/ui_settings.h b/main/Application/Tasks/GUI/UI/ui_settings.h new file mode 100644 index 0000000..953f6f5 --- /dev/null +++ b/main/Application/Tasks/GUI/UI/ui_settings.h @@ -0,0 +1,47 @@ +/* + * ui_settings.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Settings UI implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef UI_SETTINGS_H_ +#define UI_SETTINGS_H_ + +#include + +#include + +extern lv_obj_t *ui_settings_wifi_status_label; +extern lv_obj_t *ui_settings_wifi_connect_btn; + +/** @brief Initializes the settings UI. + * @param p_Parent Pointer to the parent object where the settings UI will be attached + */ +void ui_settings_init(lv_obj_t *p_Parent); + +/** @brief Updates flash partition usage information in the Flash settings page. + * Call this when the Flash settings page becomes visible to show + * current storage and coredump partition usage. + * @note This function queries the FlashManager for current partition usage + * and updates the UI labels accordingly. + */ +void ui_settings_update_memory_usage(void); + +#endif /* UI_SETTINGS_H_ */ \ No newline at end of file diff --git a/main/Application/Tasks/GUI/UI/ui_settings_events.cpp b/main/Application/Tasks/GUI/UI/ui_settings_events.cpp new file mode 100644 index 0000000..44922ff --- /dev/null +++ b/main/Application/Tasks/GUI/UI/ui_settings_events.cpp @@ -0,0 +1,346 @@ +/* + * ui_Settings_Events.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Settings UI implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include + +#include "managers.h" + +#include "ui_settings_events.h" + +static const char *TAG = "ui_settings_events"; + +void on_Lepton_Emissivity_Slider_Callback(lv_event_t *e) +{ + int Value; + Settings_Lepton_t LeptonSettings; + lv_obj_t *Slider = static_cast(lv_event_get_target(e)); + Slider_Widgets_t *Widgets = static_cast(lv_obj_get_user_data(Slider)); + + SettingsManager_GetLepton(&LeptonSettings); + + Value = static_cast(lv_slider_get_value(Slider)); + lv_label_set_text_fmt(Widgets->Label, "%d", Value); + + /* Save on release only */ + if (lv_event_get_code(e) == LV_EVENT_RELEASED) { + SettingsManager_ChangeNotification_t Changed; + + Changed.ID = SETTINGS_ID_LEPTON_EMISSIVITY; + Changed.Value = Value; + LeptonSettings.CurrentEmissivity = static_cast(Value); + SettingsManager_UpdateLepton(&LeptonSettings, &Changed); + } +} + +void on_Display_Brightness_Slider_Callback(lv_event_t *e) +{ + int Value; + Settings_Display_t DisplaySettings; + lv_obj_t *Slider = static_cast(lv_event_get_target(e)); + Slider_Widgets_t *Widgets = static_cast(lv_obj_get_user_data(Slider)); + + SettingsManager_GetDisplay(&DisplaySettings); + + Value = static_cast(lv_slider_get_value(Slider)); + lv_label_set_text_fmt(Widgets->Label, "%d", Value); + + /* Save on release only */ + if (lv_event_get_code(e) == LV_EVENT_RELEASED) { + DisplaySettings.Brightness = static_cast(Value); + SettingsManager_UpdateDisplay(&DisplaySettings); + } +} + +void on_Lepton_Dropdown_Callback(lv_event_t *e) +{ + int Value; + Settings_Lepton_t LeptonSettings; + SettingsManager_ChangeNotification_t Changed; + lv_obj_t *Dropdown = static_cast(lv_event_get_target(e)); + Slider_Widgets_t *Widgets = static_cast(lv_obj_get_user_data(Dropdown)); + + SettingsManager_GetLepton(&LeptonSettings); + + Value = LeptonSettings.EmissivityPresets[lv_dropdown_get_selected(Dropdown)].Value * 100; + lv_slider_set_value(Widgets->Slider, Value, LV_ANIM_ON); + lv_label_set_text_fmt(Widgets->Label, "%d", Value); + + /* Save settings directly without triggering additional events */ + Changed.ID = SETTINGS_ID_LEPTON_EMISSIVITY; + Changed.Value = Value; + LeptonSettings.CurrentEmissivity = static_cast(Value); + SettingsManager_UpdateLepton(&LeptonSettings, &Changed); +} + +void on_WiFi_Autoconnect_Callback(lv_event_t *e) +{ + Settings_WiFi_t WiFiSettings; + lv_obj_t *switch_obj = static_cast(lv_event_get_target(e)); + + SettingsManager_GetWiFi(&WiFiSettings); + + if (lv_obj_has_state(switch_obj, LV_STATE_CHECKED)) { + WiFiSettings.AutoConnect = true; + + ESP_LOGI(TAG, "WiFi autoconnect enabled"); + } else { + WiFiSettings.AutoConnect = false; + + ESP_LOGI(TAG, "WiFi autoconnect disabled"); + } + + SettingsManager_UpdateWiFi(&WiFiSettings); +} + +void on_WiFi_Connect_Callback(lv_event_t *e) +{ + ESP_LOGI(TAG, "WiFi connect button clicked, posting network event..."); + + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_OPEN_WIFI_REQUEST, NULL, 0, 0); +} + +void on_Network_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "Network event received: ID=%d", ID); + + switch (ID) { + case NETWORK_EVENT_WIFI_GOT_IP: { + break; + } + case NETWORK_EVENT_WIFI_DISCONNECTED: { + break; + } + } +} + +void on_Settings_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "Settings event received: ID=%d", ID); + + switch (ID) { + case SETTINGS_EVENT_LEPTON_CHANGED: { + /* + SettingsManager_ChangeNotification_t Changed; + + memcpy(&Changed, p_Data, sizeof(SettingsManager_ChangeNotification_t)); + + ESP_LOGD(TAG, "Lepton settings changed: ID=%d", Changed.ID); + ESP_LOGD(TAG, "Lepton settings changed: Value=%d", Changed.Value); + + if (Changed.ID == SETTINGS_ID_LEPTON_EMISSIVITY) { + lv_slider_set_value(emissivity_widgets.Slider, Changed.Value, LV_ANIM_ON); + lv_label_set_text_fmt(emissivity_widgets.Label, "%d", static_cast(Changed.Value)); + } +*/ + break; + } + case SETTINGS_EVENT_WIFI_CHANGED: { + ESP_LOGI(TAG, "WiFi settings changed."); + + break; + } + } +} + +void on_USB_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "USB event received: ID=%d", ID); + + switch (ID) { + case USB_EVENT_CABLE_CONNECTED: { + lv_obj_remove_state(usb_mode_switch, LV_STATE_DISABLED); + lv_obj_remove_state(usb_uvc_switch, LV_STATE_DISABLED); + lv_obj_clear_state(usb_mode_switch, LV_STATE_CHECKED); + lv_obj_clear_state(usb_uvc_switch, LV_STATE_CHECKED); + + ESP_LOGD(TAG, "USB cable connected, switches enabled"); + + break; + } + case USB_EVENT_CABLE_DISCONNECTED: { + lv_obj_add_state(usb_mode_switch, LV_STATE_DISABLED); + lv_obj_add_state(usb_uvc_switch, LV_STATE_DISABLED); + lv_obj_clear_state(usb_mode_switch, LV_STATE_CHECKED); + lv_obj_clear_state(usb_uvc_switch, LV_STATE_CHECKED); + + ESP_LOGD(TAG, "USB cable disconnected, switches disabled"); + + break; + } + case USB_EVENT_INITIALIZED: { + ESP_LOGD(TAG, "USB subsystem initialized"); + + break; + } + case USB_EVENT_UNINITIALIZED: { + lv_obj_add_state(usb_mode_switch, LV_STATE_DISABLED); + lv_obj_add_state(usb_uvc_switch, LV_STATE_DISABLED); + lv_obj_clear_state(usb_mode_switch, LV_STATE_CHECKED); + lv_obj_clear_state(usb_uvc_switch, LV_STATE_CHECKED); + + break; + } + } +} + +void on_Memory_ClearNVS_Callback(lv_event_t *e) +{ + esp_err_t Error; + + ESP_LOGD(TAG, "Resetting settings to factory defaults..."); + + Error = SettingsManager_ResetToDefaults(); + if (Error == ESP_OK) { + ESP_LOGD(TAG, "Settings reset successfully, restarting..."); + + vTaskDelay(pdMS_TO_TICKS(500)); + esp_restart(); + } else { + ESP_LOGE(TAG, "Failed to reset settings: %d!", Error); + } +} + +void on_Image_Format_Dropdown_Callback(lv_event_t *e) +{ + Settings_System_t SystemSettings; + + SettingsManager_GetSystem(&SystemSettings); + + /* Update image format */ + SystemSettings.ImageFormat = static_cast(lv_dropdown_get_selected(static_cast(lv_event_get_target(e)))); + SettingsManager_UpdateSystem(&SystemSettings, NULL); + + /* Show/hide JPEG quality slider based on format */ + if (SystemSettings.ImageFormat == IMAGE_FORMAT_JPEG) { + lv_obj_remove_flag(jpeg_quality_row, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(lv_obj_get_parent(jpeg_quality_row), LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(jpeg_quality_row, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(lv_obj_get_parent(jpeg_quality_row), LV_OBJ_FLAG_HIDDEN); + } + + ESP_LOGI(TAG, "Image format changed to: %d", SystemSettings.ImageFormat); +} + +void on_Image_JpegQuality_Slider_Callback(lv_event_t *e) +{ + int Value; + Settings_System_t SystemSettings; + lv_obj_t *Slider = static_cast(lv_event_get_target(e)); + Slider_Widgets_t *widgets = static_cast(lv_obj_get_user_data(Slider)); + + SettingsManager_GetSystem(&SystemSettings); + + Value = static_cast(lv_slider_get_value(Slider)); + lv_label_set_text_fmt(widgets->Label, "%d", Value); + + /* Update immediately for live preview */ + SystemSettings.JpegQuality = static_cast(Value); + SettingsManager_UpdateSystem(&SystemSettings, NULL); +} + +void on_Memory_ClearStorage_Callback(lv_event_t *e) +{ + esp_err_t Error; + + ESP_LOGI(TAG, "Erasing storage partition..."); + + Error = MemoryManager_EraseStorage(); + if (Error == ESP_OK) { + ESP_LOGI(TAG, "Storage partition erased successfully"); + + ui_settings_update_memory_usage(); + } else { + ESP_LOGE(TAG, "Failed to erase storage partition: %d!", Error); + } +} + +void on_Memory_ClearCoredump_Callback(lv_event_t *e) +{ + esp_err_t Error; + + ESP_LOGI(TAG, "Erasing coredump partition..."); + + Error = MemoryManager_EraseCoredump(); + if (Error == ESP_OK) { + ESP_LOGI(TAG, "Coredump partition erased successfully"); + + ui_settings_update_memory_usage(); + } else { + ESP_LOGE(TAG, "Failed to erase coredump partition: %d!", Error); + } +} + +void on_USB_Mode_Switch_Callback(lv_event_t *e) +{ + esp_err_t Error; + lv_obj_t *Switch = static_cast(lv_event_get_target(e)); + bool Enable = lv_obj_has_state(Switch, LV_STATE_CHECKED); + + ESP_LOGI(TAG, "%s USB MSC...", Enable ? "Enabling" : "Disabling"); + + Error = USBManager_EnableMSC(Enable); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to enqueue MSC command: %d!", Error); + + if (Enable) { + lv_obj_clear_state(Switch, LV_STATE_CHECKED); + } else { + lv_obj_add_state(Switch, LV_STATE_CHECKED); + } + } +} + +void on_USB_UVC_Switch_Callback(lv_event_t *e) +{ + esp_err_t Error; + lv_obj_t *Switch = static_cast(lv_event_get_target(e)); + bool Enable = lv_obj_has_state(Switch, LV_STATE_CHECKED); + + ESP_LOGI(TAG, "%s USB UVC...", Enable ? "Enabling" : "Disabling"); + + Error = USBManager_EnableUVC(Enable); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to enqueue UVC command: %d!", Error); + + if (Enable) { + lv_obj_clear_state(Switch, LV_STATE_CHECKED); + } else { + lv_obj_add_state(Switch, LV_STATE_CHECKED); + } + } +} + +void on_USB_CDC_Switch_Callback(lv_event_t *e) +{ + Settings_USB_t USBSettings; + + SettingsManager_GetUSB(&USBSettings); + + USBSettings.CDC_Enabled = lv_obj_has_state(static_cast(lv_event_get_target(e)), LV_STATE_CHECKED); + + ESP_LOGI(TAG, "CDC %s in USB settings (takes effect on next USB enable).", + USBSettings.CDC_Enabled ? "enabled" : "disabled"); + + SettingsManager_UpdateUSB(&USBSettings, NULL); +} diff --git a/main/Application/Tasks/GUI/UI/ui_settings_events.h b/main/Application/Tasks/GUI/UI/ui_settings_events.h new file mode 100644 index 0000000..db1bd41 --- /dev/null +++ b/main/Application/Tasks/GUI/UI/ui_settings_events.h @@ -0,0 +1,158 @@ +/* + * ui_Settings_Events.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Settings UI implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef UI_SETTINGS_EVENTS_H_ +#define UI_SETTINGS_EVENTS_H_ + +#include + +#include + +/** @brief + */ +typedef struct { + lv_obj_t *Slider; + lv_obj_t *Label; +} Slider_Widgets_t; + +/** @brief + */ +typedef struct { + lv_obj_t *Container; + lv_obj_t *Page; +} Menu_Page_Result_t; + +extern Slider_Widgets_t brightness_widgets; +extern Slider_Widgets_t emissivity_widgets; +extern Slider_Widgets_t jpeg_quality_widgets; + +extern lv_obj_t *usb_mode_switch; +extern lv_obj_t *usb_uvc_switch; +extern lv_obj_t *usb_cdc_switch; +extern lv_obj_t *image_format_dropdown; +extern lv_obj_t *jpeg_quality_row; + +/** @brief Display brightness slider event callback to update value label. + * @param e Pointer to the event object + */ +void on_Lepton_Emissivity_Slider_Callback(lv_event_t *e); + +/** @brief Display brightness slider event callback to update value label. + * @param e Pointer to the event object + */ +void on_Display_Brightness_Slider_Callback(lv_event_t *e); + +/** @brief Dropdown event callback to update slider and label with selected emissivity preset value. + * @param e Pointer to the event object + */ +void on_Lepton_Dropdown_Callback(lv_event_t *e); + +/** @brief Switch event callback to toggle WiFi autoconnect setting. + * @param e Pointer to the event object + */ +void on_WiFi_Autoconnect_Callback(lv_event_t *e); + +/** @brief WiFi connect button callback to open WiFi connection dialog. + * @param e Pointer to the event object + */ +void on_WiFi_Connect_Callback(lv_event_t *e); + +/** @brief Forward declaration for memory usage update function. + * Implemented in ui_Settings.cpp to update storage and + * coredump partition usage displays. + */ +void ui_settings_update_memory_usage(void); + +/** @brief Flash clear NVS button callback to reset all settings to factory defaults. + * @param e Pointer to the event object + */ +void on_Memory_ClearNVS_Callback(lv_event_t *e); + +/** @brief Memory clear storage button callback to erase storage partition. + * @param e Pointer to the event object + */ +void on_Memory_ClearStorage_Callback(lv_event_t *e); + +/** @brief Memory clear coredump button callback to erase coredump partition. + * @param e Pointer to the event object + */ +void on_Memory_ClearCoredump_Callback(lv_event_t *e); + +/** @brief USB mode switch callback to enable/disable the composite USB device. + * Reads current UVC and CDC enable settings and starts all configured classes. + * @param e Pointer to the event object + */ +void on_USB_Mode_Switch_Callback(lv_event_t *e); + +/** @brief USB UVC switch callback to enable/disable UVC in USB settings. + * Changes take effect on the next USB enable/disable cycle. + * @param e Pointer to the event object + */ +void on_USB_UVC_Switch_Callback(lv_event_t *e); + +/** @brief USB CDC switch callback to enable/disable CDC-ACM in USB settings. + * Changes take effect on the next USB enable/disable cycle. + * @param e Pointer to the event object + */ +void on_USB_CDC_Switch_Callback(lv_event_t *e); + +/** @brief Image format dropdown callback to change image format setting. + * @param e Pointer to the event object + */ +void on_Image_Format_Dropdown_Callback(lv_event_t *e); + +/** @brief JPEG quality slider callback to update quality setting. + * @param e Pointer to the event object + */ +void on_Image_JpegQuality_Slider_Callback(lv_event_t *e); + +/** @brief Network event handler which is used to handle network-related events such as WiFi connection changes. + * This can be used to update the UI or internal state based on network events. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +void on_Network_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data); + +/** @brief Settings event handler which is used to update the UI elements when a + * settings change event is received from the Settings Manager. + * This ensures that the UI always reflects the current settings values. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +void on_Settings_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data); + +/** @brief USB event handler which is used to update the UI elements when a + * USB change event is received from the USB Manager. + * This ensures that the UI always reflects the current settings values. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +void on_USB_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data); + +#endif /* UI_SETTINGS_EVENTS_H_ */ \ No newline at end of file diff --git a/main/Application/Tasks/GUI/guiTask.cpp b/main/Application/Tasks/GUI/guiTask.cpp new file mode 100644 index 0000000..2ea9ef0 --- /dev/null +++ b/main/Application/Tasks/GUI/guiTask.cpp @@ -0,0 +1,1344 @@ +/* + * guiTask.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: GUI task implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "guiTask.h" +#include "Export/ui.h" +#include "Application/application.h" +#include "Application/Manager/managers.h" +#include "Application/Manager/Network/Server/server.h" +#include "Private/guiHelper.h" +#include "Private/guiImageSave.h" +#include "UI/ui_messagebox.h" +#include "UI/ui_settings.h" + +#include "lepton.h" + +ESP_EVENT_DEFINE_BASE(GUI_EVENTS); + +GUI_Task_State_t _GUI_Task_State; + +static const char *TAG = "GUI-Task"; + +/** @brief Event handler for GUI events (e.g., image save completion). + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_GUI_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "GUI event received: ID=%d", ID); + + switch (ID) { + case GUI_EVENT_THERMAL_IMAGE_SAVED: { + ESP_LOGD(TAG, "Thermal image saved successfully"); + + break; + } + case GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED: { + ESP_LOGE(TAG, "Thermal image save failed"); + + break; + } + } +} + +/** @brief Event handler for the Devices events to receive updates when settings are changed. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_Devices_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "Devices event received: ID=%d", ID); + + switch (ID) { + case DEVICE_EVENT_RESPONSE_BATTERY_VOLTAGE: { + if (p_Data != NULL) { + memcpy(&_GUI_Task_State.BatteryInfo, p_Data, sizeof(App_Devices_Battery_t)); + + xEventGroupSetBits(_GUI_Task_State.EventGroup, BATTERY_VOLTAGE_READY); + } else { + ESP_LOGE(TAG, "DEVICE_EVENT_RESPONSE_BATTERY_VOLTAGE received with NULL data"); + } + + break; + } + case DEVICE_EVENT_RESPONSE_CHARGING: { + xEventGroupSetBits(_GUI_Task_State.EventGroup, BATTERY_CHARGING_STATUS_READY); + + break; + } + } +} + +/** @brief Event handler for the Network events to receive updates when network events are triggered (e.g., WiFi connection changes). + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_Network_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "Network event received: ID=%d", ID); + + switch (ID) { + case NETWORK_EVENT_WIFI_CONNECTED: { + break; + } + case NETWORK_EVENT_WIFI_GOT_IP: { + memcpy(&_GUI_Task_State.IP_Info, p_Data, sizeof(Network_IP_Info_t)); + _GUI_Task_State.WiFiConnected = true; + + xEventGroupSetBits(_GUI_Task_State.EventGroup, WIFI_CONNECTION_STATE_CHANGED); + + break; + } + case NETWORK_EVENT_WIFI_DISCONNECTED: { + _GUI_Task_State.WiFiConnected = false; + + xEventGroupSetBits(_GUI_Task_State.EventGroup, WIFI_CONNECTION_STATE_CHANGED); + + break; + } + case NETWORK_EVENT_PROV_STARTED: { + _GUI_Task_State.ProvisioningActive = true; + _GUI_Task_State.WiFiConnected = false; + + xEventGroupSetBits(_GUI_Task_State.EventGroup, PROVISIONING_STATE_CHANGED); + + break; + } + case NETWORK_EVENT_PROV_STOPPED: { + _GUI_Task_State.ProvisioningActive = false; + _GUI_Task_State.WiFiConnected = false; + + xEventGroupSetBits(_GUI_Task_State.EventGroup, PROVISIONING_STATE_CHANGED); + xEventGroupSetBits(_GUI_Task_State.EventGroup, WIFI_CONNECTION_STATE_CHANGED); + + break; + } + case NETWORK_EVENT_PROV_SUCCESS: { + _GUI_Task_State.ProvisioningActive = false; + + xEventGroupSetBits(_GUI_Task_State.EventGroup, PROVISIONING_STATE_CHANGED); + + break; + } + case NETWORK_EVENT_PROV_TIMEOUT: { + _GUI_Task_State.ProvisioningActive = false; + _GUI_Task_State.WiFiConnected = false; + + xEventGroupSetBits(_GUI_Task_State.EventGroup, PROVISIONING_STATE_CHANGED); + xEventGroupSetBits(_GUI_Task_State.EventGroup, WIFI_CONNECTION_STATE_CHANGED); + + break; + } + case NETWORK_EVENT_SERVER_STARTED: { + ESP_LOGD(TAG, "Network frame registered with server"); + + /* Register thermal frame with server (called after server is started) */ + Server_SetThermalFrame(&_GUI_Task_State.NetworkFrame); + + break; + } + case NETWORK_EVENT_AP_STA_CONNECTED: { + break; + } + case NETWORK_EVENT_AP_STA_DISCONNECTED: { + break; + } + } +} + +/** @brief Event handler for the USB events to receive UVC streaming state changes. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_USB_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "USB event received in GUI: ID=%d", ID); + + switch (ID) { + case USB_EVENT_UVC_STREAMING_START: { + _GUI_Task_State.isUVCStreaming = true; + + xEventGroupSetBits(_GUI_Task_State.EventGroup, UVC_STREAMING_STATE_CHANGED); + + break; + } + case USB_EVENT_UVC_STREAMING_STOP: { + _GUI_Task_State.isUVCStreaming = false; + + xEventGroupSetBits(_GUI_Task_State.EventGroup, UVC_STREAMING_STATE_CHANGED); + + break; + } + case USB_EVENT_UNINITIALIZED: { + if (_GUI_Task_State.isUVCStreaming) { + _GUI_Task_State.isUVCStreaming = false; + + xEventGroupSetBits(_GUI_Task_State.EventGroup, UVC_STREAMING_STATE_CHANGED); + } + + break; + } + default: { + break; + } + } +} + +/** @brief Event handler for the Lepton events to receive updates when Lepton events are triggered (e.g., new frame ready, camera errors). + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_Lepton_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "Lepton event received: ID=%d", ID); + + switch (ID) { + case LEPTON_EVENT_CAMERA_READY: { + if (p_Data != NULL) { + memcpy(&_GUI_Task_State.LeptonDeviceInfo, static_cast(p_Data), sizeof(App_Lepton_Device_t)); + + xEventGroupSetBits(_GUI_Task_State.EventGroup, LEPTON_CAMERA_READY); + } else { + ESP_LOGE(TAG, "LEPTON_EVENT_CAMERA_READY received with NULL data"); + } + + break; + } + case LEPTON_EVENT_CAMERA_ERROR: { + xEventGroupSetBits(_GUI_Task_State.EventGroup, LEPTON_CAMERA_ERROR); + + break; + } + case LEPTON_EVENT_RESPONSE_FPA_AUX_TEMP: { + if (p_Data != NULL) { + memcpy(&_GUI_Task_State.LeptonTemperatures, p_Data, sizeof(App_Lepton_Temperatures_t)); + + xEventGroupSetBits(_GUI_Task_State.EventGroup, LEPTON_TEMP_READY); + } else { + ESP_LOGE(TAG, "LEPTON_EVENT_RESPONSE_FPA_AUX_TEMP received with NULL data"); + } + + break; + } + case LEPTON_EVENT_RESPONSE_SPOTMETER: { + if (p_Data != NULL) { + memcpy(&_GUI_Task_State.ROIResult, p_Data, sizeof(App_Lepton_ROI_Result_t)); + + xEventGroupSetBits(_GUI_Task_State.EventGroup, LEPTON_SPOTMETER_READY); + } else { + ESP_LOGE(TAG, "LEPTON_EVENT_RESPONSE_SPOTMETER received with NULL data"); + } + + break; + } + case LEPTON_EVENT_RESPONSE_SCENE_STATISTICS: { + if (p_Data != NULL) { + memcpy(&_GUI_Task_State.ROIResult, p_Data, sizeof(App_Lepton_ROI_Result_t)); + + xEventGroupSetBits(_GUI_Task_State.EventGroup, LEPTON_SCENE_STATISTICS_READY); + } else { + ESP_LOGE(TAG, "LEPTON_EVENT_RESPONSE_SCENE_STATISTICS received with NULL data"); + } + + break; + } + case LEPTON_EVENT_RESPONSE_UPTIME: { + if (p_Data != NULL) { + memcpy(&_GUI_Task_State.LeptonUptime, p_Data, sizeof(uint32_t)); + + xEventGroupSetBits(_GUI_Task_State.EventGroup, LEPTON_UPTIME_READY); + } else { + ESP_LOGE(TAG, "LEPTON_EVENT_RESPONSE_UPTIME received with NULL data"); + } + + break; + } + case LEPTON_EVENT_RESPONSE_PIXEL_TEMPERATURE: { + if (p_Data != NULL) { + memcpy(&_GUI_Task_State.SpotTemperature, p_Data, sizeof(float)); + + xEventGroupSetBits(_GUI_Task_State.EventGroup, LEPTON_PIXEL_TEMPERATURE_READY); + } else { + ESP_LOGE(TAG, "LEPTON_EVENT_RESPONSE_PIXEL_TEMPERATURE received with NULL data"); + } + + break; + } + default: { + ESP_LOGW(TAG, "Unhandled Lepton event ID: %d", ID); + + break; + } + } +} + +/** @brief Update the information screen labels. + */ +static void GUI_Update_Info(void) +{ + uint8_t MAC[6]; + char Buffer[32]; + + esp_efuse_mac_get_default(MAC); + snprintf(Buffer, sizeof(Buffer), "%02X:%02X:%02X:%02X:%02X:%02X", MAC[0], MAC[1], MAC[2], MAC[3], MAC[4], MAC[5]); + + lv_label_set_text(ui_Label_Info_Lepton_Serial, _GUI_Task_State.LeptonDeviceInfo.SerialNumber); + lv_label_set_text(ui_Label_Info_Lepton_Part, _GUI_Task_State.LeptonDeviceInfo.PartNumber); + lv_label_set_text(ui_Label_Info_Lepton_GPP_Revision, _GUI_Task_State.LeptonDeviceInfo.SoftwareRevision.GPP_Revision); + lv_label_set_text(ui_Label_Info_Lepton_DSP_Revision, _GUI_Task_State.LeptonDeviceInfo.SoftwareRevision.DSP_Revision); + lv_label_set_text(ui_Label_Info_MAC, Buffer); +} + +/** @brief Update the ROI rectangle on the GUI and on the Lepton. + * @param Type ROI type + * @param x X position of the ROI (in Lepton coordinates 0-159) + * @param y Y position (in Lepton coordinates 0-119) + * @param w Width of the ROI + * @param h Height of the ROI + */ +static void GUI_Update_ROI(Settings_ROI_t ROI) +{ + int32_t DisplayWidth; + int32_t DisplayHeight; + Settings_Lepton_t SettingsLepton; + + if ((ROI.x + ROI.w) > 160) { + ROI.w = 160 - ROI.x; + } + + if ((ROI.y + ROI.h) > 120) { + ROI.h = 120 - ROI.y; + } + + /* Minimum size constraints */ + if (ROI.w < 1) { + ROI.w = 1; + } else if (ROI.h < 1) { + ROI.h = 1; + } + + /* Update visual rectangle on display (convert Lepton coords to display coords) */ + ESP_LOGD(TAG, "Updating ROI rectangle - Start: (%ld,%ld), End: (%ld,%ld), Size: %ldx%ld", + ROI.x, ROI.y, ROI.x + ROI.w, ROI.y + ROI.h, ROI.w, ROI.h); + + DisplayWidth = lv_obj_get_width(ui_Image_Thermal); + DisplayHeight = lv_obj_get_height(ui_Image_Thermal); + + int32_t disp_x = (ROI.x * DisplayWidth) / 160; + int32_t disp_y = (ROI.y * DisplayHeight) / 120; + int32_t disp_w = (ROI.w * DisplayWidth) / 160; + int32_t disp_h = (ROI.h * DisplayHeight) / 120; + + switch (ROI.Type) { + case ROI_TYPE_SPOTMETER: { + if (ui_Image_Main_Thermal_Spotmeter_ROI != NULL) { + lv_obj_set_align(ui_Image_Main_Thermal_Spotmeter_ROI, LV_ALIGN_TOP_LEFT); + lv_obj_set_pos(ui_Image_Main_Thermal_Spotmeter_ROI, disp_x, disp_y); + lv_obj_set_size(ui_Image_Main_Thermal_Spotmeter_ROI, disp_w, disp_h); + } + + break; + } + case ROI_TYPE_SCENE: { + if (ui_Image_Main_Thermal_Scene_ROI != NULL) { + lv_obj_set_align(ui_Image_Main_Thermal_Scene_ROI, LV_ALIGN_TOP_LEFT); + lv_obj_set_pos(ui_Image_Main_Thermal_Scene_ROI, disp_x, disp_y); + lv_obj_set_size(ui_Image_Main_Thermal_Scene_ROI, disp_w, disp_h); + } + + break; + } + case ROI_TYPE_AGC: { + if (ui_Image_Main_Thermal_AGC_ROI != NULL) { + lv_obj_set_align(ui_Image_Main_Thermal_AGC_ROI, LV_ALIGN_TOP_LEFT); + lv_obj_set_pos(ui_Image_Main_Thermal_AGC_ROI, disp_x, disp_y); + lv_obj_set_size(ui_Image_Main_Thermal_AGC_ROI, disp_w, disp_h); + } + + break; + } + case ROI_TYPE_VIDEO_FOCUS: { + if (ui_Image_Main_Thermal_Video_Focus_ROI != NULL) { + lv_obj_set_align(ui_Image_Main_Thermal_Video_Focus_ROI, LV_ALIGN_TOP_LEFT); + lv_obj_set_pos(ui_Image_Main_Thermal_Video_Focus_ROI, disp_x, disp_y); + lv_obj_set_size(ui_Image_Main_Thermal_Video_Focus_ROI, disp_w, disp_h); + } + + break; + } + default: { + ESP_LOGW(TAG, "Invalid GUI ROI type: %d", ROI.Type); + + return; + } + } + + SettingsManager_GetLepton(&SettingsLepton); + + /* Check if an update is required */ + if ((SettingsLepton.ROI[ROI.Type].x == ROI.x) && + (SettingsLepton.ROI[ROI.Type].y == ROI.y) && + (SettingsLepton.ROI[ROI.Type].w == ROI.w) && + (SettingsLepton.ROI[ROI.Type].h == ROI.h)) { + ESP_LOGW(TAG, "ROI unchanged, not updating NVS"); + + return; + } + + /* Copy the new ROI in the existing settings structure */ + memcpy(&SettingsLepton.ROI[ROI.Type], &ROI, sizeof(Settings_ROI_t)); + + /* Save the new ROI to NVS */ + SettingsManager_UpdateLepton(&SettingsLepton); + SettingsManager_Save(); + + /* The Lepton task needs the ROI with the Lepton coordinates */ + SettingsLepton.ROI[ROI.Type] = { + .Type = ROI.Type, + .x = static_cast(ROI.x), + .y = static_cast(ROI.y), + .w = static_cast(ROI.w), + .h = static_cast(ROI.h) + }; + + esp_event_post(GUI_EVENTS, GUI_EVENT_REQUEST_ROI, &SettingsLepton.ROI[ROI.Type], sizeof(Settings_ROI_t), + pdMS_TO_TICKS(100)); +} + +/** @brief Create temperature gradient canvas for palette visualization. + * Generates a vertical gradient from hot (top) to cold (bottom). + */ +static void UI_Canvas_AddTempGradient(void) +{ + /* Generate gradient pixel by pixel */ + uint16_t *Buffer = reinterpret_cast(_GUI_Task_State.GradientCanvasBuffer); + + for (uint32_t y = 0; y < _GUI_Task_State.GradientImageDescriptor.header.h; y++) { + uint32_t Index; + + /* Map y position to palette index (0 = top/hot = white, 179 = bottom/cold = black) + * Iron palette: index 0 = black (cold), index 255 = white (hot) + * So we need to invert: top (y=0) should be index 255, bottom (y=179) should be index 0 + */ + Index = 255 - (y * 255 / (_GUI_Task_State.GradientImageDescriptor.header.h - 1)); + + /* Get RGB888 values from palette */ + uint8_t r8 = Lepton_Palette_Iron[Index][0]; + uint8_t g8 = Lepton_Palette_Iron[Index][1]; + uint8_t b8 = Lepton_Palette_Iron[Index][2]; + + /* Convert RGB888 to RGB565 */ + uint16_t r5 = (r8 >> 3) & 0x1F; + uint16_t g6 = (g8 >> 2) & 0x3F; + uint16_t b5 = (b8 >> 3) & 0x1F; + + /* Fill entire row with same color */ + for (uint32_t x = 0; x < _GUI_Task_State.GradientImageDescriptor.header.w; x++) { + Buffer[y * _GUI_Task_State.GradientImageDescriptor.header.w + x] = (r5 << 11) | (g6 << 5) | b5; + } + } +} + +/** @brief LVGL touch read callback. + * @param p_Indev Input device handle + * @param p_Data Input device data + */ +static void Touch_LVGL_ReadCallback(lv_indev_t *p_Indev, lv_indev_data_t *p_Data) +{ + uint8_t Count; + esp_lcd_touch_point_data_t Data[1]; + esp_lcd_touch_handle_t Touch; + + Touch = static_cast(lv_indev_get_user_data(p_Indev)); + + /* Check if touch controller is available */ + if (Touch == NULL) { + p_Data->state = LV_INDEV_STATE_RELEASED; + + return; + } + + if ((esp_lcd_touch_read_data(Touch) == ESP_OK) && + (esp_lcd_touch_get_data(Touch, Data, &Count, sizeof(Data) / sizeof(Data[0])) == ESP_OK) && + (Count > 0) + ) { + p_Data->point.x = CONFIG_GUI_WIDTH - Data[0].x; + p_Data->point.y = Data[0].y; + p_Data->state = LV_INDEV_STATE_PRESSED; + + ESP_LOGD(TAG, "Raw: (%d, %d)", Data[0].x, Data[0].y); + ESP_LOGD(TAG, "Mapped: (%d, %d)", p_Data->point.x, p_Data->point.y); + +#ifdef CONFIG_GUI_TOUCH_DEBUG + /* Update touch debug visualization */ + if (_GUI_Task_State.TouchDebugCircle != NULL) { + lv_obj_set_pos(_GUI_Task_State.TouchDebugCircle, p_Data->point.x - 10, p_Data->point.y - 10); + lv_obj_clear_flag(_GUI_Task_State.TouchDebugCircle, LV_OBJ_FLAG_HIDDEN); + } + + if (_GUI_Task_State.TouchDebugLabel != NULL) { + char Buffer[64]; + + snprintf(Buffer, sizeof(Buffer), "Raw: %u,%u\nMap: %li,%li", Data[0].x, Data[0].y, p_Data->point.x, p_Data->point.y); + lv_label_set_text(_GUI_Task_State.TouchDebugLabel, Buffer); + lv_obj_clear_flag(_GUI_Task_State.TouchDebugLabel, LV_OBJ_FLAG_HIDDEN); + } +#endif + } else { + p_Data->state = LV_INDEV_STATE_RELEASED; + +#ifdef CONFIG_GUI_TOUCH_DEBUG + /* Hide touch debug visualization when released */ + if (_GUI_Task_State.TouchDebugCircle != NULL) { + lv_obj_add_flag(_GUI_Task_State.TouchDebugCircle, LV_OBJ_FLAG_HIDDEN); + } + + if (_GUI_Task_State.TouchDebugLabel != NULL) { + lv_obj_add_flag(_GUI_Task_State.TouchDebugLabel, LV_OBJ_FLAG_HIDDEN); + } +#endif + } +} + +/** @brief GUI Task main function. + * @param p_Parameters Task parameters + */ +void Task_GUI(void *p_Parameters) +{ + uint32_t Timeout; + App_Context_t *App_Context; + Settings_Lepton_t LeptonSettings; + + /* Precompute x bilinear coefficients once per frame (constant across all rows). + * Saves ImageHeight (180) redundant divisions per pixel column. + * Stack cost: 3 * CONFIG_GUI_WIDTH = 960 bytes. + * NOTE: x_lut_xi is NOT stored — computing 256-x_frac inline avoids a uint8_t + * overflow: when x_frac==0, 256 would truncate to 0, zeroing all weights → black + * pixels. x_inv is computed as uint32_t in the inner loop instead. + */ + uint8_t x_lut_x0[CONFIG_GUI_WIDTH]; + uint8_t x_lut_x1[CONFIG_GUI_WIDTH]; + uint8_t x_lut_xf[CONFIG_GUI_WIDTH]; + + esp_task_wdt_add(NULL); + + App_Context = static_cast(p_Parameters); + _GUI_Task_State.AppContext = App_Context; + ESP_LOGD(TAG, "GUI Task started on core %d", xPortGetCoreID()); + + /* Show splash screen first and wait for all components to become ready before starting the application. */ + /* Initialization process: */ + /* - Loading the settings */ + /* - Waiting for the Lepton */ + Timeout = 0; + do { + EventBits_t EventBits; + + esp_task_wdt_reset(); + + EventBits = xEventGroupGetBits(_GUI_Task_State.EventGroup); + if (EventBits & LEPTON_CAMERA_READY) { + + lv_bar_set_value(ui_SplashScreen_LoadingBar, 100, LV_ANIM_OFF); + + xEventGroupClearBits(_GUI_Task_State.EventGroup, LEPTON_CAMERA_READY); + } else if (EventBits & LEPTON_CAMERA_ERROR) { + lv_label_set_text(ui_SplashScreen_StatusText, "Camera Error"); + + xEventGroupClearBits(_GUI_Task_State.EventGroup, LEPTON_CAMERA_ERROR); + } + + if(Timeout >= 30000) { + esp_event_post(GUI_EVENTS, GUI_EVENT_INIT_ERROR, NULL, 0, pdMS_TO_TICKS(500)); + + break; + } + + lv_timer_handler(); + vTaskDelay(pdMS_TO_TICKS(100)); + Timeout += 100; + } while (lv_bar_get_value(ui_SplashScreen_LoadingBar) < lv_bar_get_max_value(ui_SplashScreen_LoadingBar)); + + lv_disp_load_scr(ui_Main); + + /* Process layout changes after loading new screen */ + lv_timer_handler(); + + /* Set the initial ROI FIRST to give it a size */ + SettingsManager_GetLepton(&LeptonSettings); + GUI_Update_ROI(LeptonSettings.ROI[ROI_TYPE_SPOTMETER]); + GUI_Update_ROI(LeptonSettings.ROI[ROI_TYPE_SCENE]); + GUI_Update_ROI(LeptonSettings.ROI[ROI_TYPE_AGC]); + GUI_Update_ROI(LeptonSettings.ROI[ROI_TYPE_VIDEO_FOCUS]); + + GUI_Update_Info(); + + esp_event_post(GUI_EVENTS, GUI_EVENT_APP_STARTED, NULL, 0, pdMS_TO_TICKS(500)); + + /* Variables for Illuminance values for the scene label. + * Fetch all these values at the beginning to not waste CPU performance because these + * functions aren´t simple get functions + * 0 = Max label + * 1 = Min label + * 2 = Mean label + * 3 = Crosshair label + * 4 = Pixel temperature label + * + * NOTE on coordinate spaces: + * - Labels [3] (Crosshair) and [4] (PixelTemperature) are DIRECT children of + * ui_Image_Thermal, so lv_obj_get_x/y() already returns image-relative coords. + * - Labels [0..2] (Max/Min/Mean) are children of ui_Container_Main_Thermal_Scene_Statistics, + * which itself is a child of ui_Image_Thermal. Their lv_obj_get_x/y() is relative + * to the container, so the container's own offset within the image must be added. + */ + int32_t SceneStatsContainer_x = lv_obj_get_x(ui_Container_Main_Thermal_Scene_Statistics); + int32_t SceneStatsContainer_y = lv_obj_get_y(ui_Container_Main_Thermal_Scene_Statistics); + + int32_t SceneLabel_x0[5] = { SceneStatsContainer_x + lv_obj_get_x(ui_Label_Main_Thermal_Scene_Max), + SceneStatsContainer_x + lv_obj_get_x(ui_Label_Main_Thermal_Scene_Min), + SceneStatsContainer_x + lv_obj_get_x(ui_Label_Main_Thermal_Scene_Mean), + lv_obj_get_x(ui_Label_Main_Thermal_Crosshair), + lv_obj_get_x(ui_Label_Main_Thermal_PixelTemperature) + }; + int32_t SceneLabel_y0[5] = { SceneStatsContainer_y + lv_obj_get_y(ui_Label_Main_Thermal_Scene_Max), + SceneStatsContainer_y + lv_obj_get_y(ui_Label_Main_Thermal_Scene_Min), + SceneStatsContainer_y + lv_obj_get_y(ui_Label_Main_Thermal_Scene_Mean), + lv_obj_get_y(ui_Label_Main_Thermal_Crosshair), + lv_obj_get_y(ui_Label_Main_Thermal_PixelTemperature) + }; + int32_t SceneLabel_w[5] = { lv_obj_get_width(ui_Label_Main_Thermal_Scene_Max), + lv_obj_get_width(ui_Label_Main_Thermal_Scene_Min), + lv_obj_get_width(ui_Label_Main_Thermal_Scene_Mean), + lv_obj_get_width(ui_Label_Main_Thermal_Crosshair), + lv_obj_get_width(ui_Label_Main_Thermal_PixelTemperature) + }; + int32_t SceneLabel_h[5] = { lv_obj_get_height(ui_Label_Main_Thermal_Scene_Max), + lv_obj_get_height(ui_Label_Main_Thermal_Scene_Min), + lv_obj_get_height(ui_Label_Main_Thermal_Scene_Mean), + lv_obj_get_height(ui_Label_Main_Thermal_Crosshair), + lv_obj_get_height(ui_Label_Main_Thermal_PixelTemperature) + }; + + while (_GUI_Task_State.isRunning) { + EventBits_t EventBits; + App_Lepton_FrameReady_t LeptonFrame; + + esp_task_wdt_reset(); + + /* Check for new thermal frame */ + if (xQueueReceive(App_Context->Lepton_FrameEventQueue, &LeptonFrame, 0) == pdTRUE) { + /* During UVC streaming, skip expensive image processing to avoid + SPI contention and watchdog timeouts. Just drain the queue. */ + if (_GUI_Task_State.isUVCStreaming) { + ESP_LOGD(TAG, "UVC streaming active - skipping GUI frame processing"); + } else { + uint8_t *Dst; + uint32_t ImageWidth; + uint32_t ImageHeight; + char Buffer[16]; + uint32_t SceneLabelIlluminance[5] = {0, 0, 0, 0, 0}; + uint32_t SceneLabelCount[5] = {0, 0, 0, 0, 0}; + uint8_t SceneLabelAverageLuminance[5] = {0, 0, 0, 0, 0}; + + /* Reset watchdog before image processing */ + esp_task_wdt_reset(); + + /* Scale from source (160x120) to destination (240x180) using bilinear interpolation */ + Dst = _GUI_Task_State.ThermalCanvasBuffer; + ImageWidth = lv_obj_get_width(ui_Image_Thermal); + ImageHeight = lv_obj_get_height(ui_Image_Thermal); + + /* Skip if image widget not properly initialized yet */ + if ((ImageWidth == 0) || (ImageHeight == 0)) { + ESP_LOGW(TAG, "Image widget not ready yet (size: %ux%u), skipping frame", ImageWidth, ImageHeight); + + continue; + } + + for (uint32_t x = 0; x < ImageWidth; x++) { + uint32_t src_x_fixed = x * ((LeptonFrame.Width - 1) << 16) / ImageWidth; + uint32_t xp = src_x_fixed >> 16; + x_lut_x0[x] = static_cast(xp); + x_lut_x1[x] = static_cast(((xp + 1) < LeptonFrame.Width) ? (xp + 1) : xp); + x_lut_xf[x] = static_cast((src_x_fixed >> 8) & 0xFF); + } + + for (uint32_t y = 0; y < ImageHeight; y++) { + /* Reset watchdog every 20 rows to prevent timeout during image processing */ + if ((y % 20) == 0) { + esp_task_wdt_reset(); + } + + uint32_t src_y_fixed = y * ((LeptonFrame.Height - 1) << 16) / ImageHeight; + uint32_t y0 = src_y_fixed >> 16; + uint32_t y1 = ((y0 + 1) < LeptonFrame.Height) ? (y0 + 1) : y0; + uint32_t y_frac = (src_y_fixed >> 8) & 0xFF; /* 8-bit fractional part */ + uint32_t y_inv = 256 - y_frac; + + for (uint32_t x = 0; x < ImageWidth; x++) { + /* Use precomputed x LUT — avoids 1 multiply + 1 divide per pixel per row. + * x_inv is computed inline (not cached) to avoid uint8_t overflow when x_frac==0. */ + uint32_t x0 = x_lut_x0[x]; + uint32_t x1 = x_lut_x1[x]; + uint32_t x_frac = x_lut_xf[x]; + uint32_t x_inv = 256u - x_frac; + + /* Get the four surrounding pixels */ + uint32_t idx00 = ((y0 * LeptonFrame.Width) + x0) * 3; + uint32_t idx10 = ((y0 * LeptonFrame.Width) + x1) * 3; + uint32_t idx01 = ((y1 * LeptonFrame.Width) + x0) * 3; + uint32_t idx11 = ((y1 * LeptonFrame.Width) + x1) * 3; + + /* Bilinear interpolation using fixed-point arithmetic (8.8 format) */ + /* Weight: (256 - x_frac) * (256 - y_frac), x_frac*(256 - y_frac), etc. */ + uint32_t w00 = (x_inv * y_inv) >> 8; + uint32_t w10 = (x_frac * y_inv) >> 8; + uint32_t w01 = (x_inv * y_frac) >> 8; + uint32_t w11 = (x_frac * y_frac) >> 8; + + uint32_t r = (LeptonFrame.Buffer[idx00 + 0] * w00 + + LeptonFrame.Buffer[idx10 + 0] * w10 + + LeptonFrame.Buffer[idx01 + 0] * w01 + + LeptonFrame.Buffer[idx11 + 0] * w11) >> 8; + + uint32_t g = (LeptonFrame.Buffer[idx00 + 1] * w00 + + LeptonFrame.Buffer[idx10 + 1] * w10 + + LeptonFrame.Buffer[idx01 + 1] * w01 + + LeptonFrame.Buffer[idx11 + 1] * w11) >> 8; + + uint32_t b = (LeptonFrame.Buffer[idx00 + 2] * w00 + + LeptonFrame.Buffer[idx10 + 2] * w10 + + LeptonFrame.Buffer[idx01 + 2] * w01 + + LeptonFrame.Buffer[idx11 + 2] * w11) >> 8; + + /* Inside the image area under the label: Add the Luminance + * Note: The image widget has 180° rotation applied via lv_image_set_rotation. + * - x_rot = Image_Width - 1 - x (X axis is inverted due to rotation) + * - y is used directly (Y axis matches label position directly) + */ + /* Map buffer coordinates to display coordinates for 180° rotated image: + * x: x_rot = ImageWidth - 1 - x (horizontal mirror) + * y: y_rot = ImageHeight - 1 - y (vertical mirror) + * Labels use display coordinates, so both axes must be inverted. */ + uint32_t x_rot = ImageWidth - 1 - x; + uint32_t y_rot = ImageHeight - 1 - y; + + /* Max label */ + if ((x_rot >= SceneLabel_x0[0]) && + (x_rot < (SceneLabel_x0[0] + SceneLabel_w[0])) && + (y_rot >= SceneLabel_y0[0]) && + (y_rot < (SceneLabel_y0[0] + SceneLabel_h[0]))) { + /* BT.601 luma in integer: (77*R + 150*G + 29*B) >> 8 (coefficients sum to 256) */ + SceneLabelIlluminance[0] += ((77u * r) + (150u * g) + (29u * b)) >> 8u; + SceneLabelCount[0]++; + } + + /* Min label */ + if ((x_rot >= SceneLabel_x0[1]) && + (x_rot < (SceneLabel_x0[1] + SceneLabel_w[1])) && + (y_rot >= SceneLabel_y0[1]) && + (y_rot < (SceneLabel_y0[1] + SceneLabel_h[1]))) { + SceneLabelIlluminance[1] += ((77u * r) + (150u * g) + (29u * b)) >> 8u; + SceneLabelCount[1]++; + } + + /* Mean label */ + if ((x_rot >= SceneLabel_x0[2]) && + (x_rot < (SceneLabel_x0[2] + SceneLabel_w[2])) && + (y_rot >= SceneLabel_y0[2]) && + (y_rot < (SceneLabel_y0[2] + SceneLabel_h[2]))) { + SceneLabelIlluminance[2] += ((77u * r) + (150u * g) + (29u * b)) >> 8u; + SceneLabelCount[2]++; + } + + /* Crosshair label */ + if ((x_rot >= SceneLabel_x0[3]) && + (x_rot < (SceneLabel_x0[3] + SceneLabel_w[3])) && + (y_rot >= SceneLabel_y0[3]) && + (y_rot < (SceneLabel_y0[3] + SceneLabel_h[3]))) { + SceneLabelIlluminance[3] += ((77u * r) + (150u * g) + (29u * b)) >> 8u; + SceneLabelCount[3]++; + } + + /* Pixel temperature label */ + if ((x_rot >= SceneLabel_x0[4]) && + (x_rot < (SceneLabel_x0[4] + SceneLabel_w[4])) && + (y_rot >= SceneLabel_y0[4]) && + (y_rot < (SceneLabel_y0[4] + SceneLabel_h[4]))) { + SceneLabelIlluminance[4] += ((77u * r) + (150u * g) + (29u * b)) >> 8u; + SceneLabelCount[4]++; + } + + uint32_t dst_idx = (y * ImageWidth) + x; + + /* Convert to RGB565 - LVGL handles swapping with RGB565_SWAPPED */ + uint16_t rgb565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + + /* Low byte first */ + Dst[(dst_idx * 2) + 0] = rgb565 & 0xFF; + + /* High byte second */ + Dst[(dst_idx * 2) + 1] = (rgb565 >> 8) & 0xFF; + } + } + + /* Set the average Luminance and the text color */ + for (uint8_t i = 0; i < (sizeof(SceneLabelAverageLuminance) / sizeof(SceneLabelAverageLuminance[0])); i++) { + if (SceneLabelCount[i] > 0) { + SceneLabelAverageLuminance[i] = SceneLabelIlluminance[i] / SceneLabelCount[i]; + } else { + SceneLabelAverageLuminance[i] = 0; + } + } + + lv_obj_set_style_text_color(ui_Label_Main_Thermal_Scene_Max, + (SceneLabelAverageLuminance[0] > 128) ? lv_color_black() : lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_text_color(ui_Label_Main_Thermal_Scene_Min, + (SceneLabelAverageLuminance[1] > 128) ? lv_color_black() : lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_text_color(ui_Label_Main_Thermal_Scene_Mean, + (SceneLabelAverageLuminance[2] > 128) ? lv_color_black() : lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_text_color(ui_Label_Main_Thermal_Crosshair, + (SceneLabelAverageLuminance[3] > 128) ? lv_color_black() : lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_text_color(ui_Label_Main_Thermal_PixelTemperature, + (SceneLabelAverageLuminance[4] > 128) ? lv_color_black() : lv_color_white(), LV_PART_MAIN); + + /* Reset watchdog after image processing */ + esp_task_wdt_reset(); + + /* Max temperature (top of gradient) */ + float temp_max_celsius = (LeptonFrame.Max / 100.0f) - 273.15f; + snprintf(Buffer, sizeof(Buffer), "%.1f °C", temp_max_celsius); + lv_label_set_text(ui_Label_TempScaleMax, Buffer); + + /* Min temperature (bottom of gradient) */ + float temp_min_celsius = (LeptonFrame.Min / 100.0f) - 273.15f; + snprintf(Buffer, sizeof(Buffer), "%.1f °C", temp_min_celsius); + lv_label_set_text(ui_Label_TempScaleMin, Buffer); + + /* Trigger LVGL to redraw the image */ + lv_obj_invalidate(ui_Image_Thermal); + ESP_LOGD(TAG, "Updated thermal image display (src: %ux%u -> dst: %ux%u)", LeptonFrame.Width, LeptonFrame.Height, + ImageWidth, ImageHeight); + + /* Save frame if requested */ + if (_GUI_Task_State.SaveNextFrameRequested) { + App_Lepton_FrameReady_t SaveFrame; + + _GUI_Task_State.SaveNextFrameRequested = false; + + /* Prepare frame data for save task (using scaled RGB565 buffer) */ + SaveFrame.Buffer = _GUI_Task_State.ThermalCanvasBuffer; /* Use scaled display buffer */ + SaveFrame.Width = ImageWidth; + SaveFrame.Height = ImageHeight; + SaveFrame.Channels = 2; /* RGB565 = 2 bytes per pixel */ + SaveFrame.Min = LeptonFrame.Min; + SaveFrame.Max = LeptonFrame.Max; + + /* Send to background save task (non-blocking) */ + if (xQueueSend(_GUI_Task_State.ImageSaveQueue, &SaveFrame, 0) != pdTRUE) { + ESP_LOGW(TAG, "Image save queue full, skipping save"); + + esp_event_post(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED, NULL, 0, pdMS_TO_TICKS(100)); + } + } + + /* Update network frame for server streaming if server is running */ + if (Server_IsRunning()) { + if (xSemaphoreTake(_GUI_Task_State.NetworkFrame.Mutex, 0) == pdTRUE) { + /* Convert scaled RGB565 buffer to RGB888 for network transmission */ + uint8_t *rgb888_dst = _GUI_Task_State.NetworkRGBBuffer; + + /* Reset watchdog before RGB conversion */ + esp_task_wdt_reset(); + + for (uint32_t i = 0; i < (ImageWidth * ImageHeight); i++) { + /* Read RGB565 value (little endian) */ + uint16_t rgb565 = Dst[(i * 2) + 0] | (Dst[(i * 2) + 1] << 8); + + /* Convert RGB565 to RGB888 */ + uint8_t r = (rgb565 >> 8) & 0xF8; + uint8_t g = (rgb565 >> 3) & 0xFC; + uint8_t b = (rgb565 << 3) & 0xF8; + + /* Store as RGB888 */ + rgb888_dst[(i * 3) + 0] = r; + rgb888_dst[(i * 3) + 1] = g; + rgb888_dst[(i * 3) + 2] = b; + } + + _GUI_Task_State.NetworkFrame.Buffer = _GUI_Task_State.NetworkRGBBuffer; + _GUI_Task_State.NetworkFrame.Width = ImageWidth; + _GUI_Task_State.NetworkFrame.Height = ImageHeight; + _GUI_Task_State.NetworkFrame.Timestamp = esp_timer_get_time() / 1000; + + xSemaphoreGive(_GUI_Task_State.NetworkFrame.Mutex); + + /* Reset watchdog after RGB conversion */ + esp_task_wdt_reset(); + } + + Server_NotifyClients(); + } + } + } + + /* Process the recieved system events */ + EventBits = xEventGroupGetBits(_GUI_Task_State.EventGroup); + if (EventBits & GUI_TASK_STOP_REQUEST) { + ESP_LOGD(TAG, "Stop request received"); + + _GUI_Task_State.isRunning = false; + + xEventGroupClearBits(_GUI_Task_State.EventGroup, GUI_TASK_STOP_REQUEST); + + break; + } else if (EventBits & BATTERY_VOLTAGE_READY) { + char Buffer[16]; + + snprintf(Buffer, sizeof(Buffer), "%d%%", _GUI_Task_State.BatteryInfo.Percentage); + + if (_GUI_Task_State.BatteryInfo.Percentage == 100) { + snprintf(Buffer, sizeof(Buffer), LV_SYMBOL_BATTERY_FULL " %d%%", _GUI_Task_State.BatteryInfo.Percentage); + lv_obj_set_style_bg_color(ui_Label_Main_Battery_Remaining, lv_color_hex(0x00FF00), 0); + } else if (_GUI_Task_State.BatteryInfo.Percentage >= 75) { + snprintf(Buffer, sizeof(Buffer), LV_SYMBOL_BATTERY_3 " %d%%", _GUI_Task_State.BatteryInfo.Percentage); + lv_obj_set_style_bg_color(ui_Label_Main_Battery_Remaining, lv_color_hex(0x00FF00), 0); + } else if (_GUI_Task_State.BatteryInfo.Percentage >= 50) { + snprintf(Buffer, sizeof(Buffer), LV_SYMBOL_BATTERY_2 " %d%%", _GUI_Task_State.BatteryInfo.Percentage); + lv_obj_set_style_bg_color(ui_Label_Main_Battery_Remaining, lv_color_hex(0xFFFF00), 0); + } else if (_GUI_Task_State.BatteryInfo.Percentage >= 25) { + snprintf(Buffer, sizeof(Buffer), LV_SYMBOL_BATTERY_1 " %d%%", _GUI_Task_State.BatteryInfo.Percentage); + lv_obj_set_style_bg_color(ui_Label_Main_Battery_Remaining, lv_color_hex(0xFFFF00), 0); + } else { + snprintf(Buffer, sizeof(Buffer), LV_SYMBOL_BATTERY_EMPTY " %d%%", _GUI_Task_State.BatteryInfo.Percentage); + lv_obj_set_style_bg_color(ui_Label_Main_Battery_Remaining, lv_color_hex(0xFF0000), 0); + } + + lv_label_set_text(ui_Label_Main_Battery_Remaining, Buffer); + + xEventGroupClearBits(_GUI_Task_State.EventGroup, BATTERY_VOLTAGE_READY); + } else if ( EventBits & BATTERY_CHARGING_STATUS_READY) { + xEventGroupClearBits(_GUI_Task_State.EventGroup, BATTERY_CHARGING_STATUS_READY); + } else if (EventBits & WIFI_CONNECTION_STATE_CHANGED) { + if (_GUI_Task_State.WiFiConnected) { + char Buffer[32]; + + memset(Buffer, 0, sizeof(Buffer)); + snprintf(Buffer, sizeof(Buffer), "IP: %lu.%lu.%lu.%lu", + (_GUI_Task_State.IP_Info.IP >> 0) & 0xFF, + (_GUI_Task_State.IP_Info.IP >> 8) & 0xFF, + (_GUI_Task_State.IP_Info.IP >> 16) & 0xFF, + (_GUI_Task_State.IP_Info.IP >> 24) & 0xFF); + + /* Skip "IP: " prefix for label */ + lv_label_set_text(ui_Label_Info_IP, &Buffer[4]); + lv_label_set_text(ui_settings_wifi_status_label, &Buffer[4]); + lv_obj_set_style_text_color(ui_Image_Main_WiFi, lv_color_hex(0x00FF00), LV_PART_MAIN); + + MessageBox_Show(Buffer, 5); + + /* Disable WiFi buttons in the settings menu */ + lv_obj_remove_flag(ui_settings_wifi_connect_btn, LV_OBJ_FLAG_CLICKABLE); + } else { + lv_label_set_text(ui_Label_Info_IP, "Not connected"); + lv_label_set_text(ui_settings_wifi_status_label, "Not connected"); + lv_obj_set_style_text_color(ui_Image_Main_WiFi, lv_color_hex(0xFF0000), LV_PART_MAIN); + + /* Disable WiFi buttons in the settings menu */ + lv_obj_add_flag(ui_settings_wifi_connect_btn, LV_OBJ_FLAG_CLICKABLE); + } + + xEventGroupClearBits(_GUI_Task_State.EventGroup, WIFI_CONNECTION_STATE_CHANGED); + } else if (EventBits & PROVISIONING_STATE_CHANGED) { + if ((_GUI_Task_State.WiFiConnected == false) && _GUI_Task_State.ProvisioningActive) { + lv_obj_set_style_text_color(ui_Image_Main_WiFi, lv_color_hex(0xFF8800), LV_PART_MAIN); + } else { + lv_obj_set_style_text_color(ui_Image_Main_WiFi, lv_color_hex(0xFF0000), LV_PART_MAIN); + } + + xEventGroupClearBits(_GUI_Task_State.EventGroup, PROVISIONING_STATE_CHANGED); + } else if (EventBits & SD_CARD_STATE_CHANGED) { + ESP_LOGD(TAG, "SD card state changed: %s", _GUI_Task_State.CardPresent ? "present" : "removed"); + + xEventGroupClearBits(_GUI_Task_State.EventGroup, SD_CARD_STATE_CHANGED); + } else if (EventBits & SD_CARD_MOUNTED) { + ESP_LOGD(TAG, "SD card mounted - updating GUI"); + + xEventGroupClearBits(_GUI_Task_State.EventGroup, SD_CARD_MOUNTED); + } else if (EventBits & SD_CARD_MOUNT_ERROR) { + ESP_LOGE(TAG, "SD card mount failed - keeping card present status"); + + xEventGroupClearBits(_GUI_Task_State.EventGroup, SD_CARD_MOUNT_ERROR); + } else if (EventBits & LEPTON_SPOTMETER_READY) { + xEventGroupClearBits(_GUI_Task_State.EventGroup, LEPTON_SPOTMETER_READY); + } else if (EventBits & LEPTON_UPTIME_READY) { + char Buffer[32]; + uint32_t Uptime; + + Uptime = _GUI_Task_State.LeptonUptime / 1000; + + snprintf(Buffer, sizeof(Buffer), "%02lu:%02lu:%02lu", Uptime / 3600, (Uptime % 3600) / 60, Uptime % 60); + lv_label_set_text(ui_Label_Info_Lepton_Uptime, Buffer); + + xEventGroupClearBits(_GUI_Task_State.EventGroup, LEPTON_UPTIME_READY); + } else if (EventBits & LEPTON_TEMP_READY) { + char Buffer[32]; + + snprintf(Buffer, sizeof(Buffer), "%.2f °C", _GUI_Task_State.LeptonTemperatures.FPA); + lv_label_set_text(ui_Label_Info_Lepton_FPA, Buffer); + snprintf(Buffer, sizeof(Buffer), "%.2f °C", _GUI_Task_State.LeptonTemperatures.AUX); + lv_label_set_text(ui_Label_Info_Lepton_AUX, Buffer); + + xEventGroupClearBits(_GUI_Task_State.EventGroup, LEPTON_TEMP_READY); + } else if (EventBits & LEPTON_PIXEL_TEMPERATURE_READY) { + char Buffer[16]; + + snprintf(Buffer, sizeof(Buffer), "%.2f °C", _GUI_Task_State.SpotTemperature); + lv_label_set_text(ui_Label_Main_Thermal_PixelTemperature, Buffer); + + xEventGroupClearBits(_GUI_Task_State.EventGroup, LEPTON_PIXEL_TEMPERATURE_READY); + } else if (EventBits & LEPTON_SCENE_STATISTICS_READY) { + char Buffer[16]; + float Temp; + + Temp = _GUI_Task_State.ROIResult.Max; + snprintf(Buffer, sizeof(Buffer), "%.1f °C", Temp); + lv_label_set_text(ui_Label_Main_Thermal_Scene_Max, Buffer); + + Temp = _GUI_Task_State.ROIResult.Min; + snprintf(Buffer, sizeof(Buffer), "%.1f °C", Temp); + lv_label_set_text(ui_Label_Main_Thermal_Scene_Min, Buffer); + + Temp = _GUI_Task_State.ROIResult.Mean ; + snprintf(Buffer, sizeof(Buffer), "%.1f °C", Temp); + lv_label_set_text(ui_Label_Main_Thermal_Scene_Mean, Buffer); + + xEventGroupClearBits(_GUI_Task_State.EventGroup, LEPTON_SCENE_STATISTICS_READY); + } else if (EventBits & UVC_STREAMING_STATE_CHANGED) { + if (_GUI_Task_State.isUVCStreaming) { + /* Clear thermal canvas to black */ + memset(_GUI_Task_State.ThermalCanvasBuffer, 0x00, 240 * 180 * 2); + lv_obj_invalidate(ui_Image_Thermal); + + /* Show UVC overlay */ + lv_obj_remove_flag(_GUI_Task_State.UVCOverlayLabel, LV_OBJ_FLAG_HIDDEN); + + /* Hide ROI rectangles */ + lv_obj_add_flag(ui_Image_Main_Thermal_Spotmeter_ROI, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(ui_Image_Main_Thermal_Scene_ROI, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(ui_Image_Main_Thermal_AGC_ROI, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(ui_Image_Main_Thermal_Video_Focus_ROI, LV_OBJ_FLAG_HIDDEN); + + /* Hide temperature labels */ + lv_obj_add_flag(ui_Label_Main_Thermal_PixelTemperature, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(ui_Label_Main_Thermal_Scene_Max, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(ui_Label_Main_Thermal_Scene_Min, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(ui_Label_Main_Thermal_Scene_Mean, LV_OBJ_FLAG_HIDDEN); + + /* Hide temperature scale labels */ + lv_obj_add_flag(ui_Label_TempScaleMax, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(ui_Label_TempScaleMin, LV_OBJ_FLAG_HIDDEN); + } else { + /* Hide UVC overlay */ + lv_obj_add_flag(_GUI_Task_State.UVCOverlayLabel, LV_OBJ_FLAG_HIDDEN); + + /* Restore ROI rectangles */ + lv_obj_remove_flag(ui_Image_Main_Thermal_Spotmeter_ROI, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(ui_Image_Main_Thermal_Scene_ROI, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(ui_Image_Main_Thermal_AGC_ROI, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(ui_Image_Main_Thermal_Video_Focus_ROI, LV_OBJ_FLAG_HIDDEN); + + /* Restore temperature labels */ + lv_obj_remove_flag(ui_Label_Main_Thermal_PixelTemperature, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(ui_Label_Main_Thermal_Scene_Max, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(ui_Label_Main_Thermal_Scene_Min, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(ui_Label_Main_Thermal_Scene_Mean, LV_OBJ_FLAG_HIDDEN); + + /* Restore temperature scale labels */ + lv_obj_remove_flag(ui_Label_TempScaleMax, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(ui_Label_TempScaleMin, LV_OBJ_FLAG_HIDDEN); + } + + xEventGroupClearBits(_GUI_Task_State.EventGroup, UVC_STREAMING_STATE_CHANGED); + } + + _lock_acquire(&_GUI_Task_State.LVGL_API_Lock); + lv_timer_handler(); + _lock_release(&_GUI_Task_State.LVGL_API_Lock); + + esp_task_wdt_reset(); + + vTaskDelay(pdMS_TO_TICKS(10)); + } + + _GUI_Task_State.TaskHandle = NULL; + + esp_task_wdt_delete(NULL); + vTaskDelete(NULL); +} + +esp_err_t GUI_Task_Init(void) +{ + BaseType_t Error; + uint32_t Caps; + + if (_GUI_Task_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + ESP_ERROR_CHECK(GUI_Helper_Init(&_GUI_Task_State, Touch_LVGL_ReadCallback)); + + ui_init(); + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM; + #else + Caps = 0; + #endif + + _GUI_Task_State.ThermalCanvasBuffer = static_cast(heap_caps_malloc(240 * 180 * 2, Caps)); + _GUI_Task_State.GradientCanvasBuffer = static_cast(heap_caps_malloc(20 * 180 * 2, Caps)); + _GUI_Task_State.NetworkRGBBuffer = static_cast(heap_caps_malloc(240 * 180 * 3, Caps)); + + if (_GUI_Task_State.ThermalCanvasBuffer == NULL) { + ESP_LOGE(TAG, "Failed to allocate thermal canvas buffer!"); + + return ESP_ERR_NO_MEM; + } + + if (_GUI_Task_State.GradientCanvasBuffer == NULL) { + ESP_LOGE(TAG, "Failed to allocate gradient canvas buffer!"); + + heap_caps_free(_GUI_Task_State.ThermalCanvasBuffer); + + return ESP_ERR_NO_MEM; + } + + if (_GUI_Task_State.NetworkRGBBuffer == NULL) { + ESP_LOGE(TAG, "Failed to allocate network RGB buffer!"); + + heap_caps_free(_GUI_Task_State.ThermalCanvasBuffer); + heap_caps_free(_GUI_Task_State.GradientCanvasBuffer); + + return ESP_ERR_NO_MEM; + } + + /* Create image save queue and task */ + _GUI_Task_State.ImageSaveQueue = xQueueCreate(1, sizeof(App_Lepton_FrameReady_t)); + if (_GUI_Task_State.ImageSaveQueue == NULL) { + ESP_LOGE(TAG, "Failed to create image save queue!"); + + heap_caps_free(_GUI_Task_State.ThermalCanvasBuffer); + heap_caps_free(_GUI_Task_State.GradientCanvasBuffer); + heap_caps_free(_GUI_Task_State.NetworkRGBBuffer); + + return ESP_ERR_NO_MEM; + } + + Error = xTaskCreatePinnedToCore(Task_ImageSave, "Task_ImgSave", 8192, NULL, CONFIG_GUI_TASK_PRIO - 1, &_GUI_Task_State.ImageSaveTaskHandle, 1); + if (Error != pdPASS) { + ESP_LOGE(TAG, "Failed to create image save task!"); + + vQueueDelete(_GUI_Task_State.ImageSaveQueue); + + heap_caps_free(_GUI_Task_State.ThermalCanvasBuffer); + heap_caps_free(_GUI_Task_State.GradientCanvasBuffer); + heap_caps_free(_GUI_Task_State.NetworkRGBBuffer); + + _GUI_Task_State.ImageSaveQueue = NULL; + _GUI_Task_State.ThermalCanvasBuffer = NULL; + _GUI_Task_State.GradientCanvasBuffer = NULL; + _GUI_Task_State.NetworkRGBBuffer = NULL; + + return ESP_FAIL; + } + + /* Initialize buffers with black pixels (RGB565 = 0x0000) */ + memset(_GUI_Task_State.ThermalCanvasBuffer, 0x00, 240 * 180 * 2); + memset(_GUI_Task_State.GradientCanvasBuffer, 0x00, 20 * 180 * 2); + + /* Now configure the image descriptors with allocated buffers */ + _GUI_Task_State.ThermalImageDescriptor.header.cf = LV_COLOR_FORMAT_RGB565; + _GUI_Task_State.ThermalImageDescriptor.header.w = 240; + _GUI_Task_State.ThermalImageDescriptor.header.h = 180; + _GUI_Task_State.ThermalImageDescriptor.data = _GUI_Task_State.ThermalCanvasBuffer; + _GUI_Task_State.ThermalImageDescriptor.data_size = 240 * 180 * 2; + + _GUI_Task_State.GradientImageDescriptor.header.cf = LV_COLOR_FORMAT_RGB565; + _GUI_Task_State.GradientImageDescriptor.header.w = 20; + _GUI_Task_State.GradientImageDescriptor.header.h = 180; + _GUI_Task_State.GradientImageDescriptor.data = _GUI_Task_State.GradientCanvasBuffer; + _GUI_Task_State.GradientImageDescriptor.data_size = 20 * 180 * 2; + + UI_Canvas_AddTempGradient(); + + lv_img_set_src(ui_Image_Thermal, &_GUI_Task_State.ThermalImageDescriptor); + lv_img_set_src(ui_Image_Gradient, &_GUI_Task_State.GradientImageDescriptor); + + /* Create UVC streaming overlay label (hidden by default) */ + _GUI_Task_State.UVCOverlayLabel = lv_label_create(ui_Container_Main_Thermal); + lv_label_set_text(_GUI_Task_State.UVCOverlayLabel, "USB Video Mode"); + lv_obj_set_align(_GUI_Task_State.UVCOverlayLabel, LV_ALIGN_CENTER); + lv_obj_set_style_text_color(_GUI_Task_State.UVCOverlayLabel, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_text_font(_GUI_Task_State.UVCOverlayLabel, &lv_font_montserrat_14, LV_PART_MAIN); + lv_obj_add_flag(_GUI_Task_State.UVCOverlayLabel, LV_OBJ_FLAG_HIDDEN); + _GUI_Task_State.isUVCStreaming = false; + + /* Initialize network frame for server streaming */ + _GUI_Task_State.NetworkFrame.Mutex = xSemaphoreCreateMutex(); + if (_GUI_Task_State.NetworkFrame.Mutex == NULL) { + ESP_LOGE(TAG, "Failed to create NetworkFrame mutex!"); + + heap_caps_free(_GUI_Task_State.ThermalCanvasBuffer); + heap_caps_free(_GUI_Task_State.GradientCanvasBuffer); + heap_caps_free(_GUI_Task_State.NetworkRGBBuffer); + + return ESP_ERR_NO_MEM; + } + + esp_event_handler_register(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVED, on_GUI_Event_Handler, NULL); + esp_event_handler_register(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED, on_GUI_Event_Handler, NULL); + esp_event_handler_register(DEVICE_EVENTS, ESP_EVENT_ANY_ID, on_Devices_Event_Handler, NULL); + esp_event_handler_register(NETWORK_EVENTS, ESP_EVENT_ANY_ID, on_Network_Event_Handler, NULL); + esp_event_handler_register(LEPTON_EVENTS, ESP_EVENT_ANY_ID, on_Lepton_Event_Handler, NULL); + esp_event_handler_register(USB_EVENTS, ESP_EVENT_ANY_ID, on_USB_Event_Handler, NULL); + + _GUI_Task_State.SaveNextFrameRequested = false; + _GUI_Task_State.isInitialized = true; + + return ESP_OK; +} + +void GUI_Task_Deinit(void) +{ + if (_GUI_Task_State.isInitialized == false) { + return; + } + + esp_event_handler_unregister(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVED, on_GUI_Event_Handler); + esp_event_handler_unregister(GUI_EVENTS, GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED, on_GUI_Event_Handler); + esp_event_handler_unregister(DEVICE_EVENTS, ESP_EVENT_ANY_ID, on_Devices_Event_Handler); + esp_event_handler_unregister(NETWORK_EVENTS, ESP_EVENT_ANY_ID, on_Network_Event_Handler); + esp_event_handler_unregister(LEPTON_EVENTS, ESP_EVENT_ANY_ID, on_Lepton_Event_Handler); + esp_event_handler_unregister(USB_EVENTS, ESP_EVENT_ANY_ID, on_USB_Event_Handler); + + ui_destroy(); + + GUI_Helper_Deinit(&_GUI_Task_State); + + if (_GUI_Task_State.NetworkFrame.Mutex != NULL) { + vSemaphoreDelete(_GUI_Task_State.NetworkFrame.Mutex); + _GUI_Task_State.NetworkFrame.Mutex = NULL; + } + + _GUI_Task_State.Display = NULL; + _GUI_Task_State.isInitialized = false; +} + +esp_err_t GUI_Task_Start(App_Context_t *p_AppContext) +{ + BaseType_t Error; + + if (p_AppContext == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (_GUI_Task_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (_GUI_Task_State.isRunning) { + ESP_LOGW(TAG, "Task already running"); + + return ESP_OK; + } + + _GUI_Task_State.isRunning = true; + + ESP_LOGD(TAG, "Starting GUI Task"); + + Error = xTaskCreatePinnedToCore(Task_GUI, "Task_GUI", CONFIG_GUI_TASK_STACKSIZE, p_AppContext, CONFIG_GUI_TASK_PRIO, &_GUI_Task_State.TaskHandle, CONFIG_GUI_TASK_CORE); + if (Error != pdPASS) { + ESP_LOGE(TAG, "Failed to create GUI task: %d!", Error); + + return ESP_ERR_NO_MEM; + } + + return ESP_OK; +} + +esp_err_t GUI_Task_Stop(void) +{ + if (_GUI_Task_State.isRunning == false) { + return ESP_OK; + } + + xEventGroupSetBits(_GUI_Task_State.EventGroup, GUI_TASK_STOP_REQUEST); + + return ESP_OK; +} + +bool GUI_Task_IsRunning(void) +{ + return _GUI_Task_State.isRunning; +} + +esp_err_t GUI_SaveThermalImage(void) +{ + /* Check if filesystem is locked (USB active) */ + if (MemoryManager_IsFilesystemLocked()) { + ESP_LOGW(TAG, "Cannot save image - USB mode active!"); + + return ESP_ERR_INVALID_STATE; + } + + /* Set flag to trigger save on next frame update */ + _GUI_Task_State.SaveNextFrameRequested = true; + ESP_LOGD(TAG, "Image save requested - will capture next frame"); + + return ESP_OK; +} \ No newline at end of file diff --git a/main/Application/Tasks/GUI/guiTask.h b/main/Application/Tasks/GUI/guiTask.h new file mode 100644 index 0000000..788b42b --- /dev/null +++ b/main/Application/Tasks/GUI/guiTask.h @@ -0,0 +1,75 @@ +/* + * guiTask.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: GUI task definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef GUI_TASK_H_ +#define GUI_TASK_H_ + +#include +#include + +#include + +#include "Application/application.h" + +/** @brief Initialize the GUI task. + * @return ESP_OK on success, error code otherwise + */ +esp_err_t GUI_Task_Init(void); + +/** @brief Deinitialize the GUI task. + */ +void GUI_Task_Deinit(void); + +/** @brief Start the GUI task. + * @param p_AppContext Pointer to the application context + * @return ESP_OK on success, error code otherwise + */ +esp_err_t GUI_Task_Start(App_Context_t *p_AppContext); + +/** @brief Stop the GUI task. + * @return ESP_OK on success, error code otherwise + */ +esp_err_t GUI_Task_Stop(void); + +/** @brief Check if the GUI task is running. + * @return true if running, false otherwise + */ +bool GUI_Task_IsRunning(void); + +/** @brief Toggle ROI (Region of Interest) edit mode. + * When enabled, shows a draggable rectangle overlay on the thermal image + * that can be moved by touch to adjust the spotmeter region. + */ +void GUI_Toggle_ROI_EditMode(void); + +/** @brief Request to save the next thermal image to storage as PNG file. + * @note Sets a flag that triggers image save on the next frame update. + * The actual save happens in background task (non-blocking). + * A message box will be displayed upon completion or error. + * Saves the scaled 240x180 display image (not the raw 160x120 frame). + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if filesystem is locked (USB active) + */ +esp_err_t GUI_SaveThermalImage(void); + +#endif /* GUI_TASK_H_ */ \ No newline at end of file diff --git a/main/Application/Tasks/Lepton/leptonTask.cpp b/main/Application/Tasks/Lepton/leptonTask.cpp new file mode 100644 index 0000000..60893f5 --- /dev/null +++ b/main/Application/Tasks/Lepton/leptonTask.cpp @@ -0,0 +1,842 @@ +/* + * leptonTask.c + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Lepton camera task definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include "lepton.h" +#include "leptonTask.h" +#include "Application/application.h" +#include "Application/Manager/Devices/devicesManager.h" +#include "Application/Manager/USB/usbManager.h" +#include "Application/Manager/USB/UVC/usbUVC.h" +#include + +#define LEPTON_TASK_STOP_REQUEST BIT0 +#define LEPTON_TASK_UPDATE_ROI_REQUEST BIT1 +#define LEPTON_TASK_UPDATE_TEMP_REQUEST BIT2 +#define LEPTON_TASK_UPDATE_UPTIME_REQUEST BIT3 +#define LEPTON_TASK_UPDATE_PIXEL_TEMPERATURE BIT4 +#define LEPTON_TASK_UPDATE_SPOTMETER BIT5 +#define LEPTON_TASK_UPDATE_SCENE_STATISTICS BIT6 +#define LEPTON_TASK_UPDATE_EMISSIVITY BIT7 + +ESP_EVENT_DEFINE_BASE(LEPTON_EVENTS); + +typedef struct { + bool isInitialized; + bool isRunning; + bool ApplicationStarted; + bool isUVCStreaming; /**< UVC streaming is active. */ + uint8_t *p_JpegBuffer; /**< JPEG compression output buffer. */ + size_t JpegBufferSize; /**< JPEG buffer allocated size. */ + TaskHandle_t TaskHandle; + EventGroupHandle_t EventGroup; + uint8_t *RGB_Buffer[2]; + uint8_t CurrentReadBuffer; + SemaphoreHandle_t BufferMutex; + QueueHandle_t RawFrameQueue; + Lepton_FrameBuffer_t RawFrame; + Lepton_Conf_t LeptonConf; + Lepton_t Lepton; + Settings_ROI_t ROI; + App_GUI_Screenposition_t ScreenPosition; + SettingsManager_ChangeNotification_t NewSetting; +} Lepton_Task_State_t; + +static Lepton_Task_State_t _Lepton_Task_State; + +static const char *TAG = "Lepton-Task"; + +/** @brief Event handler for the Settings task to receive updates when settings are changed. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_Settings_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "Settings event received: ID=%d", ID); + + switch (ID) { + case SETTINGS_EVENT_LEPTON_CHANGED: { + if (p_Data != NULL) { + memcpy(&_Lepton_Task_State.NewSetting, p_Data, sizeof(SettingsManager_ChangeNotification_t)); + + ESP_LOGD(TAG, "Lepton settings changed: ID=%d", _Lepton_Task_State.NewSetting.ID); + ESP_LOGD(TAG, "Lepton settings changed: Value=%d", _Lepton_Task_State.NewSetting.Value); + + if (_Lepton_Task_State.NewSetting.ID == SETTINGS_ID_LEPTON_EMISSIVITY) { + xEventGroupSetBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_EMISSIVITY); + } + } else { + ESP_LOGD(TAG, "Lepton settings changed (no specific setting data provided)"); + } + + break; + } + } +} + +/** @brief Event handler for the GUI task to receive updates when GUI events are triggered (e.g., ROI change requests). + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_GUI_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "GUI event received: ID=%d", ID); + + switch (ID) { + case GUI_EVENT_APP_STARTED: { + ESP_LOGD(TAG, "Application started event received"); + + _Lepton_Task_State.ApplicationStarted = true; + + break; + } + case GUI_EVENT_REQUEST_ROI: { + if (p_Data != NULL) { + memcpy(&_Lepton_Task_State.ROI, p_Data, sizeof(Settings_ROI_t)); + + xEventGroupSetBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_ROI_REQUEST); + } else { + ESP_LOGE(TAG, "GUI_EVENT_REQUEST_ROI received with NULL data"); + } + + break; + } + case GUI_EVENT_REQUEST_FPA_AUX_TEMP: { + xEventGroupSetBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_TEMP_REQUEST); + break; + } + case GUI_EVENT_REQUEST_UPTIME: { + xEventGroupSetBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_UPTIME_REQUEST); + + break; + } + case GUI_EVENT_REQUEST_PIXEL_TEMPERATURE: { + if (p_Data != NULL) { + _Lepton_Task_State.ScreenPosition = *static_cast(p_Data); + + xEventGroupSetBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_PIXEL_TEMPERATURE); + } else { + ESP_LOGE(TAG, "GUI_EVENT_REQUEST_PIXEL_TEMPERATURE received with NULL data"); + } + + break; + } + case GUI_EVENT_REQUEST_SPOTMETER: { + xEventGroupSetBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_SPOTMETER); + + break; + } + case GUI_EVENT_REQUEST_SCENE_STATISTICS: { + xEventGroupSetBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_SCENE_STATISTICS); + + break; + } + default: { + ESP_LOGW(TAG, "Unhandled GUI event ID: %d", ID); + + break; + } + } +} + +/** @brief Event handler for the USB Manager to receive UVC streaming events. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_USB_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "USB event received: ID=%d", ID); + + switch (ID) { + case USB_EVENT_UVC_STREAMING_START: { + ESP_LOGD(TAG, "UVC streaming started - redirecting frames to USB"); + _Lepton_Task_State.isUVCStreaming = true; + + break; + } + case USB_EVENT_UVC_STREAMING_STOP: { + ESP_LOGD(TAG, "UVC streaming stopped - resuming GUI frames"); + _Lepton_Task_State.isUVCStreaming = false; + + break; + } + case USB_EVENT_UNINITIALIZED: { + if (_Lepton_Task_State.isUVCStreaming) { + ESP_LOGI(TAG, "USB deinitialized while UVC streaming - resuming GUI frames"); + _Lepton_Task_State.isUVCStreaming = false; + } + + break; + } + default: { + break; + } + } +} + +/** @brief Loads the Lepton settings from the Settings Manager and applies them to the Lepton. + */ +static void Lepton_LoadSettings(void) +{ + Settings_Lepton_t LeptonSettings; + + SettingsManager_GetLepton(&LeptonSettings); + + ESP_LOGI(TAG, "Loading Lepton settings..."); + ESP_LOGI(TAG, "Emissivity: %d", LeptonSettings.CurrentEmissivity); + + Lepton_SetEmissivity(&_Lepton_Task_State.Lepton, static_cast(LeptonSettings.CurrentEmissivity)); +} + +/** @brief Lepton camera task main loop. + * @param p_Parameters Task parameters + */ +static void Task_Lepton(void *p_Parameters) +{ + Lepton_Error_t Lepton_Error; + App_Context_t *App_Context; + App_Lepton_Device_t DeviceInfo; + + esp_task_wdt_add(NULL); + App_Context = static_cast(p_Parameters); + + ESP_LOGD(TAG, "Lepton task started on core %d", xPortGetCoreID()); + + Lepton_Error = Lepton_Init(&_Lepton_Task_State.Lepton, &_Lepton_Task_State.LeptonConf); + if (Lepton_Error != LEPTON_ERR_OK) { + ESP_LOGE(TAG, "Lepton initialization failed with error: %d!", Lepton_Error); + + esp_event_post(LEPTON_EVENTS, LEPTON_EVENT_CAMERA_ERROR, NULL, 0, pdMS_TO_TICKS(500)); + esp_task_wdt_delete(NULL); + + vTaskDelete(NULL); + } + + /* Format serial number as readable string: XXXX-XXXX-XXXX-XXXX */ + snprintf(DeviceInfo.SerialNumber, sizeof(DeviceInfo.SerialNumber), + "%02X%02X-%02X%02X-%02X%02X-%02X%02X", + _Lepton_Task_State.Lepton.SerialNumber[0], _Lepton_Task_State.Lepton.SerialNumber[1], + _Lepton_Task_State.Lepton.SerialNumber[2], _Lepton_Task_State.Lepton.SerialNumber[3], + _Lepton_Task_State.Lepton.SerialNumber[4], _Lepton_Task_State.Lepton.SerialNumber[5], + _Lepton_Task_State.Lepton.SerialNumber[6], _Lepton_Task_State.Lepton.SerialNumber[7]); + memcpy(DeviceInfo.PartNumber, _Lepton_Task_State.Lepton.PartNumber, sizeof(DeviceInfo.PartNumber)); + + snprintf(DeviceInfo.SoftwareRevision.GPP_Revision, sizeof(DeviceInfo.SoftwareRevision.GPP_Revision), + "%u.%u.%u", + _Lepton_Task_State.Lepton.SoftwareVersion.gpp_major, + _Lepton_Task_State.Lepton.SoftwareVersion.gpp_minor, + _Lepton_Task_State.Lepton.SoftwareVersion.gpp_build); + + snprintf(DeviceInfo.SoftwareRevision.DSP_Revision, sizeof(DeviceInfo.SoftwareRevision.DSP_Revision), + "%u.%u.%u", + _Lepton_Task_State.Lepton.SoftwareVersion.dsp_major, + _Lepton_Task_State.Lepton.SoftwareVersion.dsp_minor, + _Lepton_Task_State.Lepton.SoftwareVersion.dsp_build); + + ESP_LOGD(TAG, " Part number: %s", DeviceInfo.PartNumber); + ESP_LOGD(TAG, " Serial number: %s", DeviceInfo.SerialNumber); + ESP_LOGI(TAG, " GPP revision: %s", DeviceInfo.SoftwareRevision.GPP_Revision); + ESP_LOGI(TAG, " DSP revision: %s", DeviceInfo.SoftwareRevision.DSP_Revision); + + esp_event_post(LEPTON_EVENTS, LEPTON_EVENT_CAMERA_READY, &DeviceInfo, sizeof(App_Lepton_Device_t), pdMS_TO_TICKS(500)); + + while (_Lepton_Task_State.ApplicationStarted == false) { + esp_task_wdt_reset(); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + Lepton_FluxLinearParams_t FluxParams; + Lepton_GetFluxLinearParameters(&_Lepton_Task_State.Lepton, &FluxParams); + ESP_LOGI(TAG, + "Flux Linear Parameters - Scene Emissivity: %u, TBkgK: %u, TauWindow: %u, TWindowK: %u, TauAtm: %u, TAtmK: %u, ReflWindow: %u, TReflK: %u", + FluxParams.SceneEmissivity, + FluxParams.TBkgK, + FluxParams.TauWindow, + FluxParams.TWindowK, + FluxParams.TauAtm, + FluxParams.TAtmK, + FluxParams.ReflWindow, + FluxParams.TReflK); + + ESP_LOGD(TAG, "Start image capturing..."); + + Lepton_LoadSettings(); + + if (Lepton_StartCapture(&_Lepton_Task_State.Lepton, _Lepton_Task_State.RawFrameQueue) != LEPTON_ERR_OK) { + ESP_LOGE(TAG, "Can not start image capturing!"); + + esp_event_post(LEPTON_EVENTS, LEPTON_EVENT_CAMERA_ERROR, NULL, 0, pdMS_TO_TICKS(500)); + + /* Critical error - cannot continue without capture task */ + _Lepton_Task_State.isRunning = false; + _Lepton_Task_State.TaskHandle = NULL; + + esp_task_wdt_delete(NULL); + vTaskDelete(NULL); + + return; + } + + while (_Lepton_Task_State.isRunning) { + EventBits_t EventBits; + + esp_task_wdt_reset(); + + /* Wait for a new raw frame with longer timeout to avoid busy waiting */ + if (xQueueReceive(_Lepton_Task_State.RawFrameQueue, &_Lepton_Task_State.RawFrame, pdMS_TO_TICKS(500)) == pdTRUE) { + uint8_t WriteBufferIdx; + uint8_t *WriteBuffer; + int16_t Min = 0; + int16_t Max = 0; + Lepton_Telemetry_t Telemetry; + Lepton_VideoFormat_t VideoFormat; + + if (_Lepton_Task_State.RawFrame.Telemetry_Buffer != NULL) { + memcpy(&Telemetry, _Lepton_Task_State.RawFrame.Telemetry_Buffer, sizeof(Lepton_Telemetry_t)); + ESP_LOGD(TAG, "Telemetry - FrameCounter: %u, FPA_Temp: %uK, Housing_Temp: %uK", + Telemetry.FrameCounter, + Telemetry.FPA_Temp, + Telemetry.Housing_Temp); + } + + ESP_LOGD(TAG, "Processing frame..."); + + /* Determine which buffer to write to (ping-pong) */ + if (xSemaphoreTake(_Lepton_Task_State.BufferMutex, pdMS_TO_TICKS(100)) == pdTRUE) { + /* Find a buffer that's not currently being read */ + WriteBufferIdx = (_Lepton_Task_State.CurrentReadBuffer + 1) % 2; + WriteBuffer = _Lepton_Task_State.RGB_Buffer[WriteBufferIdx]; + xSemaphoreGive(_Lepton_Task_State.BufferMutex); + } else { + ESP_LOGW(TAG, "Failed to acquire mutex for buffer selection!"); + + continue; + } + + /* Process frame based on video format */ + Lepton_GetVideoFormat(&_Lepton_Task_State.Lepton, &VideoFormat); + + if (VideoFormat == LEPTON_FORMAT_RGB888) { + /* RGB888: Data is already in RGB format, just copy it */ + size_t ImageSize = _Lepton_Task_State.RawFrame.Width * _Lepton_Task_State.RawFrame.Height * + _Lepton_Task_State.RawFrame.BytesPerPixel; + + memcpy(WriteBuffer, _Lepton_Task_State.RawFrame.Image_Buffer, ImageSize); + + ESP_LOGD(TAG, "Copied RGB888 frame: %ux%u (%u bytes)", _Lepton_Task_State.RawFrame.Width, + _Lepton_Task_State.RawFrame.Height, static_cast(ImageSize)); + } else { + /* RAW14: Convert to RGB */ + Lepton_Raw14ToRGB(&_Lepton_Task_State.Lepton, _Lepton_Task_State.RawFrame.Image_Buffer, WriteBuffer, &Min, &Max, + _Lepton_Task_State.RawFrame.Width, + _Lepton_Task_State.RawFrame.Height); + } + + /* Mark buffer as ready and update read buffer index */ + if (xSemaphoreTake(_Lepton_Task_State.BufferMutex, pdMS_TO_TICKS(100)) == pdTRUE) { + _Lepton_Task_State.CurrentReadBuffer = WriteBufferIdx; + xSemaphoreGive(_Lepton_Task_State.BufferMutex); + } else { + ESP_LOGW(TAG, "Failed to acquire mutex for buffer ready!"); + + continue; + } + + /* If UVC streaming is active, also send frames to USB */ + if (_Lepton_Task_State.isUVCStreaming && (_Lepton_Task_State.p_JpegBuffer != NULL)) { + /* Double-check streaming state to avoid submitting after host closed stream */ + if (USBUVC_IsStreaming() == false) { + _Lepton_Task_State.isUVCStreaming = false; + + ESP_LOGI(TAG, "UVC streaming ended - resuming GUI frames"); + } else { + jpeg_error_t JpegError; + jpeg_enc_config_t EncConfig = DEFAULT_JPEG_ENC_CONFIG(); + jpeg_enc_handle_t JpegEncoder = NULL; + int JpegSize = 0; + + /* Configure JPEG encoder for Lepton thermal camera */ + EncConfig.src_type = JPEG_PIXEL_FORMAT_RGB888; + EncConfig.subsampling = JPEG_SUBSAMPLE_420; + EncConfig.quality = 80; + EncConfig.width = _Lepton_Task_State.RawFrame.Width; + EncConfig.height = _Lepton_Task_State.RawFrame.Height; + + /* Create encoder instance */ + JpegError = jpeg_enc_open(&EncConfig, &JpegEncoder); + if (JpegError == JPEG_ERR_OK) { + JpegError = jpeg_enc_process(JpegEncoder, WriteBuffer, + static_cast(_Lepton_Task_State.RawFrame.Width * _Lepton_Task_State.RawFrame.Height * 3), + _Lepton_Task_State.p_JpegBuffer, + static_cast(_Lepton_Task_State.JpegBufferSize), + &JpegSize); + if (JpegError == JPEG_ERR_OK) { + /* Submit JPEG frame to UVC */ + esp_err_t UVCError = USBUVC_SubmitFrame(_Lepton_Task_State.p_JpegBuffer, static_cast(JpegSize)); + if (UVCError == ESP_ERR_INVALID_STATE) { + /* Streaming was stopped - immediately stop submitting. + Next frame iteration will route to GUI path. */ + _Lepton_Task_State.isUVCStreaming = false; + + ESP_LOGI(TAG, "UVC stream no longer active - resuming GUI frames"); + } else if (UVCError != ESP_OK) { + ESP_LOGW(TAG, "Failed to submit frame to UVC: %d", UVCError); + } else { + ESP_LOGD(TAG, "UVC frame submitted: %d bytes JPEG", JpegSize); + } + } else { + ESP_LOGW(TAG, "JPEG encoding failed: %d", JpegError); + } + + jpeg_enc_close(JpegEncoder); + } else { + ESP_LOGW(TAG, "Failed to open JPEG encoder: %d", JpegError); + } + } + } + + /* Send frame notification to GUI/network task */ + App_Lepton_FrameReady_t FrameEvent = { + .Buffer = WriteBuffer, + .Width = _Lepton_Task_State.RawFrame.Width, + .Height = _Lepton_Task_State.RawFrame.Height, + .Channels = 3, + .Min = Min, + .Max = Max + }; + + /* Use xQueueOverwrite to always have the latest frame */ + xQueueOverwrite(App_Context->Lepton_FrameEventQueue, &FrameEvent); + ESP_LOGD(TAG, "Frame sent to queue successfully"); + } else { + ESP_LOGW(TAG, "No raw frame received from VoSPI"); + } + + EventBits = xEventGroupGetBits(_Lepton_Task_State.EventGroup); + if (EventBits & LEPTON_TASK_UPDATE_ROI_REQUEST) { + Lepton_ROI_t ROI; + Lepton_Error_t Error; + + ROI.Start_Col = _Lepton_Task_State.ROI.x; + ROI.Start_Row = _Lepton_Task_State.ROI.y; + ROI.End_Col = _Lepton_Task_State.ROI.x + _Lepton_Task_State.ROI.w - 1; + ROI.End_Row = _Lepton_Task_State.ROI.y + _Lepton_Task_State.ROI.h - 1; + + switch (_Lepton_Task_State.ROI.Type) { + case ROI_TYPE_SPOTMETER: { + Error = Lepton_SetSpotmeterROI(&_Lepton_Task_State.Lepton, &ROI); + + break; + } + case ROI_TYPE_SCENE: { + Error = Lepton_SetSceneROI(&_Lepton_Task_State.Lepton, &ROI); + + break; + } + case ROI_TYPE_AGC: { + Error = Lepton_SetAGCROI(&_Lepton_Task_State.Lepton, &ROI); + + break; + } + case ROI_TYPE_VIDEO_FOCUS: { + Error = Lepton_SetVideoFocusROI(&_Lepton_Task_State.Lepton, &ROI); + + break; + } + default: { + ESP_LOGW(TAG, "Invalid ROI type in GUI event: %d", _Lepton_Task_State.ROI.Type); + + return; + } + } + + if (Error == LEPTON_ERR_OK) { + ESP_LOGD(TAG, "New Lepton ROI (Type %d) - Start_Col: %u, Start_Row: %u, End_Col: %u, End_Row: %u", + _Lepton_Task_State.ROI.Type, + ROI.Start_Col, + ROI.Start_Row, + ROI.End_Col, + ROI.End_Row); + } else { + ESP_LOGE(TAG, "Failed to update Lepton ROI with type %d!", _Lepton_Task_State.ROI.Type); + } + + xEventGroupClearBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_ROI_REQUEST); + } else if (EventBits & LEPTON_TASK_UPDATE_TEMP_REQUEST) { + uint16_t FPA_Temp; + uint16_t AUX_Temp; + App_Lepton_Temperatures_t Temperatures; + + Lepton_GetTemperature(&_Lepton_Task_State.Lepton, &FPA_Temp, &AUX_Temp); + + Temperatures.FPA = (static_cast(FPA_Temp) * 0.01f) - 273.0f; + Temperatures.AUX = (static_cast(AUX_Temp) * 0.01f) - 273.0f; + esp_event_post(LEPTON_EVENTS, LEPTON_EVENT_RESPONSE_FPA_AUX_TEMP, &Temperatures, sizeof(App_Lepton_Temperatures_t), 0); + + xEventGroupClearBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_TEMP_REQUEST); + } else if (EventBits & LEPTON_TASK_UPDATE_UPTIME_REQUEST) { + uint32_t Uptime; + + Uptime = Lepton_GetUptime(&_Lepton_Task_State.Lepton); + esp_event_post(LEPTON_EVENTS, LEPTON_EVENT_RESPONSE_UPTIME, &Uptime, sizeof(uint32_t), 0); + + xEventGroupClearBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_UPTIME_REQUEST); + } else if (EventBits & LEPTON_TASK_STOP_REQUEST) { + ESP_LOGI(TAG, "Stop request received"); + + _Lepton_Task_State.isRunning = false; + + xEventGroupClearBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_STOP_REQUEST); + + break; + } else if (EventBits & LEPTON_TASK_UPDATE_PIXEL_TEMPERATURE) { + int16_t x; + int16_t y; + float Temperature; + Lepton_VideoFormat_t VideoFormat; + + Lepton_GetVideoFormat(&_Lepton_Task_State.Lepton, &VideoFormat); + if (((_Lepton_Task_State.RawFrame.Width == 0) || (_Lepton_Task_State.RawFrame.Height == 0)) && + (VideoFormat != LEPTON_FORMAT_RAW14)) { + ESP_LOGW(TAG, "Invalid Lepton frame! Cannot get pixel temperature!"); + + xEventGroupClearBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_PIXEL_TEMPERATURE); + + continue; + } + + /* Convert the screen position to the Lepton frame coordinates */ + x = (_Lepton_Task_State.ScreenPosition.x * _Lepton_Task_State.RawFrame.Width) / _Lepton_Task_State.ScreenPosition.Width; + y = (_Lepton_Task_State.ScreenPosition.y * _Lepton_Task_State.RawFrame.Height) / + _Lepton_Task_State.ScreenPosition.Height; + + ESP_LOGD(TAG, "Crosshair center in Lepton Frame: (%d,%d), size (%d,%d)", x, y, _Lepton_Task_State.RawFrame.Width, + _Lepton_Task_State.RawFrame.Height); + + if (_Lepton_Task_State.RawFrame.Image_Buffer != NULL) { + if (Lepton_GetPixelTemperature(&_Lepton_Task_State.Lepton, + _Lepton_Task_State.RawFrame.Image_Buffer[(y * _Lepton_Task_State.RawFrame.Width) + x], + &Temperature) == LEPTON_ERR_OK) { + esp_event_post(LEPTON_EVENTS, LEPTON_EVENT_RESPONSE_PIXEL_TEMPERATURE, &Temperature, sizeof(float), 0); + } else { + ESP_LOGW(TAG, "Failed to get pixel temperature!"); + } + } else { + ESP_LOGW(TAG, "Image buffer is NULL, cannot get pixel temperature"); + } + + xEventGroupClearBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_PIXEL_TEMPERATURE); + } else if (EventBits & LEPTON_TASK_UPDATE_SPOTMETER) { + Lepton_Spotmeter_t Spotmeter; + + ESP_LOGD(TAG, "Getting spotmeter - I2C Bus: %p, I2C Dev: %p", + _Lepton_Task_State.Lepton.Internal.CCI.I2C_Bus_Handle, + _Lepton_Task_State.Lepton.Internal.CCI.I2C_Dev_Handle); + + if (Lepton_GetSpotmeter(&_Lepton_Task_State.Lepton, &Spotmeter) == LEPTON_ERR_OK) { + App_Lepton_ROI_Result_t App_Lepton_Spotmeter; + + ESP_LOGD(TAG, "Spotmeter: Spot=%uK, Min=%uK, Max=%uK", + Spotmeter.Value, + Spotmeter.Min, + Spotmeter.Max); + + App_Lepton_Spotmeter.Min = Spotmeter.Min - 273.0f; + App_Lepton_Spotmeter.Max = Spotmeter.Max - 273.0f; + App_Lepton_Spotmeter.Average = Spotmeter.Value - 273.0f; + + esp_event_post(LEPTON_EVENTS, LEPTON_EVENT_RESPONSE_SPOTMETER, &App_Lepton_Spotmeter, sizeof(App_Lepton_ROI_Result_t), + 0); + } else { + ESP_LOGW(TAG, "Failed to read spotmeter!"); + } + + xEventGroupClearBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_SPOTMETER); + } else if (EventBits & LEPTON_TASK_UPDATE_SCENE_STATISTICS) { + Lepton_SceneStatistics_t SceneStats; + + if (Lepton_GetSceneStatistics(&_Lepton_Task_State.Lepton, &SceneStats) == LEPTON_ERR_OK) { + App_Lepton_ROI_Result_t App_Lepton_Scene; + + App_Lepton_Scene.Min = SceneStats.MinIntensity; + App_Lepton_Scene.Max = SceneStats.MaxIntensity; + App_Lepton_Scene.Average = SceneStats.MeanIntensity; + + ESP_LOGD(TAG, "Scene Statistics: Min=%.2f°C, Max=%.2f°C, Average=%.2f°C", + App_Lepton_Scene.Min, App_Lepton_Scene.Max, App_Lepton_Scene.Average); + + esp_event_post(LEPTON_EVENTS, LEPTON_EVENT_RESPONSE_SCENE_STATISTICS, &App_Lepton_Scene, + sizeof(App_Lepton_ROI_Result_t), 0); + } else { + ESP_LOGW(TAG, "Failed to read scene statistics!"); + } + + xEventGroupClearBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_SCENE_STATISTICS); + } else if (EventBits & LEPTON_TASK_UPDATE_EMISSIVITY) { + Lepton_Error_t Error; + + Error = Lepton_SetEmissivity(&_Lepton_Task_State.Lepton, + static_cast(_Lepton_Task_State.NewSetting.Value)); + if (Error == LEPTON_ERR_OK) { + ESP_LOGD(TAG, "Updated emissivity to %u", _Lepton_Task_State.NewSetting.Value); + } else { + ESP_LOGE(TAG, "Failed to update emissivity to %u!", _Lepton_Task_State.NewSetting.Value); + } + + xEventGroupClearBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_UPDATE_EMISSIVITY); + } + } + + ESP_LOGD(TAG, "Lepton task shutting down"); + Lepton_Deinit(&_Lepton_Task_State.Lepton); + + _Lepton_Task_State.TaskHandle = NULL; + + esp_task_wdt_delete(NULL); + vTaskDelete(NULL); +} + +esp_err_t Lepton_Task_Init(void) +{ + uint32_t Caps; + + if (_Lepton_Task_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + ESP_LOGD(TAG, "Initializing Lepton Task"); + _Lepton_Task_State.CurrentReadBuffer = 0; + + _Lepton_Task_State.EventGroup = xEventGroupCreate(); + if (_Lepton_Task_State.EventGroup == NULL) { + ESP_LOGE(TAG, "Failed to create event group!"); + + return ESP_ERR_NO_MEM; + } + + _Lepton_Task_State.BufferMutex = xSemaphoreCreateMutex(); + if (_Lepton_Task_State.BufferMutex == NULL) { + ESP_LOGE(TAG, "Failed to create buffer mutex!"); + + vEventGroupDelete(_Lepton_Task_State.EventGroup); + + return ESP_ERR_NO_MEM; + } + + _Lepton_Task_State.LeptonConf = LEPTON_DEFAULT_CONF; + LEPTON_ASSIGN_FUNC(_Lepton_Task_State.LeptonConf, NULL, NULL, I2CM_Write, I2CM_Read); + LEPTON_ASSIGN_I2C_HANDLE(_Lepton_Task_State.LeptonConf, DevicesManager_GetI2CBusHandle()); + + /* Allocate RGB buffers - both RAW14 and RGB888 use 160x120 resolution + * RAW14: 160x120x3 = 57,600 bytes (after conversion to RGB) + * RGB888: 160x120x3 = 57,600 bytes (native RGB data) + */ + size_t RGB_Buffer_Size = 160 * 120 * 3; + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SIMD | MALLOC_CAP_SPIRAM; + #else + Caps = MALLOC_CAP_SIMD; + #endif + + _Lepton_Task_State.RGB_Buffer[0] = static_cast(heap_caps_malloc(RGB_Buffer_Size, Caps)); + _Lepton_Task_State.RGB_Buffer[1] = static_cast(heap_caps_malloc(RGB_Buffer_Size, Caps)); + + if ((_Lepton_Task_State.RGB_Buffer[0] == NULL) || (_Lepton_Task_State.RGB_Buffer[1] == NULL)) { + ESP_LOGE(TAG, "Can not allocate RGB buffers!"); + + if (_Lepton_Task_State.RGB_Buffer[0]) { + heap_caps_free(_Lepton_Task_State.RGB_Buffer[0]); + } + + if (_Lepton_Task_State.RGB_Buffer[1]) { + heap_caps_free(_Lepton_Task_State.RGB_Buffer[1]); + } + + Lepton_Deinit(&_Lepton_Task_State.Lepton); + vSemaphoreDelete(_Lepton_Task_State.BufferMutex); + vEventGroupDelete(_Lepton_Task_State.EventGroup); + + return ESP_ERR_NO_MEM; + } + + ESP_LOGD(TAG, "RGB buffers allocated: 2 x %u bytes", static_cast(RGB_Buffer_Size)); + + /* Create internal queue to receive raw frames from VoSPI capture task */ + _Lepton_Task_State.RawFrameQueue = xQueueCreate(1, sizeof(Lepton_FrameBuffer_t)); + if (_Lepton_Task_State.RawFrameQueue == NULL) { + ESP_LOGE(TAG, "Failed to create raw frame queue!"); + + heap_caps_free(_Lepton_Task_State.RGB_Buffer[0]); + heap_caps_free(_Lepton_Task_State.RGB_Buffer[1]); + Lepton_Deinit(&_Lepton_Task_State.Lepton); + vSemaphoreDelete(_Lepton_Task_State.BufferMutex); + vEventGroupDelete(_Lepton_Task_State.EventGroup); + + return ESP_ERR_NO_MEM; + } + + esp_event_handler_register(GUI_EVENTS, ESP_EVENT_ANY_ID, on_GUI_Event_Handler, NULL); + esp_event_handler_register(SETTINGS_EVENTS, SETTINGS_EVENT_LEPTON_CHANGED, on_Settings_Event_Handler, NULL); + esp_event_handler_register(USB_EVENTS, ESP_EVENT_ANY_ID, on_USB_Event_Handler, NULL); + + #ifdef CONFIG_SPIRAM + Caps = MALLOC_CAP_SPIRAM; + #else + Caps = 0; + #endif + + /* Allocate JPEG compression buffer in PSRAM */ + _Lepton_Task_State.JpegBufferSize = 160 * 120 * 3; + _Lepton_Task_State.p_JpegBuffer = static_cast(heap_caps_malloc(_Lepton_Task_State.JpegBufferSize, Caps)); + if (_Lepton_Task_State.p_JpegBuffer == NULL) { + ESP_LOGW(TAG, "Failed to allocate JPEG buffer - UVC streaming will not work"); + } else { + ESP_LOGD(TAG, "JPEG buffer allocated: %u bytes", static_cast(_Lepton_Task_State.JpegBufferSize)); + } + + _Lepton_Task_State.isUVCStreaming = false; + + ESP_LOGD(TAG, "Lepton Task initialized"); + + _Lepton_Task_State.isInitialized = true; + + return ESP_OK; +} + +void Lepton_Task_Deinit(void) +{ + if (_Lepton_Task_State.isInitialized == false) { + return; + } + + if (_Lepton_Task_State.isRunning) { + Lepton_Task_Stop(); + } + + ESP_LOGI(TAG, "Deinitializing Lepton Task"); + + if (_Lepton_Task_State.EventGroup != NULL) { + vEventGroupDelete(_Lepton_Task_State.EventGroup); + _Lepton_Task_State.EventGroup = NULL; + } + + esp_event_handler_unregister(SETTINGS_EVENTS, SETTINGS_EVENT_LEPTON_CHANGED, on_Settings_Event_Handler); + esp_event_handler_unregister(GUI_EVENTS, ESP_EVENT_ANY_ID, on_GUI_Event_Handler); + esp_event_handler_unregister(USB_EVENTS, ESP_EVENT_ANY_ID, on_USB_Event_Handler); + + if (_Lepton_Task_State.p_JpegBuffer != NULL) { + heap_caps_free(_Lepton_Task_State.p_JpegBuffer); + _Lepton_Task_State.p_JpegBuffer = NULL; + } + + Lepton_Deinit(&_Lepton_Task_State.Lepton); + + if (_Lepton_Task_State.BufferMutex != NULL) { + vSemaphoreDelete(_Lepton_Task_State.BufferMutex); + _Lepton_Task_State.BufferMutex = NULL; + } + + if (_Lepton_Task_State.RGB_Buffer[0] != NULL) { + heap_caps_free(_Lepton_Task_State.RGB_Buffer[0]); + _Lepton_Task_State.RGB_Buffer[0] = NULL; + } + + if (_Lepton_Task_State.RGB_Buffer[1] != NULL) { + heap_caps_free(_Lepton_Task_State.RGB_Buffer[1]); + _Lepton_Task_State.RGB_Buffer[1] = NULL; + } + + if (_Lepton_Task_State.RawFrameQueue != NULL) { + vQueueDelete(_Lepton_Task_State.RawFrameQueue); + _Lepton_Task_State.RawFrameQueue = NULL; + } + + _Lepton_Task_State.isInitialized = false; +} + +esp_err_t Lepton_Task_Start(App_Context_t *p_AppContext) +{ + BaseType_t Error; + + if (p_AppContext == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (_Lepton_Task_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (_Lepton_Task_State.isRunning) { + ESP_LOGW(TAG, "Task already Running"); + + return ESP_OK; + } + + _Lepton_Task_State.isRunning = true; + + ESP_LOGD(TAG, "Starting Lepton Task"); + + Error = xTaskCreatePinnedToCore(Task_Lepton, "Task_Lepton", CONFIG_LEPTON_TASK_STACKSIZE, p_AppContext, CONFIG_LEPTON_TASK_PRIO, &_Lepton_Task_State.TaskHandle, CONFIG_LEPTON_TASK_CORE); + if (Error != pdPASS) { + ESP_LOGE(TAG, "Failed to create Lepton Task: %d!", Error); + + return ESP_ERR_NO_MEM; + } + + return ESP_OK; +} + +esp_err_t Lepton_Task_Stop(void) +{ + if (_Lepton_Task_State.isRunning == false) { + return ESP_OK; + } + + ESP_LOGI(TAG, "Stopping Lepton Task"); + + xEventGroupSetBits(_Lepton_Task_State.EventGroup, LEPTON_TASK_STOP_REQUEST); + + return ESP_OK; +} + +bool Lepton_Task_IsRunning(void) +{ + return _Lepton_Task_State.isRunning; +} \ No newline at end of file diff --git a/main/Application/Tasks/Lepton/leptonTask.h b/main/Application/Tasks/Lepton/leptonTask.h new file mode 100644 index 0000000..a218657 --- /dev/null +++ b/main/Application/Tasks/Lepton/leptonTask.h @@ -0,0 +1,86 @@ +/* + * leptonTask.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Lepton camera task definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef LEPTON_TASK_H_ +#define LEPTON_TASK_H_ + +#include +#include + +#include + +#include "Application/application.h" + +/** @brief Initialize the Lepton camera task. + * Creates FreeRTOS task for Lepton thermal camera frame acquisition. + * Sets up queues, event handlers, and camera interface. + * @note Task priority and stack size defined in task header. + * Call this before Lepton_Task_Start(). + * Requires Lepton camera connected via SPI. + * @return ESP_OK on success + * ESP_ERR_NO_MEM if task or queue creation fails + * ESP_FAIL if Lepton initialization fails + */ +esp_err_t Lepton_Task_Init(void); + +/** @brief Deinitialize the Lepton camera task. + * Stops the task, deletes queues, and frees all resources. + * Camera is left in its current state. + * @note Task must be stopped before calling this. + * @warning All frame data and queues are lost. + */ +void Lepton_Task_Deinit(void); + +/** @brief Start the Lepton camera task. + * Resumes the FreeRTOS task to begin frame acquisition from the + * Lepton thermal camera. Frames are posted as events. + * @note Frame rate depends on Lepton model (9 Hz typical). + * Posts APP_EVENT_LEPTON_FRAME_READY with frame data. + * @param p_AppContext Pointer to the application context + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_AppContext is NULL + * ESP_ERR_INVALID_STATE if not initialized + * ESP_FAIL if camera start fails + */ +esp_err_t Lepton_Task_Start(App_Context_t *p_AppContext); + +/** @brief Stop the Lepton camera task. + * Suspends frame acquisition. Camera remains initialized and can + * be restarted with Lepton_Task_Start(). + * @note Frame events stop being posted. + * Camera power remains on (low power mode). + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not running + * ESP_FAIL if stop operation fails + */ +esp_err_t Lepton_Task_Stop(void); + +/** @brief Check if the Lepton task is currently running. + * Returns the current task execution status. + * @note Thread-safe. + * @return true if task is running and acquiring frames + * false if task is stopped or not initialized + */ +bool Lepton_Task_IsRunning(void); + +#endif /* LEPTON_TASK_H_ */ \ No newline at end of file diff --git a/main/Application/Tasks/Network/networkTask.cpp b/main/Application/Tasks/Network/networkTask.cpp new file mode 100644 index 0000000..79eb651 --- /dev/null +++ b/main/Application/Tasks/Network/networkTask.cpp @@ -0,0 +1,566 @@ +/* + * networkTask.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Network task implementation. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "managers.h" +#include "networkTask.h" +#include "Application/Tasks/GUI/guiTask.h" +#include "Application/Manager/Network/Server/RemoteControl/remoteControl.h" +#include "Application/Manager/Network/Server/ImageEncoder/imageEncoder.h" + +#define NETWORK_TASK_STOP_REQUEST BIT0 +#define NETWORK_TASK_BROADCAST_FRAME BIT1 +#define NETWORK_TASK_PROV_SUCCESS BIT2 +#define NETWORK_TASK_WIFI_CONNECTED BIT3 +#define NETWORK_TASK_WIFI_DISCONNECTED BIT4 +#define NETWORK_TASK_WIFI_DATA_CHANGE BIT9 +#define NETWORK_TASK_OPEN_WIFI_REQUEST BIT5 +#define NETWORK_TASK_PROV_TIMEOUT BIT6 +#define NETWORK_TASK_SNTP_TIME_SYNCED BIT8 +#define NETWORK_TASK_SETTINGS_CHANGED BIT7 +#define LEPTON_SPOTMETER_READY BIT11 + +typedef struct { + bool isInitialized; + bool isConnected; + bool isRunning; + bool ApplicationStarted; + TaskHandle_t TaskHandle; + EventGroupHandle_t EventGroup; + uint32_t StartTime; + Network_State_t State; + App_Lepton_ROI_Result_t ROIResult; +} Network_Task_State_t; + +static Network_Task_State_t _Network_Task_State; + +static const char *TAG = "Network-Task"; + +/** @brief Event handler for the Lepton event to receive updates when Lepton events are triggered (e.g., new frame ready, camera errors). + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_Lepton_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + ESP_LOGD(TAG, "Lepton event received: ID=%d", ID); + + switch (ID) { + case LEPTON_EVENT_RESPONSE_SPOTMETER: { + memcpy(&_Network_Task_State.ROIResult, p_Data, sizeof(App_Lepton_ROI_Result_t)); + + xEventGroupSetBits(_Network_Task_State.EventGroup, LEPTON_SPOTMETER_READY); + + break; + } + } +} + +/** @brief Event handler for the SNTP events to receive updates when SNTP events are triggered (e.g., time synchronization, timezone changes). + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_SNTP_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + switch (ID) { + case SNTP_EVENT_SNTP_SYNCED: { + ESP_LOGD(TAG, "SNTP time synchronized"); + + xEventGroupSetBits(_Network_Task_State.EventGroup, NETWORK_TASK_SNTP_TIME_SYNCED); + + break; + } + default: { + break; + } + } +} + +/** @brief Network event handler. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_Network_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + switch (ID) { + case NETWORK_EVENT_WIFI_CONNECTED: { + ESP_LOGD(TAG, "WiFi connected"); + + break; + } + case NETWORK_EVENT_WIFI_DISCONNECTED: { + ESP_LOGD(TAG, "WiFi disconnected"); + + _Network_Task_State.State = NETWORK_STATE_DISCONNECTED; + _Network_Task_State.isConnected = false; + + xEventGroupSetBits(_Network_Task_State.EventGroup, NETWORK_TASK_WIFI_DISCONNECTED); + + break; + } + case NETWORK_EVENT_WIFI_GOT_IP: { + _Network_Task_State.State = NETWORK_STATE_CONNECTED; + _Network_Task_State.isConnected = true; + + xEventGroupSetBits(_Network_Task_State.EventGroup, NETWORK_TASK_WIFI_CONNECTED); + + break; + } + case NETWORK_EVENT_PROV_SUCCESS: { + ESP_LOGD(TAG, "Provisioning success"); + + xEventGroupSetBits(_Network_Task_State.EventGroup, NETWORK_TASK_PROV_SUCCESS); + + break; + } + case NETWORK_EVENT_PROV_FAILED: { + ESP_LOGE(TAG, "Provisioning failed!"); + + break; + } + case NETWORK_EVENT_PROV_TIMEOUT: { + ESP_LOGW(TAG, "Provisioning timeout - scheduling stop"); + + xEventGroupSetBits(_Network_Task_State.EventGroup, NETWORK_TASK_PROV_TIMEOUT); + + break; + } + case NETWORK_EVENT_OPEN_WIFI_REQUEST: { + ESP_LOGD(TAG, "Open WiFi request received"); + + xEventGroupSetBits(_Network_Task_State.EventGroup, NETWORK_TASK_OPEN_WIFI_REQUEST); + + break; + } + default: { + break; + } + } +} + +/** @brief GUI event handler. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_GUI_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + switch (ID) { + case GUI_EVENT_APP_STARTED: { + ESP_LOGD(TAG, "Application started event received"); + + _Network_Task_State.ApplicationStarted = true; + + break; + } + default: { + break; + } + } +} + +/** @brief Settings event handler. + * @param p_HandlerArgs Handler argument + * @param Base Event base + * @param ID Event ID + * @param p_Data Event-specific data + */ +static void on_Settings_Event_Handler(void *p_HandlerArgs, esp_event_base_t Base, int32_t ID, void *p_Data) +{ + switch (ID) { + case SETTINGS_EVENT_WIFI_CHANGED: { + SettingsManager_ChangeNotification_t Changed; + + memcpy(&Changed, p_Data, sizeof(SettingsManager_ChangeNotification_t)); + + if (Changed.ID == SETTINGS_ID_WIFI_SSID) { + ESP_LOGD(TAG, "WiFi settings changed, ID: %d", Changed.ID); + + xEventGroupSetBits(_Network_Task_State.EventGroup, NETWORK_TASK_WIFI_DATA_CHANGE); + } + + break; + } + case SETTINGS_EVENT_SYSTEM_CHANGED: { + /* Defer to network task - SettingsManager_GetSystem() acquires a mutex. + * Calling it here (event loop task context) can block the entire event loop. */ + xEventGroupSetBits(_Network_Task_State.EventGroup, NETWORK_TASK_SETTINGS_CHANGED); + + break; + } + default: { + break; + } + } +} + +/** @brief Network task main loop. + * @param p_Parameters Task parameters + */ +static void Task_Network(void *p_Parameters) +{ + Settings_WiFi_t WiFiSettings; + + esp_task_wdt_add(NULL); + + ESP_LOGD(TAG, "Network task started on core %d", xPortGetCoreID()); + + while (_Network_Task_State.ApplicationStarted == false) { + esp_task_wdt_reset(); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + SettingsManager_GetWiFi(&WiFiSettings); + + ESP_LOGD(TAG, "Autoconnect is %s", (WiFiSettings.AutoConnect) ? "enabled" : "disabled"); + + /* If autoconnect is disabled, wait for explicit WiFi open request in main loop */ + bool WaitingForWiFiRequest = (WiFiSettings.AutoConnect == false); + + if (WaitingForWiFiRequest == false) { + SettingsManager_GetWiFi(&WiFiSettings); + + /* Check if WiFi credentials are available */ + if (strlen(WiFiSettings.SSID) == 0) { + ESP_LOGW(TAG, "No credentials found, starting provisioning"); + + Provisioning_Start(); + + _Network_Task_State.State = NETWORK_STATE_PROVISIONING; + } else { + ESP_LOGD(TAG, "Credentials found, connecting to WiFi"); + + NetworkManager_StartSTA(); + } + } else { + ESP_LOGD(TAG, "Waiting for WiFi open request..."); + _Network_Task_State.State = NETWORK_STATE_IDLE; + } + + while (_Network_Task_State.isRunning) { + EventBits_t EventBits; + + esp_task_wdt_reset(); + + EventBits = xEventGroupGetBits(_Network_Task_State.EventGroup); + if (EventBits & NETWORK_TASK_STOP_REQUEST) { + ESP_LOGD(TAG, "Stop request received"); + + _Network_Task_State.isRunning = false; + + xEventGroupClearBits(_Network_Task_State.EventGroup, NETWORK_TASK_STOP_REQUEST); + + break; + } else if (EventBits & NETWORK_TASK_WIFI_CONNECTED) { + /* Notify Time Manager that network is available */ + TimeManager_OnNetworkConnected(); + + if (NetworkManager_StartServer() == ESP_OK) { + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_SERVER_STARTED, NULL, 0, pdMS_TO_TICKS(100)); + } else { + ESP_LOGE(TAG, "Failed to start HTTP/WebSocket server"); + + esp_event_post(NETWORK_EVENTS, NETWORK_EVENT_SERVER_ERROR, NULL, 0, pdMS_TO_TICKS(100)); + } + + xEventGroupClearBits(_Network_Task_State.EventGroup, NETWORK_TASK_WIFI_CONNECTED); + } else if (EventBits & NETWORK_TASK_WIFI_DISCONNECTED) { + ESP_LOGD(TAG, "Handling WiFi disconnection"); + + SettingsManager_GetWiFi(&WiFiSettings); + + /* Notify Time Manager that network is unavailable */ + TimeManager_OnNetworkDisconnected(); + + /* Only start provisioning if not already active and we have no credentials */ + if ((Provisioning_IsActive() == false) && (strlen(WiFiSettings.SSID) == 0)) { + Provisioning_Start(); + } + + xEventGroupClearBits(_Network_Task_State.EventGroup, NETWORK_TASK_WIFI_DISCONNECTED); + } else if (EventBits & NETWORK_TASK_PROV_SUCCESS) { + ESP_LOGD(TAG, "Provisioning success - stopping provisioning and connecting to WiFi"); + + /* Provisioning_Stop() calls esp_wifi_stop() which can block for several seconds. + * NetworkManager_StartSTA() also performs blocking WiFi initialization. + * Unregister from WDT for the duration to prevent false WDT triggers. */ + esp_task_wdt_delete(NULL); + + /* Stop provisioning (HTTP server on port 80 and DNS) */ + Provisioning_Stop(); + + ESP_LOGD(TAG, "Provisioning stopped, starting WiFi STA connection"); + + /* Connect to WiFi with the new credentials */ + NetworkManager_StartSTA(); + + ESP_LOGD(TAG, "WiFi STA connection initiated"); + + /* Re-register with WDT now that the long blocking operations are complete */ + esp_task_wdt_add(NULL); + esp_task_wdt_reset(); + + xEventGroupClearBits(_Network_Task_State.EventGroup, NETWORK_TASK_PROV_SUCCESS); + } else if (EventBits & NETWORK_TASK_PROV_TIMEOUT) { + ESP_LOGD(TAG, "Handling provisioning timeout"); + + /* Provisioning_Stop() calls esp_wifi_stop() which can block for several seconds. + * Unregister from WDT for the duration to prevent false WDT triggers. */ + esp_task_wdt_delete(NULL); + + Provisioning_Stop(); + + /* Re-register with WDT now that the long blocking operation is complete */ + esp_task_wdt_add(NULL); + esp_task_wdt_reset(); + + xEventGroupClearBits(_Network_Task_State.EventGroup, NETWORK_TASK_PROV_TIMEOUT); + } else if (EventBits & NETWORK_TASK_OPEN_WIFI_REQUEST) { + ESP_LOGD(TAG, "Handling WiFi open request"); + + /* Start WiFi connection if we were waiting */ + if (WaitingForWiFiRequest) { + SettingsManager_GetWiFi(&WiFiSettings); + + if ((Provisioning_isProvisioned() == false) && (strlen(WiFiSettings.SSID) == 0)) { + ESP_LOGW(TAG, "No credentials found, starting provisioning"); + + Provisioning_Start(); + + _Network_Task_State.State = NETWORK_STATE_PROVISIONING; + } else { + NetworkManager_StartSTA(); + } + + WaitingForWiFiRequest = false; + } + + xEventGroupClearBits(_Network_Task_State.EventGroup, NETWORK_TASK_OPEN_WIFI_REQUEST); + } else if (EventBits & NETWORK_TASK_WIFI_DATA_CHANGE) { + ESP_LOGD(TAG, "Handling WiFi data change"); + + /* If we're currently connected, restart WiFi to apply new settings */ + if (_Network_Task_State.isConnected) { + NetworkManager_Stop(); + NetworkManager_StartSTA(); + } + + xEventGroupClearBits(_Network_Task_State.EventGroup, NETWORK_TASK_WIFI_DATA_CHANGE); + } else if (EventBits & LEPTON_SPOTMETER_READY) { + if (Server_IsRunning()) { + esp_err_t Error; + + Error = RemoteControl_UpdateSpotmeter(_Network_Task_State.ROIResult.Min, + _Network_Task_State.ROIResult.Max, + _Network_Task_State.ROIResult.Average); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to update spotmeter in server: 0x%x!", Error); + } + } + + xEventGroupClearBits(_Network_Task_State.EventGroup, LEPTON_SPOTMETER_READY); + } else if (EventBits & NETWORK_TASK_SNTP_TIME_SYNCED) { + xEventGroupClearBits(_Network_Task_State.EventGroup, NETWORK_TASK_SNTP_TIME_SYNCED); + } else if (EventBits & NETWORK_TASK_SETTINGS_CHANGED) { + Settings_System_t SystemSettings; + + SettingsManager_GetSystem(&SystemSettings); + ImageEncoder_SetQuality(SystemSettings.JpegQuality); + + ESP_LOGD(TAG, "System settings applied - JPEG quality: %d", SystemSettings.JpegQuality); + + xEventGroupClearBits(_Network_Task_State.EventGroup, NETWORK_TASK_SETTINGS_CHANGED); + } + + vTaskDelay(pdMS_TO_TICKS(10)); + } + + ESP_LOGD(TAG, "Network task shutting down"); + Provisioning_Stop(); + NetworkManager_Stop(); + + _Network_Task_State.TaskHandle = NULL; + + esp_task_wdt_delete(NULL); + vTaskDelete(NULL); +} + +esp_err_t Network_Task_Init(App_Context_t *p_AppContext) +{ + esp_err_t Error; + + if (p_AppContext == NULL) { + return ESP_ERR_INVALID_ARG; + } else if (_Network_Task_State.isInitialized) { + ESP_LOGW(TAG, "Already initialized"); + + return ESP_OK; + } + + ESP_LOGD(TAG, "Initializing network task"); + + Error = nvs_flash_init(); + if ((Error == ESP_ERR_NVS_NO_FREE_PAGES) || (Error == ESP_ERR_NVS_NEW_VERSION_FOUND)) { + ESP_ERROR_CHECK(nvs_flash_erase()); + Error = nvs_flash_init(); + } + + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to init NVS flash: 0x%x!", Error); + + return Error; + } + + _Network_Task_State.EventGroup = xEventGroupCreate(); + if (_Network_Task_State.EventGroup == NULL) { + ESP_LOGE(TAG, "Failed to create event group!"); + + return ESP_ERR_NO_MEM; + } + + if ((esp_event_handler_register(NETWORK_EVENTS, ESP_EVENT_ANY_ID, on_Network_Event_Handler, NULL) != ESP_OK) || + (esp_event_handler_register(GUI_EVENTS, GUI_EVENT_APP_STARTED, on_GUI_Event_Handler, NULL) != ESP_OK) || + (esp_event_handler_register(SNTP_EVENTS, SNTP_EVENT_SNTP_SYNCED, on_SNTP_Event_Handler, NULL) != ESP_OK) || + (esp_event_handler_register(LEPTON_EVENTS, LEPTON_EVENT_RESPONSE_SPOTMETER, on_Lepton_Event_Handler, NULL) != ESP_OK) || + (esp_event_handler_register(SETTINGS_EVENTS, ESP_EVENT_ANY_ID, on_Settings_Event_Handler, NULL) != ESP_OK)) { + ESP_LOGE(TAG, "Failed to register event handler: %d!", Error); + + vEventGroupDelete(_Network_Task_State.EventGroup); + + return Error; + } + + Error = NetworkManager_Init(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to init WiFi manager: 0x%x!", Error); + + esp_event_handler_unregister(SNTP_EVENTS, SNTP_EVENT_SNTP_SYNCED, on_SNTP_Event_Handler); + esp_event_handler_unregister(NETWORK_EVENTS, ESP_EVENT_ANY_ID, on_Network_Event_Handler); + esp_event_handler_unregister(GUI_EVENTS, GUI_EVENT_APP_STARTED, on_GUI_Event_Handler); + esp_event_handler_unregister(LEPTON_EVENTS, LEPTON_EVENT_RESPONSE_SPOTMETER, on_Lepton_Event_Handler); + esp_event_handler_unregister(SETTINGS_EVENTS, ESP_EVENT_ANY_ID, on_Settings_Event_Handler); + vEventGroupDelete(_Network_Task_State.EventGroup); + + return Error; + } + + Error = Provisioning_Init(); + if (Error != ESP_OK) { + ESP_LOGE(TAG, "Failed to init provisioning: 0x%x!", Error); + /* Continue anyway, provisioning is optional */ + } + + _Network_Task_State.State = NETWORK_STATE_IDLE; + _Network_Task_State.isInitialized = true; + + ESP_LOGD(TAG, "Network Task initialized"); + + return ESP_OK; +} + +void Network_Task_Deinit(void) +{ + if (_Network_Task_State.isInitialized == false) { + return; + } + + ESP_LOGD(TAG, "Deinitializing Network Task"); + + Network_Task_Stop(); + Provisioning_Deinit(); + NetworkManager_Deinit(); + + esp_event_handler_unregister(LEPTON_EVENTS, LEPTON_EVENT_RESPONSE_SPOTMETER, on_Lepton_Event_Handler); + esp_event_handler_unregister(SNTP_EVENTS, SNTP_EVENT_SNTP_SYNCED, on_SNTP_Event_Handler); + esp_event_handler_unregister(NETWORK_EVENTS, ESP_EVENT_ANY_ID, on_Network_Event_Handler); + esp_event_handler_unregister(GUI_EVENTS, GUI_EVENT_APP_STARTED, on_GUI_Event_Handler); + esp_event_handler_unregister(SETTINGS_EVENTS, ESP_EVENT_ANY_ID, on_Settings_Event_Handler); + + if (_Network_Task_State.EventGroup != NULL) { + vEventGroupDelete(_Network_Task_State.EventGroup); + _Network_Task_State.EventGroup = NULL; + } + + _Network_Task_State.isInitialized = false; +} + +esp_err_t Network_Task_Start(void) +{ + BaseType_t Error; + + if (_Network_Task_State.isInitialized == false) { + return ESP_ERR_INVALID_STATE; + } else if (_Network_Task_State.isRunning) { + ESP_LOGW(TAG, "Task already Running"); + return ESP_OK; + } + + _Network_Task_State.isRunning = true; + + ESP_LOGD(TAG, "Starting Network Task"); + + Error = xTaskCreatePinnedToCore(Task_Network, "Task_Network", CONFIG_NETWORK_TASK_STACKSIZE, NULL, CONFIG_NETWORK_TASK_PRIO, &_Network_Task_State.TaskHandle, CONFIG_NETWORK_TASK_CORE); + if (Error != pdPASS) { + ESP_LOGE(TAG, "Failed to create Network Task: %d!", Error); + + return ESP_ERR_NO_MEM; + } + + _Network_Task_State.StartTime = xTaskGetTickCount() / configTICK_RATE_HZ; + + return ESP_OK; +} + +esp_err_t Network_Task_Stop(void) +{ + if (_Network_Task_State.isRunning == false) { + return ESP_OK; + } + + ESP_LOGD(TAG, "Stopping Network Task"); + + xEventGroupSetBits(_Network_Task_State.EventGroup, NETWORK_TASK_STOP_REQUEST); + + return ESP_OK; +} + +bool Network_Task_IsRunning(void) +{ + return _Network_Task_State.isRunning; +} \ No newline at end of file diff --git a/main/Application/Tasks/Network/networkTask.h b/main/Application/Tasks/Network/networkTask.h new file mode 100644 index 0000000..7934f38 --- /dev/null +++ b/main/Application/Tasks/Network/networkTask.h @@ -0,0 +1,85 @@ +/* + * networkTask.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Network task definition. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef NETWORK_TASK_H_ +#define NETWORK_TASK_H_ + +#include +#include + +#include + +#include "Application/application.h" +#include "Application/Manager/Network/networkTypes.h" + +/** @brief Initialize the network task. + * Creates FreeRTOS task for network operations including WiFi + * management, HTTP server, WebSocket, and VISA server. + * @note Call this after NetworkManager_Init(). + * Task handles WiFi events and client connections. + * @param p_AppContext Pointer to the application context + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if p_AppContext is NULL + * ESP_ERR_NO_MEM if task creation fails + * ESP_FAIL if network initialization fails + */ +esp_err_t Network_Task_Init(App_Context_t *p_AppContext); + +/** @brief Deinitialize the network task. + * Stops the task and frees all resources. Network connections are + * terminated. + * @note Task must be stopped before calling this. + * All client connections are closed. + */ +void Network_Task_Deinit(void); + +/** @brief Start the network task. + * Resumes the task to handle network operations and event processing. + * @note Task processes WiFi events and manages connections. + * Servers start when WiFi connection is established. + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not initialized + * ESP_FAIL if task start fails + */ +esp_err_t Network_Task_Start(void); + +/** @brief Stop the network task. + * Suspends network task execution. Existing connections remain active + * but no new connections are accepted. + * @note WiFi remains connected. + * To disconnect WiFi, use NetworkManager_DisconnectWiFi(). + * @return ESP_OK on success + * ESP_ERR_INVALID_STATE if not running + * ESP_FAIL if stop operation fails + */ +esp_err_t Network_Task_Stop(void); + +/** @brief Check if the network task is running. + * Returns the current task execution status. + * @note Thread-safe. + * @return true if task is running and processing events + * false if task is stopped or not initialized + */ +bool Network_Task_IsRunning(void); + +#endif /* NETWORK_TASK_H_ */ \ No newline at end of file diff --git a/main/Application/Tasks/tasks.h b/main/Application/Tasks/tasks.h new file mode 100644 index 0000000..452e041 --- /dev/null +++ b/main/Application/Tasks/tasks.h @@ -0,0 +1,33 @@ +/* + * tasks.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Task includes for all application tasks. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Errors and commissions should be reported to DanielKampert@kampis-elektroecke.de + */ + +#ifndef TASKS_H_ +#define TASKS_H_ + +#include "Lepton/leptonTask.h" +#include "Network/networkTask.h" +#include "Devices/devicesTask.h" +#include "GUI/guiTask.h" +#include "Camera/cameraTask.h" + +#endif /* TASKS_H_ */ \ No newline at end of file diff --git a/main/Application/application.h b/main/Application/application.h new file mode 100644 index 0000000..f09196d --- /dev/null +++ b/main/Application/application.h @@ -0,0 +1,147 @@ +/* + * application.h + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Application header file. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef APPLICATION_H_ +#define APPLICATION_H_ + +#include +#include +#include + +#include "Manager/managers.h" + +#include + +ESP_EVENT_DECLARE_BASE(LEPTON_EVENTS); +ESP_EVENT_DECLARE_BASE(DEVICE_EVENTS); +ESP_EVENT_DECLARE_BASE(GUI_EVENTS); + +/** @brief Lepton camera event identifiers. + */ +enum { + LEPTON_EVENT_CAMERA_READY, /**< Lepton camera is ready. + Data is transmitted in a App_Lepton_Device_t structure. */ + LEPTON_EVENT_CAMERA_ERROR, /**< Lepton camera error occurred. */ + LEPTON_EVENT_RESPONSE_FPA_AUX_TEMP, /**< FPA and AUX temperatures are ready. + Data is transmitted in a App_Lepton_Temperatures_t structure. */ + LEPTON_EVENT_RESPONSE_SPOTMETER, /**< Spotmeter data is ready. + Data is transmitted in a App_Lepton_Spotmeter_t structure with temperature value Celsius. */ + LEPTON_EVENT_RESPONSE_UPTIME, /**< Uptime data is ready. + Data is transmitted as a uint32_t representing uptime in milliseconds. */ + LEPTON_EVENT_RESPONSE_PIXEL_TEMPERATURE, /**< Pixel temperature data is ready. + Data is transmitted as a float. */ + LEPTON_EVENT_RESPONSE_SCENE_STATISTICS, /**< Scene statistics data is ready. + Data is transmitted in a App_Lepton_SceneStatistics_t structure. */ +}; + +/** @brief Device status event identifiers. + */ +enum { + DEVICE_EVENT_RESPONSE_BATTERY_VOLTAGE, /**< New battery voltage reading available. + Data is transmitted in a App_Devices_Battery_t structure. */ + DEVICE_EVENT_RESPONSE_CHARGING, /**< Charging state changed. + Data is transmitted as a bool. */ + DEVICE_EVENT_RESPONSE_TIME, /**< Device RTC time has been updated. + Data is transmitted in a struct tm structure. */ +}; + +/** @brief GUI event identifiers. + */ +enum { + GUI_EVENT_INIT_DONE, /**< GUI task initialization done. */ + GUI_EVENT_INIT_ERROR, /**< GUI task initialization error occurred. */ + GUI_EVENT_APP_STARTED, /**< Application has started. */ + GUI_EVENT_REQUEST_ROI, /**< Update the ROI rectangle on the GUI. + Data is transmitted in a Settings_ROI_t structure. */ + GUI_EVENT_REQUEST_FPA_AUX_TEMP, /**< Request update of the FPA and AUX temperature. */ + GUI_EVENT_REQUEST_UPTIME, /**< Request update of the uptime. */ + GUI_EVENT_REQUEST_PIXEL_TEMPERATURE, /**< Request update of pixel temperature. + Data is transmitted in a App_GUI_Screenposition_t structure. */ + GUI_EVENT_REQUEST_SPOTMETER, /**< Request update of spotmeter data. */ + GUI_EVENT_REQUEST_SCENE_STATISTICS, /**< Request update of scene statistics data. */ + GUI_EVENT_THERMAL_IMAGE_SAVED, /**< Thermal image successfully saved to storage. */ + GUI_EVENT_THERMAL_IMAGE_SAVE_FAILED, /**< Thermal image save operation failed. + Data is transmitted as an int representing the errno value. */ +}; + +/** @brief Structure representing a screen position. + */ +typedef struct { + int16_t x; /**< X coordinate (0 to 159). */ + int16_t y; /**< Y coordinate (0 to 119). */ + int32_t Width; /**< Width of the screen element where the position is related to. */ + int32_t Height; /**< Height of the screen element where the position is related to. */ +} App_GUI_Screenposition_t; + +/** @brief Structure representing battery information. + */ +typedef struct { + int Voltage; /**< Battery voltage in millivolts. */ + uint8_t Percentage; /**< Battery percentage (0-100%). */ +} App_Devices_Battery_t; + +/** @brief Structure representing a ready frame from the Lepton camera. + */ +typedef struct { + uint8_t *Buffer; /**< Pointer to the image buffer (Width * Height * Channels). */ + uint32_t Width; /**< Width of the frame in pixels. */ + uint32_t Height; /**< Height of the frame in pixels. */ + uint32_t Channels; /**< Number of color channels (e.g., 3 for RGB). */ + int16_t Min; /**< Minimum value in the frame. */ + int16_t Max; /**< Maximum value in the frame. */ +} App_Lepton_FrameReady_t; + +/** @brief Structure representing FPA and AUX temperature from the Lepton camera. + */ +typedef struct { + float FPA; /**< Focal Plane Array temperature in Degree Celsius. */ + float AUX; /**< Auxiliary temperature in Degree Celsius. */ +} App_Lepton_Temperatures_t; + +/** @brief Structure representing the Lepton camera device status. + */ +typedef struct { + char PartNumber[33]; /**< Lepton device part number. */ + char SerialNumber[24]; /**< Lepton device serial number. */ + struct { + char GPP_Revision[24]; /**< Lepton GPP software revision. */ + char DSP_Revision[24]; /**< Lepton DSP software revision. */ + } SoftwareRevision; +} App_Lepton_Device_t; + +/** @brief Structure representing the ROI results from the Lepton camera. + */ +typedef struct { + float Max; /**< Maximum value within the specified ROI. */ + float Min; /**< Minimum value within the specified ROI. */ + union { + float Average; /**< Average value within the specified ROI. */ + float Mean; /**< Mean value within the specified ROI. */ + }; +} App_Lepton_ROI_Result_t; + +/** @brief Application context aggregating shared resources. + */ +typedef struct { + QueueHandle_t Lepton_FrameEventQueue; /**< Queue for Lepton frame ready events. */ +} App_Context_t; + +#endif /* APPLICATION_H_ */ \ No newline at end of file diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..2f87150 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,20 @@ +FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/main/*.*) + +idf_component_register(SRCS ${app_sources} + INCLUDE_DIRS "Application/Manager" + REQUIRES esp_http_server esp_timer esp_wifi json nvs_flash driver fatfs wear_levelling + EMBED_TXTFILES "../webserver/provision.html" + EMBED_TXTFILES "../webserver/logo.png" + EMBED_TXTFILES "../LICENSE" +) + +# Allow descriptors.cpp to use UVC descriptor macros from esp_tinyusb private headers +target_include_directories(${COMPONENT_LIB} PRIVATE + "${CMAKE_SOURCE_DIR}/components/esp_tinyusb/include_private" +) + +target_compile_definitions(${COMPONENT_LIB} PUBLIC + PYROVISION_VERSION_MAJOR=1 + PYROVISION_VERSION_MINOR=0 + PYROVISION_VERSION_BUILD=0 +) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild new file mode 100644 index 0000000..d03c42f --- /dev/null +++ b/main/Kconfig.projbuild @@ -0,0 +1,424 @@ +menu "PyroVision" + menu "Device" + config DEVICE_MANUFACTURER + string "Device manufacturer" + default "PyroVision" + + config DEVICE_NAME + string "Device name" + default "ThermalCam" + endmenu + + menu "USB" + config USB_VID + hex "USB Vendor ID" + default 0x1234 + + config USB_PID + hex "USB Product ID" + default 0x5678 + endmenu + + menu "Settings" + config SETTINGS_NAMESPACE + string "Settings namespace for the NVS" + default "PyroVision" + endmenu + + menu "Battery" + choice + prompt "ADC Unit" + default BATTERY_ADC_1 + help + Select the used ADC unit. + + config BATTERY_ADC_1 + bool "ADC 1" + config BATTERY_ADC_2 + bool "ADC 2" + endchoice + + config BATTERY_ADC_CHANNEL + int "ADC channel used for battery measurement" + range 0 9 + default 2 + + choice + prompt "ADC Attenuation" + default BATTERY_ADC_ATT_12 + help + Select the used ADC attenuation. + + config BATTERY_ADC_ATT_0 + bool "0 dB ADC attenuation" + config BATTERY_ADC_ATT_2_5 + bool "2.5 dB ADC attenuation" + config BATTERY_ADC_ATT_6 + bool "6 dB ADC attenuation" + config BATTERY_ADC_ATT_12 + bool "12 dB ADC attenuation" + endchoice + + config BATTERY_ADC_MIN_MV + int "Minimal battery voltage in Millivolt" + default 3000 + + config BATTERY_ADC_MAX_MV + int "Maximal battery voltage in Millivolt" + default 4200 + + config BATTERY_ADC_R1 + int "Value of the R1 resistor of the voltage divider" + default 2200 + + config BATTERY_ADC_R2 + int "Value of the R2 resistor of the voltage divider" + default 2200 + + config BATTERY_ADC_SAMPLES + int "Number of ADC samples to average" + range 1 64 + default 16 + help + Number of ADC samples to take and average for battery voltage measurement. + Higher values give more accurate readings but take longer. + + config BATTERY_ADC_SAMPLE_DELAY_MS + int "Delay between ADC samples in milliseconds" + range 0 100 + default 10 + help + Delay between consecutive ADC samples in milliseconds. + A small delay can improve accuracy by allowing the ADC to settle. + + config BATTERY_ADC_EMA_ALPHA + int "Exponential moving average alpha (0-100)" + range 0 100 + default 20 + help + Exponential moving average alpha factor (0-100, representing 0.0-1.0). + Lower values give more smoothing but slower response to changes. + 20 represents 0.2 (20% new value, 80% old value). + + config BATTERY_ADC_OUTLIER_THRESHOLD + int "Maximum deviation from median in millivolts" + range 50 500 + default 200 + help + Maximum allowed deviation from the median value in mV. + Samples exceeding this threshold are considered outliers and excluded. + endmenu + + menu "Network" + menu "Websocket" + config NETWORK_WEBSOCKET_CLIENTS + int "Maximum number of WebSocket clients" + default 4 + endmenu + + menu "Task" + config NETWORK_TASK_STACKSIZE + int "Stack size" + default 6144 + + config NETWORK_TASK_PRIO + int "Task prio" + default 16 + + config NETWORK_TASK_CORE + int "Task core" + default 1 + endmenu + + menu "VISA" + config NETWORK_VISA_ERROR_QUEUE_LLENGTH + int "Length of the error queue" + default 10 + + config NETWORK_VISA_MAX_CLIENTS + int "Maximum number of clients" + default 2 + endmenu + endmenu + + menu "Image Encoding" + choice + prompt "Default Image Format" + default IMAGE_FORMAT_JPEG + help + Select the default image format for thermal frame encoding. + This can be changed at runtime through the settings. + + config IMAGE_FORMAT_JPEG + bool "JPEG" + help + Lossy JPEG compression. Smallest file size, fastest encoding. + Recommended for most use cases. + + config IMAGE_FORMAT_PNG + bool "PNG (Not implemented)" + help + Lossless PNG compression. Larger file size than JPEG. + Currently not implemented - will fall back to RAW format. + + config IMAGE_FORMAT_BITMAP + bool "Bitmap (BMP)" + help + Uncompressed 24-bit RGB bitmap. Largest file size. + Compatible with most image viewers. + + config IMAGE_FORMAT_RAW + bool "RAW RGB" + help + Raw RGB888 pixel data without headers or compression. + Largest file size, requires custom parsing. + endchoice + + config IMAGE_JPEG_QUALITY + int "JPEG Quality (1-100)" + range 1 100 + default 80 + depends on IMAGE_FORMAT_JPEG + help + JPEG compression quality (1-100). + Higher values produce better quality but larger files. + Recommended range: 70-90 + - 70: Good quality, small file size + - 80: High quality (default) + - 90: Very high quality, larger files + endmenu + + menu "Lepton" + menu "Task" + config LEPTON_TASK_STACKSIZE + int "Stack size" + default 8192 + + config LEPTON_TASK_PRIO + int "Task prio" + default 12 + + config LEPTON_TASK_CORE + int "Task core" + default 1 + endmenu + endmenu + + menu "Camera" + menu "Task" + config CAMERA_TASK_STACKSIZE + int "Stack size" + default 8192 + + config CAMERA_TASK_PRIO + int "Task prio" + default 12 + + config CAMERA_TASK_CORE + int "Task core" + default 1 + endmenu + endmenu + + menu "Devices" + menu "I2C" + choice + prompt "Peripheral I2C Bus" + default DEVICES_I2C_I2C1_HOST + help + Select the I2C Bus for the peripheral components. + + config DEVICES_I2C_I2C0_HOST + bool "I2C0_HOST" + config DEVICES_I2C_I2C1_HOST + bool "I2C1_HOST" + endchoice + + config DEVICES_I2C_SCL + int "SCL pin number" + range 0 48 + default 21 + + config DEVICES_I2C_SDA + int "SDA pin number" + range 0 48 + default 47 + endmenu + + menu "SPI" + config SPI_SCLK + int "SCLK GPIO number" + range 0 48 + default 46 + + config SPI_MOSI + int "MOSI GPIO number" + range 0 48 + default 48 + + config SPI_MISO + int "MISO GPIO number" + range 0 48 + default 9 + + config SPI_TRANSFER_SIZE + int "Max. transfer size in bytes" + default 4096 + + menu "Touch" + choice + prompt "Touch I2C Bus" + default TOUCH_I2C0_HOST + help + Select the I2C Bus the Touch Controller is attached to. + + config TOUCH_I2C0_HOST + bool "I2C0_HOST" + config TOUCH_I2C1_HOST + bool "I2C1_HOST" + endchoice + + config TOUCH_CLOCK + int "Touch bus clock" + default 400000 + + config TOUCH_SDA + int "Touch interrupt GPIO number" + range -1 48 + default 39 + + config TOUCH_SCL + int "Touch interrupt GPIO number" + range -1 48 + default 40 + + config TOUCH_IRQ + int "Touch interrupt GPIO number" + range -1 48 + default 38 + + config TOUCH_RST + int "Touch reset GPIO number" + range -1 48 + default -1 + endmenu + + menu "SD-Card" + choice + prompt "SD-Card SPI Bus" + default SD_CARD_SPI3_HOST + help + Select the SPI Bus the SD-Card is attached to. + + config SD_CARD_SPI2_HOST + bool "SPI2_HOST" + config SD_CARD_SPI3_HOST + bool "SPI3_HOST" + endchoice + + config SD_CARD_CLOCK + int "SD-Card SPI clock" + default 2000000 + + config SD_CARD_FORMAT_CARD + bool "Format the card if mount failed" + default n + + config SD_CARD_PIN_CS + int "Card select GPIO" + default 4 + + config SD_CARD_PIN_CD + int "Card detection GPIO" + default 17 + endmenu + + menu "LCD" + choice + prompt "Display Bus" + default LCD_SPI3_HOST + help + Select the SPI Bus the Display is attached to. + + config LCD_SPI2_HOST + bool "SPI2_HOST" + config LCD_SPI3_HOST + bool "SPI3_HOST" + endchoice + + config LCD_CLOCK + int "SPI clock" + default 200000000 + + config LCD_CS + int "Chip Select GPIO" + range 0 48 + default 18 + + config LCD_DC + int "DC GPIO" + range 0 48 + default 3 + + config LCD_RST + int "Reset GPIO" + range -1 48 + default 8 + + config LCD_BL + int "Backlight GPIO" + range -1 48 + default -1 + endmenu + endmenu + + menu "Task" + config DEVICES_TASK_STACKSIZE + int "Stack size" + default 4096 + + config DEVICES_TASK_PRIO + int "Task prio" + default 16 + + config DEVICES_TASK_CORE + int "Task core" + default 1 + endmenu + endmenu + + menu "GUI" + config GUI_WIDTH + int "Screen width" + default 320 + + config GUI_HEIGHT + int "Screen Height" + default 240 + + config GUI_TOUCH_DEBUG + bool "Enable touch debug visualization" + default n + help + Show touch input visualization overlay with coordinates and crosshair. + Useful for calibrating touch input. + + config GUI_LVGL_TICK_PERIOD_MS + int "LVGL Tick Period" + default 2 + + menu "Task" + config GUI_TASK_STACKSIZE + int "Stack size" + default 16384 + + config GUI_TASK_PRIO + int "Task prio" + default 2 + + config GUI_TASK_CORE + int "Task core" + default 0 + endmenu + endmenu +endmenu \ No newline at end of file diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..f383dda --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,24 @@ +dependencies: + espressif/esp-dsp: + version: "^1.7.0" + espressif/esp_new_jpeg: + version: "^1.0.0" + espressif/bootloader_support_plus: + version: "^0.1.0" + espressif/esp_lcd_ili9341: + version: "^2.0.1" + espressif/esp_lcd_touch_gt911: + version: "^1.2.0" + espressif/esp32-camera: + version: "^2.1.0" + espressif/libpng: + version: "^1.6.54" + espressif/max17048: + version: "^0.1.1" + lvgl/lvgl: + version: "^9.4.0" + joltwallet/littlefs: + version: "^1.20.3" + tinyusb: + version: ">=0.17.0~2" + public: true diff --git a/main/main.cpp b/main/main.cpp new file mode 100644 index 0000000..5e08575 --- /dev/null +++ b/main/main.cpp @@ -0,0 +1,86 @@ +/* + * main.cpp + * + * Copyright (C) Daniel Kampert, 2026 + * Website: www.kampis-elektroecke.de + * File info: Main application entry point. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include + +#include "managers.h" +#include "Application/Tasks/tasks.h" +#include "Application/application.h" + +static App_Context_t _App_Context; + +static const char *TAG = "main"; + +/** @brief Main application entry point. + * Initializes all managers, tasks, and starts the application. + */ +extern "C" void app_main(void) +{ + i2c_master_dev_handle_t RtcHandle = NULL; + + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + _App_Context.Lepton_FrameEventQueue = xQueueCreate(1, sizeof(App_Lepton_FrameReady_t)); + if (_App_Context.Lepton_FrameEventQueue == NULL) { + ESP_LOGE(TAG, "Failed to create frame queue!"); + + return; + } + + ESP_ERROR_CHECK(SettingsManager_Init()); + ESP_ERROR_CHECK(Devices_Task_Init()); + ESP_ERROR_CHECK(MemoryManager_Init()); + ESP_ERROR_CHECK(USBManager_Init()); + + if (DevicesManager_GetRTCHandle(&RtcHandle) == ESP_OK) { + if (TimeManager_Init(RtcHandle) == ESP_OK) { + Settings_System_t SystemSettings; + + SettingsManager_GetSystem(&SystemSettings); + TimeManager_SetTimezone(SystemSettings.Timezone); + } else { + ESP_LOGW(TAG, "Failed to initialize Time Manager!"); + } + } else { + ESP_LOGW(TAG, "RTC not available, Time Manager initialization skipped"); + } + + ESP_ERROR_CHECK(GUI_Task_Init()); + ESP_ERROR_CHECK(Lepton_Task_Init()); + ESP_ERROR_CHECK(Camera_Task_Init()); + ESP_ERROR_CHECK(Network_Task_Init(&_App_Context)); + ESP_LOGI(TAG, " Initialization successful"); + + ESP_LOGI(TAG, "Starting tasks..."); + ESP_ERROR_CHECK(Devices_Task_Start(&_App_Context)); + ESP_ERROR_CHECK(GUI_Task_Start(&_App_Context)); + ESP_ERROR_CHECK(Lepton_Task_Start(&_App_Context)); + ESP_ERROR_CHECK(Network_Task_Start()); + ESP_LOGI(TAG, " Tasks started"); + + /* Main task can now be deleted - no need to remove from watchdog as it was never added */ + vTaskDelete(NULL); +} \ No newline at end of file diff --git a/partitions.csv b/partitions.csv new file mode 100644 index 0000000..92f06c0 --- /dev/null +++ b/partitions.csv @@ -0,0 +1,9 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +nvs_keys, data, nvs_keys, 0xD000, 0x1000, +otadata, data, ota, 0xE000, 0x2000, +app0, app, ota_0, 0x10000, 0x2F0000, +app1, app, ota_1, 0x300000, 0x2F0000, +storage, data, fat, 0x5F0000, 0x160000, +settings, data, nvs, 0x750000, 0x10000, +coredump, data, coredump, 0x760000, 0x10000, \ No newline at end of file diff --git a/patches/0001-feat-esp_lcd-Add-PSRAM-mode-support-in-SPI-LCD-panel.patch b/patches/0001-feat-esp_lcd-Add-PSRAM-mode-support-in-SPI-LCD-panel.patch new file mode 100644 index 0000000..56db6c6 --- /dev/null +++ b/patches/0001-feat-esp_lcd-Add-PSRAM-mode-support-in-SPI-LCD-panel.patch @@ -0,0 +1,96 @@ +From c87755f74b4379079ae46dbcd736c5f03d3091f4 Mon Sep 17 00:00:00 2001 +From: Daniel Kampert +Date: Thu, 26 Feb 2026 11:06:25 +0100 +Subject: [PATCH] feat(esp_lcd): Add PSRAM mode support in SPI LCD panel IO + +Add support for the `SPI_TRANS_DMA_USE_PSRAM` flag to the +`esp_lcd_panel_io_spi_transmit()` function, allowing it to +utilize PSRAM for DMA transfers when the flag is set. + +Closes: #18283 + +Signed-off-by: Daniel Kampert +--- + components/esp_lcd/include/esp_lcd_io_spi.h | 1 + + components/esp_lcd/spi/esp_lcd_panel_io_spi.c | 19 +++++++++++++++++++ + 2 files changed, 20 insertions(+) + +diff --git a/components/esp_lcd/include/esp_lcd_io_spi.h b/components/esp_lcd/include/esp_lcd_io_spi.h +index 3a4b338b35..4871cecb40 100644 +--- a/components/esp_lcd/include/esp_lcd_io_spi.h ++++ b/components/esp_lcd/include/esp_lcd_io_spi.h +@@ -41,6 +41,7 @@ typedef struct { + unsigned int sio_mode: 1; /*!< Read and write through a single data line (MOSI) */ + unsigned int lsb_first: 1; /*!< transmit LSB bit first */ + unsigned int cs_high_active: 1; /*!< CS line is high active */ ++ unsigned int psram_mode: 1; /*!< Enable direct transmissions from the PSRAM */ + } flags; /*!< Extra flags to fine-tune the SPI device */ + } esp_lcd_panel_io_spi_config_t; + +diff --git a/components/esp_lcd/spi/esp_lcd_panel_io_spi.c b/components/esp_lcd/spi/esp_lcd_panel_io_spi.c +index eae42c890d..2063a8f005 100644 +--- a/components/esp_lcd/spi/esp_lcd_panel_io_spi.c ++++ b/components/esp_lcd/spi/esp_lcd_panel_io_spi.c +@@ -63,6 +63,7 @@ typedef struct { + unsigned int dc_param_level: 1; // Indicates the level of DC line when transferring parameters + unsigned int octal_mode: 1; // Indicates whether the transmitting is enabled with octal mode (8 data lines) + unsigned int quad_mode: 1; // Indicates whether the transmitting is enabled with quad mode (4 data lines) ++ unsigned int psram_mode: 1; // Indicates whether the transmitting is done from PSRAM + } flags; + lcd_spi_trans_descriptor_t trans_pool[]; // Transaction pool + } esp_lcd_panel_io_spi_t; +@@ -106,6 +107,7 @@ esp_err_t esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t bus, const esp_lcd_p + spi_panel_io->flags.dc_param_level = !io_config->flags.dc_low_on_param; + spi_panel_io->flags.octal_mode = io_config->flags.octal_mode; + spi_panel_io->flags.quad_mode = io_config->flags.quad_mode; ++ spi_panel_io->flags.psram_mode = io_config->flags.psram_mode; + spi_panel_io->on_color_trans_done = io_config->on_color_trans_done; + spi_panel_io->user_ctx = io_config->user_ctx; + spi_panel_io->lcd_cmd_bits = io_config->lcd_cmd_bits; +@@ -232,6 +234,10 @@ static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons + lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT); + } + ++ if (spi_panel_io->flags.psram_mode) { ++ lcd_trans->base.flags |= SPI_TRANS_DMA_USE_PSRAM; ++ } ++ + if (send_cmd) { + spi_lcd_prepare_cmd_buffer(spi_panel_io, &lcd_cmd); + lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_cmd_level; // set D/C level in command phase +@@ -286,6 +292,10 @@ static esp_err_t panel_io_spi_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void + lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT); + } + ++ if (spi_panel_io->flags.psram_mode) { ++ lcd_trans->base.flags |= SPI_TRANS_DMA_USE_PSRAM; ++ } ++ + if (send_cmd) { + spi_lcd_prepare_cmd_buffer(spi_panel_io, &lcd_cmd); + lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_cmd_level; // set D/C level in command phase +@@ -347,6 +357,11 @@ static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons + // use 8 lines for transmitting command, address and data + lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT); + } ++ ++ if (spi_panel_io->flags.psram_mode) { ++ lcd_trans->base.flags |= SPI_TRANS_DMA_USE_PSRAM; ++ } ++ + // command is short, using polling mode + ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base); + ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) command failed"); +@@ -391,6 +406,10 @@ static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons + lcd_trans->base.flags |= SPI_TRANS_MODE_QIO; + } + ++ if (spi_panel_io->flags.psram_mode) { ++ lcd_trans->base.flags |= SPI_TRANS_DMA_USE_PSRAM; ++ } ++ + // color data is usually large, using queue+blocking mode + ret = spi_device_queue_trans(spi_panel_io->spi_dev, &lcd_trans->base, portMAX_DELAY); + ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (queue) color failed"); +-- +2.53.0.windows.1 \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..4b6a18d --- /dev/null +++ b/platformio.ini @@ -0,0 +1,68 @@ +[platformio] +src_dir = main +default_envs = debug + +[env] +# This project requires esp-idf >=5.5.3 to have access to SPI PSRAM mode +platform = https://github.com/platformio/platform-espressif32#bf8f9c1f6d42c06b328d691890461bdfdc3d4db0 +framework = espidf + +platform_packages = + +extra_scripts = + pre:scripts/clean.py + pre:scripts/patch.py + scripts/format.py + +build_flags = + # Can not be set via Kconfig and the display requires it + -DLV_COLOR_16_SWAP=1 + +lib_deps = + +board = esp32-s3-devkitc-1 +board_build.flash_mode = dio +board_build.psram_type = opi +board_build.extra_flags = -DBOARD_HAS_PSRAM +board_upload.flash_size = 8MB +board_upload.maximum_size = 8388608 +board_build.partitions = partitions.csv +board_build.filesystem = littlefs +board_build.embed_txtfiles = + LICENSE + webserver/provision.html + webserver/logo.png + +debug_tool = esp-builtin +debug_port = ${sysenv.PIO_UPLOAD_PORT, /dev/ttyUSB0} + +upload_speed = 921600 +upload_port = ${sysenv.PIO_UPLOAD_PORT, /dev/ttyUSB0} + +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +monitor_port = ${sysenv.PIO_UPLOAD_PORT, /dev/ttyUSB0} + +[env:debug] +build_type = debug +build_flags = + ${env.build_flags} + -D DEBUG + +lib_deps = + ${env.lib_deps} + +extra_scripts = + ${env.extra_scripts} + +[env:release] +build_type = release +build_flags = + ${env.build_flags} + -D NDEBUG + +lib_deps = + ${env.lib_deps} + +extra_scripts = + ${env.extra_scripts} diff --git a/scripts/astyle.cfg b/scripts/astyle.cfg new file mode 100644 index 0000000..57097a5 --- /dev/null +++ b/scripts/astyle.cfg @@ -0,0 +1,33 @@ +# Configuration file for AStyle +# Config same as in ubxlib +# K&R style +--style=kr +# space padding around operators: a = bar((b - c) * a, d--); +--pad-oper +# space padding between a header (e.g. 'if', 'for', 'while'...) and the following bracket +--pad-header +# Pointer sticks to the name: char *pThing +--align-pointer=name +# Reference sticks to the name: char &thing +--align-reference=name +# Nice long max continuation indent, as long as the line length +--max-continuation-indent=100 +# No minimum conditional indent, align everything wherever it occurs +--min-conditional-indent=0 +# All conditions have braces +--add-braces +# Indent the case in switch statements +--indent-switches +# All tabs become [4] spaces +--convert-tabs +# Max line length +--max-code-length=120 +# When breaking lines, leave the logical operator on the end of the line +--break-after-logical +# Ignore unfound excludes +--ignore-exclude-errors + +# Other config used +--suffix=none +--verbose +--errors-to-stdout \ No newline at end of file diff --git a/scripts/clean.py b/scripts/clean.py new file mode 100644 index 0000000..a9d0768 --- /dev/null +++ b/scripts/clean.py @@ -0,0 +1,6 @@ +import os + +from pathlib import Path + +for path in Path("..").rglob("desktop.ini"): + os.remove(path) diff --git a/scripts/format.py b/scripts/format.py new file mode 100644 index 0000000..3426928 --- /dev/null +++ b/scripts/format.py @@ -0,0 +1,82 @@ +""" +format.py + +Copyright (C) Daniel Kampert, 2026 +Website: www.kampis-elektroecke.de +File info: AStyle formatting script for PlatformIO. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +Import("env") +import os +import subprocess + +from pathlib import Path + +PATTERNS = ["*.c", "*.cpp", "*.h"] + +def run_astyle(source, target, env): + project_dir = env.get("PROJECT_DIR") + astyle_config = os.path.join(project_dir, "scripts/astyle.cfg") + + try: + subprocess.run(["astyle", "--version"], capture_output = True, check = True) + except (FileNotFoundError, subprocess.CalledProcessError): + print("AStyle not found!") + return + + if(not(os.path.exists(astyle_config))): + print("AStyle config not found: {}".format(astyle_config)) + return + + files_to_format = [] + main_dir = Path(project_dir) / "main" + for pattern in PATTERNS: + files_to_format.extend(main_dir.rglob(pattern)) + + if(not(files_to_format)): + print("No C/C++ files found to format") + return + + print("🎨 Formatting {} files with AStyle...".format(len(files_to_format))) + print(" Config: {}".format(astyle_config)) + + cmd = ["astyle", "--options={}".format(astyle_config)] + [str(f) for f in files_to_format] + + try: + result = subprocess.run(cmd, capture_output = True, text = True, check = True) + + if(result.stdout): + print(result.stdout) + + formatted_count = result.stdout.count("Formatted") + unchanged_count = result.stdout.count("Unchanged") + + print("✅ Done!") + print(" Formatted: {}".format(formatted_count)) + print(" Unchanged: {}".format(unchanged_count)) + + except subprocess.CalledProcessError as e: + print("❌ AStyle failed") + + if(e.stdout): + print(e.stdout) + + if(e.stderr): + print(e.stderr) + + raise + +env.AlwaysBuild(env.Alias("format", None, run_astyle)) diff --git a/scripts/patch.py b/scripts/patch.py new file mode 100644 index 0000000..5182b1a --- /dev/null +++ b/scripts/patch.py @@ -0,0 +1,182 @@ +""" +patch.py + +Copyright (C) Daniel Kampert, 2026 +Website: www.kampis-elektroecke.de +File info: PlatformIO pre-build script that applies all *.patch files found in + the project's patches/ directory to the ESP-IDF framework package + used by the current build environment. + + Patches are applied idempotently: a SHA-256 stamp file stored in + .pio/patches.applied tracks which patches have already been applied + so that repeated builds do not re-apply or error on existing patches. + As a secondary check, `git apply --check --reverse` is used to + confirm whether a patch is already present even when the stamp file + has been deleted or the patches/ directory has changed. + + Patches must be unified diff format (git format-patch output is + ideal). File paths inside the patch must be relative to the root + of the ESP-IDF framework directory. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +Import("env") + +import hashlib +import os +import subprocess +import sys + +from pathlib import Path + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _sha256(path: Path) -> str: + """Return the SHA-256 hex digest of a file's content.""" + h = hashlib.sha256() + h.update(path.read_bytes()) + return h.hexdigest() + + +def _git_available() -> bool: + """Return True if git is on PATH.""" + try: + subprocess.run(["git", "--version"], capture_output=True, check=True) + return True + except (FileNotFoundError, subprocess.CalledProcessError): + return False + + +def _is_already_applied(patch_path: Path, idf_dir: Path) -> bool: + """Return True when the patch's reverse applies cleanly (i.e. patch is present).""" + result = subprocess.run( + ["git", "apply", "--check", "--reverse", str(patch_path)], + cwd=str(idf_dir), + capture_output=True, + ) + return result.returncode == 0 + + +def _apply_patch(patch_path: Path, idf_dir: Path) -> None: + """Apply a single patch file to idf_dir; raise on failure.""" + result = subprocess.run( + ["git", "apply", "--whitespace=nowarn", str(patch_path)], + cwd=str(idf_dir), + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(" stdout: {}".format(result.stdout.strip())) + print(" stderr: {}".format(result.stderr.strip())) + raise RuntimeError("git apply failed for '{}'".format(patch_path.name)) + + +def _load_stamp(stamp_path: Path) -> set: + """Load the set of already-applied patch SHA-256 hashes from the stamp file.""" + if stamp_path.exists(): + return set(stamp_path.read_text(encoding="utf-8").splitlines()) + return set() + + +def _save_stamp(stamp_path: Path, applied: set) -> None: + """Persist the set of applied patch hashes to the stamp file.""" + stamp_path.parent.mkdir(parents=True, exist_ok=True) + stamp_path.write_text("\n".join(sorted(applied)), encoding="utf-8") + + +# --------------------------------------------------------------------------- +# Main logic +# --------------------------------------------------------------------------- + +def apply_patches(source, target, env): + project_dir = Path(env.get("PROJECT_DIR")) + patches_dir = project_dir / "patches" + stamp_file = project_dir / ".pio" / "patches.applied" + + # Resolve the ESP-IDF framework package directory + try: + idf_dir = Path(env.PioPlatform().get_package_dir("framework-espidf")) + except Exception as exc: + print("patch.py: Cannot determine ESP-IDF path: {}".format(exc)) + return + + if not idf_dir.exists(): + print("patch.py: ESP-IDF directory not found: {}".format(idf_dir)) + return + + # Collect patches sorted by name so numbered prefixes control order + patch_files = sorted(patches_dir.glob("*.patch")) + + if not patch_files: + # Nothing to do – not an error + return + + if not _git_available(): + print("patch.py: 'git' not found on PATH – cannot apply patches") + sys.exit(1) + + applied_hashes = _load_stamp(stamp_file) + newly_applied = 0 + skipped = 0 + errors = 0 + + print("patch.py: Applying {} patch(es) to ESP-IDF at".format(len(patch_files))) + print(" {}".format(idf_dir)) + + for patch in patch_files: + digest = _sha256(patch) + + # Fast path: stamp file says we already applied this exact file content + if digest in applied_hashes: + print(" [skip] {} (stamp)".format(patch.name)) + skipped += 1 + continue + + # Slower but reliable: ask git whether the patch is already present + if _is_already_applied(patch, idf_dir): + print(" [skip] {} (already applied)".format(patch.name)) + applied_hashes.add(digest) + skipped += 1 + continue + + # Apply the patch + print(" [apply] {}".format(patch.name)) + try: + _apply_patch(patch, idf_dir) + applied_hashes.add(digest) + newly_applied += 1 + except RuntimeError as exc: + print(" [ERROR] {}".format(exc)) + errors += 1 + + # Persist updated stamp regardless of errors so successful patches are not re-run + _save_stamp(stamp_file, applied_hashes) + + if errors > 0: + print("patch.py: {} patch(es) failed – stopping build".format(errors)) + sys.exit(1) + + if newly_applied > 0: + print("patch.py: Applied {}, skipped {}".format(newly_applied, skipped)) + else: + print("patch.py: All patches already applied ({} skipped)".format(skipped)) + + +# Run immediately during the pre-build phase (script is loaded with "pre:" prefix +# in extra_scripts, so module-level code executes before any compilation starts). +apply_patches(None, None, env) + diff --git a/sdkconfig.debug b/sdkconfig.debug new file mode 100644 index 0000000..8944fd7 --- /dev/null +++ b/sdkconfig.debug @@ -0,0 +1,3370 @@ +# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) 5.5.3 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_TXOP_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="5.4.1" +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 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 is not set +CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y +# end of Format + +# +# Settings +# +CONFIG_BOOTLOADER_LOG_MODE_TEXT_EN=y +CONFIG_BOOTLOADER_LOG_MODE_TEXT=y +# end of Settings +# end of Log + +# +# Serial Flash Configurations +# +# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set +CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y +# end of Serial Flash Configurations + +CONFIG_BOOTLOADER_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=y +# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE="8MB" +# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set +CONFIG_ESPTOOLPY_BEFORE_RESET=y +# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +# CONFIG_ESPTOOLPY_AFTER_NORESET is not set +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +# end of Serial flasher config + +# +# Partition Table +# +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# PyroVision +# + +# +# Device +# +CONFIG_DEVICE_MANUFACTURER="PyroVision" +CONFIG_DEVICE_NAME="ThermalCam" +# end of Device + +# +# USB +# +CONFIG_USB_VID=0x1234 +CONFIG_USB_PID=0x5678 +# end of USB + +# +# Settings +# +CONFIG_SETTINGS_NAMESPACE="Calendar" +# end of Settings + +# +# Battery +# +CONFIG_BATTERY_ADC_1=y +# CONFIG_BATTERY_ADC_2 is not set +CONFIG_BATTERY_ADC_CHANNEL=0 +CONFIG_BATTERY_ADC_ATT_0=y +# CONFIG_BATTERY_ADC_ATT_2_5 is not set +# CONFIG_BATTERY_ADC_ATT_6 is not set +# CONFIG_BATTERY_ADC_ATT_12 is not set +CONFIG_BATTERY_ADC_MIN_MV=3000 +CONFIG_BATTERY_ADC_MAX_MV=4200 +CONFIG_BATTERY_ADC_R1=10000 +CONFIG_BATTERY_ADC_R2=3300 +CONFIG_BATTERY_ADC_SAMPLES=16 +CONFIG_BATTERY_ADC_SAMPLE_DELAY_MS=10 +CONFIG_BATTERY_ADC_EMA_ALPHA=20 +CONFIG_BATTERY_ADC_OUTLIER_THRESHOLD=200 +# end of Battery + +# +# Network +# + +# +# Websocket +# +CONFIG_NETWORK_WEBSOCKET_CLIENTS=4 +# end of Websocket + +# +# Task +# +CONFIG_NETWORK_TASK_STACKSIZE=8192 +CONFIG_NETWORK_TASK_PRIO=16 +CONFIG_NETWORK_TASK_CORE=1 +# end of Task + +# +# VISA +# +CONFIG_NETWORK_VISA_ERROR_QUEUE_LLENGTH=10 +CONFIG_NETWORK_VISA_MAX_CLIENTS=2 +# end of VISA +# end of Network + +# +# Image Encoding +# +CONFIG_IMAGE_FORMAT_JPEG=y +# CONFIG_IMAGE_FORMAT_PNG is not set +# CONFIG_IMAGE_FORMAT_BITMAP is not set +# CONFIG_IMAGE_FORMAT_RAW is not set +CONFIG_IMAGE_JPEG_QUALITY=80 +# end of Image Encoding + +# +# Lepton +# + +# +# Task +# +CONFIG_LEPTON_TASK_STACKSIZE=8192 +CONFIG_LEPTON_TASK_PRIO=16 +CONFIG_LEPTON_TASK_CORE=1 +# end of Task +# end of Lepton + +# +# Camera +# + +# +# Task +# +CONFIG_CAMERA_TASK_STACKSIZE=8192 +CONFIG_CAMERA_TASK_PRIO=12 +CONFIG_CAMERA_TASK_CORE=1 +# end of Task +# end of Camera + +# +# Devices +# + +# +# I2C +# +# CONFIG_DEVICES_I2C_I2C0_HOST is not set +CONFIG_DEVICES_I2C_I2C1_HOST=y +CONFIG_DEVICES_I2C_SCL=21 +CONFIG_DEVICES_I2C_SDA=47 +# end of I2C + +# +# SPI +# +CONFIG_SPI_SCLK=46 +CONFIG_SPI_MOSI=48 +CONFIG_SPI_MISO=9 +CONFIG_SPI_TRANSFER_SIZE=4096 + +# +# Touch +# +CONFIG_TOUCH_I2C0_HOST=y +# CONFIG_TOUCH_I2C1_HOST is not set +CONFIG_TOUCH_CLOCK=400000 +CONFIG_TOUCH_SDA=39 +CONFIG_TOUCH_SCL=40 +CONFIG_TOUCH_IRQ=38 +CONFIG_TOUCH_RST=-1 +# end of Touch + +# +# SD-Card +# +# CONFIG_SD_CARD_SPI2_HOST is not set +CONFIG_SD_CARD_SPI3_HOST=y +CONFIG_SD_CARD_CLOCK=2000000 +CONFIG_SD_CARD_FORMAT_CARD=y +CONFIG_SD_CARD_PIN_CS=4 +CONFIG_SD_CARD_PIN_CD=-1 +# end of SD-Card + +# +# LCD +# +# CONFIG_LCD_SPI2_HOST is not set +CONFIG_LCD_SPI3_HOST=y +CONFIG_LCD_CLOCK=40000000 +CONFIG_LCD_CS=18 +CONFIG_LCD_DC=3 +CONFIG_LCD_RST=8 +CONFIG_LCD_BL=-1 +# end of LCD +# end of SPI + +# +# Task +# +CONFIG_DEVICES_TASK_STACKSIZE=4096 +CONFIG_DEVICES_TASK_PRIO=16 +CONFIG_DEVICES_TASK_CORE=1 +# end of Task +# end of Devices + +# +# GUI +# +CONFIG_GUI_WIDTH=320 +CONFIG_GUI_HEIGHT=240 +# CONFIG_GUI_TOUCH_DEBUG is not set +CONFIG_GUI_LVGL_TICK_PERIOD_MS=2 + +# +# Task +# +CONFIG_GUI_TASK_STACKSIZE=16384 +CONFIG_GUI_TASK_PRIO=2 +CONFIG_GUI_TASK_CORE=0 +# end of Task +# end of GUI +# end of PyroVision + +# +# TinyUSB Stack +# +CONFIG_TINYUSB_DEBUG_LEVEL=0 + +# +# TinyUSB DCD +# +# CONFIG_TINYUSB_MODE_SLAVE is not set +CONFIG_TINYUSB_MODE_DMA=y +# end of TinyUSB DCD + +# +# TinyUSB callbacks +# +# CONFIG_TINYUSB_SUSPEND_CALLBACK is not set +# CONFIG_TINYUSB_RESUME_CALLBACK is not set +# end of TinyUSB callbacks + +# +# Descriptor configuration +# + +# +# You can provide your custom descriptors via tinyusb_driver_install() +# +CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=y +CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=y +CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100 +CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Espressif Systems" +CONFIG_TINYUSB_DESC_PRODUCT_STRING="Espressif Device" +CONFIG_TINYUSB_DESC_SERIAL_STRING="123456" +CONFIG_TINYUSB_DESC_CDC_STRING="Espressif CDC Device" +CONFIG_TINYUSB_DESC_MSC_STRING="Espressif MSC Device" +# end of Descriptor configuration + +# +# Mass Storage Class (MSC) +# +CONFIG_TINYUSB_MSC_ENABLED=y +CONFIG_TINYUSB_MSC_BUFSIZE=4096 +CONFIG_TINYUSB_MSC_MOUNT_PATH="/data" +# end of Mass Storage Class (MSC) + +# +# Communication Device Class (CDC) +# +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_COUNT=1 +CONFIG_TINYUSB_CDC_RX_BUFSIZE=512 +CONFIG_TINYUSB_CDC_TX_BUFSIZE=512 +CONFIG_TINYUSB_CDC_EP_BUFSIZE=512 +# end of Communication Device Class (CDC) + +# +# Musical Instrument Digital Interface (MIDI) +# +CONFIG_TINYUSB_MIDI_COUNT=0 +# end of Musical Instrument Digital Interface (MIDI) + +# +# Human Interface Device Class (HID) +# +CONFIG_TINYUSB_HID_COUNT=0 +# end of Human Interface Device Class (HID) + +# +# Device Firmware Upgrade (DFU) +# +# CONFIG_TINYUSB_DFU_MODE_DFU is not set +# CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME is not set +CONFIG_TINYUSB_DFU_MODE_NONE=y +# end of Device Firmware Upgrade (DFU) + +# +# Bluetooth Host Class (BTH) +# +# CONFIG_TINYUSB_BTH_ENABLED is not set +# end of Bluetooth Host Class (BTH) + +# +# USB Video Class (UVC) +# +CONFIG_TINYUSB_UVC_ENABLED=y +CONFIG_TINYUSB_DESC_UVC_STRING="PyroVision Thermal Camera" +# CONFIG_TINYUSB_UVC_SUPPORT_TWO_CAM is not set +CONFIG_TINYUSB_UVC_CAM1_FRAMERATE=9 +CONFIG_TINYUSB_UVC_CAM1_FRAMESIZE_WIDTH=160 +CONFIG_TINYUSB_UVC_CAM1_FRAMESIZE_HEIGHT=120 +CONFIG_TINYUSB_UVC_FORMAT_MJPEG_CAM1=y +# CONFIG_TINYUSB_UVC_FORMAT_H264_CAM1 is not set +# CONFIG_TINYUSB_UVC_CAM1_MULTI_FRAMESIZE is not set +# CONFIG_TINYUSB_UVC_CAM1_BULK_MODE is not set +CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_1=640 +CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_1=480 +CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_1=15 +CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_2=320 +CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_2=240 +CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_2=15 +CONFIG_TINYUSB_UVC_MULTI_FRAME_WIDTH_3=160 +CONFIG_TINYUSB_UVC_MULTI_FRAME_HEIGHT_3=120 +CONFIG_TINYUSB_UVC_MULTI_FRAME_FPS_3=15 +CONFIG_TINYUSB_UVC_CAM1_TASK_PRIORITY=5 +CONFIG_TINYUSB_UVC_CAM1_TASK_CORE=1 +CONFIG_TINYUSB_UVC_TINYUSB_TASK_PRIORITY=5 +CONFIG_TINYUSB_UVC_TINYUSB_TASK_CORE=1 +# end of USB Video Class (UVC) + +# +# Network driver (ECM/NCM/RNDIS) +# +# CONFIG_TINYUSB_NET_MODE_ECM_RNDIS is not set +# CONFIG_TINYUSB_NET_MODE_NCM is not set +CONFIG_TINYUSB_NET_MODE_NONE=y +# end of Network driver (ECM/NCM/RNDIS) + +# +# Vendor Specific Interface +# +CONFIG_TINYUSB_VENDOR_COUNT=0 +# end of Vendor Specific Interface +# end of TinyUSB Stack + +# +# Bootloader config (Custom) +# +# CONFIG_BOOTLOADER_COMPRESSED_ENABLED is not set +# CONFIG_BOOTLOADER_CUSTOM_DEBUG_ON is not set +# CONFIG_SKIP_VALIDATE_CUSTOM_COMPRESSED_HEADER is not set +# CONFIG_SKIP_VALIDATE_CUSTOM_COMPRESSED_DATA is not set +# end of Bootloader config (Custom) + +# +# Compiler options +# +CONFIG_COMPILER_OPTIMIZATION_DEBUG=y +# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set +# CONFIG_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_COMPILER_OPTIMIZATION_NONE is not set +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set +CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE=y +CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=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 is not set +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set +# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set +# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set +CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y +# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set +# CONFIG_COMPILER_DUMP_RTL_FILES is not set +CONFIG_COMPILER_RT_LIB_GCCLIB=y +CONFIG_COMPILER_RT_LIB_NAME="gcc" +CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y +# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set +# CONFIG_COMPILER_STATIC_ANALYZER is not set +# end of Compiler options + +# +# Component config +# + +# +# Application Level Tracing +# +# CONFIG_APPTRACE_DEST_JTAG is not set +CONFIG_APPTRACE_DEST_NONE=y +# CONFIG_APPTRACE_DEST_UART1 is not set +# CONFIG_APPTRACE_DEST_UART2 is not set +# CONFIG_APPTRACE_DEST_USB_CDC is not set +CONFIG_APPTRACE_DEST_UART_NONE=y +CONFIG_APPTRACE_UART_TASK_PRIO=1 +CONFIG_APPTRACE_LOCK_ENABLE=y +# end of Application Level Tracing + +# +# Bluetooth +# +# CONFIG_BT_ENABLED is not set + +# +# Common Options +# + +# +# BLE Log +# +# CONFIG_BLE_LOG_ENABLED is not set +# end of BLE Log + +# CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set +# CONFIG_BT_BLE_LOG_UHCI_OUT_ENABLED is not set +# CONFIG_BT_LE_USED_MEM_STATISTICS_ENABLED is not set +# end of Common Options +# end of Bluetooth + +# +# Console Library +# +# CONFIG_CONSOLE_SORTED_HELP is not set +# end of Console Library + +# +# Driver Configurations +# + +# +# Legacy TWAI Driver Configurations +# +# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set +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_USE_DS_PERIPHERAL=y +# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set +# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set +# CONFIG_ESP_TLS_PSK_VERIFICATION is not set +# CONFIG_ESP_TLS_INSECURE is not set +CONFIG_ESP_TLS_DYN_BUF_STRATEGY_SUPPORTED=y +# end of ESP-TLS + +# +# ADC and ADC Calibration +# +# CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM is not set +# CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is not set +# CONFIG_ADC_CONTINUOUS_FORCE_USE_ADC2_ON_C3_S3 is not set +# CONFIG_ADC_ENABLE_DEBUG_LOG is not set +# end of ADC and ADC Calibration + +# +# 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 is not set +CONFIG_ESP_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y +# end of Common ESP-related + +# +# ESP-Driver:Camera Controller Configurations +# +# CONFIG_CAM_CTLR_DVP_CAM_ISR_CACHE_SAFE is not set +# end of ESP-Driver:Camera Controller Configurations + +# +# ESP-Driver:GPIO Configurations +# +# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:GPIO Configurations + +# +# ESP-Driver:GPTimer Configurations +# +CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y +# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set +# CONFIG_GPTIMER_ISR_CACHE_SAFE is not set +CONFIG_GPTIMER_OBJ_CACHE_SAFE=y +# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:GPTimer Configurations + +# +# ESP-Driver:I2C Configurations +# +# CONFIG_I2C_ISR_IRAM_SAFE is not set +# CONFIG_I2C_ENABLE_DEBUG_LOG is not set +# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set +CONFIG_I2C_MASTER_ISR_HANDLER_IN_IRAM=y +# end of ESP-Driver:I2C Configurations + +# +# ESP-Driver:I2S Configurations +# +# CONFIG_I2S_ISR_IRAM_SAFE is not set +# CONFIG_I2S_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:I2S Configurations + +# +# ESP-Driver: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_ISR_IN_IRAM=y +# CONFIG_SPI_SLAVE_IN_IRAM is not set +# CONFIG_SPI_SLAVE_ISR_IN_IRAM is not set +# end of ESP-Driver:SPI Configurations + +# +# ESP-Driver:Touch Sensor Configurations +# +# CONFIG_TOUCH_CTRL_FUNC_IN_IRAM is not set +# CONFIG_TOUCH_ISR_IRAM_SAFE is not set +# CONFIG_TOUCH_ENABLE_DEBUG_LOG is not set +# CONFIG_TOUCH_SKIP_FSM_CHECK is not set +# end of ESP-Driver:Touch Sensor Configurations + +# +# ESP-Driver:Temperature Sensor Configurations +# +# CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set +# 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 + +# +# Ethernet +# +# CONFIG_ETH_USE_SPI_ETHERNET is not set +# CONFIG_ETH_USE_OPENETH is not set +# end of Ethernet + +# +# Event Loop Library +# +# CONFIG_ESP_EVENT_LOOP_PROFILING is not set +CONFIG_ESP_EVENT_POST_FROM_ISR=y +CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y +# end of Event Loop Library + +# +# GDB Stub +# +CONFIG_ESP_GDBSTUB_ENABLED=y +# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set +CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y +CONFIG_ESP_GDBSTUB_MAX_TASKS=32 +# end of GDB Stub + +# +# ESP HID +# +CONFIG_ESPHID_TASK_SIZE_BT=2048 +CONFIG_ESPHID_TASK_SIZE_BLE=4096 +# end of ESP HID + +# +# ESP HTTP client +# +CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y +# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set +CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000 +# end of ESP HTTP client + +# +# HTTP Server +# +CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_URI_LEN=512 +CONFIG_HTTPD_ERR_RESP_NO_DELAY=y +CONFIG_HTTPD_PURGE_BUF_LEN=32 +# CONFIG_HTTPD_LOG_PURGE_DATA is not set +CONFIG_HTTPD_WS_SUPPORT=y +# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set +CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000 +# CONFIG_HTTPD_WS_PRE_HANDSHAKE_CB_SUPPORT is not set +# end of HTTP Server + +# +# ESP HTTPS OTA +# +# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set +# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set +CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000 +# end of ESP HTTPS OTA + +# +# ESP HTTPS server +# +# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set +CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000 +# CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK is not set +# end of ESP HTTPS server + +# +# Hardware Settings +# +CONFIG_ESP_HW_SUPPORT_FUNC_IN_IRAM=y + +# +# 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 +CONFIG_RTC_CLK_FUNC_IN_IRAM=y +CONFIG_RTC_TIME_FUNC_IN_IRAM=y +# 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=y +CONFIG_SPIRAM_RODATA=y +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_USE_MEMMAP is not set +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_MEMTEST=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=163840 +CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y +CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY=y +# 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 is not set +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240 + +# +# 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_GDBSTUB is not set +CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 +CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y +CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y + +# +# 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=8192 +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=y +# CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG is not set +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=5 +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_TX_BUFFER_TYPE=0 +CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=16 +CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM=32 +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_AMSDU_TX_ENABLED is not set +CONFIG_ESP_WIFI_NVS_ENABLED=y +CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y +# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1 is not set +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_WAPI_PSK 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 is not set +# end of Wi-Fi + +# +# Core dump +# +CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y +# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set +# CONFIG_ESP_COREDUMP_ENABLE_TO_NONE is not set +# CONFIG_ESP_COREDUMP_DATA_FORMAT_BIN is not set +CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y +CONFIG_ESP_COREDUMP_CHECKSUM_CRC32=y +# CONFIG_ESP_COREDUMP_CHECKSUM_SHA256 is not set +# CONFIG_ESP_COREDUMP_CAPTURE_DRAM is not set +CONFIG_ESP_COREDUMP_CHECK_BOOT=y +CONFIG_ESP_COREDUMP_ENABLE=y +CONFIG_ESP_COREDUMP_LOGS=y +CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=64 +# CONFIG_ESP_COREDUMP_FLASH_NO_OVERWRITE is not set +CONFIG_ESP_COREDUMP_USE_STACK_SIZE=y +CONFIG_ESP_COREDUMP_STACK_SIZE=1792 +# end of Core dump + +# +# FAT Filesystem support +# +CONFIG_FATFS_VOLUME_COUNT=2 +CONFIG_FATFS_LFN_NONE=y +# CONFIG_FATFS_LFN_HEAP is not set +# CONFIG_FATFS_LFN_STACK is not set +# CONFIG_FATFS_SECTOR_512 is not set +CONFIG_FATFS_SECTOR_4096=y +# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set +CONFIG_FATFS_CODEPAGE_437=y +# CONFIG_FATFS_CODEPAGE_720 is not set +# CONFIG_FATFS_CODEPAGE_737 is not set +# CONFIG_FATFS_CODEPAGE_771 is not set +# CONFIG_FATFS_CODEPAGE_775 is not set +# CONFIG_FATFS_CODEPAGE_850 is not set +# CONFIG_FATFS_CODEPAGE_852 is not set +# CONFIG_FATFS_CODEPAGE_855 is not set +# CONFIG_FATFS_CODEPAGE_857 is not set +# CONFIG_FATFS_CODEPAGE_860 is not set +# CONFIG_FATFS_CODEPAGE_861 is not set +# CONFIG_FATFS_CODEPAGE_862 is not set +# CONFIG_FATFS_CODEPAGE_863 is not set +# CONFIG_FATFS_CODEPAGE_864 is not set +# CONFIG_FATFS_CODEPAGE_865 is not set +# CONFIG_FATFS_CODEPAGE_866 is not set +# CONFIG_FATFS_CODEPAGE_869 is not set +# CONFIG_FATFS_CODEPAGE_932 is not set +# CONFIG_FATFS_CODEPAGE_936 is not set +# CONFIG_FATFS_CODEPAGE_949 is not set +# CONFIG_FATFS_CODEPAGE_950 is not set +CONFIG_FATFS_CODEPAGE=437 +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y +CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y +# CONFIG_FATFS_USE_FASTSEEK is not set +CONFIG_FATFS_USE_STRFUNC_NONE=y +# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set +# CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV is not set +CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0 +# CONFIG_FATFS_IMMEDIATE_FSYNC is not set +# CONFIG_FATFS_USE_LABEL is not set +CONFIG_FATFS_LINK_LOCK=y +# CONFIG_FATFS_USE_DYN_BUFFERS is not set + +# +# File system free space calculation behavior +# +CONFIG_FATFS_DONT_TRUST_FREE_CLUSTER_CNT=0 +CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0 +# end of File system free space calculation behavior +# end of FAT Filesystem support + +# +# FreeRTOS +# + +# +# Kernel +# +# CONFIG_FREERTOS_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_TASK_FUNCTION_WRAPPER=y +# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set +CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y +# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set +# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set +CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y +CONFIG_FREERTOS_ISR_STACKSIZE=2096 +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=y +# 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 is not set +CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y +# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set +# end of Format + +# +# Settings +# +CONFIG_LOG_MODE_TEXT_EN=y +CONFIG_LOG_MODE_TEXT=y +# end of Settings + +CONFIG_LOG_IN_IRAM=y +# end of Log + +# +# LWIP +# +CONFIG_LWIP_ENABLE=y +CONFIG_LWIP_LOCAL_HOSTNAME="PyroVision" +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_WND_SCALE is not set +CONFIG_LWIP_TCP_RTO_TIME=1500 +# end of TCP + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +# end of UDP + +# +# Checksums +# +# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set +# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set +CONFIG_LWIP_CHECKSUM_CHECK_ICMP=y +# end of Checksums + +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF +CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 +CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 +CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5 +CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3 +CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10 +# CONFIG_LWIP_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 + +# +# ESP-MQTT Configurations +# +CONFIG_MQTT_PROTOCOL_311=y +# CONFIG_MQTT_PROTOCOL_5 is not set +CONFIG_MQTT_TRANSPORT_SSL=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y +# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set +# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set +# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set +# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set +# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set +# CONFIG_MQTT_CUSTOM_OUTBOX is not set +# end of ESP-MQTT Configurations + +# +# LibC +# +CONFIG_LIBC_NEWLIB=y +CONFIG_LIBC_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 + +# +# OpenThread +# +# CONFIG_OPENTHREAD_ENABLED is not set + +# +# OpenThread Spinel +# +# CONFIG_OPENTHREAD_SPINEL_ONLY is not set +# end of OpenThread Spinel + +# CONFIG_OPENTHREAD_DEBUG is not set +# end of OpenThread + +# +# Protocomm +# +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y +# end of Protocomm + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_PTHREAD_STACK_MIN=768 +CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y +# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set +# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set +CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" +# end of PThreads + +# +# MMU Config +# +CONFIG_MMU_PAGE_SIZE_64KB=y +CONFIG_MMU_PAGE_MODE="64KB" +CONFIG_MMU_PAGE_SIZE=0x10000 +# end of MMU Config + +# +# Main Flash configuration +# + +# +# SPI Flash behavior when brownout +# +CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y +CONFIG_SPI_FLASH_BROWNOUT_RESET=y +# end of SPI Flash behavior when brownout + +# +# Optional and Experimental Features (READ DOCS FIRST) +# + +# +# Features here require specific hardware (READ DOCS FIRST!) +# +# CONFIG_SPI_FLASH_HPM_ENA is not set +CONFIG_SPI_FLASH_HPM_AUTO=y +# CONFIG_SPI_FLASH_HPM_DIS is not set +CONFIG_SPI_FLASH_HPM_ON=y +CONFIG_SPI_FLASH_HPM_DC_AUTO=y +# CONFIG_SPI_FLASH_HPM_DC_DISABLE is not set +# CONFIG_SPI_FLASH_AUTO_SUSPEND is not set +CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 +# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set +# CONFIG_SPI_FLASH_FORCE_ENABLE_C6_H2_SUSPEND is not set +CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM=y +# end of Optional and Experimental Features (READ DOCS FIRST) +# end of Main Flash configuration + +# +# SPI Flash driver +# +# CONFIG_SPI_FLASH_VERIFY_WRITE is not set +# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +# CONFIG_SPI_FLASH_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 + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +# CONFIG_SPIFFS_CACHE_STATS is not set +# end of SPIFFS Cache Configuration + +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +# CONFIG_SPIFFS_GC_STATS is not set +CONFIG_SPIFFS_PAGE_SIZE=256 +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y +CONFIG_SPIFFS_META_LENGTH=4 +CONFIG_SPIFFS_USE_MTIME=y + +# +# Debug Configuration +# +# CONFIG_SPIFFS_DBG is not set +# CONFIG_SPIFFS_API_DBG is not set +# CONFIG_SPIFFS_GC_DBG is not set +# CONFIG_SPIFFS_CACHE_DBG is not set +# CONFIG_SPIFFS_CHECK_DBG is not set +# CONFIG_SPIFFS_TEST_VISUALISATION is not set +# end of Debug Configuration +# end of SPIFFS Configuration + +# +# TCP Transport +# + +# +# Websocket +# +CONFIG_WS_TRANSPORT=y +CONFIG_WS_BUFFER_SIZE=1024 +# CONFIG_WS_DYNAMIC_BUFFER is not set +# end of Websocket +# end of TCP Transport + +# +# Ultra Low Power (ULP) Co-processor +# +CONFIG_ULP_COPROC_ENABLED=y +# CONFIG_ULP_COPROC_TYPE_FSM is not set +CONFIG_ULP_COPROC_TYPE_RISCV=y +CONFIG_ULP_COPROC_RESERVE_MEM=4096 + +# +# ULP RISC-V Settings +# +# CONFIG_ULP_RISCV_INTERRUPT_ENABLE is not set +CONFIG_ULP_RISCV_UART_BAUDRATE=9600 +CONFIG_ULP_RISCV_I2C_RW_TIMEOUT=500 +# end of ULP RISC-V Settings + +# +# ULP Debugging Options +# +# end of ULP Debugging Options +# end of Ultra Low Power (ULP) Co-processor + +# +# Unity unit testing library +# +CONFIG_UNITY_ENABLE_FLOAT=y +CONFIG_UNITY_ENABLE_DOUBLE=y +# CONFIG_UNITY_ENABLE_64BIT is not set +# CONFIG_UNITY_ENABLE_COLOR is not set +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y +# CONFIG_UNITY_ENABLE_FIXTURE is not set +# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set +# CONFIG_UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE is not set +# end of Unity unit testing library + +# +# USB-OTG +# +CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=256 +CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED=y +# CONFIG_USB_HOST_HW_BUFFER_BIAS_IN is not set +# CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT is not set + +# +# Hub Driver Configuration +# + +# +# Root Port configuration +# +CONFIG_USB_HOST_DEBOUNCE_DELAY_MS=250 +CONFIG_USB_HOST_RESET_HOLD_MS=30 +CONFIG_USB_HOST_RESET_RECOVERY_MS=30 +CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS=10 +# end of Root Port configuration + +# CONFIG_USB_HOST_HUBS_SUPPORTED is not set +# end of Hub Driver Configuration + +# CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK is not set +CONFIG_USB_OTG_SUPPORTED=y +# end of USB-OTG + +# +# Virtual file system +# +CONFIG_VFS_SUPPORT_IO=y +CONFIG_VFS_SUPPORT_DIR=y +CONFIG_VFS_SUPPORT_SELECT=y +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +# CONFIG_VFS_SELECT_IN_RAM 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 + +# +# Wear Levelling +# +# CONFIG_WL_SECTOR_SIZE_512 is not set +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 +# end of Wear Levelling + +# +# Wi-Fi Provisioning Manager +# +CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 +CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 +CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y +# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set +# end of Wi-Fi Provisioning Manager + +# +# ESP32-Lepton +# + +# +# GPIO +# + +# +# V-Sync +# +# CONFIG_LEPTON_GPIO_USE_VSYNC is not set +# end of V-Sync + +# +# Reset +# +CONFIG_LEPTON_GPIO_RESET_PIN=-1 +# end of Reset + +# +# Power-Down +# +CONFIG_LEPTON_GPIO_PWR_PIN=-1 +# end of Power-Down +# end of GPIO + +# +# VoSPI +# +CONFIG_LEPTON_VOSPI_SPI2_HOST=y +# CONFIG_LEPTON_VOSPI_SPI3_HOST is not set +CONFIG_LEPTON_VOSPI_MISO=12 +CONFIG_LEPTON_VOSPI_SCK=13 +CONFIG_LEPTON_VOSPI_CS=14 +CONFIG_LEPTON_VOSPI_FRAME_BUFFERS=2 +# end of VoSPI + +# +# Capture Task +# +CONFIG_LEPTON_CAPTURE_TASK_CORE_AFFINITY=y +CONFIG_LEPTON_CAPTURE_TASK_CORE=0 +CONFIG_LEPTON_CAPTURE_TASK_STACK=6144 +CONFIG_LEPTON_CAPTURE_TASK_PRIORITY=12 +# end of Capture Task + +# +# Misc +# +CONFIG_LEPTON_MISC_ERROR_BASE=0xB000 +# end of Misc +# end of ESP32-Lepton + +# +# CMake Utilities +# +# CONFIG_CU_RELINKER_ENABLE is not set +# CONFIG_CU_DIAGNOSTICS_COLOR_NEVER is not set +CONFIG_CU_DIAGNOSTICS_COLOR_ALWAYS=y +# CONFIG_CU_DIAGNOSTICS_COLOR_AUTO is not set +# CONFIG_CU_GCC_LTO_ENABLE is not set +# CONFIG_CU_GCC_STRING_1BYTE_ALIGN is not set +# end of CMake Utilities + +# +# DSP Library +# +CONFIG_DSP_OPTIMIZATIONS_SUPPORTED=y +# CONFIG_DSP_ANSI is not set +CONFIG_DSP_OPTIMIZED=y +CONFIG_DSP_OPTIMIZATION=1 +# CONFIG_DSP_MAX_FFT_SIZE_512 is not set +# CONFIG_DSP_MAX_FFT_SIZE_1024 is not set +# CONFIG_DSP_MAX_FFT_SIZE_2048 is not set +CONFIG_DSP_MAX_FFT_SIZE_4096=y +# CONFIG_DSP_MAX_FFT_SIZE_8192 is not set +# CONFIG_DSP_MAX_FFT_SIZE_16384 is not set +# CONFIG_DSP_MAX_FFT_SIZE_32768 is not set +CONFIG_DSP_MAX_FFT_SIZE=4096 +# end of DSP Library + +# +# ESP LCD TOUCH +# +CONFIG_ESP_LCD_TOUCH_MAX_POINTS=5 +CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS=1 +# end of ESP LCD TOUCH + +# +# Bus Options +# + +# +# I2C Bus Options +# +# CONFIG_I2C_BUS_DYNAMIC_CONFIG is not set +CONFIG_I2C_MS_TO_WAIT=200 +# end of I2C Bus Options +# end of Bus Options + +# +# LittleFS +# +# CONFIG_LITTLEFS_SDMMC_SUPPORT is not set +CONFIG_LITTLEFS_MAX_PARTITIONS=3 +CONFIG_LITTLEFS_PAGE_SIZE=256 +CONFIG_LITTLEFS_OBJ_NAME_LEN=64 +CONFIG_LITTLEFS_READ_SIZE=128 +CONFIG_LITTLEFS_WRITE_SIZE=128 +CONFIG_LITTLEFS_LOOKAHEAD_SIZE=128 +CONFIG_LITTLEFS_CACHE_SIZE=512 +CONFIG_LITTLEFS_BLOCK_CYCLES=512 +CONFIG_LITTLEFS_USE_MTIME=y +# CONFIG_LITTLEFS_USE_ONLY_HASH is not set +# CONFIG_LITTLEFS_HUMAN_READABLE is not set +CONFIG_LITTLEFS_MTIME_USE_SECONDS=y +# CONFIG_LITTLEFS_MTIME_USE_NONCE is not set +# CONFIG_LITTLEFS_SPIFFS_COMPAT is not set +# CONFIG_LITTLEFS_FLUSH_FILE_EVERY_WRITE is not set +# CONFIG_LITTLEFS_FCNTL_GET_PATH is not set +# CONFIG_LITTLEFS_MULTIVERSION is not set +# CONFIG_LITTLEFS_MALLOC_STRATEGY_DISABLE is not set +CONFIG_LITTLEFS_MALLOC_STRATEGY_DEFAULT=y +# CONFIG_LITTLEFS_MALLOC_STRATEGY_INTERNAL is not set +# CONFIG_LITTLEFS_MALLOC_STRATEGY_SPIRAM is not set +CONFIG_LITTLEFS_ASSERTS=y +# CONFIG_LITTLEFS_MMAP_PARTITION is not set +# CONFIG_LITTLEFS_WDT_RESET is not set +# end of LittleFS + +# +# LVGL configuration +# +CONFIG_LV_CONF_SKIP=y +# CONFIG_LV_CONF_MINIMAL is not set + +# +# Color Settings +# +# CONFIG_LV_COLOR_DEPTH_32 is not set +# CONFIG_LV_COLOR_DEPTH_24 is not set +CONFIG_LV_COLOR_DEPTH_16=y +# CONFIG_LV_COLOR_DEPTH_8 is not set +# CONFIG_LV_COLOR_DEPTH_1 is not set +CONFIG_LV_COLOR_DEPTH=16 +# end of Color Settings + +# +# Memory Settings +# +# CONFIG_LV_USE_BUILTIN_MALLOC is not set +CONFIG_LV_USE_CLIB_MALLOC=y +# CONFIG_LV_USE_MICROPYTHON_MALLOC is not set +# CONFIG_LV_USE_RTTHREAD_MALLOC is not set +# CONFIG_LV_USE_CUSTOM_MALLOC is not set +# CONFIG_LV_USE_BUILTIN_STRING is not set +CONFIG_LV_USE_CLIB_STRING=y +# CONFIG_LV_USE_CUSTOM_STRING is not set +# CONFIG_LV_USE_BUILTIN_SPRINTF is not set +CONFIG_LV_USE_CLIB_SPRINTF=y +# CONFIG_LV_USE_CUSTOM_SPRINTF is not set +# end of Memory Settings + +# +# HAL Settings +# +CONFIG_LV_DEF_REFR_PERIOD=33 +CONFIG_LV_DPI_DEF=130 +# end of HAL Settings + +# +# Operating System (OS) +# +CONFIG_LV_OS_NONE=y +# CONFIG_LV_OS_PTHREAD is not set +# CONFIG_LV_OS_FREERTOS is not set +# CONFIG_LV_OS_CMSIS_RTOS2 is not set +# CONFIG_LV_OS_RTTHREAD is not set +# CONFIG_LV_OS_WINDOWS is not set +# CONFIG_LV_OS_MQX is not set +# CONFIG_LV_OS_SDL2 is not set +# CONFIG_LV_OS_CUSTOM is not set +# 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_USE_DRAW_SW=y +CONFIG_LV_DRAW_SW_SUPPORT_RGB565=y +CONFIG_LV_DRAW_SW_SUPPORT_RGB565_SWAPPED=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_RISCV_V 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 is not set +# CONFIG_LV_USE_ASSERT_MEM_INTEGRITY is not set +# CONFIG_LV_USE_ASSERT_OBJ is not set +CONFIG_LV_ASSERT_HANDLER_INCLUDE="assert.h" +# end of Asserts + +# +# Debug +# +# CONFIG_LV_USE_REFR_DEBUG is not set +# CONFIG_LV_USE_LAYER_DEBUG is not set +# CONFIG_LV_USE_PARALLEL_DRAW_DEBUG is not set +# end of Debug + +# +# Others +# +# CONFIG_LV_ENABLE_GLOBAL_CUSTOM is not set +CONFIG_LV_CACHE_DEF_SIZE=0 +CONFIG_LV_IMAGE_HEADER_CACHE_DEF_CNT=0 +CONFIG_LV_GRADIENT_MAX_STOPS=2 +CONFIG_LV_COLOR_MIX_ROUND_OFS=128 +# CONFIG_LV_OBJ_STYLE_CACHE is not set +# CONFIG_LV_USE_OBJ_ID is not set +# CONFIG_LV_USE_OBJ_NAME is not set +# CONFIG_LV_USE_OBJ_PROPERTY is not set +# CONFIG_LV_USE_EXT_DATA 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=y +# 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=y +CONFIG_LV_FONT_MONTSERRAT_10=y +CONFIG_LV_FONT_MONTSERRAT_12=y +CONFIG_LV_FONT_MONTSERRAT_14=y +# CONFIG_LV_FONT_MONTSERRAT_16 is not set +# CONFIG_LV_FONT_MONTSERRAT_18 is not set +# CONFIG_LV_FONT_MONTSERRAT_20 is not set +# CONFIG_LV_FONT_MONTSERRAT_22 is not set +# CONFIG_LV_FONT_MONTSERRAT_24 is not set +# CONFIG_LV_FONT_MONTSERRAT_26 is not set +# CONFIG_LV_FONT_MONTSERRAT_28 is not set +# CONFIG_LV_FONT_MONTSERRAT_30 is not set +CONFIG_LV_FONT_MONTSERRAT_32=y +# CONFIG_LV_FONT_MONTSERRAT_34 is not set +# CONFIG_LV_FONT_MONTSERRAT_36 is not set +# CONFIG_LV_FONT_MONTSERRAT_38 is not set +# CONFIG_LV_FONT_MONTSERRAT_40 is not set +# CONFIG_LV_FONT_MONTSERRAT_42 is not set +# CONFIG_LV_FONT_MONTSERRAT_44 is not set +# CONFIG_LV_FONT_MONTSERRAT_46 is not set +CONFIG_LV_FONT_MONTSERRAT_48=y +# 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 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_LIBWEBP 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_NANOVG 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_COLOR_FILTER is not set +CONFIG_LVGL_VERSION_MAJOR=9 +CONFIG_LVGL_VERSION_MINOR=5 +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_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 is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set +CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y +# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL=3 +# CONFIG_FLASH_ENCRYPTION_ENABLED is not set +# CONFIG_FLASHMODE_QIO is not set +# CONFIG_FLASHMODE_QOUT is not set +CONFIG_FLASHMODE_DIO=y +# CONFIG_FLASHMODE_DOUT is not set +CONFIG_MONITOR_BAUD=115200 +CONFIG_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y +# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set +CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y +# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set +CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_CXX_EXCEPTIONS is not set +CONFIG_STACK_CHECK_NONE=y +# CONFIG_STACK_CHECK_NORM is not set +# CONFIG_STACK_CHECK_STRONG is not set +# CONFIG_STACK_CHECK_ALL is not set +# CONFIG_WARN_WRITE_STRINGS is not set +# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y +# CONFIG_EXTERNAL_COEX_ENABLE is not set +# CONFIG_ESP_WIFI_EXTERNAL_COEXIST_ENABLE is not set +# CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE is not set +# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set +# CONFIG_MCPWM_ISR_IRAM_SAFE is not set +# CONFIG_EVENT_LOOP_PROFILING is not set +CONFIG_POST_EVENTS_FROM_ISR=y +CONFIG_POST_EVENTS_FROM_IRAM_ISR=y +CONFIG_GDBSTUB_SUPPORT_TASKS=y +CONFIG_GDBSTUB_MAX_TASKS=32 +# CONFIG_OTA_ALLOW_HTTP is not set +CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_ESP32S3_RTC_CLK_CAL_CYCLES=1024 +CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y +CONFIG_BROWNOUT_DET=y +CONFIG_ESP32S3_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_7=y +CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_1 is not set +CONFIG_BROWNOUT_DET_LVL=7 +CONFIG_ESP32S3_BROWNOUT_DET_LVL=7 +CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y +CONFIG_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 is not set +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=240 +CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_MAIN_TASK_STACK_SIZE=8192 +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=5 +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP32S3_DEBUG_OCDAWARE=y +CONFIG_IPC_TASK_STACK_SIZE=1280 +CONFIG_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP32_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_TX_BUFFER_TYPE=0 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16 +CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32 +# CONFIG_ESP32_WIFI_CSI_ENABLED is not set +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +# CONFIG_ESP32_WIFI_AMSDU_TX_ENABLED is not set +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y +# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_WPA_MBEDTLS_CRYPTO=y +# CONFIG_WPA_WAPI_PSK is not set +# CONFIG_WPA_11KV_SUPPORT is not set +# CONFIG_WPA_MBO_SUPPORT is not set +# CONFIG_WPA_DPP_SUPPORT is not set +# CONFIG_WPA_11R_SUPPORT is not set +# CONFIG_WPA_WPS_SOFTAP_REGISTRAR is not set +# CONFIG_WPA_WPS_STRICT is not set +# CONFIG_WPA_DEBUG_PRINT is not set +CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH=y +# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set +# CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE is not set +# CONFIG_ESP32_COREDUMP_DATA_FORMAT_BIN is not set +CONFIG_ESP32_COREDUMP_DATA_FORMAT_ELF=y +CONFIG_ESP32_COREDUMP_CHECKSUM_CRC32=y +# CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256 is not set +CONFIG_ESP32_ENABLE_COREDUMP=y +CONFIG_ESP32_CORE_DUMP_MAX_TASKS_NUM=64 +CONFIG_ESP32_CORE_DUMP_STACK_SIZE=1792 +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/sdkconfig.release b/sdkconfig.release new file mode 100644 index 0000000..34e0501 --- /dev/null +++ b/sdkconfig.release @@ -0,0 +1,3305 @@ +# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) 5.5.0 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_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_GPIO_SUPPORT_HOLD_IO_IN_DSLP=y +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_TX_ASYNC_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_COUNT=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_SYNCHRO=y +CONFIG_SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY=y +CONFIG_SOC_RMT_SUPPORT_XTAL=y +CONFIG_SOC_RMT_SUPPORT_RC_FAST=y +CONFIG_SOC_RMT_SUPPORT_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_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="5.4.1" +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 is not set +# end of Application Rollback + +# +# Bootloader Rollback +# +# end of Bootloader 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 is not set +CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y +# end of Format + +# +# Settings +# +CONFIG_BOOTLOADER_LOG_MODE_TEXT_EN=y +CONFIG_BOOTLOADER_LOG_MODE_TEXT=y +# end of Settings +# end of Log + +# +# Serial Flash Configurations +# +# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set +CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y +# end of Serial Flash Configurations + +CONFIG_BOOTLOADER_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=y +# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE="8MB" +# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set +CONFIG_ESPTOOLPY_BEFORE_RESET=y +# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +# CONFIG_ESPTOOLPY_AFTER_NORESET is not set +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +# end of Serial flasher config + +# +# Partition Table +# +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# PyroVision +# + +# +# Device +# +CONFIG_DEVICE_MANUFACTURER="PyroVision" +CONFIG_DEVICE_NAME="ThermalCam" +# end of Device + +# +# USB +# +CONFIG_USB_VID=0x1234 +CONFIG_USB_PID=0x5678 +# end of USB + +# +# Settings +# +CONFIG_SETTINGS_NAMESPACE="Calendar" +# end of Settings + +# +# Battery +# +CONFIG_BATTERY_ADC_1=y +# CONFIG_BATTERY_ADC_2 is not set +CONFIG_BATTERY_ADC_CHANNEL=0 +CONFIG_BATTERY_ADC_ATT_0=y +# CONFIG_BATTERY_ADC_ATT_2_5 is not set +# CONFIG_BATTERY_ADC_ATT_6 is not set +# CONFIG_BATTERY_ADC_ATT_12 is not set +CONFIG_BATTERY_ADC_MIN_MV=3000 +CONFIG_BATTERY_ADC_MAX_MV=4200 +CONFIG_BATTERY_ADC_R1=10000 +CONFIG_BATTERY_ADC_R2=3300 +# end of Battery + +# +# Network +# + +# +# Websocket +# +CONFIG_NETWORK_WEBSOCKET_CLIENTS=4 +# end of Websocket + +# +# Task +# +CONFIG_NETWORK_TASK_STACKSIZE=8192 +CONFIG_NETWORK_TASK_PRIO=16 +CONFIG_NETWORK_TASK_CORE=1 +# end of Task + +# +# VISA +# +# end of Network + +# +# Lepton +# + +# +# Task +# +CONFIG_LEPTON_TASK_STACKSIZE=8192 +CONFIG_LEPTON_TASK_PRIO=16 +CONFIG_LEPTON_TASK_CORE=1 +# end of Task +# end of Lepton + +# +# Camera +# + +# +# Task +# +CONFIG_CAMERA_TASK_STACKSIZE=8192 +CONFIG_CAMERA_TASK_PRIO=12 +CONFIG_CAMERA_TASK_CORE=1 +# end of Task +# end of Camera + +# +# Devices +# + +# +# I2C +# +CONFIG_DEVICES_I2C_HOST=1 +CONFIG_DEVICES_I2C_SCL=21 +CONFIG_DEVICES_I2C_SDA=47 +# end of I2C + +# +# SPI +# +CONFIG_SPI_SCLK=46 +CONFIG_SPI_MOSI=48 +CONFIG_SPI_MISO=9 +CONFIG_SPI_TRANSFER_SIZE=4096 + +# +# Touch +# +CONFIG_TOUCH_I2C0_HOST=y +# CONFIG_TOUCH_I2C1_HOST is not set +CONFIG_TOUCH_CLOCK=400000 +CONFIG_TOUCH_SDA=39 +CONFIG_TOUCH_SCL=40 +CONFIG_TOUCH_IRQ=38 +CONFIG_TOUCH_RST=-1 +# end of Touch + +# +# SD-Card +# +# CONFIG_SD_CARD_SPI2_HOST is not set +CONFIG_SD_CARD_SPI3_HOST=y +CONFIG_SD_CARD_CLOCK=2000000 +CONFIG_SD_CARD_FORMAT_CARD=y +CONFIG_SD_CARD_PIN_CS=4 +CONFIG_SD_CARD_PIN_CD=-1 +# end of SD-Card + +# +# LCD +# +# CONFIG_LCD_SPI2_HOST is not set +CONFIG_LCD_SPI3_HOST=y +CONFIG_LCD_CLOCK=8000000 +CONFIG_LCD_CS=18 +CONFIG_LCD_DC=3 +CONFIG_LCD_RST=8 +CONFIG_LCD_BL=-1 +# end of LCD +# end of SPI + +# +# Task +# +CONFIG_DEVICES_TASK_STACKSIZE=4096 +CONFIG_DEVICES_TASK_PRIO=16 +CONFIG_DEVICES_TASK_CORE=1 +# end of Task +# end of Devices + +# +# GUI +# +CONFIG_GUI_WIDTH=320 +CONFIG_GUI_HEIGHT=240 +# CONFIG_GUI_TOUCH_DEBUG is not set +CONFIG_GUI_LVGL_TICK_PERIOD_MS=2 + +# +# Task +# +CONFIG_GUI_TASK_STACKSIZE=16384 +CONFIG_GUI_TASK_PRIO=2 +CONFIG_GUI_TASK_CORE=0 +# end of Task +# end of GUI +# end of PyroVision + +# +# Bootloader config (Custom) +# +# CONFIG_BOOTLOADER_COMPRESSED_ENABLED is not set +# CONFIG_BOOTLOADER_CUSTOM_DEBUG_ON is not set +# CONFIG_SKIP_VALIDATE_CUSTOM_COMPRESSED_HEADER is not set +# CONFIG_SKIP_VALIDATE_CUSTOM_COMPRESSED_DATA is not set +# end of Bootloader config (Custom) + +# +# Compiler options +# +CONFIG_COMPILER_OPTIMIZATION_DEBUG=y +# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set +# CONFIG_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_COMPILER_OPTIMIZATION_NONE is not set +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set +CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE=y +CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=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 is not set +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set +# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set +# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set +CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y +# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set +# CONFIG_COMPILER_DUMP_RTL_FILES is not set +CONFIG_COMPILER_RT_LIB_GCCLIB=y +CONFIG_COMPILER_RT_LIB_NAME="gcc" +CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y +# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set +# CONFIG_COMPILER_STATIC_ANALYZER is not set +# end of Compiler options + +# +# Component config +# + +# +# Application Level Tracing +# +# CONFIG_APPTRACE_DEST_JTAG is not set +CONFIG_APPTRACE_DEST_NONE=y +# CONFIG_APPTRACE_DEST_UART1 is not set +# CONFIG_APPTRACE_DEST_UART2 is not set +# CONFIG_APPTRACE_DEST_USB_CDC is not set +CONFIG_APPTRACE_DEST_UART_NONE=y +CONFIG_APPTRACE_UART_TASK_PRIO=1 +CONFIG_APPTRACE_LOCK_ENABLE=y +# end of Application Level Tracing + +# +# Bluetooth +# +# CONFIG_BT_ENABLED is not set + +# +# Common Options +# +# CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set +# end of Common Options +# end of Bluetooth + +# +# Console Library +# +# CONFIG_CONSOLE_SORTED_HELP is not set +# end of Console Library + +# +# Driver Configurations +# + +# +# Legacy TWAI Driver Configurations +# +# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set +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_USE_DS_PERIPHERAL=y +# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set +# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set +# CONFIG_ESP_TLS_PSK_VERIFICATION is not set +# CONFIG_ESP_TLS_INSECURE is not set +# end of ESP-TLS + +# +# ADC and ADC Calibration +# +# CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM is not set +# CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is not set +# CONFIG_ADC_CONTINUOUS_FORCE_USE_ADC2_ON_C3_S3 is not set +# CONFIG_ADC_ENABLE_DEBUG_LOG is not set +# end of ADC and ADC Calibration + +# +# 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 is not set +CONFIG_ESP_ALLOW_BSS_SEG_EXTERNAL_MEMORY=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:Touch Sensor Configurations +# +# CONFIG_TOUCH_CTRL_FUNC_IN_IRAM is not set +# CONFIG_TOUCH_ISR_IRAM_SAFE is not set +# CONFIG_TOUCH_ENABLE_DEBUG_LOG is not set +# CONFIG_TOUCH_SKIP_FSM_CHECK is not set +# end of ESP-Driver:Touch Sensor Configurations + +# +# ESP-Driver:Temperature Sensor Configurations +# +# CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Temperature Sensor Configurations + +# +# ESP-Driver:TWAI Configurations +# +# CONFIG_TWAI_ISR_IN_IRAM is not set +# CONFIG_TWAI_ISR_CACHE_SAFE is not set +# CONFIG_TWAI_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:TWAI Configurations + +# +# ESP-Driver:UART Configurations +# +# CONFIG_UART_ISR_IN_IRAM 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 + +# +# Ethernet +# +# CONFIG_ETH_USE_SPI_ETHERNET is not set +# CONFIG_ETH_USE_OPENETH is not set +# end of Ethernet + +# +# Event Loop Library +# +# CONFIG_ESP_EVENT_LOOP_PROFILING is not set +CONFIG_ESP_EVENT_POST_FROM_ISR=y +CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y +# end of Event Loop Library + +# +# GDB Stub +# +CONFIG_ESP_GDBSTUB_ENABLED=y +# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set +CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y +CONFIG_ESP_GDBSTUB_MAX_TASKS=32 +# end of GDB Stub + +# +# ESP HID +# +CONFIG_ESPHID_TASK_SIZE_BT=2048 +CONFIG_ESPHID_TASK_SIZE_BLE=4096 +# end of ESP HID + +# +# ESP HTTP client +# +CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y +# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set +CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000 +# end of ESP HTTP client + +# +# HTTP Server +# +CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_URI_LEN=512 +CONFIG_HTTPD_ERR_RESP_NO_DELAY=y +CONFIG_HTTPD_PURGE_BUF_LEN=32 +# CONFIG_HTTPD_LOG_PURGE_DATA is not set +CONFIG_HTTPD_WS_SUPPORT=y +# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set +CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000 +# end of HTTP Server + +# +# ESP HTTPS OTA +# +# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set +# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set +CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000 +# end of ESP HTTPS OTA + +# +# ESP HTTPS server +# +# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set +CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000 +# CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK is not set +# end of ESP HTTPS server + +# +# Hardware Settings +# + +# +# Chip revision +# +CONFIG_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_ENABLE_DEBUG_LOG is not set +# CONFIG_LCD_RGB_ISR_IRAM_SAFE is not set +# CONFIG_LCD_RGB_RESTART_IN_VSYNC 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_DEBUG is not set +# CONFIG_ESP_PHY_RECORD_USED_TIME is not set +CONFIG_ESP_PHY_IRAM_OPT=y +# 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_USE_MEMMAP is not set +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_MEMTEST=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=163840 +CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y +CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY=y +# 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 is not set +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240 + +# +# 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_PANIC_PRINT_HALT is not set +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set +# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set +CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 +CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y +CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y + +# +# 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=8192 +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=5 +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_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_TX_BUFFER_TYPE=0 +CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=16 +CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM=32 +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_AMSDU_TX_ENABLED is not set +CONFIG_ESP_WIFI_NVS_ENABLED=y +CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y +# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1 is not set +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_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 +# end of WPS Configuration Options + +# CONFIG_ESP_WIFI_DEBUG_PRINT is not set +# CONFIG_ESP_WIFI_TESTING_OPTIONS is not set +# CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT is not set +# end of Wi-Fi + +# +# Core dump +# +CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y +# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set +# CONFIG_ESP_COREDUMP_ENABLE_TO_NONE is not set +# CONFIG_ESP_COREDUMP_DATA_FORMAT_BIN is not set +CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y +CONFIG_ESP_COREDUMP_CHECKSUM_CRC32=y +# CONFIG_ESP_COREDUMP_CHECKSUM_SHA256 is not set +# CONFIG_ESP_COREDUMP_CAPTURE_DRAM is not set +CONFIG_ESP_COREDUMP_CHECK_BOOT=y +CONFIG_ESP_COREDUMP_ENABLE=y +CONFIG_ESP_COREDUMP_LOGS=y +CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=64 +# CONFIG_ESP_COREDUMP_FLASH_NO_OVERWRITE is not set +CONFIG_ESP_COREDUMP_USE_STACK_SIZE=y +CONFIG_ESP_COREDUMP_STACK_SIZE=1792 +# end of Core dump + +# +# FAT Filesystem support +# +CONFIG_FATFS_VOLUME_COUNT=2 +CONFIG_FATFS_LFN_NONE=y +# CONFIG_FATFS_LFN_HEAP is not set +# CONFIG_FATFS_LFN_STACK is not set +# CONFIG_FATFS_SECTOR_512 is not set +CONFIG_FATFS_SECTOR_4096=y +# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set +CONFIG_FATFS_CODEPAGE_437=y +# CONFIG_FATFS_CODEPAGE_720 is not set +# CONFIG_FATFS_CODEPAGE_737 is not set +# CONFIG_FATFS_CODEPAGE_771 is not set +# CONFIG_FATFS_CODEPAGE_775 is not set +# CONFIG_FATFS_CODEPAGE_850 is not set +# CONFIG_FATFS_CODEPAGE_852 is not set +# CONFIG_FATFS_CODEPAGE_855 is not set +# CONFIG_FATFS_CODEPAGE_857 is not set +# CONFIG_FATFS_CODEPAGE_860 is not set +# CONFIG_FATFS_CODEPAGE_861 is not set +# CONFIG_FATFS_CODEPAGE_862 is not set +# CONFIG_FATFS_CODEPAGE_863 is not set +# CONFIG_FATFS_CODEPAGE_864 is not set +# CONFIG_FATFS_CODEPAGE_865 is not set +# CONFIG_FATFS_CODEPAGE_866 is not set +# CONFIG_FATFS_CODEPAGE_869 is not set +# CONFIG_FATFS_CODEPAGE_932 is not set +# CONFIG_FATFS_CODEPAGE_936 is not set +# CONFIG_FATFS_CODEPAGE_949 is not set +# CONFIG_FATFS_CODEPAGE_950 is not set +CONFIG_FATFS_CODEPAGE=437 +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y +CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y +# CONFIG_FATFS_USE_FASTSEEK is not set +CONFIG_FATFS_USE_STRFUNC_NONE=y +# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set +# CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV is not set +CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0 +# CONFIG_FATFS_IMMEDIATE_FSYNC is not set +# CONFIG_FATFS_USE_LABEL is not set +CONFIG_FATFS_LINK_LOCK=y +# CONFIG_FATFS_USE_DYN_BUFFERS is not set + +# +# File system free space calculation behavior +# +CONFIG_FATFS_DONT_TRUST_FREE_CLUSTER_CNT=0 +CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0 +# end of File system free space calculation behavior +# end of FAT Filesystem support + +# +# FreeRTOS +# + +# +# Kernel +# +# CONFIG_FREERTOS_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_TASK_FUNCTION_WRAPPER=y +# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set +CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y +# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set +# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set +CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y +CONFIG_FREERTOS_ISR_STACKSIZE=2096 +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 is not set +CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y +# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set +# end of Format + +# +# Settings +# +CONFIG_LOG_MODE_TEXT_EN=y +CONFIG_LOG_MODE_TEXT=y +# end of Settings + +CONFIG_LOG_IN_IRAM=y +# end of Log + +# +# LWIP +# +CONFIG_LWIP_ENABLE=y +CONFIG_LWIP_LOCAL_HOSTNAME="PyroVision" +CONFIG_LWIP_TCPIP_TASK_PRIO=18 +# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set +# CONFIG_LWIP_CHECK_THREAD_SAFETY is not set +CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y +# CONFIG_LWIP_L2_TO_L3_COPY is not set +# CONFIG_LWIP_IRAM_OPTIMIZATION is not set +# CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION is not set +CONFIG_LWIP_TIMERS_ONDEMAND=y +CONFIG_LWIP_ND6=y +# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set +CONFIG_LWIP_MAX_SOCKETS=10 +# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set +# CONFIG_LWIP_SO_LINGER is not set +CONFIG_LWIP_SO_REUSE=y +CONFIG_LWIP_SO_REUSE_RXTOALL=y +# CONFIG_LWIP_SO_RCVBUF is not set +# CONFIG_LWIP_NETBUF_RECVINFO is not set +CONFIG_LWIP_IP_DEFAULT_TTL=64 +CONFIG_LWIP_IP4_FRAG=y +CONFIG_LWIP_IP6_FRAG=y +# CONFIG_LWIP_IP4_REASSEMBLY is not set +# CONFIG_LWIP_IP6_REASSEMBLY is not set +CONFIG_LWIP_IP_REASS_MAX_PBUFS=10 +# CONFIG_LWIP_IP_FORWARD is not set +# CONFIG_LWIP_STATS is not set +CONFIG_LWIP_ESP_GRATUITOUS_ARP=y +CONFIG_LWIP_GARP_TMR_INTERVAL=60 +CONFIG_LWIP_ESP_MLDV6_REPORT=y +CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set +# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set +# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set +CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y +# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set +CONFIG_LWIP_DHCP_OPTIONS_LEN=68 +CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0 +CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 + +# +# DHCP server +# +CONFIG_LWIP_DHCPS=y +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y +CONFIG_LWIP_DHCPS_ADD_DNS=y +# end of DHCP server + +# CONFIG_LWIP_AUTOIP is not set +CONFIG_LWIP_IPV4=y +CONFIG_LWIP_IPV6=y +# CONFIG_LWIP_IPV6_AUTOCONFIG is not set +CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 +# CONFIG_LWIP_IPV6_FORWARD is not set +# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +CONFIG_LWIP_MAX_LISTENING_TCP=16 +CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y +CONFIG_LWIP_TCP_MAXRTX=12 +CONFIG_LWIP_TCP_SYNMAXRTX=12 +CONFIG_LWIP_TCP_MSS=1440 +CONFIG_LWIP_TCP_TMR_INTERVAL=250 +CONFIG_LWIP_TCP_MSL=60000 +CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT=20000 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760 +CONFIG_LWIP_TCP_WND_DEFAULT=5760 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_ACCEPTMBOX_SIZE=6 +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +CONFIG_LWIP_TCP_OOSEQ_TIMEOUT=6 +CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=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_WND_SCALE is not set +CONFIG_LWIP_TCP_RTO_TIME=1500 +# end of TCP + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +# end of UDP + +# +# Checksums +# +# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set +# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set +CONFIG_LWIP_CHECKSUM_CHECK_ICMP=y +# end of Checksums + +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF +CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 +CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 +CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5 +CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3 +CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10 +# CONFIG_LWIP_PPP_SUPPORT 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_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 + +# +# ESP-MQTT Configurations +# +CONFIG_MQTT_PROTOCOL_311=y +# CONFIG_MQTT_PROTOCOL_5 is not set +CONFIG_MQTT_TRANSPORT_SSL=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y +# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set +# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set +# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set +# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set +# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set +# CONFIG_MQTT_CUSTOM_OUTBOX is not set +# end of ESP-MQTT Configurations + +# +# LibC +# +CONFIG_LIBC_NEWLIB=y +CONFIG_LIBC_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 + +# +# OpenThread +# +# CONFIG_OPENTHREAD_ENABLED is not set + +# +# OpenThread Spinel +# +# CONFIG_OPENTHREAD_SPINEL_ONLY is not set +# end of OpenThread Spinel +# end of OpenThread + +# +# Protocomm +# +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y +# end of Protocomm + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_PTHREAD_STACK_MIN=768 +CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y +# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set +# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set +CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" +# end of PThreads + +# +# MMU Config +# +CONFIG_MMU_PAGE_SIZE_64KB=y +CONFIG_MMU_PAGE_MODE="64KB" +CONFIG_MMU_PAGE_SIZE=0x10000 +# end of MMU Config + +# +# Main Flash configuration +# + +# +# SPI Flash behavior when brownout +# +CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y +CONFIG_SPI_FLASH_BROWNOUT_RESET=y +# end of SPI Flash behavior when brownout + +# +# Optional and Experimental Features (READ DOCS FIRST) +# + +# +# Features here require specific hardware (READ DOCS FIRST!) +# +# CONFIG_SPI_FLASH_HPM_ENA is not set +CONFIG_SPI_FLASH_HPM_AUTO=y +# CONFIG_SPI_FLASH_HPM_DIS is not set +CONFIG_SPI_FLASH_HPM_ON=y +CONFIG_SPI_FLASH_HPM_DC_AUTO=y +# CONFIG_SPI_FLASH_HPM_DC_DISABLE is not set +# CONFIG_SPI_FLASH_AUTO_SUSPEND is not set +CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 +# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set +# CONFIG_SPI_FLASH_FORCE_ENABLE_C6_H2_SUSPEND is not set +CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM=y +# end of Optional and Experimental Features (READ DOCS FIRST) +# end of Main Flash configuration + +# +# SPI Flash driver +# +# CONFIG_SPI_FLASH_VERIFY_WRITE is not set +# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +# CONFIG_SPI_FLASH_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_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_GD_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_ISSI_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_MXIC_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_WINBOND_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_BOYA_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_TH_SUPPORTED=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 + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +# CONFIG_SPIFFS_CACHE_STATS is not set +# end of SPIFFS Cache Configuration + +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +# CONFIG_SPIFFS_GC_STATS is not set +CONFIG_SPIFFS_PAGE_SIZE=256 +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y +CONFIG_SPIFFS_META_LENGTH=4 +CONFIG_SPIFFS_USE_MTIME=y + +# +# Debug Configuration +# +# CONFIG_SPIFFS_DBG is not set +# CONFIG_SPIFFS_API_DBG is not set +# CONFIG_SPIFFS_GC_DBG is not set +# CONFIG_SPIFFS_CACHE_DBG is not set +# CONFIG_SPIFFS_CHECK_DBG is not set +# CONFIG_SPIFFS_TEST_VISUALISATION is not set +# end of Debug Configuration +# end of SPIFFS Configuration + +# +# TCP Transport +# + +# +# Websocket +# +CONFIG_WS_TRANSPORT=y +CONFIG_WS_BUFFER_SIZE=1024 +# CONFIG_WS_DYNAMIC_BUFFER is not set +# end of Websocket +# end of TCP Transport + +# +# Ultra Low Power (ULP) Co-processor +# +CONFIG_ULP_COPROC_ENABLED=y +# CONFIG_ULP_COPROC_TYPE_FSM is not set +CONFIG_ULP_COPROC_TYPE_RISCV=y +CONFIG_ULP_COPROC_RESERVE_MEM=4096 + +# +# ULP RISC-V Settings +# +# CONFIG_ULP_RISCV_INTERRUPT_ENABLE is not set +CONFIG_ULP_RISCV_UART_BAUDRATE=9600 +CONFIG_ULP_RISCV_I2C_RW_TIMEOUT=500 +# end of ULP RISC-V Settings + +# +# ULP Debugging Options +# +# end of ULP Debugging Options +# end of Ultra Low Power (ULP) Co-processor + +# +# Unity unit testing library +# +CONFIG_UNITY_ENABLE_FLOAT=y +CONFIG_UNITY_ENABLE_DOUBLE=y +# CONFIG_UNITY_ENABLE_64BIT is not set +# CONFIG_UNITY_ENABLE_COLOR is not set +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y +# CONFIG_UNITY_ENABLE_FIXTURE is not set +# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set +# CONFIG_UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE is not set +# end of Unity unit testing library + +# +# USB-OTG +# +CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=256 +CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED=y +# CONFIG_USB_HOST_HW_BUFFER_BIAS_IN is not set +# CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT is not set + +# +# Hub Driver Configuration +# + +# +# Root Port configuration +# +CONFIG_USB_HOST_DEBOUNCE_DELAY_MS=250 +CONFIG_USB_HOST_RESET_HOLD_MS=30 +CONFIG_USB_HOST_RESET_RECOVERY_MS=30 +CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS=10 +# end of Root Port configuration + +# CONFIG_USB_HOST_HUBS_SUPPORTED is not set +# end of Hub Driver Configuration + +# CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK is not set +CONFIG_USB_OTG_SUPPORTED=y +# end of USB-OTG + +# +# Virtual file system +# +CONFIG_VFS_SUPPORT_IO=y +CONFIG_VFS_SUPPORT_DIR=y +CONFIG_VFS_SUPPORT_SELECT=y +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +# CONFIG_VFS_SELECT_IN_RAM 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 + +# +# Wear Levelling +# +# CONFIG_WL_SECTOR_SIZE_512 is not set +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 +# end of Wear Levelling + +# +# Wi-Fi Provisioning Manager +# +CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 +CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 +CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y +# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set +# end of Wi-Fi Provisioning Manager + +# +# ESP32-Lepton +# + +# +# GPIO +# + +# +# V-Sync +# +# CONFIG_LEPTON_GPIO_USE_VSYNC is not set +# end of V-Sync + +# +# Reset +# +CONFIG_LEPTON_GPIO_RESET_PIN=-1 +# end of Reset + +# +# Power-Down +# +CONFIG_LEPTON_GPIO_PWR_PIN=-1 +# end of Power-Down +# end of GPIO + +# +# VoSPI +# +CONFIG_LEPTON_VOSPI_SPI2_HOST=y +# CONFIG_LEPTON_VOSPI_SPI3_HOST is not set +CONFIG_LEPTON_VOSPI_MISO=12 +CONFIG_LEPTON_VOSPI_SCK=13 +CONFIG_LEPTON_VOSPI_CS=14 +CONFIG_LEPTON_VOSPI_FRAME_BUFFERS=2 +# end of VoSPI + +# +# Capture Task +# +CONFIG_LEPTON_CAPTURE_TASK_CORE_AFFINITY=y +CONFIG_LEPTON_CAPTURE_TASK_CORE=0 +CONFIG_LEPTON_CAPTURE_TASK_STACK=6144 +CONFIG_LEPTON_CAPTURE_TASK_PRIORITY=12 +# end of Capture Task + +# +# Misc +# +CONFIG_LEPTON_MISC_ERROR_BASE=0xB000 +# end of Misc +# end of ESP32-Lepton + +# +# CMake Utilities +# +# CONFIG_CU_RELINKER_ENABLE is not set +# CONFIG_CU_DIAGNOSTICS_COLOR_NEVER is not set +CONFIG_CU_DIAGNOSTICS_COLOR_ALWAYS=y +# CONFIG_CU_DIAGNOSTICS_COLOR_AUTO is not set +# CONFIG_CU_GCC_LTO_ENABLE is not set +# CONFIG_CU_GCC_STRING_1BYTE_ALIGN is not set +# end of CMake Utilities + +# +# Camera configuration +# +# CONFIG_OV7670_SUPPORT is not set +# CONFIG_OV7725_SUPPORT is not set +# CONFIG_NT99141_SUPPORT is not set +# CONFIG_OV2640_SUPPORT is not set +# CONFIG_OV3660_SUPPORT is not set +CONFIG_OV5640_SUPPORT=y +# CONFIG_GC2145_SUPPORT is not set +# CONFIG_GC032A_SUPPORT is not set +# CONFIG_GC0308_SUPPORT is not set +# CONFIG_BF3005_SUPPORT is not set +# CONFIG_BF20A6_SUPPORT is not set +# CONFIG_SC101IOT_SUPPORT is not set +# CONFIG_SC030IOT_SUPPORT is not set +# CONFIG_SC031GS_SUPPORT is not set +# CONFIG_HM1055_SUPPORT is not set +# CONFIG_HM0360_SUPPORT is not set +# CONFIG_MEGA_CCM_SUPPORT is not set +# CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY is not set +CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW=y +# CONFIG_SCCB_HARDWARE_I2C_PORT0 is not set +CONFIG_SCCB_HARDWARE_I2C_PORT1=y +CONFIG_SCCB_CLK_FREQ=400000 +CONFIG_CAMERA_TASK_STACK_SIZE=4096 +CONFIG_CAMERA_CORE0=y +# CONFIG_CAMERA_CORE1 is not set +# CONFIG_CAMERA_NO_AFFINITY is not set +CONFIG_CAMERA_DMA_BUFFER_SIZE_MAX=32768 +CONFIG_CAMERA_PSRAM_DMA=y +CONFIG_CAMERA_JPEG_MODE_FRAME_SIZE_AUTO=y +# CONFIG_CAMERA_JPEG_MODE_FRAME_SIZE_CUSTOM is not set +# CONFIG_CAMERA_CONVERTER_ENABLED is not set +CONFIG_LCD_CAM_ISR_IRAM_SAFE=y +# end of Camera configuration + +# +# JPEG Decoder +# +CONFIG_JD_USE_ROM=y +# end of JPEG Decoder + +# +# ESP LCD TOUCH +# +CONFIG_ESP_LCD_TOUCH_MAX_POINTS=5 +CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS=1 +# end of ESP LCD TOUCH + +# +# TinyUSB Stack +# +CONFIG_TINYUSB_DEBUG_LEVEL=0 + +# +# TinyUSB DCD +# +# CONFIG_TINYUSB_MODE_SLAVE is not set +CONFIG_TINYUSB_MODE_DMA=y +# end of TinyUSB DCD + +# +# TinyUSB callbacks +# +# CONFIG_TINYUSB_SUSPEND_CALLBACK is not set +# CONFIG_TINYUSB_RESUME_CALLBACK is not set +# end of TinyUSB callbacks + +# +# Descriptor configuration +# + +# +# You can provide your custom descriptors via tinyusb_driver_install() +# +# CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID is not set +CONFIG_TINYUSB_DESC_CUSTOM_VID=0x1234 +# CONFIG_TINYUSB_DESC_USE_DEFAULT_PID is not set +CONFIG_TINYUSB_DESC_CUSTOM_PID=0x5678 +CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100 +CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="PyroVision" +CONFIG_TINYUSB_DESC_PRODUCT_STRING="ThermalCam Storage" +CONFIG_TINYUSB_DESC_SERIAL_STRING="123456" +CONFIG_TINYUSB_DESC_MSC_STRING="Mass Storage" +# end of Descriptor configuration + +# +# Mass Storage Class (MSC) +# +CONFIG_TINYUSB_MSC_ENABLED=y +CONFIG_TINYUSB_MSC_BUFSIZE=4096 +CONFIG_TINYUSB_MSC_MOUNT_PATH="/data" +# end of Mass Storage Class (MSC) + +# +# Communication Device Class (CDC) +# +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_COUNT=1 +CONFIG_TINYUSB_CDC_RX_BUFSIZE=512 +# end of Communication Device Class (CDC) + +# +# Musical Instrument Digital Interface (MIDI) +# +CONFIG_TINYUSB_MIDI_COUNT=0 +# end of Musical Instrument Digital Interface (MIDI) + +# +# Human Interface Device Class (HID) +# +CONFIG_TINYUSB_HID_COUNT=0 +# end of Human Interface Device Class (HID) + +# +# Device Firmware Upgrade (DFU) +# +# CONFIG_TINYUSB_DFU_MODE_DFU is not set +# CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME is not set +CONFIG_TINYUSB_DFU_MODE_NONE=y +# end of Device Firmware Upgrade (DFU) + +# +# Bluetooth Host Class (BTH) +# +# CONFIG_TINYUSB_BTH_ENABLED is not set +# end of Bluetooth Host Class (BTH) + +# +# Network driver (ECM/NCM/RNDIS) +# +# CONFIG_TINYUSB_NET_MODE_ECM_RNDIS is not set +# CONFIG_TINYUSB_NET_MODE_NCM is not set +CONFIG_TINYUSB_NET_MODE_NONE=y +# end of Network driver (ECM/NCM/RNDIS) + +# +# Vendor Specific Interface +# +CONFIG_TINYUSB_VENDOR_COUNT=0 +# end of Vendor Specific Interface +# end of TinyUSB Stack + +# +# LittleFS +# +# CONFIG_LITTLEFS_SDMMC_SUPPORT is not set +CONFIG_LITTLEFS_MAX_PARTITIONS=3 +CONFIG_LITTLEFS_PAGE_SIZE=256 +CONFIG_LITTLEFS_OBJ_NAME_LEN=64 +CONFIG_LITTLEFS_READ_SIZE=128 +CONFIG_LITTLEFS_WRITE_SIZE=128 +CONFIG_LITTLEFS_LOOKAHEAD_SIZE=128 +CONFIG_LITTLEFS_CACHE_SIZE=512 +CONFIG_LITTLEFS_BLOCK_CYCLES=512 +CONFIG_LITTLEFS_USE_MTIME=y +# CONFIG_LITTLEFS_USE_ONLY_HASH is not set +# CONFIG_LITTLEFS_HUMAN_READABLE is not set +CONFIG_LITTLEFS_MTIME_USE_SECONDS=y +# CONFIG_LITTLEFS_MTIME_USE_NONCE is not set +# CONFIG_LITTLEFS_SPIFFS_COMPAT is not set +# CONFIG_LITTLEFS_FLUSH_FILE_EVERY_WRITE is not set +# CONFIG_LITTLEFS_FCNTL_GET_PATH is not set +# CONFIG_LITTLEFS_MULTIVERSION is not set +# CONFIG_LITTLEFS_MALLOC_STRATEGY_DISABLE is not set +CONFIG_LITTLEFS_MALLOC_STRATEGY_DEFAULT=y +# CONFIG_LITTLEFS_MALLOC_STRATEGY_INTERNAL is not set +# CONFIG_LITTLEFS_MALLOC_STRATEGY_SPIRAM is not set +CONFIG_LITTLEFS_ASSERTS=y +# CONFIG_LITTLEFS_MMAP_PARTITION is not set +# CONFIG_LITTLEFS_WDT_RESET is not set +# end of LittleFS + +# +# LVGL configuration +# +CONFIG_LV_CONF_SKIP=y +# CONFIG_LV_CONF_MINIMAL is not set + +# +# Color Settings +# +# CONFIG_LV_COLOR_DEPTH_32 is not set +# CONFIG_LV_COLOR_DEPTH_24 is not set +CONFIG_LV_COLOR_DEPTH_16=y +# CONFIG_LV_COLOR_DEPTH_8 is not set +# CONFIG_LV_COLOR_DEPTH_1 is not set +CONFIG_LV_COLOR_DEPTH=16 +# end of Color Settings + +# +# Memory Settings +# +# CONFIG_LV_USE_BUILTIN_MALLOC is not set +CONFIG_LV_USE_CLIB_MALLOC=y +# CONFIG_LV_USE_MICROPYTHON_MALLOC is not set +# CONFIG_LV_USE_RTTHREAD_MALLOC is not set +# CONFIG_LV_USE_CUSTOM_MALLOC is not set +# CONFIG_LV_USE_BUILTIN_STRING is not set +CONFIG_LV_USE_CLIB_STRING=y +# CONFIG_LV_USE_CUSTOM_STRING is not set +# CONFIG_LV_USE_BUILTIN_SPRINTF is not set +CONFIG_LV_USE_CLIB_SPRINTF=y +# CONFIG_LV_USE_CUSTOM_SPRINTF is not set +# end of Memory Settings + +# +# HAL Settings +# +CONFIG_LV_DEF_REFR_PERIOD=33 +CONFIG_LV_DPI_DEF=130 +# end of HAL Settings + +# +# Operating System (OS) +# +CONFIG_LV_OS_NONE=y +# CONFIG_LV_OS_PTHREAD is not set +# CONFIG_LV_OS_FREERTOS is not set +# CONFIG_LV_OS_CMSIS_RTOS2 is not set +# CONFIG_LV_OS_RTTHREAD is not set +# CONFIG_LV_OS_WINDOWS is not set +# CONFIG_LV_OS_MQX is not set +# CONFIG_LV_OS_SDL2 is not set +# CONFIG_LV_OS_CUSTOM is not set +# 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_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 is not set +# CONFIG_LV_USE_ASSERT_MEM_INTEGRITY is not set +# CONFIG_LV_USE_ASSERT_OBJ is not set +CONFIG_LV_ASSERT_HANDLER_INCLUDE="assert.h" +# end of Asserts + +# +# Debug +# +# CONFIG_LV_USE_REFR_DEBUG is not set +# CONFIG_LV_USE_LAYER_DEBUG is not set +# CONFIG_LV_USE_PARALLEL_DRAW_DEBUG is not set +# end of Debug + +# +# Others +# +# CONFIG_LV_ENABLE_GLOBAL_CUSTOM is not set +CONFIG_LV_CACHE_DEF_SIZE=0 +CONFIG_LV_IMAGE_HEADER_CACHE_DEF_CNT=0 +CONFIG_LV_GRADIENT_MAX_STOPS=2 +CONFIG_LV_COLOR_MIX_ROUND_OFS=128 +# CONFIG_LV_OBJ_STYLE_CACHE is not set +# CONFIG_LV_USE_OBJ_ID is not set +# CONFIG_LV_USE_OBJ_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=y +# 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=y +CONFIG_LV_FONT_MONTSERRAT_10=y +CONFIG_LV_FONT_MONTSERRAT_12=y +CONFIG_LV_FONT_MONTSERRAT_14=y +# CONFIG_LV_FONT_MONTSERRAT_16 is not set +# CONFIG_LV_FONT_MONTSERRAT_18 is not set +# CONFIG_LV_FONT_MONTSERRAT_20 is not set +# CONFIG_LV_FONT_MONTSERRAT_22 is not set +# CONFIG_LV_FONT_MONTSERRAT_24 is not set +# CONFIG_LV_FONT_MONTSERRAT_26 is not set +# CONFIG_LV_FONT_MONTSERRAT_28 is not set +# CONFIG_LV_FONT_MONTSERRAT_30 is not set +CONFIG_LV_FONT_MONTSERRAT_32=y +# CONFIG_LV_FONT_MONTSERRAT_34 is not set +# CONFIG_LV_FONT_MONTSERRAT_36 is not set +# CONFIG_LV_FONT_MONTSERRAT_38 is not set +# CONFIG_LV_FONT_MONTSERRAT_40 is not set +# CONFIG_LV_FONT_MONTSERRAT_42 is not set +# CONFIG_LV_FONT_MONTSERRAT_44 is not set +# CONFIG_LV_FONT_MONTSERRAT_46 is not set +CONFIG_LV_FONT_MONTSERRAT_48=y +# 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 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 is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set +CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y +# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL=3 +# CONFIG_FLASH_ENCRYPTION_ENABLED is not set +# CONFIG_FLASHMODE_QIO is not set +# CONFIG_FLASHMODE_QOUT is not set +CONFIG_FLASHMODE_DIO=y +# CONFIG_FLASHMODE_DOUT is not set +CONFIG_MONITOR_BAUD=115200 +CONFIG_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y +# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set +CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y +# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set +CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_CXX_EXCEPTIONS is not set +CONFIG_STACK_CHECK_NONE=y +# CONFIG_STACK_CHECK_NORM is not set +# CONFIG_STACK_CHECK_STRONG is not set +# CONFIG_STACK_CHECK_ALL is not set +# CONFIG_WARN_WRITE_STRINGS is not set +# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y +# 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_GDBSTUB_SUPPORT_TASKS=y +CONFIG_GDBSTUB_MAX_TASKS=32 +# CONFIG_OTA_ALLOW_HTTP is not set +CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_ESP32S3_RTC_CLK_CAL_CYCLES=1024 +CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y +CONFIG_BROWNOUT_DET=y +CONFIG_ESP32S3_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_7=y +CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_1 is not set +CONFIG_BROWNOUT_DET_LVL=7 +CONFIG_ESP32S3_BROWNOUT_DET_LVL=7 +CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y +CONFIG_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 is not set +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=240 +CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_MAIN_TASK_STACK_SIZE=8192 +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=5 +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP32S3_DEBUG_OCDAWARE=y +CONFIG_IPC_TASK_STACK_SIZE=1280 +CONFIG_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP32_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_TX_BUFFER_TYPE=0 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16 +CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32 +# CONFIG_ESP32_WIFI_CSI_ENABLED is not set +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +# CONFIG_ESP32_WIFI_AMSDU_TX_ENABLED is not set +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y +# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_WPA_MBEDTLS_CRYPTO=y +# CONFIG_WPA_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_WPA_TESTING_OPTIONS is not set +CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH=y +# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set +# CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE is not set +# CONFIG_ESP32_COREDUMP_DATA_FORMAT_BIN is not set +CONFIG_ESP32_COREDUMP_DATA_FORMAT_ELF=y +CONFIG_ESP32_COREDUMP_CHECKSUM_CRC32=y +# CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256 is not set +CONFIG_ESP32_ENABLE_COREDUMP=y +CONFIG_ESP32_CORE_DUMP_MAX_TASKS_NUM=64 +CONFIG_ESP32_CORE_DUMP_STACK_SIZE=1792 +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/ui/PyroVision.sll b/ui/PyroVision.sll new file mode 100644 index 0000000..179b8fa --- /dev/null +++ b/ui/PyroVision.sll @@ -0,0 +1,40 @@ +{ + "name": "SquareLine_Project.spj", + "depth": 1, + "width": 320, + "height": 240, + "rotation": 90, + "offset_x": 0, + "offset_y": 0, + "shape": "RECTANGLE", + "multilang": "DISABLE", + "description": "", + "board": "ESP WROVER KIT", + "board_version": "2.0.0", + "editor_version": "1.6.0", + "image": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCADwAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDxmiiikSFFFFABRRRQBLa273V3Dbx/flkVF+pOK7q8Phvw9J/Z76SupXMQxNIz7FDegOCTXJ+Hf+Rn0n/r8h/9DFXtf/5GPU/+vuX/ANDNbQfLDmtrcpaK5q/274d/6FGD/wACT/8AE10Og6GfE1i97pHgCC5t0kMTP/aCJhgASMNg9GH515zXpOg2Olah8Hni1fWf7Ktxr5ZZ/srT7m+zgBdq8jgk59vel7aXZfcv8guUtftLfwvPDDrPgOO1aZS0ZN2HVgDg4ZQRkcZGcjI9RWR/bvh3/oUYP/Ak/wDxNdJ40jtdD+HGg6HYX02q2V3cvfxagzBYxgFTEkeSy435YHGGJ7khfNqPbS7L7l/kFzrrM+G/EMn9nppK6bcyjEMivvUt6E4BFcLdW72t3NbyffikZG+oOK3NA/5GPTP+vuL/ANDFUfEX/Iz6t/1+Tf8AoZpzfNDmtrcHqrmZRRRWJIUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBYsLprHULa8RQzQSrKAe5Ug/wBK7O/8Py6/eS6poLR3cFyxlaPzVWSJjyQwJHeuEorSE0lyyV0NM7L/AIQfxH/0Dv8AyPH/APFV12g2t9p/hV9A1fwZ/atub03qn+1Fg2tsCD7vJ4B79+nFeP0U70v5X96/yHdHqfinT/EfiH7Da2vh6HTdK0+Mx2lnFcRts3YLszkgszEZJP8APJPPf8IP4j/6B3/keP8A+KrjaKL0v5X96/yC6O7sPD8ugXkWqa80dpBbMJVj81WklYcgKAT3rjL+6a+1C5vHUK08rSkDsWJP9ar0UpzTXLFWQmwooorMQUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB1vgSztbxvEn2q2hn8nQrqWPzUDbHG3DDPQjPUc1zO1fQflXWfDv73in/sXrv/2SuUoGdXFNosvha3tobvTrG4WJ/taz6eZp5pNzFTHJtYKu3aMApgg9etV9Vv8AwxLe6dJZaPceRFbJHcxNcqvmNsxwVjGGB5LHO70Fc5RQB1VprHhd3u1u9Akjt1sPs9qkU6GQvv3F3cxHLnOA2BgDGD2zbfV7CC70SVtGglj08/v432/6YPNZ/n+X+6wTkHhfwrHooA2JdT0m40q3gl0ULewRiIXEMwjRwGJ3MmzLOQcZ3enFZd01vLdzSW1v5EDyM0cRbf5ak8Lu74HGe9R0UCE2r6D8qNq+g/KlooATavoPyo2r6D8qWigBNq+g/KjavoPypaKAE2r6D8qNq+g/KlooATavoPyo2r6D8qWigBNq+g/KjavoPypaKAE2r6D8qNq+g/KlooATavoPyo2r6D8qWigBNq+g/KjavoPypaKAE2r6D8qNq+g/KlooATavoPyo2r6D8qWigDpfC9/oOm6dqkurWUV5dqYXsYJItwdwWyGOOE5UsMjcBim/2rpUFto8x0+zu5ljuFv4DAIw+92x8wAwQpBUj7uB6YrnKKAOq0/WfDS6raLc6CY9Mt4pht81ZJpJHXAaRzHhgMAABRtPPrl9td+GbfwxG1xo63UjanKyR/a1S4SIJHtDuE+ZCS38I5BxjmuSooGdRa61YQRxajc6FpVxuvrqR7cFVyJIlCpsAysaE5XnrkDBGarf23pENxaLa+HYPscQlE0VzIJZJ/MABzIFXbtAGzA+U5POTnAooEa+q6lpV1p9rZ6booshBLI7TPMJZZQwXAZtq9NpxgAc9M5JZ4YghuPFmjQzRRyRSX0COjqCrKZFBBB6g1l1seEv+Rz0L/sIW/8A6MWgZV8UwxW/i7WoII0iijv50SNFCqqiRgAAOgArJrZ8X/8AI669/wBhG4/9GNWNQI7P4d/e8U/9i9d/+yVh6Rp0epXMySztBHFA8zOse84UZIAyP51ufDv73in/ALF67/8AZKzfDd7BY6hO888cAe2kjSSSMuoYjAyoByPwq6aTklLYaEOk2MtnJdWmoySRQOgnEltsZVY43ABiG+mRUx8I6v8A2j9nW0maAy7BdCM+WVz9/PpjmptV1OGTQmszfQXU7TrIDa23kIFAIO75V3dRjjjB5qNdVtBo4uS5/tdYTZKNp/1Z48zPTITKY+hrblpXs/z/AOHK0M9dNjlttSuIbkvFZldhMePNDPtB68eveo7aw+0adfXfmbfsoQ7dud25tvXtWtp8emR6LewS61bpLdpFhTDKdhDBiCQn4cZp9pDpVvpeo2ra9al7kRhSIJsDa2Tn5KlU07bbPr11t19BWKUWlWC6VbXt7qE0P2hnVEjthJjaRnJ3j1FLF4flvrGe60vz7wR3AiCLBhipUncQCccjGPetO2v7WLRbayh1ixieCWXcZ7JpQ4JG0rmM46H0qrG9hPpF1YzarbxSG+E6yeTJtddpGQFXjk9MCr5Kenp3629f0HZGbqeltpl8llJIWuAq+cuzARjztBz82ARz6/nUWp2X9m6pdWXmeZ5ErR79uN2DjOO1bsmqadHcW04uGupdNtRHA0kZHny7iVPPRUz3wTgVl6lexanawXcshOor+6nyDmUAfK+emcfKfoD3NROMEnZiaRl0UUVgSFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFbHhL/kc9C/7CFv/wCjFrHrY8Jf8jnoX/YQt/8A0YtAEHi//kdde/7CNx/6Masatnxf/wAjrr3/AGEbj/0Y1Y1AHVeAdSsrHW7u11GcW9rqdjNYPcEcQ+YBhj7ZA/OtB/hb4sLk21jDdwZ+SeC6iKSD1GWBx9RXC0UDO4/4VZ4z/wCgN/5NQ/8AxdH/AAqzxn/0Bv8Ayah/+Lrh6KAO4/4VZ4z/AOgN/wCTUP8A8XR/wqzxn/0Bv/JqH/4uuHooA7j/AIVZ4z/6A3/k1D/8XR/wqzxn/wBAb/yah/8Ai64eigDuP+FWeM/+gN/5NQ//ABdH/CrPGf8A0Bv/ACah/wDi64eigDuP+FWeM/8AoDf+TUP/AMXR/wAKs8Z/9Ab/AMmof/i64eigDuP+FWeM/wDoDf8Ak1D/APF0f8Ks8Z/9Ab/yah/+Lrh6KAO4/wCFWeM/+gN/5NQ//F0f8Ks8Z/8AQG/8mof/AIuuHooA7j/hVnjP/oDf+TUP/wAXR/wqzxn/ANAb/wAmof8A4uuHooA7j/hVnjP/AKA3/k1D/wDF0f8ACrPGf/QG/wDJqH/4uuHooA7j/hVnjP8A6A3/AJNQ/wDxdH/CrPGf/QG/8mof/i64eigDuP8AhVnjP/oDf+TUP/xdH/CrPGf/AEBv/JqH/wCLrh6KAO4/4VZ4z/6A3/k1D/8AF0f8Ks8Z/wDQG/8AJqH/AOLrh6KAO4/4VZ4z/wCgN/5NQ/8AxdH/AAqzxn/0Bv8Ayah/+Lrh6KAO4/4VZ4z/AOgN/wCTUP8A8XR/wqzxn/0Bv/JqH/4uuHooA7j/AIVZ4z/6A3/k1D/8XR/wqzxn/wBAb/yah/8Ai64eigDuP+FWeM/+gN/5NQ//ABdH/CrPGf8A0Bv/ACah/wDi64eigDuP+FWeM/8AoDf+TUP/AMXR/wAKs8Z/9Ab/AMmof/i64eigDuP+FWeM/wDoDf8Ak1D/APF1o6H4F1Lw7rNprPiVoNK0+xlW5LyTo7SFDuCIqk5JIH4e/FebUUAXdYv/AO1Nbv8AUAmwXVzJPt/u7mLY/WqVFFAjqvAOm2V9rd3dajALi10yxmv3tyeJvLAwp9skflWg/wAUvFgci2vobSDPyQQWsQSMegypOPqag+Hf3vFP/YvXf/slcpQM7D/hafjP/oM/+SsP/wARR/wtPxn/ANBn/wAlYf8A4ioJPDAufC2j6haPYQyypMZzc38cLOVkIGFdxngY+UVYsfC9re/8IaZI5ILfVGeO5uASQ7id1wM5AbaFAwPQ4NMNRP8AhafjP/oM/wDkrD/8RR/wtPxn/wBBn/yVh/8AiK2oPCuj6ldwXAi0+C1ja4VobaW8zO8cRcIRLHu42/MUBOD90HFcv4p022tF0+9tI7GKC7jYqlo85U7WxuxOoYA5x1IJU0BqXv8AhafjP/oM/wDkrD/8RR/wtPxn/wBBn/yVh/8AiK0NV8K6JaX/AIgSyuba6ube2Z7fTVMytCAAXcswAYou5toY568gYLrfw14Xu7yw1Fpr6HT5rI6hNbJAGiRYsiWLzTJuyXXaPlJ+dfXNAamb/wALT8Z/9Bn/AMlYf/iKP+Fp+M/+gz/5Kw//ABFS2vhzSr3StO1S8c6bYPFKbmePL4kaZkiUBiemMnvtRj1qrr2hx6HpNlFLpZa+ktJDcTCRyInW7dA/oQVUKOx3A9aA1Jf+Fp+M/wDoM/8AkrD/APEUf8LT8Z/9Bn/yVh/+Irj6KQXOw/4Wn4z/AOgz/wCSsP8A8RR/wtPxn/0Gf/JWH/4iuPooC52H/C0/Gf8A0Gf/ACVh/wDiKP8AhafjP/oM/wDkrD/8RXH0UBc7D/hafjP/AKDP/krD/wDEUf8AC0/Gf/QZ/wDJWH/4iuPooC52H/C0/Gf/AEGf/JWH/wCIo/4Wn4z/AOgz/wCSsP8A8RXH0UBc7D/hafjP/oM/+SsP/wARR/wtPxn/ANBn/wAlYf8A4iuPooC52H/C0/Gf/QZ/8lYf/iKP+Fp+M/8AoM/+SsP/AMRXH0UBc7D/AIWn4z/6DP8A5Kw//EUf8LT8Z/8AQZ/8lYf/AIiuPooC52H/AAtPxn/0Gf8AyVh/+Io/4Wn4z/6DP/krD/8AEVx9FAXOw/4Wn4z/AOgz/wCSsP8A8RR/wtPxn/0Gf/JWH/4iuPooC52H/C0/Gf8A0Gf/ACVh/wDiKP8AhafjP/oM/wDkrD/8RXH0UBc7D/hafjP/AKDP/krD/wDEUf8AC0/Gf/QZ/wDJWH/4isLw7paa14j0/TZJGjjuZ0jd16hSece+M4rTvodJ1Tw7ealp2mf2dJY3MUZQTvIJY5A+Cd2fnBTtgHJ4GKA1LX/C0/Gf/QZ/8lYf/iKP+Fp+M/8AoM/+SsP/AMRTrDwzpl1oml6jfTPY2TQyfarpcsTIZWSIAE47ZOP4UbvWpZeEbS1e2gvdOtZLxNJuZ5UurpoommS6aNWLh1AXaB0IB4PemGpk/wDC0/Gf/QZ/8lYf/iK0dD8dal4i1m00bxKsGq6ffSrbFJIERoy52h0ZQMEEj8PfmsrxVpVnZaNpt0tnZWl5cSygpp901zA0a7cNvLuA2SQQGPGMgcZy/CX/ACOehf8AYQt//Ri0gMzWLD+y9bv9PD7xa3MkG7+9tYrn9KpVs+L/APkdde/7CNx/6MasagR2fw7+94p/7F67/wDZK5Sur+Hf3vFP/YvXf/slcpQM3rfxMselWmn3Wh6XfJabxFJcecHAZixHySKOp9KzDqd8bT7GLy4FoMYtxK3ljDFhhc44LE/Uk96ZPY3NvaWt1LHthugxhbcDuCttPHUcjvUh0q+22BFuzfbxm1VMMZfnKcAc53AjFAhbjWNUu7mG5udSvJp4MCKWWdmaPHTaScj8KivL671G5a5vrqe6nbgyzyF2P4nmtKbwprUN5bWhtEknuGZY1hnjl5UZYEqxCkDk5xgdapalpV3pM6Q3aRhnTejRTJKjDJGQyEg8g96ANm/8aXN6buVNK0y1u7yMxT3cCSGRlIwwG92C5HBwBwTWfD4ivoPDU+gp5X2SaXzGYr+8H3SVBz90lEJHqoqjf2Nzpl9LZ3kfl3ERw6bgcHr1HFaMnhXWYrBr1rVPKSITOizxtKiHB3NGG3gYIOSKBkN1rt7d6DYaNIyizsWkeNVBBZnOSW55I6D0GfU1JqPiTU9UsrO1uJz5drai0GwkebGrl1D84bBxj/dHfmoX0HUo7y9tHtts1jEZbkGRcRqMck5x/EAADySAOaH0LU01mPSDaOb6R0RIlIO4tgrgg4IIIOc45oAzqKvTaPfwiEtbswnEjReUwk3BCQ5G0ngbTz6DPSlbRb9dOW/aAC2aHz1cyLynmeVnGc/fGMYz36c0CKFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAE1pdT2N5Bd2shiuIJFkjcdVZTkH8xWnqviOfU7P7IljY2Nu03nypZxFBLJggM2SegLYAwBuOBWRHG80qRRIzyOwVUUZLE9AB3Naeq+HNW0SJJb+18uN3Me9JEkCuOqMVJ2t/snB9qAG3Wu3t3oNho0jKLOxaR41UEFmc5JbnkjoPQZ9TWlH411DFst3bWl7HBp507ZOHw8O/cNxV1ORwAQRwPxrFOl3ohtphbuyXSO8Oz5iyoSGOByMbT19M1bs/DWq34gaCCIJNbvdI8txHGvlK5jZizMAMMCOcGgY3Vdck1S3tbVbO1srS1LtFb2yttDPjcxLszEnavU9hUnhL/AJHPQv8AsIW//oxag1LQtQ0mKGW6jiMMxKxzQTxzRsRjI3RsRkZHGc81P4S/5HPQv+whb/8AoxaAIPF//I669/2Ebj/0Y1Y1bPi//kdde/7CNx/6MasagR2fw7+94p/7F67/APZK5Sur+Hf3vFP/AGL13/7JXKUDO/tBDeeDNGhjtfDt3LEk6yf2jqIglhJkJGF85O3PINULLxNpun2vhyZYbptT0V23KQhhmUzO5AOcggNwcHk9OMnj6KAO9i8a6TY3ES21ks0b+as9w2mWsEio6FAiqilXAzk7uGwBha5/xHq9jqX2SKxt0CwK2+4+xQ2rzMx7pF8oAAAHJPXnnAwqKAPUPEl1BdXWszahdeH7jS3t3+xm3a3a6MuweXgxfvPvYzvOMZz2rFuRa6boJl0J9JVJrJUuLqW7DXjs6gSxiIn5BksvCdP4jmuJooA9V8Qal5smvfbdQ0OXRbi3byEtpLdriSYL+6P7r94cPjO/jGao6d4wmg8Gx3v9tzx3ljaSabHp4uGAkLHEcuzOCFRnGccFE9a84ooC53UXiKDQ/Bui3GnXKHXMSxfKQTbRecXbI9X+Uf7u7+9VfxbdaBdabpJsJJAFsGaKCIqRBI9y7tG/OQFVmA7n5T0NcbRQAUUUUCCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKANbwvqFvpXirS7+6yLe3uUkkYDJUA8sB3I6/hWpe28OieFr+wl1KxvZ728hkhW0nEoCRiTMjEfdJ3gAHDdcgYrlaKAO6i8RQaH4N0W4065Q65iWL5SCbaLzi7ZHq/yj/d3f3q2otQ0WV7RdObT2hk0G4UWl7ciKNJXumcwsxdcYyccgkAHvXldFA7nX+LbiBND0mwhXTbaZZJpbi002fzolJ2BHL7nyxAIxuOAo6ZNZHhL/AJHPQv8AsIW//oxax62PCX/I56F/2ELf/wBGLQBB4v8A+R117/sI3H/oxqxq2fF//I669/2Ebj/0Y1Y1Ajs/h397xT/2L13/AOyVyldX8O/veKf+xeu//ZK5SgZ2mh6bpkmlW0+r6PbWunSK/malPfMs74JGYYww3YPGNjDIOT6XNT8MaXBpE1vZrYPPBp0V6108l0JWyqsSPk8naSdqjOeR82eK5qPxI40yGyudM0+7NvG0UE88bmSJCS2BhgpwWYjcDjNZ7apqD6eunvfXLWSNuW2MzGNT6hc4zQB1upeG7Gw8TeLLSaykitrG2aa0Xc2VXzolDAk/N8rHrkc1q/8ACO+HrmSAxWUAifXrSyVUN0knkuZNySCXA34C5KHjtjvw39u3s8d0t9d3t2ZYWiQvdv8AKWdXJIOdwJU5XuSD1FS2niXUo9X0u+vru7v00+4jmjhnuGYAKwbaM525xjpQBV1VI45okisjbKqMNxZj52Hb5xn2wvHHy+uaoVYu7ya9m3yySMq5Eau5YRqWLbR6DJJ/E1XoEFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXa6bpeixN4e0y+057i41pFeS7E7K1uJJWjTy1HynG3cdwOc44xXFVv6d4tvdNs7aFLWylms932O6miLS224k/Ic4OGJYbgcEkjFAzatdG0ayutD0bUNPa5udWx5t4k7KYN8rRJ5aj5TjbuO4HOccUWujaNZXWh6NqGntc3OrY828SdlMG+Vok8tR8pxt3HcDnOOKxdP8AF19p1pbxJbWU01pu+x3U0RaW23En5DnBwxLDcDgkkYo0/wAXX2nWlvEltZTTWm77HdTRFpbbcSfkOcHDEsNwOCSRigDatdG0ayutD0bUNPa5udWx5t4k7KYN8rRJ5aj5TjbuO4HOccUWujaNZXWh6NqGntc3OrY828SdlMG+Vok8tR8pxt3HcDnOOKxdP8XX2nWlvEltZTTWm77HdTRFpbbcSfkOcHDEsNwOCSRijT/F19p1pbxJbWU01pu+x3U0RaW23En5DnBwxLDcDgkkYoA2rXRtGsrrQ9G1DT2ubnVsebeJOymDfK0SeWo+U427juBznHFFro2jWV1oejahp7XNzq2PNvEnZTBvlaJPLUfKcbdx3A5zjisXT/F19p1pbxJbWU01pu+x3U0RaW23En5DnBwxLDcDgkkYo0/xdfadaW8SW1lNNabvsd1NEWlttxJ+Q5wcMSw3A4JJGKAG3ulwWvhO3uig+2f2nc20kgY4ZEjhIGOnVm5xnmovCX/I56F/2ELf/wBGLUd7rj3mi2WlLZWtvBau0u6HfulkZVVmcsxGSEXoAPapPCX/ACOehf8AYQt//Ri0AQeL/wDkdde/7CNx/wCjGrGrptZto7v4kavBKCUbUbnIBxnDuf6VjatbR2mpzQRAhFIwCc4yAf61v7Cfsfb9L2+drj5Xa503w7+94p/7F67/APZK5Sur+Hf3vFP/AGL13/7JXKVgI0YNC1K6+x+TbbvtiSPB86jeEzuPJ4xtPXHSqtnaT395DaWyh55nCIpYKCT05JAH413Hh3ULWK58IZvLSN4IbxZDNIgWNmMm3fu4Gcj73BzVXVzYnTLQa/LpjXxvVy2irAXFttO/d5WIyc7NuefvdqAOKooooEFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFdF/wg2vhJnkt7SFYbhraQzahbx7ZV6r8zjmsWytJL+7jtopII3fOGnmWJBgZ5ZiAOnc16JKLS68a67dS6zpL6Be3swvYnu1DGMMSsiL1dhnKFN3PHQkUDPPn027Swe+Mam2Sf7O0iurASYJxwe4BwehwcHirdt4Z1a7upbeG2UvDGksrNMixorgFSXLBRkMMDOe3Wuj0TQ410y7tpdY0k2GpWAkJkv4Y3guFy8YKF92cgoeOkhrL0VYdS8L6ho63dra3bXcN0huZhEkqKsild7cAjeCATzk96AMqfQtTtnmjms5ElhuFtnjON/mMCVUL1OQpwQMdPUVbufCGuWlxbQzWQD3MvkRkTxsvmf3GYNhW9mINbD61p9n8Q9HvDOtxa6cLSGa5jBIcxIqs65GSAQceoUVWvbeHRPC1/YS6lY3s97eQyQraTiUBIxJmRiPuk7wADhuuQMUAc61jcppseoNHi1kmeBJNw5dQrMMdeA6/nWh4S/wCRz0L/ALCFv/6MWruoxpa+AdKt2ubWSd76e58qG4SRkRooQpYKSVOVYYODxVLwl/yOehf9hC3/APRi0AXL/wD5Khqv/YRuv/QnrF8Qf8hu4/4D/wCgitq//wCSoar/ANhG6/8AQnrF8Qf8hu4/4D/6CK9P/mW/9v8A/tpf/Lv5nQ/Dv73in/sXrv8A9krlK6v4d/e8U/8AYvXf/slcpXmEBRXZWHhnTLrRNL1G+mexsmhk+1XS5YmQyskQAJx2ycfwo3etCPwlpcUNppWpQTW2pzafcT+fBmRjLDPKCqoWCsGjTA5HIUjqcgWPPaK7r/hG9ARdLgV7uYyWk+q3cjoIpvIRCUiVQzKC2wtnk4YH2rEfUdIvbq1W28LqjrOuYYLuZvPT+4QSTuPHKkd+OeADAooooEFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFdD4U07TNYmvtPv/ADo53tmmtZoV3uHjBdkCFlDblDdT1Ax15AOeorv7bQvC8dzoekzNcyzaxG7/AGuZPLktt/ywfu1kKnLKWOTyrjpVObSNE0rSLO4W1vdUvL298uO1uYjbuI4jiQARyNncxCg9trYFAzjKK7/SdCtZvDmizxaRol1cXhlEr6hqbW7kiQqAi+cmeMdFPNTjw/pMNpqctjp2m3Yh1aW1jbV79rXbGqggD97Hk5J6jPqBQFjzmiu3v/DljL4sh0G1smt/OuLVJrlXd0t2aPMkaE8OCTlSeTt4JBzWNeXuiXyGy0/QRaSM6rb3LXbs/UD94DlTkf3QuD64wQDBorqNck0HS7u/0WHQzK9qz24v3unWZpVJXft+4FyPu7c443Z5qXT4tNj0vWdSuvD8TtbC0WK2uJpgq7wdz/Kyk7sbhk4GeOKBHJUV3WlaPourXWkanHp3lWs8t1Bc2LTOUMkUIkBVs79p3LkZyCOuDWdEmk69peoy22jx6feafCt0BbzSNHNH5iIyMHZiG+cEEHsRjkUDOWorvNL0mxm0K8v/AOwtL8/+05IRDqeovbiCMKCEBMqbiCSOcn1o0rSrC40G6v8A+xtDkn/tJ4Al3qbQxRoEUgRsZl38k85agDg6K6zQ9PstY8R2llNpMMEUkt0C8dw/lPiPKqHLEYQjOdxyG5OKZofhgN4psdP1B7C4iuElOLe/jkUFY2I3NG528gHkigDlq2PCX/I56F/2ELf/ANGLVXUtJn0p41nnspTICQbW7inAx6lGOPxq14S/5HPQv+whb/8AoxaBFy//AOSoar/2Ebr/ANCesXxB/wAhu4/4D/6CK2r/AP5Khqv/AGEbr/0J6xfEH/IbuP8AgP8A6CK9P/mW/wDb/wD7aaf8u/mdD8O/veKf+xeu/wD2SuUrq/h397xT/wBi9d/+yVyleYQad1rt7d6DYaNIyizsWkeNVBBZnOSW55I6D0GfU1Ym8V6pNc6LcvInn6PGkVtJg5Kq5Zd3POM47cAViUUCNibxLqEviQ66hihusjakafu0QLsCBTn5Nvy4OeOKW88RSTiI2enWGlyxyiYTWKMkhcZwd5YlRz0UgdOOBWNRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFWLC+n0zUba+tW2z28iyxn/aByPwqvRQBf1bVrjWNWl1GcRxyyEbUhBVIwoAVVGeAAAB9Ktaz4o1PWtYi1SaRbe6hULG1rmPYQSxYYPBLMzEjuxrGooA37TxT9n0+ytJtF0y8+xbvIluBNuXcxY5CyBTye4p0fi2Z4LmLUdMsNS+0Xj3rtc+ap81wAxHlugwcdK56igDVvPEep3mpG+Ny0MnmxSokBKIjRqFjKr0+VRgHriptR8RnUraWNtI0qCecgzXMFuVkcg54ySqZI52Bc8jocViUUAb934rnvreX7VpmmTXssflvfvATMwxjJ52bsfxbd3fOeaj07xLcWUN5DdWltqcd2IhIt80rY8sEJgq6ngHHXoBWJRQB0KeML2LVLG7jtLJIbFXWCySNlhUOCHyA24k55JbPA54qG78SyzWEllZafY6ZbysrTLZq+6UqcqGZ2ZsA84BAyAe1YlFAG9aeKJYdPls73TbLVEluWume9MxfzGABOUkXOcd80tt4nSCxmspNC0u4tXuWuUil8/ETEBcKVlBxgDqSfesCigDXsfEVzp2rxX0EEPlQvK8Vk7SNAnmLtYAbtwyMd88DnirEPidLTVLW/stC0u1kt94KR+eVlDKVIbdKT0J6EdawKKANDU9St9QWMQaPYaeUzk2pl+fPrvduntjrVjwl/wAjnoX/AGELf/0YtY9bHhL/AJHPQv8AsIW//oxaBly//wCSoar/ANhG6/8AQnrF8Qf8hu4/4D/6CK2r/wD5Khqv/YRuv/QnrF8Qf8hu4/4D/wCgivT/AOZb/wBv/wDtpf8Ay7+Z0Pw7+94p/wCxeu//AGSs3w2I/td5I8MUpisppEWWMOoYLkHB4rS+Hf3vFP8A2L13/wCyVgabqUmmXDypDFMJImidJQSpVhg9CD+tefTaUk2QjUtpotctL6K4s7WKaC3a4jnt4RFjbjKsFwCCDjpnOK0ZY5oNN0w2kOiKr2au/wBqW3EjNlsn5+T0FYNxrcstpJa29pa2cMuPMFuhBfHIBZiTjPbOKl/t4Pb28VxpdhcG3iESPIJN20EnnDgdz2rZVI9Xr3KTRc0ZLC3s3vtVgjeO+mNrGCoHlr1kkUdtuVAx6mooNN+zWWv21zHH59t5SB2AO0+aASD2yP0rLvNQkvY7aJkjjit4/LjSMEAZJJPJPJJqxNrlzPBcRPHDm4hihkfB3MIyCp69eACfalzwtbt+q/zFdGjqd9Bo+pT6ZBpdk9vbuYmaeHdJKR1Yt1Gf9nGKs2kUi+G7Ca1h0hXklm8xrwQ5OCuADJycZNZS+IpyiGeysbmdFCrcTw7pMDpnnDY/2gaZDrhSxitJtOsrmOJnZDKrggsQT91gOw7VSqRu3f8A4A7ooXjvJeSs4hDbiCIVUJxx8u3jH0qGpJ5BNO8ixJEGORHHnavsMkn9ajrme5AUUUUgCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACtjwl/yOehf9hC3/APRi1j1seEv+Rz0L/sIW/wD6MWgC5f8A/JUNV/7CN1/6E9YviD/kN3H/AAH/ANBFa2qzRwfEzVpJXCINRuQWPQZdxWNrU0c+rzyROHQkAMOhwoFejzR/s/lvrz/+2ml/c+ZY8N6/P4a1qLUIYkmUKY5YJPuyxsMMp/z6V0D3Xw3ncyta+JrZnOTDA8Lonspb5iPrWJ/wi19/z1t/++m/wo/4Ra+/562//fTf4VH9m4v/AJ9sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/wA9bf8A76b/AAo/4Ra+/wCetv8A99N/hR/ZuL/59sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/z1t/++m/wo/4Ra+/562//AH03+FH9m4v/AJ9sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/wA9bf8A76b/AAo/4Ra+/wCetv8A99N/hR/ZuL/59sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/z1t/++m/wo/4Ra+/562//AH03+FH9m4v/AJ9sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/wA9bf8A76b/AAo/4Ra+/wCetv8A99N/hR/ZuL/59sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/z1t/++m/wo/4Ra+/562//AH03+FH9m4v/AJ9sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/wA9bf8A76b/AAo/4Ra+/wCetv8A99N/hR/ZuL/59sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/z1t/++m/wo/4Ra+/562//AH03+FH9m4v/AJ9sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/wA9bf8A76b/AAo/4Ra+/wCetv8A99N/hR/ZuL/59sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/z1t/++m/wo/4Ra+/562//AH03+FH9m4v/AJ9sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/wA9bf8A76b/AAo/4Ra+/wCetv8A99N/hR/ZuL/59sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/z1t/++m/wo/4Ra+/562//AH03+FH9m4v/AJ9sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/wA9bf8A76b/AAo/4Ra+/wCetv8A99N/hR/ZuL/59sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/z1t/++m/wo/4Ra+/562//AH03+FH9m4v/AJ9sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/wA9bf8A76b/AAo/4Ra+/wCetv8A99N/hR/ZuL/59sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/z1t/++m/wo/4Ra+/562//AH03+FH9m4v/AJ9sPZy7Gz5nw19PFn/ktR5nw19PFn/ktWN/wi19/wA9bf8A76b/AAo/4Ra+/wCetv8A99N/hR/ZuL/59sPZy7Gz5nw19PFn/ktVi08ReDPDs39oaFp2rXmpID5B1NoxHC2Mb8J94j0P6Hmue/4Ra+/562//AH03+FH/AAi19/z1t/8Avpv8KP7Nxf8Az7YckuxjTTSXE8k8zl5ZGLux6kk5Jplbn/CLX3/PW3/76b/Cj/hFr7/nrb/99N/hR/ZuL/59sXs5djrqKKK+8O0KKKKACiiigAooooAKnsVZ7+3VLcXLtIoWFs4kOeF4IPNQVLbXEtpdRXMDlJonDow7EHINTJNp2A6XVXt00m6h1BNJW/yv2eOwjAeMhvmDsnykYyMEk5qvd6T5Xg+1n2RfalkM8ygfvFhkwsbNx93Kn6bx61Su9bW7HOladETIHdo4mBfBzg/NwD3C4pv9u3Z1a41GQRySXCsksbA7GQjG3APQDGOeMD0rjhSqxStpZ3/Sy7Lf5kpM3rEw20FsdYtNHtdPeEExGLdcyqRwykZcE9QSQPwrG02GKTw9rcrxI0kaw7GZQSuZMHB7cUh8QSyW0cc9hYTyxxCFLiWImQKBgd9pwOhIJ4qS18Ri0sHs10bTXjlRFlLrLmTbyCcP1zzxikqdSKbtq2uumjv+IrM1rKGSPwrps1pDowlklnEr36wBmwV24MnJxk9KjtNNXV9IuJJktoWTUQbi4gjULHEsbFtu3jHHAHBOPWs1fEX+iR2sukadNDFI8kSuJf3e8gkDDjjgdc9Krtrlz/Zl3p0McNva3UwmkSIN26KMk/L0P4DmpVGrrZWd9/K/r2CzNmbTLEaHq2pWUObKaCJrcyAM8DiVVdM9iM/irCszS4IpPD2uyvEjSRRwmN2UEoTKoOD244qrbaxd2ukXuloVNrdlGdWBO0qQQV9DwAajttQltbG9tEVDHdqiyFgcgKwYY59RW0aVRRabv7ya9Fb/ACfqOzOvn0Wxg8StfXsCJpyNbxw26KF+0SmNDgD+6M5Y/h1NV77TNPkstZ3y2tj5WstHHK8LHC4f5BsUkDvjpxWFqHiG/wBT1C2u7lkJtgixRgEIoXHbPfHNTL4llK3iXGn2Nyl1dG6dJVfCuc9NrDjk9c1gsPXSi29Ult5eorMlsbaz0+x1HUtsGpG2kihg3K3lZcMS7KQCcbcAHjJ70ryQ61oWoXUljbW91Y+W6y20YjV1ZwpVlHGeQQQOxqlDrktteSzW1paRQzIEltAhaFwPUMSc55znjtSX2tzXloLSO3trO13b2htkKh27FiSScZPU1t7Ko5381rfZaXXz18tR2dzqZbDQh4s+zLlb14o1ggktl+zCZol25wckZOcbcZPOR1z4NKtpNC069vIQlvbtcPdsihWkwyBY8juScD0GT2qm/i24a4+1pp2nR3oVVW6EbF1woUEbmK5wBzis6bWLufRoNKZgLaGVpQBnLM3duecc4+prGFCvaKb7X18mnb79BJM6JrOz0XTFvjbRXdo2owvE8kYJkt2jfK59eCD6MvtSf2To2k3N9dedc3QsIw6JcWyeTMzj91zuOQc7unIU9K55tYu30JdHcq1qk/npkHcrYIwD6ck49adda1d3mk2unS+X5NueGA+Z+u3ce+0EgegNX9XrX1lu9fT+r7d/IOVk4hiPg15/KTzv7QCeZtG7b5ZOM+me1bJsLFIrW9ktI5FttGS5MAG0TSGUoC+OSBkE+uAKzf8AhJ1+xGz/ALC0r7OZPN2bZfvYxn/WelVV8QXqXNpPH5SNbW/2YKFyrx5JIcHIbO45/CiVOtPpbV9fw0CzZoWtzDr8F/b3GnWcEsNrJcQz2sIiKlBnaQOCCOOeckVZm0W1ki0y9uUW306HTo5bp41AaVy74UertgDPYDJ6Vj3OvzTWkttb2dlZRzYE32WIqZB1wSSTjPYYFRX+t3uo2FlYzOotrNNkcaAgH/aPqe3+TR7Grdcvuq/fbT+vzCzLXi6KCHxNcrbW8dvDsiZYoxgLmNT/AFrEq3qWoS6pfPdzqiyMqKQgIGFUKOpPYCqldVGLhTjGW6SKWwUUUVqMKKKKACiiigAooooA/9k=", + "export_temp_image": false, + "force_export_images": false, + "flat_export": false, + "advanced_alpha": false, + "pointfilter": false, + "theme_simplified": false, + "theme_dark": false, + "theme_color1": 5, + "theme_color2": 0, + "custom_variable_prefix": "uic", + "separate_screen_save": false, + "hierarchy_state_save": false, + "backup_cnt": 97, + "autosave_cnt": 0, + "group_color_cnt": 0, + "imagebytearrayprefix": "", + "lvgl_version": "9.1.0", + "callfuncsexport": "CPP_FILE", + "imageexport": "SOURCE", + "lvgl_include_path": "", + "naming": "Screen_Name", + "naming_force_lowercase": false, + "naming_add_subcomponent": false, + "nidcnt": 1001753 +} \ No newline at end of file diff --git a/ui/PyroVision.slp b/ui/PyroVision.slp new file mode 100644 index 0000000..7d37d79 --- /dev/null +++ b/ui/PyroVision.slp @@ -0,0 +1,12 @@ +{ + "uiExportFolderPath": "C:\\Users\\konta\\Desktop\\ThermalCam\\firmware\\main\\Application\\Tasks\\GUI\\Export", + "projectExportFolderPath": "C:\\Users\\konta\\Desktop\\ThermalCam\\firmware\\main\\Application\\Tasks\\GUI\\Export", + "drive_stdio": "-", + "drive_stdio_path": "", + "drive_posix": "-", + "drive_posix_path": "", + "drive_win32": "-", + "drive_win32_path": "", + "drive_fatfs": "-", + "drive_fatfs_path": "" +} \ No newline at end of file diff --git a/ui/PyroVision.spj b/ui/PyroVision.spj new file mode 100644 index 0000000..4000fa3 --- /dev/null +++ b/ui/PyroVision.spj @@ -0,0 +1,19615 @@ +{ + "root": { + "guid": "GUID93537733-852890S2924354", + "children": [ + { + "guid": "GUID66987770-852891S3274354", + "children": [ + { + "guid": "GUID20182961-853099S6974354", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "SplashScreen Logo", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + -80 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "IMAGE/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "IMAGE/Image", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "IMAGE/Asset", + "strval": "assets/Logo_80x44.png", + "InheritedType": 5 + }, + { + "nid": 1030, + "strtype": "IMAGE/Pivot", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 1040, + "strtype": "IMAGE/Rotation", + "InheritedType": 6 + }, + { + "nid": 1050, + "strtype": "IMAGE/Scale", + "integer": 256, + "InheritedType": 6 + }, + { + "nid": 1060, + "strtype": "IMAGE/Inner_align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1070, + "strtype": "IMAGE/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Image, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "IMAGE" + }, + { + "guid": "GUID96239662-853101S6354354", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "SplashScreen LogoText", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + -20 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "IMAGE/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "IMAGE/Image", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "IMAGE/Asset", + "strval": "assets/Text_218x40.png", + "InheritedType": 5 + }, + { + "nid": 1030, + "strtype": "IMAGE/Pivot", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 1040, + "strtype": "IMAGE/Rotation", + "InheritedType": 6 + }, + { + "nid": 1050, + "strtype": "IMAGE/Scale", + "integer": 256, + "InheritedType": 6 + }, + { + "nid": 1060, + "strtype": "IMAGE/Inner_align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1070, + "strtype": "IMAGE/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Image, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "IMAGE" + }, + { + "guid": "GUID34075349-853103S185294354", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "SplashScreen Subtext", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 20 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "OPEN SOURCE THERMAL CAMERA", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_10", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID88043540-853107S6554354", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "SplashScreen LoadingBar", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 45 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 17, + "strtype": "OBJECT/Size", + "intarray": [ + 200, + 5 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "BAR/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "BAR/Bar", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "BAR/Range", + "intarray": [ + 0, + 100 + ], + "InheritedType": 7 + }, + { + "nid": 1030, + "strtype": "BAR/Value", + "integer": 1, + "InheritedType": 6 + }, + { + "nid": 1040, + "strtype": "BAR/Mode", + "strval": "NORMAL", + "InheritedType": 3 + }, + { + "nid": 1050, + "strtype": "BAR/Value_start", + "InheritedType": 6 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 51, + 51, + 51, + 255 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1060, + "strtype": "BAR/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Anim1, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.INDICATOR", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1070, + "strtype": "BAR/Style_indicator", + "strval": "lv.PART.INDICATOR, Rectangle", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "BAR" + }, + { + "guid": "GUID59878104-853109S3084354", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "SplashScreen StatusText", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 65 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Initialize system...", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID86331474-853111S462274354", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "SplashScreen FirmwareVersion", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 90 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Firmware 1.0.0\\n(c) 2026 PyroVision Project", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 102, + 102, + 102, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_10", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "isPage": true, + "editor_posx": 400, + "editor_posy": -400, + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Splash", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "SCREEN/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "flags": 1048576, + "strtype": "SCREEN/Screen", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "SCREEN/Temporary", + "strval": "False", + "InheritedType": 2 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 30, + 30, + 30, + 255 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "SCREEN/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1050, + "strtype": "SCREEN/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + }, + { + "disabled": false, + "nid": 1001743, + "strtype": "_event/EventHandler", + "strval": "SCREEN_LOADED", + "childs": [ + { + "nid": 1001744, + "strtype": "_event/name", + "strval": "ScreenSplashLoaded", + "InheritedType": 10 + }, + { + "nid": 1001745, + "strtype": "_event/condition_C", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1001746, + "strtype": "_event/condition_P", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1001747, + "strtype": "_event/action", + "strval": "CALL FUNCTION", + "childs": [ + { + "nid": 1001748, + "strtype": "CALL FUNCTION/Name", + "strval": "CALL FUNCTION", + "InheritedType": 10 + }, + { + "nid": 1001749, + "strtype": "CALL FUNCTION/Call", + "strval": "<{Function_name}>( event_struct )", + "InheritedType": 10 + }, + { + "nid": 1001750, + "strtype": "CALL FUNCTION/CallC", + "strval": "<{Function_name}>( e );", + "InheritedType": 10 + }, + { + "nid": 1001751, + "strtype": "CALL FUNCTION/Function_name", + "strval": "ScreenSplashLoaded", + "InheritedType": 10 + }, + { + "nid": 1001752, + "strtype": "CALL FUNCTION/Dont_export_function", + "strval": "False", + "InheritedType": 2 + } + ], + "InheritedType": 10 + } + ], + "InheritedType": 4 + } + ], + "saved_objtypeKey": "SCREEN" + }, + { + "guid": "GUID63572141-853113S4094354", + "children": [ + { + "guid": "GUID10271921-68975S5384354", + "children": [ + { + "guid": "GUID70542204-68979S8364354", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Image Main WiFi", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 5, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "TOP_LEFT", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "W", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 0, + 0, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID85409111-68983S431164354", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Image Main SDCard", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 25, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "TOP_LEFT", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "C", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 0, + 0, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID59467401-68985S7174354", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Main Time", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 200, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "TOP_LEFT", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "12:34:56", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID38841759-68991S6814354", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Main Battery Remaining", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 280, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "TOP_LEFT", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "75%", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Container Main StatusBar", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 33, + "strtype": "OBJECT/Size", + "intarray": [ + 320, + 8 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "TOP_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 0, + 0, + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 10620, + "strtype": "_style/Bg_Image_opa", + "integer": 180, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + }, + { + "guid": "GUID69843562-430548S5034357", + "children": [ + { + "guid": "GUID40000558-68999S1814355", + "children": [ + { + "guid": "GUID90690797-862847S1705954", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Image Main Thermal Scene ROI", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 17, + "strtype": "OBJECT/Size", + "intarray": [ + 240, + 180 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "PANEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "OFF", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 0, + 0, + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 240, + 63, + 238, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "integer": 2, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "PANEL/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "PANEL/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "PANEL" + }, + { + "guid": "GUID41153353-862846S1193154", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Image Main Thermal Spotmeter ROI", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + -1, + -1 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 17, + "strtype": "OBJECT/Size", + "intarray": [ + 2, + 2 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "PANEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "OFF", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 0, + 0, + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 123, + 63, + 240, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "integer": 2, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "PANEL/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "PANEL/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "PANEL" + }, + { + "guid": "GUID326283-862848S2227654", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Image Main Thermal Video Focus ROI", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 17, + "strtype": "OBJECT/Size", + "intarray": [ + 238, + 178 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "PANEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "OFF", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 0, + 0, + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 235, + 240, + 63, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "integer": 2, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "PANEL/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "PANEL/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "PANEL" + }, + { + "guid": "GUID34728151-155431S6754356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Image Main Thermal AGC ROI", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 17, + "strtype": "OBJECT/Size", + "intarray": [ + 240, + 180 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "PANEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "OFF", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 0, + 0, + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 63, + 150, + 240, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "integer": 2, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "PANEL/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "PANEL/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "PANEL" + }, + { + "guid": "GUID49515181-98916S7624362", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Main Thermal Crosshair", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "CH", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "fa", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID79401473-98918S8004362", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Main Thermal PixelTemperature", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + -15 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "101.1", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 192 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_14", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID87870619-989445S5396453", + "children": [ + { + "guid": "GUID1108541-989447S92453", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Main Thermal Scene Max", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 10, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "LEFT_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "100.0", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 192 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 10620, + "strtype": "_style/Bg_Image_opa", + "integer": 255, + "InheritedType": 6 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 5, + 0, + 0, + 0 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID15732503-989449S57553", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Main Thermal Scene Min", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "-40.0", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 192 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 10620, + "strtype": "_style/Bg_Image_opa", + "integer": 255, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID77339875-989450S1050253", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Main Thermal Scene Mean", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + -10, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "RIGHT_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "80.0", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 192 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 10620, + "strtype": "_style/Bg_Image_opa", + "integer": 255, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Container Main Thermal Scene Statistics", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + -5 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 18, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 20 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "BOTTOM_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Image Thermal", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 10, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 17, + "strtype": "OBJECT/Size", + "intarray": [ + 240, + 180 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "IMAGE/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "IMAGE/Image", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "IMAGE/Asset", + "strval": "-", + "InheritedType": 5 + }, + { + "nid": 1030, + "strtype": "IMAGE/Pivot", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 1040, + "strtype": "IMAGE/Rotation", + "integer": 1800, + "InheritedType": 6 + }, + { + "nid": 1050, + "strtype": "IMAGE/Scale", + "integer": 256, + "InheritedType": 6 + }, + { + "nid": 1060, + "strtype": "IMAGE/Inner_align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10510, + "strtype": "_style/Bg_Radius", + "integer": 3, + "InheritedType": 6 + }, + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 0, + 0, + 0, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10550, + "strtype": "_style/Gradient direction", + "strval": "HOR", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1070, + "strtype": "IMAGE/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Image, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "IMAGE" + }, + { + "guid": "GUID78751988-155420S8764356", + "children": [ + { + "guid": "GUID884834-155424S6724356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label TempScaleMax", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + -80 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 18, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 20 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Max", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 10440, + "strtype": "_style/Text_Decor", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_10", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID79834816-155425S5454356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Image Gradient", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 18, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 150 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "IMAGE/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "IMAGE/Image", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "IMAGE/Asset", + "strval": "-", + "InheritedType": 5 + }, + { + "nid": 1030, + "strtype": "IMAGE/Pivot", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 1040, + "strtype": "IMAGE/Rotation", + "InheritedType": 6 + }, + { + "nid": 1050, + "strtype": "IMAGE/Scale", + "integer": 256, + "InheritedType": 6 + }, + { + "nid": 1060, + "strtype": "IMAGE/Inner_align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10510, + "strtype": "_style/Bg_Radius", + "integer": 3, + "InheritedType": 6 + }, + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 0, + 0, + 0, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10530, + "strtype": "_style/Bg_gradiens_Color", + "intarray": [ + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10550, + "strtype": "_style/Gradient direction", + "strval": "VER", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1070, + "strtype": "IMAGE/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Image, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "IMAGE" + }, + { + "guid": "GUID76026531-155422S4054356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label TempScaleMin", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 90 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 18, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 20 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Min", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_10", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Container Gradient", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + -135, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 17, + "strtype": "OBJECT/Size", + "intarray": [ + 40, + 190 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Container Main Thermal", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 10 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 34, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 75 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + }, + { + "guid": "GUID12873756-430552S3484357", + "children": [ + { + "guid": "GUID15522018-73173S422354359", + "children": [ + { + "guid": "GUID60809533-73172S422354359", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Main Button Save", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "S", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "fa", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Button Main Save", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 100, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 33, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 95 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "BUTTON/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 50, + 50, + 50, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "integer": 2, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "BUTTON/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "disabled": false, + "nid": 1000441, + "strtype": "_event/EventHandler", + "strval": "CLICKED", + "childs": [ + { + "nid": 1000442, + "strtype": "_event/name", + "strval": "ButtonMainSaveClicked", + "InheritedType": 10 + }, + { + "nid": 1000443, + "strtype": "_event/condition_C", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000444, + "strtype": "_event/condition_P", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000445, + "strtype": "_event/action", + "strval": "CALL FUNCTION", + "childs": [ + { + "nid": 1000446, + "strtype": "CALL FUNCTION/Name", + "strval": "CALL FUNCTION", + "InheritedType": 10 + }, + { + "nid": 1000447, + "strtype": "CALL FUNCTION/Call", + "strval": "<{Function_name}>( event_struct )", + "InheritedType": 10 + }, + { + "nid": 1000448, + "strtype": "CALL FUNCTION/CallC", + "strval": "<{Function_name}>( e );", + "InheritedType": 10 + }, + { + "nid": 1000449, + "strtype": "CALL FUNCTION/Function_name", + "strval": "ButtonMainSaveClicked", + "InheritedType": 10 + }, + { + "nid": 1000450, + "strtype": "CALL FUNCTION/Dont_export_function", + "strval": "False", + "InheritedType": 2 + } + ], + "InheritedType": 10 + } + ], + "InheritedType": 4 + } + ], + "saved_objtypeKey": "BUTTON" + }, + { + "guid": "GUID29848968-155427S6724356", + "children": [ + { + "guid": "GUID77308716-155429S1584356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Main Button ROI", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "R", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "fa", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Button Main ROI", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + -20, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 33, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 95 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "BUTTON/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 50, + 50, + 50, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "integer": 2, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "BUTTON/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "BUTTON" + }, + { + "guid": "GUID61733416-934770S366964356", + "children": [ + { + "guid": "GUID96116950-934769S366964356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Main Button Info", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "I", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "fa", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Button Main Info", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 40, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 33, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 95 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "BUTTON/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 50, + 50, + 50, + 200 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "integer": 2, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "BUTTON/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "disabled": false, + "nid": 1000292, + "strtype": "_event/EventHandler", + "strval": "CLICKED", + "childs": [ + { + "nid": 1000293, + "strtype": "_event/name", + "strval": "ButtonMainInfoClicked", + "InheritedType": 10 + }, + { + "nid": 1000294, + "strtype": "_event/condition_C", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000295, + "strtype": "_event/condition_P", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000296, + "strtype": "_event/action", + "strval": "CHANGE SCREEN", + "childs": [ + { + "nid": 1000297, + "strtype": "CHANGE SCREEN/Name", + "strval": "CHANGE SCREEN", + "InheritedType": 10 + }, + { + "nid": 1000298, + "strtype": "CHANGE SCREEN/Call", + "strval": "ChangeScreen( <{Screen_to}>, lv.SCR_LOAD_ANIM.<{Fade_mode}>, <{Speed}>, <{Delay}>)", + "InheritedType": 10 + }, + { + "nid": 1000299, + "strtype": "CHANGE SCREEN/CallC", + "strval": "_ui_screen_change( &<{Screen_to}>, LV_SCR_LOAD_ANIM_<{Fade_mode}>, <{Speed}>, <{Delay}>, &<{Screen_to}>_screen_init);", + "InheritedType": 10 + }, + { + "nid": 1000300, + "strtype": "CHANGE SCREEN/Screen_to", + "strval": "GUID10820859-155348S7984356", + "InheritedType": 9 + }, + { + "nid": 1000301, + "strtype": "CHANGE SCREEN/Fade_mode", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 1000302, + "strtype": "CHANGE SCREEN/Speed", + "InheritedType": 6 + }, + { + "nid": 1000303, + "strtype": "CHANGE SCREEN/Delay", + "InheritedType": 6 + } + ], + "InheritedType": 10 + } + ], + "InheritedType": 4 + } + ], + "saved_objtypeKey": "BUTTON" + }, + { + "guid": "GUID93999632-650710S271964356", + "children": [ + { + "guid": "GUID5832432-650712S5674356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Main Button Menu", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "M", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "fa", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Button Main Menu", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + -80, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 33, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 95 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "BUTTON/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 50, + 50, + 50, + 200 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "integer": 2, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "BUTTON/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "disabled": false, + "nid": 1000268, + "strtype": "_event/EventHandler", + "strval": "CLICKED", + "childs": [ + { + "nid": 1000269, + "strtype": "_event/name", + "strval": "ButtonMainMenuClicked", + "InheritedType": 10 + }, + { + "nid": 1000270, + "strtype": "_event/condition_C", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000271, + "strtype": "_event/condition_P", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000272, + "strtype": "_event/action", + "strval": "CHANGE SCREEN", + "childs": [ + { + "nid": 1000273, + "strtype": "CHANGE SCREEN/Name", + "strval": "CHANGE SCREEN", + "InheritedType": 10 + }, + { + "nid": 1000274, + "strtype": "CHANGE SCREEN/Call", + "strval": "ChangeScreen( <{Screen_to}>, lv.SCR_LOAD_ANIM.<{Fade_mode}>, <{Speed}>, <{Delay}>)", + "InheritedType": 10 + }, + { + "nid": 1000275, + "strtype": "CHANGE SCREEN/CallC", + "strval": "_ui_screen_change( &<{Screen_to}>, LV_SCR_LOAD_ANIM_<{Fade_mode}>, <{Speed}>, <{Delay}>, &<{Screen_to}>_screen_init);", + "InheritedType": 10 + }, + { + "nid": 1000276, + "strtype": "CHANGE SCREEN/Screen_to", + "strval": "GUID40629891-934771S164356", + "InheritedType": 9 + }, + { + "nid": 1000277, + "strtype": "CHANGE SCREEN/Fade_mode", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 1000278, + "strtype": "CHANGE SCREEN/Speed", + "InheritedType": 6 + }, + { + "nid": 1000279, + "strtype": "CHANGE SCREEN/Delay", + "InheritedType": 6 + } + ], + "InheritedType": 10 + } + ], + "InheritedType": 4 + } + ], + "saved_objtypeKey": "BUTTON" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Container Main Buttons", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 106, + -14 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 34, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 10 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + } + ], + "isPage": true, + "editor_posx": 800, + "editor_posy": -400, + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Main", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "SCREEN/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "SCREEN/Screen", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "SCREEN/Temporary", + "strval": "False", + "InheritedType": 2 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 30, + 30, + 30, + 255 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "SCREEN/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1050, + "strtype": "SCREEN/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + }, + { + "disabled": false, + "nid": 1000248, + "strtype": "_event/EventHandler", + "strval": "SCREEN_LOADED", + "childs": [ + { + "nid": 1000249, + "strtype": "_event/name", + "strval": "ScreenMainLoaded", + "InheritedType": 10 + }, + { + "nid": 1000250, + "strtype": "_event/condition_C", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000251, + "strtype": "_event/condition_P", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000252, + "strtype": "_event/action", + "strval": "CALL FUNCTION", + "childs": [ + { + "nid": 1000253, + "strtype": "CALL FUNCTION/Name", + "strval": "CALL FUNCTION", + "InheritedType": 10 + }, + { + "nid": 1000254, + "strtype": "CALL FUNCTION/Call", + "strval": "<{Function_name}>( event_struct )", + "InheritedType": 10 + }, + { + "nid": 1000255, + "strtype": "CALL FUNCTION/CallC", + "strval": "<{Function_name}>( e );", + "InheritedType": 10 + }, + { + "nid": 1000256, + "strtype": "CALL FUNCTION/Function_name", + "strval": "ScreenMainLoaded", + "InheritedType": 10 + }, + { + "nid": 1000257, + "strtype": "CALL FUNCTION/Dont_export_function", + "strval": "False", + "InheritedType": 2 + } + ], + "InheritedType": 10 + } + ], + "InheritedType": 4 + } + ], + "saved_objtypeKey": "SCREEN" + }, + { + "guid": "GUID40629891-934771S164356", + "children": [ + { + "guid": "GUID32876125-838323S38634360", + "children": [ + { + "guid": "GUID67585624-838322S38634360", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Menu Header", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "MENU", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_14", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Panel Menu Header", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 33, + "strtype": "OBJECT/Size", + "intarray": [ + 320, + 8 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "TOP_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "PANEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10510, + "strtype": "_style/Bg_Radius", + "InheritedType": 6 + }, + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 123, + 63, + 240, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 123, + 63, + 240, + 255 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "PANEL/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "PANEL/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "PANEL" + }, + { + "guid": "GUID16541557-838428S3954360", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Container Menu", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 15 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 34, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 75 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + }, + { + "guid": "GUID84576620-989468S627153", + "children": [ + { + "guid": "GUID258430-989467S627153", + "children": [ + { + "guid": "GUID30745183-989466S627153", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Menu Back", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "B", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "fa", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Button Menu Back", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + -80, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 33, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 95 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "BUTTON/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 50, + 50, + 50, + 200 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "integer": 2, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "BUTTON/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "disabled": false, + "nid": 1000268, + "strtype": "_event/EventHandler", + "strval": "CLICKED", + "childs": [ + { + "nid": 1000269, + "strtype": "_event/name", + "strval": "ButtonMenuBackClicked", + "InheritedType": 10 + }, + { + "nid": 1000270, + "strtype": "_event/condition_C", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000271, + "strtype": "_event/condition_P", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000272, + "strtype": "_event/action", + "strval": "CHANGE SCREEN", + "childs": [ + { + "nid": 1000273, + "strtype": "CHANGE SCREEN/Name", + "strval": "CHANGE SCREEN", + "InheritedType": 10 + }, + { + "nid": 1000274, + "strtype": "CHANGE SCREEN/Call", + "strval": "ChangeScreen( <{Screen_to}>, lv.SCR_LOAD_ANIM.<{Fade_mode}>, <{Speed}>, <{Delay}>)", + "InheritedType": 10 + }, + { + "nid": 1000275, + "strtype": "CHANGE SCREEN/CallC", + "strval": "_ui_screen_change( &<{Screen_to}>, LV_SCR_LOAD_ANIM_<{Fade_mode}>, <{Speed}>, <{Delay}>, &<{Screen_to}>_screen_init);", + "InheritedType": 10 + }, + { + "nid": 1000276, + "strtype": "CHANGE SCREEN/Screen_to", + "strval": "GUID63572141-853113S4094354", + "InheritedType": 9 + }, + { + "nid": 1000277, + "strtype": "CHANGE SCREEN/Fade_mode", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 1000278, + "strtype": "CHANGE SCREEN/Speed", + "InheritedType": 6 + }, + { + "nid": 1000279, + "strtype": "CHANGE SCREEN/Delay", + "InheritedType": 6 + } + ], + "InheritedType": 10 + } + ], + "InheritedType": 4 + } + ], + "saved_objtypeKey": "BUTTON" + }, + { + "guid": "GUID65690463-138553S6282537", + "children": [ + { + "guid": "GUID35373283-138552S6282537", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Menu Button Save", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "S", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "fa", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Button Menu Save", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 100, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 33, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 95 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "BUTTON/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 50, + 50, + 50, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "integer": 2, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "BUTTON/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "disabled": false, + "nid": 1001733, + "strtype": "_event/EventHandler", + "strval": "CLICKED", + "childs": [ + { + "nid": 1001734, + "strtype": "_event/name", + "strval": "ButtonMenuSaveClicked", + "InheritedType": 10 + }, + { + "nid": 1001735, + "strtype": "_event/condition_C", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1001736, + "strtype": "_event/condition_P", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1001737, + "strtype": "_event/action", + "strval": "CALL FUNCTION", + "childs": [ + { + "nid": 1001738, + "strtype": "CALL FUNCTION/Name", + "strval": "CALL FUNCTION", + "InheritedType": 10 + }, + { + "nid": 1001739, + "strtype": "CALL FUNCTION/Call", + "strval": "<{Function_name}>( event_struct )", + "InheritedType": 10 + }, + { + "nid": 1001740, + "strtype": "CALL FUNCTION/CallC", + "strval": "<{Function_name}>( e );", + "InheritedType": 10 + }, + { + "nid": 1001741, + "strtype": "CALL FUNCTION/Function_name", + "strval": "ButtonMenuSaveClicked", + "InheritedType": 10 + }, + { + "nid": 1001742, + "strtype": "CALL FUNCTION/Dont_export_function", + "strval": "False", + "InheritedType": 2 + } + ], + "InheritedType": 10 + } + ], + "InheritedType": 4 + } + ], + "saved_objtypeKey": "BUTTON" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Container Menu Buttons", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 100 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 34, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 10 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + } + ], + "isPage": true, + "editor_posx": 1200, + "editor_posy": -400, + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Menu", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "SCREEN/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "SCREEN/Screen", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "SCREEN/Temporary", + "strval": "False", + "InheritedType": 2 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 30, + 30, + 30, + 255 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "SCREEN/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1050, + "strtype": "SCREEN/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + }, + { + "disabled": false, + "nid": 1000316, + "strtype": "_event/EventHandler", + "strval": "GESTURE_RIGHT(GESTURE)", + "childs": [ + { + "nid": 1000317, + "strtype": "_event/name", + "strval": "ScreenMenuSwipeRight", + "InheritedType": 10 + }, + { + "nid": 1000318, + "strtype": "_event/condition_C", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000319, + "strtype": "_event/condition_P", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000320, + "flags": 1048576, + "strtype": "_event/action", + "strval": "CHANGE SCREEN", + "childs": [ + { + "nid": 1000321, + "strtype": "CHANGE SCREEN/Name", + "strval": "CHANGE SCREEN", + "InheritedType": 10 + }, + { + "nid": 1000322, + "strtype": "CHANGE SCREEN/Call", + "strval": "ChangeScreen( <{Screen_to}>, lv.SCR_LOAD_ANIM.<{Fade_mode}>, <{Speed}>, <{Delay}>)", + "InheritedType": 10 + }, + { + "nid": 1000323, + "strtype": "CHANGE SCREEN/CallC", + "strval": "_ui_screen_change( &<{Screen_to}>, LV_SCR_LOAD_ANIM_<{Fade_mode}>, <{Speed}>, <{Delay}>, &<{Screen_to}>_screen_init);", + "InheritedType": 10 + }, + { + "nid": 1000324, + "strtype": "CHANGE SCREEN/Screen_to", + "strval": "GUID63572141-853113S4094354", + "InheritedType": 9 + }, + { + "nid": 1000325, + "strtype": "CHANGE SCREEN/Fade_mode", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 1000326, + "strtype": "CHANGE SCREEN/Speed", + "InheritedType": 6 + }, + { + "nid": 1000327, + "strtype": "CHANGE SCREEN/Delay", + "InheritedType": 6 + } + ], + "InheritedType": 10 + } + ], + "InheritedType": 4 + } + ], + "saved_objtypeKey": "SCREEN" + }, + { + "guid": "GUID10820859-155348S7984356", + "children": [ + { + "guid": "GUID98621717-155354S7474356", + "children": [ + { + "guid": "GUID15718204-155356S9244356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Header", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "SYSTEM INFORMATION", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_14", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Panel Info Header", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 33, + "strtype": "OBJECT/Size", + "intarray": [ + 320, + 8 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "TOP_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "PANEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10510, + "strtype": "_style/Bg_Radius", + "InheritedType": 6 + }, + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 123, + 63, + 240, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 123, + 63, + 240, + 255 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "PANEL/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "PANEL/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "PANEL" + }, + { + "guid": "GUID28971676-155358S944356", + "children": [ + { + "guid": "GUID70698399-430607S5284357", + "children": [ + { + "guid": "GUID72017967-430596S5254357", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Device", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "DEVICE", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_14", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID64856691-430605S5284357", + "children": [ + { + "guid": "GUID83225215-430600S5254357", + "children": [ + { + "guid": "GUID95020504-430597S5254357", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info MAC Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "MAC:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID55120083-47164S4384359", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info IP Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "IP:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID56191097-584542S741516", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Serial Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Serial:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID94262332-44822S661553", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info PSRAM Free Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Free PSRAM:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID76328829-44823S25588553", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info RAM Free Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Free RAM:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Info Container4", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "LEFT_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + }, + { + "guid": "GUID62902718-430604S5284357", + "children": [ + { + "guid": "GUID41923710-430601S5284357", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info MAC", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "AA:BB:CC:DD:EE:FF", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID97387059-584544S524516", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info IP", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "127.0.0.1", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID64451864-584545S24208516", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Serial", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "00000001", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID16563251-44824S577553", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info PSRAM Free", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "0 MB", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID3838934-44825S8289553", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info RAM Free", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "0 MB", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Info Container5", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 2, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "RIGHT_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Container Info Device", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "TOP_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 5, + 5, + 0, + 0 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Panel Device", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 49, + "strtype": "OBJECT/Size", + "intarray": [ + 300, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "PANEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10510, + "strtype": "_style/Bg_Radius", + "integer": 6, + "InheritedType": 6 + }, + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 37, + 37, + 37, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 5, + 5, + 5, + 0 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "PANEL/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "PANEL/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "PANEL" + }, + { + "guid": "GUID57862356-155360S1954356", + "children": [ + { + "guid": "GUID71204662-155362S3724356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Battery", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "BATTERY", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_14", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID64214999-155378S3394356", + "children": [ + { + "guid": "GUID53593748-155370S562344356", + "children": [ + { + "guid": "GUID91214090-155380S114356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Battery Status Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Status:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID48938903-155382S2754356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Battery Voltage Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Voltage:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_10", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID95955079-155384S188854356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Battery Remaining Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Remaining:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Info Container2", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "LEFT_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + }, + { + "guid": "GUID61454612-155376S624356", + "children": [ + { + "guid": "GUID69353012-155390S9964356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Battery Status", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "TOP_RIGHT", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Nothing", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID31824688-155392S5694356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Battery Voltage", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "4.2 V", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID1425477-155393S19884356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Battery Remaining", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "75%", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Info Container3", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 2, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "BOTTOM_RIGHT", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Container Battery", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "TOP_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 5, + 5, + 0, + 0 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + }, + { + "guid": "GUID34128925-155388S7674356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Info Bar2", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 18, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 15 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "BOTTOM_MID", + "InheritedType": 3 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "BAR/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "BAR/Bar", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "BAR/Range", + "intarray": [ + 0, + 100 + ], + "InheritedType": 7 + }, + { + "nid": 1030, + "strtype": "BAR/Value", + "integer": 100, + "InheritedType": 6 + }, + { + "nid": 1040, + "strtype": "BAR/Mode", + "strval": "NORMAL", + "InheritedType": 3 + }, + { + "nid": 1050, + "strtype": "BAR/Value_start", + "InheritedType": 6 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 51, + 51, + 51, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 5, + 5, + 5, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1060, + "strtype": "BAR/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Anim1, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.INDICATOR", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10510, + "strtype": "_style/Bg_Radius", + "integer": 4, + "InheritedType": 6 + }, + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 255, + 0, + 0, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10530, + "strtype": "_style/Bg_gradiens_Color", + "intarray": [ + 97, + 255, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 10550, + "strtype": "_style/Gradient direction", + "strval": "HOR", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1070, + "strtype": "BAR/Style_indicator", + "strval": "lv.PART.INDICATOR, Rectangle", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "BAR" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Panel Battery", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 49, + "strtype": "OBJECT/Size", + "intarray": [ + 300, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "PANEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10510, + "strtype": "_style/Bg_Radius", + "integer": 6, + "InheritedType": 6 + }, + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 37, + 37, + 37, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 5, + 5, + 5, + 0 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "PANEL/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "PANEL/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "PANEL" + }, + { + "guid": "GUID44018081-155417S20174356", + "children": [ + { + "guid": "GUID18558271-155406S20134356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "LEPTON", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_14", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID43255-155453S4464356", + "children": [ + { + "guid": "GUID70722931-155449S3594356", + "children": [ + { + "guid": "GUID7903402-155437S25564356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton Serial Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Serial:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID29520723-155438S25564356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton Part Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Part Number:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID44302939-208082S5004365", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton GPP Revision Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "GPP Revision:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID75705018-725311S45003531", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton DSP Revision Main", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "DSP Revision:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID94008783-430544S7064357", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton Uptime Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Uptime:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID52254882-430562S8524357", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton FPA Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "FPA Temperature:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID68439637-430563S113094357", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton AUX Name", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "AUX Temperature:", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Info Container1", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + }, + { + "guid": "GUID93580312-155451S318044356", + "children": [ + { + "guid": "GUID84647470-155445S108144356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton Serial", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Nothing", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID97949053-155446S108144356", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton Part", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Nothing", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID43761664-208083S206274365", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton GPP Revision", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Nothing", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID50265110-725310S34524531", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton DSP Revision", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "Nothing", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID13171702-430545S82544357", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton Uptime", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "0", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID86685667-430564S195944357", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton FPA", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "0", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + }, + { + "guid": "GUID453239-430565S279304357", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Lepton AUX", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "0", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10410, + "strtype": "_style/Text_Color", + "intarray": [ + 255, + 255, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10430, + "strtype": "_style/Text_Align", + "strval": "RIGHT", + "InheritedType": 3 + }, + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "montserrat_12", + "InheritedType": 3 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 0, + 0, + 0, + 5 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Info Container7", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 2, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 172, + -2 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Container Lepton", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 50, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "TOP_MID", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Panel Lepton", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 80 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 49, + "strtype": "OBJECT/Size", + "intarray": [ + 300, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "PANEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 140, + "strtype": "OBJECT/Click_focusable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 210, + "strtype": "OBJECT/Gesture_bubble", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 220, + "strtype": "OBJECT/Snappable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10510, + "strtype": "_style/Bg_Radius", + "integer": 6, + "InheritedType": 6 + }, + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 37, + 37, + 37, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 5, + 5, + 5, + 0 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "PANEL/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "PANEL/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "PANEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Panel Info Content", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + -276, + 104 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 33, + "strtype": "OBJECT/Size", + "intarray": [ + 320, + 75 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "PANEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "ON", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "VER", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10510, + "strtype": "_style/Bg_Radius", + "InheritedType": 6 + }, + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 30, + 30, + 30, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "InheritedType": 6 + }, + { + "nid": 11110, + "strtype": "_style/Padding", + "intarray": [ + 5, + 0, + 0, + 0 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "PANEL/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10510, + "strtype": "_style/Bg_Radius", + "integer": 2, + "InheritedType": 6 + }, + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 123, + 63, + 240, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1020, + "strtype": "PANEL/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "PANEL" + }, + { + "guid": "GUID26295868-989474S771453", + "children": [ + { + "guid": "GUID79481689-989473S771453", + "children": [ + { + "guid": "GUID14912377-989472S771453", + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Label Info Back", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 51, + "strtype": "OBJECT/Size", + "intarray": [ + 1, + 1 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "LABEL/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "strtype": "LABEL/Label", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "LABEL/Long_mode", + "strval": "WRAP", + "InheritedType": 3 + }, + { + "nid": 1030, + "strtype": "LABEL/Text", + "strval": "B", + "InheritedType": 10 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10450, + "strtype": "_style/Text_Font", + "strval": "fa", + "InheritedType": 3 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "LABEL/Style_main", + "strval": "lv.PART.MAIN, Text, Rectangle, Pad, Transform", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "LABEL" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Button Info Back", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + -80, + 0 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 33, + "strtype": "OBJECT/Size", + "intarray": [ + 50, + 95 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "BUTTON/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 240, + "strtype": "OBJECT/Scroll_elastic", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 250, + "strtype": "OBJECT/Scroll_momentum", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 270, + "strtype": "OBJECT/Scroll_chain", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 50, + 50, + 50, + 200 + ], + "InheritedType": 7 + }, + { + "nid": 10710, + "strtype": "_style/Border_Color", + "intarray": [ + 185, + 152, + 255, + 255 + ], + "InheritedType": 7 + }, + { + "nid": 10720, + "strtype": "_style/Border width", + "integer": 2, + "InheritedType": 6 + } + ], + "InheritedType": 1 + } + ], + "nid": 1010, + "strtype": "BUTTON/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "disabled": false, + "nid": 1000268, + "strtype": "_event/EventHandler", + "strval": "CLICKED", + "childs": [ + { + "nid": 1000269, + "strtype": "_event/name", + "strval": "ButtonInfoBackClicked", + "InheritedType": 10 + }, + { + "nid": 1000270, + "strtype": "_event/condition_C", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000271, + "strtype": "_event/condition_P", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000272, + "strtype": "_event/action", + "strval": "CHANGE SCREEN", + "childs": [ + { + "nid": 1000273, + "strtype": "CHANGE SCREEN/Name", + "strval": "CHANGE SCREEN", + "InheritedType": 10 + }, + { + "nid": 1000274, + "strtype": "CHANGE SCREEN/Call", + "strval": "ChangeScreen( <{Screen_to}>, lv.SCR_LOAD_ANIM.<{Fade_mode}>, <{Speed}>, <{Delay}>)", + "InheritedType": 10 + }, + { + "nid": 1000275, + "strtype": "CHANGE SCREEN/CallC", + "strval": "_ui_screen_change( &<{Screen_to}>, LV_SCR_LOAD_ANIM_<{Fade_mode}>, <{Speed}>, <{Delay}>, &<{Screen_to}>_screen_init);", + "InheritedType": 10 + }, + { + "nid": 1000276, + "strtype": "CHANGE SCREEN/Screen_to", + "strval": "GUID63572141-853113S4094354", + "InheritedType": 9 + }, + { + "nid": 1000277, + "strtype": "CHANGE SCREEN/Fade_mode", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 1000278, + "strtype": "CHANGE SCREEN/Speed", + "InheritedType": 6 + }, + { + "nid": 1000279, + "strtype": "CHANGE SCREEN/Delay", + "InheritedType": 6 + } + ], + "InheritedType": 10 + } + ], + "InheritedType": 4 + } + ], + "saved_objtypeKey": "BUTTON" + } + ], + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Container Info Buttons", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 0, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 0, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 50, + "flags": 17, + "strtype": "OBJECT/Position", + "intarray": [ + 0, + 100 + ], + "InheritedType": 7 + }, + { + "nid": 60, + "flags": 34, + "strtype": "OBJECT/Size", + "intarray": [ + 100, + 10 + ], + "InheritedType": 7 + }, + { + "nid": 70, + "strtype": "OBJECT/Align", + "strval": "CENTER", + "InheritedType": 3 + }, + { + "nid": 75, + "strtype": "OBJECT/Extend_click_area", + "InheritedType": 6 + }, + { + "nid": 90, + "flags": 1048576, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "CONTAINER/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "part": "lv.PART.MAIN", + "childs": [], + "nid": 1010, + "strtype": "CONTAINER/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text, Transform", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1020, + "strtype": "CONTAINER/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + } + ], + "saved_objtypeKey": "CONTAINER" + } + ], + "isPage": true, + "editor_posx": 800, + "editor_posy": -740, + "properties": [ + { + "nid": 10, + "strtype": "OBJECT/Name", + "strval": "Info", + "InheritedType": 10 + }, + { + "nid": 20, + "strtype": "OBJECT/Layout", + "InheritedType": 1 + }, + { + "Flow": 1, + "Wrap": false, + "Reversed": false, + "MainAlignment": 0, + "CrossAlignment": 0, + "TrackAlignment": 0, + "LayoutType": 1, + "nid": 30, + "strtype": "OBJECT/Layout_type", + "strval": "No_layout", + "InheritedType": 13 + }, + { + "nid": 40, + "strtype": "OBJECT/Transform", + "InheritedType": 1 + }, + { + "nid": 90, + "strtype": "OBJECT/Flags", + "InheritedType": 1 + }, + { + "nid": 100, + "strtype": "SCREEN/Hidden", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 110, + "strtype": "OBJECT/Clickable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 130, + "strtype": "OBJECT/Press_lock", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 225, + "flags": 1048576, + "strtype": "OBJECT/Scrolling", + "InheritedType": 1 + }, + { + "nid": 230, + "strtype": "OBJECT/Scrollable", + "strval": "False", + "InheritedType": 2 + }, + { + "nid": 300, + "strtype": "OBJECT/Scrollbar_mode", + "strval": "AUTO", + "InheritedType": 3 + }, + { + "nid": 310, + "strtype": "OBJECT/Scroll_direction", + "strval": "ALL", + "InheritedType": 3 + }, + { + "nid": 314, + "strtype": "OBJECT/Scroll_snap_x", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 315, + "strtype": "OBJECT/Scroll_snap_y", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 320, + "flags": 1048576, + "strtype": "OBJECT/States", + "InheritedType": 1 + }, + { + "nid": 1010, + "flags": 1048576, + "strtype": "SCREEN/Screen", + "InheritedType": 1 + }, + { + "nid": 1020, + "strtype": "SCREEN/Temporary", + "strval": "False", + "InheritedType": 2 + }, + { + "part": "lv.PART.MAIN", + "childs": [ + { + "nid": 10000, + "strtype": "_style/StyleState", + "strval": "DEFAULT", + "childs": [ + { + "nid": 10520, + "strtype": "_style/Bg_Color", + "intarray": [ + 30, + 30, + 30, + 255 + ], + "InheritedType": 7 + } + ], + "InheritedType": 1 + } + ], + "nid": 1040, + "strtype": "SCREEN/Style_main", + "strval": "lv.PART.MAIN, Rectangle, Pad, Text", + "InheritedType": 11 + }, + { + "part": "lv.PART.SCROLLBAR", + "childs": [], + "nid": 1050, + "strtype": "SCREEN/Style_scrollbar", + "strval": "lv.PART.SCROLLBAR, Rectangle, Pad", + "InheritedType": 11 + }, + { + "disabled": false, + "nid": 1000304, + "strtype": "_event/EventHandler", + "strval": "GESTURE_RIGHT(GESTURE)", + "childs": [ + { + "nid": 1000305, + "strtype": "_event/name", + "strval": "ScreenInfoSwipeRight", + "InheritedType": 10 + }, + { + "nid": 1000306, + "strtype": "_event/condition_C", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000307, + "strtype": "_event/condition_P", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000359, + "strtype": "_event/action", + "strval": "CHANGE SCREEN", + "childs": [ + { + "nid": 1000360, + "strtype": "CHANGE SCREEN/Name", + "strval": "CHANGE SCREEN", + "InheritedType": 10 + }, + { + "nid": 1000361, + "strtype": "CHANGE SCREEN/Call", + "strval": "ChangeScreen( <{Screen_to}>, lv.SCR_LOAD_ANIM.<{Fade_mode}>, <{Speed}>, <{Delay}>)", + "InheritedType": 10 + }, + { + "nid": 1000362, + "strtype": "CHANGE SCREEN/CallC", + "strval": "_ui_screen_change( &<{Screen_to}>, LV_SCR_LOAD_ANIM_<{Fade_mode}>, <{Speed}>, <{Delay}>, &<{Screen_to}>_screen_init);", + "InheritedType": 10 + }, + { + "nid": 1000363, + "strtype": "CHANGE SCREEN/Screen_to", + "strval": "GUID63572141-853113S4094354", + "InheritedType": 9 + }, + { + "nid": 1000364, + "strtype": "CHANGE SCREEN/Fade_mode", + "strval": "NONE", + "InheritedType": 3 + }, + { + "nid": 1000365, + "strtype": "CHANGE SCREEN/Speed", + "InheritedType": 6 + }, + { + "nid": 1000366, + "strtype": "CHANGE SCREEN/Delay", + "InheritedType": 6 + } + ], + "InheritedType": 10 + } + ], + "InheritedType": 4 + }, + { + "disabled": false, + "nid": 1000367, + "strtype": "_event/EventHandler", + "strval": "SCREEN_LOADED", + "childs": [ + { + "nid": 1000368, + "strtype": "_event/name", + "strval": "ScreenInfoLoaded", + "InheritedType": 10 + }, + { + "nid": 1000369, + "strtype": "_event/condition_C", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000370, + "strtype": "_event/condition_P", + "strval": "", + "InheritedType": 10 + }, + { + "nid": 1000371, + "strtype": "_event/action", + "strval": "CALL FUNCTION", + "childs": [ + { + "nid": 1000372, + "strtype": "CALL FUNCTION/Name", + "strval": "CALL FUNCTION", + "InheritedType": 10 + }, + { + "nid": 1000373, + "strtype": "CALL FUNCTION/Call", + "strval": "<{Function_name}>( event_struct )", + "InheritedType": 10 + }, + { + "nid": 1000374, + "strtype": "CALL FUNCTION/CallC", + "strval": "<{Function_name}>( e );", + "InheritedType": 10 + }, + { + "nid": 1000375, + "strtype": "CALL FUNCTION/Function_name", + "strval": "ScreenInfoLoaded", + "InheritedType": 10 + }, + { + "nid": 1000376, + "strtype": "CALL FUNCTION/Dont_export_function", + "strval": "False", + "InheritedType": 2 + } + ], + "InheritedType": 10 + } + ], + "InheritedType": 4 + } + ], + "saved_objtypeKey": "SCREEN" + } + ], + "properties": [ + { + "nid": 1000201, + "strtype": "STARTEVENTS/Name", + "strval": "___initial_actions0", + "InheritedType": 10 + } + ], + "saved_objtypeKey": "STARTEVENTS" + }, + "animations": [], + "selected_theme": "Default", + "selected_screen": "GUID10820859-155348S7984356", + "info": { + "name": "SquareLine_Project.spj", + "depth": 1, + "width": 320, + "height": 240, + "rotation": 90, + "offset_x": 0, + "offset_y": 0, + "shape": "RECTANGLE", + "multilang": "DISABLE", + "description": "", + "board": "ESP WROVER KIT", + "board_version": "2.0.0", + "editor_version": "1.6.0", + "image": "", + "export_temp_image": false, + "force_export_images": false, + "flat_export": false, + "advanced_alpha": false, + "pointfilter": false, + "theme_simplified": false, + "theme_dark": false, + "theme_color1": 5, + "theme_color2": 0, + "custom_variable_prefix": "uic", + "separate_screen_save": false, + "hierarchy_state_save": false, + "backup_cnt": 97, + "autosave_cnt": 0, + "group_color_cnt": 0, + "imagebytearrayprefix": null, + "lvgl_version": "9.1.0", + "callfuncsexport": "CPP_FILE", + "imageexport": "SOURCE", + "lvgl_include_path": "", + "naming": "Screen_Name", + "naming_force_lowercase": false, + "naming_add_subcomponent": false, + "nidcnt": 1001753, + "BitDepth": 16, + "Name": "SquareLine_Project" + } +} \ No newline at end of file diff --git a/ui/PyroVision_events.py b/ui/PyroVision_events.py new file mode 100644 index 0000000..2adcf99 --- /dev/null +++ b/ui/PyroVision_events.py @@ -0,0 +1,68 @@ + +def ui_ext_init(event_struct): + return + + +def Test(event_struct): + return + + +def ui_on_Button_ROI_Clicked(event_struct): + return + + +def Update_ScreenInfo(event_struct): + return + + +def Update_ScreenMenu(event_struct): + return + + +def A(event_struct): + return + + +def C(event_struct): + return + + +def ButtonMainWiFiClicked(event_struct): + return + + +def ScreenInfoLoaded(event_struct): + return + + +def ScreenMainLoaded(event_struct): + return + + +def ScreenMenuLoaded(event_struct): + return + + +def Menu(event_struct): + return + + +def ContainerMenuLoaded(event_struct): + return + + +def ScreenSplashLoaded(event_struct): + return + + +def ButtonMenuSaveClicked(event_struct): + return + + +def ScreenSplahLoaded(event_struct): + return + + +def ButtonMainSaveClicked(event_struct): + return + diff --git a/ui/Themes.slt b/ui/Themes.slt new file mode 100644 index 0000000..a7bdc7d --- /dev/null +++ b/ui/Themes.slt @@ -0,0 +1,8 @@ +{ + "deftheme": { + "name": "Default", + "properties": [] + }, + "themes": [], + "selected_theme": "Default" +} \ No newline at end of file diff --git a/ui/assets/Font-Awesome b/ui/assets/Font-Awesome new file mode 160000 index 0000000..16ac6af --- /dev/null +++ b/ui/assets/Font-Awesome @@ -0,0 +1 @@ +Subproject commit 16ac6af0d816e1b132bb2e3f06aa59a1bc5c6d23 diff --git a/ui/assets/Logo_80x44.png b/ui/assets/Logo_80x44.png new file mode 100644 index 0000000..86f1926 Binary files /dev/null and b/ui/assets/Logo_80x44.png differ diff --git a/ui/assets/Text_218x40.png b/ui/assets/Text_218x40.png new file mode 100644 index 0000000..3b8c309 Binary files /dev/null and b/ui/assets/Text_218x40.png differ diff --git a/ui/project.info b/ui/project.info new file mode 100644 index 0000000..ef7f898 --- /dev/null +++ b/ui/project.info @@ -0,0 +1,7 @@ +{ + "project_name": "SquareLine_Project.spj", + "datetime": "2026-02-07T23:00:07.4624365+01:00", + "editor_version": "1.6.0", + "project_version": 109, + "user": "Kampert Daniel" +} \ No newline at end of file diff --git a/webserver/logo.png b/webserver/logo.png new file mode 100644 index 0000000..60e7e96 Binary files /dev/null and b/webserver/logo.png differ diff --git a/webserver/provision.html b/webserver/provision.html new file mode 100644 index 0000000..8242921 --- /dev/null +++ b/webserver/provision.html @@ -0,0 +1,338 @@ + + + + + + PyroVision WiFi Setup + + + +
+
+ +

PyroVision

+

WiFi-Konfiguration

+
+
+
+ +
+
+ + +
+ +
+ + +
+ +
+ +
+ + +
+
+ + +
+
+
+ + + +