storage partition overhaul

This commit is contained in:
Thaddeus Hughes
2026-03-12 19:12:42 -05:00
parent 18faa5b83d
commit 59e7071023
116 changed files with 18243 additions and 257 deletions

View File

@@ -4,7 +4,7 @@
include(${CMAKE_CURRENT_LIST_DIR}/version.cmake)
idf_component_register(
SRCS main.c log_test.c i2c.c rtc.c storage.c uart_comms.c control_fsm.c power_mgmt.c rf_433.c rtc.c sensors.c solar.c webserver.c simple_dns_server.c comms.c bt_hid.c # list the source files of this component
SRCS main.c log_test.c partition_test.c i2c.c rtc.c storage.c uart_comms.c control_fsm.c power_mgmt.c rf_433.c rtc.c sensors.c solar.c webserver.c simple_dns_server.c comms.c bt_hid.c # list the source files of this component
INCLUDE_DIRS "." "${CMAKE_BINARY_DIR}"
PRIV_INCLUDE_DIRS # optional, add here private include directories

View File

@@ -52,6 +52,18 @@ esp_err_t i2c_init(void) {
return ESP_OK;
}
esp_err_t i2c_post(void) {
// Verify TCA9555 responds by reading input port 0
uint16_t val = 0;
esp_err_t err = tca_read_word(TCA_REG_INPUT0, &val);
if (err != ESP_OK) {
ESP_LOGE("I2C", "POST: TCA9555 read failed: %s", esp_err_to_name(err));
return err;
}
ESP_LOGI("I2C", "POST: TCA9555 OK (port0=0x%04X)", val);
return ESP_OK;
}
esp_err_t i2c_set_relays(relay_port_t states) {
return tca_write_word_8(TCA_REG_OUTPUT1, states.raw);
}

View File

@@ -49,6 +49,7 @@ typedef union {
// Public Functions
esp_err_t i2c_init(void);
esp_err_t i2c_post(void);
esp_err_t i2c_stop(void);
esp_err_t i2c_set_relays(relay_port_t states);

View File

@@ -2,6 +2,7 @@
#include "esp_system.h"
#include "i2c.h"
#include "log_test.h"
#include "partition_test.h"
#include "storage.h"
#include "uart_comms.h"
#include "esp_err.h"
@@ -20,6 +21,23 @@
#define TAG "MAIN"
#define POST_MAX_RETRIES 3
// Try an init function up to POST_MAX_RETRIES times. On final failure, reboot.
// Critical inits (ADC, I2C, storage, FSM, sensors) use this — a permanent failure
// feeds the OTA rollback reset counter via the panic→reboot path.
static void init_critical(const char *name, esp_err_t (*fn)(void)) {
for (int attempt = 1; attempt <= POST_MAX_RETRIES; attempt++) {
esp_err_t err = fn();
if (err == ESP_OK) return;
ESP_LOGE(TAG, "%s FAILED (attempt %d/%d): %s", name, attempt, POST_MAX_RETRIES, esp_err_to_name(err));
if (attempt < POST_MAX_RETRIES) vTaskDelay(pdMS_TO_TICKS(100));
}
ESP_LOGE(TAG, "%s FAILED after %d attempts — rebooting", name, POST_MAX_RETRIES);
vTaskDelay(pdMS_TO_TICKS(500));
esp_restart();
}
int64_t last_bat_log_time = 0;
esp_err_t send_bat_log() {
if(!rtc_is_set()) return ESP_OK;
@@ -103,27 +121,21 @@ void drive_leds(led_state_t state) {
void app_main(void) {esp_task_wdt_add(NULL);
//run_all_log_tests();
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);
// TODO: Check wdt stuff
// TODO: Stack Overflow Detection
// TODO: Remove XTAL crystal stuff
// TODO: Confirm whether external RTC crystal can be dropped (see TODO.md #13)
if (rtc_xtal_init() != ESP_OK) ESP_LOGE(TAG, "RTC FAILED");
rtc_restore_time(); // Recover time from RTC domain if we crashed
// Say hello; turn on the lights
rtc_wakeup_cause(); // log wakeup cause (informational only) // TODO: Shouldnt be needed anymore
if (i2c_init() != ESP_OK) ESP_LOGE(TAG, "I2C FAILED");
// Critical inits — retry up to 3 times, then reboot (feeds OTA rollback counter)
init_critical("I2C", i2c_init);
i2c_post(); // verify TCA9555 responds
i2c_set_relays((relay_port_t){.raw=0});
drive_leds(LED_STATE_BOOTING);
// TODO: How many tasks do we have?
// Check for factory reset condition: Cold boot (power-on/ext-reset) + button held
esp_reset_reason_t boot_reset_reason = esp_reset_reason();
@@ -170,13 +182,17 @@ void app_main(void) {esp_task_wdt_add(NULL);
esp_restart();
}
// Every boot we load parameters and monitor solar, no matter what
// TODO: Do things with errors (put in real log? then reset. "assert with LOGE"?)
if (adc_init() != ESP_OK) ESP_LOGE(TAG, "ADC FAILED");
if (storage_init() != ESP_OK) ESP_LOGE(TAG, "STORAGE FAILED");
if (log_init() != ESP_OK) ESP_LOGE(TAG, "LOG FAILED");
// TODO: figure out how long logging takes (for reference, and comp to wdt)
// Critical inits — retry up to 3 times, then reboot
init_critical("ADC", adc_init);
init_critical("STORAGE", storage_init);
init_critical("LOG", log_init);
// POST checks — verify hardware is responding correctly
adc_post(); // ADC channels readable and not frozen
storage_post(); // flash write-read-verify on test sector
//run_all_log_tests();
esp_reset_reason_t reset_reason = esp_reset_reason();
esp_sleep_wakeup_cause_t wake_cause = esp_sleep_get_wakeup_cause();
@@ -189,9 +205,7 @@ void app_main(void) {esp_task_wdt_add(NULL);
log_write(boot_entry, sizeof(boot_entry), LOG_TYPE_BOOT);
}
// TODO: make sure that this is "crash proof"
// TODO: OTA rollback (triggered how? preferably with hardware... or if there are 5 resets in a row [check bootloader?]. also need way to nuke the storage partition or safe boot)
// TODO: (maybe) recovery partition that allows uploading firmware
// TODO: OTA rollback counter (see TODO.md #3)
// Write a crash log entry if we rebooted unexpectedly
if (reset_reason == ESP_RST_PANIC ||
reset_reason == ESP_RST_INT_WDT ||
@@ -205,31 +219,19 @@ void app_main(void) {esp_task_wdt_add(NULL);
log_write(crash_entry, sizeof(crash_entry), LOG_TYPE_CRASH);
}
// TODO: is this reasonable now that we eliminated deep sleep?
if (solar_run_fsm() != ESP_OK) ESP_LOGE(TAG, "SOLAR FAILED");
// TODO: Do a 12V check and enter deep sleep if there's a problem
send_bat_log();
// TODO: test strategy!!! (software verification, and unit bringup)
// TODO: A->D bringup; sanity check (sum up all inputs, wait 5ms, sum again, make sure there is a change (not frozen))
// TODO: make sure sdkconfig is sane. Make notes, have claude figure this out properly
// TODO: fix managed_components
//send_log();
//write_dummy_log_1();
/*** FULL BOOT — always, every boot ***/
if (uart_init() != ESP_OK) ESP_LOGE(TAG, "UART FAILED");
//if (power_init() != ESP_OK) ESP_LOGE(TAG, "POWER FAILED");
// TODO: Seriously, log all the errors on bluetooth
if (rf_433_init() != ESP_OK) ESP_LOGE(TAG, "RF FAILED");
if (bt_hid_init() != ESP_OK) ESP_LOGE(TAG, "BT HID FAILED");
if (fsm_init() != ESP_OK) ESP_LOGE(TAG, "FSM FAILED");
//if (sensors_init() != ESP_OK) ESP_LOGE(TAG, "SENSORS FAILED"); // TODO: Why is this off?
/*** FULL BOOT ***/
// Critical — must succeed or reboot
init_critical("UART", uart_init);
init_critical("FSM", fsm_init);
// sensors_init() is called inside control_task() — see control_fsm.c:185
// Non-critical — log error but continue booting
if (rf_433_init() != ESP_OK) ESP_LOGE(TAG, "RF FAILED");
if (bt_hid_init() != ESP_OK) ESP_LOGE(TAG, "BT HID FAILED");
if (webserver_init() != ESP_OK) ESP_LOGE(TAG, "WEBSERVER FAILED");
/*** MAIN LOOP ***/

467
main/partition_test.c Normal file
View File

@@ -0,0 +1,467 @@
#include "partition_test.h"
#include "storage.h"
#include "esp_partition.h"
#include "esp_log.h"
#include "esp_task_wdt.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#define TAG "PART_TEST"
// ============================================================================
// Test 1: Params partition read/write
// ============================================================================
bool test_params_partition_rw(void) {
ESP_LOGI(TAG, "=== Test: params partition read/write ===");
const esp_partition_t *part = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "params");
if (part == NULL) {
ESP_LOGE(TAG, "FAIL: params partition not found");
return false;
}
ESP_LOGI(TAG, "params partition: offset=0x%lx size=%lu",
(unsigned long)part->address, (unsigned long)part->size);
// Erase first sector
esp_err_t err = esp_partition_erase_range(part, 0, FLASH_SECTOR_SIZE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: erase failed: %s", esp_err_to_name(err));
return false;
}
// Write test pattern
uint8_t write_buf[32];
for (int i = 0; i < 32; i++) write_buf[i] = (uint8_t)(0xAA ^ i);
err = esp_partition_write(part, 0, write_buf, sizeof(write_buf));
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: write failed: %s", esp_err_to_name(err));
return false;
}
// Read back and verify
uint8_t read_buf[32];
err = esp_partition_read(part, 0, read_buf, sizeof(read_buf));
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: read failed: %s", esp_err_to_name(err));
return false;
}
if (memcmp(write_buf, read_buf, sizeof(write_buf)) != 0) {
ESP_LOGE(TAG, "FAIL: data mismatch");
return false;
}
// Verify erased area reads 0xFF
uint8_t erased_check;
err = esp_partition_read(part, 64, &erased_check, 1);
if (err != ESP_OK || erased_check != 0xFF) {
ESP_LOGE(TAG, "FAIL: erased area not 0xFF (got 0x%02X)", erased_check);
return false;
}
// Clean up
esp_partition_erase_range(part, 0, FLASH_SECTOR_SIZE);
ESP_LOGI(TAG, "PASS");
return true;
}
// ============================================================================
// Test 2: Log partition read/write
// ============================================================================
bool test_log_partition_rw(void) {
ESP_LOGI(TAG, "=== Test: log partition read/write ===");
const esp_partition_t *part = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "log");
if (part == NULL) {
ESP_LOGE(TAG, "FAIL: log partition not found");
return false;
}
ESP_LOGI(TAG, "log partition: offset=0x%lx size=%lu",
(unsigned long)part->address, (unsigned long)part->size);
// Verify size matches expectations (108K = 27 sectors)
uint32_t expected_sectors = part->size / FLASH_SECTOR_SIZE;
ESP_LOGI(TAG, "log partition has %lu sectors", (unsigned long)expected_sectors);
// Test write at start of partition
esp_err_t err = esp_partition_erase_range(part, 0, FLASH_SECTOR_SIZE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: erase sector 0 failed: %s", esp_err_to_name(err));
return false;
}
uint8_t write_buf[16] = {0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C};
err = esp_partition_write(part, 0, write_buf, sizeof(write_buf));
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: write failed: %s", esp_err_to_name(err));
return false;
}
uint8_t read_buf[16];
err = esp_partition_read(part, 0, read_buf, sizeof(read_buf));
if (err != ESP_OK || memcmp(write_buf, read_buf, sizeof(write_buf)) != 0) {
ESP_LOGE(TAG, "FAIL: read-back mismatch at offset 0");
return false;
}
// Test write at last sector
uint32_t last_sector = part->size - FLASH_SECTOR_SIZE;
err = esp_partition_erase_range(part, last_sector, FLASH_SECTOR_SIZE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: erase last sector failed: %s", esp_err_to_name(err));
return false;
}
err = esp_partition_write(part, last_sector, write_buf, sizeof(write_buf));
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: write to last sector failed: %s", esp_err_to_name(err));
return false;
}
err = esp_partition_read(part, last_sector, read_buf, sizeof(read_buf));
if (err != ESP_OK || memcmp(write_buf, read_buf, sizeof(write_buf)) != 0) {
ESP_LOGE(TAG, "FAIL: read-back mismatch at last sector");
return false;
}
// Clean up
esp_partition_erase_range(part, 0, FLASH_SECTOR_SIZE);
esp_partition_erase_range(part, last_sector, FLASH_SECTOR_SIZE);
ESP_LOGI(TAG, "PASS");
return true;
}
// ============================================================================
// Test 3: POST test partition read/write
// ============================================================================
bool test_post_partition_rw(void) {
ESP_LOGI(TAG, "=== Test: post_test partition read/write ===");
const esp_partition_t *part = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "post_test");
if (part == NULL) {
ESP_LOGE(TAG, "FAIL: post_test partition not found");
return false;
}
ESP_LOGI(TAG, "post_test partition: offset=0x%lx size=%lu",
(unsigned long)part->address, (unsigned long)part->size);
// Verify it's exactly 4K (1 sector)
if (part->size != FLASH_SECTOR_SIZE) {
ESP_LOGE(TAG, "FAIL: expected 4096 bytes, got %lu", (unsigned long)part->size);
return false;
}
// Run the actual storage_post() function
esp_err_t err = storage_post();
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: storage_post() returned %s", esp_err_to_name(err));
return false;
}
// Verify the sector is clean after POST (it erases on completion)
uint8_t check;
err = esp_partition_read(part, 0, &check, 1);
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: read after POST failed");
return false;
}
if (check != 0xFF) {
ESP_LOGE(TAG, "FAIL: POST didn't clean up (byte 0 = 0x%02X)", check);
return false;
}
ESP_LOGI(TAG, "PASS");
return true;
}
// ============================================================================
// Test 4: Partitions are independent (writing to one doesn't corrupt another)
// ============================================================================
bool test_partitions_independent(void) {
ESP_LOGI(TAG, "=== Test: partition independence ===");
const esp_partition_t *params = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "params");
const esp_partition_t *log = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "log");
const esp_partition_t *post = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "post_test");
if (!params || !log || !post) {
ESP_LOGE(TAG, "FAIL: one or more partitions not found");
return false;
}
// Verify no overlap
uint32_t params_end = params->address + params->size;
uint32_t log_end = log->address + log->size;
uint32_t post_end = post->address + post->size;
ESP_LOGI(TAG, "params: 0x%lx - 0x%lx", (unsigned long)params->address, (unsigned long)params_end);
ESP_LOGI(TAG, "log: 0x%lx - 0x%lx", (unsigned long)log->address, (unsigned long)log_end);
ESP_LOGI(TAG, "post: 0x%lx - 0x%lx", (unsigned long)post->address, (unsigned long)post_end);
bool overlap = false;
if (params->address < log_end && log->address < params_end) overlap = true;
if (params->address < post_end && post->address < params_end) overlap = true;
if (log->address < post_end && post->address < log_end) overlap = true;
if (overlap) {
ESP_LOGE(TAG, "FAIL: partitions overlap!");
return false;
}
// Write a sentinel to each partition, then verify none were corrupted
esp_partition_erase_range(params, 0, FLASH_SECTOR_SIZE);
esp_partition_erase_range(log, 0, FLASH_SECTOR_SIZE);
esp_partition_erase_range(post, 0, FLASH_SECTOR_SIZE);
uint8_t pat_params[4] = {0x11, 0x22, 0x33, 0x44};
uint8_t pat_log[4] = {0x55, 0x66, 0x77, 0x88};
uint8_t pat_post[4] = {0x99, 0xAA, 0xBB, 0xCC};
esp_partition_write(params, 0, pat_params, 4);
esp_partition_write(log, 0, pat_log, 4);
esp_partition_write(post, 0, pat_post, 4);
// Read back all three and verify
uint8_t rb[4];
esp_partition_read(params, 0, rb, 4);
if (memcmp(rb, pat_params, 4) != 0) {
ESP_LOGE(TAG, "FAIL: params sentinel corrupted");
return false;
}
esp_partition_read(log, 0, rb, 4);
if (memcmp(rb, pat_log, 4) != 0) {
ESP_LOGE(TAG, "FAIL: log sentinel corrupted");
return false;
}
esp_partition_read(post, 0, rb, 4);
if (memcmp(rb, pat_post, 4) != 0) {
ESP_LOGE(TAG, "FAIL: post sentinel corrupted");
return false;
}
// Clean up
esp_partition_erase_range(params, 0, FLASH_SECTOR_SIZE);
esp_partition_erase_range(log, 0, FLASH_SECTOR_SIZE);
esp_partition_erase_range(post, 0, FLASH_SECTOR_SIZE);
ESP_LOGI(TAG, "PASS");
return true;
}
// ============================================================================
// Test 5: Parameter commit and reload
// ============================================================================
bool test_params_persist_after_commit(void) {
ESP_LOGI(TAG, "=== Test: params persist after commit ===");
// Save original value
param_value_t original = get_param_value_t(PARAM_DRIVE_DIST);
float orig_val = original.f32;
// Set a distinctive test value
float test_val = 99.99f;
param_value_t test = {.f32 = test_val};
esp_err_t err = set_param_value_t(PARAM_DRIVE_DIST, test);
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: set_param_value_t failed: %s", esp_err_to_name(err));
return false;
}
// Commit to flash
err = commit_params();
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: commit_params failed: %s", esp_err_to_name(err));
return false;
}
// Verify the value stuck in RAM
param_value_t readback = get_param_value_t(PARAM_DRIVE_DIST);
if (readback.f32 != test_val) {
ESP_LOGE(TAG, "FAIL: RAM value mismatch (got %.2f, expected %.2f)",
readback.f32, test_val);
return false;
}
// Re-init storage to force reload from flash
err = storage_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: storage_init failed on reload: %s", esp_err_to_name(err));
return false;
}
readback = get_param_value_t(PARAM_DRIVE_DIST);
if (readback.f32 != test_val) {
ESP_LOGE(TAG, "FAIL: flash value mismatch after reload (got %.2f, expected %.2f)",
readback.f32, test_val);
// Restore original before returning
param_value_t orig = {.f32 = orig_val};
set_param_value_t(PARAM_DRIVE_DIST, orig);
commit_params();
return false;
}
// Restore original value
param_value_t orig = {.f32 = orig_val};
set_param_value_t(PARAM_DRIVE_DIST, orig);
err = commit_params();
if (err != ESP_OK) {
ESP_LOGE(TAG, "WARN: failed to restore original value");
}
ESP_LOGI(TAG, "PASS");
return true;
}
// ============================================================================
// Test 6: Log write/read cycle through the log API
// ============================================================================
bool test_log_write_read_cycle(void) {
ESP_LOGI(TAG, "=== Test: log write/read cycle ===");
// Erase and reinit log
esp_err_t err = log_erase_all_sectors();
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: log_erase_all_sectors failed: %s", esp_err_to_name(err));
return false;
}
esp_task_wdt_reset();
// Write 3 entries with different types
uint8_t data1[] = {0x01, 0x02, 0x03, 0x04};
uint8_t data2[] = {0xAA, 0xBB, 0xCC};
uint8_t data3[] = {0xFF, 0xFE, 0xFD, 0xFC, 0xFB};
err = log_write_blocking_test(data1, sizeof(data1), LOG_TYPE_DATA);
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: write 1 failed: %s", esp_err_to_name(err));
return false;
}
err = log_write_blocking_test(data2, sizeof(data2), LOG_TYPE_EVENT);
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: write 2 failed: %s", esp_err_to_name(err));
return false;
}
err = log_write_blocking_test(data3, sizeof(data3), LOG_TYPE_SENSOR);
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: write 3 failed: %s", esp_err_to_name(err));
return false;
}
// Wait for writes to flush through the queue
vTaskDelay(pdMS_TO_TICKS(500));
esp_task_wdt_reset();
// Read them back
log_read_reset();
uint8_t read_buf[LOG_MAX_PAYLOAD];
uint8_t read_len, read_type;
// Entry 1
err = log_read(&read_len, read_buf, &read_type);
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: read 1 failed: %s", esp_err_to_name(err));
return false;
}
if (read_len != sizeof(data1) || read_type != LOG_TYPE_DATA ||
memcmp(read_buf, data1, sizeof(data1)) != 0) {
ESP_LOGE(TAG, "FAIL: entry 1 mismatch (len=%d type=0x%02X)", read_len, read_type);
return false;
}
// Entry 2
err = log_read(&read_len, read_buf, &read_type);
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: read 2 failed: %s", esp_err_to_name(err));
return false;
}
if (read_len != sizeof(data2) || read_type != LOG_TYPE_EVENT ||
memcmp(read_buf, data2, sizeof(data2)) != 0) {
ESP_LOGE(TAG, "FAIL: entry 2 mismatch (len=%d type=0x%02X)", read_len, read_type);
return false;
}
// Entry 3
err = log_read(&read_len, read_buf, &read_type);
if (err != ESP_OK) {
ESP_LOGE(TAG, "FAIL: read 3 failed: %s", esp_err_to_name(err));
return false;
}
if (read_len != sizeof(data3) || read_type != LOG_TYPE_SENSOR ||
memcmp(read_buf, data3, sizeof(data3)) != 0) {
ESP_LOGE(TAG, "FAIL: entry 3 mismatch (len=%d type=0x%02X)", read_len, read_type);
return false;
}
// Verify no more entries
err = log_read(&read_len, read_buf, &read_type);
if (err != ESP_ERR_NOT_FOUND) {
ESP_LOGE(TAG, "FAIL: expected no more entries, got err=%s", esp_err_to_name(err));
return false;
}
ESP_LOGI(TAG, "PASS");
return true;
}
// ============================================================================
// Test runner
// ============================================================================
esp_err_t run_partition_tests(void) {
ESP_LOGI(TAG, "");
ESP_LOGI(TAG, "=================================================");
ESP_LOGI(TAG, " PARTITION VERIFICATION TEST SUITE");
ESP_LOGI(TAG, "=================================================");
typedef struct {
const char *name;
bool (*fn)(void);
} test_entry_t;
test_entry_t tests[] = {
{"params partition r/w", test_params_partition_rw},
{"log partition r/w", test_log_partition_rw},
{"post_test partition r/w", test_post_partition_rw},
{"partition independence", test_partitions_independent},
{"params persist after commit", test_params_persist_after_commit},
{"log write/read cycle", test_log_write_read_cycle},
};
int num_tests = sizeof(tests) / sizeof(tests[0]);
int passed = 0;
for (int i = 0; i < num_tests; i++) {
esp_task_wdt_reset();
ESP_LOGI(TAG, "");
bool result = tests[i].fn();
if (result) passed++;
else ESP_LOGE(TAG, "FAILED: %s", tests[i].name);
esp_task_wdt_reset();
}
ESP_LOGI(TAG, "");
ESP_LOGI(TAG, "=================================================");
ESP_LOGI(TAG, " RESULTS: %d/%d passed", passed, num_tests);
ESP_LOGI(TAG, "=================================================");
return (passed == num_tests) ? ESP_OK : ESP_FAIL;
}

