diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 030ed11..c5e3211 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 # list the source files of this component + 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 INCLUDE_DIRS "." "${CMAKE_BINARY_DIR}" PRIV_INCLUDE_DIRS # optional, add here private include directories diff --git a/main/comms.c b/main/comms.c new file mode 100644 index 0000000..57e5e97 --- /dev/null +++ b/main/comms.c @@ -0,0 +1,458 @@ +#include "comms.h" +#include "cJSON.h" +#include "control_fsm.h" +#include "esp_log.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "power_mgmt.h" +#include "rf_433.h" +#include "rtc.h" +#include "storage.h" +#include "version.h" +#include + +static const char *TAG = "COMMS"; + +/** + * Build a JSON object containing complete system status + */ +cJSON* comms_handle_get(void) { + ESP_LOGI(TAG, "GET request"); + + rtc_reset_shutdown_timer(); + + // Create root JSON object + cJSON *root = cJSON_CreateObject(); + if (root == NULL) { + ESP_LOGE(TAG, "Failed to create JSON object"); + return NULL; + } + + // Add basic system info + cJSON_AddStringToObject(root, "build_version", FIRMWARE_VERSION); + cJSON_AddStringToObject(root, "build_date", BUILD_DATE); + cJSON_AddNumberToObject(root, "time", (double)rtc_get_s()); + cJSON_AddBoolToObject(root, "rtc_set", rtc_is_set()); + cJSON_AddNumberToObject(root, "state", fsm_get_state()); + cJSON_AddNumberToObject(root, "voltage", get_battery_V()); + cJSON_AddNumberToObject(root, "remaining_dist", fsm_get_remaining_distance()); + cJSON_AddNumberToObject(root, "next_alarm", (double)rtc_get_next_alarm_s()); + + cJSON *msg_array = cJSON_CreateArray(); + if (msg_array == NULL) { + ESP_LOGE(TAG, "Failed to create msg array"); + cJSON_Delete(root); + return NULL; + } + + // Add state message + switch(fsm_get_state()) { + 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; + default: + cJSON_AddItemToArray(msg_array, cJSON_CreateString("MOVING...")); + break; + } + + // Add warning/error messages + if (fsm_get_remaining_distance() <= 0) { + cJSON_AddItemToArray(msg_array, cJSON_CreateString("DISTANCE LIMIT HIT")); + } + if (efuse_is_tripped(BRIDGE_AUX)) { + cJSON_AddItemToArray(msg_array, cJSON_CreateString("AUX EFUSE TRIP")); + } + if (efuse_is_tripped(BRIDGE_JACK)) { + cJSON_AddItemToArray(msg_array, cJSON_CreateString("JACK EFUSE TRIP")); + } + if (efuse_is_tripped(BRIDGE_DRIVE)) { + cJSON_AddItemToArray(msg_array, cJSON_CreateString("DRIVE EFUSE TRIP")); + } + if (!rtc_is_set()) { + cJSON_AddItemToArray(msg_array, cJSON_CreateString("CLOCK NOT SET")); + } + + cJSON_AddItemToObject(root, "msg", msg_array); + + // Add parameters object + cJSON *parameters = cJSON_CreateObject(); + if (parameters == NULL) { + ESP_LOGE(TAG, "Failed to create parameters object"); + cJSON_Delete(root); + return NULL; + } + + // Add all parameters + for (param_idx_t i = 0; i < NUM_PARAMS; i++) { + const char *name = get_param_name(i); + param_value_t value = get_param_value_t(i); + + switch (get_param_type(i)) { + case PARAM_TYPE_f32: + cJSON_AddNumberToObject(parameters, name, value.f32); + break; + case PARAM_TYPE_f64: + cJSON_AddNumberToObject(parameters, name, value.f64); + break; + case PARAM_TYPE_i32: + cJSON_AddNumberToObject(parameters, name, value.i32); + break; + case PARAM_TYPE_i16: + cJSON_AddNumberToObject(parameters, name, value.i16); + break; + case PARAM_TYPE_u32: + cJSON_AddNumberToObject(parameters, name, value.u32); + break; + case PARAM_TYPE_u16: + cJSON_AddNumberToObject(parameters, name, value.u16); + break; + case PARAM_TYPE_str: + cJSON_AddStringToObject(parameters, name, get_param_string(i)); + break; + default: + cJSON_AddNullToObject(parameters, name); + break; + } + } + + cJSON_AddItemToObject(root, "parameters", parameters); + + return root; +} + +/** + * Process a POST request with JSON data + */ +esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) { + ESP_LOGI(TAG, "POST request"); + + if (root == NULL || response_json == NULL) { + ESP_LOGE(TAG, "Invalid arguments to comms_handle_post"); + return ESP_FAIL; + } + + rtc_reset_shutdown_timer(); + + bool cmd_executed = false; + bool sleep_requested = false; + bool reboot_requested = false; + const char *error_msg = NULL; + int params_updated = 0; + int params_failed = 0; + + // Process time if present + cJSON *time = cJSON_GetObjectItem(root, "time"); + if (cJSON_IsNumber(time)) { + int64_t new_time = (int64_t)cJSON_GetNumberValue(time); + ESP_LOGI(TAG, "Setting time to %lld", new_time); + rtc_set_s(new_time); + } + + // Process remaining_dist if present + cJSON *remaining_dist = cJSON_GetObjectItem(root, "remaining_dist"); + if (cJSON_IsNumber(remaining_dist)) { + int64_t new_dist = (int64_t)cJSON_GetNumberValue(remaining_dist); + ESP_LOGI(TAG, "Setting remaining_dist to %lld", new_dist); + fsm_set_remaining_distance(new_dist); + } + + // Process command if present + cJSON *cmd = cJSON_GetObjectItem(root, "cmd"); + if (cJSON_IsString(cmd)) { + const char *cmd_str = cmd->valuestring; + ESP_LOGI(TAG, "Executing command: %s", cmd_str); + + if (strcmp(cmd_str, "start") == 0) { + fsm_request(FSM_CMD_START); + cmd_executed = true; + } + else if (strcmp(cmd_str, "stop") == 0) { + fsm_request(FSM_CMD_STOP); + cmd_executed = true; + } + else if (strcmp(cmd_str, "undo") == 0) { + fsm_request(FSM_CMD_UNDO); + cmd_executed = true; + } + else if (strcmp(cmd_str, "fwd") == 0) { + pulseOverride(RELAY_A1); + pulseOverride(RELAY_A3); + cmd_executed = true; + } + else if (strcmp(cmd_str, "rev") == 0) { + pulseOverride(RELAY_B1); + pulseOverride(RELAY_A3); + cmd_executed = true; + } + else if (strcmp(cmd_str, "up") == 0) { + pulseOverride(RELAY_A2); + cmd_executed = true; + } + else if (strcmp(cmd_str, "down") == 0) { + pulseOverride(RELAY_B2); + cmd_executed = true; + } + else if (strcmp(cmd_str, "aux") == 0) { + pulseOverride(RELAY_A3); + cmd_executed = true; + } + else if (strcmp(cmd_str, "reboot") == 0) { + reboot_requested = true; + cmd_executed = true; + } + else if (strcmp(cmd_str, "sleep") == 0) { + sleep_requested = true; + cmd_executed = true; + } + else if (strcmp(cmd_str, "rf_clear_temp") == 0) { + rf_433_clear_temp_keycodes(); + cmd_executed = true; + } + else if (strcmp(cmd_str, "rf_disable") == 0) { + rf_433_disable_controls(); + cmd_executed = true; + } + else if (strcmp(cmd_str, "rf_enable") == 0) { + rf_433_enable_controls(); + cmd_executed = true; + } + else if (strcmp(cmd_str, "rf_learn") == 0) { + // Start learning for a specific channel + cJSON *channel = cJSON_GetObjectItem(root, "channel"); + if (cJSON_IsNumber(channel)) { + int ch = (int)cJSON_GetNumberValue(channel); + if (ch >= 0 && ch < NUM_RF_BUTTONS) { + rf_433_learn_keycode(ch); + cmd_executed = true; + } else if (ch == -1) { + rf_433_cancel_learn_keycode(); + cmd_executed = true; + } else { + error_msg = "Invalid channel number"; + } + } else { + error_msg = "rf_learn requires channel parameter"; + } + } + else if (strcmp(cmd_str, "rf_set_temp") == 0) { + cJSON *index = cJSON_GetObjectItem(root, "index"); + cJSON *code = cJSON_GetObjectItem(root, "code"); + if (cJSON_IsNumber(index) && cJSON_IsNumber(code)) { + int idx = (int)cJSON_GetNumberValue(index); + int32_t rf_code = (int32_t)cJSON_GetNumberValue(code); + rf_433_set_temp_keycode(idx, rf_code); + cmd_executed = true; + } else { + error_msg = "rf_set_temp requires index and code parameters"; + } + } + else if (strcmp(cmd_str, "rf_status") == 0) { + // Return current temp keycodes + cJSON *response = cJSON_CreateObject(); + cJSON *codes_array = cJSON_CreateArray(); + + for (int i = 0; i < 4; i++) { // Only return first 4 for web UI + int32_t code = rf_433_get_temp_keycode(i); + cJSON_AddItemToArray(codes_array, cJSON_CreateNumber(code)); + } + + cJSON_AddItemToObject(response, "codes", codes_array); + *response_json = response; + return ESP_OK; + } + else if (strcmp(cmd_str, "cal_jack_start") == 0) { + fsm_request(FSM_CMD_CALIBRATE_JACK_PREP); + ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_PREP"); + cmd_executed = true; + } + else if (strcmp(cmd_str, "cal_jack_finish") == 0) { + cJSON *amt = cJSON_GetObjectItem(root, "amt"); + if (cJSON_IsNumber(amt) && amt->valuedouble >= 0 && amt->valuedouble < 8) { + ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_FINISH"); + fsm_set_cal_val(amt->valuedouble); + fsm_request(FSM_CMD_CALIBRATE_JACK_FINISH); + cmd_executed = true; + } else { + error_msg = "cal_jack_finish requires amt parameter (0-8)"; + } + } + else if (strcmp(cmd_str, "cal_drive_start") == 0) { + fsm_request(FSM_CMD_CALIBRATE_DRIVE_PREP); + ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_PREP"); + cmd_executed = true; + } + else if (strcmp(cmd_str, "cal_drive_finish") == 0) { + cJSON *amt = cJSON_GetObjectItem(root, "amt"); + if (cJSON_IsNumber(amt) && amt->valuedouble >= 0 && amt->valuedouble < 8) { + ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_FINISH"); + fsm_set_cal_val(amt->valuedouble); + fsm_request(FSM_CMD_CALIBRATE_DRIVE_FINISH); + cmd_executed = true; + } else { + error_msg = "cal_drive_finish requires amt parameter (0-8)"; + } + } + else if (strcmp(cmd_str, "cal_get") == 0) { + ESP_LOGI(TAG, "CAL_GET"); + + // Build JSON response with calibration values + cJSON *response = cJSON_CreateObject(); + cJSON_AddItemToObject(response, "e", cJSON_CreateNumber(fsm_get_cal_e())); + cJSON_AddItemToObject(response, "t", cJSON_CreateNumber(fsm_get_cal_t())); + + *response_json = response; + return ESP_OK; + } + else { + ESP_LOGW(TAG, "Unknown command: %s", cmd_str); + error_msg = "Unknown command"; + } + } + + // Process batch parameter updates if present + cJSON *parameters = cJSON_GetObjectItem(root, "parameters"); + if (cJSON_IsObject(parameters)) { + // Process individual parameter updates (direct key-value pairs) + cJSON *item = NULL; + cJSON_ArrayForEach(item, parameters) { + const char *key = item->string; + ESP_LOGI(TAG, "Processing parameter: %s", key); + + // Find parameter index by name + param_idx_t param_idx = NUM_PARAMS; + for (param_idx_t i = 0; i < NUM_PARAMS; i++) { + if (strcmp(get_param_name(i), key) == 0) { + param_idx = i; + break; + } + } + + if (param_idx == NUM_PARAMS) { + ESP_LOGW(TAG, "Unknown parameter: %s", key); + params_failed++; + continue; + } + + cJSON *value_json = cJSON_GetObjectItem(parameters, key); + + // Set parameter value based on type + switch (get_param_type(param_idx)) { + case PARAM_TYPE_f32: + if (cJSON_IsNumber(value_json)) { + set_param_value_t(param_idx, (param_value_t){.f32 = value_json->valuedouble}); + params_updated++; + } else { + ESP_LOGW(TAG, "Type mismatch for parameter: %s", key); + params_failed++; + } + break; + case PARAM_TYPE_f64: + if (cJSON_IsNumber(value_json)) { + set_param_value_t(param_idx, (param_value_t){.f64 = value_json->valuedouble}); + params_updated++; + } else { + ESP_LOGW(TAG, "Type mismatch for parameter: %s", key); + params_failed++; + } + break; + case PARAM_TYPE_i32: + if (cJSON_IsNumber(value_json)) { + set_param_value_t(param_idx, (param_value_t){.i32 = value_json->valueint}); + params_updated++; + } else { + ESP_LOGW(TAG, "Type mismatch for parameter: %s", key); + params_failed++; + } + break; + case PARAM_TYPE_i16: + if (cJSON_IsNumber(value_json)) { + set_param_value_t(param_idx, (param_value_t){.i16 = value_json->valueint}); + params_updated++; + } else { + ESP_LOGW(TAG, "Type mismatch for parameter: %s", key); + params_failed++; + } + break; + case PARAM_TYPE_u32: + if (cJSON_IsNumber(value_json)) { + set_param_value_t(param_idx, (param_value_t){.u32 = value_json->valueint}); + params_updated++; + } else { + ESP_LOGW(TAG, "Type mismatch for parameter: %s", key); + params_failed++; + } + break; + case PARAM_TYPE_u16: + if (cJSON_IsNumber(value_json)) { + set_param_value_t(param_idx, (param_value_t){.u16 = value_json->valueint}); + params_updated++; + } else { + ESP_LOGW(TAG, "Type mismatch for parameter: %s", key); + params_failed++; + } + break; + case PARAM_TYPE_str: + if (cJSON_IsString(value_json)) { + set_param_string(param_idx, value_json->valuestring); + params_updated++; + } else { + ESP_LOGW(TAG, "Type mismatch for parameter: %s", key); + params_failed++; + } + break; + default: + ESP_LOGW(TAG, "Unknown type for parameter: %s", key); + params_failed++; + break; + } + } + + if (params_updated > 0) { + rtc_schedule_next_alarm(); + commit_params(); + } + } + + // Build response + cJSON *response = cJSON_CreateObject(); + if (response == NULL) { + ESP_LOGE(TAG, "Failed to create response JSON"); + return ESP_FAIL; + } + + if (reboot_requested) { + cJSON_AddStringToObject(response, "status", "ok"); + cJSON_AddStringToObject(response, "message", "Rebooting..."); + cJSON_AddBoolToObject(response, "reboot", true); + *response_json = response; + return ESP_OK; + } + + if (sleep_requested) { + cJSON_AddStringToObject(response, "status", "ok"); + cJSON_AddStringToObject(response, "message", "Sleeping..."); + cJSON_AddBoolToObject(response, "sleep", true); + *response_json = response; + return ESP_OK; + } + + if (error_msg != NULL) { + cJSON_AddStringToObject(response, "status", "error"); + cJSON_AddStringToObject(response, "message", error_msg); + *response_json = response; + return ESP_FAIL; + } + + cJSON_AddStringToObject(response, "status", "ok"); + cJSON_AddNumberToObject(response, "params_updated", params_updated); + cJSON_AddNumberToObject(response, "params_failed", params_failed); + cJSON_AddBoolToObject(response, "cmd_executed", cmd_executed); + + *response_json = response; + return ESP_OK; +} \ No newline at end of file diff --git a/main/comms.h b/main/comms.h new file mode 100644 index 0000000..6bf0d02 --- /dev/null +++ b/main/comms.h @@ -0,0 +1,34 @@ +#ifndef COMMS_H +#define COMMS_H + +#include +#include "esp_err.h" +#include "cJSON.h" + +/** + * Unified communications module + * + * This module provides the core GET/POST logic that can be called from + * either the webserver (HTTP) or UART (serial) interfaces. + */ + +/** + * Process a GET request - returns complete system status as JSON + * + * @return cJSON object containing system status, or NULL on error + * Caller is responsible for deleting the returned object with cJSON_Delete() + */ +cJSON* comms_handle_get(void); + +/** + * Process a POST request - handles commands, parameter updates, time updates + * + * @param request_json The parsed JSON request object + * @param response_json Pointer to store the response JSON object + * @return ESP_OK on success, error code otherwise + * Caller is responsible for deleting the response_json with cJSON_Delete() + */ +esp_err_t comms_handle_post(cJSON *request_json, cJSON **response_json); + + +#endif // COMMS_H \ No newline at end of file diff --git a/main/control_fsm.c b/main/control_fsm.c index 62a16ea..83a15bb 100644 --- a/main/control_fsm.c +++ b/main/control_fsm.c @@ -12,6 +12,7 @@ #include "power_mgmt.h" #include "rtc_wdt.h" #include "driver/gpio.h" +#include "sc_err.h" #include "storage.h" #include "rtc.h" #include "sensors.h" @@ -25,6 +26,10 @@ #define TAG "FSM" 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; } // map from relay number to bridge bridge_t bridge_map[] = { @@ -171,19 +176,41 @@ void control_task(void *param) { 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 + switch (cmd) { case FSM_CMD_START: if (current_state == STATE_IDLE) { // Check if we have remaining distance before starting - if (remaining_distance > 0.0f - && !efuse_is_tripped(BRIDGE_DRIVE) - && !efuse_is_tripped(BRIDGE_JACK) - && !efuse_is_tripped(BRIDGE_AUX)) { - current_state = STATE_MOVE_START_DELAY; - set_timer(TRANSITION_DELAY_US); - } else { - ESP_LOGW(TAG, "Cannot start move: no remaining distance (%.2f)", remaining_distance); + if (remaining_distance <= 0.0f) { + error = SC_ERR_LEASH_HIT; + continue; + } + + if (get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) { + error = SC_ERR_LOW_BATTERY; + continue; } + if (get_safety_sensor()) { + error = SC_ERR_SAFETY_TRIP; + continue; + } + if (efuse_is_tripped(BRIDGE_DRIVE)) { + error = SC_ERR_EFUSE_TRIP_1; + continue; + } + if (efuse_is_tripped(BRIDGE_JACK)) { + error = SC_ERR_EFUSE_TRIP_2; + continue; + } + if (efuse_is_tripped(BRIDGE_AUX)) { + error = SC_ERR_EFUSE_TRIP_3; + continue; + } + + error = ESP_OK; // if everything is OK now, we're OK. + current_state = STATE_MOVE_START_DELAY; + set_timer(TRANSITION_DELAY_US); } break; case FSM_CMD_STOP: @@ -202,14 +229,16 @@ void control_task(void *param) { case FSM_CMD_CALIBRATE_JACK_PREP: ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_PREP"); - if (current_state == STATE_IDLE) { + if (current_state == STATE_IDLE + && get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) { current_state = STATE_CALIBRATE_JACK_DELAY; } break; case FSM_CMD_CALIBRATE_JACK_START: ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_START"); - if (current_state == STATE_CALIBRATE_JACK_DELAY) { + 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; set_timer(CALIBRATE_JACK_MAX_TIME); } @@ -231,14 +260,16 @@ void control_task(void *param) { case FSM_CMD_CALIBRATE_DRIVE_PREP: ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_PREP"); - if (current_state == STATE_IDLE) { + if (current_state == STATE_IDLE + && get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) { current_state = STATE_CALIBRATE_DRIVE_DELAY; } break; case FSM_CMD_CALIBRATE_DRIVE_START: ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_START"); - if (current_state == STATE_CALIBRATE_DRIVE_DELAY) { + 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; set_timer(CALIBRATE_DRIVE_MAX_TIME); set_sensor_counter(SENSOR_DRIVE, 0); @@ -306,6 +337,10 @@ void control_task(void *param) { } break; case STATE_MOVE_START_DELAY: + if (!get_safety_sensor()) { + error = SC_ERR_SAFETY_TRIP; + current_state = STATE_IDLE; + } if (timer_done()) { current_state = STATE_JACK_UP_START; set_timer(JACK_TIME / 2); // First phase is half of total jack time @@ -313,6 +348,11 @@ void control_task(void *param) { } break; case STATE_JACK_UP_START: + if (!get_safety_sensor()) { + error = SC_ERR_SAFETY_TRIP; + current_state = STATE_UNDO_JACK_START; + } + { // Track elapsed time int64_t elapsed = current_time - timer_start; @@ -332,14 +372,19 @@ void control_task(void *param) { } } - // E-fuse trip should still cause undo + // E-fuse trip if (efuse_is_tripped(BRIDGE_JACK)) { - ESP_LOGI(TAG, "START->UP BY TIME"); - current_state = STATE_UNDO_JACK_START; + error = SC_ERR_EFUSE_TRIP_2; + current_state = STATE_IDLE; } } break; case STATE_JACK_UP: + if (!get_safety_sensor()) { + error = SC_ERR_SAFETY_TRIP; + current_state = STATE_UNDO_JACK_START; + } + { if (timer_done() || efuse_is_tripped(BRIDGE_JACK)) { // Track total time including first phase @@ -348,12 +393,16 @@ void control_task(void *param) { set_timer(TRANSITION_DELAY_US); } if (efuse_is_tripped(BRIDGE_JACK)) { - ESP_LOGE(TAG, "JACK TRIPPED EFUSE"); - current_state = STATE_UNDO_JACK_START; + error = SC_ERR_EFUSE_TRIP_2; + current_state = STATE_IDLE; } } break; case STATE_DRIVE_START_DELAY: + if (!get_safety_sensor()) { + error = SC_ERR_SAFETY_TRIP; + current_state = STATE_UNDO_JACK_START; + } if (timer_done()) { current_state = STATE_DRIVE; set_timer(DRIVE_TIME); @@ -366,6 +415,10 @@ void control_task(void *param) { } break; case STATE_DRIVE: + if (!get_safety_sensor()) { + error = SC_ERR_SAFETY_TRIP; + current_state = STATE_UNDO_JACK_START; + } { int32_t current_encoder = get_sensor_counter(SENSOR_DRIVE); int32_t ticks_traveled = current_encoder - move_start_encoder; @@ -407,17 +460,27 @@ void control_task(void *param) { if (remaining_distance < 0.0f) remaining_distance = 0.0f; ESP_LOGW(TAG, "Drive fault: traveled %.2f, old_remaining %.2f, new_remaining %.2f", distance_traveled, old_remaining, remaining_distance); + + error = SC_ERR_EFUSE_TRIP_1; current_state = STATE_UNDO_JACK_START; } } break; case STATE_DRIVE_END_DELAY: + if (!get_safety_sensor()) { + 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 } break; case STATE_JACK_DOWN: + if (!get_safety_sensor()) { + 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; @@ -474,7 +537,6 @@ void control_task(void *param) { break; case STATE_CALIBRATE_JACK_MOVE: if (timer_done()) { - ESP_LOGI(TAG, "STATE_CALIBRATE_JACK_END"); current_state = STATE_IDLE; fsm_cal_t = current_time - timer_start; } @@ -485,8 +547,7 @@ void control_task(void *param) { // no way out of this except a command break; case STATE_CALIBRATE_DRIVE_MOVE: - if (timer_done()) { - ESP_LOGI(TAG, "STATE_CALIBRATE_DRIVE_END"); + if (!get_safety_sensor() || timer_done()) { current_state = STATE_IDLE; fsm_cal_t = current_time - timer_start; fsm_cal_e = get_sensor_counter(SENSOR_DRIVE); diff --git a/main/control_fsm.h b/main/control_fsm.h index 6476b6c..a61a545 100644 --- a/main/control_fsm.h +++ b/main/control_fsm.h @@ -44,7 +44,7 @@ typedef enum { } fsm_state_t; typedef enum { - RELAY_NONE = 0, + RELAY_SENSORS = 0, RELAY_C3, RELAY_B3, RELAY_A3, @@ -75,6 +75,9 @@ int64_t fsm_get_cal_t(); int64_t fsm_get_cal_e(); void fsm_request(fsm_cmd_t cmd); +esp_err_t fsm_get_error(); +void fsm_clear_error(); + float fsm_get_remaining_distance(void); void fsm_set_remaining_distance(float x); diff --git a/main/i2c.c b/main/i2c.c index 7019bff..641fcb9 100644 --- a/main/i2c.c +++ b/main/i2c.c @@ -28,8 +28,12 @@ #define REPEAT_MS 200 #define REPEAT_START_MS 700 +#define SAFETY_MASK 0b00111111 + // Static Variables static bool i2c_initted = false; +static bool safety_ok = false; // Safety interlock +static uint8_t last_relay_request = 0; // Track last relay request // === I2C LOW-LEVEL === static esp_err_t tca_write_word_8(uint8_t reg, uint8_t value) { @@ -64,12 +68,37 @@ esp_err_t i2c_init(void) { ESP_ERROR_CHECK(tca_write_word_8(TCA_REG_CONFIG1, 0b00000000)); i2c_initted = true; + safety_ok = false; // Start with safety not OK + last_relay_request = 0; + return ESP_OK; } -esp_err_t i2c_set_relays(uint8_t states) { - return tca_write_word_8(TCA_REG_OUTPUT1, states); +void i2c_set_safety_status(bool safe) { + safety_ok = safe; + + if (!safe) { + // Safety tripped - immediately turn off all relays + ESP_LOGW("I2C", "Safety interlock activated"); + tca_write_word_8(TCA_REG_OUTPUT1, last_relay_request & SAFETY_MASK); + } else { + // Safety cleared - restore last requested relay state + ESP_LOGI("I2C", "Safety interlock cleared"); + } } + +esp_err_t i2c_set_relays(uint8_t states) { + last_relay_request = states; // Always track the request + + if (!safety_ok) { + // Safety interlock active - refuse to energize relays + ESP_LOGW("I2C", "Main relay operation blocked by safety interlock"); + return tca_write_word_8(TCA_REG_OUTPUT1, states & SAFETY_MASK); + } + + return tca_write_word_8(TCA_REG_OUTPUT1, states); +} + esp_err_t i2c_set_led1(uint8_t state) { // push 3 LSB to top return tca_write_word_8(TCA_REG_OUTPUT0, state<<5); diff --git a/main/i2c.h b/main/i2c.h index b031acf..97c105c 100644 --- a/main/i2c.h +++ b/main/i2c.h @@ -11,6 +11,7 @@ esp_err_t i2c_stop(void); esp_err_t i2c_set_relays(uint8_t states); esp_err_t i2c_set_led1(uint8_t state); +void i2c_set_safety_status(bool safe); esp_err_t i2c_poll_buttons(); diff --git a/main/main.c b/main/main.c index 56a5a67..1d3bf49 100644 --- a/main/main.c +++ b/main/main.c @@ -124,7 +124,7 @@ void app_main(void) { ESP_LOGI(TAG, "Firmware: %s", FIRMWARE_STRING); ESP_LOGI(TAG, "Version: %s", FIRMWARE_VERSION); ESP_LOGI(TAG, "Branch: %s", FIRMWARE_BRANCH); - ESP_LOGI(TAG, "Built: %s", BUILD_DATE); + ESP_LOGI(TAG, "Built: %s", BUILD_DATE); // Check for factory reset condition: Cold boot + button held @@ -240,12 +240,13 @@ void app_main(void) { driveLEDs(LED_STATE_START2); } else if (i2c_get_button_ms(0) > 100){ driveLEDs(LED_STATE_START1); - } else{ + } else { if ( rtc_is_set() && !efuse_is_tripped(BRIDGE_JACK) && !efuse_is_tripped(BRIDGE_AUX) && - !efuse_is_tripped(BRIDGE_DRIVE) + !efuse_is_tripped(BRIDGE_DRIVE) && + fsm_get_error() == ESP_OK ) { driveLEDs(LED_STATE_AWAKE); } else { diff --git a/main/sc_err.h b/main/sc_err.h new file mode 100644 index 0000000..10105c4 --- /dev/null +++ b/main/sc_err.h @@ -0,0 +1,26 @@ +/* + * sc_errors.h + * + * Created on: Jan 5, 2026 + * Author: Thad + */ + +#ifndef MAIN_SC_ERR_H_ +#define MAIN_SC_ERR_H_ + +// from esp_err.h, from 0 -> 0x100 is clear +// as is 0x10D -> 0x3000 + +#define SC_ERR_EFUSE_TRIP_1 0x201 +#define SC_ERR_EFUSE_TRIP_2 0x202 +#define SC_ERR_EFUSE_TRIP_3 0x203 + +#define SC_ERR_SAFETY_TRIP 0x210 + +#define SC_ERR_LEASH_HIT 0x211 + +#define SC_ERR_RTC_NOT_SET 0x220 +#define SC_ERR_LOW_BATTERY 0x230 + + +#endif /* MAIN_SC_ERR_H_ */ diff --git a/main/sensors.c b/main/sensors.c index e05a352..360392a 100644 --- a/main/sensors.c +++ b/main/sensors.c @@ -1,10 +1,12 @@ #include "sensors.h" +#include "i2c.h" #include "esp_log.h" #include "esp_task_wdt.h" #include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" +#include "storage.h" static const char* TAG = "SENS"; @@ -15,6 +17,13 @@ static volatile uint64_t sensor_last_isr_time[N_SENSORS] = {0}; static volatile bool sensor_stable_state[N_SENSORS] = {false}; static QueueHandle_t sensor_event_queue = NULL; +// Safety sensor debouncing +static volatile bool safety_tripped = false; +static volatile uint64_t safety_low_start_time = 0; +static volatile uint64_t safety_high_start_time = 0; +#define SAFETY_TRIP_DEBOUNCE_US get_param_value_t(PARAM_SAFETY_BREAK_US).u32 +#define SAFETY_UNTRIP_DEBOUNCE_US get_param_value_t(PARAM_SAFETY_BREAK_US).u32 + #define DEBOUNCE_TIME_US 2000 // 2 ms debounce (adjust per switch) #define DEBOUNCE_TICKS pdMS_TO_TICKS(DEBOUNCE_TIME_MS) @@ -23,7 +32,7 @@ typedef struct { bool level; } sensor_event_t; -// ISR: Minimal work — just record timestamp and forward to queue +// ISR: Minimal work – just record timestamp and forward to queue static void IRAM_ATTR sensor_isr_handler(void* arg) { uint32_t gpio_num = (uint32_t)arg; uint8_t i; @@ -56,62 +65,72 @@ static void sensor_debounce_task(void* param) { last_processed_time[i] = esp_timer_get_time(); } + // Initialize safety sensor + bool safety_current = !gpio_get_level(sensor_pins[SENSOR_SAFETY]); + if (safety_current) { + safety_low_start_time = esp_timer_get_time(); + safety_high_start_time = 0; + } else { + safety_low_start_time = 0; + safety_high_start_time = esp_timer_get_time(); + } - uint8_t i = 0; - //int64_t now = -1; + uint8_t i = 0; while (1) { - if (xQueueReceive(sensor_event_queue, &evt, pdMS_TO_TICKS(100)) == pdTRUE) { + 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; + 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", "FALLING"); + sensor_count[i]++; + } if (!current_raw && last_raw_state[i]){ - ESP_LOGI("SENS", "RISING"); - sensor_count[i]++; - } - - last_raw_state[i] = current_raw; + ESP_LOGI("SENS", "RISING"); + sensor_count[i]++; + } + + 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(); - //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 trip timer"); + } else if (!safety_tripped && (now - safety_low_start_time >= SAFETY_TRIP_DEBOUNCE_US)) { + // Been low for 200ms, trip the safety + safety_tripped = true; + i2c_set_safety_status(false); + ESP_LOGW(TAG, "SAFETY TRIPPED - Relays disabled"); + } + } 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 un-trip timer"); + } else if (safety_tripped && (now - safety_high_start_time >= SAFETY_UNTRIP_DEBOUNCE_US)) { + // Been high for 300ms, un-trip the safety + safety_tripped = false; + i2c_set_safety_status(true); + ESP_LOGI(TAG, "SAFETY CLEARED - Relays enabled"); + } + } - /*// Wait for debounce period since last ISR - if (now - sensor_last_isr_time[i] >= (DEBOUNCE_TIME_US)) { - bool current_raw = !gpio_get_level(sensor_pins[i]); - - // Only update if stable and different from last stable - if (current_raw != sensor_stable_state[i]) { - bool was_high = sensor_stable_state[i]; - - // Count rising OR falling edges - if (current_raw && !sensor_stable_state[i]){ - ESP_LOGI("SENS", "FALLING"); - sensor_count[i]++; - } - if (!current_raw && sensor_stable_state[i]){ - ESP_LOGI("SENS", "RISING"); - sensor_count[i]++; - } - - - sensor_stable_state[i] = current_raw; - - last_raw_state[i] = current_raw; - last_processed_time[i] = now; - } - }*/ - esp_task_wdt_reset(); } } @@ -160,6 +179,10 @@ bool get_sensor(sensor_t i) { return sensor_stable_state[i]; } +bool get_safety_sensor(void) { + return !safety_tripped; // Returns true if safe, false if tripped +} + int32_t get_sensor_counter(sensor_t i) { return sensor_count[i]; } diff --git a/main/sensors.h b/main/sensors.h index 7135a94..bbdf67b 100644 --- a/main/sensors.h +++ b/main/sensors.h @@ -26,8 +26,9 @@ void set_sensor_counter(sensor_t i, int32_t to); int32_t get_sensor_counter(sensor_t i); bool get_sensor(sensor_t i); +bool get_safety_sensor(void); esp_err_t sensors_init(); esp_err_t sensors_stop(); -#endif /* MAIN_SENSORS_H_ */ +#endif /* MAIN_SENSORS_H_ */ \ No newline at end of file diff --git a/main/storage.h b/main/storage.h index dc4c687..889ef51 100644 --- a/main/storage.h +++ b/main/storage.h @@ -120,6 +120,7 @@ static inline uint8_t param_type_size(param_type_e type) { PARAM_DEF(JACK_I_DOWN, f32, 8.0, "A") \ PARAM_DEF(V_SENS_K, f32, 0.00766666666, "V/mV") \ PARAM_DEF(BUILD_VERSION, str, "undefined", "") \ + PARAM_DEF(SAFETY_BREAK_US, u32, 200000, "") \ // Generate enum for parameter indices diff --git a/main/uart_comms.c b/main/uart_comms.c index 4f5a952..3fee7a0 100644 --- a/main/uart_comms.c +++ b/main/uart_comms.c @@ -1,329 +1,139 @@ #include "uart_comms.h" -#include -#include -#include -#include "esp_task_wdt.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" +#include "comms.h" +#include "cJSON.h" #include "driver/uart.h" #include "esp_log.h" #include "esp_system.h" -#include "storage.h" -#include -#include -#include "rf_433.h" +#include "esp_task_wdt.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "rtc.h" +#include +#include #define TAG "UART" #define UART_NUM UART_NUM_0 #define BUF_SIZE (1024) -#define CMD_MAX_LEN (256) +#define CMD_MAX_LEN (2048) static char cmd_buffer[CMD_MAX_LEN]; static int cmd_pos = 0; static TaskHandle_t uart_task_handle = NULL; -// TODO: Set Time -// TODO: Pair Remote -// TODO: Command Move -// TODO: Show current sensor values - -// Parse value as either decimal or hex (0x prefix) -static bool parse_uint64(const char *str, uint64_t *result) { - char *endptr; - - if (str == NULL || result == NULL) { - return false; - } - - // Check for hex prefix - if (strlen(str) > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { - *result = strtoull(str, &endptr, 16); - } else { - *result = strtoull(str, &endptr, 10); - } - - // Check if conversion was successful (endptr should point to null terminator) - return (*endptr == '\0' && endptr != str); -} - -// Format parameter value for display based on its type -static void print_param_value(param_idx_t id, param_value_t val) { - param_type_e type = get_param_type(id); - - char sbuf[9] = {0}; - - switch (type) { - case PARAM_TYPE_u16: - printf("%u (0x%04X)\n", - val.u16, val.u16); - break; - - case PARAM_TYPE_i16: - printf("%d (0x%04X)\n", - val.i16, (uint16_t)val.i16); - break; - - case PARAM_TYPE_u32: - printf("%lu (0x%08lX)\n", - (unsigned long)val.u32, (unsigned long)val.u32); - break; - - case PARAM_TYPE_i32: - printf("%ld (0x%08lX)\n", - (long)val.i32, (unsigned long)val.i32); - break; - - case PARAM_TYPE_f32: - printf("%.6f (0x%08lX as bits)\n", - val.f32, (unsigned long)val.u32); - break; - - - case PARAM_TYPE_f64: - printf("%.6f (0x%016llX as bits)\n", - val.f64, (unsigned long long)val.f64); - break; - - case PARAM_TYPE_str: - memcpy(val.str, sbuf, 8); - sbuf[8] = '\0'; - printf("\"%s\"", sbuf); - break; - - default: - printf("UNKNOWN TYPE\n"); - break; - } -} - -static esp_err_t parse_param_value(const char *orig_str, param_type_e type, param_value_t *val) { - const char *str = orig_str; - // Skip leading whitespace - while (isspace((unsigned char)*str)) str++; - - // Check for negative sign on unsigned integer types - bool is_unsigned_int = (type == PARAM_TYPE_u16 || type == PARAM_TYPE_u32); - if (is_unsigned_int && *str == '-') { - return ESP_FAIL; - } - - char *endptr; - errno = 0; - - switch (type) { - case PARAM_TYPE_u16: - val->u16 = strtoull(str, &endptr, 0); - break; - case PARAM_TYPE_u32: - val->u32 = strtoull(str, &endptr, 0); - break; - - case PARAM_TYPE_i16: - val->i16 = strtoll(str, &endptr, 0); - break; - case PARAM_TYPE_i32: - val->i32 = strtoll(str, &endptr, 0); - break; - - case PARAM_TYPE_f32: - val->f32 = strtof(str, &endptr); - break; - - case PARAM_TYPE_f64: - val->f64 = strtod(str, &endptr); - break; - - default: - return ESP_FAIL; - } - - if (errno == ERANGE || endptr == str || *endptr != '\0') { - return ESP_FAIL; - } - - return ESP_OK; -} - -// Process set parameter command: sp -static void cmd_set_param(char *args) { - char *id_str = strtok(args, " \t"); - char *val_str = strtok(NULL, " \t"); - - if (id_str == NULL || val_str == NULL) { - printf("ERROR: Usage: sp \n"); - return; - } - - // Parse parameter ID - uint64_t id_u64; - if (!parse_uint64(id_str, &id_u64)) { - printf("ERROR: Invalid parameter ID\n"); - return; - } - - param_idx_t id = (param_idx_t)id_u64; - if (id >= NUM_PARAMS) { - printf("ERROR: Parameter ID %u out of range (max %d)\n", - id, NUM_PARAMS - 1); - return; - } - - param_value_t param_val = {0}; - param_type_e type = get_param_type(id); - esp_err_t parse_err = parse_param_value(val_str, type, ¶m_val); - if (parse_err != ESP_OK) { - printf("ERROR: Invalid value\n"); - return; - } - - esp_err_t err = set_param_value_t(id, param_val); - if (err == ESP_OK) { - printf("OK: Parameter %u (%s) set to ", - id, get_param_name(id)); - print_param_value(id, param_val); - printf("(Not committed - use 'cp' to save)\n"); - } else { - printf("ERROR: Failed to set parameter\n"); - } -} - -// Process get parameter command: gp -static void cmd_get_param(char *args) { - char *id_str = strtok(args, " \t"); - - if (id_str == NULL) { - printf("ERROR: Usage: gp \n"); - return; - } - - // Parse parameter ID - uint64_t id_u64; - if (!parse_uint64(id_str, &id_u64)) { - printf("ERROR: Invalid parameter ID\n"); - return; - } - - param_idx_t id = (param_idx_t)id_u64; - if (id >= NUM_PARAMS) { - printf("ERROR: Parameter ID %u out of range (max %d)\n", - id, NUM_PARAMS - 1); - return; - } - - // Get parameter - param_value_t val = get_param_value_t(id); - printf("Parameter %u (%s) = ", id, get_param_name(id)); - print_param_value(id, val); -} - -// Process commit parameters command: cp -static void cmd_commit_params(char *args) { - (void)args; // Unused - - printf("Committing parameters to flash...\n"); - commit_params(); - printf("OK: Parameters committed\n"); -} - -// Process reset parameter command: rp -static void cmd_reset_param(char *args) { - char *id_str = strtok(args, " \t"); - - if (id_str == NULL) { - printf("ERROR: Usage: rp \n"); - return; - } - - // Parse parameter ID - uint64_t id_u64; - if (!parse_uint64(id_str, &id_u64)) { - printf("ERROR: Invalid parameter ID\n"); - return; - } - - param_idx_t id = (param_idx_t)id_u64; - if (id >= NUM_PARAMS) { - printf("ERROR: Parameter ID %u out of range (max %d)\n", - id, NUM_PARAMS - 1); - return; - } - - // Reset to default - param_value_t default_val = get_param_default(id); - esp_err_t err = set_param_value_t(id, default_val); - - if (err == ESP_OK) { - printf("OK: Parameter %u (%s) reset to default: ", - id, get_param_name(id)); - print_param_value(id, default_val); - printf("(Not committed - use 'cp' to save)\n"); - } else { - printf("ERROR: Failed to reset parameter\n"); - } -} - -// Process list parameters command: lp -static void cmd_list_params(char *args) { - (void)args; // Unused - - printf("\n=== Parameter List ===\n"); - printf("ID | Name | Type | Value\n"); - printf("----+-------------------+------+------------------\n"); - - const char* type_names[] = { - "u8 ", "i8 ", "u16", "i16", "u32", "i32", "u64", "i64", "f32", "f64" - }; - - for (int i = 0; i < NUM_PARAMS; i++) { - param_value_t val = get_param_value_t(i); - param_type_e type = get_param_type(i); - - printf("%-3d | %-17s | %-4s | ", i, get_param_name(i), type_names[type]); - print_param_value(i, val); - } +// Process GET command +static void cmd_get(void) { printf("\n"); + + cJSON *response = comms_handle_get(); + if (response == NULL) { + printf("ERROR: Failed to generate GET response\n"); + return; + } + + // Pretty print the JSON + char *json_str = cJSON_Print(response); + cJSON_Delete(response); + + if (json_str == NULL) { + printf("ERROR: Failed to serialize JSON\n"); + return; + } + + printf("%s\n", json_str); + free(json_str); +} + +// Process POST command with JSON data +static void cmd_post(char *json_data) { + printf("\n"); + + // Parse the JSON input + cJSON *request = cJSON_Parse(json_data); + if (request == NULL) { + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) { + printf("ERROR: JSON parse error before: %s\n", error_ptr); + } else { + printf("ERROR: Failed to parse JSON\n"); + } + return; + } + + // Call the unified POST handler + cJSON *response = NULL; + esp_err_t err = comms_handle_post(request, &response); + cJSON_Delete(request); + + if (response == NULL) { + printf("ERROR: Failed to generate POST response\n"); + return; + } + + // Pretty print the response + char *json_str = cJSON_Print(response); + + // Check for special response flags before deleting + cJSON *reboot_flag = cJSON_GetObjectItem(response, "reboot"); + cJSON *sleep_flag = cJSON_GetObjectItem(response, "sleep"); + bool should_reboot = cJSON_IsTrue(reboot_flag); + bool should_sleep = cJSON_IsTrue(sleep_flag); + + cJSON_Delete(response); + + if (json_str == NULL) { + printf("ERROR: Failed to serialize JSON\n"); + return; + } + + printf("%s\n", json_str); + free(json_str); + + // Handle special actions + if (should_reboot) { + printf("\nRebooting in 2 seconds...\n"); + fflush(stdout); + vTaskDelay(pdMS_TO_TICKS(2000)); + esp_restart(); + } + + if (should_sleep) { + printf("\nEntering deep sleep in 2 seconds...\n"); + fflush(stdout); + vTaskDelay(pdMS_TO_TICKS(2000)); + rtc_enter_deep_sleep(); + } } // Process help command -static void cmd_help(char *args) { - (void)args; // Unused - - printf("\n=== Available Commands ===\n"); - printf("sp - Set parameter (e.g., sp 0 42 or sp 14 0xDEADBEEF)\n"); - printf("gp - Get parameter (e.g., gp 0)\n"); - printf("rp - Reset parameter to default (e.g., rp 0)\n"); - printf("cp - Commit parameters to flash\n"); - printf("lp - List all parameters\n"); - printf("help - Show this help\n"); - printf("\nNotes:\n"); - printf("- Values can be decimal (123) or hex (0xABC)\n"); - printf("- Changes are not saved until you run 'cp'\n"); - printf("- Parameter IDs range from 0 to %d\n\n", NUM_PARAMS - 1); -} - -static void cmd_rf_learn(char *args) { - char *id_str = strtok(args, " \t"); - - if (id_str == NULL) { - rf_433_cancel_learn_keycode(); - return; - } - - // Parse parameter ID - uint64_t id_u64; - if (!parse_uint64(id_str, &id_u64)) { - printf("ERROR: Invalid parameter ID\n"); - return; - } - - param_idx_t id = (param_idx_t)id_u64; - if (id < 8) { - printf("Listening for keycode for slot %d\n", id); - rf_433_learn_keycode(id); - return; - } - printf("ERROR: Keycode slot index out of bounds.\n"); +static void cmd_help(void) { + printf("\n=== UART JSON Interface ===\n"); + printf("\nCommands:\n"); + printf(" GET - Get complete system status (JSON)\n"); + printf(" POST: {json} - Send command or update parameters (JSON)\n"); + printf(" HELP - Show this help\n"); + printf("\nPOST JSON Format:\n"); + printf("{\n"); + printf(" \"time\": 1234567, // Set RTC time (optional)\n"); + printf(" \"remaining_dist\": 100, // Set remaining distance (optional)\n"); + printf(" \"cmd\": \"start\", // Execute command (optional)\n"); + printf(" \"parameters\": { // Update parameters (optional)\n"); + printf(" \"PARAM_NAME\": value,\n"); + printf(" \"PARAM_NAME2\": value\n"); + printf(" }\n"); + printf("}\n"); + printf("\nAvailable Commands:\n"); + printf(" start, stop, undo, fwd, rev, up, down, aux\n"); + printf(" reboot, sleep\n"); + printf(" rf_learn, rf_clear_temp, rf_disable, rf_enable, rf_status\n"); + printf(" cal_jack_start, cal_jack_finish, cal_drive_start, cal_drive_finish, cal_get\n"); + printf("\nExamples:\n"); + printf(" GET\n"); + printf(" POST: {\"cmd\":\"start\"}\n"); + printf(" POST: {\"time\":1704067200}\n"); + printf(" POST: {\"parameters\":{\"WIFI_SSID\":\"MyNetwork\",\"WIFI_CHANNEL\":6}}\n"); + printf(" POST: {\"cmd\":\"rf_learn\",\"channel\":0}\n"); + printf("\n"); } // Parse and execute command @@ -338,49 +148,47 @@ static void process_command(char *cmd) { return; } - // Extract command - char *space = strchr(cmd, ' '); - char *tab = strchr(cmd, '\t'); - char *delim = NULL; - - if (space && tab) { - delim = (space < tab) ? space : tab; - } else if (space) { - delim = space; - } else if (tab) { - delim = tab; - } - + // Convert to uppercase for command matching char command[16] = {0}; - if (delim) { - int cmd_len = delim - cmd; - if (cmd_len >= sizeof(command)) { - cmd_len = sizeof(command) - 1; - } - strncpy(command, cmd, cmd_len); - cmd = delim + 1; // Point to arguments - } else { - strncpy(command, cmd, sizeof(command) - 1); - cmd = cmd + strlen(cmd); // Point to empty string + int i = 0; + while (cmd[i] && cmd[i] != ' ' && cmd[i] != '\t' && cmd[i] != ':' && i < 15) { + command[i] = (cmd[i] >= 'a' && cmd[i] <= 'z') ? (cmd[i] - 32) : cmd[i]; + i++; } + command[i] = '\0'; // Execute command - if (strcmp(command, "sp") == 0) { - cmd_set_param(cmd); - } else if (strcmp(command, "gp") == 0) { - cmd_get_param(cmd); - } else if (strcmp(command, "rp") == 0) { - cmd_reset_param(cmd); - } else if (strcmp(command, "cp") == 0) { - cmd_commit_params(cmd); - } else if (strcmp(command, "lp") == 0) { - cmd_list_params(cmd); - } else if (strcmp(command, "help") == 0) { - cmd_help(cmd); - } else if (strcmp(command, "rfl") == 0) { - cmd_rf_learn(cmd); - } else { - printf("ERROR: Unknown command '%s' (type 'help' for commands)\n", command); + if (strcmp(command, "GET") == 0) { + cmd_get(); + } + else if (strcmp(command, "POST") == 0) { + // Find the start of JSON data (after ':') + char *json_start = strchr(cmd, ':'); + if (json_start == NULL) { + printf("ERROR: POST command requires JSON data after ':'\n"); + printf("Usage: POST: {json data}\n"); + return; + } + json_start++; // Skip the ':' + + // Trim whitespace + while (*json_start == ' ' || *json_start == '\t') { + json_start++; + } + + if (*json_start == '\0') { + printf("ERROR: No JSON data provided\n"); + return; + } + + cmd_post(json_start); + } + else if (strcmp(command, "HELP") == 0) { + cmd_help(); + } + else { + printf("ERROR: Unknown command '%s'\n", command); + printf("Type 'HELP' for available commands\n"); } } @@ -390,7 +198,7 @@ void uart_event_task(void *pvParameters) { uint8_t data[BUF_SIZE]; while (1) { - esp_task_wdt_reset(); + esp_task_wdt_reset(); int len = uart_read_bytes(UART_NUM, data, BUF_SIZE - 1, 20 / portTICK_PERIOD_MS); if (len > 0) { @@ -421,7 +229,7 @@ void uart_event_task(void *pvParameters) { } cmd_pos = 0; - printf("> "); + printf("\n> "); fflush(stdout); continue; } @@ -435,7 +243,7 @@ void uart_event_task(void *pvParameters) { } } -esp_err_t uart_init() { +esp_err_t uart_init(void) { // Configure UART uart_config_t uart_config = { .baud_rate = 115200, @@ -450,13 +258,14 @@ esp_err_t uart_init() { ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config)); // Print startup message - /*printf("\n\n"); - printf("=================================\n"); - printf(" ESP32 Parameter Manager\n"); - printf("=================================\n"); - printf("Type 'help' for available commands\n\n"); - printf("> "); - fflush(stdout);*/ + printf("\n\n"); + printf("========================================\n"); + printf(" UART JSON Interface Ready\n"); + printf("========================================\n"); + printf("Type 'HELP' for available commands\n"); + printf("Type 'GET' to see system status\n"); + printf("\n> "); + fflush(stdout); // Create UART task xTaskCreate(uart_event_task, "uart_event_task", 4096, NULL, 12, &uart_task_handle); @@ -465,7 +274,7 @@ esp_err_t uart_init() { return ESP_OK; } -esp_err_t uart_stop() { +esp_err_t uart_stop(void) { if (uart_task_handle == NULL) { ESP_LOGW(TAG, "UART task not running"); return ESP_OK; diff --git a/main/uart_comms.h b/main/uart_comms.h index 84bf7cc..9a17f76 100644 --- a/main/uart_comms.h +++ b/main/uart_comms.h @@ -1,16 +1,21 @@ -/* - * uart_comms.h - * - * Created on: Dec 13, 2025 - * Author: Thad - */ - -#ifndef MAIN_UART_COMMS_H_ -#define MAIN_UART_COMMS_H_ +#ifndef UART_COMMS_H +#define UART_COMMS_H #include "esp_err.h" -esp_err_t uart_init(); -esp_err_t uart_stop(); +/** + * Initialize UART communication interface + * Provides JSON-based GET/POST commands over serial + * + * Commands: + * GET - Returns complete system status as pretty-printed JSON + * POST: {json data} - Sends JSON command/parameter update + */ +esp_err_t uart_init(void); -#endif /* MAIN_UART_COMMS_H_ */ +/** + * Stop UART communication interface + */ +esp_err_t uart_stop(void); + +#endif // UART_COMMS_H \ No newline at end of file diff --git a/main/webpage.h b/main/webpage.h index ed0c21e..b9d3761 100644 --- a/main/webpage.h +++ b/main/webpage.h @@ -1,3 +1,3 @@ -const char html_content[] = {0x1f,0x8b,0x08,0x00,0x38,0xff,0x5b,0x69,0x02,0xff,0xed,0x3d,0x6b,0x57,0xdb,0x48,0xb2,0x9f,0x77,0x7e,0x45,0xc3,0x24,0x8c,0x94,0x08,0xd9,0x06,0x32,0x0f,0x1b,0x99,0x25,0xe0,0x4c,0x32,0x09,0x8f,0xc3,0x23,0x33,0x7b,0x59,0x0e,0x92,0xad,0x36,0xd6,0x20,0x4b,0x1a,0x49,0x86,0x30,0xc2,0xff,0x7d,0xab,0xfa,0x21,0xb5,0x64,0xd9,0x98,0x64,0x33,0x73,0xee,0xb9,0x77,0xf7,0x2c,0xb1,0x5b,0xdd,0xd5,0xd5,0xf5,0xae,0xea,0x92,0x77,0x7b,0xc5,0x0d,0x07,0xe9,0x7d,0x44,0xc9,0x28,0x1d,0xfb,0xdd,0xed,0xd4,0x4b,0x7d,0xda,0xdd,0x0b,0x83,0x34,0x0e,0x7d,0x72,0xec,0x04,0xd4,0xdf,0x6e,0xf0,0xc1,0xed,0x31,0x4d,0x1d,0x32,0x80,0x47,0x34,0x48,0xad,0xd5,0x3b,0xcf,0x4d,0x47,0x96,0x4b,0x6f,0xbd,0x01,0x5d,0x67,0x5f,0x0c,0x2f,0xf0,0x52,0xcf,0xf1,0xd7,0x93,0x81,0xe3,0x53,0xab,0x65,0x36,0x57,0x49,0xe0,0x8c,0xa9,0x75,0xeb,0xd1,0xbb,0x28,0x8c,0xd3,0xee,0x76,0x92,0xde,0x03,0xa4,0x6f,0xef,0x62,0x27,0x8a,0x68,0x9c,0xa5,0xf4,0x53,0xba,0xee,0xf8,0xde,0x75,0xd0,0x1e,0x00,0x50,0x1a,0x77,0xfa,0xe1,0xa7,0xf5,0xc4,0xfb,0xd3,0x0b,0xae,0xdb,0xfd,0x30,0x76,0x69,0xbc,0x0e,0x23,0xd3,0x6f,0xc5,0xae,0xd9,0xd8,0xf9,0xc4,0xf7,0x6a,0xbf,0x6a,0x36,0xa3,0x4f,0x9d,0xb1,0x13,0x5f,0x7b,0x41,0xdb,0x99,0xa4,0x61,0x27,0x72,0x5c,0x17,0xd7,0x35,0x49,0x0b,0x1e,0x4d,0xfb,0xa1,0x7b,0x5f,0xb3,0x81,0x58,0xd1,0x2c,0xa6,0x4f,0x5f,0x64,0x83,0xd0,0x0f,0xe3,0xf6,0xb7,0x1b,0x43,0xfc,0x6f,0xa7,0xef,0x0c,0x6e,0xae,0xe3,0x70,0x12,0xb8,0xeb,0xe2,0xc1,0x70,0x38,0xec,0x0c,0x01,0x85,0xf5,0xa1,0x33,0xf6,0xfc,0xfb,0xf6,0x61,0x98,0x86,0xe4,0xd4,0x09,0x12,0xe3,0x23,0x8d,0x5d,0x27,0x70,0x8c,0x04,0xbe,0xac,0x27,0x34,0xf6,0xc4,0x44,0x38,0x03,0x6d,0xb7,0xcc,0x8d,0x98,0x8e,0xa7,0x5e,0x10,0x4d,0x52,0xa3,0x3f,0x49,0xd3,0x30,0x50,0x31,0x8a,0xbd,0xeb,0x51,0x5a,0x7f,0xe2,0x1a,0x1c,0xe8,0x90,0xba,0xf4,0xa7,0x0e,0x9f,0xd3,0x6e,0x45,0x9f,0x48,0x12,0xfa,0x9e,0x4b,0xbe,0xed,0x3b,0x3f,0x7d,0xff,0xaa,0x2f,0x1e,0xac,0xc7,0x8e,0xeb,0x4d,0x92,0xf6,0x2b,0x20,0x0e,0x27,0x54,0xab,0xd9,0x7c,0xce,0x51,0xb8,0x40,0x3e,0x5b,0x88,0xc0,0xa5,0xa1,0x0c,0x04,0x93,0x71,0x9f,0xc6,0x97,0x99,0x7a,0xc0,0x71,0x18,0x84,0x49,0xe4,0x0c,0xa8,0x58,0x19,0x53,0xc7,0x0d,0x03,0xff,0xfe,0x32,0xab,0x41,0x6c,0x0b,0xff,0x3b,0x9d,0x3d,0x1f,0xa7,0xf8,0xd4,0x1c,0x8c,0x9c,0xe0,0x9a,0xba,0x06,0x70,0x71,0x3c,0xf6,0xd2,0xab,0x7e,0x1a,0x64,0x05,0x61,0x57,0xbc,0x31,0xca,0x86,0x13,0xa4,0x35,0x87,0xde,0x70,0xb6,0x7e,0xda,0x74,0x8b,0x29,0x20,0x09,0x4e,0x30,0xa0,0xfe,0x53,0x60,0xfc,0xb0,0xb1,0x59,0x02,0x90,0x23,0x61,0x94,0x80,0x4d,0xe2,0x04,0x66,0x47,0xa1,0x27,0xe4,0x90,0x91,0x39,0x08,0x03,0x2a,0xe8,0xb8,0xf5,0xea,0xb9,0x10,0x9e,0xf5,0x34,0x8c,0xda,0x28,0x63,0xb9,0x0c,0xb1,0x2f,0x8c,0x7e,0x77,0x14,0x99,0xda,0xfe,0xa1,0xd9,0x54,0x77,0xba,0x70,0xbd,0xc4,0xe9,0xfb,0xd4,0xbd,0x54,0xf7,0x2c,0x46,0xe5,0x51,0x7e,0xfc,0xf1,0xc7,0x8e,0x40,0x24,0x08,0x91,0x8c,0x7e,0x78,0x47,0xdd,0x9a,0x33,0x6d,0x6d,0x6d,0x29,0x67,0x4a,0x11,0x4a,0x26,0x04,0x00,0x66,0xf8,0x4e,0x94,0xd0,0xb6,0xfc,0xd0,0x51,0x58,0xe2,0xd3,0x61,0xaa,0xca,0x45,0xea,0x66,0xb9,0xd0,0x01,0xfb,0xc6,0xaa,0x5c,0x09,0x81,0x93,0x67,0xfc,0x11,0x74,0x2a,0x99,0x8c,0x81,0x04,0x25,0xb5,0x62,0x10,0x15,0x35,0xa9,0xa5,0x7f,0x8d,0x70,0x4a,0xb0,0xe6,0x26,0xa8,0xc8,0x0c,0xed,0xcc,0xc1,0x58,0x62,0xc6,0x79,0xa0,0x2a,0xd5,0x2b,0x54,0xaa,0x6f,0xc7,0xc9,0x75,0x8d,0xb0,0x8d,0x5a,0x59,0x31,0x75,0x43,0x4c,0x8d,0xc2,0x68,0x12,0xad,0x87,0xb7,0x34,0xf6,0x9d,0xfb,0xec,0xcf,0x75,0x2f,0x70,0xe9,0x27,0x24,0x40,0xb3,0x06,0xdd,0x26,0xfb,0x4f,0x7f,0xb3,0xf3,0xfb,0x24,0x49,0xbd,0xe1,0xfd,0xba,0xb0,0x3d,0xd2,0x80,0xb0,0xed,0xd6,0xbd,0x94,0x8e,0x13,0x39,0x54,0x10,0xb4,0x33,0xe2,0x67,0x60,0x9f,0x81,0xbd,0x11,0x6c,0xc8,0x0f,0x10,0x85,0x09,0x18,0xc7,0x30,0x68,0x0f,0xbd,0x4f,0xc0,0x52,0x14,0xa1,0x66,0x07,0x89,0x07,0x06,0x48,0x20,0x28,0x8d,0x9c,0x42,0xcd,0x1a,0x03,0x39,0x47,0x47,0x2a,0x24,0x6e,0x71,0xeb,0x28,0xad,0xe5,0x56,0x53,0x15,0xd7,0x4d,0xfc,0xc2,0xec,0xce,0x08,0xd4,0xfa,0x0e,0x2c,0xe6,0x16,0xb0,0xfd,0x7b,0xf8,0x9f,0x38,0xfd,0x96,0x5b,0xc1,0x89,0x8c,0x36,0xb2,0x85,0x4c,0x16,0x48,0x28,0x0a,0x52,0x3d,0x16,0x89,0x96,0x82,0xa0,0x32,0xba,0x85,0xdc,0xfb,0xe7,0x98,0xba,0x9e,0x43,0x92,0x41,0x4c,0x69,0x40,0x9c,0xc0,0x25,0x1a,0x3b,0xd3,0xb6,0xb5,0xf9,0x0a,0xce,0xa1,0x67,0x35,0xce,0x81,0x91,0xbf,0xf0,0x07,0x20,0x6f,0x5c,0x47,0x48,0x1a,0x13,0x26,0xf2,0x75,0x26,0x57,0x59,0x2a,0x39,0xd7,0xf7,0xc3,0xc1,0x4d,0xbe,0x34,0x13,0xa7,0x93,0xaa,0x82,0x54,0x2c,0xcf,0x94,0x47,0xe6,0xb6,0x30,0xc9,0xe6,0x9f,0xd2,0xa7,0x80,0x2b,0x78,0xc1,0x36,0xfe,0x99,0x27,0x6a,0xd7,0x4e,0x24,0x19,0x99,0x93,0x75,0x43,0xdd,0x15,0xc1,0x54,0xf6,0x24,0xc2,0x0e,0x97,0x5d,0x5a,0xd5,0xba,0xfd,0x17,0xbc,0xcb,0x18,0x10,0xca,0x49,0x56,0xb1,0x85,0x64,0xa3,0xd6,0x20,0xd6,0x21,0xda,0x1e,0xa1,0x5e,0x1a,0xb5,0xcf,0xcc,0x28,0xf6,0x98,0xbd,0x59,0x28,0x38,0x1c,0xc3,0xe9,0x42,0x08,0x7c,0x97,0x1a,0x7e,0xfc,0xe8,0xfc,0xd0,0xdc,0x7a,0x25,0x17,0x33,0x67,0xc7,0xb8,0xe0,0x78,0x41,0xed,0xfc,0x92,0x9c,0x33,0x66,0x90,0x66,0x69,0x75,0x85,0xf0,0xcb,0x28,0xf0,0x97,0x78,0xf6,0x1a,0x17,0xa4,0x2a,0x8f,0x99,0xfc,0x81,0xce,0x8d,0xcf,0xdf,0x7c,0xf5,0xbc,0x6c,0x02,0xaa,0x0c,0xda,0x6e,0xf0,0xf8,0x6c,0xbb,0x31,0x02,0x7f,0xdf,0xdd,0xc6,0x10,0xaa,0xbb,0xed,0x7a,0xb7,0xc4,0x73,0x2d,0x11,0xb3,0xe5,0xdf,0x85,0xac,0x76,0xb7,0x47,0xad,0xee,0x9e,0x3f,0x19,0xdc,0xec,0x81,0xbb,0x03,0xf5,0x84,0xc5,0x2d,0x88,0x23,0x51,0x67,0xe0,0x1f,0x98,0x9f,0x22,0x24,0xc6,0x0d,0x12,0x06,0x03,0xdf,0x1b,0xdc,0x58,0xab,0x09,0x0d,0x5c,0x31,0x5f,0xfb,0x2e,0x49,0x9d,0x38,0xfd,0x4e,0x5f,0x25,0x03,0xdf,0x49,0x12,0x0b,0x4c,0x7f,0xf7,0xf4,0x6c,0xf7,0xe4,0x6c,0xbb,0xc1,0x97,0x01,0x3e,0x08,0x63,0x09,0x38,0x61,0x54,0x02,0x43,0xd8,0x71,0xac,0xc7,0xbc,0x13,0xec,0x76,0x74,0xfc,0xd4,0xcd,0x00,0x44,0x58,0xc6,0xf9,0xfc,0x70,0xff,0xa8,0x02,0xa5,0x81,0xe7,0x6f,0x48,0x5a,0xa8,0x24,0x81,0x38,0xda,0x87,0x00,0x2b,0xb0,0x36,0xbb,0xdb,0x4c,0x6e,0x90,0xa4,0xe0,0xd2,0x88,0x8c,0xb4,0x54,0x08,0x0b,0x96,0x9c,0xff,0x76,0x75,0xf6,0xee,0xa0,0x87,0x48,0xb2,0x28,0xcb,0x02,0xc9,0xbc,0xd9,0xe3,0x01,0x97,0x96,0x8e,0xbc,0x44,0x07,0x22,0xd0,0xc8,0x6a,0x11,0x16,0xeb,0xb9,0x4e,0x4a,0x53,0x6f,0x4c,0xd7,0xc1,0x5c,0x39,0x7e,0x97,0xc8,0x33,0x02,0xa4,0x20,0xbc,0xc3,0xa0,0x24,0x3f,0x6e,0x42,0xd3,0x33,0x98,0x79,0x16,0x1e,0x86,0x77,0x9a,0xde,0x3d,0xbd,0x0f,0x06,0x04,0x07,0xea,0x8e,0x28,0xd8,0x7c,0x3a,0x18,0x51,0x77,0x02,0xb6,0xf2,0x14,0x39,0x5a,0x10,0x52,0xc5,0xf6,0xe0,0xe8,0x63,0xef,0x8a,0xb1,0xb7,0xc0,0x99,0xff,0x23,0x57,0x0b,0xb4,0x79,0xb0,0x0a,0x1b,0x2e,0xda,0xa8,0x87,0xf2,0x36,0x77,0x9b,0xde,0xe1,0xfe,0x17,0x6c,0xf2,0x2d,0x39,0x00,0xa3,0x91,0x34,0xf6,0x9d,0xfb,0xfa,0x3d,0x0e,0xcf,0x0f,0xd8,0x3e,0xa7,0x04,0x6c,0xa1,0xd5,0x5c,0x66,0x2b,0x1e,0x6b,0xd7,0x6c,0x76,0x08,0x76,0x82,0xed,0x47,0x76,0xe7,0x10,0xee,0xb0,0xf7,0xdb,0xd9,0xd5,0xee,0x87,0xdd,0x93,0x83,0x5c,0x46,0x6a,0x79,0x3a,0x0b,0xfb,0x84,0x8e,0xc1,0x9c,0x99,0x64,0xdf,0x03,0x4d,0x83,0xe0,0x93,0x68,0xc3,0x54,0xaf,0xdf,0xe4,0xa4,0x77,0x70,0xb5,0xff,0xee,0x54,0xe1,0x0d,0xfc,0x75,0x7d,0xca,0x41,0x80,0xe1,0x40,0x18,0x5c,0xbc,0x96,0x3a,0x16,0x3b,0xd1,0x12,0xfb,0xee,0x9f,0xbc,0x03,0x7e,0xb1,0x9d,0xbf,0x9c,0x96,0xbf,0x80,0x92,0x93,0xb7,0xcc,0xac,0x11,0xcd,0x0b,0xe6,0x6c,0xf9,0xcb,0xee,0xde,0xfb,0xff,0xd6,0x8e,0xaf,0x9d,0x14,0x4c,0xfb,0x3d,0xd1,0x3e,0xd6,0x6d,0x76,0x1b,0xfa,0xa9,0x73,0x4d,0x17,0xa8,0x76,0xf7,0x38,0x0e,0xaf,0x63,0x67,0x4c,0x4e,0xde,0x10,0x20,0x75,0x98,0xd2,0xb9,0x56,0x28,0xe2,0x33,0x4f,0xde,0x9c,0xd2,0x3f,0x26,0x14,0xa8,0x0a,0xba,0x29,0x57,0xef,0xfa,0x3e,0x79,0xcd,0xfd,0xdf,0x22,0x43,0x24,0x20,0xca,0xec,0x83,0x19,0xf3,0x3c,0x29,0xc9,0x37,0x42,0x69,0xc2,0x80,0xd5,0x8c,0xa9,0x1f,0x3a,0xae,0xa6,0x77,0xba,0xc0,0xc8,0x81,0x13,0xbb,0x05,0xec,0x3a,0x48,0x79,0xd2,0x93,0x43,0xe2,0x43,0xc7,0x0e,0xa0,0x98,0xa0,0x25,0x71,0x40,0x26,0xb8,0x0c,0x29,0x68,0xf6,0x63,0xfe,0x3f,0x97,0x82,0xef,0xf5,0x93,0xee,0xb6,0xc8,0x35,0xba,0x20,0x92,0x47,0x67,0x3d,0xb2,0x77,0x74,0x78,0x76,0x72,0xf4,0x01,0x5c,0x94,0x18,0x27,0x38,0x9d,0x14,0xe4,0x19,0x87,0x93,0x84,0x42,0x34,0x1b,0x80,0xa1,0x46,0xf3,0xc3,0xe9,0xa8,0x7d,0x37,0xbc,0x73,0xbf,0x33,0x08,0xbd,0x05,0x5f,0x05,0xe6,0x1a,0x9c,0x56,0x38,0x19,0x8c,0xd8,0x8c,0x85,0x13,0xb9,0x5d,0x67,0xee,0x53,0x02,0xf7,0x29,0x60,0x6e,0xa1,0x97,0x11,0x4b,0x74,0xf9,0x64,0x12,0x55,0x87,0xd9,0x2e,0xe0,0x2f,0x4a,0xe3,0xdd,0x37,0xbf,0xee,0xe7,0x27,0x5e,0x02,0xf5,0x98,0xde,0x2e,0x87,0x7a,0x79,0xe2,0x57,0x41,0xfd,0xa4,0xf7,0xf1,0x29,0xa8,0x4f,0xa2,0xe5,0x30,0x2f,0xcd,0xfb,0x2a,0x88,0x9f,0x1f,0x3f,0x05,0x6f,0x1c,0x5a,0x0e,0xf3,0xca,0xcc,0xaf,0x82,0xfb,0xfe,0xd1,0xaf,0x87,0x4f,0xc1,0xde,0x99,0x7c,0x5a,0x0e,0xf9,0xf2,0xc4,0xaf,0x82,0xfb,0xee,0xf9,0x6f,0x8a,0x11,0xca,0xd5,0xba,0x56,0xc7,0xf7,0x77,0x0f,0x7f,0xee,0x9d,0x90,0xff,0x39,0x3a,0xec,0xa9,0x0a,0x5e,0x0e,0x22,0xf7,0x20,0x8c,0xee,0xc7,0xcc,0x26,0xcd,0x8f,0xd1,0x06,0x62,0x12,0x9c,0xf1,0x77,0x70,0x03,0x10,0xa1,0x71,0x77,0x50,0x5a,0x3c,0x4b,0xd0,0xd9,0xd5,0x6e,0xec,0xdd,0x52,0x5c,0xbe,0x8f,0x1f,0x6a,0xd7,0xcf,0x9a,0xf1,0xbd,0x49,0x1c,0x63,0xaa,0xfb,0x91,0xc6,0x49,0x09,0xcd,0xc2,0x17,0xf0,0x27,0x8b,0x7c,0xc1,0x1b,0x2f,0x1e,0xdf,0x39,0x31,0xad,0xae,0x76,0x06,0x03,0x1a,0xa5,0x96,0xd9,0xf7,0x58,0xbc,0x36,0x14,0xd3,0xae,0x86,0x1e,0x04,0x42,0x3c,0xd2,0x55,0x2b,0x0e,0xdc,0x65,0xe1,0xc3,0x2e,0x51,0xe3,0xbc,0x49,0x84,0xe6,0xbc,0x64,0xa0,0xf9,0x90,0xdc,0x17,0x75,0x86,0x0d,0x90,0x02,0x93,0xb9,0x27,0xfe,0x10,0x5e,0xc3,0x34,0x7f,0xd6,0x5f,0xc1,0x4e,0x7e,0x78,0x5d,0xda,0x06,0x05,0x16,0xe1,0xc2,0x1a,0x5c,0x82,0xf2,0x2d,0x46,0x08,0x0c,0x2d,0xf0,0x58,0x42,0x12,0x10,0xa6,0x10,0x89,0xfc,0xc1,0xc2,0x18,0x3d,0xa6,0xfd,0x30,0x4c,0x3f,0x2b,0x25,0x38,0xe9,0xbd,0x3e,0x3a,0x3a,0x5b,0x20,0x2a,0xe5,0xcc,0xc3,0xa7,0xf4,0xe9,0xa9,0xc7,0xc6,0xe6,0x0f,0xdd,0xd3,0x0f,0xbd,0xde,0x71,0x9d,0x9e,0x34,0x20,0xcd,0x92,0x7f,0x45,0xc6,0x55,0x2a,0x63,0x55,0x46,0x8b,0x6c,0x6c,0xa3,0x18,0x14,0x75,0xfb,0xc6,0x68,0xa3,0xbb,0x1d,0x15,0xc3,0x63,0x9a,0x24,0x10,0x8f,0xc0,0x83,0xa8,0x02,0xa5,0x92,0xff,0xd6,0x48,0x95,0x22,0xca,0xca,0x92,0x5a,0x44,0x45,0x26,0x5e,0x3e,0x0a,0xff,0x9b,0x0c,0x62,0x2f,0x4a,0xbb,0x3e,0x4d,0x09,0x44,0xaf,0x8e,0x95,0x4d,0x8d,0x08,0xc3,0x83,0x33,0xe4,0xea,0x1e,0xe8,0x46,0x4a,0x5d,0x6b,0xa5,0x65,0x44,0xa1,0xef,0xbf,0xc3,0xac,0xf9,0xd6,0xf1,0x21,0xfa,0xf2,0x7d,0x63,0x1c,0xba,0x8e,0x7f,0x42,0x21,0x41,0xbe,0xa5,0x6c,0xa4,0x03,0xd8,0x26,0x29,0x81,0xb8,0x0d,0x36,0xee,0xba,0xe1,0x60,0x32,0x06,0x3a,0x98,0xd7,0x34,0xed,0xf9,0x14,0x3f,0xbe,0xbe,0x7f,0xe7,0x6a,0x9e,0xab,0x77,0x86,0x93,0x60,0x80,0xfa,0x4b,0x92,0x51,0x78,0x77,0x80,0x70,0x34,0x46,0x20,0x43,0xd0,0x43,0x54,0xe8,0x13,0xeb,0xe2,0xd2,0x08,0x23,0x9c,0x99,0x00,0x62,0x7a,0x86,0x58,0x0a,0xa2,0x5b,0xf3,0xe0,0xdb,0x25,0xde,0xd8,0xba,0xc1,0x20,0xf7,0xfc,0xc7,0x16,0xb0,0x69,0x30,0x5d,0xa0,0xf0,0xf8,0x02,0x31,0x11,0x96,0x08,0x6c,0x1f,0x5f,0x22,0x26,0xc2,0x12,0xc6,0xab,0x3d,0xc9,0xdd,0xc7,0xd6,0x55,0x84,0x41,0xae,0x7f,0x7c,0x43,0x36,0xcd,0xd6,0x3b,0x82,0x06,0x26,0xd6,0x40,0xf6,0xc4,0xb5,0x51,0x89,0xe2,0xf0,0xcc,0x0b,0x00,0xf4,0xdb,0xb3,0x83,0x0f,0x96,0xe4,0x82,0x20,0xbd,0x89,0x5c,0x7a,0x87,0x90,0x76,0xb4,0x32,0xde,0x26,0x93,0x4a,0x53,0x48,0xa5,0x65,0xb3,0xd2,0x9b,0x2d,0x91,0x33,0x99,0xd9,0x93,0x40,0xd8,0xe0,0x19,0x8c,0x3c,0x3c,0xd8,0x88,0x46,0x31,0x0d,0x24,0x6a,0x52,0x99,0xf7,0x11,0x87,0x60,0x62,0x31,0x09,0x36,0x18,0xd0,0x51,0xe8,0xbb,0x40,0xad,0xd2,0xd4,0xe3,0xe2,0x01,0x5b,0x20,0x92,0xe5,0x70,0x92,0x6a,0x9a,0x6e,0x75,0xe5,0xfa,0x21,0x10,0x0a,0x42,0x5e,0xa3,0xd5,0x6c,0xea,0x7a,0x7b,0xf1,0x31,0x50,0xbb,0xec,0x82,0xab,0x0a,0x65,0x6c,0xbb,0x83,0x32,0xc8,0x1f,0x09,0x7a,0xa3,0x94,0x76,0xc4,0x64,0xd8,0x26,0xee,0x39,0x83,0x91,0x06,0xc6,0xd6,0xea,0x66,0xc5,0xdc,0x82,0x51,0x03,0xa6,0x53,0x62,0xad,0x66,0xf3,0xc7,0xc0,0x22,0x51,0x25,0x53,0x39,0x04,0x40,0xd8,0x77,0x03,0x3f,0x88,0xf2,0xd9,0xda,0x9a,0x98,0xc8,0xec,0xdb,0x07,0xc8,0xe7,0x4c,0xc7,0x45,0x7e,0xf3,0xc7,0xb9,0x34,0x9a,0xd2,0x3c,0x22,0x11,0x18,0x22,0x31,0x4d,0x26,0x7e,0x6a,0xcd,0x32,0x35,0x13,0xe5,0x3f,0xdc,0x85,0xf1,0x82,0x13,0xbd,0x5d,0xe2,0xcf,0xb4,0x78,0xdc,0x51,0x35,0x7f,0x6d,0xcd,0xd2,0xd4,0xef,0x1a,0xdf,0x46,0x37,0xd0,0x22,0xe8,0xc6,0xc8,0x73,0x29,0x57,0x70,0x9d,0x9d,0x02,0xdc,0xba,0x8f,0xe6,0x17,0x8e,0xa1,0x7c,0x93,0x8b,0xa6,0x0a,0xd1,0xb1,0xc4,0x05,0x76,0x7d,0xe4,0xf9,0xae,0xc6,0x47,0xe5,0xd1,0x24,0xdd,0xcd,0x68,0x92,0x8c,0x34,0x81,0x3d,0x42,0x9f,0xea,0x53,0x9d,0xf1,0xe7,0x86,0xde,0xbf,0x65,0xc9,0x72,0x6c,0x51,0x38,0xbd,0x37,0xd4,0xa8,0x09,0x63,0x96,0x65,0xd9,0x3d,0xb4,0x61,0xb6,0x9e,0x51,0x20,0x28,0x0b,0xba,0xf6,0xe9,0xd0,0x81,0xbd,0x35,0xbe,0x52,0x90,0xf1,0x35,0xb0,0xaf,0xb2,0xd9,0xd0,0x03,0xf7,0xd2,0xb7,0xba,0x7d,0x53,0xe1,0x86,0xde,0x29,0x16,0xec,0x14,0x1f,0xcd,0x9c,0x47,0xc0,0x01,0x4d,0x6f,0x57,0x40,0xf9,0x34,0xb8,0x4e,0x47,0xdd,0xa6,0xe4,0xa5,0x7c,0x70,0x51,0x3b,0x6f,0xbd,0x75,0x59,0x81,0x37,0xa5,0x7e,0x02,0x2e,0x58,0x3d,0x15,0x64,0x83,0x11,0x9d,0x7f,0x2c,0x9e,0x54,0x2e,0x38,0xd5,0x4a,0xe5,0x58,0xf9,0x82,0x9d,0xfc,0xd3,0xe7,0x1e,0xaa,0x39,0x83,0xfe,0xb4,0x73,0x07,0xdb,0x86,0x77,0x26,0x13,0x9c,0xf7,0x39,0xb3,0xd6,0xd6,0x72,0x2d,0x89,0x21,0x76,0xbd,0xa5,0x3d,0x3c,0x09,0xca,0x38,0x05,0x05,0xd4,0x6c,0x38,0x2c,0x06,0x2e,0xb6,0x51,0xbf,0x5c,0x9f,0x33,0x6e,0x15,0xe2,0x60,0xe4,0x1b,0x80,0xd2,0xcc,0x83,0x7e,0xa3,0x40,0x14,0x6e,0xa4,0x6a,0x21,0xb0,0xda,0x6f,0x4f,0x73,0x37,0xa6,0x48,0xf9,0x67,0xf9,0xa8,0xce,0x9c,0x6d,0xb8,0x21,0x5a,0xa8,0x6e,0x2b,0x2d,0xa9,0x6a,0xf3,0x68,0xaa,0x7d,0x1d,0xa2,0xb2,0x4d,0x0b,0x12,0xb0,0xc7,0x60,0xb8,0x30,0x16,0xd6,0xa4,0x03,0x61,0x2e,0xc6,0xb2,0xc5,0x30,0x88,0x67,0x4c,0xd3,0x49,0x1c,0x90,0x80,0xde,0x91,0xe3,0x38,0x1c,0x7b,0x09,0xb3,0x17,0x2c,0x82,0xe8,0x66,0xa5,0x80,0x42,0x0c,0x1b,0xf3,0x22,0x84,0x0b,0x76,0xe3,0xd8,0xb6,0xf7,0x98,0x78,0xda,0x06,0xb3,0x4c,0xed,0x95,0xd6,0xd4,0x10,0x0f,0x8e,0xde,0xe7,0x83,0x4d,0x43,0xde,0x3d,0xac,0x34,0xa7,0x97,0x60,0x24,0x2a,0x68,0xef,0xc2,0x79,0xd2,0x2a,0xd2,0x87,0x61,0xea,0x0d,0xe8,0x57,0xc1,0xf9,0x09,0xa8,0xe1,0x8e,0xd1,0x0c,0x6e,0xcc,0x6e,0xdb,0x86,0xcb,0x75,0x9c,0x39,0x4b,0x70,0x4d,0x4f,0x44,0x15,0x9d,0x41,0x37,0xe3,0x1f,0xd6,0xd6,0xf8,0xbf,0x42,0x53,0x77,0xe2,0x92,0x31,0xe7,0x8e,0x56,0x6f,0xcb,0x51,0xce,0xfa,0xaf,0xc0,0x1a,0x23,0xcb,0xbd,0x12,0x3e,0xca,0x43,0x86,0xb6,0x1a,0x31,0xb0,0xe3,0xb6,0xd5,0xb3,0x1b,0xd5,0x40,0xa0,0x6d,0xdb,0xd3,0x12,0x2d,0x11,0xec,0x09,0x4b,0x41,0xa4,0x9a,0x16,0xc8,0xdb,0xfb,0xac,0x71,0x86,0xf0,0xe7,0x5e,0x70,0x6d,0x1b,0xf6,0x31,0x16,0x09,0xef,0x3c,0xdf,0x07,0xaf,0x39,0x84,0x63,0x8f,0x08,0x64,0x7a,0xdb,0x58,0xf2,0xc7,0xa0,0x7a,0x55,0x06,0xfa,0x93,0x20,0x45,0xdd,0x59,0xed,0xbe,0x82,0x5c,0x19,0x1e,0x76,0x49,0x42,0x21,0x4a,0x73,0x13,0xd3,0x34,0x6d,0xe3,0xe2,0x52,0x18,0x5f,0x39,0xcf,0x7a,0x65,0xe4,0x9f,0xf3,0x58,0x1a,0xe2,0x15,0xf9,0x99,0x05,0x2c,0x59,0x3e,0x65,0x7d,0xbd,0xbc,0xfc,0xf1,0x70,0x2f,0x9f,0x0a,0x26,0x45,0x59,0x06,0x36,0x40,0xf9,0x56,0x0a,0x31,0xf2,0x71,0xbd,0x40,0x6d,0xdb,0x6a,0xe2,0x0a,0x9f,0x3a,0x71,0x8e,0xd9,0x0c,0xde,0xba,0x31,0x53,0x95,0x04,0x91,0x68,0xd1,0xcd,0x2a,0xd5,0xcf,0xf6,0xf0,0xbe,0xa2,0x86,0xec,0x18,0xa5,0x11,0xd0,0x32,0x72,0x4a,0x81,0xb5,0xf6,0xd9,0x88,0x12,0xde,0xc2,0x04,0xa9,0x1b,0x84,0x91,0x20,0xc5,0xd4,0x4d,0x48,0x1a,0x92,0x3e,0xe4,0xd4,0x00,0x62,0x14,0x87,0x81,0xf7,0x27,0x75,0x91,0xb2,0x42,0x90,0xf2,0x9b,0x90,0x5a,0x79,0x32,0x64,0x78,0xd1,0x76,0x70,0x3d,0x23,0x6e,0xf9,0x2a,0xc5,0x70,0xee,0x1c,0x0f,0x29,0xac,0x56,0x45,0xa7,0xa8,0x87,0x3c,0x95,0x81,0x8c,0x3b,0xa0,0xee,0xd1,0xb0,0x77,0x74,0x06,0x39,0x50,0x39,0x6d,0x81,0x31,0x79,0x28,0x65,0xda,0xc3,0x83,0x56,0x5a,0xd4,0x54,0xb4,0xc4,0x3e,0x82,0x6c,0x2d,0x1c,0x92,0xb3,0xd8,0xb9,0x45,0xbd,0x90,0x82,0x07,0x1e,0x36,0x00,0x32,0xa0,0x6d,0x26,0x40,0x65,0xcf,0x87,0x8f,0x31,0xde,0x72,0xe3,0x34,0xac,0xe8,0xf2,0x92,0xbd,0x97,0x10,0xd1,0x85,0xa2,0x50,0x60,0xbe,0x29,0xd1,0xa7,0x28,0x3c,0x9e,0x60,0xd7,0x3b,0x97,0xa7,0x68,0xf9,0x11,0x62,0x5e,0x1c,0x1a,0xf0,0xbc,0x19,0x18,0xa3,0x24,0xd1,0x72,0xb0,0x93,0xc6,0xf7,0x59,0xe0,0xdc,0x7a,0xd7,0x4e,0x1a,0xc6,0xe6,0xad,0x28,0xc8,0x6c,0x40,0x08,0x3d,0x05,0xde,0x0f,0x46,0xd9,0x54,0x61,0xb6,0x52,0xd4,0x12,0x00,0x0c,0x5e,0xd5,0xca,0xd8,0x3f,0x6b,0x6b,0xec,0x9f,0x99,0xf8,0xc4,0x28,0x50,0x5c,0x5b,0x2b,0x0b,0x5d,0xf1,0x44,0x37,0x2a,0xf8,0x2a,0xab,0x66,0x35,0xa8,0x32,0x17,0xc4,0xf2,0x55,0x53,0x15,0x4b,0xa5,0x36,0x96,0xa9,0xbb,0x5b,0xda,0xfc,0xfd,0xb9,0xcd,0x63,0x82,0x44,0x0a,0x48,0x2a,0xd1,0xc6,0xee,0xa1,0x33,0xa6,0x3a,0x46,0x9b,0x2b,0xf2,0x1b,0xc6,0x66,0x8c,0x30,0xf6,0xda,0xda,0x0a,0x17,0xb6,0x92,0xa3,0xb4,0x7f,0x45,0x1b,0xd3,0xa7,0xd7,0x1e,0xda,0xfb,0x5b,0x30,0x3e,0x64,0x9d,0x44,0x80,0x04,0xc4,0x77,0x03,0x3e,0xc7,0xb4,0x75,0x1d,0x16,0xab,0x10,0x79,0x31,0x65,0x1e,0x48,0x21,0x55,0xc2,0x7a,0xe1,0x4c,0x80,0xc9,0x0e,0x86,0xe0,0xb9,0x6e,0x61,0xef,0x86,0x14,0x2c,0x93,0xec,0x82,0xb4,0xdd,0x87,0x13,0x92,0x4c,0x62,0xba,0x33,0xbb,0x1d,0x2b,0xa9,0x2c,0xb3,0x1b,0x9b,0x38,0x0b,0x4e,0x47,0x31,0x12,0x19,0x48,0x04,0x7a,0x45,0x2d,0x0e,0x68,0x48,0x41,0x84,0x34,0xdb,0x6c,0x44,0x61,0x02,0x16,0x20,0x1b,0xd3,0x74,0x14,0xba,0x6d,0xfb,0xf8,0xe8,0xf4,0xcc,0x36,0xf0,0xde,0x9b,0xc6,0x49,0x3b,0x5b,0x15,0xb6,0x6a,0x1d,0x3d,0xc1,0x6a,0xdb,0x86,0xc4,0x00,0xe2,0x48,0x66,0x7a,0x1a,0xbf,0x27,0x90,0x3b,0x41,0xd6,0x10,0xba,0xf7,0xed,0x5f,0x4e,0x8f,0x0e,0x21,0x80,0xc2,0x53,0x7a,0xc3,0x7b,0x2d,0x83,0x13,0xb4,0xc5,0x29,0x58,0x62,0x80,0x7c,0x91,0x18,0x98,0xe1,0x8d,0x9e,0x29,0xc7,0xe1,0x11,0x80,0x2d,0x78,0x49,0xbe,0x7b,0x96,0xc9,0x95,0xdf,0x91,0xa1,0xe3,0xf9,0xd4,0x6d,0x93,0x67,0x59,0xbe,0x1a,0x08,0x97,0x4e,0x92,0xe9,0xec,0xd0,0x19,0x28,0xe5,0x14,0x8c,0x2f,0x77,0xc4,0xd3,0x59,0x9e,0xed,0xcc,0x78,0xa2,0xb6,0x92,0xb0,0x32,0x8a,0x9c,0x32,0x48,0x46,0xa1,0x65,0x1a,0x08,0x15,0xda,0xa3,0x10,0x42,0x43,0x3f,0xbc,0x86,0xaf,0xc6,0x2c,0xea,0x87,0x34,0xbd,0x0b,0xe3,0x1b,0x42,0xe3,0x38,0x8c,0x11,0x59,0x6a,0x0a,0x7f,0x0c,0xf8,0x4c,0xab,0x82,0x5b,0x54,0x57,0x31,0x47,0xe7,0x41,0xab,0x44,0x96,0x65,0xed,0x80,0x31,0x16,0x6d,0xed,0x1d,0x1b,0xa6,0x5e,0xb1,0x8f,0x6d,0xf6,0x91,0x15,0x63,0x6d,0x24,0x66,0x9d,0x34,0x9c,0x8d,0xc0,0x4a,0x31,0x59,0xc8,0x77,0x20,0x29,0x18,0xf6,0x67,0x19,0x42,0x9d,0x9a,0x04,0x79,0xe9,0x05,0x13,0x26,0x17,0xff,0x7b,0xc4,0xa2,0xa8,0x38,0x7f,0x89,0x34,0xcc,0x02,0xe6,0x64,0xc1,0xae,0xcc,0x78,0x37,0xd5,0x9a,0xba,0x99,0x86,0xe7,0xd8,0xd9,0xb1,0x07,0xfa,0xaf,0xe9,0x2f,0xd9,0xc3,0x04,0x4e,0x45,0xb5,0x96,0x3e,0xcd,0x49,0x9a,0x5b,0x5b,0xf4,0x04,0xba,0xb1,0x94,0xfc,0x3c,0x59,0x60,0x14,0x1b,0xa7,0x7a,0x4c,0xc6,0xb0,0x20,0xbc,0xb3,0x1a,0x2f,0xc8,0x3f,0xaf,0xae,0x8e,0xcf,0x4f,0x7a,0x57,0x57,0xe4,0x45,0x83,0xc5,0x9c,0xfb,0xc0,0x6e,0x1e,0x8b,0x59,0xd7,0x54,0xb3,0x45,0x43,0x04,0x10,0x80,0x8d,0x89,0x3a,0x0f,0x1c,0x1a,0xd6,0x63,0x00,0xf3,0x06,0x4c,0xea,0xbf,0xc0,0x2c,0x81,0xcb,0x5d,0x7f,0x96,0x9d,0x32,0x16,0x69,0xe2,0xd9,0x01,0x30,0x77,0x04,0x24,0x68,0xe9,0x66,0xe4,0xb8,0xac,0x89,0x41,0xdb,0x30,0xec,0xa6,0x5d,0x33,0x17,0xb7,0x85,0xe8,0x63,0x66,0xe2,0x59,0x75,0xe2,0xdb,0x70,0x12,0x27,0x75,0x33,0xdb,0x33,0xdb,0x83,0x8c,0xa6,0x74,0xb9,0xb9,0xa7,0x3c,0xe4,0xab,0x9b,0x0b,0xb9,0x9b,0xd2,0x03,0xc2,0xa3,0x68,0x25,0xc0,0x57,0x9e,0x51,0x1f,0xdc,0xa4,0x5f,0x2d,0xea,0x88,0x76,0x5d,0x60,0x32,0xd2,0xb3,0xb8,0xcf,0xb5,0x75,0x53,0x5e,0xf4,0x62,0x6d,0x96,0x3d,0xcc,0xaf,0x8d,0xcb,0x0f,0x95,0x24,0x75,0x6e,0xfb,0x00,0x47,0x2c,0xab,0xc1,0x95,0x87,0x41,0x89,0xb8,0x80,0x67,0x81,0x79,0x62,0x5d,0xd8,0x45,0xe7,0x08,0x44,0x31,0xb2,0xbf,0x03,0x3e,0xe6,0x6d,0x18,0x18,0xdc,0xe4,0x7d,0x04,0xf0,0x25,0xbf,0xe1,0xb7,0x2f,0x8b,0x10,0xa4,0x72,0xbb,0x3f,0xf9,0x74,0x25,0x10,0x01,0x95,0x64,0x35,0xe8,0xab,0x42,0x96,0x8e,0x77,0x4f,0x76,0x0f,0xae,0x9e,0x65,0x72,0x92,0xe9,0xb9,0x66,0x32,0xe9,0x73,0xbd,0xd6,0x20,0xe8,0xc4,0xca,0xad,0x82,0x7f,0x0e,0xac,0x34,0xaa,0x40,0xd5,0x8d,0x1c,0x94,0x34,0x78,0xd8,0xb9,0x61,0x33,0x09,0xbf,0x18,0xa1,0xa4,0x18,0x63,0x2e,0x05,0x97,0x56,0x3e,0x95,0x89,0xb0,0x09,0xd9,0xb9,0x07,0x1a,0xd4,0x06,0x4a,0x8f,0x9d,0x48,0x3b,0x64,0xbd,0x08,0x7a,0x47,0x81,0x2e,0x64,0x5d,0x63,0x70,0x5e,0x7c,0xdf,0x7c,0x29,0x40,0xe9,0xf0,0x99,0x57,0x6f,0x66,0x27,0xc3,0x48,0x42,0xdf,0x40,0x28,0x9d,0x6a,0xe5,0xfd,0xf4,0x87,0x87,0x66,0xc1,0xc6,0x49,0x84,0x5d,0x26,0xa7,0x25,0x96,0x80,0x5e,0x0e,0xc3,0x58,0x63,0x01,0x9f,0xd5,0xec,0x78,0xdb,0x65,0x8e,0x89,0xda,0x4c,0xc7,0x7b,0xf9,0x52,0x07,0xda,0x4a,0xe8,0x52,0x49,0x9f,0x65,0xe5,0xe9,0x17,0xde,0x25,0x92,0x73,0x1e,0x0b,0x6a,0x27,0xaf,0x68,0x2b,0x12,0xec,0xc3,0xc3,0x8a,0x4a,0x67,0x0c,0x26,0xf2,0xf3,0x14,0xf2,0x2d,0x8a,0xd9,0x89,0x22,0xe4,0x0f,0x0f,0x45,0x75,0x06,0xce,0x7a,0x2b,0x8b,0xa4,0xc0,0x9b,0x9c,0x9d,0x2a,0xfe,0x35,0x8c,0x23,0x8c,0xe0,0xd6,0x81,0x93,0x8e,0xcc,0xa1,0x1f,0x02,0x4d,0x66,0xe8,0xdc,0xd8,0xfc,0x1e,0x2c,0xa3,0xe4,0xed,0xc2,0xa9,0xcf,0x71,0x6a,0xe3,0xfb,0xa6,0xde,0x29,0x33,0x04,0x6d,0x98,0xb0,0x01,0x6c,0xbf,0x45,0x46,0x42,0x32,0x7e,0xd6,0x36,0x70,0x39,0xa8,0x40,0x9e,0xc1,0x61,0xc6,0x73,0x97,0x72,0x15,0xee,0xb8,0x39,0x01,0x85,0x76,0xe6,0x34,0xfc,0x63,0x42,0xe3,0xfb,0x53,0xea,0xd3,0x01,0x04,0xef,0xbb,0x3e,0xe4,0x1f,0x82,0x07,0x92,0xde,0xe8,0xfa,0x4a,0x6b,0x85,0x9c,0x00,0x49,0x9b,0x3a,0xf7,0x59,0xbc,0x26,0xea,0xdc,0x63,0x82,0x67,0x65,0xd3,0x4e,0x2e,0x65,0xec,0xe6,0x09,0x72,0x99,0xd2,0x7a,0x8e,0x0e,0x24,0xc8,0x52,0x45,0x71,0x07,0xf8,0x0a,0x2c,0xca,0x7d,0x01,0x9b,0xe2,0xa6,0x96,0xf4,0x17,0x9a,0x2a,0xe8,0xc6,0x3d,0xb8,0x03,0xcb,0x4d,0xcb,0xde,0xc1,0x18,0xa3,0x33,0x10,0xc3,0xc2,0x31,0x18,0x2e,0x96,0xd7,0xd2,0xc2,0xfc,0x1b,0x9c,0xf3,0x7c,0x48,0x18,0xfa,0x9c,0xc9,0x62,0xa9,0x34,0xea,0x86,0xc8,0xd2,0xc5,0x78,0x6e,0xc0,0x3b,0xe2,0xa4,0x26,0x0a,0x94,0x2a,0x19,0xb8,0x85,0x79,0x7e,0xb6,0xa7,0x21,0x7e,0x1c,0x1d,0x44,0xc0,0x28,0x99,0x09,0x09,0x55,0x6f,0xb0,0x2c,0x58,0xd6,0x68,0xf3,0xf3,0xcb,0x86,0x2e,0x5b,0x97,0xdb,0xc4,0xd2,0x16,0x5f,0x61,0x2c,0xae,0x1a,0x80,0x8a,0xf6,0x77,0x0a,0x60,0x26,0xf3,0xfc,0xc9,0xaf,0x1e,0x50,0x41,0xa8,0x24,0x84,0x52,0x19,0xe7,0x13,0x08,0x05,0x0b,0xe1,0x4a,0xc6,0xf1,0x7b,0x9d,0x67,0x88,0x96,0x02,0xb4,0x53,0x56,0x20,0xde,0x4d,0x05,0x21,0xbe,0x36,0x63,0x8a,0x2a,0x98,0xa0,0x59,0xe0,0xc8,0xb3,0xdd,0x28,0x24,0x48,0xc9,0xc3,0x03,0xbf,0x35,0xac,0x8e,0x5f,0xe4,0x08,0x5d,0x5a,0x5c,0x96,0xa7,0x29,0xeb,0x20,0x2e,0x42,0x59,0xfb,0x14,0x32,0x28,0x4c,0x4b,0xf0,0xee,0xb1,0x6d,0x4b,0x20,0xbc,0x6e,0xf2,0x97,0xc6,0x85,0x72,0xe7,0xe5,0x22,0xc2,0x37,0x2c,0x0a,0xc4,0xe2,0x44,0x82,0x2d,0x54,0x5c,0x0b,0x92,0xcf,0xcb,0x11,0x4a,0x1a,0x28,0xef,0x8f,0xb8,0xd9,0xed,0x56,0xad,0x26,0xaf,0xe0,0x2a,0x36,0x73,0x61,0x64,0xd0,0x5c,0x14,0x19,0x34,0xbf,0x52,0xe0,0x58,0xb1,0x57,0x0a,0x64,0x30,0x57,0x8f,0x85,0xfc,0xa0,0x8d,0x76,0x0d,0x03,0xa4,0xc8,0xb0,0x2d,0x55,0xea,0xb3,0x85,0x84,0xd3,0x15,0xc4,0xa7,0x42,0xe9,0x9c,0xc6,0xec,0x66,0x9b,0x6f,0x95,0x4f,0x41,0x59,0xd0,0xb0,0xea,0xa5,0x48,0xe3,0xcf,0x61,0x2a,0x25,0x11,0xff,0x81,0x18,0x81,0x39,0xdb,0xf3,0x77,0x5a,0x4d,0x2a,0x26,0x90,0xe9,0xe1,0x3f,0x1c,0x11,0x14,0xe4,0x1c,0x17,0xaa,0x06,0xd1,0x05,0x9c,0x0c,0x01,0x9b,0x71,0x3a,0xb8,0x02,0xf2,0x3f,0x3c,0xcc,0x96,0xc8,0xd8,0xce,0xe6,0x38,0xb9,0x5e,0xb1,0xac,0xdb,0xd0,0x73,0x09,0x16,0xe3,0x90,0x8f,0x30,0x04,0x0c,0xe4,0x0a,0x2a,0xe7,0x88,0xd9,0xa2,0xad,0xb1,0xba,0x42,0x0c,0x97,0x57,0x89,0x41,0x48,0x35,0xde,0xe0,0xeb,0x2f,0xda,0x06,0xc8,0x90,0xc6,0x9e,0xf4,0x27,0x9e,0xef,0x5e,0x89,0xb6,0x18,0x70,0xc6,0xc5,0x18,0x22,0xaf,0x4b,0xa0,0xfc,0x79,0x19,0x68,0x69,0xe9,0x4b,0x9b,0x68,0xf6,0xcb,0xca,0xf2,0x97,0xb6,0x6e,0x73,0xb5,0x46,0xe3,0xfa,0xae,0x2e,0x49,0x18,0x72,0x34,0xf0,0xb9,0x72,0x92,0x95,0x7c,0xfe,0xe2,0xe8,0x41,0xb9,0x3c,0x2a,0x45,0x0f,0x00,0x2a,0x87,0x90,0x7b,0x9f,0x79,0xd9,0x4b,0x81,0xc1,0x0b,0x34,0xe2,0x9d,0x62,0xef,0xdc,0xf1,0x73,0x9f,0x01,0xce,0xa0,0x36,0x7d,0xc9,0x9f,0x2e,0x95,0xc0,0xe4,0xb3,0x1f,0x4f,0x61,0xf2,0xa9,0x4b,0x24,0x31,0x05,0x12,0xcb,0xa4,0x31,0xf9,0xec,0x45,0x89,0xcc,0x54,0x72,0xee,0x68,0x92,0x2a,0xac,0x2b,0x3a,0xa1,0x15,0x06,0x06,0x60,0xe0,0xae,0x1c,0xdf,0x89,0xc7,0x39,0x1b,0x97,0x24,0x7c,0xb1,0xb2,0x20,0x3f,0xdf,0xf0,0xff,0x3c,0xfd,0x19,0x7d,0x0a,0xdf,0x0a,0xca,0xc8,0x6d,0xca,0x71,0xde,0xac,0xa3,0x49,0x73,0x55,0xcd,0x0d,0xa4,0x37,0x55,0x12,0xbf,0x92,0xfe,0x15,0x81,0x49,0x87,0x1b,0xa7,0x52,0x58,0xa2,0xaa,0xe2,0x2c,0x8c,0xcf,0xd6,0xc9,0x59,0x50,0x70,0xa4,0x1a,0xf8,0x8a,0x91,0x29,0xe3,0x95,0x1b,0xb0,0x96,0xae,0x57,0x0d,0xad,0x4a,0x14,0x26,0x79,0xac,0x47,0x8d,0x9d,0x97,0x7d,0xc2,0xaa,0x49,0x88,0xf5,0x13,0x1e,0x47,0x5b,0x47,0xfd,0xdf,0x21,0x4a,0x36,0x01,0xb3,0xd8,0x03,0x76,0x55,0x68,0xad,0x9b,0x38,0x59,0xd3,0x1c,0xa3,0xaf,0x5b,0x5d,0x07,0x2f,0xb4,0x59,0x7f,0x3f,0xdd,0x0b,0xc7,0x11,0x76,0xe9,0xf5,0x61,0x48,0x67,0xf2,0x3f,0xd3,0x3a,0xa5,0x8b,0x88,0xf9,0xe2,0x86,0xde,0xf3,0x30,0xec,0x12,0xa2,0x66,0x75,0x73,0x11,0x34,0xcf,0xa4,0x59,0xb0,0x60,0xca,0x95,0x8a,0x67,0x3e,0x2b,0xde,0x17,0xd1,0xdb,0x13,0x24,0x56,0x93,0x0d,0x1e,0xd5,0xb1,0xe0,0x32,0xab,0x6d,0xfa,0x9a,0xa5,0xa6,0xf4,0xa8,0x2c,0xbc,0xcd,0x18,0x31,0xcb,0xed,0x31,0x4b,0x9e,0x37,0x0e,0xef,0x2c,0xb9,0x3a,0x81,0xb0,0xe2,0x84,0x5d,0xc7,0x40,0x9c,0xe2,0xb7,0x2c,0x78,0x26,0x46,0xf7,0xe0,0xbb,0xd6,0xe4,0xe3,0x1b,0xd5,0xf1,0x96,0xde,0x61,0xf3,0x4b,0xf7,0x59,0xb0,0x6d,0xa7,0xa0,0xe7,0xbc,0xa6,0x1b,0xd9,0x16,0xa5,0x44,0xc1,0xf8,0x07,0x30,0xe5,0x84,0xc1,0xa2,0x3d,0xea,0xa6,0xbd,0xc3,0x2f,0x1e,0xdb,0x32,0x42,0x36,0x64,0x66,0x63,0x95,0xf9,0x64,0xcc,0x90,0x55,0x8c,0xe4,0xaf,0x32,0x48,0x09,0xd5,0xca,0x75,0x16,0xf6,0x4a,0xc3,0x94,0x1f,0xb0,0xd4,0xf2,0x22,0x0a,0x30,0x8f,0x65,0x73,0xe0,0x86,0x2b,0x21,0x63,0xc6,0x37,0x9e,0xed,0x6f,0x60,0xdd,0x17,0xb6,0xa1,0x20,0x82,0x9b,0x9b,0x09,0x83,0xa9,0xe1,0x75,0xa9,0x5e,0xd7,0xfa,0xd7,0x9c,0x09,0xe9,0xaa,0x3d,0xaa,0x8c,0xa1,0xd8,0xdd,0x5a,0x58,0x95,0x52,0x47,0x2c,0x10,0x3a,0x7f,0x2a,0x7b,0x40,0x44,0x05,0xbb,0xfe,0xa6,0xba,0x98,0x9d,0x93,0x0f,0xef,0x75,0xe4,0xf5,0xb3,0xec,0xf2,0xc1,0x69,0x56,0x31,0x17,0x3f,0x61,0xb7,0x09,0xaa,0x0c,0x7e,0xe6,0x37,0x31,0xf8,0xc9,0xc4,0x9f,0x3e,0x00,0xed,0x76,0x45,0xda,0x84,0x7d,0xbb,0x98,0x34,0xcd,0xc6,0xb6,0xc7,0xfc,0xea,0x85,0xd3,0x84,0x38,0x84,0x75,0xf8,0x8a,0x33,0x08,0xc9,0x9f,0x53,0xf9,0x16,0x7d,0xba,0xf2,0xe0,0x6c,0x11,0x59,0x7d,0x96,0xe5,0xfb,0x4f,0x57,0x77,0xfe,0x1d,0xfc,0x3b,0xa8,0xb9,0x9d,0x71,0x86,0x60,0x65,0x04,0x55,0x4d,0xa5,0x2e,0xee,0xc4,0xb1,0x73,0xff,0x7a,0x32,0x1c,0xd2,0x58,0xc6,0xc9,0x08,0x4c,0x19,0x06,0x7d,0x54,0xae,0x17,0x39,0x0a,0x18,0x84,0x4a,0xde,0xd8,0x86,0xcd,0x1a,0x41,0x79,0xf3,0xe8,0xea,0x4c,0xdf,0x2b,0x91,0x2f,0x51,0xae,0x76,0xbf,0xf9,0xc7,0x3f,0x48,0xe5,0x3f,0xcb,0x2d,0x25,0xe5,0x57,0x71,0xd9,0x2f,0x3e,0x00,0xbc,0x02,0x19,0x95,0x06,0xa6,0x69,0xf2,0xbe,0xd3,0xcf,0xdf,0xae,0x78,0xb1,0x92,0xe0,0x4f,0x3a,0x88,0x01,0xf1,0xea,0x24,0xe9,0x87,0x3e,0x9c,0x86,0x5d,0xd2,0x73,0x82,0xae,0xb3,0xb7,0x74,0x20,0x3f,0x59,0xed,0x36,0x9f,0xd7,0xee,0xcd,0x07,0x8b,0xeb,0xfa,0x4f,0xa3,0x98,0x55,0x27,0x7e,0x3b,0xf8,0xf0,0x36,0x4d,0xa3,0x13,0x7c,0xb9,0x27,0x49,0x3b,0x30,0x6c,0x0a,0x1e,0xcd,0xea,0x96,0xdc,0x04,0xc2,0x7e,0xd9,0x6f,0xc6,0x6b,0x29,0xe8,0x22,0x26,0xcc,0xd0,0x89,0x1c,0x9d,0xc6,0xf8,0xf2,0x29,0x0e,0xc3,0x57,0x51,0x64,0x60,0x07,0xc5,0x25,0x00,0x9c,0xba,0x0d,0x8c,0xcd,0x53,0xc7,0x7f,0x81,0xad,0x8a,0x86,0x84,0xbc,0xe8,0xf2,0xbf,0x72,0x52,0x1b,0xbb,0xd2,0xe4,0x2a,0x30,0xf9,0xc5,0x97,0x92,0xa9,0xac,0xa0,0xf2,0xd2,0x7e,0x8e,0xe9,0x1b,0xa7,0x01,0x87,0x28,0x34,0xd3,0x52,0xb5,0x54,0xaa,0x29,0xe4,0x5a,0xe8,0x33,0xf1,0xba,0x15,0x29,0x33,0x4b,0x12,0x04,0x60,0x1b,0x9a,0x9c,0xc0,0xf3,0xa2,0xae,0x05,0xd9,0xe5,0xda,0x5a,0x31,0xb0,0xbd,0xd9,0x6c,0xe6,0xdd,0x26,0xd8,0x61,0x82,0x40,0xb5,0x6a,0x94,0xd8,0xe3,0xa9,0x96,0xd4,0xb1,0xfc,0x0a,0xa6,0x80,0x33,0x2d,0x7d,0x13,0x29,0x36,0x5a,0xb4,0x7a,0xe4,0x58,0xee,0x26,0xb0,0x5b,0xbc,0x67,0x29,0xdb,0x25,0xee,0x84,0x5d,0xa2,0x72,0xf2,0x2c,0xda,0xc0,0xe9,0x83,0xcb,0x5b,0x6a,0x03,0x71,0x28,0x9e,0xa5,0xfb,0x94,0x43,0x05,0x36,0x20,0xdc,0x10,0xfc,0x81,0x26,0x0a,0x1c,0x90,0x1d,0x83,0x54,0xd8,0x7c,0x43,0xc8,0x1d,0x85,0x5c,0xbe,0x65,0x75,0x0f,0xbc,0xba,0x2c,0xaa,0x1e,0x30,0x59,0xad,0x7a,0x84,0x83,0x94,0x82,0xca,0xa4,0x60,0xd1,0xc7,0xf9,0x7a,0x10,0x38,0xc5,0x90,0x48,0x7b,0x5c,0xe2,0xbb,0xda,0xba,0x20,0x2d,0x0a,0x39,0x67,0x21,0x01,0xec,0x90,0x8f,0xf0,0x35,0x90,0x8f,0x27,0x93,0xc1,0x00,0xc4,0x6c,0x08,0x71,0xf9,0xfd,0x0a,0x11,0x86,0xce,0x4b,0x84,0x99,0x03,0xba,0xa1,0xf6,0xf7,0xe3,0xee,0xff,0x37,0xd7,0x7c,0x41,0x73,0x4d,0x5e,0x86,0x50,0x3b,0x6b,0x67,0x1d,0x59,0xaf,0xbe,0x38,0x53,0x04,0xca,0xf9,0xcb,0x8b,0x9a,0xa7,0x67,0x7f,0xc1,0x95,0xab,0x1d,0x0f,0xaf,0xf0,0xd0,0x81,0x6d,0xa0,0x4b,0x0f,0xa8,0xdf,0xf6,0x58,0x8f,0x56,0x25,0xb8,0xa8,0x79,0xa9,0x52,0x69,0xa3,0xc6,0x62,0x22,0x5e,0x40,0xbd,0x09,0x63,0x90,0x3d,0xb0,0x2f,0xf6,0x09,0xc5,0xaa,0x03,0x0a,0xe4,0x79,0x84,0xb7,0x4e,0xc8,0x9f,0x4b,0x83,0xed,0x44,0xdd,0xbd,0xd0,0xc5,0xe9,0xec,0x75,0x85,0xf2,0x9f,0xcb,0xc7,0x6f,0xb0,0x05,0x26,0xd8,0x74,0x43,0xb6,0xf0,0x75,0x50,0xde,0x56,0x42,0xe4,0xcf,0x1a,0x78,0x78,0x37,0xca,0x71,0x34,0xbf,0xf9,0xe6,0x18,0x2d,0x2c,0x39,0x7a,0xcf,0x1b,0x96,0xc0,0x21,0x1a,0x78,0xf1,0x0d,0xc1,0x43,0x88,0x3d,0x3b,0xec,0x12,0x3c,0x62,0xbd,0x7c,0x89,0xf9,0xb7,0x5e,0x7d,0x23,0x1f,0x98,0xf4,0x5d,0xa5,0x74,0x1c,0xd9,0xd3,0x27,0x17,0x3c,0xd9,0x62,0x24,0x07,0xae,0xc7,0x5e,0x9a,0x98,0x95,0x98,0x94,0xf0,0xe8,0xef,0x39,0x94,0xa8,0x6c,0xb2,0x13,0x19,0xcb,0x1f,0x47,0x2c,0xc3,0x03,0x0d,0xf8,0x4f,0x6d,0x25,0xc5,0x61,0xca,0x97,0x6a,0x5b,0xec,0x02,0x2d,0xfb,0x4b,0x4f,0x55,0xa7,0x32,0x8b,0x62,0x67,0xa5,0xa6,0x2b,0x1d,0x35,0x7b,0xe3,0xa1,0xc6,0x44,0xf0,0xd7,0x93,0xc1,0x44,0x78,0x2f,0x5b,0xd3,0xc6,0x16,0x1a,0x0b,0x45,0xc9,0xf0,0x5e,0x0f,0xe3,0x55,0x2e,0xd7,0xbc,0x87,0xa3,0xf2,0x98,0xe4,0x2f,0x62,0x61,0xbb,0x4f,0x2c,0xf5,0x03,0xaf,0xc5,0x8b,0x95,0xa0,0x11,0x77,0xa8,0x07,0x2e,0xbe,0x07,0xa7,0x81,0x0b,0xc5,0x1f,0x58,0x01,0x55,0x28,0xb4,0x85,0xbd,0xdf,0x48,0x26,0x81,0xd0,0xb7,0x31,0x24,0xcd,0x26,0xb6,0x60,0x0a,0xf5,0x7b,0x2d,0x5e,0x90,0x30,0xfe,0x4e,0xba,0xaf,0xb7,0x98,0xa2,0xfc,0xbd,0x6a,0xcb,0x63,0x9b,0x7a,0x95,0xad,0x2d,0x4a,0x0f,0x46,0x74,0x70,0x83,0xf1,0x0a,0x08,0xf7,0xdc,0x1a,0x79,0xc9,0x56,0x02,0x5b,0x2d,0x7e,0xdb,0x94,0xe5,0x6f,0x84,0xd5,0xd6,0xcd,0x3b,0xd5,0x55,0xac,0x66,0x32,0xc0,0x6f,0x3b,0xc5,0x47,0x78,0xd0,0x86,0xfc,0x71,0x71,0xe9,0xbc,0x16,0x4b,0x5a,0x83,0xd7,0xb4,0x3a,0x62,0xa1,0x2f,0xfd,0x6b,0x59,0x40,0x53,0x6e,0x37,0x0d,0xfe,0xc3,0x55,0x9e,0x81,0xe7,0x84,0x33,0xb2,0x6e,0xe3,0x3a,0x6b,0x51,0x57,0xdc,0x79,0xdf,0xfb,0xd7,0xde,0xd1,0x7e,0xef,0x0a,0x54,0x6f,0x2a,0xab,0x10,0x95,0xd2,0x4c,0xe5,0xa8,0x35,0x7d,0x24,0xbc,0xbf,0xb3,0xa8,0x52,0x59,0x99,0x04,0xdb,0x6c,0x97,0x56,0x37,0x2f,0x0d,0xf9,0xa4,0x55,0x7e,0xd2,0x2a,0x9e,0x6c,0x94,0x9f,0x6c,0x14,0x4f,0x36,0xcb,0x4f,0x36,0x2f,0xa7,0x9d,0xbf,0xdc,0xc6,0x17,0xa7,0x64,0xf6,0xaf,0x64,0xdc,0xbf,0x90,0xea,0x8f,0xde,0xb0,0x4d,0x97,0xbf,0x50,0x57,0xee,0xcf,0xc5,0x65,0xc9,0x67,0x5d,0xcc,0x89,0xbb,0xe3,0x47,0x6f,0x1d,0x99,0xcb,0x72,0x9f,0x7a,0xed,0xf8,0x97,0xea,0x0b,0x0d,0x72,0x9f,0xcc,0xc2,0x6d,0xf1,0x42,0xb9,0x65,0xe7,0xbf,0xae,0x41,0x84,0x9d,0x1f,0xa3,0x05,0x90,0xb9,0xe8,0xca,0x37,0xdf,0xd8,0x9d,0x3a,0xc6,0xd6,0x58,0x80,0x1d,0x01,0xf3,0x25,0x5e,0x0e,0x54,0x3c,0x54,0x9b,0xac,0xe3,0x6f,0x53,0x34,0xba,0x76,0x7b,0xe1,0xac,0x67,0x59,0x05,0xf0,0x94,0xaf,0xea,0xcc,0xf0,0x40,0x80,0x29,0x79,0x23,0x79,0xbb,0xb9,0xe8,0xd2,0x14,0xce,0x1b,0x29,0x07,0x7d,0x6a,0x97,0xa6,0xf3,0xc9,0x4b,0x1e,0xbf,0x3d,0xfd,0x8a,0xbc,0xc4,0xd6,0xcf,0x67,0x19,0xa2,0x31,0xbd,0xe2,0x0d,0xcd,0x4f,0x8f,0x1c,0xd9,0x3a,0x22,0xa0,0xa8,0xdd,0x8c,0x75,0x02,0x5c,0x04,0x61,0xac,0x14,0x36,0x4e,0x2d,0x05,0xb4,0x78,0x2b,0xc6,0xe6,0x21,0x46,0x11,0x86,0xa0,0xea,0xc6,0x26,0x29,0x0d,0x3b,0xd7,0x0e,0x44,0xea,0x6c,0xfb,0x30,0x22,0x5e,0x0a,0xb1,0xfa,0x19,0x04,0x24,0x06,0x7b,0x29,0x9f,0x05,0x36,0xce,0x20,0x9d,0x38,0xbe,0xe8,0xb7,0x47,0x5c,0x8b,0x96,0x7b,0xc8,0x48,0x03,0xf0,0x4f,0xe8,0x91,0xd4,0x2e,0x50,0x5e,0x97,0x47,0xa4,0x2c,0xd6,0x56,0xff,0xf0,0xc0,0x3f,0xe3,0x3b,0x38,0x8b,0xdb,0x47,0x95,0x34,0x5f,0x3d,0x9f,0xdc,0x51,0xed,0xbc,0x00,0x90,0xbc,0xfc,0x9f,0x1c,0x3a,0x87,0x9a,0x9c,0x81,0x5d,0x52,0xe2,0x23,0xa4,0x92,0x75,0xdb,0xbd,0x0b,0xc0,0x85,0x78,0xca,0x29,0xd8,0x8f,0x9d,0x51,0x17,0xe8,0xc2,0xab,0x9b,0xec,0x3b,0x71,0x08,0xff,0x1d,0x44,0x30,0x24,0xbc,0xb8,0x6d,0xfe,0xdd,0x41,0x3c,0xca,0x18,0x36,0x00,0x3c,0x31,0x82,0xbf,0x66,0xef,0x37,0x16,0x24,0x66,0x17,0xf8,0x8f,0x4a,0x14,0xac,0xd8,0x9f,0x1f,0xdd,0x20,0x77,0x41,0x4a,0x91,0xa5,0xbc,0xe1,0x99,0xbb,0x94,0x1b,0x6a,0x89,0x75,0x26,0x6d,0xe4,0x2c,0x69,0xb4,0x36,0x74,0xe3,0x26,0xcd,0x1f,0xa5,0xe5,0x47,0x2f,0x5a,0xe6,0x86,0x71,0xa3,0x14,0xc7,0xb9,0x3b,0xe2,0xfd,0x91,0xef,0x7b,0x36,0xae,0x9d,0xf3,0x10,0xaf,0xe4,0xc4,0xca,0x1d,0xed,0xa6,0x74,0x13,0x7d,0xa3,0xde,0xe0,0x97,0xc2,0x03,0x31,0x4f,0xd7,0xdb,0x95,0x70,0x6b,0x2f,0x9c,0xf8,0x2e,0xc1,0x57,0x4d,0xf0,0xad,0x4f,0x32,0x0f,0x0d,0xd8,0xa9,0x74,0xf7,0xa6,0x94,0x28,0x6f,0x2a,0xfd,0x94,0x62,0xe2,0xd3,0xb6,0x3a,0xc3,0xad,0x38,0x8e,0x0f,0x0f,0x72,0xcf,0x59,0x06,0xf3,0x1f,0xfb,0x50,0x19,0x3b,0x90,0xde,0x01,0xb3,0x0b,0xbc,0x78,0xe1,0x4b,0xed,0xf7,0x3d,0x0d,0x7f,0x58,0x2d,0x69,0x0c,0x53,0xbd,0x8d,0xe3,0x0a,0x65,0x20,0x87,0xb1,0xf1,0x6d,0x30,0x18,0x4e,0xe5,0xf4,0x33,0x2d,0xe5,0x29,0x12,0x19,0xcb,0x35,0xe5,0x33,0xca,0x45,0x90,0xc3,0x88,0xfb,0x00,0xa5,0xc9,0x07,0xd4,0xa4,0xbd,0x0c,0xb6,0xb2,0x4a,0x59,0x21,0x85,0xa4,0x37,0x81,0xc8,0x57,0x12,0x44,0x89,0xe2,0x00,0x7a,0xde,0x39,0x26,0x25,0x90,0x75,0xdf,0x0b,0x01,0x54,0xa5,0x4c,0x0a,0x59,0x8d,0xfc,0xb0,0x7e,0x5b,0x2e,0x3e,0x9f,0xcd,0xd4,0x9a,0x94,0x91,0xfd,0x7e,0xcb,0x5c,0x96,0xc0,0x41,0x54,0xca,0x7a,0x81,0x8e,0x6a,0x38,0x43,0xda,0x79,0x54,0xd5,0xdb,0xda,0x32,0x52,0x94,0x1f,0x6d,0x19,0xfc,0xe6,0x30,0x41,0xc0,0x28,0xe8,0x8e,0xdb,0x4f,0x17,0xf9,0x6f,0xd5,0x86,0x2f,0xe7,0xbc,0x67,0x7e,0x62,0xe5,0x51,0xf7,0xed,0x87,0xd7,0xf6,0x13,0x5d,0xaa,0xdc,0x84,0xc0,0x5a,0x76,0x3b,0xf4,0x59,0x2d,0x68,0xac,0xbe,0xe6,0x87,0xfd,0xaa,0x31,0xc4,0x31,0x4d,0x37,0x16,0xb6,0xfb,0x43,0x94,0x36,0xc6,0x9f,0x5c,0x73,0xf1,0x9b,0xd2,0x18,0xfb,0x48,0x6f,0xfe,0xb3,0xec,0xc2,0xfe,0x65,0xf7,0x10,0xeb,0xc8,0xbd,0xd7,0xd8,0x44,0xbe,0x7b,0x02,0x7f,0x77,0x8f,0x4f,0xd8,0xe7,0x7f,0x61,0xcb,0xf8,0xf9,0x21,0xfb,0xfb,0x01,0xc7,0xcf,0x7f,0x86,0xbf,0xa7,0xbd,0x63,0xf8,0x7b,0xb4,0x87,0xc5,0xf0,0xc3,0xa3,0x8f,0x58,0xed,0xeb,0xed,0xd9,0x97,0x17,0xe5,0x57,0x06,0x2e,0xa7,0x4b,0xbd,0x60,0x30,0xb7,0x8d,0xe3,0x09,0xaf,0x02,0xd8,0xc6,0x24,0xf6,0xad,0xf3,0x93,0x0f,0xe2,0x7e,0x99,0xb7,0x11,0xc0,0x77,0x0d,0x29,0x07,0x02,0x3a,0xf7,0x02,0xda,0xc1,0x40,0xdc,0x1c,0xc5,0x74,0x68,0x01,0x08,0xc3,0x31,0x25,0x23,0xf1,0xe6,0x99,0x55,0xd4,0x00,0xe1,0x12,0x69,0xa7,0xec,0xd2,0xb2,0x78,0x7f,0x1d,0xdd,0x68,0xe9,0xc2,0xd8,0x81,0xfd,0xf2,0xdb,0xd5,0xf2,0x34,0x9e,0xd3,0xe4,0xd3,0x10,0xe1,0x98,0xde,0x86,0x37,0x0a,0xc2,0x80,0x45,0x1e,0xbf,0xa2,0x78,0xd7,0x89,0x1d,0x4f,0xd7,0x25,0xa6,0x18,0xc6,0xaa,0x52,0xc7,0x96,0xcd,0x79,0x85,0x04,0x89,0x76,0x1c,0xfa,0x3e,0x92,0x55,0x54,0x9b,0x65,0xd0,0x5c,0xfe,0x61,0x19,0xb5,0x5e,0xaf,0xf6,0x24,0x6e,0x96,0xdf,0x2b,0x85,0x48,0xae,0x00,0xa7,0x02,0x98,0x7d,0x87,0x4f,0x7d,0x2a,0xdf,0xe2,0x5b,0xf0,0x23,0x00,0xb7,0x5e,0xe2,0xf5,0x3d,0xdf,0x4b,0xef,0xb9,0x61,0x2a,0xdd,0x97,0xe7,0xeb,0x46,0x9e,0xeb,0xd2,0x60,0xa7,0x84,0x47,0x5b,0xfb,0xcc,0x73,0x61,0xb4,0x23,0xde,0xb1,0x0f,0xb9,0x0c,0x28,0x5b,0x7e,0x41,0x77,0x67,0x89,0xe8,0xd3,0xce,0x76,0x43,0xfc,0xd4,0xcf,0x76,0x83,0xff,0x98,0x6c,0x83,0xfd,0xdf,0x0c,0xfc,0x07,0x7d,0x53,0xca,0x86,0x76,0x60,0x00,0x00}; +const char html_content[] = {0x1f,0x8b,0x08,0x00,0xdb,0x47,0x5c,0x69,0x02,0xff,0xed,0x3d,0x6b,0x57,0xdb,0x48,0xb2,0x9f,0x77,0x7e,0x45,0xc3,0x24,0x8c,0x94,0x08,0xd9,0x06,0x32,0x0f,0x1b,0x99,0x25,0xe0,0x4c,0x32,0x09,0x8f,0xc3,0x23,0x33,0x7b,0x59,0x0e,0x92,0xad,0x36,0xd6,0x20,0x4b,0x1a,0x49,0x86,0x30,0xc2,0xff,0x7d,0xab,0xfa,0x21,0xb5,0x64,0xd9,0x98,0x64,0x33,0x73,0xee,0xb9,0x77,0xf7,0x2c,0xb1,0x5b,0xdd,0xd5,0xd5,0xf5,0xae,0xea,0x92,0x77,0x7b,0xc5,0x0d,0x07,0xe9,0x7d,0x44,0xc9,0x28,0x1d,0xfb,0xdd,0xed,0xd4,0x4b,0x7d,0xda,0xdd,0x0b,0x83,0x34,0x0e,0x7d,0x72,0xec,0x04,0xd4,0xdf,0x6e,0xf0,0xc1,0xed,0x31,0x4d,0x1d,0x32,0x80,0x47,0x34,0x48,0xad,0xd5,0x3b,0xcf,0x4d,0x47,0x96,0x4b,0x6f,0xbd,0x01,0x5d,0x67,0x5f,0x0c,0x2f,0xf0,0x52,0xcf,0xf1,0xd7,0x93,0x81,0xe3,0x53,0xab,0x65,0x36,0x57,0x49,0xe0,0x8c,0xa9,0x75,0xeb,0xd1,0xbb,0x28,0x8c,0xd3,0xee,0x76,0x92,0xde,0x03,0xa4,0x6f,0xef,0x62,0x27,0x8a,0x68,0x9c,0xa5,0xf4,0x53,0xba,0xee,0xf8,0xde,0x75,0xd0,0x1e,0x00,0x50,0x1a,0x77,0xfa,0xe1,0xa7,0xf5,0xc4,0xfb,0xd3,0x0b,0xae,0xdb,0xfd,0x30,0x76,0x69,0xbc,0x0e,0x23,0xd3,0x6f,0xc5,0xae,0xd9,0xd8,0xf9,0xc4,0xf7,0x6a,0xbf,0x6a,0x36,0xa3,0x4f,0x9d,0xb1,0x13,0x5f,0x7b,0x41,0xdb,0x99,0xa4,0x61,0x27,0x72,0x5c,0x17,0xd7,0x35,0x49,0x0b,0x1e,0x4d,0xfb,0xa1,0x7b,0x5f,0xb3,0x81,0x58,0xd1,0x2c,0xa6,0x4f,0x5f,0x64,0x83,0xd0,0x0f,0xe3,0xf6,0xb7,0x1b,0x43,0xfc,0x6f,0xa7,0xef,0x0c,0x6e,0xae,0xe3,0x70,0x12,0xb8,0xeb,0xe2,0xc1,0x70,0x38,0xec,0x0c,0x01,0x85,0xf5,0xa1,0x33,0xf6,0xfc,0xfb,0xf6,0x61,0x98,0x86,0xe4,0xd4,0x09,0x12,0xe3,0x23,0x8d,0x5d,0x27,0x70,0x8c,0x04,0xbe,0xac,0x27,0x34,0xf6,0xc4,0x44,0x38,0x03,0x6d,0xb7,0xcc,0x8d,0x98,0x8e,0xa7,0x5e,0x10,0x4d,0x52,0xa3,0x3f,0x49,0xd3,0x30,0x50,0x31,0x8a,0xbd,0xeb,0x51,0x5a,0x7f,0xe2,0x1a,0x1c,0xe8,0x90,0xba,0xf4,0xa7,0x0e,0x9f,0xd3,0x6e,0x45,0x9f,0x48,0x12,0xfa,0x9e,0x4b,0xbe,0xed,0x3b,0x3f,0x7d,0xff,0xaa,0x2f,0x1e,0xac,0xc7,0x8e,0xeb,0x4d,0x92,0xf6,0x2b,0x20,0x0e,0x27,0x54,0xab,0xd9,0x7c,0xce,0x51,0xb8,0x40,0x3e,0x5b,0x88,0xc0,0xa5,0xa1,0x0c,0x04,0x93,0x71,0x9f,0xc6,0x97,0x99,0x7a,0xc0,0x71,0x18,0x84,0x49,0xe4,0x0c,0xa8,0x58,0x19,0x53,0xc7,0x0d,0x03,0xff,0xfe,0x32,0xab,0x41,0x6c,0x0b,0xff,0x3b,0x9d,0x3d,0x1f,0xa7,0xf8,0xd4,0x1c,0x8c,0x9c,0xe0,0x9a,0xba,0x06,0x70,0x71,0x3c,0xf6,0xd2,0xab,0x7e,0x1a,0x64,0x05,0x61,0x57,0xbc,0x31,0xca,0x86,0x13,0xa4,0x35,0x87,0xde,0x70,0xb6,0x7e,0xda,0x74,0x8b,0x29,0x20,0x09,0x4e,0x30,0xa0,0xfe,0x53,0x60,0xfc,0xb0,0xb1,0x59,0x02,0x90,0x23,0x61,0x94,0x80,0x4d,0xe2,0x04,0x66,0x47,0xa1,0x27,0xe4,0x90,0x91,0x39,0x08,0x03,0x2a,0xe8,0xb8,0xf5,0xea,0xb9,0x10,0x9e,0xf5,0x34,0x8c,0xda,0x28,0x63,0xb9,0x0c,0xb1,0x2f,0x8c,0x7e,0x77,0x14,0x99,0xda,0xfe,0xa1,0xd9,0x54,0x77,0xba,0x70,0xbd,0xc4,0xe9,0xfb,0xd4,0xbd,0x54,0xf7,0x2c,0x46,0xe5,0x51,0x7e,0xfc,0xf1,0xc7,0x8e,0x40,0x24,0x08,0x91,0x8c,0x7e,0x78,0x47,0xdd,0x9a,0x33,0x6d,0x6d,0x6d,0x29,0x67,0x4a,0x11,0x4a,0x26,0x04,0x00,0x66,0xf8,0x4e,0x94,0xd0,0xb6,0xfc,0xd0,0x51,0x58,0xe2,0xd3,0x61,0xaa,0xca,0x45,0xea,0x66,0xb9,0xd0,0x01,0xfb,0xc6,0xaa,0x5c,0x09,0x81,0x93,0x67,0xfc,0x11,0x74,0x2a,0x99,0x8c,0x81,0x04,0x25,0xb5,0x62,0x10,0x15,0x35,0xa9,0xa5,0x7f,0x8d,0x70,0x4a,0xb0,0xe6,0x26,0xa8,0xc8,0x0c,0xed,0xcc,0xc1,0x58,0x62,0xc6,0x79,0xa0,0x2a,0xd5,0x2b,0x54,0xaa,0x6f,0xc7,0xc9,0x75,0x8d,0xb0,0x8d,0x5a,0x59,0x31,0x75,0x43,0x4c,0x8d,0xc2,0x68,0x12,0xad,0x87,0xb7,0x34,0xf6,0x9d,0xfb,0xec,0xcf,0x75,0x2f,0x70,0xe9,0x27,0x24,0x40,0xb3,0x06,0xdd,0x26,0xfb,0x4f,0x7f,0xb3,0xf3,0xfb,0x24,0x49,0xbd,0xe1,0xfd,0xba,0xb0,0x3d,0xd2,0x80,0xb0,0xed,0xd6,0xbd,0x94,0x8e,0x13,0x39,0x54,0x10,0xb4,0x33,0xe2,0x67,0x60,0x9f,0x81,0xbd,0x11,0x6c,0xc8,0x0f,0x10,0x85,0x09,0x18,0xc7,0x30,0x68,0x0f,0xbd,0x4f,0xc0,0x52,0x14,0xa1,0x66,0x07,0x89,0x07,0x06,0x48,0x20,0x28,0x8d,0x9c,0x42,0xcd,0x1a,0x03,0x39,0x47,0x47,0x2a,0x24,0x6e,0x71,0xeb,0x28,0xad,0xe5,0x56,0x53,0x15,0xd7,0x4d,0xfc,0xc2,0xec,0xce,0x08,0xd4,0xfa,0x0e,0x2c,0xe6,0x16,0xb0,0xfd,0x7b,0xf8,0x9f,0x38,0xfd,0x96,0x5b,0xc1,0x89,0x8c,0x36,0xb2,0x85,0x4c,0x16,0x48,0x28,0x0a,0x52,0x3d,0x16,0x89,0x96,0x82,0xa0,0x32,0xba,0x85,0xdc,0xfb,0xe7,0x98,0xba,0x9e,0x43,0x92,0x41,0x4c,0x69,0x40,0x9c,0xc0,0x25,0x1a,0x3b,0xd3,0xb6,0xb5,0xf9,0x0a,0xce,0xa1,0x67,0x35,0xce,0x81,0x91,0xbf,0xf0,0x07,0x20,0x6f,0x5c,0x47,0x48,0x1a,0x13,0x26,0xf2,0x75,0x26,0x57,0x59,0x2a,0x39,0xd7,0xf7,0xc3,0xc1,0x4d,0xbe,0x34,0x13,0xa7,0x93,0xaa,0x82,0x54,0x2c,0xcf,0x94,0x47,0xe6,0xb6,0x30,0xc9,0xe6,0x9f,0xd2,0xa7,0x80,0x2b,0x78,0xc1,0x36,0xfe,0x99,0x27,0x6a,0xd7,0x4e,0x24,0x19,0x99,0x93,0x75,0x43,0xdd,0x15,0xc1,0x54,0xf6,0x24,0xc2,0x0e,0x97,0x5d,0x5a,0xd5,0xba,0xfd,0x17,0xbc,0xcb,0x18,0x10,0xca,0x49,0x56,0xb1,0x85,0x64,0xa3,0xd6,0x20,0xd6,0x21,0xda,0x1e,0xa1,0x5e,0x1a,0xb5,0xcf,0xcc,0x28,0xf6,0x98,0xbd,0x59,0x28,0x38,0x1c,0xc3,0xe9,0x42,0x08,0x7c,0x97,0x1a,0x7e,0xfc,0xe8,0xfc,0xd0,0xdc,0x7a,0x25,0x17,0x33,0x67,0xc7,0xb8,0xe0,0x78,0x41,0xed,0xfc,0x92,0x9c,0x33,0x66,0x90,0x66,0x69,0x75,0x85,0xf0,0xcb,0x28,0xf0,0x97,0x78,0xf6,0x1a,0x17,0xa4,0x2a,0x8f,0x99,0xfc,0x81,0xce,0x8d,0xcf,0xdf,0x7c,0xf5,0xbc,0x6c,0x02,0xaa,0x0c,0xda,0x6e,0xf0,0xf8,0x6c,0xbb,0x31,0x02,0x7f,0xdf,0xdd,0xc6,0x10,0xaa,0xbb,0xed,0x7a,0xb7,0xc4,0x73,0x2d,0x11,0xb3,0xe5,0xdf,0x85,0xac,0x76,0xb7,0x47,0xad,0xee,0x9e,0x3f,0x19,0xdc,0xec,0x81,0xbb,0x03,0xf5,0x84,0xc5,0x2d,0x88,0x23,0x51,0x67,0xe0,0x1f,0x98,0x9f,0x22,0x24,0xc6,0x0d,0x12,0x06,0x03,0xdf,0x1b,0xdc,0x58,0xab,0x09,0x0d,0x5c,0x31,0x5f,0xfb,0x2e,0x49,0x9d,0x38,0xfd,0x4e,0x5f,0x25,0x03,0xdf,0x49,0x12,0x0b,0x4c,0x7f,0xf7,0xf4,0x6c,0xf7,0xe4,0x6c,0xbb,0xc1,0x97,0x01,0x3e,0x08,0x63,0x09,0x38,0x61,0x54,0x02,0x43,0xd8,0x71,0xac,0xc7,0xbc,0x13,0xec,0x76,0x74,0xfc,0xd4,0xcd,0x00,0x44,0x58,0xc6,0xf9,0xfc,0x70,0xff,0xa8,0x02,0xa5,0x81,0xe7,0x6f,0x48,0x5a,0xa8,0x24,0x81,0x38,0xda,0x87,0x00,0x2b,0xb0,0x36,0xbb,0xdb,0x4c,0x6e,0x90,0xa4,0xe0,0xd2,0x88,0x8c,0xb4,0x54,0x08,0x0b,0x96,0x9c,0xff,0x76,0x75,0xf6,0xee,0xa0,0x87,0x48,0xb2,0x28,0xcb,0x02,0xc9,0xbc,0xd9,0xe3,0x01,0x97,0x96,0x8e,0xbc,0x44,0x07,0x22,0xd0,0xc8,0x6a,0x11,0x16,0xeb,0xb9,0x4e,0x4a,0x53,0x6f,0x4c,0xd7,0xc1,0x5c,0x39,0x7e,0x97,0xc8,0x33,0x02,0xa4,0x20,0xbc,0xc3,0xa0,0x24,0x3f,0x6e,0x42,0xd3,0x33,0x98,0x79,0x16,0x1e,0x86,0x77,0x9a,0xde,0x3d,0xbd,0x0f,0x06,0x04,0x07,0xea,0x8e,0x28,0xd8,0x7c,0x3a,0x18,0x51,0x77,0x02,0xb6,0xf2,0x14,0x39,0x5a,0x10,0x52,0xc5,0xf6,0xe0,0xe8,0x63,0xef,0x8a,0xb1,0xb7,0xc0,0x99,0xff,0x23,0x57,0x0b,0xb4,0x79,0xb0,0x0a,0x1b,0x2e,0xda,0xa8,0x87,0xf2,0x36,0x77,0x9b,0xde,0xe1,0xfe,0x17,0x6c,0xf2,0x2d,0x39,0x00,0xa3,0x91,0x34,0xf6,0x9d,0xfb,0xfa,0x3d,0x0e,0xcf,0x0f,0xd8,0x3e,0xa7,0x04,0x6c,0xa1,0xd5,0x5c,0x66,0x2b,0x1e,0x6b,0xd7,0x6c,0x76,0x08,0x76,0x82,0xed,0x47,0x76,0xe7,0x10,0xee,0xb0,0xf7,0xdb,0xd9,0xd5,0xee,0x87,0xdd,0x93,0x83,0x5c,0x46,0x6a,0x79,0x3a,0x0b,0xfb,0x84,0x8e,0xc1,0x9c,0x99,0x64,0xdf,0x03,0x4d,0x83,0xe0,0x93,0x68,0xc3,0x54,0xaf,0xdf,0xe4,0xa4,0x77,0x70,0xb5,0xff,0xee,0x54,0xe1,0x0d,0xfc,0x75,0x7d,0xca,0x41,0x80,0xe1,0x40,0x18,0x5c,0xbc,0x96,0x3a,0x16,0x3b,0xd1,0x12,0xfb,0xee,0x9f,0xbc,0x03,0x7e,0xb1,0x9d,0xbf,0x9c,0x96,0xbf,0x80,0x92,0x93,0xb7,0xcc,0xac,0x11,0xcd,0x0b,0xe6,0x6c,0xf9,0xcb,0xee,0xde,0xfb,0xff,0xd6,0x8e,0xaf,0x9d,0x14,0x4c,0xfb,0x3d,0xd1,0x3e,0xd6,0x6d,0x76,0x1b,0xfa,0xa9,0x73,0x4d,0x17,0xa8,0x76,0xf7,0x38,0x0e,0xaf,0x63,0x67,0x4c,0x4e,0xde,0x10,0x20,0x75,0x98,0xd2,0xb9,0x56,0x28,0xe2,0x33,0x4f,0xde,0x9c,0xd2,0x3f,0x26,0x14,0xa8,0x0a,0xba,0x29,0x57,0xef,0xfa,0x3e,0x79,0xcd,0xfd,0xdf,0x22,0x43,0x24,0x20,0xca,0xec,0x83,0x19,0xf3,0x3c,0x29,0xc9,0x37,0x42,0x69,0xc2,0x80,0xd5,0x8c,0xa9,0x1f,0x3a,0xae,0xa6,0x77,0xba,0xc0,0xc8,0x81,0x13,0xbb,0x05,0xec,0x3a,0x48,0x79,0xd2,0x93,0x43,0xe2,0x43,0xc7,0x0e,0xa0,0x98,0xa0,0x25,0x71,0x40,0x26,0xb8,0x0c,0x29,0x68,0xf6,0x63,0xfe,0x3f,0x97,0x82,0xef,0xf5,0x93,0xee,0xb6,0xc8,0x35,0xba,0x20,0x92,0x47,0x67,0x3d,0xb2,0x77,0x74,0x78,0x76,0x72,0xf4,0x01,0x5c,0x94,0x18,0x27,0x38,0x9d,0x14,0xe4,0x19,0x87,0x93,0x84,0x42,0x34,0x1b,0x80,0xa1,0x46,0xf3,0xc3,0xe9,0xa8,0x7d,0x37,0xbc,0x73,0xbf,0x33,0x08,0xbd,0x05,0x5f,0x05,0xe6,0x1a,0x9c,0x56,0x38,0x19,0x8c,0xd8,0x8c,0x85,0x13,0xb9,0x5d,0x67,0xee,0x53,0x02,0xf7,0x29,0x60,0x6e,0xa1,0x97,0x11,0x4b,0x74,0xf9,0x64,0x12,0x55,0x87,0xd9,0x2e,0xe0,0x2f,0x4a,0xe3,0xdd,0x37,0xbf,0xee,0xe7,0x27,0x5e,0x02,0xf5,0x98,0xde,0x2e,0x87,0x7a,0x79,0xe2,0x57,0x41,0xfd,0xa4,0xf7,0xf1,0x29,0xa8,0x4f,0xa2,0xe5,0x30,0x2f,0xcd,0xfb,0x2a,0x88,0x9f,0x1f,0x3f,0x05,0x6f,0x1c,0x5a,0x0e,0xf3,0xca,0xcc,0xaf,0x82,0xfb,0xfe,0xd1,0xaf,0x87,0x4f,0xc1,0xde,0x99,0x7c,0x5a,0x0e,0xf9,0xf2,0xc4,0xaf,0x82,0xfb,0xee,0xf9,0x6f,0x8a,0x11,0xca,0xd5,0xba,0x56,0xc7,0xf7,0x77,0x0f,0x7f,0xee,0x9d,0x90,0xff,0x39,0x3a,0xec,0xa9,0x0a,0x5e,0x0e,0x22,0xf7,0x20,0x8c,0xee,0xc7,0xcc,0x26,0xcd,0x8f,0xd1,0x06,0x62,0x12,0x9c,0xf1,0x77,0x70,0x03,0x10,0xa1,0x71,0x77,0x50,0x5a,0x3c,0x4b,0xd0,0xd9,0xd5,0x6e,0xec,0xdd,0x52,0x5c,0xbe,0x8f,0x1f,0x6a,0xd7,0xcf,0x9a,0xf1,0xbd,0x49,0x1c,0x63,0xaa,0xfb,0x91,0xc6,0x49,0x09,0xcd,0xc2,0x17,0xf0,0x27,0x8b,0x7c,0xc1,0x1b,0x2f,0x1e,0xdf,0x39,0x31,0xad,0xae,0x76,0x06,0x03,0x1a,0xa5,0x96,0xd9,0xf7,0x58,0xbc,0x36,0x14,0xd3,0xae,0x86,0x1e,0x04,0x42,0x3c,0xd2,0x55,0x2b,0x0e,0xdc,0x65,0xe1,0xc3,0x2e,0x51,0xe3,0xbc,0x49,0x84,0xe6,0xbc,0x64,0xa0,0xf9,0x90,0xdc,0x17,0x75,0x86,0x0d,0x90,0x02,0x93,0xb9,0x27,0xfe,0x10,0x5e,0xc3,0x34,0x7f,0xd6,0x5f,0xc1,0x4e,0x7e,0x78,0x5d,0xda,0x06,0x05,0x16,0xe1,0xc2,0x1a,0x5c,0x82,0xf2,0x2d,0x46,0x08,0x0c,0x2d,0xf0,0x58,0x42,0x12,0x10,0xa6,0x10,0x89,0xfc,0xc1,0xc2,0x18,0x3d,0xa6,0xfd,0x30,0x4c,0x3f,0x2b,0x25,0x38,0xe9,0xbd,0x3e,0x3a,0x3a,0x5b,0x20,0x2a,0xe5,0xcc,0xc3,0xa7,0xf4,0xe9,0xa9,0xc7,0xc6,0xe6,0x0f,0xdd,0xd3,0x0f,0xbd,0xde,0x71,0x9d,0x9e,0x34,0x20,0xcd,0x92,0x7f,0x45,0xc6,0x55,0x2a,0x63,0x55,0x46,0x8b,0x6c,0x6c,0xa3,0x18,0x14,0x75,0xfb,0xc6,0x68,0xa3,0xbb,0x1d,0x15,0xc3,0x63,0x9a,0x24,0x10,0x8f,0xc0,0x83,0xa8,0x02,0xa5,0x92,0xff,0xd6,0x48,0x95,0x22,0xca,0xca,0x92,0x5a,0x44,0x45,0x26,0x5e,0x3e,0x0a,0xff,0x9b,0x0c,0x62,0x2f,0x4a,0xbb,0x3e,0x4d,0x09,0x44,0xaf,0x8e,0x95,0x4d,0x8d,0x08,0xc3,0x83,0x33,0xe4,0xea,0x1e,0xe8,0x46,0x4a,0x5d,0x6b,0xa5,0x65,0x44,0xa1,0xef,0xbf,0xc3,0xac,0xf9,0xd6,0xf1,0x21,0xfa,0xf2,0x7d,0x63,0x1c,0xba,0x8e,0x7f,0x42,0x21,0x41,0xbe,0xa5,0x6c,0xa4,0x03,0xd8,0x26,0x29,0x81,0xb8,0x0d,0x36,0xee,0xba,0xe1,0x60,0x32,0x06,0x3a,0x98,0xd7,0x34,0xed,0xf9,0x14,0x3f,0xbe,0xbe,0x7f,0xe7,0x6a,0x9e,0xab,0x77,0x86,0x93,0x60,0x80,0xfa,0x4b,0x92,0x51,0x78,0x77,0x80,0x70,0x34,0x46,0x20,0x43,0xd0,0x43,0x54,0xe8,0x13,0xeb,0xe2,0xd2,0x08,0x23,0x9c,0x99,0x00,0x62,0x7a,0x86,0x58,0x0a,0xa2,0x5b,0xf3,0xe0,0xdb,0x25,0xde,0xd8,0xba,0xc1,0x20,0xf7,0xfc,0xc7,0x16,0xb0,0x69,0x30,0x5d,0xa0,0xf0,0xf8,0x02,0x31,0x11,0x96,0x08,0x6c,0x1f,0x5f,0x22,0x26,0xc2,0x12,0xc6,0xab,0x3d,0xc9,0xdd,0xc7,0xd6,0x55,0x84,0x41,0xae,0x7f,0x7c,0x43,0x36,0xcd,0xd6,0x3b,0x82,0x06,0x26,0xd6,0x40,0xf6,0xc4,0xb5,0x51,0x89,0xe2,0xf0,0xcc,0x0b,0x00,0xf4,0xdb,0xb3,0x83,0x0f,0x96,0xe4,0x82,0x20,0xbd,0x89,0x5c,0x7a,0x87,0x90,0x76,0xb4,0x32,0xde,0x26,0x93,0x4a,0x53,0x48,0xa5,0x65,0xb3,0xd2,0x9b,0x2d,0x91,0x33,0x99,0xd9,0x93,0x40,0xd8,0xe0,0x19,0x8c,0x3c,0x3c,0xd8,0x88,0x46,0x31,0x0d,0x24,0x6a,0x52,0x99,0xf7,0x11,0x87,0x60,0x62,0x31,0x09,0x36,0x18,0xd0,0x51,0xe8,0xbb,0x40,0xad,0xd2,0xd4,0xe3,0xe2,0x01,0x5b,0x20,0x92,0xe5,0x70,0x92,0x6a,0x9a,0x6e,0x75,0xe5,0xfa,0x21,0x10,0x0a,0x42,0x5e,0xa3,0xd5,0x6c,0xea,0x7a,0x7b,0xf1,0x31,0x50,0xbb,0xec,0x82,0xab,0x0a,0x65,0x6c,0xbb,0x83,0x32,0xc8,0x1f,0x09,0x7a,0xa3,0x94,0x76,0xc4,0x64,0xd8,0x26,0xee,0x39,0x83,0x91,0x06,0xc6,0xd6,0xea,0x66,0xc5,0xdc,0x82,0x51,0x03,0xa6,0x53,0x62,0xad,0x66,0xf3,0xc7,0xc0,0x22,0x51,0x25,0x53,0x39,0x04,0x40,0xd8,0x77,0x03,0x3f,0x88,0xf2,0xd9,0xda,0x9a,0x98,0xc8,0xec,0xdb,0x07,0xc8,0xe7,0x4c,0xc7,0x45,0x7e,0xf3,0xc7,0xb9,0x34,0x9a,0xd2,0x3c,0x22,0x11,0x18,0x22,0x31,0x4d,0x26,0x7e,0x6a,0xcd,0x32,0x35,0x13,0xe5,0x3f,0xdc,0x85,0xf1,0x82,0x13,0xbd,0x5d,0xe2,0xcf,0xb4,0x78,0xdc,0x51,0x35,0x7f,0x6d,0xcd,0xd2,0xd4,0xef,0x1a,0xdf,0x46,0x37,0xd0,0x22,0xe8,0xc6,0xc8,0x73,0x29,0x57,0x70,0x9d,0x9d,0x02,0xdc,0xba,0x8f,0xe6,0x17,0x8e,0xa1,0x7c,0x93,0x8b,0xa6,0x0a,0xd1,0xb1,0xc4,0x05,0x76,0x7d,0xe4,0xf9,0xae,0xc6,0x47,0xe5,0xd1,0x24,0xdd,0xcd,0x68,0x92,0x8c,0x34,0x81,0x3d,0x42,0x9f,0xea,0x53,0x9d,0xf1,0xe7,0x86,0xde,0xbf,0x65,0xc9,0x72,0x6c,0x51,0x38,0xbd,0x37,0xd4,0xa8,0x09,0x63,0x96,0x65,0xd9,0x3d,0xb4,0x61,0xb6,0x9e,0x51,0x20,0x28,0x0b,0xba,0xf6,0xe9,0xd0,0x81,0xbd,0x35,0xbe,0x52,0x90,0xf1,0x35,0xb0,0xaf,0xb2,0xd9,0xd0,0x03,0xf7,0xd2,0xb7,0xba,0x7d,0x53,0xe1,0x86,0xde,0x29,0x16,0xec,0x14,0x1f,0xcd,0x9c,0x47,0xc0,0x01,0x4d,0x6f,0x57,0x40,0xf9,0x34,0xb8,0x4e,0x47,0xdd,0xa6,0xe4,0xa5,0x7c,0x70,0x51,0x3b,0x6f,0xbd,0x75,0x59,0x81,0x37,0xa5,0x7e,0x02,0x2e,0x58,0x3d,0x15,0x64,0x83,0x11,0x9d,0x7f,0x2c,0x9e,0x54,0x2e,0x38,0xd5,0x4a,0xe5,0x58,0xf9,0x82,0x9d,0xfc,0xd3,0xe7,0x1e,0xaa,0x39,0x83,0xfe,0xb4,0x73,0x07,0xdb,0x86,0x77,0x26,0x13,0x9c,0xf7,0x39,0xb3,0xd6,0xd6,0x72,0x2d,0x89,0x21,0x76,0xbd,0xa5,0x3d,0x3c,0x09,0xca,0x38,0x05,0x05,0xd4,0x6c,0x38,0x2c,0x06,0x2e,0xb6,0x51,0xbf,0x5c,0x9f,0x33,0x6e,0x15,0xe2,0x60,0xe4,0x1b,0x80,0xd2,0xcc,0x83,0x7e,0xa3,0x40,0x14,0x6e,0xa4,0x6a,0x21,0xb0,0xda,0x6f,0x4f,0x73,0x37,0xa6,0x48,0xf9,0x67,0xf9,0xa8,0xce,0x9c,0x6d,0xb8,0x21,0x5a,0xa8,0x6e,0x2b,0x2d,0xa9,0x6a,0xf3,0x68,0xaa,0x7d,0x1d,0xa2,0xb2,0x4d,0x0b,0x12,0xb0,0xc7,0x60,0xb8,0x30,0x16,0xd6,0xa4,0x03,0x61,0x2e,0xc6,0xb2,0xc5,0x30,0x88,0x67,0x4c,0xd3,0x49,0x1c,0x90,0x80,0xde,0x91,0xe3,0x38,0x1c,0x7b,0x09,0xb3,0x17,0x2c,0x82,0xe8,0x66,0xa5,0x80,0x42,0x0c,0x1b,0xf3,0x22,0x84,0x0b,0x76,0xe3,0xd8,0xb6,0xf7,0x98,0x78,0xda,0x06,0xb3,0x4c,0xed,0x95,0xd6,0xd4,0x10,0x0f,0x8e,0xde,0xe7,0x83,0x4d,0x43,0xde,0x3d,0xac,0x34,0xa7,0x97,0x60,0x24,0x2a,0x68,0xef,0xc2,0x79,0xd2,0x2a,0xd2,0x87,0x61,0xea,0x0d,0xe8,0x57,0xc1,0xf9,0x09,0xa8,0xe1,0x8e,0xd1,0x0c,0x6e,0xcc,0x6e,0xdb,0x86,0xcb,0x75,0x9c,0x39,0x4b,0x70,0x4d,0x4f,0x44,0x15,0x9d,0x41,0x37,0xe3,0x1f,0xd6,0xd6,0xf8,0xbf,0x42,0x53,0x77,0xe2,0x92,0x31,0xe7,0x8e,0x56,0x6f,0xcb,0x51,0xce,0xfa,0xaf,0xc0,0x1a,0x23,0xcb,0xbd,0x12,0x3e,0xca,0x43,0x86,0xb6,0x1a,0x31,0xb0,0xe3,0xb6,0xd5,0xb3,0x1b,0xd5,0x40,0xa0,0x6d,0xdb,0xd3,0x12,0x2d,0x11,0xec,0x09,0x4b,0x41,0xa4,0x9a,0x16,0xc8,0xdb,0xfb,0xac,0x71,0x86,0xf0,0xe7,0x5e,0x70,0x6d,0x1b,0xf6,0x31,0x16,0x09,0xef,0x3c,0xdf,0x07,0xaf,0x39,0x84,0x63,0x8f,0x08,0x64,0x7a,0xdb,0x58,0xf2,0xc7,0xa0,0x7a,0x55,0x06,0xfa,0x93,0x20,0x45,0xdd,0x59,0xed,0xbe,0x82,0x5c,0x19,0x1e,0x76,0x49,0x42,0x21,0x4a,0x73,0x13,0xd3,0x34,0x6d,0xe3,0xe2,0x52,0x18,0x5f,0x39,0xcf,0x7a,0x65,0xe4,0x9f,0xf3,0x58,0x1a,0xe2,0x15,0xf9,0x99,0x05,0x2c,0x59,0x3e,0x65,0x7d,0xbd,0xbc,0xfc,0xf1,0x70,0x2f,0x9f,0x0a,0x26,0x45,0x59,0x06,0x36,0x40,0xf9,0x56,0x0a,0x31,0xf2,0x71,0xbd,0x40,0x6d,0xdb,0x6a,0xe2,0x0a,0x9f,0x3a,0x71,0x8e,0xd9,0x0c,0xde,0xba,0x31,0x53,0x95,0x04,0x91,0x68,0xd1,0xcd,0x2a,0xd5,0xcf,0xf6,0xf0,0xbe,0xa2,0x86,0xec,0x18,0xa5,0x11,0xd0,0x32,0x72,0x4a,0x81,0xb5,0xf6,0xd9,0x88,0x12,0xde,0xc2,0x04,0xa9,0x1b,0x84,0x91,0x20,0xc5,0xd4,0x4d,0x48,0x1a,0x92,0x3e,0xe4,0xd4,0x00,0x62,0x14,0x87,0x81,0xf7,0x27,0x75,0x91,0xb2,0x42,0x90,0xf2,0x9b,0x90,0x5a,0x79,0x32,0x64,0x78,0xd1,0x76,0x70,0x3d,0x23,0x6e,0xf9,0x2a,0xc5,0x70,0xee,0x1c,0x0f,0x29,0xac,0x56,0x45,0xa7,0xa8,0x87,0x3c,0x95,0x81,0x8c,0x3b,0xa0,0xee,0xd1,0xb0,0x77,0x74,0x06,0x39,0x50,0x39,0x6d,0x81,0x31,0x79,0x28,0x65,0xda,0xc3,0x83,0x56,0x5a,0xd4,0x54,0xb4,0xc4,0x3e,0x82,0x6c,0x2d,0x1c,0x92,0xb3,0xd8,0xb9,0x45,0xbd,0x90,0x82,0x07,0x1e,0x36,0x00,0x32,0xa0,0x6d,0x26,0x40,0x65,0xcf,0x87,0x8f,0x31,0xde,0x72,0xe3,0x34,0xac,0xe8,0xf2,0x92,0xbd,0x97,0x10,0xd1,0x85,0xa2,0x50,0x60,0xbe,0x29,0xd1,0xa7,0x28,0x3c,0x9e,0x60,0xd7,0x3b,0x97,0xa7,0x68,0xf9,0x11,0x62,0x5e,0x1c,0x1a,0xf0,0xbc,0x19,0x18,0xa3,0x24,0xd1,0x72,0xb0,0x93,0xc6,0xf7,0x59,0xe0,0xdc,0x7a,0xd7,0x4e,0x1a,0xc6,0xe6,0xad,0x28,0xc8,0x6c,0x40,0x08,0x3d,0x05,0xde,0x0f,0x46,0xd9,0x54,0x61,0xb6,0x52,0xd4,0x12,0x00,0x0c,0x5e,0xd5,0xca,0xd8,0x3f,0x6b,0x6b,0xec,0x9f,0x99,0xf8,0xc4,0x28,0x50,0x5c,0x5b,0x2b,0x0b,0x5d,0xf1,0x44,0x37,0x2a,0xf8,0x2a,0xab,0x66,0x35,0xa8,0x32,0x17,0xc4,0xf2,0x55,0x53,0x15,0x4b,0xa5,0x36,0x96,0xa9,0xbb,0x5b,0xda,0xfc,0xfd,0xb9,0xcd,0x63,0x82,0x44,0x0a,0x48,0x2a,0xd1,0xc6,0xee,0xa1,0x33,0xa6,0x3a,0x46,0x9b,0x2b,0xf2,0x1b,0xc6,0x66,0x8c,0x30,0xf6,0xda,0xda,0x0a,0x17,0xb6,0x92,0xa3,0xb4,0x7f,0x45,0x1b,0xd3,0xa7,0xd7,0x1e,0xda,0xfb,0x5b,0x30,0x3e,0x64,0x9d,0x44,0x80,0x04,0xc4,0x77,0x03,0x3e,0xc7,0xb4,0x75,0x1d,0x16,0xab,0x10,0x79,0x31,0x65,0x1e,0x48,0x21,0x55,0xc2,0x7a,0xe1,0x4c,0x80,0xc9,0x0e,0x86,0xe0,0xb9,0x6e,0x61,0xef,0x86,0x14,0x2c,0x93,0xec,0x82,0xb4,0xdd,0x87,0x13,0x92,0x4c,0x62,0xba,0x33,0xbb,0x1d,0x2b,0xa9,0x2c,0xb3,0x1b,0x9b,0x38,0x0b,0x4e,0x47,0x31,0x12,0x19,0x48,0x04,0x7a,0x45,0x2d,0x0e,0x68,0x48,0x41,0x84,0x34,0xdb,0x6c,0x44,0x61,0x02,0x16,0x20,0x1b,0xd3,0x74,0x14,0xba,0x6d,0xfb,0xf8,0xe8,0xf4,0xcc,0x36,0xf0,0xde,0x9b,0xc6,0x49,0x3b,0x5b,0x15,0xb6,0x6a,0x1d,0x3d,0xc1,0x6a,0xdb,0x86,0xc4,0x00,0xe2,0x48,0x66,0x7a,0x1a,0xbf,0x27,0x90,0x3b,0x41,0xd6,0x10,0xba,0xf7,0xed,0x5f,0x4e,0x8f,0x0e,0x21,0x80,0xc2,0x53,0x7a,0xc3,0x7b,0x2d,0x83,0x13,0xb4,0xc5,0x29,0x58,0x62,0x80,0x7c,0x91,0x18,0x98,0xe1,0x8d,0x9e,0x29,0xc7,0xe1,0x11,0x80,0x2d,0x78,0x49,0xbe,0x7b,0x96,0xc9,0x95,0xdf,0x91,0xa1,0xe3,0xf9,0xd4,0x6d,0x93,0x67,0x59,0xbe,0x1a,0x08,0x97,0x4e,0x92,0xe9,0xec,0xd0,0x19,0x28,0xe5,0x14,0x8c,0x2f,0x77,0xc4,0xd3,0x59,0x9e,0xed,0xcc,0x78,0xa2,0xb6,0x92,0xb0,0x32,0x8a,0x9c,0x32,0x48,0x46,0xa1,0x65,0x1a,0x08,0x15,0xda,0xa3,0x10,0x42,0x43,0x3f,0xbc,0x86,0xaf,0xc6,0x2c,0xea,0x87,0x34,0xbd,0x0b,0xe3,0x1b,0x42,0xe3,0x38,0x8c,0x11,0x59,0x6a,0x0a,0x7f,0x0c,0xf8,0x4c,0xab,0x82,0x5b,0x54,0x57,0x31,0x47,0xe7,0x41,0xab,0x44,0x96,0x65,0xed,0x80,0x31,0x16,0x6d,0xed,0x1d,0x1b,0xa6,0x5e,0xb1,0x8f,0x6d,0xf6,0x91,0x15,0x63,0x6d,0x24,0x66,0x9d,0x34,0x9c,0x8d,0xc0,0x4a,0x31,0x59,0xc8,0x77,0x20,0x29,0x18,0xf6,0x67,0x19,0x42,0x9d,0x9a,0x04,0x79,0xe9,0x05,0x13,0x26,0x17,0xff,0x7b,0xc4,0xa2,0xa8,0x38,0x7f,0x89,0x34,0xcc,0x02,0xe6,0x64,0xc1,0xae,0xcc,0x78,0x37,0xd5,0x9a,0xba,0x99,0x86,0xe7,0xd8,0xd9,0xb1,0x07,0xfa,0xaf,0xe9,0x2f,0xd9,0xc3,0x04,0x4e,0x45,0xb5,0x96,0x3e,0xcd,0x49,0x9a,0x5b,0x5b,0xf4,0x04,0xba,0xb1,0x94,0xfc,0x3c,0x59,0x60,0x14,0x1b,0xa7,0x7a,0x4c,0xc6,0xb0,0x20,0xbc,0xb3,0x1a,0x2f,0xc8,0x3f,0xaf,0xae,0x8e,0xcf,0x4f,0x7a,0x57,0x57,0xe4,0x45,0x83,0xc5,0x9c,0xfb,0xc0,0x6e,0x1e,0x8b,0x59,0xd7,0x54,0xb3,0x45,0x43,0x04,0x10,0x80,0x8d,0x89,0x3a,0x0f,0x1c,0x1a,0xd6,0x63,0x00,0xf3,0x06,0x4c,0xea,0xbf,0xc0,0x2c,0x81,0xcb,0x5d,0x7f,0x96,0x9d,0x32,0x16,0x69,0xe2,0xd9,0x01,0x30,0x77,0x04,0x24,0x68,0xe9,0x66,0xe4,0xb8,0xac,0x89,0x41,0xdb,0x30,0xec,0xa6,0x5d,0x33,0x17,0xb7,0x85,0xe8,0x63,0x66,0xe2,0x59,0x75,0xe2,0xdb,0x70,0x12,0x27,0x75,0x33,0xdb,0x33,0xdb,0x83,0x8c,0xa6,0x74,0xb9,0xb9,0xa7,0x3c,0xe4,0xab,0x9b,0x0b,0xb9,0x9b,0xd2,0x03,0xc2,0xa3,0x68,0x25,0xc0,0x57,0x9e,0x51,0x1f,0xdc,0xa4,0x5f,0x2d,0xea,0x88,0x76,0x5d,0x60,0x32,0xd2,0xb3,0xb8,0xcf,0xb5,0x75,0x53,0x5e,0xf4,0x62,0x6d,0x96,0x3d,0xcc,0xaf,0x8d,0xcb,0x0f,0x95,0x24,0x75,0x6e,0xfb,0x00,0x47,0x2c,0xab,0xc1,0x95,0x87,0x41,0x89,0xb8,0x80,0x67,0x81,0x79,0x62,0x5d,0xd8,0x45,0xe7,0x08,0x44,0x31,0xb2,0xbf,0x03,0x3e,0xe6,0x6d,0x18,0x18,0xdc,0xe4,0x7d,0x04,0xf0,0x25,0xbf,0xe1,0xb7,0x2f,0x8b,0x10,0xa4,0x72,0xbb,0x3f,0xf9,0x74,0x25,0x10,0x01,0x95,0x64,0x35,0xe8,0xab,0x42,0x96,0x8e,0x77,0x4f,0x76,0x0f,0xae,0x9e,0x65,0x72,0x92,0xe9,0xb9,0x66,0x32,0xe9,0x73,0xbd,0xd6,0x20,0xe8,0xc4,0xca,0xad,0x82,0x7f,0x0e,0xac,0x34,0xaa,0x40,0xd5,0x8d,0x1c,0x94,0x34,0x78,0xd8,0xb9,0x61,0x33,0x09,0xbf,0x18,0xa1,0xa4,0x18,0x63,0x2e,0x05,0x97,0x56,0x3e,0x95,0x89,0xb0,0x09,0xd9,0xb9,0x07,0x1a,0xd4,0x06,0x4a,0x8f,0x9d,0x48,0x3b,0x64,0xbd,0x08,0x7a,0x47,0x81,0x2e,0x64,0x5d,0x63,0x70,0x5e,0x7c,0xdf,0x7c,0x29,0x40,0xe9,0xf0,0x99,0x57,0x6f,0x66,0x27,0xc3,0x48,0x42,0xdf,0x40,0x28,0x9d,0x6a,0xe5,0xfd,0xf4,0x87,0x87,0x66,0xc1,0xc6,0x49,0x84,0x5d,0x26,0xa7,0x25,0x96,0x80,0x5e,0x0e,0xc3,0x58,0x63,0x01,0x9f,0xd5,0xec,0x78,0xdb,0x65,0x8e,0x89,0xda,0x4c,0xc7,0x7b,0xf9,0x52,0x07,0xda,0x4a,0xe8,0x52,0x49,0x9f,0x65,0xe5,0xe9,0x17,0xde,0x25,0x92,0x73,0x1e,0x0b,0x6a,0x27,0xaf,0x68,0x2b,0x12,0xec,0xc3,0xc3,0x8a,0x4a,0x67,0x0c,0x26,0xf2,0xf3,0x14,0xf2,0x2d,0x8a,0xd9,0x89,0x22,0xe4,0x0f,0x0f,0x45,0x75,0x06,0xce,0x7a,0x2b,0x8b,0xa4,0xc0,0x9b,0x9c,0x9d,0x2a,0xfe,0x35,0x8c,0x23,0x8c,0xe0,0xd6,0x81,0x93,0x8e,0xcc,0xa1,0x1f,0x02,0x4d,0x66,0xe8,0xdc,0xd8,0xfc,0x1e,0x2c,0xa3,0xe4,0xed,0xc2,0xa9,0xcf,0x71,0x6a,0xe3,0xfb,0xa6,0xde,0x29,0x33,0x04,0x6d,0x98,0xb0,0x01,0x6c,0xbf,0x45,0x46,0x42,0x32,0x7e,0xd6,0x36,0x70,0x39,0xa8,0x40,0x9e,0xc1,0x61,0xc6,0x73,0x97,0x72,0x15,0xee,0xb8,0x39,0x01,0x85,0x76,0xe6,0x34,0xfc,0x63,0x42,0xe3,0xfb,0x53,0xea,0xd3,0x01,0x04,0xef,0xbb,0x3e,0xe4,0x1f,0x82,0x07,0x92,0xde,0xe8,0xfa,0x4a,0x6b,0x85,0x9c,0x00,0x49,0x9b,0x3a,0xf7,0x59,0xbc,0x26,0xea,0xdc,0x63,0x82,0x67,0x65,0xd3,0x4e,0x2e,0x65,0xec,0xe6,0x09,0x72,0x99,0xd2,0x7a,0x8e,0x0e,0x24,0xc8,0x52,0x45,0x71,0x07,0xf8,0x0a,0x2c,0xca,0x7d,0x01,0x9b,0xe2,0xa6,0x96,0xf4,0x17,0x9a,0x2a,0xe8,0xc6,0x3d,0xb8,0x03,0xcb,0x4d,0xcb,0xde,0xc1,0x18,0xa3,0x33,0x10,0xc3,0xc2,0x31,0x18,0x2e,0x96,0xd7,0xd2,0xc2,0xfc,0x1b,0x9c,0xf3,0x7c,0x48,0x18,0xfa,0x9c,0xc9,0x62,0xa9,0x34,0xea,0x86,0xc8,0xd2,0xc5,0x78,0x6e,0xc0,0x3b,0xe2,0xa4,0x26,0x0a,0x94,0x2a,0x19,0xb8,0x85,0x79,0x7e,0xb6,0xa7,0x21,0x7e,0x1c,0x1d,0x44,0xc0,0x28,0x99,0x09,0x09,0x55,0x6f,0xb0,0x2c,0x58,0xd6,0x68,0xf3,0xf3,0xcb,0x86,0x2e,0x5b,0x97,0xdb,0xc4,0xd2,0x16,0x5f,0x61,0x2c,0xae,0x1a,0x80,0x8a,0xf6,0x77,0x0a,0x60,0x26,0xf3,0xfc,0xc9,0xaf,0x1e,0x50,0x41,0xa8,0x24,0x84,0x52,0x19,0xe7,0x13,0x08,0x05,0x0b,0xe1,0x4a,0xc6,0xf1,0x7b,0x9d,0x67,0x88,0x96,0x02,0xb4,0x53,0x56,0x20,0xde,0x4d,0x05,0x21,0xbe,0x36,0x63,0x8a,0x2a,0x98,0xa0,0x59,0xe0,0xc8,0xb3,0xdd,0x28,0x24,0x48,0xc9,0xc3,0x03,0xbf,0x35,0xac,0x8e,0x5f,0xe4,0x08,0x5d,0x5a,0x5c,0x96,0xa7,0x29,0xeb,0x20,0x2e,0x42,0x59,0xfb,0x14,0x32,0x28,0x4c,0x4b,0xf0,0xee,0xb1,0x6d,0x4b,0x20,0xbc,0x6e,0xf2,0x97,0xc6,0x85,0x72,0xe7,0xe5,0x22,0xc2,0x37,0x2c,0x0a,0xc4,0xe2,0x44,0x82,0x2d,0x54,0x5c,0x0b,0x92,0xcf,0xcb,0x11,0x4a,0x1a,0x28,0xef,0x8f,0xb8,0xd9,0xed,0x56,0xad,0x26,0xaf,0xe0,0x2a,0x36,0x73,0x61,0x64,0xd0,0x5c,0x14,0x19,0x34,0xbf,0x52,0xe0,0x58,0xb1,0x57,0x0a,0x64,0x30,0x57,0x8f,0x85,0xfc,0xa0,0x8d,0x76,0x0d,0x03,0xa4,0xc8,0xb0,0x2d,0x55,0xea,0xb3,0x85,0x84,0xd3,0x15,0xc4,0xa7,0x42,0xe9,0x9c,0xc6,0xec,0x66,0x9b,0x6f,0x95,0x4f,0x41,0x59,0xd0,0xb0,0xea,0xa5,0x48,0xe3,0xcf,0x61,0x2a,0x25,0x11,0xff,0x81,0x18,0x81,0x39,0xdb,0xf3,0x77,0x5a,0x4d,0x2a,0x26,0x90,0xe9,0xe1,0x3f,0x1c,0x11,0x14,0xe4,0x1c,0x17,0xaa,0x06,0xd1,0x05,0x9c,0x0c,0x01,0x9b,0x71,0x3a,0xb8,0x02,0xf2,0x3f,0x3c,0xcc,0x96,0xc8,0xd8,0xce,0xe6,0x38,0xb9,0x5e,0xb1,0xac,0xdb,0xd0,0x73,0x09,0x16,0xe3,0x90,0x8f,0x30,0x04,0x0c,0xe4,0x0a,0x2a,0xe7,0x88,0xd9,0xa2,0xad,0xb1,0xba,0x42,0x0c,0x97,0x57,0x89,0x41,0x48,0x35,0xde,0xe0,0xeb,0x2f,0xda,0x06,0xc8,0x90,0xc6,0x9e,0xf4,0x27,0x9e,0xef,0x5e,0x89,0xb6,0x18,0x70,0xc6,0xc5,0x18,0x22,0xaf,0x4b,0xa0,0xfc,0x79,0x19,0x68,0x69,0xe9,0x4b,0x9b,0x68,0xf6,0xcb,0xca,0xf2,0x97,0xb6,0x6e,0x73,0xb5,0x46,0xe3,0xfa,0xae,0x2e,0x49,0x18,0x72,0x34,0xf0,0xb9,0x72,0x92,0x95,0x7c,0xfe,0xe2,0xe8,0x41,0xb9,0x3c,0x2a,0x45,0x0f,0x00,0x2a,0x87,0x90,0x7b,0x9f,0x79,0xd9,0x4b,0x81,0xc1,0x0b,0x34,0xe2,0x9d,0x62,0xef,0xdc,0xf1,0x73,0x9f,0x01,0xce,0xa0,0x36,0x7d,0xc9,0x9f,0x2e,0x95,0xc0,0xe4,0xb3,0x1f,0x4f,0x61,0xf2,0xa9,0x4b,0x24,0x31,0x05,0x12,0xcb,0xa4,0x31,0xf9,0xec,0x45,0x89,0xcc,0x54,0x72,0xee,0x68,0x92,0x2a,0xac,0x2b,0x3a,0xa1,0x15,0x06,0x06,0x60,0xe0,0xae,0x1c,0xdf,0x89,0xc7,0x39,0x1b,0x97,0x24,0x7c,0xb1,0xb2,0x20,0x3f,0xdf,0xf0,0xff,0x3c,0xfd,0x19,0x7d,0x0a,0xdf,0x0a,0xca,0xc8,0x6d,0xca,0x71,0xde,0xac,0xa3,0x49,0x73,0x55,0xcd,0x0d,0xa4,0x37,0x55,0x12,0xbf,0x92,0xfe,0x15,0x81,0x49,0x87,0x1b,0xa7,0x52,0x58,0xa2,0xaa,0xe2,0x2c,0x8c,0xcf,0xd6,0xc9,0x59,0x50,0x70,0xa4,0x1a,0xf8,0x8a,0x91,0x29,0xe3,0x95,0x1b,0xb0,0x96,0xae,0x57,0x0d,0xad,0x4a,0x14,0x26,0x79,0xac,0x47,0x8d,0x9d,0x97,0x7d,0xc2,0xaa,0x49,0x88,0xf5,0x13,0x1e,0x47,0x5b,0x47,0xfd,0xdf,0x21,0x4a,0x36,0x01,0xb3,0xd8,0x03,0x76,0x55,0x68,0xad,0x9b,0x38,0x59,0xd3,0x1c,0xa3,0xaf,0x5b,0x5d,0x07,0x2f,0xb4,0x59,0x7f,0x3f,0xdd,0x0b,0xc7,0x11,0x76,0xe9,0xf5,0x61,0x48,0x67,0xf2,0x3f,0xd3,0x3a,0xa5,0x8b,0x88,0xf9,0xe2,0x86,0xde,0xf3,0x30,0xec,0x12,0xa2,0x66,0x75,0x73,0x11,0x34,0xcf,0xa4,0x59,0xb0,0x60,0xca,0x95,0x8a,0x67,0x3e,0x2b,0xde,0x17,0xd1,0xdb,0x13,0x24,0x56,0x93,0x0d,0x1e,0xd5,0xb1,0xe0,0x32,0xab,0x6d,0xfa,0x9a,0xa5,0xa6,0xf4,0xa8,0x2c,0xbc,0xcd,0x18,0x31,0xcb,0xed,0x31,0x4b,0x9e,0x37,0x0e,0xef,0x2c,0xb9,0x3a,0x81,0xb0,0xe2,0x84,0x5d,0xc7,0x40,0x9c,0xe2,0xb7,0x2c,0x78,0x26,0x46,0xf7,0xe0,0xbb,0xd6,0xe4,0xe3,0x1b,0xd5,0xf1,0x96,0xde,0x61,0xf3,0x4b,0xf7,0x59,0xb0,0x6d,0xa7,0xa0,0xe7,0xbc,0xa6,0x1b,0xd9,0x16,0xa5,0x44,0xc1,0xf8,0x07,0x30,0xe5,0x84,0xc1,0xa2,0x3d,0xea,0xa6,0xbd,0xc3,0x2f,0x1e,0xdb,0x32,0x42,0x36,0x64,0x66,0x63,0x95,0xf9,0x64,0xcc,0x90,0x55,0x8c,0xe4,0xaf,0x32,0x48,0x09,0xd5,0xca,0x75,0x16,0xf6,0x4a,0xc3,0x94,0x1f,0xb0,0xd4,0xf2,0x22,0x0a,0x30,0x8f,0x65,0x73,0xe0,0x86,0x2b,0x21,0x63,0xc6,0x37,0x9e,0xed,0x6f,0x60,0xdd,0x17,0xb6,0xa1,0x20,0x82,0x9b,0x9b,0x09,0x83,0xa9,0xe1,0x75,0xa9,0x5e,0xd7,0xfa,0xd7,0x9c,0x09,0xe9,0xaa,0x3d,0xaa,0x8c,0xa1,0xd8,0xdd,0x5a,0x58,0x95,0x52,0x47,0x2c,0x10,0x3a,0x7f,0x2a,0x7b,0x40,0x44,0x05,0xbb,0xfe,0xa6,0xba,0x98,0x9d,0x93,0x0f,0xef,0x75,0xe4,0xf5,0xb3,0xec,0xf2,0xc1,0x69,0x56,0x31,0x17,0x3f,0x61,0xb7,0x09,0xaa,0x0c,0x7e,0xe6,0x37,0x31,0xf8,0xc9,0xc4,0x9f,0x3e,0x00,0xed,0x76,0x45,0xda,0x84,0x7d,0xbb,0x98,0x34,0xcd,0xc6,0xb6,0xc7,0xfc,0xea,0x85,0xd3,0x84,0x38,0x84,0x75,0xf8,0x8a,0x33,0x08,0xc9,0x9f,0x53,0xf9,0x16,0x7d,0xba,0xf2,0xe0,0x6c,0x11,0x59,0x7d,0x96,0xe5,0xfb,0x4f,0x57,0x77,0xfe,0x1d,0xfc,0x3b,0xa8,0xb9,0x9d,0x71,0x86,0x60,0x65,0x04,0x55,0x4d,0xa5,0x2e,0xee,0xc4,0xb1,0x73,0xff,0x7a,0x32,0x1c,0xd2,0x58,0xc6,0xc9,0x08,0x4c,0x19,0x06,0x7d,0x54,0xae,0x17,0x39,0x0a,0x18,0x84,0x4a,0xde,0xd8,0x86,0xcd,0x1a,0x41,0x79,0xf3,0xe8,0xea,0x4c,0xdf,0x2b,0x91,0x2f,0x51,0xae,0x76,0xbf,0xf9,0xc7,0x3f,0x48,0xe5,0x3f,0xcb,0x2d,0x25,0xe5,0x57,0x71,0xd9,0x2f,0x3e,0x00,0xbc,0x02,0x19,0x95,0x06,0xa6,0x69,0xf2,0xbe,0xd3,0xcf,0xdf,0xae,0x78,0xb1,0x92,0xe0,0x4f,0x3a,0x88,0x01,0xf1,0xea,0x24,0xe9,0x87,0x3e,0x9c,0x86,0x5d,0xd2,0x73,0x82,0xae,0xb3,0xb7,0x74,0x20,0x3f,0x59,0xed,0x36,0x9f,0xd7,0xee,0xcd,0x07,0x8b,0xeb,0xfa,0x4f,0xa3,0x98,0x55,0x27,0x7e,0x3b,0xf8,0xf0,0x36,0x4d,0xa3,0x13,0x7c,0xb9,0x27,0x49,0x3b,0x30,0x6c,0x0a,0x1e,0xcd,0xea,0x96,0xdc,0x04,0xc2,0x7e,0xd9,0x6f,0xc6,0x6b,0x29,0xe8,0x22,0x26,0xcc,0xd0,0x89,0x1c,0x9d,0xc6,0xf8,0xf2,0x29,0x0e,0xc3,0x57,0x51,0x64,0x60,0x07,0xc5,0x25,0x00,0x9c,0xba,0x0d,0x8c,0xcd,0x53,0xc7,0x7f,0x81,0xad,0x8a,0x86,0x84,0xbc,0xe8,0xf2,0xbf,0x72,0x52,0x1b,0xbb,0xd2,0xe4,0x2a,0x30,0xf9,0xc5,0x97,0x92,0xa9,0xac,0xa0,0xf2,0xd2,0x7e,0x8e,0xe9,0x1b,0xa7,0x01,0x87,0x28,0x34,0xd3,0x52,0xb5,0x54,0xaa,0x29,0xe4,0x5a,0xe8,0x33,0xf1,0xba,0x15,0x29,0x33,0x4b,0x12,0x04,0x60,0x1b,0x9a,0x9c,0xc0,0xf3,0xa2,0xae,0x05,0xd9,0xe5,0xda,0x5a,0x31,0xb0,0xbd,0xd9,0x6c,0xe6,0xdd,0x26,0xd8,0x61,0x82,0x40,0xb5,0x6a,0x94,0xd8,0xe3,0xa9,0x96,0xd4,0xb1,0xfc,0x0a,0xa6,0x80,0x33,0x2d,0x7d,0x13,0x29,0x36,0x5a,0xb4,0x7a,0xe4,0x58,0xee,0x26,0xb0,0x5b,0xbc,0x67,0x29,0xdb,0x25,0xee,0x84,0x5d,0xa2,0x72,0xf2,0x2c,0xda,0xc0,0xe9,0x83,0xcb,0x5b,0x6a,0x03,0x71,0x28,0x9e,0xa5,0xfb,0x94,0x43,0x05,0x36,0x20,0xdc,0x10,0xfc,0x81,0x26,0x0a,0x1c,0x90,0x1d,0x83,0x54,0xd8,0x7c,0x43,0xc8,0x1d,0x85,0x5c,0xbe,0x65,0x75,0x0f,0xbc,0xba,0x2c,0xaa,0x1e,0x30,0x59,0xad,0x7a,0x84,0x83,0x94,0x82,0xca,0xa4,0x60,0xd1,0xc7,0xf9,0x7a,0x10,0x38,0xc5,0x90,0x48,0x7b,0x5c,0xe2,0xbb,0xda,0xba,0x20,0x2d,0x0a,0x39,0x67,0x21,0x01,0xec,0x90,0x8f,0xf0,0x35,0x90,0x8f,0x27,0x93,0xc1,0x00,0xc4,0x6c,0x08,0x71,0xf9,0xfd,0x0a,0x11,0x86,0xce,0x4b,0x84,0x99,0x03,0xba,0xa1,0xf6,0xf7,0xe3,0xee,0xff,0x37,0xd7,0x7c,0x41,0x73,0x4d,0x5e,0x86,0x50,0x3b,0x6b,0x67,0x1d,0x59,0xaf,0xbe,0x38,0x53,0x04,0xca,0xf9,0xcb,0x8b,0x9a,0xa7,0x67,0x7f,0xc1,0x95,0xab,0x1d,0x0f,0xaf,0xf0,0xd0,0x81,0x6d,0xa0,0x4b,0x0f,0xa8,0xdf,0xf6,0x58,0x8f,0x56,0x25,0xb8,0xa8,0x79,0xa9,0x52,0x69,0xa3,0xc6,0x62,0x22,0x5e,0x40,0xbd,0x09,0x63,0x90,0x3d,0xb0,0x2f,0xf6,0x09,0xc5,0xaa,0x03,0x0a,0xe4,0x79,0x84,0xb7,0x4e,0xc8,0x9f,0x4b,0x83,0xed,0x44,0xdd,0xbd,0xd0,0xc5,0xe9,0xec,0x75,0x85,0xf2,0x9f,0xcb,0xc7,0x6f,0xb0,0x05,0x26,0xd8,0x74,0x43,0xb6,0xf0,0x75,0x50,0xde,0x56,0x42,0xe4,0xcf,0x1a,0x78,0x78,0x37,0xca,0x71,0x34,0xbf,0xf9,0xe6,0x18,0x2d,0x2c,0x39,0x7a,0xcf,0x1b,0x96,0xc0,0x21,0x1a,0x78,0xf1,0x0d,0xc1,0x43,0x88,0x3d,0x3b,0xec,0x12,0x3c,0x62,0xbd,0x7c,0x89,0xf9,0xb7,0x5e,0x7d,0x23,0x1f,0x98,0xf4,0x5d,0xa5,0x74,0x1c,0xd9,0xd3,0x27,0x17,0x3c,0xd9,0x62,0x24,0x07,0xae,0xc7,0x5e,0x9a,0x98,0x95,0x98,0x94,0xf0,0xe8,0xef,0x39,0x94,0xa8,0x6c,0xb2,0x13,0x19,0xcb,0x1f,0x47,0x2c,0xc3,0x03,0x0d,0xf8,0x4f,0x6d,0x25,0xc5,0x61,0xca,0x97,0x6a,0x5b,0xec,0x02,0x2d,0xfb,0x4b,0x4f,0x55,0xa7,0x32,0x8b,0x62,0x67,0xa5,0xa6,0x2b,0x1d,0x35,0x7b,0xe3,0xa1,0xc6,0x44,0xf0,0xd7,0x93,0xc1,0x44,0x78,0x2f,0x5b,0xd3,0xc6,0x16,0x1a,0x0b,0x45,0xc9,0xf0,0x5e,0x0f,0xe3,0x55,0x2e,0xd7,0xbc,0x87,0xa3,0xf2,0x98,0xe4,0x2f,0x62,0x61,0xbb,0x4f,0x2c,0xf5,0x03,0xaf,0xc5,0x8b,0x95,0xa0,0x11,0x77,0xa8,0x07,0x2e,0xbe,0x07,0xa7,0x81,0x0b,0xc5,0x1f,0x58,0x01,0x55,0x28,0xb4,0x85,0xbd,0xdf,0x48,0x26,0x81,0xd0,0xb7,0x31,0x24,0xcd,0x26,0xb6,0x60,0x0a,0xf5,0x7b,0x2d,0x5e,0x90,0x30,0xfe,0x4e,0xba,0xaf,0xb7,0x98,0xa2,0xfc,0xbd,0x6a,0xcb,0x63,0x9b,0x7a,0x95,0xad,0x2d,0x4a,0x0f,0x46,0x74,0x70,0x83,0xf1,0x0a,0x08,0xf7,0xdc,0x1a,0x79,0xc9,0x56,0x02,0x5b,0x2d,0x7e,0xdb,0x94,0xe5,0x6f,0x84,0xd5,0xd6,0xcd,0x3b,0xd5,0x55,0xac,0x66,0x32,0xc0,0x6f,0x3b,0xc5,0x47,0x78,0xd0,0x86,0xfc,0x71,0x71,0xe9,0xbc,0x16,0x4b,0x5a,0x83,0xd7,0xb4,0x3a,0x62,0xa1,0x2f,0xfd,0x6b,0x59,0x40,0x53,0x6e,0x37,0x0d,0xfe,0xc3,0x55,0x9e,0x81,0xe7,0x84,0x33,0xb2,0x6e,0xe3,0x3a,0x6b,0x51,0x57,0xdc,0x79,0xdf,0xfb,0xd7,0xde,0xd1,0x7e,0xef,0x0a,0x54,0x6f,0x2a,0xab,0x10,0x95,0xd2,0x4c,0xe5,0xa8,0x35,0x7d,0x24,0xbc,0xbf,0xb3,0xa8,0x52,0x59,0x99,0x04,0xdb,0x6c,0x97,0x56,0x37,0x2f,0x0d,0xf9,0xa4,0x55,0x7e,0xd2,0x2a,0x9e,0x6c,0x94,0x9f,0x6c,0x14,0x4f,0x36,0xcb,0x4f,0x36,0x2f,0xa7,0x9d,0xbf,0xdc,0xc6,0x17,0xa7,0x64,0xf6,0xaf,0x64,0xdc,0xbf,0x90,0xea,0x8f,0xde,0xb0,0x4d,0x97,0xbf,0x50,0x57,0xee,0xcf,0xc5,0x65,0xc9,0x67,0x5d,0xcc,0x89,0xbb,0xe3,0x47,0x6f,0x1d,0x99,0xcb,0x72,0x9f,0x7a,0xed,0xf8,0x97,0xea,0x0b,0x0d,0x72,0x9f,0xcc,0xc2,0x6d,0xf1,0x42,0xb9,0x65,0xe7,0xbf,0xae,0x41,0x84,0x9d,0x1f,0xa3,0x05,0x90,0xb9,0xe8,0xca,0x37,0xdf,0xd8,0x9d,0x3a,0xc6,0xd6,0x58,0x80,0x1d,0x01,0xf3,0x25,0x5e,0x0e,0x54,0x3c,0x54,0x9b,0xac,0xe3,0x6f,0x53,0x34,0xba,0x76,0x7b,0xe1,0xac,0x67,0x59,0x05,0xf0,0x94,0xaf,0xea,0xcc,0xf0,0x40,0x80,0x29,0x79,0x23,0x79,0xbb,0xb9,0xe8,0xd2,0x14,0xce,0x1b,0x29,0x07,0x7d,0x6a,0x97,0xa6,0xf3,0xc9,0x4b,0x1e,0xbf,0x3d,0xfd,0x8a,0xbc,0xc4,0xd6,0xcf,0x67,0x19,0xa2,0x31,0xbd,0xe2,0x0d,0xcd,0x4f,0x8f,0x1c,0xd9,0x3a,0x22,0xa0,0xa8,0xdd,0x8c,0x75,0x02,0x5c,0x04,0x61,0xac,0x14,0x36,0x4e,0x2d,0x05,0xb4,0x78,0x2b,0xc6,0xe6,0x21,0x46,0x11,0x86,0xa0,0xea,0xc6,0x26,0x29,0x0d,0x3b,0xd7,0x0e,0x44,0xea,0x6c,0xfb,0x30,0x22,0x5e,0x0a,0xb1,0xfa,0x19,0x04,0x24,0x06,0x7b,0x29,0x9f,0x05,0x36,0xce,0x20,0x9d,0x38,0xbe,0xe8,0xb7,0x47,0x5c,0x8b,0x96,0x7b,0xc8,0x48,0x03,0xf0,0x4f,0xe8,0x91,0xd4,0x2e,0x50,0x5e,0x97,0x47,0xa4,0x2c,0xd6,0x56,0xff,0xf0,0xc0,0x3f,0xe3,0x3b,0x38,0x8b,0xdb,0x47,0x95,0x34,0x5f,0x3d,0x9f,0xdc,0x51,0xed,0xbc,0x00,0x90,0xbc,0xfc,0x9f,0x1c,0x3a,0x87,0x9a,0x9c,0x81,0x5d,0x52,0xe2,0x23,0xa4,0x92,0x75,0xdb,0xbd,0x0b,0xc0,0x85,0x78,0xca,0x29,0xd8,0x8f,0x9d,0x51,0x17,0xe8,0xc2,0xab,0x9b,0xec,0x3b,0x71,0x08,0xff,0x1d,0x44,0x30,0x24,0xbc,0xb8,0x6d,0xfe,0xdd,0x41,0x3c,0xca,0x18,0x36,0x00,0x3c,0x31,0x82,0xbf,0x66,0xef,0x37,0x16,0x24,0x66,0x17,0xf8,0x8f,0x4a,0x14,0xac,0xd8,0x9f,0x1f,0xdd,0x20,0x77,0x41,0x4a,0x91,0xa5,0xbc,0xe1,0x99,0xbb,0x94,0x1b,0x6a,0x89,0x75,0x26,0x6d,0xe4,0x2c,0x69,0xb4,0x36,0x74,0xe3,0x26,0xcd,0x1f,0xa5,0xe5,0x47,0x2f,0x5a,0xe6,0x86,0x71,0xa3,0x14,0xc7,0xb9,0x3b,0xe2,0xfd,0x91,0xef,0x7b,0x36,0xae,0x9d,0xf3,0x10,0xaf,0xe4,0xc4,0xca,0x1d,0xed,0xa6,0x74,0x13,0x7d,0xa3,0xde,0xe0,0x97,0xc2,0x03,0x31,0x4f,0xd7,0xdb,0x95,0x70,0x6b,0x2f,0x9c,0xf8,0x2e,0xc1,0x57,0x4d,0xf0,0xad,0x4f,0x32,0x0f,0x0d,0xd8,0xa9,0x74,0xf7,0xa6,0x94,0x28,0x6f,0x2a,0xfd,0x94,0x62,0xe2,0xd3,0xb6,0x3a,0xc3,0xad,0x38,0x8e,0x0f,0x0f,0x72,0xcf,0x59,0x06,0xf3,0x1f,0xfb,0x50,0x19,0x3b,0x90,0xde,0x01,0xb3,0x0b,0xbc,0x78,0xe1,0x4b,0xed,0xf7,0x3d,0x0d,0x7f,0x58,0x2d,0x69,0x0c,0x53,0xbd,0x8d,0xe3,0x0a,0x65,0x20,0x87,0xb1,0xf1,0x6d,0x30,0x18,0x4e,0xe5,0xf4,0x33,0x2d,0xe5,0x29,0x12,0x19,0xcb,0x35,0xe5,0x33,0xca,0x45,0x90,0xc3,0x88,0xfb,0x00,0xa5,0xc9,0x07,0xd4,0xa4,0xbd,0x0c,0xb6,0xb2,0x4a,0x59,0x21,0x85,0xa4,0x37,0x81,0xc8,0x57,0x12,0x44,0x89,0xe2,0x00,0x7a,0xde,0x39,0x26,0x25,0x90,0x75,0xdf,0x0b,0x01,0x54,0xa5,0x4c,0x0a,0x59,0x8d,0xfc,0xb0,0x7e,0x5b,0x2e,0x3e,0x9f,0xcd,0xd4,0x9a,0x94,0x91,0xfd,0x7e,0xcb,0x5c,0x96,0xc0,0x41,0x54,0xca,0x7a,0x81,0x8e,0x6a,0x38,0x43,0xda,0x79,0x54,0xd5,0xdb,0xda,0x32,0x52,0x94,0x1f,0x6d,0x19,0xfc,0xe6,0x30,0x41,0xc0,0x28,0xe8,0x8e,0xdb,0x4f,0x17,0xf9,0x6f,0xd5,0x86,0x2f,0xe7,0xbc,0x67,0x7e,0x62,0xe5,0x51,0xf7,0xed,0x87,0xd7,0xf6,0x13,0x5d,0xaa,0xdc,0x84,0xc0,0x5a,0x76,0x3b,0xf4,0x59,0x2d,0x68,0xac,0xbe,0xe6,0x87,0xfd,0xaa,0x31,0xc4,0x31,0x4d,0x37,0x16,0xb6,0xfb,0x43,0x94,0x36,0xc6,0x9f,0x5c,0x73,0xf1,0x9b,0xd2,0x18,0xfb,0x48,0x6f,0xfe,0xb3,0xec,0xc2,0xfe,0x65,0xf7,0x10,0xeb,0xc8,0xbd,0xd7,0xd8,0x44,0xbe,0x7b,0x02,0x7f,0x77,0x8f,0x4f,0xd8,0xe7,0x7f,0x61,0xcb,0xf8,0xf9,0x21,0xfb,0xfb,0x01,0xc7,0xcf,0x7f,0x86,0xbf,0xa7,0xbd,0x63,0xf8,0x7b,0xb4,0x87,0xc5,0xf0,0xc3,0xa3,0x8f,0x58,0xed,0xeb,0xed,0xd9,0x97,0x17,0xe5,0x57,0x06,0x2e,0xa7,0x4b,0xbd,0x60,0x30,0xb7,0x8d,0xe3,0x09,0xaf,0x02,0xd8,0xc6,0x24,0xf6,0xad,0xf3,0x93,0x0f,0xe2,0x7e,0x99,0xb7,0x11,0xc0,0x77,0x0d,0x29,0x07,0x02,0x3a,0xf7,0x02,0xda,0xc1,0x40,0xdc,0x1c,0xc5,0x74,0x68,0x01,0x08,0xc3,0x31,0x25,0x23,0xf1,0xe6,0x99,0x55,0xd4,0x00,0xe1,0x12,0x69,0xa7,0xec,0xd2,0xb2,0x78,0x7f,0x1d,0xdd,0x68,0xe9,0xc2,0xd8,0x81,0xfd,0xf2,0xdb,0xd5,0xf2,0x34,0x9e,0xd3,0xe4,0xd3,0x10,0xe1,0x98,0xde,0x86,0x37,0x0a,0xc2,0x80,0x45,0x1e,0xbf,0xa2,0x78,0xd7,0x89,0x1d,0x4f,0xd7,0x25,0xa6,0x18,0xc6,0xaa,0x52,0xc7,0x96,0xcd,0x79,0x85,0x04,0x89,0x76,0x1c,0xfa,0x3e,0x92,0x55,0x54,0x9b,0x65,0xd0,0x5c,0xfe,0x61,0x19,0xb5,0x5e,0xaf,0xf6,0x24,0x6e,0x96,0xdf,0x2b,0x85,0x48,0xae,0x00,0xa7,0x02,0x98,0x7d,0x87,0x4f,0x7d,0x2a,0xdf,0xe2,0x5b,0xf0,0x23,0x00,0xb7,0x5e,0xe2,0xf5,0x3d,0xdf,0x4b,0xef,0xb9,0x61,0x2a,0xdd,0x97,0xe7,0xeb,0x46,0x9e,0xeb,0xd2,0x60,0xa7,0x84,0x47,0x5b,0xfb,0xcc,0x73,0x61,0xb4,0x23,0xde,0xb1,0x0f,0xb9,0x0c,0x28,0x5b,0x7e,0x41,0x77,0x67,0x89,0xe8,0xd3,0xce,0x76,0x43,0xfc,0xd4,0xcf,0x76,0x83,0xff,0x98,0x6c,0x83,0xfd,0xdf,0x0c,0xfc,0x07,0x7d,0x53,0xca,0x86,0x76,0x60,0x00,0x00}; const unsigned int html_content_len = 6845; diff --git a/main/landingpage.html b/main/webpage.html similarity index 97% rename from main/landingpage.html rename to main/webpage.html index e76950d..c83f20e 100644 --- a/main/landingpage.html +++ b/main/webpage.html @@ -333,6 +333,7 @@ black: #2f2f2f let paramTableCreated = false; // Track if param table has been created let pollInterval = null; // Store interval ID for polling let modalResolve = null; + let modalDismissed = false; // Track if out-of-distance modal was dismissed const ge = (id) => document.getElementById(id); @@ -834,6 +835,11 @@ black: #2f2f2f remainingDistInput.value = data.remaining_dist.toFixed(1); } + // Check if we should show the "out of distance" modal + if (!modalDismissed && (data.remaining_dist === undefined || data.remaining_dist === null || data.remaining_dist <= 0)) { + showOutOfDistanceModal(); + } + } @@ -1282,6 +1288,34 @@ black: #2f2f2f } } + async function showOutOfDistanceModal() { + const distance = await modalPrompt( + 'Remaining distance is 0 or less. Would you like to add more distance?', + 'Add Distance (feet)' + ); + + if (distance === null || distance === '') { + // User clicked cancel or entered nothing - mark as dismissed + modalDismissed = true; + return; + } + + const dist = parseFloat(distance); + if (isNaN(dist) || dist <= 0) { + await modalAlert('Invalid distance. Modal dismissed.'); + modalDismissed = true; + return; + } + + // Set the remaining distance input and commit + const remainingDistInput = ge('UX_REM_DIST'); + if (remainingDistInput) { + remainingDistInput.value = dist.toFixed(1); + markChanged(remainingDistInput); + await commitParams(); + } + } + // Start automatic polling function startPolling() { // Initial fetch diff --git a/main/webpage_compile.py b/main/webpage_compile.py index d3188a8..f4bddcd 100644 --- a/main/webpage_compile.py +++ b/main/webpage_compile.py @@ -138,7 +138,7 @@ def print_compression_stats(original_size, minified_size, gzip_size, brotli_size def main(): - input_file = "landingpage.html" + input_file = "webpage.html" # Check if input file exists if not Path(input_file).exists(): diff --git a/main/webpage_gzip.h b/main/webpage_gzip.h index e2fbe63..7d54aa3 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, 0x38, 0xff, 0x5b, 0x69, 0x02, 0xff, 0xed, 0x3d, 0x6b, 0x57, 0xdb, 0x48, + 0x1f, 0x8b, 0x08, 0x00, 0xdb, 0x47, 0x5c, 0x69, 0x02, 0xff, 0xed, 0x3d, 0x6b, 0x57, 0xdb, 0x48, 0xb2, 0x9f, 0x77, 0x7e, 0x45, 0xc3, 0x24, 0x8c, 0x94, 0x08, 0xd9, 0x06, 0x32, 0x0f, 0x1b, 0x99, 0x25, 0xe0, 0x4c, 0x32, 0x09, 0x8f, 0xc3, 0x23, 0x33, 0x7b, 0x59, 0x0e, 0x92, 0xad, 0x36, 0xd6, 0x20, 0x4b, 0x1a, 0x49, 0x86, 0x30, 0xc2, 0xff, 0x7d, 0xab, 0xfa, 0x21, 0xb5, 0x64, 0xd9, 0x98, diff --git a/main/webserver.c b/main/webserver.c index 0caf178..8e1996e 100644 --- a/main/webserver.c +++ b/main/webserver.c @@ -8,6 +8,7 @@ */ #include "cJSON.h" +#include "comms.h" #include "control_fsm.h" #include "endian.h" #include "esp_ota_ops.h" @@ -332,6 +333,9 @@ static esp_err_t log_handler(httpd_req_t *req) { * ... other parameters as direct key-value pairs * } */ +/** + * Unified GET handler - returns complete system status + */ static esp_err_t get_handler(httpd_req_t *req) { ESP_LOGI(TAG, "get_handler"); @@ -340,139 +344,43 @@ static esp_err_t get_handler(httpd_req_t *req) { return ESP_FAIL; } - rtc_reset_shutdown_timer(); - - int head = 0; - - // Start JSON object - head += sprintf(httpBuffer+head, "{"); - head += sprintf(httpBuffer+head, "\"build_version\":\"%s\",", FIRMWARE_VERSION); - head += sprintf(httpBuffer+head, "\"build_date\":\"%s\",", BUILD_DATE); - head += sprintf(httpBuffer+head, "\"time\":%lld,", (long long)rtc_get_s()); - head += sprintf(httpBuffer+head, "\"rtc_set\":%s,", rtc_is_set() ? "true" : "false"); - head += sprintf(httpBuffer+head, "\"state\":%d,", fsm_get_state()); - head += sprintf(httpBuffer+head, "\"voltage\":%.3f,", get_battery_V()); - head += sprintf(httpBuffer+head, "\"remaining_dist\":%.3f,", fsm_get_remaining_distance()); - head += sprintf(httpBuffer+head, "\"next_alarm\":%lld,", rtc_get_next_alarm_s()); - - head += sprintf(httpBuffer+head, "\"msg\":\""); - - switch(fsm_get_state()) { - case STATE_IDLE: - head += sprintf(httpBuffer+head, "IDLE"); - break; - case STATE_UNDO_JACK: - case STATE_UNDO_JACK_START: - head += sprintf(httpBuffer+head, "CANCELLING MOVE"); - break; - default: - head += sprintf(httpBuffer+head, "MOVING..."); - break; - } - - if (fsm_get_remaining_distance()<=0) { - head += sprintf(httpBuffer+head, " | DISTANCE LIMIT HIT"); - } - if (efuse_is_tripped(BRIDGE_AUX)) head += sprintf(httpBuffer+head, " | AUX EFUSE TRIP"); - if (efuse_is_tripped(BRIDGE_JACK)) head += sprintf(httpBuffer+head, " | JACK EFUSE TRIP"); - if (efuse_is_tripped(BRIDGE_DRIVE)) head += sprintf(httpBuffer+head, " | DRIVE EFUSE TRIP"); - if (!rtc_is_set()) { - head += sprintf(httpBuffer+head, " | CLOCK NOT SET"); - } - - // Add parameters metadata object - head += sprintf(httpBuffer+head, "\",\"parameters\":{"); - - // Values array - //head += sprintf(httpBuffer+head, "\"names\":["); - for (param_idx_t i = 0; i < NUM_PARAMS; i++) { - if (i > 0) { - head += sprintf(httpBuffer+head, ","); - } - - head += sprintf(httpBuffer+head, "\"%s\":", get_param_name(i)); - - param_value_t value = get_param_value_t(i); - - switch (get_param_type(i)) { - case PARAM_TYPE_f32: - head += sprintf(httpBuffer+head, "%.4f", value.f32); - break; - case PARAM_TYPE_f64: - head += sprintf(httpBuffer+head, "%.4f", value.f64); - break; - case PARAM_TYPE_i32: - head += sprintf(httpBuffer+head, "%ld", (long)value.i32); - break; - case PARAM_TYPE_i16: - head += sprintf(httpBuffer+head, "%d", value.i16); - break; - case PARAM_TYPE_u32: - head += sprintf(httpBuffer+head, "%lu", (long)value.u32); - break; - case PARAM_TYPE_u16: - head += sprintf(httpBuffer+head, "%u", value.u16); - break; - case PARAM_TYPE_str: - head += sprintf(httpBuffer+head, "\"%s\"", get_param_string(i)); - break; - default: - head += sprintf(httpBuffer+head, "null"); - break; - } - } - /*head += sprintf(httpBuffer+head, "],"); - - // Names array - head += sprintf(httpBuffer+head, "\"names\":["); - for (param_idx_t i = 0; i < NUM_PARAMS; i++) { - if (i > 0) { - head += sprintf(httpBuffer+head, ","); - } - head += sprintf(httpBuffer+head, "\"%s\"", get_param_name(i)); - } - head += sprintf(httpBuffer+head, "],"); - - // Units array - head += sprintf(httpBuffer+head, "\"units\":["); - for (param_idx_t i = 0; i < NUM_PARAMS; i++) { - if (i > 0) { - head += sprintf(httpBuffer+head, ","); - } - head += sprintf(httpBuffer+head, "\"%s\"", get_param_unit(i)); - } - head += sprintf(httpBuffer+head, "]");*/ - - // Close parameters object - head += sprintf(httpBuffer+head, "}"); - - // Close main JSON object - head += sprintf(httpBuffer+head, "}"); - - // Check if buffer might overflow - if (head >= (int)(sizeof(httpBuffer) - 100)) { - ESP_LOGE(TAG, "GET response buffer near overflow"); + // Call unified GET handler + cJSON *response = comms_handle_get(); + if (response == NULL) { + ESP_LOGE(TAG, "Failed to generate GET response"); return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, - "Status data too large"); + "Failed to generate response"); } - + + // Convert to string (not pretty printed for web - save bandwidth) + char *json_str = cJSON_PrintUnformatted(response); + cJSON_Delete(response); + + if (json_str == NULL) { + ESP_LOGE(TAG, "Failed to serialize JSON"); + return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, + "Failed to serialize response"); + } + + // Send response esp_err_t err = httpd_resp_set_type(req, "application/json"); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set response type: %s", esp_err_to_name(err)); + free(json_str); return err; } - err = httpd_resp_send(req, httpBuffer, head); + err = httpd_resp_send(req, json_str, strlen(json_str)); + free(json_str); + if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send status response: %s", esp_err_to_name(err)); + ESP_LOGE(TAG, "Failed to send response: %s", esp_err_to_name(err)); return err; } - err = httpd_resp_set_hdr(req, "Connection", "close"); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to set connection header: %s", esp_err_to_name(err)); - // Continue anyway + ESP_LOGW(TAG, "Failed to set connection header: %s", esp_err_to_name(err)); } return err; @@ -492,6 +400,9 @@ static esp_err_t get_handler(httpd_req_t *req) { * } * } */ +/** + * Unified POST handler - handles commands, parameter updates, time updates + */ static esp_err_t post_handler(httpd_req_t *req) { ESP_LOGI(TAG, "post_handler"); @@ -500,8 +411,6 @@ static esp_err_t post_handler(httpd_req_t *req) { return ESP_FAIL; } - rtc_reset_shutdown_timer(); - // Receive POST data int ret = httpd_req_recv(req, httpBuffer, sizeof(httpBuffer)); if (ret <= 0) { @@ -532,390 +441,63 @@ static esp_err_t post_handler(httpd_req_t *req) { return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); } - bool cmd_executed = false; - bool sleep_requested = false; - bool reboot_requested = false; - const char *error_msg = NULL; - int params_updated = 0; - int params_failed = 0; - - // Process time if present - cJSON *time = cJSON_GetObjectItem(root, "time"); - if (cJSON_IsNumber(time)) { - int64_t new_time = (int64_t)cJSON_GetNumberValue(time); - ESP_LOGI(TAG, "Setting time to %lld", new_time); - rtc_set_s(new_time); - } - - - cJSON *remaining_dist = cJSON_GetObjectItem(root, "remaining_dist"); - if (cJSON_IsNumber(remaining_dist)) { - int64_t new_dist = (int64_t)cJSON_GetNumberValue(remaining_dist); - ESP_LOGI(TAG, "Setting time to %lld", new_dist); - fsm_set_remaining_distance(new_dist); - } - - // WE DO NOT PROCESS STATE. STATE IS A READ-ONLY. - - // Process command if present - cJSON *cmd = cJSON_GetObjectItem(root, "cmd"); - if (cJSON_IsString(cmd)) { - const char *cmd_str = cmd->valuestring; - ESP_LOGI(TAG, "Executing command: %s", cmd_str); - - /* - // Claude made this, wtf? - if (strcmp(cmd_str, "trigger") == 0) { - // Trigger RF433 transmitter - rf_433_transmit(); - cmd_executed = true; - } else */ - if (strcmp(cmd_str, "start") == 0) { - // Start operation - transition FSM to running state - //fsm_set_state(FSM_STATE_RUNNING); - fsm_request(FSM_CMD_START); - cmd_executed = true; - } - else if (strcmp(cmd_str, "stop") == 0) { - // Stop operation - transition FSM to idle state - //fsm_set_state(FSM_STATE_IDLE); - fsm_request(FSM_CMD_STOP); - cmd_executed = true; - } - else if (strcmp(cmd_str, "undo") == 0) { - // Stop operation - transition FSM to idle state - //fsm_set_state(FSM_STATE_IDLE); - fsm_request(FSM_CMD_UNDO); - cmd_executed = true; - } - else if (strcmp(cmd_str, "fwd") == 0) { - pulseOverride(RELAY_A1); pulseOverride(RELAY_A3); - cmd_executed = true; - } - else if (strcmp(cmd_str, "rev") == 0) { - pulseOverride(RELAY_B1); pulseOverride(RELAY_A3); - cmd_executed = true; - } - else if (strcmp(cmd_str, "up") == 0) { - pulseOverride(RELAY_A2); - cmd_executed = true; - } - else if (strcmp(cmd_str, "down") == 0) { - pulseOverride(RELAY_B2); - cmd_executed = true; - } - else if (strcmp(cmd_str, "aux") == 0) { - pulseOverride(RELAY_A3); - cmd_executed = true; - } - else if (strcmp(cmd_str, "reboot") == 0) { - reboot_requested = true; - cmd_executed = true; - } - else if (strcmp(cmd_str, "sleep") == 0) { - sleep_requested = true; - cmd_executed = true; - } - /*else if (strcmp(cmd_str, "rfp") == 0) { - // RF programming command - get channel parameter - cJSON *channel = cJSON_GetObjectItem(root, "channel"); - if (cJSON_IsNumber(channel)) { - int ch = (int)cJSON_GetNumberValue(channel); - ESP_LOGI(TAG, "RF programming channel: %d", ch); - rf_433_learn(ch); - cmd_executed = true; - } else { - ESP_LOGW(TAG, "rfp command missing or invalid channel parameter"); - error_msg = "rfp requires channel parameter"; - } - }*/ - else if (strcmp(cmd_str, "rf_clear_temp") == 0) { - rf_433_clear_temp_keycodes(); - cmd_executed = true; - } - else if (strcmp(cmd_str, "rf_disable") == 0) { - rf_433_disable_controls(); - cmd_executed = true; - } - else if (strcmp(cmd_str, "rf_enable") == 0) { - rf_433_enable_controls(); - cmd_executed = true; - } - else if (strcmp(cmd_str, "rf_learn") == 0) { - // Start learning for a specific channel - cJSON *channel = cJSON_GetObjectItem(root, "channel"); - if (cJSON_IsNumber(channel)) { - int ch = (int)cJSON_GetNumberValue(channel); - if (ch >= 0 && ch < NUM_RF_BUTTONS) { - rf_433_learn_keycode(ch); - cmd_executed = true; - } else if (ch == -1) { - rf_433_cancel_learn_keycode(); - cmd_executed = true; - } else { - error_msg = "Invalid channel number"; - } - } else { - error_msg = "rf_learn requires channel parameter"; - } - } - else if (strcmp(cmd_str, "rf_set_temp") == 0) { - cJSON *index = cJSON_GetObjectItem(root, "index"); - cJSON *code = cJSON_GetObjectItem(root, "code"); - if (cJSON_IsNumber(index) && cJSON_IsNumber(code)) { - int idx = (int)cJSON_GetNumberValue(index); - int32_t rf_code = (int32_t)cJSON_GetNumberValue(code); - rf_433_set_temp_keycode(idx, rf_code); - cmd_executed = true; - } else { - error_msg = "rf_set_temp requires index and code parameters"; - } - } - else if (strcmp(cmd_str, "rf_status") == 0) { - // Return current temp keycodes - cJSON *response = cJSON_CreateObject(); - cJSON *codes_array = cJSON_CreateArray(); - - for (int i = 0; i < 4; i++) { // Only return first 4 for web UI - int32_t code = rf_433_get_temp_keycode(i); - cJSON_AddItemToArray(codes_array, cJSON_CreateNumber(code)); - } - - cJSON_AddItemToObject(response, "codes", codes_array); - - char *json_str = cJSON_Print(response); - cJSON_Delete(response); - cJSON_Delete(root); - - esp_err_t err = httpd_resp_set_type(req, "application/json"); - if (err == ESP_OK) { - err = httpd_resp_send(req, json_str, strlen(json_str)); - } - free(json_str); - return err; - } - - else if (strcmp(cmd_str, "cal_jack_start") == 0) { - fsm_request(FSM_CMD_CALIBRATE_JACK_PREP); - ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_PREP"); - cmd_executed = true; - } - else if (strcmp(cmd_str, "cal_jack_finish") == 0) { - cJSON *i = cJSON_GetObjectItem(root, "amt"); - if (cJSON_IsNumber(i) && i->valuedouble >= 0 && i->valuedouble < 8) { - ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_FINISH"); - fsm_set_cal_val(i->valuedouble); - fsm_request(FSM_CMD_CALIBRATE_JACK_FINISH); - cmd_executed = true; - } else { - error_msg = "cal_jack_finish requires amt parameter (0-8)"; - } - } - else if (strcmp(cmd_str, "cal_drive_start") == 0) { - fsm_request(FSM_CMD_CALIBRATE_DRIVE_PREP); - ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_PREP"); - cmd_executed = true; - } - else if (strcmp(cmd_str, "cal_drive_finish") == 0) { - cJSON *i = cJSON_GetObjectItem(root, "amt"); - if (cJSON_IsNumber(i) && i->valuedouble >= 0 && i->valuedouble < 8) { - ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_FINISH"); - fsm_set_cal_val(i->valuedouble); - fsm_request(FSM_CMD_CALIBRATE_DRIVE_FINISH); - cmd_executed = true; - } else { - error_msg = "cal_drive_finish requires amt parameter (0-8)"; - } - } - - else if (strcmp(cmd_str, "cal_get") == 0) { - ESP_LOGI(TAG, "CAL_GET"); - - // Build JSON response with calibration values - cJSON *response = cJSON_CreateObject(); - cJSON_AddItemToObject(response, "e", cJSON_CreateNumber(fsm_get_cal_e())); - cJSON_AddItemToObject(response, "t", cJSON_CreateNumber(fsm_get_cal_t())); - - char *json_str = cJSON_Print(response); - cJSON_Delete(response); - cJSON_Delete(root); - - esp_err_t err = httpd_resp_set_type(req, "application/json"); - if (err == ESP_OK) { - err = httpd_resp_send(req, json_str, strlen(json_str)); - } - free(json_str); - return err; - } - - else { - ESP_LOGW(TAG, "Unknown command: %s", cmd_str); - error_msg = "Unknown command"; - } - } - - // Process batch parameter updates if present - cJSON *parameters = cJSON_GetObjectItem(root, "parameters"); - if (cJSON_IsObject(parameters)) { - // Process individual parameter updates (direct key-value pairs) - cJSON *item = NULL; - cJSON_ArrayForEach(item, parameters) { - const char *key = item->string; - ESP_LOGW(TAG, "PARAMETER %s", key); - - // Find parameter index by name - param_idx_t param_idx = NUM_PARAMS; - for (param_idx_t i = 0; i < NUM_PARAMS; i++) { - if (strcmp(get_param_name(i), key) == 0) { - param_idx = i; - break; - } - } - - if (param_idx == NUM_PARAMS) { - ESP_LOGW(TAG, "UNKNOWN PARAMETER %s", key); - // Not a known parameter, skip silently - continue; - } - - cJSON *value_json = cJSON_GetObjectItem(parameters, key); - - // Set parameter value based on type - switch (get_param_type(param_idx)) { - case PARAM_TYPE_f32: - if (cJSON_IsNumber(value_json)) { - set_param_value_t(param_idx, - (param_value_t){.f32=value_json->valueint}); - params_updated++; - } else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); } - break; - case PARAM_TYPE_f64: - if (cJSON_IsNumber(value_json)) { - set_param_value_t(param_idx, - (param_value_t){.f64=value_json->valueint}); - params_updated++; - } else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); } - break; - case PARAM_TYPE_i32: - if (cJSON_IsNumber(value_json)) { - set_param_value_t(param_idx, - (param_value_t){.i32=value_json->valueint}); - params_updated++; - } else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); } - break; - case PARAM_TYPE_i16: - if (cJSON_IsNumber(value_json)) { - set_param_value_t(param_idx, - (param_value_t){.i16=value_json->valueint}); - params_updated++; - } else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); } - break; - case PARAM_TYPE_u32: - if (cJSON_IsNumber(value_json)) { - set_param_value_t(param_idx, - (param_value_t){.u32=value_json->valueint}); - params_updated++; - } else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); } - break; - case PARAM_TYPE_u16: - if (cJSON_IsNumber(value_json)) { - set_param_value_t(param_idx, - (param_value_t){.u16=value_json->valueint}); - params_updated++; - } else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); } - break; - case PARAM_TYPE_str: - if (cJSON_IsString(value_json)) { - set_param_string(param_idx, value_json->valuestring); - params_updated++; - } else { ESP_LOGW(TAG, "PARAM TYPE MISMATCH FOR %s", key); } - break; - default: - break; - } - } - - if (params_updated > 0) { - rtc_schedule_next_alarm(); - commit_params(); - } - } - + // Call unified POST handler + cJSON *response = NULL; + esp_err_t err = comms_handle_post(root, &response); cJSON_Delete(root); + if (response == NULL) { + ESP_LOGE(TAG, "Failed to generate POST response"); + return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, + "Failed to generate response"); + } + + // Check for special response flags + cJSON *reboot_flag = cJSON_GetObjectItem(response, "reboot"); + cJSON *sleep_flag = cJSON_GetObjectItem(response, "sleep"); + bool should_reboot = cJSON_IsTrue(reboot_flag); + bool should_sleep = cJSON_IsTrue(sleep_flag); + + // Convert response to string + char *json_str = cJSON_PrintUnformatted(response); + cJSON_Delete(response); + + if (json_str == NULL) { + ESP_LOGE(TAG, "Failed to serialize JSON"); + return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, + "Failed to serialize response"); + } + // Send response - esp_err_t err; - char response[256]; - int response_len; - - if (reboot_requested) { - // Special handling for reboot - response_len = snprintf(response, sizeof(response), - "{\"status\":\"ok\",\"message\":\"Rebooting...\"}"); - - if (response_len > 0 && response_len < (int)sizeof(response)) { - err = httpd_resp_set_type(req, "application/json"); - if (err == ESP_OK) { - err = httpd_resp_send(req, response, response_len); - } - } - - ESP_LOGI(TAG, "Rebooting in 2 seconds..."); - vTaskDelay(pdMS_TO_TICKS(2000)); - esp_restart(); - return ESP_OK; // Never reached - } - - if (sleep_requested) { - // Special handling for sleep - response_len = snprintf(response, sizeof(response), - "{\"status\":\"ok\",\"message\":\"Sleeping...\"}"); - - if (response_len > 0 && response_len < (int)sizeof(response)) { - err = httpd_resp_set_type(req, "application/json"); - if (err == ESP_OK) { - err = httpd_resp_send(req, response, response_len); - } - } - - ESP_LOGI(TAG, "Sleeping in 2 seconds..."); - vTaskDelay(pdMS_TO_TICKS(2000)); - rtc_enter_deep_sleep(); - return ESP_OK; // Never reached - } - - if (error_msg != NULL) { - err = httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, error_msg); - } else { - response_len = snprintf(response, sizeof(response), - "{\"status\":\"ok\",\"params_updated\":%d,\"params_failed\":%d,\"cmd_executed\":%s}", - params_updated, params_failed, cmd_executed ? "true" : "false"); - - if (response_len < 0 || response_len >= (int)sizeof(response)) { - ESP_LOGE(TAG, "Failed to format response"); - return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, - "Internal error"); - } - - err = httpd_resp_set_type(req, "application/json"); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to set response type: %s", esp_err_to_name(err)); - return err; - } - - err = httpd_resp_send(req, response, response_len); + err = httpd_resp_set_type(req, "application/json"); + if (err == ESP_OK) { + err = httpd_resp_send(req, json_str, strlen(json_str)); } + free(json_str); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send response: %s", esp_err_to_name(err)); return err; } + // Handle special actions after response is sent + if (should_reboot) { + ESP_LOGI(TAG, "Rebooting in 2 seconds..."); + vTaskDelay(pdMS_TO_TICKS(2000)); + esp_restart(); + return ESP_OK; // Never reached + } + + if (should_sleep) { + ESP_LOGI(TAG, "Sleeping in 2 seconds..."); + vTaskDelay(pdMS_TO_TICKS(2000)); + rtc_enter_deep_sleep(); + return ESP_OK; // Never reached + } err = httpd_resp_set_hdr(req, "Connection", "close"); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to set connection header: %s", esp_err_to_name(err)); - // Continue anyway + ESP_LOGW(TAG, "Failed to set connection header: %s", esp_err_to_name(err)); } return err; @@ -1412,6 +994,7 @@ static esp_err_t launchSoftAp(void) { esp_err_t webserver_init(void) { ESP_LOGI(TAG, "Initializing webserver..."); + // Initialize comms module esp_err_t err = launchSoftAp(); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to launch SoftAP: %s", esp_err_to_name(err));