From 24b6536e3a275b39df66412e70647b16fb72fe1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dzi=C4=85dziak?= Date: Thu, 12 Mar 2026 04:46:42 +0100 Subject: [PATCH 01/34] Port AI tuning ML, error system, G&G scale fix from OpenTrickler-v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New files ported from Jump73/OpenTrickler-v2 (rp2350 branch): - ai_tuning.c/h: AI/ML auto-tuning system with drop telemetry, adaptive step-halving and Gaussian Process PID refinement, records last 10 drops with coarse/fine Kp/Kd history - rest_ai_tuning.c/h: REST API endpoints for AI tuning session - error.c/h: centralized error handling system (100+ error codes, 8-entry circular buffer, reports to LED/display/REST) - rest_errors.c/h: REST API for error querying and clearing - flash_storage.c/h: flash-based persistence for ML history (4KB sector at end of 2MB flash, faster than I2C EEPROM) - app_state.c/h: application state management for mode transitions - display_config.c/h: display type/rotation/brightness persistence - styles.css.h, favicon.ico.h, web_portal_basic.html.h: web resources Modified files: - src/gng_scale.c: fix partial-frame carryover on timeout — reset buffer index and flush stale RX data at start of each poll cycle; add 200ms response timeout with mutex-protected scale_write() - src/rest_endpoints.c: register AI tuning, error, display_config endpoints; add flash_storage_init/ai_tuning_init/rest_ai_tuning_init calls; add /basic, /styles.css, /favicon.ico handlers Co-Authored-By: Claude Sonnet 4.6 --- src/ai_tuning.c | 862 +++++++++++++++++++++++ src/ai_tuning.h | 176 +++++ src/app_state.c | 9 + src/app_state.h | 23 + src/display_config.c | 132 ++++ src/display_config.h | 47 ++ src/error.c | 322 +++++++++ src/error.h | 133 ++++ src/favicon.ico.h | 1307 +++++++++++++++++++++++++++++++++++ src/flash_storage.c | 83 +++ src/flash_storage.h | 50 ++ src/gng_scale.c | 67 +- src/rest_ai_tuning.c | 601 ++++++++++++++++ src/rest_ai_tuning.h | 98 +++ src/rest_endpoints.c | 60 ++ src/rest_errors.c | 97 +++ src/rest_errors.h | 23 + src/styles.css.h | 10 + src/web_portal_basic.html.h | 10 + 19 files changed, 4089 insertions(+), 21 deletions(-) create mode 100644 src/ai_tuning.c create mode 100644 src/ai_tuning.h create mode 100644 src/app_state.c create mode 100644 src/app_state.h create mode 100644 src/display_config.c create mode 100644 src/display_config.h create mode 100644 src/error.c create mode 100644 src/error.h create mode 100644 src/favicon.ico.h create mode 100644 src/flash_storage.c create mode 100644 src/flash_storage.h create mode 100644 src/rest_ai_tuning.c create mode 100644 src/rest_ai_tuning.h create mode 100644 src/rest_errors.c create mode 100644 src/rest_errors.h create mode 100644 src/styles.css.h create mode 100644 src/web_portal_basic.html.h diff --git a/src/ai_tuning.c b/src/ai_tuning.c new file mode 100644 index 00000000..e4bd89e7 --- /dev/null +++ b/src/ai_tuning.c @@ -0,0 +1,862 @@ +/** + * @file ai_tuning.c + * @brief AI Auto-Tuning Implementation for RP2040 + * + * Binary step algorithm for tuning Kp/Kd parameters. + */ + +#include "ai_tuning.h" +#include "charge_mode.h" +#include "flash_storage.h" +#include "eeprom.h" +#include "profile.h" +#include +#include +#include "FreeRTOS.h" +#include "semphr.h" +#include "encoder.h" + +// Access charge mode config for thresholds +extern charge_mode_config_t charge_mode_config; + +// Thread safety mutex +static SemaphoreHandle_t g_ai_tuning_mutex = NULL; + +// ============================================================================= +// PARAMETER RANGES - Coarse is gentle (0-1), Fine is aggressive (0-10) +// ============================================================================= +#define COARSE_KP_MIN 0.01f +#define COARSE_KP_MAX 1.0f +#define COARSE_KD_MIN 0.01f +#define COARSE_KD_MAX 2.0f + +#define FINE_KP_MIN 0.01f +#define FINE_KP_MAX 5.0f +#define FINE_KD_MIN 0.01f +#define FINE_KD_MAX 20.0f + +// Minimum step sizes based on range +#define COARSE_MIN_STEP 0.02f +#define FINE_MIN_STEP 0.1f + +// Global tuning session state +static ai_tuning_session_t g_session; +static ai_tuning_config_t g_config; +static ai_tuning_config_eeprom_t g_config_eeprom; +static ai_tuning_history_t g_history; +static bool g_initialized = false; +static bool g_history_loaded = false; +static bool g_config_loaded = false; + +// Original profile values saved at tuning start (for cancel/restore) +static float g_orig_coarse_kp = 0.0f; +static float g_orig_coarse_kd = 0.0f; +static float g_orig_fine_kp = 0.0f; +static float g_orig_fine_kd = 0.0f; + +// Tuning state for adaptive Kp/Kd adjustment +typedef enum { + TUNING_PHASE_ADAPTIVE_KP, + TUNING_PHASE_ADAPTIVE_KD +} tuning_phase_t; + +static tuning_phase_t g_coarse_tuning_phase; +static tuning_phase_t g_fine_tuning_phase; + +// Adaptive step sizes +static float g_coarse_kp_step; +static float g_coarse_kd_step; +static float g_fine_kp_step; +static float g_fine_kd_step; + +// Track for oscillation detection +static bool g_coarse_had_overthrow; +static bool g_fine_had_overthrow; + +// Forward declarations +static void calculate_next_params_phase1(const ai_drop_telemetry_t* drop); +static void calculate_next_params_phase2(const ai_drop_telemetry_t* drop); +static void load_history_from_flash(void); +static void save_history_to_flash(void); +static void load_config_from_eeprom(void); +static bool save_config_to_eeprom(void); + +// ============================================================================= +// Initialization +// ============================================================================= + +void ai_tuning_init(void) { + if (g_initialized) { + return; + } + + // Create mutex for thread safety + if (g_ai_tuning_mutex == NULL) { + g_ai_tuning_mutex = xSemaphoreCreateRecursiveMutex(); + } + + // Initialize default configuration + g_config.max_overthrow_percent = 6.67f; + + g_config.coarse_kp_min = COARSE_KP_MIN; + g_config.coarse_kp_max = COARSE_KP_MAX; + g_config.coarse_kd_min = COARSE_KD_MIN; + g_config.coarse_kd_max = COARSE_KD_MAX; + + g_config.fine_kp_min = FINE_KP_MIN; + g_config.fine_kp_max = FINE_KP_MAX; + g_config.fine_kd_min = FINE_KD_MIN; + g_config.fine_kd_max = FINE_KD_MAX; + + g_config.noise_margin = 0.05f; // ±0.05gn for fine Kd phase + + // Tuning acceptance factors + g_config.coarse_kp_max_factor = 1.02f; // Kp phase: threshold <= drop <= threshold * 1.02 + g_config.coarse_kd_max_factor = 1.01f; // Kd phase: drop <= threshold * 1.01 + g_config.fine_kp_max_factor = 1.003f; // Kp phase: target <= drop <= target * 1.003 + + // Clear session + memset(&g_session, 0, sizeof(ai_tuning_session_t)); + g_session.state = AI_TUNING_IDLE; + + // Load config from EEPROM (user settings) + load_config_from_eeprom(); + + // Load ML history from flash (runtime learning data) + load_history_from_flash(); + + // Register EEPROM save handler + eeprom_register_handler(save_config_to_eeprom); + + g_initialized = true; +} + +// ============================================================================= +// Config Accessor +// ============================================================================= + +ai_tuning_config_t* ai_tuning_get_config(void) { + return &g_config; +} + +// ============================================================================= +// Tuning Session Start +// ============================================================================= + +bool ai_tuning_start(profile_t* profile) { + if (!g_initialized) { + ai_tuning_init(); + } + + if (g_ai_tuning_mutex != NULL) { + if (xSemaphoreTakeRecursive(g_ai_tuning_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) { + return false; + } + } + + if (profile == NULL) { + if (g_ai_tuning_mutex != NULL) xSemaphoreGiveRecursive(g_ai_tuning_mutex); + return false; + } + + // Save original profile values for restore on cancel + g_orig_coarse_kp = profile->coarse_kp; + g_orig_coarse_kd = profile->coarse_kd; + g_orig_fine_kp = profile->fine_kp; + g_orig_fine_kd = profile->fine_kd; + + // Initialize session + memset(&g_session, 0, sizeof(ai_tuning_session_t)); + g_session.state = AI_TUNING_PHASE_1_COARSE; + g_session.target_profile = profile; + g_session.drops_completed = 0; + g_session.max_drops_allowed = 30; + g_session.phase2_start_idx = 0; + + // Get profile index for ML suggestions + uint8_t profile_idx = 0; + for (int i = 0; i < MAX_PROFILE_CNT; i++) { + if (profile_select(i) == profile) { + profile_idx = i; + break; + } + } + + // Start from suggestions or zero + float suggested_coarse_kp, suggested_coarse_kd; + float suggested_fine_kp, suggested_fine_kd; + bool have_suggestions = ai_tuning_get_suggestions(profile_idx, + &suggested_coarse_kp, &suggested_coarse_kd, + &suggested_fine_kp, &suggested_fine_kd); + + if (have_suggestions) { + g_session.coarse_kp_best = suggested_coarse_kp * 0.7f; + g_session.coarse_kd_best = suggested_coarse_kd * 0.7f; + g_session.fine_kp_best = suggested_fine_kp * 0.7f; + g_session.fine_kd_best = suggested_fine_kd * 0.7f; + } else { + g_session.coarse_kp_best = g_config.coarse_kp_min; + g_session.coarse_kd_best = g_config.coarse_kd_min; + g_session.fine_kp_best = g_config.fine_kp_min; + g_session.fine_kd_best = g_config.fine_kd_min; + } + + g_coarse_tuning_phase = TUNING_PHASE_ADAPTIVE_KP; + g_fine_tuning_phase = TUNING_PHASE_ADAPTIVE_KP; + + // Start at midpoint for binary search + g_session.coarse_kp_best = (g_config.coarse_kp_min + g_config.coarse_kp_max) / 2.0f; + g_session.coarse_kd_best = (g_config.coarse_kd_min + g_config.coarse_kd_max) / 2.0f; + g_session.fine_kp_best = (g_config.fine_kp_min + g_config.fine_kp_max) / 2.0f; + g_session.fine_kd_best = (g_config.fine_kd_min + g_config.fine_kd_max) / 2.0f; + + g_coarse_kp_step = (g_config.coarse_kp_max - g_config.coarse_kp_min) / 2.0f; + g_coarse_kd_step = (g_config.coarse_kd_max - g_config.coarse_kd_min) / 2.0f; + g_fine_kp_step = (g_config.fine_kp_max - g_config.fine_kp_min) / 2.0f; + g_fine_kd_step = (g_config.fine_kd_max - g_config.fine_kd_min) / 2.0f; + + g_coarse_had_overthrow = false; + g_fine_had_overthrow = false; + + if (g_ai_tuning_mutex != NULL) xSemaphoreGiveRecursive(g_ai_tuning_mutex); + return true; +} + +// ============================================================================= +// Get Next Parameters +// ============================================================================= + +bool ai_tuning_get_next_params(float* coarse_kp, float* coarse_kd, + float* fine_kp, float* fine_kd) { + if (g_ai_tuning_mutex != NULL) { + if (xSemaphoreTakeRecursive(g_ai_tuning_mutex, pdMS_TO_TICKS(50)) != pdTRUE) { + return false; + } + } + + bool result = false; + + if (g_session.state == AI_TUNING_PHASE_1_COARSE) { + *coarse_kp = g_session.coarse_kp_best; + *coarse_kd = g_session.coarse_kd_best; + *fine_kp = 0.0f; + *fine_kd = 0.0f; + result = true; + } + else if (g_session.state == AI_TUNING_PHASE_2_FINE) { + *coarse_kp = g_session.recommended_coarse_kp; + *coarse_kd = g_session.recommended_coarse_kd; + *fine_kp = g_session.fine_kp_best; + *fine_kd = g_session.fine_kd_best; + result = true; + } + + if (g_ai_tuning_mutex != NULL) xSemaphoreGiveRecursive(g_ai_tuning_mutex); + return result; +} + +// ============================================================================= +// Record Drop Telemetry +// ============================================================================= + +bool ai_tuning_record_drop(const ai_drop_telemetry_t* telemetry) { + if (g_ai_tuning_mutex != NULL) { + if (xSemaphoreTakeRecursive(g_ai_tuning_mutex, pdMS_TO_TICKS(50)) != pdTRUE) { + return false; + } + } + + if (!ai_tuning_is_active() || telemetry == NULL) { + if (g_ai_tuning_mutex != NULL) xSemaphoreGiveRecursive(g_ai_tuning_mutex); + return false; + } + + if (g_session.drops_completed >= g_session.max_drops_allowed) { + g_session.state = AI_TUNING_ERROR; + snprintf(g_session.error_message, sizeof(g_session.error_message), + "Did not converge in %d drops", g_session.max_drops_allowed); + g_session.recommended_coarse_kp = g_session.coarse_kp_best; + g_session.recommended_coarse_kd = g_session.coarse_kd_best; + g_session.recommended_fine_kp = g_session.fine_kp_best; + g_session.recommended_fine_kd = g_session.fine_kd_best; + if (g_ai_tuning_mutex != NULL) xSemaphoreGiveRecursive(g_ai_tuning_mutex); + return false; + } + + // Store telemetry in circular buffer + uint8_t idx = g_session.drop_write_idx; + memcpy(&g_session.drops[idx], telemetry, sizeof(ai_drop_telemetry_t)); + g_session.drop_write_idx = (g_session.drop_write_idx + 1) % AI_TUNING_DROP_BUF_SIZE; + g_session.drops_completed++; + + // Dispatch to correct phase handler + if (g_session.state == AI_TUNING_PHASE_1_COARSE) { + calculate_next_params_phase1(telemetry); + } else if (g_session.state == AI_TUNING_PHASE_2_FINE) { + calculate_next_params_phase2(telemetry); + } + + if (g_ai_tuning_mutex != NULL) xSemaphoreGiveRecursive(g_ai_tuning_mutex); + return true; +} + +// ============================================================================= +// Phase 1: Tune Coarse Trickler (Binary Search) +// 1. Increase Kp until threshold <= drop <= threshold * 1.02 +// 2. Increase Kd until drop <= threshold * 1.02 +// If time > target, bump Kp by step and continue Kd +// ============================================================================= + +static void calculate_next_params_phase1(const ai_drop_telemetry_t* drop) { + float coarse_stop_threshold = charge_mode_config.eeprom_charge_mode_data.coarse_stop_threshold; + float target_time_ms = (float)charge_mode_config.eeprom_charge_mode_data.coarse_time_target_ms; + + // "threshold" = coarse stop point (where coarse should stop) + float threshold = drop->target_weight - coarse_stop_threshold; + float final_weight = drop->final_weight; + + // Kp acceptance bounds: threshold <= drop <= target / 1.015 + float accept_min = threshold; + float accept_max = drop->target_weight / 1.015f; + // Kd acceptance: drop <= target - (threshold * 0.02) + float kd_accept_max = drop->target_weight - (threshold * 0.02f); + + bool time_ok = (drop->coarse_time_ms <= target_time_ms); + + // Kp phase: change Kp until threshold <= drop <= target / 1.015 + if (g_coarse_tuning_phase == TUNING_PHASE_ADAPTIVE_KP) { + // Halve step first (binary search) + g_coarse_kp_step /= 2.0f; + + if (final_weight >= accept_min && final_weight <= accept_max) { + // In range - move to Kd phase + g_coarse_tuning_phase = TUNING_PHASE_ADAPTIVE_KD; + g_session.coarse_kd_best = (g_config.coarse_kd_min + g_config.coarse_kd_max) / 2.0f; + g_coarse_kd_step = (g_config.coarse_kd_max - g_config.coarse_kd_min) / 2.0f; + } else if (final_weight > accept_max) { + // Too high - decrease Kp + g_session.coarse_kp_best -= g_coarse_kp_step; + } else if (final_weight < accept_min) { + // Too low - increase Kp + g_session.coarse_kp_best += g_coarse_kp_step; + } + + // Step too small - move to Kd phase anyway + if (g_coarse_kp_step < COARSE_MIN_STEP) { + g_coarse_tuning_phase = TUNING_PHASE_ADAPTIVE_KD; + g_session.coarse_kd_best = (g_config.coarse_kd_min + g_config.coarse_kd_max) / 2.0f; + g_coarse_kd_step = (g_config.coarse_kd_max - g_config.coarse_kd_min) / 2.0f; + } + } + // Kd phase: change Kd until drop <= target - (threshold * 0.02) + // If time > target, increase Kp by step, then go to Fine + else if (g_coarse_tuning_phase == TUNING_PHASE_ADAPTIVE_KD) { + // Halve step first (binary search) + g_coarse_kd_step /= 2.0f; + + // If time bad, bump Kp + if (!time_ok) { + g_session.coarse_kp_best += g_coarse_kp_step; + } + + if (final_weight <= kd_accept_max) { + // In range - done with coarse, go directly to Fine + g_session.recommended_coarse_kp = g_session.coarse_kp_best; + g_session.recommended_coarse_kd = g_session.coarse_kd_best; + g_session.phase2_start_idx = g_session.drops_completed; + g_session.state = AI_TUNING_PHASE_2_FINE; + g_session.fine_kp_best = (g_config.fine_kp_min + g_config.fine_kp_max) / 2.0f; + g_session.fine_kd_best = (g_config.fine_kd_min + g_config.fine_kd_max) / 2.0f; + g_fine_kp_step = (g_config.fine_kp_max - g_config.fine_kp_min) / 2.0f; + g_fine_kd_step = (g_config.fine_kd_max - g_config.fine_kd_min) / 2.0f; + g_fine_tuning_phase = TUNING_PHASE_ADAPTIVE_KP; + } else { + // Too high - increase Kd + g_session.coarse_kd_best += g_coarse_kd_step; + } + + // Step too small - move to Fine + if (g_coarse_kd_step < COARSE_MIN_STEP) { + g_session.recommended_coarse_kp = g_session.coarse_kp_best; + g_session.recommended_coarse_kd = g_session.coarse_kd_best; + g_session.phase2_start_idx = g_session.drops_completed; + g_session.state = AI_TUNING_PHASE_2_FINE; + g_session.fine_kp_best = (g_config.fine_kp_min + g_config.fine_kp_max) / 2.0f; + g_session.fine_kd_best = (g_config.fine_kd_min + g_config.fine_kd_max) / 2.0f; + g_fine_kp_step = (g_config.fine_kp_max - g_config.fine_kp_min) / 2.0f; + g_fine_kd_step = (g_config.fine_kd_max - g_config.fine_kd_min) / 2.0f; + g_fine_tuning_phase = TUNING_PHASE_ADAPTIVE_KP; + } + } + + // Clamp to valid range + g_session.coarse_kp_best = fminf(fmaxf(g_session.coarse_kp_best, g_config.coarse_kp_min), g_config.coarse_kp_max); + g_session.coarse_kd_best = fminf(fmaxf(g_session.coarse_kd_best, g_config.coarse_kd_min), g_config.coarse_kd_max); +} + +// ============================================================================= +// Phase 2: Tune Fine Trickler (Binary Search) +// 1. Increase Kp until target <= drop <= target * 1.003 +// 2. Increase Kd until drop = target ± 0.05gn +// ============================================================================= + +static void calculate_next_params_phase2(const ai_drop_telemetry_t* drop) { + float target = drop->target_weight; + float final_weight = drop->final_weight; + float target_time_ms = (float)charge_mode_config.eeprom_charge_mode_data.total_time_target_ms; + float noise_margin = g_config.noise_margin; // 0.05gn + + // Acceptance bounds: + // Kp: target <= drop <= target * 1.003 + // Kd: drop = target ± 0.05gn + float kp_max = target * g_config.fine_kp_max_factor; // target * 1.003 + + bool time_ok = (drop->total_time_ms <= target_time_ms); + + // Kp phase: change Kp until target <= drop <= target * 1.003 + if (g_fine_tuning_phase == TUNING_PHASE_ADAPTIVE_KP) { + // Halve step first (binary search) + g_fine_kp_step /= 2.0f; + + if (final_weight >= target && final_weight <= kp_max) { + // In range - move to Kd phase + g_fine_tuning_phase = TUNING_PHASE_ADAPTIVE_KD; + g_session.fine_kd_best = (g_config.fine_kd_min + g_config.fine_kd_max) / 2.0f; + g_fine_kd_step = (g_config.fine_kd_max - g_config.fine_kd_min) / 2.0f; + } else if (final_weight > kp_max) { + // Too high - decrease Kp + g_session.fine_kp_best -= g_fine_kp_step; + } else if (final_weight < target) { + // Too low - increase Kp + g_session.fine_kp_best += g_fine_kp_step; + } + + // Step too small - move to Kd phase anyway + if (g_fine_kp_step < FINE_MIN_STEP) { + g_fine_tuning_phase = TUNING_PHASE_ADAPTIVE_KD; + g_session.fine_kd_best = (g_config.fine_kd_min + g_config.fine_kd_max) / 2.0f; + g_fine_kd_step = (g_config.fine_kd_max - g_config.fine_kd_min) / 2.0f; + } + } + // Kd phase: change Kd until drop = target ± 0.05gn + // If time > target, increase Kp by step + else if (g_fine_tuning_phase == TUNING_PHASE_ADAPTIVE_KD) { + // Halve step first (binary search) + g_fine_kd_step /= 2.0f; + + // If time bad, bump Kp + if (!time_ok) { + g_session.fine_kp_best += g_fine_kp_step; + } + + float error = fabsf(final_weight - target); + + if (error <= noise_margin) { + // Within ±0.05gn of target - done! + g_session.recommended_fine_kp = g_session.fine_kp_best; + g_session.recommended_fine_kd = g_session.fine_kd_best; + g_session.state = AI_TUNING_COMPLETE; + } else if (final_weight > target + noise_margin) { + // Too high - increase Kd + g_session.fine_kd_best += g_fine_kd_step; + } else if (final_weight < target - noise_margin) { + // Too low - decrease Kd + g_session.fine_kd_best -= g_fine_kd_step; + } + + // Step too small - done + if (g_fine_kd_step < FINE_MIN_STEP) { + g_session.recommended_fine_kp = g_session.fine_kp_best; + g_session.recommended_fine_kd = g_session.fine_kd_best; + g_session.state = AI_TUNING_COMPLETE; + } + } + + // Clamp to valid range + g_session.fine_kp_best = fminf(fmaxf(g_session.fine_kp_best, g_config.fine_kp_min), g_config.fine_kp_max); + g_session.fine_kd_best = fminf(fmaxf(g_session.fine_kd_best, g_config.fine_kd_min), g_config.fine_kd_max); +} + +// ============================================================================= +// Finalize Recommendations +// ============================================================================= + +static void finalize_recommendations(void) { + g_session.state = AI_TUNING_COMPLETE; + + float total_overthrow = 0.0f; + float total_time = 0.0f; + int count = g_session.drops_completed; + int buf_count = (count < AI_TUNING_DROP_BUF_SIZE) ? count : AI_TUNING_DROP_BUF_SIZE; + + for (int i = 0; i < buf_count; i++) { + total_overthrow += g_session.drops[i].overthrow; + total_time += g_session.drops[i].total_time_ms; + } + + if (buf_count > 0) { + g_session.avg_overthrow = total_overthrow / buf_count; + g_session.avg_total_time = total_time / buf_count; + } +} + +// ============================================================================= +// Session State Queries +// ============================================================================= + +bool ai_tuning_is_complete(void) { + return g_session.state == AI_TUNING_COMPLETE; +} + +ai_tuning_session_t* ai_tuning_get_session(void) { + return &g_session; +} + +void ai_tuning_get_session_copy(ai_tuning_session_t* out) { + if (g_ai_tuning_mutex != NULL) { + xSemaphoreTakeRecursive(g_ai_tuning_mutex, pdMS_TO_TICKS(50)); + } + memcpy(out, &g_session, sizeof(ai_tuning_session_t)); + if (g_ai_tuning_mutex != NULL) { + xSemaphoreGiveRecursive(g_ai_tuning_mutex); + } +} + +void ai_tuning_get_history_copy(ai_tuning_history_t* out) { + if (g_ai_tuning_mutex != NULL) { + xSemaphoreTakeRecursive(g_ai_tuning_mutex, pdMS_TO_TICKS(50)); + } + memcpy(out, &g_history, sizeof(ai_tuning_history_t)); + if (g_ai_tuning_mutex != NULL) { + xSemaphoreGiveRecursive(g_ai_tuning_mutex); + } +} + +bool ai_tuning_get_recommended_params(float* coarse_kp, float* coarse_kd, + float* fine_kp, float* fine_kd) { + if (g_session.state != AI_TUNING_COMPLETE && g_session.state != AI_TUNING_ERROR) { + return false; + } + + *coarse_kp = g_session.recommended_coarse_kp; + *coarse_kd = g_session.recommended_coarse_kd; + *fine_kp = g_session.recommended_fine_kp; + *fine_kd = g_session.recommended_fine_kd; + + return true; +} + +bool ai_tuning_apply_params(void) { + if ((g_session.state != AI_TUNING_COMPLETE && g_session.state != AI_TUNING_ERROR) || + g_session.target_profile == NULL) { + return false; + } + + g_session.target_profile->coarse_kp = g_session.recommended_coarse_kp; + g_session.target_profile->coarse_kd = g_session.recommended_coarse_kd; + g_session.target_profile->fine_kp = g_session.recommended_fine_kp; + g_session.target_profile->fine_kd = g_session.recommended_fine_kd; + + g_session.state = AI_TUNING_IDLE; + + return true; +} + +void ai_tuning_cancel(void) { + bool was_active = ai_tuning_is_active(); + + // Restore original profile values if we have a target profile + if (g_session.target_profile != NULL) { + g_session.target_profile->coarse_kp = g_orig_coarse_kp; + g_session.target_profile->coarse_kd = g_orig_coarse_kd; + g_session.target_profile->fine_kp = g_orig_fine_kp; + g_session.target_profile->fine_kd = g_orig_fine_kd; + } + + // Clear session state + g_session.state = AI_TUNING_IDLE; + memset(&g_session, 0, sizeof(ai_tuning_session_t)); + + // Exit charge mode so the trickler stops (only if was actually running) + if (was_active) { + charge_mode_config.charge_mode_state = CHARGE_MODE_EXIT; + ButtonEncoderEvent_t button_event = BUTTON_RST_PRESSED; + xQueueSend(encoder_event_queue, &button_event, 0); + } +} + +bool ai_tuning_is_active(void) { + return g_session.state == AI_TUNING_PHASE_1_COARSE || + g_session.state == AI_TUNING_PHASE_2_FINE; +} + +ai_motor_mode_t ai_tuning_get_motor_mode(void) { + switch (g_session.state) { + case AI_TUNING_PHASE_1_COARSE: + return AI_MOTOR_MODE_COARSE_ONLY; + case AI_TUNING_PHASE_2_FINE: + return AI_MOTOR_MODE_FINE_ONLY; + default: + return AI_MOTOR_MODE_NORMAL; + } +} + +uint8_t ai_tuning_get_progress_percent(void) { + if (g_session.max_drops_allowed == 0) { + return 0; + } + return (100 * g_session.drops_completed) / g_session.max_drops_allowed; +} + +// ============================================================================= +// Flash Persistence +// ============================================================================= + +static void load_history_from_flash(void) { + if (g_history_loaded) { + return; + } + + bool ok = flash_ml_history_read((uint8_t*)&g_history, sizeof(ai_tuning_history_t)); + + if (!ok || g_history.revision != AI_TUNING_HISTORY_REV) { + memset(&g_history, 0, sizeof(ai_tuning_history_t)); + g_history.revision = AI_TUNING_HISTORY_REV; + } + + g_history_loaded = true; +} + +static void save_history_to_flash(void) { + flash_ml_history_write((const uint8_t*)&g_history, sizeof(ai_tuning_history_t)); +} + +// ============================================================================= +// EEPROM Config Persistence +// ============================================================================= + +static void load_config_from_eeprom(void) { + if (g_config_loaded) { + return; + } + + bool ok = eeprom_read(EEPROM_AI_TUNING_CONFIG_BASE_ADDR, + (uint8_t*)&g_config_eeprom, + sizeof(ai_tuning_config_eeprom_t)); + + if (!ok || g_config_eeprom.revision != AI_TUNING_CONFIG_REV) { + // Keep defaults set in ai_tuning_init() + } else { + // Apply EEPROM config to runtime config + g_config.coarse_kp_min = g_config_eeprom.coarse_kp_min; + g_config.coarse_kp_max = g_config_eeprom.coarse_kp_max; + g_config.coarse_kd_min = g_config_eeprom.coarse_kd_min; + g_config.coarse_kd_max = g_config_eeprom.coarse_kd_max; + g_config.fine_kp_min = g_config_eeprom.fine_kp_min; + g_config.fine_kp_max = g_config_eeprom.fine_kp_max; + g_config.fine_kd_min = g_config_eeprom.fine_kd_min; + g_config.fine_kd_max = g_config_eeprom.fine_kd_max; + g_config.noise_margin = g_config_eeprom.noise_margin; + } + + g_config_loaded = true; +} + +static bool save_config_to_eeprom(void) { + // Copy runtime config to EEPROM structure + g_config_eeprom.revision = AI_TUNING_CONFIG_REV; + g_config_eeprom.coarse_kp_min = g_config.coarse_kp_min; + g_config_eeprom.coarse_kp_max = g_config.coarse_kp_max; + g_config_eeprom.coarse_kd_min = g_config.coarse_kd_min; + g_config_eeprom.coarse_kd_max = g_config.coarse_kd_max; + g_config_eeprom.fine_kp_min = g_config.fine_kp_min; + g_config_eeprom.fine_kp_max = g_config.fine_kp_max; + g_config_eeprom.fine_kd_min = g_config.fine_kd_min; + g_config_eeprom.fine_kd_max = g_config.fine_kd_max; + g_config_eeprom.noise_margin = g_config.noise_margin; + + bool ok = eeprom_write(EEPROM_AI_TUNING_CONFIG_BASE_ADDR, + (uint8_t*)&g_config_eeprom, + sizeof(ai_tuning_config_eeprom_t)); + + return ok; +} + +void ai_tuning_save_config(void) { + save_config_to_eeprom(); +} + +ai_tuning_history_t* ai_tuning_get_history(void) { + load_history_from_flash(); + return &g_history; +} + +void ai_tuning_record_charge(uint8_t profile_idx, + float coarse_kp, float coarse_kd, + float fine_kp, float fine_kd, + float overthrow, + float coarse_time_ms, float fine_time_ms) { + load_history_from_flash(); + + ai_drop_record_t record = { + .coarse_kp = coarse_kp, + .coarse_kd = coarse_kd, + .fine_kp = fine_kp, + .fine_kd = fine_kd, + .overthrow = overthrow, + .coarse_time_ms = coarse_time_ms, + .fine_time_ms = fine_time_ms, + .total_time_ms = coarse_time_ms + fine_time_ms, + .profile_idx = profile_idx + }; + + g_history.drops[g_history.next_idx] = record; + g_history.next_idx = (g_history.next_idx + 1) % AI_TUNING_HISTORY_SIZE; + if (g_history.count < AI_TUNING_HISTORY_SIZE) { + g_history.count++; + } + + if (g_history.count >= 3) { + ai_tuning_calculate_refinements(profile_idx); + } + + save_history_to_flash(); +} + +void ai_tuning_calculate_refinements(uint8_t profile_idx) { + load_history_from_flash(); + + if (g_history.count < 3) { + g_history.has_suggestions = false; + return; + } + + float avg_overthrow = 0.0f; + float avg_fine_kp = 0.0f, avg_fine_kd = 0.0f; + + for (int i = 0; i < g_history.count; i++) { + ai_drop_record_t* d = &g_history.drops[i]; + avg_overthrow += d->overthrow; + avg_fine_kp += d->fine_kp; + avg_fine_kd += d->fine_kd; + } + + float inv_count = 1.0f / g_history.count; + avg_overthrow *= inv_count; + avg_fine_kp *= inv_count; + avg_fine_kd *= inv_count; + + // ML only adjusts fine parameters - coarse stays unchanged + float fine_kp_adj = 0.0f, fine_kd_adj = 0.0f; + + if (avg_overthrow > g_config.noise_margin) { + // Overthrow: increase Kd to slow down + fine_kd_adj = 0.1f; + } else if (avg_overthrow < -g_config.noise_margin) { + // Underthrow: increase Kp to speed up + fine_kp_adj = 0.1f; + } + + // Coarse values unchanged (set to 0 to indicate no suggestion) + g_history.suggested_coarse_kp = 0.0f; + g_history.suggested_coarse_kd = 0.0f; + g_history.suggested_fine_kp = fminf(fmaxf(avg_fine_kp + fine_kp_adj, g_config.fine_kp_min), g_config.fine_kp_max); + g_history.suggested_fine_kd = fminf(fmaxf(avg_fine_kd + fine_kd_adj, g_config.fine_kd_min), g_config.fine_kd_max); + g_history.has_suggestions = true; +} + +bool ai_tuning_get_refined_params(float* coarse_kp, float* coarse_kd, + float* fine_kp, float* fine_kd) { + load_history_from_flash(); + + if (!g_history.has_suggestions) { + return false; + } + + *coarse_kp = g_history.suggested_coarse_kp; + *coarse_kd = g_history.suggested_coarse_kd; + *fine_kp = g_history.suggested_fine_kp; + *fine_kd = g_history.suggested_fine_kd; + + return true; +} + +bool ai_tuning_get_suggestions(uint8_t profile_idx, + float* coarse_kp, float* coarse_kd, + float* fine_kp, float* fine_kd) { + load_history_from_flash(); + + if (g_history.count < 3) { + return false; + } + + float sum_coarse_kp = 0, sum_coarse_kd = 0; + float sum_fine_kp = 0, sum_fine_kd = 0; + int count = 0; + + for (int i = 0; i < g_history.count; i++) { + ai_drop_record_t* d = &g_history.drops[i]; + if (profile_idx == 0xFF || d->profile_idx == profile_idx) { + sum_coarse_kp += d->coarse_kp; + sum_coarse_kd += d->coarse_kd; + sum_fine_kp += d->fine_kp; + sum_fine_kd += d->fine_kd; + count++; + } + } + + if (count < 3) { + if (g_history.has_suggestions) { + *coarse_kp = g_history.suggested_coarse_kp; + *coarse_kd = g_history.suggested_coarse_kd; + *fine_kp = g_history.suggested_fine_kp; + *fine_kd = g_history.suggested_fine_kd; + return true; + } + return false; + } + + float inv_count = 1.0f / count; + *coarse_kp = sum_coarse_kp * inv_count; + *coarse_kd = sum_coarse_kd * inv_count; + *fine_kp = sum_fine_kp * inv_count; + *fine_kd = sum_fine_kd * inv_count; + + return true; +} + +bool ai_tuning_apply_refined_params(uint8_t profile_idx) { + load_history_from_flash(); + + if (!g_history.has_suggestions) { + return false; + } + + profile_t* profile = profile_select(profile_idx); + if (!profile) { + return false; + } + + // ML only adjusts fine parameters - coarse stays unchanged + profile->fine_kp = g_history.suggested_fine_kp; + profile->fine_kd = g_history.suggested_fine_kd; + + profile_data_save(); + + g_history.count = 0; + g_history.next_idx = 0; + g_history.has_suggestions = false; + save_history_to_flash(); + + return true; +} + +void ai_tuning_clear_history(void) { + memset(&g_history, 0, sizeof(ai_tuning_history_t)); + g_history.revision = AI_TUNING_HISTORY_REV; + save_history_to_flash(); +} + +float ai_tuning_get_scale_compensation(void) { + return 0.0f; // Disabled - was over-engineering +} diff --git a/src/ai_tuning.h b/src/ai_tuning.h new file mode 100644 index 00000000..5500d008 --- /dev/null +++ b/src/ai_tuning.h @@ -0,0 +1,176 @@ +#ifndef AI_TUNING_H_ +#define AI_TUNING_H_ + +#include +#include +#include "profile.h" + +#define AI_TUNING_HISTORY_SIZE 10 +#define AI_TUNING_HISTORY_REV 4 // Bumped: added scale_latency_compensation +#define AI_TUNING_DROP_BUF_SIZE 10 +#define AI_TUNING_CONFIG_REV 2 // EEPROM config revision (v2: removed coarse_overthrow_max_percent) + +/** + * AI Tuning System + * + * Automatically tunes Kp and Kd parameters for both coarse and fine tricklers. + * Uses adaptive step-halving algorithm with GP refinement. + */ + +typedef enum { + AI_TUNING_IDLE = 0, + AI_TUNING_PHASE_1_COARSE, + AI_TUNING_PHASE_2_FINE, + AI_TUNING_COMPLETE, + AI_TUNING_ERROR +} ai_tuning_state_t; + +typedef enum { + AI_MOTOR_MODE_NORMAL = 0, + AI_MOTOR_MODE_COARSE_ONLY, + AI_MOTOR_MODE_FINE_ONLY +} ai_motor_mode_t; + +typedef struct { + uint8_t drop_number; + float coarse_time_ms; + float fine_time_ms; + float total_time_ms; + float final_weight; + float target_weight; + float overthrow; + float overthrow_percent; + float coarse_kp_used; + float coarse_kd_used; + float fine_kp_used; + float fine_kd_used; +} ai_drop_telemetry_t; + +typedef struct { + ai_tuning_state_t state; + profile_t* target_profile; + uint8_t drops_completed; + uint8_t max_drops_allowed; + uint8_t phase2_start_idx; + + // Circular buffer for last 10 drops + ai_drop_telemetry_t drops[AI_TUNING_DROP_BUF_SIZE]; + uint8_t drop_write_idx; + + float coarse_kp_best; + float coarse_kd_best; + float fine_kp_best; + float fine_kd_best; + + float recommended_coarse_kp; + float recommended_coarse_kd; + float recommended_fine_kp; + float recommended_fine_kd; + + float avg_overthrow; + float avg_total_time; + + char error_message[64]; +} ai_tuning_session_t; + +typedef struct { + float max_overthrow_percent; + + float coarse_kp_min; + float coarse_kp_max; + float coarse_kd_min; + float coarse_kd_max; + + float fine_kp_min; + float fine_kp_max; + float fine_kd_min; + float fine_kd_max; + + float noise_margin; + + // Tuning acceptance factors + float coarse_kp_max_factor; // Kp phase: threshold <= drop <= threshold * this (1.2) + float coarse_kd_max_factor; // Kd phase: drop <= threshold * this (1.1) + float fine_kp_max_factor; // Kp phase: target <= drop <= target * this (1.02) +} ai_tuning_config_t; + +typedef struct { + float coarse_kp; + float coarse_kd; + float fine_kp; + float fine_kd; + float overthrow; + float coarse_time_ms; + float fine_time_ms; + float total_time_ms; + uint8_t profile_idx; +} ai_drop_record_t; + +// AI tuning config stored in EEPROM (separate from ML history) +typedef struct { + uint16_t revision; + float coarse_kp_min; + float coarse_kp_max; + float coarse_kd_min; + float coarse_kd_max; + float fine_kp_min; + float fine_kp_max; + float fine_kd_min; + float fine_kd_max; + float noise_margin; +} __attribute__((packed)) ai_tuning_config_eeprom_t; + +// ML history stored in flash (runtime learning data) +typedef struct { + uint16_t revision; + uint8_t count; + uint8_t next_idx; + ai_drop_record_t drops[AI_TUNING_HISTORY_SIZE]; + float suggested_coarse_kp; + float suggested_coarse_kd; + float suggested_fine_kp; + float suggested_fine_kd; + bool has_suggestions; + float scale_latency_compensation; // Learned overthrow compensation (grains) +} ai_tuning_history_t; + +#ifdef __cplusplus +extern "C" { +#endif + +void ai_tuning_init(void); +ai_tuning_config_t* ai_tuning_get_config(void); + +bool ai_tuning_start(profile_t* profile); +bool ai_tuning_record_drop(const ai_drop_telemetry_t* telemetry); +bool ai_tuning_get_next_params(float* coarse_kp, float* coarse_kd, float* fine_kp, float* fine_kd); +bool ai_tuning_is_complete(void); +ai_tuning_session_t* ai_tuning_get_session(void); +void ai_tuning_get_session_copy(ai_tuning_session_t* out); +void ai_tuning_get_history_copy(ai_tuning_history_t* out); +bool ai_tuning_get_recommended_params(float* coarse_kp, float* coarse_kd, float* fine_kp, float* fine_kd); +bool ai_tuning_apply_params(void); +void ai_tuning_cancel(void); +bool ai_tuning_is_active(void); +ai_motor_mode_t ai_tuning_get_motor_mode(void); +uint8_t ai_tuning_get_progress_percent(void); + +// ML Learning +ai_tuning_history_t* ai_tuning_get_history(void); +void ai_tuning_record_charge(uint8_t profile_idx, float coarse_kp, float coarse_kd, + float fine_kp, float fine_kd, float overthrow, + float coarse_time_ms, float fine_time_ms); +void ai_tuning_calculate_refinements(uint8_t profile_idx); +bool ai_tuning_get_refined_params(float* coarse_kp, float* coarse_kd, float* fine_kp, float* fine_kd); +bool ai_tuning_get_suggestions(uint8_t profile_idx, float* coarse_kp, float* coarse_kd, + float* fine_kp, float* fine_kd); +bool ai_tuning_apply_refined_params(uint8_t profile_idx); +void ai_tuning_clear_history(void); +void ai_tuning_save_config(void); +float ai_tuning_get_scale_compensation(void); // Get learned scale latency compensation + +#ifdef __cplusplus +} +#endif + +#endif // AI_TUNING_H_ diff --git a/src/app_state.c b/src/app_state.c new file mode 100644 index 00000000..f9da4159 --- /dev/null +++ b/src/app_state.c @@ -0,0 +1,9 @@ +/** + * @file app_state.c + * @brief Application state variable shared between display types + */ + +#include "app_state.h" + +// Global exit state - used for mode transitions +AppState_t exit_state = APP_STATE_DEFAULT; diff --git a/src/app_state.h b/src/app_state.h new file mode 100644 index 00000000..454fb83d --- /dev/null +++ b/src/app_state.h @@ -0,0 +1,23 @@ +/** + * @file app_state.h + * @brief Application state variable shared between display types + */ + +#ifndef APP_STATE_H +#define APP_STATE_H + +#include "app.h" // For AppState_t + +#ifdef __cplusplus +extern "C" { +#endif + +// Global exit state - used for mode transitions +// Defined in app_state.c +extern AppState_t exit_state; + +#ifdef __cplusplus +} +#endif + +#endif // APP_STATE_H diff --git a/src/display_config.c b/src/display_config.c new file mode 100644 index 00000000..775b31b9 --- /dev/null +++ b/src/display_config.c @@ -0,0 +1,132 @@ +#include +#include +#include + +#include "display_config.h" +#include "eeprom.h" +#include "common.h" + +// Global config +static display_config_t display_config; + +bool display_config_save(void) { + bool is_ok = eeprom_write(EEPROM_DISPLAY_CONFIG_BASE_ADDR, (uint8_t *)&display_config, sizeof(display_config)); + return is_ok; +} + +// Get the compiled display type (cannot be changed at runtime) +static display_type_t get_compiled_display_type(void) { +#if defined(USE_TFT35) + return DISPLAY_TYPE_TFT35; +#elif defined(USE_TFT43) + return DISPLAY_TYPE_TFT43; +#else + return DISPLAY_TYPE_MINI_12864; +#endif +} + +bool display_config_init(void) { + bool is_ok; + + // Read configuration from EEPROM + memset(&display_config, 0x0, sizeof(display_config)); + is_ok = eeprom_read(EEPROM_DISPLAY_CONFIG_BASE_ADDR, (uint8_t *)&display_config, sizeof(display_config)); + + bool need_defaults = false; + if (!is_ok) { + printf("Unable to read display config from EEPROM at address %x, using defaults\n", EEPROM_DISPLAY_CONFIG_BASE_ADDR); + need_defaults = true; + } else if (display_config.data_rev != EEPROM_DISPLAY_CONFIG_DATA_REV) { + need_defaults = true; + } + + if (need_defaults) { + display_config.data_rev = EEPROM_DISPLAY_CONFIG_DATA_REV; + + // Set defaults + display_config.rotation = DISPLAY_ROTATION_0; + display_config.brightness = 255; // Max brightness + display_config.inverted_encoder = false; + + // Write back (may fail if no EEPROM) + if (is_ok && !display_config_save()) { + printf("Unable to write display config to %x\n", EEPROM_DISPLAY_CONFIG_BASE_ADDR); + } + } + + // Always use compiled display type (firmware-specific) + display_config.display_type = get_compiled_display_type(); + + printf("Display config loaded: type=%d, rotation=%d, brightness=%d\n", + display_config.display_type, display_config.rotation, display_config.brightness); + return true; +} + +display_type_t display_config_get_type(void) { + return display_config.display_type; +} + +void display_config_set_type(display_type_t type) { + display_config.display_type = type; +} + +display_config_t* display_config_get(void) { + return &display_config; +} + +bool http_rest_display_config(struct fs_file *file, int num_params, char *params[], char *values[]) { + // Mappings: + // d0 (int): display_type (0=Mini12864, 1=TFT35, 2=TFT43) - READ ONLY, set by firmware + // d1 (int): rotation (0=0°, 1=90°, 2=180°, 3=270°) + // d2 (int): brightness (0-255) + // d3 (bool): inverted_encoder + // ee (bool): save to eeprom + static char buf[256]; + bool save_to_eeprom = false; + + // Process parameters + for (int idx = 0; idx < num_params; idx++) { + // d0 (display_type) is ignored - it's firmware-specific and cannot be changed + if (strcmp(params[idx], "d1") == 0) { + int rot = atoi(values[idx]); + if (rot >= DISPLAY_ROTATION_0 && rot <= DISPLAY_ROTATION_270) { + display_config.rotation = (display_rotation_t)rot; + } + } + else if (strcmp(params[idx], "d2") == 0) { + int bright = atoi(values[idx]); + if (bright >= 0 && bright <= 255) { + display_config.brightness = (uint8_t)bright; + } + } + else if (strcmp(params[idx], "d3") == 0) { + display_config.inverted_encoder = string_to_boolean(values[idx]); + } + else if (strcmp(params[idx], "ee") == 0) { + save_to_eeprom = string_to_boolean(values[idx]); + } + } + + // Save to EEPROM if requested + if (save_to_eeprom) { + display_config_save(); + } + + // Response + snprintf(buf, sizeof(buf), + "%s" + "{\"d0\":%d,\"d1\":%d,\"d2\":%d,\"d3\":%s}", + http_json_header, + display_config.display_type, + display_config.rotation, + display_config.brightness, + boolean_to_string(display_config.inverted_encoder)); + + size_t response_len = strlen(buf); + file->data = buf; + file->len = response_len; + file->index = response_len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + + return true; +} diff --git a/src/display_config.h b/src/display_config.h new file mode 100644 index 00000000..9c34c097 --- /dev/null +++ b/src/display_config.h @@ -0,0 +1,47 @@ +#ifndef DISPLAY_CONFIG_H_ +#define DISPLAY_CONFIG_H_ + +#include +#include +#include "http_rest.h" + +#define EEPROM_DISPLAY_CONFIG_BASE_ADDR (13 * 1024) // 13K - Display type selection +#define EEPROM_DISPLAY_CONFIG_DATA_REV 2 + +typedef enum { + DISPLAY_TYPE_MINI_12864 = 0, // Default: Mini 12864 monochrome + DISPLAY_TYPE_TFT35 = 1, // TFT35 V3.0.1: 3.5 inch, 480x320 + DISPLAY_TYPE_TFT43 = 2, // TFT43 V3.0: 4.3 inch, 480x272 +} display_type_t; + +typedef enum { + DISPLAY_ROTATION_0 = 0, + DISPLAY_ROTATION_90 = 1, + DISPLAY_ROTATION_180 = 2, + DISPLAY_ROTATION_270 = 3, +} display_rotation_t; + +typedef struct { + uint32_t data_rev; + display_type_t display_type; + display_rotation_t rotation; + uint8_t brightness; // 0-255, for TFT displays + bool inverted_encoder; // For Mini 12864 rotary encoder +} display_config_t; + +#ifdef __cplusplus +extern "C" { +#endif + +bool display_config_init(void); +bool display_config_save(void); +display_type_t display_config_get_type(void); +void display_config_set_type(display_type_t type); +display_config_t* display_config_get(void); +bool http_rest_display_config(struct fs_file *file, int num_params, char *params[], char *values[]); + +#ifdef __cplusplus +} +#endif + +#endif // DISPLAY_CONFIG_H_ diff --git a/src/error.c b/src/error.c new file mode 100644 index 00000000..5ab05de4 --- /dev/null +++ b/src/error.c @@ -0,0 +1,322 @@ +#include +#include +#include +#include + +#include "error.h" +#include "neopixel_led.h" + +// Only include u8g2 display code for Mini 12864 builds +#ifndef USE_COLOR_TFT +#include "display.h" +#endif + +// Error system state +static error_system_state_t error_state = { + .eeprom_ready = false, + .neopixel_ready = false, + .display_ready = false, + .wifi_ready = false, +}; + +// Error log circular buffer +static error_code_t error_log[ERROR_LOG_SIZE]; +static uint8_t error_log_count = 0; +static uint8_t error_log_write_idx = 0; + +// Last error that occurred +static error_code_t last_error = ERR_NONE; +static bool error_occurred = false; + +// Error LED color (red) +#define ERROR_LED_COLOR 0xFF0000 + +#ifndef USE_COLOR_TFT +// Display layout constants (Mini 12864 only) +#define ERROR_TITLE_HEIGHT 13 +#define ERROR_LINE_HEIGHT 8 +#define ERROR_MAX_DISPLAY_LINES 5 +#endif + +void error_system_init(void) { + error_state.eeprom_ready = false; + error_state.neopixel_ready = false; + error_state.display_ready = false; + error_state.wifi_ready = false; + last_error = ERR_NONE; + error_occurred = false; + error_log_count = 0; + error_log_write_idx = 0; + memset(error_log, 0, sizeof(error_log)); +} + +void error_set_eeprom_ready(bool ready) { + error_state.eeprom_ready = ready; +} + +void error_set_neopixel_ready(bool ready) { + error_state.neopixel_ready = ready; +} + +void error_set_display_ready(bool ready) { + error_state.display_ready = ready; +} + +void error_set_wifi_ready(bool ready) { + error_state.wifi_ready = ready; +} + +// Get short category string (max 3 chars) +static const char* error_get_short_category(error_code_t code) { + if (code >= 100 && code < 200) return "EEP"; + if (code >= 200 && code < 300) return "DSP"; + if (code >= 300 && code < 400) return "LED"; + if (code >= 400 && code < 500) return "WIF"; + if (code >= 500 && code < 600) return "MOT"; + if (code >= 600 && code < 700) return "SCL"; + if (code >= 700 && code < 800) return "SRV"; + if (code >= 800 && code < 900) return "PRF"; + if (code >= 900 && code < 1000) return "CHG"; + if (code >= 1000 && code < 1100) return "RST"; + if (code >= 1100 && code < 1200) return "MEM"; + if (code >= 1200 && code < 1300) return "CAL"; + return "UNK"; +} + +const char* error_code_to_string(error_code_t code) { + switch (code) { + case ERR_NONE: return "No error"; + + // EEPROM + case ERR_EEPROM_I2C_INIT: return "EEPROM I2C init"; + case ERR_EEPROM_READ_FAIL: return "EEPROM read"; + case ERR_EEPROM_WRITE_FAIL: return "EEPROM write"; + case ERR_EEPROM_MUTEX_CREATE: return "EEPROM mutex"; + case ERR_EEPROM_HANDLER_ALLOC: return "EEPROM alloc"; + case ERR_EEPROM_INVALID_SIZE: return "EEPROM size"; + + // Display + case ERR_DISPLAY_MUTEX_CREATE: return "Display mutex"; + case ERR_DISPLAY_INIT_FAIL: return "Display init"; + case ERR_DISPLAY_TASK_CREATE: return "Display task"; + case ERR_DISPLAY_ENCODER_QUEUE_CREATE: return "Encoder queue"; + + // Neopixel + case ERR_NEOPIXEL_MUTEX_CREATE: return "Neopixel mutex"; + case ERR_NEOPIXEL_PIO_INIT: return "Neopixel PIO"; + + // WiFi + case ERR_WIFI_INIT_FAIL: return "WiFi init"; + case ERR_WIFI_CONNECT_FAIL: return "WiFi connect"; + case ERR_WIFI_QUEUE_CREATE: return "WiFi queue"; + case ERR_WIFI_TASK_CREATE: return "WiFi task"; + case ERR_WIFI_DNS_BIND_FAIL: return "DNS bind fail"; + + // Motors + case ERR_MOTOR_UART_INIT: return "Motor UART"; + case ERR_MOTOR_DRIVER_ALLOC: return "Motor alloc"; + case ERR_MOTOR_COARSE_INIT: return "Coarse motor"; + case ERR_MOTOR_FINE_INIT: return "Fine motor"; + case ERR_MOTOR_QUEUE_CREATE: return "Motor queue"; + case ERR_MOTOR_TASK_CREATE: return "Motor task"; + case ERR_MOTOR_INVALID_SELECT: return "Invalid motor"; + + // Scale + case ERR_SCALE_UART_INIT: return "Scale UART"; + case ERR_SCALE_SEMAPHORE_CREATE: return "Scale sema"; + case ERR_SCALE_MUTEX_CREATE: return "Scale mutex"; + case ERR_SCALE_TASK_CREATE: return "Scale task"; + case ERR_SCALE_DRIVER_SELECT: return "Scale driver"; + + // Servo + case ERR_SERVO_QUEUE_CREATE: return "Servo queue"; + case ERR_SERVO_SEMAPHORE_CREATE: return "Servo sema"; + case ERR_SERVO_TASK_CREATE: return "Servo task"; + + // Profile + case ERR_PROFILE_EEPROM_READ: return "Profile read"; + case ERR_PROFILE_EEPROM_WRITE: return "Profile write"; + + // Charge mode + case ERR_CHARGE_EEPROM_READ: return "Charge read"; + case ERR_CHARGE_EEPROM_WRITE: return "Charge write"; + + // REST + case ERR_REST_QUEUE_CREATE: return "REST queue"; + case ERR_REST_ALLOC_FAIL: return "REST alloc"; + + // Memory + case ERR_MEMORY_ALLOC: return "Memory alloc"; + case ERR_MEMORY_BUFFER_OVERFLOW: return "Buffer overflow"; + case ERR_MEMORY_STACK_OVERFLOW: return "Stack overflow"; + case ERR_MEMORY_MALLOC_FAILED: return "Malloc failed"; + + // Calibration + case ERR_CALIBRATE_TASK_CREATE: return "Calibrate task"; + + default: return "Unknown"; + } +} + +// Get error category from code (for serial output) +static const char* error_get_category(error_code_t code) { + if (code >= 100 && code < 200) return "EEPROM"; + if (code >= 200 && code < 300) return "DISPLAY"; + if (code >= 300 && code < 400) return "LED"; + if (code >= 400 && code < 500) return "WIFI"; + if (code >= 500 && code < 600) return "MOTOR"; + if (code >= 600 && code < 700) return "SCALE"; + if (code >= 700 && code < 800) return "SERVO"; + if (code >= 800 && code < 900) return "PROFILE"; + if (code >= 900 && code < 1000) return "CHARGE"; + if (code >= 1000 && code < 1100) return "REST"; + if (code >= 1100 && code < 1200) return "MEMORY"; + if (code >= 1200 && code < 1300) return "CALIBRATE"; + return "UNKNOWN"; +} + +#ifndef USE_COLOR_TFT +// Update display with scrolling error list (Mini 12864 only) +static void error_update_display(void) { + if (!error_state.display_ready) { + return; + } + + u8g2_t *display = get_display_handler(); + if (display == NULL) { + return; + } + + acquire_display_buffer_access(); + + u8g2_ClearBuffer(display); + + // Title with error count + u8g2_SetFont(display, u8g2_font_helvB08_tr); + char title[20]; + snprintf(title, sizeof(title), "ERRORS (%d)", error_log_count); + u8g2_DrawStr(display, 5, 10, title); + + // Line under title + u8g2_DrawHLine(display, 0, ERROR_TITLE_HEIGHT, u8g2_GetDisplayWidth(display)); + + // Show most recent errors (newest at bottom, scrolls up) + u8g2_SetFont(display, u8g2_font_4x6_tf); // Tiny fixed-width font, ~25 chars/row + + uint8_t lines_to_show = (error_log_count < ERROR_MAX_DISPLAY_LINES) + ? error_log_count + : ERROR_MAX_DISPLAY_LINES; + + // Calculate starting index for oldest error to display + int start_idx; + if (error_log_count <= ERROR_MAX_DISPLAY_LINES) { + // Show all errors from beginning + start_idx = 0; + } else { + // Show most recent ERROR_MAX_DISPLAY_LINES errors + start_idx = error_log_count - ERROR_MAX_DISPLAY_LINES; + } + + for (uint8_t i = 0; i < lines_to_show; i++) { + // Get error from log (accounting for circular buffer) + int log_idx; + if (error_log_count < ERROR_LOG_SIZE) { + log_idx = start_idx + i; + } else { + log_idx = (error_log_write_idx + start_idx + i) % ERROR_LOG_SIZE; + } + + error_code_t code = error_log[log_idx]; + + // Format: "EEP:105 EEPROM alloc" + char line[32]; + snprintf(line, sizeof(line), "%s:%d %s", + error_get_short_category(code), + (int)code, + error_code_to_string(code)); + + int y_pos = ERROR_TITLE_HEIGHT + 10 + (i * ERROR_LINE_HEIGHT); + u8g2_DrawStr(display, 2, y_pos, line); + } + + u8g2_SendBuffer(display); + + release_display_buffer_access(); +} +#endif + +void report_error(error_code_t code) { + if (code == ERR_NONE) { + return; + } + + // Store error + last_error = code; + error_occurred = true; + + // Add to error log + error_log[error_log_write_idx] = code; + error_log_write_idx = (error_log_write_idx + 1) % ERROR_LOG_SIZE; + if (error_log_count < ERROR_LOG_SIZE) { + error_log_count++; + } + + // 1. Always log to printf (USB serial) - available from boot + printf("ERROR [%s] %d: %s\n", + error_get_category(code), + (int)code, + error_code_to_string(code)); + + // 2. Set LED to red if neopixel is ready + if (error_state.neopixel_ready) { + // Set all LEDs to red to indicate error + // Use mutex-protected public API instead of raw _neopixel_led_set_colour + rgbw_u32_t error_colour = { ._raw_colour = ERROR_LED_COLOR }; + neopixel_led_set_colour(error_colour, error_colour, error_colour, false); + } + + // 3. Update display with scrolling error list (Mini 12864 only) +#ifndef USE_COLOR_TFT + error_update_display(); +#endif + // For TFT displays, errors are logged to serial only for now + // LVGL-based error display can be added later if needed + + // 4. WiFi/REST notification could be added here + // if (error_state.wifi_ready) { ... } +} + +error_code_t error_get_last(void) { + return last_error; +} + +void error_clear_last(void) { + last_error = ERR_NONE; + error_occurred = false; + error_log_count = 0; + error_log_write_idx = 0; +} + +bool error_has_occurred(void) { + return error_occurred; +} + +uint8_t error_get_count(void) { + return error_log_count; +} + +error_code_t error_get_at(uint8_t index) { + if (index >= error_log_count) { + return ERR_NONE; + } + + // Calculate actual index in circular buffer + int log_idx; + if (error_log_count < ERROR_LOG_SIZE) { + log_idx = index; + } else { + log_idx = (error_log_write_idx + index) % ERROR_LOG_SIZE; + } + + return error_log[log_idx]; +} diff --git a/src/error.h b/src/error.h new file mode 100644 index 00000000..0466e854 --- /dev/null +++ b/src/error.h @@ -0,0 +1,133 @@ +#ifndef ERROR_H_ +#define ERROR_H_ + +#include +#include + +// Error codes for all modules +// Format: ERR__ +typedef enum { + ERR_NONE = 0, + + // EEPROM errors (1xx) + ERR_EEPROM_I2C_INIT = 100, + ERR_EEPROM_READ_FAIL, + ERR_EEPROM_WRITE_FAIL, + ERR_EEPROM_MUTEX_CREATE, + ERR_EEPROM_HANDLER_ALLOC, + ERR_EEPROM_INVALID_SIZE, + + // Display errors (2xx) + ERR_DISPLAY_MUTEX_CREATE = 200, + ERR_DISPLAY_INIT_FAIL, + ERR_DISPLAY_TASK_CREATE, + ERR_DISPLAY_ENCODER_QUEUE_CREATE, + + // Neopixel LED errors (3xx) + ERR_NEOPIXEL_MUTEX_CREATE = 300, + ERR_NEOPIXEL_PIO_INIT, + + // Wireless errors (4xx) + ERR_WIFI_INIT_FAIL = 400, + ERR_WIFI_CONNECT_FAIL, + ERR_WIFI_QUEUE_CREATE, + ERR_WIFI_TASK_CREATE, + ERR_WIFI_DNS_BIND_FAIL, + + // Motor errors (5xx) + ERR_MOTOR_UART_INIT = 500, + ERR_MOTOR_DRIVER_ALLOC, + ERR_MOTOR_COARSE_INIT, + ERR_MOTOR_FINE_INIT, + ERR_MOTOR_QUEUE_CREATE, + ERR_MOTOR_TASK_CREATE, + ERR_MOTOR_INVALID_SELECT, + + // Scale errors (6xx) + ERR_SCALE_UART_INIT = 600, + ERR_SCALE_SEMAPHORE_CREATE, + ERR_SCALE_MUTEX_CREATE, + ERR_SCALE_TASK_CREATE, + ERR_SCALE_DRIVER_SELECT, + + // Servo gate errors (7xx) + ERR_SERVO_QUEUE_CREATE = 700, + ERR_SERVO_SEMAPHORE_CREATE, + ERR_SERVO_TASK_CREATE, + + // Profile errors (8xx) + ERR_PROFILE_EEPROM_READ = 800, + ERR_PROFILE_EEPROM_WRITE, + + // Charge mode errors (9xx) + ERR_CHARGE_EEPROM_READ = 900, + ERR_CHARGE_EEPROM_WRITE, + + // REST API errors (10xx) + ERR_REST_QUEUE_CREATE = 1000, + ERR_REST_ALLOC_FAIL, + + // Memory errors (11xx) + ERR_MEMORY_ALLOC = 1100, + ERR_MEMORY_BUFFER_OVERFLOW, + ERR_MEMORY_STACK_OVERFLOW, + ERR_MEMORY_MALLOC_FAILED, + + // Calibration errors (12xx) + ERR_CALIBRATE_TASK_CREATE = 1200, + + // Generic/unknown + ERR_UNKNOWN = 9999, +} error_code_t; + +// Initialization state flags (what's available for error reporting) +typedef struct { + bool eeprom_ready; + bool neopixel_ready; + bool display_ready; + bool wifi_ready; +} error_system_state_t; + +#ifdef __cplusplus +extern "C" { +#endif + +// Initialize error system (call early in main) +void error_system_init(void); + +// Mark subsystems as ready for error reporting +void error_set_eeprom_ready(bool ready); +void error_set_neopixel_ready(bool ready); +void error_set_display_ready(bool ready); +void error_set_wifi_ready(bool ready); + +// Main error reporting function +// Reports via all available channels (printf, LED, display, etc.) +void report_error(error_code_t code); + +// Get human-readable error string +const char* error_code_to_string(error_code_t code); + +// Get last error (for REST API queries) +error_code_t error_get_last(void); + +// Clear last error +void error_clear_last(void); + +// Check if system has any errors +bool error_has_occurred(void); + +// Get error count +uint8_t error_get_count(void); + +// Get error at index (0 = oldest in buffer) +error_code_t error_get_at(uint8_t index); + +// Maximum errors stored in log +#define ERROR_LOG_SIZE 8 + +#ifdef __cplusplus +} +#endif + +#endif // ERROR_H_ diff --git a/src/favicon.ico.h b/src/favicon.ico.h new file mode 100644 index 00000000..b3a7b56c --- /dev/null +++ b/src/favicon.ico.h @@ -0,0 +1,1307 @@ +// ---------------------------------------------------------- // +// This file is autogenerated by bin2header.py; do not edit! // +// ---------------------------------------------------------- // + +#ifndef FAVICON_ICO_H_ +#define FAVICON_ICO_H_ + +#include + +// HTTP header + binary data combined +static const unsigned char favicon_ico[] = { + 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x20, 0x32, 0x30, 0x30, + 0x20, 0x4f, 0x4b, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x2f, 0x78, 0x2d, 0x69, 0x63, 0x6f, 0x6e, 0x0d, 0x0a, 0x43, 0x61, 0x63, + 0x68, 0x65, 0x2d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x3a, 0x20, + 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, 0x3d, 0x38, 0x36, 0x34, 0x30, + 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x10, + 0x10, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x68, 0x04, 0x00, 0x00, 0x36, + 0x00, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x28, + 0x11, 0x00, 0x00, 0x9e, 0x04, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x01, + 0x00, 0x20, 0x00, 0x68, 0x26, 0x00, 0x00, 0xc6, 0x15, 0x00, 0x00, 0x28, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x80, 0x02, 0x00, + 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xc0, 0xfc, 0x4d, 0x00, + 0xbf, 0xf7, 0x20, 0x00, 0xc1, 0xfd, 0x7c, 0x00, 0xc3, 0xfd, 0x7f, 0x00, + 0xc1, 0xfd, 0x7b, 0x00, 0xc1, 0xfd, 0x7b, 0x00, 0xc1, 0xfd, 0x7f, 0x00, + 0xc1, 0xfd, 0x77, 0x00, 0xb5, 0xea, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xc1, 0xfe, 0xba, 0x00, 0xbf, 0xfb, 0x74, 0x00, 0xaa, 0xd4, 0x0c, 0x00, + 0xb9, 0xf7, 0x21, 0x00, 0xc1, 0xfc, 0x99, 0x00, 0x9f, 0xdf, 0x08, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xb4, 0xe1, 0x11, 0x00, 0xbf, 0xfb, 0x7c, 0x00, + 0xc1, 0xf9, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xb9, 0xf7, 0x21, 0x00, 0x00, 0x00, 0x01, 0xc6, + 0x55, 0x00, 0x09, 0xc7, 0x80, 0x25, 0xa0, 0x14, 0xbb, 0xe6, 0xe4, 0x00, + 0xc2, 0xfc, 0xb4, 0x00, 0x9f, 0xdf, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xc1, 0xfc, 0x5b, 0x00, 0xc0, 0xfc, 0x51, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xb1, 0xeb, 0x0d, 0x00, 0xaa, 0xea, 0x0c, 0x00, 0x00, 0x00, 0x01, 0xc6, + 0x71, 0x00, 0x09, 0xe4, 0x76, 0x07, 0xb5, 0xe4, 0x76, 0x07, 0xff, 0xd0, + 0x7d, 0x1c, 0xe3, 0x11, 0xbc, 0xec, 0xc7, 0x00, 0xc1, 0xfd, 0x95, 0x00, + 0x9f, 0xdf, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xc1, 0xfb, 0x80, 0x00, 0xb9, 0xe8, 0x16, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xc0, 0xfd, 0x72, 0x00, 0xb9, 0xf3, 0x16, 0x00, + 0x55, 0xaa, 0x03, 0xdf, 0x76, 0x0b, 0xb5, 0xe3, 0x76, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xe4, 0x76, 0x07, 0xb3, 0xba, 0x7d, 0x32, 0x76, 0x12, + 0xb0, 0xe9, 0xff, 0x00, 0xc2, 0xfe, 0xb3, 0x00, 0x9f, 0xdf, 0x08, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xc2, 0xf3, 0x15, 0x00, 0xc2, 0xfd, 0x75, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xc2, 0xfd, 0x7e, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x78, 0xfd, 0x73, 0x5b, 0xa1, 0x9a, 0xff, 0xe2, + 0x76, 0x08, 0xff, 0xe3, 0x76, 0x07, 0xb4, 0xe1, 0x76, 0x06, 0x79, 0xe4, + 0x76, 0x07, 0xfe, 0xb9, 0x78, 0x35, 0xff, 0x12, 0xb1, 0xe9, 0xfe, 0x00, + 0xc1, 0xfd, 0x91, 0x00, 0x9f, 0xdf, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xc1, 0xfd, 0x84, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xc0, 0xfb, 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x52, 0xfd, 0xcd, 0x00, + 0x77, 0xfd, 0xfe, 0x6e, 0x9a, 0x87, 0x95, 0xe1, 0x76, 0x06, 0x79, 0xe4, + 0x76, 0x07, 0xfe, 0xe4, 0x76, 0x07, 0xff, 0xe4, 0x76, 0x07, 0xfb, 0xc6, + 0x7e, 0x29, 0x63, 0x13, 0xbb, 0xe8, 0xd2, 0x00, 0xc2, 0xfc, 0xb4, 0x00, + 0x9f, 0xdf, 0x08, 0x00, 0xc2, 0xfd, 0x81, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xc0, 0xfb, 0x76, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x52, 0xfb, 0x41, 0x00, 0x52, 0xfb, 0x3e, 0x59, 0xa0, 0x97, 0x53, 0xe0, + 0x77, 0x0b, 0xfe, 0xe3, 0x76, 0x07, 0xff, 0xe4, 0x76, 0x07, 0xfb, 0xe4, + 0x77, 0x07, 0x67, 0xe4, 0x76, 0x08, 0xc9, 0xcf, 0x7d, 0x1c, 0xe4, 0x14, + 0xbb, 0xe6, 0xe4, 0x00, 0xc2, 0xfb, 0x8d, 0x00, 0xc0, 0xfd, 0x82, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xc1, 0xfd, 0x77, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x4f, 0xf6, 0x1d, 0x00, + 0x70, 0xfd, 0xef, 0x4b, 0xa4, 0xab, 0xff, 0xde, 0x77, 0x0b, 0xfb, 0xdf, + 0x76, 0x07, 0x68, 0xe3, 0x75, 0x08, 0xca, 0xe4, 0x76, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xc3, 0x80, 0x27, 0x89, 0x00, 0xb6, 0xf3, 0x15, 0x00, + 0xc2, 0xfd, 0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xc2, 0xfd, 0x6c, 0x00, 0xaf, 0xdf, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x53, 0xf5, 0x31, 0x00, 0x53, 0xfd, 0xff, 0x00, 0x72, 0xfd, 0xe2, 0x52, + 0xa0, 0xa0, 0x3e, 0x69, 0x9a, 0x8a, 0xb1, 0xe2, 0x76, 0x08, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0x99, 0x55, 0x55, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xbd, 0xf6, 0x3a, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xbc, 0xf2, 0x13, 0x00, 0xc0, 0xfd, 0x72, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x2a, 0xd4, 0x06, 0x00, 0x4f, 0xf9, 0x2a, 0x00, + 0x4b, 0xf0, 0x11, 0x00, 0x51, 0xfc, 0x52, 0x00, 0x78, 0xfd, 0xff, 0x5d, + 0xa1, 0x98, 0xff, 0xdf, 0x76, 0x0a, 0x97, 0x55, 0x55, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xb3, 0xe6, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xbe, 0xfc, 0x4a, 0x00, 0xbf, 0xfc, 0x4c, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x52, 0xfb, 0x44, 0x00, + 0x53, 0xfc, 0xc2, 0x00, 0x72, 0xfc, 0x57, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xbe, 0xfb, 0x47, 0x00, 0xc2, 0xfd, 0xa7, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xc1, 0xfc, 0x4a, 0x00, + 0xc2, 0xfd, 0x6d, 0x00, 0xa2, 0xe8, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xaa, 0xe3, 0x09, 0x00, 0xc0, 0xfa, 0x6e, 0x00, 0xc1, 0xfa, 0x5e, 0x00, + 0xc0, 0xfb, 0x39, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xb9, 0xf3, 0x16, 0x00, 0xc2, 0xfd, 0x6d, 0x00, + 0xc0, 0xfd, 0x72, 0x00, 0xbf, 0xfa, 0x70, 0x00, 0xc0, 0xfa, 0x71, 0x00, + 0xc0, 0xfd, 0x76, 0x00, 0xc0, 0xfd, 0x76, 0x00, 0xbd, 0xf7, 0x1f, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x80, 0x02, 0x00, + 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 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, 0x28, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x04, 0x00, + 0xbf, 0xff, 0x04, 0x00, 0x99, 0x99, 0x05, 0x00, 0x80, 0x80, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xc5, 0xff, 0x30, 0x00, + 0x92, 0xb6, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8e, 0xc6, 0x09, 0x00, 0xc4, 0xff, 0x4a, 0x00, 0xc1, 0xfc, 0x95, 0x00, + 0xc2, 0xfc, 0xc9, 0x00, 0xc3, 0xff, 0xe8, 0x00, 0xc0, 0xfc, 0xf0, 0x00, + 0xc3, 0xfe, 0xef, 0x00, 0xc2, 0xfe, 0xe3, 0x00, 0xc1, 0xfb, 0xc0, 0x00, + 0xc2, 0xff, 0x86, 0x00, 0xbe, 0xf6, 0x3b, 0x00, 0x80, 0xff, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0xfb, 0xcb, 0x00, 0xc2, 0xff, 0x32, 0x00, + 0xa2, 0xd1, 0x0b, 0x00, 0xc1, 0xfd, 0x74, 0x00, 0xc3, 0xff, 0xe1, 0x00, + 0xc0, 0xfb, 0xbe, 0x00, 0xc2, 0xfd, 0x6d, 0x00, 0xbe, 0xfa, 0x33, 0x00, + 0xaf, 0xef, 0x10, 0x00, 0xaa, 0xff, 0x03, 0x00, 0x6d, 0x92, 0x07, 0x00, + 0xbf, 0xf2, 0x14, 0x00, 0xbf, 0xf6, 0x3c, 0x00, 0xc1, 0xfd, 0x7b, 0x00, + 0xc3, 0xff, 0xcd, 0x00, 0xc0, 0xfb, 0xd2, 0x00, 0xc0, 0xfc, 0x59, 0x00, + 0x80, 0x80, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xc2, 0xfe, 0xc8, 0x00, 0xbe, 0xfb, 0x72, 0x00, 0xc3, 0xfe, 0xd8, 0x00, + 0xc1, 0xfc, 0xa9, 0x00, 0xbe, 0xf3, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x80, 0x80, 0x02, 0x00, 0xc0, 0xfa, 0x6d, 0x00, + 0xb9, 0xf6, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x80, 0x80, 0x02, 0x00, + 0xc0, 0xfb, 0x3d, 0x00, 0xc0, 0xfb, 0xc2, 0x00, 0xc2, 0xfe, 0xc0, 0x00, + 0xb6, 0xf0, 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xc1, 0xfc, 0xc5, 0x00, + 0xc2, 0xfe, 0xea, 0x00, 0xba, 0xf5, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbf, 0xf9, 0x80, 0x00, 0xc2, 0xfe, 0xff, 0x00, 0xc2, 0xfd, 0xda, 0x00, + 0xb9, 0xf6, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x80, 0xff, 0x02, 0x00, 0xbd, 0xf8, 0x6d, 0x00, 0xc3, 0xfe, 0xe4, 0x00, + 0xbf, 0xf7, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0xf9, 0x5c, 0x00, 0xc1, 0xf8, 0x25, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0xdd, 0x77, 0x08, 0x1e, 0x67, 0xa1, 0x8d, 0x8d, 0x00, + 0xc0, 0xfc, 0xf8, 0x00, 0xc3, 0xfe, 0xff, 0x00, 0xc2, 0xfd, 0xda, 0x00, + 0xc1, 0xf6, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbd, 0xf7, 0x42, 0x00, 0xc3, 0xff, 0xe5, 0x00, + 0xc1, 0xfb, 0x3e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xde, 0x73, 0x08, 0x1f, 0xe3, + 0x76, 0x07, 0xdc, 0xe3, 0x76, 0x07, 0xf9, 0x74, 0x9c, 0x80, 0xa2, 0x00, + 0xc0, 0xfc, 0xf8, 0x00, 0xc3, 0xfe, 0xff, 0x00, 0xc2, 0xfd, 0xda, 0x00, + 0xb9, 0xf6, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbc, 0xf4, 0x45, 0x00, 0xc3, 0xfe, 0xe4, 0x00, + 0xb7, 0xef, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0xde, + 0x73, 0x08, 0x1f, 0xe2, 0x75, 0x07, 0xdc, 0xe5, 0x77, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xf8, 0x72, 0x9c, 0x82, 0x9f, 0x00, + 0xc0, 0xfc, 0xf8, 0x00, 0xc3, 0xfe, 0xff, 0x00, 0xc2, 0xfb, 0x85, 0x00, + 0xb9, 0xf3, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf9, 0x76, 0x00, 0xc3, 0xfe, 0xba, 0x00, + 0x55, 0x55, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0xbe, 0xff, 0x2f, 0x00, 0xbc, 0xf4, 0x2e, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0xdf, 0x78, 0x08, 0x20, 0xe3, 0x76, 0x07, 0xdc, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xf8, 0x62, 0xa2, 0x93, 0x87, 0x00, + 0xc1, 0xfc, 0xa0, 0x00, 0xc2, 0xff, 0xdf, 0x00, 0xc2, 0xfd, 0xda, 0x00, + 0xb9, 0xed, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xbf, 0xff, 0x04, 0x00, 0xc1, 0xfb, 0xcd, 0x00, 0xc2, 0xff, 0x50, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0xfb, 0xc3, 0x00, 0xc2, 0xff, 0x4f, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x6c, 0x08, 0x21, 0xe4, + 0x76, 0x07, 0xdc, 0xe3, 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xd9, 0xdb, 0x76, 0x09, 0x1c, 0x28, 0xa6, 0xd1, 0x59, 0x00, + 0xbd, 0xfb, 0xff, 0x00, 0xc3, 0xfe, 0xff, 0x00, 0xc2, 0xfd, 0xd9, 0x00, + 0xb9, 0xed, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xc3, 0xff, 0x4d, 0x00, 0xc1, 0xfc, 0xc8, 0x00, 0x00, 0xff, 0x01, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0xbf, 0xf9, 0x28, 0x00, 0xc2, 0xfe, 0xde, 0x00, + 0x80, 0xaa, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xaa, 0xc6, 0x09, 0xd6, 0x7b, 0x15, 0xd9, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x76, 0x07, 0xd9, 0xe4, 0x76, 0x09, 0x1c, 0xe1, + 0x75, 0x08, 0x66, 0xdd, 0x74, 0x0e, 0xfc, 0x48, 0x81, 0xb0, 0xff, 0x00, + 0xbe, 0xfb, 0xff, 0x00, 0xc3, 0xfe, 0xff, 0x00, 0xc2, 0xfd, 0xd9, 0x00, + 0xb9, 0xed, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x99, 0x99, 0x05, 0x00, + 0xc3, 0xff, 0xde, 0x00, 0xb8, 0xf4, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0xfb, 0x72, 0x00, 0xc0, 0xfb, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb3, 0xfd, 0x76, 0x47, + 0xaa, 0xb0, 0xff, 0xde, 0x79, 0x0d, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xd9, 0xe4, + 0x76, 0x09, 0x1c, 0xde, 0x75, 0x08, 0x66, 0xe5, 0x76, 0x07, 0xfc, 0xe3, + 0x76, 0x07, 0xff, 0xdd, 0x75, 0x0e, 0xff, 0x48, 0x81, 0xb0, 0xff, 0x00, + 0xbe, 0xfb, 0xff, 0x00, 0xc3, 0xfe, 0xff, 0x00, 0xc2, 0xfe, 0xd8, 0x00, + 0xb6, 0xed, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xc2, 0xfd, 0x92, 0x00, + 0xc1, 0xff, 0x78, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xc3, 0xff, 0xa9, 0x00, + 0xbf, 0xf9, 0x50, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x52, 0xf9, 0x5a, 0x00, 0x6a, 0xff, 0xfb, 0x00, 0xba, 0xfd, 0xff, 0x48, + 0xa9, 0xae, 0xff, 0xdf, 0x78, 0x0d, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe4, + 0x77, 0x07, 0xd9, 0xd4, 0x6e, 0x08, 0x1e, 0xe1, 0x75, 0x08, 0x66, 0xe5, + 0x76, 0x07, 0xfc, 0xe2, 0x75, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xdd, 0x75, 0x0e, 0xff, 0x48, 0x81, 0xb0, 0xff, 0x00, + 0xbe, 0xfb, 0xfd, 0x00, 0xc2, 0xff, 0xc5, 0x00, 0xc0, 0xfc, 0x9b, 0x00, + 0xb9, 0xf6, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0xfc, 0x55, 0x00, 0xc1, 0xfc, 0xb0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0xfb, 0xce, 0x00, 0xc1, 0xf8, 0x25, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x53, 0xff, 0x9d, 0x00, + 0x52, 0xfb, 0xff, 0x00, 0x6a, 0xfe, 0xff, 0x00, 0xba, 0xfd, 0xff, 0x4a, + 0xa9, 0xad, 0xff, 0xd8, 0x7b, 0x15, 0xd6, 0xce, 0x73, 0x08, 0x1f, 0xe6, + 0x77, 0x08, 0x65, 0xe4, 0x76, 0x07, 0xfc, 0xe3, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xdf, 0x76, 0x0c, 0xe8, 0x0a, 0xbd, 0xf5, 0x32, 0x00, + 0xc0, 0xfb, 0xba, 0x00, 0xc3, 0xfe, 0xff, 0x00, 0xc2, 0xfd, 0xda, 0x00, + 0xb9, 0xf6, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0xc2, 0xff, 0x2e, 0x00, 0xc2, 0xfe, 0xd5, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xc3, 0xff, 0xdd, 0x00, 0xa6, 0xd9, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x52, 0xfc, 0x99, 0x00, 0x53, 0xfe, 0xff, 0x00, + 0x52, 0xfb, 0xff, 0x00, 0x69, 0xfe, 0xfc, 0x00, 0xb2, 0xfd, 0x77, 0x00, + 0x8e, 0xc6, 0x09, 0xe3, 0x77, 0x08, 0x65, 0xe3, 0x75, 0x07, 0xfc, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, 0x77, 0x06, 0xee, 0xdf, + 0x72, 0x09, 0x38, 0xe1, 0x73, 0x08, 0x3c, 0x6b, 0x9e, 0x8a, 0x96, 0x00, + 0xc0, 0xfc, 0xf8, 0x00, 0xc3, 0xfe, 0xff, 0x00, 0xc2, 0xfd, 0xda, 0x00, + 0xb9, 0xf6, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0xe6, 0x1e, 0x00, + 0xc3, 0xff, 0xe5, 0x00, 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xc2, 0xfd, 0xdd, 0x00, + 0xbb, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x52, 0xfc, 0x57, 0x00, 0x52, 0xfc, 0xab, 0x00, 0x52, 0xff, 0xa1, 0x00, + 0x52, 0xf6, 0x57, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x7a, 0x19, 0x5c, 0xe4, + 0x76, 0x07, 0xfc, 0xe5, 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe4, + 0x77, 0x06, 0xee, 0xdb, 0x72, 0x09, 0x38, 0xe4, 0x76, 0x08, 0x41, 0xe3, + 0x76, 0x07, 0xf3, 0xe3, 0x76, 0x07, 0xf8, 0x73, 0x9c, 0x81, 0xa0, 0x00, + 0xc0, 0xfc, 0xf8, 0x00, 0xc3, 0xfe, 0xff, 0x00, 0xc2, 0xfd, 0xda, 0x00, + 0xb6, 0xed, 0x1c, 0x00, 0xbf, 0xf6, 0x1c, 0x00, 0xc1, 0xfd, 0xe5, 0x00, + 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc2, 0xfc, 0xcc, 0x00, 0xb7, 0xef, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x77, 0xcc, 0x0f, 0x31, 0xb2, 0xc9, 0xe1, 0xd2, 0x7b, 0x19, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x08, 0xee, 0xdf, 0x74, 0x09, 0x37, 0xe4, + 0x76, 0x08, 0x41, 0xe3, 0x76, 0x07, 0xf3, 0xe5, 0x77, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xf8, 0x72, 0x9b, 0x82, 0xa1, 0x00, + 0xc0, 0xfc, 0xf8, 0x00, 0xc3, 0xff, 0xfa, 0x00, 0xbe, 0xf7, 0x43, 0x00, + 0xbb, 0xfa, 0x31, 0x00, 0xc3, 0xff, 0xd5, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0xc2, 0xff, 0xa8, 0x00, 0xbd, 0xf8, 0x46, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x44, 0xdd, 0x0f, 0x00, 0x59, 0xfe, 0xbf, 0x00, + 0xb0, 0xfb, 0xff, 0x2d, 0xb3, 0xcd, 0xff, 0xd2, 0x7b, 0x19, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe2, 0x76, 0x08, 0xee, 0xe3, + 0x74, 0x05, 0x37, 0xe0, 0x74, 0x08, 0x42, 0xe3, 0x76, 0x07, 0xf3, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xf8, 0x55, 0xa6, 0xa1, 0x75, 0x00, + 0xbd, 0xf9, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xfc, 0x58, 0x00, + 0xc1, 0xfc, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf8, 0x71, 0x00, + 0xc2, 0xff, 0x7e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x51, 0xfc, 0x65, 0x00, 0x53, 0xfd, 0xff, 0x00, 0x5f, 0xfe, 0xff, 0x00, + 0xb1, 0xfb, 0xff, 0x2d, 0xb3, 0xcd, 0xff, 0xd2, 0x7b, 0x19, 0xff, 0xe3, + 0x76, 0x08, 0xee, 0xe3, 0x74, 0x05, 0x37, 0xd9, 0x72, 0x08, 0x43, 0xe5, + 0x77, 0x07, 0xf3, 0xe3, 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xaf, 0xbf, 0x60, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0xc3, 0xff, 0x95, 0x00, 0xc2, 0xfd, 0x79, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0xc1, 0xf8, 0x25, 0x00, 0xc0, 0xfb, 0xcc, 0x00, + 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x52, 0xfc, 0x63, 0x00, + 0x53, 0xfd, 0xff, 0x00, 0x53, 0xfd, 0xff, 0x00, 0x60, 0xfe, 0xff, 0x00, + 0xb1, 0xfb, 0xff, 0x34, 0xb0, 0xc6, 0xbf, 0xcd, 0x78, 0x19, 0x33, 0x23, + 0xb1, 0xcd, 0x24, 0xdd, 0x79, 0x10, 0xf4, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe1, 0x75, 0x07, 0xb0, 0xdb, 0x6d, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbf, 0xfb, 0xb8, 0x00, 0xbe, 0xf9, 0x2b, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0xc3, 0xff, 0xbf, 0x00, 0xba, 0xf2, 0x3b, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x51, 0xf7, 0x5e, 0x00, 0x53, 0xfe, 0xff, 0x00, + 0x53, 0xfd, 0xff, 0x00, 0x52, 0xfc, 0xff, 0x00, 0x57, 0xfd, 0x8d, 0x00, + 0x40, 0x80, 0x04, 0x00, 0x55, 0xaa, 0x03, 0x00, 0xb2, 0xfc, 0xad, 0x4c, + 0xa8, 0xab, 0xff, 0xdf, 0x78, 0x0c, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xaf, 0xdb, + 0x6d, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0xff, 0xff, 0x01, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xc0, 0xfc, 0x49, 0x00, 0xc2, 0xff, 0xb8, 0x00, 0x40, 0x40, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x51, 0xff, 0x13, 0x00, 0x51, 0xf6, 0x58, 0x00, 0x51, 0xfc, 0x4f, 0x00, + 0x51, 0xf7, 0x3f, 0x00, 0x00, 0x55, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x52, 0xfb, 0x8c, 0x00, 0x6a, 0xfe, 0xff, 0x00, 0xba, 0xfd, 0xff, 0x4b, + 0xa9, 0xac, 0xff, 0xdf, 0x78, 0x0d, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x76, 0x07, 0xaf, 0xbf, 0x60, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x02, 0x00, + 0xc1, 0xfc, 0xb2, 0x00, 0xc1, 0xfc, 0x5b, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x53, 0xff, 0xbc, 0x00, + 0x52, 0xfb, 0xff, 0x00, 0x6a, 0xfe, 0xff, 0x00, 0xba, 0xfd, 0xff, 0x4a, + 0xa9, 0xad, 0xfe, 0xd7, 0x7b, 0x14, 0xa6, 0xaa, 0x71, 0x1c, 0x09, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xba, 0xf4, 0x30, 0x00, 0xc1, 0xf6, 0x1d, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0xf5, 0x1a, 0x00, + 0xc1, 0xfd, 0xd7, 0x00, 0xc1, 0xff, 0x2d, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x52, 0xfc, 0xb8, 0x00, 0x53, 0xfe, 0xff, 0x00, + 0x52, 0xfb, 0xff, 0x00, 0x69, 0xff, 0xec, 0x00, 0xb0, 0xf8, 0x44, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xbd, 0xf8, 0x23, 0x00, + 0xc3, 0xff, 0xe8, 0x00, 0xbf, 0xfa, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0xfa, 0x36, 0x00, + 0xc1, 0xfd, 0xd6, 0x00, 0xc4, 0xff, 0x2b, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x52, 0xf9, 0x57, 0x00, 0x53, 0xfd, 0x8a, 0x00, 0x52, 0xfd, 0x80, 0x00, + 0x4e, 0xf4, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0xbf, 0xff, 0x20, 0x00, 0xc2, 0xfd, 0xd8, 0x00, 0xc2, 0xfe, 0xdd, 0x00, + 0xc1, 0xff, 0x6b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe, 0xfa, 0x37, 0x00, + 0xc2, 0xfd, 0xd6, 0x00, 0xc2, 0xff, 0x50, 0x00, 0x55, 0x55, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0xc2, 0xfb, 0x43, 0x00, 0xc1, 0xfc, 0xdf, 0x00, + 0xc3, 0xff, 0x5e, 0x00, 0xc0, 0xfb, 0x85, 0x00, 0xc0, 0xfc, 0x5d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0xf6, 0x1d, 0x00, + 0xc1, 0xfe, 0xb9, 0x00, 0xc2, 0xfe, 0xaa, 0x00, 0xb6, 0xed, 0x2a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0xbf, 0xf7, 0x20, 0x00, 0xc0, 0xfc, 0x9e, 0x00, + 0xc2, 0xfd, 0xd4, 0x00, 0xc3, 0xff, 0x37, 0x00, 0x00, 0x00, 0x03, 0x00, + 0xff, 0xff, 0x01, 0x00, 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa, 0x03, 0x00, + 0xbf, 0xfc, 0x54, 0x00, 0xc3, 0xff, 0xc9, 0x00, 0xc0, 0xfc, 0xb6, 0x00, + 0xc1, 0xfc, 0x63, 0x00, 0xbe, 0xf8, 0x27, 0x00, 0x92, 0xdb, 0x07, 0x00, + 0xff, 0xff, 0x01, 0x00, 0x40, 0x40, 0x04, 0x00, 0x99, 0xff, 0x05, 0x00, + 0xbf, 0xf8, 0x24, 0x00, 0xc1, 0xfc, 0x5e, 0x00, 0xc3, 0xff, 0xaf, 0x00, + 0xc0, 0xfc, 0xdc, 0x00, 0xc2, 0xff, 0x70, 0x00, 0xaa, 0xe3, 0x09, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x55, 0x55, 0x03, 0x00, 0xbf, 0xff, 0x34, 0x00, 0xbf, 0xf9, 0x84, 0x00, + 0xc2, 0xff, 0xbc, 0x00, 0xc1, 0xfd, 0xda, 0x00, 0xc2, 0xfd, 0xdc, 0x00, + 0xc3, 0xff, 0xdc, 0x00, 0xc1, 0xfc, 0xde, 0x00, 0xc3, 0xff, 0xc5, 0x00, + 0xc1, 0xfd, 0x90, 0x00, 0xbd, 0xf8, 0x49, 0x00, 0xcc, 0xff, 0x05, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0xff, 0x01, 0x00, 0x80, 0xbf, 0x04, 0x00, 0x99, 0x99, 0x05, 0x00, + 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x04, 0x00, 0x92, 0xb6, 0x07, 0x00, + 0xd4, 0xff, 0x06, 0x00, 0xaa, 0xff, 0x06, 0x00, 0x92, 0xb6, 0x07, 0x00, + 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0xb9, 0xff, 0x0b, 0x00, 0xc8, 0xff, 0x0e, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0xaa, 0xea, 0x0c, 0x00, + 0xc3, 0xff, 0x1e, 0x00, 0xc1, 0xfb, 0x42, 0x00, 0xc1, 0xfb, 0x7f, 0x00, + 0xc3, 0xfe, 0xb2, 0x00, 0xc3, 0xff, 0xd6, 0x00, 0xc1, 0xfd, 0xea, 0x00, + 0xc2, 0xfe, 0xf2, 0x00, 0xc2, 0xff, 0xf0, 0x00, 0xc2, 0xfe, 0xe5, 0x01, + 0xc0, 0xfb, 0xcd, 0x00, 0xc2, 0xfc, 0xa3, 0x00, 0xc1, 0xfd, 0x6b, 0x00, + 0xc3, 0xff, 0x33, 0x09, 0xad, 0xe4, 0x1c, 0x00, 0xb6, 0xff, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0xc0, 0xfb, 0x75, 0x00, 0xc2, 0xfd, 0x81, 0x00, + 0xcc, 0xff, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xbf, 0xff, 0x14, 0x00, 0xc1, 0xfc, 0x5a, 0x00, 0xc0, 0xfa, 0x9e, 0x00, + 0xc3, 0xfe, 0xd7, 0x00, 0xc4, 0xff, 0xed, 0x00, 0xc0, 0xfc, 0xcc, 0x00, + 0xc0, 0xfb, 0xab, 0x00, 0xc3, 0xff, 0x91, 0x00, 0xc3, 0xff, 0x80, 0x00, + 0xbe, 0xfb, 0x79, 0x00, 0xc1, 0xfb, 0x7c, 0x00, 0xc3, 0xff, 0x84, 0x00, + 0xc2, 0xff, 0x96, 0x00, 0xc0, 0xfb, 0xb2, 0x00, 0xc1, 0xfd, 0xd7, 0x00, + 0xc4, 0xff, 0xec, 0x00, 0xc3, 0xfe, 0xc7, 0x00, 0xc0, 0xf9, 0x8a, 0x00, + 0xc1, 0xff, 0x42, 0x00, 0xb6, 0xff, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0xfa, 0xa9, 0x00, 0xbf, 0xfb, 0xb3, 0x00, + 0xc3, 0xff, 0x11, 0x00, 0xcc, 0xff, 0x05, 0x00, 0xbd, 0xf6, 0x55, 0x00, + 0xc2, 0xfe, 0xca, 0x00, 0xc2, 0xff, 0xf0, 0x00, 0xc1, 0xfe, 0xd6, 0x02, + 0xbf, 0xf9, 0x7b, 0x00, 0xc1, 0xfa, 0x31, 0x00, 0xba, 0xff, 0x1a, 0x00, + 0xbb, 0xee, 0x0f, 0x1a, 0x99, 0xb3, 0x0a, 0x00, 0x80, 0xff, 0x02, 0x00, + 0xff, 0xff, 0x01, 0x00, 0x55, 0x55, 0x03, 0x00, 0x99, 0x99, 0x05, 0x00, + 0xbf, 0xff, 0x08, 0x00, 0xc3, 0xff, 0x11, 0x00, 0xb9, 0xf0, 0x21, 0x00, + 0xbf, 0xfb, 0x40, 0x00, 0xc4, 0xff, 0x93, 0x00, 0xc2, 0xfe, 0xe5, 0x01, + 0xc0, 0xfb, 0xea, 0x00, 0xc1, 0xfc, 0xb1, 0x00, 0xc2, 0xff, 0x32, 0x00, + 0x55, 0x55, 0x03, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc1, 0xfd, 0xa5, 0x00, 0xc0, 0xfb, 0xba, 0x00, + 0xba, 0xf5, 0x34, 0x00, 0xc3, 0xff, 0x9e, 0x00, 0xc2, 0xff, 0xf0, 0x01, + 0xc0, 0xfa, 0xd1, 0x00, 0xbf, 0xfc, 0x63, 0x00, 0xbf, 0xff, 0x0c, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xff, 0xff, 0x01, 0x00, 0xbd, 0xf8, 0x23, 0x00, 0xb8, 0xed, 0x2b, 0x00, + 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x80, 0x02, 0x00, 0xbd, 0xff, 0x1b, 0x00, + 0xc2, 0xfd, 0x85, 0x01, 0xc0, 0xfa, 0xe1, 0x00, 0xc2, 0xfe, 0xe6, 0x00, + 0xc2, 0xff, 0x70, 0x00, 0xbc, 0xf2, 0x13, 0x00, 0x55, 0x55, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0xc1, 0xfd, 0xa1, 0x00, 0xc2, 0xff, 0xe3, 0x00, + 0xc2, 0xfc, 0xdc, 0x00, 0xc0, 0xfd, 0xe0, 0x00, 0xc2, 0xff, 0x76, 0x00, + 0xbf, 0xff, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x40, 0x80, 0x04, 0x00, + 0xbf, 0xff, 0x44, 0x00, 0xc2, 0xff, 0xd5, 0x00, 0xc2, 0xfd, 0xd9, 0x00, + 0xbe, 0xf9, 0x4f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0xff, 0x01, 0x00, 0xc1, 0xf9, 0x2d, 0x00, 0xbf, 0xfa, 0x94, 0x00, + 0xc2, 0xfe, 0xed, 0x00, 0xc3, 0xff, 0xad, 0x00, 0xba, 0xf5, 0x34, 0x00, + 0x80, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x02, 0xc0, 0xfa, 0x9d, 0x00, 0xc3, 0xff, 0xfe, 0x00, + 0xc2, 0xff, 0xc6, 0x00, 0xba, 0xf4, 0x30, 0x00, 0x9f, 0xbf, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe, 0xf2, 0x27, 0x01, + 0xbf, 0xfa, 0xd3, 0x00, 0xc2, 0xfd, 0xff, 0x00, 0xc3, 0xff, 0xff, 0x00, + 0xc3, 0xfe, 0xe8, 0x03, 0xbb, 0xf5, 0x4f, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xea, 0x0c, 0x03, + 0xbe, 0xf6, 0x52, 0x00, 0xc3, 0xfe, 0xe8, 0x00, 0xc3, 0xff, 0xcf, 0x00, + 0xc1, 0xfa, 0x31, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbe, 0xfc, 0x66, 0x00, 0xc0, 0xfc, 0xa2, 0x00, + 0xc8, 0xff, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x99, 0x66, 0x00, 0x05, 0x94, 0x8e, 0x5f, 0x2b, 0x11, + 0xbd, 0xeb, 0xb6, 0x01, 0xc0, 0xfb, 0xfb, 0x00, 0xc2, 0xfe, 0xff, 0x00, + 0xc3, 0xff, 0xff, 0x00, 0xc3, 0xfe, 0xe8, 0x00, 0xbe, 0xf9, 0x4f, 0x00, + 0xbf, 0xbf, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x05, 0xbd, 0xf1, 0x36, 0x00, 0xc2, 0xfe, 0xd2, 0x00, + 0xc3, 0xff, 0xde, 0x00, 0xbf, 0xfa, 0x30, 0x00, 0x40, 0x80, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x02, 0x00, 0xb6, 0xdb, 0x07, 0x00, + 0x55, 0x55, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xde, 0x73, 0x09, 0x54, 0xe3, 0x76, 0x07, 0xce, 0xcc, + 0x7f, 0x22, 0xaf, 0x1c, 0xb8, 0xdf, 0xaf, 0x00, 0xc0, 0xfc, 0xf8, 0x00, + 0xc2, 0xfe, 0xff, 0x00, 0xc3, 0xff, 0xff, 0x00, 0xc2, 0xfd, 0xda, 0x00, + 0xbe, 0xf9, 0x4f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa, 0x03, 0x00, 0xba, 0xf7, 0x3f, 0x00, + 0xc3, 0xfe, 0xd3, 0x00, 0xc3, 0xff, 0xcd, 0x00, 0xbd, 0xf5, 0x32, 0x00, + 0x55, 0x55, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x55, 0x55, 0x00, 0x03, 0xe3, + 0x78, 0x06, 0x53, 0xe4, 0x77, 0x07, 0xea, 0xe2, 0x75, 0x07, 0xff, 0xe4, + 0x75, 0x07, 0xfc, 0xce, 0x7e, 0x1f, 0xc0, 0x1a, 0xb9, 0xe0, 0xae, 0x01, + 0xc0, 0xfb, 0xfb, 0x00, 0xc2, 0xfe, 0xff, 0x00, 0xc3, 0xff, 0xff, 0x00, + 0xc3, 0xfe, 0xe8, 0x03, 0xbb, 0xf5, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa, 0x03, 0x04, + 0xb9, 0xf2, 0x3a, 0x00, 0xc2, 0xff, 0xeb, 0x00, 0xc3, 0xff, 0xa9, 0x00, + 0xb8, 0xf1, 0x12, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0xbf, 0x80, 0x00, 0x04, 0xdd, 0x75, 0x06, 0x53, 0xe3, + 0x76, 0x08, 0xea, 0xe5, 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe1, + 0x75, 0x08, 0xff, 0xe4, 0x76, 0x07, 0xfb, 0xcc, 0x80, 0x22, 0xae, 0x17, + 0xba, 0xe4, 0xbd, 0x01, 0xc0, 0xfb, 0xfb, 0x00, 0xc2, 0xfe, 0xff, 0x00, + 0xc3, 0xff, 0xff, 0x00, 0xc3, 0xfe, 0xc7, 0x00, 0xb3, 0xec, 0x1b, 0x00, + 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x03, 0xbc, 0xf7, 0x5c, 0x00, 0xc2, 0xfd, 0xef, 0x00, + 0xc3, 0xff, 0x66, 0x00, 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x01, 0x00, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x55, + 0x55, 0x00, 0x03, 0xe3, 0x76, 0x06, 0x52, 0xe4, 0x76, 0x07, 0xdc, 0xe2, + 0x76, 0x07, 0xff, 0xe2, 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xf8, 0xcb, + 0x7f, 0x22, 0xab, 0x18, 0xba, 0xe4, 0xab, 0x00, 0xc0, 0xfc, 0xf8, 0x00, + 0xc3, 0xfe, 0xd9, 0x00, 0xc2, 0xff, 0x97, 0x00, 0xc2, 0xfe, 0xab, 0x00, + 0xbd, 0xf5, 0x4d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbb, 0xee, 0x0f, 0x00, 0xc0, 0xfa, 0x9f, 0x00, + 0xc2, 0xfe, 0xe3, 0x00, 0xc7, 0xff, 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x66, 0x99, 0x05, 0x00, + 0xc0, 0xff, 0x7e, 0x00, 0xc3, 0xfd, 0x78, 0x00, 0x40, 0x40, 0x04, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xe1, + 0x75, 0x06, 0x55, 0xe2, 0x76, 0x08, 0xea, 0xe4, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe2, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xfb, 0xca, 0x80, 0x23, 0x9a, 0x00, 0xc1, 0xfc, 0x5f, 0x02, + 0xbf, 0xf9, 0x84, 0x00, 0xc2, 0xfe, 0xec, 0x00, 0xc4, 0xff, 0xfe, 0x00, + 0xc3, 0xfe, 0xe8, 0x03, 0xbe, 0xf5, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xfb, 0x3a, 0x01, + 0xc0, 0xfb, 0xe6, 0x00, 0xc2, 0xfd, 0xa4, 0x00, 0xbf, 0xff, 0x04, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0xf5, 0x33, 0x00, + 0xc1, 0xfd, 0xe4, 0x00, 0xc3, 0xfd, 0xa1, 0x00, 0x80, 0x80, 0x02, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x92, 0x49, 0x24, 0x07, 0xe3, 0x76, 0x06, 0x52, 0xe5, + 0x77, 0x07, 0xea, 0xe4, 0x76, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe2, 0x76, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe1, + 0x75, 0x08, 0xe8, 0xe0, 0x74, 0x07, 0x4b, 0xbf, 0x80, 0x40, 0x04, 0x07, + 0xbc, 0xf7, 0x9c, 0x01, 0xbe, 0xfb, 0xff, 0x00, 0xc2, 0xfe, 0xff, 0x00, + 0xc3, 0xff, 0xff, 0x00, 0xc1, 0xfe, 0xe8, 0x00, 0xc0, 0xf8, 0x4d, 0x00, + 0xbf, 0xbf, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0xaa, 0xaa, 0x03, 0x00, + 0xc2, 0xfd, 0x9e, 0x01, 0xc0, 0xfc, 0xe6, 0x00, 0xbf, 0xfa, 0x38, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0xaa, 0xff, 0x03, 0x00, 0xc2, 0xff, 0x76, 0x00, + 0xc0, 0xfc, 0xf0, 0x00, 0xbd, 0xf5, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0xe0, 0x78, 0x0d, 0x51, 0xe2, 0x75, 0x07, 0xdc, 0xe4, + 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe2, + 0x75, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe2, + 0x76, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe4, 0x77, 0x07, 0xd9, 0xe5, + 0x74, 0x07, 0x4d, 0xb3, 0x66, 0x1a, 0x0a, 0xe1, 0x74, 0x08, 0x5c, 0xa7, + 0x79, 0x4b, 0xb1, 0x11, 0x99, 0xeb, 0xff, 0x00, 0xbe, 0xfb, 0xff, 0x00, + 0xc2, 0xfe, 0xff, 0x00, 0xc3, 0xff, 0xff, 0x00, 0xc2, 0xfd, 0xd9, 0x00, + 0xc0, 0xf8, 0x4d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xc2, 0xff, 0x2e, 0x00, 0xc3, 0xff, 0xef, 0x00, 0xc0, 0xfb, 0x7e, 0x00, + 0x99, 0xcc, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x0c, 0xaa, 0xdb, 0x15, 0x00, 0xc3, 0xff, 0xb3, 0x00, + 0xc2, 0xff, 0xb3, 0x00, 0x80, 0x80, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0c, + 0xae, 0xdc, 0x16, 0xc2, 0x82, 0x2c, 0xe3, 0xe4, 0x76, 0x07, 0xff, 0xe1, + 0x75, 0x08, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xe1, 0x75, 0x08, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x76, 0x08, 0xe8, 0xde, 0x76, 0x07, 0x4e, 0xff, + 0x55, 0x00, 0x03, 0xe3, 0x74, 0x06, 0x5c, 0xe2, 0x75, 0x08, 0xeb, 0xe1, + 0x75, 0x0b, 0xfe, 0x99, 0x73, 0x59, 0xff, 0x11, 0x99, 0xeb, 0xff, 0x01, + 0xbe, 0xfb, 0xff, 0x00, 0xc2, 0xfe, 0xff, 0x00, 0xc3, 0xff, 0xff, 0x00, + 0xc1, 0xfe, 0xe8, 0x03, 0xbd, 0xf5, 0x4d, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x55, 0xaa, 0x03, 0x00, 0xc4, 0xff, 0xb4, 0x00, 0xc3, 0xfe, 0xba, 0x0b, + 0xaa, 0xd4, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbc, 0xf2, 0x26, 0x00, 0xc0, 0xfb, 0xe3, 0x00, + 0xc3, 0xff, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x06, 0x02, + 0xbd, 0xfa, 0x8e, 0x39, 0xaf, 0xbf, 0xff, 0xc8, 0x80, 0x26, 0xff, 0xe4, + 0x76, 0x08, 0xff, 0xe1, 0x75, 0x08, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xe8, 0xe5, 0x74, 0x07, 0x4d, 0xc6, 0x71, 0x1c, 0x09, 0xde, + 0x73, 0x08, 0x5d, 0xe5, 0x77, 0x07, 0xf7, 0xe5, 0x77, 0x07, 0xff, 0xe1, + 0x75, 0x08, 0xff, 0xe0, 0x75, 0x0b, 0xff, 0x8a, 0x71, 0x69, 0xff, 0x11, + 0xa1, 0xeb, 0xff, 0x01, 0xbe, 0xfb, 0xff, 0x00, 0xc2, 0xfe, 0xff, 0x00, + 0xc3, 0xff, 0xff, 0x00, 0xc1, 0xfe, 0xe8, 0x00, 0xbf, 0xf8, 0x48, 0x00, + 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0xbf, 0xfc, 0x5f, 0x00, 0xc3, 0xff, 0xe8, 0x00, + 0xc1, 0xff, 0x29, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xc1, 0xff, 0x4a, 0x00, 0xc2, 0xfd, 0xe5, 0x00, + 0xbc, 0xf3, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x39, 0xaa, 0x09, 0x00, 0x5a, 0xff, 0x60, 0x00, + 0xa9, 0xfe, 0xec, 0x04, 0xbf, 0xf8, 0xff, 0x48, 0xa9, 0xaf, 0xff, 0xc9, + 0x80, 0x25, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xd9, 0xdc, + 0x73, 0x0a, 0x50, 0xdb, 0x6d, 0x00, 0x07, 0xe3, 0x77, 0x08, 0x65, 0xe3, + 0x75, 0x08, 0xeb, 0xe2, 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xdd, 0x75, 0x0e, 0xff, 0x8b, + 0x71, 0x69, 0xff, 0x12, 0x99, 0xeb, 0xff, 0x00, 0xbe, 0xfb, 0xff, 0x00, + 0xc2, 0xfe, 0xff, 0x00, 0xc2, 0xff, 0xfc, 0x00, 0xc2, 0xfd, 0x93, 0x00, + 0xb7, 0xeb, 0x27, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbb, 0xf4, 0x2d, 0x00, 0xc2, 0xfd, 0xea, 0x00, + 0xc2, 0xff, 0x53, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0xc2, 0xff, 0x7d, 0x00, 0xc2, 0xff, 0xc5, 0x00, + 0xb8, 0xf5, 0x19, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0xf7, 0x40, 0x00, 0x53, 0xfc, 0xea, 0x00, + 0x62, 0xfe, 0xff, 0x00, 0xad, 0xfe, 0xff, 0x04, 0xbf, 0xf8, 0xff, 0x3c, + 0xae, 0xbc, 0xff, 0xd5, 0x7c, 0x18, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe1, + 0x75, 0x08, 0xff, 0xe4, 0x77, 0x07, 0xe8, 0xe5, 0x74, 0x07, 0x4d, 0x99, + 0x66, 0x00, 0x05, 0xde, 0x72, 0x08, 0x5e, 0xe5, 0x76, 0x08, 0xeb, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe2, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe0, + 0x75, 0x0b, 0xff, 0x9a, 0x73, 0x58, 0xff, 0x12, 0x9a, 0xeb, 0xfb, 0x01, + 0xbf, 0xfb, 0xfe, 0x00, 0xc1, 0xfe, 0xcf, 0x00, 0xc4, 0xff, 0x9f, 0x00, + 0xc2, 0xfe, 0xda, 0x03, 0xbb, 0xf5, 0x4f, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xba, 0xff, 0x1a, 0x00, 0xbf, 0xfb, 0xcc, 0x00, + 0xc1, 0xfd, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x02, 0xbf, 0xfa, 0xa7, 0x00, 0xc3, 0xfd, 0xa9, 0x00, + 0xc8, 0xff, 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x51, 0xfc, 0x5f, 0x00, 0x52, 0xfc, 0xff, 0x00, + 0x53, 0xfc, 0xff, 0x00, 0x63, 0xff, 0xff, 0x00, 0xaa, 0xfe, 0xff, 0x01, + 0xbf, 0xfb, 0xff, 0x3d, 0xae, 0xbb, 0xff, 0xc9, 0x80, 0x25, 0xfe, 0xe2, + 0x77, 0x09, 0xe7, 0xd9, 0x73, 0x0a, 0x50, 0xbf, 0x60, 0x00, 0x08, 0xe1, + 0x76, 0x05, 0x5d, 0xe4, 0x77, 0x07, 0xf7, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe2, 0x76, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe1, + 0x75, 0x08, 0xff, 0xe1, 0x76, 0x09, 0xfb, 0xb5, 0x7e, 0x39, 0x8a, 0x02, + 0xc1, 0xfd, 0x6b, 0x02, 0xbe, 0xf9, 0x7e, 0x00, 0xc3, 0xfe, 0xf6, 0x00, + 0xc3, 0xff, 0xff, 0x00, 0xc3, 0xfe, 0xe8, 0x00, 0xbe, 0xf5, 0x4f, 0x00, + 0xbf, 0xbf, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0xc3, 0xff, 0x11, 0x00, 0xc3, 0xfe, 0xb2, 0x01, + 0xbf, 0xfb, 0xb3, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc3, 0xff, 0xbf, 0x00, 0xc1, 0xfc, 0x97, 0x00, + 0xaa, 0xe3, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x53, 0xff, 0x59, 0x00, 0x52, 0xff, 0xfe, 0x00, + 0x53, 0xfc, 0xff, 0x00, 0x53, 0xfc, 0xff, 0x00, 0x6a, 0xfe, 0xff, 0x00, + 0xaa, 0xfe, 0xff, 0x04, 0xbe, 0xf7, 0xf4, 0x55, 0xa5, 0xa2, 0xa8, 0xcb, + 0x80, 0x22, 0x44, 0xbf, 0x60, 0x00, 0x08, 0xe1, 0x75, 0x08, 0x66, 0xe4, + 0x76, 0x08, 0xeb, 0xe5, 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe2, + 0x75, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe2, + 0x76, 0x07, 0xff, 0xe2, 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x76, 0x08, 0xc9, 0xdc, 0x73, 0x0a, 0x33, 0xff, + 0x55, 0x00, 0x03, 0x08, 0xbf, 0xf5, 0x83, 0x00, 0xc0, 0xfc, 0xf8, 0x00, + 0xc2, 0xfe, 0xff, 0x00, 0xc3, 0xff, 0xff, 0x00, 0xc2, 0xfd, 0xda, 0x00, + 0xbe, 0xf5, 0x4f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0xa4, 0xdb, 0x0e, 0x00, 0xc3, 0xff, 0xa1, 0x00, + 0xc3, 0xff, 0xcb, 0x00, 0x55, 0x55, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x80, 0x80, 0x02, 0x00, 0xc3, 0xff, 0xcc, 0x00, 0xc2, 0xfd, 0x8d, 0x1c, + 0x8e, 0xaa, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x03, 0x50, 0xf9, 0x56, 0x00, 0x52, 0xfe, 0xfe, 0x00, + 0x53, 0xff, 0xff, 0x00, 0x53, 0xfc, 0xff, 0x00, 0x53, 0xfc, 0xff, 0x00, + 0x60, 0xfe, 0xf7, 0x00, 0x91, 0xff, 0x5d, 0x0f, 0xa5, 0xe1, 0x11, 0x00, + 0x80, 0x80, 0x02, 0xe3, 0x77, 0x06, 0x5c, 0xe4, 0x76, 0x08, 0xeb, 0xe1, + 0x75, 0x08, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xe1, 0x75, 0x08, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe2, 0x76, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xd1, 0xe3, 0x75, 0x07, 0x25, 0xc8, 0x6d, 0x12, 0x0e, 0xe3, + 0x75, 0x07, 0x92, 0xcd, 0x7e, 0x20, 0xb6, 0x1b, 0xba, 0xe2, 0xad, 0x01, + 0xc0, 0xfb, 0xfb, 0x00, 0xc2, 0xfe, 0xff, 0x00, 0xc3, 0xff, 0xff, 0x00, + 0xc3, 0xfe, 0xe8, 0x03, 0xbb, 0xf5, 0x4f, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa2, 0xd1, 0x0b, 0x00, 0xc2, 0xfc, 0x9a, 0x00, + 0xc3, 0xff, 0xd8, 0x00, 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0xc2, 0xfd, 0xcd, 0x00, 0xc2, 0xff, 0x8b, 0x00, + 0x99, 0xff, 0x05, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x51, 0xfb, 0x3c, 0x00, 0x52, 0xfc, 0xdf, 0x00, + 0x52, 0xff, 0xe2, 0x00, 0x53, 0xff, 0xe0, 0x00, 0x52, 0xfb, 0xd6, 0x00, + 0x51, 0xf9, 0x55, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa, 0xaa, 0x03, 0xd7, + 0x76, 0x10, 0x5f, 0xe4, 0x76, 0x07, 0xf7, 0xe5, 0x77, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xe1, 0x75, 0x08, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xfd, 0xe2, 0x75, 0x08, 0xca, 0xd8, + 0x6f, 0x07, 0x27, 0xeb, 0x76, 0x00, 0x0d, 0xe5, 0x76, 0x08, 0x88, 0xe1, + 0x75, 0x08, 0xf8, 0xe4, 0x76, 0x07, 0xfb, 0xcc, 0x80, 0x22, 0xae, 0x17, + 0xba, 0xe5, 0xbd, 0x01, 0xc0, 0xfb, 0xfb, 0x00, 0xc2, 0xfe, 0xff, 0x00, + 0xc3, 0xff, 0xff, 0x00, 0xc3, 0xfe, 0xe8, 0x00, 0xbb, 0xf5, 0x4f, 0x00, + 0xaa, 0xff, 0x03, 0x00, 0xc6, 0xff, 0x09, 0x00, 0xc0, 0xfc, 0x99, 0x00, + 0xc1, 0xfe, 0xd7, 0x00, 0x80, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0xfb, 0xbf, 0x00, 0xc1, 0xfc, 0x93, 0x00, + 0xb6, 0xff, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x55, 0xff, 0x06, 0x00, 0x4d, 0xf2, 0x28, 0x00, + 0x51, 0xee, 0x2c, 0x00, 0x4e, 0xf8, 0x24, 0x00, 0x55, 0xff, 0x18, 0x00, + 0x33, 0x99, 0x05, 0x00, 0x00, 0x00, 0x03, 0x1c, 0xb7, 0xe3, 0x2e, 0xb3, + 0x86, 0x3c, 0xe0, 0xe2, 0x75, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe2, + 0x75, 0x07, 0xff, 0xe4, 0x77, 0x06, 0xc9, 0xe3, 0x76, 0x05, 0x36, 0xbb, + 0x66, 0x11, 0x0f, 0xe1, 0x75, 0x07, 0x89, 0xe5, 0x77, 0x07, 0xf3, 0xe5, + 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xf8, 0xcc, + 0x80, 0x22, 0xae, 0x1a, 0xb9, 0xe2, 0xae, 0x00, 0xc0, 0xfc, 0xf8, 0x00, + 0xc2, 0xfe, 0xff, 0x00, 0xc3, 0xff, 0xff, 0x00, 0xc2, 0xfc, 0xcc, 0x00, + 0xb2, 0xe6, 0x1e, 0x00, 0xbf, 0xff, 0x0c, 0x00, 0xc3, 0xff, 0xa2, 0x00, + 0xc0, 0xfc, 0xcc, 0x00, 0x55, 0x55, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc2, 0xfd, 0xa4, 0x00, 0xbf, 0xfa, 0xa4, 0x00, + 0xb1, 0xeb, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x71, 0xc6, 0x09, 0x02, 0xb9, 0xfb, 0x87, 0x1e, + 0xb9, 0xdd, 0xfa, 0xb6, 0x86, 0x39, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe1, + 0x75, 0x08, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xd1, 0xd2, 0x73, 0x0d, 0x28, 0xea, 0x6a, 0x00, 0x0c, 0xe4, + 0x76, 0x07, 0x97, 0xe2, 0x76, 0x07, 0xf9, 0xe2, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xfb, 0xce, 0x7f, 0x20, 0xbf, 0x1a, 0xb9, 0xe0, 0xae, 0x01, + 0xc0, 0xfb, 0xfb, 0x00, 0xc2, 0xfe, 0xfc, 0x00, 0xc3, 0xff, 0xa1, 0x00, + 0xbb, 0xee, 0x0f, 0x0c, 0xaa, 0xdb, 0x15, 0x00, 0xc4, 0xff, 0xb4, 0x00, + 0xc4, 0xff, 0xb1, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0xc3, 0xff, 0x7b, 0x00, 0xc2, 0xfe, 0xbc, 0x0b, + 0xaa, 0xdf, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x40, 0xdf, 0x08, 0x00, 0x5a, 0xff, 0x69, 0x00, 0x8f, 0xfc, 0xf4, 0x00, + 0xbf, 0xfc, 0xff, 0x1e, 0xb8, 0xdd, 0xff, 0xb0, 0x88, 0x3f, 0xff, 0xdf, + 0x76, 0x0a, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xe1, 0x75, 0x08, 0xfd, 0xe3, 0x75, 0x06, 0xca, 0xe3, + 0x75, 0x07, 0x25, 0xdb, 0x6d, 0x12, 0x0e, 0xe0, 0x74, 0x07, 0x8b, 0xe5, + 0x77, 0x07, 0xf8, 0xe5, 0x77, 0x07, 0xff, 0xe2, 0x76, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe1, + 0x75, 0x08, 0xff, 0xe4, 0x76, 0x07, 0xfb, 0xc9, 0x7f, 0x24, 0xa5, 0x0d, + 0xbf, 0xf0, 0xaf, 0x01, 0xc0, 0xfb, 0xae, 0x00, 0xc2, 0xf5, 0x19, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xb6, 0xed, 0x1c, 0x00, 0xc1, 0xfd, 0xcf, 0x00, + 0xc2, 0xfd, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0xbe, 0xfc, 0x4a, 0x00, 0xc3, 0xff, 0xd9, 0x00, + 0xc1, 0xff, 0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x53, 0xfa, 0x69, 0x00, 0x53, 0xfe, 0xf5, 0x00, 0x5f, 0xfe, 0xff, 0x00, + 0x97, 0xfc, 0xff, 0x01, 0xc0, 0xfb, 0xff, 0x2d, 0xb3, 0xcd, 0xff, 0xb0, + 0x88, 0x3f, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x75, 0x06, 0xca, 0xdf, 0x72, 0x09, 0x38, 0xd8, + 0x76, 0x00, 0x0d, 0xe5, 0x78, 0x08, 0x88, 0xe3, 0x76, 0x07, 0xf3, 0xe2, + 0x75, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe2, + 0x76, 0x07, 0xff, 0xe2, 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xfb, 0xe3, 0x76, 0x07, 0x9c, 0x8b, + 0x97, 0x68, 0x16, 0x00, 0xb9, 0xe8, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xc6, 0xff, 0x2d, 0x00, 0xc1, 0xfd, 0xec, 0x00, + 0xbe, 0xf6, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xba, 0xf1, 0x25, 0x00, 0xc2, 0xfd, 0xdd, 0x00, + 0xc2, 0xff, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, + 0x53, 0xfd, 0xa9, 0x01, 0x52, 0xfb, 0xff, 0x00, 0x53, 0xfe, 0xff, 0x00, + 0x5a, 0xff, 0xff, 0x00, 0x97, 0xfc, 0xff, 0x01, 0xc0, 0xfb, 0xff, 0x1e, + 0xb8, 0xdd, 0xff, 0xb6, 0x86, 0x39, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe3, + 0x76, 0x07, 0xd1, 0xe3, 0x75, 0x07, 0x25, 0xd8, 0x76, 0x00, 0x0d, 0xdf, + 0x75, 0x07, 0x98, 0xe4, 0x76, 0x07, 0xf8, 0xe5, 0x77, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xe1, 0x75, 0x08, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe2, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe6, 0x76, 0x07, 0xbe, 0xd8, 0x74, 0x08, 0x21, 0x80, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0xc2, 0xfc, 0x65, 0x00, 0xc4, 0xff, 0xe8, 0x00, + 0xbc, 0xf9, 0x2a, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc3, 0xff, 0x11, 0x00, 0xc0, 0xfb, 0xb1, 0x00, + 0xc0, 0xfc, 0x95, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x53, 0xff, 0xa5, 0x00, 0x53, 0xfd, 0xff, 0x00, 0x52, 0xfb, 0xff, 0x00, + 0x53, 0xfe, 0xff, 0x00, 0x5a, 0xff, 0xff, 0x00, 0x99, 0xfc, 0xff, 0x00, + 0xbf, 0xfc, 0xff, 0x1d, 0xb8, 0xde, 0xf1, 0xa9, 0x8a, 0x47, 0xba, 0xc6, + 0x73, 0x1a, 0x28, 0x49, 0x92, 0x92, 0x07, 0xc5, 0x81, 0x2a, 0x7f, 0xe2, + 0x77, 0x09, 0xf9, 0xe1, 0x75, 0x08, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe5, + 0x77, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xfb, 0xe3, + 0x76, 0x08, 0xbe, 0xe4, 0x6b, 0x0d, 0x13, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x02, 0xbf, 0xfa, 0xa7, 0x00, 0xc1, 0xfe, 0xb6, 0x00, + 0xc9, 0xff, 0x13, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0xaa, 0xff, 0x03, 0x00, 0xc4, 0xff, 0x74, 0x00, + 0xc1, 0xfd, 0xe1, 0x00, 0xba, 0xf5, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x53, 0xfd, 0xa0, 0x00, 0x53, 0xff, 0xff, 0x00, 0x53, 0xfd, 0xff, 0x00, + 0x52, 0xfb, 0xff, 0x00, 0x53, 0xfe, 0xff, 0x00, 0x60, 0xfe, 0xff, 0x00, + 0x91, 0xfd, 0xd5, 0x00, 0xb8, 0xf9, 0x5a, 0x00, 0xb4, 0xf0, 0x11, 0x00, + 0x00, 0x00, 0x01, 0x0c, 0xb6, 0xe7, 0x2a, 0x4f, 0xa7, 0xa8, 0xd9, 0xcb, + 0x7f, 0x22, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe2, 0x75, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe3, 0x76, 0x07, 0xff, 0xe2, + 0x75, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xfb, 0xe5, 0x76, 0x07, 0xaf, 0xd8, + 0x74, 0x08, 0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0xfb, 0x40, 0x00, 0xbc, 0xf7, 0x41, 0x00, + 0x80, 0xbf, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0xbf, 0xfa, 0x30, 0x00, + 0xc3, 0xff, 0xe2, 0x00, 0xc0, 0xfb, 0x7e, 0x00, 0x55, 0x55, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, + 0x52, 0xf8, 0x99, 0x00, 0x53, 0xfe, 0xff, 0x00, 0x53, 0xff, 0xff, 0x00, + 0x53, 0xfd, 0xff, 0x01, 0x52, 0xfb, 0xff, 0x00, 0x53, 0xfe, 0xd2, 0x00, + 0x57, 0xff, 0x35, 0x00, 0x55, 0x55, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x49, 0xff, 0x07, 0x00, 0x99, 0xff, 0xa7, 0x05, 0xc0, 0xf7, 0xfd, 0x3f, + 0xad, 0xb9, 0xff, 0xd6, 0x7c, 0x16, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe1, + 0x75, 0x08, 0xff, 0xe4, 0x76, 0x07, 0xff, 0xe5, 0x77, 0x07, 0xff, 0xe4, + 0x76, 0x07, 0xff, 0xe0, 0x74, 0x07, 0xbf, 0xe6, 0x7b, 0x08, 0x1f, 0xff, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x66, 0x99, 0x05, 0x00, + 0xc3, 0xff, 0x99, 0x00, 0xc3, 0xff, 0xdc, 0x00, 0xb7, 0xf2, 0x27, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x52, 0xfa, 0x35, 0x02, 0x50, 0xfa, 0x92, 0x00, 0x53, 0xfd, 0x8a, 0x00, + 0x54, 0xff, 0x80, 0x00, 0x52, 0xfd, 0x73, 0x00, 0x4e, 0xf4, 0x2e, 0x00, + 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xef, 0x10, 0x00, + 0x52, 0xfc, 0xa4, 0x00, 0x62, 0xff, 0xff, 0x00, 0xaa, 0xfe, 0xff, 0x01, + 0xbf, 0xfb, 0xff, 0x3e, 0xad, 0xba, 0xff, 0xcb, 0x80, 0x23, 0xff, 0xe4, + 0x76, 0x08, 0xff, 0xe1, 0x75, 0x08, 0xff, 0xe5, 0x77, 0x07, 0xfb, 0xe6, + 0x76, 0x07, 0xbe, 0xd9, 0x73, 0x0d, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xb8, 0xf1, 0x24, 0x00, 0xc2, 0xfe, 0xd9, 0x00, 0xc2, 0xff, 0x86, 0x00, + 0xa2, 0xd1, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0xfb, 0x3c, 0x00, + 0x52, 0xfc, 0xe8, 0x00, 0x53, 0xfc, 0xff, 0x00, 0x6a, 0xfe, 0xff, 0x00, + 0xaa, 0xfe, 0xff, 0x04, 0xbf, 0xf7, 0xff, 0x4b, 0xa9, 0xac, 0xff, 0xcb, + 0x80, 0x23, 0xff, 0xe5, 0x77, 0x07, 0xfb, 0xe1, 0x75, 0x07, 0xb0, 0xd7, + 0x70, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x02, 0x03, 0xbe, 0xf7, 0x5e, 0x00, 0xc3, 0xff, 0xe1, 0x00, + 0xc2, 0xff, 0x3b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x51, 0xff, 0x3c, 0x00, + 0x52, 0xff, 0xe8, 0x00, 0x53, 0xfc, 0xff, 0x00, 0x53, 0xfc, 0xff, 0x00, + 0x63, 0xff, 0xff, 0x00, 0xad, 0xfe, 0xff, 0x04, 0xbf, 0xf7, 0xff, 0x3d, + 0xad, 0xbb, 0xfc, 0xc2, 0x83, 0x2c, 0xa4, 0xd7, 0x78, 0x08, 0x20, 0x33, + 0x33, 0x33, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xb8, 0xf8, 0x24, 0x00, 0xbf, 0xfb, 0x77, 0x00, + 0xbf, 0xff, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbb, 0xee, 0x0f, 0x00, 0xc0, 0xfc, 0xa1, 0x00, + 0xc2, 0xfe, 0xd4, 0x00, 0xbd, 0xff, 0x1f, 0x00, 0x55, 0x55, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x52, 0xf6, 0x3b, 0x00, + 0x53, 0xff, 0xe7, 0x00, 0x53, 0xff, 0xff, 0x00, 0x53, 0xfc, 0xff, 0x00, + 0x53, 0xfc, 0xff, 0x00, 0x62, 0xff, 0xfd, 0x00, 0xa2, 0xfe, 0xcd, 0x04, + 0xb5, 0xf4, 0x45, 0x00, 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x55, 0x55, 0x03, 0x00, + 0xc2, 0xff, 0x15, 0x00, 0xc2, 0xfe, 0xc1, 0x00, 0xc1, 0xfc, 0xe9, 0x00, + 0xbc, 0xf8, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbc, 0xf9, 0x2a, 0x00, + 0xc0, 0xfb, 0xc6, 0x00, 0xc3, 0xfe, 0xbb, 0x00, 0xbf, 0xff, 0x28, 0x00, + 0x55, 0x55, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xf4, 0x2e, 0x00, + 0x53, 0xfa, 0xe1, 0x00, 0x53, 0xff, 0xfc, 0x00, 0x53, 0xfe, 0xfc, 0x00, + 0x53, 0xfc, 0xfa, 0x00, 0x51, 0xfb, 0xc0, 0x00, 0x50, 0xff, 0x33, 0x00, + 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb5, 0xef, 0x1f, 0x00, + 0xc0, 0xfa, 0xa9, 0x00, 0xc2, 0xfe, 0xfe, 0x00, 0xc3, 0xff, 0xe9, 0x00, + 0xb9, 0xf2, 0x28, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00, + 0xbf, 0xf9, 0x28, 0x01, 0xc0, 0xfb, 0xd4, 0x00, 0xc1, 0xfe, 0xb9, 0x00, + 0xc1, 0xff, 0x1d, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x49, 0xdb, 0x07, 0x00, + 0x52, 0xf6, 0x38, 0x04, 0x50, 0xf7, 0x43, 0x00, 0x52, 0xff, 0x38, 0x00, + 0x50, 0xfa, 0x30, 0x00, 0x4c, 0xec, 0x1b, 0x00, 0x55, 0x55, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0xbf, 0xf2, 0x14, 0x00, 0xc3, 0xff, 0xa6, 0x00, + 0xc2, 0xfe, 0xf3, 0x02, 0xc0, 0xfc, 0xa5, 0x00, 0xc3, 0xfe, 0xe8, 0x00, + 0xc8, 0xff, 0x25, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbf, 0xf9, 0x28, 0x00, 0xc1, 0xfb, 0xc8, 0x00, + 0xc3, 0xff, 0xcc, 0x00, 0xc3, 0xff, 0x33, 0x00, 0x8e, 0xc6, 0x09, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0xbf, 0xff, 0x04, 0x00, + 0xc4, 0xf8, 0x27, 0x01, 0xbf, 0xfa, 0xbc, 0x00, 0xc2, 0xfe, 0xe6, 0x00, + 0xc6, 0xff, 0x59, 0x00, 0xc1, 0xfc, 0x4a, 0x00, 0xc0, 0xfc, 0xe0, 0x00, + 0xbf, 0xf8, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00, 0xbb, 0xf4, 0x2d, 0x00, + 0xc0, 0xfa, 0xa6, 0x00, 0xc3, 0xff, 0xdd, 0x00, 0xc2, 0xff, 0x79, 0x00, + 0xb6, 0xed, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xb3, 0xe6, 0x14, 0x00, 0xbf, 0xfa, 0x6b, 0x00, + 0xc3, 0xff, 0xd8, 0x00, 0xc2, 0xfe, 0xcd, 0x00, 0xbc, 0xf9, 0x50, 0x00, + 0xaa, 0xd4, 0x06, 0x00, 0xc4, 0xff, 0x1a, 0x00, 0xbf, 0xfa, 0x60, 0x00, + 0xaf, 0xdf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbf, 0xef, 0x10, 0x02, 0xbd, 0xf8, 0x68, 0x00, 0xc3, 0xff, 0xdd, 0x00, + 0xc3, 0xff, 0xd0, 0x00, 0xbf, 0xfa, 0x60, 0x00, 0x9d, 0xd8, 0x0d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x8e, 0xc6, 0x09, 0x00, + 0xc4, 0xff, 0x4e, 0x00, 0xc2, 0xff, 0xc6, 0x00, 0xc1, 0xfc, 0xed, 0x00, + 0xc1, 0xfd, 0x99, 0x00, 0xc3, 0xff, 0x1e, 0x00, 0xaa, 0xaa, 0x03, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x02, 0x00, 0xb7, 0xf4, 0x2e, 0x00, + 0xc1, 0xfd, 0xa9, 0x00, 0xc4, 0xff, 0xe5, 0x00, 0xc1, 0xfb, 0xcd, 0x00, + 0xc0, 0xfa, 0x6d, 0x00, 0xc1, 0xff, 0x29, 0x00, 0xc5, 0xff, 0x16, 0x12, + 0x92, 0xc8, 0x0e, 0x00, 0x80, 0xbf, 0x04, 0x00, 0x00, 0xff, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0xff, 0xff, 0x01, 0x00, + 0x80, 0xff, 0x02, 0x00, 0xb3, 0xe6, 0x0a, 0x00, 0xb1, 0xe9, 0x17, 0x00, + 0xc3, 0xf8, 0x26, 0x00, 0xc3, 0xff, 0x62, 0x00, 0xc0, 0xfb, 0xc6, 0x00, + 0xc2, 0xfd, 0xed, 0x00, 0xc2, 0xff, 0xc8, 0x00, 0xc2, 0xfc, 0x4f, 0x20, + 0x80, 0x9f, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x80, 0x9f, 0x08, 0x00, 0xc0, 0xff, 0x3d, 0x00, 0xc1, 0xfd, 0x84, 0x00, + 0xc0, 0xfb, 0xc1, 0x00, 0xc1, 0xfd, 0xdd, 0x00, 0xc3, 0xfe, 0xc0, 0x00, + 0xc1, 0xfd, 0x9d, 0x00, 0xc1, 0xfb, 0x83, 0x00, 0xbf, 0xfa, 0x70, 0x00, + 0xc3, 0xff, 0x66, 0x00, 0xc3, 0xfc, 0x66, 0x00, 0xbf, 0xfa, 0x70, 0x00, + 0xbf, 0xfb, 0x80, 0x00, 0xc3, 0xff, 0x99, 0x00, 0xc1, 0xfe, 0xbd, 0x00, + 0xc1, 0xfd, 0xe1, 0x00, 0xc3, 0xff, 0xd3, 0x00, 0xc3, 0xff, 0x99, 0x00, + 0xbe, 0xf9, 0x56, 0x00, 0xb3, 0xe6, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0xaa, 0xff, 0x06, 0x00, + 0xc5, 0xff, 0x16, 0x00, 0xbb, 0xf5, 0x31, 0x00, 0xc1, 0xfa, 0x67, 0x00, + 0xc3, 0xff, 0x9d, 0x00, 0xc2, 0xfe, 0xc6, 0x00, 0xc0, 0xfc, 0xdc, 0x00, + 0xc2, 0xfe, 0xe7, 0x00, 0xc4, 0xff, 0xe8, 0x00, 0xc3, 0xfe, 0xe1, 0x01, + 0xc0, 0xfa, 0xd1, 0x00, 0xc3, 0xff, 0xad, 0x00, 0xc3, 0xff, 0x78, 0x00, + 0xbd, 0xfb, 0x3e, 0x08, 0xb2, 0xe8, 0x21, 0x00, 0xcc, 0xff, 0x0a, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0xbf, 0x04, 0x1c, + 0x8e, 0xaa, 0x09, 0x00, 0x92, 0xdb, 0x07, 0x00, 0xbf, 0xff, 0x04, 0x00, + 0x80, 0x80, 0x02, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, + 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 +}; + +static const size_t favicon_ico_len = 15483; + +#endif // FAVICON_ICO_H_ diff --git a/src/flash_storage.c b/src/flash_storage.c new file mode 100644 index 00000000..52f0106b --- /dev/null +++ b/src/flash_storage.c @@ -0,0 +1,83 @@ +#include "flash_storage.h" +#include "pico/flash.h" +#include "hardware/flash.h" +#include "hardware/sync.h" +#include +#include + +// Flash storage is memory-mapped at XIP_BASE + offset +static const uint8_t* ml_history_flash_ptr = (const uint8_t*)(XIP_BASE + FLASH_STORAGE_ML_HISTORY_OFFSET); + +// Static buffer for write operations (must persist during flash_safe_execute) +static uint8_t g_write_buffer[FLASH_STORAGE_ML_HISTORY_SIZE] __attribute__((aligned(4))); +static size_t g_write_len = 0; + +void flash_storage_init(void) { + printf("Flash storage: ML history at offset 0x%X (XIP 0x%08X)\n", + FLASH_STORAGE_ML_HISTORY_OFFSET, + (unsigned int)(XIP_BASE + FLASH_STORAGE_ML_HISTORY_OFFSET)); +} + +bool flash_ml_history_read(uint8_t* data, size_t len) { + if (data == NULL || len == 0 || len > FLASH_STORAGE_ML_HISTORY_SIZE) { + return false; + } + + // Direct read from XIP-mapped flash - no special handling needed + memcpy(data, ml_history_flash_ptr, len); + return true; +} + +// Callback for flash_safe_execute - performs the actual erase + program +static void flash_write_callback(void* param) { + (void)param; + + // Erase the sector first (4KB minimum) + flash_range_erase(FLASH_STORAGE_ML_HISTORY_OFFSET, FLASH_SECTOR_SIZE); + + // Program in page-sized chunks (256 bytes) + size_t bytes_written = 0; + while (bytes_written < g_write_len) { + size_t chunk_size = g_write_len - bytes_written; + if (chunk_size > FLASH_PAGE_SIZE) { + chunk_size = FLASH_PAGE_SIZE; + } + + // Pad to page size if needed (flash_range_program requires page-aligned writes) + uint8_t page_buffer[FLASH_PAGE_SIZE]; + memset(page_buffer, 0xFF, FLASH_PAGE_SIZE); // 0xFF is erased state + memcpy(page_buffer, g_write_buffer + bytes_written, chunk_size); + + flash_range_program(FLASH_STORAGE_ML_HISTORY_OFFSET + bytes_written, + page_buffer, FLASH_PAGE_SIZE); + bytes_written += FLASH_PAGE_SIZE; + } +} + +bool flash_ml_history_write(const uint8_t* data, size_t len) { + if (data == NULL || len == 0 || len > FLASH_STORAGE_ML_HISTORY_SIZE) { + printf("Flash storage: Invalid write parameters (len=%u)\n", (unsigned int)len); + return false; + } + + // Copy data to static buffer (must persist during flash_safe_execute) + memcpy(g_write_buffer, data, len); + g_write_len = len; + + // Use flash_safe_execute for FreeRTOS-safe flash programming + // This handles multi-core coordination and disables interrupts during flash ops + int rc = flash_safe_execute(flash_write_callback, NULL, 1000); // 1 second timeout + + if (rc != PICO_OK) { + printf("Flash storage: flash_safe_execute failed with %d\n", rc); + return false; + } + + // Verify write + if (memcmp(ml_history_flash_ptr, data, len) != 0) { + printf("Flash storage: Write verification failed\n"); + return false; + } + + return true; +} diff --git a/src/flash_storage.h b/src/flash_storage.h new file mode 100644 index 00000000..cfe6dd55 --- /dev/null +++ b/src/flash_storage.h @@ -0,0 +1,50 @@ +#ifndef FLASH_STORAGE_H_ +#define FLASH_STORAGE_H_ + +#include +#include +#include + +// Flash Storage Configuration +// Pico W has 2MB flash. Reserve last 8KB for persistent storage: +// - 0x1FE000 (2MB - 8KB): ML history sector (4KB) +// - 0x1FF000 (2MB - 4KB): Reserved for future use +// +// Note: FLASH_SECTOR_SIZE is 4KB, FLASH_PAGE_SIZE is 256 bytes +// flash_range_erase() erases in sector units, flash_range_program() writes in page units + +#define FLASH_STORAGE_ML_HISTORY_OFFSET (2 * 1024 * 1024 - 8 * 1024) // 0x1FE000 +#define FLASH_STORAGE_ML_HISTORY_SIZE (4 * 1024) // 4KB sector + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initialize flash storage subsystem + * Must be called after FreeRTOS scheduler starts + */ +void flash_storage_init(void); + +/** + * Read ML history from flash + * @param data Buffer to read into + * @param len Number of bytes to read (must not exceed sector size) + * @return true on success + */ +bool flash_ml_history_read(uint8_t* data, size_t len); + +/** + * Write ML history to flash + * Erases the sector and writes new data + * @param data Data to write + * @param len Number of bytes to write (must not exceed sector size) + * @return true on success + */ +bool flash_ml_history_write(const uint8_t* data, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif // FLASH_STORAGE_H_ diff --git a/src/gng_scale.c b/src/gng_scale.c index c2290f03..d25bcfcd 100644 --- a/src/gng_scale.c +++ b/src/gng_scale.c @@ -76,34 +76,59 @@ void _gng_scale_listener_task(void *p) { gngscale_standard_data_format_t frame; while (true) { - // Request for a data transfer (ESC p) - uart_puts(SCALE_UART, CMD_REQUEST_DATA_TRANSFER); - - // Read all data - while (uart_is_readable(SCALE_UART)) { - char ch = uart_getc(SCALE_UART); - frame.bytes[string_buf_idx++] = ch; - - // If we have received 14 bytes then we can decode the message - if (string_buf_idx == sizeof(gngscale_standard_data_format_t)) { - // Data is ready, send to decode - scale_config.current_scale_measurement = _decode_measurement_msg(&frame); - // Signal the data is ready - if (scale_config.scale_measurement_ready) { - xSemaphoreGive(scale_config.scale_measurement_ready); + // Reset frame buffer index at the start of each poll cycle (fixes partial-frame carryover on timeout) + string_buf_idx = 0; + + // Clear any stale data in RX buffer before sending request + while (uart_is_readable(SCALE_UART)) { + uart_getc(SCALE_UART); + } + + // Request for a data transfer using mutex-protected write + scale_write(CMD_REQUEST_DATA_TRANSFER, strlen(CMD_REQUEST_DATA_TRANSFER)); + + // Wait for scale to respond (up to 200ms) + TickType_t start_tick = xTaskGetTickCount(); + bool got_response = false; + + while ((xTaskGetTickCount() - start_tick) < pdMS_TO_TICKS(200)) { + while (uart_is_readable(SCALE_UART)) { + char ch = uart_getc(SCALE_UART); + frame.bytes[string_buf_idx++] = ch; + + // If we have received 14 bytes then we can decode the message + if (string_buf_idx == sizeof(gngscale_standard_data_format_t)) { + // Data is ready, send to decode + scale_config.current_scale_measurement = _decode_measurement_msg(&frame); + // Signal the data is ready + if (scale_config.scale_measurement_ready) { + xSemaphoreGive(scale_config.scale_measurement_ready); + } + + // Reset + string_buf_idx = 0; + got_response = true; } - // Reset - string_buf_idx = 0; + // \n is the terminator. Reset on receiving it. + if (ch == '\n') { + string_buf_idx = 0; + if (got_response) { + break; + } + } } - // \n is the terminator. We shall reset the receive of message on receiving any of those character. - if (ch =='\n') { - string_buf_idx = 0; + if (got_response) { + break; } + + // Small delay before checking again + vTaskDelay(pdMS_TO_TICKS(10)); } - vTaskDelay(pdMS_TO_TICKS(250)); + // Wait before next request + vTaskDelay(pdMS_TO_TICKS(50)); } } diff --git a/src/rest_ai_tuning.c b/src/rest_ai_tuning.c new file mode 100644 index 00000000..1a0b3c51 --- /dev/null +++ b/src/rest_ai_tuning.c @@ -0,0 +1,601 @@ +#include "rest_ai_tuning.h" +#include "ai_tuning.h" +#include "profile.h" +#include "http_rest.h" +#include "common.h" +#include "app.h" +#include "app_state.h" +#include "encoder.h" +#include "charge_mode.h" +#include +#include +#include +#include +#include + +// External references for triggering charge mode +extern charge_mode_config_t charge_mode_config; + +// JSON response buffer +static char ai_tuning_json_buffer[2048]; + +// Helper function for buffer overflow errors +static bool send_buffer_overflow_error(struct fs_file *file) { + static const char overflow_error[] = "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: application/json\r\n\r\n" + "{\"success\":false,\"error\":\"Response buffer overflow\"}"; + file->data = overflow_error; + file->len = sizeof(overflow_error) - 1; + file->index = file->len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; +} + +bool http_rest_ai_tuning_start(struct fs_file *file, int num_params, + char *params[], char *values[]) { + int profile_idx = -1; + float target_weight = -1.0f; // -1 = not specified, use default + + // Parse parameters + for (int idx = 0; idx < num_params; idx++) { + if (strcmp(params[idx], "profile_idx") == 0) { + profile_idx = atoi(values[idx]); + } + else if (strcmp(params[idx], "target") == 0) { + target_weight = strtof(values[idx], NULL); + } + } + + // Set target weight - use provided value or default to 30.0 grains + if (target_weight <= 0.0f) { + target_weight = 30.0f; // Reasonable default for powder charge + } + charge_mode_config.target_charge_weight = target_weight; + printf("AI Tuning: Target weight set to %.2f grains\n", target_weight); + + if (profile_idx < 0 || profile_idx > 7) { + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":false,\"error\":\"Invalid profile_idx (must be 0-7)\"}", + http_json_header); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; + } + + // Get profile + profile_t* profile = profile_select(profile_idx); + if (!profile) { + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":false,\"error\":\"Failed to select profile\"}", + http_json_header); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; + } + + // Start tuning + if (!ai_tuning_start(profile)) { + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":false,\"error\":\"Failed to start AI tuning\"}", + http_json_header); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; + } + + // CRITICAL FIX: Trigger entering charge mode so tuning actually runs + // Without this, ai_tuning just sets internal state but nothing happens + if (charge_mode_config.charge_mode_state == CHARGE_MODE_EXIT) { + // Set exit_state to enter charge mode from REST + exit_state = APP_STATE_ENTER_CHARGE_MODE_FROM_REST; + + // Signal the menu to transition to charge mode + ButtonEncoderEvent_t button_event = OVERRIDE_FROM_REST; + xQueueSend(encoder_event_queue, &button_event, 0); + + printf("AI Tuning: Triggering charge mode entry\n"); + } + + // Success + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":true,\"message\":\"AI tuning started - entering charge mode\"," + "\"profile\":\"%s\",\"target_weight\":%.2f}", + http_json_header, profile->name, target_weight); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + + return true; +} + +bool http_rest_ai_tuning_status(struct fs_file *file, int num_params, + char *params[], char *values[]) { + (void)num_params; + (void)params; + (void)values; + + ai_tuning_session_t session_copy; + ai_tuning_get_session_copy(&session_copy); + ai_tuning_session_t* session = &session_copy; + + const char* state_str; + switch (session->state) { + case AI_TUNING_IDLE: state_str = "idle"; break; + case AI_TUNING_PHASE_1_COARSE: state_str = "phase1_coarse"; break; + case AI_TUNING_PHASE_2_FINE: state_str = "phase2_fine"; break; + case AI_TUNING_COMPLETE: state_str = "complete"; break; + case AI_TUNING_ERROR: state_str = "error"; break; + default: state_str = "unknown"; break; + } + + bool is_active = ai_tuning_is_active(); + bool is_complete = ai_tuning_is_complete(); + uint8_t progress = ai_tuning_get_progress_percent(); + + // Build JSON response + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{" + "\"state\":\"%s\"," + "\"is_active\":%s," + "\"is_complete\":%s," + "\"drops_completed\":%u," + "\"drops_max\":%u," + "\"progress_percent\":%u", + http_json_header, + state_str, + is_active ? "true" : "false", + is_complete ? "true" : "false", + session->drops_completed, + session->max_drops_allowed, + progress); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + + // Add current parameters if active + if (is_active && len < (int)sizeof(ai_tuning_json_buffer)) { + float coarse_kp, coarse_kd, fine_kp, fine_kd; + if (ai_tuning_get_next_params(&coarse_kp, &coarse_kd, &fine_kp, &fine_kd)) { + len += snprintf(ai_tuning_json_buffer + len, sizeof(ai_tuning_json_buffer) - len, + ",\"current_params\":{" + "\"coarse_kp\":%.4f," + "\"coarse_kd\":%.4f," + "\"fine_kp\":%.4f," + "\"fine_kd\":%.4f" + "}", + coarse_kp, coarse_kd, fine_kp, fine_kd); + if (len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + } + + // Add last drop result if we have any drops + if (session->drops_completed > 0) { + uint8_t last_idx = (session->drop_write_idx + AI_TUNING_DROP_BUF_SIZE - 1) % AI_TUNING_DROP_BUF_SIZE; + ai_drop_telemetry_t* last = &session->drops[last_idx]; + len += snprintf(ai_tuning_json_buffer + len, sizeof(ai_tuning_json_buffer) - len, + ",\"last_drop\":{" + "\"drop_num\":%u," + "\"overthrow\":%.4f," + "\"coarse_time_ms\":%.0f," + "\"fine_time_ms\":%.0f," + "\"total_time_ms\":%.0f," + "\"kp\":%.4f," + "\"kd\":%.4f" + "}", + last->drop_number, + last->overthrow, + last->coarse_time_ms, + last->fine_time_ms, + last->total_time_ms, + (session->state == AI_TUNING_PHASE_1_COARSE) ? last->coarse_kp_used : last->fine_kp_used, + (session->state == AI_TUNING_PHASE_1_COARSE) ? last->coarse_kd_used : last->fine_kd_used); + if (len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + } + } + + // Add recommended parameters if complete + if (is_complete && len < (int)sizeof(ai_tuning_json_buffer)) { + float coarse_kp, coarse_kd, fine_kp, fine_kd; + if (ai_tuning_get_recommended_params(&coarse_kp, &coarse_kd, &fine_kp, &fine_kd)) { + len += snprintf(ai_tuning_json_buffer + len, sizeof(ai_tuning_json_buffer) - len, + ",\"recommended_params\":{" + "\"coarse_kp\":%.4f," + "\"coarse_kd\":%.4f," + "\"fine_kp\":%.4f," + "\"fine_kd\":%.4f" + "}", + coarse_kp, coarse_kd, fine_kp, fine_kd); + if (len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + + // Add statistics + len += snprintf(ai_tuning_json_buffer + len, sizeof(ai_tuning_json_buffer) - len, + ",\"statistics\":{" + "\"avg_overthrow\":%.2f," + "\"avg_time\":%.1f" + "}", + session->avg_overthrow, + session->avg_total_time); + if (len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + } + } + + // Add error info if in error state + if (session->state == AI_TUNING_ERROR && len < (int)sizeof(ai_tuning_json_buffer)) { + len += snprintf(ai_tuning_json_buffer + len, sizeof(ai_tuning_json_buffer) - len, + ",\"error_message\":\"%s\"" + ",\"best_params\":{" + "\"coarse_kp\":%.4f," + "\"coarse_kd\":%.4f," + "\"fine_kp\":%.4f," + "\"fine_kd\":%.4f" + "}", + session->error_message, + session->recommended_coarse_kp, + session->recommended_coarse_kd, + session->recommended_fine_kp, + session->recommended_fine_kd); + if (len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + } + + // Close JSON + if (len < (int)sizeof(ai_tuning_json_buffer)) { + len += snprintf(ai_tuning_json_buffer + len, sizeof(ai_tuning_json_buffer) - len, "}"); + } + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + + return true; +} + +bool http_rest_ai_tuning_apply(struct fs_file *file, int num_params, + char *params[], char *values[]) { + (void)num_params; + (void)params; + (void)values; + + if (!ai_tuning_is_complete()) { + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":false,\"error\":\"AI tuning not complete\"}", + http_json_header); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; + } + + if (!ai_tuning_apply_params()) { + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":false,\"error\":\"Failed to apply parameters\"}", + http_json_header); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; + } + + // Save profile with new parameters + profile_data_save(); + + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":true,\"message\":\"Parameters applied and saved\"}", + http_json_header); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + + return true; +} + +bool http_rest_ai_tuning_cancel(struct fs_file *file, int num_params, + char *params[], char *values[]) { + (void)num_params; + (void)params; + (void)values; + + ai_tuning_cancel(); + + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":true,\"message\":\"AI tuning cancelled\"}", + http_json_header); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + + return true; +} + +bool http_rest_ai_tuning_history(struct fs_file *file, int num_params, + char *params[], char *values[]) { + (void)num_params; + (void)params; + (void)values; + + ai_tuning_history_t history_copy; + ai_tuning_get_history_copy(&history_copy); + ai_tuning_history_t* history = &history_copy; + + // Build JSON response with drop records and suggestions + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"count\":%d,\"has_suggestions\":%s", + http_json_header, history->count, + history->has_suggestions ? "true" : "false"); + + // Add suggestions if available (ML only suggests fine params) + if (history->has_suggestions) { + len += snprintf(ai_tuning_json_buffer + len, sizeof(ai_tuning_json_buffer) - len, + ",\"suggested\":{\"fine_kp\":%.3f,\"fine_kd\":%.3f}", + history->suggested_fine_kp, history->suggested_fine_kd); + if (len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + } + + // Add drop records + len += snprintf(ai_tuning_json_buffer + len, sizeof(ai_tuning_json_buffer) - len, ",\"drops\":["); + + for (int i = 0; i < history->count && i < AI_TUNING_HISTORY_SIZE && len < (int)sizeof(ai_tuning_json_buffer) - 150; i++) { + ai_drop_record_t* d = &history->drops[i]; + if (i > 0) { + len += snprintf(ai_tuning_json_buffer + len, sizeof(ai_tuning_json_buffer) - len, ","); + } + len += snprintf(ai_tuning_json_buffer + len, sizeof(ai_tuning_json_buffer) - len, + "{\"overthrow\":%.3f,\"time\":%.0f,\"profile\":%d}", + d->overthrow, d->total_time_ms, d->profile_idx); + } + + len += snprintf(ai_tuning_json_buffer + len, sizeof(ai_tuning_json_buffer) - len, "]}"); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + + return true; +} + +bool http_rest_ai_tuning_apply_refined(struct fs_file *file, int num_params, + char *params[], char *values[]) { + int profile_idx = -1; + + for (int idx = 0; idx < num_params; idx++) { + if (strcmp(params[idx], "profile_idx") == 0) { + profile_idx = atoi(values[idx]); + } + } + + if (profile_idx < 0 || profile_idx > 7) { + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":false,\"error\":\"Invalid profile_idx\"}", + http_json_header); + if (len < 0) len = 0; + if (len >= (int)sizeof(ai_tuning_json_buffer)) len = (int)sizeof(ai_tuning_json_buffer) - 1; + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; + } + + if (!ai_tuning_apply_refined_params(profile_idx)) { + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":false,\"error\":\"No refined values to apply\"}", + http_json_header); + if (len < 0) len = 0; + if (len >= (int)sizeof(ai_tuning_json_buffer)) len = (int)sizeof(ai_tuning_json_buffer) - 1; + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; + } + + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":true,\"message\":\"Refined values applied\"}", + http_json_header); + if (len < 0) len = 0; + if (len >= (int)sizeof(ai_tuning_json_buffer)) len = (int)sizeof(ai_tuning_json_buffer) - 1; + + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + + return true; +} + +bool http_rest_ai_tuning_clear_history(struct fs_file *file, int num_params, + char *params[], char *values[]) { + (void)num_params; + (void)params; + (void)values; + + ai_tuning_clear_history(); + + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":true,\"message\":\"History cleared\"}", + http_json_header); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + + return true; +} + + +bool http_rest_ai_tuning_config_get(struct fs_file *file, int num_params, + char *params[], char *values[]) { + (void)num_params; + (void)params; + (void)values; + + ai_tuning_config_t* cfg = ai_tuning_get_config(); + + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{" + "\"coarse_kp_min\":%.3f,\"coarse_kp_max\":%.3f," + "\"coarse_kd_min\":%.3f,\"coarse_kd_max\":%.3f," + "\"fine_kp_min\":%.3f,\"fine_kp_max\":%.3f," + "\"fine_kd_min\":%.3f,\"fine_kd_max\":%.3f," + "\"noise_margin\":%.3f," + "\"max_overthrow_percent\":%.2f" + "}", + http_json_header, + cfg->coarse_kp_min, cfg->coarse_kp_max, + cfg->coarse_kd_min, cfg->coarse_kd_max, + cfg->fine_kp_min, cfg->fine_kp_max, + cfg->fine_kd_min, cfg->fine_kd_max, + cfg->noise_margin, + cfg->max_overthrow_percent); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; +} + +bool http_rest_ai_tuning_config_set(struct fs_file *file, int num_params, + char *params[], char *values[]) { + ai_tuning_config_t* cfg = ai_tuning_get_config(); + + for (int idx = 0; idx < num_params; idx++) { + if (strcmp(params[idx], "coarse_kp_min") == 0) + cfg->coarse_kp_min = strtof(values[idx], NULL); + else if (strcmp(params[idx], "coarse_kp_max") == 0) + cfg->coarse_kp_max = strtof(values[idx], NULL); + else if (strcmp(params[idx], "coarse_kd_min") == 0) + cfg->coarse_kd_min = strtof(values[idx], NULL); + else if (strcmp(params[idx], "coarse_kd_max") == 0) + cfg->coarse_kd_max = strtof(values[idx], NULL); + else if (strcmp(params[idx], "fine_kp_min") == 0) + cfg->fine_kp_min = strtof(values[idx], NULL); + else if (strcmp(params[idx], "fine_kp_max") == 0) + cfg->fine_kp_max = strtof(values[idx], NULL); + else if (strcmp(params[idx], "fine_kd_min") == 0) + cfg->fine_kd_min = strtof(values[idx], NULL); + else if (strcmp(params[idx], "fine_kd_max") == 0) + cfg->fine_kd_max = strtof(values[idx], NULL); + else if (strcmp(params[idx], "noise_margin") == 0) + cfg->noise_margin = strtof(values[idx], NULL); + else if (strcmp(params[idx], "max_overthrow_percent") == 0) + cfg->max_overthrow_percent = strtof(values[idx], NULL); + } + + printf("AI Tuning config updated: C_Kp[%.3f-%.3f] C_Kd[%.3f-%.3f] F_Kp[%.3f-%.3f] F_Kd[%.3f-%.3f] noise=%.3f\n", + cfg->coarse_kp_min, cfg->coarse_kp_max, + cfg->coarse_kd_min, cfg->coarse_kd_max, + cfg->fine_kp_min, cfg->fine_kp_max, + cfg->fine_kd_min, cfg->fine_kd_max, + cfg->noise_margin); + + // Config applied to memory - user must click Save to persist to EEPROM + // (follows same pattern as motor config, scale config, etc.) + + int len = snprintf(ai_tuning_json_buffer, sizeof(ai_tuning_json_buffer), + "%s{\"success\":true,\"message\":\"Config applied (click Save to persist)\"}", + http_json_header); + + if (len < 0 || len >= (int)sizeof(ai_tuning_json_buffer)) { + return send_buffer_overflow_error(file); + } + + file->data = ai_tuning_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; +} + +bool rest_ai_tuning_init(void) { + // Register REST endpoints + rest_register_handler("/rest/ai_tuning_start", http_rest_ai_tuning_start); + rest_register_handler("/rest/ai_tuning_status", http_rest_ai_tuning_status); + rest_register_handler("/rest/ai_tuning_apply", http_rest_ai_tuning_apply); + rest_register_handler("/rest/ai_tuning_cancel", http_rest_ai_tuning_cancel); + rest_register_handler("/rest/ai_tuning_history", http_rest_ai_tuning_history); + rest_register_handler("/rest/ai_tuning_apply_refined", http_rest_ai_tuning_apply_refined); + rest_register_handler("/rest/ai_tuning_clear_history", http_rest_ai_tuning_clear_history); + rest_register_handler("/rest/ai_tuning_config", http_rest_ai_tuning_config_get); + rest_register_handler("/rest/ai_tuning_config_set", http_rest_ai_tuning_config_set); + + printf("AI Tuning REST endpoints registered\n"); + + return true; +} diff --git a/src/rest_ai_tuning.h b/src/rest_ai_tuning.h new file mode 100644 index 00000000..5e401f20 --- /dev/null +++ b/src/rest_ai_tuning.h @@ -0,0 +1,98 @@ +#ifndef REST_AI_TUNING_H +#define REST_AI_TUNING_H + +#include +#include "lwip/apps/fs.h" + +/** + * REST API Endpoints for AI Tuning + * + * Endpoints: + * - POST /rest/ai_tuning_start - Start AI tuning for a profile + * - GET /rest/ai_tuning_status - Get current tuning status and progress + * - POST /rest/ai_tuning_apply - Apply recommended parameters to profile + * - POST /rest/ai_tuning_cancel - Cancel tuning in progress + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initialize AI tuning REST endpoints + * Registers all endpoints with the REST handler + * + * @return true if initialization successful + */ +bool rest_ai_tuning_init(void); + +/** + * POST /rest/ai_tuning_start + * + * Start AI tuning session for specified profile. + * This will automatically enter charge mode and begin tuning. + * + * Parameters: + * - profile_idx: Profile index (0-7) [required] + * - target: Target charge weight in grains (default: 30.0) [optional] + * + * Returns: JSON with success status, profile name, target_weight + * + * Example: /rest/ai_tuning_start?profile_idx=0&target=42.5 + */ +bool http_rest_ai_tuning_start(struct fs_file *file, int num_params, + char *params[], char *values[]); + +/** + * GET /rest/ai_tuning_status + * + * Returns JSON with: + * - state: "idle", "phase1_coarse", "phase2_fine", "complete", "error" + * - drops_completed: Number of drops completed + * - progress_percent: Progress percentage (0-100) + * - current_params: Current Kp/Kd being tested + * - recommended_params: Recommended values (if complete) + * - statistics: Performance statistics (avg_overthrow, avg_time) + */ +bool http_rest_ai_tuning_status(struct fs_file *file, int num_params, + char *params[], char *values[]); + +/** + * POST /rest/ai_tuning_apply + * + * Apply recommended parameters to profile + * + * Returns: Success/error message + */ +bool http_rest_ai_tuning_apply(struct fs_file *file, int num_params, + char *params[], char *values[]); + +/** + * POST /rest/ai_tuning_cancel + * + * Cancel AI tuning session in progress + * + * Returns: Success/error message + */ +bool http_rest_ai_tuning_cancel(struct fs_file *file, int num_params, + char *params[], char *values[]); + +/** + * GET /rest/ai_tuning_config + * Returns current AI tuning config (ranges, noise margin, targets) + */ +bool http_rest_ai_tuning_config_get(struct fs_file *file, int num_params, + char *params[], char *values[]); + +/** + * POST /rest/ai_tuning_config_set + * Set AI tuning config fields (any subset) + */ +bool http_rest_ai_tuning_config_set(struct fs_file *file, int num_params, + char *params[], char *values[]); + +#ifdef __cplusplus +} +#endif + +#endif // REST_AI_TUNING_H diff --git a/src/rest_endpoints.c b/src/rest_endpoints.c index ba830a7f..48325d98 100644 --- a/src/rest_endpoints.c +++ b/src/rest_endpoints.c @@ -14,11 +14,19 @@ #include "cleanup_mode.h" #include "servo_gate.h" #include "system_control.h" +#include "rest_errors.h" +#include "rest_ai_tuning.h" +#include "ai_tuning.h" +#include "flash_storage.h" +#include "display_config.h" // Generated headers by html2header.py under scripts #include "display_mirror.html.h" #include "web_portal.html.h" +#include "web_portal_basic.html.h" #include "wizard.html.h" +#include "styles.css.h" +#include "favicon.ico.h" bool http_404_error(struct fs_file *file, int num_params, char *params[], char *values[]) { @@ -69,6 +77,39 @@ bool http_wizard(struct fs_file *file, int num_params, char *params[], char *val } +bool http_web_portal_basic(struct fs_file *file, int num_params, char *params[], char *values[]) { + size_t len = strlen(html_web_portal_basic_html); + + file->data = html_web_portal_basic_html; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT; + + return true; +} + + +bool http_styles_css(struct fs_file *file, int num_params, char *params[], char *values[]) { + size_t len = strlen(css_styles_css); + + file->data = css_styles_css; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT; + + return true; +} + + +bool http_favicon(struct fs_file *file, int num_params, char *params[], char *values[]) { + file->data = (const char *)favicon_ico; + file->len = favicon_ico_len; + file->index = favicon_ico_len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT; + + return true; +} + bool rest_endpoints_init(bool default_wizard) { if (default_wizard) { @@ -100,5 +141,24 @@ bool rest_endpoints_init(bool default_wizard) { rest_register_handler("/display_buffer", http_get_display_buffer); rest_register_handler("/display_mirror", http_display_mirror); + // Additional web resources + rest_register_handler("/basic", http_web_portal_basic); + rest_register_handler("/styles.css", http_styles_css); + rest_register_handler("/favicon.ico", http_favicon); + + // Error reporting endpoints + rest_register_handler("/rest/errors", http_rest_errors); + rest_register_handler("/rest/clear_errors", http_rest_clear_errors); + + // Display configuration endpoint + rest_register_handler("/rest/display_config", http_rest_display_config); + + // Initialize flash storage for ML history + flash_storage_init(); + + // Initialize AI tuning system and REST endpoints + ai_tuning_init(); + rest_ai_tuning_init(); + return true; } diff --git a/src/rest_errors.c b/src/rest_errors.c new file mode 100644 index 00000000..24565da0 --- /dev/null +++ b/src/rest_errors.c @@ -0,0 +1,97 @@ +#include +#include + +#include "rest_errors.h" +#include "error.h" +#include "common.h" + +// Get short category string for JSON output +static const char* error_get_short_cat(error_code_t code) { + if (code >= 100 && code < 200) return "EEP"; + if (code >= 200 && code < 300) return "DSP"; + if (code >= 300 && code < 400) return "LED"; + if (code >= 400 && code < 500) return "WIF"; + if (code >= 500 && code < 600) return "MOT"; + if (code >= 600 && code < 700) return "SCL"; + if (code >= 700 && code < 800) return "SRV"; + if (code >= 800 && code < 900) return "PRF"; + if (code >= 900 && code < 1000) return "CHG"; + if (code >= 1000 && code < 1100) return "RST"; + if (code >= 1100 && code < 1200) return "MEM"; + if (code >= 1200 && code < 1300) return "CAL"; + return "UNK"; +} + +bool http_rest_errors(struct fs_file *file, int num_params, char *params[], char *values[]) { + // Buffer for JSON response + // Max size: header (~60) + {"count":N,"errors":[ (~25) + 8 errors * ~60 chars each + ]} + static char errors_json_buffer[600]; + + // Check for clear parameter + for (int idx = 0; idx < num_params; idx++) { + if (strcmp(params[idx], "clear") == 0) { + if (strcmp(values[idx], "1") == 0 || strcmp(values[idx], "true") == 0) { + error_clear_last(); + } + } + } + + uint8_t count = error_get_count(); + + // Start building JSON + int offset = snprintf(errors_json_buffer, sizeof(errors_json_buffer), + "%s{\"count\":%d,\"errors\":[", + http_json_header, count); + + // Add each error + for (uint8_t i = 0; i < count && offset < (int)sizeof(errors_json_buffer) - 80; i++) { + error_code_t code = error_get_at(i); + + if (i > 0) { + offset += snprintf(errors_json_buffer + offset, + sizeof(errors_json_buffer) - offset, ","); + } + + offset += snprintf(errors_json_buffer + offset, + sizeof(errors_json_buffer) - offset, + "{\"code\":%d,\"cat\":\"%s\",\"msg\":\"%s\"}", + (int)code, + error_get_short_cat(code), + error_code_to_string(code)); + } + + // Close JSON + snprintf(errors_json_buffer + offset, sizeof(errors_json_buffer) - offset, "]}"); + + size_t data_length = strlen(errors_json_buffer); + file->data = errors_json_buffer; + file->len = data_length; + file->index = data_length; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + + return true; +} + +bool http_rest_clear_errors(struct fs_file *file, int num_params, char *params[], char *values[]) { + (void)num_params; + (void)params; + (void)values; + + static char clear_json_buffer[128]; + + // Clear all errors + while (error_get_count() > 0) { + error_clear_last(); + } + + int len = snprintf(clear_json_buffer, sizeof(clear_json_buffer), + "%s{\"success\":true,\"message\":\"Errors cleared\"}", + http_json_header); + + file->data = clear_json_buffer; + file->len = len; + file->index = len; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + + return true; +} diff --git a/src/rest_errors.h b/src/rest_errors.h new file mode 100644 index 00000000..9ca71170 --- /dev/null +++ b/src/rest_errors.h @@ -0,0 +1,23 @@ +#ifndef REST_ERRORS_H_ +#define REST_ERRORS_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// REST endpoint handler for error log +// Returns JSON: {"count":N,"errors":[{"code":105,"cat":"EEP","msg":"EEPROM alloc"},...]} +bool http_rest_errors(struct fs_file *file, int num_params, char *params[], char *values[]); + +// REST endpoint to clear all errors +// Returns JSON: {"success":true,"message":"Errors cleared"} +bool http_rest_clear_errors(struct fs_file *file, int num_params, char *params[], char *values[]); + +#ifdef __cplusplus +} +#endif + +#endif // REST_ERRORS_H_ diff --git a/src/styles.css.h b/src/styles.css.h new file mode 100644 index 00000000..c11f7d60 --- /dev/null +++ b/src/styles.css.h @@ -0,0 +1,10 @@ +// ---------------------------------------------------------- // +// This file is autogenerated by css2header.py; do not edit! // +// ---------------------------------------------------------- // + +#ifndef STYLES_CSS_H_ +#define STYLES_CSS_H_ + +const char css_styles_css[] = "HTTP/1.1 200 OK\r\nContent-Type: text/css\r\nCache-Control: max-age=86400\r\n\r\n*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:\"\"}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root,[data-theme]{background-color:var(--fallback-b1,oklch(var(--b1)/1));color:var(--fallback-bc,oklch(var(--bc)/1))}@supports not (color:oklch(0% 0 0)){:root{color-scheme:light;--fallback-p:#491eff;--fallback-pc:#d4dbff;--fallback-s:#ff41c7;--fallback-sc:#fff9fc;--fallback-a:#00cfbd;--fallback-ac:#00100d;--fallback-n:#2b3440;--fallback-nc:#d7dde4;--fallback-b1:#fff;--fallback-b2:#e5e6e6;--fallback-b3:#e5e6e6;--fallback-bc:#1f2937;--fallback-in:#00b3f0;--fallback-inc:#000;--fallback-su:#00ca92;--fallback-suc:#000;--fallback-wa:#ffc22d;--fallback-wac:#000;--fallback-er:#ff6f70;--fallback-erc:#000}@media (prefers-color-scheme:dark){:root{color-scheme:dark;--fallback-p:#7582ff;--fallback-pc:#050617;--fallback-s:#ff71cf;--fallback-sc:#190211;--fallback-a:#00c7b5;--fallback-ac:#000e0c;--fallback-n:#2a323c;--fallback-nc:#a6adbb;--fallback-b1:#1d232a;--fallback-b2:#191e24;--fallback-b3:#15191e;--fallback-bc:#a6adbb;--fallback-in:#00b3f0;--fallback-inc:#000;--fallback-su:#00ca92;--fallback-suc:#000;--fallback-wa:#ffc22d;--fallback-wac:#000;--fallback-er:#ff6f70;--fallback-erc:#000}}}html{-webkit-tap-highlight-color:transparent}*{scrollbar-color:color-mix(in oklch,currentColor 35%,transparent) transparent}:hover{scrollbar-color:color-mix(in oklch,currentColor 60%,transparent) transparent}:root{color-scheme:light;--in:72.06% 0.191 231.6;--su:64.8% 0.150 160;--wa:84.71% 0.199 83.87;--er:71.76% 0.221 22.18;--pc:89.824% 0.06192 275.75;--ac:15.352% 0.0368 183.61;--inc:0% 0 0;--suc:0% 0 0;--wac:0% 0 0;--erc:0% 0 0;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:49.12% 0.3096 275.75;--s:69.71% 0.329 342.55;--sc:98.71% 0.0106 342.55;--a:76.76% 0.184 183.61;--n:32.1785% 0.02476 255.701624;--nc:89.4994% 0.011585 252.096176;--b1:100% 0 0;--b2:96.1151% 0 0;--b3:92.4169% 0.00108 197.137559;--bc:27.8078% 0.029596 256.847952}@media (prefers-color-scheme:dark){:root{color-scheme:dark;--in:72.06% 0.191 231.6;--su:64.8% 0.150 160;--wa:84.71% 0.199 83.87;--er:71.76% 0.221 22.18;--pc:13.138% 0.0392 275.75;--sc:14.96% 0.052 342.55;--ac:14.902% 0.0334 183.61;--inc:0% 0 0;--suc:0% 0 0;--wac:0% 0 0;--erc:0% 0 0;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:65.69% 0.196 275.75;--s:74.8% 0.26 342.55;--a:74.51% 0.167 183.61;--n:31.3815% 0.021108 254.139175;--nc:74.6477% 0.0216 264.435964;--b1:25.3267% 0.015896 252.417568;--b2:23.2607% 0.013807 253.100675;--b3:21.1484% 0.01165 254.087939;--bc:74.6477% 0.0216 264.435964}}[data-theme=light]{color-scheme:light;--in:72.06% 0.191 231.6;--su:64.8% 0.150 160;--wa:84.71% 0.199 83.87;--er:71.76% 0.221 22.18;--pc:89.824% 0.06192 275.75;--ac:15.352% 0.0368 183.61;--inc:0% 0 0;--suc:0% 0 0;--wac:0% 0 0;--erc:0% 0 0;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:49.12% 0.3096 275.75;--s:69.71% 0.329 342.55;--sc:98.71% 0.0106 342.55;--a:76.76% 0.184 183.61;--n:32.1785% 0.02476 255.701624;--nc:89.4994% 0.011585 252.096176;--b1:100% 0 0;--b2:96.1151% 0 0;--b3:92.4169% 0.00108 197.137559;--bc:27.8078% 0.029596 256.847952}[data-theme=dark]{color-scheme:dark;--in:72.06% 0.191 231.6;--su:64.8% 0.150 160;--wa:84.71% 0.199 83.87;--er:71.76% 0.221 22.18;--pc:13.138% 0.0392 275.75;--sc:14.96% 0.052 342.55;--ac:14.902% 0.0334 183.61;--inc:0% 0 0;--suc:0% 0 0;--wac:0% 0 0;--erc:0% 0 0;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:65.69% 0.196 275.75;--s:74.8% 0.26 342.55;--a:74.51% 0.167 183.61;--n:31.3815% 0.021108 254.139175;--nc:74.6477% 0.0216 264.435964;--b1:25.3267% 0.015896 252.417568;--b2:23.2607% 0.013807 253.100675;--b3:21.1484% 0.01165 254.087939;--bc:74.6477% 0.0216 264.435964}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.alert{display:grid;width:100%;grid-auto-flow:row;align-content:flex-start;align-items:center;justify-items:center;gap:1rem;text-align:center;border-radius:var(--rounded-box,1rem);border-width:1px;--tw-border-opacity:1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));padding:1rem;--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--alert-bg:var(--fallback-b2,oklch(var(--b2)/1));--alert-bg-mix:var(--fallback-b1,oklch(var(--b1)/1));background-color:var(--alert-bg)}@media (min-width:640px){.alert{grid-auto-flow:column;grid-template-columns:auto minmax(auto,1fr);justify-items:start;text-align:start}}.avatar.placeholder>div{display:flex;align-items:center;justify-content:center}.badge{display:inline-flex;justify-content:center;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s;height:1.25rem;font-size:.875rem;line-height:1.25rem;width:-moz-fit-content;width:fit-content;padding-left:.563rem;padding-right:.563rem;border-radius:var(--rounded-badge,1.9rem);border-width:1px;--tw-border-opacity:1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity:1;--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.badge,.btm-nav{align-items:center;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.btm-nav{position:fixed;bottom:0;left:0;right:0;display:flex;width:100%;flex-direction:row;justify-content:space-around;padding-bottom:env(safe-area-inset-bottom);height:4rem;--tw-bg-opacity:1;color:currentColor}.btm-nav>*{position:relative;display:flex;height:100%;flex-basis:100%;cursor:pointer;flex-direction:column;align-items:center;justify-content:center;gap:.25rem;border-color:currentColor}@media (hover:hover){.label a:hover{--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.menu li>:not(ul,.menu-title,details,.btn).active,.menu li>:not(ul,.menu-title,details,.btn):active,.menu li>details>summary:active{--tw-bg-opacity:1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.table tr.hover:hover,.table tr.hover:nth-child(2n):hover{--tw-bg-opacity:1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)))}}.btn{display:inline-flex;height:3rem;min-height:3rem;flex-shrink:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;border-radius:var(--rounded-btn,.5rem);border-color:transparent;border-color:oklch(var(--btn-color,var(--b2))/var(--tw-border-opacity));padding-left:1rem;padding-right:1rem;text-align:center;font-size:.875rem;line-height:1em;gap:.5rem;font-weight:600;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);border-width:var(--border-btn,1px);transition-property:color,background-color,border-color,opacity,box-shadow,transform;--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);outline-color:var(--fallback-bc,oklch(var(--bc)/1));background-color:oklch(var(--btn-color,var(--b2))/var(--tw-bg-opacity));--tw-bg-opacity:1;--tw-border-opacity:1}.btn-disabled,.btn:disabled,.btn[disabled]{pointer-events:none}.btn-circle,.btn-square{height:3rem;width:3rem;padding:0}.btn-circle{border-radius:9999px}:where(.btn:is(input[type=checkbox])),:where(.btn:is(input[type=radio])){width:auto;-webkit-appearance:none;-moz-appearance:none;appearance:none}.btn:is(input[type=checkbox]):after,.btn:is(input[type=radio]):after{--tw-content:attr(aria-label);content:var(--tw-content)}.card{position:relative;display:flex;flex-direction:column;border-radius:var(--rounded-box,1rem)}.card:focus{outline:2px solid transparent;outline-offset:2px}.card-body{display:flex;flex:1 1 auto;flex-direction:column;padding:var(--padding-card,2rem);gap:.5rem}.card-body :where(p){flex-grow:1}.card figure{display:flex;align-items:center;justify-content:center}.card.image-full{display:grid}.card.image-full:before{position:relative;content:\"\";z-index:10;border-radius:var(--rounded-box,1rem);--tw-bg-opacity:1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));opacity:.75}.card.image-full:before,.card.image-full>*{grid-column-start:1;grid-row-start:1}.card.image-full>figure img{height:100%;-o-object-fit:cover;object-fit:cover}.card.image-full>.card-body{position:relative;z-index:20;--tw-text-opacity:1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.checkbox{flex-shrink:0;--chkbg:var(--fallback-bc,oklch(var(--bc)/1));--chkfg:var(--fallback-b1,oklch(var(--b1)/1));height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--rounded-btn,.5rem);border-width:1px;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-border-opacity:0.2}.divider{display:flex;flex-direction:row;align-items:center;align-self:stretch;margin-top:1rem;margin-bottom:1rem;height:1rem;white-space:nowrap}.divider:after,.divider:before{height:.125rem;width:100%;flex-grow:1;--tw-content:\"\";content:var(--tw-content);background-color:var(--fallback-bc,oklch(var(--bc)/.1))}.drawer{position:relative;display:grid;grid-auto-columns:max-content auto;width:100%}.drawer-content{grid-column-start:2;grid-row-start:1;min-width:0}.drawer-side{pointer-events:none;position:fixed;inset-inline-start:0;top:0;grid-column-start:1;grid-row-start:1;display:grid;width:100%;grid-template-columns:repeat(1,minmax(0,1fr));grid-template-rows:repeat(1,minmax(0,1fr));align-items:flex-start;justify-items:start;overflow-x:hidden;overflow-y:hidden;overscroll-behavior:contain;height:100vh;height:100dvh}.drawer-side>.drawer-overlay{position:sticky;top:0;place-self:stretch;cursor:pointer;background-color:transparent;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s}.drawer-side>*{grid-column-start:1;grid-row-start:1}.drawer-side>:not(.drawer-overlay){transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.3s;will-change:transform;transform:translateX(-100%)}[dir=rtl] .drawer-side>:not(.drawer-overlay){transform:translateX(100%)}.drawer-toggle{position:fixed;height:0;width:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}.drawer-toggle:checked~.drawer-side{pointer-events:auto;visibility:visible;overflow-y:auto}.drawer-toggle:checked~.drawer-side>:not(.drawer-overlay){transform:translateX(0)}.drawer-end>.drawer-toggle~.drawer-content{grid-column-start:1}.drawer-end>.drawer-toggle~.drawer-side{grid-column-start:2;justify-items:end}.drawer-end>.drawer-toggle~.drawer-side>:not(.drawer-overlay){transform:translateX(100%)}[dir=rtl] .drawer-end>.drawer-toggle~.drawer-side>:not(.drawer-overlay){transform:translateX(-100%)}.drawer-end>.drawer-toggle:checked~.drawer-side>:not(.drawer-overlay){transform:translateX(0)}@media (hover:hover){.btm-nav>.disabled:hover,.btm-nav>[disabled]:hover{pointer-events:none;--tw-border-opacity:0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity:0.1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity:0.2}.btn:hover{--tw-border-opacity:1;border-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity)));--tw-bg-opacity:1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)))}@supports (color:color-mix(in oklab,black,black)){.btn:hover{background-color:color-mix(in oklab,oklch(var(--btn-color,var(--b2))/var(--tw-bg-opacity,1)) 90%,#000);border-color:color-mix(in oklab,oklch(var(--btn-color,var(--b2))/var(--tw-border-opacity,1)) 90%,#000)}}@supports not (color:oklch(0% 0 0)){.btn:hover{background-color:var(--btn-color,var(--fallback-b2));border-color:var(--btn-color,var(--fallback-b2))}}.btn.glass:hover{--glass-opacity:25%;--glass-border-opacity:15%}.btn-ghost:hover{border-color:transparent}@supports (color:oklch(0% 0 0)){.btn-ghost:hover{background-color:var(--fallback-bc,oklch(var(--bc)/.2))}}.btn-outline:hover{--tw-border-opacity:1;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-bg-opacity:1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)))}.btn-outline.btn-primary:hover{--tw-text-opacity:1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}@supports (color:color-mix(in oklab,black,black)){.btn-outline.btn-primary:hover{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000)}}.btn-outline.btn-secondary:hover{--tw-text-opacity:1;color:var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)))}@supports (color:color-mix(in oklab,black,black)){.btn-outline.btn-secondary:hover{background-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,#000)}}.btn-outline.btn-accent:hover{--tw-text-opacity:1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)))}@supports (color:color-mix(in oklab,black,black)){.btn-outline.btn-accent:hover{background-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,#000)}}.btn-outline.btn-success:hover{--tw-text-opacity:1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)))}@supports (color:color-mix(in oklab,black,black)){.btn-outline.btn-success:hover{background-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,#000)}}.btn-outline.btn-info:hover{--tw-text-opacity:1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)))}@supports (color:color-mix(in oklab,black,black)){.btn-outline.btn-info:hover{background-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,#000)}}.btn-outline.btn-warning:hover{--tw-text-opacity:1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)))}@supports (color:color-mix(in oklab,black,black)){.btn-outline.btn-warning:hover{background-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,#000)}}.btn-outline.btn-error:hover{--tw-text-opacity:1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}@supports (color:color-mix(in oklab,black,black)){.btn-outline.btn-error:hover{background-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,#000)}}.btn-disabled:hover,.btn:disabled:hover,.btn[disabled]:hover{--tw-border-opacity:0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity:0.2;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity:0.2}@supports (color:color-mix(in oklab,black,black)){.btn:is(input[type=checkbox]:checked):hover,.btn:is(input[type=radio]:checked):hover{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000)}}:where(.menu li:not(.menu-title,.disabled)>:not(ul,details,.menu-title)):not(.active,.btn):hover,:where(.menu li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.active,.btn):hover{cursor:pointer;outline:2px solid transparent;outline-offset:2px}@supports (color:oklch(0% 0 0)){:where(.menu li:not(.menu-title,.disabled)>:not(ul,details,.menu-title)):not(.active,.btn):hover,:where(.menu li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.active,.btn):hover{background-color:var(--fallback-bc,oklch(var(--bc)/.1))}}}.form-control{flex-direction:column}.form-control,.label{display:flex}.label{-webkit-user-select:none;-moz-user-select:none;user-select:none;align-items:center;justify-content:space-between;padding:.5rem .25rem}.input{flex-shrink:1;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;padding-left:1rem;padding-right:1rem;font-size:1rem;line-height:2;line-height:1.5rem;border-radius:var(--rounded-btn,.5rem);border-width:1px;border-color:transparent;--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.input-md[type=number]::-webkit-inner-spin-button,.input[type=number]::-webkit-inner-spin-button{margin-top:-1rem;margin-bottom:-1rem;margin-inline-end:-1rem}.input-sm[type=number]::-webkit-inner-spin-button{margin-top:0;margin-bottom:0;margin-inline-end:0}.join{display:inline-flex;align-items:stretch;border-radius:var(--rounded-btn,.5rem)}.join :where(.join-item){border-start-end-radius:0;border-end-end-radius:0;border-end-start-radius:0;border-start-start-radius:0}.join .join-item:not(:first-child):not(:last-child),.join :not(:first-child):not(:last-child) .join-item{border-start-end-radius:0;border-end-end-radius:0;border-end-start-radius:0;border-start-start-radius:0}.join .join-item:first-child:not(:last-child),.join :first-child:not(:last-child) .join-item{border-start-end-radius:0;border-end-end-radius:0}.join .dropdown .join-item:first-child:not(:last-child),.join :first-child:not(:last-child) .dropdown .join-item{border-start-end-radius:inherit;border-end-end-radius:inherit}.join :where(.join-item:first-child:not(:last-child)),.join :where(:first-child:not(:last-child) .join-item){border-end-start-radius:inherit;border-start-start-radius:inherit}.join .join-item:last-child:not(:first-child),.join :last-child:not(:first-child) .join-item{border-end-start-radius:0;border-start-start-radius:0}.join :where(.join-item:last-child:not(:first-child)),.join :where(:last-child:not(:first-child) .join-item){border-start-end-radius:inherit;border-end-end-radius:inherit}@supports not selector(:has(*)){:where(.join *){border-radius:inherit}}@supports selector(:has(*)){:where(.join :has(.join-item)){border-radius:inherit}}.link{cursor:pointer;text-decoration-line:underline}.menu{display:flex;flex-direction:column;flex-wrap:wrap;font-size:.875rem;line-height:1.25rem;padding:.5rem}.menu :where(li ul){position:relative;white-space:nowrap;margin-inline-start:1rem;padding-inline-start:.5rem}.menu :where(li:not(.menu-title)>:not(ul,details,.menu-title,.btn)),.menu :where(li:not(.menu-title)>details>summary:not(.menu-title)){display:grid;grid-auto-flow:column;align-content:flex-start;align-items:center;gap:.5rem;grid-auto-columns:minmax(auto,max-content) auto max-content;-webkit-user-select:none;-moz-user-select:none;user-select:none}.menu li.disabled{cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;user-select:none;color:var(--fallback-bc,oklch(var(--bc)/.3))}.menu :where(li>.menu-dropdown:not(.menu-dropdown-show)){display:none}:where(.menu li){position:relative;display:flex;flex-shrink:0;flex-direction:column;flex-wrap:wrap;align-items:stretch}:where(.menu li) .badge{justify-self:end}.modal{pointer-events:none;position:fixed;inset:0;margin:0;display:grid;height:100%;max-height:none;width:100%;max-width:none;justify-items:center;padding:0;opacity:0;overscroll-behavior:contain;z-index:999;background-color:transparent;color:inherit;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);transition-property:transform,opacity,visibility;overflow-y:hidden}:where(.modal){align-items:center}.modal-box{max-height:calc(100vh - 5em);grid-column-start:1;grid-row-start:1;width:91.666667%;max-width:32rem;--tw-scale-x:.9;--tw-scale-y:.9;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-bottom-right-radius:var(--rounded-box,1rem);border-bottom-left-radius:var(--rounded-box,1rem);border-top-left-radius:var(--rounded-box,1rem);border-top-right-radius:var(--rounded-box,1rem);--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));padding:1.5rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s;box-shadow:0 25px 50px -12px rgba(0,0,0,.25);overflow-y:auto;overscroll-behavior:contain}.modal-open,.modal-toggle:checked+.modal,.modal:target,.modal[open]{pointer-events:auto;visibility:visible;opacity:1}.modal-action{display:flex;margin-top:1.5rem;justify-content:flex-end}:root:has(:is(.modal-open,.modal:target,.modal-toggle:checked+.modal,.modal[open])){overflow:hidden;scrollbar-gutter:stable}.navbar{display:flex;align-items:center;padding:var(--navbar-padding,.5rem);min-height:4rem;width:100%}:where(.navbar>:not(script,style)){display:inline-flex;align-items:center}.progress{position:relative;-webkit-appearance:none;height:.5rem;background-color:var(--fallback-bc,oklch(var(--bc)/.2))}.progress,.range{width:100%;-moz-appearance:none;appearance:none;overflow:hidden;border-radius:var(--rounded-box,1rem)}.range{height:1.5rem;cursor:pointer;-webkit-appearance:none;--range-shdw:var(--fallback-bc,oklch(var(--bc)/1));background-color:transparent}.range:focus{outline:none}.select{display:inline-flex;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;min-height:3rem;padding-inline-start:1rem;padding-inline-end:2.5rem;font-size:.875rem;line-height:1.25rem;line-height:2;border-radius:var(--rounded-btn,.5rem);border-width:1px;border-color:transparent;--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));background-image:linear-gradient(45deg,transparent 50%,currentColor 0),linear-gradient(135deg,currentColor 50%,transparent 0);background-position:calc(100% - 20px) calc(1px + 50%),calc(100% - 16.1px) calc(1px + 50%);background-size:4px 4px,4px 4px;background-repeat:no-repeat}.select[multiple]{height:auto}.stat{display:inline-grid;width:100%;grid-template-columns:repeat(1,1fr);-moz-column-gap:1rem;column-gap:1rem;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-border-opacity:0.1;padding:1rem 1.5rem}.stat-title{color:var(--fallback-bc,oklch(var(--bc)/.6))}.stat-title,.stat-value{grid-column-start:1;white-space:nowrap}.stat-value{font-size:2.25rem;line-height:2.5rem;font-weight:800}.stat-desc{grid-column-start:1;white-space:nowrap;font-size:.75rem;line-height:1rem;color:var(--fallback-bc,oklch(var(--bc)/.6))}.steps{display:inline-grid;grid-auto-flow:column;overflow:hidden;overflow-x:auto;counter-reset:step;grid-auto-columns:1fr}.steps .step{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));grid-template-columns:auto;grid-template-rows:repeat(2,minmax(0,1fr));grid-template-rows:40px 1fr;place-items:center;text-align:center;min-width:4rem}.table{position:relative;width:100%;border-radius:var(--rounded-box,1rem);text-align:left;font-size:.875rem;line-height:1.25rem}.table :where(.table-pin-rows thead tr){position:sticky;top:0;z-index:1;--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.table :where(.table-pin-rows tfoot tr){position:sticky;bottom:0;z-index:1;--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.table :where(.table-pin-cols tr th){position:sticky;left:0;right:0;--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.toggle{flex-shrink:0;--tglbg:var(--fallback-b1,oklch(var(--b1)/1));--handleoffset:1.5rem;--handleoffsetcalculator:calc(var(--handleoffset)*-1);--togglehandleborder:0 0;height:1.5rem;width:3rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--rounded-badge,1.9rem);border-width:1px;border-color:currentColor;background-color:currentColor;color:var(--fallback-bc,oklch(var(--bc)/.5));transition:background,box-shadow var(--animation-input,.2s) ease-out;box-shadow:var(--handleoffsetcalculator) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset,var(--togglehandleborder)}.alert-success{border-color:var(--fallback-su,oklch(var(--su)/.2));--tw-text-opacity:1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));--alert-bg:var(--fallback-su,oklch(var(--su)/1));--alert-bg-mix:var(--fallback-b1,oklch(var(--b1)/1))}.alert-warning{border-color:var(--fallback-wa,oklch(var(--wa)/.2));--tw-text-opacity:1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));--alert-bg:var(--fallback-wa,oklch(var(--wa)/1));--alert-bg-mix:var(--fallback-b1,oklch(var(--b1)/1))}.alert-error{border-color:var(--fallback-er,oklch(var(--er)/.2));--tw-text-opacity:1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));--alert-bg:var(--fallback-er,oklch(var(--er)/1));--alert-bg-mix:var(--fallback-b1,oklch(var(--b1)/1))}.badge-neutral{border-color:var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity)));background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.badge-neutral,.badge-primary{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1}.badge-primary{border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.badge-info{background-color:var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity)));color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)))}.badge-info,.badge-success{border-color:transparent;--tw-bg-opacity:1;--tw-text-opacity:1}.badge-success{background-color:var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)))}.badge-warning{background-color:var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity)));color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)))}.badge-error,.badge-warning{border-color:transparent;--tw-bg-opacity:1;--tw-text-opacity:1}.badge-error{background-color:var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity)));color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}.badge-outline.badge-neutral{--tw-text-opacity:1;color:var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity)))}.badge-outline.badge-primary{--tw-text-opacity:1;color:var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)))}.badge-outline.badge-info{--tw-text-opacity:1;color:var(--fallback-in,oklch(var(--in)/var(--tw-text-opacity)))}.badge-outline.badge-success{--tw-text-opacity:1;color:var(--fallback-su,oklch(var(--su)/var(--tw-text-opacity)))}.badge-outline.badge-warning{--tw-text-opacity:1;color:var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity)))}.badge-outline.badge-error{--tw-text-opacity:1;color:var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity)))}.btm-nav>:not(.active){padding-top:.125rem}.btm-nav>:where(.active){border-top-width:2px;--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.btm-nav>.disabled,.btm-nav>[disabled]{pointer-events:none;--tw-border-opacity:0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity:0.1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity:0.2}.btm-nav>* .label{font-size:1rem;line-height:1.5rem}@media (prefers-reduced-motion:no-preference){.btn{animation:button-pop var(--animation-btn,.25s) ease-out}}.btn:active:focus,.btn:active:hover{animation:button-pop 0s ease-out;transform:scale(var(--btn-focus-scale,.97))}@supports not (color:oklch(0% 0 0)){.btn{background-color:var(--btn-color,var(--fallback-b2));border-color:var(--btn-color,var(--fallback-b2))}.btn-primary{--btn-color:var(--fallback-p)}.btn-neutral{--btn-color:var(--fallback-n)}.btn-success{--btn-color:var(--fallback-su)}.btn-warning{--btn-color:var(--fallback-wa)}.btn-error{--btn-color:var(--fallback-er)}}@supports (color:color-mix(in oklab,black,black)){.btn-outline.btn-primary.btn-active{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000)}.btn-outline.btn-secondary.btn-active{background-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,#000)}.btn-outline.btn-accent.btn-active{background-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,#000)}.btn-outline.btn-success.btn-active{background-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,#000)}.btn-outline.btn-info.btn-active{background-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,#000)}.btn-outline.btn-warning.btn-active{background-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,#000)}.btn-outline.btn-error.btn-active{background-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,#000)}}.btn:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px}.btn-primary{--tw-text-opacity:1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));outline-color:var(--fallback-p,oklch(var(--p)/1))}@supports (color:oklch(0% 0 0)){.btn-primary{--btn-color:var(--p)}.btn-neutral{--btn-color:var(--n)}.btn-success{--btn-color:var(--su)}.btn-warning{--btn-color:var(--wa)}.btn-error{--btn-color:var(--er)}}.btn-neutral{--tw-text-opacity:1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));outline-color:var(--fallback-n,oklch(var(--n)/1))}.btn-success{--tw-text-opacity:1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));outline-color:var(--fallback-su,oklch(var(--su)/1))}.btn-warning{--tw-text-opacity:1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));outline-color:var(--fallback-wa,oklch(var(--wa)/1))}.btn-error{--tw-text-opacity:1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));outline-color:var(--fallback-er,oklch(var(--er)/1))}.btn.glass{--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);outline-color:currentColor}.btn.glass.btn-active{--glass-opacity:25%;--glass-border-opacity:15%}.btn-ghost{border-width:1px;border-color:transparent;background-color:transparent;color:currentColor;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);outline-color:currentColor}.btn-ghost.btn-active{border-color:transparent;background-color:var(--fallback-bc,oklch(var(--bc)/.2))}.btn-outline{border-color:currentColor;background-color:transparent;--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.btn-outline.btn-active{--tw-border-opacity:1;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-bg-opacity:1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)))}.btn-outline.btn-primary{--tw-text-opacity:1;color:var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)))}.btn-outline.btn-primary.btn-active{--tw-text-opacity:1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.btn-outline.btn-secondary{--tw-text-opacity:1;color:var(--fallback-s,oklch(var(--s)/var(--tw-text-opacity)))}.btn-outline.btn-secondary.btn-active{--tw-text-opacity:1;color:var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)))}.btn-outline.btn-accent{--tw-text-opacity:1;color:var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity)))}.btn-outline.btn-accent.btn-active{--tw-text-opacity:1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)))}.btn-outline.btn-success{--tw-text-opacity:1;color:var(--fallback-su,oklch(var(--su)/var(--tw-text-opacity)))}.btn-outline.btn-success.btn-active{--tw-text-opacity:1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)))}.btn-outline.btn-info{--tw-text-opacity:1;color:var(--fallback-in,oklch(var(--in)/var(--tw-text-opacity)))}.btn-outline.btn-info.btn-active{--tw-text-opacity:1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)))}.btn-outline.btn-warning{--tw-text-opacity:1;color:var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity)))}.btn-outline.btn-warning.btn-active{--tw-text-opacity:1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)))}.btn-outline.btn-error{--tw-text-opacity:1;color:var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity)))}.btn-outline.btn-error.btn-active{--tw-text-opacity:1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}.btn.btn-disabled,.btn:disabled,.btn[disabled]{--tw-border-opacity:0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity:0.2;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity:0.2}.btn:is(input[type=checkbox]:checked),.btn:is(input[type=radio]:checked){--tw-border-opacity:1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));--tw-bg-opacity:1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.btn:is(input[type=checkbox]:checked):focus-visible,.btn:is(input[type=radio]:checked):focus-visible{outline-color:var(--fallback-p,oklch(var(--p)/1))}@keyframes button-pop{0%{transform:scale(var(--btn-focus-scale,.98))}40%{transform:scale(1.02)}to{transform:scale(1)}}.card :where(figure:first-child){overflow:hidden;border-start-start-radius:inherit;border-start-end-radius:inherit;border-end-start-radius:unset;border-end-end-radius:unset}.card :where(figure:last-child){overflow:hidden;border-start-start-radius:unset;border-start-end-radius:unset;border-end-start-radius:inherit;border-end-end-radius:inherit}.card:focus-visible{outline:2px solid currentColor;outline-offset:2px}.card.bordered{border-width:1px;--tw-border-opacity:1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)))}.card.compact .card-body{padding:1rem;font-size:.875rem;line-height:1.25rem}.card-title{display:flex;align-items:center;gap:.5rem;font-size:1.25rem;line-height:1.75rem;font-weight:600}.card.image-full :where(figure){overflow:hidden;border-radius:inherit}.checkbox:focus{box-shadow:none}.checkbox:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/1))}.checkbox:disabled{border-width:0;cursor:not-allowed;border-color:transparent;--tw-bg-opacity:1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));opacity:.2}.checkbox:checked,.checkbox[aria-checked=true]{background-repeat:no-repeat;animation:checkmark var(--animation-input,.2s) ease-out;background-color:var(--chkbg);background-image:linear-gradient(-45deg,transparent 65%,var(--chkbg) 65.99%),linear-gradient(45deg,transparent 75%,var(--chkbg) 75.99%),linear-gradient(-45deg,var(--chkbg) 40%,transparent 40.99%),linear-gradient(45deg,var(--chkbg) 30%,var(--chkfg) 30.99%,var(--chkfg) 40%,transparent 40.99%),linear-gradient(-45deg,var(--chkfg) 50%,var(--chkbg) 50.99%)}.checkbox:indeterminate{--tw-bg-opacity:1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));background-repeat:no-repeat;animation:checkmark var(--animation-input,.2s) ease-out;background-image:linear-gradient(90deg,transparent 80%,var(--chkbg) 80%),linear-gradient(-90deg,transparent 80%,var(--chkbg) 80%),linear-gradient(0deg,var(--chkbg) 43%,var(--chkfg) 43%,var(--chkfg) 57%,var(--chkbg) 57%)}@keyframes checkmark{0%{background-position-y:5px}50%{background-position-y:-2px}to{background-position-y:0}}.divider:not(:empty){gap:1rem}.drawer-toggle:checked~.drawer-side>.drawer-overlay{background-color:#0006}.drawer-toggle:focus-visible~.drawer-content label.drawer-button{outline-style:solid;outline-width:2px;outline-offset:2px}.label-text{font-size:.875rem;line-height:1.25rem;--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.input input{--tw-bg-opacity:1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));background-color:transparent}.input input:focus{outline:2px solid transparent;outline-offset:2px}.input[list]::-webkit-calendar-picker-indicator{line-height:1em}.input-bordered{border-color:var(--fallback-bc,oklch(var(--bc)/.2))}.input:focus,.input:focus-within{box-shadow:none;border-color:var(--fallback-bc,oklch(var(--bc)/.2));outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/.2))}.input-primary{--tw-border-opacity:1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)))}.input-primary:focus,.input-primary:focus-within{--tw-border-opacity:1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));outline-color:var(--fallback-p,oklch(var(--p)/1))}.input-disabled,.input:disabled,.input:has(>input[disabled]),.input[disabled]{cursor:not-allowed;--tw-border-opacity:1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity:1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));color:var(--fallback-bc,oklch(var(--bc)/.4))}.input-disabled::-moz-placeholder,.input:disabled::-moz-placeholder,.input:has(>input[disabled])::-moz-placeholder,.input[disabled]::-moz-placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity:0.2}.input-disabled::placeholder,.input:disabled::placeholder,.input:has(>input[disabled])::placeholder,.input[disabled]::placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity:0.2}.input:has(>input[disabled])>input[disabled]{cursor:not-allowed}.input::-webkit-date-and-time-value{text-align:inherit}.join>:where(:not(:first-child)){margin-top:0;margin-bottom:0;margin-inline-start:-1px}.join>:where(:not(:first-child)):is(.btn){margin-inline-start:calc(var(--border-btn)*-1)}.link:focus{outline:2px solid transparent;outline-offset:2px}.link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.loading{pointer-events:none;display:inline-block;aspect-ratio:1/1;width:1.5rem;background-color:currentColor;-webkit-mask-size:100%;mask-size:100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;-webkit-mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'24\' height=\'24\' stroke=\'%23000\'%3E%3Ccircle cx=\'12\' cy=\'12\' r=\'9.5\' fill=\'none\' stroke-linecap=\'round\' stroke-width=\'3\'%3E%3CanimateTransform attributeName=\'transform\' dur=\'2s\' from=\'0 12 12\' repeatCount=\'indefinite\' to=\'360 12 12\' type=\'rotate\'/%3E%3Canimate attributeName=\'stroke-dasharray\' dur=\'1.5s\' keyTimes=\'0;0.475;1\' repeatCount=\'indefinite\' values=\'0,150;42,150;42,150\'/%3E%3Canimate attributeName=\'stroke-dashoffset\' dur=\'1.5s\' keyTimes=\'0;0.475;1\' repeatCount=\'indefinite\' values=\'0;-16;-59\'/%3E%3C/circle%3E%3C/svg%3E\");mask-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'24\' height=\'24\' stroke=\'%23000\'%3E%3Ccircle cx=\'12\' cy=\'12\' r=\'9.5\' fill=\'none\' stroke-linecap=\'round\' stroke-width=\'3\'%3E%3CanimateTransform attributeName=\'transform\' dur=\'2s\' from=\'0 12 12\' repeatCount=\'indefinite\' to=\'360 12 12\' type=\'rotate\'/%3E%3Canimate attributeName=\'stroke-dasharray\' dur=\'1.5s\' keyTimes=\'0;0.475;1\' repeatCount=\'indefinite\' values=\'0,150;42,150;42,150\'/%3E%3Canimate attributeName=\'stroke-dashoffset\' dur=\'1.5s\' keyTimes=\'0;0.475;1\' repeatCount=\'indefinite\' values=\'0;-16;-59\'/%3E%3C/circle%3E%3C/svg%3E\")}:where(.menu li:empty){--tw-bg-opacity:1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));opacity:.1;margin:.5rem 1rem;height:1px}.menu :where(li ul):before{position:absolute;bottom:.75rem;inset-inline-start:0;top:.75rem;width:1px;--tw-bg-opacity:1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));opacity:.1;content:\"\"}.menu :where(li:not(.menu-title)>:not(ul,details,.menu-title,.btn)),.menu :where(li:not(.menu-title)>details>summary:not(.menu-title)){border-radius:var(--rounded-btn,.5rem);padding:.5rem 1rem;text-align:start;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s;text-wrap:balance}:where(.menu li:not(.menu-title,.disabled)>:not(ul,details,.menu-title)):is(summary):not(.active,.btn):focus-visible,:where(.menu li:not(.menu-title,.disabled)>:not(ul,details,.menu-title)):not(summary,.active,.btn).focus,:where(.menu li:not(.menu-title,.disabled)>:not(ul,details,.menu-title)):not(summary,.active,.btn):focus,:where(.menu li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):is(summary):not(.active,.btn):focus-visible,:where(.menu li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(summary,.active,.btn).focus,:where(.menu li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(summary,.active,.btn):focus{cursor:pointer;background-color:var(--fallback-bc,oklch(var(--bc)/.1));--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));outline:2px solid transparent;outline-offset:2px}.menu li>:not(ul,.menu-title,details,.btn).active,.menu li>:not(ul,.menu-title,details,.btn):active,.menu li>details>summary:active{--tw-bg-opacity:1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.menu :where(li>details>summary)::-webkit-details-marker{display:none}.menu :where(li>.menu-dropdown-toggle):after,.menu :where(li>details>summary):after{justify-self:end;display:block;margin-top:-.5rem;height:.5rem;width:.5rem;transform:rotate(45deg);transition-property:transform,margin-top;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,0,.2,1);content:\"\";transform-origin:75% 75%;box-shadow:2px 2px;pointer-events:none}.menu :where(li>.menu-dropdown-toggle.menu-dropdown-show):after,.menu :where(li>details[open]>summary):after{transform:rotate(225deg);margin-top:0}.mockup-phone .display{overflow:hidden;border-radius:40px;margin-top:-25px}.mockup-browser .mockup-browser-toolbar .input{position:relative;margin-left:auto;margin-right:auto;display:block;height:1.75rem;width:24rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;--tw-bg-opacity:1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));padding-left:2rem;direction:ltr}.mockup-browser .mockup-browser-toolbar .input:before{left:.5rem;aspect-ratio:1/1;height:.75rem;--tw-translate-y:-50%;border-radius:9999px;border-width:2px;border-color:currentColor}.mockup-browser .mockup-browser-toolbar .input:after,.mockup-browser .mockup-browser-toolbar .input:before{content:\"\";position:absolute;top:50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));opacity:.6}.mockup-browser .mockup-browser-toolbar .input:after{left:1.25rem;height:.5rem;--tw-translate-y:25%;--tw-rotate:-45deg;border-radius:9999px;border-width:1px;border-color:currentColor}.modal::backdrop,.modal:not(dialog:not(.modal-open)){background-color:#0006;animation:modal-pop .2s ease-out}.modal-backdrop{z-index:-1;grid-column-start:1;grid-row-start:1;display:grid;align-self:stretch;justify-self:stretch;color:transparent}.modal-open .modal-box,.modal-toggle:checked+.modal .modal-box,.modal:target .modal-box,.modal[open] .modal-box{--tw-translate-y:0px;--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.modal-action>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.modal-action:where([dir=rtl],[dir=rtl] *)>:not([hidden])~:not([hidden]){--tw-space-x-reverse:1}@keyframes modal-pop{0%{opacity:0}}.progress::-moz-progress-bar{border-radius:var(--rounded-box,1rem);background-color:currentColor}.progress-primary::-moz-progress-bar{border-radius:var(--rounded-box,1rem);--tw-bg-opacity:1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)))}.progress:indeterminate{--progress-color:var(--fallback-bc,oklch(var(--bc)/1));background-image:repeating-linear-gradient(90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90%);background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}.progress-primary:indeterminate{--progress-color:var(--fallback-p,oklch(var(--p)/1))}.progress::-webkit-progress-bar{border-radius:var(--rounded-box,1rem);background-color:transparent}.progress::-webkit-progress-value{border-radius:var(--rounded-box,1rem);background-color:currentColor}.progress-primary::-webkit-progress-value{--tw-bg-opacity:1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)))}.progress:indeterminate::-moz-progress-bar{background-color:transparent;background-image:repeating-linear-gradient(90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90%);background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}@keyframes progress-loading{50%{background-position-x:-115%}}@keyframes radiomark{0%{box-shadow:0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset}50%{box-shadow:0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset}to{box-shadow:0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset}}.range:focus-visible::-webkit-slider-thumb{--focus-shadow:0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 2rem var(--range-shdw) inset}.range:focus-visible::-moz-range-thumb{--focus-shadow:0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 2rem var(--range-shdw) inset}.range::-webkit-slider-runnable-track{height:.5rem;width:100%;border-radius:var(--rounded-box,1rem);background-color:var(--fallback-bc,oklch(var(--bc)/.1))}.range::-moz-range-track{height:.5rem;width:100%;border-radius:var(--rounded-box,1rem);background-color:var(--fallback-bc,oklch(var(--bc)/.1))}.range::-webkit-slider-thumb{position:relative;height:1.5rem;width:1.5rem;border-radius:var(--rounded-box,1rem);border-style:none;--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));appearance:none;-webkit-appearance:none;top:50%;color:var(--range-shdw);transform:translateY(-50%);--filler-size:100rem;--filler-offset:0.6rem;box-shadow:0 0 0 3px var(--range-shdw) inset,var(--focus-shadow,0 0),calc(var(--filler-size)*-1 - var(--filler-offset)) 0 0 var(--filler-size)}.range::-moz-range-thumb{position:relative;height:1.5rem;width:1.5rem;border-radius:var(--rounded-box,1rem);border-style:none;--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));top:50%;color:var(--range-shdw);--filler-size:100rem;--filler-offset:0.5rem;box-shadow:0 0 0 3px var(--range-shdw) inset,var(--focus-shadow,0 0),calc(var(--filler-size)*-1 - var(--filler-offset)) 0 0 var(--filler-size)}@keyframes rating-pop{0%{transform:translateY(-.125em)}40%{transform:translateY(-.125em)}to{transform:translateY(0)}}.select-bordered,.select:focus{border-color:var(--fallback-bc,oklch(var(--bc)/.2))}.select:focus{box-shadow:none;outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/.2))}.select-primary,.select-primary:focus{--tw-border-opacity:1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)))}.select-primary:focus{outline-color:var(--fallback-p,oklch(var(--p)/1))}.select-disabled,.select:disabled,.select[disabled]{cursor:not-allowed;--tw-border-opacity:1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity:1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));color:var(--fallback-bc,oklch(var(--bc)/.4))}.select-disabled::-moz-placeholder,.select:disabled::-moz-placeholder,.select[disabled]::-moz-placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity:0.2}.select-disabled::placeholder,.select:disabled::placeholder,.select[disabled]::placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity:0.2}.select-multiple,.select[multiple],.select[size].select:not([size=\"1\"]){background-image:none;padding-right:1rem}[dir=rtl] .select{background-position:12px calc(1px + 50%),16px calc(1px + 50%)}@keyframes skeleton{0%{background-position:150%}to{background-position:-50%}}.steps .step:before{top:0;height:.5rem;width:100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));content:\"\";margin-inline-start:-100%}.steps .step:after,.steps .step:before{grid-column-start:1;grid-row-start:1;--tw-bg-opacity:1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));--tw-text-opacity:1}.steps .step:after{content:counter(step);counter-increment:step;z-index:1;position:relative;display:grid;height:2rem;width:2rem;place-items:center;place-self:center;border-radius:9999px;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.steps .step:first-child:before{content:none}.steps .step[data-content]:after{content:attr(data-content)}.steps .step-neutral+.step-neutral:before,.steps .step-neutral:after{--tw-bg-opacity:1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.steps .step-primary+.step-primary:before,.steps .step-primary:after{--tw-bg-opacity:1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.steps .step-secondary+.step-secondary:before,.steps .step-secondary:after{--tw-bg-opacity:1;background-color:var(--fallback-s,oklch(var(--s)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)))}.steps .step-accent+.step-accent:before,.steps .step-accent:after{--tw-bg-opacity:1;background-color:var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)))}.steps .step-info+.step-info:before,.steps .step-info:after{--tw-bg-opacity:1;background-color:var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity)))}.steps .step-info:after{--tw-text-opacity:1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)))}.steps .step-success+.step-success:before,.steps .step-success:after{--tw-bg-opacity:1;background-color:var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)))}.steps .step-success:after{--tw-text-opacity:1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)))}.steps .step-warning+.step-warning:before,.steps .step-warning:after{--tw-bg-opacity:1;background-color:var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity)))}.steps .step-warning:after{--tw-text-opacity:1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)))}.steps .step-error+.step-error:before,.steps .step-error:after{--tw-bg-opacity:1;background-color:var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity)))}.steps .step-error:after{--tw-text-opacity:1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}.table:where([dir=rtl],[dir=rtl] *){text-align:right}.table :where(th,td){padding:.75rem 1rem;vertical-align:middle}.table tr.active,.table tr.active:nth-child(2n),.table-zebra tbody tr:nth-child(2n){--tw-bg-opacity:1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)))}.table-zebra tr.active,.table-zebra tr.active:nth-child(2n),.table-zebra-zebra tbody tr:nth-child(2n){--tw-bg-opacity:1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)))}.table :where(thead tr,tbody tr:not(:last-child),tbody tr:first-child:last-child){border-bottom-width:1px;--tw-border-opacity:1;border-bottom-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)))}.table :where(thead,tfoot){white-space:nowrap;font-size:.75rem;line-height:1rem;font-weight:700;color:var(--fallback-bc,oklch(var(--bc)/.6))}.table :where(tfoot){border-top-width:1px;--tw-border-opacity:1;border-top-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)))}@keyframes toast-pop{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}[dir=rtl] .toggle{--handleoffsetcalculator:calc(var(--handleoffset)*1)}.toggle:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/.2))}.toggle:hover{background-color:currentColor}.toggle:checked,.toggle[aria-checked=true]{background-image:none;--handleoffsetcalculator:var(--handleoffset);--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}[dir=rtl] .toggle:checked,[dir=rtl] .toggle[aria-checked=true]{--handleoffsetcalculator:calc(var(--handleoffset)*-1)}.toggle:indeterminate{--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));box-shadow:calc(var(--handleoffset)/2) 0 0 2px var(--tglbg) inset,calc(var(--handleoffset)/-2) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset}[dir=rtl] .toggle:indeterminate{box-shadow:calc(var(--handleoffset)/2) 0 0 2px var(--tglbg) inset,calc(var(--handleoffset)/-2) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset}.toggle-primary:focus-visible{outline-color:var(--fallback-p,oklch(var(--p)/1))}.toggle-primary:checked,.toggle-primary[aria-checked=true]{border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));--tw-border-opacity:0.1;--tw-bg-opacity:1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.toggle:disabled{cursor:not-allowed;--tw-border-opacity:1;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));background-color:transparent;opacity:.3;--togglehandleborder:0 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset,var(--handleoffsetcalculator) 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset}.btm-nav-xs>:where(.active){border-top-width:1px}.btm-nav-sm>:where(.active){border-top-width:2px}.btm-nav-md>:where(.active){border-top-width:2px}.btm-nav-lg>:where(.active){border-top-width:4px}.btn-xs{height:1.5rem;min-height:1.5rem;padding-left:.5rem;padding-right:.5rem;font-size:.75rem}.btn-sm{height:2rem;min-height:2rem;padding-left:.75rem;padding-right:.75rem;font-size:.875rem}.btn-lg{height:4rem;min-height:4rem;padding-left:1.5rem;padding-right:1.5rem;font-size:1.125rem}.btn-square:where(.btn-xs){height:1.5rem;width:1.5rem;padding:0}.btn-square:where(.btn-sm){height:2rem;width:2rem;padding:0}.btn-square:where(.btn-md){height:3rem;width:3rem;padding:0}.btn-square:where(.btn-lg){height:4rem;width:4rem;padding:0}.btn-circle:where(.btn-xs){height:1.5rem;width:1.5rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-sm){height:2rem;width:2rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-md){height:3rem;width:3rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-lg){height:4rem;width:4rem;border-radius:9999px;padding:0}.drawer-open>.drawer-toggle{display:none}.drawer-open>.drawer-toggle~.drawer-side{pointer-events:auto;visibility:visible;position:sticky;display:block;width:auto;overscroll-behavior:auto}.drawer-open>.drawer-toggle~.drawer-side>:not(.drawer-overlay),[dir=rtl] .drawer-open>.drawer-toggle~.drawer-side>:not(.drawer-overlay){transform:translateX(0)}.drawer-open>.drawer-toggle:checked~.drawer-side{pointer-events:auto;visibility:visible}.drawer-open>.drawer-side{overflow-y:auto}html:has(.drawer-toggle:checked){overflow-y:hidden;scrollbar-gutter:stable}.input-sm{height:2rem;padding-left:.75rem;padding-right:.75rem;font-size:.875rem;line-height:2rem}.join.join-vertical{flex-direction:column}.join.join-vertical .join-item:first-child:not(:last-child),.join.join-vertical :first-child:not(:last-child) .join-item{border-end-start-radius:0;border-end-end-radius:0;border-start-start-radius:inherit;border-start-end-radius:inherit}.join.join-vertical .join-item:last-child:not(:first-child),.join.join-vertical :last-child:not(:first-child) .join-item{border-start-start-radius:0;border-start-end-radius:0;border-end-start-radius:inherit;border-end-end-radius:inherit}.join.join-horizontal{flex-direction:row}.join.join-horizontal .join-item:first-child:not(:last-child),.join.join-horizontal :first-child:not(:last-child) .join-item{border-end-end-radius:0;border-start-end-radius:0;border-end-start-radius:inherit;border-start-start-radius:inherit}.join.join-horizontal .join-item:last-child:not(:first-child),.join.join-horizontal :last-child:not(:first-child) .join-item{border-end-start-radius:0;border-start-start-radius:0;border-end-end-radius:inherit;border-start-end-radius:inherit}.modal-bottom{place-items:end}.range-lg{height:2rem}.range-lg::-webkit-slider-runnable-track{height:1rem}.range-lg::-moz-range-track{height:1rem}.range-lg::-webkit-slider-thumb{height:2rem;width:2rem;--filler-offset:1rem}.range-lg::-moz-range-thumb{height:2rem;width:2rem;--filler-offset:1rem}.steps-horizontal .step{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));grid-template-rows:repeat(2,minmax(0,1fr));place-items:center;text-align:center}.steps-vertical .step{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:repeat(1,minmax(0,1fr))}.card-compact .card-body{padding:1rem;font-size:.875rem;line-height:1.25rem}.card-compact .card-title{margin-bottom:.25rem}.card-normal .card-body{padding:var(--padding-card,2rem);font-size:1rem;line-height:1.5rem}.card-normal .card-title{margin-bottom:.75rem}.drawer-open>.drawer-toggle~.drawer-side>.drawer-overlay{cursor:default;background-color:transparent}.join.join-vertical>:where(:not(:first-child)){margin-left:0;margin-right:0;margin-top:-1px}.join.join-vertical>:where(:not(:first-child)):is(.btn){margin-top:calc(var(--border-btn)*-1)}.join.join-horizontal>:where(:not(:first-child)){margin-top:0;margin-bottom:0;margin-inline-start:-1px}.join.join-horizontal>:where(:not(:first-child)):is(.btn){margin-inline-start:calc(var(--border-btn)*-1);margin-top:0}.modal-top :where(.modal-box){width:100%;max-width:none;--tw-translate-y:-2.5rem;--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-bottom-right-radius:var(--rounded-box,1rem);border-bottom-left-radius:var(--rounded-box,1rem);border-top-left-radius:0;border-top-right-radius:0}.modal-middle :where(.modal-box){width:91.666667%;max-width:32rem;--tw-translate-y:0px;--tw-scale-x:.9;--tw-scale-y:.9;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-top-left-radius:var(--rounded-box,1rem);border-top-right-radius:var(--rounded-box,1rem);border-bottom-right-radius:var(--rounded-box,1rem);border-bottom-left-radius:var(--rounded-box,1rem)}.modal-bottom :where(.modal-box){width:100%;max-width:none;--tw-translate-y:2.5rem;--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-top-left-radius:var(--rounded-box,1rem);border-top-right-radius:var(--rounded-box,1rem);border-bottom-right-radius:0;border-bottom-left-radius:0}.steps-horizontal .step{grid-template-rows:40px 1fr;grid-template-columns:auto;min-width:4rem}.steps-horizontal .step:before{height:.5rem;width:100%;--tw-translate-x:0px;--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));content:\"\";margin-inline-start:-100%}.steps-horizontal .step:where([dir=rtl],[dir=rtl] *):before{--tw-translate-x:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.steps-vertical .step{gap:.5rem;grid-template-columns:40px 1fr;grid-template-rows:auto;min-height:4rem;justify-items:start}.steps-vertical .step:before{height:100%;width:.5rem;--tw-translate-x:-50%;--tw-translate-y:-50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));margin-inline-start:50%}.steps-vertical .step:where([dir=rtl],[dir=rtl] *):before{--tw-translate-x:50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.table-xs :not(thead):not(tfoot) tr{font-size:.75rem;line-height:1rem}.table-xs :where(th,td){padding:.25rem .5rem}.fixed{position:fixed}.sticky{position:sticky}.top-0{top:0}.m-0{margin:0}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-5{height:1.25rem}.h-6{height:1.5rem}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-60{width:15rem}.w-full{width:100%}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-none{flex:none}.shrink-0{flex-shrink:0}.cursor-pointer{cursor:pointer}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.overflow-x-auto{overflow-x:auto}.whitespace-nowrap{white-space:nowrap}.rounded-box{border-radius:var(--rounded-box,1rem)}.rounded-lg{border-radius:.5rem}.rounded-r-lg{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-4{border-left-width:4px}.border-base-300{--tw-border-opacity:1;border-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity,1)))}.border-error{--tw-border-opacity:1;border-color:var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity,1)))}.bg-base-100{--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity,1)))}.bg-base-200{--tw-bg-opacity:1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity,1)))}.bg-error\\/10{background-color:var(--fallback-er,oklch(var(--er)/.1))}.bg-neutral{--tw-bg-opacity:1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity,1)))}.bg-success\\/20{background-color:var(--fallback-su,oklch(var(--su)/.2))}.stroke-current{stroke:currentColor}.p-2{padding:.5rem}.p-3{padding:.75rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pb-3{padding-bottom:.75rem}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.text-base-content\\/60{color:var(--fallback-bc,oklch(var(--bc)/.6))}.text-base-content\\/70{color:var(--fallback-bc,oklch(var(--bc)/.7))}.text-base-content\\/80{color:var(--fallback-bc,oklch(var(--bc)/.8))}.text-error{--tw-text-opacity:1;color:var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity,1)))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.text-neutral-content{--tw-text-opacity:1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity,1)))}.text-primary{--tw-text-opacity:1;color:var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity,1)))}.text-success{--tw-text-opacity:1;color:var(--fallback-su,oklch(var(--su)/var(--tw-text-opacity,1)))}.text-warning{--tw-text-opacity:1;color:var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity,1)))}.opacity-70{opacity:.7}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}@media (min-width:640px){.sm\\:modal-middle{place-items:center}.sm\\:modal-middle :where(.modal-box){width:91.666667%;max-width:32rem;--tw-translate-y:0px;--tw-scale-x:.9;--tw-scale-y:.9;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-top-left-radius:var(--rounded-box,1rem);border-top-right-radius:var(--rounded-box,1rem);border-bottom-right-radius:var(--rounded-box,1rem);border-bottom-left-radius:var(--rounded-box,1rem)}}.group:hover .group-hover\\:text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}@media (prefers-color-scheme:dark){.dark\\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.dark\\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.group:hover .dark\\:group-hover\\:text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}}"; + +#endif // STYLES_CSS_H_ diff --git a/src/web_portal_basic.html.h b/src/web_portal_basic.html.h new file mode 100644 index 00000000..4cd3686e --- /dev/null +++ b/src/web_portal_basic.html.h @@ -0,0 +1,10 @@ +// ---------------------------------------------------------- // +// This file is autogenerated by html2header.py; do not edit! // +// ---------------------------------------------------------- // + +#ifndef WEB_PORTAL_BASIC_HTML_H_ +#define WEB_PORTAL_BASIC_HTML_H_ + +const char html_web_portal_basic_html[] = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n\n\n\n \n OpenTrickler\n \n \n \n\n\n
\n

