From a1a8313525bcb63ce318e435b0429a65addbb969 Mon Sep 17 00:00:00 2001 From: Thaddeus Hughes Date: Sat, 17 Jan 2026 13:33:57 -0600 Subject: [PATCH] logging testing. logging works. e-fusing algo works right for jack. jack timing works. --- main/CMakeLists.txt | 2 +- main/comms.c | 25 +- main/control_fsm.c | 619 ++++++++++++----------- main/control_fsm.h | 22 +- main/i2c.c | 36 +- main/i2c.h | 44 +- main/log_test.c | 1180 +++++++++++++++++++++++++++++++++++++++++++ main/log_test.h | 47 ++ main/main.c | 86 ++-- main/power_mgmt.c | 231 +++++---- main/power_mgmt.h | 23 +- main/rf_433.c | 10 +- main/sensors.c | 137 +++-- main/sensors.h | 3 +- main/storage.c | 511 ++++++++++++++++--- main/storage.h | 20 +- main/webpage.h | 2 +- main/webpage_gzip.h | 2 +- main/webserver.c | 23 +- partitions.csv | 4 +- 20 files changed, 2376 insertions(+), 651 deletions(-) create mode 100644 main/log_test.c create mode 100644 main/log_test.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index a08fc71..900c214 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -4,7 +4,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/version.cmake) idf_component_register( - SRCS main.c i2c.c rtc.c storage.c uart_comms.c control_fsm.c power_mgmt.c rf_433.c rtc.c sensors.c solar.c webserver.c simple_dns_server.c comms.c # list the source files of this component + SRCS main.c log_test.c i2c.c rtc.c storage.c uart_comms.c control_fsm.c power_mgmt.c rf_433.c rtc.c sensors.c solar.c webserver.c simple_dns_server.c comms.c # list the source files of this component INCLUDE_DIRS "." "${CMAKE_BINARY_DIR}" PRIV_INCLUDE_DIRS # optional, add here private include directories diff --git a/main/comms.c b/main/comms.c index d2f46b1..49f9778 100644 --- a/main/comms.c +++ b/main/comms.c @@ -19,7 +19,7 @@ static const char *TAG = "COMMS"; * Build a JSON object containing complete system status */ cJSON* comms_handle_get(void) { - ESP_LOGI(TAG, "GET request"); + //ESP_LOGI(TAG, "GET request"); rtc_reset_shutdown_timer(); @@ -52,7 +52,6 @@ cJSON* comms_handle_get(void) { case STATE_IDLE: cJSON_AddItemToArray(msg_array, cJSON_CreateString("IDLE")); break; - case STATE_UNDO_JACK: case STATE_UNDO_JACK_START: cJSON_AddItemToArray(msg_array, cJSON_CreateString("CANCELLING MOVE")); break; @@ -65,13 +64,13 @@ cJSON* comms_handle_get(void) { if (fsm_get_remaining_distance() <= 0) { cJSON_AddItemToArray(msg_array, cJSON_CreateString("DISTANCE LIMIT HIT")); } - if (efuse_is_tripped(BRIDGE_AUX)) { + if (efuse_get(BRIDGE_AUX)) { cJSON_AddItemToArray(msg_array, cJSON_CreateString("AUX EFUSE TRIP")); } - if (efuse_is_tripped(BRIDGE_JACK)) { + if (efuse_get(BRIDGE_JACK)) { cJSON_AddItemToArray(msg_array, cJSON_CreateString("JACK EFUSE TRIP")); } - if (efuse_is_tripped(BRIDGE_DRIVE)) { + if (efuse_get(BRIDGE_DRIVE)) { cJSON_AddItemToArray(msg_array, cJSON_CreateString("DRIVE EFUSE TRIP")); } if (!rtc_is_set()) { @@ -135,7 +134,7 @@ cJSON* comms_handle_get(void) { * Process a POST request with JSON data */ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) { - ESP_LOGI(TAG, "POST request"); + //ESP_LOGI(TAG, "POST request"); if (root == NULL || response_json == NULL) { ESP_LOGE(TAG, "Invalid arguments to comms_handle_post"); @@ -171,7 +170,7 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) { cJSON *cmd = cJSON_GetObjectItem(root, "cmd"); if (cJSON_IsString(cmd)) { const char *cmd_str = cmd->valuestring; - ESP_LOGI(TAG, "Executing command: %s", cmd_str); + // ESP_LOGI(TAG, "Executing command: %s", cmd_str); if (strcmp(cmd_str, "start") == 0) { fsm_request(FSM_CMD_START); @@ -186,25 +185,23 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) { cmd_executed = true; } else if (strcmp(cmd_str, "fwd") == 0) { - pulseOverride(RELAY_A1); - pulseOverride(RELAY_A3); + pulseOverride(FSM_OVERRIDE_DRIVE_FWD); cmd_executed = true; } else if (strcmp(cmd_str, "rev") == 0) { - pulseOverride(RELAY_B1); - pulseOverride(RELAY_A3); + pulseOverride(FSM_OVERRIDE_DRIVE_REV); cmd_executed = true; } else if (strcmp(cmd_str, "up") == 0) { - pulseOverride(RELAY_A2); + pulseOverride(FSM_OVERRIDE_JACK_UP); cmd_executed = true; } else if (strcmp(cmd_str, "down") == 0) { - pulseOverride(RELAY_B2); + pulseOverride(FSM_OVERRIDE_JACK_DOWN); cmd_executed = true; } else if (strcmp(cmd_str, "aux") == 0) { - pulseOverride(RELAY_A3); + pulseOverride(FSM_OVERRIDE_AUX); cmd_executed = true; } else if (strcmp(cmd_str, "reboot") == 0) { diff --git a/main/control_fsm.c b/main/control_fsm.c index c6b33b0..8c9e53c 100644 --- a/main/control_fsm.c +++ b/main/control_fsm.c @@ -17,6 +17,7 @@ #include "rtc.h" #include "sensors.h" #include "esp_log.h" +#include #include #define TRANSITION_DELAY_US 1000000 @@ -28,24 +29,14 @@ static QueueHandle_t fsm_cmd_queue = NULL; -RTC_DATA_ATTR esp_err_t error = ESP_OK; -esp_err_t fsm_get_error() { return error; } -void fsm_clear_error() { error = ESP_OK; } +RTC_DATA_ATTR esp_err_t fsm_error = ESP_OK; +esp_err_t fsm_get_error() { return fsm_error; } +void fsm_clear_error() { fsm_error = ESP_OK; } -// map from relay number to bridge -bridge_t bridge_map[] = { - BRIDGE_AUX, - BRIDGE_AUX, - BRIDGE_AUX, - BRIDGE_AUX, - BRIDGE_JACK, - BRIDGE_JACK, - BRIDGE_DRIVE, - BRIDGE_DRIVE }; -bool relay_states[8] = {false}; -int64_t override_times[8] = {-1}; -int64_t override_cooldown[8] = {-1}; +int64_t override_time = -1; +fsm_override_t override_cmd; +//int64_t override_cooldown[8] = {-1}; bool enabled = false; float this_move_dist = 0.0f; @@ -57,40 +48,14 @@ void fsm_set_remaining_distance(float x) { remaining_distance = x;} static int32_t move_start_encoder = 0; // Track total jack up time to use for jack down duration -static int64_t jack_up_total_time = 0; +static int64_t jack_start_us = 0; +static int64_t jack_trans_us = 0; +static int64_t jack_finish_us = 0; volatile fsm_state_t current_state = STATE_IDLE; -volatile int64_t current_time = 0; +volatile int64_t fsm_now = 0; volatile bool start_running_request = false; -void setRelay(int8_t relay, bool state) { - relay_states[relay] = state; -} - -bool isRunning() { - for (int i=0;i<8;i++) { - if (relay_states[i]) return true; - } - return false; -} - -void driveRelays() { - uint8_t state = 0x00; - //relay_states[0] = (current_time / 1000000) % 2; // for testing purposes - - for (uint8_t i=0; i<8; i++) { - // if we command and efuse permits it set the relay - if (relay_states[i] && !efuse_is_tripped(bridge_map[i])) { - state |= 0x01<= timer_end; } +static inline bool timer_done() { return fsm_now >= timer_end; } -void pulseOverride(relay_t relay) { +void pulseOverride(fsm_override_t cmd) { if (current_state == STATE_IDLE) { - // Check if this relay is in cooldown - if (override_cooldown[relay] > current_time) { - // Still cooling down, ignore the command - return; - } - override_times[relay] = current_time + get_param_value_t(PARAM_RF_PULSE_LENGTH).u32; + override_cmd = cmd; + override_time = fsm_now + get_param_value_t(PARAM_RF_PULSE_LENGTH).u32; } } -/*void fsm_begin_auto_move() { - if (current_state == STATE_IDLE) - current_state = STATE_MOVE_START_DELAY; - set_timer(TRANSITION_DELAY_US); -}*/ - int64_t fsm_cal_t, fsm_cal_e; float fsm_cal_val; void fsm_set_cal_val(float v) {fsm_cal_val = v;} @@ -143,9 +98,8 @@ int8_t fsm_get_current_progress(int8_t denominator) { case STATE_MOVE_START_DELAY: case STATE_DRIVE_START_DELAY: case STATE_DRIVE_END_DELAY: - case STATE_UNDO_JACK: if (timer_end != timer_start) - x = (current_time-timer_start)*denominator/(timer_end-timer_start); + x = (fsm_now-timer_start)*denominator/(timer_end-timer_start); break; case STATE_UNDO_JACK_START: x = 0; @@ -160,9 +114,56 @@ int8_t fsm_get_current_progress(int8_t denominator) { #define JACK_TIME get_param_value_t(PARAM_JACK_KT).f32 * get_param_value_t(PARAM_JACK_DIST ).f32 +#define JACK_DOWN_TIME (jack_finish_us - jack_start_us) * 105/100 #define DRIVE_TIME get_param_value_t(PARAM_DRIVE_KT).f32 * this_move_dist #define DRIVE_DIST get_param_value_t(PARAM_DRIVE_KE).f32 * this_move_dist +int64_t last_log_time = 0; +#define LOGSIZE 39 +esp_err_t send_fsm_log() { + if(!rtc_is_set()) return ESP_OK; + + uint8_t entry[LOGSIZE] = {}; + + // Pack 64-bit timestamp into bytes 1-8 + uint64_t be_timestamp = rtc_get_ms(); + memcpy(&entry[0], &be_timestamp, 8); + + // Pack 32-bit voltages/currents into bytes 9-24 + float be_voltage = get_battery_V(); + memcpy(&entry[8], &be_voltage, 4); + float be_current1 = get_bridge_raw_A(BRIDGE_DRIVE); + memcpy(&entry[12], &be_current1, 4); + float be_current2 = get_bridge_raw_A(BRIDGE_JACK); + memcpy(&entry[16], &be_current2, 4); + float be_current3 = get_bridge_raw_A(BRIDGE_AUX); + memcpy(&entry[20], &be_current3, 4); + + int16_t be_counter = get_sensor_counter(SENSOR_DRIVE); + memcpy(&entry[24], &be_counter, 2); + + entry[26] = pack_sensors(); + + + + + float heat1 = efuse_get_heat(BRIDGE_DRIVE); + memcpy(&entry[27], &heat1, 4); + float heat2 = efuse_get_heat(BRIDGE_JACK); + memcpy(&entry[31], &heat2, 4); + float heat3 = efuse_get_heat(BRIDGE_AUX); + memcpy(&entry[35], &heat3, 4); + + last_log_time = esp_timer_get_time(); + + + log_write(entry, LOGSIZE, fsm_get_state()); + + //ESP_LOGI(TAG, "WROTE LOG; %lld / %ld/%ld; %5.2f %5.2f %5.2f", (long long)rtc_get_ms(), (unsigned long)log_get_tail(), (unsigned long)log_get_head(), heat1, heat2, heat3); + + return ESP_OK; +} + void control_task(void *param) { esp_task_wdt_add(NULL); @@ -170,11 +171,22 @@ void control_task(void *param) { const TickType_t xFrequency = pdMS_TO_TICKS(20); enabled = true; + sensors_init(); while (enabled) { vTaskDelayUntil(&xLastWakeTime, xFrequency); - current_time = esp_timer_get_time(); + fsm_now = esp_timer_get_time(); + bool log = false; + + /**** READ INPUTS ****/ + for (uint8_t i = 0; i < N_BRIDGES; i++) { + process_bridge_current(i); + } + process_battery_voltage(); + sensors_check(); + + /**** LISTEN TO COMMANDS ****/ fsm_cmd_t cmd; while (xQueueReceive(fsm_cmd_queue, &cmd, 0) == pdTRUE) { // if (error != ESP_OK) continue; // don't do anything until error is cleared @@ -184,7 +196,8 @@ void control_task(void *param) { // Check if we have remaining distance before starting if (remaining_distance <= 0.0f) { ESP_LOGI(TAG, "FAILED TO START; NO REMAINING DISTANCE"); - error = SC_ERR_LEASH_HIT; + fsm_error = SC_ERR_LEASH_HIT; + log = true; continue; } this_move_dist = MIN(get_param_value_t(PARAM_DRIVE_DIST).f32, remaining_distance); @@ -194,33 +207,34 @@ void control_task(void *param) { if (get_battery_V() < get_param_value_t(PARAM_LOW_PROTECTION_V).f32) { ESP_LOGI(TAG, "FAILED TO START; INSUFFICIENT VOLTAGE"); - error = SC_ERR_LOW_BATTERY; + fsm_error = SC_ERR_LOW_BATTERY; continue; } if (!get_is_safe()) { ESP_LOGI(TAG, "FAILED TO START; SAFETY NOT SET"); - error = SC_ERR_SAFETY_TRIP; + fsm_error = SC_ERR_SAFETY_TRIP; continue; } - if (efuse_is_tripped(BRIDGE_DRIVE)) { + if (efuse_get(BRIDGE_DRIVE)) { ESP_LOGI(TAG, "FAILED TO START; EFUSE 1 TRIP"); - error = SC_ERR_EFUSE_TRIP_1; + fsm_error = SC_ERR_EFUSE_TRIP_1; continue; } - if (efuse_is_tripped(BRIDGE_JACK)) { + if (efuse_get(BRIDGE_JACK)) { ESP_LOGI(TAG, "FAILED TO START; EFUSE 2 TRIP"); - error = SC_ERR_EFUSE_TRIP_2; + fsm_error = SC_ERR_EFUSE_TRIP_2; continue; } - if (efuse_is_tripped(BRIDGE_AUX)) { + if (efuse_get(BRIDGE_AUX)) { ESP_LOGI(TAG, "FAILED TO START; EFUSE 3 TRIP"); - error = SC_ERR_EFUSE_TRIP_3; + fsm_error = SC_ERR_EFUSE_TRIP_3; continue; } ESP_LOGI(TAG, "STARTING"); - error = ESP_OK; // if everything is OK now, we're OK. + fsm_error = ESP_OK; // if everything is OK now, we're OK. current_state = STATE_MOVE_START_DELAY; + log = true; set_timer(TRANSITION_DELAY_US); } break; @@ -229,9 +243,9 @@ void control_task(void *param) { break; case FSM_CMD_UNDO: if (current_state != STATE_IDLE && - current_state != STATE_UNDO_JACK_START && - current_state != STATE_UNDO_JACK) { + current_state != STATE_UNDO_JACK_START) { current_state = STATE_UNDO_JACK_START; + log = true; } break; case FSM_CMD_SHUTDOWN: @@ -243,6 +257,7 @@ void control_task(void *param) { if (current_state == STATE_IDLE && get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) { current_state = STATE_CALIBRATE_JACK_DELAY; + log = true; } break; @@ -251,14 +266,16 @@ void control_task(void *param) { if (current_state == STATE_CALIBRATE_JACK_DELAY && get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) { current_state = STATE_CALIBRATE_JACK_MOVE; + log = true; set_timer(CALIBRATE_JACK_MAX_TIME); } break; case FSM_CMD_CALIBRATE_JACK_END: ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_END"); if (current_state == STATE_CALIBRATE_JACK_MOVE) { - fsm_cal_t = current_time - timer_start; + fsm_cal_t = fsm_now - timer_start; current_state = STATE_IDLE; + log = true; } break; case FSM_CMD_CALIBRATE_JACK_FINISH: @@ -274,6 +291,7 @@ void control_task(void *param) { if (current_state == STATE_IDLE && get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) { current_state = STATE_CALIBRATE_DRIVE_DELAY; + log = true; } break; @@ -282,6 +300,7 @@ void control_task(void *param) { if (current_state == STATE_CALIBRATE_DRIVE_DELAY && get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) { current_state = STATE_CALIBRATE_DRIVE_MOVE; + log = true; set_timer(CALIBRATE_DRIVE_MAX_TIME); set_sensor_counter(SENSOR_DRIVE, 0); } @@ -289,9 +308,10 @@ void control_task(void *param) { case FSM_CMD_CALIBRATE_DRIVE_END: ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_END"); if (current_state == STATE_CALIBRATE_DRIVE_MOVE) { - fsm_cal_t = current_time - timer_start; + fsm_cal_t = fsm_now - timer_start; fsm_cal_e = get_sensor_counter(SENSOR_DRIVE); current_state = STATE_IDLE; + log = true; } break; case FSM_CMD_CALIBRATE_DRIVE_FINISH: @@ -307,115 +327,81 @@ void control_task(void *param) { } if (!enabled) break; - - - // State transitions + /**** STATE TRANSITIONS ****/ switch (current_state) { case STATE_IDLE: - //ESP_LOGI("FSM", "IDLE @ %lld", current_time); - for (uint8_t i = 0; i < N_RELAYS; ++i) { - //ESP_LOGI("FSM", "t[%d] %lld", i, override_times[i]); - bool active = override_times[i] > current_time; - if (active) rtc_reset_shutdown_timer(); - - // Current limiting for manual jack down override (RELAY_B2) - if (i == RELAY_B2 && active) { - int64_t elapsed = current_time - (override_times[i] - get_param_value_t(PARAM_RF_PULSE_LENGTH).u32); - int64_t delay = get_param_value_t(PARAM_EFUSE_INRUSH_US).u32; - - // After inrush delay, check for current spike - if (elapsed > delay) { - float current = get_bridge_A(BRIDGE_JACK); - float threshold = get_param_value_t(PARAM_JACK_I_DOWN).f32; - - if (current > threshold) { - // Current spike detected - stop jacking down and start cooldown - override_times[i] = -1; - override_cooldown[i] = current_time + get_param_value_t(PARAM_EFUSE_TCOOL).u32; - active = false; - } - } - } - - // prohibit movement past jack limit switch - //if (i == BRIDGE_JACK*2+(bridge_polarities[BRIDGE_JACK]>0?0:1) && get_sensor(SENSOR_JACK)) - // setRelay(i, false); - //else - setRelay(i, active); - //if (active) ESP_LOGI("FSM", "RUN CHANNEL %d (%lld %c %lld)", i, (long long) override_times[i], active ? '>':'<', (long long) current_time); - - } break; case STATE_MOVE_START_DELAY: if (!get_is_safe()) { - error = SC_ERR_SAFETY_TRIP; + fsm_error = SC_ERR_SAFETY_TRIP; current_state = STATE_IDLE; - } - if (timer_done()) { + log = true; + } else if (timer_done()) { current_state = STATE_JACK_UP_START; set_timer(JACK_TIME / 2); // First phase is half of total jack time - jack_up_total_time = 0; // Reset jack up time tracker + jack_start_us = fsm_now; } break; case STATE_JACK_UP_START: if (!get_is_safe()) { - error = SC_ERR_SAFETY_TRIP; + fsm_error = SC_ERR_SAFETY_TRIP; current_state = STATE_UNDO_JACK_START; - } - - { - // Track elapsed time - int64_t elapsed = current_time - timer_start; - jack_up_total_time = elapsed; + jack_finish_us = fsm_now; + log = true; + } else { + + if (efuse_get(BRIDGE_JACK)) { + ESP_LOGI(TAG, "START->UP BY EFUSE"); + current_state = STATE_JACK_UP; + jack_trans_us = fsm_now; + log = true; + set_timer(JACK_TIME); + } - // Get current sensing parameters - int64_t delay = get_param_value_t(PARAM_EFUSE_INRUSH_US).u32; - float current = get_bridge_A(BRIDGE_JACK); - float threshold = get_param_value_t(PARAM_JACK_I_UP).f32; - - // After inrush delay, check for current spike OR half-time timeout - if (elapsed > delay) { - if (current > threshold || timer_done()) { - ESP_LOGI(TAG, "START->UP BY CURRENT"); - current_state = STATE_JACK_UP; - set_timer(JACK_TIME); // Second phase is also half of total jack time - } + if (get_bridge_overcurrent(BRIDGE_JACK, get_param_value_t(PARAM_JACK_I_UP).f32)) { + ESP_LOGI(TAG, "START->UP BY CURRENT"); + current_state = STATE_JACK_UP; + jack_trans_us = fsm_now; + log = true; + set_timer(JACK_TIME); } - // E-fuse trip - if (efuse_is_tripped(BRIDGE_JACK)) { - error = SC_ERR_EFUSE_TRIP_2; - current_state = STATE_IDLE; + if (timer_done()) { + ESP_LOGI(TAG, "START->UP BY TIME"); + current_state = STATE_JACK_UP; + jack_trans_us = fsm_now; + log = true; + set_timer(JACK_TIME); } } break; case STATE_JACK_UP: if (!get_is_safe()) { - error = SC_ERR_SAFETY_TRIP; + fsm_error = SC_ERR_SAFETY_TRIP; current_state = STATE_UNDO_JACK_START; - } - - { - if (timer_done() || efuse_is_tripped(BRIDGE_JACK)) { + jack_finish_us = fsm_now; + set_timer(JACK_DOWN_TIME); + log = true; + } else { + if (timer_done() || efuse_get(BRIDGE_JACK)) { // Track total time including first phase - jack_up_total_time += current_time - timer_start; - current_state = STATE_DRIVE_START_DELAY; + current_state = STATE_DRIVE_START_DELAY; + jack_finish_us = fsm_now; + log = true; set_timer(TRANSITION_DELAY_US); } - if (efuse_is_tripped(BRIDGE_JACK)) { - error = SC_ERR_EFUSE_TRIP_2; - current_state = STATE_IDLE; - } } break; case STATE_DRIVE_START_DELAY: if (!get_is_safe()) { - error = SC_ERR_SAFETY_TRIP; + fsm_error = SC_ERR_SAFETY_TRIP; current_state = STATE_UNDO_JACK_START; - } - if (timer_done()) { + set_timer(JACK_DOWN_TIME); + log = true; + } else if (timer_done()) { current_state = STATE_DRIVE; + log = true; set_timer(DRIVE_TIME); // Set the encoder counter to track remaining distance in this move set_sensor_counter(SENSOR_DRIVE, -DRIVE_DIST); @@ -425,10 +411,11 @@ void control_task(void *param) { break; case STATE_DRIVE: if (!get_is_safe()) { - error = SC_ERR_SAFETY_TRIP; + fsm_error = SC_ERR_SAFETY_TRIP; current_state = STATE_UNDO_JACK_START; - } - { + set_timer(JACK_DOWN_TIME); + log = true; + } else { int32_t current_encoder = get_sensor_counter(SENSOR_DRIVE); int32_t ticks_traveled = current_encoder - move_start_encoder; float ke = get_param_value_t(PARAM_DRIVE_KE).f32; @@ -443,81 +430,80 @@ void control_task(void *param) { // remaining_distance -= distance_traveled; current_state = STATE_DRIVE_END_DELAY; + log = true; set_timer(TRANSITION_DELAY_US); } - if (efuse_is_tripped(BRIDGE_DRIVE)) { + if (efuse_get(BRIDGE_DRIVE)) { // Update remaining distance even on fault remaining_distance -= distance_traveled; if (remaining_distance < 0.0f) remaining_distance = 0.0f; - error = SC_ERR_EFUSE_TRIP_1; + fsm_error = SC_ERR_EFUSE_TRIP_1; current_state = STATE_UNDO_JACK_START; + set_timer(JACK_DOWN_TIME); + log = true; } } break; case STATE_DRIVE_END_DELAY: if (!get_is_safe()) { - error = SC_ERR_SAFETY_TRIP; + fsm_error = SC_ERR_SAFETY_TRIP; current_state = STATE_UNDO_JACK_START; - } - if (timer_done()) { - current_state = STATE_JACK_DOWN; - set_timer(jack_up_total_time); // Use the tracked jack up time + log = true; + } else if (timer_done()) { + current_state = STATE_UNDO_JACK_START; + log = true; } break; case STATE_JACK_DOWN: - if (!get_is_safe()) { - error = SC_ERR_SAFETY_TRIP; - current_state = STATE_UNDO_JACK_START; - } - { - // Get current sensing parameters - int64_t delay = get_param_value_t(PARAM_EFUSE_INRUSH_US).u32; - int64_t elapsed = current_time - timer_start; - - // After inrush delay, check for current spike - if (elapsed > delay) { - float current = get_bridge_A(BRIDGE_JACK); - float threshold = get_param_value_t(PARAM_JACK_I_DOWN).f32; - - if (current > threshold) { - ESP_LOGI(TAG, "DOWN->IDLE BY CURRENT"); - // Current spike detected - we've hit the ground - current_state = STATE_IDLE; - break; - } - } + + if (efuse_get(BRIDGE_JACK)) { - // Timeout - finished jacking down - if (timer_done()) { - ESP_LOGI(TAG, "DOWN->IDLE BY TIME"); - current_state = STATE_IDLE; - } + ESP_LOGI(TAG, "DOWN->IDLE BY EFUSE"); + // Current spike detected + current_state = STATE_IDLE; + log = true; + break; - // E-fuse trip - assume we hit something hard - if (efuse_is_tripped(BRIDGE_JACK)) { - current_state = STATE_IDLE; - } } + + if (get_bridge_overcurrent(BRIDGE_JACK, get_param_value_t(PARAM_JACK_I_DOWN).f32)) { + + ESP_LOGI(TAG, "DOWN->IDLE BY OVERCURRENT"); + // Current spike detected + current_state = STATE_IDLE; + log = true; + break; + + } + + if (get_bridge_spike(BRIDGE_JACK, get_param_value_t(PARAM_JACK_IS_DOWN).f32)) { + + ESP_LOGI(TAG, "DOWN->IDLE BY SPIKE"); + // Current spike detected + current_state = STATE_IDLE; + log = true; + break; + + } + + if (timer_done() ) { + ESP_LOGI(TAG, "DOWN->IDLE BY TIME"); + current_state = STATE_IDLE; + log = true; + break; + } + break; case STATE_UNDO_JACK_START: // wait for e-fuse to un-trip - if (!efuse_is_tripped(BRIDGE_JACK)) { - current_state = STATE_UNDO_JACK; - set_timer(JACK_TIME); - } - break; - case STATE_UNDO_JACK: - if (timer_done()){ // || get_sensor(SENSOR_JACK)) { - current_state = STATE_IDLE; - } - - // assume we are jacked up all the way (e.g. sensor broke) and should stop - if (efuse_is_tripped(BRIDGE_JACK)) { - current_state = STATE_IDLE; + if (!efuse_get(BRIDGE_JACK)) { + set_timer(JACK_DOWN_TIME); + current_state = STATE_JACK_DOWN; + log = true; } break; @@ -528,7 +514,7 @@ void control_task(void *param) { case STATE_CALIBRATE_JACK_MOVE: if (timer_done()) { current_state = STATE_IDLE; - fsm_cal_t = current_time - timer_start; + fsm_cal_t = fsm_now - timer_start; } break; @@ -539,7 +525,7 @@ void control_task(void *param) { case STATE_CALIBRATE_DRIVE_MOVE: if (!get_is_safe() || timer_done()) { current_state = STATE_IDLE; - fsm_cal_t = current_time - timer_start; + fsm_cal_t = fsm_now - timer_start; fsm_cal_e = get_sensor_counter(SENSOR_DRIVE); } break; @@ -547,110 +533,169 @@ void control_task(void *param) { default: break; } - - //int64_t elapsed_t = (current_time-timer_start); - //int64_t total_t = (timer_end-timer_start); - //int32_t ticks = get_sensor_counter(SENSOR_DRIVE); - //ESP_LOGI("FSM", "[%d] %lld / %lld ms, %ld ticks", current_state, (long long) elapsed_t, (long long) total_t, (long) ticks); - - // Output control + /**** SET OUTPUTS ****/ switch (current_state) { case STATE_IDLE: - //ESP_LOGI("FSM", "IDLE @ %lld", current_time); - for (uint8_t i = 0; i < N_RELAYS; ++i) { - //ESP_LOGI("FSM", "t[%d] %lld", i, override_times[i]); - bool active = override_times[i] > current_time; - if (active) rtc_reset_shutdown_timer(); - - // Current limiting for manual jack down override (RELAY_B2) - if (i == RELAY_B2 && active) { - int64_t elapsed = current_time - (override_times[i] - get_param_value_t(PARAM_RF_PULSE_LENGTH).u32); - int64_t delay = get_param_value_t(PARAM_EFUSE_INRUSH_US).u32; - - // After inrush delay, check for current spike - if (elapsed > delay) { - float current = get_bridge_A(BRIDGE_JACK); - float threshold = get_param_value_t(PARAM_JACK_I_DOWN).f32; - - if (current > threshold) { - // Current spike detected - stop jacking down - override_times[i] = -1; - active = false; - } - } - } - - // prohibit movement past jack limit switch - //if (i == BRIDGE_JACK*2+(bridge_polarities[BRIDGE_JACK]>0?0:1) && get_sensor(SENSOR_JACK)) - // setRelay(i, false); - //else - setRelay(i, active); - //if (active) ESP_LOGI("FSM", "RUN CHANNEL %d (%lld %c %lld)", i, (long long) override_times[i], active ? '>':'<', (long long) current_time); - - } + // In idle we still accept override commands + if (override_time > fsm_now) { + switch(override_cmd) { + case FSM_OVERRIDE_DRIVE_FWD: + if (efuse_get(BRIDGE_DRIVE)){ + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_OFF + }}); + } else { + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_FWD, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_FWD + }}); + } + break; + + case FSM_OVERRIDE_DRIVE_REV: + if (efuse_get(BRIDGE_DRIVE)){ + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_OFF + }}); + } else { + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_REV, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_OFF + }}); + } + break; + case FSM_OVERRIDE_JACK_UP: + if (efuse_get(BRIDGE_JACK)){ + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_OFF + }}); + } else { + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_FWD, + .AUX=BRIDGE_OFF + }}); + } + break; + case FSM_OVERRIDE_JACK_DOWN: + if (get_bridge_overcurrent(BRIDGE_JACK, get_param_value_t(PARAM_JACK_I_DOWN).f32) || + get_bridge_spike(BRIDGE_JACK, get_param_value_t(PARAM_JACK_IS_DOWN).f32)) + efuse_set(BRIDGE_JACK, EFUSE_OVERCURRENT); + + if (efuse_get(BRIDGE_JACK)) { + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_OFF + }}); + } else { + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_REV, + .AUX=BRIDGE_OFF + }}); + } + break; + case FSM_OVERRIDE_AUX: + if (efuse_get(BRIDGE_AUX)){ + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_OFF + }}); + } else { + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_FWD + }}); + } + break; + default: // should never hit here but just in case... + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_OFF + }}); + break; + } + rtc_reset_shutdown_timer(); + log = true; + } else { + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_OFF + }}); + } break; case STATE_CALIBRATE_JACK_MOVE: case STATE_JACK_UP_START: case STATE_JACK_UP: // jack up and fluff - setRelay(RELAY_A1, false); - setRelay(RELAY_B1, false); - - setRelay(RELAY_A2, true); - setRelay(RELAY_B2, false); - - setRelay(RELAY_A3, true); + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_FWD, + .AUX=BRIDGE_FWD + }}); rtc_reset_shutdown_timer(); + log = true; break; case STATE_CALIBRATE_DRIVE_MOVE: case STATE_DRIVE: // drive and fluff - setRelay(RELAY_A1, true); - setRelay(RELAY_B1, false); - - setRelay(RELAY_A2, false); - setRelay(RELAY_B2, false); - - setRelay(RELAY_A3, true); + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_FWD, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_FWD + }}); rtc_reset_shutdown_timer(); + log = true; break; - case STATE_UNDO_JACK: case STATE_JACK_DOWN: - // jack down and fluffer - setRelay(RELAY_A1, false); - setRelay(RELAY_B1, false); - - setRelay(RELAY_A2, false); - setRelay(RELAY_B2, true); - - setRelay(RELAY_A3, true); + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_REV, + .AUX=BRIDGE_OFF + }}); rtc_reset_shutdown_timer(); + log = true; break; case STATE_UNDO_JACK_START: case STATE_DRIVE_START_DELAY: case STATE_DRIVE_END_DELAY: // only fluffer - setRelay(RELAY_A1, false); - setRelay(RELAY_B1, false); - - setRelay(RELAY_A2, false); - setRelay(RELAY_B2, false); - - setRelay(RELAY_A3, true); + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_FWD + }}); rtc_reset_shutdown_timer(); + log = true; break; case STATE_CALIBRATE_JACK_DELAY: default: // invalid state; turn all relays off - setRelay(RELAY_A1, false); - setRelay(RELAY_B1, false); - setRelay(RELAY_A2, false); - setRelay(RELAY_B2, false); - setRelay(RELAY_A3, false); + driveRelays((relay_port_t){.bridges = { + .DRIVE=BRIDGE_OFF, + .JACK=BRIDGE_OFF, + .AUX=BRIDGE_OFF + }}); break; } + + + /**** LOGGING ****/ + if (log) send_fsm_log(); - driveRelays(); esp_task_wdt_reset(); } @@ -665,7 +710,7 @@ esp_err_t fsm_init() { if (fsm_cmd_queue == NULL) { fsm_cmd_queue = xQueueCreate(8, sizeof(fsm_cmd_t)); } - xTaskCreate(control_task, "FSM", 4096, NULL, 5, NULL); + xTaskCreate(control_task, TAG, 4096, NULL, 10, NULL); return ESP_OK; } diff --git a/main/control_fsm.h b/main/control_fsm.h index c8182e0..c153688 100644 --- a/main/control_fsm.h +++ b/main/control_fsm.h @@ -34,7 +34,6 @@ typedef enum { STATE_DRIVE, STATE_DRIVE_END_DELAY, STATE_JACK_DOWN, - STATE_UNDO_JACK, STATE_UNDO_JACK_START, STATE_CALIBRATE_JACK_DELAY, @@ -43,6 +42,7 @@ typedef enum { STATE_CALIBRATE_DRIVE_DELAY, STATE_CALIBRATE_DRIVE_MOVE } fsm_state_t; +#define LOG_TYPE_BAT 100 typedef enum { RELAY_SENSORS = 0, @@ -59,18 +59,32 @@ typedef enum { BRIDGE_AUX = 2, BRIDGE_JACK = 1, BRIDGE_DRIVE = 0, + NUM_BRIDGES = 3, } bridge_t; +typedef enum { + BRIDGE_FWD = 0b10, + BRIDGE_REV = 0b01, + BRIDGE_OFF = 0b00, + BRIDGE_ON = 0b11 +} bridge_dir_t; + +typedef enum { + FSM_OVERRIDE_DRIVE_FWD, + FSM_OVERRIDE_DRIVE_REV, + FSM_OVERRIDE_JACK_UP, + FSM_OVERRIDE_JACK_DOWN, + FSM_OVERRIDE_AUX +} fsm_override_t; + #define N_RELAYS 8 #define N_BRIDGES 3 -void pulseOverride(relay_t relay/*, int64_t pulse*/); +void pulseOverride(fsm_override_t cmd); esp_err_t fsm_init(); esp_err_t fsm_stop(); -bool isRunning(); - void fsm_set_cal_val(float v); int64_t fsm_get_cal_t(); int64_t fsm_get_cal_e(); diff --git a/main/i2c.c b/main/i2c.c index 0bb89a7..6aa900d 100644 --- a/main/i2c.c +++ b/main/i2c.c @@ -8,30 +8,6 @@ #include "esp_rom_sys.h" #include "sensors.h" -#define I2C_PORT I2C_NUM_0 -#define TCA_ADDR_READ 0x21 -#define TCA_ADDR_WRITE 0x21 -#define I2C_PULLUP GPIO_PULLUP_DISABLE -#define I2C_FREQUENCY 400000 - -// TCA9555 Registers -#define TCA_REG_INPUT0 0x00 -#define TCA_REG_INPUT1 0x01 -#define TCA_REG_OUTPUT0 0x02 -#define TCA_REG_OUTPUT1 0x03 -#define TCA_REG_POLARITY0 0x04 -#define TCA_REG_POLARITY1 0x05 -#define TCA_REG_CONFIG0 0x06 -#define TCA_REG_CONFIG1 0x07 - -// Debounce & Repeat Settings -#define DEBOUNCE_MS 50 -#define REPEAT_MS 200 -#define REPEAT_START_MS 700 - -#define SAFETY_MASK 0b00110001 // & permissible channels (jack and sensors) -#define SENSOR_EN_MASK 0b00000001 // | need forced high - // Static Variables static bool i2c_initted = false; //static bool safety_ok = false; // Safety interlock @@ -76,16 +52,8 @@ esp_err_t i2c_init(void) { return ESP_OK; } -esp_err_t i2c_set_relays(uint8_t states) { - last_relay_request = states; // Always track the request - - if (!get_is_safe()) { - // Safety interlock active - refuse to energize relays - if (states!=0) ESP_LOGW("I2C", "Main relay operation blocked by safety interlock"); - return tca_write_word_8(TCA_REG_OUTPUT1, (states | SENSOR_EN_MASK) & SAFETY_MASK); - } - - return tca_write_word_8(TCA_REG_OUTPUT1, states | SENSOR_EN_MASK); +esp_err_t i2c_set_relays(relay_port_t states) { + return tca_write_word_8(TCA_REG_OUTPUT1, states.raw); } esp_err_t i2c_set_led1(uint8_t state) { diff --git a/main/i2c.h b/main/i2c.h index b031acf..4c6e0f5 100644 --- a/main/i2c.h +++ b/main/i2c.h @@ -5,11 +5,53 @@ #include #include "esp_err.h" +#define I2C_PORT I2C_NUM_0 +#define TCA_ADDR_READ 0x21 +#define TCA_ADDR_WRITE 0x21 +#define I2C_PULLUP GPIO_PULLUP_DISABLE +#define I2C_FREQUENCY 400000 + +// TCA9555 Registers +#define TCA_REG_INPUT0 0x00 +#define TCA_REG_INPUT1 0x01 +#define TCA_REG_OUTPUT0 0x02 +#define TCA_REG_OUTPUT1 0x03 +#define TCA_REG_POLARITY0 0x04 +#define TCA_REG_POLARITY1 0x05 +#define TCA_REG_CONFIG0 0x06 +#define TCA_REG_CONFIG1 0x07 + +// Debounce & Repeat Settings +#define DEBOUNCE_MS 50 +#define REPEAT_MS 200 +#define REPEAT_START_MS 700 + +typedef union { + uint8_t raw; + struct { + uint8_t SENSORS : 1; // [0] + uint8_t C3 : 1; // [1] + uint8_t B3 : 1; // [2] + uint8_t A3 : 1; // [3] + uint8_t B2 : 1; // [4] + uint8_t A2 : 1; // [5] + uint8_t B1 : 1; // [6] + uint8_t A1 : 1; // [7] MSB + } bits; + struct { + uint8_t SENSORS : 1; + uint8_t C3 : 1; + uint8_t AUX : 2; + uint8_t JACK : 2; + uint8_t DRIVE : 2; + } bridges; +} relay_port_t; + // Public Functions esp_err_t i2c_init(void); esp_err_t i2c_stop(void); -esp_err_t i2c_set_relays(uint8_t states); +esp_err_t i2c_set_relays(relay_port_t states); esp_err_t i2c_set_led1(uint8_t state); esp_err_t i2c_poll_buttons(); diff --git a/main/log_test.c b/main/log_test.c new file mode 100644 index 0000000..6259764 --- /dev/null +++ b/main/log_test.c @@ -0,0 +1,1180 @@ +#include "log_test.h" +#include "storage.h" +#include "esp_partition.h" +#include "esp_log.h" +#include "esp_err.h" +#include "esp_task_wdt.h" +#include +#include + +#define TAG "LOG_TEST" + +// External declarations for functions we need to add to storage.c +extern esp_err_t log_erase_all_sectors(void); +extern esp_err_t log_simulate_power_cycle(void); +extern esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type); +extern void log_read_reset(void); + +// Helper to wait for log queue to clear +static void wait_for_log_queue(void) { + // Wait for all queued log entries to be written + // The queue size is LOG_QUEUE_SIZE (8), so we wait a bit longer + vTaskDelay(pdMS_TO_TICKS(500)); + esp_task_wdt_reset(); +} + +// Helper to compare two byte arrays +static bool arrays_equal(const uint8_t* a, const uint8_t* b, size_t len) { + for (size_t i = 0; i < len; i++) { + if (a[i] != b[i]) { + ESP_LOGE(TAG, "Array mismatch at index %d: expected 0x%02X, got 0x%02X", i, a[i], b[i]); + return false; + } + } + return true; +} + +// Helper to verify log entry integrity +static bool verify_log_entry(const uint8_t* expected_data, uint8_t expected_len, uint8_t expected_type) { + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len; + uint8_t read_type; + + esp_err_t err = log_read(&read_len, read_buf, &read_type); + if (err == ESP_ERR_NOT_FOUND) { + ESP_LOGE(TAG, "No more log entries to read (expected entry with length %d)", expected_len); + return false; + } + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to read log entry: %s", esp_err_to_name(err)); + return false; + } + + if (read_len != expected_len) { + ESP_LOGE(TAG, "Length mismatch: expected %d, got %d", expected_len, read_len); + return false; + } + + if (read_type != expected_type) { + ESP_LOGE(TAG, "Type mismatch: expected 0x%02X, got 0x%02X", expected_type, read_type); + return false; + } + + if (!arrays_equal(expected_data, read_buf, expected_len)) { + return false; + } + + return true; +} + +// Test 1: Basic log integrity - write and read back entries +bool test_log_integrity_basic(void) { + ESP_LOGI(TAG, "=== Test: Basic Log Integrity ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + // Erase already resets head/tail and reinitializes, no need for power cycle here + + const char* test_data1 = "Test Entry 1"; + const char* test_data2 = "Another test entry with more data"; + const char* test_data3 = "Short"; + + if (log_write_blocking_test((const uint8_t*)test_data1, strlen(test_data1), LOG_TYPE_DATA) != ESP_OK) { + ESP_LOGE(TAG, "Failed to write entry 1"); + return false; + } + + if (log_write_blocking_test((const uint8_t*)test_data2, strlen(test_data2), LOG_TYPE_EVENT) != ESP_OK) { + ESP_LOGE(TAG, "Failed to write entry 2"); + return false; + } + + if (log_write_blocking_test((const uint8_t*)test_data3, strlen(test_data3), LOG_TYPE_DEBUG) != ESP_OK) { + ESP_LOGE(TAG, "Failed to write entry 3"); + return false; + } + + wait_for_log_queue(); + log_read_reset(); + + if (!verify_log_entry((const uint8_t*)test_data1, strlen(test_data1), LOG_TYPE_DATA)) { + return false; + } + + if (!verify_log_entry((const uint8_t*)test_data2, strlen(test_data2), LOG_TYPE_EVENT)) { + return false; + } + + if (!verify_log_entry((const uint8_t*)test_data3, strlen(test_data3), LOG_TYPE_DEBUG)) { + return false; + } + + ESP_LOGI(TAG, "Basic integrity test PASSED"); + return true; +} + +// Test 2: Log integrity with wraparound +bool test_log_integrity_wraparound(void) { + ESP_LOGI(TAG, "=== Test: Log Integrity with Wraparound ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint8_t test_pattern[64]; + for (int i = 0; i < 64; i++) { + test_pattern[i] = i & 0xFF; + } + + int entries_written = 0; + for (int i = 0; i < 200; i++) { + test_pattern[0] = i & 0xFF; + if (log_write_blocking_test(test_pattern, 64, LOG_TYPE_DATA) == ESP_OK) { + entries_written++; + } else { + ESP_LOGW(TAG, "Write failed at entry %d", i); + } + + if (i % 20 == 0) { + esp_task_wdt_reset(); + } + } + + wait_for_log_queue(); + + ESP_LOGI(TAG, "Wrote %d entries, reading back last few...", entries_written); + + log_read_reset(); + + int entries_read = 0; + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len, read_type; + + for (int i = 0; i < 10; i++) { + if (log_read(&read_len, read_buf, &read_type) == ESP_OK) { + entries_read++; + if (read_len != 64) { + ESP_LOGE(TAG, "Entry %d has wrong length: %d", i, read_len); + return false; + } + if (read_type != LOG_TYPE_DATA) { + ESP_LOGE(TAG, "Entry %d has wrong type: 0x%02X", i, read_type); + return false; + } + } else { + break; + } + } + + ESP_LOGI(TAG, "Successfully read %d entries after wraparound", entries_read); + + if (entries_read == 0) { + ESP_LOGE(TAG, "Failed to read any entries after wraparound"); + return false; + } + + ESP_LOGI(TAG, "Wraparound integrity test PASSED"); + return true; +} + +// Test 3: Head/tail recovery after power cycle - empty log +bool test_log_head_tail_recovery_empty(void) { + ESP_LOGI(TAG, "=== Test: Head/Tail Recovery - Empty Log ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint32_t head = log_get_head(); + uint32_t tail = log_get_tail(); + uint32_t log_start = log_get_offset(); + + ESP_LOGI(TAG, "After power cycle: head=%lu, tail=%lu, log_start=%lu", + (unsigned long)head, (unsigned long)tail, (unsigned long)log_start); + + if (head != log_start || tail != log_start) { + ESP_LOGE(TAG, "Empty log should have head and tail at start offset"); + return false; + } + + ESP_LOGI(TAG, "Empty log recovery PASSED"); + return true; +} + +// Test 4: Head/tail recovery - partial sector +bool test_log_head_tail_recovery_partial_sector(void) { + ESP_LOGI(TAG, "=== Test: Head/Tail Recovery - Partial Sector ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint32_t head_before = log_get_head(); + + const char* data1 = "Entry 1"; + const char* data2 = "Entry 2"; + const char* data3 = "Entry 3"; + + log_write_blocking_test((const uint8_t*)data1, strlen(data1), LOG_TYPE_DATA); + log_write_blocking_test((const uint8_t*)data2, strlen(data2), LOG_TYPE_DATA); + log_write_blocking_test((const uint8_t*)data3, strlen(data3), LOG_TYPE_DATA); + + wait_for_log_queue(); + + uint32_t head_after_writes = log_get_head(); + uint32_t tail_after_writes = log_get_tail(); + + ESP_LOGI(TAG, "Before power cycle: head=%lu, tail=%lu", + (unsigned long)head_after_writes, (unsigned long)tail_after_writes); + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint32_t head_after_cycle = log_get_head(); + uint32_t tail_after_cycle = log_get_tail(); + + ESP_LOGI(TAG, "After power cycle: head=%lu, tail=%lu", + (unsigned long)head_after_cycle, (unsigned long)tail_after_cycle); + + if (head_after_cycle < head_before || head_after_cycle > head_after_writes + 10) { + ESP_LOGE(TAG, "Head not properly recovered: before=%lu, after=%lu", + (unsigned long)head_after_writes, (unsigned long)head_after_cycle); + return false; + } + + log_read_reset(); + + if (!verify_log_entry((const uint8_t*)data1, strlen(data1), LOG_TYPE_DATA)) { + ESP_LOGE(TAG, "Failed to read entry 1 after recovery"); + return false; + } + + if (!verify_log_entry((const uint8_t*)data2, strlen(data2), LOG_TYPE_DATA)) { + ESP_LOGE(TAG, "Failed to read entry 2 after recovery"); + return false; + } + + if (!verify_log_entry((const uint8_t*)data3, strlen(data3), LOG_TYPE_DATA)) { + ESP_LOGE(TAG, "Failed to read entry 3 after recovery"); + return false; + } + + ESP_LOGI(TAG, "Partial sector recovery PASSED"); + return true; +} + +// Test 5: Head/tail recovery - multiple full sectors +bool test_log_head_tail_recovery_full_sectors(void) { + ESP_LOGI(TAG, "=== Test: Head/Tail Recovery - Full Sectors ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint8_t pattern[100]; + for (int i = 0; i < 100; i++) { + pattern[i] = i & 0xFF; + } + + for (int i = 0; i < 100; i++) { + pattern[0] = i & 0xFF; + log_write_blocking_test(pattern, 100, LOG_TYPE_DATA); + + if (i % 20 == 0) { + esp_task_wdt_reset(); + } + } + + wait_for_log_queue(); + + uint32_t head_before = log_get_head(); + uint32_t tail_before = log_get_tail(); + + ESP_LOGI(TAG, "Before power cycle: head=%lu, tail=%lu", + (unsigned long)head_before, (unsigned long)tail_before); + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint32_t head_after = log_get_head(); + uint32_t tail_after = log_get_tail(); + + ESP_LOGI(TAG, "After power cycle: head=%lu, tail=%lu", + (unsigned long)head_after, (unsigned long)tail_after); + + if (head_after < head_before - 1000 || head_after > head_before + 1000) { + ESP_LOGE(TAG, "Head significantly changed after recovery"); + return false; + } + + log_read_reset(); + + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len, read_type; + int entries_read = 0; + + for (int i = 0; i < 10; i++) { + if (log_read(&read_len, read_buf, &read_type) == ESP_OK) { + entries_read++; + } + } + + if (entries_read == 0) { + ESP_LOGE(TAG, "Could not read any entries after recovery"); + return false; + } + + ESP_LOGI(TAG, "Full sectors recovery PASSED (read %d entries)", entries_read); + return true; +} + +// Test 6: Head/tail recovery with wraparound +bool test_log_head_tail_recovery_wraparound(void) { + ESP_LOGI(TAG, "=== Test: Head/Tail Recovery - Wraparound ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint8_t pattern[80]; + for (int i = 0; i < 80; i++) { + pattern[i] = i & 0xFF; + } + + for (int i = 0; i < 250; i++) { + pattern[0] = i & 0xFF; + log_write_blocking_test(pattern, 80, LOG_TYPE_DATA); + + if (i % 20 == 0) { + esp_task_wdt_reset(); + } + } + + wait_for_log_queue(); + + uint32_t head_before = log_get_head(); + uint32_t tail_before = log_get_tail(); + + ESP_LOGI(TAG, "Before power cycle (wraparound): head=%lu, tail=%lu", + (unsigned long)head_before, (unsigned long)tail_before); + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint32_t head_after = log_get_head(); + uint32_t tail_after = log_get_tail(); + + ESP_LOGI(TAG, "After power cycle (wraparound): head=%lu, tail=%lu", + (unsigned long)head_after, (unsigned long)tail_after); + + log_read_reset(); + + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len, read_type; + int entries_read = 0; + + for (int i = 0; i < 10; i++) { + if (log_read(&read_len, read_buf, &read_type) == ESP_OK) { + entries_read++; + } + } + + if (entries_read == 0) { + ESP_LOGE(TAG, "Could not read any entries after wraparound recovery"); + return false; + } + + ESP_LOGI(TAG, "Wraparound recovery PASSED (read %d entries)", entries_read); + return true; +} + +// Test 7: Head equals tail (empty or full) +bool test_log_head_equals_tail(void) { + ESP_LOGI(TAG, "=== Test: Head Equals Tail ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint32_t head = log_get_head(); + uint32_t tail = log_get_tail(); + + if (head != tail) { + ESP_LOGE(TAG, "Empty log should have head == tail"); + return false; + } + + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len, read_type; + + esp_err_t err = log_read(&read_len, read_buf, &read_type); + if (err == ESP_OK) { + ESP_LOGW(TAG, "Read succeeded on empty log (head == tail), which may be okay"); + } + + ESP_LOGI(TAG, "Head equals tail test PASSED"); + return true; +} + +// Test 8: Head = 0, Tail = 0 +bool test_log_head_zero_tail_zero(void) { + ESP_LOGI(TAG, "=== Test: Head=0, Tail=0 (actually LOG_START_OFFSET) ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint32_t head = log_get_head(); + uint32_t tail = log_get_tail(); + uint32_t log_start = log_get_offset(); + + if (head != log_start || tail != log_start) { + ESP_LOGE(TAG, "Fresh log should have both at LOG_START_OFFSET"); + return false; + } + + const char* data = "First entry"; + log_write_blocking_test((const uint8_t*)data, strlen(data), LOG_TYPE_DATA); + wait_for_log_queue(); + + uint32_t head_after = log_get_head(); + uint32_t tail_after = log_get_tail(); + + if (head_after == log_start) { + ESP_LOGE(TAG, "Head should have advanced after write"); + return false; + } + + if (tail_after != log_start) { + ESP_LOGE(TAG, "Tail should still be at start"); + return false; + } + + ESP_LOGI(TAG, "Head=0, Tail=0 test PASSED"); + return true; +} + +// Test 9: Head > Tail (normal case) +bool test_log_head_greater_than_tail(void) { + ESP_LOGI(TAG, "=== Test: Head > Tail (Normal Case) ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + for (int i = 0; i < 10; i++) { + char data[32]; + snprintf(data, sizeof(data), "Entry %d", i); + log_write_blocking_test((const uint8_t*)data, strlen(data), LOG_TYPE_DATA); + } + + wait_for_log_queue(); + + uint32_t head = log_get_head(); + uint32_t tail = log_get_tail(); + + ESP_LOGI(TAG, "Head=%lu, Tail=%lu", (unsigned long)head, (unsigned long)tail); + + if (head <= tail && tail != log_get_offset()) { + ESP_LOGE(TAG, "Expected head > tail in normal case"); + return false; + } + + log_read_reset(); + + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len, read_type; + int entries_read = 0; + + for (int i = 0; i < 10; i++) { + if (log_read(&read_len, read_buf, &read_type) == ESP_OK) { + entries_read++; + } + } + + if (entries_read != 10) { + ESP_LOGE(TAG, "Expected to read 10 entries, got %d", entries_read); + return false; + } + + ESP_LOGI(TAG, "Head > Tail test PASSED"); + return true; +} + +// Test 10: Head < Tail (wraparound case) +bool test_log_head_less_than_tail(void) { + ESP_LOGI(TAG, "=== Test: Head < Tail (Wraparound) ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint8_t pattern[90]; + for (int i = 0; i < 90; i++) { + pattern[i] = i & 0xFF; + } + + for (int i = 0; i < 300; i++) { + pattern[0] = i & 0xFF; + log_write_blocking_test(pattern, 90, LOG_TYPE_DATA); + + if (i % 20 == 0) { + esp_task_wdt_reset(); + } + } + + wait_for_log_queue(); + + uint32_t head = log_get_head(); + uint32_t tail = log_get_tail(); + + ESP_LOGI(TAG, "After wraparound: Head=%lu, Tail=%lu", + (unsigned long)head, (unsigned long)tail); + + if (tail < head && tail != log_get_offset()) { + ESP_LOGW(TAG, "Expected wraparound condition, but may depend on partition size"); + } + + log_read_reset(); + + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len, read_type; + + if (log_read(&read_len, read_buf, &read_type) != ESP_OK) { + ESP_LOGE(TAG, "Failed to read after wraparound"); + return false; + } + + ESP_LOGI(TAG, "Head < Tail test PASSED"); + return true; +} + +// Test 11: Multiple wraparounds +bool test_log_wraparound_multiple_times(void) { + ESP_LOGI(TAG, "=== Test: Multiple Wraparounds ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint8_t pattern[100]; + for (int i = 0; i < 100; i++) { + pattern[i] = i & 0xFF; + } + + int total_written = 0; + for (int i = 0; i < 500; i++) { + pattern[0] = i & 0xFF; + if (log_write_blocking_test(pattern, 100, LOG_TYPE_DATA) == ESP_OK) { + total_written++; + } + + if (i % 20 == 0) { + esp_task_wdt_reset(); + } + } + + wait_for_log_queue(); + + ESP_LOGI(TAG, "Wrote %d entries across multiple wraps", total_written); + + log_read_reset(); + + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len, read_type; + int entries_read = 0; + + for (int i = 0; i < 20; i++) { + if (log_read(&read_len, read_buf, &read_type) == ESP_OK) { + entries_read++; + } else { + break; + } + } + + ESP_LOGI(TAG, "Read %d entries after multiple wraps", entries_read); + + if (entries_read == 0) { + ESP_LOGE(TAG, "Failed to read any entries after multiple wraps"); + return false; + } + + ESP_LOGI(TAG, "Multiple wraparound test PASSED"); + return true; +} + +// Test 12: Sector boundary conditions +bool test_log_sector_boundary_conditions(void) { + ESP_LOGI(TAG, "=== Test: Sector Boundary Conditions ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint8_t pattern[100]; + for (int i = 0; i < 100; i++) { + pattern[i] = i & 0xFF; + } + + for (int i = 0; i < 50; i++) { + pattern[0] = i & 0xFF; + log_write_blocking_test(pattern, 100, LOG_TYPE_DATA); + + // Slow down to prevent queue overflow + if (i % 5 == 0) { + vTaskDelay(pdMS_TO_TICKS(50)); + } + + if (i % 20 == 0) { + esp_task_wdt_reset(); + } + } + + wait_for_log_queue(); + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + log_read_reset(); + + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len, read_type; + int entries_read = 0; + + for (int i = 0; i < 50; i++) { + if (log_read(&read_len, read_buf, &read_type) == ESP_OK) { + entries_read++; + if (read_len != 100) { + ESP_LOGE(TAG, "Entry %d has wrong length: %d", i, read_len); + return false; + } + } else { + break; + } + } + + if (entries_read != 50) { + ESP_LOGE(TAG, "Expected 50 entries, read %d", entries_read); + return false; + } + + ESP_LOGI(TAG, "Sector boundary test PASSED"); + return true; +} + +// Test 13: Maximum payload size +bool test_log_maximum_payload(void) { + ESP_LOGI(TAG, "=== Test: Maximum Payload Size ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint8_t max_payload[LOG_MAX_PAYLOAD]; + for (int i = 0; i < LOG_MAX_PAYLOAD; i++) { + max_payload[i] = i & 0xFF; + } + + if (log_write_blocking_test(max_payload, LOG_MAX_PAYLOAD, LOG_TYPE_DATA) != ESP_OK) { + ESP_LOGE(TAG, "Failed to write maximum payload"); + return false; + } + + wait_for_log_queue(); + log_read_reset(); + + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len, read_type; + + if (log_read(&read_len, read_buf, &read_type) != ESP_OK) { + ESP_LOGE(TAG, "Failed to read maximum payload"); + return false; + } + + if (read_len != LOG_MAX_PAYLOAD) { + ESP_LOGE(TAG, "Read length %d != expected %d", read_len, LOG_MAX_PAYLOAD); + return false; + } + + if (!arrays_equal(max_payload, read_buf, LOG_MAX_PAYLOAD)) { + return false; + } + + ESP_LOGI(TAG, "Maximum payload test PASSED"); + return true; +} + +// Test 14: Minimum payload size (1 byte) +bool test_log_minimum_payload(void) { + ESP_LOGI(TAG, "=== Test: Minimum Payload Size ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint8_t min_payload[1] = {0x42}; + + if (log_write_blocking_test(min_payload, 1, LOG_TYPE_DEBUG) != ESP_OK) { + ESP_LOGE(TAG, "Failed to write minimum payload"); + return false; + } + + wait_for_log_queue(); + log_read_reset(); + + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len, read_type; + + if (log_read(&read_len, read_buf, &read_type) != ESP_OK) { + ESP_LOGE(TAG, "Failed to read minimum payload"); + return false; + } + + if (read_len != 1) { + ESP_LOGE(TAG, "Read length %d != expected 1", read_len); + return false; + } + + if (read_buf[0] != 0x42) { + ESP_LOGE(TAG, "Read data 0x%02X != expected 0x42", read_buf[0]); + return false; + } + + if (read_type != LOG_TYPE_DEBUG) { + ESP_LOGE(TAG, "Read type 0x%02X != expected 0x%02X", read_type, LOG_TYPE_DEBUG); + return false; + } + + ESP_LOGI(TAG, "Minimum payload test PASSED"); + return true; +} + +// Test 15: Corrupted entry recovery +bool test_log_corrupted_entry_recovery(void) { + ESP_LOGI(TAG, "=== Test: Corrupted Entry Recovery ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + const char* data1 = "Good entry 1"; + const char* data2 = "Good entry 2"; + + log_write_blocking_test((const uint8_t*)data1, strlen(data1), LOG_TYPE_DATA); + log_write_blocking_test((const uint8_t*)data2, strlen(data2), LOG_TYPE_DATA); + + wait_for_log_queue(); + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + log_read_reset(); + + if (!verify_log_entry((const uint8_t*)data1, strlen(data1), LOG_TYPE_DATA)) { + ESP_LOGE(TAG, "Failed to read entry 1 after simulated corruption test"); + return false; + } + + if (!verify_log_entry((const uint8_t*)data2, strlen(data2), LOG_TYPE_DATA)) { + ESP_LOGE(TAG, "Failed to read entry 2 after simulated corruption test"); + return false; + } + + ESP_LOGI(TAG, "Corrupted entry recovery test PASSED (basic validation)"); + return true; +} + +// Test 16: Full partition handling +bool test_log_full_partition(void) { + ESP_LOGI(TAG, "=== Test: Full Partition Handling ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + uint8_t pattern[200]; + for (int i = 0; i < 200; i++) { + pattern[i] = i & 0xFF; + } + + int successful_writes = 0; + for (int i = 0; i < 1000; i++) { + pattern[0] = i & 0xFF; + if (log_write_blocking_test(pattern, 200, LOG_TYPE_DATA) == ESP_OK) { + successful_writes++; + } else { + ESP_LOGW(TAG, "Write failed at iteration %d", i); + } + + if (i % 50 == 0) { + vTaskDelay(pdMS_TO_TICKS(100)); + esp_task_wdt_reset(); + } + } + + ESP_LOGI(TAG, "Successfully wrote %d entries before partition full", successful_writes); + + wait_for_log_queue(); + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle on full partition"); + return false; + } + + log_read_reset(); + + uint8_t read_buf[LOG_MAX_PAYLOAD]; + uint8_t read_len, read_type; + + if (log_read(&read_len, read_buf, &read_type) != ESP_OK) { + ESP_LOGE(TAG, "Failed to read from full partition"); + return false; + } + + ESP_LOGI(TAG, "Full partition test PASSED"); + return true; +} + +// Test 17: Read after write consistency +bool test_log_read_after_write(void) { + ESP_LOGI(TAG, "=== Test: Read After Write Consistency ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + for (int i = 0; i < 20; i++) { + char data[32]; + snprintf(data, sizeof(data), "Entry number %d", i); + + if (log_write_blocking_test((const uint8_t*)data, strlen(data), LOG_TYPE_DATA) != ESP_OK) { + ESP_LOGE(TAG, "Failed to write entry %d", i); + return false; + } + } + + wait_for_log_queue(); + log_read_reset(); + + for (int i = 0; i < 20; i++) { + char expected[32]; + snprintf(expected, sizeof(expected), "Entry number %d", i); + + if (!verify_log_entry((const uint8_t*)expected, strlen(expected), LOG_TYPE_DATA)) { + ESP_LOGE(TAG, "Failed to verify entry %d", i); + return false; + } + } + + ESP_LOGI(TAG, "Read after write consistency test PASSED"); + return true; +} + +// Test 18: Multiple log types +bool test_log_multiple_types(void) { + ESP_LOGI(TAG, "=== Test: Multiple Log Types ==="); + + if (log_erase_all_sectors() != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log sectors"); + return false; + } + + if (log_simulate_power_cycle() != ESP_OK) { + ESP_LOGE(TAG, "Failed to simulate power cycle"); + return false; + } + + const char* data_entry = "Data entry"; + const char* event_entry = "Event entry"; + const char* error_entry = "Error entry"; + const char* debug_entry = "Debug entry"; + const char* sensor_entry = "Sensor entry"; + + log_write_blocking_test((const uint8_t*)data_entry, strlen(data_entry), LOG_TYPE_DATA); + log_write_blocking_test((const uint8_t*)event_entry, strlen(event_entry), LOG_TYPE_EVENT); + log_write_blocking_test((const uint8_t*)error_entry, strlen(error_entry), LOG_TYPE_ERROR); + log_write_blocking_test((const uint8_t*)debug_entry, strlen(debug_entry), LOG_TYPE_DEBUG); + log_write_blocking_test((const uint8_t*)sensor_entry, strlen(sensor_entry), LOG_TYPE_SENSOR); + + wait_for_log_queue(); + log_read_reset(); + + if (!verify_log_entry((const uint8_t*)data_entry, strlen(data_entry), LOG_TYPE_DATA)) { + return false; + } + + if (!verify_log_entry((const uint8_t*)event_entry, strlen(event_entry), LOG_TYPE_EVENT)) { + return false; + } + + if (!verify_log_entry((const uint8_t*)error_entry, strlen(error_entry), LOG_TYPE_ERROR)) { + return false; + } + + if (!verify_log_entry((const uint8_t*)debug_entry, strlen(debug_entry), LOG_TYPE_DEBUG)) { + return false; + } + + if (!verify_log_entry((const uint8_t*)sensor_entry, strlen(sensor_entry), LOG_TYPE_SENSOR)) { + return false; + } + + ESP_LOGI(TAG, "Multiple log types test PASSED"); + return true; +} + +// Helper function to print test results +void print_test_results(test_result_t* results, int num_tests) { + ESP_LOGI(TAG, "\n\n"); + ESP_LOGI(TAG, "================================================="); + ESP_LOGI(TAG, "TEST RESULTS SUMMARY"); + ESP_LOGI(TAG, "================================================="); + + int passed = 0; + int failed = 0; + + ESP_LOGI(TAG, "\n--- FAILED TESTS ---"); + bool any_failures = false; + for (int i = 0; i < num_tests; i++) { + if (!results[i].passed) { + ESP_LOGE(TAG, "[FAIL] Test %d: %s", i + 1, results[i].test_name); + if (results[i].error_msg) { + ESP_LOGE(TAG, " Error: %s", results[i].error_msg); + } + failed++; + any_failures = true; + } + } + + if (!any_failures) { + ESP_LOGI(TAG, "None - all tests passed!"); + } + + ESP_LOGI(TAG, "\n--- PASSED TESTS ---"); + for (int i = 0; i < num_tests; i++) { + if (results[i].passed) { + ESP_LOGI(TAG, "[PASS] Test %d: %s", i + 1, results[i].test_name); + passed++; + } + } + + ESP_LOGI(TAG, "\n================================================="); + ESP_LOGI(TAG, "Total: %d | Passed: %d | Failed: %d", num_tests, passed, failed); + if (failed > 0) { + ESP_LOGE(TAG, "FAILED TESTS: %d", failed); + } else { + ESP_LOGI(TAG, "ALL TESTS PASSED!"); + } + ESP_LOGI(TAG, "=================================================\n"); +} + +int count_passed_tests(test_result_t* results, int num_tests) { + int passed = 0; + for (int i = 0; i < num_tests; i++) { + if (results[i].passed) { + passed++; + } + } + return passed; +} + +// Main test runner +esp_err_t run_all_log_tests(void) { + ESP_LOGI(TAG, "\n\n"); + ESP_LOGI(TAG, "================================================="); + ESP_LOGI(TAG, "STARTING COMPREHENSIVE LOG TESTS"); + ESP_LOGI(TAG, "=================================================\n"); + + if (storage_init() != ESP_OK) { + ESP_LOGE(TAG, "STORAGE FAILED"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Storage initialized successfully"); + + typedef struct { + const char* name; + bool (*test_func)(void); + } test_entry_t; + + test_entry_t tests[] = { + {"Basic Log Integrity", test_log_integrity_basic}, + {"Log Integrity with Wraparound", test_log_integrity_wraparound}, + {"Head/Tail Recovery - Empty Log", test_log_head_tail_recovery_empty}, + {"Head/Tail Recovery - Partial Sector", test_log_head_tail_recovery_partial_sector}, + {"Head/Tail Recovery - Full Sectors", test_log_head_tail_recovery_full_sectors}, + {"Head/Tail Recovery - Wraparound", test_log_head_tail_recovery_wraparound}, + {"Head Equals Tail", test_log_head_equals_tail}, + {"Head=0, Tail=0", test_log_head_zero_tail_zero}, + {"Head > Tail (Normal)", test_log_head_greater_than_tail}, + {"Head < Tail (Wraparound)", test_log_head_less_than_tail}, + {"Multiple Wraparounds", test_log_wraparound_multiple_times}, + {"Sector Boundary Conditions", test_log_sector_boundary_conditions}, + {"Maximum Payload Size", test_log_maximum_payload}, + {"Minimum Payload Size", test_log_minimum_payload}, + {"Corrupted Entry Recovery", test_log_corrupted_entry_recovery}, + {"Full Partition Handling", test_log_full_partition}, + {"Read After Write Consistency", test_log_read_after_write}, + {"Multiple Log Types", test_log_multiple_types}, + }; + + int num_tests = sizeof(tests) / sizeof(tests[0]); + test_result_t* results = malloc(num_tests * sizeof(test_result_t)); + + if (results == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for test results"); + return ESP_ERR_NO_MEM; + } + + for (int i = 0; i < num_tests; i++) { + ESP_LOGI(TAG, "\n--- Running Test %d/%d: %s ---", i + 1, num_tests, tests[i].name); + + esp_task_wdt_reset(); + + results[i].test_name = tests[i].name; + results[i].passed = tests[i].test_func(); + results[i].error_msg = NULL; + + if (results[i].passed) { + ESP_LOGI(TAG, ">>> TEST PASSED: %s <<<", tests[i].name); + } else { + ESP_LOGE(TAG, ">>> TEST FAILED: %s <<<", tests[i].name); + ESP_LOGE(TAG, ">>> STOPPING AT FIRST FAILURE <<<"); + + for (int j = i + 1; j < num_tests; j++) { + results[j].test_name = tests[j].name; + results[j].passed = false; + results[j].error_msg = "Not run - stopped at first failure"; + } + break; + } + + esp_task_wdt_reset(); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + print_test_results(results, num_tests); + + int passed = count_passed_tests(results, num_tests); + free(results); + + if (passed == num_tests) { + ESP_LOGI(TAG, "ALL TESTS PASSED!"); + + } else { + ESP_LOGE(TAG, "SOME TESTS FAILED!"); + + } + + while(1) { esp_task_wdt_reset(); vTaskDelay(pdMS_TO_TICKS(100)); } + return ESP_OK; +} \ No newline at end of file diff --git a/main/log_test.h b/main/log_test.h new file mode 100644 index 0000000..2bd8fa8 --- /dev/null +++ b/main/log_test.h @@ -0,0 +1,47 @@ +#ifndef LOG_TEST_H +#define LOG_TEST_H + +#include "freertos/FreeRTOS.h" // Must be FIRST +#include "freertos/projdefs.h" +#include "freertos/task.h" +#include "freertos/queue.h" + +#include "esp_err.h" +#include +#include + +// Test result structure +typedef struct { + const char* test_name; + bool passed; + const char* error_msg; +} test_result_t; + +// Main test suite runner +esp_err_t run_all_log_tests(void); + +// Individual test functions +bool test_log_integrity_basic(void); +bool test_log_integrity_wraparound(void); +bool test_log_head_tail_recovery_empty(void); +bool test_log_head_tail_recovery_partial_sector(void); +bool test_log_head_tail_recovery_full_sectors(void); +bool test_log_head_tail_recovery_wraparound(void); +bool test_log_head_equals_tail(void); +bool test_log_head_zero_tail_zero(void); +bool test_log_head_greater_than_tail(void); +bool test_log_head_less_than_tail(void); +bool test_log_wraparound_multiple_times(void); +bool test_log_sector_boundary_conditions(void); +bool test_log_maximum_payload(void); +bool test_log_minimum_payload(void); +bool test_log_corrupted_entry_recovery(void); +bool test_log_full_partition(void); +bool test_log_read_after_write(void); +bool test_log_multiple_types(void); + +// Helper functions for testing +void print_test_results(test_result_t* results, int num_tests); +int count_passed_tests(test_result_t* results, int num_tests); + +#endif // LOG_TEST_H \ No newline at end of file diff --git a/main/main.c b/main/main.c index 35198cc..ec4dbe0 100644 --- a/main/main.c +++ b/main/main.c @@ -1,5 +1,6 @@ #include "esp_task_wdt.h" #include "i2c.h" +#include "log_test.h" #include "storage.h" #include "uart_comms.h" #include "esp_err.h" @@ -17,49 +18,25 @@ #define TAG "MAIN" -int64_t last_log_time = 0; -#define LOGSIZE 40 -esp_err_t send_log() { - uint8_t entry[LOGSIZE] = {}; +int64_t last_bat_log_time = 0; +esp_err_t send_bat_log() { + if(!rtc_is_set()) return ESP_OK; + + uint8_t entry[12] = {}; - entry[0] = fsm_get_state(); // Pack 64-bit timestamp into bytes 1-8 uint64_t be_timestamp = rtc_get_ms(); - memcpy(&entry[1], &be_timestamp, 8); + memcpy(&entry[0], &be_timestamp, 8); // Pack 32-bit voltages/currents into bytes 9-24 float be_voltage = get_battery_V(); - memcpy(&entry[9], &be_voltage, 4); - float be_current1 = get_bridge_A(BRIDGE_DRIVE); - memcpy(&entry[13], &be_current1, 4); - float be_current2 = get_bridge_A(BRIDGE_JACK); - memcpy(&entry[17], &be_current2, 4); - float be_current3 = get_bridge_A(BRIDGE_AUX); - memcpy(&entry[21], &be_current3, 4); + memcpy(&entry[8], &be_voltage, 4); - int16_t be_counter = get_sensor_counter(SENSOR_DRIVE); - memcpy(&entry[25], &be_counter, 2); + last_bat_log_time = esp_timer_get_time(); - entry[27] = pack_sensors(); - - - - - float heat1 = get_bridge_heat(BRIDGE_DRIVE); - memcpy(&entry[28], &heat1, 4); - float heat2 = get_bridge_heat(BRIDGE_JACK); - memcpy(&entry[32], &heat2, 4); - float heat3 = get_bridge_heat(BRIDGE_AUX); - memcpy(&entry[36], &heat3, 4); - - last_log_time = esp_timer_get_time(); - - - log_write(entry, LOGSIZE); - - ESP_LOGI(TAG, "WROTE LOG; %lld / %ld/%ld; %5.2f %5.2f %5.2f", (long long)rtc_get_ms(), (unsigned long)log_get_tail(), (unsigned long)log_get_head(), heat1, heat2, heat3); + log_write(entry, 12, LOG_TYPE_BAT); return ESP_OK; } @@ -126,15 +103,16 @@ void driveLEDs(led_state_t state) { RTC_DATA_ATTR bool first_boot = true; -void app_main(void) { - esp_task_wdt_add(NULL); +void app_main(void) {esp_task_wdt_add(NULL); + + //run_all_log_tests(); if (rtc_xtal_init() != ESP_OK) ESP_LOGE(TAG, "RTC FAILED"); // Say hello; turn on the lights esp_sleep_wakeup_cause_t cause = rtc_wakeup_cause(); if (i2c_init() != ESP_OK) ESP_LOGE(TAG, "I2C FAILED"); - i2c_set_relays(0); + i2c_set_relays((relay_port_t){.raw=0}); driveLEDs(LED_STATE_BOOTING); ESP_LOGI(TAG, "Firmware: %s", FIRMWARE_STRING); @@ -188,8 +166,6 @@ void app_main(void) { esp_restart(); } - first_boot = false; - // Every boot we load parameters and monitor solar, no matter what if (adc_init() != ESP_OK) ESP_LOGE(TAG, "ADC FAILED"); if (storage_init() != ESP_OK) ESP_LOGE(TAG, "STORAGE FAILED"); @@ -197,8 +173,10 @@ void app_main(void) { if (solar_run_fsm() != ESP_OK) ESP_LOGE(TAG, "SOLAR FAILED"); // TODO: Do a 12V check and enter deep sleep if there's a problem + send_bat_log(); - send_log(); + + //send_log(); //write_dummy_log_1(); @@ -214,17 +192,19 @@ void app_main(void) { } else */if (cause == ESP_SLEEP_WAKEUP_EXT0) { ESP_LOGI("MAIN", "Woke from button press"); } else { - if (!rtc_alarm_tripped()) { - //enter_deep_sleep(); + if (!rtc_alarm_tripped() && !first_boot) { + rtc_enter_deep_sleep(); } } + first_boot = false; + /*** FULL BOOT ***/ - //if (uart_init() != ESP_OK) ESP_LOGE(TAG, "UART FAILED"); - if (power_init() != ESP_OK) ESP_LOGE(TAG, "POWER FAILED"); + if (uart_init() != ESP_OK) ESP_LOGE(TAG, "UART FAILED"); + //if (power_init() != ESP_OK) ESP_LOGE(TAG, "POWER FAILED"); if (rf_433_init() != ESP_OK) ESP_LOGE(TAG, "RF FAILED"); if (fsm_init() != ESP_OK) ESP_LOGE(TAG, "FSM FAILED"); - if (sensors_init() != ESP_OK) ESP_LOGE(TAG, "SENSORS FAILED"); + //if (sensors_init() != ESP_OK) ESP_LOGE(TAG, "SENSORS FAILED"); if (webserver_init() != ESP_OK) ESP_LOGE(TAG, "WEBSERVER FAILED"); /*** MAIN LOOP ***/ @@ -276,16 +256,16 @@ void app_main(void) { } // when not actively moving we log at a low frequency - if (isRunning() || (esp_timer_get_time() > last_log_time + 3000000)) //DEEP_SLEEP_US)) - send_log(); + if ((esp_timer_get_time() > last_bat_log_time + DEEP_SLEEP_US)) + send_bat_log(); if(i2c_get_button_ms(0) > 2100) fsm_request(FSM_CMD_START); break; - case STATE_UNDO_JACK: + //case STATE_UNDO_JACK: case STATE_UNDO_JACK_START: // it's running the jack, but undoing - send_log(); + //send_log(); driveLEDs(LED_STATE_CANCELLING); if (i2c_get_button_tripped(0)) { ESP_LOGI(TAG, "AAAAH STOP!!!"); @@ -294,31 +274,31 @@ void app_main(void) { break; case STATE_CALIBRATE_JACK_DELAY: - send_log(); + //send_log(); if (i2c_get_button_tripped(0)) fsm_request(FSM_CMD_CALIBRATE_JACK_START); break; case STATE_CALIBRATE_JACK_MOVE: - send_log(); + //send_log(); if (i2c_get_button_tripped(0)) fsm_request(FSM_CMD_CALIBRATE_JACK_END); break; case STATE_CALIBRATE_DRIVE_DELAY: - send_log(); + //send_log(); if (i2c_get_button_tripped(0)) fsm_request(FSM_CMD_CALIBRATE_DRIVE_START); break; case STATE_CALIBRATE_DRIVE_MOVE: - send_log(); + //send_log(); if (i2c_get_button_tripped(0)) fsm_request(FSM_CMD_CALIBRATE_DRIVE_END); break; default: // it's running in every other case - send_log(); + //send_log(); driveLEDs(LED_STATE_DRIVING); if (i2c_get_button_tripped(0)) { fsm_request(FSM_CMD_UNDO); diff --git a/main/power_mgmt.c b/main/power_mgmt.c index 427eaba..d46cfb1 100644 --- a/main/power_mgmt.c +++ b/main/power_mgmt.c @@ -28,6 +28,8 @@ #include "esp_timer.h" #include "driver/gpio.h" #include "control_fsm.h" +#include "i2c.h" +#include "sensors.h" #include "soc/rtc_io_reg.h" #include "power_mgmt.h" @@ -43,32 +45,104 @@ #define PIN_V_BATTERY ADC_CHANNEL_3 // GPIO39 / VN #define PIN_V_SENS_BAT PIN_V_BATTERY +// map from relay number to bridge +/*bridge_t bridge_map[] = { + -1, + BRIDGE_AUX, + BRIDGE_AUX, + BRIDGE_AUX, + BRIDGE_JACK, + BRIDGE_JACK, + BRIDGE_DRIVE, + BRIDGE_DRIVE };*/ // update time #define UPDATE_MS 20 #define UPDATE_S 0.02f -int64_t now; // us +extern int64_t fsm_now; // us +// E-fuse data typedef struct { int64_t az_enable_time; // Timestamp to enable autozeroing at (negative to disable) float az_offset; // Accumulated zero offset bool az_initialized; // First valid zero established + + float raw_current; bool ema_init; float ema_current; float current; // with all the corrections applied + float current_spike; float heat; - bool tripped; + efuse_trip_t tripped; int64_t trip_time; - // Inrush tolerance tracking - int64_t inrush_start_time; // When instantaneous overcurrent first detected (0 = not in overcurrent) + int64_t on_us; + int64_t off_us; } isens_channel_t; static isens_channel_t isens[N_BRIDGES] = {0}; + +/**** DRIVE RELAYS ****/ +bool relay_states[8] = {false}; + +//int64_t bridge_transitions_on[NUM_BRIDGES] = {-1}; // last time relay turned on (used to ignore inrush) +//int64_t bridge_transitions_off[NUM_BRIDGES] = {-1}; // last time relay turned off (used to enable autozero) + +relay_port_t last_relay_state; + +// actually write relay states, taking note of transitions, and debouncing transitions to on. +#define BRIDGE_TRANSITION_LOGIC(BRIDGE_NAME) \ + if (relay_state.bridges.BRIDGE_NAME == last_relay_state.bridges.BRIDGE_NAME) { \ + /* no change; no need to do anything */ \ + if(false) if (BRIDGE_##BRIDGE_NAME == BRIDGE_JACK) ESP_LOGI(TAG, "NO CHANGE"); \ + } \ + else if (last_relay_state.bridges.BRIDGE_NAME != BRIDGE_OFF && relay_state.bridges.BRIDGE_NAME == BRIDGE_OFF) { \ + isens[BRIDGE_##BRIDGE_NAME].off_us = fsm_now; \ + if(false) if (BRIDGE_##BRIDGE_NAME == BRIDGE_JACK) ESP_LOGI(TAG, "ON -> OFF"); \ + } \ + else if (last_relay_state.bridges.BRIDGE_NAME == BRIDGE_OFF && relay_state.bridges.BRIDGE_NAME != BRIDGE_OFF) { \ + if (fsm_now > isens[BRIDGE_##BRIDGE_NAME].off_us + 2*get_param_value_t(PARAM_EFUSE_INRUSH_US).u32) { \ + isens[BRIDGE_##BRIDGE_NAME].on_us = fsm_now; \ + if(false) if (BRIDGE_##BRIDGE_NAME == BRIDGE_JACK) ESP_LOGI(TAG, "OFF -> ON"); \ + } else { \ + relay_state.bridges.BRIDGE_NAME = BRIDGE_OFF; \ + if(false) if (BRIDGE_##BRIDGE_NAME == BRIDGE_JACK) ESP_LOGI(TAG, "NOT YET; -> OFF"); \ + } \ + } \ + else { \ + if(false) if (BRIDGE_##BRIDGE_NAME == BRIDGE_JACK) ESP_LOGE(TAG, "TOO FAST OF TRANSITION"); \ + isens[BRIDGE_##BRIDGE_NAME].off_us = fsm_now; \ + relay_state.bridges.BRIDGE_NAME = BRIDGE_OFF; \ + } + +esp_err_t driveRelays(relay_port_t relay_state) { + // Four types of transitions. + // Not a transition: this does nothing + // Anything -> off: always allowed. Record the transition time + // off -> anything: has debouncing; set & record transition if fsm_now > bridge_transitions_off + debounce, otherwise keep bridge off. + // fwd/rev/on -> fwd/rev/on: not allowed. Actually go to 0. Record the transition time. + + BRIDGE_TRANSITION_LOGIC(DRIVE) + BRIDGE_TRANSITION_LOGIC(JACK) + BRIDGE_TRANSITION_LOGIC(AUX) + + relay_state.bridges.SENSORS = 1; + + if (!get_is_safe()) + relay_state.bridges.DRIVE = 0; + + last_relay_state = relay_state; + + //ESP_LOGI(TAG, "RELAY STATE: %x", state); + return i2c_set_relays(relay_state); +} + +/**** CURRENT / VOLTAGE MONITORING ****/ + // === ADC Handles === static adc_oneshot_unit_handle_t adc1_handle = NULL; static adc_cali_handle_t adc_cali_handle = NULL; @@ -141,13 +215,28 @@ esp_err_t process_battery_voltage(void) return ESP_OK; } -void set_autozero(bridge_t bridge) { +void disable_autozero(bridge_t bridge) { // enable autozeroing for this bridge 1 second from now - isens[bridge].az_enable_time = now+1000000; + isens[bridge].az_enable_time = fsm_now+1000000; //ESP_LOGI(TAG, "KILLING BRIDGE %d; %lld -> %lld", bridge, (long long int) now, (long long int) isens[bridge].az_enable_time); } +bool get_bridge_overcurrent(bridge_t bridge, float threshold) { + if (bridge < 0 || bridge>=NUM_BRIDGES) return true; // I GUESS? + if (fsm_now < isens[bridge].on_us + get_param_value_t(PARAM_EFUSE_INRUSH_US).u32) return false; + if (isens[bridge].raw_current < threshold) return false; + return true; +} +bool get_bridge_spike(bridge_t bridge, float threshold) { + if (bridge < 0 || bridge>=NUM_BRIDGES) return true; // I GUESS? + if (fsm_now < isens[bridge].on_us + get_param_value_t(PARAM_EFUSE_INRUSH_US).u32) return false; + if (isens[bridge].current_spike < threshold) return false; + return true; +} + esp_err_t process_bridge_current(bridge_t bridge) { + if (bridge < 0 || bridge >= NUM_BRIDGES) return ESP_ERR_INVALID_ARG; + int adc_raw = 0; int voltage_mv = 0; @@ -158,7 +247,7 @@ esp_err_t process_bridge_current(bridge_t bridge) { case BRIDGE_DRIVE: pin = PIN_V_ISENS1; break; case BRIDGE_JACK: pin = PIN_V_ISENS2; break; case BRIDGE_AUX: pin = PIN_V_ISENS3; break; - default: return -42069; // lol + default: return ESP_ERR_INVALID_ARG; } if (adc_oneshot_read(adc1_handle, pin, &adc_raw) != ESP_OK) { @@ -168,39 +257,41 @@ esp_err_t process_bridge_current(bridge_t bridge) { return 0; } - float raw_a = NAN; + float last_current = channel->raw_current; + channel->raw_current = NAN; switch (bridge) { case BRIDGE_JACK: case BRIDGE_AUX: // ACS37042KLHBLT-030B3 is 30A capable and 44 mV/A - raw_a = (voltage_mv - 1650.0f) / 44.0f; + channel->raw_current = (voltage_mv - 1650.0f) / 44.0f; break; case BRIDGE_DRIVE: // ACS37220LEZATR-100B3 is 100A capable and 13.2 mV/A - raw_a = -(voltage_mv - 1650.0f) / 13.2f; + channel->raw_current = -(voltage_mv - 1650.0f) / 13.2f; break; + default: break; } if (!channel->ema_init) { - channel->ema_current = (float)raw_a; + channel->ema_current = channel->raw_current; channel->ema_init = true; } else { float alpha = get_param_value_t(PARAM_ADC_ALPHA_ISENS).f32; - if (isnan(raw_a)) { + if (isnan(channel->raw_current)) { //ESP_LOGI(TAG, "RAW BATTERY IS NAN"); channel->ema_current = NAN; } else { if (isnan(ema_battery) || isnan(alpha)) { - channel->ema_current = raw_a; + channel->ema_current = channel->raw_current; } else { - channel->ema_current = alpha * raw_a + (1.0f - alpha) * channel->ema_current; + channel->ema_current = alpha * channel->raw_current + (1.0f - alpha) * channel->ema_current; } } } // === AUTO-ZERO LEARNING PHASE === - if (now > channel->az_enable_time) { + if (fsm_now > channel->az_enable_time) { //ESP_LOGI(TAG, "AZING %d", bridge); float db = get_param_value_t(PARAM_ADC_DB_IAZ).f32; if (isnan(db) || fabsf(channel->ema_current) <= db) { @@ -210,7 +301,7 @@ esp_err_t process_bridge_current(bridge_t bridge) { channel->az_initialized = true; } else { float alpha = get_param_value_t(PARAM_ADC_ALPHA_IAZ).f32; - if (isnan(raw_a)) { + if (isnan(channel->raw_current)) { //ESP_LOGI(TAG, "RAW BATTERY IS NAN"); } else { if (isnan(ema_battery) || isnan(alpha)) { @@ -225,7 +316,9 @@ esp_err_t process_bridge_current(bridge_t bridge) { } // Apply the offset - channel->current = channel->ema_current - channel->az_offset; + channel->current = channel->raw_current - channel->az_offset; + channel->raw_current = channel->raw_current - channel->az_offset; + channel->current_spike = channel->raw_current - last_current; // PARAMETERS FOR E-FUSING ALGORITHM @@ -245,31 +338,21 @@ esp_err_t process_bridge_current(bridge_t bridge) { case BRIDGE_AUX: I_nominal = get_param_value_t(PARAM_EFUSE_INOM_3).f32; break; - } - - // Normalize the current as a fraction of rated current + default: break; + } + + // Normalize the current as a fraction of rated current float I_norm = fabsf(channel->current / I_nominal); - // Instant trip on extreme overcurrent - but with inrush tolerance - if (I_norm >= get_param_value_t(PARAM_EFUSE_KINST).f32) { - // Start tracking if this is the first time we've seen overcurrent - if (channel->inrush_start_time == 0) { - channel->inrush_start_time = now; - } - + // Instant trip on extreme overcurrent + if (fsm_now > channel->on_us + get_param_value_t(PARAM_EFUSE_INRUSH_US).u32 + && I_norm >= get_param_value_t(PARAM_EFUSE_KINST).f32) { // Check if overcurrent has persisted long enough - int64_t inrush_duration = now - channel->inrush_start_time; - if (inrush_duration >= get_param_value_t(PARAM_EFUSE_INRUSH_US).u32) { - channel->tripped = true; - channel->trip_time = now; - channel->inrush_start_time = 0; // Reset for next time - //ESP_LOGI(TAG, "FUSE TRIP: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat); - return ESP_OK; // no more processing, if we're over, we're over - } + channel->tripped = true; + channel->trip_time = fsm_now; + //ESP_LOGI(TAG, "FUSE TRIP: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat); + return ESP_OK; // no more processing, if we're over, we're over // Still in overcurrent but within inrush tolerance window - don't trip yet - } else { - // Current dropped below threshold - reset inrush timer - channel->inrush_start_time = 0; } // Accumulate heat @@ -289,19 +372,20 @@ esp_err_t process_bridge_current(bridge_t bridge) { // Ergo, heat is measured in seconds if (channel->heat > get_param_value_t(PARAM_EFUSE_HEAT_THRESH).f32) { channel->tripped = true; - channel->trip_time = now; + channel->trip_time = fsm_now; // If we're not overheated // And enough time has passed // Go ahead and reset the e-fuse } else if (channel->tripped && - (now - channel->trip_time) > get_param_value_t(PARAM_EFUSE_TCOOL).u32) { + (fsm_now - channel->trip_time) > get_param_value_t(PARAM_EFUSE_TCOOL).u32) { channel->tripped = false; // channel.heat = 0.0f // I think we should wait for the e-fuse to catch up } - //if (bridge == BRIDGE_JACK) - //ESP_LOGI(TAG, "FUSE: raw_a: %+.4f cur: %+.4f Inorm: %+.5f HEAT:%+2.5f", raw_a, channel->current, I_norm, channel->heat); + //if (bridge == BRIDGE_JACK) ESP_LOGI(TAG, "TIME: %lld", (long long) fsm_now); + + //if (bridge == BRIDGE_JACK) ESP_LOGI(TAG, "FUSE: trip [%d] %lld, raw_a: %+.4f cur: %+.4f Inorm: %+.5f HEAT:%+2.5f", channel->tripped, channel->trip_time, channel->raw_current, channel->current, I_norm, channel->heat); return ESP_OK; } @@ -313,9 +397,13 @@ float get_bridge_A(bridge_t bridge) if (bridge >= N_BRIDGES) return NAN; return isens[bridge].current; } +float get_bridge_raw_A(bridge_t bridge) +{ + if (bridge >= N_BRIDGES) return NAN; + return isens[bridge].raw_current; +} - -float get_bridge_heat(bridge_t bridge) { +float efuse_get_heat(bridge_t bridge) { if (bridge >= N_BRIDGES) return NAN; return isens[bridge].heat; } @@ -327,57 +415,14 @@ float get_battery_V(void) return get_raw_battery_voltage(); } -// === Public E-Fuse Controls === -/*void efuse_reset_all(void) -{ - for (uint8_t i = 0; i < N_BRIDGES; i++) { - isens[i].heat = 0.0f; - isens[i].tripped = false; - } -}*/ - -bool efuse_is_tripped(bridge_t bridge) +efuse_trip_t efuse_get(bridge_t bridge) { if (bridge >= N_BRIDGES) return false; return isens[bridge].tripped; } - -// === Power Management Task === -void power_mgmt_task(void *param) { - esp_task_wdt_add(NULL); - - - - TickType_t xLastWakeTime = xTaskGetTickCount(); - const TickType_t xFrequency = pdMS_TO_TICKS(UPDATE_MS); - - while (1) { - vTaskDelayUntil(&xLastWakeTime, xFrequency); - now = esp_timer_get_time(); // us - - /*if (now - last_wake_time < period) { - uint32_t delay_us = (period - (now - last_wake_time)) / 1000; - if (delay_us > 0) vTaskDelay(pdMS_TO_TICKS(delay_us)); - continue; - } - last_wake_time = now;*/ - - // Sample currents - for (uint8_t i = 0; i < N_BRIDGES; i++) { - process_bridge_current(i); - } - - process_battery_voltage(); - esp_task_wdt_reset(); - } -} - -esp_err_t power_init() { - xTaskCreate(power_mgmt_task, "PWR", 4096, NULL, 5, NULL); - - return ESP_OK; -} - -esp_err_t power_stop() { - return ESP_OK; +void efuse_set(bridge_t bridge, efuse_trip_t state) +{ + if (bridge >= N_BRIDGES) return; + isens[bridge].tripped = state; + isens[bridge].trip_time = fsm_now; } \ No newline at end of file diff --git a/main/power_mgmt.h b/main/power_mgmt.h index 550eeda..16dfe10 100644 --- a/main/power_mgmt.h +++ b/main/power_mgmt.h @@ -12,19 +12,36 @@ #include #include #include "esp_err.h" +#include "i2c.h" +typedef enum { + EFUSE_OK = 0, + EFUSE_OVERCURRENT = 1, + EFUSE_OVERHEAT = 2, +} efuse_trip_t; //void efuse_reset_all(void); // Clear all trip states (manual/programmatic reset) -bool efuse_is_tripped(bridge_t bridge); // Query if bridge is currently faulted +void efuse_clear (bridge_t bridge); +efuse_trip_t efuse_get (bridge_t bridge); // Query if bridge is currently faulted +float efuse_get_heat(bridge_t bridge); +void efuse_set(bridge_t bridge, efuse_trip_t state); float get_bridge_A(bridge_t bridge); +float get_bridge_raw_A(bridge_t bridge); float get_battery_V(); -float get_bridge_heat(bridge_t bridge); -void set_autozero(bridge_t bridge); +void disable_autozero(bridge_t bridge); +bool get_bridge_overcurrent(bridge_t bridge, float threshold); +bool get_bridge_spike(bridge_t bridge, float threshold); + +esp_err_t process_bridge_current(bridge_t bridge); +esp_err_t process_battery_voltage(); esp_err_t adc_init(); esp_err_t power_init(); esp_err_t power_stop(); + +esp_err_t driveRelays(relay_port_t relay_state); + #endif /* MAIN_POWER_MGMT_H_ */ \ No newline at end of file diff --git a/main/rf_433.c b/main/rf_433.c index 9ff676a..1088dbd 100644 --- a/main/rf_433.c +++ b/main/rf_433.c @@ -155,10 +155,10 @@ static void rf_433_receiver_task(void* param) { // Compare just the code (lower 32 bits) if ((uint32_t)match == code && code!=0) { switch (i) { - case 0: pulseOverride(RELAY_A1); pulseOverride(RELAY_A3); break; - case 1: pulseOverride(RELAY_B1); pulseOverride(RELAY_A3); break; - case 2: pulseOverride(RELAY_A2); break; - case 3: pulseOverride(RELAY_B2); break; + case 0: pulseOverride(FSM_OVERRIDE_DRIVE_FWD); break; + case 1: pulseOverride(FSM_OVERRIDE_DRIVE_REV); break; + case 2: pulseOverride(FSM_OVERRIDE_JACK_UP); break; + case 3: pulseOverride(FSM_OVERRIDE_JACK_DOWN); break; default: break; } } @@ -210,7 +210,7 @@ static void rf_433_receiver_task(void* param) { } esp_err_t rf_433_init() { - xTaskCreate(rf_433_receiver_task, TAG, 4096, NULL, 10, NULL); + xTaskCreate(rf_433_receiver_task, TAG, 4096, NULL, 5, NULL); return ESP_OK; } diff --git a/main/sensors.c b/main/sensors.c index 96a29df..a524989 100644 --- a/main/sensors.c +++ b/main/sensors.c @@ -58,10 +58,31 @@ static void IRAM_ATTR sensor_isr_handler(void* arg) { if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); } -// Debounce task: Processes queue, updates state & count -static void sensor_debounce_task(void* param) { - esp_task_wdt_add(NULL); - sensor_event_t evt; +esp_err_t sensors_init() { + + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << sensor_pins[0]) | (1ULL << sensor_pins[1]), + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_ANYEDGE, + }; + ESP_ERROR_CHECK(gpio_config(&io_conf)); + + sensor_event_queue = xQueueCreate(16, sizeof(sensor_event_t)); + if (!sensor_event_queue) { + ESP_LOGE(TAG, "Failed to create sensor queue"); + return ESP_FAIL; + } + + // Install ISR service + ESP_ERROR_CHECK(gpio_install_isr_service(0)); + + for (uint8_t i = 0; i < N_SENSORS; i++) { + ESP_ERROR_CHECK(gpio_isr_handler_add(sensor_pins[i], sensor_isr_handler, INT2VOIDP(sensor_pins[i]))); + sensor_stable_state[i] = !gpio_get_level(sensor_pins[i]); + } + //static uint64_t last_processed_time[N_SENSORS] = {0}; // Initialize stable state @@ -82,61 +103,65 @@ static void sensor_debounce_task(void* param) { safety_high_start_time = esp_timer_get_time(); } + return ESP_OK; +} + +void sensors_check() { + sensor_event_t evt; + uint8_t i = 0; + + if (xQueueReceive(sensor_event_queue, &evt, pdMS_TO_TICKS(10)) == pdTRUE) { + i = evt.sensor_id; + + ESP_LOGI("SENS", "EVENT %d", i); + + bool current_raw = !gpio_get_level(sensor_pins[i]); - while (1) { - if (xQueueReceive(sensor_event_queue, &evt, pdMS_TO_TICKS(10)) == pdTRUE) { - i = evt.sensor_id; - - ESP_LOGI("SENS", "EVENT %d", i); - - bool current_raw = !gpio_get_level(sensor_pins[i]); - - sensor_stable_state[i] = current_raw; - - if (current_raw && !last_raw_state[i]){ - ESP_LOGI("SENS", "FALLING"); - sensor_count[i]++; - } - if (!current_raw && last_raw_state[i]){ - ESP_LOGI("SENS", "RISING"); - sensor_count[i]++; - } - - last_raw_state[i] = current_raw; + sensor_stable_state[i] = current_raw; + + if (current_raw && !last_raw_state[i]){ + ESP_LOGI("SENS", "FALLING"); + sensor_count[i]++; + } + if (!current_raw && last_raw_state[i]){ + ESP_LOGI("SENS", "RISING"); + sensor_count[i]++; } - // Handle safety sensor debouncing with asymmetric timing - bool safety_current = !gpio_get_level(sensor_pins[SENSOR_SAFETY]); - uint64_t now = esp_timer_get_time(); - - if (safety_current) { - // Safety sensor is LOW (active) - if (safety_low_start_time == 0) { - // First time going low, start timing - safety_low_start_time = now; - safety_high_start_time = 0; - ESP_LOGI(TAG, "Safety sensor went LOW, starting make timer"); - } else if (!is_safe && (now - safety_low_start_time >= SAFETY_MAKE_DEBOUNCE_US)) { - is_safe = true; - ESP_LOGW(TAG, "SAFETY MADE - Relays enabled"); - } - } else { - // Safety sensor is HIGH (inactive) - if (safety_high_start_time == 0) { - // First time going high, start timing - safety_high_start_time = now; - safety_low_start_time = 0; - ESP_LOGI(TAG, "Safety sensor went HIGH, starting break timer"); - } else if (is_safe && (now - safety_high_start_time >= SAFETY_BREAK_DEBOUNCE_US)) { - is_safe = false; - i2c_set_relays(0); - ESP_LOGI(TAG, "SAFETY BREAK - Relays disabled"); - } - } - - esp_task_wdt_reset(); + last_raw_state[i] = current_raw; } + + // Handle safety sensor debouncing with asymmetric timing + bool safety_current = !gpio_get_level(sensor_pins[SENSOR_SAFETY]); + uint64_t now = esp_timer_get_time(); + + if (safety_current) { + // Safety sensor is LOW (active) + if (safety_low_start_time == 0) { + // First time going low, start timing + safety_low_start_time = now; + safety_high_start_time = 0; + ESP_LOGI(TAG, "Safety sensor went LOW, starting make timer"); + } else if (!is_safe && (now - safety_low_start_time >= SAFETY_MAKE_DEBOUNCE_US)) { + is_safe = true; + ESP_LOGW(TAG, "SAFETY MADE - Relays enabled"); + } + } else { + // Safety sensor is HIGH (inactive) + if (safety_high_start_time == 0) { + // First time going high, start timing + safety_high_start_time = now; + safety_low_start_time = 0; + ESP_LOGI(TAG, "Safety sensor went HIGH, starting break timer"); + } else if (is_safe && (now - safety_high_start_time >= SAFETY_BREAK_DEBOUNCE_US)) { + is_safe = false; + i2c_set_relays((relay_port_t){.raw=0}); + ESP_LOGI(TAG, "SAFETY BREAK - Relays disabled"); + } + } + + esp_task_wdt_reset(); } int8_t pack_sensors() { @@ -148,7 +173,7 @@ int8_t pack_sensors() { return ret; } -esp_err_t sensors_init() { +/*esp_err_t sensors_init() { gpio_config_t io_conf = { .pin_bit_mask = (1ULL << sensor_pins[0]) | (1ULL << sensor_pins[1]), .mode = GPIO_MODE_INPUT, @@ -185,7 +210,7 @@ esp_err_t sensors_stop() { vQueueDelete(sensor_event_queue); return ESP_OK; -} +}*/ // Public API bool get_sensor(sensor_t i) { diff --git a/main/sensors.h b/main/sensors.h index 5bdf0ab..7e3ba05 100644 --- a/main/sensors.h +++ b/main/sensors.h @@ -33,6 +33,7 @@ bool get_is_safe(void); int8_t pack_sensors(); esp_err_t sensors_init(); -esp_err_t sensors_stop(); +void sensors_check(); +//esp_err_t sensors_stop(); #endif /* MAIN_SENSORS_H_ */ \ No newline at end of file diff --git a/main/storage.c b/main/storage.c index e59c5f8..6774de6 100644 --- a/main/storage.c +++ b/main/storage.c @@ -29,8 +29,9 @@ static bool log_task_running = false; // Log queue entry structure typedef struct { - uint8_t data[LOG_MAX_PAYLOAD + 1]; // +1 for length byte + uint8_t data[LOG_MAX_PAYLOAD]; // +1 for length byte uint8_t len; + uint8_t type; } log_queue_entry_t; // ============================================================================ @@ -48,9 +49,6 @@ typedef struct { // Helper macro to check if a byte is a valid log type #define IS_VALID_LOG_TYPE(x) ((x) >= 0xC0 && (x) <= 0xCF) -// Maximum payload size per log entry (255 max due to 1-byte size field) -#define LOG_MAX_PAYLOAD 255 - // ============================================================================ // PARAMETER TABLE GENERATION // ============================================================================ @@ -498,32 +496,50 @@ static inline uint32_t log_sector_end(uint32_t x) { } -// Helper function to check if a sector is erased (all 0xFF) -static bool is_sector_erased(uint32_t sector_offset) { - uint8_t buf[256]; - esp_err_t err = esp_partition_read(storage_partition, sector_offset, buf, 256); +// Helper function to check if a sector is erased (starts with 0xFF) +static bool is_sector_erased(uint32_t x) { + uint8_t buf; //[256]; + esp_err_t err = esp_partition_read(storage_partition, LOG_START_OFFSET + x * FLASH_SECTOR_SIZE, &buf, 1); if (err != ESP_OK) return false; + if (buf == 0xFF) return true; - for (int i = 0; i < 256; i++) { + /*for (int i = 0; i < 256; i++) { if (buf[i] != 0xFF) return false; - } + }*/ + return false; +} + +static bool is_sector_full(uint32_t x) { + uint8_t buf; //[256]; + esp_err_t err = esp_partition_read(storage_partition, LOG_START_OFFSET + (x+1) * FLASH_SECTOR_SIZE - 1, &buf, 1); + if (err != ESP_OK) return false; + if (buf == 0xFF) return false; + + /*for (int i = 0; i < 256; i++) { + if (buf[i] != 0xFF) return false; + }*/ return true; } +static inline void find_head_tail(int32_t num_sectors, int32_t *head, int32_t *tail) { + +} + // Helper function to check if a sector has data (contains non-0xFF, non-0x00 bytes) -static bool sector_has_data(uint32_t sector_offset) { - uint8_t buf[256]; - esp_err_t err = esp_partition_read(storage_partition, sector_offset, buf, 256); +/*static bool sector_has_data(uint32_t sector_offset) { + uint8_t buf; //[256]; + esp_err_t err = esp_partition_read(storage_partition, sector_offset, &buf, 256); if (err != ESP_OK) return false; + if (buf ) for (int i = 0; i < 256; i++) { if (buf[i] != 0xFF && buf[i] != 0x00) return true; } return false; -} +}*/ // Replace log_write with this non-blocking version: -esp_err_t log_write(uint8_t* buf, uint8_t len) { +esp_err_t log_write(uint8_t* buf, uint8_t len, uint8_t type) { if (!log_initialized || storage_partition == NULL) { ESP_LOGE(TAG, "Logging not initialized"); return ESP_FAIL; @@ -539,9 +555,15 @@ esp_err_t log_write(uint8_t* buf, uint8_t len) { return ESP_FAIL; } + if (type == 0xFF) { + ESP_LOGE(TAG, "Attempt to log with type=0xFF; not allowed"); + return ESP_ERR_INVALID_ARG; + } + // Create queue entry log_queue_entry_t entry; entry.len = len; + entry.type = type; memcpy(entry.data, buf, len); // Try to send to queue (non-blocking) @@ -554,7 +576,7 @@ esp_err_t log_write(uint8_t* buf, uint8_t len) { } // The actual blocking write function (called by the task) -static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len) { +static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len, uint8_t type) { if (!log_initialized || storage_partition == NULL) { ESP_LOGE(TAG, "Logging not initialized"); return ESP_FAIL; @@ -568,8 +590,8 @@ static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len) { if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); // check if we will overrun the sector - if (log_head_offset + len+1 >= log_sector_end(log_head_offset)) { - ESP_LOGI(TAG, "WILL OVERRUN (%ld >= %ld)", (long)log_head_offset + len+1, (long)log_sector_end(log_head_offset)); + if (log_head_offset + len+2 >= log_sector_end(log_head_offset)) { + //ESP_LOGI(TAG, "WILL OVERRUN (%ld >= %ld)", (long)log_head_offset + len+2, (long)log_sector_end(log_head_offset)); // zero the rest of sector char zeros[256] = {0}; esp_partition_write(storage_partition, @@ -588,7 +610,7 @@ static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len) { // Erase the next sector if (err == ESP_OK && check_byte != 0xFF) { - ESP_LOGI(TAG, "Erasing sector %lu for log", (unsigned long)log_head_offset); + //ESP_LOGI(TAG, "Erasing sector %lu for log", (unsigned long)log_head_offset); err = esp_partition_erase_range(storage_partition, log_head_offset, FLASH_SECTOR_SIZE); @@ -606,13 +628,15 @@ static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len) { if (log_tail_offset >= storage_partition->size) log_tail_offset = LOG_START_OFFSET; - ESP_LOGI(TAG, "Tail/Head are now %lu/%lu", + ESP_LOGI(TAG, "Erased; Tail/Head are now %lu/%lu", (unsigned long)log_tail_offset, (unsigned long)log_head_offset); } + len++; // account for type bit esp_partition_write(storage_partition, log_head_offset, &len, 1); - esp_partition_write(storage_partition, log_head_offset+1, buf, len); + esp_partition_write(storage_partition, log_head_offset+1, buf, len-1); + esp_partition_write(storage_partition, log_head_offset+len, &type, 1); log_head_offset+=len+1; ESP_LOGI(TAG, "Wrote; Tail/Head are now %lu/%lu", @@ -629,17 +653,20 @@ static void log_writer_task(void *pvParameters) { ESP_LOGI(TAG, "Log writer task started"); while (log_task_running) { + // Feed watchdog + //esp_task_wdt_reset(); // Wait for log entry (with timeout to check running flag) if (xQueueReceive(log_queue, &entry, pdMS_TO_TICKS(100)) == pdTRUE) { // Write the log entry (blocking) - esp_err_t err = log_write_blocking(entry.data, entry.len); + esp_err_t err = log_write_blocking(entry.data, entry.len, entry.type); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to write log entry: %s", esp_err_to_name(err)); } + + //esp_task_wdt_reset(); } - // Feed watchdog - esp_task_wdt_reset(); + } ESP_LOGI(TAG, "Log writer task stopping"); @@ -664,28 +691,50 @@ esp_err_t log_init() { log_head_offset = LOG_START_OFFSET; log_tail_offset = LOG_START_OFFSET; - // ... [INCLUDE THE BISECTION ALGORITHM FROM EARLIER HERE] ... - // Binary search to find the first erased sector - int32_t left = 0; - int32_t right = num_sectors - 1; - int32_t head_sector = -1; + // Binary search for the first non-full sector + int32_t l = 0; + int32_t r = num_sectors - 1; + int32_t head_sector = 0; + int32_t tail_sector = 0; - while (left <= right) { - int32_t mid = left + (right - left) / 2; - uint32_t sector_offset = LOG_START_OFFSET + mid * FLASH_SECTOR_SIZE; - - esp_task_wdt_reset(); - - bool erased = is_sector_erased(sector_offset); - bool prev_has_data = (mid > 0) ? sector_has_data(LOG_START_OFFSET + (mid-1) * FLASH_SECTOR_SIZE) : false; - - if (erased && (mid == 0 || prev_has_data)) { - head_sector = mid; - break; - } else if (erased) { - right = mid - 1; + while (l < r) { + int32_t m = (l + r) / 2; + if (is_sector_full(m)) { + l = m + 1; } else { - left = mid + 1; + r = m; + } + } + + int32_t first_non_full = l; + + // Determine head based on what the first non-full sector is + if (first_non_full >= num_sectors) { + // All sectors are full (wraparound case) + head_sector = 0; + } else if (is_sector_erased(first_non_full)) { + // First non-full is erased -> head is this erased sector + head_sector = first_non_full; + } else { + // First non-full is partial -> head is this partial sector + head_sector = first_non_full; + } + + // Binary search for tail: first full sector after head (with wraparound) + // Search from head+1 to head+num_sectors-1 (wrapping around) + l = 1; + r = num_sectors - 1; + + while (l <= r) { + int32_t m = (l + r) / 2; + int32_t sector = (head_sector + m) % num_sectors; + + if (is_sector_full(sector)) { + // Found a full sector, but need the FIRST one + tail_sector = sector; + r = m - 1; + } else { + l = m + 1; } } @@ -737,31 +786,6 @@ esp_err_t log_init() { log_head_offset = cursor; } - // Find tail - int32_t tail_sector = -1; - - for (int32_t i = head_sector + 1; i < num_sectors; i++) { - uint32_t sector_offset = LOG_START_OFFSET + i * FLASH_SECTOR_SIZE; - esp_task_wdt_reset(); - - if (sector_has_data(sector_offset)) { - tail_sector = i; - break; - } - } - - if (tail_sector == -1 && head_sector > 0) { - for (int32_t i = 0; i < head_sector; i++) { - uint32_t sector_offset = LOG_START_OFFSET + i * FLASH_SECTOR_SIZE; - esp_task_wdt_reset(); - - if (sector_has_data(sector_offset)) { - tail_sector = i; - break; - } - } - } - if (tail_sector >= 0) { log_tail_offset = LOG_START_OFFSET + tail_sector * FLASH_SECTOR_SIZE; } else { @@ -800,8 +824,10 @@ esp_err_t log_init() { } // Add the task to watchdog - esp_task_wdt_add(log_task_handle); - + //if (esp_task_wdt_add(log_task_handle) != ESP_OK) { + // ESP_LOGW(TAG, "Log task already subscribed to watchdog or failed to add"); + //} + ESP_LOGI(TAG, "Log writer task created successfully"); if (log_mutex) xSemaphoreGive(log_mutex); @@ -811,17 +837,17 @@ esp_err_t log_init() { // Update storage_deinit to stop the task void storage_deinit(void) { // Stop the log writer task - if (log_task_running) { - log_task_running = false; - - // Wait a bit for task to finish - vTaskDelay(pdMS_TO_TICKS(200)); - - if (log_task_handle != NULL) { - esp_task_wdt_delete(log_task_handle); - log_task_handle = NULL; - } - } + if (log_task_running) { + log_task_running = false; + vTaskDelay(pdMS_TO_TICKS(200)); + + if (log_task_handle != NULL) { + // Don't try to delete from watchdog - task was never added + // esp_task_wdt_delete(log_task_handle); // <-- REMOVE THIS LINE + vTaskDelay(pdMS_TO_TICKS(100)); + log_task_handle = NULL; + } + } // Delete the queue if (log_queue != NULL) { @@ -836,3 +862,324 @@ void storage_deinit(void) { log_mutex = NULL; } } + + + + + + +/* + * ADDITIONS TO storage.c + * + * Add these functions to storage.c to support the test suite. + * These provide the ability to erase all log sectors, simulate power cycles, + * and read back log entries for verification. + */ + +// Add these function declarations to storage.h: +/* +esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type); +void log_read_reset(void); +esp_err_t log_erase_all_sectors(void); +esp_err_t log_simulate_power_cycle(void); +*/ + +// Add this static variable near the top of storage.c with other log static variables +// (around line 118, after log_tail_offset and log_initialized): + +static uint32_t log_read_cursor = 0; + + +// Add these functions to storage.c (after the existing log functions): + +/* + * ADDITIONS TO storage.c + * + * Add these functions to storage.c to support the test suite. + * These provide the ability to erase all log sectors, simulate power cycles, + * and read back log entries for verification. + */ + +// Add these function declarations to storage.h: +/* +esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type); +void log_read_reset(void); +esp_err_t log_erase_all_sectors(void); +esp_err_t log_simulate_power_cycle(void); +*/ + +// Add this static variable near the top of storage.c with other log static variables +// (around line 118, after log_tail_offset and log_initialized): +/* +static uint32_t log_read_cursor = 0; +*/ + +// Add these functions to storage.c (after the existing log functions): + +/** + * @brief Read a log entry from the current read cursor position + * + * Reads a log entry from flash starting at the current read cursor and advances + * the cursor to the next entry. The read cursor is independent of tail - reading + * does NOT affect the tail pointer (which marks the boundary of valid data). + * + * The tail is only moved by the log write system when sectors need to be erased + * during wraparound. Reading is completely non-destructive. + * + * This is a test/debug function - the production logging system is write-only. + * + * Log entry format in flash: + * [1 byte: length] [length-1 bytes: data] [1 byte: type] + * + * @param len Pointer to store the entry length (data length, not including type byte) + * @param buf Buffer to store the entry data (must be at least LOG_MAX_PAYLOAD bytes) + * @param type Pointer to store the entry type + * @return ESP_OK on success, ESP_ERR_NOT_FOUND if no more entries, error code otherwise + */ +esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type) { + // NOTE: log_read_cursor must be declared as a file-scope static variable + // Add this declaration near the other log static variables in storage.c: + // static uint32_t log_read_cursor = 0; + + if (!log_initialized || storage_partition == NULL) { + ESP_LOGE(TAG, "Logging not initialized"); + return ESP_ERR_INVALID_STATE; + } + + if (len == NULL || buf == NULL || type == NULL) { + ESP_LOGE(TAG, "NULL pointer passed to log_read"); + return ESP_ERR_INVALID_ARG; + } + + if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); + + // Initialize read cursor to tail on first read (when cursor is 0) + if (log_read_cursor == 0) { + log_read_cursor = log_tail_offset; + } + + // Check if we've caught up to head (no more entries to read) + if (log_read_cursor == log_head_offset) { + if (log_mutex) xSemaphoreGive(log_mutex); + return ESP_ERR_NOT_FOUND; + } + + // Read the length byte + uint8_t entry_len; + esp_err_t err = esp_partition_read(storage_partition, log_read_cursor, &entry_len, 1); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to read entry length at offset %lu", (unsigned long)log_read_cursor); + if (log_mutex) xSemaphoreGive(log_mutex); + return err; + } + + // Check for erased flash (0xFF) - means we've reached the end + if (entry_len == 0xFF) { + if (log_mutex) xSemaphoreGive(log_mutex); + return ESP_ERR_NOT_FOUND; + } + + // Check for sector padding (0x00) - skip to next sector + if (entry_len == 0x00) { + // Move to next sector + uint32_t next_sector = ((log_read_cursor / FLASH_SECTOR_SIZE) + 1) * FLASH_SECTOR_SIZE; + + // Handle wraparound + if (next_sector >= storage_partition->size) { + log_read_cursor = LOG_START_OFFSET; + } else { + log_read_cursor = next_sector; + } + + if (log_mutex) xSemaphoreGive(log_mutex); + return log_read(len, buf, type); // Recursive call to read from next sector + } + + // Validate length + if (entry_len > LOG_MAX_PAYLOAD + 1) { // +1 for type byte included in length + ESP_LOGE(TAG, "Invalid entry length %d at offset %lu", entry_len, (unsigned long)log_read_cursor); + if (log_mutex) xSemaphoreGive(log_mutex); + return ESP_ERR_INVALID_SIZE; + } + + // Read the data (length-1 bytes, since length includes the type byte) + uint8_t data_len = entry_len - 1; + err = esp_partition_read(storage_partition, log_read_cursor + 1, buf, data_len); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to read entry data at offset %lu", (unsigned long)(log_read_cursor + 1)); + if (log_mutex) xSemaphoreGive(log_mutex); + return err; + } + + // Read the type byte + uint8_t entry_type; + err = esp_partition_read(storage_partition, log_read_cursor + entry_len, &entry_type, 1); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to read entry type at offset %lu", (unsigned long)(log_read_cursor + entry_len)); + if (log_mutex) xSemaphoreGive(log_mutex); + return err; + } + + // Validate type + if (!IS_VALID_LOG_TYPE(entry_type)) { + ESP_LOGW(TAG, "Invalid log type 0x%02X at offset %lu", entry_type, (unsigned long)(log_read_cursor + entry_len)); + // Continue anyway - might be valid data with unknown type + } + + // Set output parameters + *len = data_len; + *type = entry_type; + + // Advance read cursor (NOT tail - tail is only moved by writes during wraparound) + log_read_cursor += entry_len + 1; // +1 for the type byte after data + + // Handle wraparound + if (log_read_cursor >= storage_partition->size) { + log_read_cursor = LOG_START_OFFSET; + } + + if (log_mutex) xSemaphoreGive(log_mutex); + return ESP_OK; +} + +/** + * @brief Reset the log read cursor to start reading from the beginning + * + * Resets the internal read cursor so that the next call to log_read() + * will start reading from the tail (oldest valid entry). + * This is useful for tests that need to re-read the log. + * + * IMPORTANT: This does NOT affect the tail pointer. The tail marks the + * boundary of valid data and is only moved by the write system. + */ +void log_read_reset(void) { + if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); + log_read_cursor = 0; // Reset to 0 so next read starts from tail + if (log_mutex) xSemaphoreGive(log_mutex); +} + +/** + * @brief Erase all sectors in the log area + * + * This function erases the entire log area of the flash partition, + * resetting it to a clean state. Useful for testing. + * + * @return ESP_OK on success, error code otherwise + */ +esp_err_t log_erase_all_sectors(void) { + if (storage_partition == NULL) { + ESP_LOGE(TAG, "Storage partition not initialized"); + return ESP_ERR_INVALID_STATE; + } + // Stop the log writer task + if (log_task_running) { + log_task_running = false; + vTaskDelay(pdMS_TO_TICKS(200)); + + if (log_task_handle != NULL) { + // Don't try to delete from watchdog - task was never added + // esp_task_wdt_delete(log_task_handle); // <-- REMOVE THIS LINE + vTaskDelay(pdMS_TO_TICKS(100)); + log_task_handle = NULL; + } + } + + // Clear the queue + if (log_queue != NULL) { + xQueueReset(log_queue); + } + + if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); + + uint32_t log_area_size = storage_partition->size - LOG_START_OFFSET; + + ESP_LOGI(TAG, "Erasing all log sectors (%lu bytes)...", (unsigned long)log_area_size); + + esp_err_t err = esp_partition_erase_range(storage_partition, + LOG_START_OFFSET, + log_area_size); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase log area: %s", esp_err_to_name(err)); + if (log_mutex) xSemaphoreGive(log_mutex); + return err; + } + + ESP_LOGI(TAG, "All log sectors erased successfully"); + + if (log_mutex) xSemaphoreGive(log_mutex); + + // Reinitialize + log_initialized = false; + log_read_cursor = 0; + + err = log_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to reinitialize log after erase"); + return err; + } + + return ESP_OK; +} + +/** + * @brief Simulate a power cycle by re-running log initialization + * + * This function simulates what happens when the device powers off and back on: + * - Clears the in-memory head/tail tracking (simulated by marking as uninitialized) + * - Re-runs log_init() to scan flash and recover head/tail positions + * + * This is crucial for testing that the log system can correctly recover its + * state from flash after a power loss. + * + * @return ESP_OK on success, error code otherwise + */ +esp_err_t log_simulate_power_cycle(void) { + ESP_LOGI(TAG, "Simulating power cycle..."); + // Stop the log writer task + if (log_task_running) { + log_task_running = false; + vTaskDelay(pdMS_TO_TICKS(200)); + + if (log_task_handle != NULL) { + // Don't try to delete from watchdog - task was never added + // esp_task_wdt_delete(log_task_handle); // <-- REMOVE THIS LINE + vTaskDelay(pdMS_TO_TICKS(100)); + log_task_handle = NULL; + } + } + + // Clear the queue + if (log_queue != NULL) { + xQueueReset(log_queue); + } + + // Mark log as uninitialized (simulates losing RAM state) + log_initialized = false; + + // Reset read cursor (simulates losing RAM state) + log_read_cursor = 0; + + // Re-run log initialization to scan flash and recover state + esp_err_t err = log_init(); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to reinitialize log after simulated power cycle"); + return err; + } + + ESP_LOGI(TAG, "Power cycle simulation complete. Head=%lu, Tail=%lu", + (unsigned long)log_head_offset, (unsigned long)log_tail_offset); + + return ESP_OK; +} + +esp_err_t log_write_blocking_test(uint8_t* buf, uint8_t len, uint8_t type) { + // Check queue space - wait if nearly full + while (uxQueueSpacesAvailable(log_queue) < 2) { + vTaskDelay(pdMS_TO_TICKS(10)); + esp_task_wdt_reset(); + } + + return log_write(buf, len, type); +} \ No newline at end of file diff --git a/main/storage.h b/main/storage.h index f5c925f..bfd0a12 100644 --- a/main/storage.h +++ b/main/storage.h @@ -28,7 +28,7 @@ // 0xCA-0xCF reserved for future use // Maximum payload size per log entry (255 max due to 1-byte size field) -#define LOG_MAX_PAYLOAD 255 +#define LOG_MAX_PAYLOAD 200 // Helper macro to check if a byte is a valid log type #define IS_VALID_LOG_TYPE(x) ((x) >= 0xC0 && (x) <= 0xCF) @@ -75,10 +75,10 @@ typedef struct { PARAM_DEF(ADC_ALPHA_IAZ, f32, 0.005, "-") \ PARAM_DEF(ADC_DB_IAZ, f32, 5.0, "A") \ PARAM_DEF(EFUSE_INOM_1, f32, 40.0, "A") \ - PARAM_DEF(EFUSE_INOM_2, f32, 6.0, "A") \ + PARAM_DEF(EFUSE_INOM_2, f32, 14.0, "A") \ PARAM_DEF(EFUSE_INOM_3, f32, 4.0, "A") \ PARAM_DEF(EFUSE_HEAT_THRESH, f32, 60.0, "i/i^2-s") \ - PARAM_DEF(EFUSE_KINST, f32, 5.0, "i/i") \ + PARAM_DEF(EFUSE_KINST, f32, 2.0, "i/i") \ PARAM_DEF(EFUSE_TAUCOOL, f32, 0.2, "i") \ PARAM_DEF(EFUSE_TCOOL, u32, 5000000, "us") \ PARAM_DEF(LOW_PROTECTION_V, f32, 10.0, "V") \ @@ -91,13 +91,14 @@ typedef struct { PARAM_DEF(WIFI_CHANNEL, u16, 6, "") \ PARAM_DEF(WIFI_SSID, str, "sc.local", "") \ PARAM_DEF(WIFI_PASS, str, "password", "") \ - PARAM_DEF(EFUSE_INRUSH_US, u32, 300000, "us") \ - PARAM_DEF(JACK_I_UP, f32, 5.0, "A") \ - PARAM_DEF(JACK_I_DOWN, f32, 8.0, "A") \ + PARAM_DEF(EFUSE_INRUSH_US, u32, 250000, "us") \ + PARAM_DEF(JACK_I_UP, f32, 8.0, "A") \ + PARAM_DEF(JACK_I_DOWN, f32, 15.0, "A") \ PARAM_DEF(V_SENS_K, f32, 0.00766666666, "V/mV") \ PARAM_DEF(BUILD_VERSION, str, "undefined", "") \ PARAM_DEF(SAFETY_BREAK_US, u32, 300000, "") \ PARAM_DEF(SAFETY_MAKE_US, u32, 1000000, "") \ + PARAM_DEF(JACK_IS_DOWN, f32, 8.0, "A") \ // Generate enum for parameter indices #define PARAM_DEF(name, type, default_val, unit) PARAM_##name, @@ -163,7 +164,7 @@ esp_err_t commit_params(void); // Logging functions esp_err_t log_init(void); -esp_err_t log_write(uint8_t* buf, uint8_t len); +esp_err_t log_write(uint8_t* buf, uint8_t len, uint8_t type); uint32_t log_get_head(void); uint32_t log_get_tail(void); uint32_t log_get_offset(void); @@ -176,4 +177,9 @@ esp_err_t write_dummy_log_1(void); esp_err_t write_dummy_log_2(void); esp_err_t write_dummy_log_3(void); +esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type); +esp_err_t log_erase_all_sectors(void); +esp_err_t log_simulate_power_cycle(void); +void log_read_reset(void); +esp_err_t log_write_blocking_test(uint8_t* buf, uint8_t len, uint8_t type); #endif // STORAGE_H \ No newline at end of file diff --git a/main/webpage.h b/main/webpage.h index 066673a..a646290 100644 --- a/main/webpage.h +++ b/main/webpage.h @@ -1,3 +1,3 @@ -const char html_content[] = {0x1f,0x8b,0x08,0x00,0x68,0x76,0x5c,0x69,0x02,0xff,0xed,0x3d,0x6b,0x57,0xdb,0xc6,0xb6,0x9f,0x4f,0x7f,0xc5,0x40,0x52,0x2a,0x25,0x42,0xb6,0x81,0xf4,0x61,0x23,0x73,0x08,0x38,0x6d,0x9a,0xf0,0x58,0x3c,0xd2,0x9e,0xcb,0x61,0x21,0xd9,0x1a,0x63,0x15,0x59,0x72,0x25,0x19,0x42,0x85,0xff,0xfb,0xd9,0x7b,0x1e,0xd2,0xe8,0x61,0x63,0x92,0x93,0x74,0xdd,0x75,0x6f,0xbb,0x6a,0xec,0x79,0xec,0xd9,0xb3,0xdf,0x7b,0x66,0x4b,0xdd,0x5e,0x71,0xc3,0x41,0x72,0x3f,0xa1,0x64,0x94,0x8c,0xfd,0xee,0x76,0xe2,0x25,0x3e,0xed,0xee,0x85,0x41,0x12,0x85,0x3e,0x39,0x76,0x02,0xea,0x6f,0x37,0x78,0xe3,0xf6,0x98,0x26,0x0e,0x19,0x40,0x17,0x0d,0x12,0x6b,0xf5,0xce,0x73,0x93,0x91,0xe5,0xd2,0x5b,0x6f,0x40,0xd7,0xd9,0x0f,0xc3,0x0b,0xbc,0xc4,0x73,0xfc,0xf5,0x78,0xe0,0xf8,0xd4,0x6a,0x99,0xcd,0x55,0x12,0x38,0x63,0x6a,0xdd,0x7a,0xf4,0x6e,0x12,0x46,0x49,0x77,0x3b,0x4e,0xee,0x01,0xd2,0xb3,0xbb,0xc8,0x99,0x4c,0x68,0x94,0x26,0xf4,0x63,0xb2,0xee,0xf8,0xde,0x75,0xd0,0x1e,0x00,0x50,0x1a,0x75,0xfa,0xe1,0xc7,0xf5,0xd8,0xfb,0xcb,0x0b,0xae,0xdb,0xfd,0x30,0x72,0x69,0xb4,0x0e,0x2d,0xb3,0x67,0x62,0xd5,0x74,0xec,0x7c,0xe4,0x6b,0xb5,0x5f,0x35,0x9b,0x93,0x8f,0x9d,0xb1,0x13,0x5d,0x7b,0x41,0xdb,0x99,0x26,0x61,0x67,0xe2,0xb8,0x2e,0xce,0x6b,0x92,0x16,0x74,0xcd,0xfa,0xa1,0x7b,0x5f,0xb3,0x80,0x98,0xd1,0xcc,0x87,0xcf,0x5e,0xa4,0x83,0xd0,0x0f,0xa3,0xf6,0xb3,0x8d,0x21,0xfe,0xdb,0xe9,0x3b,0x83,0x9b,0xeb,0x28,0x9c,0x06,0xee,0xba,0xe8,0x18,0x0e,0x87,0x9d,0x21,0xa0,0xb0,0x3e,0x74,0xc6,0x9e,0x7f,0xdf,0x3e,0x0c,0x93,0x90,0x9c,0x3a,0x41,0x6c,0x7c,0xa0,0x91,0xeb,0x04,0x8e,0x11,0xc3,0x8f,0xf5,0x98,0x46,0x9e,0x18,0x08,0x7b,0xa0,0xed,0x96,0xb9,0x11,0xd1,0xf1,0xcc,0x0b,0x26,0xd3,0xc4,0xe8,0x4f,0x93,0x24,0x0c,0x54,0x8c,0x22,0xef,0x7a,0x94,0xd4,0xef,0xb8,0x06,0x07,0x3a,0xa4,0x2e,0xfd,0xa9,0xc3,0xc7,0xb4,0x5b,0x93,0x8f,0x24,0x0e,0x7d,0xcf,0x25,0xcf,0xfa,0xce,0x4f,0xdf,0xbf,0xea,0x8b,0x8e,0xf5,0xc8,0x71,0xbd,0x69,0xdc,0x7e,0x05,0xc4,0xe1,0x84,0x6a,0x35,0x9b,0xdf,0x72,0x14,0x2e,0x90,0xcf,0x16,0x22,0x70,0x69,0x28,0x0d,0xc1,0x74,0xdc,0xa7,0xd1,0x65,0xaa,0x6e,0x70,0x1c,0x06,0x61,0x3c,0x71,0x06,0x54,0xcc,0x8c,0xa8,0xe3,0x86,0x81,0x7f,0x7f,0x99,0xd6,0x20,0xb6,0x85,0xff,0xce,0xaa,0xfb,0xe3,0x14,0x9f,0x99,0x83,0x91,0x13,0x5c,0x53,0xd7,0x00,0x2e,0x8e,0xc7,0x5e,0x72,0xd5,0x4f,0x82,0x34,0x27,0xec,0x8a,0x37,0x46,0xd9,0x70,0x82,0xa4,0x66,0xd3,0x1b,0xce,0xd6,0x4f,0x9b,0x6e,0x3e,0x04,0x24,0xc1,0x09,0x06,0xd4,0x7f,0x0a,0x8c,0x1f,0x36,0x36,0x0b,0x00,0x32,0x24,0x8c,0x02,0xb0,0x69,0x14,0xc3,0xe8,0x49,0xe8,0x09,0x39,0x64,0x64,0x0e,0xc2,0x80,0x0a,0x3a,0x6e,0xbd,0xfa,0x56,0x08,0xcf,0x7a,0x12,0x4e,0xda,0x28,0x63,0x99,0x0c,0xb1,0x1f,0x8c,0x7e,0x77,0x14,0x99,0xda,0xfe,0xa1,0xd9,0x54,0x57,0xba,0x70,0xbd,0xd8,0xe9,0xfb,0xd4,0xbd,0x54,0xd7,0xcc,0x5b,0xe5,0x56,0x7e,0xfc,0xf1,0xc7,0x8e,0x40,0x24,0x08,0x91,0x8c,0x7e,0x78,0x47,0xdd,0x9a,0x3d,0x6d,0x6d,0x6d,0x29,0x7b,0x4a,0x10,0x4a,0x2a,0x04,0x00,0x46,0xf8,0xce,0x24,0xa6,0x6d,0xf9,0xa5,0xa3,0xb0,0xc4,0xa7,0xc3,0x44,0x95,0x8b,0xc4,0x4d,0x33,0xa1,0x03,0xf6,0x8d,0x55,0xb9,0x12,0x02,0x27,0xf7,0xf8,0x23,0xe8,0x54,0x3c,0x1d,0x03,0x09,0x0a,0x6a,0xc5,0x20,0x2a,0x6a,0x52,0x4b,0xff,0x1a,0xe1,0x94,0x60,0xcd,0x4d,0x50,0x91,0x0a,0xed,0xcc,0xc1,0x58,0x62,0xc6,0x79,0xa0,0x2a,0xd5,0x2b,0x54,0xaa,0x67,0xe3,0xf8,0xba,0x46,0xd8,0x46,0xad,0x34,0x1f,0xba,0x21,0x86,0x4e,0xc2,0xc9,0x74,0xb2,0x1e,0xde,0xd2,0xc8,0x77,0xee,0xd3,0xbf,0xd6,0xbd,0xc0,0xa5,0x1f,0x91,0x00,0xcd,0x1a,0x74,0x9b,0xec,0x9f,0xfe,0x66,0xe7,0x8f,0x69,0x9c,0x78,0xc3,0xfb,0x75,0x61,0x7b,0xa4,0x01,0x61,0xcb,0xad,0x7b,0x09,0x1d,0xc7,0xb2,0x29,0x27,0x68,0x67,0xc4,0xf7,0xc0,0xbe,0x03,0x7b,0x27,0xb0,0x20,0xdf,0xc0,0x24,0x8c,0xc1,0x38,0x86,0x41,0x7b,0xe8,0x7d,0x04,0x96,0xa2,0x08,0x35,0x3b,0x48,0x3c,0x30,0x40,0x02,0x41,0x69,0xe4,0x14,0x6a,0xd6,0x18,0xc8,0x39,0x3a,0x52,0x22,0x71,0x8b,0x5b,0x47,0x69,0x2d,0xb7,0x9a,0xaa,0xb8,0x6e,0xe2,0x0f,0x66,0x77,0x46,0xa0,0xd6,0x77,0x60,0x31,0xb7,0x80,0xed,0xdf,0xc3,0x7f,0x62,0xf7,0x5b,0x6e,0x09,0x27,0x32,0xda,0x48,0x17,0x32,0x59,0x20,0xa1,0x28,0x48,0x79,0x5b,0x64,0xb2,0x14,0x04,0x95,0xd1,0x2d,0xe4,0xde,0x3f,0xc7,0xd4,0xf5,0x1c,0x12,0x0f,0x22,0x4a,0x03,0xe2,0x04,0x2e,0xd1,0xd8,0x9e,0xb6,0xad,0xcd,0x57,0xb0,0x0f,0x3d,0xad,0x71,0x0e,0x8c,0xfc,0xb9,0x3f,0x00,0x79,0xe3,0x3a,0x42,0x92,0x88,0x30,0x91,0xaf,0x33,0xb9,0xca,0x54,0xc9,0xb9,0xbe,0x1f,0x0e,0x6e,0xb2,0xa9,0xa9,0xd8,0x9d,0x54,0x15,0xa4,0x62,0x71,0xa4,0xdc,0x32,0xb7,0x85,0x71,0x3a,0x7f,0x97,0x3e,0x05,0x5c,0xc1,0x0b,0xb6,0xf1,0x63,0x9e,0xa8,0x5d,0x3b,0x13,0xc9,0xc8,0x8c,0xac,0x1b,0xea,0xaa,0x08,0xa6,0xb4,0x26,0x11,0x76,0xb8,0xe8,0xd2,0xca,0xd6,0xed,0xbf,0xe0,0x5d,0xc6,0x80,0x50,0x46,0xb2,0x92,0x2d,0x24,0x1b,0xb5,0x06,0xb1,0x0e,0xd1,0xf6,0x08,0xf5,0xd2,0xa8,0xed,0x33,0x27,0x91,0xc7,0xec,0xcd,0x42,0xc1,0xe1,0x18,0xce,0x16,0x42,0xe0,0xab,0xd4,0xf0,0xe3,0x47,0xe7,0x87,0xe6,0xd6,0x2b,0x39,0x99,0x39,0x3b,0xc6,0x05,0xc7,0x0b,0x6a,0xc7,0x17,0xe4,0x9c,0x31,0x83,0x34,0x0b,0xb3,0x4b,0x84,0x5f,0x46,0x81,0x3f,0xc7,0xb3,0xd7,0xb8,0x20,0x55,0x79,0xcc,0xf8,0x4f,0x74,0x6e,0x7c,0xfc,0xe6,0xab,0x6f,0x8b,0x26,0xa0,0xcc,0xa0,0xed,0x06,0x8f,0xcf,0xb6,0x1b,0x23,0xf0,0xf7,0xdd,0x6d,0x0c,0xa1,0xba,0xdb,0xae,0x77,0x4b,0x3c,0xd7,0x12,0x31,0x5b,0xf6,0x5b,0xc8,0x6a,0x77,0x7b,0xd4,0xea,0xee,0xf9,0xd3,0xc1,0xcd,0x1e,0xb8,0x3b,0x50,0x4f,0x98,0xdc,0x82,0x38,0x12,0x75,0x06,0xfe,0xc0,0xf8,0x04,0x21,0x31,0x6e,0x90,0x30,0x18,0xf8,0xde,0xe0,0xc6,0x5a,0x8d,0x69,0xe0,0x8a,0xf1,0xda,0x77,0x71,0xe2,0x44,0xc9,0x77,0xfa,0x2a,0x19,0xf8,0x4e,0x1c,0x5b,0x60,0xfa,0xbb,0xa7,0x67,0xbb,0x27,0x67,0xdb,0x0d,0x3e,0x0d,0xf0,0x41,0x18,0x4b,0xc0,0x09,0x27,0x05,0x30,0x84,0x6d,0xc7,0x7a,0xcc,0x3b,0xc1,0x6a,0x47,0xc7,0x4f,0x5d,0x0c,0x40,0x84,0x45,0x9c,0xcf,0x0f,0xf7,0x8f,0x4a,0x50,0x1a,0xb8,0xff,0x86,0xa4,0x85,0x4a,0x12,0x88,0xa3,0x7d,0x08,0xb0,0x02,0x6b,0xb3,0xbb,0xcd,0xe4,0x06,0x49,0x0a,0x2e,0x8d,0xc8,0x48,0x4b,0x85,0xb0,0x60,0xca,0xf9,0xef,0x57,0x67,0x6f,0x0f,0x7a,0x88,0x24,0x8b,0xb2,0x2c,0x90,0xcc,0x9b,0x3d,0x1e,0x70,0x69,0xc9,0xc8,0x8b,0x75,0x20,0x02,0x9d,0x58,0x2d,0xc2,0x62,0x3d,0xd7,0x49,0x68,0xe2,0x8d,0xe9,0x3a,0x98,0x2b,0xc7,0xef,0x12,0xb9,0x47,0x80,0x14,0x84,0x77,0x18,0x94,0x64,0xdb,0x8d,0x69,0x72,0x06,0x23,0xcf,0xc2,0xc3,0xf0,0x4e,0xd3,0xbb,0xa7,0xf7,0xc1,0x80,0x60,0x43,0xdd,0x16,0x05,0x9b,0x4f,0x07,0x23,0xea,0x4e,0xc1,0x56,0x9e,0x22,0x47,0x73,0x42,0xaa,0xd8,0x1e,0x1c,0x7d,0xe8,0x5d,0x31,0xf6,0xe6,0x38,0xf3,0x3f,0x72,0xb6,0x40,0x9b,0x07,0xab,0xb0,0xe0,0xa2,0x85,0x7a,0x28,0x6f,0x73,0x97,0xe9,0x1d,0xee,0x7f,0xc6,0x22,0xcf,0xc8,0x01,0x18,0x8d,0xb8,0xb1,0xef,0xdc,0xd7,0xaf,0x71,0x78,0x7e,0xc0,0xd6,0x39,0x25,0x60,0x0b,0xad,0xe6,0x32,0x4b,0xf1,0x58,0xbb,0x66,0xb1,0x43,0xb0,0x13,0x6c,0x3d,0xb2,0x3b,0x87,0x70,0x87,0xbd,0xdf,0xcf,0xae,0x76,0xdf,0xef,0x9e,0x1c,0x64,0x32,0x52,0xcb,0xd3,0x2a,0xec,0x13,0x3a,0x06,0x73,0x66,0x92,0x7d,0x0f,0x34,0x0d,0x82,0x4f,0xa2,0x0d,0x13,0xbd,0x7e,0x91,0x93,0xde,0xc1,0xd5,0xfe,0xdb,0x53,0x85,0x37,0xf0,0xe9,0xfa,0x94,0x83,0x00,0xc3,0x81,0x30,0xb8,0x78,0x2d,0xb5,0x2d,0xb6,0xa3,0x25,0xd6,0xdd,0x3f,0x79,0x0b,0xfc,0x62,0x2b,0x7f,0x3e,0x2d,0x7f,0x05,0x25,0x27,0xbf,0x30,0xb3,0x46,0x34,0x2f,0x98,0xb3,0xe4,0xaf,0xbb,0x7b,0xef,0xfe,0x5b,0x2b,0xbe,0x76,0x12,0x30,0xed,0xf7,0x44,0xfb,0x50,0xb7,0xd8,0x6d,0xe8,0x27,0xce,0x35,0x5d,0xa0,0xda,0xdd,0xe3,0x28,0xbc,0x8e,0x9c,0x31,0x39,0x79,0x43,0x80,0xd4,0x61,0x42,0xe7,0x5a,0xa1,0x09,0x1f,0x79,0xf2,0xe6,0x94,0xfe,0x39,0xa5,0x40,0x55,0xd0,0x4d,0x39,0x7b,0xd7,0xf7,0xc9,0x6b,0xee,0xff,0x16,0x19,0x22,0x01,0x51,0x66,0x1f,0xcc,0x98,0x67,0x49,0x49,0xb6,0x10,0x4a,0x13,0x06,0xac,0x66,0x44,0xfd,0xd0,0x71,0x35,0xbd,0xd3,0x05,0x46,0x0e,0x9c,0xc8,0xcd,0x61,0xd7,0x41,0xca,0x92,0x9e,0x0c,0x12,0x6f,0x3a,0x76,0x00,0xc5,0x18,0x2d,0x89,0x03,0x32,0xc1,0x65,0x48,0x41,0xb3,0x1f,0xf1,0xff,0x5c,0x0a,0xbe,0xd7,0x8f,0xbb,0xdb,0x22,0xd7,0xe8,0x82,0x48,0x1e,0x9d,0xf5,0xc8,0xde,0xd1,0xe1,0xd9,0xc9,0xd1,0x7b,0x70,0x51,0xa2,0x9d,0xe0,0x70,0x92,0x93,0x67,0x1c,0x4e,0x63,0x0a,0xd1,0x6c,0x00,0x86,0x1a,0xcd,0x0f,0xa7,0xa3,0xf6,0xdd,0xf0,0xce,0xfd,0xce,0x20,0xf4,0x16,0x7c,0x15,0x98,0x6b,0x70,0x5a,0xe1,0x74,0x30,0x62,0x23,0x16,0x0e,0xe4,0x76,0x9d,0xb9,0x4f,0x09,0xdc,0xa7,0x80,0xb9,0x85,0x5e,0x46,0x4c,0xd1,0x65,0xcf,0x74,0x52,0x6e,0x66,0xab,0x80,0xbf,0x28,0xb4,0x77,0xdf,0xfc,0xb6,0x9f,0xed,0x78,0x09,0xd4,0x23,0x7a,0xbb,0x1c,0xea,0xc5,0x81,0x5f,0x04,0xf5,0x93,0xde,0x87,0xa7,0xa0,0x3e,0x9d,0x2c,0x87,0x79,0x61,0xdc,0x17,0x41,0xfc,0xfc,0xf8,0x29,0x78,0x63,0xd3,0x72,0x98,0x97,0x46,0x7e,0x11,0xdc,0xf7,0x8f,0x7e,0x3b,0x7c,0x0a,0xf6,0xce,0xf4,0xe3,0x72,0xc8,0x17,0x07,0x7e,0x11,0xdc,0x77,0xcf,0x7f,0x57,0x8c,0x50,0xa6,0xd6,0xb5,0x3a,0xbe,0xbf,0x7b,0xf8,0x73,0xef,0x84,0xfc,0xcf,0xd1,0x61,0x4f,0x55,0xf0,0x62,0x10,0xb9,0x07,0x61,0x74,0x3f,0x62,0x36,0x69,0x7e,0x8c,0x36,0x10,0x83,0x60,0x8f,0x7f,0x80,0x1b,0x80,0x08,0x8d,0xbb,0x83,0xc2,0xe4,0x2a,0x41,0xab,0xb3,0xdd,0xc8,0xbb,0xa5,0x38,0x7d,0x1f,0xbf,0xd4,0xce,0xaf,0x9a,0xf1,0xbd,0x69,0x14,0x61,0xaa,0xfb,0x81,0x46,0x71,0x01,0xcd,0xdc,0x17,0xf0,0x9e,0x45,0xbe,0xe0,0x8d,0x17,0x8d,0xef,0x9c,0x88,0x96,0x67,0x3b,0x83,0x01,0x9d,0x24,0x96,0xd9,0xf7,0x58,0xbc,0x36,0x14,0xc3,0xae,0x86,0x1e,0x04,0x42,0x3c,0xd2,0x55,0x4f,0x1c,0xb8,0xcb,0xc2,0xce,0x2e,0x51,0xe3,0xbc,0xe9,0x04,0xcd,0x79,0xc1,0x40,0xf3,0x26,0xb9,0x2e,0xea,0x0c,0x6b,0x20,0x39,0x26,0x73,0x77,0xfc,0x3e,0xbc,0x86,0x61,0x7e,0xd5,0x5f,0xc1,0x4a,0x7e,0x78,0x5d,0x58,0x06,0x05,0x16,0xe1,0xc2,0x1c,0x9c,0x82,0xf2,0x2d,0x5a,0x08,0x34,0x2d,0xf0,0x58,0x42,0x12,0x10,0xa6,0x10,0x89,0xac,0x63,0x61,0x8c,0x1e,0xd1,0x7e,0x18,0x26,0x9f,0x94,0x12,0x9c,0xf4,0x5e,0x1f,0x1d,0x9d,0x2d,0x10,0x95,0x62,0xe6,0xe1,0x53,0xfa,0xf4,0xd4,0x63,0x63,0xf3,0x87,0xee,0xe9,0xfb,0x5e,0xef,0xb8,0x4e,0x4f,0x1a,0x90,0x66,0xc9,0x4f,0x91,0x71,0x15,0x8e,0xb1,0x4a,0xad,0x79,0x36,0xb6,0x91,0x37,0x8a,0x73,0xfb,0xc6,0x68,0xa3,0xbb,0x3d,0xc9,0x9b,0xc7,0x34,0x8e,0x21,0x1e,0x81,0x8e,0x49,0x09,0x4a,0x29,0xff,0xad,0x91,0x2a,0x45,0x94,0x95,0x29,0xb5,0x88,0x8a,0x4c,0xbc,0xb8,0x15,0xfe,0x19,0x0f,0x22,0x6f,0x92,0x74,0x7d,0x9a,0x10,0x88,0x5e,0x1d,0x2b,0x9d,0x19,0x13,0x0c,0x0f,0xce,0x90,0xab,0x7b,0xa0,0x1b,0x09,0x75,0xad,0x95,0x96,0x31,0x09,0x7d,0xff,0x2d,0x66,0xcd,0xb7,0x8e,0x0f,0xd1,0x97,0xef,0x1b,0xe3,0xd0,0x75,0xfc,0x13,0x0a,0x09,0xf2,0x2d,0x55,0x5a,0x20,0x32,0x19,0x7b,0x71,0xcc,0x66,0x75,0x00,0xff,0x38,0x21,0x10,0xc9,0x01,0x2a,0x5d,0x37,0x1c,0x4c,0xc7,0x40,0x19,0xf3,0x9a,0x26,0x3d,0x9f,0xe2,0xd7,0xd7,0xf7,0x6f,0x5d,0xcd,0x73,0xf5,0xce,0x70,0x1a,0x0c,0x50,0xa3,0x49,0x3c,0x0a,0xef,0x0e,0x10,0x8e,0xc6,0x48,0x66,0x08,0x0a,0x89,0x33,0xfb,0xd8,0xba,0xb8,0x34,0xc2,0x09,0x8e,0x8c,0x01,0x55,0x3d,0x45,0xbc,0x05,0x1b,0xac,0x79,0xf0,0xed,0x02,0xb7,0x6c,0xdd,0x60,0x90,0x7b,0xfe,0x63,0x13,0xd8,0x30,0x18,0x2e,0x50,0x78,0x7c,0x82,0x18,0x08,0x53,0x04,0xb6,0x8f,0x4f,0x11,0x03,0x61,0x0a,0xe3,0xde,0x9e,0xe4,0xf7,0x63,0xf3,0x4a,0xe2,0x21,0xe7,0x3f,0xbe,0x20,0x1b,0x66,0xeb,0x1d,0x41,0x03,0x13,0x4f,0x45,0xf6,0xc4,0x45,0x52,0x81,0xe2,0xd0,0xe7,0x05,0x00,0xfa,0x97,0xb3,0x83,0xf7,0x96,0xe4,0x82,0x20,0xbd,0x89,0x5c,0x7a,0x8b,0x90,0x76,0xb4,0x22,0xde,0x26,0x93,0x53,0x53,0xc8,0xa9,0x65,0xb3,0xc3,0x38,0x5b,0x22,0x67,0x32,0x43,0x28,0x81,0xb0,0xc6,0x33,0x68,0x79,0x78,0xb0,0x11,0x8d,0x7c,0x18,0xc8,0xd8,0xb4,0x34,0xee,0x03,0x36,0xc1,0xc0,0x7c,0x10,0x2c,0x30,0xa0,0xa3,0xd0,0x77,0x81,0x5a,0x85,0xa1,0xc7,0x79,0x07,0x9b,0x20,0xd2,0xe7,0x70,0x9a,0x68,0x9a,0x6e,0x75,0xe5,0xfc,0x21,0x10,0x0a,0x82,0x60,0xa3,0xd5,0x6c,0xea,0x7a,0x7b,0xf1,0x36,0x50,0xdf,0xec,0x9c,0xab,0x0a,0x65,0x6c,0xbb,0x83,0x32,0xc8,0xbb,0x04,0xbd,0x51,0x4a,0x3b,0x62,0x30,0x2c,0x13,0xf5,0x9c,0xc1,0x48,0x03,0xf3,0x6b,0x75,0xd3,0x7c,0x6c,0xce,0xa8,0x01,0xd3,0x32,0x31,0x57,0xb3,0x79,0x37,0xb0,0x48,0x9c,0x9b,0xa9,0x1c,0x02,0x20,0xec,0xb7,0x81,0x5f,0xc4,0x81,0xda,0xda,0x9a,0x18,0xc8,0x2c,0xde,0x7b,0xc8,0xf0,0x4c,0xc7,0x45,0x7e,0xf3,0xee,0x4c,0x1a,0x4d,0x69,0x30,0x91,0x08,0x0c,0x91,0x88,0xc6,0x53,0x3f,0xb1,0xaa,0x4c,0x4d,0xc5,0x81,0x20,0xae,0xc2,0x78,0xc1,0x89,0xde,0x2e,0xf0,0x67,0x96,0x77,0x77,0x54,0x5b,0xb0,0xb6,0x66,0x69,0xea,0x6f,0x8d,0x2f,0xa3,0x1b,0x68,0x23,0x74,0x63,0xe4,0xb9,0x94,0x2b,0xb8,0xce,0x76,0x01,0x8e,0xde,0x47,0x83,0x0c,0xdb,0x50,0x7e,0xc9,0x49,0x33,0x85,0xe8,0x78,0xe8,0x05,0x96,0x7e,0xe4,0xf9,0xae,0xc6,0x5b,0xe5,0xd6,0x24,0xdd,0xcd,0xc9,0x34,0x1e,0x69,0x02,0x7b,0x84,0x3e,0xd3,0x67,0x3a,0xe3,0xcf,0x0d,0xbd,0xff,0x85,0xa5,0xcf,0x91,0x45,0x61,0xf7,0xde,0x50,0xa3,0x26,0xb4,0x59,0x96,0x65,0xf7,0xd0,0xaa,0xd9,0x7a,0x4a,0x81,0xa0,0x2c,0x0c,0xdb,0xa7,0x43,0x07,0xd6,0xd6,0xf8,0x4c,0x41,0xc6,0xd7,0xc0,0xbe,0xd2,0x62,0x43,0x0f,0x1c,0x4e,0xdf,0xea,0xf6,0x4d,0x85,0x1b,0x7a,0x27,0x9f,0xb0,0x93,0x7f,0x35,0x33,0x1e,0x01,0x07,0x34,0xbd,0x5d,0x02,0xe5,0xd3,0xe0,0x3a,0x19,0x75,0x9b,0x92,0x97,0xb2,0xe3,0xa2,0x76,0xdc,0x7a,0xeb,0xb2,0x04,0x6f,0x46,0xfd,0x18,0x9c,0xb2,0xba,0x2b,0xc8,0x0f,0x27,0x74,0xfe,0xb6,0x78,0x9a,0xb9,0x60,0x57,0x2b,0xa5,0x6d,0x65,0x13,0x76,0xb2,0x6f,0x9f,0xba,0xa9,0x66,0x05,0xfd,0x59,0xe7,0x0e,0x96,0x0d,0xef,0x4c,0x26,0x38,0xef,0x32,0x66,0xad,0xad,0x65,0x5a,0x12,0x41,0x34,0x7b,0x4b,0x7b,0xb8,0x13,0x94,0x71,0x0a,0x0a,0xa8,0xd9,0xb0,0x59,0x0c,0x65,0x6c,0xa3,0x7e,0xba,0x3e,0xa7,0xdd,0xca,0xc5,0xc1,0xc8,0x16,0x00,0xa5,0x99,0x07,0xfd,0x46,0x81,0x28,0xdc,0x48,0xd9,0x42,0xe0,0xf9,0xbf,0x3d,0xcb,0xdc,0x98,0x22,0xe5,0x9f,0xe4,0xa3,0x3a,0x73,0x96,0xe1,0x86,0x68,0xa1,0xba,0xad,0xb4,0xa4,0xaa,0xcd,0xa3,0xa9,0xf6,0x65,0x88,0xca,0x16,0xcd,0x49,0xc0,0xba,0xc1,0x70,0x61,0x74,0xac,0x49,0x07,0xc2,0x5c,0x8c,0x65,0x8b,0x66,0x10,0xcf,0x88,0x26,0xd3,0x28,0x20,0x01,0xbd,0x23,0xc7,0x51,0x08,0x91,0x03,0xb3,0x17,0x2c,0xa6,0xe8,0xa6,0x85,0x10,0x43,0x34,0x1b,0xf3,0x22,0x84,0x0b,0x76,0x07,0xd9,0xb6,0xf7,0x98,0x78,0xda,0x06,0xb3,0x4c,0xed,0x95,0xd6,0xcc,0x10,0x1d,0x47,0xef,0xb2,0xc6,0xa6,0x21,0x6f,0x23,0x56,0x9a,0xb3,0x4b,0x30,0x12,0x25,0xb4,0x77,0x61,0x3f,0x49,0x19,0xe9,0xc3,0x30,0xf1,0x06,0xf4,0x8b,0xe0,0xfc,0x04,0xd4,0x70,0xc5,0x49,0x05,0x37,0x66,0xb7,0x6d,0xc3,0xe5,0x3a,0xce,0x9c,0x25,0xb8,0xa6,0x27,0xa2,0x8a,0xce,0xa0,0x9b,0xf2,0x2f,0x6b,0x6b,0xfc,0xaf,0xd0,0xd4,0x9d,0xa8,0x60,0xcc,0xb9,0xa3,0xd5,0xdb,0xb2,0x95,0xb3,0xfe,0x0b,0xb0,0xc6,0x48,0x33,0xaf,0x84,0x5d,0x59,0xc8,0xd0,0x56,0x23,0x06,0xb6,0xdd,0xb6,0xba,0x77,0xa3,0x1c,0x08,0xb4,0x6d,0x7b,0x56,0xa0,0x25,0x82,0x3d,0x61,0x49,0x89,0x54,0xd3,0x1c,0x79,0x7b,0x9f,0x95,0xd2,0x10,0xde,0xef,0x05,0xd7,0xb6,0x61,0x1f,0xe3,0xb1,0xe1,0x9d,0xe7,0xfb,0xe0,0x35,0x87,0xb0,0xed,0x11,0x81,0xdc,0x6f,0x1b,0x2f,0x01,0x30,0xcc,0x5e,0x95,0xa1,0xff,0x34,0x48,0x50,0x77,0x56,0xbb,0xaf,0x20,0x7b,0x86,0xce,0x2e,0x89,0x29,0x44,0x69,0x6e,0x6c,0x9a,0xa6,0x6d,0x5c,0x5c,0x0a,0xe3,0x2b,0xc7,0x59,0xaf,0x8c,0xec,0x7b,0x16,0x5d,0x43,0xbc,0x22,0xbf,0xb3,0x80,0x25,0xcd,0x86,0xac,0xaf,0x17,0xa7,0x3f,0x1e,0xee,0x65,0x43,0xc1,0xa4,0x28,0xd3,0xc0,0x06,0x28,0xbf,0x0a,0x21,0x46,0xd6,0xae,0xe7,0xa8,0x6d,0x5b,0x4d,0x9c,0xe1,0x53,0x27,0xca,0x30,0xab,0xe0,0xad,0x1b,0x95,0x73,0x4a,0x10,0x89,0x16,0xdd,0x2c,0x53,0xfd,0x6c,0x0f,0x6f,0x30,0x6a,0xc8,0x8e,0x51,0x1a,0x01,0x2d,0x23,0xa7,0x14,0x58,0x6b,0x9f,0x8d,0x28,0xe1,0x45,0x4d,0x90,0xcc,0x41,0x18,0x09,0x52,0x4c,0xdd,0x98,0x24,0x21,0xe9,0x43,0x96,0x0d,0x20,0x46,0x51,0x18,0x78,0x7f,0x51,0x17,0x29,0x2b,0x04,0x29,0xbb,0x1b,0xa9,0x95,0x27,0x43,0x86,0x17,0x6d,0x07,0xe7,0x33,0xe2,0x16,0x2f,0x57,0x0c,0xe7,0xce,0xf1,0x90,0xc2,0xea,0x39,0xe9,0x0c,0xf5,0x90,0xa7,0x32,0x90,0x83,0x07,0xd4,0x3d,0x1a,0xf6,0x8e,0xce,0x30,0xbf,0x29,0x6c,0x0c,0xda,0xe4,0xa6,0x94,0x61,0x0f,0x0f,0x5a,0x61,0x52,0x53,0xd1,0x12,0xfb,0x08,0xf2,0xb7,0x70,0x48,0xce,0x22,0xe7,0x16,0xf5,0x42,0x0a,0x1e,0x78,0xd8,0x00,0xc8,0x80,0xb6,0x99,0x00,0x95,0x3d,0x1f,0xbe,0x46,0x78,0xef,0x8d,0xc3,0xf0,0x8c,0x97,0x1f,0xe2,0x7b,0x31,0x11,0x75,0x29,0x0a,0x05,0xe6,0x9b,0x12,0x7d,0x86,0xc2,0xe3,0x09,0x76,0xbd,0x75,0x99,0xc1,0xce,0xb7,0x10,0xf1,0xe3,0xa2,0x01,0xcf,0xa4,0x81,0x31,0x4a,0x5a,0x2d,0x1b,0x3b,0x49,0x74,0x9f,0x06,0xce,0xad,0x77,0xed,0x24,0x61,0x64,0xde,0x8a,0x23,0x9a,0x0d,0x08,0xa1,0x67,0xc0,0xfb,0xc1,0x28,0x9d,0x29,0xcc,0x56,0x8e,0xb9,0x04,0x00,0x83,0x9f,0x73,0xa5,0xec,0xcf,0xda,0x1a,0xfb,0x53,0x89,0x4f,0x8c,0x1c,0xc5,0xb5,0xb5,0xa2,0xd0,0xe5,0x3d,0xba,0x51,0xc2,0x57,0x99,0x55,0xd5,0xa0,0xd2,0x58,0x10,0xcb,0x57,0x4d,0x55,0x2c,0x95,0xd3,0xb2,0x54,0x5d,0xdd,0xd2,0xe6,0xaf,0xcf,0x6d,0x1e,0x13,0x24,0x92,0x43,0x52,0x89,0x36,0x76,0x0f,0x9d,0x31,0xd5,0x31,0xda,0x5c,0x91,0xbf,0x30,0x36,0x63,0x84,0xb1,0xd7,0xd6,0x56,0xb8,0xb0,0x15,0x1c,0xa5,0xfd,0x1b,0xda,0x98,0x3e,0xbd,0xf6,0xd0,0xde,0xdf,0x82,0xf1,0x21,0xeb,0x64,0x02,0x48,0x40,0x7c,0x37,0xe0,0x63,0x4c,0x5b,0xd7,0x61,0xb2,0x0a,0x91,0x1f,0xaf,0xcc,0x03,0x29,0xa4,0x4a,0x58,0x2f,0x1c,0x09,0x30,0xd9,0xc6,0x10,0x3c,0xd7,0x2d,0xac,0xe6,0x90,0x82,0x65,0x92,0x5d,0x90,0xb6,0xfb,0x70,0x4a,0xe2,0x69,0x44,0x77,0xaa,0xcb,0xb1,0x43,0x96,0x65,0x56,0x63,0x03,0xab,0xe0,0x74,0x14,0x23,0x91,0x81,0x4c,0x40,0xaf,0xa8,0xc5,0x01,0x0d,0x29,0x88,0x90,0x66,0x9b,0x8d,0x49,0x18,0x83,0x05,0x48,0xc7,0x34,0x19,0x85,0x6e,0xdb,0x3e,0x3e,0x3a,0x3d,0xb3,0x0d,0xbc,0x09,0xa7,0x51,0xdc,0x4e,0x57,0x85,0xad,0x5a,0x47,0x4f,0xb0,0xda,0xb6,0x21,0x31,0x80,0x38,0x92,0x99,0x9e,0xc6,0x1f,0x31,0xe4,0x4e,0x90,0x35,0x84,0xee,0x7d,0xfb,0xd7,0xd3,0xa3,0x43,0x08,0xa0,0x70,0x97,0xde,0xf0,0x5e,0x4b,0x61,0x07,0x6d,0xb1,0x0b,0x96,0x18,0x20,0x5f,0x24,0x06,0x66,0x78,0xa3,0xa7,0xca,0x76,0x78,0x04,0x60,0x0b,0x5e,0x92,0xef,0x9e,0xa7,0x72,0xe6,0x77,0x64,0xe8,0x78,0x3e,0x75,0xdb,0xe4,0x79,0x9a,0xcd,0x06,0xc2,0x25,0xd3,0x78,0x56,0x6d,0x3a,0x03,0xa5,0x9c,0x81,0xf1,0xe5,0x8e,0x78,0x56,0xe5,0xd9,0x4e,0xc5,0x13,0xb5,0x95,0x84,0x95,0x51,0xe4,0x94,0x41,0x32,0x72,0x2d,0xd3,0x40,0xa8,0xd0,0x1e,0x85,0x10,0x1a,0xfa,0xe1,0x35,0xfc,0x34,0xaa,0xa8,0x1f,0xd2,0xe4,0x2e,0x8c,0x6e,0x08,0x8d,0xa2,0x30,0x42,0x64,0xa9,0x29,0xfc,0x31,0xe0,0x33,0x2b,0x0b,0x6e,0x7e,0xde,0x8a,0x39,0x3a,0x0f,0x5a,0x25,0xb2,0x2c,0x6b,0x07,0x8c,0xf1,0x18,0xd7,0xde,0xb1,0x61,0xe8,0x15,0xfb,0xda,0x66,0x5f,0xd9,0xf1,0xac,0x8d,0xc4,0xac,0x93,0x86,0xb3,0x11,0x58,0x29,0x26,0x0b,0xd9,0x0a,0x24,0x01,0xc3,0xfe,0x3c,0x45,0xa8,0x33,0x93,0x20,0x2f,0xbd,0x60,0xca,0xe4,0xe2,0x7f,0x8f,0x58,0xe4,0x67,0xd0,0x9f,0x23,0x0d,0x55,0xc0,0x9c,0x2c,0x58,0xa7,0x19,0xed,0x26,0x5a,0x53,0x37,0x93,0xf0,0x1c,0x6b,0x3d,0xf6,0x40,0xff,0x35,0xfd,0x25,0xeb,0x8c,0x61,0x57,0x54,0x6b,0xe9,0xb3,0x8c,0xa4,0x99,0xb5,0x45,0x4f,0xa0,0x1b,0x4b,0xc9,0xcf,0x93,0x05,0x46,0xb1,0x71,0xaa,0xc7,0x64,0x0c,0x0b,0xc2,0x3b,0xab,0xf1,0x82,0xfc,0xf3,0xea,0xea,0xf8,0xfc,0xa4,0x77,0x75,0x45,0x5e,0x34,0x58,0xcc,0xb9,0x0f,0xec,0xe6,0xb1,0x98,0x75,0x4d,0x35,0x5b,0x94,0x48,0x00,0x01,0x58,0x9b,0x38,0xe7,0x81,0x4d,0xc3,0x7c,0x0c,0x60,0xde,0x80,0x49,0xfd,0x17,0x98,0x25,0x70,0xb9,0xeb,0xcf,0xd3,0x53,0xc6,0x22,0x4d,0xf4,0x1d,0x00,0x73,0x47,0x40,0x82,0x96,0x6e,0x4e,0x1c,0x97,0x95,0x35,0x68,0x1b,0x86,0xdd,0xb4,0x6b,0xc6,0xe2,0xb2,0x10,0x7d,0x54,0x06,0x9e,0x95,0x07,0xfe,0x12,0x4e,0xa3,0xb8,0x6e,0x64,0xbb,0xb2,0x3c,0xc8,0x68,0x42,0x97,0x1b,0x7b,0xca,0x43,0xbe,0xba,0xb1,0x90,0xbb,0x29,0x55,0x21,0x3c,0x8a,0x56,0x02,0x7c,0xa5,0x8f,0xfa,0xe0,0x26,0xfd,0xf2,0xa1,0x8e,0x28,0xe0,0x05,0x26,0x23,0x3d,0xf3,0x1b,0x5e,0x5b,0x37,0xe5,0xd5,0x2f,0x9e,0xd6,0xb2,0xce,0xec,0x22,0xb9,0xd8,0xa9,0x24,0xa9,0x73,0x0b,0x0a,0x38,0x62,0x69,0x0d,0xae,0x3c,0x0c,0x8a,0xc5,0x95,0x3c,0x0b,0xcc,0x63,0xeb,0xc2,0xce,0x6b,0x49,0x20,0x8a,0x91,0x15,0x1f,0xf0,0x35,0x2b,0xcc,0xc0,0xe0,0x26,0xab,0x2c,0x80,0x1f,0xd9,0x9d,0xbf,0x7d,0x99,0x87,0x20,0xa5,0xfb,0xfe,0xe9,0xc7,0x2b,0x81,0x08,0xa8,0x24,0x3b,0x95,0xbe,0xca,0x65,0xe9,0x78,0xf7,0x64,0xf7,0xe0,0xea,0x79,0x2a,0x07,0x99,0x9e,0x6b,0xc6,0xd3,0x3e,0xd7,0x6b,0x0d,0x82,0x4e,0x3c,0xb9,0x55,0xf0,0xcf,0x80,0x15,0x5a,0x15,0xa8,0xba,0x91,0x81,0x92,0x06,0x0f,0x6b,0x39,0x6c,0x26,0xe1,0x17,0x23,0x94,0x14,0x63,0xcc,0xa5,0xe0,0xd2,0xca,0x86,0x32,0x11,0x36,0x21,0x3b,0xf7,0x40,0x83,0xda,0x40,0xe9,0xb1,0x33,0xd1,0x0e,0x59,0x75,0x82,0xde,0x51,0xa0,0x0b,0x59,0xd7,0x18,0x9c,0x17,0xdf,0x37,0x5f,0x0a,0x50,0x3a,0x7c,0xe7,0xa7,0x37,0xd5,0xc1,0xd0,0x12,0xd3,0x37,0x10,0x4a,0x27,0x5a,0x71,0x3d,0xfd,0xe1,0xa1,0x99,0xb3,0x71,0x3a,0xc1,0xba,0x93,0xd3,0x02,0x4b,0x40,0x2f,0x87,0x61,0xa4,0xb1,0x80,0xcf,0x6a,0x76,0xbc,0xed,0x22,0xc7,0xc4,0xd9,0x4c,0xc7,0x7b,0xf9,0x52,0x07,0xda,0x4a,0xe8,0x52,0x49,0x9f,0xa7,0xc5,0xe1,0x17,0xde,0x25,0x92,0x73,0x1e,0x0b,0x6a,0x07,0xaf,0x68,0x2b,0x12,0xec,0xc3,0xc3,0x8a,0x4a,0x67,0x0c,0x26,0xb2,0xfd,0xe4,0xf2,0x2d,0x0e,0xb3,0x63,0x45,0xc8,0x1f,0x1e,0xf2,0xd3,0x19,0xd8,0xeb,0xad,0x3c,0x24,0x05,0xde,0x64,0xec,0x54,0xf1,0xaf,0x61,0x1c,0x61,0x04,0xb7,0x0e,0x9c,0x64,0x64,0x0e,0xfd,0x10,0x68,0x52,0xa1,0x73,0x63,0xf3,0x7b,0xb0,0x8c,0x92,0xb7,0x0b,0x87,0x7e,0x8b,0x43,0x1b,0xdf,0x37,0xf5,0x4e,0x91,0x21,0x68,0xc3,0x84,0x0d,0x60,0xeb,0x2d,0x32,0x12,0x92,0xf1,0x55,0xdb,0xc0,0xe5,0xa0,0x04,0xb9,0x82,0x43,0xc5,0x73,0x17,0x72,0x15,0xee,0xb8,0x39,0x01,0x85,0x76,0x66,0x34,0xfc,0x73,0x4a,0xa3,0xfb,0x53,0xea,0xd3,0x01,0x04,0xef,0xbb,0x3e,0xe4,0x1f,0x82,0x07,0x92,0xde,0xe8,0xfa,0x0a,0x73,0x85,0x9c,0x00,0x49,0x9b,0x3a,0xf7,0x59,0xfc,0x4c,0xd4,0xb9,0xc7,0x04,0xcf,0x4a,0x67,0x9d,0x4c,0xca,0xd8,0x5d,0x14,0xe4,0x32,0x85,0xf9,0x1c,0x1d,0x48,0x90,0xa5,0x8a,0xe2,0x0a,0xf0,0x13,0x58,0x94,0xf9,0x02,0x36,0xc4,0x4d,0x2c,0xe9,0x2f,0x34,0x55,0xd0,0x8d,0x7b,0x70,0x07,0x96,0x9b,0x14,0xbd,0x83,0x31,0x46,0x67,0x20,0x9a,0x85,0x63,0x30,0x5c,0x3c,0x5e,0x4b,0x72,0xf3,0x6f,0x70,0xce,0xf3,0x26,0x61,0xe8,0x33,0x26,0x8b,0xa9,0xd2,0xa8,0x1b,0x22,0x4b,0x17,0xed,0x99,0x01,0xef,0x88,0x9d,0x9a,0x28,0x50,0xaa,0x64,0xe0,0x12,0xe6,0xf9,0xd9,0x9e,0x86,0xf8,0x71,0x74,0x10,0x01,0xa3,0x60,0x26,0x24,0x54,0xbd,0xc1,0xb2,0x60,0x79,0x46,0x9b,0xed,0x5f,0x96,0x78,0xd9,0xba,0x5c,0x26,0x92,0xb6,0xf8,0x0a,0x63,0x71,0xd5,0x00,0x94,0xb4,0xbf,0x93,0x03,0x33,0x99,0xe7,0x8f,0x7f,0xf3,0x80,0x0a,0x42,0x25,0x21,0x94,0x4a,0x39,0x9f,0x40,0x28,0x58,0x08,0x57,0x30,0x8e,0xdf,0xeb,0x3c,0x43,0xb4,0x14,0xa0,0x9d,0xa2,0x02,0xf1,0xfa,0x2a,0x08,0xf1,0xb5,0x8a,0x29,0x2a,0x61,0x82,0x66,0x81,0x23,0xcf,0x56,0xa3,0x90,0x20,0xc5,0x0f,0x0f,0xfc,0x1e,0xb1,0xdc,0x7e,0x91,0x21,0x74,0x69,0x71,0x59,0x9e,0x25,0xac,0xa6,0x38,0x0f,0x65,0xed,0x53,0xc8,0xa0,0x30,0x2d,0xc1,0xdb,0xc8,0xb6,0x2d,0x81,0xf0,0x73,0x93,0xaf,0x1a,0x17,0xca,0x95,0x97,0x8b,0x08,0xdf,0xb0,0x28,0x10,0x0f,0x27,0x62,0x2c,0xaa,0xe2,0x5a,0x10,0x7f,0x5a,0x8e,0x50,0xd0,0x40,0x79,0x7f,0xc4,0xcd,0x6e,0xb7,0x6c,0x35,0xf9,0x09,0xae,0x62,0x33,0x17,0x46,0x06,0xcd,0x45,0x91,0x41,0xf3,0x0b,0x05,0x8e,0x25,0x7b,0xa5,0x40,0x06,0x73,0xf5,0x58,0xc8,0x0f,0xda,0x68,0xd7,0x30,0x40,0x8a,0x0c,0x5b,0x52,0xa5,0x3e,0x9b,0x48,0x38,0x5d,0x41,0x7c,0x4a,0x94,0xce,0x68,0xcc,0xee,0xba,0xf9,0x52,0xd9,0x10,0x94,0x05,0x0d,0x4f,0xbd,0x14,0x69,0xfc,0x39,0x4c,0xa4,0x24,0xe2,0x1f,0x88,0x11,0x98,0xb3,0x3d,0x7f,0xab,0xd5,0xa4,0x62,0x02,0x99,0x1e,0xfe,0xe1,0x88,0xa0,0x20,0x67,0xb8,0x50,0x35,0x88,0xce,0xe1,0xa4,0x08,0xd8,0x8c,0x92,0xc1,0x15,0x90,0xff,0xe1,0xa1,0x7a,0x44,0xc6,0x56,0x36,0xc7,0xf1,0xf5,0x8a,0x65,0xdd,0x86,0x9e,0x4b,0xf0,0x30,0x0e,0xf9,0x08,0x4d,0xc0,0x40,0xae,0xa0,0x72,0x8c,0x18,0x2d,0x0a,0x1d,0xcb,0x33,0x44,0x73,0x71,0x96,0x68,0x84,0x54,0xe3,0x0d,0x3e,0x10,0xa3,0x6d,0x80,0x0c,0x69,0xac,0xa7,0x3f,0xf5,0x7c,0xf7,0x4a,0x14,0xca,0x80,0x33,0xce,0xdb,0x10,0x79,0x5d,0x02,0xe5,0xfd,0x45,0xa0,0x85,0xa9,0x2f,0x6d,0xa2,0xd9,0x2f,0x4b,0xd3,0x5f,0xda,0xba,0xcd,0xd5,0x1a,0x8d,0xeb,0xdb,0xba,0x24,0x61,0xc8,0xd1,0xc0,0x7e,0x65,0x27,0x2b,0xd9,0xf8,0xc5,0xd1,0x83,0x72,0x79,0x54,0x88,0x1e,0x00,0x54,0x06,0x21,0xf3,0x3e,0xf3,0xb2,0x97,0x1c,0x83,0x17,0x68,0xc4,0x3b,0xf9,0xda,0x99,0xe3,0xe7,0x3e,0x03,0x9c,0x41,0x6d,0xfa,0x92,0xf5,0x2e,0x95,0xc0,0x64,0xa3,0x1f,0x4f,0x61,0xb2,0xa1,0x4b,0x24,0x31,0x39,0x12,0xcb,0xa4,0x31,0xd9,0xe8,0x45,0x89,0xcc,0x4c,0x72,0xee,0x68,0x9a,0x28,0xac,0xcb,0x6b,0xa3,0x15,0x06,0x06,0x60,0xe0,0xae,0x1c,0xdf,0x89,0xc6,0x19,0x1b,0x97,0x24,0x7c,0x3e,0x33,0x27,0x3f,0x5f,0xf0,0xff,0x3c,0xfd,0x19,0x7d,0x72,0xdf,0x0a,0xca,0xc8,0x6d,0xca,0x71,0x56,0xbe,0xa3,0x49,0x73,0x55,0xce,0x0d,0xa4,0x37,0x55,0x12,0xbf,0x82,0xfe,0xe5,0x81,0x49,0x87,0x1b,0xa7,0x42,0x58,0xa2,0xaa,0x62,0x15,0xc6,0x27,0xeb,0x64,0x15,0x14,0x6c,0xa9,0x06,0xbe,0x62,0x64,0x8a,0x78,0x65,0x06,0xac,0x05,0x06,0x6c,0xa5,0x58,0x8c,0x84,0x97,0x9e,0xd5,0x19,0x96,0xdc,0x89,0xb0,0x6d,0x95,0x5e,0x3c,0xe3,0xad,0xed,0xdb,0x86,0x80,0x78,0x6d,0x0d,0x6d,0x35,0x48,0xe3,0xd1,0x50,0xd6,0xd5,0x0b,0x8b,0x5d,0x36,0xf3,0x2a,0x4b,0x98,0xdc,0xb3,0x9a,0x39,0x46,0x6d,0xf6,0x0d,0xcf,0x6c,0x42,0x3c,0xbd,0xe1,0x51,0xbc,0x75,0xd4,0xff,0x03,0x62,0x74,0x13,0xe8,0x12,0x79,0x20,0x2c,0x25,0x4e,0xeb,0x26,0x0e,0xd6,0x34,0xc7,0xe8,0xeb,0x56,0xd7,0xc1,0xeb,0x74,0xf6,0xbc,0x01,0xdd,0x0b,0xc7,0x13,0xac,0x1a,0xec,0x43,0x93,0xce,0xb4,0xaf,0x52,0xca,0xa5,0x8b,0x78,0xfd,0xe2,0x86,0xde,0xf3,0x20,0xf0,0x12,0x62,0x76,0x75,0x71,0x11,0xb2,0x57,0x92,0x3c,0x98,0x30,0xe3,0x2a,0xcd,0xf3,0xae,0x15,0xef,0xb3,0xb8,0xed,0x09,0x06,0xab,0xa9,0x0e,0x8f,0x29,0x59,0x68,0x9b,0xd6,0x16,0xa1,0x55,0xa9,0x29,0xfd,0x39,0x0b,0xae,0x53,0x46,0xcc,0x62,0x71,0xce,0x92,0xfb,0x8d,0xc2,0x3b,0x4b,0xce,0x8e,0x21,0xa8,0x39,0x61,0x97,0x41,0x10,0x25,0xf9,0x2d,0x0b,0xfa,0x44,0xeb,0x1e,0xfc,0xd6,0x9a,0xbc,0x7d,0xa3,0xdc,0xde,0xd2,0x3b,0x6c,0x7c,0xe1,0x36,0x0d,0x96,0xed,0xe4,0xf4,0x9c,0x57,0xf2,0x23,0x8b,0xb2,0x94,0x18,0x1c,0x3f,0x00,0x53,0x4e,0x18,0xbc,0x32,0x40,0xcb,0x60,0xef,0xf0,0x6b,0xcf,0xb6,0x8c,0xcf,0x0d,0x99,0x57,0x59,0x45,0x3e,0x19,0x15,0xb2,0x8a,0x96,0xec,0xd1,0x0a,0x29,0xa1,0x5a,0xf1,0x94,0x87,0x3d,0x62,0x31,0xe3,0x1b,0x2c,0x14,0xdc,0x88,0xe3,0x9f,0xc7,0x72,0x49,0x08,0x02,0x4a,0x01,0x6b,0xca,0x17,0xae,0x56,0x57,0xb0,0xda,0x0f,0xdb,0x50,0x10,0xc1,0xc5,0xcd,0x98,0xc1,0xd4,0xf0,0xb2,0x56,0xaf,0x2b,0x45,0x6c,0x56,0x02,0xca,0x72,0xcd,0x2c,0x63,0x28,0x56,0xdb,0xe6,0x36,0xad,0x50,0xa1,0x0b,0x84,0xce,0x7a,0x65,0x05,0x8a,0x38,0x3f,0xaf,0xbf,0x27,0xcf,0x47,0x67,0xe4,0xc3,0x5b,0x25,0x79,0xf9,0x2d,0x6b,0x8c,0x70,0x98,0x95,0x8f,0xc5,0x6f,0x58,0xeb,0x82,0x2a,0x83,0xdf,0xf9,0x3d,0x10,0x7e,0x33,0xf1,0x55,0x0c,0xa0,0xdd,0xae,0x48,0xda,0xb0,0x8e,0x18,0x53,0xb6,0x6a,0x64,0x7d,0xcc,0x2f,0x7e,0x38,0x4d,0x88,0x43,0x58,0xc5,0xb1,0xd8,0x83,0x90,0xfc,0x39,0xe7,0xee,0xa2,0x6e,0x58,0x6e,0x9c,0x4d,0x22,0xab,0xcf,0xd3,0x6c,0xfd,0xd9,0xea,0xce,0xbf,0x83,0x7f,0x07,0x35,0x77,0x43,0xce,0x10,0xac,0x8c,0xa0,0xaa,0xa9,0x9c,0xca,0x3b,0x51,0xe4,0xdc,0xbf,0x9e,0x0e,0x87,0x34,0x92,0x51,0x3a,0x02,0x53,0x9a,0x41,0x1f,0x95,0xcb,0x4d,0x8e,0x02,0x86,0xc0,0x92,0x37,0xb6,0x61,0xb3,0xc2,0x54,0x5e,0xcc,0xba,0x5a,0xa9,0xc3,0x25,0xf2,0xa1,0xce,0xd5,0xee,0x37,0xff,0xf8,0x07,0x29,0xfd,0xb3,0xdc,0x54,0x52,0x7c,0x34,0x98,0xbd,0x81,0x02,0xe0,0xe5,0xc8,0xa8,0x34,0x30,0x4d,0x93,0xd7,0xc1,0x7e,0xfa,0x72,0xf9,0x83,0x9e,0x04,0x5f,0x31,0x21,0x1a,0xc4,0xa3,0x9c,0xa4,0x1f,0xfa,0xb0,0x1b,0x56,0x22,0xc0,0x09,0xba,0xce,0x9e,0x1a,0x82,0xec,0x68,0xb5,0xdb,0xfc,0xb6,0x76,0x6d,0xde,0x98,0x17,0x0b,0x7c,0x1c,0x45,0xec,0x6c,0xe4,0xf7,0x83,0xf7,0xbf,0x24,0xc9,0xe4,0x04,0x1f,0x36,0x8a,0x93,0x0e,0x34,0x9b,0x82,0x47,0x55,0xdd,0x92,0x8b,0x40,0xd2,0x21,0xab,0xdd,0xf8,0x49,0x0e,0xba,0x88,0x29,0x33,0x74,0xe2,0x84,0x80,0x46,0xf8,0x30,0x2c,0x36,0xc3,0x4f,0x71,0xc4,0xc1,0x36,0x8a,0x53,0x00,0x38,0x75,0x1b,0x98,0x19,0x24,0x8e,0xff,0x02,0x0b,0x25,0x0d,0x09,0x79,0x51,0xe9,0x41,0x69,0xa7,0x36,0xd6,0xc4,0xc9,0x59,0x60,0xf2,0xf3,0x1f,0x05,0x53,0x59,0x42,0xe5,0xa5,0xfd,0x2d,0x26,0x8f,0x9c,0x06,0x1c,0xa2,0xd0,0x4c,0x4b,0xd5,0x52,0xa9,0xa6,0x90,0xe9,0xa1,0xcf,0xc4,0xcb,0x5e,0xa4,0x4c,0x95,0x24,0x08,0xc0,0x36,0x34,0x39,0x80,0x67,0x65,0x5d,0x0b,0x72,0xdb,0xb5,0xb5,0xbc,0x61,0x7b,0xb3,0xd9,0xcc,0x6a,0x5d,0xb0,0xbe,0x05,0x81,0x6a,0xe5,0x18,0xb5,0xc7,0x13,0x3d,0xa9,0x63,0xd9,0x05,0x50,0x0e,0x67,0x56,0xf8,0x25,0x12,0x7c,0xb4,0x68,0xf5,0xc8,0xb1,0xcc,0x51,0x60,0xb7,0x78,0xcd,0x42,0xae,0x4d,0xdc,0x29,0xbb,0xc2,0xe5,0xe4,0x59,0xb4,0x80,0xd3,0x07,0x97,0xb7,0xd4,0x02,0x62,0x53,0xfc,0x8c,0xc0,0xa7,0x1c,0x2a,0xb0,0x01,0xe1,0x86,0xe0,0x0f,0x34,0x71,0xbc,0x02,0xb9,0x39,0x48,0x85,0xcd,0x17,0x84,0xcc,0x55,0xc8,0xe5,0x2f,0xec,0xd4,0x05,0x2f,0x4e,0xf3,0x33,0x17,0x18,0xac,0x9e,0xb9,0x84,0x83,0x84,0x82,0xca,0x24,0x60,0xd1,0xc7,0xd9,0x7c,0x10,0x38,0xc5,0x90,0x48,0x7b,0x5c,0xe0,0xbb,0x5a,0x38,0x21,0x2d,0x0a,0x39,0x67,0x21,0x01,0xac,0x90,0xb5,0xf0,0x39,0xd4,0x25,0xf1,0x74,0x30,0x00,0x31,0x1b,0x42,0x0c,0x77,0xbf,0x42,0x84,0xa1,0xf3,0x62,0x61,0xe6,0x80,0x6e,0xa8,0xfd,0xfd,0xa8,0xfb,0xff,0xa5,0x3d,0x9f,0x51,0xda,0x93,0x1d,0x82,0xa8,0x75,0xbd,0x55,0x47,0xd6,0xab,0x3f,0x1a,0xca,0x03,0xe5,0xec,0x61,0x4a,0xcd,0xd3,0xd3,0xaf,0x70,0xe1,0x6b,0x47,0xc3,0x2b,0xdc,0x74,0x60,0x1b,0xe8,0xd2,0x03,0xea,0xb7,0x3d,0x56,0x21,0x56,0x0a,0x2e,0x6a,0x1e,0xf2,0x54,0x8a,0xb8,0xf1,0x28,0x13,0xaf,0xbf,0xde,0x84,0x11,0xc8,0x1e,0xd8,0x17,0xfb,0x84,0xe2,0x99,0x07,0x0a,0xe4,0xf9,0x04,0xef,0xbc,0x90,0x3f,0x97,0x06,0x5b,0x89,0xba,0x7b,0xa1,0x8b,0xc3,0xd9,0xc3,0x12,0xc5,0x8f,0xcb,0xc7,0xef,0xcf,0x05,0x26,0x58,0xf2,0x43,0xb6,0xf0,0xf1,0x54,0x5e,0xd4,0x42,0xe4,0x6b,0x16,0x3c,0xbc,0x99,0xe5,0x38,0x9a,0xdf,0x7c,0x73,0x8c,0x16,0x96,0x1c,0xbd,0xe3,0xe5,0x52,0xe0,0x10,0x0d,0xbc,0x76,0x87,0xe0,0x21,0xc4,0x8a,0x21,0x76,0x05,0x3f,0x61,0x95,0x84,0xb1,0xf9,0xb7,0x5e,0xbc,0x23,0x1f,0x98,0xf4,0x5d,0x25,0x74,0x3c,0xb1,0x67,0x4f,0x3e,0x6e,0x65,0x93,0x91,0x1c,0x38,0x1f,0x2b,0x79,0x22,0x76,0xc0,0xa5,0x84,0x47,0x7f,0xcf,0xa6,0xc4,0xb9,0x2a,0xdb,0x91,0xb1,0xfc,0x76,0xc4,0x34,0xdc,0xd0,0x80,0xbf,0xfa,0x2b,0xce,0x37,0x53,0xbc,0xd2,0xdb,0x62,0xd7,0x77,0xe9,0x57,0xdd,0x55,0x9d,0xca,0x2c,0x8a,0x9d,0x95,0x13,0x65,0xe9,0xa8,0xd9,0xf3,0x16,0x35,0x26,0x82,0x3f,0x2e,0x0d,0x26,0xc2,0x7b,0xd9,0x9a,0x35,0xb6,0xd0,0x58,0x28,0x4a,0x86,0xb7,0x8a,0x18,0xaf,0x72,0xb9,0xe6,0x15,0x24,0xa5,0x6e,0x92,0x3d,0x18,0x86,0xc5,0x46,0x91,0xd4,0x0f,0xbc,0x94,0xcf,0x67,0x82,0x46,0xdc,0xa1,0x1e,0xb8,0xf8,0x5c,0x9e,0x06,0x2e,0x14,0x5f,0xf8,0x02,0xaa,0x90,0x6b,0x0b,0x7b,0xde,0x92,0x4c,0x03,0xa1,0x6f,0x63,0x48,0x9a,0x4d,0x2c,0x00,0x15,0xea,0xf7,0x5a,0x3c,0x9e,0x61,0xfc,0x9d,0x74,0x5f,0x6f,0x31,0x45,0xf9,0x7b,0xd5,0x96,0xc7,0x36,0xf5,0x2a,0x5b,0x7b,0x24,0x3e,0x18,0xd1,0xc1,0x0d,0xc6,0x2b,0x20,0xdc,0x73,0x4f,0xe8,0x0b,0xb6,0x12,0xd8,0x6a,0xf1,0xbb,0xae,0x34,0x7b,0x42,0xad,0xf6,0xd4,0xbe,0x53,0x9e,0xc5,0xce,0x4c,0x06,0xf8,0x6b,0x27,0xff,0x0a,0x1d,0x6d,0xc8,0x1f,0x17,0x1f,0xdc,0xd7,0x62,0x49,0x6b,0xf0,0x9a,0x95,0x5b,0x2c,0xf4,0xa5,0x5f,0x97,0x05,0x34,0xe1,0x76,0xd3,0xe0,0x2f,0xd2,0xf2,0x0c,0xdc,0x27,0xec,0x91,0xd5,0x3a,0xd7,0x59,0x8b,0xba,0xc3,0x9d,0x77,0xbd,0x7f,0xed,0x1d,0xed,0xf7,0xae,0x40,0xf5,0x66,0xf2,0x14,0xa2,0x74,0x34,0x53,0xda,0x6a,0x4d,0x15,0x0b,0xaf,0x2e,0xcd,0x4f,0xa9,0xac,0x54,0x82,0x6d,0xb6,0x0b,0xb3,0x9b,0x97,0x86,0xec,0x69,0x15,0x7b,0x5a,0x79,0xcf,0x46,0xb1,0x67,0x23,0xef,0xd9,0x2c,0xf6,0x6c,0x5e,0xce,0x3a,0x5f,0xdd,0xc6,0xe7,0xbb,0x64,0xf6,0xaf,0x60,0xdc,0x3f,0x93,0xea,0x8f,0xde,0xef,0xcd,0x96,0xbf,0xce,0x57,0x6e,0xef,0xc5,0x55,0xcd,0x27,0x5d,0x0b,0x8a,0x9b,0xeb,0x47,0xef,0x3c,0x99,0xcb,0x72,0x9f,0x7a,0xe9,0xf9,0x55,0xf5,0x85,0x06,0x99,0x4f,0x66,0xe1,0xb6,0x78,0xc0,0xdd,0xb2,0xb3,0xb7,0x7d,0x10,0x61,0xe7,0xc7,0x68,0x01,0x64,0x2e,0xba,0xf2,0xcd,0x37,0x76,0xa7,0x8e,0xb1,0x35,0x16,0x60,0x47,0xc0,0x7c,0x89,0x57,0x13,0x25,0x0f,0xd5,0x26,0xeb,0xf8,0xae,0x8c,0x46,0xd7,0x6e,0x2f,0x1c,0xf5,0x3c,0x2d,0x01,0x9e,0xf1,0x59,0x9d,0x0a,0x0f,0x04,0x98,0x82,0x37,0x92,0x77,0xab,0x8b,0xae,0x6c,0x61,0xbf,0x13,0x65,0xa3,0x4f,0xad,0x11,0x75,0x3e,0x7a,0xf1,0xe3,0x77,0xb7,0x5f,0x90,0x97,0x58,0x78,0xfa,0x3c,0x45,0x34,0x66,0x57,0xbc,0x9c,0xfa,0xe9,0x91,0x23,0x9b,0x47,0x04,0x14,0xb5,0x96,0xb2,0x4e,0x80,0xf3,0x20,0x8c,0x1d,0x85,0x8d,0x13,0x4b,0x01,0x2d,0x9e,0xc9,0xb1,0x79,0x88,0x91,0x87,0x21,0xa8,0xba,0x91,0x49,0x0a,0xcd,0xce,0xb5,0x03,0x91,0x3a,0x5b,0x3e,0x9c,0x10,0x2f,0x81,0x58,0xfd,0x0c,0x02,0x12,0x83,0xbd,0x24,0x80,0x05,0x36,0xce,0x20,0x99,0x3a,0xbe,0xa8,0xf6,0x47,0x5c,0xf3,0x82,0x7f,0xc8,0x48,0x03,0xf0,0x4f,0xe8,0x91,0xd4,0x1a,0x54,0x7e,0x2e,0x8f,0x48,0xc9,0x2b,0x0b,0xfe,0x1d,0x9f,0x00,0x5a,0x5c,0xbc,0xaa,0xa4,0xf9,0xea,0xfe,0xe4,0x8a,0x6a,0xdd,0x07,0x80,0xe4,0xc7,0xff,0xf1,0xa1,0x73,0xa8,0xc9,0x11,0x58,0xa3,0x25,0xbe,0xe2,0x9d,0x48,0xcd,0x72,0x6f,0x03,0x70,0x21,0x9e,0xb2,0x0b,0xf6,0xf2,0x35,0xea,0x02,0x5d,0xf8,0xe9,0x26,0xfb,0x4d,0x1c,0xc2,0xdf,0xcb,0x08,0x86,0x84,0x1f,0x6e,0x9b,0x7f,0x77,0x10,0x8f,0x32,0x86,0xe5,0x07,0x4f,0x8c,0xe0,0xaf,0xd9,0xd3,0x95,0x39,0x89,0x59,0xf9,0xc0,0xa3,0x12,0x05,0x33,0xf6,0xe7,0x47,0x37,0xc8,0x5d,0x90,0x52,0x64,0x29,0x2f,0xb7,0xe6,0x2e,0xe5,0x86,0x5a,0x62,0x9e,0x49,0x1b,0x19,0x4b,0x1a,0xad,0x0d,0xdd,0xb8,0x49,0xb2,0xae,0xa4,0xd8,0xf5,0xa2,0x65,0x6e,0x18,0x37,0xca,0xe1,0x38,0x77,0x47,0xbc,0x3a,0xf3,0x5d,0xcf,0xc6,0xb9,0x73,0x3a,0xf1,0x42,0x50,0xcc,0xdc,0xd1,0x6e,0x0a,0xf7,0xe0,0x37,0x6a,0xfd,0x40,0x21,0x3c,0x10,0xe3,0x74,0xbd,0x5d,0x0a,0xb7,0xf6,0xc2,0xa9,0xef,0x12,0x7c,0xd0,0x05,0x9f,0x39,0x25,0xf3,0xd0,0x80,0x95,0x0a,0x37,0x7f,0xca,0x11,0xe5,0x4d,0xa9,0x9a,0x53,0x0c,0x7c,0xda,0x52,0x67,0xb8,0x14,0xc7,0xf1,0xe1,0x41,0xae,0x59,0x65,0x30,0x7f,0xf9,0x88,0xca,0xd8,0x81,0xf4,0x0e,0x98,0x5d,0xe0,0xc5,0x0b,0x9f,0x6a,0xbf,0xeb,0x69,0xf8,0xa2,0xb7,0xb8,0x31,0x4c,0xf4,0x36,0xb6,0x2b,0x94,0x81,0x1c,0xc6,0xc6,0x67,0xd1,0xa0,0x39,0x91,0xc3,0xcf,0xb4,0x84,0xa7,0x48,0x64,0x2c,0xe7,0x14,0xf7,0x28,0x27,0x41,0x0e,0x23,0xee,0x03,0x94,0x12,0x23,0x50,0x93,0xf6,0x32,0xd8,0xca,0x53,0xca,0x12,0x29,0x24,0xbd,0x09,0x44,0xbe,0x92,0x20,0x4a,0x14,0x07,0xd0,0xb3,0xba,0x35,0x29,0x81,0xac,0xf6,0x5f,0x08,0xa0,0x2a,0x65,0x52,0xc8,0x6a,0xe4,0x87,0x55,0xfb,0x72,0xf1,0xf9,0x64,0xa6,0xd6,0xa4,0x8c,0xec,0x7d,0x32,0x73,0x59,0x02,0x1b,0x51,0x29,0xeb,0x05,0x3a,0xaa,0x61,0x85,0xb4,0xf3,0xa8,0xaa,0xb7,0xb5,0x65,0xa4,0x28,0xdb,0xda,0x32,0xf8,0xcd,0x61,0x82,0x80,0x91,0xd3,0x1d,0x97,0x9f,0x2d,0xf2,0xdf,0xaa,0x0d,0x5f,0xce,0x79,0x57,0x5e,0xf9,0xf2,0xa8,0xfb,0xf6,0xc3,0x6b,0xfb,0x89,0x2e,0x55,0x2e,0x42,0x60,0x2e,0xbb,0x1d,0xfa,0xa4,0x02,0x38,0x76,0xbe,0xe6,0x87,0xfd,0xb2,0x31,0xc4,0x36,0x4d,0x37,0x16,0x3e,0x6c,0x00,0x51,0xda,0x18,0x5f,0x01,0xe7,0xe2,0x2f,0xa5,0x2c,0xf7,0x91,0x27,0x03,0x9e,0xa7,0x17,0xf6,0xaf,0xbb,0x87,0x78,0x8e,0xdc,0x7b,0x8d,0x25,0xec,0xbb,0x27,0xf0,0xb9,0x7b,0x7c,0xc2,0xbe,0xff,0x0b,0x0b,0xd6,0xcf,0x0f,0xd9,0xe7,0x7b,0x6c,0x3f,0xff,0x19,0x3e,0x4f,0x7b,0xc7,0xf0,0x79,0xb4,0x87,0x87,0xe1,0x87,0x47,0x1f,0xf0,0xb4,0xaf,0xb7,0x67,0x5f,0x5e,0x14,0x1f,0x58,0xb8,0x9c,0x2d,0xf5,0x78,0xc3,0xdc,0x22,0x92,0x27,0x3c,0x88,0x60,0x1b,0xd3,0xc8,0xb7,0xce,0x4f,0xde,0x8b,0xfb,0x65,0x5e,0x46,0x00,0xbf,0x35,0xa4,0x1c,0x08,0xe8,0xdc,0x0b,0x68,0x07,0x03,0x71,0x73,0x14,0xd1,0xa1,0x05,0x20,0x0c,0xc7,0x94,0x8c,0xc4,0x9b,0x67,0x76,0xa2,0x06,0x08,0x17,0x48,0x3b,0x63,0x97,0x96,0xf9,0xd3,0xf3,0xe8,0x46,0x0b,0x17,0xc6,0x0e,0xac,0x97,0xdd,0xae,0x16,0x87,0xf1,0x9c,0x26,0x1b,0x86,0x08,0x47,0xf4,0x36,0xbc,0x51,0x10,0x06,0x2c,0xb2,0xf8,0x15,0xc5,0xbb,0x4e,0xec,0x78,0xba,0x2e,0x31,0xc5,0x30,0x56,0x95,0x3a,0x36,0x6d,0x91,0x42,0xcc,0x2b,0xde,0x48,0x0b,0x01,0x50,0x4d,0x94,0x97,0x3d,0x30,0x51,0x78,0x12,0xb3,0x89,0x16,0xd4,0x87,0xe5,0x4c,0xf2,0x1b,0xd3,0x6e,0x7c,0xe0,0xcd,0xf7,0x6e,0x28,0xea,0x85,0xe3,0xba,0xfc,0x11,0x4e,0x39,0x63,0x07,0xa5,0x08,0x1a,0x95,0x17,0x32,0x52,0x9a,0xe8,0xa2,0x66,0x4a,0x2e,0x9e,0x15,0xa0,0xe4,0x0d,0x18,0xd2,0x95,0xdf,0xb3,0xd3,0x2c,0x87,0x6e,0x6a,0xd8,0x96,0x05,0x6a,0xc5,0xd8,0x4d,0xc4,0x6d,0x4b,0xc6,0x6c,0x26,0x61,0xc4,0xc1,0xdf,0x7c,0x51,0x7c,0xcc,0x68,0x21,0x1a,0x4b,0x15,0x17,0x3d,0xb1,0xe4,0xa7,0x58,0xe3,0x53,0x70,0x12,0xd5,0x49,0xf5,0x8f,0xf2,0xea,0xa5,0xc7,0x53,0x8f,0x43,0xdf,0x47,0xe5,0x12,0x77,0x0e,0x32,0x75,0x2a,0xbe,0xee,0x48,0xbd,0xb5,0x51,0xeb,0x62,0x37,0x8b,0xcf,0x36,0x43,0x3c,0x9f,0x83,0x53,0x01,0x54,0x9f,0x23,0x55,0x7b,0xe5,0x93,0xa4,0x0b,0x5e,0x44,0x71,0xeb,0xc5,0x5e,0xdf,0xf3,0xbd,0xe4,0x9e,0xbb,0xa7,0x42,0xd5,0x44,0x36,0x6f,0xe4,0xb9,0x2e,0x0d,0x76,0x0a,0x78,0xb4,0xb5,0x4f,0xdc,0x17,0xc6,0xbc,0xe2,0x3d,0x0f,0x21,0xb7,0x04,0xca,0x92,0x9f,0x51,0x61,0x5c,0x20,0xfa,0xac,0xb3,0xdd,0x10,0x2f,0xa0,0xda,0x6e,0xf0,0x57,0x1c,0x37,0xd8,0xff,0xfc,0xe2,0x3f,0xb3,0x6b,0xf0,0x74,0x0c,0x63,0x00,0x00}; +const char html_content[] = {0x1f,0x8b,0x08,0x00,0x11,0xd0,0x6b,0x69,0x02,0xff,0xed,0x3d,0x6b,0x57,0xdb,0xc6,0xb6,0x9f,0x4f,0x7f,0xc5,0x40,0x52,0x2a,0x25,0x42,0xb6,0x81,0xf4,0x61,0x23,0x73,0x08,0x38,0x6d,0x9a,0xf0,0x58,0x3c,0xd2,0x9e,0xcb,0x61,0x21,0xd9,0x1a,0x63,0x15,0x59,0x72,0x25,0x19,0x42,0x85,0xff,0xfb,0xd9,0x7b,0x1e,0xd2,0xe8,0x61,0x63,0x92,0x93,0x74,0xdd,0x75,0x6f,0xbb,0x6a,0xec,0x79,0xec,0xd9,0xb3,0xdf,0x7b,0x66,0x4b,0xdd,0x5e,0x71,0xc3,0x41,0x72,0x3f,0xa1,0x64,0x94,0x8c,0xfd,0xee,0x76,0xe2,0x25,0x3e,0xed,0xee,0x85,0x41,0x12,0x85,0x3e,0x39,0x76,0x02,0xea,0x6f,0x37,0x78,0xe3,0xf6,0x98,0x26,0x0e,0x19,0x40,0x17,0x0d,0x12,0x6b,0xf5,0xce,0x73,0x93,0x91,0xe5,0xd2,0x5b,0x6f,0x40,0xd7,0xd9,0x0f,0xc3,0x0b,0xbc,0xc4,0x73,0xfc,0xf5,0x78,0xe0,0xf8,0xd4,0x6a,0x99,0xcd,0x55,0x12,0x38,0x63,0x6a,0xdd,0x7a,0xf4,0x6e,0x12,0x46,0x49,0x77,0x3b,0x4e,0xee,0x01,0xd2,0xb3,0xbb,0xc8,0x99,0x4c,0x68,0x94,0x26,0xf4,0x63,0xb2,0xee,0xf8,0xde,0x75,0xd0,0x1e,0x00,0x50,0x1a,0x75,0xfa,0xe1,0xc7,0xf5,0xd8,0xfb,0xcb,0x0b,0xae,0xdb,0xfd,0x30,0x72,0x69,0xb4,0x0e,0x2d,0xb3,0x67,0x62,0xd5,0x74,0xec,0x7c,0xe4,0x6b,0xb5,0x5f,0x35,0x9b,0x93,0x8f,0x9d,0xb1,0x13,0x5d,0x7b,0x41,0xdb,0x99,0x26,0x61,0x67,0xe2,0xb8,0x2e,0xce,0x6b,0x92,0x16,0x74,0xcd,0xfa,0xa1,0x7b,0x5f,0xb3,0x80,0x98,0xd1,0xcc,0x87,0xcf,0x5e,0xa4,0x83,0xd0,0x0f,0xa3,0xf6,0xb3,0x8d,0x21,0xfe,0xdb,0xe9,0x3b,0x83,0x9b,0xeb,0x28,0x9c,0x06,0xee,0xba,0xe8,0x18,0x0e,0x87,0x9d,0x21,0xa0,0xb0,0x3e,0x74,0xc6,0x9e,0x7f,0xdf,0x3e,0x0c,0x93,0x90,0x9c,0x3a,0x41,0x6c,0x7c,0xa0,0x91,0xeb,0x04,0x8e,0x11,0xc3,0x8f,0xf5,0x98,0x46,0x9e,0x18,0x08,0x7b,0xa0,0xed,0x96,0xb9,0x11,0xd1,0xf1,0xcc,0x0b,0x26,0xd3,0xc4,0xe8,0x4f,0x93,0x24,0x0c,0x54,0x8c,0x22,0xef,0x7a,0x94,0xd4,0xef,0xb8,0x06,0x07,0x3a,0xa4,0x2e,0xfd,0xa9,0xc3,0xc7,0xb4,0x5b,0x93,0x8f,0x24,0x0e,0x7d,0xcf,0x25,0xcf,0xfa,0xce,0x4f,0xdf,0xbf,0xea,0x8b,0x8e,0xf5,0xc8,0x71,0xbd,0x69,0xdc,0x7e,0x05,0xc4,0xe1,0x84,0x6a,0x35,0x9b,0xdf,0x72,0x14,0x2e,0x90,0xcf,0x16,0x22,0x70,0x69,0x28,0x0d,0xc1,0x74,0xdc,0xa7,0xd1,0x65,0xaa,0x6e,0x70,0x1c,0x06,0x61,0x3c,0x71,0x06,0x54,0xcc,0x8c,0xa8,0xe3,0x86,0x81,0x7f,0x7f,0x99,0xd6,0x20,0xb6,0x85,0xff,0xce,0xaa,0xfb,0xe3,0x14,0x9f,0x99,0x83,0x91,0x13,0x5c,0x53,0xd7,0x00,0x2e,0x8e,0xc7,0x5e,0x72,0xd5,0x4f,0x82,0x34,0x27,0xec,0x8a,0x37,0x46,0xd9,0x70,0x82,0xa4,0x66,0xd3,0x1b,0xce,0xd6,0x4f,0x9b,0x6e,0x3e,0x04,0x24,0xc1,0x09,0x06,0xd4,0x7f,0x0a,0x8c,0x1f,0x36,0x36,0x0b,0x00,0x32,0x24,0x8c,0x02,0xb0,0x69,0x14,0xc3,0xe8,0x49,0xe8,0x09,0x39,0x64,0x64,0x0e,0xc2,0x80,0x0a,0x3a,0x6e,0xbd,0xfa,0x56,0x08,0xcf,0x7a,0x12,0x4e,0xda,0x28,0x63,0x99,0x0c,0xb1,0x1f,0x8c,0x7e,0x77,0x14,0x99,0xda,0xfe,0xa1,0xd9,0x54,0x57,0xba,0x70,0xbd,0xd8,0xe9,0xfb,0xd4,0xbd,0x54,0xd7,0xcc,0x5b,0xe5,0x56,0x7e,0xfc,0xf1,0xc7,0x8e,0x40,0x24,0x08,0x91,0x8c,0x7e,0x78,0x47,0xdd,0x9a,0x3d,0x6d,0x6d,0x6d,0x29,0x7b,0x4a,0x10,0x4a,0x2a,0x04,0x00,0x46,0xf8,0xce,0x24,0xa6,0x6d,0xf9,0xa5,0xa3,0xb0,0xc4,0xa7,0xc3,0x44,0x95,0x8b,0xc4,0x4d,0x33,0xa1,0x03,0xf6,0x8d,0x55,0xb9,0x12,0x02,0x27,0xf7,0xf8,0x23,0xe8,0x54,0x3c,0x1d,0x03,0x09,0x0a,0x6a,0xc5,0x20,0x2a,0x6a,0x52,0x4b,0xff,0x1a,0xe1,0x94,0x60,0xcd,0x4d,0x50,0x91,0x0a,0xed,0xcc,0xc1,0x58,0x62,0xc6,0x79,0xa0,0x2a,0xd5,0x2b,0x54,0xaa,0x67,0xe3,0xf8,0xba,0x46,0xd8,0x46,0xad,0x34,0x1f,0xba,0x21,0x86,0x4e,0xc2,0xc9,0x74,0xb2,0x1e,0xde,0xd2,0xc8,0x77,0xee,0xd3,0xbf,0xd6,0xbd,0xc0,0xa5,0x1f,0x91,0x00,0xcd,0x1a,0x74,0x9b,0xec,0x9f,0xfe,0x66,0xe7,0x8f,0x69,0x9c,0x78,0xc3,0xfb,0x75,0x61,0x7b,0xa4,0x01,0x61,0xcb,0xad,0x7b,0x09,0x1d,0xc7,0xb2,0x29,0x27,0x68,0x67,0xc4,0xf7,0xc0,0xbe,0x03,0x7b,0x27,0xb0,0x20,0xdf,0xc0,0x24,0x8c,0xc1,0x38,0x86,0x41,0x7b,0xe8,0x7d,0x04,0x96,0xa2,0x08,0x35,0x3b,0x48,0x3c,0x30,0x40,0x02,0x41,0x69,0xe4,0x14,0x6a,0xd6,0x18,0xc8,0x39,0x3a,0x52,0x22,0x71,0x8b,0x5b,0x47,0x69,0x2d,0xb7,0x9a,0xaa,0xb8,0x6e,0xe2,0x0f,0x66,0x77,0x46,0xa0,0xd6,0x77,0x60,0x31,0xb7,0x80,0xed,0xdf,0xc3,0x7f,0x62,0xf7,0x5b,0x6e,0x09,0x27,0x32,0xda,0x48,0x17,0x32,0x59,0x20,0xa1,0x28,0x48,0x79,0x5b,0x64,0xb2,0x14,0x04,0x95,0xd1,0x2d,0xe4,0xde,0x3f,0xc7,0xd4,0xf5,0x1c,0x12,0x0f,0x22,0x4a,0x03,0xe2,0x04,0x2e,0xd1,0xd8,0x9e,0xb6,0xad,0xcd,0x57,0xb0,0x0f,0x3d,0xad,0x71,0x0e,0x8c,0xfc,0xb9,0x3f,0x00,0x79,0xe3,0x3a,0x42,0x92,0x88,0x30,0x91,0xaf,0x33,0xb9,0xca,0x54,0xc9,0xb9,0xbe,0x1f,0x0e,0x6e,0xb2,0xa9,0xa9,0xd8,0x9d,0x54,0x15,0xa4,0x62,0x71,0xa4,0xdc,0x32,0xb7,0x85,0x71,0x3a,0x7f,0x97,0x3e,0x05,0x5c,0xc1,0x0b,0xb6,0xf1,0x63,0x9e,0xa8,0x5d,0x3b,0x13,0xc9,0xc8,0x8c,0xac,0x1b,0xea,0xaa,0x08,0xa6,0xb4,0x26,0x11,0x76,0xb8,0xe8,0xd2,0xca,0xd6,0xed,0xbf,0xe0,0x5d,0xc6,0x80,0x50,0x46,0xb2,0x92,0x2d,0x24,0x1b,0xb5,0x06,0xb1,0x0e,0xd1,0xf6,0x08,0xf5,0xd2,0xa8,0xed,0x33,0x27,0x91,0xc7,0xec,0xcd,0x42,0xc1,0xe1,0x18,0xce,0x16,0x42,0xe0,0xab,0xd4,0xf0,0xe3,0x47,0xe7,0x87,0xe6,0xd6,0x2b,0x39,0x99,0x39,0x3b,0xc6,0x05,0xc7,0x0b,0x6a,0xc7,0x17,0xe4,0x9c,0x31,0x83,0x34,0x0b,0xb3,0x4b,0x84,0x5f,0x46,0x81,0x3f,0xc7,0xb3,0xd7,0xb8,0x20,0x55,0x79,0xcc,0xf8,0x4f,0x74,0x6e,0x7c,0xfc,0xe6,0xab,0x6f,0x8b,0x26,0xa0,0xcc,0xa0,0xed,0x06,0x8f,0xcf,0xb6,0x1b,0x23,0xf0,0xf7,0xdd,0x6d,0x0c,0xa1,0xba,0xdb,0xae,0x77,0x4b,0x3c,0xd7,0x12,0x31,0x5b,0xf6,0x5b,0xc8,0x6a,0x77,0x7b,0xd4,0xea,0xee,0xf9,0xd3,0xc1,0xcd,0x1e,0xb8,0x3b,0x50,0x4f,0x98,0xdc,0x82,0x38,0x12,0x75,0x06,0xfe,0xc0,0xf8,0x04,0x21,0x31,0x6e,0x90,0x30,0x18,0xf8,0xde,0xe0,0xc6,0x5a,0x8d,0x69,0xe0,0x8a,0xf1,0xda,0x77,0x71,0xe2,0x44,0xc9,0x77,0xfa,0x2a,0x19,0xf8,0x4e,0x1c,0x5b,0x60,0xfa,0xbb,0xa7,0x67,0xbb,0x27,0x67,0xdb,0x0d,0x3e,0x0d,0xf0,0x41,0x18,0x4b,0xc0,0x09,0x27,0x05,0x30,0x84,0x6d,0xc7,0x7a,0xcc,0x3b,0xc1,0x6a,0x47,0xc7,0x4f,0x5d,0x0c,0x40,0x84,0x45,0x9c,0xcf,0x0f,0xf7,0x8f,0x4a,0x50,0x1a,0xb8,0xff,0x86,0xa4,0x85,0x4a,0x12,0x88,0xa3,0x7d,0x08,0xb0,0x02,0x6b,0xb3,0xbb,0xcd,0xe4,0x06,0x49,0x0a,0x2e,0x8d,0xc8,0x48,0x4b,0x85,0xb0,0x60,0xca,0xf9,0xef,0x57,0x67,0x6f,0x0f,0x7a,0x88,0x24,0x8b,0xb2,0x2c,0x90,0xcc,0x9b,0x3d,0x1e,0x70,0x69,0xc9,0xc8,0x8b,0x75,0x20,0x02,0x9d,0x58,0x2d,0xc2,0x62,0x3d,0xd7,0x49,0x68,0xe2,0x8d,0xe9,0x3a,0x98,0x2b,0xc7,0xef,0x12,0xb9,0x47,0x80,0x14,0x84,0x77,0x18,0x94,0x64,0xdb,0x8d,0x69,0x72,0x06,0x23,0xcf,0xc2,0xc3,0xf0,0x4e,0xd3,0xbb,0xa7,0xf7,0xc1,0x80,0x60,0x43,0xdd,0x16,0x05,0x9b,0x4f,0x07,0x23,0xea,0x4e,0xc1,0x56,0x9e,0x22,0x47,0x73,0x42,0xaa,0xd8,0x1e,0x1c,0x7d,0xe8,0x5d,0x31,0xf6,0xe6,0x38,0xf3,0x3f,0x72,0xb6,0x40,0x9b,0x07,0xab,0xb0,0xe0,0xa2,0x85,0x7a,0x28,0x6f,0x73,0x97,0xe9,0x1d,0xee,0x7f,0xc6,0x22,0xcf,0xc8,0x01,0x18,0x8d,0xb8,0xb1,0xef,0xdc,0xd7,0xaf,0x71,0x78,0x7e,0xc0,0xd6,0x39,0x25,0x60,0x0b,0xad,0xe6,0x32,0x4b,0xf1,0x58,0xbb,0x66,0xb1,0x43,0xb0,0x13,0x6c,0x3d,0xb2,0x3b,0x87,0x70,0x87,0xbd,0xdf,0xcf,0xae,0x76,0xdf,0xef,0x9e,0x1c,0x64,0x32,0x52,0xcb,0xd3,0x2a,0xec,0x13,0x3a,0x06,0x73,0x66,0x92,0x7d,0x0f,0x34,0x0d,0x82,0x4f,0xa2,0x0d,0x13,0xbd,0x7e,0x91,0x93,0xde,0xc1,0xd5,0xfe,0xdb,0x53,0x85,0x37,0xf0,0xe9,0xfa,0x94,0x83,0x00,0xc3,0x81,0x30,0xb8,0x78,0x2d,0xb5,0x2d,0xb6,0xa3,0x25,0xd6,0xdd,0x3f,0x79,0x0b,0xfc,0x62,0x2b,0x7f,0x3e,0x2d,0x7f,0x05,0x25,0x27,0xbf,0x30,0xb3,0x46,0x34,0x2f,0x98,0xb3,0xe4,0xaf,0xbb,0x7b,0xef,0xfe,0x5b,0x2b,0xbe,0x76,0x12,0x30,0xed,0xf7,0x44,0xfb,0x50,0xb7,0xd8,0x6d,0xe8,0x27,0xce,0x35,0x5d,0xa0,0xda,0xdd,0xe3,0x28,0xbc,0x8e,0x9c,0x31,0x39,0x79,0x43,0x80,0xd4,0x61,0x42,0xe7,0x5a,0xa1,0x09,0x1f,0x79,0xf2,0xe6,0x94,0xfe,0x39,0xa5,0x40,0x55,0xd0,0x4d,0x39,0x7b,0xd7,0xf7,0xc9,0x6b,0xee,0xff,0x16,0x19,0x22,0x01,0x51,0x66,0x1f,0xcc,0x98,0x67,0x49,0x49,0xb6,0x10,0x4a,0x13,0x06,0xac,0x66,0x44,0xfd,0xd0,0x71,0x35,0xbd,0xd3,0x05,0x46,0x0e,0x9c,0xc8,0xcd,0x61,0xd7,0x41,0xca,0x92,0x9e,0x0c,0x12,0x6f,0x3a,0x76,0x00,0xc5,0x18,0x2d,0x89,0x03,0x32,0xc1,0x65,0x48,0x41,0xb3,0x1f,0xf1,0xff,0x5c,0x0a,0xbe,0xd7,0x8f,0xbb,0xdb,0x22,0xd7,0xe8,0x82,0x48,0x1e,0x9d,0xf5,0xc8,0xde,0xd1,0xe1,0xd9,0xc9,0xd1,0x7b,0x70,0x51,0xa2,0x9d,0xe0,0x70,0x92,0x93,0x67,0x1c,0x4e,0x63,0x0a,0xd1,0x6c,0x00,0x86,0x1a,0xcd,0x0f,0xa7,0xa3,0xf6,0xdd,0xf0,0xce,0xfd,0xce,0x20,0xf4,0x16,0x7c,0x15,0x98,0x6b,0x70,0x5a,0xe1,0x74,0x30,0x62,0x23,0x16,0x0e,0xe4,0x76,0x9d,0xb9,0x4f,0x09,0xdc,0xa7,0x80,0xb9,0x85,0x5e,0x46,0x4c,0xd1,0x65,0xcf,0x74,0x52,0x6e,0x66,0xab,0x80,0xbf,0x28,0xb4,0x77,0xdf,0xfc,0xb6,0x9f,0xed,0x78,0x09,0xd4,0x23,0x7a,0xbb,0x1c,0xea,0xc5,0x81,0x5f,0x04,0xf5,0x93,0xde,0x87,0xa7,0xa0,0x3e,0x9d,0x2c,0x87,0x79,0x61,0xdc,0x17,0x41,0xfc,0xfc,0xf8,0x29,0x78,0x63,0xd3,0x72,0x98,0x97,0x46,0x7e,0x11,0xdc,0xf7,0x8f,0x7e,0x3b,0x7c,0x0a,0xf6,0xce,0xf4,0xe3,0x72,0xc8,0x17,0x07,0x7e,0x11,0xdc,0x77,0xcf,0x7f,0x57,0x8c,0x50,0xa6,0xd6,0xb5,0x3a,0xbe,0xbf,0x7b,0xf8,0x73,0xef,0x84,0xfc,0xcf,0xd1,0x61,0x4f,0x55,0xf0,0x62,0x10,0xb9,0x07,0x61,0x74,0x3f,0x62,0x36,0x69,0x7e,0x8c,0x36,0x10,0x83,0x60,0x8f,0x7f,0x80,0x1b,0x80,0x08,0x8d,0xbb,0x83,0xc2,0xe4,0x2a,0x41,0xab,0xb3,0xdd,0xc8,0xbb,0xa5,0x38,0x7d,0x1f,0xbf,0xd4,0xce,0xaf,0x9a,0xf1,0xbd,0x69,0x14,0x61,0xaa,0xfb,0x81,0x46,0x71,0x01,0xcd,0xdc,0x17,0xf0,0x9e,0x45,0xbe,0xe0,0x8d,0x17,0x8d,0xef,0x9c,0x88,0x96,0x67,0x3b,0x83,0x01,0x9d,0x24,0x96,0xd9,0xf7,0x58,0xbc,0x36,0x14,0xc3,0xae,0x86,0x1e,0x04,0x42,0x3c,0xd2,0x55,0x4f,0x1c,0xb8,0xcb,0xc2,0xce,0x2e,0x51,0xe3,0xbc,0xe9,0x04,0xcd,0x79,0xc1,0x40,0xf3,0x26,0xb9,0x2e,0xea,0x0c,0x6b,0x20,0x39,0x26,0x73,0x77,0xfc,0x3e,0xbc,0x86,0x61,0x7e,0xd5,0x5f,0xc1,0x4a,0x7e,0x78,0x5d,0x58,0x06,0x05,0x16,0xe1,0xc2,0x1c,0x9c,0x82,0xf2,0x2d,0x5a,0x08,0x34,0x2d,0xf0,0x58,0x42,0x12,0x10,0xa6,0x10,0x89,0xac,0x63,0x61,0x8c,0x1e,0xd1,0x7e,0x18,0x26,0x9f,0x94,0x12,0x9c,0xf4,0x5e,0x1f,0x1d,0x9d,0x2d,0x10,0x95,0x62,0xe6,0xe1,0x53,0xfa,0xf4,0xd4,0x63,0x63,0xf3,0x87,0xee,0xe9,0xfb,0x5e,0xef,0xb8,0x4e,0x4f,0x1a,0x90,0x66,0xc9,0x4f,0x91,0x71,0x15,0x8e,0xb1,0x4a,0xad,0x79,0x36,0xb6,0x91,0x37,0x8a,0x73,0xfb,0xc6,0x68,0xa3,0xbb,0x3d,0xc9,0x9b,0xc7,0x34,0x8e,0x21,0x1e,0x81,0x8e,0x49,0x09,0x4a,0x29,0xff,0xad,0x91,0x2a,0x45,0x94,0x95,0x29,0xb5,0x88,0x8a,0x4c,0xbc,0xb8,0x15,0xfe,0x19,0x0f,0x22,0x6f,0x92,0x74,0x7d,0x9a,0x10,0x88,0x5e,0x1d,0x2b,0x9d,0x19,0x13,0x0c,0x0f,0xce,0x90,0xab,0x7b,0xa0,0x1b,0x09,0x75,0xad,0x95,0x96,0x31,0x09,0x7d,0xff,0x2d,0x66,0xcd,0xb7,0x8e,0x0f,0xd1,0x97,0xef,0x1b,0xe3,0xd0,0x75,0xfc,0x13,0x0a,0x09,0xf2,0x2d,0x55,0x5a,0x20,0x32,0x19,0x7b,0x71,0xcc,0x66,0x75,0x00,0xff,0x38,0x21,0x10,0xc9,0x01,0x2a,0x5d,0x37,0x1c,0x4c,0xc7,0x40,0x19,0xf3,0x9a,0x26,0x3d,0x9f,0xe2,0xd7,0xd7,0xf7,0x6f,0x5d,0xcd,0x73,0xf5,0xce,0x70,0x1a,0x0c,0x50,0xa3,0x49,0x3c,0x0a,0xef,0x0e,0x10,0x8e,0xc6,0x48,0x66,0x08,0x0a,0x89,0x33,0xfb,0xd8,0xba,0xb8,0x34,0xc2,0x09,0x8e,0x8c,0x01,0x55,0x3d,0x45,0xbc,0x05,0x1b,0xac,0x79,0xf0,0xed,0x02,0xb7,0x6c,0xdd,0x60,0x90,0x7b,0xfe,0x63,0x13,0xd8,0x30,0x18,0x2e,0x50,0x78,0x7c,0x82,0x18,0x08,0x53,0x04,0xb6,0x8f,0x4f,0x11,0x03,0x61,0x0a,0xe3,0xde,0x9e,0xe4,0xf7,0x63,0xf3,0x4a,0xe2,0x21,0xe7,0x3f,0xbe,0x20,0x1b,0x66,0xeb,0x1d,0x41,0x03,0x13,0x4f,0x45,0xf6,0xc4,0x45,0x52,0x81,0xe2,0xd0,0xe7,0x05,0x00,0xfa,0x97,0xb3,0x83,0xf7,0x96,0xe4,0x82,0x20,0xbd,0x89,0x5c,0x7a,0x8b,0x90,0x76,0xb4,0x22,0xde,0x26,0x93,0x53,0x53,0xc8,0xa9,0x65,0xb3,0xc3,0x38,0x5b,0x22,0x67,0x32,0x43,0x28,0x81,0xb0,0xc6,0x33,0x68,0x79,0x78,0xb0,0x11,0x8d,0x7c,0x18,0xc8,0xd8,0xb4,0x34,0xee,0x03,0x36,0xc1,0xc0,0x7c,0x10,0x2c,0x30,0xa0,0xa3,0xd0,0x77,0x81,0x5a,0x85,0xa1,0xc7,0x79,0x07,0x9b,0x20,0xd2,0xe7,0x70,0x9a,0x68,0x9a,0x6e,0x75,0xe5,0xfc,0x21,0x10,0x0a,0x82,0x60,0xa3,0xd5,0x6c,0xea,0x7a,0x7b,0xf1,0x36,0x50,0xdf,0xec,0x9c,0xab,0x0a,0x65,0x6c,0xbb,0x83,0x32,0xc8,0xbb,0x04,0xbd,0x51,0x4a,0x3b,0x62,0x30,0x2c,0x13,0xf5,0x9c,0xc1,0x48,0x03,0xf3,0x6b,0x75,0xd3,0x7c,0x6c,0xce,0xa8,0x01,0xd3,0x32,0x31,0x57,0xb3,0x79,0x37,0xb0,0x48,0x9c,0x9b,0xa9,0x1c,0x02,0x20,0xec,0xb7,0x81,0x5f,0xc4,0x81,0xda,0xda,0x9a,0x18,0xc8,0x2c,0xde,0x7b,0xc8,0xf0,0x4c,0xc7,0x45,0x7e,0xf3,0xee,0x4c,0x1a,0x4d,0x69,0x30,0x91,0x08,0x0c,0x91,0x88,0xc6,0x53,0x3f,0xb1,0xaa,0x4c,0x4d,0xc5,0x81,0x20,0xae,0xc2,0x78,0xc1,0x89,0xde,0x2e,0xf0,0x67,0x96,0x77,0x77,0x54,0x5b,0xb0,0xb6,0x66,0x69,0xea,0x6f,0x8d,0x2f,0xa3,0x1b,0x68,0x23,0x74,0x63,0xe4,0xb9,0x94,0x2b,0xb8,0xce,0x76,0x01,0x8e,0xde,0x47,0x83,0x0c,0xdb,0x50,0x7e,0xc9,0x49,0x33,0x85,0xe8,0x78,0xe8,0x05,0x96,0x7e,0xe4,0xf9,0xae,0xc6,0x5b,0xe5,0xd6,0x24,0xdd,0xcd,0xc9,0x34,0x1e,0x69,0x02,0x7b,0x84,0x3e,0xd3,0x67,0x3a,0xe3,0xcf,0x0d,0xbd,0xff,0x85,0xa5,0xcf,0x91,0x45,0x61,0xf7,0xde,0x50,0xa3,0x26,0xb4,0x59,0x96,0x65,0xf7,0xd0,0xaa,0xd9,0x7a,0x4a,0x81,0xa0,0x2c,0x0c,0xdb,0xa7,0x43,0x07,0xd6,0xd6,0xf8,0x4c,0x41,0xc6,0xd7,0xc0,0xbe,0xd2,0x62,0x43,0x0f,0x1c,0x4e,0xdf,0xea,0xf6,0x4d,0x85,0x1b,0x7a,0x27,0x9f,0xb0,0x93,0x7f,0x35,0x33,0x1e,0x01,0x07,0x34,0xbd,0x5d,0x02,0xe5,0xd3,0xe0,0x3a,0x19,0x75,0x9b,0x92,0x97,0xb2,0xe3,0xa2,0x76,0xdc,0x7a,0xeb,0xb2,0x04,0x6f,0x46,0xfd,0x18,0x9c,0xb2,0xba,0x2b,0xc8,0x0f,0x27,0x74,0xfe,0xb6,0x78,0x9a,0xb9,0x60,0x57,0x2b,0xa5,0x6d,0x65,0x13,0x76,0xb2,0x6f,0x9f,0xba,0xa9,0x66,0x05,0xfd,0x59,0xe7,0x0e,0x96,0x0d,0xef,0x4c,0x26,0x38,0xef,0x32,0x66,0xad,0xad,0x65,0x5a,0x12,0x41,0x34,0x7b,0x4b,0x7b,0xb8,0x13,0x94,0x71,0x0a,0x0a,0xa8,0xd9,0xb0,0x59,0x0c,0x65,0x6c,0xa3,0x7e,0xba,0x3e,0xa7,0xdd,0xca,0xc5,0xc1,0xc8,0x16,0x00,0xa5,0x99,0x07,0xfd,0x46,0x81,0x28,0xdc,0x48,0xd9,0x42,0xe0,0xf9,0xbf,0x3d,0xcb,0xdc,0x98,0x22,0xe5,0x9f,0xe4,0xa3,0x3a,0x73,0x96,0xe1,0x86,0x68,0xa1,0xba,0xad,0xb4,0xa4,0xaa,0xcd,0xa3,0xa9,0xf6,0x65,0x88,0xca,0x16,0xcd,0x49,0xc0,0xba,0xc1,0x70,0x61,0x74,0xac,0x49,0x07,0xc2,0x5c,0x8c,0x65,0x8b,0x66,0x10,0xcf,0x88,0x26,0xd3,0x28,0x20,0x01,0xbd,0x23,0xc7,0x51,0x08,0x91,0x03,0xb3,0x17,0x2c,0xa6,0xe8,0xa6,0x85,0x10,0x43,0x34,0x1b,0xf3,0x22,0x84,0x0b,0x76,0x07,0xd9,0xb6,0xf7,0x98,0x78,0xda,0x06,0xb3,0x4c,0xed,0x95,0xd6,0xcc,0x10,0x1d,0x47,0xef,0xb2,0xc6,0xa6,0x21,0x6f,0x23,0x56,0x9a,0xb3,0x4b,0x30,0x12,0x25,0xb4,0x77,0x61,0x3f,0x49,0x19,0xe9,0xc3,0x30,0xf1,0x06,0xf4,0x8b,0xe0,0xfc,0x04,0xd4,0x70,0xc5,0x49,0x05,0x37,0x66,0xb7,0x6d,0xc3,0xe5,0x3a,0xce,0x9c,0x25,0xb8,0xa6,0x27,0xa2,0x8a,0xce,0xa0,0x9b,0xf2,0x2f,0x6b,0x6b,0xfc,0xaf,0xd0,0xd4,0x9d,0xa8,0x60,0xcc,0xb9,0xa3,0xd5,0xdb,0xb2,0x95,0xb3,0xfe,0x0b,0xb0,0xc6,0x48,0x33,0xaf,0x84,0x5d,0x59,0xc8,0xd0,0x56,0x23,0x06,0xb6,0xdd,0xb6,0xba,0x77,0xa3,0x1c,0x08,0xb4,0x6d,0x7b,0x56,0xa0,0x25,0x82,0x3d,0x61,0x49,0x89,0x54,0xd3,0x1c,0x79,0x7b,0x9f,0x95,0xd2,0x10,0xde,0xef,0x05,0xd7,0xb6,0x61,0x1f,0xe3,0xb1,0xe1,0x9d,0xe7,0xfb,0xe0,0x35,0x87,0xb0,0xed,0x11,0x81,0xdc,0x6f,0x1b,0x2f,0x01,0x30,0xcc,0x5e,0x95,0xa1,0xff,0x34,0x48,0x50,0x77,0x56,0xbb,0xaf,0x20,0x7b,0x86,0xce,0x2e,0x89,0x29,0x44,0x69,0x6e,0x6c,0x9a,0xa6,0x6d,0x5c,0x5c,0x0a,0xe3,0x2b,0xc7,0x59,0xaf,0x8c,0xec,0x7b,0x16,0x5d,0x43,0xbc,0x22,0xbf,0xb3,0x80,0x25,0xcd,0x86,0xac,0xaf,0x17,0xa7,0x3f,0x1e,0xee,0x65,0x43,0xc1,0xa4,0x28,0xd3,0xc0,0x06,0x28,0xbf,0x0a,0x21,0x46,0xd6,0xae,0xe7,0xa8,0x6d,0x5b,0x4d,0x9c,0xe1,0x53,0x27,0xca,0x30,0xab,0xe0,0xad,0x1b,0x95,0x73,0x4a,0x10,0x89,0x16,0xdd,0x2c,0x53,0xfd,0x6c,0x0f,0x6f,0x30,0x6a,0xc8,0x8e,0x51,0x1a,0x01,0x2d,0x23,0xa7,0x14,0x58,0x6b,0x9f,0x8d,0x28,0xe1,0x45,0x4d,0x90,0xcc,0x41,0x18,0x09,0x52,0x4c,0xdd,0x98,0x24,0x21,0xe9,0x43,0x96,0x0d,0x20,0x46,0x51,0x18,0x78,0x7f,0x51,0x17,0x29,0x2b,0x04,0x29,0xbb,0x1b,0xa9,0x95,0x27,0x43,0x86,0x17,0x6d,0x07,0xe7,0x33,0xe2,0x16,0x2f,0x57,0x0c,0xe7,0xce,0xf1,0x90,0xc2,0xea,0x39,0xe9,0x0c,0xf5,0x90,0xa7,0x32,0x90,0x83,0x07,0xd4,0x3d,0x1a,0xf6,0x8e,0xce,0x30,0xbf,0x29,0x6c,0x0c,0xda,0xe4,0xa6,0x94,0x61,0x0f,0x0f,0x5a,0x61,0x52,0x53,0xd1,0x12,0xfb,0x08,0xf2,0xb7,0x70,0x48,0xce,0x22,0xe7,0x16,0xf5,0x42,0x0a,0x1e,0x78,0xd8,0x00,0xc8,0x80,0xb6,0x99,0x00,0x95,0x3d,0x1f,0xbe,0x46,0x78,0xef,0x8d,0xc3,0xf0,0x8c,0x97,0x1f,0xe2,0x7b,0x31,0x11,0x75,0x29,0x0a,0x05,0xe6,0x9b,0x12,0x7d,0x86,0xc2,0xe3,0x09,0x76,0xbd,0x75,0x99,0xc1,0xce,0xb7,0x10,0xf1,0xe3,0xa2,0x01,0xcf,0xa4,0x81,0x31,0x4a,0x5a,0x2d,0x1b,0x3b,0x49,0x74,0x9f,0x06,0xce,0xad,0x77,0xed,0x24,0x61,0x64,0xde,0x8a,0x23,0x9a,0x0d,0x08,0xa1,0x67,0xc0,0xfb,0xc1,0x28,0x9d,0x29,0xcc,0x56,0x8e,0xb9,0x04,0x00,0x83,0x9f,0x73,0xa5,0xec,0xcf,0xda,0x1a,0xfb,0x53,0x89,0x4f,0x8c,0x1c,0xc5,0xb5,0xb5,0xa2,0xd0,0xe5,0x3d,0xba,0x51,0xc2,0x57,0x99,0x55,0xd5,0xa0,0xd2,0x58,0x10,0xcb,0x57,0x4d,0x55,0x2c,0x95,0xd3,0xb2,0x54,0x5d,0xdd,0xd2,0xe6,0xaf,0xcf,0x6d,0x1e,0x13,0x24,0x92,0x43,0x52,0x89,0x36,0x76,0x0f,0x9d,0x31,0xd5,0x31,0xda,0x5c,0x91,0xbf,0x30,0x36,0x63,0x84,0xb1,0xd7,0xd6,0x56,0xb8,0xb0,0x15,0x1c,0xa5,0xfd,0x1b,0xda,0x98,0x3e,0xbd,0xf6,0xd0,0xde,0xdf,0x82,0xf1,0x21,0xeb,0x64,0x02,0x48,0x40,0x7c,0x37,0xe0,0x63,0x4c,0x5b,0xd7,0x61,0xb2,0x0a,0x91,0x1f,0xaf,0xcc,0x03,0x29,0xa4,0x4a,0x58,0x2f,0x1c,0x09,0x30,0xd9,0xc6,0x10,0x3c,0xd7,0x2d,0xac,0xe6,0x90,0x82,0x65,0x92,0x5d,0x90,0xb6,0xfb,0x70,0x4a,0xe2,0x69,0x44,0x77,0xaa,0xcb,0xb1,0x43,0x96,0x65,0x56,0x63,0x03,0xab,0xe0,0x74,0x14,0x23,0x91,0x81,0x4c,0x40,0xaf,0xa8,0xc5,0x01,0x0d,0x29,0x88,0x90,0x66,0x9b,0x8d,0x49,0x18,0x83,0x05,0x48,0xc7,0x34,0x19,0x85,0x6e,0xdb,0x3e,0x3e,0x3a,0x3d,0xb3,0x0d,0xbc,0x09,0xa7,0x51,0xdc,0x4e,0x57,0x85,0xad,0x5a,0x47,0x4f,0xb0,0xda,0xb6,0x21,0x31,0x80,0x38,0x92,0x99,0x9e,0xc6,0x1f,0x31,0xe4,0x4e,0x90,0x35,0x84,0xee,0x7d,0xfb,0xd7,0xd3,0xa3,0x43,0x08,0xa0,0x70,0x97,0xde,0xf0,0x5e,0x4b,0x61,0x07,0x6d,0xb1,0x0b,0x96,0x18,0x20,0x5f,0x24,0x06,0x66,0x78,0xa3,0xa7,0xca,0x76,0x78,0x04,0x60,0x0b,0x5e,0x92,0xef,0x9e,0xa7,0x72,0xe6,0x77,0x64,0xe8,0x78,0x3e,0x75,0xdb,0xe4,0x79,0x9a,0xcd,0x06,0xc2,0x25,0xd3,0x78,0x56,0x6d,0x3a,0x03,0xa5,0x9c,0x81,0xf1,0xe5,0x8e,0x78,0x56,0xe5,0xd9,0x4e,0xc5,0x13,0xb5,0x95,0x84,0x95,0x51,0xe4,0x94,0x41,0x32,0x72,0x2d,0xd3,0x40,0xa8,0xd0,0x1e,0x85,0x10,0x1a,0xfa,0xe1,0x35,0xfc,0x34,0xaa,0xa8,0x1f,0xd2,0xe4,0x2e,0x8c,0x6e,0x08,0x8d,0xa2,0x30,0x42,0x64,0xa9,0x29,0xfc,0x31,0xe0,0x33,0x2b,0x0b,0x6e,0x7e,0xde,0x8a,0x39,0x3a,0x0f,0x5a,0x25,0xb2,0x2c,0x6b,0x07,0x8c,0xf1,0x18,0xd7,0xde,0xb1,0x61,0xe8,0x15,0xfb,0xda,0x66,0x5f,0xd9,0xf1,0xac,0x8d,0xc4,0xac,0x93,0x86,0xb3,0x11,0x58,0x29,0x26,0x0b,0xd9,0x0a,0x24,0x01,0xc3,0xfe,0x3c,0x45,0xa8,0x33,0x93,0x20,0x2f,0xbd,0x60,0xca,0xe4,0xe2,0x7f,0x8f,0x58,0xe4,0x67,0xd0,0x9f,0x23,0x0d,0x55,0xc0,0x9c,0x2c,0x58,0xa7,0x19,0xed,0x26,0x5a,0x53,0x37,0x93,0xf0,0x1c,0x6b,0x3d,0xf6,0x40,0xff,0x35,0xfd,0x25,0xeb,0x8c,0x61,0x57,0x54,0x6b,0xe9,0xb3,0x8c,0xa4,0x99,0xb5,0x45,0x4f,0xa0,0x1b,0x4b,0xc9,0xcf,0x93,0x05,0x46,0xb1,0x71,0xaa,0xc7,0x64,0x0c,0x0b,0xc2,0x3b,0xab,0xf1,0x82,0xfc,0xf3,0xea,0xea,0xf8,0xfc,0xa4,0x77,0x75,0x45,0x5e,0x34,0x58,0xcc,0xb9,0x0f,0xec,0xe6,0xb1,0x98,0x75,0x4d,0x35,0x5b,0x94,0x48,0x00,0x01,0x58,0x9b,0x38,0xe7,0x81,0x4d,0xc3,0x7c,0x0c,0x60,0xde,0x80,0x49,0xfd,0x17,0x98,0x25,0x70,0xb9,0xeb,0xcf,0xd3,0x53,0xc6,0x22,0x4d,0xf4,0x1d,0x00,0x73,0x47,0x40,0x82,0x96,0x6e,0x4e,0x1c,0x97,0x95,0x35,0x68,0x1b,0x86,0xdd,0xb4,0x6b,0xc6,0xe2,0xb2,0x10,0x7d,0x54,0x06,0x9e,0x95,0x07,0xfe,0x12,0x4e,0xa3,0xb8,0x6e,0x64,0xbb,0xb2,0x3c,0xc8,0x68,0x42,0x97,0x1b,0x7b,0xca,0x43,0xbe,0xba,0xb1,0x90,0xbb,0x29,0x55,0x21,0x3c,0x8a,0x56,0x02,0x7c,0xa5,0x8f,0xfa,0xe0,0x26,0xfd,0xf2,0xa1,0x8e,0x28,0xe0,0x05,0x26,0x23,0x3d,0xf3,0x1b,0x5e,0x5b,0x37,0xe5,0xd5,0x2f,0x9e,0xd6,0xb2,0xce,0xec,0x22,0xb9,0xd8,0xa9,0x24,0xa9,0x73,0x0b,0x0a,0x38,0x62,0x69,0x0d,0xae,0x3c,0x0c,0x8a,0xc5,0x95,0x3c,0x0b,0xcc,0x63,0xeb,0xc2,0xce,0x6b,0x49,0x20,0x8a,0x91,0x15,0x1f,0xf0,0x35,0x2b,0xcc,0xc0,0xe0,0x26,0xab,0x2c,0x80,0x1f,0xd9,0x9d,0xbf,0x7d,0x99,0x87,0x20,0xa5,0xfb,0xfe,0xe9,0xc7,0x2b,0x81,0x08,0xa8,0x24,0x3b,0x95,0xbe,0xca,0x65,0xe9,0x78,0xf7,0x64,0xf7,0xe0,0xea,0x79,0x2a,0x07,0x99,0x9e,0x6b,0xc6,0xd3,0x3e,0xd7,0x6b,0x0d,0x82,0x4e,0x3c,0xb9,0x55,0xf0,0xcf,0x80,0x15,0x5a,0x15,0xa8,0xba,0x91,0x81,0x92,0x06,0x0f,0x6b,0x39,0x6c,0x26,0xe1,0x17,0x23,0x94,0x14,0x63,0xcc,0xa5,0xe0,0xd2,0xca,0x86,0x32,0x11,0x36,0x21,0x3b,0xf7,0x40,0x83,0xda,0x40,0xe9,0xb1,0x33,0xd1,0x0e,0x59,0x75,0x82,0xde,0x51,0xa0,0x0b,0x59,0xd7,0x18,0x9c,0x17,0xdf,0x37,0x5f,0x0a,0x50,0x3a,0x7c,0xe7,0xa7,0x37,0xd5,0xc1,0xd0,0x12,0xd3,0x37,0x10,0x4a,0x27,0x5a,0x71,0x3d,0xfd,0xe1,0xa1,0x99,0xb3,0x71,0x3a,0xc1,0xba,0x93,0xd3,0x02,0x4b,0x40,0x2f,0x87,0x61,0xa4,0xb1,0x80,0xcf,0x6a,0x76,0xbc,0xed,0x22,0xc7,0xc4,0xd9,0x4c,0xc7,0x7b,0xf9,0x52,0x07,0xda,0x4a,0xe8,0x52,0x49,0x9f,0xa7,0xc5,0xe1,0x17,0xde,0x25,0x92,0x73,0x1e,0x0b,0x6a,0x07,0xaf,0x68,0x2b,0x12,0xec,0xc3,0xc3,0x8a,0x4a,0x67,0x0c,0x26,0xb2,0xfd,0xe4,0xf2,0x2d,0x0e,0xb3,0x63,0x45,0xc8,0x1f,0x1e,0xf2,0xd3,0x19,0xd8,0xeb,0xad,0x3c,0x24,0x05,0xde,0x64,0xec,0x54,0xf1,0xaf,0x61,0x1c,0x61,0x04,0xb7,0x0e,0x9c,0x64,0x64,0x0e,0xfd,0x10,0x68,0x52,0xa1,0x73,0x63,0xf3,0x7b,0xb0,0x8c,0x92,0xb7,0x0b,0x87,0x7e,0x8b,0x43,0x1b,0xdf,0x37,0xf5,0x4e,0x91,0x21,0x68,0xc3,0x84,0x0d,0x60,0xeb,0x2d,0x32,0x12,0x92,0xf1,0x55,0xdb,0xc0,0xe5,0xa0,0x04,0xb9,0x82,0x43,0xc5,0x73,0x17,0x72,0x15,0xee,0xb8,0x39,0x01,0x85,0x76,0x66,0x34,0xfc,0x73,0x4a,0xa3,0xfb,0x53,0xea,0xd3,0x01,0x04,0xef,0xbb,0x3e,0xe4,0x1f,0x82,0x07,0x92,0xde,0xe8,0xfa,0x0a,0x73,0x85,0x9c,0x00,0x49,0x9b,0x3a,0xf7,0x59,0xfc,0x4c,0xd4,0xb9,0xc7,0x04,0xcf,0x4a,0x67,0x9d,0x4c,0xca,0xd8,0x5d,0x14,0xe4,0x32,0x85,0xf9,0x1c,0x1d,0x48,0x90,0xa5,0x8a,0xe2,0x0a,0xf0,0x13,0x58,0x94,0xf9,0x02,0x36,0xc4,0x4d,0x2c,0xe9,0x2f,0x34,0x55,0xd0,0x8d,0x7b,0x70,0x07,0x96,0x9b,0x14,0xbd,0x83,0x31,0x46,0x67,0x20,0x9a,0x85,0x63,0x30,0x5c,0x3c,0x5e,0x4b,0x72,0xf3,0x6f,0x70,0xce,0xf3,0x26,0x61,0xe8,0x33,0x26,0x8b,0xa9,0xd2,0xa8,0x1b,0x22,0x4b,0x17,0xed,0x99,0x01,0xef,0x88,0x9d,0x9a,0x28,0x50,0xaa,0x64,0xe0,0x12,0xe6,0xf9,0xd9,0x9e,0x86,0xf8,0x71,0x74,0x10,0x01,0xa3,0x60,0x26,0x24,0x54,0xbd,0xc1,0xb2,0x60,0x79,0x46,0x9b,0xed,0x5f,0x96,0x78,0xd9,0xba,0x5c,0x26,0x92,0xb6,0xf8,0x0a,0x63,0x71,0xd5,0x00,0x94,0xb4,0xbf,0x93,0x03,0x33,0x99,0xe7,0x8f,0x7f,0xf3,0x80,0x0a,0x42,0x25,0x21,0x94,0x4a,0x39,0x9f,0x40,0x28,0x58,0x08,0x57,0x30,0x8e,0xdf,0xeb,0x3c,0x43,0xb4,0x14,0xa0,0x9d,0xa2,0x02,0xf1,0xfa,0x2a,0x08,0xf1,0xb5,0x8a,0x29,0x2a,0x61,0x82,0x66,0x81,0x23,0xcf,0x56,0xa3,0x90,0x20,0xc5,0x0f,0x0f,0xfc,0x1e,0xb1,0xdc,0x7e,0x91,0x21,0x74,0x69,0x71,0x59,0x9e,0x25,0xac,0xa6,0x38,0x0f,0x65,0xed,0x53,0xc8,0xa0,0x30,0x2d,0xc1,0xdb,0xc8,0xb6,0x2d,0x81,0xf0,0x73,0x93,0xaf,0x1a,0x17,0xca,0x95,0x97,0x8b,0x08,0xdf,0xb0,0x28,0x10,0x0f,0x27,0x62,0x2c,0xaa,0xe2,0x5a,0x10,0x7f,0x5a,0x8e,0x50,0xd0,0x40,0x79,0x7f,0xc4,0xcd,0x6e,0xb7,0x6c,0x35,0xf9,0x09,0xae,0x62,0x33,0x17,0x46,0x06,0xcd,0x45,0x91,0x41,0xf3,0x0b,0x05,0x8e,0x25,0x7b,0xa5,0x40,0x06,0x73,0xf5,0x58,0xc8,0x0f,0xda,0x68,0xd7,0x30,0x40,0x8a,0x0c,0x5b,0x52,0xa5,0x3e,0x9b,0x48,0x38,0x5d,0x41,0x7c,0x4a,0x94,0xce,0x68,0xcc,0xee,0xba,0xf9,0x52,0xd9,0x10,0x94,0x05,0x0d,0x4f,0xbd,0x14,0x69,0xfc,0x39,0x4c,0xa4,0x24,0xe2,0x1f,0x88,0x11,0x98,0xb3,0x3d,0x7f,0xab,0xd5,0xa4,0x62,0x02,0x99,0x1e,0xfe,0xe1,0x88,0xa0,0x20,0x67,0xb8,0x50,0x35,0x88,0xce,0xe1,0xa4,0x08,0xd8,0x8c,0x92,0xc1,0x15,0x90,0xff,0xe1,0xa1,0x7a,0x44,0xc6,0x56,0x36,0xc7,0xf1,0xf5,0x8a,0x65,0xdd,0x86,0x9e,0x4b,0xf0,0x30,0x0e,0xf9,0x08,0x4d,0xc0,0x40,0xae,0xa0,0x72,0x8c,0x18,0x2d,0x0a,0x1d,0xcb,0x33,0x44,0x73,0x71,0x96,0x68,0x84,0x54,0xe3,0x0d,0x3e,0x10,0xa3,0x6d,0x80,0x0c,0x69,0xac,0xa7,0x3f,0xf5,0x7c,0xf7,0x4a,0x14,0xca,0x80,0x33,0xce,0xdb,0x10,0x79,0x5d,0x02,0xe5,0xfd,0x45,0xa0,0x85,0xa9,0x2f,0x6d,0xa2,0xd9,0x2f,0x4b,0xd3,0x5f,0xda,0xba,0xcd,0xd5,0x1a,0x8d,0xeb,0xdb,0xba,0x24,0x61,0xc8,0xd1,0xc0,0x7e,0x65,0x27,0x2b,0xd9,0xf8,0xc5,0xd1,0x83,0x72,0x79,0x54,0x88,0x1e,0x00,0x54,0x06,0x21,0xf3,0x3e,0xf3,0xb2,0x97,0x1c,0x83,0x17,0x68,0xc4,0x3b,0xf9,0xda,0x99,0xe3,0xe7,0x3e,0x03,0x9c,0x41,0x6d,0xfa,0x92,0xf5,0x2e,0x95,0xc0,0x64,0xa3,0x1f,0x4f,0x61,0xb2,0xa1,0x4b,0x24,0x31,0x39,0x12,0xcb,0xa4,0x31,0xd9,0xe8,0x45,0x89,0xcc,0x4c,0x72,0xee,0x68,0x9a,0x28,0xac,0xcb,0x6b,0xa3,0x15,0x06,0x06,0x60,0xe0,0xae,0x1c,0xdf,0x89,0xc6,0x19,0x1b,0x97,0x24,0x7c,0x3e,0x33,0x27,0x3f,0x5f,0xf0,0xff,0x3c,0xfd,0x19,0x7d,0x72,0xdf,0x0a,0xca,0xc8,0x6d,0xca,0x71,0x56,0xbe,0xa3,0x49,0x73,0x55,0xce,0x0d,0xa4,0x37,0x55,0x12,0xbf,0x82,0xfe,0xe5,0x81,0x49,0x87,0x1b,0xa7,0x42,0x58,0xa2,0xaa,0x62,0x15,0xc6,0x27,0xeb,0x64,0x15,0x14,0x6c,0xa9,0x06,0xbe,0x62,0x64,0x8a,0x78,0x65,0x06,0xac,0x05,0x06,0x6c,0xa5,0x58,0x8c,0x84,0x97,0x9e,0xd5,0x19,0x96,0xdc,0x89,0xb0,0x6d,0x95,0x5e,0x3c,0xe3,0xad,0xed,0xdb,0x86,0x80,0x78,0x6d,0x0d,0x6d,0x35,0x48,0xe3,0xd1,0x50,0xd6,0xd5,0x0b,0x8b,0x5d,0x36,0xf3,0x2a,0x4b,0x98,0xdc,0xb3,0x9a,0x39,0x46,0x6d,0xf6,0x0d,0xcf,0x6c,0x42,0x3c,0xbd,0xe1,0x51,0xbc,0x75,0xd4,0xff,0x03,0x62,0x74,0x13,0xe8,0x12,0x79,0x20,0x2c,0x25,0x4e,0xeb,0x26,0x0e,0xd6,0x34,0xc7,0xe8,0xeb,0x56,0xd7,0xc1,0xeb,0x74,0xf6,0xbc,0x01,0xdd,0x0b,0xc7,0x13,0xac,0x1a,0xec,0x43,0x93,0xce,0xb4,0xaf,0x52,0xca,0xa5,0x8b,0x78,0xfd,0xe2,0x86,0xde,0xf3,0x20,0xf0,0x12,0x62,0x76,0x75,0x71,0x11,0xb2,0x57,0x92,0x3c,0x98,0x30,0xe3,0x2a,0xcd,0xf3,0xae,0x15,0xef,0xb3,0xb8,0xed,0x09,0x06,0xab,0xa9,0x0e,0x8f,0x29,0x59,0x68,0x9b,0xd6,0x16,0xa1,0x55,0xa9,0x29,0xfd,0x39,0x0b,0xae,0x53,0x46,0xcc,0x62,0x71,0xce,0x92,0xfb,0x8d,0xc2,0x3b,0x4b,0xce,0x8e,0x21,0xa8,0x39,0x61,0x97,0x41,0x10,0x25,0xf9,0x2d,0x0b,0xfa,0x44,0xeb,0x1e,0xfc,0xd6,0x9a,0xbc,0x7d,0xa3,0xdc,0xde,0xd2,0x3b,0x6c,0x7c,0xe1,0x36,0x0d,0x96,0xed,0xe4,0xf4,0x9c,0x57,0xf2,0x23,0x8b,0xb2,0x94,0x18,0x1c,0x3f,0x00,0x53,0x4e,0x18,0xbc,0x32,0x40,0xcb,0x60,0xef,0xf0,0x6b,0xcf,0xb6,0x8c,0xcf,0x0d,0x99,0x57,0x59,0x45,0x3e,0x19,0x15,0xb2,0x8a,0x96,0xec,0xd1,0x0a,0x29,0xa1,0x5a,0xf1,0x94,0x87,0x3d,0x62,0x31,0xe3,0x1b,0x2c,0x14,0xdc,0x88,0xe3,0x9f,0xc7,0x72,0x49,0x08,0x02,0x4a,0x01,0x6b,0xca,0x17,0xae,0x56,0x57,0xb0,0xda,0x0f,0xdb,0x50,0x10,0xc1,0xc5,0xcd,0x98,0xc1,0xd4,0xf0,0xb2,0x56,0xaf,0x2b,0x45,0x6c,0x56,0x02,0xca,0x72,0xcd,0x2c,0x63,0x28,0x56,0xdb,0xe6,0x36,0xad,0x50,0xa1,0x0b,0x84,0xce,0x7a,0x65,0x05,0x8a,0x38,0x3f,0xaf,0xbf,0x27,0xcf,0x47,0x67,0xe4,0xc3,0x5b,0x25,0x79,0xf9,0x2d,0x6b,0x8c,0x70,0x98,0x95,0x8f,0xc5,0x6f,0x58,0xeb,0x82,0x2a,0x83,0xdf,0xf9,0x3d,0x10,0x7e,0x33,0xf1,0x55,0x0c,0xa0,0xdd,0xae,0x48,0xda,0xb0,0x8e,0x18,0x53,0xb6,0x6a,0x64,0x7d,0xcc,0x2f,0x7e,0x38,0x4d,0x88,0x43,0x58,0xc5,0xb1,0xd8,0x83,0x90,0xfc,0x39,0xe7,0xee,0xa2,0x6e,0x58,0x6e,0x9c,0x4d,0x22,0xab,0xcf,0xd3,0x6c,0xfd,0xd9,0xea,0xce,0xbf,0x83,0x7f,0x07,0x35,0x77,0x43,0xce,0x10,0xac,0x8c,0xa0,0xaa,0xa9,0x9c,0xca,0x3b,0x51,0xe4,0xdc,0xbf,0x9e,0x0e,0x87,0x34,0x92,0x51,0x3a,0x02,0x53,0x9a,0x41,0x1f,0x95,0xcb,0x4d,0x8e,0x02,0x86,0xc0,0x92,0x37,0xb6,0x61,0xb3,0xc2,0x54,0x5e,0xcc,0xba,0x5a,0xa9,0xc3,0x25,0xf2,0xa1,0xce,0xd5,0xee,0x37,0xff,0xf8,0x07,0x29,0xfd,0xb3,0xdc,0x54,0x52,0x7c,0x34,0x98,0xbd,0x81,0x02,0xe0,0xe5,0xc8,0xa8,0x34,0x30,0x4d,0x93,0xd7,0xc1,0x7e,0xfa,0x72,0xf9,0x83,0x9e,0x04,0x5f,0x31,0x21,0x1a,0xc4,0xa3,0x9c,0xa4,0x1f,0xfa,0xb0,0x1b,0x56,0x22,0xc0,0x09,0xba,0xce,0x9e,0x1a,0x82,0xec,0x68,0xb5,0xdb,0xfc,0xb6,0x76,0x6d,0xde,0x98,0x17,0x0b,0x7c,0x1c,0x45,0xec,0x6c,0xe4,0xf7,0x83,0xf7,0xbf,0x24,0xc9,0xe4,0x04,0x1f,0x36,0x8a,0x93,0x0e,0x34,0x9b,0x82,0x47,0x55,0xdd,0x92,0x8b,0x40,0xd2,0x21,0xab,0xdd,0xf8,0x49,0x0e,0xba,0x88,0x29,0x33,0x74,0xe2,0x84,0x80,0x46,0xf8,0x30,0x2c,0x36,0xc3,0x4f,0x71,0xc4,0xc1,0x36,0x8a,0x53,0x00,0x38,0x75,0x1b,0x98,0x19,0x24,0x8e,0xff,0x02,0x0b,0x25,0x0d,0x09,0x79,0x51,0xe9,0x41,0x69,0xa7,0x36,0xd6,0xc4,0xc9,0x59,0x60,0xf2,0xf3,0x1f,0x05,0x53,0x59,0x42,0xe5,0xa5,0xfd,0x2d,0x26,0x8f,0x9c,0x06,0x1c,0xa2,0xd0,0x4c,0x4b,0xd5,0x52,0xa9,0xa6,0x90,0xe9,0xa1,0xcf,0xc4,0xcb,0x5e,0xa4,0x4c,0x95,0x24,0x08,0xc0,0x36,0x34,0x39,0x80,0x67,0x65,0x5d,0x0b,0x72,0xdb,0xb5,0xb5,0xbc,0x61,0x7b,0xb3,0xd9,0xcc,0x6a,0x5d,0xb0,0xbe,0x05,0x81,0x6a,0xe5,0x18,0xb5,0xc7,0x13,0x3d,0xa9,0x63,0xd9,0x05,0x50,0x0e,0x67,0x56,0xf8,0x25,0x12,0x7c,0xb4,0x68,0xf5,0xc8,0xb1,0xcc,0x51,0x60,0xb7,0x78,0xcd,0x42,0xae,0x4d,0xdc,0x29,0xbb,0xc2,0xe5,0xe4,0x59,0xb4,0x80,0xd3,0x07,0x97,0xb7,0xd4,0x02,0x62,0x53,0xfc,0x8c,0xc0,0xa7,0x1c,0x2a,0xb0,0x01,0xe1,0x86,0xe0,0x0f,0x34,0x71,0xbc,0x02,0xb9,0x39,0x48,0x85,0xcd,0x17,0x84,0xcc,0x55,0xc8,0xe5,0x2f,0xec,0xd4,0x05,0x2f,0x4e,0xf3,0x33,0x17,0x18,0xac,0x9e,0xb9,0x84,0x83,0x84,0x82,0xca,0x24,0x60,0xd1,0xc7,0xd9,0x7c,0x10,0x38,0xc5,0x90,0x48,0x7b,0x5c,0xe0,0xbb,0x5a,0x38,0x21,0x2d,0x0a,0x39,0x67,0x21,0x01,0xac,0x90,0xb5,0xf0,0x39,0xd4,0x25,0xf1,0x74,0x30,0x00,0x31,0x1b,0x42,0x0c,0x77,0xbf,0x42,0x84,0xa1,0xf3,0x62,0x61,0xe6,0x80,0x6e,0xa8,0xfd,0xfd,0xa8,0xfb,0xff,0xa5,0x3d,0x9f,0x51,0xda,0x93,0x1d,0x82,0xa8,0x75,0xbd,0x55,0x47,0xd6,0xab,0x3f,0x1a,0xca,0x03,0xe5,0xec,0x61,0x4a,0xcd,0xd3,0xd3,0xaf,0x70,0xe1,0x6b,0x47,0xc3,0x2b,0xdc,0x74,0x60,0x1b,0xe8,0xd2,0x03,0xea,0xb7,0x3d,0x56,0x21,0x56,0x0a,0x2e,0x6a,0x1e,0xf2,0x54,0x8a,0xb8,0xf1,0x28,0x13,0xaf,0xbf,0xde,0x84,0x11,0xc8,0x1e,0xd8,0x17,0xfb,0x84,0xe2,0x99,0x07,0x0a,0xe4,0xf9,0x04,0xef,0xbc,0x90,0x3f,0x97,0x06,0x5b,0x89,0xba,0x7b,0xa1,0x8b,0xc3,0xd9,0xc3,0x12,0xc5,0x8f,0xcb,0xc7,0xef,0xcf,0x05,0x26,0x58,0xf2,0x43,0xb6,0xf0,0xf1,0x54,0x5e,0xd4,0x42,0xe4,0x6b,0x16,0x3c,0xbc,0x99,0xe5,0x38,0x9a,0xdf,0x7c,0x73,0x8c,0x16,0x96,0x1c,0xbd,0xe3,0xe5,0x52,0xe0,0x10,0x0d,0xbc,0x76,0x87,0xe0,0x21,0xc4,0x8a,0x21,0x76,0x05,0x3f,0x61,0x95,0x84,0xb1,0xf9,0xb7,0x5e,0xbc,0x23,0x1f,0x98,0xf4,0x5d,0x25,0x74,0x3c,0xb1,0x67,0x4f,0x3e,0x6e,0x65,0x93,0x91,0x1c,0x38,0x1f,0x2b,0x79,0x22,0x76,0xc0,0xa5,0x84,0x47,0x7f,0xcf,0xa6,0xc4,0xb9,0x2a,0xdb,0x91,0xb1,0xfc,0x76,0xc4,0x34,0xdc,0xd0,0x80,0xbf,0xfa,0x2b,0xce,0x37,0x53,0xbc,0xd2,0xdb,0x62,0xd7,0x77,0xe9,0x57,0xdd,0x55,0x9d,0xca,0x2c,0x8a,0x9d,0x95,0x13,0x65,0xe9,0xa8,0xd9,0xf3,0x16,0x35,0x26,0x82,0x3f,0x2e,0x0d,0x26,0xc2,0x7b,0xd9,0x9a,0x35,0xb6,0xd0,0x58,0x28,0x4a,0x86,0xb7,0x8a,0x18,0xaf,0x72,0xb9,0xe6,0x15,0x24,0xa5,0x6e,0x92,0x3d,0x18,0x86,0xc5,0x46,0x91,0xd4,0x0f,0xbc,0x94,0xcf,0x67,0x82,0x46,0xdc,0xa1,0x1e,0xb8,0xf8,0x5c,0x9e,0x06,0x2e,0x14,0x5f,0xf8,0x02,0xaa,0x90,0x6b,0x0b,0x7b,0xde,0x92,0x4c,0x03,0xa1,0x6f,0x63,0x48,0x9a,0x4d,0x2c,0x00,0x15,0xea,0xf7,0x5a,0x3c,0x9e,0x61,0xfc,0x9d,0x74,0x5f,0x6f,0x31,0x45,0xf9,0x7b,0xd5,0x96,0xc7,0x36,0xf5,0x2a,0x5b,0x7b,0x24,0x3e,0x18,0xd1,0xc1,0x0d,0xc6,0x2b,0x20,0xdc,0x73,0x4f,0xe8,0x0b,0xb6,0x12,0xd8,0x6a,0xf1,0xbb,0xae,0x34,0x7b,0x42,0xad,0xf6,0xd4,0xbe,0x53,0x9e,0xc5,0xce,0x4c,0x06,0xf8,0x6b,0x27,0xff,0x0a,0x1d,0x6d,0xc8,0x1f,0x17,0x1f,0xdc,0xd7,0x62,0x49,0x6b,0xf0,0x9a,0x95,0x5b,0x2c,0xf4,0xa5,0x5f,0x97,0x05,0x34,0xe1,0x76,0xd3,0xe0,0x2f,0xd2,0xf2,0x0c,0xdc,0x27,0xec,0x91,0xd5,0x3a,0xd7,0x59,0x8b,0xba,0xc3,0x9d,0x77,0xbd,0x7f,0xed,0x1d,0xed,0xf7,0xae,0x40,0xf5,0x66,0xf2,0x14,0xa2,0x74,0x34,0x53,0xda,0x6a,0x4d,0x15,0x0b,0xaf,0x2e,0xcd,0x4f,0xa9,0xac,0x54,0x82,0x6d,0xb6,0x0b,0xb3,0x9b,0x97,0x86,0xec,0x69,0x15,0x7b,0x5a,0x79,0xcf,0x46,0xb1,0x67,0x23,0xef,0xd9,0x2c,0xf6,0x6c,0x5e,0xce,0x3a,0x5f,0xdd,0xc6,0xe7,0xbb,0x64,0xf6,0xaf,0x60,0xdc,0x3f,0x93,0xea,0x8f,0xde,0xef,0xcd,0x96,0xbf,0xce,0x57,0x6e,0xef,0xc5,0x55,0xcd,0x27,0x5d,0x0b,0x8a,0x9b,0xeb,0x47,0xef,0x3c,0x99,0xcb,0x72,0x9f,0x7a,0xe9,0xf9,0x55,0xf5,0x85,0x06,0x99,0x4f,0x66,0xe1,0xb6,0x78,0xc0,0xdd,0xb2,0xb3,0xb7,0x7d,0x10,0x61,0xe7,0xc7,0x68,0x01,0x64,0x2e,0xba,0xf2,0xcd,0x37,0x76,0xa7,0x8e,0xb1,0x35,0x16,0x60,0x47,0xc0,0x7c,0x89,0x57,0x13,0x25,0x0f,0xd5,0x26,0xeb,0xf8,0xae,0x8c,0x46,0xd7,0x6e,0x2f,0x1c,0xf5,0x3c,0x2d,0x01,0x9e,0xf1,0x59,0x9d,0x0a,0x0f,0x04,0x98,0x82,0x37,0x92,0x77,0xab,0x8b,0xae,0x6c,0x61,0xbf,0x13,0x65,0xa3,0x4f,0xad,0x11,0x75,0x3e,0x7a,0xf1,0xe3,0x77,0xb7,0x5f,0x90,0x97,0x58,0x78,0xfa,0x3c,0x45,0x34,0x66,0x57,0xbc,0x9c,0xfa,0xe9,0x91,0x23,0x9b,0x47,0x04,0x14,0xb5,0x96,0xb2,0x4e,0x80,0xf3,0x20,0x8c,0x1d,0x85,0x8d,0x13,0x4b,0x01,0x2d,0x9e,0xc9,0xb1,0x79,0x88,0x91,0x87,0x21,0xa8,0xba,0x91,0x49,0x0a,0xcd,0xce,0xb5,0x03,0x91,0x3a,0x5b,0x3e,0x9c,0x10,0x2f,0x81,0x58,0xfd,0x0c,0x02,0x12,0x83,0xbd,0x24,0x80,0x05,0x36,0xce,0x20,0x99,0x3a,0xbe,0xa8,0xf6,0x47,0x5c,0xf3,0x82,0x7f,0xc8,0x48,0x03,0xf0,0x4f,0xe8,0x91,0xd4,0x1a,0x54,0x7e,0x2e,0x8f,0x48,0xc9,0x2b,0x0b,0xfe,0x1d,0x9f,0x00,0x5a,0x5c,0xbc,0xaa,0xa4,0xf9,0xea,0xfe,0xe4,0x8a,0x6a,0xdd,0x07,0x80,0xe4,0xc7,0xff,0xf1,0xa1,0x73,0xa8,0xc9,0x11,0x58,0xa3,0x25,0xbe,0xe2,0x9d,0x48,0xcd,0x72,0x6f,0x03,0x70,0x21,0x9e,0xb2,0x0b,0xf6,0xf2,0x35,0xea,0x02,0x5d,0xf8,0xe9,0x26,0xfb,0x4d,0x1c,0xc2,0xdf,0xcb,0x08,0x86,0x84,0x1f,0x6e,0x9b,0x7f,0x77,0x10,0x8f,0x32,0x86,0xe5,0x07,0x4f,0x8c,0xe0,0xaf,0xd9,0xd3,0x95,0x39,0x89,0x59,0xf9,0xc0,0xa3,0x12,0x05,0x33,0xf6,0xe7,0x47,0x37,0xc8,0x5d,0x90,0x52,0x64,0x29,0x2f,0xb7,0xe6,0x2e,0xe5,0x86,0x5a,0x62,0x9e,0x49,0x1b,0x19,0x4b,0x1a,0xad,0x0d,0xdd,0xb8,0x49,0xb2,0xae,0xa4,0xd8,0xf5,0xa2,0x65,0x6e,0x18,0x37,0xca,0xe1,0x38,0x77,0x47,0xbc,0x3a,0xf3,0x5d,0xcf,0xc6,0xb9,0x73,0x3a,0xf1,0x42,0x50,0xcc,0xdc,0xd1,0x6e,0x0a,0xf7,0xe0,0x37,0x6a,0xfd,0x40,0x21,0x3c,0x10,0xe3,0x74,0xbd,0x5d,0x0a,0xb7,0xf6,0xc2,0xa9,0xef,0x12,0x7c,0xd0,0x05,0x9f,0x39,0x25,0xf3,0xd0,0x80,0x95,0x0a,0x37,0x7f,0xca,0x11,0xe5,0x4d,0xa9,0x9a,0x53,0x0c,0x7c,0xda,0x52,0x67,0xb8,0x14,0xc7,0xf1,0xe1,0x41,0xae,0x59,0x65,0x30,0x7f,0xf9,0x88,0xca,0xd8,0x81,0xf4,0x0e,0x98,0x5d,0xe0,0xc5,0x0b,0x9f,0x6a,0xbf,0xeb,0x69,0xf8,0xa2,0xb7,0xb8,0x31,0x4c,0xf4,0x36,0xb6,0x2b,0x94,0x81,0x1c,0xc6,0xc6,0x67,0xd1,0xa0,0x39,0x91,0xc3,0xcf,0xb4,0x84,0xa7,0x48,0x64,0x2c,0xe7,0x14,0xf7,0x28,0x27,0x41,0x0e,0x23,0xee,0x03,0x94,0x12,0x23,0x50,0x93,0xf6,0x32,0xd8,0xca,0x53,0xca,0x12,0x29,0x24,0xbd,0x09,0x44,0xbe,0x92,0x20,0x4a,0x14,0x07,0xd0,0xb3,0xba,0x35,0x29,0x81,0xac,0xf6,0x5f,0x08,0xa0,0x2a,0x65,0x52,0xc8,0x6a,0xe4,0x87,0x55,0xfb,0x72,0xf1,0xf9,0x64,0xa6,0xd6,0xa4,0x8c,0xec,0x7d,0x32,0x73,0x59,0x02,0x1b,0x51,0x29,0xeb,0x05,0x3a,0xaa,0x61,0x85,0xb4,0xf3,0xa8,0xaa,0xb7,0xb5,0x65,0xa4,0x28,0xdb,0xda,0x32,0xf8,0xcd,0x61,0x82,0x80,0x91,0xd3,0x1d,0x97,0x9f,0x2d,0xf2,0xdf,0xaa,0x0d,0x5f,0xce,0x79,0x57,0x5e,0xf9,0xf2,0xa8,0xfb,0xf6,0xc3,0x6b,0xfb,0x89,0x2e,0x55,0x2e,0x42,0x60,0x2e,0xbb,0x1d,0xfa,0xa4,0x02,0x38,0x76,0xbe,0xe6,0x87,0xfd,0xb2,0x31,0xc4,0x36,0x4d,0x37,0x16,0x3e,0x6c,0x00,0x51,0xda,0x18,0x5f,0x01,0xe7,0xe2,0x2f,0xa5,0x2c,0xf7,0x91,0x27,0x03,0x9e,0xa7,0x17,0xf6,0xaf,0xbb,0x87,0x78,0x8e,0xdc,0x7b,0x8d,0x25,0xec,0xbb,0x27,0xf0,0xb9,0x7b,0x7c,0xc2,0xbe,0xff,0x0b,0x0b,0xd6,0xcf,0x0f,0xd9,0xe7,0x7b,0x6c,0x3f,0xff,0x19,0x3e,0x4f,0x7b,0xc7,0xf0,0x79,0xb4,0x87,0x87,0xe1,0x87,0x47,0x1f,0xf0,0xb4,0xaf,0xb7,0x67,0x5f,0x5e,0x14,0x1f,0x58,0xb8,0x9c,0x2d,0xf5,0x78,0xc3,0xdc,0x22,0x92,0x27,0x3c,0x88,0x60,0x1b,0xd3,0xc8,0xb7,0xce,0x4f,0xde,0x8b,0xfb,0x65,0x5e,0x46,0x00,0xbf,0x35,0xa4,0x1c,0x08,0xe8,0xdc,0x0b,0x68,0x07,0x03,0x71,0x73,0x14,0xd1,0xa1,0x05,0x20,0x0c,0xc7,0x94,0x8c,0xc4,0x9b,0x67,0x76,0xa2,0x06,0x08,0x17,0x48,0x3b,0x63,0x97,0x96,0xf9,0xd3,0xf3,0xe8,0x46,0x0b,0x17,0xc6,0x0e,0xac,0x97,0xdd,0xae,0x16,0x87,0xf1,0x9c,0x26,0x1b,0x86,0x08,0x47,0xf4,0x36,0xbc,0x51,0x10,0x06,0x2c,0xb2,0xf8,0x15,0xc5,0xbb,0x4e,0xec,0x78,0xba,0x2e,0x31,0xc5,0x30,0x56,0x95,0x3a,0x36,0x6d,0x91,0x42,0xcc,0x2b,0xde,0x48,0x0b,0x01,0x50,0x4d,0x94,0x97,0x3d,0x30,0x51,0x78,0x12,0xb3,0x89,0x16,0xd4,0x87,0xe5,0x4c,0xf2,0x1b,0xd3,0x6e,0x7c,0xe0,0xcd,0xf7,0x6e,0x28,0xea,0x85,0xe3,0xba,0xfc,0x11,0x4e,0x39,0x63,0x07,0xa5,0x08,0x1a,0x95,0x17,0x32,0x52,0x9a,0xe8,0xa2,0x66,0x4a,0x2e,0x9e,0x15,0xa0,0xe4,0x0d,0x18,0xd2,0x95,0xdf,0xb3,0xd3,0x2c,0x87,0x6e,0x6a,0xd8,0x96,0x05,0x6a,0xc5,0xd8,0x4d,0xc4,0x6d,0x4b,0xc6,0x6c,0x26,0x61,0xc4,0xc1,0xdf,0x7c,0x51,0x7c,0xcc,0x68,0x21,0x1a,0x4b,0x15,0x17,0x3d,0xb1,0xe4,0xa7,0x58,0xe3,0x53,0x70,0x12,0xd5,0x49,0xf5,0x8f,0xf2,0xea,0xa5,0xc7,0x53,0x8f,0x43,0xdf,0x47,0xe5,0x12,0x77,0x0e,0x32,0x75,0x2a,0xbe,0xee,0x48,0xbd,0xb5,0x51,0xeb,0x62,0x37,0x8b,0xcf,0x36,0x43,0x3c,0x9f,0x83,0x53,0x01,0x54,0x9f,0x23,0x55,0x7b,0xe5,0x93,0xa4,0x0b,0x5e,0x44,0x71,0xeb,0xc5,0x5e,0xdf,0xf3,0xbd,0xe4,0x9e,0xbb,0xa7,0x42,0xd5,0x44,0x36,0x6f,0xe4,0xb9,0x2e,0x0d,0x76,0x0a,0x78,0xb4,0xb5,0x4f,0xdc,0x17,0xc6,0xbc,0xe2,0x3d,0x0f,0x21,0xb7,0x04,0xca,0x92,0x9f,0x51,0x61,0x5c,0x20,0xfa,0xac,0xb3,0xdd,0x10,0x2f,0xa0,0xda,0x6e,0xf0,0x57,0x1c,0x37,0xd8,0xff,0xfc,0xe2,0x3f,0xb3,0x6b,0xf0,0x74,0x0c,0x63,0x00,0x00}; const unsigned int html_content_len = 6999; diff --git a/main/webpage_gzip.h b/main/webpage_gzip.h index 4cbc9d7..65058b5 100644 --- a/main/webpage_gzip.h +++ b/main/webpage_gzip.h @@ -4,7 +4,7 @@ #include const unsigned char PROGMEM html_content_gz[] = { - 0x1f, 0x8b, 0x08, 0x00, 0x68, 0x76, 0x5c, 0x69, 0x02, 0xff, 0xed, 0x3d, 0x6b, 0x57, 0xdb, 0xc6, + 0x1f, 0x8b, 0x08, 0x00, 0x11, 0xd0, 0x6b, 0x69, 0x02, 0xff, 0xed, 0x3d, 0x6b, 0x57, 0xdb, 0xc6, 0xb6, 0x9f, 0x4f, 0x7f, 0xc5, 0x40, 0x52, 0x2a, 0x25, 0x42, 0xb6, 0x81, 0xf4, 0x61, 0x23, 0x73, 0x08, 0x38, 0x6d, 0x9a, 0xf0, 0x58, 0x3c, 0xd2, 0x9e, 0xcb, 0x61, 0x21, 0xd9, 0x1a, 0x63, 0x15, 0x59, 0x72, 0x25, 0x19, 0x42, 0x85, 0xff, 0xfb, 0xd9, 0x7b, 0x1e, 0xd2, 0xe8, 0x61, 0x63, 0x92, diff --git a/main/webserver.c b/main/webserver.c index 7f396e9..1881634 100644 --- a/main/webserver.c +++ b/main/webserver.c @@ -61,7 +61,7 @@ char httpBuffer[4096]; /* Handler to serve the HTML page */ static esp_err_t root_get_handler(httpd_req_t *req) { - ESP_LOGI(TAG, "root_get_handler"); + //ESP_LOGI(TAG, "root_get_handler"); if (req == NULL) { ESP_LOGE(TAG, "Null request pointer"); @@ -199,9 +199,9 @@ static esp_err_t log_handler(httpd_req_t *req) { // Total size: 4 (json length) + json + 8 (head/tail) + log_data uint32_t total_size = 4 + json_len + 8 + log_data_size; - ESP_LOGI(TAG, "Log request: tail=%ld, head=%ld, json_len=%lu, log_size=%ld, total=%lu", - (long)tail, (long)head, (unsigned long)json_len, (long)log_data_size, - (unsigned long)total_size); + //ESP_LOGI(TAG, "Log request: tail=%ld, head=%ld, json_len=%lu, log_size=%ld, total=%lu", + // (long)tail, (long)head, (unsigned long)json_len, (long)log_data_size, + // (unsigned long)total_size); // Send HTTP headers char len_str[16]; @@ -368,7 +368,7 @@ static esp_err_t log_handler(httpd_req_t *req) { * Unified GET handler - returns complete system status */ static esp_err_t get_handler(httpd_req_t *req) { - ESP_LOGI(TAG, "get_handler"); + //ESP_LOGI(TAG, "get_handler"); if (req == NULL) { ESP_LOGE(TAG, "Null request pointer"); @@ -692,7 +692,7 @@ static esp_err_t ota_post_handler(httpd_req_t *req) { static esp_err_t catchall_handler(httpd_req_t *req) { - ESP_LOGI(TAG, "catchall_handler; %s", req->uri); + //ESP_LOGI(TAG, "catchall_handler; %s", req->uri); const char *uri = req->uri; // Windows NCSI @@ -868,6 +868,9 @@ static void wifi_event_handler(void* arg, esp_event_base_t event_base, static esp_err_t launchSoftAp(void) { esp_err_t err; + + ESP_LOGI(TAG, "AP LAUNCHING"); + err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { // NVS partition was truncated and needs to be erased @@ -880,11 +883,17 @@ static esp_err_t launchSoftAp(void) { // Retry init after erase err = nvs_flash_init(); } + + + ESP_LOGI(TAG, "AP LAUNCHING..."); + if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize NVS: %s", esp_err_to_name(err)); return err; } + ESP_LOGI(TAG, "HI THERE"); + err = esp_netif_init(); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize netif: %s", esp_err_to_name(err)); @@ -1032,6 +1041,8 @@ esp_err_t webserver_init(void) { return err; } + ESP_LOGI(TAG, "AP LAUNCHED"); + err = startHttpServer(); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to start HTTP server: %s", esp_err_to_name(err)); diff --git a/partitions.csv b/partitions.csv index 72c2131..d0efcba 100644 --- a/partitions.csv +++ b/partitions.csv @@ -1,8 +1,8 @@ -# ESP32 Partition Table - 8MB (0x800000) Flash with OTA Support +# ESP32 Partition Table - 8MB (0x800000) Flash with OTA Support #5568K, # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x4000, otadata, data, ota, 0xd000, 0x2000, phy_init, data, phy, 0xf000, 0x1000, ota_0, app, ota_0, 0x10000, 1280K, ota_1, app, ota_1, 0x150000, 1280K, -storage, data, 0x40, 0x290000, 5568K, \ No newline at end of file +storage, data, 0x40, 0x290000, 128K, \ No newline at end of file