19
main/partition_test.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef PARTITION_TEST_H
#define PARTITION_TEST_H
#include "esp_err.h"
#include <stdbool.h>
// Run all partition verification tests
// Call from app_main() after storage_init() and log_init()
esp_err_t run_partition_tests(void);
// Individual tests
bool test_params_partition_rw(void);
bool test_log_partition_rw(void);
bool test_post_partition_rw(void);
bool test_partitions_independent(void);
bool test_params_persist_after_commit(void);
bool test_log_write_read_cycle(void);
#endif // PARTITION_TEST_H

View File

@@ -179,6 +179,41 @@ esp_err_t adc_init() {
return ESP_OK;
}
esp_err_t adc_post(void) {
// Read all 4 channels twice with a short delay; flag if frozen or wildly out of range
const adc_channel_t channels[] = { PIN_V_ISENS1, PIN_V_ISENS2, PIN_V_ISENS3, PIN_V_SENS_BAT };
const char *names[] = { "ISENS1", "ISENS2", "ISENS3", "BATTERY" };
int first[4], second[4];
for (int i = 0; i < 4; i++) {
if (adc_oneshot_read(adc1_handle, channels[i], &first[i]) != ESP_OK) {
ESP_LOGE(TAG, "POST: ADC read failed on %s", names[i]);
return ESP_FAIL;
}
}
vTaskDelay(pdMS_TO_TICKS(5));
for (int i = 0; i < 4; i++) {
if (adc_oneshot_read(adc1_handle, channels[i], &second[i]) != ESP_OK) {
ESP_LOGE(TAG, "POST: ADC read failed on %s (2nd)", names[i]);
return ESP_FAIL;
}
}
// Check for frozen ADC (identical readings on noise-bearing current sense channels)
for (int i = 0; i < 3; i++) { // only current sense, not battery (battery can be stable)
if (first[i] == second[i] && first[i] != 0) {
ESP_LOGW(TAG, "POST: ADC %s may be frozen (both reads = %d)", names[i], first[i]);
// Warning only — a truly stuck ADC will trip efuse protections anyway
}
}
ESP_LOGI(TAG, "POST: ADC OK (BAT=%d/%d, I1=%d/%d, I2=%d/%d, I3=%d/%d)",
first[3], second[3], first[0], second[0], first[1], second[1], first[2], second[2]);
return ESP_OK;
}
float get_raw_battery_voltage(void) {
int adc_raw = 0;
int voltage_mv = 0;

View File

@@ -38,6 +38,7 @@ esp_err_t process_bridge_current(bridge_t bridge);
esp_err_t process_battery_voltage();
esp_err_t adc_init();
esp_err_t adc_post(void);
esp_err_t power_init();
esp_err_t power_stop();

View File

@@ -7,6 +7,7 @@
#include "esp_crc.h"
#include "esp_task_wdt.h"
#include "storage.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "nvs_flash.h"
@@ -107,11 +108,13 @@ size_t param_type_size(param_type_e x) {
return -1;
}
// Partition pointer
static const esp_partition_t *storage_partition = NULL;
// Partition pointers (separate partitions for params, log, and POST test)
static const esp_partition_t *params_partition = NULL;
static const esp_partition_t *log_partition = NULL;
static const esp_partition_t *post_partition = NULL;
// Log head/tail tracking with mutex protection
// These now track byte offsets within the log area, not entry indices
// These track byte offsets within the log partition (0-based)
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;
@@ -134,8 +137,13 @@ uint32_t log_get_tail(void) {
return tail;
}
uint32_t log_get_offset(void) {
return LOG_START_OFFSET;
uint32_t log_get_offset(void) {
return 0;
}
uint32_t log_get_size(void) {
if (log_partition == NULL) return 0;
return log_partition->size;
}
// ============================================================================
@@ -350,23 +358,22 @@ static void unpack_param(const uint8_t *src, param_idx_t id) {
// COMMIT PARAMETERS TO FLASH
// ============================================================================
esp_err_t commit_params(void) {
if (storage_partition == NULL) {
ESP_LOGE(TAG, "Storage partition not initialized");
if (params_partition == NULL) {
ESP_LOGE(TAG, "Params partition not initialized");
return ESP_ERR_INVALID_STATE;
}
ESP_LOGI(TAG, "Committing %d parameters to flash...", NUM_PARAMS);
// Erase parameter sectors first
esp_err_t err = esp_partition_erase_range(storage_partition, PARAMS_OFFSET,
PARAMETER_NUM_SECTORS * FLASH_SECTOR_SIZE);
// Erase entire params partition
esp_err_t err = esp_partition_erase_range(params_partition, 0, params_partition->size);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to erase parameter sectors: %s", esp_err_to_name(err));
ESP_LOGE(TAG, "Failed to erase params partition: %s", esp_err_to_name(err));
return err;
}
// Write each parameter with CRC
uint32_t flash_offset = PARAMS_OFFSET;
uint32_t flash_offset = 0;
for (int i = 0; i < NUM_PARAMS; i++) {
param_stored_t stored;
memset(&stored, 0, sizeof(param_stored_t));
@@ -380,7 +387,7 @@ esp_err_t commit_params(void) {
stored.crc = esp_crc32_le(crc_input, stored.data, size);
// Write to flash
err = esp_partition_write(storage_partition, flash_offset,
err = esp_partition_write(params_partition, flash_offset,
&stored, sizeof(param_stored_t));
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to write parameter %d (%s): %s",
@@ -419,6 +426,58 @@ esp_err_t factory_reset(void) {
return ESP_OK;
}
// ============================================================================
// FLASH POST (Power-On Self-Test)
// ============================================================================
esp_err_t storage_post(void) {
if (post_partition == NULL) {
// Find post_test partition if not already found
post_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_ANY,
"post_test");
if (post_partition == NULL) {
ESP_LOGE(TAG, "POST: post_test partition not found");
return ESP_ERR_NOT_FOUND;
}
}
uint8_t write_buf[16];
uint8_t read_buf[16];
// Fill with a pattern based on boot time so we don't pass on stale data
uint32_t seed = (uint32_t)esp_timer_get_time();
for (int i = 0; i < 16; i++) write_buf[i] = (uint8_t)(seed + i * 37);
esp_err_t err = esp_partition_erase_range(post_partition, 0, FLASH_SECTOR_SIZE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "POST: flash erase failed: %s", esp_err_to_name(err));
return err;
}
err = esp_partition_write(post_partition, 0, write_buf, sizeof(write_buf));
if (err != ESP_OK) {
ESP_LOGE(TAG, "POST: flash write failed: %s", esp_err_to_name(err));
return err;
}
err = esp_partition_read(post_partition, 0, read_buf, sizeof(read_buf));
if (err != ESP_OK) {
ESP_LOGE(TAG, "POST: flash read failed: %s", esp_err_to_name(err));
return err;
}
if (memcmp(write_buf, read_buf, sizeof(write_buf)) != 0) {
ESP_LOGE(TAG, "POST: flash verify MISMATCH");
return ESP_FAIL;
}
// Erase the test sector so it's clean for next boot
esp_partition_erase_range(post_partition, 0, FLASH_SECTOR_SIZE);
ESP_LOGI(TAG, "POST: flash OK");
return ESP_OK;
}
// ============================================================================
// STORAGE INITIALIZATION
// ============================================================================
@@ -443,27 +502,27 @@ esp_err_t storage_init(void) {
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");
params_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_ANY,
"params");
if (params_partition == NULL) {
ESP_LOGE(TAG, "Params partition not found");
if (log_mutex) xSemaphoreGive(log_mutex);
return ESP_ERR_NOT_FOUND;
}
ESP_LOGI(TAG, "Storage partition found: size=%lu bytes",
(unsigned long)storage_partition->size);
ESP_LOGI(TAG, "Params partition found: size=%lu bytes",
(unsigned long)params_partition->size);
// Load parameters from flash
uint32_t flash_offset = PARAMS_OFFSET;
uint32_t flash_offset = 0;
//bool all_valid = true;
for (int i = 0; i < NUM_PARAMS; i++) {
param_stored_t stored;
esp_err_t err = esp_partition_read(storage_partition, flash_offset,
esp_err_t err = esp_partition_read(params_partition, flash_offset,
&stored, sizeof(param_stored_t));
if (err != ESP_OK) {
@@ -509,63 +568,41 @@ static inline uint32_t log_sector_end(uint32_t x) {
// Helper function to check if a sector is erased (starts with 0xFF)
static bool is_sector_erased(uint32_t x) {
uint8_t buf; //[256];
esp_err_t err = esp_partition_read(storage_partition, LOG_START_OFFSET + x * FLASH_SECTOR_SIZE, &buf, 1);
uint8_t buf;
esp_err_t err = esp_partition_read(log_partition, x * FLASH_SECTOR_SIZE, &buf, 1);
if (err != ESP_OK) return false;
if (buf == 0xFF) return true;
/*for (int i = 0; i < 256; i++) {
if (buf[i] != 0xFF) return false;
}*/
return false;
return (buf == 0xFF);
}
static bool is_sector_full(uint32_t x) {
uint8_t buf; //[256];
esp_err_t err = esp_partition_read(storage_partition, LOG_START_OFFSET + (x+1) * FLASH_SECTOR_SIZE - 1, &buf, 1);
uint8_t buf;
esp_err_t err = esp_partition_read(log_partition, (x+1) * FLASH_SECTOR_SIZE - 1, &buf, 1);
if (err != ESP_OK) return false;
if (buf == 0xFF) return false;
/*for (int i = 0; i < 256; i++) {
if (buf[i] != 0xFF) return false;
}*/
return true;
return (buf != 0xFF);
}
static inline void find_head_tail(int32_t num_sectors, int32_t *head, int32_t *tail) {
}
// Helper function to check if a sector has data (contains non-0xFF, non-0x00 bytes)
/*static bool sector_has_data(uint32_t sector_offset) {
uint8_t buf; //[256];
esp_err_t err = esp_partition_read(storage_partition, sector_offset, &buf, 256);
if (err != ESP_OK) return false;
if (buf )
for (int i = 0; i < 256; i++) {
if (buf[i] != 0xFF && buf[i] != 0x00) return true;
}
return false;
}*/
// Replace log_write with this non-blocking version:
esp_err_t log_write(uint8_t* buf, uint8_t len, uint8_t type) {
if (!log_initialized || storage_partition == NULL) {
if (!log_initialized || log_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_queue == NULL) {
ESP_LOGE(TAG, "Log queue not initialized");
return ESP_FAIL;
}
if (type == 0xFF) {
ESP_LOGE(TAG, "Attempt to log with type=0xFF; not allowed");
return ESP_ERR_INVALID_ARG;
@@ -588,67 +625,65 @@ esp_err_t log_write(uint8_t* buf, uint8_t len, uint8_t type) {
// The actual blocking write function (called by the task)
static esp_err_t log_write_blocking(uint8_t* buf, uint8_t len, uint8_t type) {
if (!log_initialized || storage_partition == NULL) {
if (!log_initialized || log_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);
// check if we will overrun the sector
if (log_head_offset + len+2 >= log_sector_end(log_head_offset)) {
//ESP_LOGI(TAG, "WILL OVERRUN (%ld >= %ld)", (long)log_head_offset + len+2, (long)log_sector_end(log_head_offset));
// zero the rest of sector
char zeros[256] = {0};
esp_partition_write(storage_partition,
esp_partition_write(log_partition,
log_head_offset, &zeros,
log_sector_end(log_head_offset)-log_head_offset);
// 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;
if (log_head_offset >= log_partition->size)
log_head_offset = 0;
// 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, log_head_offset,
esp_err_t err = esp_partition_read(log_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)log_head_offset);
err = esp_partition_erase_range(storage_partition,
log_head_offset,
err = esp_partition_erase_range(log_partition,
log_head_offset,
FLASH_SECTOR_SIZE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to erase sector: %s", esp_err_to_name(err));
if (log_mutex) xSemaphoreGive(log_mutex);
return ESP_FAIL;
}
}
// 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, "Erased; Tail/Head are now %lu/%lu",
if (log_tail_offset >= log_partition->size)
log_tail_offset = 0;
ESP_LOGI(TAG, "Erased; Tail/Head are now %lu/%lu",
(unsigned long)log_tail_offset, (unsigned long)log_head_offset);
}
len++; // account for type bit
esp_partition_write(storage_partition, log_head_offset, &len, 1);
esp_partition_write(storage_partition, log_head_offset+1, buf, len-1);
esp_partition_write(storage_partition, log_head_offset+len, &type, 1);
esp_partition_write(log_partition, log_head_offset, &len, 1);
esp_partition_write(log_partition, log_head_offset+1, buf, len-1);
esp_partition_write(log_partition, log_head_offset+len, &type, 1);
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);
@@ -686,21 +721,29 @@ static void log_writer_task(void *pvParameters) {
// 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;
// Find the log partition
log_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_ANY,
"log");
if (log_partition == NULL) {
ESP_LOGE(TAG, "Log partition not found");
return ESP_ERR_NOT_FOUND;
}
if (log_mutex == NULL) {
log_mutex = xSemaphoreCreateMutex();
if (log_mutex == NULL) return ESP_ERR_NO_MEM;
}
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;
uint32_t num_sectors = log_partition->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;
log_head_offset = 0;
log_tail_offset = 0;
// Binary search for the first non-full sector
int32_t l = 0;
@@ -750,10 +793,10 @@ esp_err_t log_init() {
}
if (head_sector == -1) {
if (is_sector_erased(LOG_START_OFFSET)) {
if (is_sector_erased(0)) {
ESP_LOGI(TAG, "Log is empty");
log_head_offset = LOG_START_OFFSET;
log_tail_offset = LOG_START_OFFSET;
log_head_offset = 0;
log_tail_offset = 0;
} else {
head_sector = 0;
ESP_LOGW(TAG, "Log appears full, searching from start");
@@ -763,17 +806,17 @@ esp_err_t log_init() {
// 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;
cursor = (head_sector - 1) * FLASH_SECTOR_SIZE;
} else {
cursor = LOG_START_OFFSET;
cursor = 0;
}
uint32_t head_sector_start = LOG_START_OFFSET + head_sector * FLASH_SECTOR_SIZE;
uint32_t head_sector_start = head_sector * FLASH_SECTOR_SIZE;
bool found_head = false;
while (cursor < head_sector_start + FLASH_SECTOR_SIZE && cursor < storage_partition->size) {
while (cursor < head_sector_start + FLASH_SECTOR_SIZE && cursor < log_partition->size) {
uint8_t buf;
esp_err_t err = esp_partition_read(storage_partition, cursor, &buf, 1);
esp_err_t err = esp_partition_read(log_partition, cursor, &buf, 1);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read during log init at offset %lu", (unsigned long)cursor);
if (log_mutex) xSemaphoreGive(log_mutex);
@@ -798,9 +841,9 @@ esp_err_t log_init() {
}
if (tail_sector >= 0) {
log_tail_offset = LOG_START_OFFSET + tail_sector * FLASH_SECTOR_SIZE;
log_tail_offset = tail_sector * FLASH_SECTOR_SIZE;
} else {
log_tail_offset = LOG_START_OFFSET;
log_tail_offset = 0;
}
log_initialized = true;
@@ -866,7 +909,9 @@ void storage_deinit(void) {
log_queue = NULL;
}
storage_partition = NULL;
params_partition = NULL;
log_partition = NULL;
post_partition = NULL;
log_initialized = false;
if (log_mutex) {
vSemaphoreDelete(log_mutex);
@@ -879,54 +924,12 @@ void storage_deinit(void) {
/*
* ADDITIONS TO storage.c
*
* Add these functions to storage.c to support the test suite.
* These provide the ability to erase all log sectors, simulate power cycles,
* and read back log entries for verification.
*/
// Add these function declarations to storage.h:
/*
esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type);
void log_read_reset(void);
esp_err_t log_erase_all_sectors(void);
esp_err_t log_simulate_power_cycle(void);
*/
// Add this static variable near the top of storage.c with other log static variables
// (around line 118, after log_tail_offset and log_initialized):
// ============================================================================
// LOG READ / TEST SUPPORT FUNCTIONS
// ============================================================================
static uint32_t log_read_cursor = 0;
// Add these functions to storage.c (after the existing log functions):
/*
* ADDITIONS TO storage.c
*
* Add these functions to storage.c to support the test suite.
* These provide the ability to erase all log sectors, simulate power cycles,
* and read back log entries for verification.
*/
// Add these function declarations to storage.h:
/*
esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type);
void log_read_reset(void);
esp_err_t log_erase_all_sectors(void);
esp_err_t log_simulate_power_cycle(void);
*/
// Add this static variable near the top of storage.c with other log static variables
// (around line 118, after log_tail_offset and log_initialized):
/*
static uint32_t log_read_cursor = 0;
*/
// Add these functions to storage.c (after the existing log functions):
/**
* @brief Read a log entry from the current read cursor position
*
@@ -952,18 +955,18 @@ esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type) {
// Add this declaration near the other log static variables in storage.c:
// static uint32_t log_read_cursor = 0;
if (!log_initialized || storage_partition == NULL) {
if (!log_initialized || log_partition == NULL) {
ESP_LOGE(TAG, "Logging not initialized");
return ESP_ERR_INVALID_STATE;
}
if (len == NULL || buf == NULL || type == NULL) {
ESP_LOGE(TAG, "NULL pointer passed to log_read");
return ESP_ERR_INVALID_ARG;
}
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
// Initialize read cursor to tail on first read (when cursor is 0)
if (log_read_cursor == 0) {
log_read_cursor = log_tail_offset;
@@ -977,7 +980,7 @@ esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type) {
// Read the length byte
uint8_t entry_len;
esp_err_t err = esp_partition_read(storage_partition, log_read_cursor, &entry_len, 1);
esp_err_t err = esp_partition_read(log_partition, log_read_cursor, &entry_len, 1);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read entry length at offset %lu", (unsigned long)log_read_cursor);
if (log_mutex) xSemaphoreGive(log_mutex);
@@ -996,8 +999,8 @@ esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type) {
uint32_t next_sector = ((log_read_cursor / FLASH_SECTOR_SIZE) + 1) * FLASH_SECTOR_SIZE;
// Handle wraparound
if (next_sector >= storage_partition->size) {
log_read_cursor = LOG_START_OFFSET;
if (next_sector >= log_partition->size) {
log_read_cursor = 0;
} else {
log_read_cursor = next_sector;
}
@@ -1015,7 +1018,7 @@ esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type) {
// Read the data (length-1 bytes, since length includes the type byte)
uint8_t data_len = entry_len - 1;
err = esp_partition_read(storage_partition, log_read_cursor + 1, buf, data_len);
err = esp_partition_read(log_partition, log_read_cursor + 1, buf, data_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read entry data at offset %lu", (unsigned long)(log_read_cursor + 1));
if (log_mutex) xSemaphoreGive(log_mutex);
@@ -1024,7 +1027,7 @@ esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type) {
// Read the type byte
uint8_t entry_type;
err = esp_partition_read(storage_partition, log_read_cursor + entry_len, &entry_type, 1);
err = esp_partition_read(log_partition, log_read_cursor + entry_len, &entry_type, 1);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read entry type at offset %lu", (unsigned long)(log_read_cursor + entry_len));
if (log_mutex) xSemaphoreGive(log_mutex);
@@ -1045,8 +1048,8 @@ esp_err_t log_read(uint8_t* len, uint8_t* buf, uint8_t* type) {
log_read_cursor += entry_len + 1; // +1 for the type byte after data
// Handle wraparound
if (log_read_cursor >= storage_partition->size) {
log_read_cursor = LOG_START_OFFSET;
if (log_read_cursor >= log_partition->size) {
log_read_cursor = 0;
}
if (log_mutex) xSemaphoreGive(log_mutex);
@@ -1078,37 +1081,31 @@ void log_read_reset(void) {
* @return ESP_OK on success, error code otherwise
*/
esp_err_t log_erase_all_sectors(void) {
if (storage_partition == NULL) {
ESP_LOGE(TAG, "Storage partition not initialized");
if (log_partition == NULL) {
ESP_LOGE(TAG, "Log partition not initialized");
return ESP_ERR_INVALID_STATE;
}
// Stop the log writer task
if (log_task_running) {
log_task_running = false;
vTaskDelay(pdMS_TO_TICKS(200));
if (log_task_handle != NULL) {
// Don't try to delete from watchdog - task was never added
// esp_task_wdt_delete(log_task_handle); // <-- REMOVE THIS LINE
vTaskDelay(pdMS_TO_TICKS(100));
log_task_handle = NULL;
}
}
// Clear the queue
if (log_queue != NULL) {
xQueueReset(log_queue);
}
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
uint32_t log_area_size = storage_partition->size - LOG_START_OFFSET;
ESP_LOGI(TAG, "Erasing all log sectors (%lu bytes)...", (unsigned long)log_area_size);
esp_err_t err = esp_partition_erase_range(storage_partition,
LOG_START_OFFSET,
log_area_size);
ESP_LOGI(TAG, "Erasing all log sectors (%lu bytes)...", (unsigned long)log_partition->size);
esp_err_t err = esp_partition_erase_range(log_partition, 0, log_partition->size);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to erase log area: %s", esp_err_to_name(err));

View File

@@ -10,9 +10,6 @@
// FLASH LAYOUT CONSTANTS
// ============================================================================
#define FLASH_SECTOR_SIZE 4096
#define PARAMS_OFFSET 0
#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)
@@ -153,6 +150,7 @@ typedef struct {
// Initialization
esp_err_t storage_init(void);
esp_err_t storage_post(void);
void storage_deinit(void);
// Parameter access

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
#include <Arduino.h>
const unsigned char PROGMEM html_content_gz[] = {
0x1f, 0x8b, 0x08, 0x00, 0xd7, 0xc3, 0xb2, 0x69, 0x02, 0xff, 0xed, 0x3d, 0xfb, 0x5b, 0xdb, 0x48,
0x1f, 0x8b, 0x08, 0x00, 0x18, 0x55, 0xb3, 0x69, 0x02, 0xff, 0xed, 0x3d, 0xfb, 0x5b, 0xdb, 0x48,
0x92, 0x3f, 0xef, 0xfc, 0x15, 0x0d, 0x93, 0x21, 0x52, 0x10, 0xb2, 0x0d, 0x64, 0x66, 0xd6, 0x46,
0x66, 0x09, 0x38, 0x3b, 0x4c, 0x12, 0xe0, 0xc3, 0x90, 0xcc, 0x1c, 0xc7, 0x87, 0x64, 0xab, 0x8d,
0x35, 0xc8, 0x92, 0x57, 0x92, 0x21, 0x5e, 0xe3, 0xff, 0xfd, 0xaa, 0xfa, 0x21, 0xb5, 0x1e, 0x36,

View File

@@ -99,7 +99,7 @@ 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;
static const esp_partition_t *cached_log_partition = NULL;
// In webserver.c - Replace the log_handler function
@@ -144,17 +144,17 @@ static esp_err_t log_handler(httpd_req_t *req) {
return httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED, "Method not allowed");
}
const esp_partition_t *storage_partition = cached_storage_partition;
if (storage_partition == NULL) {
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");
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Storage partition not found");
const esp_partition_t *log_part = cached_log_partition;
if (log_part == NULL) {
log_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_ANY,
"log");
if (log_part == NULL) {
ESP_LOGE(TAG, "Log partition not found");
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Log partition not found");
}
cached_storage_partition = storage_partition;
cached_log_partition = log_part;
}
int32_t head = log_get_head();
@@ -163,7 +163,7 @@ static esp_err_t log_handler(httpd_req_t *req) {
if (tail < 0) {
tail = log_get_tail();
} else {
if (tail < log_start || tail >= (int32_t)storage_partition->size) {
if (tail < log_start || tail >= (int32_t)log_part->size) {
ESP_LOGW(TAG, "Invalid tail pointer %ld, using current tail", (long)tail);
tail = log_get_tail();
}
@@ -176,7 +176,7 @@ static esp_err_t log_handler(httpd_req_t *req) {
} else if (tail < head) {
log_data_size = head - tail;
} else {
log_data_size = (storage_partition->size - tail) + (head - log_start);
log_data_size = (log_part->size - tail) + (head - log_start);
}
// Generate JSON header (same as /get endpoint)
@@ -270,7 +270,7 @@ static esp_err_t log_handler(httpd_req_t *req) {
// Normal case: tail before head
while (offset < head) {
size_t to_read = MIN(sizeof(http_buffer), head - offset);
err = esp_partition_read(storage_partition, offset, http_buffer, to_read);
err = esp_partition_read(log_part, offset, http_buffer, to_read);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read partition at offset %ld: %s",
(long)offset, esp_err_to_name(err));
@@ -290,9 +290,9 @@ static esp_err_t log_handler(httpd_req_t *req) {
}
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(http_buffer), storage_partition->size - offset);
err = esp_partition_read(storage_partition, offset, http_buffer, to_read);
while (offset < (int32_t)log_part->size) {
size_t to_read = MIN(sizeof(http_buffer), log_part->size - offset);
err = esp_partition_read(log_part, offset, http_buffer, to_read);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read partition at offset %ld: %s",
(long)offset, esp_err_to_name(err));
@@ -314,7 +314,7 @@ static esp_err_t log_handler(httpd_req_t *req) {
offset = log_start;
while (offset < head) {
size_t to_read = MIN(sizeof(http_buffer), head - offset);
err = esp_partition_read(storage_partition, offset, http_buffer, to_read);
err = esp_partition_read(log_part, offset, http_buffer, to_read);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read partition at offset %ld: %s",
(long)offset, esp_err_to_name(err));