OpenTrickler

\n
\n\n
\n \n
\n
\n

WiFi Status

\n
\n
\n Status\n --\n
\n
\n SSID\n --\n
\n
\n IP Address\n --\n
\n
\n
\n
\n\n \n
\n
\n

WiFi Configuration

\n
\n
\n \n \n
\n\n
\n \n \n
\n\n
\n \n \n
\n\n \n\n
\n \n \n
\n
\n
\n
\n\n \n
\n
\n

Display Type

\n
\n \n
\n
\n \n \n
\n
\n
\n\n \n
\n
\n

Pico LED

\n
\n \n \n
\n
\n
\n\n \n
\n
\n
\n

Errors

\n 0 errors\n
\n
\n
No errors
\n
\n
\n \n \n
\n
\n
\n
\n\n \n\n\n"; + +#endif // WEB_PORTAL_BASIC_HTML_H_ From 5fb3de8b4018c28bbcaf30c9f6c7958025b28e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dzi=C4=85dziak?= Date: Thu, 12 Mar 2026 05:06:18 +0100 Subject: [PATCH 02/34] Fix build errors for RP2350 port of AI tuning feature - Replace missing encoder.h with mini_12864_module.h in ai_tuning.c and rest_ai_tuning.c - Add acquire/release_display_buffer_access() declarations to display.h - Add EEPROM_AI_TUNING_CONFIG_BASE_ADDR to eeprom.h - Add coarse_time_target_ms and total_time_target_ms fields to eeprom_charge_mode_data_t - Remove duplicate display_rotation_t from display_config.h (use mini_12864_module.h) - Remove duplicate exit_state definition from menu.c (owned by app_state.c) - Add app_state.h include to menu.c Co-Authored-By: Claude Sonnet 4.6 --- src/ai_tuning.c | 3 ++- src/charge_mode.h | 6 +++++- src/display.h | 2 ++ src/display_config.h | 8 +------- src/eeprom.h | 1 + src/menu.c | 4 +--- src/rest_ai_tuning.c | 3 ++- 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/ai_tuning.c b/src/ai_tuning.c index e4bd89e7..c354405c 100644 --- a/src/ai_tuning.c +++ b/src/ai_tuning.c @@ -14,7 +14,8 @@ #include #include "FreeRTOS.h" #include "semphr.h" -#include "encoder.h" +#include "mini_12864_module.h" +extern QueueHandle_t encoder_event_queue; // Access charge mode config for thresholds extern charge_mode_config_t charge_mode_config; diff --git a/src/charge_mode.h b/src/charge_mode.h index 78193ce8..86c9d26d 100644 --- a/src/charge_mode.h +++ b/src/charge_mode.h @@ -7,7 +7,7 @@ #include "neopixel_led.h" -#define EEPROM_CHARGE_MODE_DATA_REV 8 // 16 byte +#define EEPROM_CHARGE_MODE_DATA_REV 9 // 16 byte #define WEIGHT_STRING_LEN 8 @@ -36,6 +36,10 @@ typedef struct { uint32_t precharge_time_ms; float precharge_speed_rps; + // AI tuning time targets + uint32_t coarse_time_target_ms; + uint32_t total_time_target_ms; + // LED related settings rgbw_u32_t neopixel_normal_charge_colour; rgbw_u32_t neopixel_under_charge_colour; diff --git a/src/display.h b/src/display.h index 5487e86e..c4ac7de6 100644 --- a/src/display.h +++ b/src/display.h @@ -9,6 +9,8 @@ extern "C" { #endif u8g2_t *get_display_handler(void); +void acquire_display_buffer_access(void); +void release_display_buffer_access(void); // REST bool http_get_display_buffer(struct fs_file *file, int num_params, char *params[], char *values[]); diff --git a/src/display_config.h b/src/display_config.h index 9c34c097..2acdcce9 100644 --- a/src/display_config.h +++ b/src/display_config.h @@ -4,6 +4,7 @@ #include #include #include "http_rest.h" +#include "mini_12864_module.h" #define EEPROM_DISPLAY_CONFIG_BASE_ADDR (13 * 1024) // 13K - Display type selection #define EEPROM_DISPLAY_CONFIG_DATA_REV 2 @@ -14,13 +15,6 @@ typedef enum { DISPLAY_TYPE_TFT43 = 2, // TFT43 V3.0: 4.3 inch, 480x272 } display_type_t; -typedef enum { - DISPLAY_ROTATION_0 = 0, - DISPLAY_ROTATION_90 = 1, - DISPLAY_ROTATION_180 = 2, - DISPLAY_ROTATION_270 = 3, -} display_rotation_t; - typedef struct { uint32_t data_rev; display_type_t display_type; diff --git a/src/eeprom.h b/src/eeprom.h index 773e9fc5..a12330a7 100644 --- a/src/eeprom.h +++ b/src/eeprom.h @@ -16,6 +16,7 @@ #define EEPROM_MINI_12864_CONFIG_BASE_ADDR 8 * 1024 // 8k #define EEPROM_PROFILE_DATA_BASE_ADDR 9 * 1024 // 9k #define EEPROM_SERVO_GATE_CONFIG_BASE_ADDR 10 * 1024 // 10k +#define EEPROM_AI_TUNING_CONFIG_BASE_ADDR 14 * 1024 // 14k #define EEPROM_METADATA_REV 2 // 16 byte diff --git a/src/menu.c b/src/menu.c index b7617c83..33337a0d 100644 --- a/src/menu.c +++ b/src/menu.c @@ -17,6 +17,7 @@ #include "eeprom.h" #include "wireless.h" #include "system_control.h" +#include "app_state.h" // External variables extern muif_t muif_list[]; @@ -27,9 +28,6 @@ extern const size_t muif_cnt; // Local variables -AppState_t exit_state = APP_STATE_DEFAULT; - - void menu_task(void *p){ u8g2_t * display_handler = get_display_handler(); // Create UI element diff --git a/src/rest_ai_tuning.c b/src/rest_ai_tuning.c index 1a0b3c51..860fc773 100644 --- a/src/rest_ai_tuning.c +++ b/src/rest_ai_tuning.c @@ -5,13 +5,14 @@ #include "common.h" #include "app.h" #include "app_state.h" -#include "encoder.h" +#include "mini_12864_module.h" #include "charge_mode.h" #include #include #include #include #include +extern QueueHandle_t encoder_event_queue; // External references for triggering charge mode extern charge_mode_config_t charge_mode_config; From 489a4c397b0f53f91eb34e0d2af8bc9e9f914705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dzi=C4=85dziak?= Date: Thu, 12 Mar 2026 05:29:06 +0100 Subject: [PATCH 03/34] Integrate AI tuning into charge_mode.cpp - charge_mode_wait_for_complete() now consults ai_tuning_get_motor_mode() and ai_tuning_get_next_params() to inject tuned Kp/Kd into the PID loop - Phase 2 (FINE_ONLY): coarse pre-charge using session->coarse_kp_best before fine PID loop runs - Motors skipped per AI motor mode (COARSE_ONLY skips fine, FINE_ONLY skips normal coarse) - ai_tuning_record_drop() called after each charge with full telemetry - ai_tuning_record_charge() called during normal charges when ml_data_collection_enabled is true - RST during AI tuning calls ai_tuning_cancel() - charge_mode_menu() exits after cup removal when ai_tuning_is_complete() - charge_mode.h: add ml_data_collection_enabled field, bump EEPROM rev to 10 - profile.h: export profile_get_selected_idx() declaration Co-Authored-By: Claude Sonnet 4.6 --- src/charge_mode.cpp | 140 +++++++++++++++++++++++++++++++++++--------- src/charge_mode.h | 5 +- src/profile.h | 1 + 3 files changed, 117 insertions(+), 29 deletions(-) diff --git a/src/charge_mode.cpp b/src/charge_mode.cpp index ad497e08..d6605927 100644 --- a/src/charge_mode.cpp +++ b/src/charge_mode.cpp @@ -21,6 +21,7 @@ #include "profile.h" #include "common.h" #include "servo_gate.h" +#include "ai_tuning.h" uint8_t charge_weight_digits[] = {0, 0, 0, 0, 0}; @@ -48,6 +49,13 @@ const eeprom_charge_mode_data_t default_charge_mode_data = { .precharge_time_ms = 1000, .precharge_speed_rps = 2, + // AI tuning time targets (defaults: 500ms coarse pre-charge, 3000ms total) + .coarse_time_target_ms = 500, + .total_time_target_ms = 3000, + + // ML data collection disabled by default + .ml_data_collection_enabled = false, + // LED related .neopixel_normal_charge_colour = RGB_COLOUR_GREEN, // green .neopixel_under_charge_colour = RGB_COLOUR_YELLOW, // yellow @@ -204,11 +212,20 @@ void charge_mode_wait_for_complete() { charge_start_tick = xTaskGetTickCount(); + // AI tuning: determine motor mode and get next params for this drop + ai_motor_mode_t ai_motor_mode = ai_tuning_get_motor_mode(); + bool ai_active = ai_tuning_is_active(); + float ai_coarse_kp = 0.0f, ai_coarse_kd = 0.0f; + float ai_fine_kp = 0.0f, ai_fine_kd = 0.0f; + if (ai_active) { + ai_tuning_get_next_params(&ai_coarse_kp, &ai_coarse_kd, &ai_fine_kp, &ai_fine_kd); + } + // Set colour to under charge neopixel_led_set_colour( neopixel_led_config.eeprom_neopixel_led_metadata.default_led_colours.mini12864_backlight_colour, - charge_mode_config.eeprom_charge_mode_data.neopixel_under_charge_colour, - charge_mode_config.eeprom_charge_mode_data.neopixel_under_charge_colour, + charge_mode_config.eeprom_charge_mode_data.neopixel_under_charge_colour, + charge_mode_config.eeprom_charge_mode_data.neopixel_under_charge_colour, true ); @@ -216,17 +233,21 @@ void charge_mode_wait_for_complete() { if (servo_gate.eeprom_servo_gate_config.servo_gate_enable) { servo_gate_set_ratio(SERVO_GATE_RATIO_OPEN, false); } + // Update current status char target_weight_string[WEIGHT_STRING_LEN]; float_to_string(target_weight_string, charge_mode_config.target_charge_weight, charge_mode_config.eeprom_charge_mode_data.decimal_places); - - snprintf(title_string, sizeof(title_string), - "Target: %s", - target_weight_string); + snprintf(title_string, sizeof(title_string), "Target: %s", target_weight_string); // Read trickling parameter from the current profile profile_t * current_profile = profile_get_selected(); + // Use AI-tuned params if active, otherwise use profile params + float coarse_kp_used = ai_active ? ai_coarse_kp : current_profile->coarse_kp; + float coarse_kd_used = ai_active ? ai_coarse_kd : current_profile->coarse_kd; + float fine_kp_used = ai_active ? ai_fine_kp : current_profile->fine_kp; + float fine_kd_used = ai_active ? ai_fine_kd : current_profile->fine_kd; + // Find the minimum of max speed from the motor and the profile float coarse_trickler_max_speed = fmin(get_motor_max_speed(SELECT_COARSE_TRICKLER_MOTOR), current_profile->coarse_max_flow_speed_rps); @@ -237,18 +258,42 @@ void charge_mode_wait_for_complete() { float fine_trickler_min_speed = fmax(get_motor_min_speed(SELECT_FINE_TRICKLER_MOTOR), current_profile->fine_min_flow_speed_rps); + // Phase 2 (FINE_ONLY): run a coarse pre-charge using Phase 1's best coarse params before fine PID + TickType_t coarse_stop_tick = 0; + if (ai_active && ai_motor_mode == AI_MOTOR_MODE_FINE_ONLY) { + ai_tuning_session_t *session = ai_tuning_get_session(); + uint32_t precharge_ms = charge_mode_config.eeprom_charge_mode_data.coarse_time_target_ms; + if (precharge_ms > 0 && session->coarse_kp_best > 0.0f) { + float init_weight; + if (scale_block_wait_for_next_measurement(200, &init_weight)) { + float init_error = charge_mode_config.target_charge_weight - init_weight; + float precharge_speed = fmax(coarse_trickler_min_speed, + fmin(session->coarse_kp_best * init_error, coarse_trickler_max_speed)); + motor_set_speed(SELECT_COARSE_TRICKLER_MOTOR, precharge_speed); + vTaskDelay(pdMS_TO_TICKS(precharge_ms)); + motor_set_speed(SELECT_COARSE_TRICKLER_MOTOR, 0); + } + } + coarse_stop_tick = xTaskGetTickCount(); + } + float integral = 0.0f; float last_error = 0.0f; TickType_t last_sample_tick = xTaskGetTickCount(); TickType_t current_sample_tick = last_sample_tick; - bool should_coarse_trickler_move = true; + + // In FINE_ONLY mode coarse trickler was already handled by pre-charge above + bool should_coarse_trickler_move = (ai_motor_mode != AI_MOTOR_MODE_FINE_ONLY); while (true) { // Non block waiting for the input ButtonEncoderEvent_t button_encoder_event = button_wait_for_input(false); if (button_encoder_event == BUTTON_RST_PRESSED) { charge_mode_config.charge_mode_state = CHARGE_MODE_EXIT; + if (ai_active) { + ai_tuning_cancel(); + } return; } @@ -272,45 +317,44 @@ void charge_mode_wait_for_complete() { break; } - // Coarse trickler move condition + // Coarse trickler move condition else if (error < charge_mode_config.eeprom_charge_mode_data.coarse_stop_threshold && should_coarse_trickler_move) { should_coarse_trickler_move = false; motor_set_speed(SELECT_COARSE_TRICKLER_MOTOR, 0); + coarse_stop_tick = xTaskGetTickCount(); - // NEW: When the coarse trickler stops, move the servo gate to a configured ratio + // When the coarse trickler stops, move the servo gate to a configured ratio // Ratio convention: 0.0 = open, 1.0 = close if (servo_gate.eeprom_servo_gate_config.servo_gate_enable) { float r = charge_mode_config.eeprom_charge_mode_data.coarse_stop_gate_ratio; - servo_gate_set_ratio(r, false); // don't block the charge loop } - // TODO: When turning off the coarse trickler, also move reverse to back off some powder } - // Update PID variables float elapse_time_ms = (current_sample_tick - last_sample_tick) / portTICK_RATE_MS; integral += error; float derivative = (error - last_error) / elapse_time_ms; - // Update fine trickler speed - float new_p = current_profile->fine_kp * error; - float new_i = current_profile->fine_ki * integral; - float new_d = current_profile->fine_kd * derivative; - float new_speed = fmax(fine_trickler_min_speed, fmin(new_p + new_i + new_d, fine_trickler_max_speed)); + float new_p, new_i, new_d, new_speed; - motor_set_speed(SELECT_FINE_TRICKLER_MOTOR, new_speed); + // Update fine trickler speed (skip if COARSE_ONLY mode) + if (ai_motor_mode != AI_MOTOR_MODE_COARSE_ONLY) { + new_p = fine_kp_used * error; + new_i = current_profile->fine_ki * integral; + new_d = fine_kd_used * derivative; + new_speed = fmax(fine_trickler_min_speed, fmin(new_p + new_i + new_d, fine_trickler_max_speed)); + motor_set_speed(SELECT_FINE_TRICKLER_MOTOR, new_speed); + } // Update coarse trickler speed if (should_coarse_trickler_move) { - new_p = current_profile->coarse_kp * error; + new_p = coarse_kp_used * error; new_i = current_profile->coarse_ki * integral; - new_d = current_profile->coarse_kd * derivative; - + new_d = coarse_kd_used * derivative; new_speed = fmax(coarse_trickler_min_speed, fmin(new_p + new_i + new_d, coarse_trickler_max_speed)); - motor_set_speed(SELECT_COARSE_TRICKLER_MOTOR, new_speed); } @@ -319,19 +363,55 @@ void charge_mode_wait_for_complete() { last_error = error; } - // Stop the timer - TickType_t now = xTaskGetTickCount(); - TickType_t elapsed_ticks = now - charge_start_tick; + // Stop the timer + TickType_t end_tick = xTaskGetTickCount(); + TickType_t elapsed_ticks = end_tick - charge_start_tick; last_charge_elapsed_seconds = (float)(elapsed_ticks * portTICK_PERIOD_MS) / 1000.0f; + // Record AI telemetry or background ML data + float final_weight = scale_get_current_measurement(); + float total_ms = (float)(elapsed_ticks * portTICK_PERIOD_MS); + float coarse_ms = coarse_stop_tick ? (float)((coarse_stop_tick - charge_start_tick) * portTICK_PERIOD_MS) : 0.0f; + float fine_ms = total_ms - coarse_ms; + + if (ai_active) { + float overthrow = final_weight - charge_mode_config.target_charge_weight; + ai_drop_telemetry_t telemetry = {}; + telemetry.drop_number = ai_tuning_get_session()->drops_completed; + telemetry.coarse_time_ms = coarse_ms; + telemetry.fine_time_ms = fine_ms; + telemetry.total_time_ms = total_ms; + telemetry.final_weight = final_weight; + telemetry.target_weight = charge_mode_config.target_charge_weight; + telemetry.overthrow = overthrow; + telemetry.overthrow_percent = (charge_mode_config.target_charge_weight > 0.0f) + ? (overthrow / charge_mode_config.target_charge_weight) * 100.0f + : 0.0f; + telemetry.coarse_kp_used = coarse_kp_used; + telemetry.coarse_kd_used = coarse_kd_used; + telemetry.fine_kp_used = fine_kp_used; + telemetry.fine_kd_used = fine_kd_used; + ai_tuning_record_drop(&telemetry); + } + else if (charge_mode_config.eeprom_charge_mode_data.ml_data_collection_enabled) { + // Background ML data collection during normal charges + ai_tuning_record_charge( + (uint8_t) profile_get_selected_idx(), + current_profile->coarse_kp, current_profile->coarse_kd, + current_profile->fine_kp, current_profile->fine_kd, + final_weight - charge_mode_config.target_charge_weight, + coarse_ms, fine_ms + ); + } + // Close the gate if the servo gate is present if (servo_gate.eeprom_servo_gate_config.servo_gate_enable) { - servo_gate_set_ratio(SERVO_GATE_RATIO_CLOSED, true); + servo_gate_set_ratio(SERVO_GATE_RATIO_CLOSED, true); } // Precharge if (charge_mode_config.eeprom_charge_mode_data.precharge_enable && - servo_gate.eeprom_servo_gate_config.servo_gate_enable) { + servo_gate.eeprom_servo_gate_config.servo_gate_enable) { // Set a fixed delay between closing the gate and precharge to allow the gate to fully close vTaskDelay(pdMS_TO_TICKS(500)); @@ -342,7 +422,7 @@ void charge_mode_wait_for_complete() { motor_set_speed(SELECT_COARSE_TRICKLER_MOTOR, 0); } else { - vTaskDelay(pdMS_TO_TICKS(20)); // Wait for other tasks to complete + vTaskDelay(pdMS_TO_TICKS(20)); // Wait for other tasks to complete } charge_mode_config.charge_mode_state = CHARGE_MODE_WAIT_FOR_CUP_REMOVAL; @@ -537,6 +617,10 @@ uint8_t charge_mode_menu(bool charge_mode_skip_user_input) { break; case CHARGE_MODE_WAIT_FOR_CUP_REMOVAL: charge_mode_wait_for_cup_removal(); + // If AI tuning just completed, exit after the user removes the cup + if (ai_tuning_is_complete()) { + charge_mode_config.charge_mode_state = CHARGE_MODE_EXIT; + } break; case CHARGE_MODE_WAIT_FOR_CUP_RETURN: charge_mode_wait_for_cup_return(); diff --git a/src/charge_mode.h b/src/charge_mode.h index 86c9d26d..ef87c0b9 100644 --- a/src/charge_mode.h +++ b/src/charge_mode.h @@ -7,7 +7,7 @@ #include "neopixel_led.h" -#define EEPROM_CHARGE_MODE_DATA_REV 9 // 16 byte +#define EEPROM_CHARGE_MODE_DATA_REV 10 // 16 byte #define WEIGHT_STRING_LEN 8 @@ -40,6 +40,9 @@ typedef struct { uint32_t coarse_time_target_ms; uint32_t total_time_target_ms; + // ML data collection during normal (non-tuning) charges + bool ml_data_collection_enabled; + // LED related settings rgbw_u32_t neopixel_normal_charge_colour; rgbw_u32_t neopixel_under_charge_colour; diff --git a/src/profile.h b/src/profile.h index 1763cd50..477e09f9 100644 --- a/src/profile.h +++ b/src/profile.h @@ -54,6 +54,7 @@ bool profile_data_save(); profile_t * profile_select(uint8_t idx); profile_t * profile_get_selected(); +uint16_t profile_get_selected_idx(void); // REST interface bool http_rest_profile_config(struct fs_file *file, int num_params, char *params[], char *values[]); From 47df537507374e08de0dd00839141771359f3cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dzi=C4=85dziak?= Date: Thu, 12 Mar 2026 05:54:38 +0100 Subject: [PATCH 04/34] Add AI tuning web UI and REST config fields c14/c15/c16 - charge_mode.cpp: extend JSON response with c14 (coarse_time_target_ms), c15 (total_time_target_ms), c16 (ml_data_collection_enabled) - web_portal.html: add AI tuning live panel (aiTuningMainPanel) and ML suggestions banner to trickler section; add full AI Tuning settings page with profile/target/time-target fields, status display, start/ cancel/apply/clear-history controls, advanced Kp/Kd range config, and ML data collection toggle; add AI Tuning nav item; add all supporting JS functions (loadAiTuningPage, refreshAiTuningStatus, pollAiTuningMainPanel, aiTuningStart/Cancel/Apply/ClearHistory, aiTuningSaveThresholds, aiToggleMlDataCollection, aiTuningSaveAdvancedConfig) Co-Authored-By: Claude Sonnet 4.6 --- src/charge_mode.cpp | 23 ++- src/html/web_portal.html | 416 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 421 insertions(+), 18 deletions(-) diff --git a/src/charge_mode.cpp b/src/charge_mode.cpp index d6605927..c9e1b6a3 100644 --- a/src/charge_mode.cpp +++ b/src/charge_mode.cpp @@ -689,9 +689,12 @@ bool http_rest_charge_mode_config(struct fs_file *file, int num_params, char *pa // c11 (int): precharge_time_ms // c12 (float): precharge_speed_rps // c13 (float): coarse_stop_gate_ratio + // c14 (uint32): coarse_time_target_ms (AI tuning coarse pre-charge duration) + // c15 (uint32): total_time_target_ms (AI tuning total time target) + // c16 (bool): ml_data_collection_enabled // ee (bool): save to eeprom - static char charge_mode_json_buffer[256]; + static char charge_mode_json_buffer[512]; bool save_to_eeprom = false; // Control @@ -726,6 +729,16 @@ bool http_rest_charge_mode_config(struct fs_file *file, int num_params, char *pa charge_mode_config.eeprom_charge_mode_data.coarse_stop_gate_ratio = strtof(values[idx], NULL); } + // AI tuning related settings + else if (strcmp(params[idx], "c14") == 0) { + charge_mode_config.eeprom_charge_mode_data.coarse_time_target_ms = (uint32_t) strtol(values[idx], NULL, 10); + } + else if (strcmp(params[idx], "c15") == 0) { + charge_mode_config.eeprom_charge_mode_data.total_time_target_ms = (uint32_t) strtol(values[idx], NULL, 10); + } + else if (strcmp(params[idx], "c16") == 0) { + charge_mode_config.eeprom_charge_mode_data.ml_data_collection_enabled = string_to_boolean(values[idx]); + } // LED related settings else if (strcmp(params[idx], "c1") == 0) { @@ -755,7 +768,8 @@ bool http_rest_charge_mode_config(struct fs_file *file, int num_params, char *pa sizeof(charge_mode_json_buffer), "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" "{\"c1\":\"#%06lx\",\"c2\":\"#%06lx\",\"c3\":\"#%06lx\",\"c4\":\"#%06lx\"," - "\"c5\":%.3f,\"c6\":%.3f,\"c7\":%.3f,\"c8\":%.3f,\"c9\":%d,\"c10\":%s,\"c11\":%ld,\"c12\":%0.3f,\"c13\":%0.3f}", + "\"c5\":%.3f,\"c6\":%.3f,\"c7\":%.3f,\"c8\":%.3f,\"c9\":%d,\"c10\":%s,\"c11\":%ld,\"c12\":%0.3f,\"c13\":%0.3f," + "\"c14\":%lu,\"c15\":%lu,\"c16\":%s}", charge_mode_config.eeprom_charge_mode_data.neopixel_normal_charge_colour._raw_colour, charge_mode_config.eeprom_charge_mode_data.neopixel_under_charge_colour._raw_colour, charge_mode_config.eeprom_charge_mode_data.neopixel_over_charge_colour._raw_colour, @@ -768,7 +782,10 @@ bool http_rest_charge_mode_config(struct fs_file *file, int num_params, char *pa boolean_to_string(charge_mode_config.eeprom_charge_mode_data.precharge_enable), charge_mode_config.eeprom_charge_mode_data.precharge_time_ms, charge_mode_config.eeprom_charge_mode_data.precharge_speed_rps, - charge_mode_config.eeprom_charge_mode_data.coarse_stop_gate_ratio); + charge_mode_config.eeprom_charge_mode_data.coarse_stop_gate_ratio, + (unsigned long) charge_mode_config.eeprom_charge_mode_data.coarse_time_target_ms, + (unsigned long) charge_mode_config.eeprom_charge_mode_data.total_time_target_ms, + boolean_to_string(charge_mode_config.eeprom_charge_mode_data.ml_data_collection_enabled)); size_t data_length = strlen(charge_mode_json_buffer); file->data = charge_mode_json_buffer; diff --git a/src/html/web_portal.html b/src/html/web_portal.html index 5178950b..eb004e2b 100644 --- a/src/html/web_portal.html +++ b/src/html/web_portal.html @@ -74,6 +74,38 @@ + + + + + + + + + - @@ -1687,6 +1693,7 @@

