From 982ada9787559634b03d394a951cdec8f41da3f4 Mon Sep 17 00:00:00 2001 From: Thaddeus Hughes Date: Tue, 13 Jan 2026 18:35:31 -0600 Subject: [PATCH] Refining logging --- main/comms.c | 6 + main/control_fsm.c | 78 +++--- main/control_fsm.h | 1 + main/i2c.c | 33 +-- main/i2c.h | 1 - main/main.c | 44 ++- main/power_mgmt.c | 6 + main/power_mgmt.h | 1 + main/sensors.c | 55 ++-- main/sensors.h | 16 +- main/storage.c | 654 +++++++++++++++++++++++---------------------- main/storage.h | 18 +- main/webserver.c | 180 ++++++++----- partitions.csv | 8 +- 14 files changed, 594 insertions(+), 507 deletions(-) diff --git a/main/comms.c b/main/comms.c index 57e5e97..d2f46b1 100644 --- a/main/comms.c +++ b/main/comms.c @@ -8,6 +8,7 @@ #include "power_mgmt.h" #include "rf_433.h" #include "rtc.h" +#include "sensors.h" #include "storage.h" #include "version.h" #include @@ -77,6 +78,11 @@ cJSON* comms_handle_get(void) { cJSON_AddItemToArray(msg_array, cJSON_CreateString("CLOCK NOT SET")); } + if (!get_is_safe()) { + cJSON_AddItemToArray(msg_array, cJSON_CreateString("SAFETY SENSOR BREAK")); + } + + cJSON_AddItemToObject(root, "msg", msg_array); // Add parameters object diff --git a/main/control_fsm.c b/main/control_fsm.c index 83a15bb..c6b33b0 100644 --- a/main/control_fsm.c +++ b/main/control_fsm.c @@ -17,6 +17,7 @@ #include "rtc.h" #include "sensors.h" #include "esp_log.h" +#include #define TRANSITION_DELAY_US 1000000 @@ -47,7 +48,7 @@ int64_t override_times[8] = {-1}; int64_t override_cooldown[8] = {-1}; bool enabled = false; - +float this_move_dist = 0.0f; RTC_DATA_ATTR float remaining_distance = 0.0f; float fsm_get_remaining_distance(void) { return remaining_distance; } void fsm_set_remaining_distance(float x) { remaining_distance = x;} @@ -159,8 +160,8 @@ int8_t fsm_get_current_progress(int8_t denominator) { #define JACK_TIME get_param_value_t(PARAM_JACK_KT).f32 * get_param_value_t(PARAM_JACK_DIST ).f32 -#define DRIVE_TIME get_param_value_t(PARAM_DRIVE_KT).f32 * get_param_value_t(PARAM_DRIVE_DIST).f32 -#define DRIVE_DIST get_param_value_t(PARAM_DRIVE_KE).f32 * get_param_value_t(PARAM_DRIVE_DIST).f32 +#define DRIVE_TIME get_param_value_t(PARAM_DRIVE_KT).f32 * this_move_dist +#define DRIVE_DIST get_param_value_t(PARAM_DRIVE_KE).f32 * this_move_dist void control_task(void *param) { esp_task_wdt_add(NULL); @@ -180,34 +181,44 @@ void control_task(void *param) { switch (cmd) { case FSM_CMD_START: + // Check if we have remaining distance before starting + if (remaining_distance <= 0.0f) { + ESP_LOGI(TAG, "FAILED TO START; NO REMAINING DISTANCE"); + error = SC_ERR_LEASH_HIT; + continue; + } + this_move_dist = MIN(get_param_value_t(PARAM_DRIVE_DIST).f32, remaining_distance); + case FSM_CMD_START_IGNORE_OVERTRAVEL: + this_move_dist = get_param_value_t(PARAM_DRIVE_DIST).f32; if (current_state == STATE_IDLE) { - // Check if we have remaining distance before starting - if (remaining_distance <= 0.0f) { - error = SC_ERR_LEASH_HIT; - continue; - } - if (get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) { + if (get_battery_V() < get_param_value_t(PARAM_LOW_PROTECTION_V).f32) { + ESP_LOGI(TAG, "FAILED TO START; INSUFFICIENT VOLTAGE"); error = SC_ERR_LOW_BATTERY; continue; } - if (get_safety_sensor()) { + if (!get_is_safe()) { + ESP_LOGI(TAG, "FAILED TO START; SAFETY NOT SET"); error = SC_ERR_SAFETY_TRIP; continue; } if (efuse_is_tripped(BRIDGE_DRIVE)) { + ESP_LOGI(TAG, "FAILED TO START; EFUSE 1 TRIP"); error = SC_ERR_EFUSE_TRIP_1; continue; } if (efuse_is_tripped(BRIDGE_JACK)) { + ESP_LOGI(TAG, "FAILED TO START; EFUSE 2 TRIP"); error = SC_ERR_EFUSE_TRIP_2; continue; } if (efuse_is_tripped(BRIDGE_AUX)) { + ESP_LOGI(TAG, "FAILED TO START; EFUSE 3 TRIP"); error = SC_ERR_EFUSE_TRIP_3; continue; } + ESP_LOGI(TAG, "STARTING"); error = ESP_OK; // if everything is OK now, we're OK. current_state = STATE_MOVE_START_DELAY; set_timer(TRANSITION_DELAY_US); @@ -337,7 +348,7 @@ void control_task(void *param) { } break; case STATE_MOVE_START_DELAY: - if (!get_safety_sensor()) { + if (!get_is_safe()) { error = SC_ERR_SAFETY_TRIP; current_state = STATE_IDLE; } @@ -348,7 +359,7 @@ void control_task(void *param) { } break; case STATE_JACK_UP_START: - if (!get_safety_sensor()) { + if (!get_is_safe()) { error = SC_ERR_SAFETY_TRIP; current_state = STATE_UNDO_JACK_START; } @@ -380,7 +391,7 @@ void control_task(void *param) { } break; case STATE_JACK_UP: - if (!get_safety_sensor()) { + if (!get_is_safe()) { error = SC_ERR_SAFETY_TRIP; current_state = STATE_UNDO_JACK_START; } @@ -399,7 +410,7 @@ void control_task(void *param) { } break; case STATE_DRIVE_START_DELAY: - if (!get_safety_sensor()) { + if (!get_is_safe()) { error = SC_ERR_SAFETY_TRIP; current_state = STATE_UNDO_JACK_START; } @@ -410,12 +421,10 @@ void control_task(void *param) { set_sensor_counter(SENSOR_DRIVE, -DRIVE_DIST); // Record starting encoder position AFTER setting it move_start_encoder = get_sensor_counter(SENSOR_DRIVE); - ESP_LOGI(TAG, "STATE_DRIVE starting: encoder=%ld, remaining_distance=%.2f, DRIVE_DIST=%.2f", - (long)move_start_encoder, remaining_distance, DRIVE_DIST); } break; case STATE_DRIVE: - if (!get_safety_sensor()) { + if (!get_is_safe()) { error = SC_ERR_SAFETY_TRIP; current_state = STATE_UNDO_JACK_START; } @@ -425,41 +434,22 @@ void control_task(void *param) { float ke = get_param_value_t(PARAM_DRIVE_KE).f32; float distance_traveled = ticks_traveled / ke; - ESP_LOGI(TAG, "STATE_DRIVE: current_encoder=%ld, move_start=%ld, ticks=%ld, ke=%.2f, dist_traveled=%.2f, remaining=%.2f", - (long)current_encoder, (long)move_start_encoder, (long)ticks_traveled, - ke, distance_traveled, remaining_distance); - - // Check if we'll exceed remaining distance with a full move - bool will_exceed = distance_traveled >= remaining_distance; - // Stop if timer expires OR encoder target reached OR we've used up remaining distance - if (timer_done() || current_encoder > 0 || will_exceed) { - ESP_LOGI(TAG, "Drive stopping: timer_done=%d, encoder>0=%d, will_exceed=%d", - timer_done(), current_encoder > 0, will_exceed); - + if (timer_done() || current_encoder > 0) { // Update remaining distance based on actual travel - float old_remaining = remaining_distance; - if (will_exceed) { - ESP_LOGI(TAG, "Move stopped early - reached remaining distance limit (%.2f)", remaining_distance); - remaining_distance = 0.0f; - } else { - remaining_distance -= distance_traveled; - if (remaining_distance < 0.0f) remaining_distance = 0.0f; - } - ESP_LOGI(TAG, "Drive complete: traveled %.2f, old_remaining %.2f, new_remaining %.2f", - distance_traveled, old_remaining, remaining_distance); + //if (current_encoder < 0) + remaining_distance -= this_move_dist; + //else + // remaining_distance -= distance_traveled; current_state = STATE_DRIVE_END_DELAY; set_timer(TRANSITION_DELAY_US); } if (efuse_is_tripped(BRIDGE_DRIVE)) { - float old_remaining = remaining_distance; // Update remaining distance even on fault remaining_distance -= distance_traveled; 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; @@ -467,7 +457,7 @@ void control_task(void *param) { } break; case STATE_DRIVE_END_DELAY: - if (!get_safety_sensor()) { + if (!get_is_safe()) { error = SC_ERR_SAFETY_TRIP; current_state = STATE_UNDO_JACK_START; } @@ -477,7 +467,7 @@ void control_task(void *param) { } break; case STATE_JACK_DOWN: - if (!get_safety_sensor()) { + if (!get_is_safe()) { error = SC_ERR_SAFETY_TRIP; current_state = STATE_UNDO_JACK_START; } @@ -547,7 +537,7 @@ void control_task(void *param) { // no way out of this except a command break; case STATE_CALIBRATE_DRIVE_MOVE: - if (!get_safety_sensor() || timer_done()) { + if (!get_is_safe() || 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 a61a545..c8182e0 100644 --- a/main/control_fsm.h +++ b/main/control_fsm.h @@ -9,6 +9,7 @@ typedef enum { FSM_CMD_START, + FSM_CMD_START_IGNORE_OVERTRAVEL, FSM_CMD_STOP, FSM_CMD_UNDO, FSM_CMD_SHUTDOWN, diff --git a/main/i2c.c b/main/i2c.c index 641fcb9..0bb89a7 100644 --- a/main/i2c.c +++ b/main/i2c.c @@ -6,6 +6,7 @@ #include "esp_log.h" #include "driver/i2c.h" #include "esp_rom_sys.h" +#include "sensors.h" #define I2C_PORT I2C_NUM_0 #define TCA_ADDR_READ 0x21 @@ -28,11 +29,12 @@ #define REPEAT_MS 200 #define REPEAT_START_MS 700 -#define SAFETY_MASK 0b00111111 +#define SAFETY_MASK 0b00110001 // & permissible channels (jack and sensors) +#define SENSOR_EN_MASK 0b00000001 // | need forced high // Static Variables static bool i2c_initted = false; -static bool safety_ok = false; // Safety interlock +//static bool safety_ok = false; // Safety interlock static uint8_t last_relay_request = 0; // Track last relay request // === I2C LOW-LEVEL === @@ -68,35 +70,22 @@ 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 + //safety_ok = false; // Start with safety not OK last_relay_request = 0; return ESP_OK; } -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) { + if (!get_is_safe()) { // 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); + if (states!=0) ESP_LOGW("I2C", "Main relay operation blocked by safety interlock"); + return tca_write_word_8(TCA_REG_OUTPUT1, (states | SENSOR_EN_MASK) & SAFETY_MASK); } - return tca_write_word_8(TCA_REG_OUTPUT1, states); + return tca_write_word_8(TCA_REG_OUTPUT1, states | SENSOR_EN_MASK); } esp_err_t i2c_set_led1(uint8_t state) { @@ -106,8 +95,8 @@ esp_err_t i2c_set_led1(uint8_t state) { esp_err_t i2c_stop() { if (!i2c_initted) return ESP_OK; - i2c_set_relays(0); - i2c_set_led1(0); + tca_write_word_8(TCA_REG_OUTPUT0, 0); + tca_write_word_8(TCA_REG_OUTPUT1, 0); return ESP_OK; } diff --git a/main/i2c.h b/main/i2c.h index 97c105c..b031acf 100644 --- a/main/i2c.h +++ b/main/i2c.h @@ -11,7 +11,6 @@ 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 c61ad7e..35198cc 100644 --- a/main/main.c +++ b/main/main.c @@ -18,10 +18,12 @@ #define TAG "MAIN" int64_t last_log_time = 0; +#define LOGSIZE 40 esp_err_t send_log() { - // >Hqfffflccc - uint8_t entry[32] = {0}; - entry[0] = 32; + uint8_t entry[LOGSIZE] = {}; + + + entry[0] = fsm_get_state(); // Pack 64-bit timestamp into bytes 1-8 uint64_t be_timestamp = rtc_get_ms(); @@ -37,16 +39,29 @@ esp_err_t send_log() { float be_current3 = get_bridge_A(BRIDGE_AUX); memcpy(&entry[21], &be_current3, 4); - int32_t be_counter = get_sensor_counter(SENSOR_DRIVE); - memcpy(&entry[25], &be_counter, 4); + int16_t be_counter = get_sensor_counter(SENSOR_DRIVE); + memcpy(&entry[25], &be_counter, 2); - entry[29] = get_sensor(SENSOR_SAFETY); - entry[30] = get_sensor(SENSOR_DRIVE); - entry[31] = fsm_get_state(); + entry[27] = pack_sensors(); + + + + + float heat1 = get_bridge_heat(BRIDGE_DRIVE); + memcpy(&entry[28], &heat1, 4); + float heat2 = get_bridge_heat(BRIDGE_JACK); + memcpy(&entry[32], &heat2, 4); + float heat3 = get_bridge_heat(BRIDGE_AUX); + memcpy(&entry[36], &heat3, 4); last_log_time = esp_timer_get_time(); - return write_log(LOG_TYPE_DATA, entry, 32); + + log_write(entry, LOGSIZE); + + ESP_LOGI(TAG, "WROTE LOG; %lld / %ld/%ld; %5.2f %5.2f %5.2f", (long long)rtc_get_ms(), (unsigned long)log_get_tail(), (unsigned long)log_get_head(), heat1, heat2, heat3); + + return ESP_OK; } @@ -242,7 +257,7 @@ void app_main(void) { } else if (i2c_get_button_ms(0) > 100){ driveLEDs(LED_STATE_START1); } else { - if ( + /*if ( rtc_is_set() && !efuse_is_tripped(BRIDGE_JACK) && !efuse_is_tripped(BRIDGE_AUX) && @@ -252,11 +267,16 @@ void app_main(void) { driveLEDs(LED_STATE_AWAKE); } else { driveLEDs(LED_STATE_ERROR); - } + }*/ + + int8_t state = 0b001; + if (get_is_safe()) state |= 0b010; + if (get_sensor(SENSOR_SAFETY)) state |= 0b100; + i2c_set_led1(state); } // when not actively moving we log at a low frequency - if (isRunning() || (esp_timer_get_time() > last_log_time + DEEP_SLEEP_US)) + if (isRunning() || (esp_timer_get_time() > last_log_time + 3000000)) //DEEP_SLEEP_US)) send_log(); if(i2c_get_button_ms(0) > 2100) diff --git a/main/power_mgmt.c b/main/power_mgmt.c index b8c9bb2..427eaba 100644 --- a/main/power_mgmt.c +++ b/main/power_mgmt.c @@ -314,6 +314,12 @@ float get_bridge_A(bridge_t bridge) return isens[bridge].current; } + +float get_bridge_heat(bridge_t bridge) { + if (bridge >= N_BRIDGES) return NAN; + return isens[bridge].heat; +} + float get_battery_V(void) { if (ema_battery_init) diff --git a/main/power_mgmt.h b/main/power_mgmt.h index a9dd876..550eeda 100644 --- a/main/power_mgmt.h +++ b/main/power_mgmt.h @@ -19,6 +19,7 @@ bool efuse_is_tripped(bridge_t bridge); // Query if bridge is currently fault float get_bridge_A(bridge_t bridge); float get_battery_V(); +float get_bridge_heat(bridge_t bridge); void set_autozero(bridge_t bridge); diff --git a/main/sensors.c b/main/sensors.c index a5aa288..96a29df 100644 --- a/main/sensors.c +++ b/main/sensors.c @@ -1,12 +1,13 @@ #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 "i2c.h" #include "storage.h" +#include // make the compiler shut up about casting an int to a void #define INT2VOIDP(i) (void*)(uintptr_t)(i) @@ -14,19 +15,22 @@ static const char* TAG = "SENS"; -uint8_t sensor_pins[N_SENSORS] = {GPIO_NUM_27, GPIO_NUM_14}; +uint8_t sensor_pins[N_SENSORS] = {GPIO_NUM_27, GPIO_NUM_14, GPIO_NUM_16, GPIO_NUM_19}; -volatile int32_t sensor_count[N_SENSORS] = {0}; +volatile int16_t sensor_count[N_SENSORS] = {0}; 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; + +static volatile bool last_raw_state[N_SENSORS] = {false}; + // Safety sensor debouncing -static volatile bool safety_tripped = false; +static volatile bool is_safe = true; 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 SAFETY_BREAK_DEBOUNCE_US get_param_value_t(PARAM_SAFETY_BREAK_US).u32 +#define SAFETY_MAKE_DEBOUNCE_US get_param_value_t(PARAM_SAFETY_MAKE_US).u32 #define DEBOUNCE_TIME_US 2000 // 2 ms debounce (adjust per switch) #define DEBOUNCE_TICKS pdMS_TO_TICKS(DEBOUNCE_TIME_MS) @@ -59,7 +63,6 @@ static void sensor_debounce_task(void* param) { esp_task_wdt_add(NULL); sensor_event_t evt; //static uint64_t last_processed_time[N_SENSORS] = {0}; - static bool last_raw_state[N_SENSORS] = {false}; // Initialize stable state for (uint8_t i = 0; i < N_SENSORS; i++) { @@ -113,12 +116,10 @@ static void sensor_debounce_task(void* param) { // 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"); + ESP_LOGI(TAG, "Safety sensor went LOW, starting make timer"); + } else if (!is_safe && (now - safety_low_start_time >= SAFETY_MAKE_DEBOUNCE_US)) { + is_safe = true; + ESP_LOGW(TAG, "SAFETY MADE - Relays enabled"); } } else { // Safety sensor is HIGH (inactive) @@ -126,12 +127,11 @@ static void sensor_debounce_task(void* param) { // 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"); + ESP_LOGI(TAG, "Safety sensor went HIGH, starting break timer"); + } else if (is_safe && (now - safety_high_start_time >= SAFETY_BREAK_DEBOUNCE_US)) { + is_safe = false; + i2c_set_relays(0); + ESP_LOGI(TAG, "SAFETY BREAK - Relays disabled"); } } @@ -139,6 +139,15 @@ static void sensor_debounce_task(void* param) { } } +int8_t pack_sensors() { + int8_t ret = 0; + for(uint8_t i=0; i #include +#include #include "esp_partition.h" #include "esp_err.h" #include "esp_log.h" #include "esp_crc.h" +#include "esp_task_wdt.h" #include "storage.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -11,6 +13,26 @@ #define TAG "STORAGE" +// Add these includes at the top +#include "freertos/queue.h" +#include "freertos/task.h" + +// Add these defines near the top +#define LOG_QUEUE_SIZE 8 // Number of log entries that can be queued +#define LOG_TASK_STACK_SIZE 4096 +#define LOG_TASK_PRIORITY 5 + +// Add these static variables after the existing log variables (around line 100) +static QueueHandle_t log_queue = NULL; +static TaskHandle_t log_task_handle = NULL; +static bool log_task_running = false; + +// Log queue entry structure +typedef struct { + uint8_t data[LOG_MAX_PAYLOAD + 1]; // +1 for length byte + uint8_t len; +} log_queue_entry_t; + // ============================================================================ // LOG TYPE DEFINITIONS (Magic values 0xC0-0xCF) // ============================================================================ @@ -21,7 +43,6 @@ #define LOG_TYPE_SENSOR 0xC4 // Sensor reading #define LOG_TYPE_COMMAND 0xC5 // Command executed #define LOG_TYPE_STATUS 0xC6 // Status update -#define LOG_TYPE_PADDING 0xCE // Padding to sector boundary #define LOG_TYPE_CUSTOM 0xCF // Custom/user-defined // Helper macro to check if a byte is a valid log type @@ -92,28 +113,29 @@ static const esp_partition_t *storage_partition = NULL; // Log head/tail tracking with mutex protection // These now track byte offsets within the log area, not entry indices -static uint32_t log_head_offset = 0; // Offset from LOG_START_OFFSET -static uint32_t log_tail_offset = 0; // Offset from LOG_START_OFFSET -static SemaphoreHandle_t log_mutex = NULL; -static bool log_initialized = false; +RTC_DATA_ATTR static uint32_t log_head_offset = 0; +RTC_DATA_ATTR static uint32_t log_tail_offset = 0; +RTC_DATA_ATTR static bool log_initialized = false; -uint32_t get_log_head(void) { +static SemaphoreHandle_t log_mutex = NULL; + +uint32_t log_get_head(void) { uint32_t head; if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); - head = LOG_START_OFFSET + log_head_offset; + head = log_head_offset; if (log_mutex) xSemaphoreGive(log_mutex); return head; } -uint32_t get_log_tail(void) { +uint32_t log_get_tail(void) { uint32_t tail; if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); - tail = LOG_START_OFFSET + log_tail_offset; + tail = log_tail_offset; if (log_mutex) xSemaphoreGive(log_mutex); return tail; } -uint32_t get_log_offset(void) { +uint32_t log_get_offset(void) { return LOG_START_OFFSET; } @@ -385,6 +407,8 @@ esp_err_t factory_reset(void) { memcpy(¶meter_table[i], ¶meter_defaults[i], sizeof(param_value_t)); } + // TODO: WIPE ENTIRE PARTITION + // Commit defaults to flash esp_err_t err = commit_params(); if (err != ESP_OK) { @@ -402,11 +426,21 @@ esp_err_t factory_reset(void) { esp_err_t storage_init(void) { ESP_LOGI(TAG, "Initializing storage system..."); + + + log_mutex = xSemaphoreCreateMutex(); + if (log_mutex == NULL) { + ESP_LOGE(TAG, "Failed to create log mutex"); + return ESP_ERR_NO_MEM; + } + if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); + storage_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage"); if (storage_partition == NULL) { ESP_LOGE(TAG, "Storage partition not found"); + if (log_mutex) xSemaphoreGive(log_mutex); return ESP_ERR_NOT_FOUND; } @@ -415,7 +449,7 @@ esp_err_t storage_init(void) { // Load parameters from flash uint32_t flash_offset = PARAMS_OFFSET; - bool all_valid = true; + //bool all_valid = true; for (int i = 0; i < NUM_PARAMS; i++) { param_stored_t stored; @@ -427,7 +461,7 @@ esp_err_t storage_init(void) { ESP_LOGW(TAG, "Failed to read parameter %d (%s), using default", i, parameter_names[i]); memcpy(¶meter_table[i], ¶meter_defaults[i], sizeof(param_value_t)); - all_valid = false; + //all_valid = false; flash_offset += sizeof(param_stored_t); continue; } @@ -443,244 +477,120 @@ esp_err_t storage_init(void) { ESP_LOGW(TAG, "Parameter %d (%s) failed CRC check, using default", i, parameter_names[i]); memcpy(¶meter_table[i], ¶meter_defaults[i], sizeof(param_value_t)); - all_valid = false; + //all_valid = false; } flash_offset += sizeof(param_stored_t); } - if (all_valid) { + /*if (all_valid) { ESP_LOGI(TAG, "All parameters loaded successfully from flash"); } else { ESP_LOGW(TAG, "Some parameters failed validation, using defaults"); - } + }*/ + if (log_mutex) xSemaphoreGive(log_mutex); return ESP_OK; } -// ============================================================================ -// VARIABLE-LENGTH LOGGING FUNCTIONS -// ============================================================================ - -/** - * Find the first valid log entry by scanning for magic bytes. - * Returns absolute flash offset, or -1 if no valid entry found. - */ -/*static int32_t find_first_valid_entry(uint32_t start_offset, uint32_t end_offset) { - if (storage_partition == NULL) { - return -1; - } - - uint8_t buffer[256]; - uint32_t scan_pos = start_offset; - - while (scan_pos < end_offset) { - size_t chunk_size = (end_offset - scan_pos) < sizeof(buffer) ? - (end_offset - scan_pos) : sizeof(buffer); - - esp_err_t err = esp_partition_read(storage_partition, scan_pos, buffer, chunk_size); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to read during scan at offset %lu", (unsigned long)scan_pos); - return -1; - } - - // Scan for valid type byte - for (size_t i = 0; i < chunk_size; i++) { - if (IS_VALID_LOG_TYPE(buffer[i])) { - // Found potential entry - verify we can read size byte - if (i + 1 < chunk_size) { - // Size byte is in buffer - return scan_pos + i; - } else if (scan_pos + i + 1 < end_offset) { - // Size byte is in next read - return scan_pos + i; - } - } - } - - // Move to next chunk, with 1-byte overlap to catch split entries - scan_pos += chunk_size - 1; - } - - return -1; -}*/ - -/** - * Initialize the log system by finding head position - */ -esp_err_t log_init(void) { - if (storage_partition == NULL) { - ESP_LOGE(TAG, "Storage partition not initialized, call storage_init() first"); - return ESP_ERR_INVALID_STATE; - } - - log_mutex = xSemaphoreCreateMutex(); - if (log_mutex == NULL) { - ESP_LOGE(TAG, "Failed to create log mutex"); - return ESP_ERR_NO_MEM; - } - - //uint32_t log_area_size = storage_partition->size - LOG_START_OFFSET; - uint32_t log_area_end = storage_partition->size; - - // Scan for first empty (0xFF) byte to find head - uint8_t buffer[256]; - bool found_head = false; - - for (uint32_t offset = LOG_START_OFFSET; offset < log_area_end; offset += sizeof(buffer)) { - size_t to_read = (log_area_end - offset) < sizeof(buffer) ? - (log_area_end - offset) : sizeof(buffer); - - esp_err_t err = esp_partition_read(storage_partition, offset, buffer, to_read); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to read during log init at offset %lu", (unsigned long)offset); - vSemaphoreDelete(log_mutex); - log_mutex = NULL; - return err; - } - - // Look for first 0xFF byte (empty flash) - for (size_t i = 0; i < to_read; i++) { - if (buffer[i] == 0xFF) { - log_head_offset = (offset + i) - LOG_START_OFFSET; - found_head = true; - ESP_LOGI(TAG, "Log head found at offset %lu (absolute: %lu)", - (unsigned long)log_head_offset, - (unsigned long)(LOG_START_OFFSET + log_head_offset)); - break; - } - } - - if (found_head) break; - } - - if (!found_head) { - // Log is completely full, wrap to beginning - log_head_offset = 0; - ESP_LOGI(TAG, "Log is full, wrapping to beginning"); - - // Erase first sector - esp_err_t err = esp_partition_erase_range(storage_partition, LOG_START_OFFSET, - FLASH_SECTOR_SIZE); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to erase first log sector"); - vSemaphoreDelete(log_mutex); - log_mutex = NULL; - return err; - } - } - - // Set tail to start of log area initially (will be updated as sectors are erased) - log_tail_offset = 0; - - log_initialized = true; - ESP_LOGI(TAG, "Log system initialized. Head offset: %lu, Tail offset: %lu", - (unsigned long)log_head_offset, (unsigned long)log_tail_offset); - - return ESP_OK; +static inline uint32_t log_sector_end(uint32_t x) { + return (x/FLASH_SECTOR_SIZE+1)*FLASH_SECTOR_SIZE; } -/** - * Write a variable-length log entry - * @param type Log entry type (0xC0-0xCF range) - * @param data Payload data pointer - * @param size Payload size in bytes (0-255) - */ -/*esp_err_t write_log(char* entry) { - // Legacy interface for compatibility - treat as raw 32-byte entry - // Extract type and size from first two bytes if they look valid - uint8_t type = (uint8_t)entry[0]; - uint8_t size = (uint8_t)entry[1]; - if (!IS_VALID_LOG_TYPE(type)) { - // Old format - use as LOG_TYPE_DATA with 30 bytes - type = LOG_TYPE_DATA; - size = 30; // Assume old 32-byte format minus 2-byte header +// Helper function to check if a sector is erased (all 0xFF) +static bool is_sector_erased(uint32_t sector_offset) { + uint8_t buf[256]; + esp_err_t err = esp_partition_read(storage_partition, sector_offset, buf, 256); + if (err != ESP_OK) return false; + + for (int i = 0; i < 256; i++) { + if (buf[i] != 0xFF) return false; } - - return write_log(type, (const uint8_t*)&entry[2], size); -}*/ + return true; +} -/** - * Write a variable-length log entry (new interface) - */ -esp_err_t write_log(uint8_t type, const uint8_t* data, uint8_t size) { +// Helper function to check if a sector has data (contains non-0xFF, non-0x00 bytes) +static bool sector_has_data(uint32_t sector_offset) { + uint8_t buf[256]; + esp_err_t err = esp_partition_read(storage_partition, sector_offset, buf, 256); + if (err != ESP_OK) return false; + + for (int i = 0; i < 256; i++) { + if (buf[i] != 0xFF && buf[i] != 0x00) return true; + } + return false; +} + +// Replace log_write with this non-blocking version: +esp_err_t log_write(uint8_t* buf, uint8_t len) { if (!log_initialized || storage_partition == NULL) { ESP_LOGE(TAG, "Logging not initialized"); return ESP_FAIL; } - if (!IS_VALID_LOG_TYPE(type)) { - ESP_LOGE(TAG, "Invalid log type: 0x%02X", type); - return ESP_ERR_INVALID_ARG; + if (len > LOG_MAX_PAYLOAD) { + ESP_LOGE(TAG, "Log payload too large: %d bytes (max %d)", len, LOG_MAX_PAYLOAD); + return ESP_ERR_INVALID_SIZE; } - /*if (size > LOG_MAX_PAYLOAD) { - ESP_LOGE(TAG, "Log payload too large: %d bytes (max %d)", size, LOG_MAX_PAYLOAD); + if (log_queue == NULL) { + ESP_LOGE(TAG, "Log queue not initialized"); + return ESP_FAIL; + } + + // Create queue entry + log_queue_entry_t entry; + entry.len = len; + memcpy(entry.data, buf, len); + + // Try to send to queue (non-blocking) + if (xQueueSend(log_queue, &entry, 0) != pdTRUE) { + ESP_LOGW(TAG, "Log queue full, dropping entry"); + return ESP_ERR_NO_MEM; + } + + return ESP_OK; +} + +// The actual blocking write function (called by the task) +static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len) { + if (!log_initialized || storage_partition == NULL) { + ESP_LOGE(TAG, "Logging not initialized"); + return ESP_FAIL; + } + + if (len > LOG_MAX_PAYLOAD) { + ESP_LOGE(TAG, "Log payload too large: %d bytes (max %d)", len, LOG_MAX_PAYLOAD); return ESP_ERR_INVALID_SIZE; - }*/ + } if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); - uint32_t log_area_size = storage_partition->size - LOG_START_OFFSET; - uint32_t log_area_end = storage_partition->size; - uint32_t entry_total_size = LOG_HEADER_SIZE + size; // 2 + payload - - // Calculate absolute offsets - uint32_t abs_head = LOG_START_OFFSET + log_head_offset; - - // Check if entry would cross sector boundary - uint32_t current_sector = abs_head / FLASH_SECTOR_SIZE; - uint32_t entry_end = abs_head + entry_total_size; - uint32_t end_sector = entry_end / FLASH_SECTOR_SIZE; - - if (end_sector != current_sector) { - // Entry would cross sector boundary - write padding - uint32_t bytes_to_sector_end = FLASH_SECTOR_SIZE - (abs_head % FLASH_SECTOR_SIZE); + // check if we will overrun the sector + if (log_head_offset + len+1 >= log_sector_end(log_head_offset)) { + ESP_LOGI(TAG, "WILL OVERRUN (%ld >= %ld)", (long)log_head_offset + len+1, (long)log_sector_end(log_head_offset)); + // zero the rest of sector + char zeros[256] = {0}; + esp_partition_write(storage_partition, + log_head_offset, &zeros, + log_sector_end(log_head_offset)-log_head_offset); - ESP_LOGI(TAG, "Entry would cross sector boundary, padding %lu bytes", - (unsigned long)bytes_to_sector_end); - - // Write padding entry (type + size = 0) - uint8_t pad_entry[2] = {LOG_TYPE_PADDING, 0x00}; - esp_err_t err = esp_partition_write(storage_partition, abs_head, pad_entry, 2); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to write padding: %s", esp_err_to_name(err)); - if (log_mutex) xSemaphoreGive(log_mutex); - return ESP_FAIL; - } - - // Advance head to next sector boundary - log_head_offset += bytes_to_sector_end; - - // Handle wrap-around - if (log_head_offset >= log_area_size) { - log_head_offset = 0; - } - - // Recalculate abs_head for actual entry write - abs_head = LOG_START_OFFSET + log_head_offset; - current_sector = abs_head / FLASH_SECTOR_SIZE; - } - - // Check if we need to erase the next sector before writing - uint32_t next_offset = abs_head + entry_total_size; - if (next_offset >= log_area_end) { - next_offset = LOG_START_OFFSET; // Wrap - } - - uint32_t next_sector = next_offset / FLASH_SECTOR_SIZE; - - if (next_sector != current_sector) { + // set head to next sector, and check for wrap + log_head_offset = log_sector_end(log_head_offset); + if (log_head_offset >= storage_partition->size) + log_head_offset = LOG_START_OFFSET; + // Next write will be in a new sector - check if it needs erasing uint8_t check_byte; - esp_err_t err = esp_partition_read(storage_partition, next_sector * FLASH_SECTOR_SIZE, + esp_err_t err = esp_partition_read(storage_partition, log_head_offset, &check_byte, 1); + // Erase the next sector if (err == ESP_OK && check_byte != 0xFF) { - ESP_LOGI(TAG, "Erasing sector %lu for log", (unsigned long)next_sector); + ESP_LOGI(TAG, "Erasing sector %lu for log", (unsigned long)log_head_offset); err = esp_partition_erase_range(storage_partition, - next_sector * FLASH_SECTOR_SIZE, + log_head_offset, FLASH_SECTOR_SIZE); if (err != ESP_OK) { @@ -689,128 +599,240 @@ esp_err_t write_log(uint8_t type, const uint8_t* data, uint8_t size) { return ESP_FAIL; } - // Update tail - it's now at the start of the sector we just erased - uint32_t new_tail_abs = next_sector * FLASH_SECTOR_SIZE; - if (new_tail_abs < LOG_START_OFFSET) { - new_tail_abs = LOG_START_OFFSET; + } + + // update the tail, if needed + if (log_tail_offset >= log_head_offset + FLASH_SECTOR_SIZE) log_tail_offset = log_head_offset + FLASH_SECTOR_SIZE; + if (log_tail_offset >= storage_partition->size) + log_tail_offset = LOG_START_OFFSET; + + ESP_LOGI(TAG, "Tail/Head are now %lu/%lu", + (unsigned long)log_tail_offset, (unsigned long)log_head_offset); + } + + + esp_partition_write(storage_partition, log_head_offset, &len, 1); + esp_partition_write(storage_partition, log_head_offset+1, buf, len); + + log_head_offset+=len+1; + ESP_LOGI(TAG, "Wrote; Tail/Head are now %lu/%lu", + (unsigned long)log_tail_offset, (unsigned long)log_head_offset); + + if (log_mutex) xSemaphoreGive(log_mutex); + return ESP_OK; +} + +// Log writer task +static void log_writer_task(void *pvParameters) { + log_queue_entry_t entry; + + ESP_LOGI(TAG, "Log writer task started"); + + while (log_task_running) { + // Wait for log entry (with timeout to check running flag) + if (xQueueReceive(log_queue, &entry, pdMS_TO_TICKS(100)) == pdTRUE) { + // Write the log entry (blocking) + esp_err_t err = log_write_blocking(entry.data, entry.len); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to write log entry: %s", esp_err_to_name(err)); } - log_tail_offset = new_tail_abs - LOG_START_OFFSET; - - ESP_LOGI(TAG, "Tail/Head are now %lu/%lu", - (unsigned long)log_tail_offset, (unsigned long)log_head_offset); + } + + // Feed watchdog + esp_task_wdt_reset(); + } + + ESP_LOGI(TAG, "Log writer task stopping"); + vTaskDelete(NULL); +} + +// Modified log_init to create queue and task +esp_err_t log_init() { + if (storage_partition == NULL) { + ESP_LOGE(TAG, "Storage partition not initialized, call storage_init() first"); + return ESP_ERR_INVALID_STATE; + } + + if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); + + uint32_t log_area_size = storage_partition->size - LOG_START_OFFSET; + uint32_t num_sectors = log_area_size / FLASH_SECTOR_SIZE; + + ESP_LOGI(TAG, "Log init: scanning %lu sectors with bisection", (unsigned long)num_sectors); + + // Default to empty log + log_head_offset = LOG_START_OFFSET; + log_tail_offset = LOG_START_OFFSET; + + // ... [INCLUDE THE BISECTION ALGORITHM FROM EARLIER HERE] ... + // Binary search to find the first erased sector + int32_t left = 0; + int32_t right = num_sectors - 1; + int32_t head_sector = -1; + + while (left <= right) { + int32_t mid = left + (right - left) / 2; + uint32_t sector_offset = LOG_START_OFFSET + mid * FLASH_SECTOR_SIZE; + + esp_task_wdt_reset(); + + bool erased = is_sector_erased(sector_offset); + bool prev_has_data = (mid > 0) ? sector_has_data(LOG_START_OFFSET + (mid-1) * FLASH_SECTOR_SIZE) : false; + + if (erased && (mid == 0 || prev_has_data)) { + head_sector = mid; + break; + } else if (erased) { + right = mid - 1; + } else { + left = mid + 1; } } - // Write the actual log entry header + payload - uint8_t header[LOG_HEADER_SIZE] = {type, size}; - - esp_err_t err = esp_partition_write(storage_partition, abs_head, header, LOG_HEADER_SIZE); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to write log header: %s", esp_err_to_name(err)); - if (log_mutex) xSemaphoreGive(log_mutex); - return ESP_FAIL; + if (head_sector == -1) { + if (is_sector_erased(LOG_START_OFFSET)) { + ESP_LOGI(TAG, "Log is empty"); + log_head_offset = LOG_START_OFFSET; + log_tail_offset = LOG_START_OFFSET; + } else { + head_sector = 0; + ESP_LOGW(TAG, "Log appears full, searching from start"); + } } - if (size > 0) { - err = esp_partition_write(storage_partition, abs_head + LOG_HEADER_SIZE, data, size); + // Walk the data structure to find exact head + uint32_t cursor; + if (head_sector > 0) { + cursor = LOG_START_OFFSET + (head_sector - 1) * FLASH_SECTOR_SIZE; + } else { + cursor = LOG_START_OFFSET; + } + + uint32_t head_sector_start = LOG_START_OFFSET + head_sector * FLASH_SECTOR_SIZE; + + bool found_head = false; + while (cursor < head_sector_start + FLASH_SECTOR_SIZE && cursor < storage_partition->size) { + uint8_t buf; + esp_err_t err = esp_partition_read(storage_partition, cursor, &buf, 1); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to write log payload: %s", esp_err_to_name(err)); + ESP_LOGE(TAG, "Failed to read during log init at offset %lu", (unsigned long)cursor); if (log_mutex) xSemaphoreGive(log_mutex); - return ESP_FAIL; + return err; + } + + if (buf == 0xFF) { + log_head_offset = cursor; + found_head = true; + break; + } else if (buf == 0x00) { + cursor = log_sector_end(cursor); + } else { + cursor += buf + 1; + } + + esp_task_wdt_reset(); + } + + if (!found_head) { + log_head_offset = cursor; + } + + // Find tail + int32_t tail_sector = -1; + + for (int32_t i = head_sector + 1; i < num_sectors; i++) { + uint32_t sector_offset = LOG_START_OFFSET + i * FLASH_SECTOR_SIZE; + esp_task_wdt_reset(); + + if (sector_has_data(sector_offset)) { + tail_sector = i; + break; } } - // Advance head - log_head_offset += entry_total_size; - if (log_head_offset >= log_area_size) { - log_head_offset -= log_area_size; // Wrap around + if (tail_sector == -1 && head_sector > 0) { + for (int32_t i = 0; i < head_sector; i++) { + uint32_t sector_offset = LOG_START_OFFSET + i * FLASH_SECTOR_SIZE; + esp_task_wdt_reset(); + + if (sector_has_data(sector_offset)) { + tail_sector = i; + break; + } + } } + if (tail_sector >= 0) { + log_tail_offset = LOG_START_OFFSET + tail_sector * FLASH_SECTOR_SIZE; + } else { + log_tail_offset = LOG_START_OFFSET; + } + + log_initialized = true; + ESP_LOGI(TAG, "Log system initialized (bisection). Head: %lu, Tail: %lu", + (unsigned long)log_head_offset, (unsigned long)log_tail_offset); + + // Create the log queue + log_queue = xQueueCreate(LOG_QUEUE_SIZE, sizeof(log_queue_entry_t)); + if (log_queue == NULL) { + ESP_LOGE(TAG, "Failed to create log queue"); + if (log_mutex) xSemaphoreGive(log_mutex); + return ESP_ERR_NO_MEM; + } + + // Create the log writer task + log_task_running = true; + BaseType_t task_created = xTaskCreate( + log_writer_task, + "log_writer", + LOG_TASK_STACK_SIZE, + NULL, + LOG_TASK_PRIORITY, + &log_task_handle + ); + + if (task_created != pdPASS) { + ESP_LOGE(TAG, "Failed to create log writer task"); + vQueueDelete(log_queue); + log_queue = NULL; + if (log_mutex) xSemaphoreGive(log_mutex); + return ESP_ERR_NO_MEM; + } + + // Add the task to watchdog + esp_task_wdt_add(log_task_handle); + + ESP_LOGI(TAG, "Log writer task created successfully"); + if (log_mutex) xSemaphoreGive(log_mutex); return ESP_OK; } -// ============================================================================ -// TEST FUNCTIONS FOR VARIABLE-LENGTH LOGS -// ============================================================================ - -esp_err_t write_dummy_log_1(void) { - ESP_LOGI(TAG, "Writing dummy variable-length log pattern 1"); - - if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); - log_head_offset = 0; - log_tail_offset = 0; - if (log_mutex) xSemaphoreGive(log_mutex); - - // Write varied-size entries - for (uint32_t i = 0; i < 100; i++) { - uint8_t size = (i % 3 == 0) ? 16 : (i % 3 == 1) ? 48 : 32; - uint8_t data[256]; - - // Fill with pattern - for (uint8_t j = 0; j < size; j++) { - data[j] = (i + j) & 0xFF; - } - - uint8_t type = LOG_TYPE_DATA + (i % 4); // Vary types - write_log(type, data, size); - - if (i % 10 == 0) { - ESP_LOGI(TAG, "Wrote entry %lu (type=0x%02X, size=%d)", - (unsigned long)i, type, size); - } - } - - return ESP_OK; -} - -esp_err_t write_dummy_log_2(void) { - ESP_LOGI(TAG, "Writing dummy variable-length log pattern 2"); - - if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); - log_head_offset = 1000; - log_tail_offset = 5000; - if (log_mutex) xSemaphoreGive(log_mutex); - - for (uint32_t i = 0; i < 50; i++) { - uint8_t size = 10 + (i * 3) % 200; // Varied sizes - uint8_t data[256]; - memset(data, 0xAA + i, size); - - write_log(LOG_TYPE_SENSOR, data, size); - } - - return ESP_OK; -} - -esp_err_t write_dummy_log_3(void) { - ESP_LOGI(TAG, "Writing dummy variable-length log pattern 3"); - - if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY); - log_head_offset = 8000; - log_tail_offset = 2000; - if (log_mutex) xSemaphoreGive(log_mutex); - - // Write maximum-size entries - for (uint32_t i = 0; i < 20; i++) { - uint8_t data[LOG_MAX_PAYLOAD]; - for (uint16_t j = 0; j < LOG_MAX_PAYLOAD; j++) { - data[j] = (i * 17 + j) & 0xFF; - } - - write_log(LOG_TYPE_DEBUG, data, LOG_MAX_PAYLOAD); - - ESP_LOGI(TAG, "Wrote max-size entry %lu", (unsigned long)i); - } - - return ESP_OK; -} - +// Update storage_deinit to stop the task void storage_deinit(void) { + // Stop the log writer task + if (log_task_running) { + log_task_running = false; + + // Wait a bit for task to finish + vTaskDelay(pdMS_TO_TICKS(200)); + + if (log_task_handle != NULL) { + esp_task_wdt_delete(log_task_handle); + log_task_handle = NULL; + } + } + + // Delete the queue + if (log_queue != NULL) { + vQueueDelete(log_queue); + log_queue = NULL; + } + storage_partition = NULL; log_initialized = false; if (log_mutex) { vSemaphoreDelete(log_mutex); log_mutex = NULL; } -} \ No newline at end of file +} diff --git a/main/storage.h b/main/storage.h index 398e2c0..f5c925f 100644 --- a/main/storage.h +++ b/main/storage.h @@ -9,7 +9,8 @@ // ============================================================================ #define FLASH_SECTOR_SIZE 4096 #define PARAMS_OFFSET 0 -#define LOG_START_OFFSET 4096 // Start after first sector (parameters) +#define PARAMETER_NUM_SECTORS 4 +#define LOG_START_OFFSET FLASH_SECTOR_SIZE*PARAMETER_NUM_SECTORS // Start after first sector (parameters) // ============================================================================ // LOG ENTRY TYPE DEFINITIONS (Magic values 0xC0-0xCF) @@ -40,7 +41,6 @@ // [1]: Payload size (0-255 bytes) // [2-N]: Payload data typedef struct { - uint8_t type; // Type/Magic byte (0xC0-0xCF range) uint8_t size; // Payload size in bytes (0-255) uint8_t data[]; // Flexible array member for payload } __attribute__((packed)) log_entry_header_t; @@ -51,7 +51,6 @@ typedef struct { // PARAMETER SYSTEM // ============================================================================ -#define PARAMETER_NUM_SECTORS 4 #define PARAM_LIST \ PARAM_DEF(BOOT_TIME, i32, 0, "us") \ @@ -97,7 +96,8 @@ typedef struct { 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, "") \ + PARAM_DEF(SAFETY_BREAK_US, u32, 300000, "") \ + PARAM_DEF(SAFETY_MAKE_US, u32, 1000000, "") \ // Generate enum for parameter indices #define PARAM_DEF(name, type, default_val, unit) PARAM_##name, @@ -163,11 +163,11 @@ esp_err_t commit_params(void); // Logging functions esp_err_t log_init(void); -esp_err_t write_log(uint8_t type, const uint8_t* data, uint8_t size); -uint32_t get_log_head(void); -uint32_t get_log_tail(void); -uint32_t get_log_offset(void); -uint32_t get_log_size(void); +esp_err_t log_write(uint8_t* buf, uint8_t len); +uint32_t log_get_head(void); +uint32_t log_get_tail(void); +uint32_t log_get_offset(void); +uint32_t log_get_size(void); esp_err_t factory_reset(); diff --git a/main/webserver.c b/main/webserver.c index e915e5d..7f396e9 100644 --- a/main/webserver.c +++ b/main/webserver.c @@ -99,9 +99,9 @@ static esp_err_t root_get_handler(httpd_req_t *req) { // Cache the storage partition pointer to avoid repeated lookups static const esp_partition_t *cached_storage_partition = NULL; +// In webserver.c - Replace the log_handler function + static esp_err_t log_handler(httpd_req_t *req) { - //ESP_LOGI(TAG, "log_handler"); - if (req == NULL) { ESP_LOGE(TAG, "Null request pointer"); return ESP_FAIL; @@ -112,7 +112,7 @@ static esp_err_t log_handler(httpd_req_t *req) { int32_t tail = -1; if (req->method == HTTP_GET) { - // give the whole log + // GET without parameters - return JSON + full log } else if (req->method == HTTP_POST) { int ret = httpd_req_recv(req, httpBuffer, sizeof(httpBuffer)); @@ -130,12 +130,9 @@ static esp_err_t log_handler(httpd_req_t *req) { return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Request too large"); } - httpBuffer[ret] = '\0'; // Null-terminate the string + httpBuffer[ret] = '\0'; - //ESP_LOGI(TAG, "LOG POST %.*s", ret, httpBuffer); - if(sscanf(httpBuffer, "%ld", (long*)&tail) != 1) { - // if malformed, just send the whole log. ESP_LOGW(TAG, "Malformed tail parameter, using default"); tail = -1; } @@ -145,10 +142,8 @@ static esp_err_t log_handler(httpd_req_t *req) { return httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED, "Method not allowed"); } - // Use cached partition pointer instead of looking it up each time const esp_partition_t *storage_partition = cached_storage_partition; if (storage_partition == NULL) { - // Fall back to lookup if cache is empty (shouldn't happen in normal operation) storage_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage"); @@ -160,69 +155,102 @@ static esp_err_t log_handler(httpd_req_t *req) { cached_storage_partition = storage_partition; } - // Get head and tail atomically and store in local variables - // This releases the mutex before we do any partition operations - int32_t head = get_log_head(); - int32_t log_start = get_log_offset(); + int32_t head = log_get_head(); + int32_t log_start = log_get_offset(); if (tail < 0) { - tail = get_log_tail(); + tail = log_get_tail(); } else { - // Validate tail is within log area bounds if (tail < log_start || tail >= (int32_t)storage_partition->size) { ESP_LOGW(TAG, "Invalid tail pointer %ld, using current tail", (long)tail); - tail = get_log_tail(); + tail = log_get_tail(); } } - // Calculate total size to send - int32_t total_size; + // Calculate log data size + int32_t log_data_size; if (tail == head) { - // Empty log - just send pointers - total_size = 8; + log_data_size = 0; } else if (tail < head) { - // Normal case: tail before head - total_size = head - tail + 8; // +8 for head/tail pointers + log_data_size = head - tail; } else { - // Wrapped case: tail after head - total_size = (storage_partition->size - tail) + (head - log_start) + 8; + log_data_size = (storage_partition->size - tail) + (head - log_start); } - ESP_LOGI(TAG, "Log request: tail=%ld, head=%ld, total_size=%ld", - (long)tail, (long)head, (long)total_size); + // Generate JSON header (same as /get endpoint) + cJSON *json_response = comms_handle_get(); + if (json_response == NULL) { + ESP_LOGE(TAG, "Failed to generate JSON response"); + return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, + "Failed to generate response"); + } + + char *json_str = cJSON_PrintUnformatted(json_response); + cJSON_Delete(json_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"); + } + + uint32_t json_len = strlen(json_str); + + // Total size: 4 (json length) + json + 8 (head/tail) + log_data + uint32_t total_size = 4 + json_len + 8 + log_data_size; + + ESP_LOGI(TAG, "Log request: tail=%ld, head=%ld, json_len=%lu, log_size=%ld, total=%lu", + (long)tail, (long)head, (unsigned long)json_len, (long)log_data_size, + (unsigned long)total_size); // Send HTTP headers char len_str[16]; - int written = snprintf(len_str, sizeof(len_str), "%u", (unsigned)total_size); - if (written < 0 || written >= (int)sizeof(len_str)) { - ESP_LOGE(TAG, "Failed to format content length"); - return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, - "Internal error formatting response"); - } + snprintf(len_str, sizeof(len_str), "%u", (unsigned)total_size); esp_err_t err = httpd_resp_set_type(req, "application/octet-stream"); 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_set_hdr(req, "Content-Disposition", "attachment; filename=\"sc_storage.bin\""); + err = httpd_resp_set_hdr(req, "Content-Disposition", "attachment; filename=\"sc_log.bin\""); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set content disposition: %s", esp_err_to_name(err)); + free(json_str); return err; } err = httpd_resp_set_hdr(req, "Content-Length", len_str); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set content length: %s", esp_err_to_name(err)); + free(json_str); return err; } - // Send head/tail pointers in big-endian format + // Send JSON length (4 bytes, big-endian) + uint32_t json_len_be = htobe32(json_len); + memcpy(&httpBuffer[0], &json_len_be, 4); + err = httpd_resp_send_chunk(req, (const char *)httpBuffer, 4); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send JSON length: %s", esp_err_to_name(err)); + free(json_str); + return err; + } + + // Send JSON string + err = httpd_resp_send_chunk(req, json_str, json_len); + free(json_str); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send JSON: %s", esp_err_to_name(err)); + return err; + } + + // Send head/tail pointers (8 bytes, big-endian) int32_t htail = htobe32(tail); int32_t hhead = htobe32(head); - memcpy(&httpBuffer[0], &(htail), 4); - memcpy(&httpBuffer[4], &(hhead), 4); + memcpy(&httpBuffer[0], &htail, 4); + memcpy(&httpBuffer[4], &hhead, 4); err = httpd_resp_send_chunk(req, (const char *)httpBuffer, 8); if (err != ESP_OK) { @@ -230,43 +258,58 @@ static esp_err_t log_handler(httpd_req_t *req) { return err; } + // Send log data (same as before) int32_t offset = tail; - // Only send data if there's something to send - if (tail != head) { - // Handle wrapped case: send from tail to end of partition first - if (tail > head) { - //ESP_LOGI(TAG, "Wrapped log: sending tail=%ld to partition_end=%lu", - // (long)tail, (unsigned long)storage_partition->size); - - while (offset < (int32_t)storage_partition->size) { - size_t to_read = MIN(sizeof(httpBuffer), storage_partition->size - offset); - err = esp_partition_read(storage_partition, offset, httpBuffer, to_read); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to read partition at offset %ld: %s", - (long)offset, esp_err_to_name(err)); - return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, - "Failed to read storage"); - } - - err = httpd_resp_send_chunk(req, (const char *)httpBuffer, to_read); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send chunk at offset %ld: %s", - (long)offset, esp_err_to_name(err)); - return err; - } - - offset += to_read; + if (tail == head) { + // Empty log, nothing more to send + } + else if (tail < head) { + // Normal case: tail before head + while (offset < head) { + size_t to_read = MIN(sizeof(httpBuffer), head - offset); + err = esp_partition_read(storage_partition, offset, httpBuffer, to_read); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to read partition at offset %ld: %s", + (long)offset, esp_err_to_name(err)); + return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, + "Failed to read storage"); } - // Wrap to beginning of log area - offset = log_start; - //ESP_LOGI(TAG, "Wrapped to log start, offset=%ld", (long)offset); + err = httpd_resp_send_chunk(req, (const char *)httpBuffer, to_read); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send chunk at offset %ld: %s", + (long)offset, esp_err_to_name(err)); + return err; + } + + offset += to_read; + } + } + else { + // Wrapped case: tail after head, read from tail to end, then start to head + while (offset < (int32_t)storage_partition->size) { + size_t to_read = MIN(sizeof(httpBuffer), storage_partition->size - offset); + err = esp_partition_read(storage_partition, offset, httpBuffer, to_read); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to read partition at offset %ld: %s", + (long)offset, esp_err_to_name(err)); + return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, + "Failed to read storage"); + } + + err = httpd_resp_send_chunk(req, (const char *)httpBuffer, to_read); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send chunk at offset %ld: %s", + (long)offset, esp_err_to_name(err)); + return err; + } + + offset += to_read; } - // Send from current offset to head - //ESP_LOGI(TAG, "Sending final section: offset=%ld to head=%ld", (long)offset, (long)head); - + // Now read from start to head + offset = log_start; while (offset < head) { size_t to_read = MIN(sizeof(httpBuffer), head - offset); err = esp_partition_read(storage_partition, offset, httpBuffer, to_read); @@ -295,12 +338,9 @@ static esp_err_t log_handler(httpd_req_t *req) { return err; } - //ESP_LOGI(TAG, "Successfully sent log data"); - 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 } return err; diff --git a/partitions.csv b/partitions.csv index 125a814..72c2131 100644 --- a/partitions.csv +++ b/partitions.csv @@ -1,8 +1,8 @@ -# ESP32 Partition Table - 8MB Flash with OTA Support +# ESP32 Partition Table - 8MB (0x800000) Flash with OTA Support # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x4000, otadata, data, ota, 0xd000, 0x2000, phy_init, data, phy, 0xf000, 0x1000, -ota_0, app, ota_0, 0x10000, 1536K, -ota_1, app, ota_1, 0x290000, 1536K, -storage, data, 0x40, 0x410000, 32K, \ No newline at end of file +ota_0, app, ota_0, 0x10000, 1280K, +ota_1, app, ota_1, 0x150000, 1280K, +storage, data, 0x40, 0x290000, 5568K, \ No newline at end of file