Error

const settingsNavButton = document.getElementById("settingsNavButton") const purgeNavButton = document.getElementById("purgeNavButton"); const tricklerNavButton = document.getElementById("tricklerNavButton"); + const aiTuningNavButton = document.getElementById("aiTuningNavButton"); switch (button) { case "trickler": @@ -1701,6 +1708,7 @@

Error

// Button active control settingsNavButton.classList.remove("active"); purgeNavButton.classList.remove("active"); + aiTuningNavButton.classList.remove("active"); tricklerNavButton.classList.add("active"); // Stop AI history poll when leaving settings @@ -1721,6 +1729,7 @@

Error

// Button active control settingsNavButton.classList.remove("active"); tricklerNavButton.classList.remove("active"); + aiTuningNavButton.classList.remove("active"); purgeNavButton.classList.add("active"); // Restart @@ -1739,6 +1748,7 @@

Error

// Button active control tricklerNavButton.classList.remove("active"); purgeNavButton.classList.remove("active"); + aiTuningNavButton.classList.remove("active"); settingsNavButton.classList.add("active"); // Remove polling event @@ -1750,6 +1760,25 @@

Error

// Load the first settings onSettingsLinkClicked("settings-scale"); + break; + case "aiTuning": + sectionTrickler.style.display = "none"; + sectionSettings.style.display = "block"; + + // Button active control + tricklerNavButton.classList.remove("active"); + purgeNavButton.classList.remove("active"); + settingsNavButton.classList.remove("active"); + aiTuningNavButton.classList.add("active"); + + // Remove polling event + clearTimeout(pollSetTimeoutId); + + // Pre-load profile names + loadProfileDropdowns(); + + // Navigate directly to AI Tuning settings page + onSettingsLinkClicked("settings-ai-tuning"); break; default: break; From 8894336d0ad96fc1c0eb5d274168feed2ec577f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Dzi=C4=85dziak?= Date: Sat, 21 Mar 2026 18:12:58 +0100 Subject: [PATCH 28/34] Continuous ML history + stabilization before Remove Cup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ML History: - AI_TUNING_HISTORY_SIZE: 10 → 50 records (continuous recording per profile) - ML suggestion threshold: 10 → 3 drops in web UI - JSON buffer: 2048 → 4096 bytes for larger history response - History rev bumped to 6 Stabilization timing: - Add CHARGE_MODE_STABILIZING (state 5) between charging and cup removal - After motors stop → state=STABILIZING, display shows "Stabilizing..." - After wait completes → state=WAIT_FOR_CUP_REMOVAL, display shows "Remove Cup" - Web UI steps bar shows "Settling..." during state 5, "Remove Cup" after - Both main charge view and AI tuning charge view updated Co-Authored-By: Claude Sonnet 4.6 --- src/ai_tuning.h | 4 ++-- src/charge_mode.cpp | 25 +++++++++++++++++-------- src/charge_mode.h | 1 + src/html/web_portal.html | 39 +++++++++++++++++++++++++++------------ src/rest_ai_tuning.c | 2 +- 5 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/ai_tuning.h b/src/ai_tuning.h index f5d659cf..3dc96846 100644 --- a/src/ai_tuning.h +++ b/src/ai_tuning.h @@ -5,8 +5,8 @@ #include #include "profile.h" -#define AI_TUNING_HISTORY_SIZE 10 -#define AI_TUNING_HISTORY_REV 5 // Bumped: added current_profile_idx for per-profile ML data +#define AI_TUNING_HISTORY_SIZE 50 +#define AI_TUNING_HISTORY_REV 6 // Bumped: increased history buffer to 50 records #define AI_TUNING_DROP_BUF_SIZE 10 #define AI_TUNING_CONFIG_REV 2 // EEPROM config revision (v2: removed coarse_overthrow_max_percent) diff --git a/src/charge_mode.cpp b/src/charge_mode.cpp index 7b448b13..faf6706c 100644 --- a/src/charge_mode.cpp +++ b/src/charge_mode.cpp @@ -135,7 +135,8 @@ void scale_measurement_render_task(void *p) { // Format the timer string based on current state if (charge_mode_config.charge_mode_state == CHARGE_MODE_WAIT_FOR_COMPLETE) { format_elapsed_time(time_buffer, sizeof(time_buffer), charge_start_tick); - } else if (charge_mode_config.charge_mode_state == CHARGE_MODE_WAIT_FOR_CUP_REMOVAL || + } else if (charge_mode_config.charge_mode_state == CHARGE_MODE_STABILIZING || + charge_mode_config.charge_mode_state == CHARGE_MODE_WAIT_FOR_CUP_REMOVAL || charge_mode_config.charge_mode_state == CHARGE_MODE_WAIT_FOR_CUP_RETURN || charge_mode_config.charge_mode_state == CHARGE_MODE_WAIT_FOR_ZERO) { snprintf(time_buffer, sizeof(time_buffer), "%.2f s", last_charge_elapsed_seconds); @@ -543,16 +544,12 @@ void charge_mode_wait_for_complete() { vTaskDelay(pdMS_TO_TICKS(20)); // Wait for other tasks to complete } - charge_mode_config.charge_mode_state = CHARGE_MODE_WAIT_FOR_CUP_REMOVAL; + charge_mode_config.charge_mode_state = CHARGE_MODE_STABILIZING; } -void charge_mode_wait_for_cup_removal() { - // Update current status - snprintf(title_string, sizeof(title_string), "Remove Cup"); - - FloatRingBuffer data_buffer(5); +void charge_mode_stabilize() { + snprintf(title_string, sizeof(title_string), "Stabilizing..."); - // Post charge analysis (while waiting for removal of the cup) // Wait for scale to stabilize after motors stopped if (charge_mode_config.eeprom_charge_mode_data.stabilization_enabled) { // Fixed configured wait @@ -574,6 +571,15 @@ void charge_mode_wait_for_cup_removal() { } } + charge_mode_config.charge_mode_state = CHARGE_MODE_WAIT_FOR_CUP_REMOVAL; +} + +void charge_mode_wait_for_cup_removal() { + // Update current status + snprintf(title_string, sizeof(title_string), "Remove Cup"); + + FloatRingBuffer data_buffer(5); + // Take current measurement (settled reading used for ML overthrow too) float current_measurement = scale_get_current_measurement(); float error = charge_mode_config.target_charge_weight - current_measurement; @@ -791,6 +797,9 @@ uint8_t charge_mode_menu(bool charge_mode_skip_user_input) { case CHARGE_MODE_WAIT_FOR_COMPLETE: charge_mode_wait_for_complete(); break; + case CHARGE_MODE_STABILIZING: + charge_mode_stabilize(); + break; case CHARGE_MODE_WAIT_FOR_CUP_REMOVAL: charge_mode_wait_for_cup_removal(); // If AI tuning just completed, exit after the user removes the cup diff --git a/src/charge_mode.h b/src/charge_mode.h index 4fea0e1d..84f9a8cf 100644 --- a/src/charge_mode.h +++ b/src/charge_mode.h @@ -17,6 +17,7 @@ typedef enum { CHARGE_MODE_WAIT_FOR_COMPLETE = 2, CHARGE_MODE_WAIT_FOR_CUP_REMOVAL = 3, CHARGE_MODE_WAIT_FOR_CUP_RETURN = 4, + CHARGE_MODE_STABILIZING = 5, } charge_mode_state_t; typedef struct { diff --git a/src/html/web_portal.html b/src/html/web_portal.html index 970b2a2e..18b0121d 100644 --- a/src/html/web_portal.html +++ b/src/html/web_portal.html @@ -1252,13 +1252,14 @@

Error